diff --git a/scenes/pribor-uyep/sch3-view.gd b/scenes/pribor-uyep/sch3-view.gd index 49dc46cf..69bb62be 100644 --- a/scenes/pribor-uyep/sch3-view.gd +++ b/scenes/pribor-uyep/sch3-view.gd @@ -8,132 +8,88 @@ 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: = 7 ## Адрес устройства на шине Modbus -@export var request_period: = 0.2 ## Период в секундах отправки команд на шину Modbus -@export var reconnect_period: = 3.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 switches_sync: = false -var timer = Timer.new() -var timer_reconnect = Timer.new() -var step_fsm: = Sch3Fsm.CONNECT +var regs_input: = {} ## Регистры входные +var regs_holding: = {} ## Регистры хранения +var switches_sync: = false ## Флаг. Если установлен, то можно переключать -enum Sch3Fsm { - READ_INPUT, - READ_HOLDING, - WRITE, - CONNECT } +func on_error(msg): push_error(msg) -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, mbc: ModbusClientRtu) -> void: - push_warning(step_fsm) - if step_fsm == Sch3Fsm.WRITE: - timer.wait_time = request_period - if write_stack.size(): - var ad = write_stack.pop_front() - mbc.write(ad.addr, ad.data) - step_fsm = Sch3Fsm.READ_INPUT - elif step_fsm == Sch3Fsm.READ_INPUT: - unit_sch3.read_input(base_input_address, regs_input_count) - elif step_fsm == Sch3Fsm.READ_HOLDING: - unit_sch3.read_holding(base_holding_address, regs_holding_count) - elif step_fsm == Sch3Fsm.CONNECT: - timer.wait_time = reconnect_period - mbc.queue_clear() - mbc.flush() - mbc.try_reconnect() - mbc.request_read(base_input_address, regs_input_count) +## Вызывается, когда состояния прибора Щ3 "на связи" изменяется[br] +## [param unit_sch3] - Экземпляр представления прибора Щ3 +func on_line_changed_sch3(online: bool): + for item in get_tree().get_nodes_in_group('bits_items_good'): + item.texture = textures[STATE_VAL.NONE] + if not online: + for item in get_tree().get_nodes_in_group('bits_items_switch'): + item.disabled = true + regs_holding.clear() + regs_input.clear() + switches_sync = false + $margin/vbox/grid1/lbl_freq.text = '<нет данных>' + $margin/vbox/grid1/state_freq.texture = textures[STATE_VAL.NONE] + $margin/vbox/grid/state.texture = textures[STATE_VAL.NONE] ## Вызывается при изменении состояния выключателя[br] ## [param val] - Состояние выключателя[br] ## [param meta] - Адреса регистров и индексы битов назначенных выключателю[br] -func on_toggled_switch(val: bool, meta: Array): +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[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() + unit_sch3.push_write_holding(regs_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: +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] - 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.connect('toggled', on_toggled_switch.bind(meta, unit_sch3)) 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) - var mbc = network.mbcs[unit_key] - timer.connect('timeout', _on_timeout.bind(unit_sch3, mbc)) - add_child(timer) - timer.start(request_period) + 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 on_command_fail(unit_sch3): - unit_sch3.cmd_state = unit.Unit.CmdState.DONE - - -## Вызывается, когда состояния прибора Щ3 "на связи" изменяется[br] -## [param unit_sch3] - Экземпляр представления прибора Щ3 -func on_line_changed(unit_sch3: sch_3.Sch3): - for item in get_tree().get_nodes_in_group('bits_items_good'): - item.texture = textures[STATE_VAL.NONE] - if not unit_sch3.online: - for item in get_tree().get_nodes_in_group('bits_items_switch'): - item.disabled = true - regs_holding.clear() - regs_input.clear() - write_stack.clear() - switches_sync = false - $margin/vbox/grid1/lbl_freq.text = '<нет данных>' - $margin/vbox/grid1/state_freq.texture = textures[STATE_VAL.NONE] - $margin/vbox/grid/state.texture = textures[STATE_VAL.NONE] - step_fsm = Sch3Fsm.CONNECT - else: - timer.wait_time = request_period +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): - step_fsm = Sch3Fsm.WRITE +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(): @@ -151,14 +107,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): - step_fsm = Sch3Fsm.READ_HOLDING +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): diff --git a/scripts/network.gd b/scripts/network.gd index d8adc3ca..fc68b199 100644 --- a/scripts/network.gd +++ b/scripts/network.gd @@ -48,32 +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:%s %s' % [unit_name, port, params]) - return false - set_response_timeout(params[5]) - set_indication_timeout(params[5]) - thread_run() - return true - - var poll_sockets: Array[Socket] var tcp_sockets: Array var units: Dictionary @@ -127,15 +101,9 @@ func create_serial(nm) -> Serial: return sp -func create_modbus(nm, mbc_unit) -> Modbus: - 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) - mbc.unit_name = nm - mbc.try_reconnect() - mbc.thread_run() - return mbc +func create_modbus(nm) -> sch_3.Sch3: + var unit_sch3: = sch_3.Sch3.new(nm) + return unit_sch3 func _ready() -> void: @@ -193,10 +161,8 @@ func _ready() -> void: 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) @@ -299,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 d3589856..937e71f1 100644 --- a/scripts/sch3.gd +++ b/scripts/sch3.gd @@ -1,162 +1,148 @@ 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 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: + push_warning(tick_fsm_delta) + 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) -> bool: - 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': - rc = false - _: - var msg: String = 'код команды \"%s\" - не распознан' % message_code + if online: + if (tick - rx_tick) > ONLINE_TIMEOUT: + online = false + if cmd_state == CmdState.WAIT: 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', online) + 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 msg: Dictionary - if return_code == Error.OK: - rx_tick = Time.get_ticks_msec() - msg['code'] = 'resp_holding' - msg['data'] = [base_addr] + data - else: - msg['code'] = 'exception' - msg['data'] = 'операция чтения входных регистров не выполнена (%d)' % base_addr - parse(msg) + 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 msg: Dictionary - if return_code == Error.OK: - rx_tick = Time.get_ticks_msec() - msg['code'] = 'resp_input' - msg['data'] = [base_addr] + data - else: - msg['code'] = 'exception' - msg['data'] = 'операция чтения входных регистров не выполнена (%d)' % base_addr - parse(msg) + 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 msg: Dictionary - if return_code == Error.OK: - rx_tick = Time.get_ticks_msec() - msg['data'] = [] - msg['code'] = 'resp_write' - else: - msg['data'] = 'операция записи в регистры не выполнена (%d, %s)' % [base_addr, data] - msg['code'] = 'except_received' - parse(msg) + func on_write(return_code: Error, base_addr: int, data: Array): + parse(return_code, base_addr, data, 'write')