C#-DL/T 645—2007协议

2020-12-18 18:32

阅读:586

标签:gif   sum   prot   Nid   fse   log   scl   pac   push   

  前面一篇写了97版的协议,今天就来看下07版的DL/T 645协议,总的来说,差别不是很大,也是就是数据项标识的不同。

  1.  帧格式

  技术图片

   帧格式是和之前97的一版是一样的,

  注意:

  (1)97一版忘了说,地址域是BCD码,若电表地址是112233445566,那么传输的字节就是0x66 0x55 0x44 0x33 0x22 0x11,即小端传输;

  (2)数据标识项与97的有差别,97的是两个字节,07的是4个字节,如图所示DI3 DI2 DI2 DI0;

  (3)依然要发送4个字节的FEH,来唤醒对方;

  (4)传输+-0x33的如图更加详细,即发送方+0x33后发送,接收方收到数据后-0x33进行处理;

  技术图片

  技术图片

   2. 数据项标识

  这次数据项标识采用发送请求块的方式进行请求数据,比如ABC相电压,只发送一次请求即可返回三相电压。

  如图所示可以请求00 01 FF 00正向有功电能数据块的数据,对方会把包含的所有数据都会返回,文档上写的是63个电能,实际上大多数电表是没有的,所以不用太计较那么多的数据,一般就是尖峰平谷4段。

  技术图片

   3. 数据库表,依然采用初始化数据表配置的方式进行处理(其实是先写的07版的协议...)

技术图片技术图片
DROP TABLE IF EXISTS `protocol_DLT645_07package`;
CREATE TABLE `protocol_DLT645_07package` (
  `id`                  int(11)     NOT NULL AUTO_INCREMENT,
  `devicetype`            int(11)     NOT NULL,                    -- 设备类型
  `blockaddr`           int(11)     NOT NULL,                    -- 块地址
  `blockname`           varchar(50) NOT NULL,                    -- 块名称
  `blockcontent`        varchar(50) NOT NULL,                    -- 块内容
  `visible`                int(11)     NOT NULL,                    -- 是否请求;0不请求;1请求;
  PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;

INSERT INTO `protocol_DLT645_07package`  VALUES (null, -1, 130816,     正向有功总电能数据块,     65536, 65792, 66048, 66304, 66560,         1); --0001FF00
INSERT INTO `protocol_DLT645_07package`  VALUES (null, -1, 196352,     反向有功总电能数据块,     131072, 131328, 131584, 131840, 132096,     1); --0002FF00
INSERT INTO `protocol_DLT645_07package`  VALUES (null, -1, 33685248,     ABC相电压数据块,             33620224, 33620480, 33620736,             1); --0201FF00
INSERT INTO `protocol_DLT645_07package`  VALUES (null, -1, 33750784,     ABC相电流数据块,             33685760, 33686016, 33686272,             1); --0202FF00
INSERT INTO `protocol_DLT645_07package`  VALUES (null, -1, 33816320,     瞬时总有功功率数据块,     33751040, 33751296, 33751552, 33751808,     1); --0203FF00
INSERT INTO `protocol_DLT645_07package`  VALUES (null, -1, 33881856,     瞬时总无功功率数据块,     33816576, 33816832, 33817088, 33817344,     1); --0204FF00
INSERT INTO `protocol_DLT645_07package`  VALUES (null, -1, 33947392,     瞬时总视在功率数据块,     33882112, 33882368, 33882624, 33882880,     1); --0205FF00
INSERT INTO `protocol_DLT645_07package`  VALUES (null, -1, 34012928,     总功率因数数据块,         33947648, 33947904, 33948160, 33948416,     1); --0206FF00
INSERT INTO `protocol_DLT645_07package`  VALUES (null, -1, 41943042,     电网频率数据,             41943042,                                 1); --02800002
INSERT INTO `protocol_DLT645_07package`  VALUES (null, -1, 41943048,     时钟电池电压,             41943048,                                 1); --02800008



DROP TABLE IF EXISTS `protocol_DLT645_07signal`;
CREATE TABLE `protocol_DLT645_07signal` (
  `id`                   int(11)     NOT NULL AUTO_INCREMENT,
  `devicetype`            int(11)     NOT NULL,                    -- 设备类型
  `signaladdr`           int(11)     NOT NULL,                    -- 信号地址
  `signalname`           varchar(50) NOT NULL,                    -- 信号名称
  `signalid`             int(11)     NOT NULL,                    -- 信号ID
  `datalength`           int(11)     NOT NULL,                    -- 数据长度,所占字节数
  `ratio`                float         NOT NULL,                    -- 变比
  `ismultiplifct`       int(11)     NOT NULL,                    -- 是否乘于互感器变比,变比在协议中配置
  `visible`               int(11)     NOT NULL,                    -- 是否需要解析;0不解析;1解析;
  PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;

INSERT INTO `protocol_DLT645_07signal` VALUES (null, -1, 65536,         正向有功总电能,              -1, 4, 0.01,         1, 1 );
INSERT INTO `protocol_DLT645_07signal` VALUES (null, -1, 65792,         (当前)正向有功费率1电量,  -1, 4, 0.01,         1, 1 );
INSERT INTO `protocol_DLT645_07signal` VALUES (null, -1, 66048,         (当前)正向有功费率2电量,  -1, 4, 0.01,         1, 1 );
INSERT INTO `protocol_DLT645_07signal` VALUES (null, -1, 66304,         (当前)正向有功费率3电量,  -1, 4, 0.01,         1, 1 );
INSERT INTO `protocol_DLT645_07signal` VALUES (null, -1, 66560,         (当前)正向有功费率4电量,  -1, 4, 0.01,         1, 1 );
INSERT INTO `protocol_DLT645_07signal` VALUES (null, -1, 131072,        反向有功总电能,              -1, 4, 0.01,         1, 1 );
INSERT INTO `protocol_DLT645_07signal` VALUES (null, -1, 131328,        (当前)反向有功费率1电量,  -1, 4, 0.01,         1, 1 );
INSERT INTO `protocol_DLT645_07signal` VALUES (null, -1, 131584,        (当前)反向有功费率2电量,  -1, 4, 0.01,         1, 1 );
INSERT INTO `protocol_DLT645_07signal` VALUES (null, -1, 131840,        (当前)反向有功费率3电量,  -1, 4, 0.01,         1, 1 );
INSERT INTO `protocol_DLT645_07signal` VALUES (null, -1, 132096,        (当前)反向有功费率4电量,  -1, 4, 0.01,         1, 1 );
INSERT INTO `protocol_DLT645_07signal` VALUES (null, -1, 33620224,     A相电压,                  -1, 2, 0.1,         0, 1 );
INSERT INTO `protocol_DLT645_07signal` VALUES (null, -1, 33620480,     B相电压,                  -1, 2, 0.1,         0, 1 );
INSERT INTO `protocol_DLT645_07signal` VALUES (null, -1, 33620736,     C相电压,                  -1, 2, 0.1,         0, 1 );
INSERT INTO `protocol_DLT645_07signal` VALUES (null, -1, 33685760,     A相电流,                  -1, 3, 0.001,     1, 1 );
INSERT INTO `protocol_DLT645_07signal` VALUES (null, -1, 33686016,     B相电流,                  -1, 3, 0.001,     1, 1 );
INSERT INTO `protocol_DLT645_07signal` VALUES (null, -1, 33686272,     C相电流,                  -1, 3, 0.001,     1, 1 );
INSERT INTO `protocol_DLT645_07signal` VALUES (null, -1, 33751040,     瞬时总有功功率,              -1, 3, 0.0001,     1, 1 );
INSERT INTO `protocol_DLT645_07signal` VALUES (null, -1, 33751296,     瞬时A相有功功率,             -1, 3, 0.0001,     1, 1 );
INSERT INTO `protocol_DLT645_07signal` VALUES (null, -1, 33751552,     瞬时B相有功功率,             -1, 3, 0.0001,     1, 1 );
INSERT INTO `protocol_DLT645_07signal` VALUES (null, -1, 33751808,     瞬时C相有功功率,             -1, 3, 0.0001,     1, 1 );
INSERT INTO `protocol_DLT645_07signal` VALUES (null, -1, 33816576,     瞬时总无功功率,              -1, 3, 0.0001,     0, 1 );
INSERT INTO `protocol_DLT645_07signal` VALUES (null, -1, 33816832,     瞬时A相无功功率,             -1, 3, 0.0001,     0, 1 );
INSERT INTO `protocol_DLT645_07signal` VALUES (null, -1, 33817088,     瞬时B相无功功率,             -1, 3, 0.0001,     0, 1 );
INSERT INTO `protocol_DLT645_07signal` VALUES (null, -1, 33817344,     瞬时C相无功功率,             -1, 3, 0.0001,     0, 1 );
INSERT INTO `protocol_DLT645_07signal` VALUES (null, -1, 33882112,     瞬时总视在功率,              -1, 3, 0.0001,     1, 1 );
INSERT INTO `protocol_DLT645_07signal` VALUES (null, -1, 33882368,     瞬时A相视在功率,             -1, 3, 0.0001,     1, 1 );
INSERT INTO `protocol_DLT645_07signal` VALUES (null, -1, 33882624,     瞬时B相视在功率,             -1, 3, 0.0001,     1, 1 );
INSERT INTO `protocol_DLT645_07signal` VALUES (null, -1, 33882880,     瞬时C相视在功率,             -1, 3, 0.0001,     1, 1 );
INSERT INTO `protocol_DLT645_07signal` VALUES (null, -1, 33947648,     总功率因数,               -1, 2, 0.001,     0, 1 );
INSERT INTO `protocol_DLT645_07signal` VALUES (null, -1, 33947904,     A相功率因数,              -1, 2, 0.001,     0, 1 );
INSERT INTO `protocol_DLT645_07signal` VALUES (null, -1, 33948160,     B相功率因数,              -1, 2, 0.001,     0, 1 );
INSERT INTO `protocol_DLT645_07signal` VALUES (null, -1, 33948416,     C相功率因数,              -1, 2, 0.001,     0, 1 );
INSERT INTO `protocol_DLT645_07signal` VALUES (null, -1, 41943042,     电网频率,                  -1, 2, 0.01,         0, 1 );
INSERT INTO `protocol_DLT645_07signal` VALUES (null, -1, 41943048,     时钟电池电压,              -1, 2, 0.01,         0, 1 );
View Code

   protocol_DLT645_07package表示请求的数据块表,主要是对应了请求块和应答包之间的关系。用的是十进制,只把请求块的内容又转换成了十六进制,可以对照协议文档进行查找;

  另外,07版的我是取用的+0x33之后的数据,07版本我是取用的原有数据的标识,可能会更加清晰;

  4. 上代码,环境和97版的是一样的,

  初始化配置项:

技术图片技术图片
        /// 
        /// 电表物理地址
        /// 
        public byte[] MeterPhysicalAddr { set; get; }
        /// 
        /// 请求实时数据,块
        /// 
        public Dictionaryint, int[]> RequestRealDataDic { set; get; }
        /// 
        /// 信号ID MAP
        /// 
        public Dictionaryint, Signal> SignalIdMap { set; get; }
View Code

  初始化请求包:

技术图片技术图片
        /// 
        /// 初始化请求实时数据,块
        /// 
        private void InitRequestRealDataDic()
        {
            RequestRealDataDic = new Dictionaryint, int[]>();

            List pcakageS = DbHelp.GetList().
                Where(s => s.visible == (int)EnumYesOrNo.Yes).
                Where(s => s.devicetype == this.DeviceType).ToList();
            if(pcakageS == null || pcakageS.Any() == false)
            {
                return;
            }
            foreach(protocol_dlt645package package in pcakageS)
            {
                string[] siganlAddrS = package.blockcontent.Split( , ,, ).Where(s => !string.IsNullOrEmpty(s)).ToArray();
                int[] signalAddrList = Array.ConvertAll(siganlAddrS, int.Parse);
                if (signalAddrList != null && signalAddrList.Count() != 0)
                {
                    RequestRealDataDic.Add(package.blockaddr, signalAddrList);
                }
            }
        }
View Code

  初始化解析数据包:

技术图片技术图片
       /// 
        /// 初始化信号ID MAP
        /// 
        private void InitSignalIdMap()
        {
            SignalIdMap = new Dictionaryint, Signal>();

            List signalList = DbHelp.GetList().Where(x => x.devicetype == this.DeviceType).ToList();
            if (signalList == null || signalList.Any() == false)
            {
                return;
            }
            foreach(protocol_dlt645signal dlt645signal in signalList)
            {
                Signal signal = new Signal
                {
                    SignalID = dlt645signal.signalid,
                    SignalName = dlt645signal.signalname,
                    DataLength = dlt645signal.datalength,
                    Visible = dlt645signal.visible,
                    Ratio = dlt645signal.ismultiplifct == (int)EnumYesOrNo.Yes ? dlt645signal.ratio*ConstValue.FCT : dlt645signal.ratio,
                };
                SignalIdMap.Add(dlt645signal.signaladdr, signal);
            }
        }
View Code

  初始化电表地址等:

技术图片技术图片
        /// 
        /// 初始化属性
        /// 
        private void InitAttribute()
        {
            try
            {
                MeterPhysicalAddr = new byte[6];
                var addr = ConfigurationManager.AppSettings["MeterPhysicalAddr"];
                if (addr != null)
                {
                    string[] strList = addr.Split(|);
                    for (int i = 0; i 6; i++)
                    {
                        MeterPhysicalAddr[i] = BCD_to_HEX(byte.Parse(strList[5 - i]));
                    }
                }
            }
            catch(Exception ex)
            {
                LogEvent.Loger.Fatal(ex.ToString());
            }
        }
View Code

  两个线程一发一收,我们这里用到了redis当做消息队列处理,收发都是经过redis进行处理的,可以理解为当前的发送不是直接到设备的,而是经过一层统一处理放到redis的消息队列当中,相当于收发都取redis的,这个不重要,和直连是一样的。

  发送请求实时数据线程:

技术图片技术图片
        /// 
        /// 按块请求实时数据
        /// 
        private void RequestBlockThread()
        {
            while (true)
            {
                Thread.Sleep(RealDataInterval);
                try
                {
                    foreach (int dataFlag in RequestRealDataDic.Keys)
                    {
                        Thread.Sleep(RealDataInterval);
                        // 发送实时数据请求
                        SendDataRequest(dataFlag);

                        // 有功功率请求频繁
                        //Thread.Sleep(RealDataInterval);
                        //SendDataRequest(ConstValue.ActivePowerFlag);
                    }
                }
                catch (Exception ex)
                {
                    LogEvent.Loger.Fatal(ex.ToString());
                }
            }
        }

        /// 
        /// 发送实时数据请求
        /// 
        /// 
        private void SendDataRequest(int dataFlag)
        {
            MessageInfo sendMessage = new MessageInfo
            {
                MessageHead = ConstValue.MessageHead,
                Addr = MeterPhysicalAddr,                                    // 改为自动获取或
                StartHead = ConstValue.MessageHead,
                ControlCode = ConstValue.ControlCode,
                DataLength = ConstValue.DataFlagLength,
                DataFlag = ConvertDataFlag(dataFlag, EnumAddDataFlag.Add),
                EndFrame = ConstValue.MessageEndFrame
            };

            SaveSendMessage(sendMessage);
        }


        /// 
        /// 转换数据标识,-+0x33
        /// 
        /// 
        /// 
        /// 
        private int ConvertDataFlag(int dataFlag, EnumAddDataFlag addFlag)
        {
            byte[] dataS = new byte[4];
            ConvertIntToByteArray(dataFlag, ref dataS);

            if (addFlag == EnumAddDataFlag.Add)
            {
                for (int i = 0; i 4; i++)
                {
                    dataS[i] += ConstValue.DataFlagDeviation;
                }
            }
            else
            {
                for (int i = 0; i 4; i++)
                {
                    dataS[i] -= ConstValue.DataFlagDeviation;
                }
            }
            int index = 0;
            return DataCopy.GetINT(dataS, ref index);
        }

        /// 
        /// 发送数据
        /// 
        /// 
        private void SaveSendMessage(MessageInfo message)
        {
            try
            {
                byte[] SendData = message.ObjectToByte();
                LogEvent.LogInfo.FatalFormat("发送:\r\n{0}", DataCopy.ToHexString(SendData));
                RedisHelper redisclient = new RedisHelper((int)EnumUserRedisNum.Protocol);
                string sessionId = redisclient.HashGetString(ConstValue.DeviceCodeSessionKey, string.Format("{0}|{1}", StationID, CanID));

                SendMessage sendMessage = new SendMessage();
                sendMessage.SessionID = sessionId;
                sendMessage.SendData = SendData;

                redisclient.ListRightPush(String.Format("{0}|发送", ProtocolMeterHelp.ProtocolID), sendMessage);
            }
            catch (Exception ex)
            {
                LogEvent.Loger.Fatal(ex.ToString());
            }
        }
View Code

  接收实时数据应答线程:

技术图片技术图片
        /// 
        /// 获取消息线程
        /// 
        private void GetMessageTh()
        {
            RedisHelper redisclient = new RedisHelper((int)EnumUserRedisNum.Protocol);
            string key = String.Format("{0}|接收", ProtocolID);
            ProtocolMeter ProtocolMeter = ProtocolMeter.GetInstance();
            while (true)
            {
                Thread.Sleep(RequestInterval * 100);
                try
                {
                    if (Work == false)
                    {
                        continue;
                    }

                    ReviceMessage message = redisclient.ListLeftPop(key);
                    if (message == null)
                    {
                        continue;
                    }
                    
                    ProtocolMeter = ProtocolMeter.GetInstance();
                    ProtocolMeter.ParseProtocol(message);
                }
                catch(Exception ex)
                {
                    LogEvent.Loger.Fatal(ex.ToString());
                }
            }
        }

        /// 
        /// 解析数据包
        /// 
        /// 
        public void ParseProtocol(ReviceMessage rMsg)
        {
            try
            {
                SaveClientStatus(rMsg);

                if (rMsg.Key == EnumSocektKeyType.连接.ToString())
                {
                    LogEvent.LogInfo.Debug("**************************客户端连接**************************");

                    // 保存设备、局站、协议等、
                    StationLogin(rMsg.SessionID);
                    return;
                }
                //
                LogEvent.LogInfo.DebugFormat("接收:\r\n{0}", DataCopy.ToHexString(rMsg.ReviceData));

                MessageInfo messageInfo = new MessageInfo(rMsg);
                if (!messageInfo.Valid)
                {
                    MessageRecord.SaveInvalidMessage(rMsg.ReviceData);          //记录非法数据包
                    return;
                }
                // 数据解析
                ReviceRealData(messageInfo.Data, messageInfo.DataFlag);
            }
            catch (Exception ex)
            {
                LogEvent.Loger.Fatal(ex.ToString());
            }
        }

        /// 
        /// 接收到实时数据
        /// 
        /// 数据域
        /// 数据标识
        private void ReviceRealData(byte[] data, int dataFlag)
        {
            int flag = ConvertDataFlag(dataFlag, EnumAddDataFlag.Reduce);       // 减0x33
            SaveRealData(flag, data);
        }

        /// 
        /// 保存实时数据
        /// 
        /// 
        /// 
        private void SaveRealData(int dataFlag, byte[] data)
        {
            List realList = new List();
            Dictionaryint, byte[]> dataByteDic = new Dictionaryint, byte[]>();
            int index = 0;

            int[] dataFlagS = RequestRealDataDic[dataFlag];      
            foreach(int flag in dataFlagS)
            {
                int dstOffset = 0;
                byte[] bS = new byte[SignalIdMap[flag].DataLength];
                DataCopy.ByteCopy(data, index, bS, ref dstOffset, data.Length, SignalIdMap[flag].DataLength);
                index += SignalIdMap[flag].DataLength;

                dataByteDic.Add(flag, bS);
            }
            LogEvent.LogInfo.FatalFormat("dataByteDic---{0}", dataByteDic.Keys.Count);
            foreach (int flag in dataByteDic.Keys)
            {
                if(SignalIdMap[flag].Visible == (int)EnumYesOrNo.No)
                {
                    continue;
                }
                int count = dataByteDic[flag].Length;
                double value = 0;
                int minus_flag = 0;
                if((((dataByteDic[flag][count-1] - ConstValue.DataFlagDeviation) >> 7) & 0x01) == 1)
                {
                    minus_flag = 1;
                }
                value += HEX_to_BCD((byte)((dataByteDic[flag][count - 1] - ConstValue.DataFlagDeviation) & 0x7F)) * Math.Pow(10, (count - 1) * 2);

                for (int i = 0; i 1; i++


评论


亲,登录后才可以留言!