Question

I've been scratching my head around calculating a checksum to communicate with Unitronics PLCs using binary commands. 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.

Specification PDF (the checksum calculation is near the end)

C# driver source (checksum calculation in 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.

Attempt #1

My understanding is the tilde (~) operator in Python is directly derived from C/C++. After a day of writing the Python that creates the message I came up with this (stripped down version):

#!/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

I see an L in there, which is a different result to yesterday, which wasn't any closer. 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.

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.

Attempt #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.

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;
}
Was it helpful?

Solution

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:

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).

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). 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.

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). 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. 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.

But in Python, there are no unsigned integers. The int -0x08fb has nothing to do with 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.

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.

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. If you do "%04hx" % (-0x08fb % 0x10000), you'll get f705, just as you did in C. And likewise for your existing code.

OTHER TIPS

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).

Now, I don't actually know python, but it looks to me like you are adding up half-byte values. 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). 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. 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. Your friend's C++ version uses the short datatype so no modulo is necessary at all because the short will naturally overflow

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top