Контрольная сумма для двоичной связи ПЛК

Я ломал голову над вычислением контрольной суммы для связи с ПЛК Unitronics с помощью бинарных команд. Они предлагают исходный код, но он находится в реализации C # только для Windows, что мне мало помогает, кроме базового синтаксиса.

Спецификация PDF (расчет контрольной суммы близок к концу)

C # источник драйвера (расчет контрольной суммы в Utils.cs)

Намеченный результат

Ниже указывается байтовый индекс, описание сообщения и пример, который работает.

#  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

Спецификация требует вычисления накопленного значения 22-байтового заголовка сообщения и, отдельно, 6-байтовой детализации, получения значения суммы по модулю 65536 и последующего возврата двух дополнений к этому значению.

Попытка № 1

Насколько я понимаю, оператор тильды (~) в Python является производным от C / C ++. После дня написания 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

Сообщение: 2F5F4F504C4300FE010000004D000000000001000600600L010001FF010001005C

Я вижу там букву L, которая отличается от вчерашнего, который не был ближе. Если вы хотите быстрый результат формулы, основанный на остальной части сообщения: Контрольная сумма (Заголовок) должна возвращать F1FC, а Контрольная сумма (Подробности) должна возвращать FEFE.

Возвращаемое значение нигде не совпадает с примером спецификации. Я полагаю, что проблема может быть в одной или двух вещах: метод Checksum неправильно вычисляет сумму шестнадцатеричной строки или Python ~ оператор не эквивалентен C ++ ~ оператор.

Попытка № 2

Друг дал мне свою 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;
}

2

Решение

Я не совсем уверен, каким должен быть правильный код … но если намерение для Checksum(Header) вернуть f705, и он возвращает 08fb, вот проблема:

x = ( ~( x % 0x10000 ) ) + 1

Короткая версия, что вы хотите это:

x = (( ~( x % 0x10000 ) ) + 1) % 0x10000

Проблема здесь не в том ~ означает что-то другое. Как документация говорит, ~x возвращает «биты x инвертированный », что фактически означает то же, что и в С (по крайней мере, на платформах с 2-мя дополнениями, включая все платформы Windows).

Здесь вы можете столкнуться с проблемой различия между типами C и Python (целочисленные типы C имеют фиксированный размер и переполняются; целочисленные типы Python имеют практически бесконечный размер и растут по мере необходимости). Но я не думаю, что это твоя проблема здесь.

Проблема заключается только в том, как вы конвертируете результат в строку.

Результат звонка Checksum(Header)с точностью до форматирования -2299 или -0x08fb в обеих версиях.

В C вы можете в значительной степени рассматривать целое число со знаком как целое число без знака того же размера (хотя в некоторых случаях вам, возможно, придется игнорировать предупреждения, чтобы сделать это). Что именно это делает, зависит от вашей платформы, но на платформе с дополнением 2s, подписанный короткий -0x08fb является побитовым эквивалентом беззнакового 0xf705. Так, например, если вы делаете sprintf(buf, "%04hx", -0x08fb), он работает просто отлично — и дает вам (на большинстве платформ, включая все Windows) эквивалент без знака, f705,

Но в Python нет целых чисел без знака. Int -0x08fb не имеет ничего общего с 0xf705. Если вы делаете "%04hx" % -0x08fb, ты получишь -8fbи нет никакого способа принудительно «привести его к неподписанному» или что-то в этом роде.

Ваш код на самом деле делает hex(-0x08fb), который дает вам -0x8fb, который ты тогда split на x, давая вам 8fbкоторый ты zfill в 08fb, что делает проблему немного труднее заметить (потому что это выглядит как совершенно правильная пара шестнадцатеричных байтов вместо знака минус и трех шестнадцатеричных цифр), но это та же проблема.

В любом случае, вы должны явно решить, что вы подразумеваете под «беззнаковым эквивалентом», и написать код для этого. Поскольку вы пытаетесь сопоставить то, что делает C на платформе с комплементом 2s, вы можете записать это явное преобразование как % 0x10000, Если вы делаете "%04hx" % (-0x08fb % 0x10000), ты получишь f705так же, как вы сделали в C. И аналогично для вашего существующего кода.

2

Другие решения

Это довольно просто. Я проверил алгоритм вашего друга, добавив все байты заголовка вручную на калькуляторе, и он дает правильный результат (0xfcf1).

На самом деле я не знаю Python, но мне кажется, что вы складываете полубайтовые значения. Вы сделали свою строку заголовка так:

Header = '2F5F4F504C4300FE010000004D000000000001000600'

И затем вы выполняете преобразование каждого байта в этой строке из шестнадцатеричного кода и добавление его. Это означает, что вы имеете дело со значениями от 0 до 15. Вам необходимо рассматривать каждые два байта как пару и преобразовывать их (значения от 0 до 255). Или вам нужно использовать реальные двоичные данные вместо текстового представления двоичных данных.

В конце алгоритма вам не нужно делать ~ оператор, если вы не доверяете ему. Вместо этого вы можете сделать (0xffff - (x % 0x10000)) + 1, Имейте в виду, что до добавления 1 значение может быть 0xffffтак что вам нужно по модулю весь результат 0x10000 после этого. C ++ версия вашего друга использует short тип данных, поэтому по модулю не нужно вообще, потому что short естественно переполнится

2