From bcf2bb98775b912989429866208778094cf790f3 Mon Sep 17 00:00:00 2001 From: CZ Date: Thu, 25 Jul 2024 11:58:14 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B6=88=E9=98=B2=E6=95=B0=E6=8D=AE=E6=B5=8B?= =?UTF-8?q?=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Service/Fire/Client/FireClient.cs | 60 ++++ Service/Fire/Client/FireMgr.cs | 75 ++++ Service/Fire/Codec/Decoder.cs | 152 ++++++++ Service/Fire/Codec/Encoder.cs | 41 +++ Service/Fire/Common/CRC16.cs | 42 +++ Service/Fire/Common/FireConst.cs | 335 ++++++++++++++++++ .../Fire/Handler/DataNoAddresReqHandler.cs | 34 ++ Service/Fire/Handler/DataReqHandler.cs | 57 +++ Service/Fire/Handler/IBaseHandler.cs | 8 + Service/Fire/Handler/PollingReqHandler.cs | 30 ++ Service/Fire/Msg/Apci.cs | 38 ++ Service/Fire/Msg/Fire/ASDU.cs | 36 ++ .../Fire/Msg/Fire/Req/BroadcastResetReq.cs | 9 + Service/Fire/Msg/Fire/Req/DataNoAddresReq.cs | 119 +++++++ Service/Fire/Msg/Fire/Req/DataReq.cs | 148 ++++++++ Service/Fire/Msg/Fire/Req/PollingReq.cs | 20 ++ Service/Fire/Msg/Host/Resp/PollingResp.cs | 28 ++ Service/Service.csproj | 1 + Service/Station/ChargeOrderService.cs | 2 +- WebStarter/Program.cs | 10 +- 20 files changed, 1243 insertions(+), 2 deletions(-) create mode 100644 Service/Fire/Client/FireClient.cs create mode 100644 Service/Fire/Client/FireMgr.cs create mode 100644 Service/Fire/Codec/Decoder.cs create mode 100644 Service/Fire/Codec/Encoder.cs create mode 100644 Service/Fire/Common/CRC16.cs create mode 100644 Service/Fire/Common/FireConst.cs create mode 100644 Service/Fire/Handler/DataNoAddresReqHandler.cs create mode 100644 Service/Fire/Handler/DataReqHandler.cs create mode 100644 Service/Fire/Handler/IBaseHandler.cs create mode 100644 Service/Fire/Handler/PollingReqHandler.cs create mode 100644 Service/Fire/Msg/Apci.cs create mode 100644 Service/Fire/Msg/Fire/ASDU.cs create mode 100644 Service/Fire/Msg/Fire/Req/BroadcastResetReq.cs create mode 100644 Service/Fire/Msg/Fire/Req/DataNoAddresReq.cs create mode 100644 Service/Fire/Msg/Fire/Req/DataReq.cs create mode 100644 Service/Fire/Msg/Fire/Req/PollingReq.cs create mode 100644 Service/Fire/Msg/Host/Resp/PollingResp.cs diff --git a/Service/Fire/Client/FireClient.cs b/Service/Fire/Client/FireClient.cs new file mode 100644 index 0000000..b2bc948 --- /dev/null +++ b/Service/Fire/Client/FireClient.cs @@ -0,0 +1,60 @@ +using System.Collections.Concurrent; +using System.Reflection; +using Autofac; +using Entity.DbModel.Station; +using HybirdFrameworkCore.Autofac; +using HybirdFrameworkCore.Autofac.Attribute; +using HybirdFrameworkCore.Entity; +using HybirdFrameworkCore.Redis; +using HybirdFrameworkCore.Utils; +using HybirdFrameworkDriver.Session; +using HybirdFrameworkDriver.TcpClient; +using log4net; +using Newtonsoft.Json; +using Repository.Station; +using Service.Fire.Codec; +using Service.Fire.Common; +using Service.Fire.Handler; +using SqlSugar; + +namespace Service.Fire.Client; + +[Scope("InstancePerDependency")] +public class FireClient : TcpClient +{ + /// + /// 消防设备编号 + /// + public string Sn { get; set; } + + private ILog Log() + { + var name = "Fire"; + ILog logger = LogManager.GetLogger(name); + + Console.WriteLine(name + "-" + logger.GetHashCode()); + return logger; + } + /// + /// + /// + /// + public bool Connect() + { + base.BaseConnect(); + Log().Info("Fire connect succeed"); + return Connected; + } + + /// + /// + /// + /// + /// + public void SessionAttr(string sn) + { + ChannelUtils.AddAttr(Channel, FireConst.FireSn, sn); + ChannelUtils.AddAttr(Channel, FireConst.EqmTypeNo, sn); + ChannelUtils.AddAttr(Channel, FireConst.EqmCode, sn); + } +} \ No newline at end of file diff --git a/Service/Fire/Client/FireMgr.cs b/Service/Fire/Client/FireMgr.cs new file mode 100644 index 0000000..841131a --- /dev/null +++ b/Service/Fire/Client/FireMgr.cs @@ -0,0 +1,75 @@ +using System.Collections.Concurrent; +using Autofac; +using DotNetty.Transport.Channels; +using HybirdFrameworkCore.Autofac; +using HybirdFrameworkDriver.Session; +using log4net; +using Service.Fire.Common; + +namespace Service.Fire.Client; + +public class FireMgr +{ + private static readonly ILog Log = LogManager.GetLogger(typeof(FireMgr)); + + public static readonly ConcurrentDictionary Dictionary = new(); + + public static void ConnClient() + { + Log.Info($"begin to connect fire"); + FireClient client = AppInfo.Container.Resolve(); + client.AutoReconnect = true; + client.Sn = FireConst.Sn; + client.LogName = "Fire" + FireConst.EqmCode; + client.ConnectedEventHandler += (sender, b) => + { + client.SessionAttr(FireConst.Sn); + }; + client.InitBootstrap(FireConst.Ip, FireConst.Port); + + Task.Run(() => + { + client.Connect(); + client.SessionAttr(FireConst.Sn); + Log.Info($"succeed to connect loading..."); + }); + + AddBySn(FireConst.Sn, client); + Log.Info($"begin to connect fire"); + } + public static void AddBySn(string sn, FireClient client) + { + Dictionary[sn] = client; + } + /// + /// 通过channel获取client + /// + /// + /// + /// 获取不到,client则为空 + /// + public static bool TryGetClient(IChannel channel, out string sn, out FireClient? client) + { + string? snt = ChannelUtils.GetAttr(channel, FireConst.EqmCode); + + if (!string.IsNullOrWhiteSpace(snt)) + { + var chargerClient = GetBySn(snt); + if (chargerClient != null) + { + sn = snt; + client = chargerClient; + return true; + } + } + + sn = string.Empty; + client = null; + return false; + } + public static FireClient? GetBySn(string sn) + { + Dictionary.TryGetValue(sn, out var o); + return o; + } +} \ No newline at end of file diff --git a/Service/Fire/Codec/Decoder.cs b/Service/Fire/Codec/Decoder.cs new file mode 100644 index 0000000..6669e30 --- /dev/null +++ b/Service/Fire/Codec/Decoder.cs @@ -0,0 +1,152 @@ +using System.Diagnostics; +using DotNetty.Buffers; +using DotNetty.Codecs; +using DotNetty.Transport.Channels; +using HybirdFrameworkCore.Utils; +using HybirdFrameworkDriver.Session; +using log4net; +using Newtonsoft.Json; +using Service.Cloud.Msg; +using Service.Fire.Common; +using Service.Fire.Msg; +using Service.Fire.Msg.Fire; +using Service.Fire.Msg.Fire.Req; + +namespace Service.Fire.Codec; + +public class Decoder : ByteToMessageDecoder +{ + private readonly IByteBuffer[] _delimiters = { Unpooled.CopiedBuffer(FireConst.StartChar) }; + + private ILog Log(string? chargerSn) + { + if (ObjUtils.IsNotNullOrWhiteSpace(chargerSn)) + { + return LogManager.GetLogger("Charger" + chargerSn); + } + + return LogManager.GetLogger(typeof(Decoder)); + } + + protected override void Decode(IChannelHandlerContext context, IByteBuffer buffer, List output) + { + string? chargerSn = ChannelUtils.GetAttr(context.Channel, FireConst.FireSn); + IByteBuffer? delimiter = FindDelimiter(buffer); + if (delimiter != null) + { + //分隔符索引 + int delimiterIndex = IndexOf(buffer, delimiter); + //帧长索引 + int frameLengthIndex = delimiterIndex + delimiter.Capacity + 1; + + if (delimiterIndex > 0) + { + buffer.SkipBytes(delimiterIndex); + return; + } + + if (buffer.ReadableBytes < frameLengthIndex + 2) + { + // 数据不足,等待更多数据 + return; + } + + // 读取长度字段 + byte frameLength = buffer.GetByte(buffer.ReaderIndex + frameLengthIndex); + //总帧长 + int totalFrameLength = delimiterIndex + delimiter.Capacity + 2 + 2 + frameLength; + // 最小总帧长过滤 + if (totalFrameLength < 5) + { + return; + } + + if (buffer.ReadableBytes < totalFrameLength) + { + // 数据不足,等待更多数据 + return; + } + + byte[]? data = null; + try + { + ASDU asdu = Parse(buffer, totalFrameLength, delimiter, out data); + Log(chargerSn) + .Info( + $"receive {BitUtls.BytesToHexStr(data)}:{JsonConvert.SerializeObject(asdu)} from {chargerSn}"); + output.Add(asdu); + } + catch (Exception e) + { + Log(chargerSn).Error($"decode fail msg={BitUtls.BytesToHexStr(data)}"); + } + } + } + + private IByteBuffer? FindDelimiter(IByteBuffer buffer) + { + foreach (IByteBuffer delimiter in _delimiters) + { + int delimiterIndex = IndexOf(buffer, delimiter); + if (delimiterIndex >= 0) + { + return delimiter; + } + } + + return null; + } + + private static int IndexOf(IByteBuffer haystack, IByteBuffer needle) + { + for (int i = haystack.ReaderIndex; i < haystack.WriterIndex; i++) + { + int num = i; + int j; + for (j = 0; j < needle.Capacity && haystack.GetByte(num) == needle.GetByte(j); j++) + { + num++; + if (num == haystack.WriterIndex && j != needle.Capacity - 1) + { + return -1; + } + } + + if (j == needle.Capacity) + { + return i - haystack.ReaderIndex; + } + } + + return -1; + } + + public ASDU Parse(IByteBuffer byteBuffer, int totalFrameLength, IByteBuffer delimiter, out byte[] data) + { + data = new byte[totalFrameLength]; + byteBuffer.ReadBytes(data); + + int removeIndex = delimiter.Capacity; + + ushort cmd = data[2 + removeIndex]; + byte[] bytes = new byte[data[2]]; + Array.Copy(data, 2 + removeIndex, bytes, 0, bytes.Length); + byte length = data[2]; + ASDU asdu = cmd switch + { + 0xA1 => ModelConvert.Decode(bytes), + 0xA2 => ModelConvert.Decode(bytes), + 0xA5 => length switch + { + 20 => ModelConvert.Decode(bytes), + 40 => ModelConvert.Decode(bytes), + _ => new ASDU(), + }, + + _ => new ASDU(), + }; + ASDU.ParseHeader(data, asdu); + + return asdu; + } +} \ No newline at end of file diff --git a/Service/Fire/Codec/Encoder.cs b/Service/Fire/Codec/Encoder.cs new file mode 100644 index 0000000..836f853 --- /dev/null +++ b/Service/Fire/Codec/Encoder.cs @@ -0,0 +1,41 @@ +using DotNetty.Buffers; +using DotNetty.Codecs; +using DotNetty.Transport.Channels; +using HybirdFrameworkCore.Utils; +using HybirdFrameworkDriver.Session; +using log4net; +using Newtonsoft.Json; +using Service.Fire.Common; +using Service.Fire.Msg; + +namespace Service.Fire.Codec; + +public class Encoder: MessageToByteEncoder +{ + private ILog? Log(string? chargerSn) + { + if (ObjUtils.IsNotNullOrWhiteSpace(chargerSn)) + { + return LogManager.GetLogger("fire" + chargerSn); + } + return LogManager.GetLogger(typeof(Encoder)); + } + + /// + /// + /// + /// + /// + /// + protected override void Encode(IChannelHandlerContext context, APCI obj, IByteBuffer output) + { + + string? sn = ChannelUtils.GetAttr(context.Channel, FireConst.FireSn); + byte[] bytes = obj.ToBytes(); + Log(sn)?.Info( + $"send {BitUtls.BytesToHexStr(bytes)}:{JsonConvert.SerializeObject(obj)} to {ChannelUtils.GetAttr(context.Channel, FireConst.FireSn)}"); + + + context.WriteAndFlushAsync(bytes); + } +} \ No newline at end of file diff --git a/Service/Fire/Common/CRC16.cs b/Service/Fire/Common/CRC16.cs new file mode 100644 index 0000000..0e7ee7a --- /dev/null +++ b/Service/Fire/Common/CRC16.cs @@ -0,0 +1,42 @@ +using System.Text; + +namespace Service.Fire.Common; + +public class CRC16 +{ + private const ushort Polynomial = 0x1021; + private ushort[] crcTable = new ushort[256]; + + public CRC16() + { + for (int i = 0; i < 256; i++) + { + ushort value = 0; + ushort temp = (ushort)(i << 8); + for (byte j = 0; j < 8; j++) + { + if (((value ^ temp) & 0x8000) != 0) + { + value = (ushort)((value << 1) ^ Polynomial); + } + else + { + value = (ushort)(value << 1); + } + temp = (ushort)(temp << 1); + } + crcTable[i] = value; + } + } + + public ushort ComputeChecksum(byte[] bytes) + { + ushort crc = 0xFFFF; // 初始值 + for (int i = 0; i < bytes.Length; i++) + { + byte index = (byte)((crc ^ bytes[i]) & 0xff); + crc = (ushort)((crc >> 8) ^ crcTable[index]); + } + return crc; + } +} \ No newline at end of file diff --git a/Service/Fire/Common/FireConst.cs b/Service/Fire/Common/FireConst.cs new file mode 100644 index 0000000..6d54b32 --- /dev/null +++ b/Service/Fire/Common/FireConst.cs @@ -0,0 +1,335 @@ +using DotNetty.Common.Utilities; + +namespace Service.Fire.Common; + +public class FireConst +{ + public static readonly AttributeKey FireSn = AttributeKey.ValueOf("fire_sn");//fire + public static readonly AttributeKey EqmTypeNo = AttributeKey.ValueOf("eqm_type_no");//5 + public static readonly AttributeKey EqmCode = AttributeKey.ValueOf("eqm_code");//fire + //public static readonly AttributeKey DestAddr = AttributeKey.ValueOf("dest_addr");// + public static readonly byte[] StartChar = { 0xF0 /* ,0xEE*/ }; + + public static string Ip = "127.0.0.1"; + public static int Port = 502; + public static string Sn = "fire"; + public static string No = "1"; + public static string Code = "5"; + + /// + /// 功能属性说明 + /// + /// + /// + public static string FunctionalAttribute(byte bvalue) + { + switch (bvalue) + { + case 0xC1: return "启动"; + case 0xC2: return "故障"; + case 0xC3: return "火警"; + case 0xC5: return "屏蔽"; + case 0xC6: return "其它"; + case 0xC7: return "监管"; + default: return ""; + } + } + + /// + /// 信息类型说明 + /// + /// + /// + public static string InformationType(byte bvalue) + { + switch (bvalue) + { + case 0x01: return "探头"; + case 0x02: return "模块"; + case 0x03: return "回路"; + case 0x04: return "主电"; + case 0x05: return "备电"; + case 0x06: return "计算"; + case 0x07: return "显示盘"; + case 0x08: return "多线"; + case 0x09: return "控制器"; + case 0x0A: return "网络模"; + case 0x0B: return "充电"; + case 0x0C: return "系统板"; + case 0x1B: return "无线终端"; + case 0x1C: return "无线中继器"; + case 0x1D: return "气灭终端"; + default: return ""; + } + } + + + /// + /// 设备状态说明 + /// + /// + /// + public static string DeviceStatus(byte bvalue) + { + switch (bvalue) + { + case 0x00: return "正常"; + case 0x01: return "地址丢失"; + case 0x02: return "设备错误"; + case 0x03: return "重码"; + case 0x04: return "动作失败"; + case 0x05: return "火警"; + case 0x06: return "现场动作"; + case 0x07: return "动作成功"; + case 0x08: return "开路"; + case 0x09: return "故障"; + case 0x0A: return "故障恢复"; + case 0x0B: return "短路"; + case 0x0E: return "多线短路"; + case 0x0F: return "多线开路"; + case 0x10: return "多线故障"; + case 0x11: return "多线正常"; + case 0x12: return "自诊断故障"; + case 0x13: return "污染"; + case 0x14: return "通信故障"; + case 0x15: return "通信正常"; + case 0x16: return "flash故障"; + case 0x17: return "flash故障恢复"; + case 0x18: return "多线启动"; + case 0x19: return "多线停止"; + case 0x1A: return "多线启成功"; + case 0x1B: return "多线停成功"; + case 0x1C: return "多线现场启"; + case 0x1D: return "多线现场停"; + case 0x1E: return "多线启失败"; + case 0x1F: return "多线停失败"; + case 0x21: return "开机"; + case 0x22: return "复位"; + case 0x23: return "手动转自动"; + case 0x24: return "火警确认"; + case 0x25: return "预警确认"; + case 0x26: return "预警复位"; + case 0x28: return "屏蔽"; + case 0x29: return "屏蔽解除"; + case 0x2A: return "屏蔽"; + case 0x2B: return "屏蔽解除"; + case 0x2C: return "手动启动"; + case 0x2D: return "手动停止"; + case 0x2E: return "联动启动"; + case 0x2F: return "联动延时"; + case 0x30: return "总线短路"; + case 0x31: return "24V总线异常"; + case 0x32: return "24V总线漏电流异常"; + case 0x33: return "总线参考电压异常"; + case 0x34: return "5v总线漏电流异常"; + case 0x35: return "5V总线电压异常"; + case 0x36: return "0V总线电压异常"; + case 0x37: return "0V总线漏电流异常"; + case 0x38: return "高压中断"; + case 0x39: return "总线电压过低"; + case 0x3A: return "总线电压过高"; + case 0x3B: return "正电流异常"; + case 0x3C: return "漏电流异常"; + case 0x3D: return "总线负线异常"; + case 0x3E: return "总线正线异常"; + case 0x3F: return "总线不能上电"; + case 0x40: return "总线正常"; + case 0x41: return "预警"; + case 0x42: return "故障"; + case 0x43: return "多线启动"; + case 0x44: return "多线停止"; + case 0x45: return "多线启成功"; + case 0x46: return "多线启失败"; + case 0x47: return "多线停成功"; + case 0x48: return "多线停失败"; + case 0x55: return "气体启动"; + case 0x56: return "气体停止"; + case 0x7E: return "输入端开路"; + case 0x7F: return "输出端开路"; + case 0x80: return "输入输出端开路"; + case 0x81: return "输入端短路"; + case 0x82: return "输出端短路"; + case 0x83: return "输入输出端短路"; + case 0x8C: return "自检"; + case 0x99: return "电池低电压"; + case 0x9A: return "终端设备离线"; + case 0x9B: return "疏散启动"; + case 0x9C: return "底座低电压"; + case 0x9D: return "探测器低电压"; + case 0x9E: return "中继器离线"; + case 0x9F: return "中继器电池低电压"; + case 0xA0: return "主电故障"; + case 0xA1: return "备电故障"; + case 0xA2: return "中继器正常"; + case 0xB0: return "手动关门"; + case 0xB1: return "联动关门"; + case 0xB2: return "现场关门"; + case 0xB3: return "关门到位"; + case 0xB4: return "关门到位(左)"; + case 0xB5: return "关门到位(右)"; + case 0xB6: return "现场开门"; + case 0xB7: return "开门到位"; + case 0xB8: return "现场开门(左)"; + case 0xB9: return "现场开门(右)"; + case 0xBA: return "现场开门(双)"; + case 0xBB: return "开门未到位"; + case 0xBC: return "关门未到位"; + case 0xEC: return "气体反馈失败"; + case 0xED: return "气体现场动作"; + default: return ""; + } + } + + /// + /// 设备类型说明 + /// + /// + /// + public static string DeviceType(byte bvalue) + { + switch (bvalue) + { + case 00 : return "离子探头"; + case 01 : return "光电探头"; + case 02 : return "感温探头"; + case 03 : return "烟复合头"; + case 04 : return "光复合头"; + case 05 : return "三复合头"; + case 06 : return "激光探头"; + case 07 : return "一氧化碳"; + case 08 : return "可燃气体"; + case 09 : return "空气采样"; + case 10 : return "手动按钮"; + case 11 : return "消防栓钮"; + case 12 : return "压力开关"; + case 13 : return "水流指示"; + case 14 : return "信号阀"; + case 15 : return "接口模块"; + case 16 : return "消防栓泵启"; + case 17 : return "消防栓泵停"; + case 18 : return "喷淋泵启"; + case 19 : return "喷淋泵停"; + case 20 : return "泡沫泵启"; + case 21 : return "泡沫蹦停"; + case 22 : return "备用泵启"; + case 23 : return "备用泵停"; + case 24 : return "稳压泵启"; + case 25 : return "稳压泵停"; + case 26 : return "雨淋泵启"; + case 27 : return "雨淋泵停"; + case 28 : return "正压风机启"; + case 29 : return "正压风机停"; + case 30 : return "排烟风机启"; + case 31 : return "排烟风机停"; + case 32 : return "干粉系统启"; + case 33 : return "干粉系统停"; + case 34 : return "新风机启"; + case 35 : return "新风机停"; + case 36 : return "发电机"; + case 37 : return "消防泵故障"; + case 38 : return "喷淋泵故障"; + case 39 : return "稳压泵故障"; + case 40 : return "水冷却"; + case 41 : return "泡沫喷淋"; + case 42 : return "送风机启"; + case 43 : return "送风机停"; + case 44 : return "讯响器"; + case 45 : return "试验阀"; + case 46 : return "消防栓灯"; + case 47 : return "泡沫阀"; + case 48 : return "空调"; + case 49 : return "消防广播"; + case 50 : return "消防警铃"; + case 51 : return "防火门"; + case 52 : return "防火阀"; + case 53 : return "排烟阀"; + case 54 : return "电梯"; + case 55 : return "疏散指示"; + case 56 : return "事故照明"; + case 57 : return "非消防电"; + case 58 : return "水幕"; + case 59 : return "送风口"; + case 60 : return "挡烟垂壁"; + case 61 : return "雨淋阀"; + case 62 : return "湿式阀"; + case 63 : return "水冷却泵"; + case 64 : return "卷帘半降"; + case 65 : return "卷帘全降"; + case 66 : return "排风机启"; + case 67 : return "排烟风机"; + case 68 : return "气体灭火器"; + case 69 : return "气体灭火停"; + case 70 : return "备用设备启"; + case 71 : return "备用设备停"; + case 72 : return "码座离子"; + case 73 : return "码座光电"; + case 74 : return "码座感温"; + case 75 : return "低倍泡沫"; + case 76 : return "高倍泡沫"; + case 77 : return "水雾"; + case 78 : return "普通光电"; + case 79 : return "普通温感"; + case 80 : return "气体报警"; + case 81 : return "气体喷放"; + case 82 : return "气体故障"; + case 83 : return "气体失重"; + case 84 : return "高水位"; + case 85 : return "低水位"; + case 86 : return "电话模块"; + case 87 : return "总线电话"; + case 88 : return "总线显示盘"; + case 89 : return "声光报警器"; + case 90 : return "气体灭火盘"; + case 91 : return "显示盘"; + case 92 : return "探测器"; + case 93 : return "模块"; + case 94 : return "启停按钮"; + case 100 : return "输入2"; + case 101 : return "面板按钮"; + case 102 : return "传感器"; + case 103 : return "漏电传感器"; + case 104 : return "温度传感器"; + case 105 : return "电流传感器"; + case 106 : return "FS8110"; + case 107 : return "FS8120"; + case 108 : return "FS8130"; + case 109 : return "FS8150"; + case 110 : return "面板启动"; + case 111 : return "面板停止"; + case 112 : return "总线"; + case 113 : return "电源箱"; + case 130 : return "电动先关"; + case 131 : return "电动后关"; + case 132 : return "释放器先放"; + case 133 : return "释放器后放"; + case 134 : return "门磁左开"; + case 135 : return "门磁右开"; + case 136 : return "门磁双开"; + case 137 : return "火警模块"; + case 138 : return "火警输入"; + case 139 : return "常闭防火门"; + case 140 : return "常开防火门"; + + default: return ""; + } + } + + /// + /// 获取16进制字符串的字节数组 + /// + /// hexString 16进制字符串 + /// 字节数组 + public static byte[] ToByteByHexStr(string hexString) + { + if (hexString == null) + return null; + + hexString = hexString.Replace(" ", ""); + if ((hexString.Length % 2) != 0) + hexString += " "; + byte[] returnBytes = new byte[hexString.Length / 2]; + for (int i = 0; i < returnBytes.Length; i++) + returnBytes[i] = Convert.ToByte(hexString.Substring(i * 2, 2), 16); + return returnBytes; + } +} \ No newline at end of file diff --git a/Service/Fire/Handler/DataNoAddresReqHandler.cs b/Service/Fire/Handler/DataNoAddresReqHandler.cs new file mode 100644 index 0000000..a1cc6bd --- /dev/null +++ b/Service/Fire/Handler/DataNoAddresReqHandler.cs @@ -0,0 +1,34 @@ +using DotNetty.Transport.Channels; +using HybirdFrameworkCore.Autofac.Attribute; +using log4net; +using Service.Fire.Client; +using Service.Fire.Common; +using Service.Fire.Msg.Fire.Req; + +namespace Service.Fire.Handler; + +/// +/// 发送事件 +/// +/// 1,保存日志到log +/// +/// +[Order(8)] +[Scope("InstancePerDependency")] +public class DataNoAddresReqHandler : SimpleChannelInboundHandler, IBaseHandler +{ + private static readonly ILog Log = LogManager.GetLogger(typeof(DataNoAddresReqHandler)); + protected override void ChannelRead0(IChannelHandlerContext ctx, DataNoAddresReq msg) + { + if (FireMgr.TryGetClient(ctx.Channel, out var sn, out var client)) + { + Log.Info($"receive {msg} from {sn}"); + + string datatime = (new DateTime(2000 + msg.Years, msg.Month, msg.Day, msg.Hour, msg.Minute, msg.Seconds)).ToString(); + string functionalAttribute = FireConst.FunctionalAttribute(msg.FunctionalAttribute); + string informationType = FireConst.InformationType(msg.InformationType); + string deviceStatus = FireConst.DeviceStatus(msg.DeviceStatus); + string deviceType = FireConst.DeviceType(msg.DeviceType); + } + } +} \ No newline at end of file diff --git a/Service/Fire/Handler/DataReqHandler.cs b/Service/Fire/Handler/DataReqHandler.cs new file mode 100644 index 0000000..9f3044e --- /dev/null +++ b/Service/Fire/Handler/DataReqHandler.cs @@ -0,0 +1,57 @@ +using System.Text; +using DotNetty.Transport.Channels; +using HybirdFrameworkCore.Autofac.Attribute; +using log4net; +using Service.Fire.Client; +using Service.Fire.Common; +using Service.Fire.Msg.Fire.Req; +using Service.Fire.Msg.Host.Resp; + +namespace Service.Fire.Handler +{ + /// + /// 发送事件 + /// + /// 1,保存日志到log + /// + /// + [Order(8)] + [Scope("InstancePerDependency")] + public class DataReqHandler : SimpleChannelInboundHandler, IBaseHandler + { + private static readonly ILog Log = LogManager.GetLogger(typeof(PollingReqHandler)); + + protected override void ChannelRead0(IChannelHandlerContext ctx, DataReq msg) + { + if (FireMgr.TryGetClient(ctx.Channel, out var sn, out var client)) + { + Log.Info($"receive {msg} from {sn}"); + + string datatime = + (new DateTime(2000 + msg.Years, msg.Month, msg.Day, msg.Hour, msg.Minute, msg.Seconds)).ToString(); + string functionalAttribute = FireConst.FunctionalAttribute(msg.FunctionalAttribute); + string informationType = FireConst.InformationType(msg.InformationType); + string deviceStatus = FireConst.DeviceStatus(msg.DeviceStatus); + string deviceType = FireConst.DeviceType(msg.DeviceType); + + string strDecode = ""; + for (int i = 0; i < msg.BytesLocationCode.Length; i++) + { + if (msg.BytesLocationCode[i] != 0) + { + string strhex = msg.BytesLocationCode[i].ToString("X"); + strDecode += strhex; + } + else + { + break; + } + } + + byte[] bytesVelNo = FireConst.ToByteByHexStr(strDecode); + Encoding gbEcoding = Encoding.GetEncoding("gb2312"); + string strVelNo = gbEcoding.GetString(bytesVelNo); + } + } + } +} \ No newline at end of file diff --git a/Service/Fire/Handler/IBaseHandler.cs b/Service/Fire/Handler/IBaseHandler.cs new file mode 100644 index 0000000..19cd98c --- /dev/null +++ b/Service/Fire/Handler/IBaseHandler.cs @@ -0,0 +1,8 @@ +using DotNetty.Transport.Channels; + +namespace Service.Fire.Handler; + +public interface IBaseHandler : IChannelHandler +{ + +} \ No newline at end of file diff --git a/Service/Fire/Handler/PollingReqHandler.cs b/Service/Fire/Handler/PollingReqHandler.cs new file mode 100644 index 0000000..67a9542 --- /dev/null +++ b/Service/Fire/Handler/PollingReqHandler.cs @@ -0,0 +1,30 @@ +using DotNetty.Transport.Channels; +using HybirdFrameworkCore.Autofac.Attribute; +using log4net; +using Service.Fire.Client; +using Service.Fire.Msg.Fire.Req; +using Service.Fire.Msg.Host.Resp; + +namespace Service.Fire.Handler; + + +/// +/// 巡检 +/// +/// 1,保存日志到log +/// +/// +[Order(8)] +[Scope("InstancePerDependency")] +public class PollingReqHandler : SimpleChannelInboundHandler, IBaseHandler +{ + private static readonly ILog Log = LogManager.GetLogger(typeof(PollingReqHandler)); + protected override void ChannelRead0(IChannelHandlerContext ctx, PollingReq msg) + { + if (FireMgr.TryGetClient(ctx.Channel, out var sn, out var client)) + { + Log.Info($"receive {msg} from {sn}"); + ctx.Channel.WriteAndFlushAsync(new PollingResp()); + } + } +} \ No newline at end of file diff --git a/Service/Fire/Msg/Apci.cs b/Service/Fire/Msg/Apci.cs new file mode 100644 index 0000000..b474644 --- /dev/null +++ b/Service/Fire/Msg/Apci.cs @@ -0,0 +1,38 @@ +using HybirdFrameworkDriver.Common; +using Service.Fire.Common; + +namespace Service.Fire.Msg; + +public abstract class APCI : IToBytes +{ + /// + /// 目标地址 + /// + public byte DestAddress { get; set; } + /// + /// 数据长度 + /// + public byte Length { get; set; } + + public byte CrcHight { get; set; } + public byte CrcLow { get; set; } + + public byte[] ToBytes() + { + var bodyBytes = GetBytes(); + var list = new List(); + list.AddRange(FireConst.StartChar); + list.Add(DestAddress); + list.Add((byte)bodyBytes.Length); + + list.AddRange(bodyBytes); + + CRC16 CRC16 = new(); + + //进行CRC校验 + list.AddRange(BitConverter.GetBytes(CRC16.ComputeChecksum(list.ToArray()))); + + return list.ToArray(); + } + public abstract byte[] GetBytes(); +} \ No newline at end of file diff --git a/Service/Fire/Msg/Fire/ASDU.cs b/Service/Fire/Msg/Fire/ASDU.cs new file mode 100644 index 0000000..e909689 --- /dev/null +++ b/Service/Fire/Msg/Fire/ASDU.cs @@ -0,0 +1,36 @@ +using DotNetty.Buffers; +using HybirdFrameworkCore.Utils; +using Service.Fire.Common; + +namespace Service.Fire.Msg.Fire; + +public class ASDU : APCI +{ + public override byte[] GetBytes() + { + var list = new List(); + + list.AddRange(ModelConvert.Encode(this)); + return list.ToArray(); + } + + public static void ParseHeader(byte[] data, ASDU asdu) + { + IByteBuffer byteBuffer = Unpooled.WrappedBuffer(data); + try + { + var start = FireConst.StartChar.Length - 1; + + asdu.DestAddress = byteBuffer.GetByte(start + 1); + asdu.Length = byteBuffer.GetByte(start + 2); + } + catch (Exception e) + { + throw e; + } + finally + { + byteBuffer.Release(); + } + } +} \ No newline at end of file diff --git a/Service/Fire/Msg/Fire/Req/BroadcastResetReq.cs b/Service/Fire/Msg/Fire/Req/BroadcastResetReq.cs new file mode 100644 index 0000000..3d3d7e0 --- /dev/null +++ b/Service/Fire/Msg/Fire/Req/BroadcastResetReq.cs @@ -0,0 +1,9 @@ +namespace Service.Fire.Msg.Fire.Req; + +/// +/// 广播复位 +/// +public class BroadcastResetReq:ASDU +{ + +} \ No newline at end of file diff --git a/Service/Fire/Msg/Fire/Req/DataNoAddresReq.cs b/Service/Fire/Msg/Fire/Req/DataNoAddresReq.cs new file mode 100644 index 0000000..d61d06b --- /dev/null +++ b/Service/Fire/Msg/Fire/Req/DataNoAddresReq.cs @@ -0,0 +1,119 @@ +using HybirdFrameworkCore.Autofac.Attribute; + +namespace Service.Fire.Msg.Fire.Req; + +public class DataNoAddresReq:ASDU +{ + /// + /// 功能码/命令字 + /// + [Property(0, 8)] + public byte CommandWord { get; set; } + /// + /// 功能属性 + /// + [Property(8, 8)] + public byte FunctionalAttribute { get; set; } + + /// + /// 信息类型 + /// + [Property(16, 8)] + public byte InformationType { get; set; } + + /// + /// 设备状态 + /// + [Property(24, 8)] + public byte DeviceStatus { get; set; } + + /// + /// 网络号 + /// + [Property(32, 8)] + public byte NetworkNumber { get; set; } + + /// + /// 主从和机号 + /// + [Property(40, 8)] + public byte MasterSlaveAndPhoneNumber { get; set; } + + /// + /// 年 + /// + [Property(48, 8)] + public byte Years { get; set; } + + /// + /// 月 + /// + [Property(56, 8)] + public byte Month { get; set; } + + /// + /// 日 + /// + [Property(64, 8)] + public byte Day { get; set; } + + /// + /// 时 + /// + [Property(72, 8)] + public byte Hour { get; set; } + + /// + /// 分 + /// + [Property(80, 8)] + public byte Minute { get; set; } + + /// + /// 秒 + /// + [Property(88, 8)] + public byte Seconds { get; set; } + + /// + /// 回路 + /// + [Property(96, 8)] + public byte Loop { get; set; } + + /// + /// 地址 + /// + [Property(104, 8)] + public byte Address { get; set; } + + /// + /// 栋 + /// + [Property(112, 8)] + public byte Building { get; set; } + + /// + /// 区 + /// + [Property(120, 8)] + public byte Area { get; set; } + + /// + /// 层 + /// + [Property(128, 8)] + public byte Layer { get; set; } + + /// + /// 号 + /// + [Property(136, 16)] + public ushort No { get; set; } + + /// + /// 设备类型 + /// + [Property(152, 8)] + public byte DeviceType { get; set; } +} \ No newline at end of file diff --git a/Service/Fire/Msg/Fire/Req/DataReq.cs b/Service/Fire/Msg/Fire/Req/DataReq.cs new file mode 100644 index 0000000..2f62e44 --- /dev/null +++ b/Service/Fire/Msg/Fire/Req/DataReq.cs @@ -0,0 +1,148 @@ +using HybirdFrameworkCore.Autofac.Attribute; + +namespace Service.Fire.Msg.Fire.Req; + +public class DataReq : ASDU +{ + /// + /// 功能码/命令字 + /// + [Property(0, 8)] + public byte CommandWord { get; set; } + /// + /// 功能属性 + /// 启动 0xC1 + /// 故障 0xC2 + /// 火警 0xC3 + /// 屏蔽 0xC5 + /// 其它 0xC6 + /// 监管 0xC7 + /// + [Property(8, 8)] + public byte FunctionalAttribute { get; set; } + + /// + /// 信息类型 + /// 探头 0x01 + /// 模块 0x02 + /// 回路 0x03 + /// 主电 0x04 + /// 备电 0x05 + /// 计算机 0x06 + /// 显示盘 0x07 + /// 多线 0x08 + /// 控制器 0x09 + /// 网络模块 0x0A + /// 充电 0x0B + /// 系统板 0x0C + /// 无线终端 0x1B + /// 无线中继器 0x1C + /// 气灭终端 0x1D + /// + [Property(16, 8)] + public byte InformationType { get; set; } + + /// + /// 设备状态 + /// + [Property(24, 8)] + public byte DeviceStatus { get; set; } + + /// + /// 网络号 + /// + [Property(32, 8)] + public byte NetworkNumber { get; set; } + + /// + /// 主从和机号 + /// + [Property(40, 8)] + public byte MasterSlaveAndPhoneNumber { get; set; } + + /// + /// 年 + /// + [Property(48, 8)] + public byte Years { get; set; } + + /// + /// 月 + /// + [Property(56, 8)] + public byte Month { get; set; } + + /// + /// 日 + /// + [Property(64, 8)] + public byte Day { get; set; } + + /// + /// 时 + /// + [Property(72, 8)] + public byte Hour { get; set; } + + /// + /// 分 + /// + [Property(80, 8)] + public byte Minute { get; set; } + + /// + /// 秒 + /// + [Property(88, 8)] + public byte Seconds { get; set; } + + /// + /// 回路 + /// + [Property(96, 8)] + public byte Loop { get; set; } + + /// + /// 地址 + /// + [Property(104, 8)] + public byte Address { get; set; } + + /// + /// 栋 + /// + [Property(112, 8)] + public byte Building { get; set; } + + /// + /// 区 + /// + [Property(120, 8)] + public byte Area { get; set; } + + /// + /// 层 + /// + [Property(128, 8)] + public byte Layer { get; set; } + + /// + /// 号 + /// + [Property(136, 16)] + public ushort No { get; set; } + + /// + /// 设备类型 + /// + [Property(152, 8)] + public byte DeviceType { get; set; } + + /// + /// 位置代码 + /// + [Property(160, 8*20)] + public string LocationCode { get; set; } + [Property(160, 8*20)] + public byte[] BytesLocationCode { get; set; } +} \ No newline at end of file diff --git a/Service/Fire/Msg/Fire/Req/PollingReq.cs b/Service/Fire/Msg/Fire/Req/PollingReq.cs new file mode 100644 index 0000000..16d0ea2 --- /dev/null +++ b/Service/Fire/Msg/Fire/Req/PollingReq.cs @@ -0,0 +1,20 @@ +using HybirdFrameworkCore.Autofac.Attribute; + +namespace Service.Fire.Msg.Fire.Req; + +/// +/// 巡检 +/// +public class PollingReq:ASDU +{ + /// + /// 数据段 + /// + [Property(0, 8)] + public byte DataSegment1 { get; set; } + /// + /// 数据段 + /// + [Property(8, 8)] + public byte DataSegment2 { get; set; } +} \ No newline at end of file diff --git a/Service/Fire/Msg/Host/Resp/PollingResp.cs b/Service/Fire/Msg/Host/Resp/PollingResp.cs new file mode 100644 index 0000000..17624f8 --- /dev/null +++ b/Service/Fire/Msg/Host/Resp/PollingResp.cs @@ -0,0 +1,28 @@ +using HybirdFrameworkCore.Autofac.Attribute; +using Service.Fire.Msg.Fire; + +namespace Service.Fire.Msg.Host.Resp; + +public class PollingResp: ASDU +{ + /// + /// 功能码/命令字 + /// + [Property(0, 8)] + public byte CommandWord { get; set; } + /// + /// 信息体1 + /// + [Property(8, 8)] + public byte Resp1 { get; set; } + /// + /// 信息体2 + /// + [Property(16, 8)] + public byte Resp2 { get; set; } + + public PollingResp() + { + CommandWord = 0xA2; + } +} \ No newline at end of file diff --git a/Service/Service.csproj b/Service/Service.csproj index d47b90f..991c943 100644 --- a/Service/Service.csproj +++ b/Service/Service.csproj @@ -37,6 +37,7 @@ + diff --git a/Service/Station/ChargeOrderService.cs b/Service/Station/ChargeOrderService.cs index e9695b1..350cb25 100644 --- a/Service/Station/ChargeOrderService.cs +++ b/Service/Station/ChargeOrderService.cs @@ -160,7 +160,7 @@ public class ChargeOrderService : BaseServices /// - /// + /// 充电订单手动上传云平台 /// /// /// diff --git a/WebStarter/Program.cs b/WebStarter/Program.cs index 3f4f0bc..6d61d27 100644 --- a/WebStarter/Program.cs +++ b/WebStarter/Program.cs @@ -14,6 +14,8 @@ using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.IdentityModel.Tokens; using Service.Cloud.Client; using Service.Execute; +using Service.Fire.Client; +using Service.Fire.Common; using Service.FireControl.Client; using Service.Plc.Client; using Service.RealTime; @@ -186,9 +188,15 @@ if (AppSettingsHelper.GetBool("fire", "enable")) FireControlMgr.Init(); } +//中卫消防 +if (AppSettingsHelper.GetBool("fireZw", "enable")) +{ + FireMgr.ConnClient(); +} + TaskInit.Init(); QuartzSchedulerFactory.Init(); app.Lifetime.ApplicationStopping.Register(QuartzSchedulerFactory.Shutdown); - +Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); app.Run();