diff --git a/addons/godot-logger/scripts/logger.gd b/addons/godot-logger/scripts/logger.gd index 7a7211f..e102aca 100644 --- a/addons/godot-logger/scripts/logger.gd +++ b/addons/godot-logger/scripts/logger.gd @@ -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. diff --git a/modbus/pymodbus/constants.gd b/modbus/pymodbus/constants.gd new file mode 100644 index 0000000..11350d3 --- /dev/null +++ b/modbus/pymodbus/constants.gd @@ -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 diff --git a/modbus/pymodbus/exceptions.gd b/modbus/pymodbus/exceptions.gd new file mode 100644 index 0000000..455f5ed --- /dev/null +++ b/modbus/pymodbus/exceptions.gd @@ -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 ': %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() diff --git a/modbus/pymodbus/pdu.gd b/modbus/pymodbus/pdu.gd new file mode 100644 index 0000000..5da034a --- /dev/null +++ b/modbus/pymodbus/pdu.gd @@ -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) diff --git a/modbus/pymodbus/server/reactive/default_config.json b/modbus/pymodbus/server/reactive/default_config.json new file mode 100644 index 0000000..e69de29 diff --git a/modbus/pymodbus/utilities.gd b/modbus/pymodbus/utilities.gd new file mode 100644 index 0000000..bc0e47c --- /dev/null +++ b/modbus/pymodbus/utilities.gd @@ -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 diff --git a/modbus/pymodbus/version.gd b/modbus/pymodbus/version.gd new file mode 100644 index 0000000..008111d --- /dev/null +++ b/modbus/pymodbus/version.gd @@ -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 + ## ...
+    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()]
diff --git a/scenes/pribor-uyep/block-input.tscn b/scenes/pribor-uyep/block-input.tscn
index 49adf91..524610a 100644
--- a/scenes/pribor-uyep/block-input.tscn
+++ b/scenes/pribor-uyep/block-input.tscn
@@ -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"]
diff --git a/scenes/pribor-uyep/sch3-view.gd b/scenes/pribor-uyep/sch3-view.gd
index b057192..319abb0 100644
--- a/scenes/pribor-uyep/sch3-view.gd
+++ b/scenes/pribor-uyep/sch3-view.gd
@@ -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]
diff --git a/scenes/tilemap/Control.gd b/scenes/tilemap/Control.gd
index ccba361..e69de29 100644
--- a/scenes/tilemap/Control.gd
+++ b/scenes/tilemap/Control.gd
@@ -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)
diff --git a/scenes/tilemap/navig.tscn b/scenes/tilemap/navig.tscn
deleted file mode 100644
index a2178f3..0000000
--- a/scenes/tilemap/navig.tscn
+++ /dev/null
@@ -1,3 +0,0 @@
-[gd_scene format=3 uid="uid://e3sw6q228iai"]
-
-[node name="Navig" type="Node2D"]
diff --git a/scenes/контроль/элемент-я.tscn b/scenes/контроль/элемент-я.tscn
index 4ba2c86..0cd75eb 100644
--- a/scenes/контроль/элемент-я.tscn
+++ b/scenes/контроль/элемент-я.tscn
@@ -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
diff --git a/scripts/network.gd b/scripts/network.gd
index adc4ba5..fc68b19 100644
--- a/scripts/network.gd
+++ b/scripts/network.gd
@@ -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]
diff --git a/scripts/sch3.gd b/scripts/sch3.gd
index 426a347..d69a2b9 100644
--- a/scripts/sch3.gd
+++ b/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')
diff --git a/scripts/unit.gd b/scripts/unit.gd
index 5209cc9..2e69501 100644
--- a/scripts/unit.gd
+++ b/scripts/unit.gd
@@ -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)]])
diff --git a/scripts/yau07.gd b/scripts/yau07.gd
index 8c7619e..7a2e0f8 100644
--- a/scripts/yau07.gd
+++ b/scripts/yau07.gd
@@ -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)