178 lines
5.4 KiB
GDScript
178 lines
5.4 KiB
GDScript
extends Node
|
|
|
|
## Modbus Utilities.
|
|
## A collection of utilities for packing data, unpacking
|
|
## data computing checksums, and decode checksums.
|
|
|
|
|
|
## Modbus Client States
|
|
class ModbusTransactionState extends Object:
|
|
const IDLE: = 0
|
|
const SENDING: = 1
|
|
const WAITING_FOR_REPLY: = 2
|
|
const WAITING_TURNAROUND_DELAY: = 3
|
|
const PROCESSING_REPLY: = 4
|
|
const PROCESSING_ERROR: = 5
|
|
const TRANSACTION_COMPLETE: = 6
|
|
const RETRYING: = 7
|
|
const NO_RESPONSE_STATE: = 8
|
|
|
|
func state_to_string(state) -> String:
|
|
var states: = {
|
|
ModbusTransactionState.IDLE: 'IDLE',
|
|
ModbusTransactionState.SENDING: 'SENDING',
|
|
ModbusTransactionState.WAITING_FOR_REPLY: 'WAITING_FOR_REPLY',
|
|
ModbusTransactionState.WAITING_TURNAROUND_DELAY: 'WAITING_TURNAROUND_DELAY',
|
|
ModbusTransactionState.PROCESSING_REPLY: 'PROCESSING_REPLY',
|
|
ModbusTransactionState.PROCESSING_ERROR: 'PROCESSING_ERROR',
|
|
ModbusTransactionState.TRANSACTION_COMPLETE: 'TRANSACTION_COMPLETE',
|
|
ModbusTransactionState.RETRYING: 'RETRYING TRANSACTION' }
|
|
return states.get(state, null)
|
|
|
|
var __crc16_table: = []
|
|
|
|
func _ready():
|
|
__crc16_table = __generate_crc16_table()
|
|
|
|
|
|
## Creates a string out of an array of bits
|
|
## [code]
|
|
## var bits = [False, True, False, True]
|
|
## var result = pack_bitstring(bits)
|
|
## [/code]
|
|
func pack_bitstring(bits: Array):
|
|
var ret = PackedByteArray()
|
|
var i = 0
|
|
var packed: int = 0
|
|
for bit in bits:
|
|
if bit:
|
|
packed += 128
|
|
i += 1
|
|
if i == 8:
|
|
ret += packed
|
|
i = 0
|
|
packed = 0
|
|
else:
|
|
packed >>= 1
|
|
if 0 < i < 8:
|
|
packed >>= (7 - i)
|
|
ret += packed
|
|
return ret
|
|
|
|
|
|
## Creates bit array out of a string
|
|
## param string: The modbus data packet to decode
|
|
## [code]
|
|
## var bytes = 'bytes to decode'
|
|
## var result = unpack_bitstring(bytes)
|
|
## [/code]
|
|
func unpack_bitstring(string: String):
|
|
var psa: = string.to_ascii_buffer()
|
|
var byte_count = psa.size()
|
|
var bits: = []
|
|
for byte in byte_count:
|
|
var value = psa.decode_u8(byte)
|
|
for i in 8:
|
|
bits.append((value & (1 << i)) > 0)
|
|
return bits
|
|
|
|
|
|
## Returns byte string from a given string
|
|
## [param s]
|
|
func make_byte_string(s) -> PackedByteArray:
|
|
return PackedByteArray(s)
|
|
|
|
|
|
# Error Detection Functions
|
|
func __generate_crc16_table() -> Array:
|
|
## Generates a crc16 lookup table
|
|
## note:: This will only be generated once
|
|
var result: = []
|
|
for byte in 256:
|
|
var crc = 0x0000
|
|
for i in 8:
|
|
if (byte ^ crc) & 0x0001:
|
|
crc = (crc >> 1) ^ 0xa001
|
|
else:
|
|
crc >>= 1
|
|
byte >>= 1
|
|
result.append(crc)
|
|
return result
|
|
|
|
|
|
## Computes a crc16 on the passed in string. For modbus,
|
|
## this is only used on the binary serial protocols (in this
|
|
## case RTU). The difference between modbus's crc16 and a normal crc16
|
|
## is that modbus starts the crc value out at 0xffff.
|
|
## [param data] The data to create a crc16 of
|
|
## [b]returns[/b] The calculated CRC
|
|
func computeCRC(data: PackedByteArray) -> int:
|
|
var crc: int = 0xffff
|
|
for a in data:
|
|
var idx = __crc16_table[(crc ^ a) & 0xff]
|
|
crc = ((crc >> 8) & 0xff) ^ idx
|
|
return ((crc << 8) & 0xff00) | ((crc >> 8) & 0x00ff)
|
|
|
|
|
|
## Checks if the data matches the passed in CRC
|
|
## [param data] The data to create a crc16 of
|
|
## [param check] The CRC to validate
|
|
## [b]returns[/b] True if matched, False otherwise
|
|
func checkCRC(data, check: int) -> bool:
|
|
return computeCRC(data) == check
|
|
|
|
|
|
## Used to compute the longitudinal redundancy check
|
|
## against a string. This is only used on the serial ASCII
|
|
## modbus protocol. A full description of this implementation
|
|
## can be found in appendex B of the serial line modbus description.
|
|
## [param data] The data to apply a lrc to
|
|
## [b]return[/b] The calculated LRC
|
|
func computeLRC(data: PackedByteArray) -> int:
|
|
var lrc: int = 0
|
|
for a in data:
|
|
lrc += a
|
|
lrc &= 0xff
|
|
lrc = (lrc ^ 0xff) + 1
|
|
return lrc & 0xff
|
|
|
|
|
|
## Checks if the passed in data matches the LRC
|
|
## [param data] The data to calculate
|
|
## [param check] The LRC to validate
|
|
## [b]returns[/b] True if matched, False otherwise
|
|
func checkLRC(data: PackedByteArray, check: int) -> bool:
|
|
return computeLRC(data) == check
|
|
|
|
|
|
## Calculates the size of the frame based on the byte count.
|
|
## param data: The buffer containing the frame.
|
|
## param byte_count_pos: The index of the byte count in the buffer.
|
|
## returns: The size of the frame.
|
|
##
|
|
## The structure of frames with a byte count field is always the
|
|
## same:
|
|
##
|
|
## first, there are some header fields
|
|
## then the byte count field
|
|
## then as many data bytes as indicated by the byte count,
|
|
## finally the CRC (two bytes).
|
|
##
|
|
## To calculate the frame size, it is therefore sufficient to extract
|
|
## the contents of the byte count field, add the position of this
|
|
## field, and finally increment the sum by three (one byte for the
|
|
## byte count field, two for the CRC).
|
|
func rtuFrameSize(data: PackedByteArray, byte_count_pos: int) -> int:
|
|
return data.decode_u32(byte_count_pos) + byte_count_pos + 3
|
|
|
|
|
|
## Returns hex representation of bytestring received
|
|
## [param packet]
|
|
## [b]return[/b]
|
|
func hexlify_packets(packet: PackedByteArray):
|
|
if not packet: return ''
|
|
var s = String()
|
|
for ch in packet:
|
|
s += '%02x' % ch
|
|
return s
|