Рефактор. Потому что приборы!

This commit is contained in:
sasha80
2024-04-25 13:53:21 +03:00
parent 0a63790322
commit ec1778e188
49 changed files with 473 additions and 875 deletions

View File

@@ -1,50 +0,0 @@
extends Node
## Реализация "Протокол информационного сопряжения
## СПО АСУ изделия «МП550» с СПО «Управления КАПС РПБ»"
const ONLINE_TIMEOUT = 3000 ## Время ожидания пакета от КАПС РПБ, мс
const BUFFER_SIZE = 2048 ## Размер приёмного буфера для датаграмм
class CapsRpb:
signal line_changed(unit: CapsRpb) ## Сигнал состояние связи изменилось
signal data_received(unit: CapsRpb) ## Сигнал данные приняты
signal command_fail(unit: CapsRpb) ## Сигнал при не выполнении команды
signal parse_error(unit: CapsRpb, msg: String) ## Сигнал ошибки разбора сообщения
static var json_conv: = JSON.new()
var online: = false ## Состояние связи с источником сообщений
var name: String ## Имя экземпляра
var json_dic: Dictionary ## Аппарат для разбора сообщений
var tick: = 0 ## Тик время последнего обновления, мс
var tx_data: = PackedByteArray() ## Пакет для отправки
var tx_stack: Array ## Массив пакетов для отправки
func _to_string() -> String: return String('капсрпб: "%s" %s ' % [self.name, ['отключен', 'на связи'][int(online)]])
func _init(nm): self.name = nm
func parse(data: PackedByteArray, now_tick: int):
tick = now_tick
if not online:
online = true
emit_signal('line_changed', self)
var utf8_data = data.get_string_from_utf8()
if Error.OK == json_conv.parse(utf8_data):
json_dic.merge(json_conv.get_data(), true)
emit_signal('data_received', self)
else:
var msg = json_conv.get_error_message()
emit_signal('parse_error', self, msg)
func process(now_tick: int):
if online and ((now_tick - tick) > ONLINE_TIMEOUT):
online = false
emit_signal('line_changed', self)
if len(tx_stack):
tx_data = tx_stack[0]
tx_stack.remove_at(0)
return Error.OK
else:
return Error.ERR_UNAVAILABLE

View File

@@ -1,38 +0,0 @@
extends table
var FILES_EXCLUDE_NAMES = ['.', '..']
var FILES_HEADERS_TEXT = ['Имя файла', 'Размер', 'Дата' ]
var FILES_COLUMN_WIDTHS = [ 400, 150, 250 ]
var FILES_HEADERS_ALIGNMENT = [ HORIZONTAL_ALIGNMENT_CENTER, HORIZONTAL_ALIGNMENT_CENTER, HORIZONTAL_ALIGNMENT_CENTER ]
var FILES_ROWS_ALIGNMENT = [ HORIZONTAL_ALIGNMENT_LEFT, HORIZONTAL_ALIGNMENT_RIGHT, HORIZONTAL_ALIGNMENT_CENTER ]
func fill_files_table(tbl: table, folder: String, rows_types: Array):
var dir: = DirAccess.open(folder)
if dir == null:
return
var files: = []
var exclude_names: = ['.', '..']
dir.list_dir_begin() # TODOGODOT4 fill missing arguments https://github.com/godotengine/godot/pull/40547
while true:
var file: = dir.get_next()
if file == '':
break
if file in exclude_names:
continue
var file_name = folder + '/' + file
var fd = FileAccess.open(file_name, FileAccess.READ)
if not fd:
continue
var glob_path = ProjectSettings.globalize_path(file_name)
var sz = fd.get_length()
var ut = fd.get_modified_time(file_name)
fd.close()
var mt = Time.get_datetime_dict_from_unix_time(ut)
var file_row = [file, '%d' % sz, '%d-%02d-%02d %02d:%02d' % [mt.year, mt.month, mt.day, mt.hour, mt.minute]]
tbl.add_row(rows_types)
var i_row = tbl.get_rows_count() - 1
tbl.set_row_text(i_row, file_row)
tbl.set_node_meta(0, i_row, 'file_path', glob_path)
dir.list_dir_end()

View File

@@ -1,20 +0,0 @@
extends Node
func _ready() -> void:
signaller.connect('full_screen', Callable(self, 'toggle_full_screen').bind(0))
func _input(event: InputEvent) -> void:
if event is InputEventKey:
if event.pressed and event.physical_keycode == KEY_F11:
signaller.emit_signal('full_screen')
## Переключает режим окна программы полный - обычный экран
func toggle_full_screen(id: int) -> void:
var mode = DisplayServer.window_get_mode(id)
mode = DisplayServer.WINDOW_MODE_WINDOWED \
if mode == DisplayServer.WINDOW_MODE_FULLSCREEN \
else DisplayServer.WINDOW_MODE_FULLSCREEN
DisplayServer.window_set_mode(mode, id)

View File

@@ -1,222 +0,0 @@
extends Node
## Представление помехи
## Режимы помехи
const ITERFER_MODE = {
'off': ['Откл', ['freq', 'width']],
'auto': ['АВТО', ['freq', 'width']],
'srni1': ['СРН-И1', ['freq', 'width']],
'srnm2': ['СРН-М2', ['freq', 'width']],
'lchm1': ['ЛЧМ-1', ['freq', 'width']],
'mchm1': ['МЧМ-1', ['freq', 'width']],
'pdm2': ['ПД-М2', ['freq', 'width']],
'pdm4': ['ПД-М4', ['freq', 'width']],
'pdm4s': ['ПД-М4С', ['freq', 'width']],
'pdchm': ['ПД-ЧМ', ['freq', 'width']],
'svi1': ['СВ-И1', ['freq', 'width']],
'svm2': ['СВ-М2', ['freq', 'width']],
'k1': ['K1', ['freq', 'width', 'dur', 'period']],
'ozp1': ['ОЗП-1', ['freq', 'width']] }
const TYPE_TH = ['th','fs']
const ID_INTERFERS_LEN = 63 ## Размер пула номеров целей
const INTERFER_OFF = 'off'
const STATE_TSMT = 5 ## Значение svk для цели, когда ответ ещё не получен
var msg_ss: int = 0 ## счётчик сообщений
var id_array: Array ## Массив id воздействий
var thrs: Dictionary ## Словарь воздействия для целей, ключ - id воздействия
var fs: Dictionary ## Словарь воздействий для секторов, ключ - id воздействия
## Сеанс помехи
class Interfer:
var ss: int = 0 ## Счётчик сообщений.
var ispp: int = 0 ## Индекс сеанса.
var svk: = 0 ## Состояние сеанса. Ответ КАПСРПБ. Возможные значения в [param ITERFER_STATE].
var krp: String = '' ## Тип помехи. Возможные значения в [param INTERFER_MODE].
var powp: float = 0.0 ## Мощность излучения выраженная в процентах.
var kni: = 0.0 ## Курсовой угол направления излучения.
var params: = {} ## Параметры помехи.
var get_params: Array[String] ## Запрос массива параметров (список запрошенных параметров).
var retss: int ## Значение счётчика сообщения в котором был произведён запрос массива параметров.
var tick_tx: int ## Тик время обновления цели, мс.
var type_th: String = '' ## Тип объекта по которой поставлена помеха
func _to_string() -> String:
return 'номер: %d название: %s частота: %0.1f МГц пеленг: %0.1f гр.' % [ispp, ITERFER_MODE[krp][0], params['freq'], kni]
func get_msg6_data():
var data_to_json : Dictionary = {}
data_to_json['ts'] = 6
data_to_json['ss'] = ss
data_to_json['ispp'] = ispp
data_to_json['krp'] = krp
if krp != INTERFER_OFF:
data_to_json['kni'] = kni
data_to_json['powp'] = powp # Необходимо добавить управление мощностью
data_to_json['get'] = []
data_to_json['params'] = params
var json = JSON.stringify(data_to_json)
var pac = json.to_utf8_buffer()
return pac
func set_from_dic(dic: Dictionary):
var names: = tools.get_members_names(self)
names.any(func(vn): if dic.has(vn): set(vn, dic[vn]); return false)
func get_value(value_key):
var param = self.get(value_key)
if param == null:
if params.has(value_key): param = params[value_key]
return param
func set_value(value, value_key):
var param = self.get(value_key)
if param == null:
params[value_key] = value
else:
self.set(value_key, value)
signal interfer_preparing(ecm: Interfer) ## Вызывается в момент запуска помехи.
signal interfer_run(ecm: Interfer) ## Вызывается в момент начала работы помехи.
signal interfer_error(ecm: Interfer, msg: String) ## Вызывается при ошибке.
signal interfer_update(ecm: Interfer) ## Вызывается при обновлении помехи.
signal interfer_resized(ecms: Dictionary) ## Вызывается при изменении количества сеансов помех.
func _ready():
var ecms: Dictionary = {} ## Словарь помех
for i in range(ID_INTERFERS_LEN): # Заполняет массив ID целей
id_array.append(i + 1)
var unit_key = repsettings.get_unit_key('уарэп-капсрпб-отп')
var unit = repnetwork.units[unit_key]
var timer_check_state: = Timer.new()
add_child(timer_check_state)
var resend_timeout = ProjectSettings.get_setting('application/interfer/resend_timeout', 1000.0)
timer_check_state.connect('timeout', Callable(self, 'on_timer_check_state').bind(unit, ecms, resend_timeout))
timer_check_state.start(resend_timeout / 1000.0)
unit.connect('data_received', Callable(self, 'on_data_capsrpb_received').bind(ecms))
signaller.connect('interfer_create', Callable(self, 'on_interfer_create').bind(unit, ecms))
signaller.connect('interfer_new', Callable(self, 'send_ecm').bind(unit))
signaller.connect('interfer_off_all', Callable(self, 'on_interfer_off_all').bind(unit, ecms))
# Проверяет состояние отправки пакета и если не доставлен, повторяет отправку
func on_timer_check_state(unit, ecms, resend_timeout):
var tick = Time.get_ticks_msec()
for ecm in ecms.values():
if ecm.svk == STATE_TSMT:
if (tick - ecm.tick_tx) >= resend_timeout:
send_ecm(ecm, unit)
ecm.tick_tx = tick
## Производит приём состояния помех. [param unit] -> [param ecms]
func on_data_capsrpb_received(unit: capsrpb.CapsRpb, ecms: Dictionary):
if not unit.json_dic.has('ts'): return
if not (unit.json_dic['ts'] is float): return
if unit.json_dic['ts'] != 5: return
if not unit.json_dic.has('msvk'): return
if not (unit.json_dic['msvk'] is Array): return
var msvk = unit.json_dic['msvk']
var sz: = ecms.size()
for dic_msvk in msvk:
if not (dic_msvk is Dictionary): continue
if dic_msvk.has('ispp'):
var ispp = int(dic_msvk['ispp'])
if ecms.has(ispp):
var ecm = ecms[ispp]
ecm.set_from_dic(dic_msvk)
call_deferred('emit_signal', 'interfer_update', ecm)
else:
var ecm = Interfer.new()
ecm.set_from_dic(dic_msvk)
ecm.krp = INTERFER_OFF
send_ecm(ecm, unit)
if sz != ecms.size():
call_deferred('emit_signal', 'interfer_resized', ecms)
## Вызыватся, когда пользователь выбрал помеху и выбраны цели
func on_interfer_create(btn, sel_threats, fs_arr, _unit, ecms):
for item in sel_threats:
var param: Dictionary = {}
var kni: float = item.aoa
var id = item.id
param['freq'] = item.freq
param['width'] = item.width
interfer_create(btn, ecms, param, kni, id, thrs, item, TYPE_TH[0])
for item in fs_arr:
var param: Dictionary = {}
var kni: float = item[0]
var id = item[3]
param['freq'] = item[1]
param['width'] = item[2]
interfer_create(btn, ecms, param, kni, id, fs, item, TYPE_TH[1])
func interfer_create(btn, ecms, param, kni, id, dict, item, type_th):
var ispp: int = 0
var interfer_name: String = btn.get_meta('rfi_name')
if interfer_name == 'k1':
param['dur'] = 1.0 # Необходимо добавить параметры управления
param['period'] = 1.0 # Необходимо добавить параметры управления
var key = dict.find_key(id)
if key != null:
ispp = key
else:
if interfer_name == 'off':
return
ispp = pop_id()
if ispp == 0: return
dict[ispp] = id
msg_ss += 1
var ecm: Interfer
if ecms.has(ispp):
ecm = ecms[ispp]
else:
ecm = Interfer.new()
ecm.ispp = ispp
ecms[ispp] = ecm
ecm.svk = STATE_TSMT # Подготовка к передаче сообщения
ecm.ss = msg_ss
ecm.kni = kni
ecm.krp = interfer_name
ecm.params = param
ecm.powp = 100.0 # Необходимо добавить управление мощностью
ecm.type_th = type_th
signaller.emit_signal('interfer_new', ecm)
if interfer_name == 'off':
signaller.emit_signal('interfer_off', ecm, ecms, thrs)
push_id(ecm.ispp)
thrs.erase(ispp)
ecm.tick_tx = Time.get_ticks_msec()
func send_ecm(ecm, unit):
var json_data = ecm.get_msg6_data()
unit.tx_stack.append(json_data)
func on_interfer_off_all(unit, ecms):
for ecm in ecms.values():
ecm.krp = INTERFER_OFF
push_id(ecm.ispp)
send_ecm(ecm, unit)
signaller.emit_signal('interfer_off',ecm, ecms, thrs)
func pop_id():
var id = 0
if len(id_array):
id = id_array[0]
id_array.remove_at(0)
return id
func push_id(id : int):
id_array.append(id)
id_array.sort()

View File

@@ -1,748 +0,0 @@
extends Node
## Утилиты для работы с меркаторовскими XYZ-плитками.
const R2D: = 180.0 / PI
const RE: = 6378137.0 ## Радиус Земли экваториальный в метрах
const RP: = 6356752.31424518 ## Радиус Земли полярный в метрах
const CE: = 2.0 * PI * RE ## Длина окружности Земли в метрах по экватору
const CP: = 2.0 * PI * RP ## Длина окружности Земли в метрах через полюсы
const EPSILON: = 1e-14
const LL_EPSILON: = 1e-11
const MAX_ZOOM: = 20
const MAX_NORTH = 85.051129
const MAX_SOUTH = -85.051129
## XYZ-плитка веб-меркатора [param x], [param y], [param z] индексы
# [b]x[/b] и [b]y[/b] плитки и уровень масштабирования [b]z[/b].
class Tile:
var x: int
var y: int
var z: int
var lng: float ## Координаты, которые плитка должна покрывать, долгота, градусы
var lat: float ## Широта, градусы
var is_root: bool ## Признак, что плитка корневая (покрывает координаты)
func as_array(): return [self.x, self.y, self.z]
func _to_string() -> String: return '[%d, %d, %d]' % [self.x, self.y, self.z]
func _init(x0: int = 0, y0: int = 0, z0: int = 0, is_root0: bool = true):
x = x0
y = y0
z = z0
is_root = is_root0
func is_valid() -> bool:
if (z >= 0) and (z < MAX_ZOOM):
var mz: int = 2 ** z
return (x >= 0) and (x < mz) \
and (y >= 0) and (y < mz)
return false
func _bound_index(index: int) -> int:
var mi: int = 2 ** z
if index < 0:
index *= -1
index %= mi
index = mi - index
else:
index %= mi
return index
func fix_xy():
x = _bound_index(x)
y = _bound_index(y)
## Пара долготы и широты.
class LngLat:
var lng: float = 0.0 ## Долгота в десятичных градусах восточной долготы.
var lat: float = 0.0 ## Широта в десятичных градусах северной широты.
## Возвращает в виде массива
func as_array() -> Array: return [self.lng, self.lat]
func _to_string() -> String: return '[%f, %f, %f]' % [self.x, self.y, self.z]
func _init(lng0, lat0):
self.lng = lng0
self.lat = lat0
## Географические границы. Хранит значения границ в десятичных градусах.
class LngLatBbox:
var west: float ## Запад
var south: float ## Юг
var east: float ## Восток
var north: float ## Север
## Возвращает в виде массива
func as_array() -> Array: return [self.west, self.south, self.east, self.north]
func _to_string() -> String: return '[%f, %f, %f, %f]' % [self.west, self.south, self.east, self.north]
func _init(w: float = 0.0, s: float = 0.0, e: float = 0.0, n: float = 0.0):
self.west = w
self.south = s
self.east = e
self.north = n
## Граничная область веб-меркатора. Хранит значения границ в метрах.
class Bbox:
var left: float
var bottom: float
var right: float
var top: float
func as_array() -> Array: return [self.left, self.bottom, self.right, self.top]
func _init(l: float = 0.0, b: float = 0.0, r: float = 0.0, t: float = 0.0):
self.left = l
self.bottom = b
self.right = r
self.top = t
func degrees(v: float) -> float: return (v * 180.0) / PI
func radians(v: float) -> float: return (v * PI) / 180.0
func rshift(val: int, n: int) -> int: return (val % 0x100000000) >> n
# Возвращает арксинус гиперболический от значения [param val]
func asinh(val: float): return log(val + sqrt(val * val + 1.0))
# Преобразует [param lon] в градусах восточной долготы, [param lat] в градусах
# северной долготы, [param alt] высоты в метрах в фиксированные координаты (x, y, z) Земли.
func lnglat_to_ecef(lng: float, lat: float, alt: float = 0.0) -> Array:
alt += (RE + RP) / 2.0
var latrad: float = radians(lat)
var lonrad: float = radians(lng)
var coslat: float = cos(latrad)
var sinlat: float = sin(latrad)
var coslon: float = cos(lonrad)
var sinlon: float = sin(lonrad)
var term1: float = (RE * coslat) / tools.hypot(RE * coslat, RP * sinlat)
var term2: float = alt * coslat + term1;
var x: float = coslon * term2
var y: float = sinlon * term2
var z: float = alt * sinlat + (RP * sinlat) / tools.hypot(RE * coslat, RP * sinlat)
return [x, y, z]
func find_up_left(tiles: Array):
var x = 0
var y = 0
for tile in tiles:
if x < tile.x: x = tile.x
if y < tile.y: y = tile.y
for tile in tiles:
if tile.x == x and tile.y == y:
return tile
return null
## Lon./lat. to tile numbers
func deg2num(lng: float, lat: float, zoom: int):
var n: = float(1 << zoom)
var xtile: = int((lng + 180.0) / 360.0 * n)
var ytile: = int((1.0 - asinh(tan(radians(lat))) / PI) / 2.0 * n)
return [xtile, ytile]
## Tile numbers to lon./lat.
func num2deg(x: int, y: int, zoom: int):
var n: = float(1 << zoom)
var lng: = float(x) / n * 360.0 - 180.0
var lat: = atan(sinh(PI * (1.0 - 2.0 * float(y) / n)))
return [lng, degrees(lat)]
## Расстояние по горизонтали в метрах, представленное одним пикселем
## [param lat] - Широта, градусы.[br]
## [param zoom] - Уровень детализации.[br]
## [param size] - Размер плитки в пикселях.[br]
func dist_per_pixel(lat: float, zoom: int, size: int = 256) -> float:
return (CE / float(size)) * cos(radians(lat)) / float(2 ** zoom)
## Расстояние по горизонтали в метрах, представленное одной плиткой
## [param lat] - Широта, градусы.[br]
## [param zoom] - Уровень детализации.[br]
func dist_per_tile(lat: float, zoom: int) -> float:
return CE * cos(radians(lat)) / float(2 ** zoom)
## Возвращает плитки и занимаемый ими размер области в пикселях:
## ширина, высота, левая граница, верхняя граница, плитки.[br]
## [param west] - Западная граница области.[br]
## [param east] - Восточная граница области.[br]
## [param south] - Южная граница области.[br]
## [param north] - Северная граница области.[br]
## [param size] - Размер плитки в пикселях.[br]
## [param zoom] - Детализация.[br]
func get_tiles(west: float, south: float, east: float, north: float, zoom: int, size: int) -> Array:
var tiles: = create_tiles(west, south, east, north, zoom)
var mm = []
for t in tiles: mm.append(t.x)
var min_x = mm.min()
var max_x = mm.max()
mm.clear()
for t in tiles: mm.append(t.y)
var min_y = mm.min()
var max_y = mm.max()
var w = size * (max_x - min_x + 1)
var h = size * (max_y - min_y + 1)
return [w, h, min_x, min_y, tiles, max_x - min_x + 1]
## Возвращает размер области в метрах.[br]
## [param west] - Западная граница области.[br]
## [param param] - Восточная граница области.[br]
## [param south] - Южная граница области.[br]
## [param north] - Северная граница области.[br]
func get_size_from_degrees(west: float, south: float, east: float, north: float) -> Array[float]:
var xy0: = xy(west, north, true)
var xy1: = xy(east, south, true)
return [xy0[0] - xy1[0], xy0[1] - xy1[1]]
## Возвращает границы области в градусах.[br]
## [param m] - Размер карты в метрах.[br]
## [param lng] - Долгота центра области в градусах (-180...180).[br]
## [param lat] - Широта центра области в градусах (-90...90).[br]
func get_degrees_bounds(m: float, lng: float, lat: float) -> Array[float]:
m /= 2.0
var x_y = xy(lng, lat, true)
return [x_y[0] - m, x_y[1] - m, x_y[0] + m, x_y[1] + m]
## Возвращает границы плиток в градусах
func get_tiles_bounds(tiles: Array[Tile]) -> Array:
var mm = []
for t in tiles: mm.append(bounds(t).west)
var mmw = mm.min(); mm.clear()
for t in tiles: mm.append(bounds(t).south)
var mms = mm.min(); mm.clear()
for t in tiles: mm.append(bounds(t).east)
var mme = mm.max(); mm.clear()
for t in tiles: mm.append(bounds(t).north)
var mmn = mm.max(); mm.clear()
return [mmw, mms, mme, mmn]
## Возвращает левую верхнюю долготу и широту плитки.[br]
## [param tile] Плитка.[br]
## [codeblock]
## >>> ul(Tile(x=0, y=0, z=1))
## LngLat(lng=-180.0, lat=85.0511287798066)
## >>> mercantile.ul(1, 1, 1)
## LngLat(lng=0.0, lat=0.0)
## [/codeblock]
func ul(tile: Tile, zoom: int) -> LngLat:
var Z2 = pow(2, zoom)
var lon_deg = tile.x / Z2 * 360.0 - 180.0
var lat_rad = atan(sinh(PI * (1 - 2 * tile.y / Z2)))
var lat_deg = degrees(lat_rad)
return LngLat.new(lon_deg, lat_deg)
## Возвращает левую верхнюю долготу и широту плитки.[br]
## [param tile] - Плитка.[br]
## [codeblock]
## >>> ul(Tile(x=0, y=0, z=1))
## LngLat(lng=-180.0, lat=85.0511287798066)
## >>> mercantile.ul(1, 1, 1)
## LngLat(lng=0.0, lat=0.0)
## [/codeblock]
func ul2(tile: Tile) -> Array[float]:
var z2 = pow(2, tile.z)
var lon_deg = tile.x / z2 * 360.0 - 180.0
var lat_rad = atan(sinh(PI * (1 - 2 * tile.y / z2)))
var lat_deg = degrees(lat_rad)
return [lon_deg, lat_deg]
## Возвращает граничную область плитки.[br]
## [param tile] - Плитка.[br]
func bounds(tile: Tile) -> LngLatBbox:
var z2: = pow(2, tile.z)
var ul_lon_deg = float(tile.x) / z2 * 360.0 - 180.0
var lr_lon_deg = (float(tile.x) + 1.0) / z2 * 360.0 - 180.0
var ul_lat_rad = atan(sinh(PI * (1.0 - 2.0 * float(tile.y) / z2)))
var ul_lat_deg = degrees(ul_lat_rad)
var lr_lat_rad = atan(sinh(PI * (1.0 - 2.0 * (float(tile.y) + 1.0) / z2)))
var lr_lat_deg = degrees(lr_lat_rad)
return LngLatBbox.new(ul_lon_deg, lr_lat_deg, lr_lon_deg, ul_lat_deg)
func truncate_lnglat(lng: float, lat: float) -> Array:
if lng > 180.0: lng = 180.0
elif lng < -180.0: lng = -180.0
if lat > 90.0: lat = 90.0
elif lat < -90.0: lat = -90.0
return [lng, lat]
## Преобразование долготы и широты в веб-меркатор x, y в метрах.[br]
## [param lng] - Долгота в десятичных градусах.[br]
## [param lat] - Широта в десятичных градусах.[br]
## [param truncate] - Нужно ли усекать входные данные до пределов веб-меркатора.[br]
## ([param lat] >= 90) [b]inf[/b] на северном и [b]-inf[/b] на южном полюсе ([param lat] <= -90).
func xy(lng: float, lat: float, truncate: bool = false) -> Array[float]:
var ll = truncate_lnglat(lng, lat) if truncate else [lng, lat]
var x: float = RE * radians(lng)
var y: float = 0.0
if ll[1] <= -90.0: y = float('-inf')
elif ll[1] >= 90.0: y = float('inf')
else: y = RE * log(tan((PI * 0.25) + (0.5 * radians(lat))))
return [x, y]
## Преобразование координат веб-меркатора x, y в долготу и широту.[br]
## [param x], [param y] - Координаты веб-меркатора в метрах.[br]
## [param truncate] - Нужно ли усекать или обрезать входные данные до границ веб-меркатора.
func lnglat(x: float, y: float, truncate: bool = false) -> LngLat:
var lng: = x * R2D / RE
var lat: = ((PI * 0.5) - 2.0 * atan(exp(-y / RE))) * R2D
var ll: = truncate_lnglat(lng, lat) if truncate else [lng, lat]
return LngLat.new(ll[0], ll[1])
## Соседи плитки. [method neighbors] возвращает до восьми
## соседних плиток, при этом плитки будут опущены, если они не являются действительными, например.
## Tile(-1, -1, z). [param tile]: Плитка или последовательность int
## Может быть либо экземпляр Tile, либо 3 ints, X, Y, Z.
## [return] Массив
## [codeblock]
## >>> neighbors(Tile(486, 332, 10))
## [Tile(x=485, y=331, z=10), Tile(x=485, y=332, z=10), Tile(x=485, y=333, z=10), Tile(x=486, y=331, z=10), Tile(x=486, y=333, z=10), Tile(x=487, y=331, z=10), Tile(x=487, y=332, z=10), Tile(x=487, y=333, z=10)]
## [/codeblock]
func neighbors(tile: Tile) -> Array:
var tiles: = []
for i in [-1, 0, 1]:
for j in [-1, 0, 1]:
if i == 0 and j == 0:
continue
var t = Tile.new(tile.x + i, tile.y + j, tile.z)
t.fix_xy()
tiles.append(t)
return tiles
## Возвращает все плитки в радиусе [param r] вокруг заданной плитки [param tile]
func get_allaround_tiles(tile: Tile, mr: int = 1) -> Array:
var xys = {}
var mi: int = 2 ** tile.z
for r in mr:
for a in range(0, 360, 180 / mi):
var ra = radians(a)
var ix = roundi(r * cos(ra)) + tile.x
var iy = roundi(r * sin(ra)) + tile.y
var t = Tile.new(ix, iy, tile.z)
t.fix_xy()
xys[[t.x, t.y]] = false
var tiles = []
for txy in xys.keys():
tiles.append(Tile.new(txy[0], txy[1], tile.z))
return tiles
## Возвращает все плитки по периметру заданной плитки
func get_perimetr_tiles(tile: Tile, mr: int = 1) -> Array:
var tiles = []
for i in range(-mr, mr + 1):
for j in range(-mr, mr + 1):
if i == 0 and j == 0: continue
var t = Tile.new(i + tile.x, j + tile.y, tile.z)
t.fix_xy()
tiles.append(t)
return tiles
## Получение меркаторской граничной рамки веб-плитки.[br]
## [param tile] - Плитка.[br]
## [i]Эпсилон вычитается из правой границы и прибавляется к нижней[/i]
func xy_bounds(tile: Tile, zoom: int) -> Bbox:
var tile_size = CE / pow(2, zoom)
var left = tile.x * tile_size - CE / 2
var right = left + tile_size
var top = CE / 2 - tile.y * tile_size
var bottom = top - tile_size
return Bbox.new(left, bottom, right, top)
func _xy(lng: float, lat: float, truncate=false) -> Array:
var ll: = truncate_lnglat(lng, lat) if truncate else [lng, lat]
var x: float = ll[0] / 360.0 + 0.5
var sinlat: = sin(radians(ll[1]))
var y: = 0.5 - 0.25 * log((1.0 + sinlat) / (1.0 - sinlat)) / PI
return [x, y]
## Получение плитки, содержащей долготу и широту
## [param lng], [param lat] - Пара долготы и широты в десятичных градусах.[br]
## [param zoom] - Уровень масштабирования веб-меркатора.[br]
## [param truncate] - Нужно ли усекать вводимые данные до границ веб-меркатора.[br]
func create_tile(lng: float, lat: float, zoom: int, truncate: bool=false) -> Tile:
var xy0: = _xy(lng, lat, truncate)
var z2 = pow(2, zoom)
# Чтобы устранить потерю точности при переходе от одной плитки к другой
# и lng/lat, точки в пределах EPSILON от правой стороны плитки
# считаются в следующей за ней плитке.
var tile: = Tile.new()
if xy0[0] <= 0: tile.x = 0
elif xy0[0] >= 1: tile.x = int(z2 - 1)
else: tile.x = int(floor((xy0[0] + EPSILON) * z2))
if xy0[1] <= 0: tile.y = 0
elif xy0[1] >= 1: tile.y = int(z2 - 1)
else: tile.y = int(floor((xy0[1] + EPSILON) * z2))
tile.z = zoom
tile.lng = lng
tile.lat = lat
return tile
## Получение четверного ключа плитки.[br]
## [param tile] - Плитка
func quadkey(tile: Tile, zoom: int) -> String:
var rs: = ''
for z in range(zoom, 0, -1):
var digit = 0
var mask = 1 << (z - 1)
if tile.x & mask:
digit += 1
if tile.y & mask:
digit += 2
rs += str(digit)
return rs
## Получение плитки, соответствующей четверному ключу.[br]
## [param qk] - Строка четверного ключа.
func quadkey_to_tile(qk: Array) -> Tile:
var tile: = Tile.new()
if len(qk) == 0: return tile
var i = 0
qk.reverse()
for digit in qk:
var mask = 1 << i
i += 1
if digit == '1':
tile.x |= mask
elif digit == '2':
tile.y |= mask
elif digit == '3':
tile.x |= mask
tile.y |= mask
elif digit == '0':
continue
else:
push_error('Не ожидаемый четырёхзначный знак: \"%s\"' % digit)
tile.z = i
return tile
## Получение плиток, перекрываемых географической рамкой.[br]
## [param west], [param south], [param east], [param north] - Значения границ в десятичных градусах.[br]
## [param zooms] - Один или несколько уровней масштабирования.[br]
## [param truncate] [bool] - Нужно ли усекать вводимые данные до границ веб-меркатора.[br]
## [i]В параметрах south и east используется небольшой эпсилон, чтобы
## функция выдавала ровно одну плитку при задании границ этой же плитки.[/i]
func create_tiles(west: float, south: float, east: float, north: float, zooms) -> Array[Tile]:
var bboxes = [[west, south, east, north]] if west <= east else [[-180.0, south, east, north], [west, south, 180.0, north]]
var tiles = []
if not is_instance_of(zooms, TYPE_ARRAY):
zooms = [zooms]
for box in bboxes:
var w = box[0]
var s = box[1]
var e = box[2]
var n = box[3]
# Зажим граничных значений.
w = max(-180.0, w)
s = max(MAX_SOUTH, s)
e = min(180.0, e)
n = min(85.051129, n)
for z in zooms:
var ul_tile = create_tile(w, n, z)
var lr_tile = create_tile(e - LL_EPSILON, s + LL_EPSILON, z)
for i in range(ul_tile.x, lr_tile.x + 1):
for j in range(ul_tile.y, lr_tile.y + 1):
tiles.append(Tile.new(i, j, z))
return tiles
## Получение родителя плитки. Родительская плитка - это плитка на один уровень масштабирования ниже, которая
## содержит данную "дочернюю" плитку.[br]
## [param tile] - Плитка.[br]
## [param zoom] - Определяет уровень *масштабирования* возвращаемой родительской плитки.
## По умолчанию это значение на один ниже, чем у плитки (непосредственного родителя).[br]
## [codeblock]
## >>> parent(Tile(0, 0, 2))
## Tile(x=0, y=0, z=1)
## >>> parent(Tile(0, 0, 2), zoom=0)
## Tile(x=0, y=0, z=0)
## [/codeblock]
func parent(tile: Tile, zoom=null) -> Tile:
if tile.z == 0: return null
assert((zoom != null) and (tile.z > zoom or zoom == int(zoom)), 'Масштаб (zoom) должен быть целым числом и меньше масштаба входной плитки (tile).')
assert(tile.x == int(tile.x) and tile.y == int(tile.y) and tile.z == int(tile.z), 'Родитель нецелочисленной плитки (tile) является неуточнённым')
var target_zoom = tile.z - 1 if zoom == null else zoom
# Алгоритм, в значительной степени вдохновленный https://github.com/mapbox/tilebelt
var return_tile = Tile.new(tile.x, tile.y, tile.z)
while return_tile.z > target_zoom:
tile.x = return_tile.x
tile.y = return_tile.y
tile.z = return_tile.z
if tile.x % 2 == 0 and tile.y % 2 == 0:
return_tile = Tile.new(tile.x / 2, tile.y / 2, tile.z - 1)
elif tile.x % 2 == 0:
return_tile = Tile.new(tile.x / 2, (tile.y - 1) / 2, tile.z - 1)
elif not tile.x % 2 == 0 and tile.y % 2 == 0:
return_tile = Tile.new((tile.x - 1) / 2, tile.y / 2, tile.z - 1)
else:
return_tile = Tile.new((tile.x - 1) / 2, (tile.y - 1) / 2, tile.z - 1)
return return_tile
## Получение дочерних элементов плитки. Дочерние элементы упорядочены:
## верхний-левый, верхний-правый, нижний-правый, нижний-левый. Возвращает все
## дочерние элементы с масштабом *zoom*, в порядке убывания глубины в порядке
## вращения по часовой стрелке. Если не указано, возвращаются ближайшие
## (т.е. zoom + 1) дочерние элементы плитки.[br]
## [param tile] - Плитка.[br]
## [param zoom] - Уровень детализации.
## [codeblock]
## >>> children(Tile(0, 0, 0))
## [Tile(x=0, y=0, z=1), Tile(x=0, y=1, z=1), Tile(x=1, y=0, z=1), Tile(x=1, y=1, z=1)]
## >>> children(Tile(0, 0, 0), zoom=2)
## [Tile(x=0, y=0, z=2), Tile(x=0, y=1, z=2), Tile(x=0, y=2, z=2), Tile(x=0, y=3, z=2), ...]
## [/codeblock]
func children(tile: Tile, zoom: int) -> Array:
var target_zoom = zoom if zoom != null else tile.z + 1
var tiles: Array[Tile] = [tile]
while tiles[0].z >= target_zoom:
tile = tiles.pop_at(0)
tiles += [
Tile.new(tile.x * 2, tile.y * 2, tile.z + 1),
Tile.new(tile.x * 2 + 1, tile.y * 2, tile.z + 1),
Tile.new(tile.x * 2 + 1, tile.y * 2 + 1, tile.z + 1),
Tile.new(tile.x * 2, tile.y * 2 + 1, tile.z + 1) ]
return tiles
## Проверяется, есть ли в [param merge_set] 4 плитки, которые могут быть объединены.
## Если есть, то происходит их слияние. Возвращает список плиток, а также булево
## число, указывающее, были ли они объединены. При многократном применяя
## [method merge], можно упростить набор плиток.
func merge(merge_set) -> Array:
var upwards_merge: = {}
for tile in merge_set.keys():
var tile_parent = parent(tile)
if not upwards_merge.has(tile_parent):
upwards_merge[tile_parent] = {}
upwards_merge[tile_parent].update({tile: false})
var current_tileset: = []
var changed: = false
for supertile in upwards_merge.keys():
var tile_children = upwards_merge[supertile]
if len(tile_children) == 4:
current_tileset += [supertile]
changed = true
else:
current_tileset += [tile_children]
return [current_tileset, changed]
## Максимально уменьшает размер набора плиток путем слияния листьев с родителями.
## [param tiles] - Последовательность плиток для слияния.
func simplify(tiles: Array) -> Array:
# Проверьте, существует ли уже плитка и ее родитель.
# Убедитесь, что плитки отсортированы по масштабу, поэтому родительские
# плитки встречаются первыми. Если да, отбросьте дочернюю плитку
# (она покрыта родительской).
var root_set = {}
tiles.sort_custom(func(a, b): return a[2] < b[2])
for tile in tiles:
var is_new_tile: bool = true
var supertiles = []
for i in tile.z:
supertiles.append(parent(tile, i))
for supertile in supertiles:
if supertile in root_set:
is_new_tile = false
continue
if is_new_tile:
root_set.update({tile: false})
# Повторять слияние до тех пор, пока дальнейшее упрощение перестанет быть невозможным.
var is_merging: bool = true
while is_merging:
var root_set_is_merging = merge(root_set)
root_set = root_set_is_merging[0]
is_merging = root_set_is_merging[1]
return root_set
## Получение наименьшей плитки, содержащей географическую границу.
## Если bbox охватывает линии lng 0 или lat 0, ограничивающая плитка будет иметь вид
## Tile(x=0, y=0, z=0).
## [param bbox] последовательность [float] значений границ запада, юга, востока,
## севера в десятичных градусах.
func bounding_tile(bbox: Bbox, truncate: bool = false) -> Tile:
if truncate:
var w_s = truncate_lnglat(bbox.left, bbox.bottom)
bbox.left = w_s[0]
bbox.bottom = w_s[1]
var e_n = truncate_lnglat(bbox.right, bbox.north)
bbox.right = e_n[0]
bbox.top = e_n[1]
bbox.right -= LL_EPSILON
bbox.bottom += LL_EPSILON
var tmin = create_tile(bbox.left, bbox.top, 32)
var tmax = create_tile(bbox.right, bbox.bottom, 32)
assert(tmin.is_valid())
assert(tmax.is_valid())
var cell = [tmin.x, tmin.y, tmax.x, tmax.y]
var z = _get_bbox_zoom(cell)
if z == 0:
return Tile.new()
var x = rshift(cell[0], 32 - z)
var y = rshift(cell[1], 32 - z)
return Tile.new(x, y, z)
func _get_bbox_zoom(bbox: Array):
for z in MAX_ZOOM:
var mask: int = 1 << (32 - (z + 1))
if (bbox[0] & mask) != (bbox[2] & mask) or (bbox[1] & mask) != (bbox[3] & mask):
return z
return MAX_ZOOM
## Получение GeoJSON-функции, соответствующей плитке.[br]
## [param tile] - Плитка.[br]
## [param fid] - Идентификатор объекта.[br]
## [param props] - Необязательные дополнительные свойства признака.[br]
## [param projected] - Можно создать нестандартный веб-меркатор GeoJSON передав 'mercator'[br]
## [param buffer] - Необязательное буферное расстояние для полигона GeoJSON.[br]
## [param precision] - Координаты GeoJSON будут усечены до данного количества знаков после запятой.[br]
func feature(tile: Tile, fid:String = 'null', props=null, projected='geographic', buffer=null) -> Dictionary:
var west_south_east_north = bounds(tile).as_array()
var west = west_south_east_north[0]
var south = west_south_east_north[1]
var east = west_south_east_north[2]
var north = west_south_east_north[3]
if projected == 'mercator':
var west_south = xy(west, south, false)
west = west_south[0]
south = west_south[1]
var east_north = xy(east, north, false)
east = east_north[0]
north = east_north[1]
if buffer:
west -= buffer
south -= buffer
east += buffer
north += buffer
west = round(west)
south = round(south)
east = round(east)
north = round(north)
var bbox: = [min(west, east), min(south, north), max(west, east), max(south, north)]
var geom: = {'type': 'Polygon', 'coordinates': [[[west, south], [west, north], [east, north], [east, south], [west, south]]] }
var xyz: = str(tile)
var feat: = {
'type': 'Feature',
'bbox': bbox,
'id': xyz,
'geometry': geom,
'properties': {'title': 'XYZ tile %s' % xyz} }
if props:
feat['properties'].update(props)
if fid != 'null':
feat['id'] = fid
return feat
func isinstance(obj, types: Array) -> bool:
for type in types:
if is_instance_of(obj, type):
return true
return false
## Все кортежи координат из геометрии, объекта или коллекции.[br]
## [param lng] - Долгота.[br]
## [param lat] - широта.[br]
func _coords(obj) -> Array:
var coordinates = []
if is_instance_of(obj, TYPE_ARRAY):
coordinates = obj
elif 'features' in obj:
for feat in obj['features']:
coordinates = feat['geometry']['coordinates']
elif 'geometry' in obj:
coordinates = obj['geometry']['coordinates']
else:
coordinates = obj.get('coordinates', obj)
var v: = []
for e in coordinates:
if isinstance(e, [TYPE_FLOAT, TYPE_INT]):
v.append(coordinates)
break
else:
for f in _coords(e):
v.append([f[0], f[1]])
return v
func _func1(bbox: Array, coords: Array) -> Array[float]:
var w = bbox[0]
var s = bbox[1]
var e = bbox[2]
var n = bbox[3]
var lng = coords[0]
var lat = coords[1]
return [min(w, lng), min(s, lat), max(e, lng), max(n, lat)]
## Возвращает граничную область объекта GeoJSON.[br]
## [param obj] - Отображение, геометрия GeoJSON, объект или коллекция объектов.[br]
func geojson_bounds(obj) -> LngLatBbox:
var coords: Array = _coords(obj)
var wsen = coords.reduce(_func1, [180.0, 90.0, -180.0, -90.0])
return LngLatBbox.new(wsen[0], wsen[1], wsen[2], wsen[3])

View File

@@ -1,131 +0,0 @@
extends Node
const ONLINE_TIMEOUT = 5.0 ## Время ожидания пакета от ячейки, секунды
class ModbusClient:
## Состояние автомата выполнения команды
enum CmdState {
DONE, ## Команда выполнена
FAIL, ## Команда не выполнена
SEND, ## Буфер готов для отправки
WAIT ## Ожидание выполнения
}
var name: String
var unit: = 1 ## Адрес устройства Modbus
var cmd_state: = CmdState.DONE ## Код текущей команды
var tx_data: PackedByteArray ## Данные для отправки
var online: = false ## Флаг. Если установлен, устройство на связи. Активный уровень - [b]true[/b].
var tick_rx: = 0 ## Время приёма крайнего сообщения
var tick_tx: = 0 ## Время отправки крайнего сообщения
static var json_conv: = JSON.new()
signal command_done() ## Вызывается при успешном выполнении команды
signal command_fail() ## Вызывается при возникновении ошибки во время выполнения команды
signal line_changed() ## Вызывается при смене состояния связи с ячейкой
signal data_received() ## Вызывается при приёме сообщения, до его разбора
signal parse_failed(msg: String) ## Вызывается при возникновении ошибки во время разбора пакета. Передаёт сообщение об ошибке
signal holding_received(addr: int, data: Array) ## Вызывается при получении сообщения c данными из регистров хранения
signal input_received(addr: int, data: Array) ## Вызывается при получении сообщения c данными из регистров входов
signal info_received(msg: String) ## Вызывается при получении информационного сообщения
signal except_received(msg: String) ## Вызывается при получении сообщения об исключительной ситуации
func _init(nm: String, u: int = 1):
unit = u
name = nm
## Изменяет состояние. Вызывать периодически[br]
## [param tick] - Текущщее время в мс[br]
func process(tick: int):
if online and ((tick - tick_rx) > ONLINE_TIMEOUT):
online = false
emit_signal('line_changed')
if (cmd_state == CmdState.WAIT) or (cmd_state == CmdState.SEND):
cmd_state = CmdState.FAIL
emit_signal('command_fail')
var rc = Error.ERR_BUSY
if cmd_state == CmdState.SEND:
rc = Error.OK
tick_tx = tick
cmd_state = CmdState.WAIT
return rc
## Производит разбор принятых данных. Вызывать при приёме данных на сокете[br]
## [param data] - Данные принятые на сокете[br]
## [param tick] - Текущщее время в мс[br]
func parse(data: PackedByteArray, tick: int) -> bool:
tick_rx = tick
if not online:
online = true
emit_signal('line_changed')
var utf8_data = data.get_string_from_utf8()
if Error.OK == json_conv.parse(utf8_data):
emit_signal('data_received')
var message: Dictionary = json_conv.get_data()
if message.has('code') and message.has('data'):
var rc: = true
var message_code = message['code']
var message_data = message['data']
match message_code:
'read_input':
var addr = message_data[0]
var regs = message_data.slice(1)
emit_signal('input_received', addr, regs)
'read_holding':
var addr = message_data[0]
var regs = message_data.slice(1)
emit_signal('holding_received', addr, regs)
'info':
var msg = message['data']
emit_signal('info_received', msg)
'exception':
var msg = message['data']
emit_signal('except_received', msg)
rc = false
_:
var msg: String = 'код команды \"%s\" - не распознан' % message_code
emit_signal('parse_failed', msg)
rc = false
if (cmd_state == CmdState.WAIT) and rc:
cmd_state = CmdState.DONE
emit_signal('command_done')
elif (cmd_state == CmdState.WAIT) and (not rc):
cmd_state = CmdState.FAIL
emit_signal('command_fail')
else:
var message = json_conv.get_error_message()
emit_signal('parse_failed', message)
return true
## Отправляет команду "записать в регистры".[br]
## [param addr] - Начальный адрес регистров.[br]
## [param count] - Данные для записи в регистры от начального адреса.[br]
func write(addr: int, data: Array) -> bool:
if cmd_state != CmdState.DONE: return false
var json_dict = {'code': 'write', 'data': [addr] + data}
tx_data = json_conv.stringify(json_dict).to_utf8_buffer()
cmd_state = CmdState.SEND
return true
## Отправляет команду "читать регистры хранения".[br]
## [param addr] - Начальный адрес регистров.[br]
## [param count] - Количество регистров от начального адреса.[br]
func read_holdind(addr: int, count: int = 1) -> bool:
if cmd_state != CmdState.DONE: return false
var json_dict = {'code': 'read_holding', 'data': [addr, count]}
tx_data = json_conv.stringify(json_dict).to_utf8_buffer()
cmd_state = CmdState.SEND
return true
## Отправляет команду "читать регистры входов".[br]
## [param addr] - Начальный адрес регистров.[br]
## [param count] - Количество регистров от начального адреса.[br]
func read_input(addr: int, count: int = 1) -> bool:
if cmd_state != CmdState.DONE: return false
var json_dict = {'code': 'read_input', 'data': [addr, count]}
tx_data = json_conv.stringify(json_dict).to_utf8_buffer()
cmd_state = CmdState.SEND
return true

View File

@@ -1,166 +0,0 @@
extends Node
const PROTOCOL_TYPES: = {'yau07tx': yau07.YaU07, 'jsontx': capsrpb.CapsRpb, 'udp-json-mbcs': modbus.ModbusClient}
const TCP_PROTO: = {'5p28': tcp5p28.TCP5P28}
class Socket extends PacketPeerUDP:
func send_to(addr, data):
self.set_dest_address(addr[0], addr[1])
self.put_packet(data)
class SocketTCP extends TCPServer:
const RX_TIMEOUT: int = 1000 # Время сброса счётчика принятия части пакета
var peerstream: = PacketPeerStream.new()
var unit_key: Array # Клюс юнита
var head_len: int = 8 # Длинна заголовка команды
var len_place: int = 4 # Номер начального байта с длинной блока данных
var type_len: int = 4 # Количество байт в размере длинны
var rx_tick: int # Время приёма последнего пакета
var rx_all: bool = true # Флаг, что пакет принят полностью
var rx_len: int # Количество принятых байт
var rx_data: PackedByteArray # Принятые данные
var data_len: int # Размер ожидаемых данных
func send_to(data):
var peer = peerstream.get_stream_peer()
peer.put_data(data)
var poll_sockets: Array[Socket]
var tcp_sockets: Array
var units: Dictionary
var units_tcp: Dictionary
var sock_unicast: Socket
var sock_capsrpb: Socket
var logger_page: Node
func create_socket(nm) -> Socket:
var addr = repsettings.UnitProfiles[nm][1]
var port = repsettings.UnitProfiles[nm][2]
var broad = repsettings.UnitProfiles[nm][3]
var bind = repsettings.UnitProfiles[nm][4]
var sock: = Socket.new()
sock.set_broadcast_enabled(broad)
var rc = Error.OK if not bind else sock.bind(port, addr)
var errlevel = log.INFO if rc == Error.OK else log.ERROR
log.message(errlevel, '%s: сокет:%s:%d привязанный:%s %s' % [nm, addr, port, ['нет', 'да'][int(bind)], error_string(rc)])
return sock
func create_tcpsocket(nm) -> SocketTCP:
var addr = repsettings.UnitProfiles[nm][1]
var port = repsettings.UnitProfiles[nm][2]
var sock = SocketTCP.new()
sock.unit_key = repsettings.get_unit_key(nm)
sock.listen(port, addr)
log.message(log.INFO, '%s: сокет:%s:%d привязанный:' % [nm, addr, port])
return sock
func _ready() -> void:
log.info('Модуль работы с сетью готов')
poll_sockets.append(create_socket('уарэп-яу07-частный'))
poll_sockets.append(create_socket('уарэп-яу07-общий'))
poll_sockets.append(create_socket('уарэп-капсрпб-пол'))
sock_unicast = poll_sockets[0]
sock_capsrpb = poll_sockets[2]
for unit_name in repsettings.UnitProfiles:
var setting = repsettings.UnitProfiles[unit_name]
var proto = setting[0]
if proto in PROTOCOL_TYPES:
var unit = PROTOCOL_TYPES[proto].new(unit_name)
var unit_key = repsettings.get_unit_key(unit_name)
units[unit_key] = unit
if logger_page:
unit.connect('line_changed', Callable(logger_page, 'on_line_changed').bind(unit_key))
unit.connect('command_fail', Callable(logger_page, 'on_command_fail').bind(unit_key))
if proto in TCP_PROTO:
var unit = TCP_PROTO[proto].new(unit_name)
var unit_key = repsettings.get_unit_key(unit_name)
units_tcp[unit_key] = unit
if logger_page:
unit.connect('line_changed', Callable(logger_page, 'on_line_changed').bind(unit_key))
unit.connect('command_fail', Callable(logger_page, 'on_command_fail').bind(unit_key))
tcp_sockets.append([create_tcpsocket('уарэп-5п28'), units_tcp[repsettings.get_unit_key('уарэп-5п28')]])
for key in units: log.info('%s %s:%d' % [units[key].name, key[0], key[1]])
for key in units_tcp: log.info('%s %s:%d' % [units_tcp[key].name, key[0], key[1]])
func poll_receive(sock: Socket, tick: int) -> bool:
while sock.get_available_packet_count() > 0:
var data: = sock.get_packet()
var addr: = sock.get_packet_ip()
var port: = sock.get_packet_port()
var addr_port: = [addr, port]
if addr_port in units: units[addr_port].parse(data, tick)
return false
func poll_receive_tcp(sock: SocketTCP, tick: int) -> bool:
if sock.is_connection_available(): # check if someone's trying to connect
var client = sock.take_connection() # accept connection
sock.peerstream.set_stream_peer(client) # bind peerstream to new client
print_debug('client_is_connected')
var peer = sock.peerstream.get_stream_peer()
if peer:
var peer_len_data = peer.get_available_bytes()
if peer_len_data > 0:
get_client_data(sock, peer_len_data, peer, tick)
if sock.unit_key in units_tcp and sock.rx_all: units_tcp[sock.unit_key].parse(sock.rx_data, tick)
return false
func get_client_data(sock: SocketTCP, peer_len_data, peer, tick):
if tick - sock.rx_tick > sock.RX_TIMEOUT:
sock.rx_all = true
sock.rx_tick = tick
if sock.rx_all:
if peer_len_data >= sock.head_len:
sock.rx_all = false
sock.rx_len = sock.head_len
var head = peer.get_data(sock.head_len)
sock.data_len = head[1].decode_u32(sock.len_place)
var len_data_in_buf = peer.get_available_bytes()
if len_data_in_buf > 0:
sock.rx_data = head[1]
var len_rx = len_data_in_buf
if len_data_in_buf >= sock.data_len:
len_rx = sock.data_len
get_tcp_data(sock, peer, len_rx)
else:
var len_rx = (sock.head_len + sock.data_len) - sock.rx_len
if peer_len_data < len_rx:
len_rx = peer_len_data
get_tcp_data(sock, peer, len_rx)
func get_tcp_data(sock: SocketTCP, peer, len_rx):
var temp_data = peer.get_data(len_rx)[1]
sock.rx_data.append_array(temp_data)
sock.rx_len += len_rx
if sock.rx_len >= sock.head_len + sock.data_len:
sock.rx_all = true
func _process(_delta: float) -> void:
var tick = Time.get_ticks_msec()
for addr in units:
var unit = units[addr]
match unit.process(tick):
Error.OK: sock_unicast.send_to(addr, unit.tx_data)
Error.FAILED: emit_signal('socket_error', 'ошибка: %s %s:%d' % [unit, addr[0], addr[1]])
for sock_unit in tcp_sockets:
var sock = sock_unit[0]
var unit = sock_unit[1]
match unit.process(tick):
Error.OK: sock.send_to(unit.tx_data)
Error.FAILED: emit_signal('socket_error', 'ошибка: %s' %unit)
poll_receive_tcp(sock, tick)
poll_sockets.any(func(sock): poll_receive(sock, tick))

View File

@@ -1,50 +0,0 @@
extends Node
## Параметры
@export var UnitProfiles: = { \
# Название | Протокол | Адрес | Порт | Широковещательное | Привязывать к адресу | Комментарий
'уарэп-яу07-частный': ['yau07rx', '10.1.1.2', 50002, false, true, 'IP-адрес на котором принимают частные сообщения от ЯУ-07'],
'уарэп-яу07-общий': ['yau07rx', '10.1.1.255', 50000, true, true, 'IP-адрес на котором принимают широковещательные сообщения от ЯУ-07'],
'уарэп-капсрпб-пол': ['jsonrx', '127.0.0.1', 42023, false, true, 'IP-адрес на котором принимают сообщения от КАПС РПБ'],
'уарэп-капсрпб-отп': ['jsontx', '127.0.0.1', 42022, false, false, 'IP-адрес на который отправляют сообщения для КАПС РПБ'],
'уарэп-эмс': ['yau07tx', '10.1.1.27', 50027, false, false, 'IP-адрес ЯУ-07 прибора УФ'],
'уарэп-яу07-2в': ['yau07tx', '10.1.1.34', 50034, false, false, 'IP-адрес ЯУ-07 прибора ПРД-В'],
'уарэп-яу07-2н': ['yau07tx', '10.1.1.33', 50033, false, false, 'IP-адрес ЯУ-07 прибора ПРД-Н'],
'уарэп-яу07-2к': ['yau07tx', '10.1.1.35', 50035, false, false, 'IP-адрес ЯУ-07 прибора ПРД-К'],
'уарэп-яу07-4в': ['yau07tx', '10.1.1.66', 50066, false, false, 'IP-адрес ЯУ-07 прибора ПРД-В'],
'уарэп-яу07-4н': ['yau07tx', '10.1.1.65', 50065, false, false, 'IP-адрес ЯУ-07 прибора ПРД-Н'],
'уарэп-яу07-4к': ['yau07tx', '10.1.1.67', 50067, false, false, 'IP-адрес ЯУ-07 прибора ПРД-К'],
'уарэп-яу07-1в': ['yau07tx', '10.1.1.18', 50018, false, false, 'IP-адрес ЯУ-07 прибора ПРД-В'],
'уарэп-яу07-1н': ['yau07tx', '10.1.1.17', 50017, false, false, 'IP-адрес ЯУ-07 прибора ПРД-Н'],
'уарэп-яу07-1к': ['yau07tx', '10.1.1.19', 50019, false, false, 'IP-адрес ЯУ-07 прибора ПРД-К'],
'уарэп-яу07-3в': ['yau07tx', '10.1.1.50', 50050, false, false, 'IP-адрес ЯУ-07 прибора ПРД-В'],
'уарэп-яу07-3н': ['yau07tx', '10.1.1.49', 50049, false, false, 'IP-адрес ЯУ-07 прибора ПРД-Н'],
'уарэп-яу07-3к': ['yau07tx', '10.1.1.51', 50051, false, false, 'IP-адрес ЯУ-07 прибора ПРД-Н'],
# Название | Протокол | Адрес | Порт получения | Комментарий
'уарэп-5п28': ['5p28', '127.0.0.1', 50000, 'IP-адрес комплекса 5П-28'],
# Название | Протокол | Адрес | Порт получения | Порт отправки | Комментарий
'уарэп-щ3': ['udp-json-mbcs', 'localhost', 48000, 44100, 'IP-адрес и порты через которые производят обмен сообщения с драйвером клиента Modbus Serial'],
# Название | Протокол | Имя порта | Скорость, бод | Чётность | Комментарий
'уарэп-спт25-1': ['uart', '/dev/tty2', 115200, false, 'Адрес прибора СПТ-25 (G1)'],
'уарэп-спт25-2': ['uart', '/dev/tty3', 115200, false, 'Адрес прибора СПТ-25 (G2)'],
}
## Параметры цели
@export var ThreatParams = {
'time_lost': 5000 ## Время после последнего обновления в мс, через которое цель считается потерянной.
}
func _ready() -> void: log.info('Модуль настроек готов')
## Возвращает ключ устройства
func get_unit_key(unit_name: String) -> Array:
assert(UnitProfiles.has(unit_name), 'ошибка: нет такого устройства \"%s\"' % unit_name)
var item = UnitProfiles[unit_name]
return [item[1], item[2]]

View File

@@ -1,112 +0,0 @@
class_name Signaller extends Node
## Обмен сигналами между узлами. Здесь объявлены сигналы, которые используют
## два и более узла, из разных файлов. Должен быть помещён в автозагрузку.
## Вызывается, когда оператор переводит программу в режим "Автомат целей и помех"
signal full_auto()
## Вызывается, когда оператор переводит программу в режим "Автомат выбора целей"
signal threat_auto()
## Вызывается, когда оператор переводит программу в режим "Автомат выбора помехи"
signal interfer_auto()
## Вызывается, когда оператор переводит программу в режим "Ручной"
signal manual_control()
## Вызывается, когда оператор выбирает цель в таблице.
## [param threats] - Словарь. Ключ - идентификатор (номер) цели. Значение - цель.[br]
## [param th] - Выбранная цель.[br]
signal threat_selected(threats: Dictionary, th)
## Вызывается, когда оператор снимает выбор цели.
## [param threats] - Словарь. Ключ - идентификатор (номер) цели. Значение - цель.[br]
## [param th] - Ранее выбранная цель.[br]
signal threat_unselected(threats: Dictionary, th)
## Вызывается, когда пользователь переключает режим окна программы полный экран - оконный
## [param id] - Идентификатор окна. Присваевается вручную.
signal full_screen(id: int)
## Вызывается, когда пользователь выбирает цель
## [param th_id] - Отметка цели на экране
signal rto_threat_sel (th_id: int)
## Вызывается, когда пользователь нажимает на значёк цели
## [param th_id] - Отметка цели на экране
signal bip_pressed (th_id: int)
## Вызывается, когда пользователь снимает выделение с цели
## [param th_id] - Отметка цели на экране
signal rto_threat_unsel (th_id: int)
## Вызывается, когда пользователь снимает выделение со всех целей
signal rto_threat_unsel_all ()
## Вызывается, когда пользователь выбирает помеху
## [param btn] - Нажатая кнопка помехи.
signal interfer_init (btn: PanelContainer, fs_arr: Array)
## Вызывается, когда выбраны цели для постановки помех
## [param btn] - Нажатая кнопка помехи.[br]
## [param sel_threats] - Cписок целей.
signal interfer_create (btn: PanelContainer, sel_threats: Array, fs_arr: Array)
## Вызывается, когда создалась новая помеха
## [param ecm] - Новая помеха
signal interfer_new (ecm: interfer.Interfer)
## Вызывается, когда отключается действующая помеха
## [param ecm] - Новая помеха, [param ecms] - словарь помех
## [param ecms] - словарь id помех и связанных с ними целей
signal interfer_off (ecm: interfer.Interfer, ecms: Dictionary, thrs: Dictionary)
## Вызывается при нажатии отключить все помехи
signal interfer_off_all ()
## Вызывается, когда отключается действующая помеха
## [param ecm] - Выбранная помеха
signal interfer_selected (ecm: interfer.Interfer)
## Вызывается, когда отключается выбираем помеху по сектору в таблице
## [param ecm] - Выбранная помеха
signal fs_selected(ecm: interfer.Interfer)
## Вызывается при запросе изменения уровня детализации карты
## [param zoom] - Вновь установленный уровень детализации
signal zoom_changed(zoom: int)
## Вызывается при завершении изменения уровня детализации карты
## [param zoom] - Вновь установленный уровень детализации
signal zoom_complete(zoom: int)
## Вызывается при изменении координат карты.
## [param lan], [param lat] - Вновь установленные координаты.
signal map_coords_changed(lan: float, lat: float)
## Вызывается при изменении размера изображения для отображения карты.
## [param mode] - Вновь установленный режим
signal map_mode_changed(mode: int)

View File

@@ -1,136 +0,0 @@
extends Node
## Реализация "Протокол информационного сопряжения
## СПО АСУ изделия «МП550» с СПО 5П-28"
const ONLINE_TIMEOUT = 5000 ## Время ожидания пакета от 5П-28, мс
const BUFFER_SIZE = 2048 ## Размер приёмного буфера для датаграмм
## Тип модуляции для запаковки в пакет для 5П-28
const MOD_TYPES: Array = ['',
'am',
'ook',
'ask',
'fm',
'qpsk',
'oqpsk',
'pi4qpsk',
'qam16',
'qam32',
'qam64',
'qam128',
'qam256',
'qam512',
'cpfsk',
'psk',
'ofdm',]
class TCP5P28:
const PACK_TYPE_CMD: int = 1
const PACK_TYPE_RES: int = 2
const CMD_TYPE_STATE: int = 1
const CMD_TYPE_TH: int = 2
const CMD_TYPE_IN: int = 3
signal line_changed(unit: TCP5P28) ## Сигнал состояние связи изменилось
signal data_received(unit: TCP5P28) ## Сигнал данные приняты
signal command_fail(unit: TCP5P28) ## Сигнал при не выполнении команды
signal parse_error(unit: TCP5P28, msg: String) ## Сигнал ошибки разбора сообщения
signal get_threats(unit: TCP5P28) ## Сигнал запрос целей
var online: = false ## Состояние связи с источником сообщений
var name: String ## Имя экземпляра
var rx_tick: = 0 ## Тик время последнего приёма, мс
var tx_data: = PackedByteArray() ## Пакет для отправки
var tx_stack: Array ## Массив пакетов для отправки
func _to_string() -> String: return String('капсрпб: "%s" %s ' % [self.name, ['отключен', 'на связи'][int(online)]])
func _init(nm): self.name = nm
func parse(data: PackedByteArray, now_tick: int):
rx_tick = now_tick
if not online:
online = true
emit_signal('line_changed', self)
var pack_type = data.decode_u8(2)
if pack_type == PACK_TYPE_CMD:
var cmd_type = data.decode_u8(8)
if cmd_type == CMD_TYPE_TH:
emit_signal('get_threats')
func process(now_tick: int):
if online and ((now_tick - rx_tick) > ONLINE_TIMEOUT):
online = false
emit_signal('line_changed', self)
if len(tx_stack):
tx_data = tx_stack[0]
tx_stack.remove_at(0)
return Error.OK
else:
return Error.ERR_UNAVAILABLE
func pack_threats(threats):
var data = PackedByteArray()
var data_len = 4 + len(threats) * 80 # длинна блока данных в байтах
var tick = Time.get_ticks_msec()
data.resize(4)
data.encode_u16(0, 0) # Код ошибки
data.encode_u16(2, len(threats)) # Количество целей
for th in threats.values():
data.append_array(get_threats_data(th, int(tick)))
var data_to_send: PackedByteArray = create_header(data_len)
data_to_send.append_array(data)
tx_stack.append(data_to_send)
func create_header(data_len):
var head_data = PackedByteArray()
head_data.resize(8) # Размер заголовка
head_data.encode_u16(0, 0) # Код ошибки
head_data.encode_u8(2, 2) # Тип пакета, ответ на запрос
head_data.encode_u8(3, 5) # Версия протокола, всегда 5
head_data.encode_u32(4, data_len) # Длинна пакета данных
return head_data
func get_threats_data(th, tick):
var th_data = PackedByteArray()
th_data.resize(80) # Количество байт в массиве
th_data.encode_u64(0, tick)
var flags: int = 0 # с 0 по 7 бит рекомендованная помеха
flags |= 0 << 8 # Флаг рекомендуемой помехи
flags |= int(th.fflags['aoa']) << 9 # Флаг доставерности пеленга
flags |= int(th.fflags['alt']) << 10 # Флаг доставерности высоты
flags |= int(th.fflags['power']) << 11 # Флаг доставерности мощности
flags |= int(th.fflags['baud']) << 12 # Флаг доставерности ск. мод.
flags |= int(th.fflags['slon']) << 13 # Флаг доставерности долготы ст. позиции
flags |= int(th.fflags['slat']) << 14 # Флаг доставерности широты ст. позиции
flags |= int(th.fflags['lon']) << 15 # Флаг доставерности долготы
flags |= int(th.fflags['lat']) << 16 # Флаг доставерности широты
flags |= int(0) << 30 # Флаг признак назначения помехи на цель
flags |= int(0) << 31 # Флаг признак назначения опасной цель
th_data.encode_u32(8, flags) # Флаги
th_data.encode_u16(12, th.id) # Идентификатор объекта
th_data.encode_u16(14, int(round(th.aoa/0.1))) # Пеленг объекта
th_data.encode_u16(16, int(th.alt)) # Высота объекта
th_data.encode_u16(18, int(th.freq)) # Частота объекта
th_data.encode_u16(20, int(th.width)) # Ширина частотной полосы объекта
th_data.encode_u8(22, int(th.power)) # Мощность излучения объекта
var mod = MOD_TYPES.find(th.tmod)
if mod == -1: mod = 0
th_data.encode_u8(23, int(mod)) # Тип модуляции
th_data.encode_u32(24, int(th.baud)) # Скорость модуляции, бод
to_cp866(th.proto, th_data)
var c1 = pow(2, 30) / 180.0
th_data.encode_s32(64, int(th.slon * c1))
th_data.encode_s32(68, int(th.slat * c1))
th_data.encode_s32(72, int(th.lon * c1))
th_data.encode_s32(76, int(th.lat * c1))
return th_data
func to_cp866(proto: String, th_data):
var cp866_proto: PackedByteArray = proto.to_ascii_buffer()
cp866_proto.resize(36)
for i in len(cp866_proto):
th_data[28 + i] = cp866_proto[i]

View File

@@ -1,137 +0,0 @@
extends Node
## Работа с целями
## Цель
class Threat:
var alt: float ## Текущая высота, метры.
var aoa: float ## Курсовой угол на источник сигнала, градусы.
var atick: float ## Тик обнаружения.
var baud: float ## Модуляционная скорость.
var fflags: Dictionary ## Флаги достоверности полей.
var freq: float ## Частота несущей или центральная частота, МГц.
var freqm: String ## Режим смены частоты.
var id: int ## Идентификатор цели.
var lat: float ## Текущие координаты, широта в градусах.
var lon: float ## Текущие координаты, долгота в градусах.
var power: float ## Уровень принимаемого сигнала, дБм.
var proto: String ## Тип протокола источника сигнала.
var rkrp: String ## Рекомендуемый режим помехи.
var rparams: String ## Рекомендуемые параметры помехи.
var salt: float ## Координаты старта, высота от уровня моря, м.
var slat: float ## Координаты старта, широта в градусах.
var slon: float ## Координаты старта, долгота в градусах.
var tick: int ## Тик время приёма сигнала, мс.
var tick_rx: int ## Тик время обновления цели, мс.
var tmod: String ## Тип модуляции.
var width: float ## Ширина занимаемого диапазона частот, МГц.
var selected: bool ## Флаг, что цель выбрана
func _init(): clear_fflags()
func _to_string() -> String: return 'номер: %d пеленг: %0.1f частота: %0.1f МГц' % [id, aoa, freq]
func clear_fflags(): tools.get_members_names(self).any(func(vn): fflags[vn] = false)
func set_from_dic(dic: Dictionary):
var items: = fflags.keys().filter(func(vn): return dic.has(vn))
items.any(func(vn): set(vn, dic[vn]); fflags[vn] = true; return false)
signal threat_new(th: Threat)
signal threat_lost(th: Threat)
signal threat_update(th: Threat)
signal threats_resized(th: Dictionary)
func _ready() -> void:
log.info('Модуль обработки целей готов')
var threats = {} ## Список целей. Индекс - <номер цели>
var unit_key = repsettings.get_unit_key('уарэп-капсрпб-отп')
var unit = repnetwork.units[unit_key]
var unit_5p28_key = repsettings.get_unit_key('уарэп-5п28')
var unit_5p28 = repnetwork.units_tcp[unit_5p28_key]
var timer_check_lost: = Timer.new()
add_child(timer_check_lost)
unit.connect('data_received', Callable(self, 'on_data_capsrpb_received').bind(threats))
unit_5p28.connect('get_threats', Callable(self, 'on_get_threats').bind(unit_5p28, threats))
timer_check_lost.connect('timeout', Callable(self, 'on_timer_check_lost').bind(repsettings.ThreatParams.time_lost, threats))
timer_check_lost.start(repsettings.ThreatParams.time_lost / 1000.0)
signaller.connect('rto_threat_sel', Callable(self, 'on_rto_threat_sel').bind(threats))
signaller.connect('rto_threat_unsel', Callable(self, 'on_rto_threat_unsel').bind(threats))
signaller.connect('rto_threat_unsel_all', Callable(self, 'on_rto_threat_unsel_all').bind(threats))
signaller.connect('threat_selected', Callable(self, 'on_threat_selected').bind(threats))
signaller.connect('interfer_init', Callable(self, 'on_interfer_init').bind(threats))
## Периодически удаляет устаревшие цели
func on_timer_check_lost(time_lost: int, threats: Dictionary):
var tick = Time.get_ticks_msec()
var sz: = threats.size()
for th in threats.values():
if (tick - th.tick_rx) >= time_lost:
threats.erase(th.id)
call_deferred('emit_signal', 'threat_lost', th)
if sz != threats.size():
call_deferred('emit_signal', 'threats_resized', threats)
## отправляет словапь целей в юнит, для запаковки и отправки в 5П28
func on_get_threats(unit, threats): unit.pack_threats(threats)
## Снимает выделение с цели, если нажать по отметке цели.
func on_rto_threat_unsel(th_id, threats): threats[th_id].selected = false
## Снимает выделение со всех целей
func on_rto_threat_unsel_all(threats):
for th in threats.values():
signaller.emit_signal('rto_threat_unsel', th.id)
## Выбирает цель, если нажать по отметке цели.
func on_rto_threat_sel(th_id, threats): on_threat_selected(threats[th_id], threats)
## Выбирает цель, если выбрать в таблице
func on_threat_selected(th, threats):
for threat in threats.values():
threat.selected = false
th.selected = true
## Формирует список выбранных целей для включения помехи
func on_interfer_init(btn, fs_arr, threats):
var sel_threats: Array = []
for threat in threats.values():
if threat.selected:
sel_threats.append(threat)
if len(sel_threats) or (len(fs_arr)):
signaller.emit_signal('interfer_create', btn, sel_threats, fs_arr)
## Производит приём целей
func on_data_capsrpb_received(unit: capsrpb.CapsRpb, threats: Dictionary):
if not unit.json_dic.has('ts'): return
if unit.json_dic['ts'] != 5: return
if not unit.json_dic.has('mis'): return
var mis = unit.json_dic['mis']
var sz: = threats.size()
for dic_th in mis:
if dic_th is Dictionary:
if dic_th.has('id'):
var id = int(dic_th['id'])
if threats.has(id):
var th = threats[id]
th.clear_fflags()
th.set_from_dic(dic_th)
th.tick_rx = unit.tick
call_deferred('emit_signal', 'threat_update', th)
else:
var th = Threat.new()
th.set_from_dic(dic_th)
threats[id] = th
th.tick_rx = unit.tick
call_deferred('emit_signal', 'threat_new', th)
if sz != threats.size():
call_deferred('emit_signal', 'threats_resized', threats)

View File

@@ -1,95 +0,0 @@
extends Node
## Простые часто используемые функции
## Возвращает округлённое значение [param v] с точностью [param a]
func round_to(v: float, a: float) -> float: return snappedf(v, -a if v < 0 else a)
## Возвращает [b]true[/b], если [param val] число, [b]false[/b] - если иначе.
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 битном слове[br]
## [i]Следует использовать встроенные в[/i] [b]PackedByteArray[/b] [i]методы вместо этой функции.[/i]
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)
## Проверяет пересечение диапазона [param x1], [param x2] с диапазоном [param y1], [param 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 r = ((freq - f0) / (f1 - f0)) * r1 + r0
var aoa_rad = radians(aoa) - PI / 2.0
return Vector2(cos(aoa_rad), sin(aoa_rad)) * r + pos0
## Отсекает значение до заданного диапазона
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)
## Преобразует декартовые координаты в полярные.[br]
## [param pos0] - координаты центра.[br]
## [param pos] - координаты точки.[br]
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
## Возвращает [b]true[/b], если точка внутри заданного полигона
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] (младшее слово).[br]
## [i]Следует использовать встроенные в[/i] [b]PackedByteArray[/b] [i]методы вместо этой функции.[/i]
func set_wordi(buf: PackedByteArray, index: int, val: int) -> void:
buf[index] = val & 0xff
buf[index + 1] = (val >> 8) & 0xff

View File

@@ -1,249 +0,0 @@
extends Node
## Протокол ячейки ЯУ-07
const BUFFER_SIZE = 2048 ## Размер буфера для приёма датаграмм
const ONLINE_TIMEOUT = 5.0 ## Время ожидания пакета от ячейки, секунды
const RESEND_TIMEOUT = 0.3 ## Задержка для повторной отправки команды, секунды
const RETRY_COUNT = 5 ## Количество повторных отправок
## Ячейка ЯУ-07
class YaU07:
## Коды выполняемых команд
enum CmdCode {
READ_BLOCK = 61, ## Код команды "читать блок данных ISA" внешней шины ячейки ЯУ-07
READ_ISA = 0, ## Код команды "читать порт ISA" ячейки ЯУ-07
WRITE_BLOCK = 60, ## Код команды "записать блок данных ISA" внешней шины ячейки ЯУ-07
WRITE_ISA = 1, ## Код команды "записать порт ISA" ячейки ЯУ-07
}
## Состояние автомата выполнения команды
enum CmdState {
DONE, ## Команда выполнена
FAIL, ## Команда не выполнена
SEND, ## Буфер готов для отправки
UNCK, ## Номер команды не ининциирован
WAIT ## Ожидание выполнения
}
signal command_done(unit: YaU07) ## Вызывается после выполнения команды
signal command_fail(unit: YaU07) ## Вызывается если возникла ошибка при выполнении команды и команда не выполнена
signal line_changed(unit: YaU07) ## Вызывается когда меняется состояние связи с ячейкой
signal parse_failed(unit: YaU07) ## Вызывается если возникла ошибка при разборе пакета
var cmd_data: = PackedByteArray() ## Данные команды. Устанавливается в ЯУ-07
var cmd_num_rx: = 0 ## Номер команды принятый
var cmd_num_tx: = 0 ## Номер команды переданный
var cmd_result: = 1 ## Результат выполнения команды. Устанавливается в ЯУ-07
var cmd_retry: = 0 ## Счётчик оставшихся попыток выполнить команду
var cmd_state: = CmdState.UNCK ## Состояние выполнения команды
var cmd_tick_rx: = 0 ## Время последнего приёма команды
var cmd_tick_tx: = 0 ## Время последней отправки команды
var isa_block: = {} ## Результат выполнения команды "читать массив из порта"
var isa_ports: = {} ## Результат выполнения команды "читать порт"
var name: = String() ## Имя
var online: = false ## Состояние связи с ячейкой
var status: = PackedByteArray() ## Поле "состояние прибора"
var tx_data: = PackedByteArray() ## Буфер отправленный
var tx_len: = 0 ## Длина последнего отправленного пакета
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)
func process(tick: float):
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) # Исправление для сетевого стека "nm-callback" процессоров STM
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.
## [param rx_data] - Принятые данные.[br]
## Возвращает [b]null[/b] - Если код команды не найден.[br]
## Возвращает [b]0...255[/b] - Код команды.[br]
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, tick: int) -> 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 < 11:
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

View File

@@ -1,15 +0,0 @@
extends Node
class EmsBoard:
var id_board: String
var in_devs: Dictionary ## Словарь с входными устройствами
var out_devs: Dictionary ## Словарь с выходными устройствами
var base_addr_isa: int ## Базовый адрес ячейки isa
var in_tbl: Array
var out_tbl: Array
func _init(id, base_addr, in_table, out_table):
id_board = id
in_tbl = in_table
out_tbl = out_table
base_addr_isa = base_addr

View File

@@ -1,144 +0,0 @@
extends Node
##
const INPUT_ROWS1 = [
[12,['283333 МР-231S, Строб ЭМС', 'X52', '0.00', '0.00']],
[13,['29НРЛС МР-231-3, УИЗП', 'X54', '0.00', '0.00']],
[14,['30НРЛС МР-231-3, Строб ЭМС', 'X55', '0.00', '0.00']],
[15,['31ЦИСВС-1010, УИЗП', 'X23', '0.00', '0.00']],
[21,['32НРЛС МР-231S, УИЗП', 'X21', '0.00', '0.00']],
[22,['33НРЛС МР-231S, Строб ЭМС', 'X22', '0.00', '0.00']],
[23,['34НРЛС МР-231-3, УИЗП', 'X24', '0.00', '0.00']],
[32,['352222 МР-231S, Строб ЭМС', 'X32', '0.00', '0.00']],
[33,['36НРЛС МР-231-3, УИЗП', 'X34', '0.00', '0.00']],
[34,['37НРЛС МР-231-3, Строб ЭМС', 'X35', '0.00', '0.00']],
[35,['38ЦИСВС-1010, УИЗП', 'X43', '0.00', '0.00']],
[38,['394302-01, 4302-04, УИЗП', 'X46', '0.00', '0.00']],
[31,['40НРЛС МР-231S, УИЗП', 'X51', '0.00', '0.00']]]
## Таблица с данными для выходов, первое значение - индекс устройства в ячейке КЭМС
const OUTPUT_ROWS1 = [
[41,['41НРЛС МР-231S, БИ', 'X61', '0.00', '0.00']],
[42,['42НРЛС МР-231S', 'X62', '0.00', '0.00']],
[43,['43НРЛС МР-231-3, БИ', 'X64', '0.00', '0.00']],
[44,['44НРЛС МР-231-3', 'X65', '0.00', '0.00']],
[45,['45ЦИСВС-1010, БИ', 'X63', '0.00', '0.00']],
[46,['464302-01, 4302-04, БИ', 'X66', '0.00', '0.00']],
[48,['47НРЛС 11111111', 'X69', '0.00', '0.00']],
[62,['48НРЛС МР-231S', 'X62', '0.00', '0.00']],
[63,['49НРЛС МР-231-3, БИ', 'X64', '0.00', '0.00']],
[64,['50НРЛС МР-231-3', 'X65', '0.00', '0.00']],
[65,['51ЦИСВС-1010, БИ', 'X63', '0.00', '0.00']],
[66,['524302-01, 4302-04, БИ', 'X66', '0.00', '0.00']],
[68,['53НРЛС 11111111', 'X69', '0.00', '0.00']]]
## Таблица с данными для входов, первое значение - индекс устройства в ячейке КЭМС
const INPUT_ROWS = [
[1,['УИЗП №1', 'Х7', '0.00', '0.00']],
[2,['УИЗПФ №1', 'Х8', '0.00', '0.00']],
[3,['УИЗП №2', 'Х10', '0.00', '0.00']],
[4,['УИЗПФ №2', 'Х11', '0.00', '0.00']],
[5,['УИЗП №3', 'Х13', '0.00', '0.00']],
[6,['УИЗПФ №3', 'Х14', '0.00', '0.00']],
[7,['УИЗП №4', 'Х16', '0.00', '0.00']],
[8,['УИЗПФ №4', 'Х17', '0.00', '0.00']],
[9,['УИЗП №5', 'Х19', '0.00', '0.00']],
[10,['УИЗПФ №5', 'Х20', '0.00', '0.00']],
[11,['УИЗП №6', 'Х22', '0.00', '0.00']],
[12,['УИЗПФ №6','Х23', '0.00', '0.00']],
[13,['УИЗП №7','Х25', '0.00', '0.00']],
[14,['УИЗПФ №7','Х26', '0.00', '0.00']],
[15,['СИ1 Л1','X31 : 2', '0.00', '0.00']],
[16,['СИ1 Л6','X31 : 5', '0.00', '0.00']],
[17,['СИ1 Л7','X31 : 8', '0.00', '0.00']],
[18,['СИ2 Л1','X32 : 2', '0.00', '0.00']],
[19,['СИ2 Л6','X32 : 5', '0.00', '0.00']],
[20,['СИ2 Л7','X32 : 8', '0.00', '0.00']],
[21,['СИ3 Л1','X33 : 2', '0.00', '0.00']],
[22,['СИ3 Л6','X33 : 5', '0.00', '0.00']],
[23,['СИ3 Л7','X33 : 8', '0.00', '0.00']],
[24,['СИ4 Л1','X34 : 2', '0.00', '0.00']],
[25,['СИ4 Л6','X34 : 5', '0.00', '0.00']],
[26,['СИ4 Л7','X34 : 8', '0.00', '0.00']],
[27,['СИ1 Л2','X38 : 2', '0.00', '0.00']],
[28,['СИ1 Л3','X38 : 5', '0.00', '0.00']],
[29,['СИ2 Л2','X39 : 2', '0.00', '0.00']],
[30,['СИ2 Л3','X39 : 5', '0.00', '0.00']],
[31,['СИ3 Л2','X40 : 2', '0.00', '0.00']],
[32,['СИ3 Л3','X40 : 5', '0.00', '0.00']],
[33,['СИ4 Л2','X41 : 2', '0.00', '0.00']],
[34,['СИ4 Л3','X41 : 5', '0.00', '0.00']],
[35,['СИ1 Л4','X42 : 2', '0.00', '0.00']],
[36,['СИ1 Л5','X42 : 5', '0.00', '0.00']],
[37,['СИ2 Л4','X43 : 2', '0.00', '0.00']],
[38,['СИ2 Л5','X43 : 5', '0.00', '0.00']],
[39,['СИ3 Л4','X44 : 2', '0.00', '0.00']],
[40,['СИ3 Л5','X44 : 5', '0.00', '0.00']],
[41,['СИ4 Л4','X45 : 2', '0.00', '0.00']],
[42,['СИ4 Л5','X45 : 5', '0.00', '0.00']],
[43,['БИ В Лб','Х49 : 37', '0.00', '0.00']],
[44,['БИ В ПрБ','Х49 : 31', '0.00', '0.00']],
[45,['БИ Г Лб','Х49 : 15', '0.00', '0.00']],
[46,['БИ Г ПрБ','Х49 : 8', '0.00', '0.00']],
[47,['БИ Д Лб','Х49 : 6', '0.00', '0.00']],
[48,['БИ Д ПрБ','Х49 : 10', '0.00', '0.00']],
[49,['БИ Е Лб','Х49 : 25', '0.00', '0.00']],
[50,['БИ Е ПрБ','Х49 : 40', '0.00', '0.00']],
[51,['СЗИ В Лб','Х49 : 42', '0.00', '0.00']],
[52,['СЗИ В ПрБ','Х49 : 32', '0.00', '0.00']],
[53,['СЗИ Г Лб','Х49 : 46', '0.00', '0.00']],
[54,['СЗИ Г ПрБ','Х49 : 32', '0.00', '0.00']],
[55,['СЗИ Д Лб','Х49 : 16', '0.00', '0.00']],
[56,['СЗИ Д ПрБ','Х49 : 47', '0.00', '0.00']],
[57,['СЗИ Е Лб','Х49 : 1', '0.00', '0.00']],
[58,['СЗИ Е ПрБ','Х49 : 50', '0.00', '0.00']]]
## Таблица с данными для выходов, первое значение - индекс устройства в ячейке КЭМС
const OUTPUT_ROWS = [
[11,['Бланк №1','Х6', '0.00', '0.00']],
[12,['Бланк №2','Х9', '0.00', '0.00']],
[13,['Бланк №3','Х12', '0.00', '0.00']],
[14,['Бланк №4','Х15', '0.00', '0.00']],
[15,['Бланк №5','Х18', '0.00', '0.00']],
[16,['Бланк №6','Х21', '0.00', '0.00']],
[17,['Бланк №7','Х24', '0.00', '0.00']],
[18,['СЗИ1 Л1','X31 : 11', '0.00', '0.00']],
[19,['СЗИ1 Л6','X31 :14', '0.00', '0.00']],
[20,['СЗИ1 Л7','X31 : 17', '0.00', '0.00']],
[21,['СЗИ2 Л1','X32 : 11', '0.00', '0.00']],
[22,['СЗИ2 Л6','X32 : 14', '0.00', '0.00']],
[23,['СЗИ2 Л7','X32: 17', '0.00', '0.00']],
[24,['СЗИ3 Л1','X33 : 11', '0.00', '0.00']],
[25,['СЗИ3 Л6','X33 :14', '0.00', '0.00']],
[26,['СЗИ3 Л7','X33: 17', '0.00', '0.00']],
[27,['СЗИ4 Л1','X34 : 11', '0.00', '0.00']],
[28,['СЗИ4 Л6','X34 :14', '0.00', '0.00']],
[29,['СЗИ4 Л7','X34: 17', '0.00', '0.00']],
[30,['СЗИ1 Л2','X38 : 11', '0.00', '0.00']],
[31,['СЗИ1 Л3','X38 : 14', '0.00', '0.00']],
[32,['СЗИ2 Л2','X39 : 11', '0.00', '0.00']],
[33,['СЗИ2 Л3','X39 : 14', '0.00', '0.00']],
[34,['СЗИ3 Л2','X40 : 11', '0.00', '0.00']],
[35,['СЗИ3 Л3','X40 : 14', '0.00', '0.00']],
[36,['СЗИ4 Л2','X41 : 11', '0.00', '0.00']],
[37,['СЗИ4 Л3','X41 :14', '0.00', '0.00']],
[38,['СЗИ1 Л4','X42 : 11', '0.00', '0.00']],
[39,['СЗИ1 Л5','X42 :14', '0.00', '0.00']],
[40,['СЗИ2 Л4','X43 : 11', '0.00', '0.00']],
[41,['СЗИ2 Л5','X43 : 14', '0.00', '0.00']],
[42,['СЗИ3 Л4','X44 : 11', '0.00', '0.00']],
[43,['СЗИ3 Л5','X44 :14', '0.00', '0.00']],
[44,['СЗИ4 Л4','X45 : 11', '0.00', '0.00']],
[45,['СЗИ4 Л5','X45 : 14', '0.00', '0.00']],
[46,['БИ РТР №1','X47 : 2', '0.00', '0.00']],
[47,['БИ РТР №2','X47 : 5', '0.00', '0.00']],
[48,['БИ РТР №3','X47 : 8', '0.00', '0.00']],
[49,['БИ РТР №4','X47 : 11', '0.00', '0.00']],
[50,['СИ В Лб','Х49 : 21', '0.00', '0.00']],
[51,['СИ В ПрБ','Х49 : 19', '0.00', '0.00']],
[52,['СИ Г Лб','Х49 : 28', '0.00', '0.00']],
[53,['СИ Г ПрБ','Х49 : 30', '0.00', '0.00']],
[54,['СИ Д Лб','Х49 : 13', '0.00', '0.00']],
[55,['СИ Д ПрБ','Х49 : 11', '0.00', '0.00']],
[56,['СИ Е Лб','Х49 : 26', '0.00', '0.00']],
[57,['СИ Е ПрБ','Х49 : 35', '0.00', '0.00']]]

View File

@@ -1,37 +0,0 @@
extends Node
class InDev:
var id: int ## Идентификатор индекс устройства
var dev_name: String ## Имя подключаемого комплекса
var connector: String ## Номер разъёма в приборе
var delay: float ## Задержка фронта импульса
var offset: float ## Смещение фронта импульса
var state: int ## Состояние входа
var freq: int ## Предполагаемая частота на которой работает комплекс
var ports_data: Dictionary ## Словарь состояния портов устройства
var tbl_row_id: int ## Индекс устройства в таблице выходов
var out_dots: Array ## Список выходных устройств в порядке, как в таблице и точки матрицы
var connect_devs: Dictionary ## Список присоединённых устройств
var board: String ## Плата в которой находится устройство
func _init(dev_id : int, params : Array, row_id : int):
id = dev_id
dev_name = String(params[0])
connector = String(params[1])
delay = float(params[2])
offset = float(params[3])
tbl_row_id = row_id
class OutDev extends InDev:
var mode: String ## Режим работы выхода
func _init(dev_id : int, params : Array, row_id : int):
id = dev_id
dev_name = String(params[0])
connector = String(params[1])
delay = float(params[2])
offset = float(params[3])
tbl_row_id = row_id
mode = 'Работа'