diff --git a/modbus/modbus.gd b/modbus/modbus.gd deleted file mode 100644 index 706df262..00000000 --- a/modbus/modbus.gd +++ /dev/null @@ -1,10 +0,0 @@ -extends Node - - -class ModbusClient: - var name: String - signal command_done(unit: ModbusClient) ## Вызывается при успешном выполнении команды - signal command_fail(unit: ModbusClient) ## Вызывается при возникновении ошибки во время выполнения команды - signal line_changed(unit: ModbusClient) ## Вызывается при смене состояния связи с ячейкой - signal parse_failed(unit: ModbusClient) ## Вызывается при возникновении ошибки во время разбора пакета - func process(tick): pass diff --git a/project.godot b/project.godot index 30566f6e..eeac9e4f 100644 --- a/project.godot +++ b/project.godot @@ -56,7 +56,7 @@ yemsconsts="*res://scripts/yems-consts.gd" interfer="*res://scripts/interfer.gd" hotkeys="*res://scripts/hotkeys.gd" mercator="*res://scripts/mercator.gd" -modbus="*res://modbus/modbus.gd" +modbus="*res://scripts/modbus.gd" tcp5p28="*res://scripts/tcp5p28.gd" [debug] diff --git a/scripts/modbus.gd b/scripts/modbus.gd new file mode 100644 index 00000000..db99eb12 --- /dev/null +++ b/scripts/modbus.gd @@ -0,0 +1,130 @@ +extends Node + +const ONLINE_TIMEOUT = 5.0 ## Время ожидания пакета от ячейки, секунды + +class ModbusClient: + + ## Состояние автомата выполнения команды + enum CmdState { + DONE, ## Команда выполнена + FAIL, ## Команда не выполнена + SEND, ## Буфер готов для отправки + WAIT ## Ожидание выполнения + } + + var name: String + var unit: = 1 ## Адрес устройства Modbus + var cmd_state: = CmdState.DONE ## Код текущей команды + var tx_data: PackedByteArray ## Данные для отправки + var online: = false ## Флаг. Если установлен, устройство на связи. Активный уровень - [b]true[/b]. + var tick_rx: = 0 ## Время приёма крайнего сообщения + var tick_tx: = 0 ## Время отправки крайнего сообщения + + static var json_conv: = JSON.new() + + signal command_done() ## Вызывается при успешном выполнении команды + signal command_fail() ## Вызывается при возникновении ошибки во время выполнения команды + signal line_changed() ## Вызывается при смене состояния связи с ячейкой + signal data_received() ## Вызывается при приёме сообщения, до его разбора + signal parse_failed(msg: String) ## Вызывается при возникновении ошибки во время разбора пакета. Передаёт сообщение об ошибке + + signal holding_received(addr: int, data: Array) ## Вызывается при получении сообщения c данными из регистров хранения + signal input_received(addr: int, data: Array) ## Вызывается при получении сообщения c данными из регистров входов + signal info_received(msg: String) ## Вызывается при получении информационного сообщения + signal except_received(msg: String) ## Вызывается при получении сообщения об исключительной ситуации + + func _init(nm: String, u: int = 1): + unit = u + name = nm + + ## Изменяет состояние. Вызывать периодически[br] + ## [param tick] - Текущщее время в мс[br] + func process(tick: int): + if online and ((tick - tick_rx) > ONLINE_TIMEOUT): + online = false + emit_signal('line_changed') + if cmd_state == CmdState.WAIT: + cmd_state = CmdState.FAIL + emit_signal('command_fail') + var rc = Error.ERR_BUSY + if cmd_state == CmdState.SEND: + rc = Error.OK + tick_tx = tick + cmd_state = CmdState.WAIT + return rc + + ## Производит разбор принятых данных. Вызывать при приёме данных на сокете[br] + ## [param data] - Данные принятые на сокете[br] + ## [param tick] - Текущщее время в мс[br] + func parse(data: PackedByteArray, tick: int) -> bool: + tick_rx = tick + if not online: + online = true + emit_signal('line_changed') + var utf8_data = data.get_string_from_utf8() + if Error.OK == json_conv.parse(utf8_data): + emit_signal('data_received') + var message: Dictionary = json_conv.get_data() + if message.has('code') and message.has('data'): + var rc: = true + var message_data = message['data'] + match message['code']: + 'read_input': + var addr = message_data[0] + var regs = message_data.slice(1) + emit_signal('input_received', addr, regs) + 'read_holding': + var addr = message_data[0] + var regs = message_data.slice(1) + emit_signal('holding_received', addr, regs) + 'info': + var msg = message['data'] + emit_signal('info_received', msg) + 'exception': + var msg = message['data'] + emit_signal('except_received', msg) + rc = false + _: + var msg: String = 'код команды \"%s\" - не распознан' % message['code'] + 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') + else: + var message = json_conv.get_error_message() + emit_signal('parse_failed', message) + return true + + ## Отправляет команду "записать в регистры".[br] + ## [param addr] - Начальный адрес регистров.[br] + ## [param count] - Данные для записи в регистры от начального адреса.[br] + func write(addr: int, data: Array) -> bool: + if cmd_state != CmdState.DONE: return false + var json_dict = {'code': 'write', 'data': [addr] + data} + tx_data = json_conv.stringify(json_dict).to_utf8_buffer() + cmd_state = CmdState.SEND + return true + + ## Отправляет команду "читать регистры хранения".[br] + ## [param addr] - Начальный адрес регистров.[br] + ## [param count] - Количество регистров от начального адреса.[br] + func read_holdind(addr: int, count: int = 1) -> bool: + if cmd_state != CmdState.DONE: return false + var json_dict = {'code': 'read_holding', 'data': [addr, count]} + tx_data = json_conv.stringify(json_dict).to_utf8_buffer() + 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 + var json_dict = {'code': 'read_input', 'data': [addr, count]} + tx_data = json_conv.stringify(json_dict).to_utf8_buffer() + cmd_state = CmdState.SEND + return true