"""
Implementation of CRC16 for IM871-A (CCITT)
*******************************************
:synopsis: CRC16 CCITT implementation based on Steffen's C implementation.
:authors: Steffen and Janus.
:date: 29 Oct 2020.
- See IMST's WMBUS_HCL_Spec_V1.6.pdf.
- CRC computation starts from the Control Field and ends with the last octet of the Payload Field.
- IM871A uses CRC16-CCITT Polynomial G(x) = 1 + x^5 + x^12 + x^16.
"""
from binascii import hexlify
from struct import pack
[docs]def crc16_im871a_calc(m: bytes) -> bytes:
"""
Compute CRC16 (CCITT) for a message received serially from IM871-A.
Argument must:
- NOT contain the first field, e.g. 0xA5.
- Start and contain the control field, e.g. 0x8203.
- Contain full payload, e.g. 0x2744...2637.
- NOT contain the trailing 2 bytes of expected CRC16 value, e.g. 0xC1AB.
Full message example: a5 8203 27442d2c5768663230028d20cd12340720519df247ff65e751662a300bc4e5c67da86477f0182637 c1ab.
"""
hex_radix = 16
g = 0x8408 # Generator polynomial, g(x)
crc = 0xFFFF # Init value for CCITT CRC16
for byte in range(0, len(m), 2): # Loop over all bytes in message
b = int(m[byte:byte + 2], hex_radix) # Make byte value from hex digits
for _ in range(0, 8): # Repeat for 8 bits in a byte
if (b & 1) ^ (crc & 1): # Is there a remainder for division by the poly for this bit?
crc = (crc >> 1) ^ g # Get remainder from division
else:
crc >>= 1 # Just advance to next bit in division
b >>= 1 # Move on to next bit in this byte of the message
crc = crc ^ 0xFFFF # Perform final complement
crc16 = hexlify(pack('<H', crc)) # CRC16 as little-endian
return crc16
[docs]def crc16_im871a_check(m: bytes) -> bool:
"""
Confirm CRC16 integrity of a full bytestring received from IM871-A.
Argument must be the entire message from IM871-A.
Function returns TRUE when the check sum matches the expected CRC16 value.
"""
checksum = m[-4:] # Store the expected CRC16 value
data = m[2:-4] # Removes SOF field and CRC16 value
crc16 = crc16_im871a_calc(data)
if checksum == crc16: # Check if sum matches expected CRC16 value
return True
else:
return False
def test_crc16():
# Captured messages from IM871-A
test_full_frames =[(b'a5820321442d2c952742761b168d206d82c40222942c7a5414f7be5ea4411ebab435fe4995ff91'),
(b'a5820327442d2c5768663230028d201a13e00920dddf142f84b1107cae4e84dbcb98210fc275ddc868ce8d2554'),
(b'a5820321442d2c952742761b168d206e83c40222c72670296e74f24339982b162de91fa1af1400'),
(b'a5820321442d2c622842761b168d20d550c00222055d80a5b9011be3fafdf8a7d65cd64c95ffd1'),
(b'a5820327442d2c5768663230028d201b20e00920b34c894aa121ef88baa3f6e4e8b1b4cebe009978e5d7c6b153'),
(b'a5820321442d2c952742761b168d206f90c402228d949f169fdc2fde9782cff6a0fb175aba02f0'),
(b'a5820324442d2c622842761b168d20d651c00222420d6c7a7e8d04496a16e01a306e1d90c99bf2b41ef3'),
(b'a582032d442d2c5768663230028d201c21e00920d1c1b4c7ae4e8ea21285041754ef54367a7a00d2af4d23384dc51872b5970c'),
(b'a5820321442d2c952742761b168d207091c4022276925e4ab3801499711e3b6da8477f7e5bbca0'),
(b'a5820321442d2c622842761b168d20d752c0022275f0e5b100964f3edbef976e959ca83fa00465'),
(b'a5820327442d2c5768663230028d201d22e00920ab4b8e693509c3c3e562d34c5d0734d5e1414c23a8d1ec8fa9')]
for test_vector in test_full_frames:
assert crc16_im871a_check(test_vector) == True
if __name__ == '__main__':
print("Self test:")
test_crc16()
print("OK.")