DLT645

This class implements DL/T-645 support. DL/T-645 is Chinese electricity meter standard.

We do not read DL/T-645 meters anymore and this file is not maintenance.

This project was implemented in part of Tekes and the Ministry of Science and Technology of People’s Republic of China (MoST) together with Jiangsu and Zhejiang S&T Departments project 2013.

This simple example uses GXSerial component to communicate with serial meter.

GXDLT645 parser = new GXDLT645();
int addr = 0;//TODO: Update register address to read here.
byte[] data = parser.ReadValue(addr);
    lock (media.Synchronous)
    {					
        media.Send(data, null);
        ReceiveParameters<byte[]> p = new ReceiveParameters<byte[]>()
        {
            Eop = media.Eop,
            WaitTime = device.WaitTime
        };
        while (!parser.IsPacketComplete(addr, p.Reply)))
        {
            if (!media.Receive<byte[]>(p))
            {
               break;
            }
        }
Actual parser is here:

//
// --------------------------------------------------------------------------
//  Gurux Ltd
// 
//
//
// Filename:        $HeadURL$
//
// Version:         $Revision$,
//                  $Date$
//                  $Author$
//
// Copyright (c) Gurux Ltd
//
//---------------------------------------------------------------------------
//
//  DESCRIPTION
//
// This file is a part of Gurux Device Framework.
//
// Gurux Device Framework is Open Source software; you can redistribute it
// and/or modify it under the terms of the GNU General Public License 
// as published by the Free Software Foundation; version 2 of the License.
// Gurux Device Framework is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of 
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 
// See the GNU General Public License for more details.
//
// More information of Gurux products: http://www.gurux.org
//
// This code is licensed under the GNU General Public License v2. 
// Full text may be retrieved at http://www.gnu.org/licenses/gpl-2.0.txt
//---------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;

namespace Gurux.DLT645
{
    public class GXDLT645
    {
        string m_OperatorCode, m_MeterPassword, m_RelayControlPassword, m_LowClassPassword;
        enum Function
        {
            Read = 1,
            ReadSubsequent = 2,
            ReRead = 3,
            Write = 4,
            CorrectTimeByBroadcast = 8,
            WriteDeviceAddress = 10,
            ChangeCommunicationSpeed = 12,
            ChangePassword = 15,
            ClearMaximumDemand = 16
        }

        /// <summary>
        /// Make control code.
        /// </summary>
        /// <param name="func"></param>
        /// <param name="send"></param>
        /// <param name="subSequent"></param>
        /// <returns></returns>
        byte MakeControlCode(Function func, bool send, bool subSequent)
        {
            byte value = (byte)((byte)func & 0x1F);
            if (!send)
            {
                value += 0x80;
            }

            if (subSequent)
            {
                value += 0x20;
            }
            return value;
        }

        /// <summary>
        /// Device OperatorCode.
        /// </summary>
        /// <remarks>
        /// Operator Code maximum length is four charachters.
        /// </remarks>
        public string OperatorCode
        {
            get
            {
                return m_OperatorCode;
            }
            set
            {
                if (m_OperatorCode != null && m_OperatorCode.Length > 4)
                {
                    throw new ArgumentException("Operator Code is maximum four charachters.");
                }
                m_OperatorCode = value;
            }
        }

        /// <summary>
        /// Collector address.
        /// </summary>
        public ulong CollectorAddress
        {
            get;
            set;
        }

        /// <summary>
        /// Receiver awake bytes.
        /// </summary>
        /// <remarks>
        /// If awaker is used 0xFE is usually send one to four times.
        /// </remarks>
        public byte[] FrontLeadingBytes
        {
            get;
            set;
        }

        /// <summary>
        /// Password of the meter.
        /// </summary>
        /// <remarks>
        /// Maimum size is eight charachters.
        /// </remarks>
        public string MeterPassword
        {
            get
            {
                return m_MeterPassword;
            }
            set
            {
                if (value != null && value.Length > 8)
                {
                    throw new ArgumentException("Length of the meter password is maximum eight charachters.");
                }
                m_MeterPassword = value;
            }
        }

        /// <summary>
        /// Password of the relay control.
        /// </summary>
        /// <remarks>
        /// Maimum size is eight charachters.
        /// </remarks>
        public string RelayControlPassword
        {
            get
            {
                return m_RelayControlPassword;
            }
            set
            {
                if (value != null && value.Length > 8)
                {
                    throw new ArgumentException("Length of the relay control password is maximum eight charachters.");
                }
                m_RelayControlPassword = value;
            }
        }

        /// <summary>
        /// Password of the low class.
        /// </summary>
        /// <remarks>
        /// Maimum size is eight charachters.
        /// </remarks>
        public string LowClassPassword
        {
            get
            {
                return m_LowClassPassword;
            }
            set
            {
                if (value != null && value.Length > 8)
                {
                    throw new ArgumentException("Length of the low class password is maximum eight charachters.");
                }
                m_LowClassPassword = value;
            }
        }

        const byte Bop = 0x68;
        const byte Eop = 0x16;

        /// <summary>
        /// Are BOP, EOP and Checksum added to data.
        /// </summary>
        [DefaultValue(false)]
        public bool IgnoreFrame
        {
            get;
            set;
        }

        public ulong DeviceAddress
        {
            get;
            set;
        }

        void AddFrame(List<byte> data)
        {
            if (!IgnoreFrame)
            {
                int pos = 0;
                if (FrontLeadingBytes != null)
                {
                    pos = FrontLeadingBytes.Length;
                }
                data.Insert(pos, Bop);
                byte crc = 0;
                for (int a = pos; a != data.Count; ++a)
                {
                    crc = (byte)((crc + data[a]) & 0xFF);
                }
                data.Add(crc);
                data.Add(Eop);
            }
        }

        void AddDeviceAddress(List<byte> data)
        {
            int add = 0;
            if (CollectorAddress != 0)
            {
                add = 0x33;
            }
            string str = string.Format("{0:000000000000}", DeviceAddress);
            for (int pos = str.Length; pos > 0; pos -= 2)
            {
                string tmp = str.Substring(pos - 2, 2);
                data.Add((byte)(Convert.ToInt32(tmp, 16) + add));
            }
        }

        void AddDataID(List<byte> data, ulong dataID)
        {           
            string str = string.Format("{0:x8}", dataID);
            for (int pos = str.Length; pos > 0; pos -= 2)
            {
                string tmp = str.Substring(pos - 2, 2);
                data.Add((byte)(Convert.ToInt32(tmp, 16) + 0x33));
            }
        }


        byte[] MakePacket(Function func, ulong dataID, object value)
        {
            List<byte> data = new List<byte>(11);
            if (FrontLeadingBytes != null)
            {
                data.AddRange(FrontLeadingBytes);
            }
            int LenPos = 0;
            if (CollectorAddress != 0)
            {
                data.Add((byte)(CollectorAddress & 0xff));
                data.Add((byte)((CollectorAddress >> 8) & 0xff));
                data.Add((byte)((CollectorAddress >> 16) & 0xff));
                data.Add((byte)((CollectorAddress >> 24) & 0xff));
                data.Add((byte)((CollectorAddress >> 32) & 0xff));
                data.Add((byte)((CollectorAddress >> 40) & 0xff));
                data.Add(Bop);
                // Add Control Code.
                data.Add(MakeControlCode(func, true, false));
                LenPos = data.Count;
                AddDataID(data, dataID);
                AddDeviceAddress(data);
            }
            else
            {
                AddDeviceAddress(data);
                data.Add(Bop);
                // Add Control Code.
                data.Add(MakeControlCode(func, true, false));
                LenPos = data.Count;
                AddDataID(data, dataID);
            }
            if ((func & Function.Write) != 0)
            {
                //Password is always eight digits.
                string tmp = "";
                for (int pos = MeterPassword.Length; pos != 8; ++pos)
                {
                    tmp += "0" + tmp;
                }
                tmp += MeterPassword;
                for (int pos = tmp.Length; pos > 0; pos -= 2)
                {
                    string tmp2 = tmp.Substring(pos - 2, 2);
                    data.Add((byte)(Convert.ToInt32(tmp2, 16) + 0x33));
                }
                //Operator code is always found digits.
                tmp = "";
                for (int a = 0; a != 4 - OperatorCode.Length; ++a)
                {
                    tmp += "0";
                }
                tmp += OperatorCode;
                char[] items = tmp.ToCharArray();
                Array.Reverse(items);
                foreach (byte it in items)
                {
                    data.Add((byte)(it + 0x33));
                }
            }

            if (value != null)
            {
                if (value is DateTime)
                {
                    DateTime dt = (DateTime)value;
                    data.Add((byte)((int)dt.DayOfWeek + 0x33));
                    data.Add((byte)(dt.Day + 0x33));
                    data.Add((byte)(dt.Month + 0x33));
                    data.Add((byte)((dt.Year - 2000 + 6 + 0x33)));
                }
            }
            data.Insert(LenPos, (byte)(data.Count - LenPos));
            AddFrame(data);
            return data.ToArray();
        }

        /// <summary>
        /// Generates read message.
        /// </summary>
        /// <param name="dataID"></param>
        /// <returns></returns>
        /// <seealso cref="GetValue"/>
        public byte[] ReadValue(ulong dataID)
        {
            return MakePacket(Function.Read | Function.ClearMaximumDemand, dataID, null);
        }

        /// <summary>
        /// Generates Write message.
        /// </summary>
        /// <param name="dataID"></param>
        /// <param name="value"></param>
        /// <returns></returns>
        public byte[] WriteValue(ulong dataID, object value)
        {
            return MakePacket(Function.Write | Function.ClearMaximumDemand, dataID, value);
        }

        /// <summary>
        /// Check is there more data available.
        /// </summary>
        /// <param name="dataID"></param>
        /// <param name="reply"></param>
        /// <returns></returns>
        public bool IsMoreDataAvailable(ulong dataID, byte[] reply)
        {
            int index = -1;
            int StartIndex = -1;
            int cc = GetControlCode(reply, ref index, out StartIndex);
            if ((cc & 0x80) != 0x80)
            {
                throw new Exception("Parse Failed. Not a reply packet.");
            }
            return (cc & 0x20) == 0x20;
        }

        /// <summary>
        /// Generates an acknowledgment message, with which the server is informed to 
        /// send next packets.
        /// </summary>
        /// <returns></returns>
        public byte[] ReceiverReady()
        {
            throw new NotImplementedException();
        }

        byte GetControlCode(byte[] reply, ref int index, out int StartIndex)
        {
            if (IgnoreFrame)
            {
                index = StartIndex = 0;
            }
            else
            {
                StartIndex = -1;
                //Find Bop.
                for (int pos = 0; pos != reply.Length; ++pos)
                {
                    if (reply[pos] == Bop)
                    {
                        StartIndex = pos;
                        index = pos + 1;
                        break;
                    }
                }
            }
            if (StartIndex == -1)
            {
                throw new OutOfMemoryException("BOP Not found.");
            }
            //Check that device addresses matchs.
            ulong address = 0;
            for (int pos = 0; pos != 6; ++pos)
            {
                byte val = (byte)reply[pos + index];
                int tmp = (val >> 4) * 10 | val & 0xF;
                address += (ulong)(tmp * Math.Pow(100, pos));
            }
            index += 6;
            if (address != DeviceAddress)
            {
                throw new Exception("Parse Failed. Invalid Address.");
            }
            if (reply[index++] != 0x68)
            {
                throw new Exception("Parse Failed. Invalid data.");
            }
            //Get Control Code.
            return reply[index++];
        }

        public bool IsPacketComplete(ulong dataID, byte[] reply)
        {
            try
            {
                if (reply == null)
                {
                    return false;
                }
                GetValue(dataID, reply, typeof(byte[]));
                return true;
            }
            catch
            {
                return false;
            }
        }

        public bool IsError(byte[] reply)
        {
            int index = -1;
            int StartIndex = -1;
            int cc = GetControlCode(reply, ref index, out StartIndex);
            return (cc & 0x40) == 0x40;
        }

        /// <summary>
        /// Removes the DL/T 645 header from the packet, and returns payload data only.
        /// </summary>
        /// <param name="packet">The received packet, from the device, as byte array.</param>
        /// <param name="data">The exported data.</param>
        /// <returns>Received Data</returns>
        public bool GetDataFromPacket(ulong dataID, byte[] reply, ref byte[] allData)
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// Parse value.
        /// </summary>
        /// <param name="dataID"></param>
        /// <param name="reply"></param>
        /// <returns></returns>
        /// <seealso cref="ReadValue"/>
        public object GetValue(ulong dataID, byte[] reply, Type type)
        {
            int index = -1;
            int StartIndex = -1;
            int cc = GetControlCode(reply, ref index, out StartIndex);
            if ((cc & 0x80) != 0x80)
            {
                throw new Exception("Parse Failed. Not a reply packet.");
            }
            int len = reply[index++];
            if (reply.Length < index + len + (int)(IgnoreFrame ? 0 : 1))
            {
                throw new OutOfMemoryException();
            }
            int crcPos = index + len;
            if (!IgnoreFrame)
            {
                //Check CRC.                
                byte readCrc = reply[crcPos];
                byte countCrc = 0;
                for (int pos = StartIndex; pos != index + len; ++pos)
                {
                    countCrc = (byte)((countCrc + reply[pos]) & 0xFF);
                }
                if (countCrc != readCrc)
                {
                    throw new Exception("Parse Failed. CRC do not match.");
                }
                if (reply[index + len + 1] != 0x16)
                {
                    throw new Exception("Parse Failed. EOP not found.");
                }
            }
            if (index + 2 < reply.Length)
            {
                ulong address = 0;
                for (int pos = 0; pos != 4; ++pos)
                {
                    byte val = (byte)(reply[index++] - 0x33);
                    address += (ulong)(val << (8 * pos));
                }
                if (dataID != address)
                {
                    throw new Exception("Parse Failed. data identification do not match.");
                }
                byte[] buff = new byte[crcPos - index];
                int a = 0;
                while (index != crcPos)
                {
                    buff[a++] = (byte)(reply[index++] - 0x33);
                }
                int cnt;                 
                return Gurux.Shared.GXCommon.ByteArrayToObject(buff, type, out cnt);
            }
            return null;
        }
    }
}

Undefined