first commit

This commit is contained in:
2024-11-13 11:09:11 +03:00
commit af3aad619a
25 changed files with 1548 additions and 0 deletions

2
.gitattributes vendored Normal file
View File

@@ -0,0 +1,2 @@
# Normalize EOL for all files that Git considers text files.
* text=auto eol=lf

2
.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
# Godot 4+ specific ignores
.godot/

3
.idea/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,3 @@
# Default ignored files
/shelf/
/workspace.xml

8
.idea/PRD-TU-MP550.iml generated Normal file
View 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>

View File

@@ -0,0 +1,6 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="ReassignedToPlainText" enabled="false" level="WARNING" enabled_by_default="false" />
</profile>
</component>

View 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
View 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.12" project-jdk-type="Python SDK" />
</project>

8
.idea/modules.xml generated Normal file
View 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/PRD-TU-MP550.iml" filepath="$PROJECT_DIR$/.idea/PRD-TU-MP550.iml" />
</modules>
</component>
</project>

6
.idea/vcs.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

122
PRD.gd Normal file
View File

@@ -0,0 +1,122 @@
extends Node2D
var unit = Yau07.YaU07.new('ЯУ07')
var soc_unicast: Socket
var soc_brodcast: Socket
var address: String
var ports_wr: Dictionary # Словарь с портами для записи
var port: int
var broadcast_packet: PackedByteArray
var node_select1: Node
const CONST_MIN_TEMP: int = -25
const MAXIMUM_CODE_ADC: int = 3796
const MINIMUM_CODE_AD: int = 2660
const TEMP: float = 115.0 / (MAXIMUM_CODE_ADC - MINIMUM_CODE_AD)
## Класс для отправки данных в сокет
class Socket extends PacketPeerUDP:
func send_to(addr: String, port: int, data: PackedByteArray):
self.set_dest_address(addr, port)
self.put_packet(data)
func poll_receive(sock: Socket) -> bool: ## Приёмник
while sock.get_available_packet_count() > 0:
broadcast_packet = sock.get_packet()
var addr_receive = sock.get_packet_ip()
var port_receive = sock.get_packet_port()
if (address == addr_receive) and (port == port_receive): # Если есть коннект с ЯУ-07Б
var data = unit.parse(broadcast_packet)
print('DATA: ', data)
return false
func _ready() -> void:
# Таблица 1
node_select1 = $scroll1/table1
draw_tabl(node_select1, Constants.ROWS_REGS_DATA, Constants.ROWS_REGS_DATA.size())
# Привязка для принятия широковещательного канала
soc_brodcast = Socket.new()
var rc = soc_brodcast.bind(Constants.BROADCAST_PORT, '*')
if rc != OK:
print('Ошибка: Неудачная привязка широковещательного адреса')
soc_unicast = Socket.new()
rc = soc_unicast.bind(Constants.UNICAST_PORT, Constants.UNICAST_ADDRESS)
if rc != OK:
print('Ошибка: неудачная привязка адреса: ', address)
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 _process(delta: float) -> void:
poll_receive(soc_brodcast)
if broadcast_packet:
parse_broadcast(broadcast_packet)
match unit.process(delta):
Error.OK: soc_unicast.send_to(address, port, unit.tx_data.slice(0, unit.tx_len))
Error.FAILED: print('Ошибка отправки данных')
func parse_broadcast(packet):
var power: int # Мощность с широковещания
var temperature: int # Температура с широковещания
var dry_contact: int # Контроль сухих контактов
var DKM: int
var EMS_G: int
$Packet.text = 'broadcast: ' + str(packet)
dry_contact = packet.decode_u16(35)
node_select1.set_node_text(2, 0, '%d' % dry_contact)
DKM = packet.decode_u16(37)
node_select1.set_node_text(3, 0, '%d' % DKM)
for i in range(8):
power = packet.decode_u16(39 + 2 * i)
node_select1.set_node_text(0, i, '%d' % power)
for i in range(8):
var TMP = packet.decode_u16(55 + 2 * i)
temperature = CONST_MIN_TEMP + (MAXIMUM_CODE_ADC - TMP) * TEMP
node_select1.set_node_text(1, i, '%d' % temperature)
for i in range(8):
power = packet.decode_u16(71 + 2 * i)
node_select1.set_node_text(0, i+8, '%d' % power)
for i in range(8):
var TMP = packet.decode_u16(87 + 2 * i)
temperature = CONST_MIN_TEMP + (MAXIMUM_CODE_ADC - TMP) * TEMP
node_select1.set_node_text(1, i+8, '%d' % temperature)
for i in range(15):
EMS_G = packet.decode_u16(9 + 2 * i)
node_select1.set_node_text(3, i+2, '%d' % EMS_G)
func on_line_changed(_unit) -> void:
if unit.online:
print_debug('Online')
else:
print_debug('Offline')
## Ессли отсутствует подключение к ячейке ЯУ-07
func on_command_fail(_unit) -> void:
print_debug('Command send fail')
## Индикация чтения портов
func on_command_done(_unit) -> void:
pass

42
PRD95A4.tmp Normal file
View File

@@ -0,0 +1,42 @@
[gd_scene load_steps=6 format=3 uid="uid://cvor2jm4xk3ha"]
[ext_resource type="Script" path="res://PRD.gd" id="1_v273n"]
[ext_resource type="Script" path="res://table/table.gd" id="2_vmbyo"]
[sub_resource type="StyleBoxTexture" id="StyleBoxTexture_5yjx2"]
[sub_resource type="StyleBoxTexture" id="StyleBoxTexture_kofy6"]
[sub_resource type="StyleBoxTexture" id="StyleBoxTexture_hdj8q"]
[node name="Node2D" type="Node2D"]
script = ExtResource("1_v273n")
[node name="Background" type="ColorRect" parent="."]
offset_right = 1321.0
offset_bottom = 790.0
color = Color(0.356863, 0.278431, 0.356863, 1)
metadata/_edit_lock_ = true
[node name="scroll1" type="ScrollContainer" parent="."]
offset_left = 586.0
offset_top = 45.0
offset_right = 949.0
offset_bottom = 629.0
[node name="table1" type="GridContainer" parent="scroll1"]
layout_mode = 2
script = ExtResource("2_vmbyo")
metadata/_edit_lock_ = true
[node name="TextEdit" type="TextEdit" parent="."]
offset_left = 1028.0
offset_top = 602.0
offset_right = 1140.0
offset_bottom = 640.0
theme_override_colors/font_readonly_color = Color(1, 1, 1, 1)
theme_override_styles/normal = SubResource("StyleBoxTexture_5yjx2")
theme_override_styles/focus = SubResource("StyleBoxTexture_kofy6")
theme_override_styles/read_only = SubResource("StyleBoxTexture_hdj8q")
text = "Test PRD"
editable = false

55
PRD_MP550.tscn Normal file
View File

@@ -0,0 +1,55 @@
[gd_scene load_steps=6 format=3 uid="uid://cvor2jm4xk3ha"]
[ext_resource type="Script" path="res://PRD.gd" id="1_v273n"]
[ext_resource type="Script" path="res://table/table.gd" id="2_vmbyo"]
[sub_resource type="StyleBoxTexture" id="StyleBoxTexture_5yjx2"]
[sub_resource type="StyleBoxTexture" id="StyleBoxTexture_kofy6"]
[sub_resource type="StyleBoxTexture" id="StyleBoxTexture_hdj8q"]
[node name="Node2D" type="Node2D"]
script = ExtResource("1_v273n")
[node name="Background" type="ColorRect" parent="."]
offset_right = 2226.0
offset_bottom = 1153.0
color = Color(0.356863, 0.278431, 0.356863, 1)
metadata/_edit_lock_ = true
[node name="scroll1" type="ScrollContainer" parent="."]
offset_left = 586.0
offset_top = 45.0
offset_right = 949.0
offset_bottom = 629.0
[node name="table1" type="GridContainer" parent="scroll1"]
layout_mode = 2
script = ExtResource("2_vmbyo")
metadata/_edit_lock_ = true
[node name="TextEdit" type="TextEdit" parent="."]
offset_left = 1027.0
offset_top = 633.0
offset_right = 1139.0
offset_bottom = 671.0
theme_override_colors/font_readonly_color = Color(1, 1, 1, 1)
theme_override_styles/normal = SubResource("StyleBoxTexture_5yjx2")
theme_override_styles/focus = SubResource("StyleBoxTexture_kofy6")
theme_override_styles/read_only = SubResource("StyleBoxTexture_hdj8q")
text = "Test PRD"
editable = false
[node name="Packet" type="TextEdit" parent="."]
offset_left = 23.0
offset_top = 532.0
offset_right = 1121.0
offset_bottom = 712.0
theme_override_colors/font_readonly_color = Color(1, 1, 1, 1)
theme_override_styles/normal = SubResource("StyleBoxTexture_5yjx2")
theme_override_styles/focus = SubResource("StyleBoxTexture_kofy6")
theme_override_styles/read_only = SubResource("StyleBoxTexture_hdj8q")
text = "broadcast"
editable = false
wrap_mode = 1

26
constants.gd Normal file
View File

@@ -0,0 +1,26 @@
extends Node
const UNICAST_PORT: int = 50003
const UNICAST_ADDRESS: String = '10.1.1.3'
const BROADCAST_PORT: int = 50000
const DEFAULT_ADDRESS: String = '10.1.1.52'
const DEFAULT_PORT: String = '50052'
const BASE_ADDR: int = 0x100
const TableNode = preload("res://table/node.tscn")
const ROWS_REGS_DATA: Array = [
[TableNode, TableNode, TableNode, TableNode],
[TableNode, TableNode, TableNode, TableNode],
[TableNode, TableNode, TableNode, TableNode],
[TableNode, TableNode, TableNode, TableNode],
[TableNode, TableNode, TableNode, TableNode],
[TableNode, TableNode, TableNode, TableNode],
[TableNode, TableNode, TableNode, TableNode],
[TableNode, TableNode, TableNode, TableNode],
[TableNode, TableNode, TableNode, TableNode],
[TableNode, TableNode, TableNode, TableNode],
[TableNode, TableNode, TableNode, TableNode],
[TableNode, TableNode, TableNode, TableNode],
[TableNode, TableNode, TableNode, TableNode],
[TableNode, TableNode, TableNode, TableNode],
[TableNode, TableNode, TableNode, TableNode],
[TableNode, TableNode, TableNode, TableNode],
[TableNode, TableNode, TableNode, TableNode],]

1
icon.svg Normal file
View 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 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 814 0v-39l-134 12-5 69q-2 10-14 13l-162 11q-12 0-16-11l-10-65H446l-10 65q-4 11-16 11l-162-11q-12-3-14-13l-5-69z" fill="#478cbf"/><path d="M483 600c0 34 58 34 58 0v-86c0-34-58-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: 949 B

37
icon.svg.import Normal file
View File

@@ -0,0 +1,37 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://hm2lphceeto5"
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

31
project.godot Normal file
View File

@@ -0,0 +1,31 @@
; 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="ПРД_для_МП-550"
run/main_scene="res://PRD_MP550.tscn"
config/features=PackedStringArray("4.2", "GL Compatibility")
config/icon="res://icon.svg"
[autoload]
Constants="*res://constants.gd"
Yau07="*res://yau07.gd"
[display]
window/size/viewport_width=1237
window/size/viewport_height=800
[rendering]
renderer/rendering_method="gl_compatibility"
renderer/rendering_method.mobile="gl_compatibility"

12
table/header.tscn Normal file
View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 125 B

View 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
View 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_top = -103.0
offset_right = -279.0
offset_bottom = -714.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")
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
View 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
View 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
View 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
View 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

297
yau07.gd Normal file
View File

@@ -0,0 +1,297 @@
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
ERASE_FLASH = 49, ## Код команды "стереть данные из FLASH" ячейки ЯУ-07
WRITE_FLASH = 50, ## Код команды "записать данные в FLASH" ячейки ЯУ-07
READ_FLASH = 51, ## Код команды "прочитать данные из FLASH" ячейки ЯУ-07
LOAD_FLASH = 52, ## Код команды "записать данные из FLASH в КЭМС" ячейки ЯУ-07
WRITE_FLASH_B = 55, ## Код команды "записать данные в FLASH" ячейки ЯУ-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 cmd_code_rx = 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 flash_data: = PackedByteArray() ## Данные прочитанные из Flash
var flash_len: = 0 ## Длинна данных прочитанных из Flash
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_flash(cmd_code: int, fl_data: Array, num_byte_rd: int) -> bool:
if (cmd_state == CmdState.WAIT) \
or (cmd_state == CmdState.SEND) \
or (cmd_state == CmdState.UNCK):
return false
if not ((cmd_code == CmdCode.ERASE_FLASH) or (cmd_code == CmdCode.WRITE_FLASH) \
or (cmd_code == CmdCode.READ_FLASH) or (cmd_code == CmdCode.LOAD_FLASH) or \
(cmd_code == CmdCode.WRITE_FLASH_B)):
return false
cmd_num_tx = (cmd_num_rx + 1) % 0xffff
tx_data.encode_u16(0, cmd_retry)
tx_data.encode_u16(2, cmd_num_tx)
tx_data.encode_u8(6, cmd_code)
if cmd_code == CmdCode.ERASE_FLASH:
tx_len = 7
elif cmd_code == CmdCode.WRITE_FLASH:
tx_data.encode_u8(7, 1)
tx_data.encode_u16(8, 0)
tx_data.encode_u16(10, len(fl_data))
tx_len = 11 + len(fl_data) * 2
for i in range(len(fl_data)):
tx_data.encode_u16(12 + i * 2, fl_data[i])
elif cmd_code == CmdCode.WRITE_FLASH_B:
tx_data.encode_u16(7, 0)
tx_data.encode_u16(9, len(fl_data))
tx_len = 11 + len(fl_data)
for i in range(len(fl_data)):
tx_data.encode_u8(11 + i, fl_data[i])
elif cmd_code == CmdCode.READ_FLASH:
tx_len = 11
tx_data.encode_u16(7, 0)
tx_data.encode_u16(9, num_byte_rd)
cmd_state = CmdState.SEND
return true
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
func parse_flash(rx_data: PackedByteArray, status_len, cmd_code) -> bool:
if cmd_code == CmdCode.READ_FLASH:
flash_len = rx_data.decode_u16(status_len + 12)
flash_data = rx_data.slice(status_len + 14)
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)
elif (cmd_code == CmdCode.ERASE_FLASH) or (cmd_code == CmdCode.WRITE_FLASH) \
or (cmd_code == CmdCode.READ_FLASH) or (cmd_code == CmdCode.LOAD_FLASH):
rc = parse_flash(rx_data, status_len, cmd_code)
if rc:
if (cmd_state == CmdState.WAIT) and (cmd_num_rx == cmd_num_tx):
cmd_state = CmdState.DONE
cmd_code_rx = cmd_code
emit_signal('command_done', self)
else:
emit_signal('parse_failed', self)
return true