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