1

I am trying to write a unit test for a CRC16 checksum for a P1 meter message (and having a hard time getting it right...).

What I have is:

  • A P1 meter message
  • Stripped it so that I have the correct part according to the spec (see below)
  • Turned it into a byte array (ASCII encoding)
  • Calculate the CRC16 checksum
  • Compare it with the checksum it should be

Given the fact that I posted a question on SO, the result is not OK...

Please bear with me, I will share what I have below:

The P1 message

    private static readonly string telegramText =
        "/KFM5KAIFA-METER\n" +
        "\n" +
        "1-3:0.2.8(42)\n" +
        "0-0:1.0.0(170124213128W)\n" +
        "0-0:96.1.1(4530303236303030303234343934333135)\n" +
        "1-0:1.8.1(000306.946*kWh)\n" +
        "1-0:1.8.2(000210.088*kWh)\n" +
        "1-0:2.8.1(000000.000*kWh)\n" +
        "1-0:2.8.2(000000.000*kWh)\n" +
        "0-0:96.14.0(0001)\n" +
        "1-0:1.7.0(02.793*kW)\n" +
        "1-0:2.7.0(00.000*kW)\n" +
        "0-0:96.7.21(00001)\n" +
        "0-0:96.7.9(00001)\n" +
        "1-0:99.97.0(1)(0-0:96.7.19)(000101000006W)(2147483647*s)\n" +
        "1-0:32.32.0(00000)\n" +
        "1-0:52.32.0(00000)\n" +
        "1-0:72.32.0(00000)\n" +
        "1-0:32.36.0(00000)\n" +
        "1-0:52.36.0(00000)\n" +
        "1-0:72.36.0(00000)\n" +
        "0-0:96.13.1()\n" +
        "0-0:96.13.0()\n" +
        "1-0:31.7.0(003*A)\n" +
        "1-0:51.7.0(005*A)\n" +
        "1-0:71.7.0(005*A)\n" +
        "1-0:21.7.0(00.503*kW)\n" +
        "1-0:41.7.0(01.100*kW)\n" +
        "1-0:61.7.0(01.190*kW)\n" +
        "1-0:22.7.0(00.000*kW)\n" +
        "1-0:42.7.0(00.000*kW)\n" +
        "1-0:62.7.0(00.000*kW)\n" +
        "0-1:24.1.0(003)\n" +
        "0-1:96.1.0(4730303331303033333738373931363136)\n" +
        "0-1:24.2.1(170124210000W)(00671.790*m3)\n" +
        "!29ED\n";

The spec

Source can be found here

CRC is a CRC16 value calculated over the preceding characters in the data message (from “/” to “!” using the polynomial: x16 + x15 + x2 + 1).

CRC16 uses no XOR in, no XOR out and is computed with least significant bit first. The value is represented as 4 hexadecimal characters (MSB first).

Get the bytes, and do the CRC

The read message is the P1 message above, but then from '/' to '!' (including the '/' and including the '!'), as dictated by above spec.

var bytes = Encoding.ASCII.GetBytes(_readMessage);
var computeChecksum = new Crc16().ComputeChecksum(bytes);

Finally, the CRC code

Source can be found here

public class Crc16
{
    const ushort polynomial = 0xA001;
    ushort[] table = new ushort[256];

    public ushort ComputeChecksum(byte[] bytes)
    {
        ushort crc = 0;
        for (int i = 0; i < bytes.Length; ++i)
        {
            byte index = (byte)(crc ^ bytes[i]);
            crc = (ushort)((crc >> 8) ^ table[index]);
        }
        return crc;
    }

    public byte[] ComputeChecksumBytes(byte[] bytes)
    {
        ushort crc = ComputeChecksum(bytes);
        return BitConverter.GetBytes(crc);
    }

    public Crc16()
    {
        ushort value;
        ushort temp;
        for (ushort i = 0; i < table.Length; ++i)
        {
            value = 0;
            temp = i;
            for (byte j = 0; j < 8; ++j)
            {
                if (((value ^ temp) & 0x0001) != 0)
                {
                    value = (ushort)((value >> 1) ^ polynomial);
                }
                else
                {
                    value >>= 1;
                }
                temp >>= 1;
            }
            table[i] = value;
        }
    }
}

The unfortunate result

The checksum of the P1 telegram should be: 0x29ED, but unfortunately I calculate 0x6500.

Can somebody point me in the right direction?

Update

@Mark Adler, I fixed the issue you found, but I still calculate the wrong checksum. I stripped all irrelevant parts below, could you be so kind to have another look? You can simply copy paste the code below if you like. Or, post back the code that does calculate the correct checksum.

Thanks a lot in advance!

class Program
{
    private static readonly string telegramText =
        "/KFM5KAIFA-METER\r\n" +
        "\r\n" +
        "1-3:0.2.8(42)\r\n" +
        "0-0:1.0.0(170124213128W)\r\n" +
        "0-0:96.1.1(4530303236303030303234343934333135)\r\n" +
        "1-0:1.8.1(000306.946*kWh)\r\n" +
        "1-0:1.8.2(000210.088*kWh)\r\n" +
        "1-0:2.8.1(000000.000*kWh)\r\n" +
        "1-0:2.8.2(000000.000*kWh)\r\n" +
        "0-0:96.14.0(0001)\r\n" +
        "1-0:1.7.0(02.793*kW)\r\n" +
        "1-0:2.7.0(00.000*kW)\r\n" +
        "0-0:96.7.21(00001)\r\n" +
        "0-0:96.7.9(00001)\r\n" +
        "1-0:99.97.0(1)(0-0:96.7.19)(000101000006W)(2147483647*s)\r\n" +
        "1-0:32.32.0(00000)\r\n" +
        "1-0:52.32.0(00000)\r\n" +
        "1-0:72.32.0(00000)\r\n" +
        "1-0:32.36.0(00000)\r\n" +
        "1-0:52.36.0(00000)\r\n" +
        "1-0:72.36.0(00000)\r\n" +
        "0-0:96.13.1()\r\n" +
        "0-0:96.13.0()\r\n" +
        "1-0:31.7.0(003*A)\r\n" +
        "1-0:51.7.0(005*A)\r\n" +
        "1-0:71.7.0(005*A)\r\n" +
        "1-0:21.7.0(00.503*kW)\r\n" +
        "1-0:41.7.0(01.100*kW)\r\n" +
        "1-0:61.7.0(01.190*kW)\r\n" +
        "1-0:22.7.0(00.000*kW)\r\n" +
        "1-0:42.7.0(00.000*kW)\r\n" +
        "1-0:62.7.0(00.000*kW)\r\n" +
        "0-1:24.1.0(003)\r\n" +
        "0-1:96.1.0(4730303331303033333738373931363136)\r\n" +
        "0-1:24.2.1(170124210000W)(00671.790*m3)\r\n" +
        "!";

    static void Main(string[] args)
    {
        var bytes = Encoding.ASCII.GetBytes(telegramText);
        var computeChecksum = new Crc16().ComputeChecksum(bytes);
    }
}

public class Crc16
{
    const ushort polynomial = 0x8005;
    ushort[] table = new ushort[256];

    public ushort ComputeChecksum(byte[] bytes)
    {
        ushort crc = 0;
        for (int i = 0; i < bytes.Length; ++i)
        {
            byte index = (byte)(crc ^ bytes[i]);
            crc = (ushort)((crc >> 8) ^ table[index]);
        }
        return crc;
    }

    public byte[] ComputeChecksumBytes(byte[] bytes)
    {
        ushort crc = ComputeChecksum(bytes);
        return BitConverter.GetBytes(crc);
    }

    public Crc16()
    {
        ushort value;
        ushort temp;
        for (ushort i = 0; i < table.Length; ++i)
        {
            value = 0;
            temp = i;
            for (byte j = 0; j < 8; ++j)
            {
                if (((value ^ temp) & 0x0001) != 0)
                {
                    value = (ushort)((value >> 1) ^ polynomial);
                }
                else
                {
                    value >>= 1;
                }
                temp >>= 1;
            }
            table[i] = value;
        }
    }
}
bas
  • 11,697
  • 16
  • 53
  • 116
  • Per [the wiki](https://en.wikipedia.org/wiki/Cyclic_redundancy_check), the representation of the polynomial you mentioned in MSB-first mode would be 0x8005, not 0xA001 (which is the LSB-first representation). – Javier Martín Jan 27 '17 at 20:20
  • @JavierMartín thx changed that but still no correct result. '0x0a6a'. Do you have any other advise on how to proceed? – bas Jan 27 '17 at 20:37
  • Not really, sorry, I was just going for the low-hanging fruit. Maybe look carefully at the code that computes the table and then the CRCs, or find some other examples. – Javier Martín Jan 27 '17 at 21:25

1 Answers1

1

The CRC code looks fine. The problem is that it is expecting carriage return-line feeds, not just line feeds. If you precede each \n with a \r, then you get 0x29ed for the CRC of the bytes from the / to the !, inclusive.

Mark Adler
  • 79,438
  • 12
  • 96
  • 137
  • Hi Mark, thanks for that, but with that fix I still calculate the wrong checksum. I updated my question with a copy/pastable program which only the relevant parts. Can you please help me further? Thanks – bas Jan 28 '17 at 07:22
  • As I said, the original CRC code looks fine. For some reason you went and changed the polynomial to `0x8005`. The original `0xa001` is correct. – Mark Adler Jan 28 '17 at 15:17
  • Yes! Thank you! It was a suggested by somebody else. I unit test finally passed....! Thanks for your help! – bas Jan 28 '17 at 18:06