class_name rtr extends Node #region Перечисления enum State { IDLE, REQUESTING_AFSP_LEFT, REQUESTING_AFSP_RIGHT, REQUESTING_BPO_LEFT, REQUESTING_BPO_RIGHT, WAITING_TIMEOUT } #endregion #region Сигналы # Сигналы отправки запроса signal request_sent_afsp_left(url: String) signal request_sent_afsp_right(url: String) signal request_sent_bpo_left(url: String) signal request_sent_bpo_right(url: String) # Сигналы получения ответа signal response_received_afsp_left(response: String, success: bool) signal response_received_afsp_right(response: String, success: bool) signal response_received_bpo_left(response: String, success: bool) signal response_received_bpo_right(response: String, success: bool) # Сигналы исправности (0 - нет связи, 1 - всё пройдено, 2 - есть ошибки) signal serviceability_rtr_afsp_left(state: int) signal serviceability_rtr_afsp_right(state: int) signal serviceability_rtr_bpo(state: int) # Общий для левого и правого БПО #endregion #region Константы const RESULT_STRINGS = { HTTPRequest.Result.RESULT_SUCCESS: 'Запрос успешен', HTTPRequest.Result.RESULT_CHUNKED_BODY_SIZE_MISMATCH: 'Несоответствие размеров частей сообщения', HTTPRequest.Result.RESULT_CANT_CONNECT: 'Запрос не выполнен при подключении', HTTPRequest.Result.RESULT_CANT_RESOLVE: 'Запрос не выполнен при разрешении', HTTPRequest.Result.RESULT_CONNECTION_ERROR: 'Запрос не выполнен из-за ошибки соединения', HTTPRequest.Result.RESULT_TLS_HANDSHAKE_ERROR: 'Запрос не выполнен на TLS рукопожатии', HTTPRequest.Result.RESULT_NO_RESPONSE: 'Запрос не имеет ответа (пока)', HTTPRequest.Result.RESULT_BODY_SIZE_LIMIT_EXCEEDED: 'Запрос превысил максимальный размер', HTTPRequest.Result.RESULT_BODY_DECOMPRESS_FAILED: 'Не удалось распаковать сообщение', HTTPRequest.Result.RESULT_REQUEST_FAILED: 'Запрос не удался', HTTPRequest.Result.RESULT_DOWNLOAD_FILE_CANT_OPEN: 'Невозможно открыть файл загрузки', HTTPRequest.Result.RESULT_DOWNLOAD_FILE_WRITE_ERROR: 'Невозможно записать в файл загрузки', HTTPRequest.Result.RESULT_REDIRECT_LIMIT_REACHED: 'Достигнут лимит перенаправлений', HTTPRequest.Result.RESULT_TIMEOUT: 'Время истекло' } #endregion #region Переменные var http_request: HTTPRequest var addresses: Dictionary var current_state: State = State.IDLE var current_url: String = '' var timeout_timer: Timer # Результаты запросов АФСП var last_response_afsp_left: String = '' var last_response_afsp_right: String = '' var last_error_afsp_left: String = '' var last_error_afsp_right: String = '' # Результаты запросов БПО var last_response_bpo_left: String = '' var last_response_bpo_right: String = '' var last_error_bpo_left: String = '' var last_error_bpo_right: String = '' # Статус online для каждого борта var online_afsp_left: bool = false var online_afsp_right: bool = false var online_bpo_left: bool = false var online_bpo_right: bool = false # Состояния исправности для БПО var bpo_left_state: int = 0 # 0 - нет связи, 1 - успешно, 2 - ошибка var bpo_right_state: int = 0 var last_success_time_afsp_left: int = 0 var last_success_time_afsp_right: int = 0 var last_success_time_bpo_left: int = 0 var last_success_time_bpo_right: int = 0 var timeout: float var current_device_type: String = '' #endregion #region Инициализация func _ready() -> void: http_request = HTTPRequest.new() add_child(http_request) http_request.request_completed.connect(_on_request_completed) timeout_timer = Timer.new() add_child(timeout_timer) timeout_timer.timeout.connect(_on_timeout) timeout_timer.one_shot = true signaller.conn(signaller.start_contol_device, Callable(self, '_on_start_contol_device')) #endregion #region Публичные методы func get_result_string(result: int) -> String: return RESULT_STRINGS.get(result, '<неизвестный код результата HTTPRequest - %d>' % result) # Геттеры АФСП func get_afsp_left_response() -> String: return last_response_afsp_left func get_afsp_right_response() -> String: return last_response_afsp_right func get_afsp_left_error() -> String: return last_error_afsp_left func get_afsp_right_error() -> String: return last_error_afsp_right func has_afsp_left_success() -> bool: return not last_response_afsp_left.is_empty() func has_afsp_right_success() -> bool: return not last_response_afsp_right.is_empty() func is_afsp_left_online() -> bool: return online_afsp_left func is_afsp_right_online() -> bool: return online_afsp_right # Геттеры БПО func get_bpo_left_response() -> String: return last_response_bpo_left func get_bpo_right_response() -> String: return last_response_bpo_right func get_bpo_left_error() -> String: return last_error_bpo_left func get_bpo_right_error() -> String: return last_error_bpo_right func has_bpo_left_success() -> bool: return not last_response_bpo_left.is_empty() func has_bpo_right_success() -> bool: return not last_response_bpo_right.is_empty() func is_bpo_left_online() -> bool: return online_bpo_left func is_bpo_right_online() -> bool: return online_bpo_right # Обновление статуса online func _update_online_status(device: String, side: String, is_online: bool) -> void: var unit_name = '' if device == 'afsp': unit_name = 'уарэп-афсп-' + ('левый' if side == 'left' else 'правый') else: unit_name = 'уарэп-бпо-' + ('1' if side == 'left' else '2') var unit_instance = network.get_unit_instance(unit_name) if unit_instance: unit_instance.rx_tick = Time.get_ticks_msec() if unit_instance.online != is_online: unit_instance.online = is_online unit_instance.emit_signal('line_changed', unit_instance) #endregion #region Обработчики сигналов func _on_start_contol_device() -> void: if current_state != State.IDLE: return # Сбрасываем предыдущие результаты last_response_afsp_left = '' last_response_afsp_right = '' last_error_afsp_left = '' last_error_afsp_right = '' last_response_bpo_left = '' last_response_bpo_right = '' last_error_bpo_left = '' last_error_bpo_right = '' bpo_left_state = 0 bpo_right_state = 0 addresses['Адрес АФСП левого борта'] = ProjectSettings.get_setting('application/config/%s' % 'Адрес АФСП левого борта') addresses['Адрес АФСП правого борта'] = ProjectSettings.get_setting('application/config/%s' % 'Адрес АФСП правого борта') addresses['Адрес БПО левого борта'] = ProjectSettings.get_setting('application/config/%s' % 'Адрес БПО левого борта') addresses['Адрес БПО правого борта'] = ProjectSettings.get_setting('application/config/%s' % 'Адрес БПО правого борта') timeout = float(ProjectSettings.get_setting('application/config/%s' % 'Период опроса состояния АФСП, секунды')) # Начинаем с запроса к АФСП левого борта _request_next(State.REQUESTING_AFSP_LEFT) func _on_request_completed(result: int, response_code: int, _headers: PackedStringArray, body: PackedByteArray) -> void: if current_state == State.WAITING_TIMEOUT: return timeout_timer.stop() var current_time = Time.get_ticks_msec() var is_afsp = (current_state == State.REQUESTING_AFSP_LEFT or current_state == State.REQUESTING_AFSP_RIGHT) var is_left = (current_state == State.REQUESTING_AFSP_LEFT or current_state == State.REQUESTING_BPO_LEFT) var side_str = 'left' if is_left else 'right' var device_str = 'afsp' if is_afsp else 'bpo' if response_code == 200: var response_text = body.get_string_from_utf8() _save_response(response_text) var test_passed_all = _check_all_tests_passed(response_text) _update_success_time(device_str, side_str, current_time) _update_online_status(device_str, side_str, true) _emit_response_signal(device_str, side_str, response_text, true) var serviceability_state = 1 if test_passed_all else 2 if is_afsp: _emit_afsp_serviceability_signal(side_str, serviceability_state) else: _update_bpo_state(side_str, serviceability_state) _emit_bpo_serviceability_signal() _on_request_finished() else: var error_msg = 'HTTP ошибка %d: %s' % [response_code, get_result_string(result)] log.message(Logger.ERROR, error_msg) _save_error(error_msg) _update_online_status(device_str, side_str, false) _emit_response_signal(device_str, side_str, error_msg, false) if is_afsp: _emit_afsp_serviceability_signal(side_str, 0) else: _update_bpo_state(side_str, 0) _emit_bpo_serviceability_signal() _on_request_finished() func _update_bpo_state(side: String, state: int) -> void: if side == 'left': bpo_left_state = state else: bpo_right_state = state func _calculate_bpo_combined_state() -> int: # Если оба не на связи -> 0 if bpo_left_state == 0 and bpo_right_state == 0: return 0 # Если хоть один с ошибкой -> 2 if bpo_left_state == 2 or bpo_right_state == 2: return 2 # Если один не на связи (0), а другой успешно (1) -> 1 if (bpo_left_state == 0 and bpo_right_state == 1) or (bpo_left_state == 1 and bpo_right_state == 0): return 1 # Оба успешно -> 1 if bpo_left_state == 1 and bpo_right_state == 1: return 1 # По умолчанию return 0 func _emit_bpo_serviceability_signal() -> void: var combined_state = _calculate_bpo_combined_state() emit_signal('serviceability_rtr_bpo', combined_state) func _state_to_string(state: int) -> String: match state: 0: return 'нет связи' 1: return 'успешно' 2: return 'ошибка' return 'неизвестно' func _emit_afsp_serviceability_signal(side: String, state: int) -> void: if side == 'left': emit_signal('serviceability_rtr_afsp_left', state) else: emit_signal('serviceability_rtr_afsp_right', state) func _check_all_tests_passed(response_text: String) -> bool: var json = JSON.new() if json.parse(response_text) != OK: log.message(Logger.ERROR, '[rtr] Ошибка парсинга JSON') return false var data = json.data var self_tests = data.get('SelfTest', []) var all_passed = true for test in self_tests: var system_list = test.get('SystemList', []) for system in system_list: # Рекурсивно проверяем систему и её подсистемы if not _check_system_passed(system): all_passed = false # Не прерываем цикл, чтобы найти все ошибки return all_passed # Рекурсивная функция для проверки SystemList и вывода ошибок func _check_system_passed(system: Dictionary) -> bool: var system_passed = true # Проверяем устройства в текущей системе var dev_list = system.get('DevList', []) for dev in dev_list: var test_passed = dev.get('TestPassed', false) if not test_passed: system_passed = false # Формируем читаемое имя устройства для вывода var dev_name = dev.get('DevName', 'Неизвестное устройство') var dev_id = dev.get('DevId', '?') var error_text = dev.get('ErrorText', 'Нет описания ошибки') print_debug('[rtr] НАЙДЕНА ОШИБКА! Устройство: %s (ID: %s). TestPassed: false. Причина: %s' % [dev_name, dev_id, error_text]) # Рекурсивно проверяем вложенные подсистемы var sub_systems = system.get('SystemList', []) for sub in sub_systems: if not _check_system_passed(sub): system_passed = false return system_passed # Исправленная функция подсчета (использует Dictionary для передачи по ссылке) func _count_tests_in_system(system: Dictionary, counters: Dictionary) -> void: var dev_list = system.get('DevList', []) for dev in dev_list: counters['total'] += 1 if dev.get('TestPassed', false): counters['passed'] += 1 var sub_systems = system.get('SystemList', []) for sub in sub_systems: _count_tests_in_system(sub, counters) func _on_timeout() -> void: var was_state = current_state current_state = State.WAITING_TIMEOUT http_request.cancel_request() var is_afsp = (was_state == State.REQUESTING_AFSP_LEFT or was_state == State.REQUESTING_AFSP_RIGHT) var is_left = (was_state == State.REQUESTING_AFSP_LEFT or was_state == State.REQUESTING_BPO_LEFT) var side_str = 'left' if is_left else 'right' var device_str = 'afsp' if is_afsp else 'bpo' var timeout_msg = 'Таймаут %.1f секунд - %s' % [timeout, get_result_string(HTTPRequest.Result.RESULT_TIMEOUT)] _save_error(timeout_msg) _update_online_status(device_str, side_str, false) _emit_response_signal(device_str, side_str, timeout_msg, false) if is_afsp: _emit_afsp_serviceability_signal(side_str, 0) else: _update_bpo_state(side_str, 0) _emit_bpo_serviceability_signal() _on_request_finished() #endregion #region Управление запросами func _request_next(next_state: State) -> void: current_state = next_state current_device_type = 'afsp' if (next_state == State.REQUESTING_AFSP_LEFT or next_state == State.REQUESTING_AFSP_RIGHT) else 'bpo' match current_state: State.REQUESTING_AFSP_LEFT: current_url = addresses.get('Адрес АФСП левого борта', '') State.REQUESTING_AFSP_RIGHT: current_url = addresses.get('Адрес АФСП правого борта', '') State.REQUESTING_BPO_LEFT: current_url = addresses.get('Адрес БПО левого борта', '') State.REQUESTING_BPO_RIGHT: current_url = addresses.get('Адрес БПО правого борта', '') State.IDLE: return if current_url.is_empty(): push_error('URL не задан для ', current_state) var error_msg = 'URL не задан' _save_error(error_msg) var is_afsp = (current_state == State.REQUESTING_AFSP_LEFT or current_state == State.REQUESTING_AFSP_RIGHT) var is_left = (current_state == State.REQUESTING_AFSP_LEFT or current_state == State.REQUESTING_BPO_LEFT) var side_str = 'left' if is_left else 'right' var device_str = 'afsp' if is_afsp else 'bpo' _update_online_status(device_str, side_str, false) _emit_response_signal(device_str, side_str, error_msg, false) if is_afsp: _emit_afsp_serviceability_signal(side_str, 0) else: _update_bpo_state(side_str, 0) _emit_bpo_serviceability_signal() _on_request_finished() return match current_state: State.REQUESTING_AFSP_LEFT: emit_signal('request_sent_afsp_left', current_url) State.REQUESTING_AFSP_RIGHT: emit_signal('request_sent_afsp_right', current_url) State.REQUESTING_BPO_LEFT: emit_signal('request_sent_bpo_left', current_url) State.REQUESTING_BPO_RIGHT: emit_signal('request_sent_bpo_right', current_url) timeout_timer.start(timeout) var error = http_request.request(current_url) if error != OK: log.message(Logger.ERROR, 'Ошибка запроса: %s' & error) var error_msg = 'Ошибка запроса: ' + error_string(error) _save_error(error_msg) var is_afsp = (current_state == State.REQUESTING_AFSP_LEFT or current_state == State.REQUESTING_AFSP_RIGHT) var is_left = (current_state == State.REQUESTING_AFSP_LEFT or current_state == State.REQUESTING_BPO_LEFT) var side_str = 'left' if is_left else 'right' var device_str = 'afsp' if is_afsp else 'bpo' _update_online_status(device_str, side_str, false) _emit_response_signal(device_str, side_str, error_msg, false) if is_afsp: _emit_afsp_serviceability_signal(side_str, 0) else: _update_bpo_state(side_str, 0) _emit_bpo_serviceability_signal() _on_request_finished() func _on_request_finished() -> void: match current_state: State.REQUESTING_AFSP_LEFT: _request_next(State.REQUESTING_AFSP_RIGHT) State.REQUESTING_AFSP_RIGHT: _request_next(State.REQUESTING_BPO_LEFT) State.REQUESTING_BPO_LEFT: _request_next(State.REQUESTING_BPO_RIGHT) State.REQUESTING_BPO_RIGHT, State.WAITING_TIMEOUT: current_state = State.IDLE #endregion #region Сохранение результатов func _save_response(response: String) -> void: match current_state: State.REQUESTING_AFSP_LEFT: last_response_afsp_left = response State.REQUESTING_AFSP_RIGHT: last_response_afsp_right = response State.REQUESTING_BPO_LEFT: last_response_bpo_left = response State.REQUESTING_BPO_RIGHT: last_response_bpo_right = response func _save_error(error: String) -> void: match current_state: State.REQUESTING_AFSP_LEFT: last_error_afsp_left = error State.REQUESTING_AFSP_RIGHT: last_error_afsp_right = error State.REQUESTING_BPO_LEFT: last_error_bpo_left = error State.REQUESTING_BPO_RIGHT: last_error_bpo_right = error func _update_success_time(device: String, side: String, time: int) -> void: if device == 'afsp': if side == 'left': last_success_time_afsp_left = time else: last_success_time_afsp_right = time else: if side == 'left': last_success_time_bpo_left = time else: last_success_time_bpo_right = time func _emit_response_signal(device: String, side: String, response: String, success: bool) -> void: if device == 'afsp': if side == 'left': emit_signal('response_received_afsp_left', response, success) else: emit_signal('response_received_afsp_right', response, success) else: if side == 'left': emit_signal('response_received_bpo_left', response, success) else: emit_signal('response_received_bpo_right', response, success) #endregion