From 90d6e70b4cc432749c845e835ae58810da2585d8 Mon Sep 17 00:00:00 2001 From: fajiao <1519100073@qq.com> Date: Thu, 25 May 2023 18:26:00 +0800 Subject: [PATCH] =?UTF-8?q?[feat]=20=E6=B7=BB=E5=8A=A0=20JiLinApp.Docking,?= =?UTF-8?q?=20JiLinApp.Biz=20=E5=BA=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- EC.Util/CameraSDK/DaHua/DaHuaOriSdk.cs | 2 +- EC.Util/CameraSDK/HiK/HiKSdk.cs | 4 +- EC.Util/CameraSDK/YuShi/YuShiOriSdk.cs | 2 +- EC.Util/CameraSDK/YuShi/YuShiSdk.cs | 6 +- JiLinApp.Biz/JiLinApp.Biz.csproj | 17 + .../Common/AlarmServiceFactory.cs | 14 + JiLinApp.Biz/TransmitAlarm/Common/Config.cs | 48 + .../TransmitAlarm/Entity/AlarmMessage.cs | 34 + .../Entity/AlarmMessageHelper.cs | 42 + .../TransmitAlarm/Service/AlarmMqttService.cs | 332 +++ .../TransmitAlarm/Service/AlarmZmqService.cs | 52 + .../Service/Interfaces/IAlarmService.cs | 44 + JiLinApp.Docking/Alarm/AlarmCode.cs | 9 + JiLinApp.Docking/Alarm/AlarmCodeHelper.cs | 40 + .../FenceAlarm/Entity/DeviceStateMessage.cs | 25 + .../FenceAlarm/Entity/SectorState.cs | 76 + .../FenceAlarm/Entity/UdpAlarmHost.cs | 158 ++ .../FenceAlarm/Entity/UdpAlarmHostMessage.cs | 29 + .../Entity/UdpDatagramReceivedEventArgs.cs | 31 + .../FenceAlarm/Entity/UdpManagerConfig.cs | 10 + .../FenceAlarm/Service/AlarmEncode.cs | 256 ++ .../FenceAlarm/Service/UdpManager.cs | 793 ++++++ .../FenceAlarm/Service/UdpServer.cs | 136 + JiLinApp.Docking/JiLinApp.Docking.csproj | 25 + JiLinApp.Docking/Military/Config.cs | 16 + .../Military/Entity/CameraLinkageInfo.cs | 18 + JiLinApp.Docking/Military/MilitaryService.cs | 165 ++ JiLinApp.Docking/Ptz/DCamera.cs | 128 + JiLinApp.Docking/Ptz/PelcoD.cs | 240 ++ JiLinApp.Docking/Ptz/PelcoP.cs | 337 +++ JiLinApp.Docking/Ptz/PtzCmd.cs | 415 +++ JiLinApp.Docking/Ptz/PtzConfig.cs | 38 + .../VibrateAlarm/Entity/ClientMessage.cs | 130 + .../VibrateAlarm/Entity/DataMessage.cs | 80 + .../VibrateAlarm/Entity/DataRequest.cs | 12 + .../VibrateAlarm/Entity/SensorState.cs | 68 + .../VibrateAlarm/Entity/TcpAlarmHost.cs | 22 + .../Entity/TcpAlarmHostMessage.cs | 19 + .../VibrateAlarm/Entity/TcpManagerConfig.cs | 10 + .../VibrateAlarm/Entity/TcpSensorTable.cs | 20 + .../VibrateAlarm/Service/AsyncTcpServer.cs | 436 ++++ JiLinApp.Docking/VibrateAlarm/Service/CRC8.cs | 49 + .../Service/TcpClientConnectedEventArgs.cs | 23 + .../Service/TcpClientDisconnectedEventArgs.cs | 23 + .../VibrateAlarm/Service/TcpClientState.cs | 53 + .../Service/TcpDatagramReceivedEventArgs.cs | 29 + .../VibrateAlarm/Service/TcpManager.cs | 765 ++++++ JiLinApp.Docking/config/alarmcode.json | 2222 +++++++++++++++++ JiLinCpApp.sln | 12 + 49 files changed, 7508 insertions(+), 7 deletions(-) create mode 100644 JiLinApp.Biz/JiLinApp.Biz.csproj create mode 100644 JiLinApp.Biz/TransmitAlarm/Common/AlarmServiceFactory.cs create mode 100644 JiLinApp.Biz/TransmitAlarm/Common/Config.cs create mode 100644 JiLinApp.Biz/TransmitAlarm/Entity/AlarmMessage.cs create mode 100644 JiLinApp.Biz/TransmitAlarm/Entity/AlarmMessageHelper.cs create mode 100644 JiLinApp.Biz/TransmitAlarm/Service/AlarmMqttService.cs create mode 100644 JiLinApp.Biz/TransmitAlarm/Service/AlarmZmqService.cs create mode 100644 JiLinApp.Biz/TransmitAlarm/Service/Interfaces/IAlarmService.cs create mode 100644 JiLinApp.Docking/Alarm/AlarmCode.cs create mode 100644 JiLinApp.Docking/Alarm/AlarmCodeHelper.cs create mode 100644 JiLinApp.Docking/FenceAlarm/Entity/DeviceStateMessage.cs create mode 100644 JiLinApp.Docking/FenceAlarm/Entity/SectorState.cs create mode 100644 JiLinApp.Docking/FenceAlarm/Entity/UdpAlarmHost.cs create mode 100644 JiLinApp.Docking/FenceAlarm/Entity/UdpAlarmHostMessage.cs create mode 100644 JiLinApp.Docking/FenceAlarm/Entity/UdpDatagramReceivedEventArgs.cs create mode 100644 JiLinApp.Docking/FenceAlarm/Entity/UdpManagerConfig.cs create mode 100644 JiLinApp.Docking/FenceAlarm/Service/AlarmEncode.cs create mode 100644 JiLinApp.Docking/FenceAlarm/Service/UdpManager.cs create mode 100644 JiLinApp.Docking/FenceAlarm/Service/UdpServer.cs create mode 100644 JiLinApp.Docking/JiLinApp.Docking.csproj create mode 100644 JiLinApp.Docking/Military/Config.cs create mode 100644 JiLinApp.Docking/Military/Entity/CameraLinkageInfo.cs create mode 100644 JiLinApp.Docking/Military/MilitaryService.cs create mode 100644 JiLinApp.Docking/Ptz/DCamera.cs create mode 100644 JiLinApp.Docking/Ptz/PelcoD.cs create mode 100644 JiLinApp.Docking/Ptz/PelcoP.cs create mode 100644 JiLinApp.Docking/Ptz/PtzCmd.cs create mode 100644 JiLinApp.Docking/Ptz/PtzConfig.cs create mode 100644 JiLinApp.Docking/VibrateAlarm/Entity/ClientMessage.cs create mode 100644 JiLinApp.Docking/VibrateAlarm/Entity/DataMessage.cs create mode 100644 JiLinApp.Docking/VibrateAlarm/Entity/DataRequest.cs create mode 100644 JiLinApp.Docking/VibrateAlarm/Entity/SensorState.cs create mode 100644 JiLinApp.Docking/VibrateAlarm/Entity/TcpAlarmHost.cs create mode 100644 JiLinApp.Docking/VibrateAlarm/Entity/TcpAlarmHostMessage.cs create mode 100644 JiLinApp.Docking/VibrateAlarm/Entity/TcpManagerConfig.cs create mode 100644 JiLinApp.Docking/VibrateAlarm/Entity/TcpSensorTable.cs create mode 100644 JiLinApp.Docking/VibrateAlarm/Service/AsyncTcpServer.cs create mode 100644 JiLinApp.Docking/VibrateAlarm/Service/CRC8.cs create mode 100644 JiLinApp.Docking/VibrateAlarm/Service/TcpClientConnectedEventArgs.cs create mode 100644 JiLinApp.Docking/VibrateAlarm/Service/TcpClientDisconnectedEventArgs.cs create mode 100644 JiLinApp.Docking/VibrateAlarm/Service/TcpClientState.cs create mode 100644 JiLinApp.Docking/VibrateAlarm/Service/TcpDatagramReceivedEventArgs.cs create mode 100644 JiLinApp.Docking/VibrateAlarm/Service/TcpManager.cs create mode 100644 JiLinApp.Docking/config/alarmcode.json diff --git a/EC.Util/CameraSDK/DaHua/DaHuaOriSdk.cs b/EC.Util/CameraSDK/DaHua/DaHuaOriSdk.cs index 6bedde1..0245c9e 100644 --- a/EC.Util/CameraSDK/DaHua/DaHuaOriSdk.cs +++ b/EC.Util/CameraSDK/DaHua/DaHuaOriSdk.cs @@ -39,7 +39,7 @@ public static class DaHuaOriSdk if (InitSuccess) return true; bool ret = CLIENT_InitEx(null, IntPtr.Zero, IntPtr.Zero); InitSuccess = ret; - if (!ret) throw new Exception("DaHuaOriSDK global init failure."); + if (!ret) throw new Exception("DaHuaOriSdk global init failure."); return ret; } diff --git a/EC.Util/CameraSDK/HiK/HiKSdk.cs b/EC.Util/CameraSDK/HiK/HiKSdk.cs index a6acd39..6f50986 100644 --- a/EC.Util/CameraSDK/HiK/HiKSdk.cs +++ b/EC.Util/CameraSDK/HiK/HiKSdk.cs @@ -39,8 +39,8 @@ public class HiKSdk : ICameraSdk ret = ConnectSuccess(); if (ret) HiKOriSdk.NET_DVR_SetReconnect(10000, 1); else BuildException(); - //HiKOriSDK.NET_DVR_DEVICEINFO_V30 deviceInfo = new(); - //LoginId = HiKOriSDK.NET_DVR_Login_V30(CameraInfo.Ip, CameraInfo.Port, CameraInfo.UserName, CameraInfo.Password, ref deviceInfo); + //HiKOriSdk.NET_DVR_DEVICEINFO_V30 deviceInfo = new(); + //LoginId = HiKOriSdk.NET_DVR_Login_V30(CameraInfo.Ip, CameraInfo.Port, CameraInfo.UserName, CameraInfo.Password, ref deviceInfo); return ret; } diff --git a/EC.Util/CameraSDK/YuShi/YuShiOriSdk.cs b/EC.Util/CameraSDK/YuShi/YuShiOriSdk.cs index ea9bbcf..b784210 100644 --- a/EC.Util/CameraSDK/YuShi/YuShiOriSdk.cs +++ b/EC.Util/CameraSDK/YuShi/YuShiOriSdk.cs @@ -39,7 +39,7 @@ public class YuShiOriSdk if (InitSuccess) return true; bool ret = NETDEV_Init(); InitSuccess = ret; - if (!ret) throw new Exception("YuShiOriSDK global init failure."); + if (!ret) throw new Exception("YuShiOriSdk global init failure."); return ret; } diff --git a/EC.Util/CameraSDK/YuShi/YuShiSdk.cs b/EC.Util/CameraSDK/YuShi/YuShiSdk.cs index eb7d3d1..c14fc89 100644 --- a/EC.Util/CameraSDK/YuShi/YuShiSdk.cs +++ b/EC.Util/CameraSDK/YuShi/YuShiSdk.cs @@ -111,9 +111,9 @@ public class YuShiSdk : ICameraSdk { hPlayWnd = hwnd, dwChannelID = Channel, - dwStreamType = 0, // YuShiOriSDK.NETDEV_LIVE_STREAM_INDEX_E - dwLinkMode = 1, // YuShiOriSDK.NETDEV_PROTOCAL_E - dwFluency = 0, // YuShiOriSDK.NETDEV_PICTURE_FLUENCY_E + dwStreamType = 0, // YuShiOriSdk.NETDEV_LIVE_STREAM_INDEX_E + dwLinkMode = 1, // YuShiOriSdk.NETDEV_PROTOCAL_E + dwFluency = 0, // YuShiOriSdk.NETDEV_PICTURE_FLUENCY_E }; RealplayHandle = YuShiOriSdk.NETDEV_RealPlay(LoginId, ref stPreviewInfo, IntPtr.Zero, IntPtr.Zero); if (RealplayHandle == IntPtr.Zero) BuildException(); diff --git a/JiLinApp.Biz/JiLinApp.Biz.csproj b/JiLinApp.Biz/JiLinApp.Biz.csproj new file mode 100644 index 0000000..d655145 --- /dev/null +++ b/JiLinApp.Biz/JiLinApp.Biz.csproj @@ -0,0 +1,17 @@ + + + + net7.0 + enable + enable + + + + + + + + + + + diff --git a/JiLinApp.Biz/TransmitAlarm/Common/AlarmServiceFactory.cs b/JiLinApp.Biz/TransmitAlarm/Common/AlarmServiceFactory.cs new file mode 100644 index 0000000..37fdfb1 --- /dev/null +++ b/JiLinApp.Biz/TransmitAlarm/Common/AlarmServiceFactory.cs @@ -0,0 +1,14 @@ +namespace JiLinApp.Biz.TransmitAlarm; + +public class AlarmServiceFactory +{ + public static IAlarmService CreateService(AlarmPlatformConfig config) + { + IAlarmService service = (config.Type) switch + { + "mqtt" => new AlarmMqttService(config.Mqtt), + _ => throw new NotSupportedException(), + }; ; + return service; + } +} \ No newline at end of file diff --git a/JiLinApp.Biz/TransmitAlarm/Common/Config.cs b/JiLinApp.Biz/TransmitAlarm/Common/Config.cs new file mode 100644 index 0000000..49e0095 --- /dev/null +++ b/JiLinApp.Biz/TransmitAlarm/Common/Config.cs @@ -0,0 +1,48 @@ +namespace JiLinApp.Biz.TransmitAlarm; + +public class AlarmPlatformConfig +{ + public bool RealPlay { get; set; } + + public string Type { get; set; } + + public MqttConfig Mqtt { get; set; } +} + +public class MqttConfig +{ + //public bool Local { get; set; } + + public string Ip { get; set; } + + public int Port { get; set; } + + public string UserName { get; set; } + + public string Password { get; set; } + + public string ClientId { get; set; } + + public int RetryTime { get; set; } + + public int RetryInterval { get; set; } + + public string PubAlarmTopic { get; set; } + + public string PubDevicesTopic { get; set; } + + public string PubSensorsTopic { get; set; } + + public string PubDeviceStateTopic { get; set; } + + public string PubSensorStateTopic { get; set; } + + public string SubCmdTopic { get; set; } +} + +public class ZmqConfig +{ + public string AlarmPubAddr { get; set; } + + public string AlarmPubTopic { get; set; } +} \ No newline at end of file diff --git a/JiLinApp.Biz/TransmitAlarm/Entity/AlarmMessage.cs b/JiLinApp.Biz/TransmitAlarm/Entity/AlarmMessage.cs new file mode 100644 index 0000000..dc32f16 --- /dev/null +++ b/JiLinApp.Biz/TransmitAlarm/Entity/AlarmMessage.cs @@ -0,0 +1,34 @@ +namespace JiLinApp.Biz.TransmitAlarm; + +public class AlarmMessage +{ + /** (必填)传感器设备编码*/ + public string LabelCode { get; set; } + public string ChannelId { get; set; } + /** (必填)报警类型((1-视频报警,2-雷达报警;3-微振动警报,4-电子围网警报,9-其他报警))*/ + public int WarnType { get; set; } + /** (必填)报警级别(1-5)*/ + public int WarnLevel { get; set; } + /** (必填)报警内容*/ + public string WarnContent { get; set; } + /** 处置方式*/ + public int DealWay { get; set; } + /** (必填)处置状态(1-未处理,2-已处理)*/ + public int DealStatus { get; set; } + /** (必填)发生地点*/ + public string CameraLoc { get; set; } + /** (必填)发生时间*/ + public DateTime CreateTime { get; set; } + /** 负责人*/ + public string Director { get; set; } + /** (必填)类型(1-预警,2-报警,3-故障,)*/ + public int Kind { get; set; } + /** 现场照片*/ + public string ImgUrl { get; set; } + + public override string ToString() + { + return string.Format("{{ labelCode:{0}, channelId:{1}, warnType:{2}, warnLevel:{3}, warnContent:{4}, createTime:{5} }}", + LabelCode, ChannelId, WarnType, WarnLevel, WarnContent, CreateTime); + } +} \ No newline at end of file diff --git a/JiLinApp.Biz/TransmitAlarm/Entity/AlarmMessageHelper.cs b/JiLinApp.Biz/TransmitAlarm/Entity/AlarmMessageHelper.cs new file mode 100644 index 0000000..26ebaa1 --- /dev/null +++ b/JiLinApp.Biz/TransmitAlarm/Entity/AlarmMessageHelper.cs @@ -0,0 +1,42 @@ +using JiLinApp.Docking.Alarm; +using JiLinApp.Docking.FenceAlarm; +using JiLinApp.Docking.VibrateAlarm; + +namespace JiLinApp.Biz.TransmitAlarm; + +public static class AlarmMessageHelper +{ + public static AlarmMessage ToAlarmMessage(this TcpAlarmHostMessage msg) + { + AlarmCode code = AlarmCodeHelper.Get(msg.CID); + AlarmMessage obj = new() + { + LabelCode = Convert.ToString(msg.DeviceID), + ChannelId = msg.SensorAddr, + WarnType = 3, + WarnLevel = code.Level, + WarnContent = code.Content, + DealStatus = 1, + CreateTime = Convert.ToDateTime(msg.AlarmTime), + Kind = 2, + }; + return obj; + } + + public static AlarmMessage ToAlarmMessage(this UdpAlarmHostMessage msg) + { + AlarmCode code = AlarmCodeHelper.Get(msg.CID); + AlarmMessage obj = new() + { + LabelCode = Convert.ToString(msg.DeviceId), + ChannelId = Convert.ToString(msg.SectorId), + WarnType = 4, + WarnLevel = code.Level, + WarnContent = code.Content, + DealStatus = 1, + CreateTime = Convert.ToDateTime(msg.AlarmTime), + Kind = 2, + }; + return obj; + } +} \ No newline at end of file diff --git a/JiLinApp.Biz/TransmitAlarm/Service/AlarmMqttService.cs b/JiLinApp.Biz/TransmitAlarm/Service/AlarmMqttService.cs new file mode 100644 index 0000000..609b5db --- /dev/null +++ b/JiLinApp.Biz/TransmitAlarm/Service/AlarmMqttService.cs @@ -0,0 +1,332 @@ +using EC.Util.Common; +using MQTTnet; +using MQTTnet.Client; +using MQTTnet.Exceptions; +using MQTTnet.Protocol; +using MQTTnet.Server; +using NewLife.Serialization; +using Newtonsoft.Json.Linq; +using System.Text; + +namespace JiLinApp.Biz.TransmitAlarm; + +public class AlarmMqttService : IAlarmService +{ + #region Fields + + private MqttConfig Config { get; } + + //private MqttServer? Server { get; } + + private IMqttClient Client { get; } + + //private MqttServerOptions ServerOptions { get; } + + private MqttClientOptions ClientOptions { get; } + + private MqttClientSubscribeOptions SubscribeOptions { get; } + + public event IAlarmService.HandleRecvEvent? OnFenceUdpSendDevices; + + public event IAlarmService.HandleRecvEvent? OnVibrateTcpSendDevices; + + public event IAlarmService.HandleRecvEvent? OnFenceUdpSendSensors; + + public event IAlarmService.HandleRecvEvent? OnVibrateTcpSendSensors; + + #endregion Fields + + public AlarmMqttService(MqttConfig config) + { + MqttFactory factory = new(); + + ClientOptions = factory.CreateClientOptionsBuilder() + .WithTcpServer(config.Ip, config.Port) + .WithClientId(config.ClientId) + .WithCredentials(config.UserName, config.Password) + .WithCleanSession(false) + .WithKeepAlivePeriod(TimeSpan.FromSeconds(60)) + .Build(); + SubscribeOptions = factory.CreateSubscribeOptionsBuilder() + .WithTopicFilter(f => f.WithTopic(config.SubCmdTopic)) + .Build(); + + //if (config.Local) + //{ + // ServerOptions = factory.CreateServerOptionsBuilder() + // .WithDefaultEndpoint() + // .WithDefaultEndpointPort(config.Port) + // .Build(); + // MqttServer server = factory.CreateMqttServer(ServerOptions); + // server.ValidatingConnectionAsync += Server_ValidatingConnectionAsync; + // server.ClientConnectedAsync += Server_ClientConnectedAsync; + // server.ClientDisconnectedAsync += Server_ClientDisconnectedAsync; + // server.ClientAcknowledgedPublishPacketAsync += Server_ClientAcknowledgedPublishPacketAsync; + // Server = server; + //} + + IMqttClient client = factory.CreateMqttClient(); + client.ApplicationMessageReceivedAsync += Client_ApplicationMessageReceivedAsync; + + Config = config; + Client = client; + } + + ~AlarmMqttService() + { + Close(); + } + + #region Base + + public void Start() + { + if (IsRuning()) return; + int retryTime = Config.RetryTime, retryInterval = Config.RetryInterval; + for (int i = 1; i <= retryTime; i++) + { + MqttClientConnectResult connResult = Client.ConnectAsync(ClientOptions, CancellationToken.None).Result; + if (connResult.ResultCode == MqttClientConnectResultCode.Success) break; + if (i == retryTime) throw new MqttCommunicationTimedOutException(); + Thread.Sleep(retryInterval); + } + for (int i = 1; i <= retryTime; i++) + { + MqttClientSubscribeResult subResult = Client.SubscribeAsync(SubscribeOptions, CancellationToken.None).Result; + bool flag = true; + foreach (var item in subResult.Items) + { + if (item.ResultCode > MqttClientSubscribeResultCode.GrantedQoS2) + { + flag = false; + break; + } + } + if (flag) break; + if (i == retryTime) throw new MqttCommunicationTimedOutException(); + Thread.Sleep(retryInterval); + } + } + + public void Close() + { + if (!IsRuning()) return; + Client.DisconnectAsync().Wait(); + } + + public bool IsRuning() + { + return Client != null && Client.IsConnected; + } + + private object StartAtomObj { get; } = new(); + + private bool StartAtom() + { + lock (StartAtomObj) + { + if (!IsRuning()) Start(); + } + return IsRuning(); + } + + #endregion Base + + #region Server Event + + private Task Server_ValidatingConnectionAsync(ValidatingConnectionEventArgs arg) + { + if (arg.ClientId.Length == 0) arg.ReasonCode = MqttConnectReasonCode.ClientIdentifierNotValid; + else if (arg.UserName != Config.UserName) arg.ReasonCode = MqttConnectReasonCode.BadUserNameOrPassword; + else if (arg.Password != Config.Password) arg.ReasonCode = MqttConnectReasonCode.BadUserNameOrPassword; + return Task.CompletedTask; + } + + private Task Server_ClientConnectedAsync(ClientConnectedEventArgs arg) + { + return Task.CompletedTask; + } + + private Task Server_ClientDisconnectedAsync(ClientDisconnectedEventArgs arg) + { + return Task.CompletedTask; + } + + private Task Server_ClientAcknowledgedPublishPacketAsync(ClientAcknowledgedPublishPacketEventArgs arg) + { + string clientId = arg.ClientId; + ushort packetIdentifier = arg.PublishPacket.PacketIdentifier; + string topic = arg.PublishPacket.Topic; + string msg = Encoding.UTF8.GetString(arg.PublishPacket.Payload); + return Task.CompletedTask; + } + + #endregion Server Event + + #region Client Event + + private Task Client_ApplicationMessageReceivedAsync(MqttApplicationMessageReceivedEventArgs arg) + { + string topic = arg.ApplicationMessage.Topic; + string msg = Encoding.UTF8.GetString(arg.ApplicationMessage.Payload); + string qos = arg.ApplicationMessage.QualityOfServiceLevel.ToString(); + string retained = arg.ApplicationMessage.Retain.ToString(); + if (topic.Equals(Config.SubCmdTopic)) + { + HandleRecvSubCmdTopic(topic, msg); + } + return Task.CompletedTask; + } + + private void HandleRecvSubCmdTopic(string topic, string reqJson) + { + JObject reqObj = JsonUtil.ToJObject(reqJson); + string cmd = reqObj.GetValue("cmd", StringComparison.OrdinalIgnoreCase)?.ToString() ?? ""; + DeviceType type = (DeviceType)(reqObj.GetValue("type", StringComparison.OrdinalIgnoreCase).ToInt()); + switch (cmd.Trim().ToLower()) + { + case "getdevices": + switch (type) + { + case DeviceType.Vibrate: + OnVibrateTcpSendDevices?.Invoke(reqObj); + break; + + case DeviceType.Fence: + OnFenceUdpSendDevices?.Invoke(reqObj); + break; + } + break; + + case "getsensors": + switch (type) + { + case DeviceType.Vibrate: + OnVibrateTcpSendSensors?.Invoke(reqObj); + break; + + case DeviceType.Fence: + OnFenceUdpSendSensors?.Invoke(reqObj); + break; + } + break; + } + } + + #endregion Client Event + + #region Send + + private int Frame { get; set; } = 0; + + private int IncFrame + { get { return ++Frame % int.MaxValue; } } + + private void Send(string topic, object obj) + { + MqttApplicationMessage mqttMsg = new MqttApplicationMessageBuilder() + .WithTopic(topic) + .WithPayload(obj.ToJson()) + .Build(); + MqttClientPublishResult? pubResult = null; + while (pubResult == null || !pubResult.IsSuccess) + { + try + { + pubResult = Client.PublishAsync(mqttMsg, CancellationToken.None).Result; + } + catch (Exception) + { + while (!IsRuning()) + { + if (StartAtom()) break; + Thread.Sleep(100); + } + } + } + } + + private void SendByFrame(string topic, object obj) + { + //((dynamic)obj).Frame = IncFrame;// no work, transto dict, add frame, transto json + MqttApplicationMessage mqttMsg = new MqttApplicationMessageBuilder() + .WithTopic(topic) + .WithPayload(obj.ToJson()) + .Build(); + MqttClientPublishResult? pubResult = null; + while (pubResult == null || !pubResult.IsSuccess) + { + try + { + pubResult = Client.PublishAsync(mqttMsg, CancellationToken.None).Result; + } + catch (Exception) + { + while (!IsRuning()) + { + if (StartAtom()) break; + Thread.Sleep(100); + } + } + } + } + + public void SendAlarm(AlarmMessage msg) + { + Send(Config.PubAlarmTopic, msg); + } + + public void SendDevices(DeviceType type, List deviceList) + { + object obj = new + { + type = type, + data = deviceList, + frame = IncFrame + }; + Send(Config.PubDevicesTopic, obj); + } + + public void SendSensors(DeviceType type, int deviceId, List sensorList) + { + object obj = new + { + type = type, + deviceId = deviceId, + data = sensorList, + frame = IncFrame + }; + Send(Config.PubSensorsTopic, obj); + } + + public void SendDeviceState(DeviceType type, object device) + { + object obj = new + { + type = type, + data = device, + frame = IncFrame + }; + Send(Config.PubDeviceStateTopic, obj); + } + + public void SendSensorState(DeviceType type, object sensor) + { + object obj = new + { + type = type, + data = sensor, + frame = IncFrame + }; + Send(Config.PubSensorStateTopic, obj); + } + + #endregion Send +} + +public enum DeviceType : int +{ + Unknown = 0, + Vibrate = 3, + Fence = 4, +} \ No newline at end of file diff --git a/JiLinApp.Biz/TransmitAlarm/Service/AlarmZmqService.cs b/JiLinApp.Biz/TransmitAlarm/Service/AlarmZmqService.cs new file mode 100644 index 0000000..9932384 --- /dev/null +++ b/JiLinApp.Biz/TransmitAlarm/Service/AlarmZmqService.cs @@ -0,0 +1,52 @@ +namespace JiLinApp.Biz.TransmitAlarm; + +public class AlarmZmqService : IAlarmService +{ + public event IAlarmService.HandleRecvEvent? OnFenceUdpSendDevices; + + public event IAlarmService.HandleRecvEvent? OnVibrateTcpSendDevices; + + public event IAlarmService.HandleRecvEvent? OnFenceUdpSendSensors; + + public event IAlarmService.HandleRecvEvent? OnVibrateTcpSendSensors; + + public void Start() + { + throw new NotImplementedException(); + } + + public void Close() + { + throw new NotImplementedException(); + } + + public bool IsRuning() + { + throw new NotImplementedException(); + } + + public void SendAlarm(AlarmMessage msg) + { + throw new NotImplementedException(); + } + + public void SendDevices(DeviceType type, List deviceList) + { + throw new NotImplementedException(); + } + + public void SendSensors(DeviceType type, int deviceId, List sensorList) + { + throw new NotImplementedException(); + } + + public void SendDeviceState(DeviceType type, object device) + { + throw new NotImplementedException(); + } + + public void SendSensorState(DeviceType type, object sensor) + { + throw new NotImplementedException(); + } +} \ No newline at end of file diff --git a/JiLinApp.Biz/TransmitAlarm/Service/Interfaces/IAlarmService.cs b/JiLinApp.Biz/TransmitAlarm/Service/Interfaces/IAlarmService.cs new file mode 100644 index 0000000..32c6342 --- /dev/null +++ b/JiLinApp.Biz/TransmitAlarm/Service/Interfaces/IAlarmService.cs @@ -0,0 +1,44 @@ +using Newtonsoft.Json.Linq; + +namespace JiLinApp.Biz.TransmitAlarm; + +public interface IAlarmService +{ + #region Event + + public delegate void HandleRecvEvent(JObject reqObj); + + public event HandleRecvEvent? OnFenceUdpSendDevices; + + public event HandleRecvEvent? OnVibrateTcpSendDevices; + + public event HandleRecvEvent? OnFenceUdpSendSensors; + + public event HandleRecvEvent? OnVibrateTcpSendSensors; + + #endregion Event + + #region Base + + void Start(); + + void Close(); + + bool IsRuning(); + + #endregion Base + + #region Send + + void SendAlarm(AlarmMessage msg); + + void SendDevices(DeviceType type, List deviceList); + + void SendSensors(DeviceType type, int deviceId, List sensorList); + + void SendDeviceState(DeviceType type, object device); + + void SendSensorState(DeviceType type, object sensor); + + #endregion Send +} \ No newline at end of file diff --git a/JiLinApp.Docking/Alarm/AlarmCode.cs b/JiLinApp.Docking/Alarm/AlarmCode.cs new file mode 100644 index 0000000..5f2c4b0 --- /dev/null +++ b/JiLinApp.Docking/Alarm/AlarmCode.cs @@ -0,0 +1,9 @@ +namespace JiLinApp.Docking.Alarm; + +public class AlarmCode +{ + public string Id { get; set; } + public int Level { get; set; } + public string Type { get; set; } + public string Content { get; set; } +} \ No newline at end of file diff --git a/JiLinApp.Docking/Alarm/AlarmCodeHelper.cs b/JiLinApp.Docking/Alarm/AlarmCodeHelper.cs new file mode 100644 index 0000000..5a9ce1f --- /dev/null +++ b/JiLinApp.Docking/Alarm/AlarmCodeHelper.cs @@ -0,0 +1,40 @@ +using Newtonsoft.Json; + +namespace JiLinApp.Docking.Alarm; + +public class AlarmCodeHelper +{ + #region Fields + + private static readonly Dictionary AlarmCodeDict; + private static readonly List LevelList; + private static readonly List TypeList; + + #endregion Fields + + static AlarmCodeHelper() + { + using StreamReader r = new(Path.Combine("config", "alarmcode.json")); + string jsonStr = r.ReadToEnd(); + List list = JsonConvert.DeserializeObject>(jsonStr) ?? new(); + AlarmCodeDict = list.ToDictionary(item => item.Id, item => item); + LevelList = list.GroupBy(item => item.Level).Select(it => it.First().Level).ToList(); + TypeList = list.GroupBy(item => item.Type).Select(it => it.First().Type).ToList(); + } + + public static void Init() + { + } + + public static AlarmCode Get(string id) + { + return AlarmCodeDict[id]; + } + + public static bool TryGet(string id, out AlarmCode alarmCode) + { + bool flag = AlarmCodeDict.TryGetValue(id, out AlarmCode? temp); + alarmCode = temp ?? new(); + return flag; + } +} \ No newline at end of file diff --git a/JiLinApp.Docking/FenceAlarm/Entity/DeviceStateMessage.cs b/JiLinApp.Docking/FenceAlarm/Entity/DeviceStateMessage.cs new file mode 100644 index 0000000..e522438 --- /dev/null +++ b/JiLinApp.Docking/FenceAlarm/Entity/DeviceStateMessage.cs @@ -0,0 +1,25 @@ +namespace JiLinApp.Docking.FenceAlarm; + +public class DeviceStateMessage +{ + public int deviceID { get; set; }//设备唯一ID + public string StatusTime { get; set; }//时间 + public int channel { get; set; }//防区 + public int StatusType { get; set; }//状态 + + public DeviceStateMessage(int id, DateTime time, int Channel, int statusType) + { + deviceID = id; + StatusTime = time.ToString("yyyy-MM-dd HH:mm:ss"); + channel = Channel; + StatusType = statusType; + } + + public DeviceStateMessage() + { + deviceID = -1; + StatusTime = ""; + channel = -1; + StatusType = -1; + } +} \ No newline at end of file diff --git a/JiLinApp.Docking/FenceAlarm/Entity/SectorState.cs b/JiLinApp.Docking/FenceAlarm/Entity/SectorState.cs new file mode 100644 index 0000000..a67e255 --- /dev/null +++ b/JiLinApp.Docking/FenceAlarm/Entity/SectorState.cs @@ -0,0 +1,76 @@ +namespace JiLinApp.Docking.FenceAlarm; + +public class SectorState +{ + #region Fields + + public int DeviceId { get; set; } + + public int Id { get; set; }//防区号 + + /// + /// 0: 防区未使用 + /// 1: 防区撤防 + /// 2: 防区布防 + /// 3: 防区旁路 + /// 4: 防区报警 + /// 5: 无线防区欠压 + /// 6: 防区掉线(与主线总线脱离) + /// + public int State { get; set; }//防区状态 + + public string StateStr + { + get + { + return State switch + { + 0 => "防区未使用", + 1 => "防区撤防", + 2 => "防区布防", + 3 => "防区旁路", + 4 => "防区报警", + 5 => "无线防区欠压", + 6 => "防区掉线", + _ => "未知状态:" + State, + }; + } + } + + /// + /// 0: 普通防区,无特殊参数 + /// 1: 张力防区需要单独查询因为这个防区显示张力线值每条线状态 + /// 2: 脉冲围栏 + /// 3: 振动光纤 + /// 4: 泄漏电缆 + /// 5: 网络或总线多子防区模块 + /// + public int Type { get; set; }//防区类型,特殊参数需单独查询 + + public string TypeStr + { + get + { + return Type switch + { + 0 => "普通防区", + 1 => "张力防区", + 2 => "脉冲围栏", + 3 => "振动光纤", + 4 => "泄漏电缆", + 5 => "网络或总线多子防区模块", + _ => "未知类型:" + Type, + }; + } + } + + #endregion Fields + + public SectorState(int deviceId, int id, byte data) + { + DeviceId = deviceId; + Id = id; + State = data & 0x0F; + Type = data >> 4; + } +} \ No newline at end of file diff --git a/JiLinApp.Docking/FenceAlarm/Entity/UdpAlarmHost.cs b/JiLinApp.Docking/FenceAlarm/Entity/UdpAlarmHost.cs new file mode 100644 index 0000000..55f1924 --- /dev/null +++ b/JiLinApp.Docking/FenceAlarm/Entity/UdpAlarmHost.cs @@ -0,0 +1,158 @@ +using System.Collections.Concurrent; + +namespace JiLinApp.Docking.FenceAlarm; + +public class UdpAlarmHost +{ + public int DeviceId { get; set; } + + public string Ip { get; set; } + + public int Port { get; set; } + + public int GroupId { get; set; }//分组号,报警主机可划入不同组 + + public int UserId { get; set; }//用户ID,指主机名称,有多台主机可以通过此名称区分 + + /// + /// 0x00: 撤防状态 + /// 0x01: 外出布防(普通布防最常用) + /// 0x02: 即时布防(所有防区没有延时) + /// 0x04: 在家布防(留守布防有些防区可能是在旁路状态) + /// 0x08: 即时留守布防(有些防区可能旁路, 但是没有旁路防区没有延时) + /// 0x09: 部分防区布防部分防区撤防 + /// + public int DefenceState { get; set; }//主机布撤状态 + + public string DefenceStateStr + { + get + { + return DefenceState switch + { + 0x00 => "撤防状态", + 0x01 => "外出布防", + 0x02 => "即时布防", + 0x04 => "在家布防", + 0x08 => "即时留守布防", + 0x09 => "部分防区布防", + _ => "未知状态号:" + DefenceState, + }; + } + } + + /// + /// 0x00:此设备处于常规工作状态,正常报警 + /// 0x01:工作在设置模式,不会处理报警,用户这进行设置,设置完一般很快退出 + /// 0x02:用户正在查询当前报警信息 + /// 0x03:用户正在设置时钟 + /// 0x04:工作在用户密码修改模式下 + /// + public int WorkState { get; set; }//设备工作状态 + + public string WorkStateStr + { + get + { + return DefenceState switch + { + 0x00 => "常规工作状态", + 0x01 => "在设置模式", + 0x02 => "用户正在查询报警", + 0x03 => "用户正在设置时钟", + 0x04 => "用户密码修改模式", + _ => "未知状态号:" + WorkState, + }; + } + } + + public int CellState { get; set; }//电池状态 1=电池故障 0=电池正常 + public int ElectricityState { get; set; }//交流状态 1=交流掉电 0=交流正常 + public int FuseState { get; set; }//警保险丝状态 1=外接警号断了0=正常 + public int SectorState { get; set; }//有线防区触发了 1=其中有线防区触发了 0=所有有线防区准备好 + public int CellTestState { get; set; }//正在电池载能测试 1=主机正在进行电池载能测试 0=无(主机会降低电压到电池12V以下,来测试电池负载特性需要2分钟) + public int DeviceElectricityState { get; set; }//设备交流电状态1=交流掉电,目前电池供电0=正常 这是即时状态 + public int SoundState { get; set; }//设备声音输出状态(=0关闭声音) =1 输出声音) 用于布防和报警延时 + public int TestState { get; set; }//主机本地测试状态1=主机在本地测试(用于测试探测好坏是没有报警记录的)0=无 + + public int CriticalAlarmState { get; set; }//软防区紧急报警状态1=有紧急报警(用户通过手动按下主机自带装置报警) 0=无 + public int FireAlarmState { get; set; }//软防区火警报警状态1=有火警报警(用户通过手动按下主机自带装置报警) 0=无 + public int StealAlarmState { get; set; }//软防区盗警报警状态1=有盗警报警(用户通过手动按下主机自带装置报警) 0=无 + public int DeviceFireAlarmState { get; set; }//设备中有火警报警状态 1=其中有火警防区触发了 0=无 整体状态 + public int DeviceAlarmState { get; set; }//设备有报警发生 1=指设备有报警状态,任一一种0=无报警 + public int DeviceBywayState { get; set; }//设备有旁路防区1=指设备某些防区有旁路了(这时软件可以要求上传具体旁路的防区) 2=无防区旁路 + public int BusOfflineState { get; set; }//有总线防区掉线状态1=指某些总线防区掉线了0=无 + public int NetOfflineState { get; set; }//有网络防区掉线状态1=指某些网络防区掉线了0=无 + + /// + /// 00: 通用设备兼容没有设置或未知设备 + /// 01: EH508报警主机 + /// 02: EH508NP网络模块 + /// 03: NETLINKBOARD32路网络模块 + /// 04: EH508NETCILENT网络终端 + /// 05: EH8008路光纤主机 + /// 06: EH508NP_UDP私有服务器网络模块 + /// 07: EH508CILENT_UDP私有服务器接警终端 + /// 08: EH508SEVER_UDP私有服务器主机 + /// 09: EH508MAP_UDP私有服务器电子地图 + /// 0A: EH508_UDP私有服务器用EH508主机 + /// 0x32: H768_IP系列网络模块 + /// 0x33: D238C_IP网络模块 + /// 0x34: H778S+系列报警主机 + /// 0x35: H778S系列报警主机 + /// 0x36: N201N202网络模块 + /// + public int DevideType { get; set; }//设备类型:区分不同的设备类型 增强可控,防止混乱,具体的设备类型见附加说明表格 + + public int SignalIntensity { get; set; }//31:信号最强,如果设备类型不是这种不需要显示 指主要带有GPRS模块设备跟据设备类型来的 + + //-------------------------------防区信息-----------------------------// + + public int SectorTotal { get; set; }//分区总数量 + + public ConcurrentDictionary SectorDict { get; } + + public bool SectorsEmpty + { + get + { + return SectorDict == null || SectorDict.IsEmpty; + } + } + + public ReaderWriterLockSlim SectorsLock { get; } = new(); + + public int OnlineState { get; set; }//设备在线状态 + + public int KeepLive { get; set; }//设备在线状态 + + public UdpAlarmHost() + { + DeviceId = 0; + GroupId = 0; + UserId = 0; + DefenceState = 0; + WorkState = 0; + CellState = 0; + ElectricityState = 0; + FuseState = 0; + SectorState = 0; + CellTestState = 0; + DeviceElectricityState = 0; + SoundState = 0; + TestState = 0; + CriticalAlarmState = 0; + FireAlarmState = 0; + StealAlarmState = 0; + DeviceFireAlarmState = 0; + DeviceAlarmState = 0; + DeviceBywayState = 0; + BusOfflineState = 0; + NetOfflineState = 0; + DevideType = 0; + SignalIntensity = 0; + SectorTotal = 0; + OnlineState = 0; + SectorDict = new(); + } +} \ No newline at end of file diff --git a/JiLinApp.Docking/FenceAlarm/Entity/UdpAlarmHostMessage.cs b/JiLinApp.Docking/FenceAlarm/Entity/UdpAlarmHostMessage.cs new file mode 100644 index 0000000..257faf6 --- /dev/null +++ b/JiLinApp.Docking/FenceAlarm/Entity/UdpAlarmHostMessage.cs @@ -0,0 +1,29 @@ +using Newtonsoft.Json.Linq; + +namespace JiLinApp.Docking.FenceAlarm; + +public class UdpAlarmHostMessage +{ + public string AlarmTime { get; set; }//报警时间 + public string CID { get; set; }//CID代码 + + /*设备信息*/ + public int DeviceId { get; set; }//设备唯一ID + public string Ip { get; set; }//设备IP + public int SectorId { get; set; }//防区号 + public int SubSectorId { get; set; }//子防区号 + public int GroupId { get; set; }//分组号,报警主机可划入不同组 + public int UserId { get; set; }//用户ID,指主机名称,有多台主机可以通过此名称区分 + public int LinkOut { get; set; }//联动输出 + public string ExtendArgs { get; set; }//扩展参数 + + /*CID信息*/ + public string AlarmLevel { get; set; }//报警级别 + public string AlarmContent { get; set; }//报警内容 + public string AlarmRemarks { get; set; }//报警备注 + public string AlarmType { get; set; }//报警类型 + + /*联动信息*/ + public bool IsLinked { get; set; }//是否有联动信息 + public JArray Linklist { get; set; } +} \ No newline at end of file diff --git a/JiLinApp.Docking/FenceAlarm/Entity/UdpDatagramReceivedEventArgs.cs b/JiLinApp.Docking/FenceAlarm/Entity/UdpDatagramReceivedEventArgs.cs new file mode 100644 index 0000000..13d433d --- /dev/null +++ b/JiLinApp.Docking/FenceAlarm/Entity/UdpDatagramReceivedEventArgs.cs @@ -0,0 +1,31 @@ +using System.Net; + +namespace JiLinApp.Docking.FenceAlarm; + +/// +/// 接收到数据报文事件参数 +/// +/// 报文类型 +public class UdpDatagramReceivedEventArgs : EventArgs +{ + /// + /// 客户端 + /// + public IPEndPoint Ipep { get; private set; } + + /// + /// 报文 + /// + public T Datagram { get; private set; } + + /// + /// 接收到数据报文事件参数 + /// + /// 客户端 + /// 报文 + public UdpDatagramReceivedEventArgs(IPEndPoint ipep, T datagram) + { + Ipep = ipep ?? throw new ArgumentNullException(nameof(ipep)); + Datagram = datagram; + } +} \ No newline at end of file diff --git a/JiLinApp.Docking/FenceAlarm/Entity/UdpManagerConfig.cs b/JiLinApp.Docking/FenceAlarm/Entity/UdpManagerConfig.cs new file mode 100644 index 0000000..77e74d9 --- /dev/null +++ b/JiLinApp.Docking/FenceAlarm/Entity/UdpManagerConfig.cs @@ -0,0 +1,10 @@ +namespace JiLinApp.Docking.FenceAlarm; + +public class UdpManagerConfig +{ + public string ServerIp { get; set; } + + public int ServerPort { get; set; } + + public int DeviceHeartKeep { get; set; } +} \ No newline at end of file diff --git a/JiLinApp.Docking/FenceAlarm/Service/AlarmEncode.cs b/JiLinApp.Docking/FenceAlarm/Service/AlarmEncode.cs new file mode 100644 index 0000000..2012437 --- /dev/null +++ b/JiLinApp.Docking/FenceAlarm/Service/AlarmEncode.cs @@ -0,0 +1,256 @@ +namespace JiLinApp.Docking.FenceAlarm; + +public class AlarmEncode +{ + #region Fields + + public static readonly byte Version = 0x12; + public static readonly byte[] Head = new byte[] { 0xF0, 0xFA }; + public static readonly byte End = 0x0D; + + public static readonly byte[] TEA_key = new byte[16] { + 0x08,0x01,0x08,0x06,0x07,0x08,0x07,0x08, + 0x08,0x90,0xC5,0x04,0x0D,0x0E,0x0F,0x10 + }; + + #endregion Fields + + public static byte[] EncodeMessage(byte[] msg) + { + if (!CheckMessage(msg)) return Array.Empty(); + return GetMessage(msg[0], BteaEncrypt(GetContent(msg, msg[0] - 1))); + } + + public static byte[] DecodeMessage(byte[] msg) + { + if (!CheckMessage(msg)) return Array.Empty(); + return GetMessage(msg[0], BteaDecrpyt(GetContent(msg, msg[0] - 1))); + } + + private static bool CheckMessage(byte[] msg) + { + if (msg == null) return false; + if (msg[0] > msg.Length) return false; + return true; + } + + private static byte[] GetMessage(byte msgLen, byte[] msg) + { + byte[] outMsg = new byte[msg.Length + 1]; + outMsg[0] = msgLen; + for (int i = 0; i < msg.Length; i++) + { + outMsg[i + 1] = msg[i]; + } + return outMsg; + } + + private static byte[] GetContent(byte[] msg, int len) + { + byte[] bytes = new byte[len]; + for (int i = 0; i < len; i++) + { + bytes[i] = msg[i + 1]; + } + return bytes; + } + + public static byte[] GetSendMessage(byte command, byte[] data) + { + byte[] msg = data != null ? new byte[data.Length + 8] : new byte[8]; + msg[0] = (byte)msg.Length; + msg[1] = Version; + msg[2] = Head[0]; + msg[3] = Head[1]; + msg[4] = command; + if (data != null) + { + for (int i = 0; i < data.Length; i++) + { + msg[i + 5] = data[i]; + } + } + byte[] subMsg = data != null ? new byte[data.Length + 4] : new byte[4]; + for (int i = 0; i < subMsg.Length; i++) + { + subMsg[i] = msg[i + 1]; + } + byte[] crc = CRC16(subMsg); + msg[^3] = crc[1]; + msg[^2] = crc[0]; + msg[^1] = End; + return msg; + } + + public static byte[] CRC16(byte[] data) + { + int len = data.Length; + if (len > 0) + { + ushort crc = 0xFFFF; + for (int i = 0; i < len; i++) + { + crc = (ushort)(crc ^ (data[i])); + for (int j = 0; j < 8; j++) + { + crc = (crc & 1) != 0 ? (ushort)((crc >> 1) ^ 0xA001) : (ushort)(crc >> 1); + } + } + byte hi = (byte)((crc & 0xFF00) >> 8); //高位置 + byte lo = (byte)(crc & 0x00FF); //低位置 + return new byte[] { hi, lo }; + } + return new byte[] { 0, 0 }; + } + + private static byte[] BteaEncrypt(byte[] bytes) + { + byte[] output = new byte[bytes.Length]; + for (int i = 0; i < bytes.Length; i++) + { + byte abyte = (byte)(bytes[i] ^ TEA_key[i % 16]); + output[i] = (byte)(((byte)(abyte >> 3)) | ((byte)(abyte << 5))); + } + return output; + } + + private static byte[] BteaDecrpyt(byte[] bytes) + { + byte[] output = new byte[bytes.Length]; + for (int i = 0; i < bytes.Length; i++) + { + byte abyte = (byte)(((byte)(bytes[i] << 3)) | ((byte)(bytes[i] >> 5))); + output[i] = (byte)(abyte ^ TEA_key[i % 16]); + } + return output; + } +} + +public enum DeviceCmd : byte +{ + /// + /// 心跳 + /// + HeartBeatCmd = 0x01, + + /// + /// 报警 + /// + AlarmCmd = 0x02, + + /// + /// 所有防区状态 + /// + AllSectorStateCmd = 0x03, + + /// + /// 实时防区状态 + /// + RtSectorStateCmd = 0x04, + + /// + /// 最大防区号 + /// + MaxSectorTotalCmd = 0x05, + + /// + /// 报警主机设置参数返回服务器 + /// + BackSettingsCmd = 0x08, + + /// + /// 布防指令 + /// + DefenceCmd = 0x80, + + /// + /// 校对时间指令 + /// + CheckTimeCmd = 0x8D, + + /// + /// 正确应答指令 + /// + OkCmd = 0x8F, + + /// + /// 主机重启指令 + /// + RebootCmd = 0xA0, +} + +public enum DeviceSectorType : byte +{ + /// + /// 防区未使用 + /// + NotUsed = 0x00, + + /// + /// 防区撤防 + /// + Withdraw = 0x01, + + /// + /// 防区布防 + /// + Deploy = 0x02, + + /// + /// 防区旁路 + /// + Byway = 0x03, + + /// + /// 防区报警 + /// + Alarm = 0x04, + + /// + /// 无线防区欠压 + /// + Undervoltage = 0x05, + + /// + /// 防区掉线(与主线总线脱离) + /// + OffBus = 0x06, + + /// + /// 未准备就绪 + /// + Unprepared = 0x07, +} + +public enum DeviceDefenceState : byte +{ + /// + /// 撤防状态 + /// + Withdraw = 0x00, + + /// + /// 外出布防(普通布防最常用) + /// + GoOut = 0x01, + + /// + /// 即时布防(所有防区没有延时) + /// + Immediately = 0x02, + + /// + /// 在家布防(留守布防有些防区可能是在旁路状态) + /// + Home = 0x04, + + /// + /// 即时留守布防(有些防区可能旁路,但是没有旁路防区没有延时) + /// + HomeImmediately = 0x08, + + /// + /// 部分防区布防部分防区撤防 + /// + Part = 0x09 +} \ No newline at end of file diff --git a/JiLinApp.Docking/FenceAlarm/Service/UdpManager.cs b/JiLinApp.Docking/FenceAlarm/Service/UdpManager.cs new file mode 100644 index 0000000..c14e9e0 --- /dev/null +++ b/JiLinApp.Docking/FenceAlarm/Service/UdpManager.cs @@ -0,0 +1,793 @@ +using JiLinApp.Docking.VibrateAlarm; +using System.Collections.Concurrent; +using System.Net; +using System.Timers; +using Timer = System.Timers.Timer; + +namespace JiLinApp.Docking.FenceAlarm; + +public class UdpManager +{ + #region Fields + + private UdpServer Server { get; set; } + + private UdpManagerConfig Config { get; set; } + + private Timer PtzCheckTimer { get; } = new(); + + private ConcurrentDictionary DeviceDict { get; } = new(); + + #region Event + + public delegate void FenceUdpDeviceStateEvent(UdpAlarmHost device); + + public delegate void FenceUdpSectorStateEvent(SectorState sector); + + public delegate void FenceUdpAlarmEvent(UdpAlarmHostMessage msg); + + public event FenceUdpDeviceStateEvent? OnFenceUdpDeviceState; + + public event FenceUdpSectorStateEvent? OnFenceUdpSectorState; + + public event FenceUdpAlarmEvent? OnFenceUdpAlarm; + + #endregion Event + + #endregion Fields + + public UdpManager() + { + } + + #region Server + + public void Start(UdpManagerConfig config) + { + if (IsRunning()) return; + Server = new(config.ServerPort); + Server.DatagramReceived += Server_DatagramReceived; + Server.Start(); + + PtzCheckTimer.Interval = 3000;//3s + PtzCheckTimer.Elapsed += PTZCheckTimer_Elapsed; + PtzCheckTimer.Enabled = true; + + Config = config; + } + + public void Stop() + { + if (!IsRunning()) return; + try + { + Server.Stop(); + } + finally + { + Server.DatagramReceived -= Server_DatagramReceived; + Server = null; + DeviceDict.Clear(); + PtzCheckTimer.Stop(); + PtzCheckTimer.Elapsed -= PTZCheckTimer_Elapsed; + } + } + + public bool IsRunning() + { + return Server != null && Server.IsRunning(); + } + + #endregion Server + + #region Events + + /// + /// 计时判断设备是否在线,若超过规定时间没有新消息发来,则设备离线 + /// + /// + /// + private void PTZCheckTimer_Elapsed(object? sender, ElapsedEventArgs e) + { + foreach (var key in DeviceDict.Keys) + { + UdpAlarmHost device = DeviceDict[key]; + if (device.KeepLive > 0) device.KeepLive--; + else ProcessDeviceStateEvent(ref device, 0, device.DefenceState); + } + } + + private void Server_DatagramReceived(object? sender, UdpDatagramReceivedEventArgs e) + { + IPEndPoint ipep = e.Ipep; + //解码 + //byte[] msg = e.Datagram; + byte[] msg = AlarmEncode.DecodeMessage(e.Datagram); + bool vaild = msg.Length >= 8 && msg[2] == AlarmEncode.Head[0] && msg[3] == AlarmEncode.Head[1]; + Console.WriteLine("Recv from {0}:{1} => {2}, {3}, {4}", ipep.Address.ToString(), ipep.Port, DataMessage.ToHexString(e.Datagram), DataMessage.ToHexString(msg), vaild); + if (!vaild) return; + //解析 + DeviceCmd deviceCmd = (DeviceCmd)msg[4]; + switch (deviceCmd) + { + case DeviceCmd.HeartBeatCmd://心跳信息 + AnalysisHeartMessage(ipep, msg); + break; + + case DeviceCmd.AlarmCmd://报警信息 + AnalysisAlarmMessage(ipep, msg); + break; + + case DeviceCmd.AllSectorStateCmd://防区信息 + AnalysisAllSectorMessage(ipep, msg); + break; + + case DeviceCmd.RtSectorStateCmd://张力防区信息 + AnalysisSectorMessage(ipep, msg); + break; + + case DeviceCmd.MaxSectorTotalCmd://最大防区信息 + AnalysisMaxSectorMessage(ipep, msg); + break; + + case DeviceCmd.BackSettingsCmd://返回报警主机设置参数回服务器,无需解析 + break; + + default: + break; + } + SendOk(ipep); + } + + #endregion Events + + #region Analysis + + private void AnalysisHeartMessage(IPEndPoint ipep, byte[] msg) + { + string ip = ipep.Address.ToString(); + int deviceId = ByteToInt(msg, 5); + if (!TryGetDevice(deviceId, out UdpAlarmHost device)) + { + device = new() + { + DeviceId = deviceId, + Ip = ip, + Port = ipep.Port, + GroupId = ByteToInt(msg, 9), + UserId = ByteToInt(msg, 13), + OnlineState = 1, + KeepLive = Config.DeviceHeartKeep + }; + AddDevice(deviceId, ref device); + } + byte defenceState = msg[17]; + //在线状态 + device.KeepLive = Config.DeviceHeartKeep; + ProcessDeviceStateEvent(ref device, 1, defenceState); + device.WorkState = msg[18]; + //设备状态1 + device.CellState = GetBit(msg[19], 0); + device.ElectricityState = GetBit(msg[19], 1); + device.FuseState = GetBit(msg[19], 2); + device.SectorState = GetBit(msg[19], 3); + device.CellTestState = GetBit(msg[19], 4); + device.DeviceElectricityState = GetBit(msg[19], 5); + device.SoundState = GetBit(msg[19], 6); + device.TestState = GetBit(msg[19], 7); + //设备状态2 + device.CriticalAlarmState = GetBit(msg[20], 0); + device.FireAlarmState = GetBit(msg[20], 1); + device.StealAlarmState = GetBit(msg[20], 2); + device.DeviceFireAlarmState = GetBit(msg[20], 3); + device.DeviceAlarmState = GetBit(msg[20], 4); + device.DeviceBywayState = GetBit(msg[20], 5); + device.BusOfflineState = GetBit(msg[20], 6); + device.NetOfflineState = GetBit(msg[20], 7); + //设备状态3,4暂不使用 + device.DevideType = msg[23]; + device.SignalIntensity = msg[24]; + device.SectorTotal = msg[25]; + } + + private void AnalysisAlarmMessage(IPEndPoint ipep, byte[] msg) + { + string ip = ipep.Address.ToString(); + int deviceId = ByteToInt(msg, 5); + if (!TryGetDevice(deviceId, out _)) + { + UdpAlarmHost device = new() + { + DeviceId = deviceId, + Ip = ip, + Port = ipep.Port, + GroupId = ByteToInt(msg, 9), + UserId = ByteToInt(msg, 13), + OnlineState = 1, + KeepLive = Config.DeviceHeartKeep + }; + AddDevice(deviceId, ref device); + } + string alarmTime = $"20{GetBCD(msg[17])}-{GetBCD(msg[18])}-{GetBCD(msg[19])} " + + $"{GetBCD(msg[20])}:{GetBCD(msg[21])}:{GetBCD(msg[22])}"; + string CID = GetCID(msg[23]) + GetCID(msg[24]) + GetCID(msg[25]) + GetCID(msg[26]); + int sectorId = msg[29] + msg[30] * 256; + UdpAlarmHostMessage alarm = new() + { + DeviceId = deviceId, + Ip = ip, + GroupId = ByteToInt(msg, 9), + UserId = ByteToInt(msg, 13), + AlarmTime = alarmTime, + //CID暂定 + CID = CID, + LinkOut = msg[27] + msg[28] * 256, + SectorId = sectorId, + SubSectorId = msg[31] + msg[32] * 256, + ExtendArgs = msg[33].ToString("X2") + " " + msg[34].ToString("X2") + " " + msg[35].ToString("X2"), + }; + ReportAlarm(alarm); + } + + private void AnalysisAllSectorMessage(IPEndPoint ipep, byte[] msg) + { + string ip = ipep.Address.ToString(); + int deviceId = ByteToInt(msg, 5); + if (!TryGetDevice(deviceId, out UdpAlarmHost device)) + { + device = new() + { + DeviceId = deviceId, + Ip = ip, + Port = ipep.Port, + GroupId = ByteToInt(msg, 9), + UserId = ByteToInt(msg, 13), + OnlineState = 1, + KeepLive = Config.DeviceHeartKeep + }; + AddDevice(deviceId, ref device); + } + device.SectorTotal = msg[17] * 256 + msg[18]; + int sectorNum = msg[19], startIndex = msg[20]; + for (int i = 0; i < sectorNum; i++) + { + int sectorId = i + startIndex;//防区序号 + int pos = 21 + i;//防区信息所在byte数组未知 + SectorState curSector = new(device.DeviceId, sectorId, msg[pos]); + if (device.SectorDict.TryGetValue(sectorId, out SectorState? sector)) + { + ProcessSectorStateEvent(ref sector, curSector.State); + } + else + { + sector = curSector; + device.SectorDict[sector.Id] = sector; + //ProcessSectorStateEvent(ref sector); + } + } + } + + private void AnalysisSectorMessage(IPEndPoint ipep, byte[] msg) + { + //东北没有张力防区,暂不解析 + string ip = ipep.Address.ToString(); + int deviceId = ByteToInt(msg, 5); + if (!TryGetDevice(deviceId, out UdpAlarmHost device)) + { + device = new() + { + DeviceId = deviceId, + Ip = ip, + Port = ipep.Port, + GroupId = ByteToInt(msg, 9), + UserId = ByteToInt(msg, 13), + OnlineState = 1, + KeepLive = Config.DeviceHeartKeep + }; + AddDevice(deviceId, ref device); + } + } + + private void AnalysisMaxSectorMessage(IPEndPoint ipep, byte[] msg) + { + string ip = ipep.Address.ToString(); + int deviceId = ByteToInt(msg, 5); + if (!TryGetDevice(deviceId, out UdpAlarmHost device)) + { + device = new() + { + DeviceId = deviceId, + Ip = ip, + Port = ipep.Port, + GroupId = ByteToInt(msg, 9), + UserId = ByteToInt(msg, 13), + OnlineState = 1, + KeepLive = Config.DeviceHeartKeep + }; + AddDevice(deviceId, ref device); + } + device.SectorTotal = msg[17] * 256 + msg[18]; + } + + private void ProcessDeviceStateEvent(ref UdpAlarmHost device, int onlineState, int defenceState) + { + bool reportFlag = false; + if (device.OnlineState != onlineState) + { + if (onlineState == 0 && device.KeepLive < 0) + { + reportFlag = true; + device.OnlineState = onlineState; + } + else if (onlineState == 1 && device.KeepLive >= 0) + { + reportFlag = true; + device.OnlineState = onlineState; + } + } + if (device.DefenceState != defenceState) + { + reportFlag = true; + device.DefenceState = defenceState; + } + if (reportFlag) ReportDeviceState(device); + } + + private void ProcessSectorStateEvent(ref SectorState sector, int state) + { + if (sector.State != state) + { + sector.State = state; + ReportSectorState(sector); + } + } + + private void ReportDeviceState(UdpAlarmHost device) + { + OnFenceUdpDeviceState?.Invoke(device); + } + + private void ReportSectorState(SectorState sector) + { + OnFenceUdpSectorState?.Invoke(sector); + } + + private void ReportAlarm(UdpAlarmHostMessage msg) + { + OnFenceUdpAlarm?.Invoke(msg); + } + + #endregion Analysis + + #region Send + + /// + /// 应答 + /// + /// + /// + private bool SendOk(IPEndPoint ipep) + { + if (!IsRunning()) return false; + byte[] bytes = new byte[] { 0x08, 0x12, 0xF0, 0xFA, 0x8F, 0x06, 0x6B, 0x0D }; + ////byte[] bytes = AlarmEncode.GetSendMessage(0x8F, Array.Empty()); + return Server.SendMessage(ipep.Address.ToString(), ipep.Port, AlarmEncode.EncodeMessage(bytes)); + //Console.WriteLine(); + //byte[] bytes = new byte[] { + // 0x24, 0x12, 0xF0, 0xFA, + // 0x02, + // 0x77, 0x35, 0x94, 0x01, + // 0x07, 0x5B, 0xCD, 0x15, + // 0x00, 0x01, 0xE2, 0x40, + // 0x23, 0x05, 0x18, 0x17, 0x12, 0x34, + // 0x01, 0x00, 0x00, 0x01, + // 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + // 0x0F, 0x41, 0x0D }; + //Console.WriteLine(DataMessage.ToHexString(AlarmEncode.EncodeMessage(bytes))); bytes = new byte[] { + // 0x24, 0x12, 0xF0, 0xFA, + // 0x02, + // 0x77, 0x35, 0x94, 0x01, + // 0x07, 0x5B, 0xCD, 0x15, + // 0x00, 0x01, 0xE2, 0x40, + // 0x23, 0x05, 0x18, 0x17, 0x12, 0x34, + // 0x01, 0x00, 0x00, 0x02, + // 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + // 0x3C, 0x05, 0x0D }; + //Console.WriteLine(DataMessage.ToHexString(AlarmEncode.EncodeMessage(bytes))); bytes = new byte[] { + // 0x24, 0x12, 0xF0, 0xFA, + // 0x02, + // 0x77, 0x35, 0x94, 0x01, + // 0x07, 0x5B, 0xCD, 0x15, + // 0x00, 0x01, 0xE2, 0x40, + // 0x23, 0x05, 0x18, 0x17, 0x12, 0x34, + // 0x01, 0x00, 0x00, 0x01, + // 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, + // 0x0E, 0x35, 0x0D }; + //Console.WriteLine(DataMessage.ToHexString(AlarmEncode.EncodeMessage(bytes))); bytes = new byte[] { + // 0x24, 0x12, 0xF0, 0xFA, + // 0x02, + // 0x77, 0x35, 0x94, 0x01, + // 0x07, 0x5B, 0xCD, 0x15, + // 0x00, 0x01, 0xE2, 0x40, + // 0x23, 0x05, 0x18, 0x17, 0x12, 0x34, + // 0x01, 0x00, 0x00, 0x02, + // 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, + // 0x3C, 0xC9, 0x0D }; + //Console.WriteLine(DataMessage.ToHexString(AlarmEncode.EncodeMessage(bytes))); + //Server.SendMessage(ipep.Address.ToString(), ipep.Port, AlarmEncode.EncodeMessage(bytes)); + //return true; + } + + /// + /// 外出布防或者布防 + /// + /// + /// + public bool SetDeviceDefence(int deviceId) + { + UdpAlarmHost device = GetDevice(deviceId); + if (CheckDevice(device)) + { + byte[] bytes = AlarmEncode.GetSendMessage(0x80, new byte[] { 0x60 }); + return Server.SendMessage(device.Ip, device.Port, AlarmEncode.EncodeMessage(bytes)); + } + return false; + } + + /// + /// 撤防 + /// + /// + /// + public bool WithdrawDeviceDefence(int deviceId) + { + UdpAlarmHost device = GetDevice(deviceId); + if (CheckDevice(device)) + { + byte[] bytes = AlarmEncode.GetSendMessage(0x80, new byte[] { 0x61 }); + return Server.SendMessage(device.Ip, device.Port, AlarmEncode.EncodeMessage(bytes)); + } + return false; + } + + /// + /// 立即布防 + /// + /// + /// + public bool SetDeviceDefenceImmediately(int deviceId) + { + UdpAlarmHost device = GetDevice(deviceId); + if (CheckDevice(device)) + { + byte[] bytes = AlarmEncode.GetSendMessage(0x80, new byte[] { 0x62 }); + return Server.SendMessage(device.Ip, device.Port, AlarmEncode.EncodeMessage(bytes)); + } + return false; + } + + /// + /// 在家布防留守布防 + /// + /// + /// + public bool SetDeviceDefenceHome(int deviceId) + { + UdpAlarmHost device = GetDevice(deviceId); + if (CheckDevice(device)) + { + byte[] bytes = AlarmEncode.GetSendMessage(0x80, new byte[] { 0x63 }); + return Server.SendMessage(device.Ip, device.Port, AlarmEncode.EncodeMessage(bytes)); + } + return false; + } + + /// + /// 即时留守布防 + /// + /// + /// + public bool SetDeviceDefenceHomeImmediately(int deviceId) + { + UdpAlarmHost device = GetDevice(deviceId); + if (CheckDevice(device)) + { + byte[] bytes = AlarmEncode.GetSendMessage(0x80, new byte[] { 0x64 }); + return Server.SendMessage(device.Ip, device.Port, AlarmEncode.EncodeMessage(bytes)); + } + return false; + } + + /// + /// 清除报警记忆(复位) + /// + /// + /// + public bool ClearDeviceDefence(int deviceId) + { + UdpAlarmHost device = GetDevice(deviceId); + if (CheckDevice(device)) + { + byte[] bytes = AlarmEncode.GetSendMessage(0x80, new byte[] { 0x65 }); + return Server.SendMessage(device.Ip, device.Port, AlarmEncode.EncodeMessage(bytes)); + } + return false; + } + + /// + /// 校对系统时间 + /// + /// + /// + /// + public bool SetDeviceTime(int deviceId) + { + UdpAlarmHost device = GetDevice(deviceId); + if (CheckDevice(device)) + { + byte[] content = GetBCDTime(DateTime.Now); + byte[] bytes = AlarmEncode.GetSendMessage(0x8D, content); + return Server.SendMessage(device.Ip, device.Port, AlarmEncode.EncodeMessage(bytes)); + } + return false; + } + + /// + /// 设备重启 + /// + /// + /// + public bool RebootDevice(int deviceId) + { + UdpAlarmHost device = GetDevice(deviceId); + if (CheckDevice(device)) + { + byte[] bytes = AlarmEncode.GetSendMessage(0xA0, Array.Empty()); + return Server.SendMessage(device.Ip, device.Port, AlarmEncode.EncodeMessage(bytes)); + } + return false; + } + + /// + /// 单防区布防 + /// + /// + /// + /// + public bool SetSectorefence(int deviceId, int sectorId) + { + UdpAlarmHost device = GetDevice(deviceId); + if (CheckDevice(device)) + { + byte[] bytes = AlarmEncode.GetSendMessage(0xC0, new byte[] { (byte)(sectorId / 256), (byte)(sectorId % 256) }); + return Server.SendMessage(device.Ip, device.Port, AlarmEncode.EncodeMessage(bytes)); + } + return false; + } + + /// + /// 单防区撤防 + /// + /// + /// + /// + public bool WithdrawSectorDefence(int deviceId, int sectorId) + { + UdpAlarmHost device = GetDevice(deviceId); + if (CheckDevice(device)) + { + byte[] bytes = AlarmEncode.GetSendMessage(0xC1, new byte[] { (byte)(sectorId / 256), (byte)(sectorId % 256) }); + return Server.SendMessage(device.Ip, device.Port, AlarmEncode.EncodeMessage(bytes)); + } + return false; + } + + /// + /// 单防区旁路 + /// + /// + /// + /// + public bool SetSectorByway(int deviceId, int sectorId) + { + UdpAlarmHost device = GetDevice(deviceId); + if (CheckDevice(device)) + { + byte[] bytes = AlarmEncode.GetSendMessage(0xC2, new byte[] { (byte)(sectorId / 256), (byte)(sectorId % 256) }); + return Server.SendMessage(device.Ip, device.Port, AlarmEncode.EncodeMessage(bytes)); + } + return false; + } + + /// + /// 单防区旁路恢复 + /// + /// + /// + /// + public bool WithdrawSectorByway(int deviceId, int sectorId) + { + UdpAlarmHost device = GetDevice(deviceId); + if (CheckDevice(device)) + { + byte[] bytes = AlarmEncode.GetSendMessage(0xC3, new byte[] { (byte)(sectorId / 256), (byte)(sectorId % 256) }); + return Server.SendMessage(device.Ip, device.Port, AlarmEncode.EncodeMessage(bytes)); + } + return false; + } + + /// + /// 查询防区状态 + /// + /// + /// + /// + public bool SearchSectorState(int deviceId, int sectorId) + { + UdpAlarmHost device = GetDevice(deviceId); + if (CheckDevice(device)) + { + byte[] bytes = AlarmEncode.GetSendMessage(0x85, new byte[] { (byte)(sectorId / 256), (byte)(sectorId % 256) }); + return Server.SendMessage(device.Ip, device.Port, AlarmEncode.EncodeMessage(bytes)); + } + return false; + } + + /// + /// 查询所有防区状态 + /// + /// + /// + public bool SearchAllSectorState(int deviceId) + { + UdpAlarmHost device = GetDevice(deviceId); + if (CheckDevice(device)) + { + int sectorId = 0x00; + byte[] bytes = AlarmEncode.GetSendMessage(0x85, new byte[] { (byte)(sectorId / 256), (byte)(sectorId % 256) }); + return Server.SendMessage(device.Ip, device.Port, AlarmEncode.EncodeMessage(bytes)); + } + return false; + } + + #endregion Send + + #region DeviceDict + + public bool ContainsDevice(int deviceId) + { + return DeviceDict.ContainsKey(deviceId); + } + + public UdpAlarmHost GetDevice(int deviceId) + { + return DeviceDict[deviceId]; + } + + public bool TryGetDevice(int deviceId, out UdpAlarmHost device) + { + return DeviceDict.TryGetValue(deviceId, out device); + } + + private bool AddDevice(int deviceId, ref UdpAlarmHost device) + { + if (ContainsDevice(deviceId)) return false; + DeviceDict[deviceId] = device; + UdpAlarmHost innerDevice = DeviceDict[deviceId]; + if (innerDevice.SectorsEmpty && innerDevice.SectorsLock.TryEnterWriteLock(1000)) + { + Task.Run(() => + { + innerDevice = DeviceDict[deviceId]; + while (innerDevice.SectorsEmpty) + { + SearchAllSectorState(innerDevice.DeviceId); + Thread.Sleep(1000); + } + innerDevice.SectorsLock.ExitWriteLock(); + }); + } + return true; + } + + private void SetDevice(int deviceId, UdpAlarmHost device) + { + DeviceDict[deviceId] = device; + } + + private bool RemoveDevice(int deviceId) + { + return DeviceDict.Remove(deviceId, out _); + } + + public List GetDeviceList() + { + return DeviceDict.Values.ToList(); + } + + private bool CheckDevice(UdpAlarmHost device) + { + if (!IsRunning()) return false; + if (device == null) return false; + if (device.OnlineState == 0) return false; + return true; + } + + #endregion DeviceDict + + #region Util + + private int GetBit(byte bytes, int index) + { + return index switch + { + 0 => bytes & 0x01, + 1 => (bytes & 0x02) >> 1, + 2 => (bytes & 0x04) >> 2, + 3 => (bytes & 0x08) >> 3, + 4 => (bytes & 0x10) >> 4, + 5 => (bytes & 0x20) >> 5, + 6 => (bytes & 0x40) >> 6, + 7 => (bytes & 0x80) >> 7, + _ => 0, + }; + } + + private int ByteToInt(byte[] msg, int start) + { + byte[] bytes = new byte[] { msg[start + 3], msg[start + 2], msg[start + 1], msg[start] }; + return BitConverter.ToInt32(bytes, 0); + } + + private byte[] IntToByte(int num) + { + byte[] bytes = BitConverter.GetBytes(num); + return new byte[] { bytes[3], bytes[2], bytes[1], bytes[0] }; + } + + private string GetCID(byte bytes) + { + return bytes switch + { + 0x00 => "0", + 0x01 => "1", + 0x02 => "2", + 0x03 => "3", + 0x04 => "4", + 0x05 => "5", + 0x06 => "6", + 0x07 => "7", + 0x08 => "8", + 0x09 => "9", + 0x0A => "A", + 0x0B => "B", + 0x0C => "C", + 0x0D => "D", + 0x0E => "E", + 0x0F => "F", + _ => "0", + }; + } + + private string GetBCD(byte bytes) + { + int num = (bytes >> 4) * 10 + (bytes & 0x0F); + return num.ToString(); + } + + private byte GetBCDByte(int num) + { + if (num >= 100) num %= 100; + int hex = num / 10; + int lex = num % 10; + return (byte)(hex * 16 + lex); + } + + private byte[] GetBCDTime(DateTime time) + { + return new byte[] { GetBCDByte(time.Year),GetBCDByte((int)time.DayOfWeek),GetBCDByte(time.Month),GetBCDByte(time.Day), + GetBCDByte(time.Hour),GetBCDByte(time.Minute),GetBCDByte(time.Second) }; + } + + #endregion Util +} \ No newline at end of file diff --git a/JiLinApp.Docking/FenceAlarm/Service/UdpServer.cs b/JiLinApp.Docking/FenceAlarm/Service/UdpServer.cs new file mode 100644 index 0000000..2793054 --- /dev/null +++ b/JiLinApp.Docking/FenceAlarm/Service/UdpServer.cs @@ -0,0 +1,136 @@ +using JiLinApp.Docking.VibrateAlarm; +using System.ComponentModel; +using System.Net; +using System.Net.Sockets; + +namespace JiLinApp.Docking.FenceAlarm; + +public class UdpServer +{ + #region Fields + + /// + /// 用于UDP接收的网络服务类 + /// + private UdpClient RecvUdp { get; } + + /// + /// 用于UDP发送的网络服务类 + /// + private UdpClient SendUdp { get; } + + private BackgroundWorker Worker { get; } + + #region Properties + + public string RecvIp { get; private set; } + + public int RecvPort { get; private set; } + + #endregion Properties + + #endregion Fields + + #region Ctors + + public UdpServer(int port) : this("0.0.0.0", port) + { + } + + public UdpServer(string ip, int port) + { + RecvIp = ip; + RecvPort = port; + RecvUdp = new(new IPEndPoint(IPAddress.Parse(ip), port)); + SendUdp = new(new IPEndPoint(IPAddress.Any, 0)); + Worker = new() + { + WorkerSupportsCancellation = true + }; + Worker.DoWork += Back_DoWork; + } + + ~UdpServer() + { + Stop(); + } + + #endregion Ctors + + #region Server + + public void Start() + { + if (IsRunning()) return; + Worker.RunWorkerAsync(); + } + + public void Stop() + { + if (!IsRunning()) return; + Worker.CancelAsync(); + RecvUdp.Close(); + SendUdp.Close(); + } + + public bool IsRunning() + { + return Worker != null && Worker.IsBusy; + } + + #endregion Server + + #region Events + + public event EventHandler>? DatagramReceived; + + private void RaiseDatagramReceived(IPEndPoint ipep, byte[] datagram) + { + DatagramReceived?.Invoke(this, new UdpDatagramReceivedEventArgs(ipep, datagram)); + } + + #endregion Events + + #region Receive + + private void Back_DoWork(object? sender, DoWorkEventArgs e) + { + IPEndPoint remoteIpep = new(IPAddress.Any, 0); + while (Worker != null && !Worker.CancellationPending) + { + try + { + byte[] bytRecv = RecvUdp.Receive(ref remoteIpep); + RaiseDatagramReceived(remoteIpep, bytRecv); + } + catch + { + } + } + } + + #endregion Receive + + #region Send + + public bool SendMessage(string ip, int port, byte[] msg) + { + IPEndPoint ipep = new(IPAddress.Parse(ip), port); + int result = SendUdp.Send(msg, msg.Length, ipep); + bool flag = result == msg.Length; + Console.WriteLine("Send to {0}:{1} => {2}, {3}, {4}", ip, port, DataMessage.ToHexString(msg), DataMessage.ToHexString(AlarmEncode.DecodeMessage(msg)), flag); + return flag; + } + + public bool SendMessage(IPEndPoint ipep, byte[] msg) + { + string ip = ipep.Address.ToString(); + int port = ipep.Port; + int result = SendUdp.Send(msg, msg.Length, ipep); + bool flag = result == msg.Length; + Console.WriteLine("Send to {0}:{1} => {2}, {3}", ip, port, DataMessage.ToHexString(AlarmEncode.DecodeMessage(msg)), flag); + return flag; + } + + #endregion Send +} \ No newline at end of file diff --git a/JiLinApp.Docking/JiLinApp.Docking.csproj b/JiLinApp.Docking/JiLinApp.Docking.csproj new file mode 100644 index 0000000..c893745 --- /dev/null +++ b/JiLinApp.Docking/JiLinApp.Docking.csproj @@ -0,0 +1,25 @@ + + + + net7.0 + enable + enable + + + + + + + + + + + + + + + PreserveNewest + + + + diff --git a/JiLinApp.Docking/Military/Config.cs b/JiLinApp.Docking/Military/Config.cs new file mode 100644 index 0000000..ef30ee5 --- /dev/null +++ b/JiLinApp.Docking/Military/Config.cs @@ -0,0 +1,16 @@ +namespace JiLinApp.Docking.Military; + +public class MilitaryConfig +{ + //public bool Enable { get; set; } + + public string Url { get; set; } + + public string UserName { get; set; } + + public string Password { get; set; } + + public int RequestTryTime { get; set; } + + public int RequestTryInterval { get; set; } +} \ No newline at end of file diff --git a/JiLinApp.Docking/Military/Entity/CameraLinkageInfo.cs b/JiLinApp.Docking/Military/Entity/CameraLinkageInfo.cs new file mode 100644 index 0000000..a2d203f --- /dev/null +++ b/JiLinApp.Docking/Military/Entity/CameraLinkageInfo.cs @@ -0,0 +1,18 @@ +namespace JiLinApp.Docking.Military; + +public class CameraLinkageInfo +{ + #region Fields + + public string Id { get; set; } + + public string DeviceId { get; set; } + + public string SensorId { get; set; } + + public string CameraId { get; set; } + + public int[] PresetIds { get; set; } + + #endregion Fields +} \ No newline at end of file diff --git a/JiLinApp.Docking/Military/MilitaryService.cs b/JiLinApp.Docking/Military/MilitaryService.cs new file mode 100644 index 0000000..1315cea --- /dev/null +++ b/JiLinApp.Docking/Military/MilitaryService.cs @@ -0,0 +1,165 @@ +using EC.Util.CameraSDK; +using EC.Util.Common; +using Flurl.Http; +using Newtonsoft.Json.Linq; + +namespace JiLinApp.Docking.Military; + +public class MilitaryService +{ + #region Fields + + private MilitaryConfig Config { get; } + + private string Token { get; set; } + + #endregion Fields + + public MilitaryService(MilitaryConfig config) + { + Config = config; + Token = Login(); + } + + #region Base + + private string GetUrl() + { + return Config.Url; + } + + private IFlurlRequest WithToken(string url) + { + return url.WithHeader("X-Access-Token", Token); + } + + #endregion Base + + #region Cmd + + public string Login() + { + string url = $"{GetUrl()}/sys/login"; + object data = new + { + username = Config.UserName, + password = Config.Password, + }; + JObject response = new(); + for (int i = 0; i < Config.RequestTryTime; i++) + { + response = WithToken(url).PostJsonAsync(data).ReceiveJson().Result; + bool success = response["success"].ToBoolean(); + if (!success) + { + Thread.Sleep(Config.RequestTryInterval); + continue; + } + string? token = response["result"]?["token"]?.ToString(); + return token ?? string.Empty; + } + string? message = response["message"]?.ToString(); + throw new Exception(message); + } + + public List GetCameraList() + { + string url = $"{GetUrl()}/camera/setting/list"; + JObject response = new(); + for (int i = 0; i < Config.RequestTryTime; i++) + { + response = WithToken(url).GetAsync().ReceiveJson().Result; + bool success = response["success"].ToBoolean(); + if (!success) + { + Thread.Sleep(Config.RequestTryInterval); + continue; + } + JToken records = response?["result"]?["records"] ?? new JObject(); + List list = new(); + foreach (var item in records) + { + CameraManufactor manufactor; + string factory = item?["factory_dictText"]?.ToString() ?? string.Empty; + if (factory.Contains("海康威视")) manufactor = CameraManufactor.HiK; + else if (factory.Contains("大华")) manufactor = CameraManufactor.DaHua; + else if (factory.Contains("宇视")) manufactor = CameraManufactor.YuShi; + else continue; + string id = item?["id"]?.ToString() ?? string.Empty; + string ip = item?["ip"]?.ToString() ?? string.Empty; + string username = item?["user"]?.ToString() ?? string.Empty; + string password = item?["password"]?.ToString() ?? string.Empty; + string name = $"{item?["siteName"]?.ToString()}-{item?["cameraName"]?.ToString()}-{ip}"; + if (VerifyUtil.IsEmpty(id) || !VerifyUtil.IsIp(ip) || VerifyUtil.IsEmpty(username) || VerifyUtil.IsEmpty(password)) continue; + CameraInfo info = CameraInfo.New(manufactor, ip, username, password); + info.Id = id; + info.Name = name; + list.Add(info); + } + return list; + } + string? message = response["message"]?.ToString(); + throw new Exception(message); + } + + public List GetFencesInfoList() + { + string url = $"{GetUrl()}/msFencesInfo/list"; + JObject response = new(); + for (int i = 0; i < Config.RequestTryTime; i++) + { + response = WithToken(url).GetAsync().ReceiveJson().Result; + bool success = response["success"].ToBoolean(); + if (!success) + { + Thread.Sleep(Config.RequestTryInterval); + continue; + } + return new List(); + } + string? message = response["message"]?.ToString(); + throw new Exception(message); + } + + public List GetCameraLinkageList() + { + string url = $"{GetUrl()}/military/MsCameraLinkage/list"; + JObject response = new(); + for (int i = 0; i < Config.RequestTryTime; i++) + { + response = WithToken(url).GetAsync().ReceiveJson().Result; + bool success = response["success"].ToBoolean(); + if (!success) + { + Thread.Sleep(Config.RequestTryInterval); + continue; + } + JToken records = response?["result"]?["records"] ?? new JObject(); + List list = new(); + foreach (var item in records) + { + string id = item?["id"]?.ToString() ?? string.Empty; + //string deviceId = item?["deviceId"]?.ToString() ?? string.Empty; + string deviceId = "0"; + string sensorId = item?["objCode"]?.ToString() ?? string.Empty; + string cameraId = item?["cameraId"]?.ToString() ?? string.Empty; + string placements = item?["placements"]?.ToString() ?? string.Empty; + int[] presetIds = Array.ConvertAll(placements.Split(","), int.Parse); + if (VerifyUtil.IsEmpty(id) || VerifyUtil.IsEmpty(sensorId) || VerifyUtil.IsEmpty(cameraId)) continue; + list.Add(new() + { + Id = id, + DeviceId = deviceId, + SensorId = sensorId, + CameraId = cameraId, + PresetIds = presetIds + }); + } + return list; + } + string? message = response["message"]?.ToString(); + throw new Exception(message); + } + + #endregion Cmd +} \ No newline at end of file diff --git a/JiLinApp.Docking/Ptz/DCamera.cs b/JiLinApp.Docking/Ptz/DCamera.cs new file mode 100644 index 0000000..b69350b --- /dev/null +++ b/JiLinApp.Docking/Ptz/DCamera.cs @@ -0,0 +1,128 @@ +namespace JiLinApp.Docking.Ptz; + +public class DCamera +{ + #region Fields + + #region Data3 + + public static double SpeedMin = 1; + public static double SpeedMax = 60; + + #endregion Data3 + + #endregion Fields + + #region BaseMethod + + private static byte[] CommandPara(byte com1, byte com2, int data1, int data2) + { + byte[] b = new byte[11]; + b[0] = 0xA1; + int temp1 = data1; + int temp2 = data2; + b[1] = 0x00; + b[2] = 0x0B; + b[3] = com1; + b[4] = com2; + b[5] = (byte)(data1 >> 8 & 0xFF); + b[6] = (byte)(temp1 & 0xFF); + b[7] = (byte)(data2 >> 8 & 0xFF); + b[8] = (byte)(temp2 & 0xFF); + b[9] = (byte)(b[0] ^ b[1] ^ b[2] ^ b[3] ^ b[4] ^ b[5] ^ b[6] ^ b[7] ^ b[8]); + b[10] = 0xAF; + return b; + } + + private static byte[] SendJoystick(double headSpeed, double pitchSpeed) + { + return CommandPara(0x4D, 0x58, (int)(headSpeed * 100), (int)(pitchSpeed * 100)); + } + + #endregion BaseMethod + + #region PtzMethod + + public static byte[] Left(double headSpeed) + { + double absHs = Math.Abs(headSpeed); + if (absHs < SpeedMin) headSpeed = -SpeedMin; + if (absHs > SpeedMax) headSpeed = -SpeedMax; + return SendJoystick(headSpeed, 0); + } + + public static byte[] Right(double headSpeed) + { + double absHs = Math.Abs(headSpeed); + if (absHs < SpeedMin) headSpeed = SpeedMin; + if (absHs > SpeedMax) headSpeed = SpeedMax; + return SendJoystick(headSpeed, 0); + } + + public static byte[] Up(double pitchSpeed) + { + double absPs = Math.Abs(pitchSpeed); + if (absPs < SpeedMin) pitchSpeed = SpeedMin; + if (absPs > SpeedMax) pitchSpeed = SpeedMax; + return SendJoystick(0, pitchSpeed); + } + + public static byte[] Down(double pitchSpeed) + { + double absPs = Math.Abs(pitchSpeed); + if (absPs < SpeedMin) pitchSpeed = -SpeedMin; + if (absPs > SpeedMax) pitchSpeed = -SpeedMax; + return SendJoystick(0, pitchSpeed); + } + + public static byte[] LeftUp(double headSpeed, double pitchSpeed) + { + double absHs = Math.Abs(headSpeed); + if (absHs < SpeedMin) headSpeed = -SpeedMin; + if (absHs > SpeedMax) headSpeed = -SpeedMax; + double absPs = Math.Abs(pitchSpeed); + if (absPs < SpeedMin) pitchSpeed = SpeedMin; + if (absPs > SpeedMax) pitchSpeed = SpeedMax; + return SendJoystick(headSpeed, pitchSpeed); + } + + public static byte[] LeftDown(double headSpeed, double pitchSpeed) + { + double absHs = Math.Abs(headSpeed); + if (absHs < SpeedMin) headSpeed = -SpeedMin; + if (absHs > SpeedMax) headSpeed = -SpeedMax; + double absPs = Math.Abs(pitchSpeed); + if (absPs < SpeedMin) pitchSpeed = -SpeedMin; + if (absPs > SpeedMax) pitchSpeed = -SpeedMax; + return SendJoystick(headSpeed, pitchSpeed); + } + + public static byte[] RightUp(double headSpeed, double pitchSpeed) + { + double absHs = Math.Abs(headSpeed); + if (absHs < SpeedMin) headSpeed = SpeedMin; + if (absHs > SpeedMax) headSpeed = SpeedMax; + double absPs = Math.Abs(pitchSpeed); + if (absPs < SpeedMin) pitchSpeed = SpeedMin; + if (absPs > SpeedMax) pitchSpeed = SpeedMax; + return SendJoystick(headSpeed, pitchSpeed); + } + + public static byte[] RightDown(double headSpeed, double pitchSpeed) + { + double absHs = Math.Abs(headSpeed); + if (absHs < SpeedMin) headSpeed = SpeedMin; + if (absHs > SpeedMax) headSpeed = SpeedMax; + double absPs = Math.Abs(pitchSpeed); + if (absPs < SpeedMin) pitchSpeed = -SpeedMin; + if (absPs > SpeedMax) pitchSpeed = -SpeedMax; + return SendJoystick(headSpeed, pitchSpeed); + } + + public static byte[] Stop() + { + return SendJoystick(0, 0); + } + + #endregion PtzMethod +} \ No newline at end of file diff --git a/JiLinApp.Docking/Ptz/PelcoD.cs b/JiLinApp.Docking/Ptz/PelcoD.cs new file mode 100644 index 0000000..4665c16 --- /dev/null +++ b/JiLinApp.Docking/Ptz/PelcoD.cs @@ -0,0 +1,240 @@ +namespace JiLinApp.Docking.Ptz; + +public class PelcoD +{ + private string watchdir = ""; //监控方向 + private static readonly byte STX = 0xFF; //同步字节 + + #region 监控方向和定时监控实体 + + public string WatchDir + { + get { return watchdir; } + set { watchdir = value; } + } + + #endregion 监控方向和定时监控实体 + + #region 基本指令定义 + + #region 指令码1 + + private const byte FocusNear = 0x01; //增加聚焦 + private const byte IrisOpen = 0x02; //减小光圈 + private const byte IrisClose = 0x04; //增加光圈 + private const byte CameraOnOff = 0x08; //摄像机打开和关闭 + private const byte AutoManualScan = 0x10; //自动和手动扫描 + private const byte Sense = 0x80; //Sence码 + + #endregion 指令码1 + + #region 指令码2 + + private const byte PanRight = 0x02; //右 + private const byte PanLeft = 0x04; //左 + + private const byte TiltUp = 0x08; //上 + private const byte TiltDown = 0x10; //下 + private const byte ZoomTele = 0x20; //增加对焦 + private const byte ZoomWide = 0x40; //减小对焦 + private const byte FocusFar = 0x80; //减小聚焦 + + #endregion 指令码2 + + #region 镜头左右平移的速度 + + public static byte PanSpeedMin = 0x00; //停止 + public static byte PanSpeedMax = 0x3F; //最高速 + + #endregion 镜头左右平移的速度 + + #region 镜头上下移动的速度 + + public static byte TiltSpeedMin = 0x00; //停止 + public static byte TiltSpeedMax = 0x3F; //最高速 + + #endregion 镜头上下移动的速度 + + #endregion 基本指令定义 + + private const byte PanRightUp = 0xa; //右上 + private const byte PanLeftUp = 0x0c; //左上 + + private const byte PanRightDown = 0x12; //右下 + private const byte PanLeftDown = 0x14; //左下 + + #region 云台控制枚举 + + public enum Switch + { + On = 0x01, + Off = 0x02 + } //雨刷控制 + + public enum Focus + { + Near = FocusNear, + Far = FocusFar + } //聚焦控制 + + public enum Zoom + { + Wide = ZoomWide, + Tele = ZoomTele + } //对焦控制 + + public enum Tilt + { + Up = TiltUp, + Down = TiltDown + } //上下控制 + + public enum Pan + { + Left = PanLeft, + Right = PanRight, + LeftUp = PanLeftUp, + LeftDown = PanLeftDown, + RightUp = PanRightUp, + RightDown = PanRightDown + } //左右控制 + + public enum Scan + { + Auto, + Manual + } //自动和手动控制 + + public enum Iris + { + Open = IrisOpen, + Close = IrisClose + } //光圈控制 + + public enum PresetAction + { + Set = 0x03, + Clear = 0x05, + Goto = 0x07 + } + + #endregion 云台控制枚举 + + #region 云台控制方法 + + //雨刷控制 + public static byte[] CameraSwitch(uint deviceAddress, Switch action) + { + byte m_action = CameraOnOff; + if (action == Switch.On) + m_action = CameraOnOff + Sense; + return Message.GetMessage(deviceAddress, m_action, 0x00, 0x00, 0x00); + } + + //光圈控制 + public static byte[] CameraIrisSwitch(uint deviceAddress, Iris action) + { + return Message.GetMessage(deviceAddress, (byte)action, 0x00, 0x00, 0x00); + } + + //聚焦控制 + public static byte[] CameraFocus(uint deviceAddress, Focus action) + { + if (action == Focus.Near) + return Message.GetMessage(deviceAddress, (byte)action, 0x00, 0x00, 0x00); + else + return Message.GetMessage(deviceAddress, 0x00, (byte)action, 0x00, 0x00); + } + + //对焦控制 + public static byte[] CameraZoom(uint deviceAddress, Zoom action) + { + return Message.GetMessage(deviceAddress, 0x00, (byte)action, 0x00, 0x00); + } + + /// + /// 上下控制 + /// + /// + /// + /// + /// + + public static byte[] CameraTilt(uint deviceAddress, Tilt action, uint speed) + { + if (speed < TiltSpeedMin) + speed = TiltSpeedMin; + if (speed > TiltSpeedMax) + speed = TiltSpeedMax; + return Message.GetMessage(deviceAddress, 0x00, (byte)action, 0x00, (byte)speed); + } + + /// + /// 左右控制 + /// + /// + /// + /// + /// + + public static byte[] CameraPan(uint deviceAddress, Pan action, uint speed) + { + if (speed < PanSpeedMin) + speed = PanSpeedMin; + if (speed > PanSpeedMax) + speed = PanSpeedMax; + return Message.GetMessage(deviceAddress, 0x00, (byte)action, (byte)speed, 0x00); + } + + //停止云台的移动 + public static byte[] CameraStop(uint deviceAddress) + { + return Message.GetMessage(deviceAddress, 0x00, 0x00, 0x00, 0x00); + } + + //自动和手动控制 + public static byte[] CameraScan(uint deviceAddress, Scan scan) + { + byte m_byte = AutoManualScan; + if (scan == Scan.Auto) + m_byte = AutoManualScan + Sense; + return Message.GetMessage(deviceAddress, m_byte, 0x00, 0x00, 0x00); + } + + public static byte[] Preset(uint deviceAddress, PresetAction action, byte presetId) + { + return Message.GetMessage(deviceAddress, 0x00, (byte)action, 0x00, presetId); + } + + #endregion 云台控制方法 + + public struct Message + { + public static byte Address; + public static byte CheckSum; + + public static byte Command1, + Command2, + Data1, + Data2; + + public static byte[] GetMessage( + uint address, + byte command1, + byte command2, + byte data1, + byte data2 + ) + { + if (address < 1 & address > 256) + throw new Exception("Pelco D协议只支持256设备"); + Address = byte.Parse(address.ToString()); + Command1 = command1; + Command2 = command2; + Data1 = data1; + Data2 = data2; + CheckSum = (byte)(Address + Command1 + Command2 + Data1 + Data2); + return new byte[] { STX, Address, Command1, Command2, Data1, Data2, CheckSum }; + } + } +} \ No newline at end of file diff --git a/JiLinApp.Docking/Ptz/PelcoP.cs b/JiLinApp.Docking/Ptz/PelcoP.cs new file mode 100644 index 0000000..de28170 --- /dev/null +++ b/JiLinApp.Docking/Ptz/PelcoP.cs @@ -0,0 +1,337 @@ +namespace JiLinApp.Docking.Ptz; + +/// +/// dot.NET Implementation of Pelco P Protocol +/// +public class PelcoP +{ + private const byte STX = 0xA0; + private const byte ETX = 0xAF; + + #region Pan and Tilt Commands + + #region Data1 + + private const byte FocusFar = 0x01; + private const byte FocusNear = 0x02; + private const byte IrisOpen = 0x04; + private const byte IrisClose = 0x08; + private const byte CameraOnOff = 0x10; + private const byte AutoscanOn = 0x20; + private const byte CameraOn = 0x40; + + #endregion Data1 + + #region Data2 + + private const byte PanRight = 0x02; + private const byte PanLeft = 0x04; + private const byte TiltUp = 0x08; + private const byte TiltDown = 0x10; + private const byte ZoomTele = 0x20; + private const byte ZoomWide = 0x40; + + #endregion Data2 + + #region Data3 + + public static byte PanSpeedMin = 0x00; + public static byte PanSpeedMax = 0x3F; + + #endregion Data3 + + #region Data4 + + public static byte TiltSpeedMin = 0x00; + public static byte TiltSpeedMax = 0x3F; + + #endregion Data4 + + #endregion Pan and Tilt Commands + + #region Enums + + public enum PatternAction + { + Start, + Stop, + Run + } + + public enum Action + { + Start, + Stop + } + + public enum LensSpeed + { + Low = 0x00, + Medium = 0x01, + High = 0x02, + Turbo = 0x03 + } + + public enum Pan + { + Left = PanLeft, + Right = PanRight + } + + public enum Tilt + { + Up = TiltUp, + Down = TiltDown + } + + public enum Iris + { + Open = IrisOpen, + Close = IrisClose + } + + public enum Zoom + { + Wide = ZoomWide, + Tele = ZoomTele + } + + public enum Switch + { + On, + Off + } + + public enum Focus + { + Near = FocusNear, + Far = FocusFar + } + + public enum PresetAction + { + Set = 0x03, + Clear = 0x05, + Goto = 0x07 + } + + #endregion Enums + + #region Extended Command Set + + public static byte[] Preset(uint deviceAddress, PresetAction action, byte presetId) + { + return Message.GetMessage(deviceAddress, 0x00, (byte)action, 0x00, presetId); + } + + public static byte[] Flip(uint deviceAddress) + { + return Message.GetMessage(deviceAddress, 0x00, 0x07, 0x00, 0x21); + } + + public static byte[] ZeroPanPosition(uint deviceAddress) + { + return Message.GetMessage(deviceAddress, 0x00, 0x07, 0x00, 0x22); + } + + public static byte[] AutoScan(uint deviceAddress, Action action) + { + byte m_action; + if (action == Action.Start) + m_action = 0x09; + else + m_action = 0x0B; + return Message.GetMessage(deviceAddress, 0x00, m_action, 0x00, 0x00); + } + + public static byte[] RemoteReset(uint deviceAddress) + { + return Message.GetMessage(deviceAddress, 0x00, 0x0F, 0x00, 0x00); + } + + public static byte[] Zone(uint deviceAddress, byte zone, Action action) + { + if (zone < 0x01 & zone > 0x08) + throw new Exception("Zone value should be between 0x01 and 0x08 include"); + byte m_action; + if (action == Action.Start) + m_action = 0x11; + else + m_action = 0x13; + + return Message.GetMessage(deviceAddress, 0x00, m_action, 0x00, zone); + } + + public static byte[] WriteToScreen(uint deviceAddress, string text) + { + if (text.Length > 40) + text = text.Remove(40, text.Length - 40); + System.Text.Encoding encoding = System.Text.Encoding.ASCII; + byte[] m_bytes = new byte[encoding.GetByteCount(text) * 8]; + int i = 0; + byte m_scrPosition; + byte m_ASCIIchr; + + foreach (char ch in text) + { + m_scrPosition = Convert.ToByte(i / 8); + m_ASCIIchr = Convert.ToByte(ch); + Array.Copy( + Message.GetMessage(deviceAddress, 0x00, 0x15, m_scrPosition, m_ASCIIchr), + 0, + m_bytes, + i, + 8 + ); + i += 8; + } + + return m_bytes; + } + + public static byte[] ClearScreen(uint deviceAddress) + { + return Message.GetMessage(deviceAddress, 0x00, 0x17, 0x00, 0x00); + } + + public static byte[] AlarmAcknowledge(uint deviceAddress, uint alarmID) + { + if (alarmID < 1 & alarmID > 8) + throw new Exception("Only 8 alarms allowed for Pelco P implementation"); + return Message.GetMessage(deviceAddress, 0x00, 0x19, 0x00, Convert.ToByte(alarmID)); + } + + public static byte[] ZoneScan(uint deviceAddress, Action action) + { + byte m_action; + if (action == Action.Start) + m_action = 0x1B; + else + m_action = 0x1D; + return Message.GetMessage(deviceAddress, 0x00, m_action, 0x00, 0x00); + } + + public static byte[] Pattern(uint deviceAddress, PatternAction action) + { + byte m_action = action switch + { + PatternAction.Start => 0x1F, + PatternAction.Stop => 0x21, + PatternAction.Run => 0x23, + _ => 0x23, + }; + return Message.GetMessage(deviceAddress, 0x00, m_action, 0x00, 0x00); + } + + public static byte[] SetZoomLensSpeed(uint deviceAddress, LensSpeed speed) + { + return Message.GetMessage(deviceAddress, 0x00, 0x25, 0x00, (byte)speed); + } + + public static byte[] SetFocusLensSpeed(uint deviceAddress, LensSpeed speed) + { + return Message.GetMessage(deviceAddress, 0x00, 0x27, 0x00, (byte)speed); + } + + #endregion Extended Command Set + + #region Base Command Set + + public static byte[] CameraSwitch(uint deviceAddress, Switch action) + { + byte m_action = CameraOnOff; + if (action == Switch.On) + m_action += CameraOnOff; //Maybe wrong !!! + return Message.GetMessage(deviceAddress, m_action, 0x00, 0x00, 0x00); + } + + public static byte[] CameraIrisSwitch(uint deviceAddress, Iris action) + { + return Message.GetMessage(deviceAddress, (byte)action, 0x00, 0x00, 0x00); + } + + public static byte[] CameraFocus(uint deviceAddress, Focus action) + { + return Message.GetMessage(deviceAddress, (byte)action, 0x00, 0x00, 0x00); + } + + public static byte[] CameraZoom(uint deviceAddress, Zoom action) + { + return Message.GetMessage(deviceAddress, 0x00, (byte)action, 0x00, 0x00); + } + + public static byte[] CameraTilt(uint deviceAddress, Tilt action, uint speed) + { + if (speed < TiltSpeedMin) + speed = TiltSpeedMin; + if (speed > TiltSpeedMax) + speed = TiltSpeedMax; + return Message.GetMessage(deviceAddress, 0x00, (byte)action, 0x00, (byte)speed); + } + + public static byte[] CameraPan(uint deviceAddress, Pan action, uint speed) + { + if (speed < PanSpeedMin) + speed = PanSpeedMin; + if (speed > PanSpeedMax) + speed = PanSpeedMax; + return Message.GetMessage(deviceAddress, 0x00, (byte)action, (byte)speed, 0x00); + } + + public static byte[] CameraPanTilt( + uint deviceAddress, + Pan panAction, + uint panSpeed, + Tilt tiltAction, + uint tiltSpeed + ) + { + byte[] m_tiltMessage = CameraTilt(deviceAddress, tiltAction, tiltSpeed); + byte[] m_panMessage = CameraPan(deviceAddress, panAction, panSpeed); + byte[] m_bytes = Message.GetMessage( + deviceAddress, + 0x00, + (byte)(m_tiltMessage[3] + m_panMessage[3]), + m_panMessage[4], + m_tiltMessage[5] + ); + return m_bytes; + } + + public static byte[] CameraStop(uint deviceAddress) + { + return Message.GetMessage(deviceAddress, 0x00, 0x00, 0x00, 0x00); + } + + #endregion Base Command Set + + public struct Message + { + public static byte Address; + public static byte CheckSum; + + public static byte Data1, + Data2, + Data3, + Data4; + + public static byte[] GetMessage( + uint address, + byte data1, + byte data2, + byte data3, + byte data4 + ) + { + if (address < 0 & address > 32) + throw new Exception("Protocol Pelco P support 32 devices only"); + Address = byte.Parse((address - 1).ToString()); + Data1 = data1; + Data2 = data2; + Data3 = data3; + Data4 = data4; + CheckSum = (byte)(STX ^ Address ^ Data1 ^ Data2 ^ Data3 ^ Data4 ^ ETX); + return new byte[] { STX, Address, Data1, Data2, Data3, Data4, ETX, CheckSum }; + } + } +} \ No newline at end of file diff --git a/JiLinApp.Docking/Ptz/PtzCmd.cs b/JiLinApp.Docking/Ptz/PtzCmd.cs new file mode 100644 index 0000000..35d3e45 --- /dev/null +++ b/JiLinApp.Docking/Ptz/PtzCmd.cs @@ -0,0 +1,415 @@ +using EC.Util.CameraSDK; + +namespace JiLinApp.Docking.Ptz; + +public class PtzCmd +{ + public static PtzControlType GetControlType(string ctrlType) + { + return ctrlType switch + { + "PelcoD" => PtzControlType.PelcoD, + "PelcoP" => PtzControlType.PelcoP, + "DCamera" => PtzControlType.DCamera, + "CameraSdk" => PtzControlType.CameraSdk, + _ => PtzControlType.None + }; + } + + public static PtzCmdType GetCmdType(string cmdStr) + { + return cmdStr switch + { + "Left" => PtzCmdType.Left, + "Right" => PtzCmdType.Right, + "Top" => PtzCmdType.Top, + "Down" => PtzCmdType.Down, + "LeftTop" => PtzCmdType.LeftTop, + "LeftDown" => PtzCmdType.LeftDown, + "RightTop" => PtzCmdType.RightTop, + "RightDown" => PtzCmdType.RightDown, + "Stop" => PtzCmdType.Stop, + "AutoMove" => PtzCmdType.AutoMove, + "ZoomIn" => PtzCmdType.ZoomIn, + "ZoomOut" => PtzCmdType.ZoomOut, + "FocusNear" => PtzCmdType.FocusNear, + "FocusFar" => PtzCmdType.FocusFar, + "IrisOpen" => PtzCmdType.IrisOpen, + "IrisClose" => PtzCmdType.IrisClose, + "PresetSet" => PtzCmdType.PresetSet, + "PresetClear" => PtzCmdType.PresetClear, + "PresetGoto" => PtzCmdType.PresetGoto, + _ => PtzCmdType.None + }; + } +} + +public class PtzComCmd +{ + /// + /// GetPelcoDCmd + /// GetPelcoPCmd + /// DCamearCmd + /// + /// + /// + /// + /// + public static byte[] GetCmd(PtzControlType ctrlType, PtzCmdType cmdType, object[] args = null) + { + byte[] cmd = ctrlType switch + { + PtzControlType.PelcoD => GetPelcoDCmd(cmdType, args), + PtzControlType.PelcoP => GetPelcoPCmd(cmdType, args), + PtzControlType.DCamera => GetDCameraCmd(cmdType, args), + _ => Array.Empty(), + }; + return cmd; + } + + public static byte[] GetPelcoDCmd(PtzCmdType cmdType, object[] args) + { + uint addr = (byte)args[0]; + uint panSpeed = 0x2f; + uint tiltSpeed = (uint)(PelcoD.TiltSpeedMax + PelcoD.TiltSpeedMin) / 2; + byte[] cmd = cmdType switch + { + PtzCmdType.Left => PelcoD.CameraPan(addr, PelcoD.Pan.Left, panSpeed), + PtzCmdType.Right => PelcoD.CameraPan(addr, PelcoD.Pan.Right, panSpeed), + PtzCmdType.Top => PelcoD.CameraTilt(addr, PelcoD.Tilt.Up, tiltSpeed), + PtzCmdType.Down => PelcoD.CameraTilt(addr, PelcoD.Tilt.Down, tiltSpeed), + PtzCmdType.LeftTop => PelcoD.CameraPan(addr, PelcoD.Pan.LeftUp, panSpeed), + PtzCmdType.LeftDown => PelcoD.CameraPan(addr, PelcoD.Pan.LeftDown, panSpeed), + PtzCmdType.RightTop => PelcoD.CameraPan(addr, PelcoD.Pan.RightUp, panSpeed), + PtzCmdType.RightDown => PelcoD.CameraPan(addr, PelcoD.Pan.RightDown, panSpeed), + PtzCmdType.Stop => PelcoD.CameraStop(addr), + PtzCmdType.AutoMove => PelcoD.CameraScan(addr, PelcoD.Scan.Auto), + //PtzCmdType.AutoStop => PelcoD.CameraScan(addr, PelcoD.Scan.Manual), + PtzCmdType.PresetGoto => PelcoD.Preset(addr, PelcoD.PresetAction.Goto, (byte)args[1]), + _ => Array.Empty(), + }; + return cmd; + } + + public static byte[] GetPelcoPCmd(PtzCmdType cmdType, object[] args) + { + uint addr = (byte)args[0]; + uint panSpeed = (uint)(PelcoP.PanSpeedMax + PelcoP.PanSpeedMin) / 2; + uint tiltSpeed = (uint)(PelcoP.TiltSpeedMax + PelcoP.TiltSpeedMin) / 2; + byte[] cmd = cmdType switch + { + PtzCmdType.Left => PelcoP.CameraPan(addr, PelcoP.Pan.Left, panSpeed), + PtzCmdType.Right => PelcoP.CameraPan(addr, PelcoP.Pan.Right, panSpeed), + PtzCmdType.Top => PelcoP.CameraTilt(addr, PelcoP.Tilt.Up, tiltSpeed), + PtzCmdType.Down => PelcoP.CameraTilt(addr, PelcoP.Tilt.Down, tiltSpeed), + PtzCmdType.LeftTop => PelcoP.CameraPanTilt(addr, PelcoP.Pan.Left, panSpeed, PelcoP.Tilt.Up, tiltSpeed), + PtzCmdType.LeftDown => PelcoP.CameraPanTilt(addr, PelcoP.Pan.Left, panSpeed, PelcoP.Tilt.Down, tiltSpeed), + PtzCmdType.RightTop => PelcoP.CameraPanTilt(addr, PelcoP.Pan.Right, panSpeed, PelcoP.Tilt.Up, tiltSpeed), + PtzCmdType.RightDown => PelcoP.CameraPanTilt(addr, PelcoP.Pan.Right, panSpeed, PelcoP.Tilt.Down, tiltSpeed), + PtzCmdType.Stop => PelcoP.CameraStop(addr), + PtzCmdType.AutoMove => PelcoP.AutoScan(addr, PelcoP.Action.Start), + //PtzCmdType.AutoStop => PelcoP.AutoScan(addr, PelcoP.Action.Stop), + PtzCmdType.PresetGoto => PelcoP.Preset(addr, PelcoP.PresetAction.Goto, (byte)args[1]), + _ => Array.Empty(), + }; + return cmd; + } + + public static byte[] GetDCameraCmd(PtzCmdType cmdType, object[] args) + { + double speed = (DCamera.SpeedMax + DCamera.SpeedMin) / 2; + byte[] cmd = cmdType switch + { + PtzCmdType.Left => DCamera.Left(speed), + PtzCmdType.Right => DCamera.Right(speed), + PtzCmdType.Top => DCamera.Up(speed), + PtzCmdType.Down => DCamera.Down(speed), + PtzCmdType.LeftTop => DCamera.LeftUp(speed, speed), + PtzCmdType.LeftDown => DCamera.LeftDown(speed, speed), + PtzCmdType.RightTop => DCamera.RightUp(speed, speed), + PtzCmdType.RightDown => DCamera.RightDown(speed, speed), + PtzCmdType.Stop => DCamera.Stop(), + _ => Array.Empty(), + }; + return cmd; + } +} + +public class PtzCameraCmd +{ + public static void PtzMove(ICameraSdk sdk, PtzCmdType cmdType, int[] args) + { + if (sdk == null || !sdk.ConnectSuccess() || args == null) return; + switch (sdk.CameraInfo.Manufactor) + { + case CameraManufactor.HiK: + HikPtzMove(sdk, cmdType, args); + break; + + case CameraManufactor.DaHua: + DaHuaPtzMove(sdk, cmdType, args); + break; + + case CameraManufactor.YuShi: + YuShiPtzMove(sdk, cmdType, args); + break; + + default: + break; + } + } + + public static void HikPtzMove(ICameraSdk sdk, PtzCmdType cmdType, int[] args) + { + int stop = args[0], presetId = args[0]; + int speed = (HiKOriSdk.PtzSpeedMin + HiKOriSdk.PtzSpeedMax) / 2; + switch (cmdType) + { + case PtzCmdType.Left: + sdk.PtzMove(HiKOriSdk.PAN_LEFT, stop, speed); + break; + + case PtzCmdType.Right: + sdk.PtzMove(HiKOriSdk.PAN_RIGHT, stop, speed); + break; + + case PtzCmdType.Top: + sdk.PtzMove(HiKOriSdk.TILT_UP, stop, speed); + break; + + case PtzCmdType.Down: + sdk.PtzMove(HiKOriSdk.TILT_DOWN, stop, speed); + break; + + case PtzCmdType.LeftTop: + sdk.PtzMove(HiKOriSdk.UP_LEFT, stop, speed); + break; + + case PtzCmdType.LeftDown: + sdk.PtzMove(HiKOriSdk.DOWN_LEFT, stop, speed); + break; + + case PtzCmdType.RightTop: + sdk.PtzMove(HiKOriSdk.UP_RIGHT, stop, speed); + break; + + case PtzCmdType.RightDown: + sdk.PtzMove(HiKOriSdk.DOWN_RIGHT, stop, speed); + break; + + case PtzCmdType.AutoMove: + sdk.PtzMove(HiKOriSdk.PAN_AUTO, stop, speed); + break; + + case PtzCmdType.ZoomIn: + sdk.PtzMove(HiKOriSdk.ZOOM_IN, stop, speed); + break; + + case PtzCmdType.ZoomOut: + sdk.PtzMove(HiKOriSdk.ZOOM_OUT, stop, speed); + break; + + case PtzCmdType.FocusNear: + sdk.PtzMove(HiKOriSdk.FOCUS_NEAR, stop, speed); + break; + + case PtzCmdType.FocusFar: + sdk.PtzMove(HiKOriSdk.FOCUS_FAR, stop, speed); + break; + + case PtzCmdType.IrisOpen: + sdk.PtzMove(HiKOriSdk.IRIS_OPEN, stop, speed); + break; + + case PtzCmdType.IrisClose: + sdk.PtzMove(HiKOriSdk.IRIS_CLOSE, stop, speed); + break; + + case PtzCmdType.PresetGoto: + sdk.PtzPreset(HiKOriSdk.GOTO_PRESET, presetId); + break; + + default: + break; + } + } + + private static void DaHuaPtzMove(ICameraSdk sdk, PtzCmdType cmdType, int[] args) + { + int stop = args[0], presetId = args[0]; + int speed = (DaHuaOriSdk.PtzSpeedMin + DaHuaOriSdk.PtzSpeedMax) / 2; + switch (cmdType) + { + case PtzCmdType.Left: + sdk.PtzMove((int)DaHuaOriSdk.EM_EXTPTZ_ControlType.LEFT, stop, speed); + break; + + case PtzCmdType.Right: + sdk.PtzMove((int)DaHuaOriSdk.EM_EXTPTZ_ControlType.RIGHT, stop, speed); + break; + + case PtzCmdType.Top: + sdk.PtzMove((int)DaHuaOriSdk.EM_EXTPTZ_ControlType.UP, stop, speed); + break; + + case PtzCmdType.Down: + sdk.PtzMove((int)DaHuaOriSdk.EM_EXTPTZ_ControlType.DOWN, stop, speed); + break; + + case PtzCmdType.LeftTop: + sdk.PtzMove((int)DaHuaOriSdk.EM_EXTPTZ_ControlType.LEFTTOP, stop, speed); + break; + + case PtzCmdType.LeftDown: + sdk.PtzMove((int)DaHuaOriSdk.EM_EXTPTZ_ControlType.LEFTDOWN, stop, speed); + break; + + case PtzCmdType.RightTop: + sdk.PtzMove((int)DaHuaOriSdk.EM_EXTPTZ_ControlType.RIGHTTOP, stop, speed); + break; + + case PtzCmdType.RightDown: + sdk.PtzMove((int)DaHuaOriSdk.EM_EXTPTZ_ControlType.RIGHTDOWN, stop, speed); + break; + + case PtzCmdType.AutoMove: + sdk.PtzMove((int)DaHuaOriSdk.EM_EXTPTZ_ControlType.STARTPANCRUISE, stop, speed); + break; + + case PtzCmdType.ZoomIn: + sdk.PtzMove((int)DaHuaOriSdk.EM_EXTPTZ_ControlType.ZOOM_ADD, stop, speed); + break; + + case PtzCmdType.ZoomOut: + sdk.PtzMove((int)DaHuaOriSdk.EM_EXTPTZ_ControlType.ZOOM_DEC, stop, speed); + break; + + case PtzCmdType.FocusNear: + sdk.PtzMove((int)DaHuaOriSdk.EM_EXTPTZ_ControlType.FOCUS_ADD, stop, speed); + break; + + case PtzCmdType.FocusFar: + sdk.PtzMove((int)DaHuaOriSdk.EM_EXTPTZ_ControlType.FOCUS_DEC, stop, speed); + break; + + case PtzCmdType.IrisOpen: + sdk.PtzMove((int)DaHuaOriSdk.EM_EXTPTZ_ControlType.APERTURE_ADD, stop, speed); + break; + + case PtzCmdType.IrisClose: + sdk.PtzMove((int)DaHuaOriSdk.EM_EXTPTZ_ControlType.APERTURE_DEC, stop, speed); + break; + + case PtzCmdType.PresetGoto: + sdk.PtzPreset((int)DaHuaOriSdk.EM_EXTPTZ_ControlType.GOTOPRESET, presetId); + break; + + default: + break; + } + } + + public static void YuShiPtzMove(ICameraSdk sdk, PtzCmdType cmdType, int[] args) + { + int stop = args[0], presetId = args[0]; + int speed = (YuShiOriSdk.PtzSpeedMax + YuShiOriSdk.PtzSpeedMin) / 2; + switch (cmdType) + { + case PtzCmdType.Left: + sdk.PtzMove(YuShiOriSdk.PANLEFT, stop, speed); + break; + + case PtzCmdType.Right: + sdk.PtzMove(YuShiOriSdk.PANRIGHT, stop, speed); + break; + + case PtzCmdType.Top: + sdk.PtzMove(YuShiOriSdk.TILTUP, stop, speed); + break; + + case PtzCmdType.Down: + sdk.PtzMove(YuShiOriSdk.TILTDOWN, stop, speed); + break; + + case PtzCmdType.LeftTop: + sdk.PtzMove(YuShiOriSdk.LEFTUP, stop, speed); + break; + + case PtzCmdType.LeftDown: + sdk.PtzMove(YuShiOriSdk.LEFTDOWN, stop, speed); + break; + + case PtzCmdType.RightTop: + sdk.PtzMove(YuShiOriSdk.RIGHTUP, stop, speed); + break; + + case PtzCmdType.RightDown: + sdk.PtzMove(YuShiOriSdk.RIGHTDOWN, stop, speed); + break; + + case PtzCmdType.ZoomIn: + sdk.PtzMove(YuShiOriSdk.ZOOMTELE, stop, speed); + break; + + case PtzCmdType.ZoomOut: + sdk.PtzMove(YuShiOriSdk.ZOOMWIDE, stop, speed); + break; + + case PtzCmdType.FocusNear: + sdk.PtzMove(YuShiOriSdk.FOCUSNEAR, stop, speed); + break; + + case PtzCmdType.FocusFar: + sdk.PtzMove(YuShiOriSdk.FOCUSFAR, stop, speed); + break; + + case PtzCmdType.IrisOpen: + sdk.PtzMove(YuShiOriSdk.IRISOPEN, stop, speed); + break; + + case PtzCmdType.IrisClose: + sdk.PtzMove(YuShiOriSdk.IRISCLOSE, stop, speed); + break; + + case PtzCmdType.PresetGoto: + sdk.PtzPreset(YuShiOriSdk.PRESET_GOTO, presetId); + break; + + default: + break; + } + } +} + +public enum PtzControlType +{ + PelcoD, + PelcoP, + DCamera, + CameraSdk, + None, +} + +public enum PtzCmdType +{ + Left, + Right, + Top, + Down, + LeftTop, + LeftDown, + RightTop, + RightDown, + Stop, + AutoMove, + ZoomIn, + ZoomOut, + FocusNear, + FocusFar, + IrisOpen, + IrisClose, + PresetSet, + PresetClear, + PresetGoto, + None +} \ No newline at end of file diff --git a/JiLinApp.Docking/Ptz/PtzConfig.cs b/JiLinApp.Docking/Ptz/PtzConfig.cs new file mode 100644 index 0000000..3f8c53f --- /dev/null +++ b/JiLinApp.Docking/Ptz/PtzConfig.cs @@ -0,0 +1,38 @@ +namespace JiLinApp.Docking.Ptz; + +public class PtzControlTypeConfig +{ + public string Type { get; set; } + + public string Name { get; set; } +} + +public class PtzControlTypeConfigHelper +{ + private static Dictionary PctConfigDict { get; set; } + + public static void Init(List ptzCtrlTypes) + { + if (PctConfigDict != null) return; + Dictionary dict = new(); + foreach (var cfg in ptzCtrlTypes) + { + dict.Add(cfg.Name, cfg); + } + PctConfigDict ??= dict; + } + + public static PtzControlType GetControlType(string ctrlName) + { + PctConfigDict.TryGetValue(ctrlName, out var cfg); + string ctrlType = cfg != null ? cfg.Type : ""; + return ctrlType switch + { + "PelcoD" => PtzControlType.PelcoD, + "PelcoP" => PtzControlType.PelcoP, + "DCamera" => PtzControlType.DCamera, + "CameraSdk" => PtzControlType.CameraSdk, + _ => PtzControlType.None + }; + } +} \ No newline at end of file diff --git a/JiLinApp.Docking/VibrateAlarm/Entity/ClientMessage.cs b/JiLinApp.Docking/VibrateAlarm/Entity/ClientMessage.cs new file mode 100644 index 0000000..67dd1d9 --- /dev/null +++ b/JiLinApp.Docking/VibrateAlarm/Entity/ClientMessage.cs @@ -0,0 +1,130 @@ +using EC.Util.Common; +using System.Collections.Concurrent; +using System.Net.Sockets; + +namespace JiLinApp.Docking.VibrateAlarm; + +public class ClientMessage +{ + #region Fields + + public TcpClient Client { get; set; } + + public TcpAlarmHost Host { get; set; } + + public int OnlineState { get; set; }//设备在线状态 + + public bool IsOnline + { + get + { + return Host != null && Host.Id >= 0 && OnlineState == 1; + } + } + + public ConcurrentDictionary SensorDict { get; } = new(); + + public int SensorTotal + { + get + { + return SensorDict.Count; + } + } + + public bool SensorsEmpty + { + get + { + return SensorDict == null || SensorDict.IsEmpty; + } + } + + public List DataList { get; } = new(); + + public string ClientAddr + { + get + { + return Client.ClientAddr(); + } + } + + public string ClientIp + { + get + { + if (Host != null) return Host.Ip; + return Client.ClientIp(); + } + } + + public string ClientPort + { + get + { + if (Host != null) return Host.Port; + return Client.ClientPort(); + } + } + + public string LocalAddr + { + get + { + return Client.LocalAddr(); + } + } + + public string LocalIp + { + get + { + return Client.LocalIp(); + } + } + + public string LocalPort + { + get + { + return Client.LocalPort(); + } + } + + #endregion Fields + + public ClientMessage() + { + } + + public void AddData(byte[] bytes) + { + DataList.AddRange(bytes); + } + + public List GetMessageList() + { + List msglist = new(); + while (DataList.Count >= 19) + { + if (DataList[0] == 0xAA && DataList[1] == 0xAA) + { + int num = DataList[17]; + if (DataList.Count < 19 + num) break; + byte[] bytes = new byte[num + 19]; + for (int i = 0; i < bytes.Length; i++) + { + bytes[i] = DataList[i]; + } + msglist.Add(bytes); + DataList.RemoveRange(0, num + 19); + } + else + { + DataList.RemoveAt(0); + } + } + return msglist; + } +} \ No newline at end of file diff --git a/JiLinApp.Docking/VibrateAlarm/Entity/DataMessage.cs b/JiLinApp.Docking/VibrateAlarm/Entity/DataMessage.cs new file mode 100644 index 0000000..4b3bb87 --- /dev/null +++ b/JiLinApp.Docking/VibrateAlarm/Entity/DataMessage.cs @@ -0,0 +1,80 @@ +using System.Text; + +namespace JiLinApp.Docking.VibrateAlarm; + +public class DataMessage +{ + private byte HeadA = 0xAA; + private byte HeadB = 0xAA; //(1)帧头 + public int DeviceId { get; set; } //(2)设备ID + public string SendIp { get; set; } //(3)原地址 + public int SendPort { get; set; }//(3)原地址 + public string ReceiveIp { get; set; }//(4)目标地址 + public int ReceivePort { get; set; }//(4)目标地址 + public byte FrameNum { get; set; }//(5)帧编号 + public byte FunctionNum { get; set; }//(6)功能码 + public byte DataLen { get; set; }//(7)传输数据长度 + public byte[] Data { get; set; }//(8)传输数据 + public byte CRC { get; set; }//(9)CRC8 + + public void Decode(byte[] bytes) + { + DeviceId = bytes[2]; + SendIp = bytes[3].ToString() + "." + bytes[4].ToString() + "." + bytes[5].ToString() + "." + bytes[6].ToString(); + SendPort = bytes[8] * 256 + bytes[7]; + ReceiveIp = bytes[9].ToString() + "." + bytes[10].ToString() + "." + bytes[11].ToString() + "." + bytes[12].ToString(); + ReceivePort = bytes[14] * 256 + bytes[13]; + FrameNum = bytes[15]; + FunctionNum = bytes[16]; + DataLen = bytes[17]; + Data = new byte[DataLen]; + for (int i = 0; i < DataLen; i++) + { + Data[i] = bytes[18 + i]; + } + CRC = bytes[18 + DataLen]; + } + + public byte[] Encode() + { + byte[] bytes = new byte[19 + DataLen]; + bytes[0] = bytes[1] = 0xAA; + bytes[2] = (byte)DeviceId; + + string[] strs = SendIp.Split('.'); + bytes[3] = byte.Parse(strs[0]); + bytes[4] = byte.Parse(strs[1]); + bytes[5] = byte.Parse(strs[2]); + bytes[6] = byte.Parse(strs[3]); + bytes[7] = (byte)(SendPort % 256); + bytes[8] = (byte)(SendPort / 256); + + string[] strs2 = ReceiveIp.Split('.'); + bytes[9] = byte.Parse(strs2[0]); + bytes[10] = byte.Parse(strs2[1]); + bytes[11] = byte.Parse(strs2[2]); + bytes[12] = byte.Parse(strs2[3]); + bytes[13] = (byte)(ReceivePort % 256); + bytes[14] = (byte)(ReceivePort / 256); + bytes[15] = FrameNum; + bytes[16] = FunctionNum; + bytes[17] = DataLen; + for (int i = 0; i < DataLen; i++) + { + bytes[18 + i] = Data[i]; + } + bytes[bytes.Length - 1] = CRC8.CRC(bytes, 0, bytes.Length - 1); + return bytes; + } + + public static string ToHexString(byte[] bytes) + { + if (bytes == null) return string.Empty; + StringBuilder builder = new(); + for (int i = 0; i < bytes.Length; i++) + { + builder.Append(bytes[i].ToString("X2") + " "); + } + return builder.ToString(); + } +} \ No newline at end of file diff --git a/JiLinApp.Docking/VibrateAlarm/Entity/DataRequest.cs b/JiLinApp.Docking/VibrateAlarm/Entity/DataRequest.cs new file mode 100644 index 0000000..3853202 --- /dev/null +++ b/JiLinApp.Docking/VibrateAlarm/Entity/DataRequest.cs @@ -0,0 +1,12 @@ +namespace JiLinApp.Docking.VibrateAlarm; + +public class DataRequest +{ + public DataMessage Request { get; set; } + + public DataMessage Responce { get; set; } + + public DataRequest() + { + } +} \ No newline at end of file diff --git a/JiLinApp.Docking/VibrateAlarm/Entity/SensorState.cs b/JiLinApp.Docking/VibrateAlarm/Entity/SensorState.cs new file mode 100644 index 0000000..7ea4aa5 --- /dev/null +++ b/JiLinApp.Docking/VibrateAlarm/Entity/SensorState.cs @@ -0,0 +1,68 @@ +namespace JiLinApp.Docking.VibrateAlarm; + +public class SensorState +{ + public int DeviceId { get; set; } + + public int Addr { get; set; } + + public int OfflineState { get; set; } + + public int OnlineState + { + get + { + if (OfflineState == 0) return 1; + else if (OfflineState == 1) return 0; + else return 0; + } + } + + public string OnlineStateStr + { + get + { + if (OfflineState == 0) + { + return "在线"; + } + else + { + return "离线"; + } + } + } + + public int AlarmState { get; set; } + + public string AlarmStateStr + { + get + { + if (AlarmState == 0) + { + return "消警"; + } + else + { + return "报警"; + } + } + } + + public SensorState(int deviceId, int sensorAddr, int state) + { + DeviceId = deviceId; + Addr = sensorAddr; + OfflineState = state % 2; + AlarmState = state / 2 % 2; + } + + public SensorState(int deviceId, int sensorAddr, int offlineState, int alarmState) + { + DeviceId = deviceId; + Addr = sensorAddr; + OfflineState = offlineState; + AlarmState = alarmState; + } +} \ No newline at end of file diff --git a/JiLinApp.Docking/VibrateAlarm/Entity/TcpAlarmHost.cs b/JiLinApp.Docking/VibrateAlarm/Entity/TcpAlarmHost.cs new file mode 100644 index 0000000..9536cec --- /dev/null +++ b/JiLinApp.Docking/VibrateAlarm/Entity/TcpAlarmHost.cs @@ -0,0 +1,22 @@ +namespace JiLinApp.Docking.VibrateAlarm; + +public class TcpAlarmHost +{ + public int Id { get; set; } + + public string Name + { + get + { + return "设备" + Id; + } + } + + public string Ip { get; set; } + + public string Port { get; set; } + + public string Lat { get; set; } + + public string Lng { get; set; } +} \ No newline at end of file diff --git a/JiLinApp.Docking/VibrateAlarm/Entity/TcpAlarmHostMessage.cs b/JiLinApp.Docking/VibrateAlarm/Entity/TcpAlarmHostMessage.cs new file mode 100644 index 0000000..ccfe099 --- /dev/null +++ b/JiLinApp.Docking/VibrateAlarm/Entity/TcpAlarmHostMessage.cs @@ -0,0 +1,19 @@ +using Newtonsoft.Json.Linq; + +namespace JiLinApp.Docking.VibrateAlarm; + +public class TcpAlarmHostMessage +{ + public string AlarmTime { get; set; }//报警时间 + public string CID { get; set; }//CID代码 + + /*设备信息*/ + public int DeviceID { get; set; }//设备唯一ID + public string SensorAddr { get; set; }//防区号 + public string Mode { get; set; } + public string Sensitivity { get; set; } + + /*联动信息*/ + public bool IsLinked { get; set; }//是否有联动信息 + public JArray Linklist { get; set; } +} \ No newline at end of file diff --git a/JiLinApp.Docking/VibrateAlarm/Entity/TcpManagerConfig.cs b/JiLinApp.Docking/VibrateAlarm/Entity/TcpManagerConfig.cs new file mode 100644 index 0000000..6d3ce78 --- /dev/null +++ b/JiLinApp.Docking/VibrateAlarm/Entity/TcpManagerConfig.cs @@ -0,0 +1,10 @@ +namespace JiLinApp.Docking.VibrateAlarm; + +public class TcpManagerConfig +{ + public string ServerIp { get; set; } + + public int ServerPort { get; set; } + + public int DeviceHeartKeep { get; set; } +} \ No newline at end of file diff --git a/JiLinApp.Docking/VibrateAlarm/Entity/TcpSensorTable.cs b/JiLinApp.Docking/VibrateAlarm/Entity/TcpSensorTable.cs new file mode 100644 index 0000000..29aa479 --- /dev/null +++ b/JiLinApp.Docking/VibrateAlarm/Entity/TcpSensorTable.cs @@ -0,0 +1,20 @@ +namespace JiLinApp.Docking.VibrateAlarm; + +public class TcpSensorTable +{ + public int Id { get; set; } + + public int DeviceId { get; set; } + + public string Name { get; set; } + + public string Lat { get; set; } + + public string Lng { get; set; } + + public string Channel { get; set; } + + public string Mode { get; set; } + + public string Sensitivity { get; set; } +} \ No newline at end of file diff --git a/JiLinApp.Docking/VibrateAlarm/Service/AsyncTcpServer.cs b/JiLinApp.Docking/VibrateAlarm/Service/AsyncTcpServer.cs new file mode 100644 index 0000000..540808a --- /dev/null +++ b/JiLinApp.Docking/VibrateAlarm/Service/AsyncTcpServer.cs @@ -0,0 +1,436 @@ +using EC.Util.Common; +using System.Collections.Concurrent; +using System.Net; +using System.Net.Sockets; +using System.Text; + +namespace JiLinApp.Docking.VibrateAlarm; + +public class AsyncTcpServer : IDisposable +{ + #region Fields + + private TcpListener Listener { get; set; } + + private ConcurrentDictionary Clients { get; } + + #region Properties + + /// + /// 监听的IP地址 + /// + public IPAddress Address { get; private set; } + + /// + /// 监听的端口 + /// + public int Port { get; private set; } + + /// + /// 通信使用的编码 + /// + public Encoding Encoding { get; set; } + + public bool Disposed { get; private set; } + + #endregion Properties + + #endregion Fields + + #region Ctors + + /// + /// 异步TCP服务器 + /// + /// 监听的端口 + public AsyncTcpServer(int port) : this(IPAddress.Any, port) + { + } + + /// + /// 异步TCP服务器 + /// + /// 监听的终结点 + public AsyncTcpServer(IPEndPoint ipep) : this(ipep.Address, ipep.Port) + { + } + + /// + /// 异步TCP服务器 + /// + /// 监听的IP地址 + /// 监听的端口 + public AsyncTcpServer(IPAddress address, int port) + { + Address = address; + Port = port; + Encoding = Encoding.Default; + Listener = new(address, port); + Clients = new(); + Listener.AllowNatTraversal(true); + } + + ~AsyncTcpServer() + { + Stop(); + } + + #endregion Ctors + + #region Server + + /// + /// 启动服务器 + /// + /// 异步TCP服务器 + public void Start() + { + Start(30); + } + + /// + /// 启动服务器 + /// + /// 服务器所允许的挂起连接序列的最大长度 + /// 异步TCP服务器 + public void Start(int backlog) + { + if (IsRunning()) return; + Listener.Start(backlog); + AcceptTcpClient(Listener); + } + + /// + /// 停止服务器 + /// + /// 异步TCP服务器 + public void Stop() + { + if (!IsRunning()) return; + try + { + Listener.Stop(); + } + finally + { + foreach (var client in Clients.Values) + { + client.Client.Client.Disconnect(false); + } + Clients.Clear(); + } + } + + public bool IsRunning() + { + return Listener != null && Listener.Server.IsBound; + } + + public ICollection GetAllClient() + { + return Clients.Values; + } + + #endregion Server + + #region Events + + /// + /// 与客户端的连接已建立事件 + /// + public event EventHandler? ClientConnected; + + /// + /// 与客户端的连接已断开事件 + /// + public event EventHandler? ClientDisconnected; + + /// + /// 接收到数据报文事件 + /// + public event EventHandler>? DatagramReceived; + + private void RaiseClientConnected(string clientAddr, TcpClientState clientState) + { + if (string.IsNullOrEmpty(clientAddr) || clientAddr.Equals(":")) return; + Clients.AddOrUpdate(clientAddr, clientState, (n, o) => { return clientState; }); + ClientConnected?.Invoke(this, new TcpClientConnectedEventArgs(clientState.Client)); + } + + private void RaiseClientDisconnected(string clientAddr, TcpClient client) + { + if (string.IsNullOrEmpty(clientAddr) || clientAddr.Equals(":")) return; + client.Client.Disconnect(false); + if (Clients.TryRemove(clientAddr, out _)) + { + ClientDisconnected?.Invoke(this, new TcpClientDisconnectedEventArgs(client)); + } + } + + private void RaiseDatagramReceived(TcpClientState sender, byte[] datagram) + { + DatagramReceived?.Invoke(this, new TcpDatagramReceivedEventArgs(sender, datagram)); + } + + #endregion Events + + #region Receive + + private void AcceptTcpClient(TcpListener listener) + { + listener.BeginAcceptTcpClient(HandleTcpClientAccepted, listener); + } + + private void HandleTcpClientAccepted(IAsyncResult ar) + { + if (!IsRunning()) return; + if (ar.AsyncState is not TcpListener listener) return; + + TcpClient? client; + try + { + client = listener.EndAcceptTcpClient(ar); + if (client == null || !client.Connected) + { + client?.Client.Disconnect(false); + return; + } + } + catch (Exception) + { + return; + } + + // add client connection to cache + string clientAddr = client.ClientAddr(); + byte[] buffer = new byte[client.ReceiveBufferSize]; + TcpClientState clientState = new(client, buffer); + RaiseClientConnected(clientAddr, clientState); + + // begin to read data + ReadBuffer(clientState); + + // keep listening to accept next connection + AcceptTcpClient(listener); + } + + private void ReadBuffer(TcpClientState clientState) + { + try + { + NetworkStream stream = clientState.GetStream; + if (clientState.IsRead) return; + lock (clientState.IsReadLock) + { + if (clientState.IsRead) return; + clientState.IsRead = true; + stream.BeginRead(clientState.Buffer, 0, clientState.Buffer.Length, HandleDatagramReceived, clientState); + } + } + catch (IOException e) + { + LogUnit.Error(e); + return; + } + catch (Exception e) + { + LogUnit.Error(e); + string clientAddr = clientState.Client.ClientAddr(); + RaiseClientDisconnected(clientAddr, clientState.Client); + } + } + + private void HandleDatagramReceived(IAsyncResult ar) + { + if (!IsRunning()) return; + if (ar.AsyncState is not TcpClientState clientState) return; + + int readNum; + string clientAddr = clientState.Client.ClientAddr(); + try + { + NetworkStream networkStream = clientState.GetStream; + // if the remote host has shutdown its connection, read will immediately return with zero bytes. + readNum = networkStream.EndRead(ar); + if (readNum == 0) + { + RaiseClientDisconnected(clientAddr, clientState.Client); + return; + } + } + catch (Exception) + { + RaiseClientDisconnected(clientAddr, clientState.Client); + return; + } + + // received byte and trigger event notification + byte[] receivedBytes = new byte[readNum]; + Buffer.BlockCopy(clientState.Buffer, 0, receivedBytes, 0, readNum); + RaiseDatagramReceived(clientState, receivedBytes); + + lock (clientState.IsReadLock) + { + clientState.IsRead = false; + } + // continue listening for tcp datagram packets + ReadBuffer(clientState); + } + + #endregion Receive + + #region Send + + /// + /// 发送报文至指定的客户端 + /// + /// 客户端 + /// 报文 + public void Send(TcpClient client, byte[] datagram) + { + if (!IsRunning()) return; + if (client == null || datagram == null) return; + try + { + NetworkStream stream = client.GetStream(); + stream.Write(datagram, 0, datagram.Length); + } + catch (Exception) + { + string clientAddr = client.ClientAddr(); + RaiseClientDisconnected(clientAddr, client); + } + } + + /// + /// 发送报文至指定的客户端 + /// + /// 客户端 + /// 报文 + public void Send(TcpClient client, string datagram) + { + Send(client, Encoding.GetBytes(datagram)); + } + + /// + /// 发送报文至所有客户端 + /// + /// 报文 + public void SendToAll(byte[] datagram) + { + if (!IsRunning()) return; + foreach (var client in Clients.Values) + { + Send(client.Client, datagram); + } + } + + /// + /// 发送报文至所有客户端 + /// + /// 报文 + public void SendToAll(string datagram) + { + if (!IsRunning()) return; + SendToAll(Encoding.GetBytes(datagram)); + } + + /// + /// 发送报文至指定的客户端 + /// + /// 客户端 + /// 报文 + public bool SendAsync(TcpClient client, byte[] datagram) + { + if (!IsRunning()) return false; + if (client == null || datagram == null) return false; + try + { + NetworkStream stream = client.GetStream(); + IAsyncResult result = stream.BeginWrite(datagram, 0, datagram.Length, HandleDatagramWritten, client); + return result.IsCompleted; + } + catch (Exception) + { + string clientAddr = client.ClientAddr(); + RaiseClientDisconnected(clientAddr, client); + } + return false; + } + + /// + /// 发送报文至指定的客户端 + /// + /// 客户端 + /// 报文 + public bool SendAsync(TcpClient client, string datagram) + { + return SendAsync(client, Encoding.GetBytes(datagram)); + } + + /// + /// 发送报文至所有客户端 + /// + /// 报文 + public void SendToAllAsync(byte[] datagram) + { + if (!IsRunning()) return; + foreach (var client in Clients.Values) + { + SendAsync(client.Client, datagram); + } + } + + /// + /// 发送报文至所有客户端 + /// + /// 报文 + public void SendToAllAsync(string datagram) + { + if (!IsRunning()) return; + SendToAllAsync(Encoding.GetBytes(datagram)); + } + + private void HandleDatagramWritten(IAsyncResult ar) + { + if (ar.AsyncState is not TcpClient client) return; + try + { + client.GetStream().EndWrite(ar); + } + catch (Exception) + { + string clientAddr = client.ClientAddr(); + RaiseClientDisconnected(clientAddr, client); + } + } + + #endregion Send + + #region IDisposable Members + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// Releases unmanaged and - optionally - managed resources + /// + /// true to release both managed and unmanaged resources; + /// false to release only unmanaged resources. + protected virtual void Dispose(bool disposing) + { + if (!Disposed) + { + if (disposing) Stop(); + Disposed = true; + } + } + + #endregion IDisposable Members +} \ No newline at end of file diff --git a/JiLinApp.Docking/VibrateAlarm/Service/CRC8.cs b/JiLinApp.Docking/VibrateAlarm/Service/CRC8.cs new file mode 100644 index 0000000..55fb289 --- /dev/null +++ b/JiLinApp.Docking/VibrateAlarm/Service/CRC8.cs @@ -0,0 +1,49 @@ +namespace JiLinApp.Docking.VibrateAlarm; + +public class CRC8 +{ + /// + /// CRC8位校验表 + /// + private static byte[] CRC8Table = new byte[] { + 0,94,188,226,97,63,221,131,194,156,126,32,163,253,31,65, + 157,195,33,127,252,162,64,30, 95,1,227,189,62,96,130,220, + 35,125,159,193,66,28,254,160,225,191,93,3,128,222,60,98, + 190,224,2,92,223,129,99,61,124,34,192,158,29,67,161,255, + 70,24,250,164,39,121,155,197,132,218,56,102,229,187,89,7, + 219,133,103,57,186,228,6,88,25,71,165,251,120,38,196,154, + 101,59,217,135,4,90,184,230,167,249,27,69,198,152,122,36, + 248,166,68,26,153,199,37,123,58,100,134,216,91,5,231,185, + 140,210,48,110,237,179,81,15,78,16,242,172,47,113,147,205, + 17,79,173,243,112,46,204,146,211,141,111,49,178,236,14,80, + 175,241,19,77,206,144,114,44,109,51,209,143,12,82,176,238, + 50,108,142,208,83,13,239,177,240,174,76,18,145,207,45,115, + 202,148,118,40,171,245,23,73,8,86,180,234,105,55,213,139, + 87,9,235,181,54,104,138,212,149,203, 41,119,244,170,72,22, + 233,183,85,11,136,214,52,106,43,117,151,201,74,20,246,168, + 116,42,200,150,21,75,169,247,182,232,10,84,215,137,107,53 }; + + public static byte CRC(byte[] buffer) + { + return CRC(buffer, 0, buffer.Length); + } + + public static byte CRC(byte[] buffer, int off, int len) + { + byte crc = 0; + if (buffer == null) + { + throw new ArgumentNullException("buffer"); + } + if (off < 0 || len < 0 || off + len > buffer.Length) + { + throw new ArgumentOutOfRangeException(); + } + + for (int i = off; i < len; i++) + { + crc = CRC8Table[crc ^ buffer[i]]; + } + return crc; + } +} \ No newline at end of file diff --git a/JiLinApp.Docking/VibrateAlarm/Service/TcpClientConnectedEventArgs.cs b/JiLinApp.Docking/VibrateAlarm/Service/TcpClientConnectedEventArgs.cs new file mode 100644 index 0000000..ead759a --- /dev/null +++ b/JiLinApp.Docking/VibrateAlarm/Service/TcpClientConnectedEventArgs.cs @@ -0,0 +1,23 @@ +using System.Net.Sockets; + +namespace JiLinApp.Docking.VibrateAlarm; + +/// +/// 与客户端的连接已建立事件参数 +/// +public class TcpClientConnectedEventArgs : EventArgs +{ + /// + /// 客户端 + /// + public TcpClient TcpClient { get; private set; } + + /// + /// 与客户端的连接已建立事件参数 + /// + /// 客户端 + public TcpClientConnectedEventArgs(TcpClient tcpClient) + { + TcpClient = tcpClient ?? throw new ArgumentNullException(nameof(tcpClient)); + } +} \ No newline at end of file diff --git a/JiLinApp.Docking/VibrateAlarm/Service/TcpClientDisconnectedEventArgs.cs b/JiLinApp.Docking/VibrateAlarm/Service/TcpClientDisconnectedEventArgs.cs new file mode 100644 index 0000000..3f93d4d --- /dev/null +++ b/JiLinApp.Docking/VibrateAlarm/Service/TcpClientDisconnectedEventArgs.cs @@ -0,0 +1,23 @@ +using System.Net.Sockets; + +namespace JiLinApp.Docking.VibrateAlarm; + +/// +/// 与客户端的连接已断开事件参数 +/// +public class TcpClientDisconnectedEventArgs : EventArgs +{ + /// + /// 客户端 + /// + public TcpClient TcpClient { get; private set; } + + /// + /// 与客户端的连接已断开事件参数 + /// + /// 客户端 + public TcpClientDisconnectedEventArgs(TcpClient tcpClient) + { + TcpClient = tcpClient ?? throw new ArgumentNullException(nameof(tcpClient)); + } +} \ No newline at end of file diff --git a/JiLinApp.Docking/VibrateAlarm/Service/TcpClientState.cs b/JiLinApp.Docking/VibrateAlarm/Service/TcpClientState.cs new file mode 100644 index 0000000..f96d572 --- /dev/null +++ b/JiLinApp.Docking/VibrateAlarm/Service/TcpClientState.cs @@ -0,0 +1,53 @@ +using System.Net.Sockets; + +namespace JiLinApp.Docking.VibrateAlarm; + +public class TcpClientState +{ + /// + /// Constructor for a new Client + /// + /// The TCP client + /// The byte array buffer + public TcpClientState(TcpClient client, byte[] buffer) + { + if (client == null) throw new ArgumentNullException("tcpClient"); + if (buffer == null) throw new ArgumentNullException("buffer"); + Client = client; + Buffer = buffer; + Temp = string.Empty; + } + + /// + /// Gets the TCP Client + /// + public TcpClient Client { get; private set; } + + /// + /// Gets the Buffer. + /// + public byte[] Buffer { get; private set; } + + /// + /// TCP接收到但未处理的数据 + /// + public string Temp { get; set; } + + /// + /// Gets the network stream + /// + public NetworkStream GetStream + { + get + { + NetworkStream stream = Client.GetStream(); + stream.ReadTimeout = 5000; + stream.WriteTimeout = 5000; + return stream; + } + } + + public bool IsRead { get; set; } + + public object IsReadLock { get; } = new(); +} \ No newline at end of file diff --git a/JiLinApp.Docking/VibrateAlarm/Service/TcpDatagramReceivedEventArgs.cs b/JiLinApp.Docking/VibrateAlarm/Service/TcpDatagramReceivedEventArgs.cs new file mode 100644 index 0000000..c2b55a8 --- /dev/null +++ b/JiLinApp.Docking/VibrateAlarm/Service/TcpDatagramReceivedEventArgs.cs @@ -0,0 +1,29 @@ +namespace JiLinApp.Docking.VibrateAlarm; + +/// +/// 接收到数据报文事件参数 +/// +/// 报文类型 +public class TcpDatagramReceivedEventArgs : EventArgs +{ + /// + /// 客户端 + /// + public TcpClientState ClientState { get; private set; } + + /// + /// 报文 + /// + public T Datagram { get; private set; } + + /// + /// 接收到数据报文事件参数 + /// + /// 客户端 + /// 报文 + public TcpDatagramReceivedEventArgs(TcpClientState tcpClient, T datagram) + { + ClientState = tcpClient ?? throw new ArgumentNullException(nameof(tcpClient)); + Datagram = datagram; + } +} \ No newline at end of file diff --git a/JiLinApp.Docking/VibrateAlarm/Service/TcpManager.cs b/JiLinApp.Docking/VibrateAlarm/Service/TcpManager.cs new file mode 100644 index 0000000..0d1209c --- /dev/null +++ b/JiLinApp.Docking/VibrateAlarm/Service/TcpManager.cs @@ -0,0 +1,765 @@ +using EC.Util.Common; +using System.Collections.Concurrent; +using System.Net; +using System.Timers; +using Timer = System.Timers.Timer; + +namespace JiLinApp.Docking.VibrateAlarm; + +public class TcpManager +{ + #region Fields + + private AsyncTcpServer Server { get; set; } + + private TcpManagerConfig Config { get; set; } + + private ConcurrentDictionary DeviceDict { get; } = new(); + + private Timer HeartTimer { get; } = new(); + + #region Event + + public delegate void VibrateTcpDeviceStateEvent(ClientMessage device); + + public delegate void VibrateTcpSensorStateEvent(SensorState sensor); + + public delegate void VibrateTcpAlarmEvent(TcpAlarmHostMessage msg); + + public event VibrateTcpDeviceStateEvent? OnVibrateTcpDeviceState; + + public event VibrateTcpSensorStateEvent? OnVibrateTcpSensorState; + + public event VibrateTcpAlarmEvent? OnVibrateTcpAlarm; + + #endregion Event + + #endregion Fields + + public TcpManager() + { + } + + #region Server + + public void Start(TcpManagerConfig config) + { + if (IsRunning()) return; + Server = new(IPAddress.Any, config.ServerPort); + Server.ClientConnected += Server_ClientConnected; + Server.ClientDisconnected += Server_ClientDisconnected; + Server.DatagramReceived += Server_DatagramReceived; + Server.Start(); + + HeartTimer.Close(); + HeartTimer.Interval = 1000 * config.DeviceHeartKeep; + HeartTimer.Elapsed += HeartTimer_Elapsed; + HeartTimer.Start(); + + Config = config; + } + + public void Stop() + { + if (!IsRunning()) return; + try + { + Server.Stop(); + } + finally + { + Server.ClientConnected -= Server_ClientConnected; + Server.ClientDisconnected -= Server_ClientDisconnected; + Server.DatagramReceived -= Server_DatagramReceived; + Server = null; + DeviceDict.Clear(); + HeartTimer.Stop(); + HeartTimer.Elapsed -= HeartTimer_Elapsed; + } + } + + public bool IsRunning() + { + return Server != null && Server.IsRunning(); + } + + #endregion Server + + #region Events + + private void HeartTimer_Elapsed(object? sender, ElapsedEventArgs e) + { + foreach (var clientMsg in DeviceDict.Values) + { + if (!clientMsg.IsOnline) continue; + SendHostHeart_01(clientMsg); + } + } + + private void Server_ClientConnected(object? sender, TcpClientConnectedEventArgs e) + { + string clientAddr = e.TcpClient.ClientAddr(); + if (clientAddr == ":") return; + string clientIp = clientAddr.Split(':')[0]; + string clientPort = clientAddr.Split(':')[1]; + Console.WriteLine("主机上线:{0}", clientIp); + if (!TryGetDevice(clientIp, out ClientMessage clientMsg)) + { + clientMsg = new() + { + Client = e.TcpClient, + Host = new() { Id = -1, Ip = clientIp, Port = clientPort } + }; + AddDeivce(clientIp, clientMsg); + } + else + { + ProcessDeviceStateEvent(ref clientMsg, 1); + } + } + + private void Server_ClientDisconnected(object? sender, TcpClientDisconnectedEventArgs e) + { + string clientAddr = e.TcpClient.ClientAddr(); + if (clientAddr == ":") return; + string clientIp = clientAddr.Split(':')[0]; + string clientPort = clientAddr.Split(':')[1]; + Console.WriteLine("主机下线:{0}", clientIp); + if (TryGetDevice(clientIp, out ClientMessage clientMsg)) + { + ProcessDeviceStateEvent(ref clientMsg, 0); + } + } + + private void Server_DatagramReceived(object? sender, TcpDatagramReceivedEventArgs e) + { + string clientAddr = e.ClientState.Client.ClientAddr(); + if (clientAddr == ":") return; + string clientIp = clientAddr.Split(':')[0]; + string clientPort = clientAddr.Split(':')[1]; + if (TryGetDevice(clientIp, out ClientMessage clientMsg)) + { + if (clientMsg.Client == null || !clientMsg.Client.Connected) clientMsg.Client = e.ClientState.Client; + clientMsg.Host ??= new() { Id = -1, Ip = clientIp, Port = clientPort }; + clientMsg.AddData(e.Datagram); + ProcessDeviceStateEvent(ref clientMsg, 1); + AnalysisClientMessage(ref clientMsg); + } + } + + #endregion Events + + #region Analysis + + private void AnalysisClientMessage(ref ClientMessage clientMsg) + { + List msgList = clientMsg.GetMessageList(); + foreach (byte[] msg in msgList) + { + bool vaild = msg.Length >= 19 && msg[0] == 0xAA && msg[1] == 0xAA; + Console.WriteLine("Recv from {0}:{1} => {2}, vaild:{3}", clientMsg.ClientIp, clientMsg.ClientPort, DataMessage.ToHexString(msg), vaild); + if (!vaild) continue; + DataMessage mm = new(); + mm.Decode(msg); + if (clientMsg.Host.Id != mm.DeviceId) clientMsg.Host.Id = mm.DeviceId; + switch (mm.FunctionNum) + { + case 0x00: + Console.WriteLine("主机登录:{0}", clientMsg.ClientAddr); + ResponseHostLogin_10(clientMsg, mm); + RequestHostAutoUploadState_24(clientMsg); + RequestSensorList_07(clientMsg); + break; + + case 0x01: + Console.WriteLine("主机心跳:{0}", clientMsg.ClientAddr); + break; + + case 0x12: + Console.WriteLine("传感器地址设置响应:{0}", clientMsg.ClientAddr); + + SetDataResponse(mm, 0x02); + break; + + case 0x13: + Console.WriteLine("传感器模式设置响应:{0}", clientMsg.ClientAddr); + + SetDataResponse(mm, 0x03); + break; + + case 0x14: + Console.WriteLine("传感器轮询状态响应:{0}", clientMsg.ClientAddr); + int sensorAddr = mm.Data[0] + mm.Data[1] * 256; + int state = mm.Data[2] + mm.Data[3] * 256; + ProcessSensorStateEvent(ref clientMsg, mm, sensorAddr, state); + + SetDataResponse(mm, 0x04); + break; + + case 0x15: + Console.WriteLine("传感器复位响应:{0}", clientMsg.ClientAddr); + + SetDataResponse(mm, 0x05); + break; + + case 0x17: + Console.WriteLine("主机返回传感器列表:{0}", clientMsg.ClientAddr); + for (int j = 2; j < mm.Data.Length; j++) + { + sensorAddr = Convert.ToByte((mm.Data[j] + mm.Data[++j] * 256)); + if (clientMsg.SensorDict.ContainsKey(sensorAddr)) continue; + SensorState sensor = new(mm.DeviceId, sensorAddr, 0, 0); + clientMsg.SensorDict[sensorAddr] = sensor; + } + RequestSensorsState_04(clientMsg); + + SetDataResponse(mm, 0x07); + break; + + case 0x18: + Console.WriteLine("传感器主动状态响应:{0}", clientMsg.ClientAddr); + sensorAddr = mm.Data[0] + mm.Data[1] * 256; + state = mm.Data[2] + mm.Data[3] * 256; + ProcessSensorStateEvent(ref clientMsg, mm, sensorAddr, state); + ResponseHostUploadState_08(clientMsg, mm); + break; + + case 0x19: + Console.WriteLine("传感器关闭响应:{0}", clientMsg.ClientAddr); + + SetDataResponse(mm, 0x09); + break; + + case 0x30: + Console.WriteLine("传感器启动响应:{0}", clientMsg.ClientAddr); + + SetDataResponse(mm, 0x20); + break; + + case 0x31: + Console.WriteLine("传感器全部启动响应:{0}", clientMsg.ClientAddr); + + SetDataResponse(mm, 0x21); + break; + + case 0x34: + Console.WriteLine("主机启动自动上传响应:{0}", clientMsg.ClientAddr); + + SetDataResponse(mm, 0x24); + break; + + case 0x35: + Console.WriteLine("主机关闭自动上传响应:{0}", clientMsg.ClientAddr); + + SetDataResponse(mm, 0x25); + break; + } + } + } + + private void ProcessDeviceStateEvent(ref ClientMessage device, int online) + { + if (device.OnlineState != online) + { + device.OnlineState = online; + ReportDeviceState(device); + } + } + + private void ProcessSensorStateEvent(ref ClientMessage client, DataMessage mm, int sensorAddr, int state) + { + int offline = state % 2; + int alarm = state / 2 % 2; + if (!client.SensorDict.TryGetValue(sensorAddr, out SensorState? sensor)) + { + sensor = new(mm.DeviceId, sensorAddr, offline, alarm); + client.SensorDict[sensorAddr] = sensor; + ReportSensorState(sensor); + } + else if (sensor.OfflineState != offline || sensor.AlarmState != alarm) + { + sensor.OfflineState = offline; + sensor.AlarmState = alarm; + ReportSensorState(sensor); + } + if (alarm == 1) + { + ReportAlarm(sensor.DeviceId, sensorAddr); + RequestSensorReset_05(client, sensorAddr); + } + } + + private void ReportDeviceState(ClientMessage device) + { + OnVibrateTcpDeviceState?.Invoke(device); + } + + private void ReportSensorState(SensorState sensor) + { + OnVibrateTcpSensorState?.Invoke(sensor); + } + + private void ReportAlarm(int deviceId, int sensorAddr) + { + TcpAlarmHostMessage alarmMsg = new() + { + AlarmTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), + CID = "1151", + DeviceID = deviceId, + SensorAddr = sensorAddr.ToString() + }; + OnVibrateTcpAlarm?.Invoke(alarmMsg); + } + + #endregion Analysis + + #region Send + + /// + /// 0x10:主机响应0x00登录 + /// + /// + /// + /// + public bool ResponseHostLogin_10(ClientMessage client, DataMessage mm) + { + if (!client.IsOnline) return false; + DataMessage msg = GetSendMessageHead(client.Host.Id, client, 0x10, 0); + return SendResponse(msg, mm.FrameNum); + } + + /// + /// 0x01:心跳 + /// + /// + /// + public bool SendHostHeart_01(ClientMessage client) + { + if (!client.IsOnline) return false; + DataMessage msg = GetSendMessageHead(client.Host.Id, client, 0x01, 1); + msg.Data = new byte[] { 0xFF }; + return SendNoRequest(msg); + } + + /// + /// 0x02:设置传感器地址 + /// + /// + /// + /// + public bool RequestSensorChange_02(ClientMessage client, int newSensorAddr) + { + if (!client.IsOnline) return false; + DataMessage msg = GetSendMessageHead(client.Host.Id, client, 0x02, 4); + msg.Data = new byte[] { (byte)(newSensorAddr % 256), (byte)(newSensorAddr / 256), (100 % 256), (100 / 256) }; + DataRequest request = new() + { + Request = msg + }; + return SendRequestTry(ref request); + } + + /// + /// 0x04:请求传感器状态 + /// + /// + /// + /// + public bool RequestSensorState_04(ClientMessage client, int sensorAddr) + { + if (!client.IsOnline) return false; + if (!client.SensorDict.TryGetValue(sensorAddr, out _)) return false; + DataMessage msg = GetSendMessageHead(client.Host.Id, client, 0x04, 2); + msg.Data = new byte[] { (byte)(sensorAddr % 256), (byte)(sensorAddr / 256) }; + DataRequest request = new() + { + Request = msg + }; + return SendRequestTry(ref request); + } + + /// + /// 0x04:请求传感器状态 + /// + /// + /// + public bool RequestSensorsState_04(ClientMessage client) + { + if (!client.IsOnline) return false; + DataMessage msg = GetSendMessageHead(client.Host.Id, client, 0x04, 2); + foreach (var item in client.SensorDict.Values) + { + int sensorAddr = item.Addr; + msg.Data = new byte[] { (byte)(sensorAddr % 256), (byte)(sensorAddr / 256) }; + DataRequest request = new() + { + Request = msg + }; + SendRequestTry(ref request); + } + return true; + } + + /// + /// 0x05:复位传感器 + /// + /// + /// + /// + public bool RequestSensorReset_05(ClientMessage client, int sensorAddr) + { + if (!client.IsOnline) return false; + if (!client.SensorDict.TryGetValue(sensorAddr, out _)) return false; + DataMessage msg = GetSendMessageHead(client.Host.Id, client, 0x05, 2); + msg.Data = new byte[] { (byte)(sensorAddr % 256), (byte)(sensorAddr / 256) }; + DataRequest request = new() + { + Request = msg + }; + return SendRequestTry(ref request); + } + + /// + /// 0x07:请求传感器列表 + /// + /// + /// + public bool RequestSensorList_07(ClientMessage client) + { + if (!client.IsOnline) return false; + DataMessage msg = GetSendMessageHead(client.Host.Id, client, 0x07, 0); + DataRequest request = new() + { + Request = msg + }; + return SendRequestMust(ref request); + } + + /// + /// 0x08:主机响应0x18主动上传状态 + /// + /// + /// + /// + public bool ResponseHostUploadState_08(ClientMessage client, DataMessage mm) + { + if (!client.IsOnline) return false; + DataMessage msg = GetSendMessageHead(client.Host.Id, client, 0x08, 2); + msg.Data = new byte[] { mm.Data[0], mm.Data[1] }; + return SendResponse(msg, mm.FrameNum); + } + + /// + /// 0x09:关闭传感器 + /// + /// + /// + /// + public bool RequestSensorTurnOff_09(ClientMessage client, int sensorAddr) + { + if (!client.IsOnline) return false; + DataMessage msg = GetSendMessageHead(client.Host.Id, client, 0x09, 2); + msg.Data = new byte[] { (byte)(sensorAddr % 256), (byte)(sensorAddr / 256) }; + DataRequest request = new() + { + Request = msg + }; + return SendRequestTry(ref request); + } + + /// + /// 0x09:关闭传感器 + /// + /// + /// + public bool RequestSensorsTurnOff_09(ClientMessage client) + { + if (!client.IsOnline) return false; + DataMessage msg = GetSendMessageHead(client.Host.Id, client, 0x09, 2); + foreach (var item in client.SensorDict.Values) + { + int sensorAddr = item.Addr; + msg.Data = new byte[] { (byte)(sensorAddr % 256), (byte)(sensorAddr / 256) }; + DataRequest request = new() + { + Request = msg + }; + SendRequestTry(ref request); + } + return true; + } + + /// + /// 0x20:启动传感器 + /// + /// + /// + /// + public bool RequestSensorTurnOn_20(ClientMessage client, int sensorAddr) + { + if (!client.IsOnline) return false; + DataMessage msg = GetSendMessageHead(client.Host.Id, client, 0x20, 2); + msg.Data = new byte[] { (byte)(sensorAddr % 256), (byte)(sensorAddr / 256) }; + DataRequest request = new() + { + Request = msg + }; + return SendRequestTry(ref request); + } + + /// + /// 0x20:启动传感器 + /// + /// + /// + public bool RequestSensorsTurnOn_20(ClientMessage client) + { + if (!client.IsOnline) return false; + DataMessage msg = GetSendMessageHead(client.Host.Id, client, 0x20, 2); + foreach (var item in client.SensorDict.Values) + { + int sensorAddr = item.Addr; + msg.Data = new byte[] { (byte)(sensorAddr % 256), (byte)(sensorAddr / 256) }; + DataRequest request = new() + { + Request = msg + }; + SendRequestTry(ref request); + } + return true; + } + + /// + /// 0x21:启动全部传感器 + /// + /// + /// + public bool RequestSensorsTurnOn_21(ClientMessage client) + { + if (!client.IsOnline) return false; + DataMessage msg = GetSendMessageHead(client.Host.Id, client, 0x21, 0); + DataRequest request = new() + { + Request = msg + }; + return SendRequestTry(ref request); + } + + /// + /// 0x24:启动自动上传 + /// + /// + /// + public bool RequestHostAutoUploadState_24(ClientMessage client) + { + if (!client.IsOnline) return false; + DataMessage msg = GetSendMessageHead(client.Host.Id, client, 0x24, 0); + DataRequest request = new() + { + Request = msg + }; + return SendRequestMust(ref request); + } + + /// + /// 0x25:关闭自动上传 + /// + /// + /// + public bool RequestSensorsTurnOn_25(ClientMessage client) + { + if (!client.IsOnline) return false; + DataMessage msg = GetSendMessageHead(client.Host.Id, client, 0x25, 0); + DataRequest request = new() + { + Request = msg + }; + return SendRequestTry(ref request); + } + + #endregion Send + + #region ClientMessage + + public bool ContainsDevice(string clientIp) + { + return DeviceDict.ContainsKey(clientIp); + } + + public ClientMessage GetDevice(string clientIp) + { + return DeviceDict[clientIp]; + } + + public bool TryGetDevice(string clientIp, out ClientMessage clientMsg) + { + return DeviceDict.TryGetValue(clientIp, out clientMsg); + } + + public bool TryGetDevice(int deviceId, out ClientMessage clientMsg) + { + foreach (var item in DeviceDict.Values) + { + if (item.Host.Id == deviceId) + { + clientMsg = item; + return true; + } + } + clientMsg = null; + return false; + } + + private bool AddDeivce(string clientIp, ClientMessage clientMsg) + { + if (ContainsDevice(clientIp)) return false; + DeviceDict[clientIp] = clientMsg; + return true; + } + + private void SetDevice(string clientIp, ClientMessage clientMsg) + { + DeviceDict[clientIp] = clientMsg; + } + + private bool RemoveDevice(string clientIp) + { + return DeviceDict.Remove(clientIp, out _); + } + + public List GetDeviceList() + { + return DeviceDict.Values.ToList(); + } + + #endregion ClientMessage + + #region Set + + private byte Frame { get; set; } = 0; + + private byte FrameInc + { get { return (byte)(++Frame % byte.MaxValue); } } + + private int SendTryTime { get; set; } = 5; + + private int SendTryInterval { get; set; } = 200; + + private int ReqWaitTime { get; set; } = 3 * 10; + + private int ReqWaitInterval { get; set; } = 100; + + private DataMessage GetSendMessageHead(int deviceId, ClientMessage client, byte funNum, byte dataLen) + { + DataMessage msg = new() + { + DeviceId = deviceId, + SendIp = client.LocalIp, + SendPort = client.LocalPort.ToInt(), + ReceiveIp = client.ClientIp, + ReceivePort = client.ClientPort.ToInt(), + FunctionNum = funNum, + DataLen = dataLen + }; + return msg; + } + + private bool SendMessage(string ip, byte[] bytes) + { + if (Server == null || !Server.IsRunning()) return false; + if (!TryGetDevice(ip, out ClientMessage clientMsg)) return false; + string cmd = DataMessage.ToHexString(bytes); + bool send = false; + for (int i = 0; i < SendTryTime; i++) + { + send = Server.SendAsync(clientMsg.Client, bytes); + if (send) break; + Thread.Sleep(SendTryInterval); + } + Console.WriteLine("Send to {0}:{1} => {2}, send:{3}", clientMsg.ClientIp, clientMsg.ClientPort, cmd, send); + return send; + } + + private bool SendRequestTry(ref DataRequest request) + { + if (request.Request == null) return false; + request.Request.FrameNum = FrameInc; + bool send = SendMessage(request.Request.ReceiveIp, request.Request.Encode()); + if (!send) return false; + bool respond = false; + SetDataRequest(request); + for (int i = 0; i < ReqWaitTime; i++) + { + respond = IsResponse(request); + if (respond) break; + Thread.Sleep(ReqWaitInterval); + } + RemoveDataRequest(request); + return respond; + } + + private bool SendRequestMust(ref DataRequest request) + { + if (request.Request == null) return false; + request.Request.FrameNum = FrameInc; + bool send, respond = false; + do + { + send = SendMessage(request.Request.ReceiveIp, request.Request.Encode()); + if (!send) continue; + SetDataRequest(request); + for (int i = 0; i < ReqWaitTime; i++) + { + respond = IsResponse(request); + if (respond) break; + Thread.Sleep(ReqWaitInterval); + } + } while (!send && !respond); + RemoveDataRequest(request); + return true; + } + + private bool SendNoRequest(DataMessage request) + { + request.FrameNum = FrameInc; + bool send = SendMessage(request.ReceiveIp, request.Encode()); + return send; + } + + private bool SendResponse(DataMessage request, byte frameNum) + { + request.FrameNum = frameNum; + bool send = SendMessage(request.ReceiveIp, request.Encode()); + return send; + } + + private ConcurrentDictionary DataRequestDict { get; } = new(); + + private void SetDataRequest(DataRequest request) + { + string key = $"{request.Request.FunctionNum}-{request.Request.FrameNum}"; + DataRequestDict[key] = request; + } + + private void SetDataResponse(DataMessage msg, byte funcNum) + { + string key = $"{funcNum}-{msg.FrameNum}"; + DataRequest? item = DataRequestDict[key]; + if (item != null) item.Responce = msg; + } + + private void RemoveDataRequest(DataRequest request) + { + string key = $"{request.Request.FunctionNum}-{request.Request.FrameNum}"; + DataRequestDict.Remove(key); + } + + private bool IsResponse(DataRequest request) + { + string key = $"{request.Request.FunctionNum}-{request.Request.FrameNum}"; + DataRequest? item = DataRequestDict[key]; + return item != null && item.Responce != null; + } + + #endregion Set +} \ No newline at end of file diff --git a/JiLinApp.Docking/config/alarmcode.json b/JiLinApp.Docking/config/alarmcode.json new file mode 100644 index 0000000..53c3382 --- /dev/null +++ b/JiLinApp.Docking/config/alarmcode.json @@ -0,0 +1,2222 @@ +[ + { + "Id": "1000", + "Level": "0", + "Type": "错误", + "Content": "接收到未定义的代码" + }, + { + "Id": "1100", + "Level": "1", + "Type": "紧急", + "Content": "个人救护报警" + }, + { + "Id": "1101", + "Level": "1", + "Type": "紧急", + "Content": "个人救护报警,紧急按钮" + }, + { + "Id": "1102", + "Level": "1", + "Type": "紧急", + "Content": "报到失败" + }, + { + "Id": "1103", + "Level": "1", + "Type": "紧急", + "Content": "紧急报警" + }, + { + "Id": "1104", + "Level": "1", + "Type": "紧急", + "Content": "紧急报警" + }, + { + "Id": "1110", + "Level": "1", + "Type": "火警", + "Content": "火警报警" + }, + { + "Id": "1111", + "Level": "1", + "Type": "火警", + "Content": "烟感探头" + }, + { + "Id": "1112", + "Level": "1", + "Type": "火警", + "Content": "燃烧" + }, + { + "Id": "1113", + "Level": "1", + "Type": "火警", + "Content": "消防水流" + }, + { + "Id": "1114", + "Level": "1", + "Type": "火警", + "Content": "热感探头" + }, + { + "Id": "1115", + "Level": "1", + "Type": "火警", + "Content": "火警手动报警" + }, + { + "Id": "1117", + "Level": "1", + "Type": "火警", + "Content": "火焰探头" + }, + { + "Id": "1118", + "Level": "1", + "Type": "火警", + "Content": "接近警报" + }, + { + "Id": "1119", + "Level": "1", + "Type": "火警", + "Content": "煤气泄漏" + }, + { + "Id": "1120", + "Level": "1", + "Type": "劫盗", + "Content": "劫盗" + }, + { + "Id": "1121", + "Level": "1", + "Type": "劫盗", + "Content": "挟持" + }, + { + "Id": "1122", + "Level": "1", + "Type": "劫盗", + "Content": "无声劫盗" + }, + { + "Id": "1123", + "Level": "1", + "Type": "劫盗", + "Content": "有声劫盗" + }, + { + "Id": "1124", + "Level": "1", + "Type": "窃盗", + "Content": "异地劫持" + }, + { + "Id": "1130", + "Level": "1", + "Type": "窃盗", + "Content": "窃盗" + }, + { + "Id": "1131", + "Level": "1", + "Type": "窃盗", + "Content": "周界窃盗" + }, + { + "Id": "1132", + "Level": "1", + "Type": "窃盗", + "Content": "内部窃盗" + }, + { + "Id": "1133", + "Level": "1", + "Type": "窃盗", + "Content": "24小时窃盗" + }, + { + "Id": "1134", + "Level": "1", + "Type": "窃盗", + "Content": "出/入窃盗" + }, + { + "Id": "1135", + "Level": "1", + "Type": "窃盗", + "Content": "日/夜防区" + }, + { + "Id": "1136", + "Level": "1", + "Type": "窃盗", + "Content": "室外窃盗" + }, + { + "Id": "1137", + "Level": "1", + "Type": "窃盗", + "Content": "拆动报警" + }, + { + "Id": "1138", + "Level": "1", + "Type": "窃盗", + "Content": "接近报警" + }, + { + "Id": "1140", + "Level": "1", + "Type": "警报", + "Content": "一般报警" + }, + { + "Id": "1141", + "Level": "1", + "Type": "警报", + "Content": "总线开路" + }, + { + "Id": "1142", + "Level": "1", + "Type": "警报", + "Content": "总线短路" + }, + { + "Id": "1143", + "Level": "1", + "Type": "警报", + "Content": "扩充器故障" + }, + { + "Id": "1144", + "Level": "1", + "Type": "警报", + "Content": "探头被拆动" + }, + { + "Id": "1145", + "Level": "1", + "Type": "警报", + "Content": "扩充器被拆" + }, + { + "Id": "1150", + "Level": "1", + "Type": "警报", + "Content": "24小时非盗窃报警" + }, + { + "Id": "1151", + "Level": "1", + "Type": "警报", + "Content": "震动报警" + }, + { + "Id": "1152", + "Level": "1", + "Type": "警报", + "Content": "挖掘报警" + }, + { + "Id": "1153", + "Level": "1", + "Type": "警报", + "Content": "步行报警" + }, + { + "Id": "1154", + "Level": "1", + "Type": "警报", + "Content": "行车报警" + }, + { + "Id": "1156", + "Level": "1", + "Type": "警报", + "Content": "日间防区" + }, + { + "Id": "1158", + "Level": "1", + "Type": "警报", + "Content": "温度过高" + }, + { + "Id": "1159", + "Level": "1", + "Type": "警报", + "Content": "温度过低" + }, + { + "Id": "1161", + "Level": "1", + "Type": "周界窃盗", + "Content": "攀爬报警" + }, + { + "Id": "1170", + "Level": "1", + "Type": "故障", + "Content": "用户离线" + }, + { + "Id": "1180", + "Level": "1", + "Type": "故障", + "Content": "GPRS设备断线" + }, + { + "Id": "1181", + "Level": "1", + "Type": "周界窃盗", + "Content": "张力围栏报警" + }, + { + "Id": "1190", + "Level": "1", + "Type": "周界窃盗", + "Content": "脉冲围栏断路报警" + }, + { + "Id": "1191", + "Level": "1", + "Type": "周界窃盗", + "Content": "脉冲围栏短路报警" + }, + { + "Id": "1192", + "Level": "1", + "Type": "周界窃盗", + "Content": "振动光纤断光" + }, + { + "Id": "1193", + "Level": "1", + "Type": "周界窃盗", + "Content": "振动光纤触网" + }, + { + "Id": "1200", + "Level": "2", + "Type": "监控", + "Content": "火警监视" + }, + { + "Id": "1201", + "Level": "2", + "Type": "监控", + "Content": "水压过低" + }, + { + "Id": "1202", + "Level": "2", + "Type": "监控", + "Content": "二氧化碳过低" + }, + { + "Id": "1203", + "Level": "2", + "Type": "监控", + "Content": "阀门感应" + }, + { + "Id": "1300", + "Level": "3", + "Type": "故障", + "Content": "系统故障" + }, + { + "Id": "1301", + "Level": "3", + "Type": "故障", + "Content": "无交流" + }, + { + "Id": "1302", + "Level": "3", + "Type": "故障", + "Content": "电池低压" + }, + { + "Id": "1303", + "Level": "3", + "Type": "故障", + "Content": "RAM校验和故障" + }, + { + "Id": "1304", + "Level": "3", + "Type": "故障", + "Content": "ROM校验和故障" + }, + { + "Id": "1305", + "Level": "3", + "Type": "故障", + "Content": "系统重新设定" + }, + { + "Id": "1306", + "Level": "3", + "Type": "故障", + "Content": "编程改动" + }, + { + "Id": "1307", + "Level": "3", + "Type": "故障", + "Content": "自检故障" + }, + { + "Id": "1308", + "Level": "3", + "Type": "故障", + "Content": "主机停机使用" + }, + { + "Id": "1309", + "Level": "3", + "Type": "故障", + "Content": "电池测试故障" + }, + { + "Id": "130A", + "Level": "3", + "Type": "故障", + "Content": "系统复位" + }, + { + "Id": "1310", + "Level": "3", + "Type": "故障", + "Content": "接地故障" + }, + { + "Id": "1320", + "Level": "3", + "Type": "故障", + "Content": "警号/继电器" + }, + { + "Id": "1321", + "Level": "3", + "Type": "故障", + "Content": "警铃#1" + }, + { + "Id": "1322", + "Level": "3", + "Type": "故障", + "Content": "警铃#2" + }, + { + "Id": "1323", + "Level": "3", + "Type": "故障", + "Content": "警报继电器" + }, + { + "Id": "1324", + "Level": "3", + "Type": "故障", + "Content": "故障继电器" + }, + { + "Id": "1325", + "Level": "3", + "Type": "故障", + "Content": "逆转继电器" + }, + { + "Id": "132A", + "Level": "3", + "Type": "故障", + "Content": "警铃保险管烧坏" + }, + { + "Id": "1330", + "Level": "3", + "Type": "故障", + "Content": "系统周边" + }, + { + "Id": "1331", + "Level": "3", + "Type": "故障", + "Content": "总线开路" + }, + { + "Id": "1332", + "Level": "3", + "Type": "故障", + "Content": "总线短路" + }, + { + "Id": "1333", + "Level": "3", + "Type": "故障", + "Content": "防区掉线" + }, + { + "Id": "1334", + "Level": "3", + "Type": "故障", + "Content": "单元控制器掉线楼道机" + }, + { + "Id": "1335", + "Level": "3", + "Type": "故障", + "Content": "打印机无纸" + }, + { + "Id": "1339", + "Level": "3", + "Type": "故障", + "Content": "振动光纤断光" + }, + { + "Id": "1340", + "Level": "3", + "Type": "故障", + "Content": "网络设备离线" + }, + { + "Id": "1336", + "Level": "3", + "Type": "故障", + "Content": "打印机故障" + }, + { + "Id": "1350", + "Level": "3", + "Type": "故障", + "Content": "通讯故障" + }, + { + "Id": "1351", + "Level": "3", + "Type": "故障", + "Content": "电话线1故障" + }, + { + "Id": "1352", + "Level": "3", + "Type": "故障", + "Content": "电话线2故障" + }, + { + "Id": "1353", + "Level": "3", + "Type": "故障", + "Content": "长距离无线发射器故障" + }, + { + "Id": "1354", + "Level": "3", + "Type": "故障", + "Content": "通讯失败" + }, + { + "Id": "1355", + "Level": "3", + "Type": "故障", + "Content": "失去长距无线监控" + }, + { + "Id": "1356", + "Level": "3", + "Type": "故障", + "Content": "失去长距无线中央监控" + }, + { + "Id": "1357", + "Level": "3", + "Type": "故障", + "Content": "无线干扰" + }, + { + "Id": "1360", + "Level": "3", + "Type": "故障", + "Content": "防区故障" + }, + { + "Id": "1361", + "Level": "3", + "Type": "故障", + "Content": "防拆故障" + }, + { + "Id": "1362", + "Level": "3", + "Type": "故障", + "Content": "充电故障" + }, + { + "Id": "1363", + "Level": "3", + "Type": "故障", + "Content": "模块未准备就绪" + }, + { + "Id": "1370", + "Level": "3", + "Type": "故障", + "Content": "保护回路" + }, + { + "Id": "1371", + "Level": "3", + "Type": "故障", + "Content": "保护回路开路" + }, + { + "Id": "1372", + "Level": "3", + "Type": "故障", + "Content": "保护回路短路" + }, + { + "Id": "1373", + "Level": "3", + "Type": "故障", + "Content": "火警回路故障" + }, + { + "Id": "137A", + "Level": "3", + "Type": "故障", + "Content": "回路故障" + }, + { + "Id": "1380", + "Level": "3", + "Type": "故障", + "Content": "感应器故障" + }, + { + "Id": "1381", + "Level": "3", + "Type": "故障", + "Content": "无线监控故障" + }, + { + "Id": "1382", + "Level": "3", + "Type": "故障", + "Content": "总线监控故障" + }, + { + "Id": "1383", + "Level": "3", + "Type": "故障", + "Content": "感应器被拆" + }, + { + "Id": "1384", + "Level": "3", + "Type": "故障", + "Content": "无线感应器电池过低" + }, + { + "Id": "1393", + "Level": "3", + "Type": "故障", + "Content": "电话故障" + }, + { + "Id": "1394", + "Level": "3", + "Type": "故障", + "Content": "卡故障" + }, + { + "Id": "13A0", + "Level": "3", + "Type": "故障", + "Content": "请求服务" + }, + { + "Id": "13A1", + "Level": "3", + "Type": "故障", + "Content": "时间重设" + }, + { + "Id": "13A2", + "Level": "3", + "Type": "故障", + "Content": "5(12)伏电源故障" + }, + { + "Id": "13A3", + "Level": "3", + "Type": "故障", + "Content": "电话线故障" + }, + { + "Id": "13A4", + "Level": "3", + "Type": "故障", + "Content": "卡故障" + }, + { + "Id": "13A5", + "Level": "3", + "Type": "故障", + "Content": "失去电池" + }, + { + "Id": "13A6", + "Level": "3", + "Type": "故障", + "Content": "进入编程模式" + }, + { + "Id": "13A7", + "Level": "3", + "Type": "故障", + "Content": "未知故障" + }, + { + "Id": "13A8", + "Level": "3", + "Type": "故障", + "Content": "无效报告" + }, + { + "Id": "13B0", + "Level": "3", + "Type": "故障", + "Content": "用户密码更改" + }, + { + "Id": "13B1", + "Level": "3", + "Type": "故障", + "Content": "错误信息" + }, + { + "Id": "1400", + "Level": "4", + "Type": "撤防", + "Content": "撤防" + }, + { + "Id": "1401", + "Level": "4", + "Type": "撤防", + "Content": "用户撤防" + }, + { + "Id": "1402", + "Level": "4", + "Type": "撤防", + "Content": "集体撤防" + }, + { + "Id": "1403", + "Level": "4", + "Type": "撤防", + "Content": "自动撤防" + }, + { + "Id": "1404", + "Level": "4", + "Type": "撤防", + "Content": "过迟撤防" + }, + { + "Id": "1405", + "Level": "4", + "Type": "撤防", + "Content": "不适用" + }, + { + "Id": "1406", + "Level": "4", + "Type": "撤防", + "Content": "取消" + }, + { + "Id": "1407", + "Level": "4", + "Type": "撤防", + "Content": "遥控撤防" + }, + { + "Id": "1408", + "Level": "4", + "Type": "撤防", + "Content": "快速撤防" + }, + { + "Id": "1409", + "Level": "4", + "Type": "撤防", + "Content": "开关撤防" + }, + { + "Id": "140A", + "Level": "4", + "Type": "撤防", + "Content": "部分撤防" + }, + { + "Id": "140B", + "Level": "4", + "Type": "撤防", + "Content": "周边撤防" + }, + { + "Id": "1411", + "Level": "4", + "Type": "遥控", + "Content": "要求回电" + }, + { + "Id": "1412", + "Level": "4", + "Type": "遥控", + "Content": "遥控编程成功" + }, + { + "Id": "1413", + "Level": "4", + "Type": "遥控", + "Content": "遥控不成功" + }, + { + "Id": "1414", + "Level": "4", + "Type": "遥控", + "Content": "关闭系统" + }, + { + "Id": "1415", + "Level": "4", + "Type": "遥控", + "Content": "关闭通讯" + }, + { + "Id": "1416", + "Level": "4", + "Type": "撤防", + "Content": "撤防操作" + }, + { + "Id": "1417", + "Level": "4", + "Type": "撤防", + "Content": "防区撤防" + }, + { + "Id": "1421", + "Level": "4", + "Type": "出入", + "Content": "拒绝出入-用户" + }, + { + "Id": "1422", + "Level": "4", + "Type": "出入", + "Content": "成功出入-用户" + }, + { + "Id": "1441", + "Level": "4", + "Type": "出入", + "Content": "在家布防" + }, + { + "Id": "1450", + "Level": "4", + "Type": "出入", + "Content": "非正常时段撤防" + }, + { + "Id": "1451", + "Level": "4", + "Type": "出入", + "Content": "过早撤防" + }, + { + "Id": "1452", + "Level": "4", + "Type": "出入", + "Content": "过迟撤防" + }, + { + "Id": "1453", + "Level": "4", + "Type": "出入", + "Content": "撤防失败" + }, + { + "Id": "1454", + "Level": "4", + "Type": "出入", + "Content": "布防失败" + }, + { + "Id": "1455", + "Level": "4", + "Type": "出入", + "Content": "自动布防失败" + }, + { + "Id": "1456", + "Level": "4", + "Type": "出入", + "Content": "部分布防" + }, + { + "Id": "1457", + "Level": "4", + "Type": "出入", + "Content": "外出错误" + }, + { + "Id": "1458", + "Level": "4", + "Type": "出入", + "Content": "操作员在现场" + }, + { + "Id": "1459", + "Level": "4", + "Type": "出入", + "Content": "最近布防" + }, + { + "Id": "1480", + "Level": "4", + "Type": "出入", + "Content": "关警号" + }, + { + "Id": "1520", + "Level": "5", + "Type": "停用", + "Content": "停用警号/继电器" + }, + { + "Id": "1521", + "Level": "5", + "Type": "停用", + "Content": "警铃1停用" + }, + { + "Id": "1522", + "Level": "5", + "Type": "停用", + "Content": "警铃2停用" + }, + { + "Id": "1523", + "Level": "5", + "Type": "停用", + "Content": "停用警报继电器" + }, + { + "Id": "1524", + "Level": "5", + "Type": "停用", + "Content": "停用故障继电器" + }, + { + "Id": "1525", + "Level": "5", + "Type": "停用", + "Content": "逆反继电器" + }, + { + "Id": "1530", + "Level": "5", + "Type": "停用", + "Content": "停用系统周边" + }, + { + "Id": "1540", + "Level": "5", + "Type": "停用", + "Content": "停用系统周边" + }, + { + "Id": "1551", + "Level": "5", + "Type": "停用", + "Content": "通讯器停用" + }, + { + "Id": "1552", + "Level": "5", + "Type": "停用", + "Content": "无线发射器停用" + }, + { + "Id": "1570", + "Level": "5", + "Type": "旁路", + "Content": "防区旁路" + }, + { + "Id": "1571", + "Level": "5", + "Type": "旁路", + "Content": "火警旁路" + }, + { + "Id": "1572", + "Level": "5", + "Type": "旁路", + "Content": "24小时防区旁路" + }, + { + "Id": "1573", + "Level": "5", + "Type": "旁路", + "Content": "窃盗旁路" + }, + { + "Id": "1574", + "Level": "5", + "Type": "旁路", + "Content": "集体旁路" + }, + { + "Id": "1580", + "Level": "5", + "Type": "旁路", + "Content": "旁路操作" + }, + { + "Id": "1601", + "Level": "6", + "Type": "测试", + "Content": "手动测试" + }, + { + "Id": "1602", + "Level": "6", + "Type": "测试", + "Content": "定期测试" + }, + { + "Id": "1603", + "Level": "6", + "Type": "测试", + "Content": "定期无线发射器测试" + }, + { + "Id": "1604", + "Level": "6", + "Type": "测试", + "Content": "火警测试" + }, + { + "Id": "1605", + "Level": "6", + "Type": "测试", + "Content": "状态报告" + }, + { + "Id": "1606", + "Level": "6", + "Type": "测试", + "Content": "监听" + }, + { + "Id": "1607", + "Level": "6", + "Type": "测试", + "Content": "不行测试模式" + }, + { + "Id": "1609", + "Level": "6", + "Type": "测试", + "Content": "图像传输" + }, + { + "Id": "1630", + "Level": "6", + "Type": "测试", + "Content": "改动时间表" + }, + { + "Id": "1631", + "Level": "6", + "Type": "测试", + "Content": "改动例外时间表" + }, + { + "Id": "1632", + "Level": "6", + "Type": "测试", + "Content": "改动出入时间表" + }, + { + "Id": "1695", + "Level": "6", + "Type": "用户", + "Content": "提早布防" + }, + { + "Id": "1696", + "Level": "6", + "Type": "用户", + "Content": "未及时布防" + }, + { + "Id": "1697", + "Level": "6", + "Type": "用户", + "Content": "提早撤防" + }, + { + "Id": "1698", + "Level": "6", + "Type": "用户", + "Content": "未及时撤防" + }, + { + "Id": "1699", + "Level": "6", + "Type": "用户", + "Content": "超时测试间隔信号" + }, + { + "Id": "16A0", + "Level": "6", + "Type": "用户", + "Content": "用户缴费时间到" + }, + { + "Id": "16B0", + "Level": "6", + "Type": "测试", + "Content": "手动测试操作" + }, + { + "Id": "16CC", + "Level": "6", + "Type": "错误", + "Content": "测试" + }, + { + "Id": "3100", + "Level": "1", + "Type": "紧急恢复", + "Content": "个人救护报警恢复" + }, + { + "Id": "3101", + "Level": "1", + "Type": "紧急恢复", + "Content": "个人救护报警,紧急按钮恢复" + }, + { + "Id": "3102", + "Level": "1", + "Type": "紧急恢复", + "Content": "报到失败恢复" + }, + { + "Id": "3103", + "Level": "1", + "Type": "紧急恢复", + "Content": "紧急报警恢复" + }, + { + "Id": "3104", + "Level": "1", + "Type": "紧急恢复", + "Content": "紧急报警恢复" + }, + { + "Id": "3110", + "Level": "1", + "Type": "火警恢复", + "Content": "火警报警恢复" + }, + { + "Id": "3111", + "Level": "1", + "Type": "火警恢复", + "Content": "烟感探头恢复" + }, + { + "Id": "3112", + "Level": "1", + "Type": "火警恢复", + "Content": "燃烧熄灭" + }, + { + "Id": "3113", + "Level": "1", + "Type": "火警恢复", + "Content": "消防水流恢复" + }, + { + "Id": "3114", + "Level": "1", + "Type": "火警恢复", + "Content": "热感探头恢复" + }, + { + "Id": "3115", + "Level": "1", + "Type": "火警恢复", + "Content": "火警手动报警恢复" + }, + { + "Id": "3116", + "Level": "1", + "Type": "火警恢复", + "Content": "空调槽烟感恢复" + }, + { + "Id": "3117", + "Level": "1", + "Type": "火警恢复", + "Content": "火焰探头恢复" + }, + { + "Id": "3118", + "Level": "1", + "Type": "火警恢复", + "Content": "接近警报恢复" + }, + { + "Id": "3119", + "Level": "1", + "Type": "火警恢复", + "Content": "煤气泄漏恢复" + }, + { + "Id": "3120", + "Level": "1", + "Type": "劫盗恢复", + "Content": "劫盗恢复" + }, + { + "Id": "3121", + "Level": "1", + "Type": "劫盗恢复", + "Content": "挟持恢复" + }, + { + "Id": "3122", + "Level": "1", + "Type": "劫盗恢复", + "Content": "无声劫盗恢复" + }, + { + "Id": "3123", + "Level": "1", + "Type": "劫盗恢复", + "Content": "有声劫盗恢复" + }, + { + "Id": "3124", + "Level": "1", + "Type": "劫盗恢复", + "Content": "异地劫持恢复" + }, + { + "Id": "3130", + "Level": "1", + "Type": "窃盗恢复", + "Content": "窃盗恢复" + }, + { + "Id": "3131", + "Level": "1", + "Type": "窃盗恢复", + "Content": "周界窃盗恢复" + }, + { + "Id": "3132", + "Level": "1", + "Type": "窃盗恢复", + "Content": "内部窃盗恢复" + }, + { + "Id": "3133", + "Level": "1", + "Type": "窃盗恢复", + "Content": "24小时窃盗恢复" + }, + { + "Id": "3134", + "Level": "1", + "Type": "窃盗恢复", + "Content": "出/入窃盗恢复" + }, + { + "Id": "3135", + "Level": "1", + "Type": "窃盗恢复", + "Content": "日/夜防区恢复" + }, + { + "Id": "3136", + "Level": "1", + "Type": "窃盗恢复", + "Content": "室外窃盗恢复" + }, + { + "Id": "3137", + "Level": "1", + "Type": "窃盗恢复", + "Content": "拆动报警恢复" + }, + { + "Id": "3138", + "Level": "1", + "Type": "窃盗恢复", + "Content": "接近报警恢复" + }, + { + "Id": "3140", + "Level": "1", + "Type": "警报恢复", + "Content": "一般报警恢复" + }, + { + "Id": "3141", + "Level": "1", + "Type": "警报恢复", + "Content": "总线开路恢复" + }, + { + "Id": "3142", + "Level": "1", + "Type": "警报恢复", + "Content": "总线短路恢复" + }, + { + "Id": "3143", + "Level": "1", + "Type": "警报恢复", + "Content": "扩充器恢复" + }, + { + "Id": "3144", + "Level": "1", + "Type": "警报恢复", + "Content": "探头被拆动恢复" + }, + { + "Id": "3145", + "Level": "1", + "Type": "警报恢复", + "Content": "扩充器被拆恢复" + }, + { + "Id": "3150", + "Level": "1", + "Type": "警报恢复", + "Content": "24小时非窃盗报警恢复" + }, + { + "Id": "3151", + "Level": "1", + "Type": "警报恢复", + "Content": "气体恢复" + }, + { + "Id": "3152", + "Level": "1", + "Type": "警报恢复", + "Content": "冷藏器恢复" + }, + { + "Id": "3153", + "Level": "1", + "Type": "警报恢复", + "Content": "加热系统恢复" + }, + { + "Id": "3154", + "Level": "1", + "Type": "警报恢复", + "Content": "漏水恢复" + }, + { + "Id": "3155", + "Level": "1", + "Type": "警报恢复", + "Content": "箔片破损恢复" + }, + { + "Id": "3156", + "Level": "1", + "Type": "警报恢复", + "Content": "日间防区恢复" + }, + { + "Id": "3157", + "Level": "1", + "Type": "警报恢复", + "Content": "气体水平过低恢复" + }, + { + "Id": "3158", + "Level": "1", + "Type": "警报恢复", + "Content": "温度过高恢复" + }, + { + "Id": "3159", + "Level": "1", + "Type": "警报恢复", + "Content": "温度过低恢复" + }, + { + "Id": "3161", + "Level": "1", + "Type": "警报恢复", + "Content": "空气流动恢复" + }, + { + "Id": "3170", + "Level": "1", + "Type": "故障恢复", + "Content": "用户离线恢复" + }, + { + "Id": "3180", + "Level": "1", + "Type": "故障", + "Content": "GPRS设备恢复连接" + }, + { + "Id": "3200", + "Level": "2", + "Type": "监控恢复", + "Content": "火警监控恢复" + }, + { + "Id": "3201", + "Level": "2", + "Type": "监控恢复", + "Content": "水压过低恢复" + }, + { + "Id": "3202", + "Level": "2", + "Type": "监控恢复", + "Content": "二氧化碳过低恢复" + }, + { + "Id": "3203", + "Level": "2", + "Type": "监控恢复", + "Content": "阀门感应恢复" + }, + { + "Id": "3204", + "Level": "2", + "Type": "监控恢复", + "Content": "水压过低恢复" + }, + { + "Id": "3205", + "Level": "2", + "Type": "监控恢复", + "Content": "水泵关闭" + }, + { + "Id": "3206", + "Level": "2", + "Type": "监控恢复", + "Content": "水泵故障恢复" + }, + { + "Id": "3300", + "Level": "3", + "Type": "故障恢复", + "Content": "系统故障恢复" + }, + { + "Id": "3301", + "Level": "3", + "Type": "故障恢复", + "Content": "交流恢复" + }, + { + "Id": "3302", + "Level": "3", + "Type": "故障恢复", + "Content": "电池低压恢复" + }, + { + "Id": "3303", + "Level": "3", + "Type": "故障恢复", + "Content": "RAM校验和故障恢复" + }, + { + "Id": "3304", + "Level": "3", + "Type": "故障恢复", + "Content": "ROM检验和故障恢复" + }, + { + "Id": "3305", + "Level": "3", + "Type": "故障恢复", + "Content": "系统重新设定恢复" + }, + { + "Id": "3306", + "Level": "3", + "Type": "故障恢复", + "Content": "编程改动恢复" + }, + { + "Id": "3307", + "Level": "3", + "Type": "故障恢复", + "Content": "自检故障恢复" + }, + { + "Id": "3308", + "Level": "3", + "Type": "故障恢复", + "Content": "主机停机使用恢复" + }, + { + "Id": "3309", + "Level": "3", + "Type": "故障恢复", + "Content": "电池测试故障恢复" + }, + { + "Id": "330A", + "Level": "3", + "Type": "故障恢复", + "Content": "系统复位恢复" + }, + { + "Id": "3310", + "Level": "3", + "Type": "故障恢复", + "Content": "接地故障恢复" + }, + { + "Id": "3320", + "Level": "3", + "Type": "故障恢复", + "Content": "警号/继电器恢复" + }, + { + "Id": "3321", + "Level": "3", + "Type": "故障恢复", + "Content": "警铃#1恢复" + }, + { + "Id": "3322", + "Level": "3", + "Type": "故障恢复", + "Content": "警铃#2恢复" + }, + { + "Id": "3323", + "Level": "3", + "Type": "故障恢复", + "Content": "警报继电器恢复" + }, + { + "Id": "3324", + "Level": "3", + "Type": "故障恢复", + "Content": "故障继电器恢复" + }, + { + "Id": "3325", + "Level": "3", + "Type": "故障恢复", + "Content": "逆转继电器恢复" + }, + { + "Id": "332A", + "Level": "3", + "Type": "故障恢复", + "Content": "警铃保险管烧坏恢复" + }, + { + "Id": "3330", + "Level": "3", + "Type": "故障恢复", + "Content": "系统周边恢复" + }, + { + "Id": "3331", + "Level": "3", + "Type": "故障恢复", + "Content": "总线开路恢复" + }, + { + "Id": "3332", + "Level": "3", + "Type": "故障恢复", + "Content": "总线短路恢复" + }, + { + "Id": "3333", + "Level": "3", + "Type": "故障恢复", + "Content": "扩充器故障" + }, + { + "Id": "3334", + "Level": "3", + "Type": "故障恢复", + "Content": "重复器故障恢复" + }, + { + "Id": "3335", + "Level": "3", + "Type": "故障恢复", + "Content": "打印机无纸恢复" + }, + { + "Id": "3336", + "Level": "3", + "Type": "故障恢复", + "Content": "打印机故障恢复" + }, + { + "Id": "3339", + "Level": "3", + "Type": "故障恢复", + "Content": "振动光纤断光恢复" + }, + { + "Id": "3340", + "Level": "3", + "Type": "故障恢复", + "Content": "网络设备在线" + }, + { + "Id": "3350", + "Level": "3", + "Type": "故障恢复", + "Content": "通讯故障恢复" + }, + { + "Id": "3351", + "Level": "3", + "Type": "故障恢复", + "Content": "电话线1故障恢复" + }, + { + "Id": "3352", + "Level": "3", + "Type": "故障恢复", + "Content": "电话线2故障恢复" + }, + { + "Id": "3353", + "Level": "3", + "Type": "故障恢复", + "Content": "长距离无线发射器故障恢复" + }, + { + "Id": "3354", + "Level": "3", + "Type": "故障恢复", + "Content": "通讯失败恢复" + }, + { + "Id": "3355", + "Level": "3", + "Type": "故障恢复", + "Content": "失去长距无线监控恢复" + }, + { + "Id": "3356", + "Level": "3", + "Type": "故障恢复", + "Content": "失去长距无线中央监控恢复" + }, + { + "Id": "3357", + "Level": "3", + "Type": "故障恢复", + "Content": "无线感干扰恢复" + }, + { + "Id": "3360", + "Level": "3", + "Type": "故障恢复", + "Content": "防区故障恢复" + }, + { + "Id": "3361", + "Level": "3", + "Type": "故障恢复", + "Content": "防拆故障恢复" + }, + { + "Id": "3362", + "Level": "3", + "Type": "故障恢复", + "Content": "充电故障恢复" + }, + { + "Id": "3363", + "Level": "3", + "Type": "故障恢复", + "Content": "模块已准备就绪" + }, + { + "Id": "3370", + "Level": "3", + "Type": "故障恢复", + "Content": "保护回路恢复" + }, + { + "Id": "3371", + "Level": "3", + "Type": "故障恢复", + "Content": "保护回路开路恢复" + }, + { + "Id": "3372", + "Level": "3", + "Type": "故障恢复", + "Content": "保护回路短路恢复" + }, + { + "Id": "3373", + "Level": "3", + "Type": "故障恢复", + "Content": "火警回路故障恢复" + }, + { + "Id": "337A", + "Level": "3", + "Type": "故障恢复", + "Content": "回路故障恢复" + }, + { + "Id": "3380", + "Level": "3", + "Type": "故障恢复", + "Content": "感应器故障恢复" + }, + { + "Id": "3381", + "Level": "3", + "Type": "故障恢复", + "Content": "无线监控故障恢复" + }, + { + "Id": "3382", + "Level": "3", + "Type": "故障恢复", + "Content": "总线监控故障恢复" + }, + { + "Id": "3383", + "Level": "3", + "Type": "故障恢复", + "Content": "感应器被拆恢复" + }, + { + "Id": "3384", + "Level": "3", + "Type": "故障恢复", + "Content": "无线感应器电池过低恢复" + }, + { + "Id": "3393", + "Level": "3", + "Type": "故障恢复", + "Content": "电话故障恢复" + }, + { + "Id": "3394", + "Level": "3", + "Type": "故障恢复", + "Content": "卡故障恢复" + }, + { + "Id": "33A0", + "Level": "3", + "Type": "故障恢复", + "Content": "请求服务恢复" + }, + { + "Id": "33A1", + "Level": "3", + "Type": "故障恢复", + "Content": "设置时间恢复" + }, + { + "Id": "33A2", + "Level": "3", + "Type": "故障恢复", + "Content": "5(12)伏电源故障恢复" + }, + { + "Id": "33A3", + "Level": "3", + "Type": "故障恢复", + "Content": "电话线故障恢复" + }, + { + "Id": "33A4", + "Level": "3", + "Type": "故障恢复", + "Content": "卡故障恢复" + }, + { + "Id": "33A5", + "Level": "3", + "Type": "故障恢复", + "Content": "失去电池恢复" + }, + { + "Id": "33A6", + "Level": "3", + "Type": "故障恢复", + "Content": "进入编程模式恢复" + }, + { + "Id": "33A7", + "Level": "3", + "Type": "故障恢复", + "Content": "未知故障恢复" + }, + { + "Id": "33A8", + "Level": "3", + "Type": "故障恢复", + "Content": "无效报告恢复" + }, + { + "Id": "33B0", + "Level": "3", + "Type": "密码恢复", + "Content": "用户密码更改恢复" + }, + { + "Id": "33B1", + "Level": "3", + "Type": "故障恢复", + "Content": "错误恢复" + }, + { + "Id": "3400", + "Level": "4", + "Type": "布防", + "Content": "布防" + }, + { + "Id": "3401", + "Level": "4", + "Type": "布防", + "Content": "用户布防" + }, + { + "Id": "3402", + "Level": "4", + "Type": "布防", + "Content": "集体布防" + }, + { + "Id": "3403", + "Level": "4", + "Type": "布防", + "Content": "自动布防" + }, + { + "Id": "3404", + "Level": "4", + "Type": "布防", + "Content": "过迟布防" + }, + { + "Id": "3405", + "Level": "4", + "Type": "布防", + "Content": "不适用恢复" + }, + { + "Id": "3406", + "Level": "4", + "Type": "布防", + "Content": "取消恢复" + }, + { + "Id": "3407", + "Level": "4", + "Type": "布防", + "Content": "遥控布防" + }, + { + "Id": "3408", + "Level": "4", + "Type": "布防", + "Content": "快速布防" + }, + { + "Id": "3409", + "Level": "4", + "Type": "布防", + "Content": "开关布防" + }, + { + "Id": "340A", + "Level": "4", + "Type": "布防", + "Content": "部分布防" + }, + { + "Id": "340B", + "Level": "4", + "Type": "布防", + "Content": "周边布防" + }, + { + "Id": "3411", + "Level": "4", + "Type": "遥控恢复", + "Content": "要求回电恢复" + }, + { + "Id": "3412", + "Level": "4", + "Type": "遥控恢复", + "Content": "遥控编程成功恢复" + }, + { + "Id": "3413", + "Level": "4", + "Type": "遥控恢复", + "Content": "遥控不成功恢复" + }, + { + "Id": "3414", + "Level": "4", + "Type": "遥控恢复", + "Content": "关闭系统恢复" + }, + { + "Id": "3415", + "Level": "4", + "Type": "遥控恢复", + "Content": "关闭通讯恢复" + }, + { + "Id": "3416", + "Level": "4", + "Type": "布防", + "Content": "布防操作" + }, + { + "Id": "3417", + "Level": "4", + "Type": "布防", + "Content": "防区布防" + }, + { + "Id": "3421", + "Level": "4", + "Type": "出入恢复", + "Content": "拒绝出入-用户恢复" + }, + { + "Id": "3422", + "Level": "4", + "Type": "出入恢复", + "Content": "成功出入-用户恢复" + }, + { + "Id": "3441", + "Level": "4", + "Type": "布防", + "Content": "留守布防" + }, + { + "Id": "3450", + "Level": "4", + "Type": "出入恢复", + "Content": "非正常时段布防" + }, + { + "Id": "3451", + "Level": "4", + "Type": "出入恢复", + "Content": "过早布防" + }, + { + "Id": "3452", + "Level": "4", + "Type": "出入恢复", + "Content": "过迟布防" + }, + { + "Id": "3453", + "Level": "4", + "Type": "出入恢复", + "Content": "撤防失败恢复" + }, + { + "Id": "3454", + "Level": "4", + "Type": "出入恢复", + "Content": "布防失败恢复" + }, + { + "Id": "3455", + "Level": "4", + "Type": "出入恢复", + "Content": "自动布防失败恢复" + }, + { + "Id": "3456", + "Level": "4", + "Type": "出入恢复", + "Content": "部分布防恢复" + }, + { + "Id": "3457", + "Level": "4", + "Type": "出入恢复", + "Content": "外出错误恢复" + }, + { + "Id": "3458", + "Level": "4", + "Type": "出入恢复", + "Content": "操作员在现场恢复" + }, + { + "Id": "3459", + "Level": "4", + "Type": "出入恢复", + "Content": "最近布防恢复" + }, + { + "Id": "3480", + "Level": "4", + "Type": "用户恢复", + "Content": "开警号" + }, + { + "Id": "3520", + "Level": "5", + "Type": "停用恢复", + "Content": "停用警号/继电器恢复" + }, + { + "Id": "3521", + "Level": "5", + "Type": "停用恢复", + "Content": "警铃1停用恢复" + }, + { + "Id": "3522", + "Level": "5", + "Type": "停用恢复", + "Content": "警铃2停用恢复" + }, + { + "Id": "3523", + "Level": "5", + "Type": "停用恢复", + "Content": "停用警报继电器恢复" + }, + { + "Id": "3524", + "Level": "5", + "Type": "停用恢复", + "Content": "停用故障继电器恢复" + }, + { + "Id": "3525", + "Level": "5", + "Type": "停用恢复", + "Content": "逆反继电器恢复" + }, + { + "Id": "3530", + "Level": "5", + "Type": "停用恢复", + "Content": "停用系统周边恢复" + }, + { + "Id": "3540", + "Level": "5", + "Type": "停用恢复", + "Content": "停用系统周边恢复" + }, + { + "Id": "3551", + "Level": "5", + "Type": "停用恢复", + "Content": "通讯器停用恢复" + }, + { + "Id": "3552", + "Level": "5", + "Type": "停用恢复", + "Content": "无线发射器停用恢复" + }, + { + "Id": "3570", + "Level": "5", + "Type": "旁路恢复", + "Content": "防区旁路恢复" + }, + { + "Id": "3571", + "Level": "5", + "Type": "旁路恢复", + "Content": "火警旁路恢复" + }, + { + "Id": "3572", + "Level": "5", + "Type": "旁路恢复", + "Content": "24小时防区旁路恢复" + }, + { + "Id": "3573", + "Level": "5", + "Type": "旁路恢复", + "Content": "窃盗旁路恢复" + }, + { + "Id": "3574", + "Level": "5", + "Type": "旁路恢复", + "Content": "集体旁路恢复" + }, + { + "Id": "3580", + "Level": "5", + "Type": "旁路恢复", + "Content": "旁路操作恢复" + }, + { + "Id": "3601", + "Level": "6", + "Type": "测试恢复", + "Content": "手动测试恢复" + }, + { + "Id": "3602", + "Level": "6", + "Type": "测试恢复", + "Content": "定期测试恢复" + }, + { + "Id": "3603", + "Level": "6", + "Type": "测试恢复", + "Content": "定期无线发射器测试恢复" + }, + { + "Id": "3604", + "Level": "6", + "Type": "测试恢复", + "Content": "火警测试恢复" + }, + { + "Id": "3605", + "Level": "6", + "Type": "测试恢复", + "Content": "状态报告恢复" + }, + { + "Id": "3606", + "Level": "6", + "Type": "测试恢复", + "Content": "监听恢复" + }, + { + "Id": "3607", + "Level": "6", + "Type": "测试恢复", + "Content": "步行测试模式恢复" + }, + { + "Id": "3609", + "Level": "6", + "Type": "测试恢复", + "Content": "图像传输恢复" + }, + { + "Id": "3630", + "Level": "6", + "Type": "测试恢复", + "Content": "改动时间表恢复" + }, + { + "Id": "3631", + "Level": "6", + "Type": "测试恢复", + "Content": "改动例外时间表恢复" + }, + { + "Id": "3632", + "Level": "6", + "Type": "测试恢复", + "Content": "改动出入时间表恢复" + }, + { + "Id": "3695", + "Level": "6", + "Type": "用户恢复", + "Content": "提早布防恢复" + }, + { + "Id": "3696", + "Level": "6", + "Type": "用户恢复", + "Content": "未及时布防恢复" + }, + { + "Id": "3697", + "Level": "6", + "Type": "用户恢复", + "Content": "提早撤防恢复" + }, + { + "Id": "3698", + "Level": "6", + "Type": "用户恢复", + "Content": "未及时撤防恢复" + }, + { + "Id": "3699", + "Level": "6", + "Type": "用户恢复", + "Content": "超过测试间隔信号恢复" + }, + { + "Id": "36B0", + "Level": "6", + "Type": "测试恢复", + "Content": "手动测试操作恢复" + } +] \ No newline at end of file diff --git a/JiLinCpApp.sln b/JiLinCpApp.sln index ce259ec..88e67cf 100644 --- a/JiLinCpApp.sln +++ b/JiLinCpApp.sln @@ -7,6 +7,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JiLinApp", "JiLinApp\JiLinA EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EC.Util", "EC.Util\EC.Util.csproj", "{B5D7ED30-805C-4A5D-BD41-D2F910806340}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JiLinApp.Biz", "JiLinApp.Biz\JiLinApp.Biz.csproj", "{F58E724C-5F85-4A90-A1B9-45191D573D22}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JiLinApp.Docking", "JiLinApp.Docking\JiLinApp.Docking.csproj", "{EA25A38F-CE70-4C41-B332-B10C2CECE1B5}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -21,6 +25,14 @@ Global {B5D7ED30-805C-4A5D-BD41-D2F910806340}.Debug|Any CPU.Build.0 = Debug|Any CPU {B5D7ED30-805C-4A5D-BD41-D2F910806340}.Release|Any CPU.ActiveCfg = Release|Any CPU {B5D7ED30-805C-4A5D-BD41-D2F910806340}.Release|Any CPU.Build.0 = Release|Any CPU + {F58E724C-5F85-4A90-A1B9-45191D573D22}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F58E724C-5F85-4A90-A1B9-45191D573D22}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F58E724C-5F85-4A90-A1B9-45191D573D22}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F58E724C-5F85-4A90-A1B9-45191D573D22}.Release|Any CPU.Build.0 = Release|Any CPU + {EA25A38F-CE70-4C41-B332-B10C2CECE1B5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EA25A38F-CE70-4C41-B332-B10C2CECE1B5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EA25A38F-CE70-4C41-B332-B10C2CECE1B5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EA25A38F-CE70-4C41-B332-B10C2CECE1B5}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE