[英]Checksum for binary PLC communication
I've been scratching my head around calculating a checksum to communicate with Unitronics PLCs using binary commands. 我一直在努力计算使用二进制命令与Unitronics PLC通信的校验和。 They offer the source code but it's in a Windows-only C# implementation, which is of little help to me other than basic syntax. 他们提供了源代码,但是它是在仅Windows的C#实现中进行的,除了基本语法之外,对我几乎没有帮助。
Specification PDF (the checksum calculation is near the end) 规格PDF (校验和计算即将结束)
C# driver source (checksum calculation in Utils.cs) C#驱动程序源 (Utils.cs中的校验和计算)
Intended Result 预期结果
Below is the byte index, message description and the sample which does work. 以下是字节索引,消息描述和起作用的示例。
# 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | 24 25 26 27 28 29 | 30 31 32
# sx--------------- id FE 01 00 00 00 cn 00 specific--------- lengt CHKSM | numbr ot FF addr- | CHKSM ex
# 2F 5F 4F 50 4C 43 00 FE 01 00 00 00 4D 00 00 00 00 00 01 00 06 00 F1 FC | 01 00 01 FF 01 00 | FE FE 5C
The specification calls for calculating the accumuated value of the 22 byte message header and, seperately, the 6+ byte detail, getting the value of sum modulo 65536, and then returning two's complement of that value. 该规范要求计算22字节消息头的累加值,以及分别计算6+字节细节的累加值,得到和模65536的值,然后返回该值的二进制补码。
Attempt #1 尝试#1
My understanding is the tilde (~) operator in Python is directly derived from C/C++. 我的理解是Python中的波浪号(〜)运算符直接来自C / C ++。 After a day of writing the Python that creates the message I came up with this (stripped down version): 在写了一天创建消息的Python之后,我想到了这个(精简版):
#!/usr/bin/env python
def Checksum( s ):
x = ( int( s, 16 ) ) % 0x10000
x = ( ~x ) + 1
return hex( x ).split( 'x' )[1].zfill( 4 )
Details = ''
Footer = ''
Header = ''
Message = ''
Details += '0x010001FF0100'
Header += '0x2F5F4F504C4300FE010000004D000000000001000600'
Header += Checksum( Header )
Footer += Checksum( Details )
Footer += '5C'
Message += Header.split( 'x' )[1].zfill( 4 )
Message += Details.split( 'x' )[1].zfill( 4 )
Message += Footer
print Message
Message: 2F5F4F504C4300FE010000004D000000000001000600600L010001FF010001005C
讯息: 2F5F4F504C4300FE010000004D000000000001000600600L010001FF010001005C
I see an L in there, which is a different result to yesterday, which wasn't any closer. 我在那儿看到了L,这与昨天的结果是不同的,直到今天为止。 If you want a quick formula result based on the rest of the message: Checksum(Header) should return F1FC and Checksum(Details) should return FEFE . 如果您需要基于消息其余部分的快速公式结果: Checksum(Header)应该返回F1FC,而Checksum(Details)应该返回FEFE 。
The value it returns is nowhere near the same as the specification's example. 它返回的值与规范的示例几乎没有相同。 I believe the issue may be one or two things: the Checksum method isn't calculating the sum of the hex string correctly or the Python ~
operator is not equivalent to the C++ ~
operator. 我认为,问题可能是一个或两件事情:校验方法不正确计算十六进制字符串的总和或Python的~
运营商是不是等同于C ++ ~
运营商。
Attempt #2 尝试#2
A friend has given me his C++ interpretation of what the calculation SHOULD be, I just can't get my head around this code, my C++ knowledge is minimal. 一位朋友给了我有关计算的C ++解释,我只是无法理解这段代码,我的C ++知识很少。
short PlcBinarySpec::CalcHeaderChecksum( std::vector<byte> _header ) {
short bytesum = 0;
for ( std::vector<byte>::iterator it = _header.begin(); it != _header.end(); ++it ) {
bytesum = bytesum + ( *it );
}
return ( ~( bytesum % 0x10000 ) ) + 1;
}
I'm not entirely sure what the correct code should be… but if the intention is for Checksum(Header)
to return f705, and it's returning 08fb, here's the problem: 我不确定要正确的代码是什么……但是如果要让Checksum(Header)
返回f705,而返回的是08fb,那就是问题所在:
x = ( ~( x % 0x10000 ) ) + 1
The short version is that you want this: 简短的版本是您想要的:
x = (( ~( x % 0x10000 ) ) + 1) % 0x10000
The problem here isn't that ~
means something different. 这里的问题不是~
意味着不同。 As the documentation says, ~x
returns "the bits of x
inverted", which is effectively the same thing it means in C (at least on 2s-complement platforms, which includes all Windows platforms). 由于文档说, ~x
回报“的位x
倒”,这实际上是它意味着在C同样的事情(至少在二进制补码平台,其中包括所有Windows平台)。
You can run into a problem with the difference between C and Python types here (C integral types are fixed-size, and overflow; Python integral types are effectively infinite-sized, and grow as needed). 您可能会遇到C和Python类型之间的差异的问题(C整数类型是固定大小且有溢出; Python整数类型实际上是无穷大,并根据需要增长)。 But I don't think that's your problem here. 但我认为这不是您的问题。
The problem is just a matter of how you convert the result to a string. 问题仅在于如何将结果转换为字符串。
The result of calling Checksum(Header)
, up to the formatting, is -2299, or -0x08fb, in both versions. 在两个版本中,调用Checksum(Header)
的结果Checksum(Header)
直至格式)均为-2299或-0x08fb。
In C, you can pretty much treat a signed integer as an unsigned integer of the same size (although you may have to ignore warnings to do so, in some cases). 在C语言中,您几乎可以将带符号整数视为相同大小的无符号整数(尽管在某些情况下,您可能不得不忽略警告)。 What exactly that does depends on your platform, but on a 2s-complement platform, signed short -0x08fb is the bit-for-bit equivalent of unsigned 0xf705. 确切的功能取决于您的平台,但在2s补码平台上,带符号的短-0x08fb与无符号的0xf705逐位等效。 So, for example, if you do sprintf(buf, "%04hx", -0x08fb)
, it works just fine—and it gives you (on most platforms, including everything Windows) the unsigned equivalent, f705
. 因此,例如,如果您执行sprintf(buf, "%04hx", -0x08fb)
,它就可以正常工作-并且(在大多数平台上,包括所有Windows上)为您提供无符号的等效值f705
。
But in Python, there are no unsigned integers. 但是在Python中,没有无符号整数。 The int -0x08fb has nothing to do with 0xf705. int -0x08fb与0xf705无关。 If you do "%04hx" % -0x08fb
, you'll get -8fb
, and there's no way to forcibly "cast it to unsigned" or anything like that. 如果您执行"%04hx" % -0x08fb
,您将得到-8fb
,并且无法强制“将其强制转换为未签名”或类似的东西。
Your code actually does hex(-0x08fb)
, which gives you -0x8fb
, which you then split
on the x
, giving you 8fb
, which you zfill
to 08fb
, which makes the problem a bit harder to notice (because that looks like a perfectly valid pair of hex bytes, instead of a minus sign and three hex digits), but it's the same problem. 您的代码实际上执行hex(-0x08fb)
,给您-0x8fb
,然后在x
上split
,给您8fb
,您将其zfill
到08fb
,这使问题更难注意到(因为看起来很完美)有效的十六进制字节对,而不是减号和三个十六进制数字),但这是相同的问题。
Anyway, you have to explicitly decide what you mean by "unsigned equivalent", and write the code to do that. 无论如何,您必须明确确定“无符号等效项”的含义,并编写代码来做到这一点。 Since you're trying to match what C does on a 2s-complement platform, you can write that explicit conversion as % 0x10000
. 由于您试图匹配C在2s补码平台上的功能,因此可以将该显式转换写为% 0x10000
。 If you do "%04hx" % (-0x08fb % 0x10000)
, you'll get f705
, just as you did in C. And likewise for your existing code. 如果执行"%04hx" % (-0x08fb % 0x10000)
, f705
得到f705
,就像在C中所做的一样。对于现有代码也是如此。
It's quite simple. 这很简单。 I checked your friend's algorithm by adding all the header bytes manually on a calculator, and it yields the correct result ( 0xfcf1
). 我通过在计算器上手动添加所有标头字节来检查您朋友的算法,它产生正确的结果( 0xfcf1
)。
Now, I don't actually know python, but it looks to me like you are adding up half-byte values. 现在,我实际上并不了解python,但是在我看来,这就像您要添加半字节值一样。 You have made your header string like this: 您已使标题字符串如下所示:
Header = '2F5F4F504C4300FE010000004D000000000001000600'
And then you go through converting each byte in that string from hex and adding it. 然后,您将十六进制转换为字符串中的每个字节并将其相加。 That means you are dealing with values from 0 to 15. You need to consider every two bytes as a pair and convert that (values from 0 to 255). 这意味着您正在处理0到15之间的值。您需要将每两个字节视为一对,并将其转换(值从0到255)。 Or you need to use actual binary data instead of a text representation of the binary data. 或者,您需要使用实际的二进制数据而不是二进制数据的文本表示形式。
At the end of the algorithm, you don't really need to do the ~
operator if you don't trust it. 在算法的最后,如果您不信任它,则实际上不需要执行~
运算符。 Instead you can do (0xffff - (x % 0x10000)) + 1
. 相反,您可以执行(0xffff - (x % 0x10000)) + 1
。 Bear in mind that prior to adding 1, the value might actually be 0xffff
, so you need to modulo the entire result by 0x10000
afterwards. 请记住,在加1之前,该值实际上可能是0xffff
,因此此后需要对整个结果乘以0x10000
。 Your friend's C++ version uses the short
datatype so no modulo is necessary at all because the short
will naturally overflow 您朋友的C ++版本使用short
数据类型,因此根本不需要模,因为short
会自然溢出
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.