P1 仪表 CRC16 校验和
P1 Meter CRC16 checksum
我正在尝试为 P1 计量消息的 CRC16 校验和编写单元测试(并且很难做到正确...)。
我有的是:
- P1 仪表消息
- 剥离它以便我根据规范获得正确的部分(见下文)
- 转成字节数组(ASCII编码)
- 计算 CRC16 校验和
- 将其与应有的校验和进行比较
考虑到我 post 在 SO 上提出了一个问题,结果不正确...
请耐心等待,我将在下面分享我的内容:
P1 消息
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";
规范
可以找到来源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).
获取字节,进行CRC校验
读取的消息是上面的P1消息,但是从'/'到'!' (包括“/”和“!”),如上述规范所述。
var bytes = Encoding.ASCII.GetBytes(_readMessage);
var computeChecksum = new Crc16().ComputeChecksum(bytes);
最后,CRC码
可以找到来源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;
}
}
}
不幸的结果
P1报文的校验和应该是:0x29ED,可惜我算出来的是0x6500
有人能指出我正确的方向吗?
更新
@Mark Adler,我解决了你发现的问题,但我仍然计算出错误的校验和。下面我把不相关的部分都去掉了,你能不能再看看?如果您愿意,可以简单地复制粘贴下面的代码。或者,post 返回计算正确校验和的代码。
提前致谢!
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;
}
}
}
CRC 码看起来没问题。问题是它期待回车 return-line 提要,而不仅仅是换行。如果您在每个 \n
之前加上 \r
,那么您会得到 0x29ed
用于从 /
到 !
的字节的 CRC,包括在内。
我正在尝试为 P1 计量消息的 CRC16 校验和编写单元测试(并且很难做到正确...)。
我有的是:
- P1 仪表消息
- 剥离它以便我根据规范获得正确的部分(见下文)
- 转成字节数组(ASCII编码)
- 计算 CRC16 校验和
- 将其与应有的校验和进行比较
考虑到我 post 在 SO 上提出了一个问题,结果不正确...
请耐心等待,我将在下面分享我的内容:
P1 消息
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";
规范
可以找到来源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).
获取字节,进行CRC校验
读取的消息是上面的P1消息,但是从'/'到'!' (包括“/”和“!”),如上述规范所述。
var bytes = Encoding.ASCII.GetBytes(_readMessage);
var computeChecksum = new Crc16().ComputeChecksum(bytes);
最后,CRC码
可以找到来源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;
}
}
}
不幸的结果
P1报文的校验和应该是:0x29ED,可惜我算出来的是0x6500
有人能指出我正确的方向吗?
更新
@Mark Adler,我解决了你发现的问题,但我仍然计算出错误的校验和。下面我把不相关的部分都去掉了,你能不能再看看?如果您愿意,可以简单地复制粘贴下面的代码。或者,post 返回计算正确校验和的代码。
提前致谢!
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;
}
}
}
CRC 码看起来没问题。问题是它期待回车 return-line 提要,而不仅仅是换行。如果您在每个 \n
之前加上 \r
,那么您会得到 0x29ed
用于从 /
到 !
的字节的 CRC,包括在内。