Merge remote-tracking branch 'sasha/master'
# Conflicts: # scenes/tilemap/Control.gd
This commit is contained in:
@@ -1,4 +1,3 @@
|
||||
@tool
|
||||
# Copyright (c) 2020-2023 Mansur Isaev and contributors - MIT License
|
||||
# See `LICENSE.md` included in the source distribution for details.
|
||||
|
||||
|
||||
205
modbus/pymodbus/constants.gd
Normal file
205
modbus/pymodbus/constants.gd
Normal file
@@ -0,0 +1,205 @@
|
||||
extends Node
|
||||
|
||||
## Constants For Modbus Server/Client
|
||||
## This is the single location for storing default
|
||||
## values for the servers and clients.
|
||||
|
||||
## A collection of modbus default values[br]
|
||||
class Defaults:
|
||||
## [param Port]
|
||||
## The default modbus tcp server port (502)[br]
|
||||
##
|
||||
## [param TLSPort]
|
||||
## The default modbus tcp over tls server port (802)[br]
|
||||
##
|
||||
## [param Backoff]
|
||||
## The default exponential backoff delay (0.3 seconds)[br]
|
||||
##
|
||||
## [param Retries]
|
||||
## The default number of times a client should retry the given
|
||||
## request before failing (3)[br]
|
||||
##
|
||||
## [param RetryOnEmpty]
|
||||
## A flag indicating if a transaction should be retried in the
|
||||
## case that an empty response is received. This is useful for
|
||||
## slow clients that may need more time to process a request.[br]
|
||||
##
|
||||
## [param RetryOnInvalid]
|
||||
## A flag indicating if a transaction should be retried in the
|
||||
## case that an invalid response is received.[br]
|
||||
##
|
||||
## [param Timeout]
|
||||
## The default amount of time a client should wait for a request
|
||||
## to be processed (3 seconds)[br]
|
||||
##
|
||||
## [param Reconnects]
|
||||
## The default number of times a client should attempt to reconnect
|
||||
## before deciding the server is down (0)[br]
|
||||
##
|
||||
## [param TransactionId]
|
||||
## The starting transaction identifier number (0)[br]
|
||||
##
|
||||
## [param ProtocolId]
|
||||
## The modbus protocol id. Currently this is set to 0 in all
|
||||
## but proprietary implementations.[br]
|
||||
##
|
||||
## [param UnitId]
|
||||
## The modbus slave addrss. Currently this is set to 0x00 which
|
||||
## means this request should be broadcast to all the slave devices
|
||||
## (really means that all the devices should respons).[br]
|
||||
##
|
||||
## [param Baudrate]
|
||||
## The speed at which the data is transmitted over the serial line.
|
||||
## This defaults to 19200.[br]
|
||||
##
|
||||
## [param Parity]
|
||||
## The type of checksum to use to verify data integrity. This can be
|
||||
## on of the following:[br]
|
||||
## - (E)ven - 1 0 1 0 | P(0)[br]
|
||||
## - (O)dd - 1 0 1 0 | P(1)[br]
|
||||
## - (N)one - 1 0 1 0 | no parity[br]
|
||||
## This defaults to (N)one.[br]
|
||||
##
|
||||
## [param Bytesize]
|
||||
## The number of bits in a byte of serial data. This can be one of
|
||||
## 5, 6, 7, or 8. This defaults to 8.[br]
|
||||
##
|
||||
## [param Stopbits]
|
||||
## The number of bits sent after each character in a message to
|
||||
## indicate the end of the byte. This defaults to 1.[br]
|
||||
##
|
||||
## [param ZeroMode]
|
||||
## Indicates if the slave datastore should use indexing at 0 or 1.
|
||||
## More about this can be read in section 4.4 of the modbus specification.[br]
|
||||
##
|
||||
## [param IgnoreMissingSlaves]
|
||||
## In case a request is made to a missing slave, this defines if an error
|
||||
## should be returned or simply ignored. This is useful for the case of a
|
||||
## serial server emulater where a request to a non-existant slave on a bus
|
||||
## will never respond. The client in this case will simply timeout.[br]
|
||||
##
|
||||
## [param broadcast_enable]
|
||||
## When False unit_id 0 will be treated as any other unit_id. When True and
|
||||
## the unit_id is 0 the server will execute all requests on all server
|
||||
## contexts and not respond and the client will skip trying to receive a
|
||||
## response. Default value False does not conform to Modbus spec but maintains
|
||||
## legacy behavior for existing pymodbus users[br]
|
||||
|
||||
const Port: = 502
|
||||
const TLSPort: = 802
|
||||
const Backoff: = 0.3
|
||||
const Retries: = 3
|
||||
const RetryOnEmpty: = false
|
||||
const RetryOnInvalid: = false
|
||||
const Timeout: = 3
|
||||
const Reconnects: = 0
|
||||
const TransactionId: = 0
|
||||
const ProtocolId: = 0
|
||||
const UnitId: = 0x00
|
||||
const Baudrate: = 19200
|
||||
const Parity: = 'N'
|
||||
const Bytesize: = 8
|
||||
const Stopbits: = 1
|
||||
const ZeroMode: = false
|
||||
const IgnoreMissingSlaves: = false
|
||||
const ReadSize: = 1024
|
||||
const broadcast_enable: = false
|
||||
|
||||
|
||||
## These represent various status codes in the modbus
|
||||
## protocol.[br]
|
||||
class ModbusStatus extends Object:
|
||||
##
|
||||
## [param Waiting]
|
||||
## This indicates that a modbus device is currently
|
||||
## waiting for a given request to finish some running task.[br]
|
||||
##
|
||||
## [param Ready]
|
||||
## This indicates that a modbus device is currently
|
||||
## free to perform the next request task.[br]
|
||||
##
|
||||
## [param On]
|
||||
## This indicates that the given modbus entity is on[br]
|
||||
##
|
||||
## [param Off]
|
||||
## This indicates that the given modbus entity is off[br]
|
||||
##
|
||||
## [param SlaveOn]
|
||||
## This indicates that the given modbus slave is running[br]
|
||||
##
|
||||
## [param SlaveOff]
|
||||
## This indicates that the given modbus slave is not running[br]
|
||||
|
||||
const Waiting: = 0xffff
|
||||
const Ready: = 0x0000
|
||||
const On: = 0xff00
|
||||
const Off: = 0x0000
|
||||
const SlaveOn: = 0xff
|
||||
const SlaveOff: = 0x00
|
||||
|
||||
|
||||
class Endian:
|
||||
## An enumeration representing the various byte endianess.
|
||||
## [param Auto]
|
||||
## This indicates that the byte order is chosen by the
|
||||
## current native environment.[br]
|
||||
## [param Big]
|
||||
## This indicates that the bytes are in little endian format.[br]
|
||||
## [param Little]
|
||||
## This indicates that the bytes are in big endian format
|
||||
## [i]I am simply borrowing the format strings from the
|
||||
## python struct module for my convenience.[/i]
|
||||
const Auto: = '@'
|
||||
const Big: = '>'
|
||||
const Little: = '<'
|
||||
|
||||
|
||||
class ModbusPlusOperation:
|
||||
## Represents the type of modbus plus request
|
||||
## [param GetStatistics]
|
||||
## Operation requesting that the current modbus plus statistics
|
||||
## be returned in the response.[br]
|
||||
## [param ClearStatistics]
|
||||
## Operation requesting that the current modbus plus statistics
|
||||
## be cleared and not returned in the response.[br]
|
||||
const GetStatistics: = 0x0003
|
||||
const ClearStatistics: = 0x0004
|
||||
|
||||
|
||||
class DeviceInformation:
|
||||
## Represents what type of device information to read
|
||||
##
|
||||
## [param Basic]
|
||||
## This is the basic (required) device information to be returned.
|
||||
## This includes VendorName, ProductCode, and MajorMinorRevision
|
||||
## code.[br]
|
||||
##
|
||||
## [param Regular]
|
||||
## In addition to basic data objects, the device provides additional
|
||||
## and optional identification and description data objects. All of
|
||||
## the objects of this category are defined in the standard but their
|
||||
## implementation is optional.[br]
|
||||
##
|
||||
## [param Extended
|
||||
## In addition to regular data objects, the device provides additional
|
||||
## and optional identification and description private data about the
|
||||
## physical device itself. All of these data are device dependent.[br]
|
||||
##
|
||||
## [param Specific]
|
||||
## Request to return a single data object.[br]
|
||||
|
||||
const Basic: = 0x01
|
||||
const Regular: = 0x02
|
||||
const Extended: = 0x03
|
||||
const Specific: = 0x04
|
||||
|
||||
|
||||
class MoreData:
|
||||
## Represents the more follows condition
|
||||
## [param Nothing]
|
||||
## This indiates that no more objects are going to be returned.[br]
|
||||
##
|
||||
## [param KeepReading]
|
||||
## This indicates that there are more objects to be returned.[br]
|
||||
const Nothing: = 0x00
|
||||
const KeepReading: = 0xFF
|
||||
87
modbus/pymodbus/exceptions.gd
Normal file
87
modbus/pymodbus/exceptions.gd
Normal file
@@ -0,0 +1,87 @@
|
||||
extends Node
|
||||
|
||||
## Pymodbus Exceptions. Custom exceptions to be used in the Modbus code.
|
||||
|
||||
|
||||
## Base modbus exception
|
||||
class ModbusException:
|
||||
var string: String = ''
|
||||
## Initialize the exception.
|
||||
## [param s] The message to append to the error
|
||||
func _init(s: String): string = s
|
||||
func _to_string(): return '<ModbusException>: %s' % string
|
||||
func isError(): return true
|
||||
func raise(): push_error(string)
|
||||
|
||||
|
||||
### Error resulting from data i/o
|
||||
class ModbusIOException extends ModbusException:
|
||||
var fcode
|
||||
var message
|
||||
## Initialize the exception
|
||||
## [param s] The message to append to the error
|
||||
func _init(s: String = '', function_code=null):
|
||||
fcode = function_code
|
||||
message = '[Input/Output] %s' % s
|
||||
ModbusException.new(message).raise()
|
||||
|
||||
|
||||
## Error resulting from invalid parameter
|
||||
class ParameterException extends ModbusException:
|
||||
## Initialize the exception
|
||||
## [param s] The message to append to the error
|
||||
func _init(s: String = ''):
|
||||
var message = '[Invalid Parameter] %s' % s
|
||||
ModbusException.new(message).raise()
|
||||
|
||||
|
||||
## Error resulting from making a request to a slave
|
||||
## that does not exist
|
||||
class NoSuchSlaveException extends ModbusException:
|
||||
## Initialize the exception.
|
||||
## [param s] The message to append to the error
|
||||
func _init(s: String = ''):
|
||||
var message = '[No Such Slave] %s' % s
|
||||
ModbusException.new(message).raise()
|
||||
|
||||
|
||||
## Error resulting from not implemented function
|
||||
class NotImplementedException extends ModbusException:
|
||||
## Initialize the exception
|
||||
## [param s]: The message to append to the error
|
||||
func _init(s: String = ''):
|
||||
var message = '[Not Implemented] %s' % s
|
||||
ModbusException.new(message).raise()
|
||||
|
||||
## Error resulting from a bad connection
|
||||
class ConnectionException extends ModbusException:
|
||||
## Initialize the exception
|
||||
## [param s] The message to append to the error
|
||||
func _init(s: String = ''):
|
||||
var message: String = '[Connection] %s' % s
|
||||
ModbusException.new(message).raise()
|
||||
|
||||
|
||||
## Error resulting from invalid response received or decoded
|
||||
class InvalidMessageReceivedException extends ModbusException:
|
||||
## Initialize the exception
|
||||
## [param s] The message to append to the error
|
||||
func _init(s: String = ''):
|
||||
var message: String = '[Invalid Message] %s' % s
|
||||
ModbusException.new(message).raise()
|
||||
|
||||
|
||||
## Error resulting from failing to register a custom message request/response
|
||||
class MessageRegisterException extends ModbusException:
|
||||
func _init(s: String = ''):
|
||||
var message: String = '[Error registering message] %s' % s
|
||||
ModbusException.new(message).raise()
|
||||
|
||||
|
||||
## Error resulting from modbus response timeout
|
||||
class TimeOutException extends ModbusException:
|
||||
## Initialize the exception
|
||||
## [param s] The message to append to the error
|
||||
func _init(s: String = ''):
|
||||
var message: String = '[Timeout] %s' % s
|
||||
ModbusException.new(message).raise()
|
||||
187
modbus/pymodbus/pdu.gd
Normal file
187
modbus/pymodbus/pdu.gd
Normal file
@@ -0,0 +1,187 @@
|
||||
extends Node
|
||||
|
||||
## Contains base classes for modbus request/response/error packets
|
||||
|
||||
# В автозагрузке должно быть
|
||||
# from pymodbus.exceptions import
|
||||
# from pymodbus.constants import Defaults
|
||||
# from pymodbus.utilities import rtuFrameSize
|
||||
|
||||
|
||||
# Base PDU's
|
||||
class ModbusPDU extends Object:
|
||||
## Base class for all Modbus messages
|
||||
## [param transaction_id]
|
||||
## This value is used to uniquely identify a request
|
||||
## response pair. It can be implemented as a simple counter
|
||||
## [param protocol_id]
|
||||
## This is a constant set at 0 to indicate Modbus. It is
|
||||
## put here for ease of expansion.
|
||||
## [param unit_id]
|
||||
## This is used to route the request to the correct child. In
|
||||
## the TCP modbus, it is used for routing (or not used at all. However,
|
||||
## for the serial versions, it is used to specify which child to perform
|
||||
## the requests against. The value 0x00 represents the broadcast address
|
||||
## (also 0xff).
|
||||
## [param check]
|
||||
## This is used for LRC/CRC in the serial modbus protocols
|
||||
## [param skip_encode]
|
||||
## This is used when the message payload has already been encoded.
|
||||
## Generally this will occur when the PayloadBuilder is being used
|
||||
## to create a complicated message. By setting this to True, the
|
||||
## request will pass the currently encoded message through instead
|
||||
## of encoding it again.
|
||||
|
||||
var transaction_id
|
||||
var protocol_id
|
||||
var unit_id
|
||||
var skip_encode
|
||||
var check
|
||||
var function_code = 0
|
||||
var exception_code = 0
|
||||
|
||||
## Initializes the base data for a modbus request
|
||||
func _init(kwargs: Dictionary):
|
||||
transaction_id = kwargs.get('transaction', constants.Defaults.TransactionId)
|
||||
protocol_id = kwargs.get('protocol', constants.Defaults.ProtocolId)
|
||||
unit_id = kwargs.get('unit', constants.Defaults.UnitId)
|
||||
skip_encode = kwargs.get('skip_encode', false)
|
||||
check = 0x0000
|
||||
|
||||
|
||||
## Encodes the message
|
||||
## [b]raises[/b] A not implemented exception
|
||||
func encode():
|
||||
exceptions.NotImplementedException.new().raise()
|
||||
|
||||
## Decodes data part of the message.
|
||||
## [param data] is a string object
|
||||
## [b]raises[/b] A not implemented exception
|
||||
func decode(data: PackedByteArray):
|
||||
exceptions.NotImplementedException.new().raise()
|
||||
|
||||
## Calculates the size of a PDU.
|
||||
## [param buffer] A buffer containing the data that have been received.
|
||||
## [b]returns[/b] The number of bytes in the PDU.
|
||||
func calculateRtuFrameSize(buffer: PackedByteArray):
|
||||
var rc = self.get('_rtu_frame_size')
|
||||
if rc != null:
|
||||
return rc
|
||||
rc = self.get('_rtu_byte_count_pos')
|
||||
if rc != null:
|
||||
return utilities.rtuFrameSize(buffer, rc)
|
||||
else:
|
||||
exceptions.NotImplementedException.new('Cannot determine RTU frame size for %s' % self).raise()
|
||||
|
||||
|
||||
## Base class for a modbus request PDU
|
||||
class ModbusRequest extends ModbusPDU:
|
||||
|
||||
func _init(kwargs: Dictionary):
|
||||
# Proxy to the lower level initializer
|
||||
super(kwargs)
|
||||
|
||||
## Builds an error response based on the function[br]
|
||||
## [param exception] The exception to return[br]
|
||||
## [b]raises[/b] An exception response[br]
|
||||
func doException(exception):
|
||||
var exc = exceptions.ExceptionResponse(function_code, exception)
|
||||
log.error(exc)
|
||||
return exc
|
||||
|
||||
|
||||
class ModbusResponse extends ModbusPDU:
|
||||
## Base class for a modbus response PDU
|
||||
## [param should_respond]
|
||||
## A flag that indicates if this response returns a result back
|
||||
## to the client issuing the request
|
||||
## [param _rtu_frame_size]
|
||||
## Indicates the size of the modbus rtu response used for
|
||||
## calculating how much to read.
|
||||
var should_respond = true
|
||||
|
||||
## Proxy to the lower level initializer
|
||||
func _init(kwargs: Dictionary):
|
||||
super(kwargs)
|
||||
|
||||
## Checks if the error is a success or failure
|
||||
func isError():
|
||||
return function_code > 0x80
|
||||
|
||||
|
||||
|
||||
## Exception PDU's. An enumeration of the valid modbus exceptions
|
||||
class ModbusExceptions:
|
||||
const IllegalFunction = 0x01
|
||||
const IllegalAddress = 0x02
|
||||
const IllegalValue = 0x03
|
||||
const SlaveFailure = 0x04
|
||||
const Acknowledge = 0x05
|
||||
const SlaveBusy = 0x06
|
||||
const MemoryParityError = 0x08
|
||||
const GatewayPathUnavailable = 0x0A
|
||||
const GatewayNoResponse = 0x0B
|
||||
|
||||
var _decoded: = {}
|
||||
|
||||
func _init():
|
||||
for prop in get_property_list():
|
||||
_decoded[get(prop.name)] = prop.name
|
||||
|
||||
## Given an error code, translate it to a string error name.[br]
|
||||
## [param code] The code number to translate
|
||||
func decode(code: int) -> String: return _decoded.get(code)
|
||||
|
||||
|
||||
## Base class for a modbus exception PDU
|
||||
class ExceptionResponse extends ModbusResponse:
|
||||
var ExceptionOffset = 0x80
|
||||
var _rtu_frame_size = 5
|
||||
var original_code = function_code
|
||||
|
||||
## Initializes the modbus exception response
|
||||
## [param function_code] The function to build an exception response for
|
||||
## [param exception_code] The specific modbus exception to return
|
||||
func _init(func_code, kwargs: Dictionary, except_code: int = 0):
|
||||
super(kwargs)
|
||||
function_code = func_code | ExceptionOffset
|
||||
exception_code = except_code
|
||||
original_code = function_code
|
||||
|
||||
## Encodes a modbus exception response
|
||||
func encode(): return exception_code
|
||||
|
||||
## Decodes a modbus exception response[br]
|
||||
## [param data] The packet data to decode
|
||||
func decode(data: PackedByteArray):
|
||||
exception_code = data.decode_u8(0)
|
||||
|
||||
## Builds a representation of an exception response[/br]
|
||||
## [b]returns[/b] The string representation of an exception response
|
||||
func _to_string() -> String:
|
||||
var message = ModbusExceptions.new().decode(self.exception_code)
|
||||
return 'Exception Response(%d, %d, %s)' % [function_code, original_code, message]
|
||||
|
||||
|
||||
## Defines the Modbus slave exception type 'Illegal Function'
|
||||
## This exception code is returned if the slave::
|
||||
## - does not implement the function code **or**
|
||||
## - is not in a state that allows it to process the function
|
||||
class IllegalFunctionRequest extends ModbusRequest:
|
||||
var ErrorCode = 1
|
||||
|
||||
## Initializes a IllegalFunctionRequest[br]
|
||||
## [param function_code] The function we are erroring on[br]
|
||||
func _init(func_code, kwargs):
|
||||
super(kwargs)
|
||||
self.function_code = func_code
|
||||
|
||||
## This is here so this failure will run correctly[br]
|
||||
## [param data] Not used[br]
|
||||
func decode(_data: PackedByteArray): pass
|
||||
|
||||
## Builds an illegal function request error response
|
||||
## [param context] The current context for the message
|
||||
## [b]returns[/b] The error response packet
|
||||
func execute(context):
|
||||
return ExceptionResponse.new(function_code, ErrorCode)
|
||||
0
modbus/pymodbus/server/reactive/default_config.json
Normal file
0
modbus/pymodbus/server/reactive/default_config.json
Normal file
177
modbus/pymodbus/utilities.gd
Normal file
177
modbus/pymodbus/utilities.gd
Normal file
@@ -0,0 +1,177 @@
|
||||
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
|
||||
44
modbus/pymodbus/version.gd
Normal file
44
modbus/pymodbus/version.gd
Normal file
@@ -0,0 +1,44 @@
|
||||
extends Node
|
||||
|
||||
## Handle the version information here; you should only have to
|
||||
## change the version tuple. Since we are using twisted's
|
||||
## version class, we can also query the svn version as well using
|
||||
## the local .entries file.
|
||||
|
||||
|
||||
var version = Version.new('pymodbus', 2, 5, 3)
|
||||
|
||||
|
||||
class Version extends Object:
|
||||
var package
|
||||
var major
|
||||
var minor
|
||||
var micro
|
||||
var pre
|
||||
var name
|
||||
|
||||
## [param package] Name of the package that this is a version of.[br]
|
||||
## [param major] The major version number.[br]
|
||||
## [param minor] The minor version number.[br]
|
||||
## [param micro] The micro version number.[br]
|
||||
## [param pre] The pre release tag.[br]
|
||||
func _init(pkg, mjr, mnr, mcr, pr=null):
|
||||
self.package = pkg
|
||||
self.major = mjr
|
||||
self.minor = mnr
|
||||
self.micro = mcr
|
||||
self.pre = pr
|
||||
self.name = 'pymodbus'
|
||||
|
||||
## Return a string in canonical short version format
|
||||
## <major>.<minor>.<micro>.<pre>
|
||||
func short():
|
||||
if self.pre:
|
||||
return '%d.%d.%d.%s' % [self.major, self.minor, self.micro, self.pre]
|
||||
else:
|
||||
return '%d.%d.%d' % [self.major, self.minor, self.micro]
|
||||
|
||||
## Returns a string representation of the object
|
||||
## A string representation of this object
|
||||
func _to_string() -> String:
|
||||
return '[%s, version %s]' % [self.package, self.short()]
|
||||
@@ -55,6 +55,11 @@ signal pressed(control: PanelContainer)
|
||||
|
||||
func _on_button_pressed():
|
||||
emit_signal('pressed', self)
|
||||
|
||||
|
||||
func _enter_tree() -> void:
|
||||
if is_inside_tree():
|
||||
$margin/vbox/title/lbl_using.self_modulate = Color.CADET_BLUE
|
||||
"
|
||||
|
||||
[sub_resource type="GDScript" id="GDScript_45hqb"]
|
||||
@@ -132,20 +137,20 @@ columns = 2
|
||||
[node name="fname" type="Label" parent="margin/vbox/title"]
|
||||
self_modulate = Color(1, 1, 1, 0.627451)
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 6
|
||||
size_flags_horizontal = 2
|
||||
theme_override_font_sizes/font_size = 17
|
||||
horizontal_alignment = 1
|
||||
vertical_alignment = 1
|
||||
|
||||
[node name="lbl_using" type="Label" parent="margin/vbox/title"]
|
||||
self_modulate = Color(0.372549, 0.619608, 0.627451, 1)
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_horizontal = 10
|
||||
size_flags_vertical = 6
|
||||
tooltip_text = "Частота питающего напряжения"
|
||||
mouse_filter = 0
|
||||
theme_override_font_sizes/font_size = 22
|
||||
text = "<нет данных>"
|
||||
horizontal_alignment = 1
|
||||
horizontal_alignment = 2
|
||||
vertical_alignment = 1
|
||||
|
||||
[node name="table" type="GridContainer" parent="margin/vbox"]
|
||||
|
||||
@@ -8,120 +8,25 @@ extends 'res://scenes/контроль/элемент-я.gd'
|
||||
## информационного взаимодействия изделия 5П-28
|
||||
## с аппаратурой УА-РЭП на кораблях проекта 23900
|
||||
|
||||
@export var base_input_address: = 0 ## Базовый адрес регистров входов
|
||||
@export var base_holding_address: = 0 ## Базовый адрес регистров хранения
|
||||
@export var regs_input_count: = 5 ## Количество регистров входов
|
||||
@export var regs_holding_count: = 4 ## Количество регистров хранения
|
||||
@export var modbus_address: = 3 ## Адрес устройства на шине Modbus
|
||||
@export var request_period: = 1.0 ## Период отправки команд на шину Modbus
|
||||
@export var reconnect_period: = 5.0 ## Период переподключения
|
||||
@export var base_input_address: = 0 ## Базовый адрес регистров входов
|
||||
@export var base_holding_address: = 0 ## Базовый адрес регистров хранения
|
||||
@export var regs_input_count: = 5 ## Количество регистров входов
|
||||
@export var regs_holding_count: = 4 ## Количество регистров хранения
|
||||
@export var modbus_address: = 7 ## Адрес устройства на шине Modbus
|
||||
@export var request_period: = 200 ## Период в секундах отправки команд на шину Modbus
|
||||
@export var reconnect_period: = 3000 ## Период переподключения в секундах
|
||||
|
||||
var regs_input: = {} ## Регистры входные
|
||||
var regs_holding: = {} ## Регистры хранения
|
||||
var write_stack: = [] ## Стек команд для записи
|
||||
var holding_stack: = [] ## Стек команд для чтения регистров хранения
|
||||
var input_stack: = [] ## Стек команд для чтения регистров входов
|
||||
var read_step: = 0
|
||||
var switches_sync: = false
|
||||
var timer = Timer.new()
|
||||
var timer_reconnect = Timer.new()
|
||||
var regs_input: = {} ## Регистры входные
|
||||
var regs_holding: = {} ## Регистры хранения
|
||||
var switches_sync: = false ## Флаг. Если установлен, то можно переключать
|
||||
|
||||
|
||||
class StackItem:
|
||||
var addr: = 0
|
||||
var data = null
|
||||
func _init(a: int, d): addr = a; data = d
|
||||
func _to_string() -> String: return '%s: %s' % [addr, data]
|
||||
|
||||
|
||||
func on_data_received(_data: PackedByteArray): pass
|
||||
|
||||
|
||||
## Обработчик таймера
|
||||
func _on_timeout(unit_sch3) -> void:
|
||||
if write_stack.size():
|
||||
var ad = write_stack.pop_front()
|
||||
if not unit_sch3.write(ad.addr, ad.data):
|
||||
write_stack.push_back(ad)
|
||||
if (read_step == 0) and (input_stack.size() == 0):
|
||||
var ad = StackItem.new(base_input_address, regs_input_count)
|
||||
input_stack.push_back(ad)
|
||||
elif (read_step == 1) and (holding_stack.size() == 0):
|
||||
var ad = StackItem.new(base_holding_address, regs_holding_count)
|
||||
holding_stack.push_back(ad)
|
||||
if input_stack.size():
|
||||
var ad = input_stack.pop_front()
|
||||
if not unit_sch3.read_input(ad.addr, ad.data):
|
||||
input_stack.push_back(ad)
|
||||
if holding_stack.size():
|
||||
var ad = holding_stack.pop_front()
|
||||
if not unit_sch3.read_holding(ad.addr, ad.data):
|
||||
holding_stack.push_back(ad)
|
||||
read_step += 1
|
||||
read_step %= 2
|
||||
|
||||
|
||||
func _on_timeout_recconect(unit_sch3):
|
||||
var unit_key = settings.get_unit_key(unit_sch3.name)
|
||||
var mbc = network.mbcs[unit_key]
|
||||
mbc.try_reconnect()
|
||||
var mb_data = {'code': 'read_holding', 'data': [0, 4], 'unit': 0}
|
||||
mbc.send_to(mb_data)
|
||||
|
||||
|
||||
## Вызывается при изменении состояния выключателя[br]
|
||||
## [param val] - Состояние выключателя[br]
|
||||
## [param meta] - Адреса регистров и индексы битов назначенных выключателю[br]
|
||||
func on_toggled_switch(val: bool, meta: Array):
|
||||
var on_addr = meta[0]
|
||||
var on_bit = meta[1]
|
||||
var off_addr = meta[2]
|
||||
var off_bit = meta[3]
|
||||
if regs_holding.has(on_addr) and regs_holding.has(off_addr):
|
||||
regs_holding[on_addr] = tools.set_bit(regs_holding[on_addr], on_bit, val)
|
||||
regs_holding[off_addr] = tools.set_bit(regs_holding[off_addr], off_bit, not val)
|
||||
push_write_holding()
|
||||
|
||||
|
||||
## Заносит в очередь команду чтения регистров хранения
|
||||
func push_write_holding():
|
||||
var regs_data: = []
|
||||
var keys = regs_holding.keys()
|
||||
keys.sort() # Ключи в словаре хранятся не упорядочено
|
||||
for addr in keys:
|
||||
regs_data.append(regs_holding[addr])
|
||||
write_stack.push_back(StackItem.new(keys[0], regs_data))
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
if Engine.is_editor_hint(): return
|
||||
var unit_key = settings.get_unit_key(unit_name)
|
||||
var unit_sch3 = network.units[unit_key]
|
||||
unit_sch3.mb_addr = modbus_address
|
||||
var items = get_tree().get_nodes_in_group('bits_items_switch')
|
||||
for item in items:
|
||||
var meta = item.get_meta('bits')
|
||||
item.connect('toggled', on_toggled_switch.bind(meta))
|
||||
item.disabled = true
|
||||
unit_sch3.connect('resp_input', on_resp_input)
|
||||
unit_sch3.connect('resp_holding', on_resp_holding)
|
||||
unit_sch3.connect('command_fail', on_command_fail)
|
||||
timer.connect('timeout', _on_timeout.bind(unit_sch3))
|
||||
add_child(timer)
|
||||
timer.start(request_period)
|
||||
timer_reconnect.connect('timeout', _on_timeout_recconect.bind(unit_sch3))
|
||||
add_child(timer_reconnect)
|
||||
|
||||
|
||||
func on_command_fail(unit_sch3):
|
||||
unit_sch3.cmd_state = unit.Unit.CmdState.DONE
|
||||
timer.stop()
|
||||
timer_reconnect.start(reconnect_period)
|
||||
func on_error(msg): push_error(msg)
|
||||
|
||||
|
||||
## Вызывается, когда состояния прибора Щ3 "на связи" изменяется[br]
|
||||
## [param unit_sch3] - Экземпляр представления прибора Щ3
|
||||
func on_line_changed(unit_sch3: sch_3.Sch3):
|
||||
func on_line_changed_sch3(unit_sch3):
|
||||
for item in get_tree().get_nodes_in_group('bits_items_good'):
|
||||
item.texture = textures[STATE_VAL.NONE]
|
||||
if not unit_sch3.online:
|
||||
@@ -129,22 +34,63 @@ func on_line_changed(unit_sch3: sch_3.Sch3):
|
||||
item.disabled = true
|
||||
regs_holding.clear()
|
||||
regs_input.clear()
|
||||
write_stack.clear()
|
||||
input_stack.clear()
|
||||
holding_stack.clear()
|
||||
switches_sync = false
|
||||
$margin/vbox/grid1/lbl_freq.text = '<нет данных>'
|
||||
$margin/vbox/grid1/lbl_freq.self_modulate = Color.CADET_BLUE
|
||||
$margin/vbox/grid1/state_freq.texture = textures[STATE_VAL.NONE]
|
||||
$margin/vbox/grid/state.texture = textures[STATE_VAL.NONE]
|
||||
else:
|
||||
timer_reconnect.stop()
|
||||
timer.start(request_period)
|
||||
|
||||
|
||||
## Вызывается при изменении состояния выключателя[br]
|
||||
## [param val] - Состояние выключателя[br]
|
||||
## [param meta] - Адреса регистров и индексы битов назначенных выключателю[br]
|
||||
func on_toggled_switch(val: bool, meta: Array, unit_sch3):
|
||||
var on_addr = meta[0]
|
||||
var on_bit = meta[1]
|
||||
var off_addr = meta[2]
|
||||
var off_bit = meta[3]
|
||||
if regs_holding.has(on_addr) and regs_holding.has(off_addr):
|
||||
regs_holding[on_addr] = tools.set_bit(regs_holding[on_addr], on_bit, val)
|
||||
regs_holding[off_addr] = tools.set_bit(regs_holding[off_addr], off_bit, not val)
|
||||
unit_sch3.push_write_holding(regs_holding)
|
||||
|
||||
|
||||
func _enter_tree() -> void:
|
||||
super._enter_tree()
|
||||
if Engine.is_editor_hint(): return
|
||||
var unit_key = settings.get_unit_key(unit_name)
|
||||
var unit_sch3 = network.units[unit_key]
|
||||
var items = get_tree().get_nodes_in_group('bits_items_switch')
|
||||
for item in items:
|
||||
var meta = item.get_meta('bits')
|
||||
item.connect('toggled', on_toggled_switch.bind(meta, unit_sch3))
|
||||
item.disabled = true
|
||||
unit_sch3.base_holding_address = base_holding_address
|
||||
unit_sch3.regs_holding_count = regs_holding_count
|
||||
unit_sch3.base_input_address = base_input_address
|
||||
unit_sch3.regs_input_count = regs_input_count
|
||||
unit_sch3.request_period = request_period
|
||||
unit_sch3.reconnect_period = reconnect_period
|
||||
unit_sch3.connect('read', on_read.bind(unit_sch3))
|
||||
unit_sch3.connect('read_input', on_input)
|
||||
unit_sch3.connect('error', on_error)
|
||||
unit_sch3.connect('line_changed', on_line_changed_sch3)
|
||||
|
||||
|
||||
func _exit_tree() -> void:
|
||||
if Engine.is_editor_hint(): return
|
||||
var unit_key = settings.get_unit_key(unit_name)
|
||||
var unit_sch3 = network.units[unit_key]
|
||||
unit_sch3.disconnect('read', on_read.bind(unit_sch3))
|
||||
unit_sch3.disconnect('read_input', on_input.bind(unit_sch3))
|
||||
unit_sch3.disconnect('error', on_error)
|
||||
unit_sch3.disconnect('line_changed', on_line_changed_sch3)
|
||||
|
||||
|
||||
## Вызывается по сигналу "приняты данные чтения регистров хранения"[br]
|
||||
## [param base_addr] - Начальный адрес регистров.[br]
|
||||
## [param data] - Массив значений прочитанных из регистров.
|
||||
func on_resp_holding(base_addr: int, data: Array):
|
||||
func on_read(base_addr: int, data: Array, unit_sch3):
|
||||
if (base_addr != base_holding_address) or (data.size() < regs_holding_count):
|
||||
return
|
||||
for i in data.size():
|
||||
@@ -162,13 +108,13 @@ func on_resp_holding(base_addr: int, data: Array):
|
||||
item.button_pressed = val0
|
||||
item.disabled = false
|
||||
regs_holding[byte1] = tools.set_bit(regs_holding[byte1], bit1, not val0)
|
||||
push_write_holding()
|
||||
unit_sch3.push_write_holding(regs_holding)
|
||||
|
||||
|
||||
## Вызывается по сигналу "приняты данные чтения регистров входов".[br]
|
||||
## [param base_addr] - Начальный адрес регистров.[br]
|
||||
## [param data] - Массив значений прочитанных из регистров.[br]
|
||||
func on_resp_input(base_addr: int, data: Array):
|
||||
func on_input(base_addr: int, data: Array):
|
||||
if !is_inside_tree():
|
||||
return
|
||||
if (base_addr != base_input_address) or (data.size() < regs_input_count):
|
||||
@@ -182,6 +128,7 @@ func on_resp_input(base_addr: int, data: Array):
|
||||
regs_input[base_addr + i] = data[i]
|
||||
# Таблица 3.15, Регистр PowerState
|
||||
$margin/vbox/grid1/lbl_freq.text = '%d' % (regs_input[4] & 0x1ff)
|
||||
$margin/vbox/grid1/lbl_freq.self_modulate = Color.WHITE
|
||||
$margin/vbox/grid1/state_freq.texture \
|
||||
= textures[STATE_VAL.GOOD] if regs_input[4] & (1 << 15) \
|
||||
else textures[STATE_VAL.ERROR]
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
extends Control
|
||||
|
||||
var scene_list=[]
|
||||
|
||||
func node_list(node):
|
||||
scene_list.append(node)
|
||||
|
||||
func _enter_tree():
|
||||
get_tree().node_added.connect(self.node_list)
|
||||
func _ready():
|
||||
get_tree().node_added.disconnect(self.node_list) #should be first string in _ready()
|
||||
print(scene_list)
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
[gd_scene format=3 uid="uid://e3sw6q228iai"]
|
||||
|
||||
[node name="Navig" type="Node2D"]
|
||||
@@ -94,7 +94,7 @@ expand_mode = 1
|
||||
[node name="fname" type="Label" parent="margin/vbox/grid"]
|
||||
self_modulate = Color(1, 1, 1, 0.627451)
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_horizontal = 2
|
||||
size_flags_vertical = 6
|
||||
theme_override_font_sizes/font_size = 17
|
||||
horizontal_alignment = 1
|
||||
|
||||
@@ -48,29 +48,6 @@ class SocketTCP extends TCPServer:
|
||||
peer.put_data(data)
|
||||
|
||||
|
||||
class Modbus extends ModbusClientRtu:
|
||||
var unit_name
|
||||
func send_to(data):
|
||||
if data['code'] == 'write':
|
||||
var addr = data['data'].pop_at(0)
|
||||
request_write(addr, data['data'])
|
||||
elif data['code'] == 'read_holding':
|
||||
request_read(data['data'][0], data['data'][1])
|
||||
elif data['code'] == 'read_input':
|
||||
request_read_input(data['data'][0], data['data'][1])
|
||||
|
||||
func try_reconnect():
|
||||
var port = settings.UnitProfiles[unit_name][1]
|
||||
var params = settings.UnitProfiles[unit_name][2]
|
||||
thread_stop()
|
||||
close()
|
||||
var rc = open(port, params[0], params[1], params[2], params[3], params[4])
|
||||
if rc != Error.OK:
|
||||
push_error('ошибка: \"%s\" - не открывается' % port)
|
||||
set_indication_timeout(params[5])
|
||||
thread_run()
|
||||
|
||||
|
||||
var poll_sockets: Array[Socket]
|
||||
var tcp_sockets: Array
|
||||
var units: Dictionary
|
||||
@@ -124,17 +101,9 @@ func create_serial(nm) -> Serial:
|
||||
return sp
|
||||
|
||||
|
||||
func create_modbus(nm, mbc_unit) -> Modbus:
|
||||
var port = settings.UnitProfiles[nm][1]
|
||||
var params = settings.UnitProfiles[nm][2]
|
||||
var mbc: = Modbus.new()
|
||||
mbc.connect('read', mbc_unit.on_read)
|
||||
mbc.connect('read_input', mbc_unit.on_read_input)
|
||||
mbc.connect('write', mbc_unit.on_write)
|
||||
var _rc = mbc.open(port, params[0], params[1], params[2], params[3], params[4])
|
||||
mbc.thread_run()
|
||||
mbc.unit_name = nm
|
||||
return mbc
|
||||
func create_modbus(nm) -> sch_3.Sch3:
|
||||
var unit_sch3: = sch_3.Sch3.new(nm)
|
||||
return unit_sch3
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
@@ -186,18 +155,14 @@ func _ready() -> void:
|
||||
unit_tcp.connect('line_changed', Callable(logger_page, 'on_line_changed').bind(unit_key))
|
||||
unit_tcp.connect('command_fail', Callable(logger_page, 'on_command_fail').bind(unit_key))
|
||||
|
||||
|
||||
if proto in MODBUS_PROTO:
|
||||
var unit_modbus = MODBUS_PROTO[proto].new(unit_name)
|
||||
var unit_key = settings.get_unit_key(unit_name)
|
||||
if logger_page:
|
||||
unit_modbus.connect('line_changed', Callable(logger_page, 'on_line_changed').bind(unit_key))
|
||||
unit_modbus.connect('command_fail', Callable(logger_page, 'on_command_fail').bind(unit_key))
|
||||
var mbc = create_modbus(unit_name, unit_modbus)
|
||||
units_modbus[unit_key] = unit_modbus
|
||||
units[unit_key] = unit_modbus
|
||||
mbcs[unit_key] = mbc
|
||||
|
||||
|
||||
if proto in SERIAL_PROTO:
|
||||
var unit_serial = SERIAL_PROTO[proto].new(unit_name)
|
||||
@@ -300,10 +265,7 @@ func _process(_delta: float) -> void:
|
||||
Error.FAILED: emit_signal('socket_error', [unit_serial, serial])
|
||||
for key in units_modbus:
|
||||
var unit_modbus = units_modbus[key]
|
||||
var mbc = mbcs[key]
|
||||
match unit_modbus.process(tick):
|
||||
Error.OK: mbc.send_to(unit_modbus.mb_data)
|
||||
Error.FAILED: emit_signal('socket_error', [unit_modbus, mbc])
|
||||
unit_modbus.process(tick)
|
||||
for sock_unit in tcp_sockets:
|
||||
var sock = sock_unit[0]
|
||||
var unit_tcp = sock_unit[1]
|
||||
|
||||
243
scripts/sch3.gd
243
scripts/sch3.gd
@@ -1,164 +1,147 @@
|
||||
class_name sch_3 extends Node
|
||||
|
||||
## Модуль Прибора Щ3
|
||||
|
||||
const ONLINE_TIMEOUT: int = 5000 ## Время ожидания данных
|
||||
const REQUEST_PERIOD: int = 1000 ## Переиод запроса, мс
|
||||
const ONLINE_TIMEOUT = 5000
|
||||
|
||||
|
||||
## Представление прибора Щ3
|
||||
class Sch3 extends unit.Unit:
|
||||
enum Sch3Fsm {
|
||||
READ_INPUT,
|
||||
READ_HOLDING,
|
||||
WRITE,
|
||||
CONNECT }
|
||||
|
||||
var mb_addr: = 0 ## Адрес устройства Modbus.
|
||||
var cmd_state: = CmdState.DONE ## Состояние выполнения текущей команды.
|
||||
var tick_rx: = 0 ## Время приёма крайнего сообщения.
|
||||
var tick_tx: = 0 ## Время отправки крайнего сообщения.
|
||||
var mb_data: Dictionary = {} ## Буфер для записи в регистры модбас
|
||||
var step_fsm: Sch3Fsm = Sch3Fsm.CONNECT
|
||||
var base_input_address: int = 0
|
||||
var regs_input_count: int = 0
|
||||
var base_holding_address: int = 0
|
||||
var regs_holding_count: int = 0
|
||||
var request_period: int = 300
|
||||
var reconnect_period: int = ONLINE_TIMEOUT
|
||||
var write_stack: Array
|
||||
var tick_fsm: int = 0
|
||||
var mbc: = ModbusClientRtu.new()
|
||||
var tick_fsm_delta: = reconnect_period
|
||||
|
||||
static var json_conv: = JSON.new()
|
||||
signal resp_input(addr: int, data: Array) ## Вызывается при получении сообщения c данными из регистров входов.
|
||||
signal resp_holding(addr: int, data: Array) ## Вызывается при получении сообщения c данными из регистров хранения.
|
||||
signal resp_write() ## Вызывается при получении подтверждения, что команда записи выполнена
|
||||
signal info_received(msg: String) ## Вызывается при получении информационного сообщения.
|
||||
signal except_received(msg: String) ## Вызывается при получении сообщения об исключительной ситуации.
|
||||
|
||||
func _init(nm: String, u: int = 0):
|
||||
mb_addr = u
|
||||
func try_connect() -> bool:
|
||||
var port = settings.UnitProfiles[name][1]
|
||||
var params = settings.UnitProfiles[name][2]
|
||||
mbc.thread_stop()
|
||||
mbc.queue_clear()
|
||||
mbc.flush()
|
||||
mbc.close()
|
||||
var rc = mbc.open(port, params[0], params[1], params[2], params[3], params[4])
|
||||
if rc != Error.OK:
|
||||
push_error('%s: не возможно открыть \"%s\" %s' % [name, port, params])
|
||||
return false
|
||||
mbc.set_response_timeout(params[5])
|
||||
mbc.thread_run()
|
||||
return true
|
||||
|
||||
|
||||
func push_write_holding(regs: Dictionary):
|
||||
var keys = regs.keys()
|
||||
keys.sort()
|
||||
var data: = []
|
||||
for k in keys:
|
||||
data.append(regs[k])
|
||||
write_stack.append([keys[0], data])
|
||||
|
||||
|
||||
func _on_timeout(tick: int) -> void:
|
||||
if step_fsm == Sch3Fsm.WRITE:
|
||||
while write_stack.size():
|
||||
var write_command = write_stack.pop_front()
|
||||
mbc.request_write(write_command[0], write_command[1])
|
||||
cmd_state = CmdState.WAIT
|
||||
tx_tick = tick
|
||||
step_fsm = Sch3Fsm.READ_INPUT
|
||||
elif step_fsm == Sch3Fsm.READ_INPUT:
|
||||
mbc.request_read_input(base_input_address, regs_input_count)
|
||||
cmd_state = CmdState.WAIT
|
||||
tx_tick = tick
|
||||
step_fsm = Sch3Fsm.READ_HOLDING
|
||||
elif step_fsm == Sch3Fsm.READ_HOLDING:
|
||||
mbc.request_read(base_holding_address, regs_holding_count)
|
||||
cmd_state = CmdState.WAIT
|
||||
tx_tick = tick
|
||||
step_fsm = Sch3Fsm.WRITE
|
||||
elif step_fsm == Sch3Fsm.CONNECT:
|
||||
try_connect()
|
||||
mbc.request_read(base_input_address, regs_input_count)
|
||||
cmd_state = CmdState.WAIT
|
||||
tx_tick = tick
|
||||
|
||||
signal write (addr: int, data: Array) ## Вызывается при получении сообщения подтверждения записи
|
||||
signal read (addr: int, data: Array) ## Вызывается при получении сообщения c данными из регистров входов.
|
||||
signal read_input (addr: int, data: Array) ## Вызывается при получении сообщения c данными из регистров хранения.
|
||||
signal error(msg: String) ## Вызывается при получении сообщения об исключительной ситуации.
|
||||
|
||||
|
||||
func _init(nm: String):
|
||||
name = nm
|
||||
mbc.connect('read', on_read)
|
||||
mbc.connect('read_input', on_read_input)
|
||||
mbc.connect('write', on_write)
|
||||
|
||||
## Изменяет состояние. Вызывать периодически.[br]
|
||||
|
||||
func parse(return_code: Error, base_addr: int, data: Array, code: String):
|
||||
if return_code == Error.OK:
|
||||
rx_tick = Time.get_ticks_msec()
|
||||
if cmd_state == CmdState.WAIT:
|
||||
cmd_state = CmdState.DONE
|
||||
emit_signal(code, base_addr, data)
|
||||
if not online:
|
||||
online = true
|
||||
emit_signal('line_changed', online)
|
||||
step_fsm = Sch3Fsm.WRITE
|
||||
tick_fsm_delta = request_period
|
||||
else:
|
||||
if cmd_state == CmdState.WAIT:
|
||||
cmd_state = CmdState.FAIL
|
||||
emit_signal('error', [code, return_code, base_addr, data])
|
||||
|
||||
|
||||
## Изменяет состояние. Вызывается периодически.[br]
|
||||
## [param tick] - Текущщее время в мс.[br]
|
||||
## Возвращает [b]Error.OK[/b] - если данные готовы для отправки.[br]
|
||||
## Возвращает [b]Error.ERR_BUSY[/b] - если данных для отправки нет.[br]
|
||||
func process(tick: int) -> Error:
|
||||
if online and ((tick - rx_tick) > ONLINE_TIMEOUT):
|
||||
online = false
|
||||
emit_signal('line_changed', self)
|
||||
if cmd_state == CmdState.WAIT:
|
||||
if (tick - tx_tick) > ONLINE_TIMEOUT:
|
||||
cmd_state = CmdState.FAIL
|
||||
emit_signal('command_fail', self)
|
||||
var rc = Error.ERR_BUSY
|
||||
if cmd_state == CmdState.SEND:
|
||||
tx_tick = tick
|
||||
cmd_state = CmdState.WAIT
|
||||
rc = Error.OK
|
||||
return rc
|
||||
|
||||
|
||||
## Производит разбор принятых данных. Вызывать при приёме данных на сокете.[br]
|
||||
## [param data] - Данные принятые на сокете.[br]
|
||||
## [param tick] - Текущщее время в мс.[br]
|
||||
func parse(message: Dictionary, tick: int) -> bool:
|
||||
rx_tick = tick
|
||||
if not online and (message['code'] != 'exception'):
|
||||
online = true
|
||||
emit_signal('line_changed', self)
|
||||
if message.has('code') and message.has('data'):
|
||||
var rc: = true
|
||||
var message_code = message['code']
|
||||
var message_data = message['data']
|
||||
match message_code:
|
||||
'resp_holding', 'resp_input':
|
||||
var addr = int(message_data[0])
|
||||
var regs = message_data.slice(1)
|
||||
for i in regs.size():
|
||||
regs[i] = int(regs[i])
|
||||
emit_signal(message_code, addr, regs)
|
||||
'resp_write':
|
||||
emit_signal(message_code)
|
||||
'info':
|
||||
var msg = message['data']
|
||||
emit_signal('info_received', msg)
|
||||
'exception':
|
||||
var msg = message['data']
|
||||
if online:
|
||||
if (tick - rx_tick) > ONLINE_TIMEOUT:
|
||||
online = false
|
||||
if cmd_state == CmdState.WAIT:
|
||||
cmd_state = CmdState.FAIL
|
||||
emit_signal('except_received', msg)
|
||||
rc = false
|
||||
_:
|
||||
var msg: String = 'код команды \"%s\" - не распознан' % message_code
|
||||
cmd_state = CmdState.FAIL
|
||||
emit_signal('parse_failed', msg)
|
||||
rc = false
|
||||
if (cmd_state == CmdState.WAIT) and rc:
|
||||
cmd_state = CmdState.DONE
|
||||
emit_signal('command_done')
|
||||
elif (cmd_state == CmdState.WAIT) and (not rc):
|
||||
cmd_state = CmdState.FAIL
|
||||
emit_signal('command_fail', self)
|
||||
return true
|
||||
|
||||
## Отправляет команду "записать в регистры".[br]
|
||||
## [param addr] - Начальный адрес регистров.[br]
|
||||
## [param count] - Данные для записи в регистры от начального адреса.[br]
|
||||
func write(addr: int, data: Array) -> bool:
|
||||
if cmd_state != CmdState.DONE: return false
|
||||
mb_data = {'code': 'write', 'data': [addr] + data, 'unit': mb_addr}
|
||||
cmd_state = CmdState.SEND
|
||||
return true
|
||||
|
||||
## Отправляет команду "читать регистры хранения".[br]
|
||||
## [param addr] - Начальный адрес регистров.[br]
|
||||
## [param count] - Количество регистров от начального адреса.[br]
|
||||
func read_holding(addr: int, count: int = 1) -> bool:
|
||||
if cmd_state != CmdState.DONE: return false
|
||||
mb_data = {'code': 'read_holding', 'data': [addr, count], 'unit': mb_addr}
|
||||
cmd_state = CmdState.SEND
|
||||
return true
|
||||
|
||||
## Отправляет команду "читать регистры входов".[br]
|
||||
## [param addr] - Начальный адрес регистров.[br]
|
||||
## [param count] - Количество регистров от начального адреса.[br]
|
||||
func read_input(addr: int, count: int = 1) -> bool:
|
||||
if cmd_state != CmdState.DONE: return false
|
||||
mb_data = {'code': 'read_input', 'data': [addr, count], 'unit': mb_addr}
|
||||
cmd_state = CmdState.SEND
|
||||
return true
|
||||
emit_signal('command_fail', name)
|
||||
emit_signal('line_changed', self)
|
||||
step_fsm = Sch3Fsm.CONNECT
|
||||
tick_fsm_delta = reconnect_period
|
||||
if (tick - tick_fsm) > tick_fsm_delta:
|
||||
tick_fsm = tick
|
||||
_on_timeout(tick)
|
||||
return Error.ERR_BUSY
|
||||
|
||||
|
||||
## Принимает результат команды "читать регистры хранения".[br]
|
||||
## [param return_code] - Код выполнения команды.[br]
|
||||
## [param base_addr] - Начальный адрес регистров.[br]
|
||||
## [param data] - Данные прочитанные из регистров.[br]
|
||||
## [param tick] - Время приёма пакета.[br]
|
||||
func on_read(return_code, base_addr, data):
|
||||
var tick = Time.get_ticks_msec()
|
||||
var msg: Dictionary
|
||||
if return_code == 0:
|
||||
msg['code'] = 'resp_holding'
|
||||
msg['data'] = [base_addr] + data
|
||||
else:
|
||||
msg['code'] = 'exception'
|
||||
parse(msg, tick)
|
||||
func on_read(return_code: Error, base_addr: int, data: Array):
|
||||
parse(return_code, base_addr, data, 'read')
|
||||
|
||||
|
||||
## Принимает результат команды "читать регистры входов".[br]
|
||||
## [param return_code] - Код выполнения команды.[br]
|
||||
## [param base_addr] - Начальный адрес регистров.[br]
|
||||
## [param data] - Данные прочитанные из регистров.[br]
|
||||
## [param tick] - Время приёма пакета.[br]
|
||||
func on_read_input(return_code, base_addr, data):
|
||||
var tick = Time.get_ticks_msec()
|
||||
var msg: Dictionary
|
||||
if return_code == 0:
|
||||
msg['code'] = 'resp_input'
|
||||
msg['data'] = [base_addr] + data
|
||||
else:
|
||||
msg['code'] = 'exception'
|
||||
parse(msg, tick)
|
||||
func on_read_input(return_code: Error, base_addr: int, data: Array):
|
||||
parse(return_code, base_addr, data, 'read_input')
|
||||
|
||||
|
||||
## Принимает результат команды "записать в регистры".[br]
|
||||
## [param return_code] - Код выполнения команды.[br]
|
||||
## [param base_addr] - Начальный адрес регистров.[br]
|
||||
## [param data] - Данные прочитанные из регистров.[br]
|
||||
## [param tick] - Время приёма пакета.[br]
|
||||
func on_write(return_code, _base_addr, _data):
|
||||
var tick = Time.get_ticks_msec()
|
||||
var msg: Dictionary
|
||||
msg['data'] = 'resp_write'
|
||||
if return_code == 0:
|
||||
msg['code'] = 'resp_write'
|
||||
else:
|
||||
msg['code'] = 'exception'
|
||||
parse(msg, tick)
|
||||
func on_write(return_code: Error, base_addr: int, data: Array):
|
||||
parse(return_code, base_addr, data, 'write')
|
||||
|
||||
@@ -5,11 +5,11 @@ class Unit extends Object:
|
||||
|
||||
## Состояние автомата выполнения команды.
|
||||
enum CmdState {
|
||||
UNCK, ## Номер команды не ининциирован.
|
||||
SEND, ## Буфер готов для отправки.
|
||||
WAIT, ## Ожидание выполнения.
|
||||
DONE, ## Команда выполнена.
|
||||
FAIL ## Команда не выполнена.
|
||||
UNCK, ## Номер команды не ининциирован.
|
||||
SEND, ## Буфер готов для отправки.
|
||||
WAIT, ## Ожидание выполнения.
|
||||
DONE, ## Команда выполнена.
|
||||
FAIL, ## Команда не выполнена.
|
||||
}
|
||||
|
||||
signal line_changed(unit) ## Вызывается при изменении состояния связи
|
||||
@@ -18,6 +18,7 @@ class Unit extends Object:
|
||||
signal parse_failed(unit) ## Вызывается при обнаружении ошибки во входных данных
|
||||
signal data_received(data: PackedByteArray) ## Вызывается по приёму данных
|
||||
|
||||
var cmd_state = CmdState.UNCK ## Состояние выполнения команды
|
||||
var tx_data: = PackedByteArray() ## Буфер для отправки.
|
||||
var tx_len: int = 0 ## Длина последнего отправленного пакета.
|
||||
var tx_tick: int = 0 ## Тик-время отправки буфера
|
||||
@@ -25,4 +26,4 @@ class Unit extends Object:
|
||||
var online: bool = false ## Состояние подключения устройства
|
||||
var name: String = '<без имени>' ## Уникальное имя устройства
|
||||
func _init(nm) -> void: self.name = nm
|
||||
func _to_string() -> String: return String('устройство: %s %s' % [self.name, ['отключен', 'на связи'][int(online)]])
|
||||
func _to_string() -> String: return String('<\"%s\", %s>' % [self.name, ['отключен', 'на связи'][int(online)]])
|
||||
|
||||
@@ -29,17 +29,16 @@ class YaU07 extends unit.Unit:
|
||||
|
||||
var cmd_num_rx: = 0 ## Номер команды принятый
|
||||
var cmd_num_tx: = 0 ## Номер команды переданный
|
||||
var cmd_state = CmdState.UNCK ## Состояние выполнения команды
|
||||
var cmd_result: = 1 ## Результат выполнения команды. Устанавливается в ЯУ-07
|
||||
var cmd_data: = PackedByteArray() ## Данные команды. Устанавливается в ЯУ-07
|
||||
var cmd_retry = RETRY_COUNT ## Счётчик оставшихся попыток выполнить команду
|
||||
var cmd_retry: = RETRY_COUNT ## Счётчик оставшихся попыток выполнить команду
|
||||
var cmd_code_rx = 0 ## Код последней выполненой команды
|
||||
var cmd_code_tx = 0 ## Код последней отправленной команды
|
||||
var cmd_tick_tx = 0.0 ## Время последней отправки команды
|
||||
var cmd_tick_rx = 0.0 ## Время последнего приёма команды
|
||||
var cmd_tick_tx: = 0 ## Время последней отправки команды
|
||||
var cmd_tick_rx: = 0 ## Время последнего приёма команды
|
||||
var status: = PackedByteArray() ## Поле "состояние прибора"
|
||||
var isa_ports = {} ## Результат выполнения команды "читать порт"
|
||||
var isa_block = {} ## Результат выполнения команды "читать массив из порта"
|
||||
var isa_ports: = {} ## Результат выполнения команды "читать порт"
|
||||
var isa_block: = {} ## Результат выполнения команды "читать массив из порта"
|
||||
var flash_data: = PackedByteArray() ## Данные прочитанные из Flash
|
||||
var flash_len: = 0 ## Длинна данных прочитанных из Flash
|
||||
|
||||
@@ -99,14 +98,14 @@ class YaU07 extends unit.Unit:
|
||||
tx_data.encode_u16(8, 0)
|
||||
tx_data.encode_u16(10, len(fl_data))
|
||||
tx_len = 11 + len(fl_data) * 2
|
||||
for i in range(len(fl_data)):
|
||||
for i in len(fl_data):
|
||||
tx_data.encode_u16(12 + i * 2, fl_data[i])
|
||||
elif cmd_code == CmdCode.WRITE_FLASH_MP550:
|
||||
tx_data.encode_u8(7, id_sector)
|
||||
tx_data.encode_u16(8, 0)
|
||||
tx_data.encode_u16(10, len(fl_data) * 2)
|
||||
tx_len = 12 + len(fl_data) * 2
|
||||
for i in range(len(fl_data)):
|
||||
for i in len(fl_data):
|
||||
tx_data.encode_u16(12 + i * 2, fl_data[i])
|
||||
elif cmd_code == CmdCode.READ_FLASH:
|
||||
tx_len = 11
|
||||
@@ -167,7 +166,6 @@ class YaU07 extends unit.Unit:
|
||||
block_len /= 2
|
||||
tx_data[6] = cmd_code
|
||||
tx_data.encode_u16(7, block_len)
|
||||
|
||||
tx_data.encode_u16(9, int(port[0]))
|
||||
cmd_state = CmdState.SEND
|
||||
cmd_code_tx = cmd_code
|
||||
@@ -178,7 +176,7 @@ class YaU07 extends unit.Unit:
|
||||
or (cmd_state == CmdState.SEND) \
|
||||
or (cmd_state == CmdState.UNCK):
|
||||
return false
|
||||
if not (cmd_code == CmdCode.LOAD_EMS_DONE):
|
||||
if cmd_code != CmdCode.LOAD_EMS_DONE:
|
||||
return false
|
||||
cmd_num_tx = (cmd_num_rx + 1) % 0xffff
|
||||
tx_data.encode_u16(0, cmd_retry)
|
||||
|
||||
Reference in New Issue
Block a user