From 4d50585cc1d57dc1cc36a424dbd60331d0b2d44d Mon Sep 17 00:00:00 2001
From: TotMaxim
Date: Sun, 7 Dec 2025 16:01:46 +0300
Subject: [PATCH] =?UTF-8?q?=D0=94=D0=BE=D1=80=D0=B0=D0=B1=D0=BE=D1=82?=
=?UTF-8?q?=D0=BA=D0=B0=20=D0=A3=D0=A4?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
main.gd | 34 ++++++++
main.tscn | 59 +++++++++++++
udp_broadcast.gd | 5 --
uf_broadcast.gd | 198 ++++++++++++++++++++++++++++++++++++++++++++
uf_broadcast.gd.uid | 1 +
5 files changed, 292 insertions(+), 5 deletions(-)
create mode 100644 uf_broadcast.gd
create mode 100644 uf_broadcast.gd.uid
diff --git a/main.gd b/main.gd
index 587f721..eeca9fc 100644
--- a/main.gd
+++ b/main.gd
@@ -3,6 +3,7 @@ extends Control
const CONFIG_PATH = "res://config/default_data.json"
const BUFFER_SIZE = 32
var udp_broadcast: UDPBroadcast
+var uf_broadcast: UF_Broadcast
var default_data: Dictionary = {}
var dictionary_yau07: Dictionary
var dictionary_sockets_bit: Dictionary ## Байт 8 для отправки о состоянии ячеек
@@ -148,6 +149,8 @@ func _ready() -> void:
for ems in ems_arr_node:
ems.connect('bits_changed', _on_modify_ems_g_status.bind(ems.get_meta('device')))
+ $uf_button.connect('toggled', on_modify_uf.bind($uf_button.get_meta('device')))
+
_update_tab_container()
_load_default_data()
_apply_default_data()
@@ -191,6 +194,22 @@ func on_modify_device(toggled: bool, name_device: String) -> void:
_update_tab_container(toggled, name_device)
+func on_modify_uf(toggled: bool, name_device: String) -> void:
+ if toggled:
+ uf_broadcast = UF_Broadcast.new(name_device)
+ uf_broadcast.add_timers_to_scene(self)
+ uf_broadcast.start_broadcast()
+ dictionary_yau07[name_device] = uf_broadcast
+ print("Устройство %s активировано" % name_device)
+ for bit in get_tree().get_nodes_in_group('uf_status'):
+ bit.connect('toggled', _on_update_uf_status.bind(bit.get_meta('bit')))
+ else:
+ if dictionary_yau07.has(name_device) and dictionary_yau07[name_device]:
+ dictionary_yau07[name_device].close_udp_unit()
+ dictionary_yau07.erase(name_device)
+ print("Устройство %s деактивировано" % name_device)
+
+
func _update_tab_container(just_activated: bool = false, activated_device: String = ""):
var tab_container = $TabContainer
var device_nodes = get_tree().get_nodes_in_group('device-addr')
@@ -226,6 +245,21 @@ func _on_update_socket_status(status: int, device_name: String)->void:
socket.set_pressed_no_signal(is_bit_set == 1)
+func _on_update_uf_status(toggled: bool, bit_index: int)->void:
+ if not dictionary_yau07.has('УФ'): return
+ var status = dictionary_yau07['УФ'].get_status_uf()
+ if bit_index == 6:
+ status = dictionary_yau07['УФ'].get_power_bit_uf()
+
+ if toggled: # Установить бит в 1
+ status = status | (1 << bit_index)
+ else: # Сбросить бит в 0
+ status = status & ~(1 << bit_index)
+
+ if bit_index == 6: dictionary_yau07['УФ'].set_power_bit_uf(status)
+ else: dictionary_yau07['УФ'].set_status_uf(status)
+
+
func _on_update_isa_ports(isa_ports: Dictionary, device_name: String)->void:
for port_addr in isa_ports:
var value = isa_ports[port_addr]
diff --git a/main.tscn b/main.tscn
index 6763cdd..1fc8da1 100644
--- a/main.tscn
+++ b/main.tscn
@@ -2217,3 +2217,62 @@ layout_mode = 2
text = "Загрузка ЭМС"
metadata/bit = 4
metadata/device = "ПРД-К4"
+
+[node name="uf_button" type="CheckButton" parent="."]
+layout_mode = 0
+offset_left = 64.0
+offset_top = 480.0
+offset_right = 140.0
+offset_bottom = 511.0
+text = "УФ "
+metadata/device = "УФ"
+
+[node name="addr" type="Label" parent="uf_button"]
+layout_mode = 0
+offset_left = 80.0
+offset_top = 3.0
+offset_right = 149.0
+offset_bottom = 26.0
+text = "10.1.1.52"
+
+[node name="GridContainer" type="GridContainer" parent="."]
+layout_mode = 0
+offset_left = 64.0
+offset_top = 520.0
+offset_right = 200.0
+offset_bottom = 761.0
+
+[node name="CheckBox" type="CheckBox" parent="GridContainer" groups=["uf_status"]]
+layout_mode = 2
+text = "КЭМС-Б1"
+metadata/bit = 0
+
+[node name="CheckBox2" type="CheckBox" parent="GridContainer" groups=["uf_status"]]
+layout_mode = 2
+text = "ЭМС-Б"
+metadata/bit = 1
+
+[node name="CheckBox3" type="CheckBox" parent="GridContainer" groups=["uf_status"]]
+layout_mode = 2
+text = "ЭМС-Б"
+metadata/bit = 2
+
+[node name="CheckBox4" type="CheckBox" parent="GridContainer" groups=["uf_status"]]
+layout_mode = 2
+text = "КЭМС-Б1 load"
+metadata/bit = 3
+
+[node name="CheckBox5" type="CheckBox" parent="GridContainer" groups=["uf_status"]]
+layout_mode = 2
+text = "ЭМС-Б load"
+metadata/bit = 4
+
+[node name="CheckBox6" type="CheckBox" parent="GridContainer" groups=["uf_status"]]
+layout_mode = 2
+text = "ЭМС-Б load"
+metadata/bit = 5
+
+[node name="CheckBox7" type="CheckBox" parent="GridContainer" groups=["uf_status"]]
+layout_mode = 2
+text = "ИП220-27Б"
+metadata/bit = 6
diff --git a/udp_broadcast.gd b/udp_broadcast.gd
index 1f8ad12..d057f59 100644
--- a/udp_broadcast.gd
+++ b/udp_broadcast.gd
@@ -355,11 +355,6 @@ func _encode_device_data(buffer: PackedByteArray) -> void:
func _encode_ems_g_data(buffer: PackedByteArray) -> void:
'''Кодирование данных EMS-G'''
- #var ems_g_data = [
- #0x9e00, 0xffff, 0xffff, 0x0000, 0xffff, 0xffff,
- #0x0000, 0xffff, 0xffff, 0x0000, 0xffff, 0xffff,
- #0xffff
- #]
buffer.encode_u16(EMS_G_DATA_OFFSET, _input_const)
buffer.encode_u16(EMS_G_DATA_OFFSET + 6, _input_impulse)
buffer.encode_u16(EMS_G_DATA_OFFSET + 12, _output_const)
diff --git a/uf_broadcast.gd b/uf_broadcast.gd
new file mode 100644
index 0000000..b08f011
--- /dev/null
+++ b/uf_broadcast.gd
@@ -0,0 +1,198 @@
+class_name UF_Broadcast
+
+const ADDRESS_LIST: Dictionary = {
+ 'УФ': [50052, '10.1.1.52']}
+const BROADCAST_ADDR: Array = [50000, '10.1.1.255']
+const MIN_PACKET_SIZE: int = 7
+
+var _self_name: String
+var _socket: SocketUDP
+var _timer: Timer
+var _cmd_number_count: int = 0
+var status: int = 0
+var ip: int = 0
+
+
+func _init(name: String):
+ '''Инициализация UDP broadcast UF клиента'''
+ _self_name = name
+ _initialize_socket()
+ _initialize_timers()
+
+
+ if not _bind_to_device_address(name):
+ push_error('Не удалось инициализировать устройство: %s' % name)
+
+
+func _initialize_socket() -> void:
+ '''Инициализация UDP сокета'''
+ _socket = SocketUDP.new()
+
+func _initialize_timers() -> void:
+ '''Инициализация таймеров'''
+ # Таймер для широковещательной рассылки (3 раза в секунду)
+ _timer = Timer.new()
+ _timer.wait_time = 1.0 / 5.0
+ _timer.timeout.connect(_on_broadcast_timeout)
+
+
+func add_timers_to_scene(parent: Node) -> void:
+ '''Добавление таймеров в дерево сцены'''
+ # Проверяем что таймеры еще не имеют родителя
+ if not _timer.get_parent():
+ parent.add_child(_timer)
+ elif _timer.get_parent() != parent:
+ push_warning('Таймер broadcast уже добавлен в другую сцену')
+
+
+func _bind_to_device_address(device_name: String) -> bool:
+ '''Привязка к адресу устройства по его имени'''
+ if not ADDRESS_LIST.has(device_name):
+ push_error('Неизвестное устройство: %s' % device_name)
+ return false
+
+ var port: int = ADDRESS_LIST[device_name][0]
+ var address: String = ADDRESS_LIST[device_name][1]
+
+ return bind_unit_address(port, address)
+
+
+func bind_unit_address(port: int, address: String) -> bool:
+ '''Привязка сокета к указанному порту и адресу'''
+ if port <= 0 or port > 65535:
+ push_error('Некорректный номер порта: %d' % port)
+ return false
+
+ var bind_result = _socket.bind(port, address)
+ if bind_result != OK:
+ push_error('Не удалось привязаться к %s:%d - %s' % [address, port, error_string(bind_result)])
+ return false
+
+ print('Успешно привязано к %s:%d' % [address, port])
+ return true
+
+
+func _on_broadcast_timeout() -> void:
+ '''Обработчик таймера широковещательной рассылки'''
+ send_broadcast()
+
+
+func send_broadcast() -> bool:
+ '''Отправка широковещательного пакета'''
+ _socket.set_broadcast_enabled(true)
+ var data: PackedByteArray = _form_status_device_packet()
+ var send_result = _socket.send_to(BROADCAST_ADDR[1], BROADCAST_ADDR[0], data)
+ if send_result == OK:
+ return true
+ else:
+ push_error('Ошибка отправки широковещательного пакета')
+ return false
+
+
+func _form_status_device_packet() -> PackedByteArray:
+ var uf: Array = [0x00, 0x00, _cmd_number_count, 0x05, 0x00, 0x00, 0x4f, 0x00, status, 0x00, ip, 0x00, 0x00, 0xff, 0xff, 0x00,\
+ 0x00, 0x00, 0x00, 0xff, 0xff, 0xc0, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0xff,\
+ 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00,\
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x01, 0x04, 0x24, 0x01, 0x00, 0x00, 0x26, 0x01,\
+ 0x00, 0x00, 0x28, 0x01, 0x00, 0x00, 0x2a, 0x01, 0xff, 0xff]
+
+ var buffer = PackedByteArray()
+ for byte in uf:
+ buffer.append(byte)
+ return buffer
+
+
+func start_broadcast() -> void:
+ '''Запуск широковещательной рассылки и опроса сокета'''
+ _timer.start()
+ print('Широковещательная рассылка запущена')
+
+
+func stop_broadcast() -> void:
+ '''Остановка широковещательной рассылки'''
+ _timer.stop()
+ print('Широковещательная рассылка остановлена')
+
+
+func close_udp_unit() -> void:
+ '''Закрытие UDP соединения'''
+ stop_broadcast()
+ _socket.close()
+ print('UDP соединение закрыто')
+
+
+func _on_poll_timeout() -> void:
+ '''Обработчик таймера опроса сокета'''
+ _poll_socket()
+
+
+func _poll_socket() -> void:
+ '''Опрос сокета для получения входящих пакетов'''
+ while _socket.get_available_packet_count() > 0:
+ var packet = _socket.get_packet()
+ _handle_received_data(packet)
+
+
+func _handle_received_data(data: PackedByteArray) -> void:
+ '''Обработка входящих данных'''
+ if not _validate_received_packet(data):
+ return
+
+ var command_type = _get_command_type(data)
+ _cmd_number_count = data.decode_u16(2)
+ if command_type == -1: # Проверяем на -1 вместо null
+ return
+
+
+func _validate_received_packet(data: PackedByteArray) -> bool:
+ '''Валидация входящего пакета'''
+ if data.size() < MIN_PACKET_SIZE:
+ push_warning('Получен слишком короткий пакет: %d байт' % data.size())
+ return false
+ return true
+
+
+func _get_command_type(data: PackedByteArray) -> int:
+ '''Получение типа команды из данных пакета'''
+ if data.size() < 6:
+ return -1 # Используем -1 для обозначения ошибки
+ return data.decode_u8(6)
+
+
+func set_status_uf(new_status: int) -> void:
+ if status != new_status:
+ status = new_status
+ print('Установлен статус УФ: %s' % status)
+
+
+func get_status_uf() -> int:
+ return status
+
+
+func set_power_bit_uf(new_status)-> void:
+ if ip != new_status:
+ ip = new_status
+
+
+func get_power_bit_uf() -> int:
+ return ip
+
+
+#region Вложенный класс SocketUDP
+
+class SocketUDP extends PacketPeerUDP:
+ '''Расширенный класс UDP сокета с улучшенной обработкой ошибок'''
+
+ func send_to(address: String, port: int, data: PackedByteArray) -> int:
+ '''Отправка данных на указанный адрес и порт'''
+ set_dest_address(address, port)
+ var result = put_packet(data)
+
+ if result != OK:
+ push_error('Ошибка отправки: %s - %s:%d' % [error_string(result), address, port])
+
+ return result
+
+#endregion
diff --git a/uf_broadcast.gd.uid b/uf_broadcast.gd.uid
new file mode 100644
index 0000000..6249b85
--- /dev/null
+++ b/uf_broadcast.gd.uid
@@ -0,0 +1 @@
+uid://lju7b4vxgjpd