First commit
This commit is contained in:
2
.gitattributes
vendored
Normal file
2
.gitattributes
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
# Normalize EOL for all files that Git considers text files.
|
||||
* text=auto eol=lf
|
||||
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
# Godot 4+ specific ignores
|
||||
.godot/
|
||||
3
.idea/.gitignore
generated
vendored
Normal file
3
.idea/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
6
.idea/inspectionProfiles/profiles_settings.xml
generated
Normal file
6
.idea/inspectionProfiles/profiles_settings.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<settings>
|
||||
<option name="USE_PROJECT_PROFILE" value="false" />
|
||||
<version value="1.0" />
|
||||
</settings>
|
||||
</component>
|
||||
4
.idea/misc.xml
generated
Normal file
4
.idea/misc.xml
generated
Normal file
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.10" project-jdk-type="Python SDK" />
|
||||
</project>
|
||||
8
.idea/modules.xml
generated
Normal file
8
.idea/modules.xml
generated
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/ТПО.iml" filepath="$PROJECT_DIR$/.idea/ТПО.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
||||
6
.idea/vcs.xml
generated
Normal file
6
.idea/vcs.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
8
.idea/ТПО.iml
generated
Normal file
8
.idea/ТПО.iml
generated
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="PYTHON_MODULE" version="4">
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$" />
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
||||
38
EMS5782.tmp
Normal file
38
EMS5782.tmp
Normal file
@@ -0,0 +1,38 @@
|
||||
[gd_scene load_steps=3 format=3 uid="uid://ccp4ypftnrbwq"]
|
||||
|
||||
[ext_resource type="Script" path="res://Main.gd" id="1_pr72k"]
|
||||
[ext_resource type="Script" path="res://table/table.gd" id="2_1lybo"]
|
||||
|
||||
[node name="Node2D" type="Node2D"]
|
||||
script = ExtResource("1_pr72k")
|
||||
|
||||
[node name="ColorRect" type="ColorRect" parent="."]
|
||||
offset_right = 1158.0
|
||||
offset_bottom = 655.0
|
||||
color = Color(0.313726, 0.227451, 0.290196, 1)
|
||||
metadata/_edit_lock_ = true
|
||||
|
||||
[node name="Port_ISA" type="Label" parent="."]
|
||||
offset_left = 37.0
|
||||
offset_top = 29.0
|
||||
offset_right = 1114.0
|
||||
offset_bottom = 52.0
|
||||
metadata/_edit_lock_ = true
|
||||
|
||||
[node name="scroll1" type="ScrollContainer" parent="."]
|
||||
offset_left = 42.0
|
||||
offset_top = 72.0
|
||||
offset_right = 1135.0
|
||||
offset_bottom = 265.0
|
||||
|
||||
[node name="table1" type="GridContainer" parent="scroll1"]
|
||||
layout_mode = 2
|
||||
script = ExtResource("2_1lybo")
|
||||
|
||||
[node name="Turn_on" type="Button" parent="."]
|
||||
offset_left = 941.0
|
||||
offset_top = 35.0
|
||||
offset_right = 1043.0
|
||||
offset_bottom = 58.0
|
||||
toggle_mode = true
|
||||
text = "Подключить"
|
||||
216
Main.gd
Normal file
216
Main.gd
Normal file
@@ -0,0 +1,216 @@
|
||||
extends Node2D
|
||||
|
||||
var TableNode = preload("res://table/node.tscn")
|
||||
|
||||
## Класс для отправки данных в сокет
|
||||
class Socket extends PacketPeerUDP:
|
||||
func send_to(addr: String, port: int, data: PackedByteArray):
|
||||
self.set_dest_address(addr, port)
|
||||
self.put_packet(data)
|
||||
|
||||
var node_select: Node
|
||||
var timer = Timer.new()
|
||||
var soc_unicast: Socket = Socket.new()
|
||||
var soc_brodcast: Socket = Socket.new()
|
||||
var unit = yau07.YaU07.new('ЯУ07')
|
||||
var state: int = 0 # Флаг для машины состояния
|
||||
var flag_online: bool = false # Флаг для подключения
|
||||
var flag_interval: bool = false # Флаг для включения непрерывной передачи с интервалом
|
||||
var ports_wr: Dictionary # Словарь с портами для записи
|
||||
var port_select: int = Constants.BASE_ADDR # Выбранный порд для чтения/записи
|
||||
var port: int
|
||||
var dt: float # Дельта
|
||||
var address: String
|
||||
var rx_data: PackedByteArray # Буфер принятый
|
||||
var addr_receive: String # Адрес обращения к ПРД
|
||||
var port_receive: int # Порт обращения к ПРД
|
||||
var ROWS_REGS_DATA: Array = [
|
||||
[TableNode, TableNode, TableNode, TableNode, TableNode],
|
||||
[TableNode, TableNode, TableNode, TableNode, TableNode],
|
||||
[TableNode, TableNode, TableNode, TableNode, TableNode]]
|
||||
|
||||
|
||||
func _ready():
|
||||
self.add_child(timer)
|
||||
timer.connect('timeout', Callable(self, 'on_timer'))
|
||||
# Таблица 1
|
||||
node_select = $scroll1/table1
|
||||
draw_tabl(node_select, ROWS_REGS_DATA, ROWS_REGS_DATA.size())
|
||||
node_select.set_columns_min_size([250, 150, 150, 200, 150])
|
||||
node_select.set_node_text(0, 0, 'НЕТ СВЯЗИ С ЯУ-07')
|
||||
node_select.set_node_text(1, 0, ' IP-АДРЕС')
|
||||
node_select.set_node_text(2, 0, '10.1.1.70')
|
||||
node_select.set_node_text(3, 0, ' IP-ПОРТ')
|
||||
node_select.set_node_text(4, 0, '50070')
|
||||
node_select.set_node_text(0, 1, 'ПОРТ ЯЧЕЙКИ (hex)')
|
||||
node_select.set_node_text(1, 2, 'ИНТЕРВАЛ')
|
||||
|
||||
# Поле для ввода (запись IP-адреса)
|
||||
var edit_ip = node_select.get_node2(2, 0)
|
||||
edit_ip.editable = true
|
||||
edit_ip.placeholder_text = 'Ввод значения'
|
||||
var edit_port = node_select.get_node2(4, 0)
|
||||
edit_port.editable = true
|
||||
edit_port.placeholder_text = 'Ввод значения'
|
||||
|
||||
# Поле для ввода (запись в порт)
|
||||
var edit_node = node_select.get_node2(2, 1)
|
||||
edit_node.editable = true
|
||||
edit_node.placeholder_text = 'Ввод значения'
|
||||
edit_node.connect('text_submitted', Callable(self, '_on_written_text_submitted'))
|
||||
|
||||
# Кнопка для включения
|
||||
$Turn_on.connect('toggled', Callable(self, '_on_button_pressed'))
|
||||
|
||||
# Поле для ввода (базовый порт)
|
||||
var edit_addr = node_select.get_node2(0, 2)
|
||||
edit_addr.editable = true
|
||||
edit_addr.placeholder_text = '%x' % port_select
|
||||
edit_addr.connect('text_submitted', Callable(self, '_on_written_addres'))
|
||||
|
||||
# Поле для ввода (интервал непрерывной записи)
|
||||
var edit_interval = node_select.get_node2(2, 2)
|
||||
edit_interval.editable = true
|
||||
edit_interval.placeholder_text = 'Ввод значения'
|
||||
edit_interval.connect('text_submitted', Callable(self, '_on_written_interval'))
|
||||
|
||||
# Привязка для принятия широковещательного канала
|
||||
var rc = soc_brodcast.bind(Constants.BROADCAST_PORT, '*')
|
||||
if rc != OK:
|
||||
print('Ошибка: Неудачная привязка широковещательного адреса')
|
||||
|
||||
ports_wr[port_select] = 0
|
||||
unit.connect('line_changed', Callable(self, 'on_line_changed'))
|
||||
unit.connect('command_fail', Callable(self, 'on_command_fail'))
|
||||
unit.connect('command_done', Callable(self, 'on_command_done'))
|
||||
|
||||
|
||||
## Рисует таблицу
|
||||
func draw_tabl(tbl, row: Array, count_row: int):
|
||||
for i_row in count_row:
|
||||
tbl.add_row(row[i_row])
|
||||
|
||||
|
||||
func poll_receive(sock: Socket, _unit, addr: String, _port: int) -> bool: ## Приёмник
|
||||
while sock.get_available_packet_count() > 0:
|
||||
rx_data = sock.get_packet()
|
||||
addr_receive = sock.get_packet_ip()
|
||||
port_receive = sock.get_packet_port()
|
||||
if (addr == addr_receive) and (port == port_receive):
|
||||
unit.parse(rx_data)
|
||||
return false
|
||||
|
||||
|
||||
func _process(delta):
|
||||
if flag_online:
|
||||
if flag_interval == false:
|
||||
poll_receive(soc_brodcast, unit, address, port)
|
||||
var fl_done = unit.cmd_state == unit.CmdState.DONE
|
||||
if state == 1 and fl_done: ## Чтение из ИСА
|
||||
unit.send_isa(unit.CmdCode.READ_ISA, [port_select])
|
||||
node_select.set_node_text(3, 1, 'ПРОЧИТАНО ИЗ %x' % port_select)
|
||||
state = 2
|
||||
elif state == 2 and fl_done: ## Запись в ИСА
|
||||
unit.send_isa(unit.CmdCode.WRITE_ISA, [port_select, ports_wr[port_select]])
|
||||
|
||||
if unit.isa_ports.has(port_select):
|
||||
node_select.set_node_text(4, 1, '%x' % unit.isa_ports[port_select])
|
||||
var isa: Array
|
||||
for i in unit.isa_ports:
|
||||
isa.append('%x: %x' % [i, unit.isa_ports[i]])
|
||||
node_select.set_node_text(1, 1, 'ЗАПИСАТЬ В %x' % port_select)
|
||||
$ISA.text = 'ISA: ' + str(isa)
|
||||
state = 1
|
||||
|
||||
var rc = unit.process(delta)
|
||||
if rc == Error.OK:
|
||||
soc_unicast.send_to(address, port, unit.tx_data.slice(0, unit.tx_len))
|
||||
dt = delta
|
||||
|
||||
|
||||
func on_timer():
|
||||
if flag_online:
|
||||
if flag_interval == true:
|
||||
poll_receive(soc_brodcast, unit, address, port)
|
||||
var fl_done = unit.cmd_state == unit.CmdState.DONE
|
||||
if state == 1 and fl_done: ## Чтение из ИСА
|
||||
unit.send_isa(unit.CmdCode.READ_ISA, [port_select, ports_wr[port_select]])
|
||||
state = 2
|
||||
elif state == 2 and fl_done: ## Запись в ИСА
|
||||
unit.send_isa(unit.CmdCode.WRITE_ISA, [port_select, ports_wr[port_select]])
|
||||
|
||||
if unit.isa_ports.has(port_select):
|
||||
node_select.set_node_text(4, 1, '%x' % unit.isa_ports[port_select])
|
||||
state = 1
|
||||
var rc = unit.process(dt)
|
||||
if rc == Error.OK:
|
||||
soc_unicast.send_to(address, port, unit.tx_data.slice(0, unit.tx_len))
|
||||
|
||||
|
||||
func on_line_changed(_unit):
|
||||
print_debug()
|
||||
|
||||
|
||||
## Ессли отсутствует подключение к ячейке ЯУ-07
|
||||
func on_command_fail(_unit):
|
||||
var line_yau07_control = node_select.get_node2(0, 0)
|
||||
line_yau07_control.text = 'НЕТ СВЯЗИ С ЯУ-07'
|
||||
line_yau07_control.modulate = Color(1, 0, 0, 1)
|
||||
flag_online = false
|
||||
soc_unicast = Socket.new()
|
||||
|
||||
|
||||
## Индикация чтения портов
|
||||
func on_command_done(_unit):
|
||||
if flag_online:
|
||||
var line_yau07_control = node_select.get_node2(0, 0)
|
||||
line_yau07_control.text = 'ЯУ-07 НА СВЯЗИ'
|
||||
line_yau07_control.modulate = Color(0, 1, 0, 1)
|
||||
|
||||
## Выставить адресс для записи
|
||||
func _on_written_addres(write_addres):
|
||||
port_select = write_addres.hex_to_int()
|
||||
ports_wr[port_select] = 0
|
||||
node_select.set_node_text(0, 2, '%x' % port_select)
|
||||
|
||||
|
||||
## Запись числа порт
|
||||
func _on_written_text_submitted(write_addres_data):
|
||||
var is_hex = true
|
||||
for chr in write_addres_data:
|
||||
if chr.to_upper() < '0' or chr.to_upper() > 'F':
|
||||
is_hex = false
|
||||
break
|
||||
ports_wr[port_select] = write_addres_data.hex_to_int() if is_hex else 0xffff
|
||||
state = 2 # Запись
|
||||
|
||||
|
||||
func _on_written_interval(write_interval):
|
||||
timer.start(float(write_interval))
|
||||
flag_interval = true
|
||||
var light_interval = node_select.get_node2(1, 2)
|
||||
light_interval.modulate = Color(0, 1, 0, 1)
|
||||
|
||||
|
||||
## Подключение
|
||||
func _on_button_pressed(turn_on):
|
||||
if turn_on:
|
||||
address = node_select.get_node2(2,0).text
|
||||
port = int(node_select.get_node2(4,0).text)
|
||||
var rc = soc_unicast.bind(Constants.UNICAST_PORT, Constants.UNICAST_ADDRESS)
|
||||
if rc != OK:
|
||||
print('Ошибка: неудачная привязка адреса: ', address)
|
||||
soc_unicast = Socket.new()
|
||||
flag_online = false
|
||||
else:
|
||||
state = 1 # Чтение
|
||||
flag_online = true
|
||||
else:
|
||||
var line_yau07_control = node_select.get_node2(0, 0)
|
||||
line_yau07_control.text = 'НЕТ СВЯЗИ С ЯУ-07'
|
||||
line_yau07_control.modulate = Color(1, 0, 0, 1)
|
||||
var light_interval = node_select.get_node2(1, 2)
|
||||
light_interval.modulate = Color(0.5, 0.5, 0.5, 1)
|
||||
soc_unicast = Socket.new()
|
||||
flag_online = false
|
||||
flag_interval = false
|
||||
50
Plata-check.tscn
Normal file
50
Plata-check.tscn
Normal file
@@ -0,0 +1,50 @@
|
||||
[gd_scene load_steps=5 format=3 uid="uid://ccp4ypftnrbwq"]
|
||||
|
||||
[ext_resource type="Script" path="res://Main.gd" id="1_8v2wa"]
|
||||
[ext_resource type="Script" path="res://table/table.gd" id="2_ri1qb"]
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_so1no"]
|
||||
bg_color = Color(0, 0, 0, 1)
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_jbuuq"]
|
||||
bg_color = Color(0.0901961, 0.52549, 0.0588235, 1)
|
||||
|
||||
[node name="Node2D" type="Node2D"]
|
||||
script = ExtResource("1_8v2wa")
|
||||
|
||||
[node name="Background" type="ColorRect" parent="."]
|
||||
offset_right = 1158.0
|
||||
offset_bottom = 655.0
|
||||
color = Color(0.313726, 0.227451, 0.290196, 1)
|
||||
metadata/_edit_lock_ = true
|
||||
|
||||
[node name="scroll1" type="ScrollContainer" parent="."]
|
||||
offset_left = 42.0
|
||||
offset_top = 72.0
|
||||
offset_right = 1135.0
|
||||
offset_bottom = 265.0
|
||||
metadata/_edit_lock_ = true
|
||||
|
||||
[node name="table1" type="GridContainer" parent="scroll1"]
|
||||
layout_mode = 2
|
||||
script = ExtResource("2_ri1qb")
|
||||
metadata/_edit_lock_ = true
|
||||
|
||||
[node name="Turn_on" type="Button" parent="."]
|
||||
offset_left = 963.0
|
||||
offset_top = 72.0
|
||||
offset_right = 1088.0
|
||||
offset_bottom = 96.0
|
||||
theme_override_font_sizes/font_size = 17
|
||||
theme_override_styles/normal = SubResource("StyleBoxFlat_so1no")
|
||||
theme_override_styles/pressed = SubResource("StyleBoxFlat_jbuuq")
|
||||
toggle_mode = true
|
||||
text = "Подключить"
|
||||
metadata/_edit_lock_ = true
|
||||
|
||||
[node name="ISA" type="Label" parent="."]
|
||||
offset_left = 46.0
|
||||
offset_top = 179.0
|
||||
offset_right = 999.0
|
||||
offset_bottom = 202.0
|
||||
metadata/_edit_lock_ = true
|
||||
6
constants.gd
Normal file
6
constants.gd
Normal file
@@ -0,0 +1,6 @@
|
||||
extends Node
|
||||
|
||||
const UNICAST_PORT: int = 50080
|
||||
const UNICAST_ADDRESS: String = '10.1.1.14'
|
||||
const BROADCAST_PORT: int = 50000
|
||||
const BASE_ADDR = 0x100
|
||||
1
icon.svg
Normal file
1
icon.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg height="128" width="128" xmlns="http://www.w3.org/2000/svg"><rect x="2" y="2" width="124" height="124" rx="14" fill="#363d52" stroke="#212532" stroke-width="4"/><g transform="scale(.101) translate(122 122)"><g fill="#fff"><path d="M105 673v33q407 354 814 0v-33z"/><path fill="#478cbf" d="m105 673 152 14q12 1 15 14l4 67 132 10 8-61q2-11 15-15h162q13 4 15 15l8 61 132-10 4-67q3-13 15-14l152-14V427q30-39 56-81-35-59-83-108-43 20-82 47-40-37-88-64 7-51 8-102-59-28-123-42-26 43-46 89-49-7-98 0-20-46-46-89-64 14-123 42 1 51 8 102-48 27-88 64-39-27-82-47-48 49-83 108 26 42 56 81zm0 33v39c0 276 813 276 813 0v-39l-134 12-5 69q-2 10-14 13l-162 11q-12 0-16-11l-10-65H447l-10 65q-4 11-16 11l-162-11q-12-3-14-13l-5-69z"/><path d="M483 600c3 34 55 34 58 0v-86c-3-34-55-34-58 0z"/><circle cx="725" cy="526" r="90"/><circle cx="299" cy="526" r="90"/></g><g fill="#414042"><circle cx="307" cy="532" r="60"/><circle cx="717" cy="532" r="60"/></g></g></svg>
|
||||
|
After Width: | Height: | Size: 950 B |
37
icon.svg.import
Normal file
37
icon.svg.import
Normal file
@@ -0,0 +1,37 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://diowevlc7bywf"
|
||||
path="res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://icon.svg"
|
||||
dest_files=["res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
||||
26
project.godot
Normal file
26
project.godot
Normal file
@@ -0,0 +1,26 @@
|
||||
; Engine configuration file.
|
||||
; It's best edited using the editor UI and not directly,
|
||||
; since the parameters that go here are not all obvious.
|
||||
;
|
||||
; Format:
|
||||
; [section] ; section goes between []
|
||||
; param=value ; assign values to parameters
|
||||
|
||||
config_version=5
|
||||
|
||||
[application]
|
||||
|
||||
config/name="Plata-check.exe"
|
||||
run/main_scene="res://Plata-check.tscn"
|
||||
config/features=PackedStringArray("4.2", "GL Compatibility")
|
||||
config/icon="res://icon.svg"
|
||||
|
||||
[autoload]
|
||||
|
||||
yau07="*res://yau07.gd"
|
||||
Constants="*res://constants.gd"
|
||||
|
||||
[rendering]
|
||||
|
||||
renderer/rendering_method="gl_compatibility"
|
||||
renderer/rendering_method.mobile="gl_compatibility"
|
||||
12
table/header.tscn
Normal file
12
table/header.tscn
Normal file
@@ -0,0 +1,12 @@
|
||||
[gd_scene load_steps=2 format=3 uid="uid://ch14g46swx74h"]
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_2thpi"]
|
||||
bg_color = Color(0.8, 0.403922, 0.27451, 1)
|
||||
|
||||
[node name="heder" type="Label"]
|
||||
offset_right = 128.0
|
||||
offset_bottom = 23.0
|
||||
theme_override_colors/font_color = Color(0, 0, 0, 1)
|
||||
theme_override_font_sizes/font_size = 15
|
||||
theme_override_styles/normal = SubResource("StyleBoxFlat_2thpi")
|
||||
horizontal_alignment = 1
|
||||
BIN
table/nine-patch.png
Normal file
BIN
table/nine-patch.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 125 B |
34
table/nine-patch.png.import
Normal file
34
table/nine-patch.png.import
Normal file
@@ -0,0 +1,34 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://bw3yv3smvaxqn"
|
||||
path="res://.godot/imported/nine-patch.png-798f7950fb3126b4e1ed0c39d9e4beba.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://table/nine-patch.png"
|
||||
dest_files=["res://.godot/imported/nine-patch.png-798f7950fb3126b4e1ed0c39d9e4beba.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
48
table/node.tscn
Normal file
48
table/node.tscn
Normal file
@@ -0,0 +1,48 @@
|
||||
[gd_scene load_steps=3 format=3 uid="uid://cef6gtuc60ggg"]
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_hrhif"]
|
||||
bg_color = Color(0, 0, 0, 1)
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_q1ixs"]
|
||||
bg_color = Color(0, 0, 0, 1)
|
||||
|
||||
[node name="Label" type="LineEdit"]
|
||||
texture_filter = 2
|
||||
texture_repeat = 2
|
||||
clip_contents = true
|
||||
layout_direction = 2
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
offset_right = -279.0
|
||||
offset_bottom = -831.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 3
|
||||
theme_override_colors/font_uneditable_color = Color(1, 1, 1, 1)
|
||||
theme_override_font_sizes/font_size = 17
|
||||
theme_override_styles/normal = SubResource("StyleBoxFlat_hrhif")
|
||||
theme_override_styles/read_only = SubResource("StyleBoxFlat_q1ixs")
|
||||
alignment = 1
|
||||
editable = false
|
||||
caret_blink = true
|
||||
caret_blink_interval = 0.5
|
||||
|
||||
[node name="Label" type="Label" parent="."]
|
||||
visible = false
|
||||
layout_mode = 0
|
||||
offset_right = 40.0
|
||||
offset_bottom = 23.0
|
||||
|
||||
[node name="Label" type="Label" parent="Label"]
|
||||
visible = false
|
||||
layout_mode = 0
|
||||
offset_right = 40.0
|
||||
offset_bottom = 23.0
|
||||
|
||||
[node name="CheckButton" type="CheckButton" parent="Label"]
|
||||
visible = false
|
||||
layout_mode = 0
|
||||
offset_right = 44.0
|
||||
offset_bottom = 24.0
|
||||
10
table/node_list.tscn
Normal file
10
table/node_list.tscn
Normal file
@@ -0,0 +1,10 @@
|
||||
[gd_scene load_steps=2 format=3 uid="uid://mxm8nthir8h4"]
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_ddnjm"]
|
||||
bg_color = Color(0, 0, 0, 1)
|
||||
|
||||
[node name="OptionButton" type="OptionButton"]
|
||||
offset_right = 170.0
|
||||
offset_bottom = 20.0
|
||||
theme_override_styles/normal = SubResource("StyleBoxFlat_ddnjm")
|
||||
alignment = 1
|
||||
15
table/selector.tscn
Normal file
15
table/selector.tscn
Normal file
@@ -0,0 +1,15 @@
|
||||
[gd_scene load_steps=2 format=3 uid="uid://pd3fcx55duj2"]
|
||||
|
||||
[ext_resource type="Texture2D" uid="uid://bw3yv3smvaxqn" path="res://table/nine-patch.png" id="1_jtmfn"]
|
||||
|
||||
[node name="NinePatchRect" type="NinePatchRect"]
|
||||
offset_right = 17.0
|
||||
offset_bottom = 16.0
|
||||
texture = ExtResource("1_jtmfn")
|
||||
draw_center = false
|
||||
patch_margin_left = 8
|
||||
patch_margin_top = 8
|
||||
patch_margin_right = 8
|
||||
patch_margin_bottom = 8
|
||||
axis_stretch_horizontal = 1
|
||||
axis_stretch_vertical = 1
|
||||
682
table/table.gd
Normal file
682
table/table.gd
Normal file
@@ -0,0 +1,682 @@
|
||||
class_name GDTable extends GridContainer
|
||||
|
||||
## Виджет "Таблица"
|
||||
|
||||
@export var node_gap: = Vector2(0.0, 0.0) ## Зазор между ячейками, который невозможно вычислить или получить
|
||||
@export var selector_color: = Color(1.0, 1.0, 1.0, 1.0) ## Окрашивающий цвет для селектора выбранного ряда
|
||||
@export var selector_key_down: Key = KEY_DOWN ## Клавиша увеличения индекса выбранного ряда
|
||||
@export var selector_key_up: Key = KEY_UP ## Клавиша уменьшения индекса выбранного ряда
|
||||
@export_file var node_type_file: = 'res://table/node.tscn': get = get_node_scene, set = load_node_scene ## Сцена ячейки устанавливаемая по умолчанию
|
||||
@export_file var selector_type_file: = 'res://table/selector.tscn': set = load_selector_scene ## Сцена селектора для выбранного ряда
|
||||
|
||||
const HEAD_PREFIX = 'HEAD_' ## Префикс ключа заголовка таблицы
|
||||
const INDEX_FORMAT = '%02d_%02d' ## Формат индекса ключа
|
||||
const MAX_INDEX = 99 ## Максимальное значение индекса для колонки и строки. Связано с форматом индекса ключа
|
||||
const NODE_PREFIX = 'NODE_' ## Префикс ключа ячейки таблицы
|
||||
const SLCT_PREFIX = 'SLCT_' ## Префикс ключа индикатора выбранного ряда таблицы
|
||||
|
||||
const HEAD_KEY_FORMAT = HEAD_PREFIX + INDEX_FORMAT ## Формат ключа заголовка
|
||||
const NODE_KEY_FORMAT = NODE_PREFIX + INDEX_FORMAT ## Формат ключа ячейки
|
||||
const SELECTOR_KEY_FORMAT = SLCT_PREFIX + INDEX_FORMAT ## Формат ключа индикаторов выбранного ряда
|
||||
const META_USER_DATA = 'user_data' ## Имя свойства для сохранения данных пользователя
|
||||
const META_CALLABLE = 'callable' ## Имя свойства для сохранения вызываемого
|
||||
const META_EVENT_NAME = 'event_name' ## Имя свойства для сохранения названия события
|
||||
|
||||
var header: GridContainer ## Заголовок таблицы
|
||||
var in_focus: = false ## Фокус пользовательского ввода
|
||||
var nodes: = {} ## Все ячейки таблицы и дополнительные элементы
|
||||
var columns_min_size = [] ## Ширина каждой колонки
|
||||
var ScnNode: PackedScene ## Прототип ячейки таблицы
|
||||
var ScnSelector: PackedScene ## Прототип индикатора выбранного ряда
|
||||
var selector_color_proc: Callable ## Ссылка на процедуру генератора цвета индикатора выбранного ряда
|
||||
var selector_size_proc: Callable ## Ссылка на процедуру генератора размера индикатора выбранного ряда
|
||||
|
||||
## Вызывается при нажатии кнопкой мыши на строку.
|
||||
## Активируемый ползователем элемент ряда должен вызывать
|
||||
## сигнал [b]focus_entered[/b], что бы сигнал [b]row_pressed[/b] работал.[br]
|
||||
## [param i_row] - Индекс ряда в котором пользователь активировал элемент.[br]
|
||||
## [i]Godot 4.2: [/i][b] Label[/b] [i] - не вызывает сигнал[/i] [b]focus_entered[/b].
|
||||
signal row_pressed(i_row: int)
|
||||
|
||||
## Вызывается при изменении выбранного ряда.[br]
|
||||
## [param i_row] - Текущий выбранный ряд.
|
||||
signal selected_changed(i_row: int)
|
||||
|
||||
var scroll: Control
|
||||
var scroll_adj: = Vector2(0, 0)
|
||||
|
||||
|
||||
func get_node_scene(): return node_type_file
|
||||
func assert_meta(i_col: int, i_row: int, pname: String, node): assert(node.has_meta(pname), 'ошибка: нет мета-свойства \"%s\" у ячейки колонка:%d ряд:%d' % [pname, i_col, i_row])
|
||||
func get_node_user_data(i_col: int, i_row: int): return get_node_meta(i_col, i_row, META_USER_DATA)
|
||||
func set_node_user_data(i_col: int, i_row: int, val) -> void: set_node_meta(i_col, i_row, META_USER_DATA, val)
|
||||
func defaultselector_color_proc(_i_row: int): return selector_color
|
||||
|
||||
|
||||
## Закрепляет заголовок таблицы.[br]
|
||||
## [param scrl] - Прокрутка родитель таблицы.[br]
|
||||
## [param prnt] - Родитель прокрутки.[br]
|
||||
func pin_header(scrl: ScrollContainer, prnt: Control):
|
||||
scrl.remove_child(header)
|
||||
prnt.add_child(header)
|
||||
var v_separation = get_theme_constant('v_separation')
|
||||
header.position = scrl.position - Vector2(0, header.size.y + v_separation)
|
||||
scroll = scrl
|
||||
scroll_adj = scrl.position
|
||||
|
||||
|
||||
## Перемещает указатель на одну строку вверх
|
||||
func selector_up():
|
||||
var i_rows: = []
|
||||
get_selected_rows(i_rows)
|
||||
for i in i_rows:
|
||||
set_row_selected(i, false)
|
||||
var rows_count: = get_rows_count()
|
||||
if not (i_rows.size() and (rows_count > 1)):
|
||||
return
|
||||
var i_row = i_rows[0]
|
||||
i_row -= 1
|
||||
if i_row < 0:
|
||||
i_row = rows_count - 1
|
||||
set_row_selected(i_row, true)
|
||||
var node = get_node2(0, i_row)
|
||||
if scroll is ScrollContainer:
|
||||
scroll.ensure_control_visible(node)
|
||||
emit_signal('selected_changed', i_row)
|
||||
|
||||
|
||||
## Перемещает указатель на одну строку вниз
|
||||
func selector_down():
|
||||
var i_rows: = []
|
||||
get_selected_rows(i_rows)
|
||||
for i in i_rows:
|
||||
set_row_selected(i, false)
|
||||
var rows_count: = get_rows_count()
|
||||
if not (i_rows.size() and (rows_count > 1)):
|
||||
return
|
||||
var i_row = i_rows[0]
|
||||
i_row += 1
|
||||
i_row %= rows_count
|
||||
set_row_selected(i_row, true)
|
||||
var node = get_node2(0, i_row)
|
||||
if scroll is ScrollContainer:
|
||||
scroll.ensure_control_visible(node)
|
||||
emit_signal('selected_changed', i_row)
|
||||
|
||||
|
||||
## Делает ряд [param i_row] видимым.
|
||||
func ensure_row_visible(i_row: int):
|
||||
assert(scroll is ScrollContainer, 'Таблица должна быть дочерней к ScrollContainer')
|
||||
var key: = get_node_key(NODE_KEY_FORMAT, 0, i_row)
|
||||
var node = nodes[key]
|
||||
scroll.ensure_control_visible(node)
|
||||
|
||||
|
||||
## Устанавливает выравнивание из массива [param alignments] для всех колонок.
|
||||
func set_columns_alignments(alignments: Array):
|
||||
assert(columns == alignments.size())
|
||||
var rows_count: = get_rows_count()
|
||||
for i_row in rows_count:
|
||||
for i_col in alignments.size():
|
||||
set_node_alligment(i_col, i_row, alignments[i_col])
|
||||
|
||||
|
||||
##
|
||||
func load_selector_scene(file_name: String):
|
||||
selector_type_file = file_name
|
||||
ScnSelector = load(file_name)
|
||||
|
||||
|
||||
##
|
||||
func load_node_scene(file_name: String):
|
||||
node_type_file = file_name
|
||||
ScnNode = load(file_name)
|
||||
|
||||
|
||||
##
|
||||
func _ready():
|
||||
ScnSelector = load(selector_type_file)
|
||||
ScnNode = load(node_type_file)
|
||||
assert(ScnSelector is PackedScene)
|
||||
assert(ScnNode is PackedScene)
|
||||
|
||||
|
||||
## Выравнивает по нижнему левому углу. Используется для выравнивания по заголовку.
|
||||
func align_to_left_bottompos(hdr: Control, gap: = Vector2(0, 0)):
|
||||
position.x = hdr.position.x + gap.x
|
||||
position.y = hdr.position.y + hdr.size.y + gap.y
|
||||
|
||||
|
||||
## Устанавливает пользовательские данные для ячейки.[br]
|
||||
## [param i_col] - Индекс колонки.[br]
|
||||
## [param i_row] - Индекс ряда.[br]
|
||||
## [param pname] - Имя пользовательских данных.[br]
|
||||
## [param val] - Значение пользовательских данных.[br]
|
||||
func set_node_meta(i_col: int, i_row: int, pname: String, val) -> void:
|
||||
var key: = get_node_key(NODE_KEY_FORMAT, i_col, i_row)
|
||||
var node = nodes[key]
|
||||
node.set_meta(pname, val)
|
||||
|
||||
|
||||
## Возвращает пользовательские данные для ячейки.[br]
|
||||
## [param i_col] - Индекс колонки.[br]
|
||||
## [param i_row] - Индекс ряда.[br]
|
||||
## [param pname] - Имя пользовательских данных.[br]
|
||||
func get_node_meta(i_col: int, i_row: int, pname: String):
|
||||
var key: = get_node_key(NODE_KEY_FORMAT, i_col, i_row)
|
||||
var node = nodes[key]
|
||||
assert_meta(i_col, i_row, pname, node)
|
||||
return node.get_meta(pname)
|
||||
|
||||
|
||||
## Устанавливает свойство ячейки.[br]
|
||||
## [param i_col] - Индекс колонки.[br]
|
||||
## [param i_row] - Индекс ряда.[br]
|
||||
## [param pname] - Имя свойства.[br]
|
||||
## [param val] - Значение свойства.[br]
|
||||
## [i]Свойство[/i] [param pname] [i]ячейки должно существовать.[/i]
|
||||
func set_node_property(i_col: int, i_row: int, pname: String, val) -> void:
|
||||
var key: = get_node_key(NODE_KEY_FORMAT, i_col, i_row)
|
||||
var node = nodes[key]
|
||||
node.set(pname, val)
|
||||
|
||||
|
||||
## Возвращает значение поля ячейки.[br]
|
||||
## [param pname] - Имя поля.[br]
|
||||
## [param i_col] - Индекс колонки.[br]
|
||||
## [param i_row] - Индекс ряда.
|
||||
func get_node_property(i_col: int, i_row: int, pname: String):
|
||||
var key: = get_node_key(NODE_KEY_FORMAT, i_col, i_row)
|
||||
var node = nodes[key]
|
||||
return node.get(pname)
|
||||
|
||||
|
||||
## Возвращает ключ для существующей ячейки. Если ячейки с заданными индексами нет,
|
||||
## вызывает исключение.[br]
|
||||
## [param i_col] - Индекс колонки.[br]
|
||||
## [param i_row] - Индекс ряда
|
||||
func get_node_key(format: String, i_col: int, i_row: int) -> String:
|
||||
var key: String = format % [i_row, i_col]
|
||||
assert(nodes.has(key), 'ошибка: нет такой ячейки - колонка:%d ряд:%d' % [i_col, i_row])
|
||||
return key
|
||||
|
||||
|
||||
## Возвращает массив с индексом ячейки:[br]
|
||||
## [b][0][/b] - индекс колонки,[br]
|
||||
## [b][1][/b] - индекс строки.
|
||||
func get_key_indexes(key: String) -> Array:
|
||||
var tokens: = key.split('_')
|
||||
assert(tokens.size() == 3, 'ошибка: неверный ключ: \"%s\"' % key)
|
||||
return [int(tokens[2]), int(tokens[1])]
|
||||
|
||||
|
||||
##
|
||||
func set_columns_min_size(sizes: Array) -> void:
|
||||
assert((columns == 1) or (columns == sizes.size()))
|
||||
columns = sizes.size()
|
||||
var rows_count: = get_rows_count()
|
||||
columns_min_size.clear()
|
||||
for sz in sizes:
|
||||
columns_min_size.append(sz)
|
||||
for i_row in rows_count:
|
||||
set_nodes_min_size(i_row, sizes, NODE_KEY_FORMAT)
|
||||
if nodes.has(HEAD_KEY_FORMAT % [0, 0]):
|
||||
set_nodes_min_size(0, sizes, HEAD_KEY_FORMAT)
|
||||
|
||||
|
||||
##
|
||||
func set_columns_aligment(alignments: Array) -> void:
|
||||
var rows_count: = get_rows_count()
|
||||
for i_row in rows_count:
|
||||
for i_col in alignments.size():
|
||||
set_node_alligment(i_col, i_row, alignments[i_col])
|
||||
|
||||
|
||||
##
|
||||
func _input(event) -> void:
|
||||
if not is_visible_in_tree(): return
|
||||
if event is InputEventKey:
|
||||
if event.pressed and in_focus:
|
||||
if event.keycode == selector_key_up:
|
||||
selector_up()
|
||||
elif event.keycode == selector_key_down:
|
||||
selector_down()
|
||||
elif event is InputEventMouseButton:
|
||||
if not event.pressed:
|
||||
return
|
||||
if check_event_pos(event.position, scroll):
|
||||
if not in_focus:
|
||||
in_focus = true
|
||||
emit_signal('focus_entered')
|
||||
elif not check_event_pos(event.position, scroll):
|
||||
if in_focus:
|
||||
in_focus = false
|
||||
emit_signal('focus_exited')
|
||||
|
||||
|
||||
##
|
||||
func _init() -> void:
|
||||
selector_color_proc = Callable(self, 'defaultselector_color_proc')
|
||||
assert(selector_color_proc.is_valid())
|
||||
selector_size_proc = Callable(self, 'default_selector_size_proc')
|
||||
assert(selector_size_proc.is_valid())
|
||||
scroll = self
|
||||
|
||||
|
||||
##
|
||||
func check_event_pos(event_pos: Vector2, obj: Control):
|
||||
return \
|
||||
(event_pos.x > obj.position.x) \
|
||||
and (event_pos.x < (obj.position.x + obj.size.x)) \
|
||||
and (event_pos.y > obj.position.y) \
|
||||
and (event_pos.y < (obj.position.y + obj.size.y))
|
||||
|
||||
|
||||
##
|
||||
func setselector_color_proc(instance: Object, function: String) -> void:
|
||||
selector_color_proc = Callable(instance, function)
|
||||
assert(selector_color_proc.is_valid())
|
||||
|
||||
|
||||
##
|
||||
func set_selector_size_proc(instance: Object, function: String) -> void:
|
||||
selector_size_proc = Callable(instance, function)
|
||||
assert(selector_size_proc.is_valid())
|
||||
|
||||
|
||||
## [param i_row] - Индекс ряда
|
||||
func default_selector_size_proc(i_row: int, rect: Array) -> void:
|
||||
get_row_rect(i_row, rect)
|
||||
rect[0] += position.x - node_gap.x / 2.0
|
||||
rect[1] += position.y - node_gap.y / 2.0
|
||||
rect[2] += node_gap.x
|
||||
rect[3] += node_gap.y
|
||||
|
||||
|
||||
##
|
||||
func clear_rows_selected():
|
||||
var rows_count: = get_rows_count()
|
||||
for i_row in rows_count:
|
||||
set_row_selected(i_row, false)
|
||||
|
||||
|
||||
## [param i_row] - Индекс ряда.
|
||||
func set_row_selected(i_row: int, selected: bool = true) -> void:
|
||||
call_deferred('_set_row_selected', i_row, selected)
|
||||
|
||||
|
||||
## [param i_row] - Индекс ряда.
|
||||
func _set_row_selected(i_row: int, selected: bool = true) -> void:
|
||||
var selector_key: = SELECTOR_KEY_FORMAT % [i_row, 0]
|
||||
var node_key: = NODE_KEY_FORMAT % [i_row, 0]
|
||||
if not nodes.has(node_key):
|
||||
push_error('ошибка: нет такого ряда: %d' % i_row)
|
||||
return
|
||||
if nodes.has(selector_key) and (not selected):
|
||||
var node = nodes[node_key]
|
||||
var selector = nodes[selector_key]
|
||||
node.remove_child(selector)
|
||||
selector.queue_free()
|
||||
nodes.erase(selector_key)
|
||||
elif (not nodes.has(selector_key)) and selected:
|
||||
var selector = ScnSelector.instantiate()
|
||||
var rect: = [0, 0, 0, 0]
|
||||
selector_size_proc.call(i_row, rect)
|
||||
selector.size.x = rect[2]
|
||||
selector.size.y = rect[3]
|
||||
selector.modulate = selector_color_proc.call(i_row)
|
||||
selector.z_index = z_index + 1
|
||||
nodes[selector_key] = selector
|
||||
var node = nodes[node_key]
|
||||
node.add_child(selector)
|
||||
|
||||
|
||||
## [param i_row] - Индекс ряда.
|
||||
func is_row_selected(i_row: int) -> bool:
|
||||
var key: = SELECTOR_KEY_FORMAT % [i_row, 0]
|
||||
return nodes.has(key)
|
||||
|
||||
|
||||
## [param i_row] - Индексы рядов.
|
||||
func get_selected_rows(i_rows: Array) -> void:
|
||||
for key in nodes.keys():
|
||||
if SLCT_PREFIX in key:
|
||||
var indexes = get_key_indexes(key)
|
||||
i_rows.append(indexes[1])
|
||||
|
||||
|
||||
## Возвращает количество рядов
|
||||
func get_rows_count() -> int:
|
||||
var nodes_count: int = 0
|
||||
for key in nodes:
|
||||
nodes_count += int(NODE_PREFIX in key)
|
||||
return nodes_count / columns
|
||||
|
||||
|
||||
## [param i_col] - Индекс колонки. [param i_row] - Индекс ряда.
|
||||
func get_node2(i_col: int, i_row: int) -> Node:
|
||||
var key: = get_node_key(NODE_KEY_FORMAT, i_col, i_row)
|
||||
return nodes[key]
|
||||
|
||||
|
||||
## Добавляет ряд. [param row_types] - Массив типов ячеек ряда
|
||||
func add_row(row_types: Array) -> void:
|
||||
assert(row_types.size() < MAX_INDEX, 'ошибка: слишком много колонок, больше %d' % (MAX_INDEX + 1))
|
||||
if columns == 1:
|
||||
set_columns(row_types.size())
|
||||
else:
|
||||
assert(columns == row_types.size(), 'ошибка: %s количество колонок установлено ранее - %d, но требуется %d' % [self, columns, row_types.size()])
|
||||
var i_row: int = get_rows_count()
|
||||
assert(i_row < MAX_INDEX, 'ошибка: слишком много рядов, больше %d' % (MAX_INDEX + 1))
|
||||
var allow_set_size = columns_min_size.size() >= row_types.size()
|
||||
for i_col in columns:
|
||||
var node_type = row_types[i_col]
|
||||
var node = node_type.instantiate()
|
||||
assert(node.name == node.name, 'ошибка: экземпляр не имеет поля \"name\"')
|
||||
var node_key: String = NODE_KEY_FORMAT % [i_row, i_col]
|
||||
node.name = node_key
|
||||
add_child(node)
|
||||
if allow_set_size:
|
||||
node.custom_minimum_size.x = columns_min_size[i_col]
|
||||
node.set_deferred('size.x', columns_min_size[i_col])
|
||||
nodes[node_key] = node
|
||||
var conn = Callable(self, '_on_focus_entered').bind(node)
|
||||
node.connect('focus_entered', conn)
|
||||
set_node_meta(i_col, i_row, 'callable_focus_entered', conn)
|
||||
|
||||
|
||||
##
|
||||
func _on_focus_entered(node):
|
||||
var indexes = get_key_indexes(node.name)
|
||||
emit_signal('row_pressed', indexes[1])
|
||||
|
||||
|
||||
##
|
||||
func set_header(header_types: Array):
|
||||
assert(header_types.size() < MAX_INDEX, 'ошибка: количество колонок в заголовке: %d больше допустимого: %d' % [header_types.size(), MAX_INDEX + 1])
|
||||
if columns == 1:
|
||||
set_columns(header_types.size())
|
||||
else:
|
||||
assert(columns == header_types.size(), 'ошибка: количество колонок в заголовке %d, но ранее установлено: %d' % [header_types.size(), columns])
|
||||
var do_adj: = false
|
||||
if not header:
|
||||
header = GridContainer.new()
|
||||
var parent = scroll.get_parent()
|
||||
parent.add_child(header)
|
||||
header.position = position
|
||||
header.columns = header_types.size()
|
||||
do_adj = true
|
||||
else:
|
||||
var childrens = header.get_children()
|
||||
for node in childrens:
|
||||
childrens.erase(node.name)
|
||||
childrens.remove_child(node)
|
||||
node.queue_free()
|
||||
for i_col in columns:
|
||||
var node = header_types[i_col].instantiate()
|
||||
var key: String = HEAD_KEY_FORMAT % [0, i_col]
|
||||
node.name = key
|
||||
nodes[key] = node
|
||||
header.add_child(node)
|
||||
if do_adj:
|
||||
var rect = [0, 0, 0, 0]
|
||||
get_node_rect(0, 0, rect, HEAD_KEY_FORMAT)
|
||||
position.y += rect[3]
|
||||
|
||||
|
||||
##
|
||||
func set_header_text(headers: Array):
|
||||
assert(columns == headers.size())
|
||||
set_row_text(0, headers, 0, HEAD_KEY_FORMAT)
|
||||
|
||||
|
||||
## [param i_row] - индекс ряда.
|
||||
func remove_row(i_row: int):
|
||||
var sel_key: String = SELECTOR_KEY_FORMAT % [i_row, 0]
|
||||
|
||||
if nodes.has(sel_key):
|
||||
var selector = nodes[sel_key]
|
||||
var prnt = selector.get_parent()
|
||||
prnt.remove_child(selector)
|
||||
selector.queue_free()
|
||||
nodes.erase(sel_key)
|
||||
|
||||
for i_col in columns:
|
||||
|
||||
var node_key: String = NODE_KEY_FORMAT % [i_row, i_col]
|
||||
var node = nodes[node_key]
|
||||
|
||||
var conn = get_node_meta(i_col, i_row, 'callable_focus_entered')
|
||||
node.disconnect('focus_entered', conn)
|
||||
|
||||
if node.has_meta(META_CALLABLE):
|
||||
conn = node.get_meta(META_CALLABLE)
|
||||
var event_name = node.get_meta(META_EVENT_NAME)
|
||||
node.disconnect(event_name, conn)
|
||||
|
||||
remove_child(node)
|
||||
nodes.erase(node_key)
|
||||
node.queue_free()
|
||||
rehash()
|
||||
|
||||
|
||||
##
|
||||
func next_index(indexes: Array) -> void:
|
||||
indexes[0].x += 1
|
||||
if indexes[0].x == columns:
|
||||
indexes[0].x = 0
|
||||
indexes[0].y += 1
|
||||
|
||||
|
||||
##
|
||||
func swap_nodes(nodes_copy, node_key_format, i0, i1) -> void:
|
||||
var key0 = node_key_format % [i0.y, i0.x]
|
||||
var key1 = node_key_format % [i1.y, i1.x]
|
||||
nodes_copy[key1] = nodes[key0]
|
||||
nodes_copy[key1].name = key1
|
||||
|
||||
|
||||
## Переназначает ключи в соответствии с размерами таблицы.
|
||||
func rehash():
|
||||
var i_node: = [Vector2i()]
|
||||
var i_hdr: = [Vector2i()]
|
||||
var i_slct: = [Vector2i()]
|
||||
var nodes_copy = {}
|
||||
for key in nodes:
|
||||
var iv = get_key_indexes(key)
|
||||
var i0 = Vector2(iv[0], iv[1])
|
||||
if HEAD_PREFIX in key:
|
||||
swap_nodes(nodes_copy, HEAD_KEY_FORMAT, i0, i_hdr[0])
|
||||
next_index(i_hdr)
|
||||
elif NODE_PREFIX in key:
|
||||
swap_nodes(nodes_copy, NODE_KEY_FORMAT, i0, i_node[0])
|
||||
next_index(i_node)
|
||||
elif SLCT_PREFIX in key:
|
||||
swap_nodes(nodes_copy, SELECTOR_KEY_FORMAT, i0, i_slct[0])
|
||||
next_index(i_slct)
|
||||
nodes.clear()
|
||||
nodes.merge(nodes_copy)
|
||||
|
||||
|
||||
## Удаляет все строки таблицы
|
||||
func clear_rows() -> void:
|
||||
var rows_count: int = get_rows_count()
|
||||
for i_row in range(rows_count - 1, -1, -1):
|
||||
remove_row(i_row)
|
||||
|
||||
|
||||
## Настраевает обратные вызовы для колонок.[br]
|
||||
## [param dest_node] - Получатель события[br]
|
||||
## [param event_name] - Имя события[br]
|
||||
## [param callback] - Имя функции получателя события[br]
|
||||
## [param clmns] - Индексы колонок[br]
|
||||
## [param user_data] - Аргументы функции получателя события[br]
|
||||
func connect_columns(dest_node: Node, event_name: String, callback: String, clmns: Array, user_data: Array = []) -> void:
|
||||
var rows_count = get_rows_count()
|
||||
for i_row in rows_count:
|
||||
for i_col in clmns:
|
||||
assert(i_col >= 0 and i_col < columns, 'ошибка: нет такой колонки: %d' % i_col)
|
||||
var node = get_node2(i_col, i_row)
|
||||
if node.has_meta(META_CALLABLE):
|
||||
var conn = node.get_meta(META_CALLABLE)
|
||||
node.disconnect(event_name, conn)
|
||||
var conn = Callable(dest_node, callback).bind([node] + user_data)
|
||||
var rc = node.connect(event_name, conn)
|
||||
if rc == Error.OK:
|
||||
node.set_meta(META_CALLABLE, conn)
|
||||
node.set_meta(META_EVENT_NAME, event_name)
|
||||
else:
|
||||
var es = error_string(rc)
|
||||
push_error('ошибка: событие \"%s\" не может быть подключено к %s: %s' % [event_name, node, es])
|
||||
|
||||
|
||||
##
|
||||
func set_rows_text(rows: Array) -> void:
|
||||
for i_row in rows.size():
|
||||
for i_col in columns:
|
||||
var key: = get_node_key(NODE_KEY_FORMAT, i_col, i_row)
|
||||
var node = nodes[key]
|
||||
node.text = rows[i_row][i_col]
|
||||
|
||||
|
||||
## [param i_row] - Индекс ряда.
|
||||
func set_nodes_min_size(i_row: int, sizes: Array, key_format: String = NODE_KEY_FORMAT) -> void:
|
||||
columns_min_size.clear()
|
||||
for sz in sizes:
|
||||
columns_min_size.append(sz)
|
||||
for i_col in columns:
|
||||
var key: = get_node_key(key_format, i_col, i_row)
|
||||
var node = nodes[key]
|
||||
node.custom_minimum_size.x = sizes[i_col]
|
||||
node.set_deferred('size.x', sizes[i_col])
|
||||
|
||||
|
||||
## [param i_col] - Индекс колонки. [param i_row] - Индекс ряда.
|
||||
func set_node_text(i_col: int, i_row: int, val: String) -> void:
|
||||
var key: = get_node_key(NODE_KEY_FORMAT, i_col, i_row)
|
||||
nodes[key].text = val
|
||||
|
||||
|
||||
##
|
||||
func set_rows_state(rows: Array) -> void:
|
||||
for i_row in rows.size():
|
||||
for i_col in columns:
|
||||
var key: = get_node_key(NODE_KEY_FORMAT, i_col, i_row)
|
||||
nodes[key].button_pressed = rows[i_row][i_col]
|
||||
|
||||
|
||||
## Устанавливает состояние ячейки.
|
||||
## [param i_col] - Индекс колонки.[br]
|
||||
## [param i_row] - Индекс ряда.[br]
|
||||
## [param val] - Новое состояние.[br]
|
||||
## [i]Ячейка должна иметь свойство[/i] [param button_pressed]
|
||||
func set_node_state(i_col: int, i_row: int, val: bool) -> void:
|
||||
var key: = get_node_key(NODE_KEY_FORMAT, i_col, i_row)
|
||||
nodes[key].button_pressed = val
|
||||
|
||||
|
||||
## [param i_col] - Индекс колонки.[br]
|
||||
## [param i_row] - Индекс ряда.
|
||||
func set_node_frame(i_col: int, i_row: int, node_name: String, val: int) -> void:
|
||||
var key: = get_node_key(NODE_KEY_FORMAT, i_col, i_row)
|
||||
var node = nodes[key]
|
||||
var sprite = node.get_node(node_name)
|
||||
sprite.frame = val
|
||||
|
||||
|
||||
## Разрешает редактирование ячейки. Ячейка должна иметь свойство [param editable].[br]
|
||||
## [param i_col] - Индекс колонки.[br]
|
||||
## [param i_row] - Индекс ряда.
|
||||
func set_node_editable(i_col: int, i_row: int, val: bool) -> void:
|
||||
var key: = get_node_key(NODE_KEY_FORMAT, i_col, i_row)
|
||||
nodes[key].editable = val
|
||||
|
||||
|
||||
## Разрешает редактирование ряда. Ячейки ряда должны иметь свойство [param editable].[br]
|
||||
## [param i_row] - Индекс ряда.
|
||||
func set_row_editable(i_row: int, val: bool) -> void:
|
||||
for i_col in columns:
|
||||
var key: = get_node_key(NODE_KEY_FORMAT, i_col, i_row)
|
||||
nodes[key].editable = val
|
||||
|
||||
|
||||
## [param i_col] - Индекс колонки.[br]
|
||||
## [param i_row] - Индекс ряда.
|
||||
func set_node_alligment(i_col: int, i_row: int, val: int) -> void:
|
||||
var key: = get_node_key(NODE_KEY_FORMAT, i_col, i_row)
|
||||
if 'horizontal_alignment' in nodes[key]:
|
||||
nodes[key].horizontal_alignment = val
|
||||
elif 'alignment' in nodes[key]:
|
||||
nodes[key].alignment = val
|
||||
|
||||
|
||||
## [param i_row] - Индекс ряда.
|
||||
func set_row_alligments(i_row: int, aligns: Array) -> void:
|
||||
for i_col in columns:
|
||||
var key: = get_node_key(NODE_KEY_FORMAT, i_col, i_row)
|
||||
nodes[key].align = aligns[i_col]
|
||||
|
||||
|
||||
## [param i_row] - Индекс ряда.
|
||||
func get_row_text(i_row: int) -> Array:
|
||||
var row: = []
|
||||
for i_col in columns:
|
||||
var key: = get_node_key(NODE_KEY_FORMAT, i_col, i_row)
|
||||
row.append(nodes[key].text)
|
||||
return row
|
||||
|
||||
|
||||
## Устанавливает текст ячеек ряда.[br]
|
||||
## [param i_row] - Индекс ряда.[br]
|
||||
## [param row] - Массив новых значений текста (поле элемента [param text]).[br]
|
||||
## [param i_begin] - Начальный индекс колонки начиная с которого установить новые значения.[br]
|
||||
## [i]Ячейки ряда должны иметь свойство[/i] [param text]
|
||||
func set_row_text(i_row: int, row: Array, i_begin: int = 0, key_format: String = NODE_KEY_FORMAT) -> void:
|
||||
assert((i_begin >= 0) and (i_begin < columns))
|
||||
assert((columns - i_begin) >= row.size())
|
||||
for i_col in row.size():
|
||||
var key: = get_node_key(key_format, i_col + i_begin, i_row)
|
||||
nodes[key].text = row[i_col]
|
||||
|
||||
|
||||
## Возвращает все строки таблицы в двумерном массиве
|
||||
func get_rows_text() -> Array:
|
||||
var rows_count: = get_rows_count()
|
||||
var rows: = []
|
||||
for i_row in rows_count:
|
||||
var row = get_row_text(i_row)
|
||||
rows.append(row)
|
||||
return rows
|
||||
|
||||
|
||||
## Возвращает массив ячеек из ряда [param i_row]
|
||||
func get_row(i_row: int) -> Array:
|
||||
var row: = []
|
||||
for i_col in columns:
|
||||
var key: = get_node_key(NODE_KEY_FORMAT, i_col, i_row)
|
||||
row.append(nodes[key])
|
||||
return row
|
||||
|
||||
|
||||
## Возвращает прямоугольник [param rect] занимаемый ячейкой в ряду [param i_row] и колонке [param i_col].[br]
|
||||
## [param key_format] - Формат ключа ячейки
|
||||
func get_node_rect(i_col: int, i_row: int, rect: Array, key_format: String = NODE_KEY_FORMAT) -> void:
|
||||
assert(rect.size() >= 4, 'ошибка: мало элементов для возврата результата')
|
||||
var key: = get_node_key(key_format, i_col, i_row)
|
||||
var cell = nodes[key]
|
||||
rect[0] = cell.position.x
|
||||
rect[1] = cell.position.y
|
||||
rect[2] = cell.size.x
|
||||
rect[3] = cell.size.y
|
||||
|
||||
|
||||
## Возвращает прямоугольник [param rect] занимаемый рядом [param i_row].[br]
|
||||
## [param key_format] - Формат ключа ячейки
|
||||
func get_row_rect(i_row: int, rect: Array, key_format: String = NODE_KEY_FORMAT) -> void:
|
||||
assert(rect.size() >= 4, 'ошибка: мало элементов для возврата результата')
|
||||
var cell0 = nodes[get_node_key(key_format, 0, i_row)]
|
||||
var cell1 = nodes[get_node_key(key_format, columns - 1, i_row)]
|
||||
rect[0] = cell0.position.x + scroll_adj.x
|
||||
rect[1] = cell0.position.y + scroll_adj.y
|
||||
rect[2] = (cell1.position.x - cell0.position.x) + cell1.size.x
|
||||
rect[3] = cell0.size.y
|
||||
91
tools.gd
Normal file
91
tools.gd
Normal file
@@ -0,0 +1,91 @@
|
||||
extends Node
|
||||
|
||||
## Простые часто используемые функции
|
||||
|
||||
|
||||
## Возвращает true, если val число, false - если иначе
|
||||
func type_is_num(val) -> bool: return (typeof(val) == TYPE_INT) or (typeof(val) == typeof(0.0))
|
||||
|
||||
|
||||
## Возвращае значение в радианах для угла в градусах [param val].
|
||||
func radians(val: float): return (val * PI) / 180.0
|
||||
|
||||
|
||||
## Возвращает значение из байтового массива в 16 битное слово
|
||||
func ibytes_to_word(buf: PackedByteArray, i: int) -> int: return buf[i] + (buf[i + 1] * 256)
|
||||
|
||||
|
||||
## Возвращает расстояние от центра координат до точки
|
||||
func hypot(x: float, y: float) -> float: return sqrt(x * x + y * y)
|
||||
|
||||
|
||||
## Проверяет пересечение диапазона x1, x2 с диапазоном y1, y2 """
|
||||
func is_overlap(x1, x2, y1, y2): return max(x1, y1) <= min(x2, y2)
|
||||
|
||||
|
||||
## Производит преобразование из декартовой в полярную систему координат
|
||||
func pos_calc(freq: float, aoa: float, pos0: Vector2, r0: float, r1: float, f0: float, f1: float):
|
||||
var df = f1 - f0
|
||||
var r = ((freq - f0) / df) * r1 + r0
|
||||
var aoa_rad = Tools.radians(aoa) - PI / 2.0
|
||||
var x = cos(aoa_rad) * r + pos0.x
|
||||
var y = sin(aoa_rad) * r + pos0.y
|
||||
return Vector2(x, y)
|
||||
|
||||
|
||||
## Отсекает значение до заданного диапазона
|
||||
func clip_value(val, val_min, val_max):
|
||||
if val < val_min: val = val_min
|
||||
if val > val_max: val = val_max
|
||||
return val
|
||||
|
||||
|
||||
## Возвращает массив с именами членов класса [param obj]
|
||||
func get_members_names(obj: Object) -> Array:
|
||||
var pl = obj.get_property_list()
|
||||
var names: = Array()
|
||||
for item in pl: names.append(item.name)
|
||||
return names
|
||||
|
||||
|
||||
## Проверяет, что заданный массив [param val] похож на ip-адрес
|
||||
func is_array_ipaddr(val: Array):
|
||||
return (val.size() == 2) \
|
||||
and (val[0] is String) \
|
||||
and (val[1] is int) \
|
||||
and (0 < val[1]) \
|
||||
and (val[1] < 65536)
|
||||
|
||||
|
||||
## Преобразует декартовые координаты в полярные. [param pos0]
|
||||
## - координаты центра, [param pos] - координаты точки
|
||||
func map_dec_to_rad(pos0: Vector2, pos: Vector2) -> Vector2:
|
||||
var x1: float = pos.x - pos0.x
|
||||
var y1: float = pos.y - pos0.y
|
||||
var ang: float = (atan2(y1, x1) * 180.0) / PI
|
||||
return Vector2(ang + 360.0 * float(ang < 0.0), hypot(x1, y1))
|
||||
|
||||
|
||||
## Переводит значение val из диапазона a0...a1 в диапазон b0...b1
|
||||
func map_range(val: float, a0: float, a1: float, b0: float, b1: float) -> float:
|
||||
return (((val - a0) * (b1 - b0)) / (a1 - a0)) + b0
|
||||
|
||||
|
||||
## Возвращает true, если точка внутри заданного полигона
|
||||
func is_point_in_polygon(pos: Vector2, p: PackedVector2Array) -> bool:
|
||||
var sz: = p.size()
|
||||
assert(sz)
|
||||
var j: int = sz - 1
|
||||
var odd_nodes: int = 0
|
||||
for i in sz:
|
||||
if ((p[i].y < pos.y and p[j].y >= pos.y or p[j].y < pos.y and p[i].y >= pos.y) and (p[i].x <= pos.x or p[j].x <= pos.x)):
|
||||
odd_nodes ^= int((p[i].x + (pos.y - p[i].y) / (p[j].y - p[i].y) * (p[j].x - p[i].x) < pos.x))
|
||||
j = i
|
||||
return odd_nodes > 0
|
||||
|
||||
|
||||
## Записывает слово [param val] в элемент байтовго массива [param buf]
|
||||
## расположенный по индексу [param index]. Младшее слово
|
||||
func set_wordi(buf: PackedByteArray, index: int, val: int) -> void:
|
||||
buf[index] = val & 0xff
|
||||
buf[index + 1] = (val >> 8) & 0xff
|
||||
251
yau07.gd
Normal file
251
yau07.gd
Normal file
@@ -0,0 +1,251 @@
|
||||
extends Node
|
||||
|
||||
## Протокол ячейки ЯУ-07
|
||||
|
||||
const ONLINE_TIMEOUT = 5.0 ## Время ожидания пакета от ячейки, секунды
|
||||
const RESEND_TIMEOUT = 0.3 ## Задержка для повторной отправки команды, секунды
|
||||
const RETRY_COUNT = 5 ## Количество повторных отправок
|
||||
const BUFFER_SIZE = 2048 ## Размер буфера для приёма датаграмм
|
||||
|
||||
|
||||
## Ячейка ЯУ-07
|
||||
class YaU07:
|
||||
|
||||
## Коды выполняемых команд
|
||||
enum CmdCode {
|
||||
READ_ISA = 0, ## Код команды "читать порт ISA" ячейки ЯУ-07
|
||||
WRITE_ISA = 1, ## Код команды "записать порт ISA" ячейки ЯУ-07
|
||||
WRITE_BLOCK = 60, ## Код команды "записать блок данных ISA" внешней шины ячейки ЯУ-07
|
||||
READ_BLOCK = 61, ## Код команды "читать блок данных ISA" внешней шины ячейки ЯУ-07
|
||||
}
|
||||
|
||||
## Состояние автомата выполнения команды
|
||||
enum CmdState {
|
||||
UNCK, ## Номер команды не ининциирован
|
||||
SEND, ## Буфер готов для отправки
|
||||
WAIT, ## Ожидание выполнения
|
||||
DONE, ## Команда выполнена
|
||||
FAIL ## Команда не выполнена
|
||||
}
|
||||
|
||||
signal line_changed(unit: YaU07) ## Вызывается когда меняется состояние связи с ячейкой
|
||||
signal command_done(unit: YaU07) ## Вызывается после выполнения команды
|
||||
signal command_fail(unit: YaU07) ## Вызывается если возникла ошибка при выполнении команды и команда не выполнена
|
||||
signal parse_failed(unit: YaU07)
|
||||
|
||||
var name: = String()
|
||||
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 = 0 ## Счётчик оставшихся попыток выполнить команду
|
||||
var tick = 0.0 ## Текущее тик-время в мс
|
||||
var cmd_tick_tx = 0.0 ## Время последней отправки команды
|
||||
var cmd_tick_rx = 0.0 ## Время последнего приёма команды
|
||||
var status: = PackedByteArray() ## Поле "состояние прибора"
|
||||
var tx_data: = PackedByteArray() ## Буфер отправленный
|
||||
var tx_len = 0 ## Длина последнего отправленного пакета
|
||||
var isa_ports = {} ## Результат выполнения команды "читать порт"
|
||||
var isa_block = {} ## Результат выполнения команды "читать массив из порта"
|
||||
var online = false ## Состояние связи с ячейкой
|
||||
|
||||
func _to_string() -> String: return String('яу07: %s %s' % [self.name, ['отключен', 'на связи'][int(online)]])
|
||||
|
||||
|
||||
func _init(nm):
|
||||
self.name = nm
|
||||
tx_data.resize(BUFFER_SIZE)
|
||||
tx_data.fill(0)
|
||||
tick = 0.0
|
||||
|
||||
|
||||
func process(delta: float):
|
||||
tick += delta
|
||||
if online and ((tick - cmd_tick_rx) > ONLINE_TIMEOUT):
|
||||
online = false
|
||||
emit_signal('line_changed', self)
|
||||
|
||||
var rc = Error.ERR_BUSY
|
||||
if cmd_state == CmdState.SEND:
|
||||
rc = Error.OK
|
||||
cmd_tick_tx = tick
|
||||
cmd_state = CmdState.WAIT
|
||||
cmd_retry = RETRY_COUNT
|
||||
elif cmd_state == CmdState.WAIT:
|
||||
if cmd_retry:
|
||||
if (tick - cmd_tick_tx) > RESEND_TIMEOUT:
|
||||
rc = Error.OK
|
||||
cmd_tick_tx = tick
|
||||
cmd_retry -= 1
|
||||
else:
|
||||
cmd_state = CmdState.FAIL
|
||||
emit_signal('command_fail', self)
|
||||
rc = Error.FAILED
|
||||
return rc
|
||||
|
||||
|
||||
func send_isa(cmd_code: int, ports_data: Array) -> bool:
|
||||
if (cmd_state == CmdState.WAIT) \
|
||||
or (cmd_state == CmdState.SEND) \
|
||||
or (cmd_state == CmdState.UNCK):
|
||||
return false
|
||||
if not ((cmd_code == CmdCode.READ_ISA) or (cmd_code == CmdCode.WRITE_ISA)):
|
||||
return false
|
||||
|
||||
cmd_num_tx = (cmd_num_rx + 1) % 0xffff
|
||||
|
||||
var ports_count = ports_data.size()
|
||||
tx_len = ports_count * 2 + 8
|
||||
tx_data.encode_u16(0, cmd_retry)
|
||||
tx_data.encode_u16(2, cmd_num_tx)
|
||||
|
||||
if cmd_code == CmdCode.WRITE_ISA:
|
||||
ports_count /= 2
|
||||
tx_data[6] = cmd_code
|
||||
tx_data[7] = ports_count
|
||||
|
||||
var i: = 0
|
||||
for word in ports_data:
|
||||
tx_data.encode_u16(8 + i, word)
|
||||
i += 2
|
||||
cmd_state = CmdState.SEND
|
||||
return true
|
||||
|
||||
func send_block(cmd_code: int, block_len: int, port: Array) -> bool:
|
||||
if (cmd_state == CmdState.WAIT) \
|
||||
or (cmd_state == CmdState.SEND) \
|
||||
or (cmd_state == CmdState.UNCK):
|
||||
return false
|
||||
if not ((cmd_code == CmdCode.READ_BLOCK) or (cmd_code == CmdCode.WRITE_BLOCK)):
|
||||
return false
|
||||
|
||||
cmd_num_tx = (cmd_num_rx + 1) % 0xffff
|
||||
|
||||
tx_len = 11
|
||||
tx_data.encode_u16(0, cmd_retry) # Исправление для сетевого стека "nm-callback" процессоров STM
|
||||
tx_data.encode_u16(2, cmd_num_tx)
|
||||
|
||||
if cmd_code == CmdCode.WRITE_BLOCK:
|
||||
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
|
||||
return true
|
||||
## Возвращает код команды ЯУ-07
|
||||
## rx_data - Принятые данные
|
||||
## Возвращает - Код команды не найден, 0...255 - код команды
|
||||
func get_cmd_code(rx_data: PackedByteArray):
|
||||
|
||||
var rx_data_len = rx_data.size()
|
||||
if rx_data_len < 7:
|
||||
return null
|
||||
var status_len = rx_data.decode_u16(6) # Длина массива байтового "состояние прибора"
|
||||
if rx_data_len <= (status_len + 6): # Нет поля "ответ на команду", разбор пакета завершить
|
||||
return null
|
||||
var cmd_code_pos = status_len + 8 # Положение в приёмном буфере "кода команды"
|
||||
if rx_data_len <= cmd_code_pos:
|
||||
return null
|
||||
return rx_data[cmd_code_pos] # "код команды"
|
||||
|
||||
|
||||
func parse_isa(rx_data: PackedByteArray, status_len) -> bool:
|
||||
var ports_count_pos = status_len + 10 # Положение в приёмном буфере количества портов
|
||||
if rx_data.size() < ports_count_pos:
|
||||
return false
|
||||
var ports_count = rx_data[ports_count_pos] # Количество портов
|
||||
var counted_len = status_len + (ports_count * 4) + 11 # Вычисленная длина пакета
|
||||
if rx_data.size() < counted_len:
|
||||
return false
|
||||
var addr_pos = status_len + 11 # Смещение положения адреса в приёмном буфере
|
||||
var data_pos = status_len + 13 # Смещение положения данных в приёмном буфере
|
||||
var i: = 0
|
||||
while i < ports_count:
|
||||
var shift: = i * 4
|
||||
var isa_addr = rx_data.decode_u16(addr_pos + shift) # Адрес порта ISA яу-07
|
||||
var isa_data = rx_data.decode_u16(data_pos + shift) # Данные порта ISA яу-07
|
||||
isa_ports[isa_addr] = isa_data
|
||||
i += 1
|
||||
cmd_data = rx_data.slice(addr_pos, (addr_pos + ports_count * 4) - 1)
|
||||
return i > 0
|
||||
|
||||
func parse_block(rx_data: PackedByteArray, status_len) -> bool:
|
||||
var ports_count_pos = status_len + 7 # Положение в приёмном буфере количества портов
|
||||
if rx_data.size() < ports_count_pos:
|
||||
return false
|
||||
|
||||
|
||||
var block_len = rx_data.decode_u16(ports_count_pos + 3)
|
||||
var block_adr = rx_data.decode_u16(ports_count_pos + 5)
|
||||
var block_data = []
|
||||
for i in block_len:
|
||||
var shift: int = i * 2
|
||||
block_data.append(rx_data.decode_u16(ports_count_pos + 7 + shift))
|
||||
isa_block[block_adr] = block_data
|
||||
return true
|
||||
|
||||
|
||||
## Заполняет состояние прибора данными прочитанными из портов ЯУ-07
|
||||
## для команд "читать порты" или "записать порты"[br]
|
||||
## [param rx_data] - Принятые данные[br]
|
||||
## Возвращает [b]true[/b] - Данные обработаны успешно[br]
|
||||
## Возвращает [b]false[/b] - Данные не обработаны из за ошибки[br]
|
||||
func parse(rx_data: PackedByteArray) -> bool:
|
||||
|
||||
cmd_tick_rx = tick
|
||||
|
||||
if not online:
|
||||
online = true
|
||||
emit_signal('line_changed', self)
|
||||
|
||||
var rx_data_len = rx_data.size()
|
||||
if rx_data_len < 10:
|
||||
return false
|
||||
|
||||
cmd_num_rx = rx_data.decode_u16(2)
|
||||
if cmd_state == CmdState.UNCK:
|
||||
cmd_state = CmdState.DONE
|
||||
|
||||
var status_len = rx_data.decode_u16(6) # Длина массива байтового "состояние прибора"
|
||||
if rx_data_len < (status_len + 6):
|
||||
return false
|
||||
var i: = 0
|
||||
var len_rx_data: = rx_data.size()
|
||||
status.resize(status_len)
|
||||
while i < status_len:
|
||||
var byte_i = i + 8
|
||||
if byte_i < len_rx_data:
|
||||
status[i] = rx_data[byte_i] # Массив байтовый "состояние прибора"
|
||||
i += 1
|
||||
|
||||
if rx_data_len == (status_len + 6):
|
||||
return true # Нет поля "ответ на команду", разбор пакета завершить
|
||||
|
||||
var cmd_code_pos = status_len + 8 # Положение в приёмном буфере "код команды"
|
||||
if rx_data_len <= cmd_code_pos:
|
||||
return false
|
||||
|
||||
var cmd_status_pos = status_len + 9 # Положение в приёмном буфере "состояния выполнения команды"
|
||||
if rx_data_len <= cmd_status_pos:
|
||||
return false
|
||||
cmd_result = rx_data[cmd_status_pos]
|
||||
if cmd_result != 1: # Команда не выполнена
|
||||
return false
|
||||
|
||||
var cmd_code: CmdCode = rx_data[cmd_code_pos] as CmdCode # "код команды"
|
||||
var rc = true
|
||||
if (cmd_code == CmdCode.READ_ISA) or (cmd_code == CmdCode.WRITE_ISA):
|
||||
rc = parse_isa(rx_data, status_len)
|
||||
|
||||
elif (cmd_code == CmdCode.READ_BLOCK) or (cmd_code == CmdCode.WRITE_BLOCK):
|
||||
rc = parse_block(rx_data, status_len)
|
||||
|
||||
if rc:
|
||||
if (cmd_state == CmdState.WAIT) and (cmd_num_rx == cmd_num_tx):
|
||||
cmd_state = CmdState.DONE
|
||||
emit_signal('command_done', self)
|
||||
else:
|
||||
emit_signal('parse_failed', self)
|
||||
return true
|
||||
Reference in New Issue
Block a user