422 lines
18 KiB
GDScript
422 lines
18 KiB
GDScript
class_name Network extends Node
|
||
|
||
const YAU07_PROTO: = {'yau07tx': yau07.YaU07}
|
||
const UDP_PROTO: = {'capsrpb': capsrpb.CapsRpb, 'trassa': trassa.Trassa}
|
||
const TCP_PROTO: = {'5p28': tcp5p28.TCP5P28}
|
||
const SERIAL_PROTO: = {'spt25': spt25.SPT25}
|
||
const MODBUS_PROTO: = {'mbcs': sch3.Sch3}
|
||
const NAVI_PROTO: = {'navi': navi.NaviSource}
|
||
const NULL_PROTO: = {null: unit.Unit}
|
||
|
||
var tick: int = 0
|
||
|
||
|
||
class SocketSerial extends SerialPort:
|
||
const P_DIV: = 0x8408
|
||
const FLAG: = 0x7e
|
||
const FLAG_MASK: = 0x20
|
||
const FSC_MASK: = 0x8000
|
||
const parity_code = {
|
||
'N': SerialPort.PARITY_NONE,
|
||
'E': SerialPort.PARITY_EVEN,
|
||
'O': SerialPort.PARITY_ODD }
|
||
static var tick: int
|
||
|
||
func on_serial_data(data: PackedByteArray, unit_instance: unit.Unit):
|
||
unit_instance.parse(data, tick)
|
||
|
||
func on_settings_changed(unit_instance: unit.Unit):
|
||
var external_ctl = ProjectSettings.get_setting('application/config/Внешнее управление', false)
|
||
try_close()
|
||
if external_ctl:
|
||
return
|
||
var rc = try_open(on_serial_data.bind(unit_instance))
|
||
if rc != Error.OK:
|
||
log.message(log.ERROR, 'невозможно открыть %s для \"%s\"' % [self, unit_instance.name])
|
||
else:
|
||
log.message(log.INFO, '%s для \"%s\"' % [self, unit_instance.name])
|
||
|
||
func format_buffer(data: PackedByteArray):
|
||
var buffer: PackedByteArray
|
||
buffer.resize(64)
|
||
var i: = 1
|
||
for v in data:
|
||
if v != 0x7e:
|
||
buffer[i] = v; i += 1
|
||
else:
|
||
buffer[i] = FLAG; i += 1
|
||
buffer[i] = FLAG ^ FLAG_MASK; i += 1
|
||
var v_sum: = get_check_sum(data)
|
||
buffer.encode_u16(i, v_sum); i += 2
|
||
buffer[i] = FLAG; i += 1
|
||
buffer.resize(i)
|
||
return buffer
|
||
|
||
func send_to(data: PackedByteArray):
|
||
if not is_open(): return
|
||
# var buffer = format_buffer(data) # Не проверено, но должно работать
|
||
# Эта последовательность для запроса состояния, получена от разработчиков СПТ-25
|
||
const tx_tula: = [0x7e, 0x01, 0x04, 0xbb, 0x50, 0x7e]
|
||
write_raw(tx_tula)
|
||
|
||
func _to_string() -> String: return 'последовательный порт \"%s\" @ %d (%s)' % [self.port, self.baudrate, 'открыт' if is_open() else 'закрыт']
|
||
|
||
func get_check_sum(src: PackedByteArray) -> int:
|
||
var fcs: = (~((src[1] << 8) + src[0])) & 0xffff
|
||
for i: int in range(2, src.size()):
|
||
var chr: = src[i]
|
||
for j: int in 8:
|
||
if chr & 1:
|
||
if fcs & 1:
|
||
fcs = ((fcs >> 1) | FSC_MASK) ^ P_DIV
|
||
else:
|
||
fcs = ((fcs >> 1) | FSC_MASK)
|
||
else:
|
||
fcs = (fcs >> 1) ^ P_DIV if fcs & 1 else fcs >> 1
|
||
chr >>= 1
|
||
for j: int in 16:
|
||
fcs = (fcs >> 1) ^ P_DIV if fcs & 1 else fcs >> 1
|
||
return (~fcs) & 0xffff
|
||
|
||
func try_open(parsing_proc: Callable):
|
||
if is_open():
|
||
return Error.OK
|
||
var rc: = open(port)
|
||
if rc == Error.OK:
|
||
data_received.connect(parsing_proc)
|
||
start_monitoring(timeout)
|
||
return rc
|
||
|
||
func try_close():
|
||
if not is_open():
|
||
return Error.FAILED
|
||
stop_monitoring()
|
||
close()
|
||
var connections = data_received.get_connections()
|
||
for conn in connections:
|
||
data_received.disconnect(conn.callable)
|
||
return Error.OK
|
||
|
||
|
||
class SocketUDP extends PacketPeerUDP:
|
||
func send_to(addr: Array, data: PackedByteArray):
|
||
set_dest_address(addr[0], addr[1])
|
||
var rc: = put_packet(data)
|
||
if rc != Error.OK:
|
||
push_error('%s: %s', [error_string(rc), addr])
|
||
|
||
|
||
class SocketTCP extends TCPServer:
|
||
const RX_TIMEOUT: int = 1000 ## Время сброса счётчика принятия части пакета.
|
||
var peerstream: = PacketPeerStream.new()
|
||
var unit_name: StringName ## Уникальное имя устройства
|
||
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: PackedByteArray):
|
||
var peer = peerstream.get_stream_peer()
|
||
peer.put_data(data)
|
||
|
||
|
||
var poll_sockets: Array[SocketUDP] ## Сокеты для непрерывного опроса наличия новых данных
|
||
var tcp_sockets: Array[Array] ## Сокеты для TCP-соединений
|
||
var units: Dictionary[StringName, unit.Unit] ## Экземпляры всех сетевых устройств
|
||
var units_udptx: Dictionary[StringName, unit.Unit] ## Экземпляры устройств с udp-соединением которым нужно передавать данные
|
||
var units_udprx: Dictionary[StringName, unit.Unit] ## Экземпляры устройств с udp-соединением от которых приходят данные
|
||
var units_tcp: Dictionary[StringName, unit.Unit] ## Экземпляры устройств с tcp-соединением
|
||
var units_serial: Dictionary[StringName, unit.Unit] ## Экземпляры устройств с uart-соединением
|
||
var units_modbus: Dictionary[StringName, unit.Unit] ## Экземпляры устройств с modbusserial-соединением
|
||
var serials: Dictionary[StringName, SocketSerial] ## Последовательные порты (uart)
|
||
var dst_ports: Dictionary[StringName, int] ## Преобразование имя устройства в порт назначения
|
||
var dst_addrs: Dictionary[StringName, StringName] ## Преобразование имя устройства в адрес назначения
|
||
var port_to_unit_name: Dictionary[int, StringName] ## Преобразование порта назначения в имя устройства
|
||
var logger_page: Node ## Ссылка на журнал работы
|
||
var send_sockets: Dictionary[StringName, SocketUDP] ## Таблица <имяустройста>: <сокет> для отправки сообщений
|
||
var addr_port_to_unit_name: Dictionary[StringName, StringName] ## Преобразование адрес:порт в имя устройства
|
||
|
||
|
||
## [param unit_name] - Уникальное имя устройства[br]
|
||
func create_socket_udp(unit_name: StringName) -> SocketUDP:
|
||
var st = settings.UnitProfiles[unit_name][1]
|
||
var addr = st[0]
|
||
var port = st[1]
|
||
var broad = st[2]
|
||
var bind = st[3]
|
||
var sock: = SocketUDP.new()
|
||
sock.set_broadcast_enabled(broad)
|
||
var rc: = sock.bind(port, addr)
|
||
if rc == Error.OK:
|
||
log.info('\"%s\" привязан к %s:%d' % [unit_name, addr, port])
|
||
else:
|
||
var errmsg: = error_string(rc)
|
||
log.error('привязка \"%s\" к %s:%d не удалась: \"%s\"' % [unit_name, addr, port, errmsg])
|
||
return sock
|
||
|
||
|
||
func create_socket_caps(unit_name: StringName) -> SocketUDP:
|
||
var st = settings.UnitProfiles[unit_name][1]
|
||
var addr = st[0]
|
||
var port = st[1]
|
||
var sock: = SocketUDP.new()
|
||
var rc: = sock.bind(port, addr)
|
||
if rc == Error.OK:
|
||
log.info('\"%s\" привязан к %s:%d' % [unit_name, addr, port])
|
||
else:
|
||
var errmsg = error_string(rc)
|
||
log.error('привязка \"%s\" к %s:%d не удалась: \"%s\"' % [unit_name, addr, port, errmsg])
|
||
return sock
|
||
|
||
|
||
## [param unit_name] - Уникальное имя устройства[br]
|
||
func create_socket_tcp(unit_name: StringName) -> SocketTCP:
|
||
var st = settings.UnitProfiles[unit_name][1]
|
||
var addr = st[0]
|
||
var port = st[1]
|
||
var sock: = SocketTCP.new()
|
||
sock.unit_name = unit_name
|
||
var rc: = sock.listen(port, addr)
|
||
if rc == Error.OK:
|
||
log.message(log.INFO, '\"%s\" слушает на %s:%d' % [unit_name, addr, port])
|
||
else:
|
||
log.message(log.ERROR, '\"%s\" %s:%d \"%s\"' % [unit_name, addr, port, error_string(rc)])
|
||
return sock
|
||
|
||
|
||
## [param unit_name] - Уникальное имя устройства[br]
|
||
func create_serial(unit_name: StringName) -> SocketSerial:
|
||
var st = settings.UnitProfiles[unit_name][1]
|
||
var serial: = SocketSerial.new()
|
||
serial.port = st[0]
|
||
serial.baudrate = st[2]
|
||
serial.parity = SocketSerial.parity_code.get(st[3], SocketSerial.PARITY_NONE)
|
||
serial.timeout = 20000
|
||
serial.bytesize = st[4]
|
||
serial.stopbits = st[5]
|
||
return serial
|
||
|
||
|
||
func on_serial_got_error(where: String, what: String, serial: SocketSerial, unit_instance: unit.Unit):
|
||
push_error([where, what, serial])
|
||
|
||
|
||
## [param unit_name] - Уникальное имя устройства[br]
|
||
func create_modbus(unit_name: StringName) -> sch_3.Sch3:
|
||
var unit_modbus: = sch_3.Sch3.new(unit_name)
|
||
return unit_modbus
|
||
|
||
|
||
func _ready() -> void:
|
||
var sock_unicast: = create_socket_udp('уарэп-яу07-частный')
|
||
var sock_broadcast: = create_socket_udp('уарэп-яу07-общий')
|
||
var sock_navi: = create_socket_udp('навигация')
|
||
var sock_caps: = create_socket_caps('уарэп-капсрпб')
|
||
var sock_trassa: = create_socket_caps('уарэп-трасса')
|
||
poll_sockets = [sock_unicast, sock_broadcast, sock_caps, sock_navi, sock_trassa]
|
||
send_sockets['уарэп-яу07-частный'] = sock_unicast
|
||
send_sockets['уарэп-яу07-общий'] = sock_broadcast
|
||
send_sockets['уарэп-капсрпб'] = sock_caps
|
||
send_sockets['уарэп-трасса'] = sock_trassa
|
||
send_sockets['уарэп-эмс'] = sock_unicast
|
||
send_sockets['уарэп-яу07-2в'] = sock_unicast
|
||
send_sockets['уарэп-яу07-2н'] = sock_unicast
|
||
send_sockets['уарэп-яу07-2к'] = sock_unicast
|
||
send_sockets['уарэп-яу07-4в'] = sock_unicast
|
||
send_sockets['уарэп-яу07-4н'] = sock_unicast
|
||
send_sockets['уарэп-яу07-4к'] = sock_unicast
|
||
send_sockets['уарэп-яу07-1в'] = sock_unicast
|
||
send_sockets['уарэп-яу07-1н'] = sock_unicast
|
||
send_sockets['уарэп-яу07-1к'] = sock_unicast
|
||
send_sockets['уарэп-яу07-3в'] = sock_unicast
|
||
send_sockets['уарэп-яу07-3н'] = sock_unicast
|
||
send_sockets['уарэп-яу07-3к'] = sock_unicast
|
||
|
||
var bind_serials: = []
|
||
var bind_modbuses: = []
|
||
|
||
for unit_name in settings.UnitProfiles:
|
||
var unit_profile = settings.UnitProfiles[unit_name][1]
|
||
var proto = settings.UnitProfiles[unit_name][0]
|
||
|
||
if proto in NAVI_PROTO:
|
||
var new_unit = NAVI_PROTO[proto].new(unit_name)
|
||
units[unit_name] = new_unit
|
||
units_udprx[unit_name] = new_unit
|
||
dst_ports[unit_name] = unit_profile[1]
|
||
|
||
if proto in YAU07_PROTO:
|
||
var new_unit = YAU07_PROTO[proto].new(unit_name)
|
||
units[unit_name] = new_unit
|
||
units_udptx[unit_name] = new_unit
|
||
dst_addrs[unit_name] = unit_profile[0]
|
||
dst_ports[unit_name] = unit_profile[1]
|
||
var addr_port: = StringName('%s:%d' % [unit_profile[0], unit_profile[1]])
|
||
addr_port_to_unit_name[addr_port] = unit_name
|
||
|
||
if proto in UDP_PROTO:
|
||
var new_unit = UDP_PROTO[proto].new(unit_name)
|
||
units[unit_name] = new_unit
|
||
units_udptx[unit_name] = new_unit
|
||
dst_addrs[unit_name] = unit_profile[0]
|
||
dst_ports[unit_name] = unit_profile[2]
|
||
|
||
if proto in TCP_PROTO:
|
||
var unit_tcp = TCP_PROTO[proto].new(unit_name)
|
||
units[unit_name] = unit_tcp
|
||
units_tcp[unit_name] = unit_tcp
|
||
|
||
if proto in MODBUS_PROTO:
|
||
var unit_modbus = MODBUS_PROTO[proto].new(unit_name)
|
||
unit_modbus.port = unit_profile[0]
|
||
unit_modbus.params = unit_profile.slice(1)
|
||
units[unit_name] = unit_modbus
|
||
units_modbus[unit_name] = unit_modbus
|
||
bind_modbuses.append([unit_modbus, unit_name])
|
||
|
||
if proto in SERIAL_PROTO:
|
||
var unit_instance = SERIAL_PROTO[proto].new(unit_name)
|
||
var serial: = create_serial(unit_name)
|
||
serial.connect('got_error', on_serial_got_error.bind(serial, unit_instance))
|
||
ProjectSettings.connect('settings_changed', serial.on_settings_changed.bind(unit_instance))
|
||
units_serial[unit_name] = unit_instance
|
||
units[unit_name] = unit_instance
|
||
serials[unit_name] = serial
|
||
bind_serials.append([serial, unit_instance, unit_name])
|
||
|
||
if proto in NULL_PROTO:
|
||
var unit_null = NULL_PROTO[proto].new(unit_name)
|
||
units[unit_name] = unit_null
|
||
|
||
|
||
tcp_sockets.append([create_socket_tcp('уарэп-5п28'), units_tcp['уарэп-5п28']])
|
||
for key: StringName in dst_ports:
|
||
port_to_unit_name[dst_ports[key]] = key
|
||
if logger_page:
|
||
for key in units:
|
||
var u = units[key]
|
||
u.connect('line_changed', Callable(logger_page, 'on_line_changed'))
|
||
u.connect('command_fail', Callable(logger_page, 'on_command_fail'))
|
||
|
||
|
||
func _on_settings_changed_modbus(bind_modbuses: Array):
|
||
var _external_ctl = ProjectSettings.get_setting('application/config/Внешнее управление', false)
|
||
for bm in bind_modbuses:
|
||
var _unit_modbus = bm[0]
|
||
var _unit_name = bm[1]
|
||
|
||
|
||
func poll_receive_udp(sock: SocketUDP) -> 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: = StringName('%s:%d' % [addr, port])
|
||
if addr_port_to_unit_name.has(addr_port):
|
||
var unit_name: = addr_port_to_unit_name[addr_port]
|
||
units[unit_name].parse(data, tick)
|
||
continue
|
||
if port_to_unit_name.has(port):
|
||
var unit_name: = port_to_unit_name[port]
|
||
if units.has(unit_name):
|
||
units[unit_name].parse(data, tick)
|
||
continue
|
||
var local_port: = sock.get_local_port()
|
||
if port_to_unit_name.has(local_port):
|
||
var unit_name: = port_to_unit_name[local_port]
|
||
var unit_udprx: = units[unit_name]
|
||
unit_udprx.source_addr = [addr, port]
|
||
unit_udprx.parse(data, tick)
|
||
continue
|
||
return false
|
||
|
||
|
||
func poll_receive_tcp(sock: SocketTCP) -> bool:
|
||
if sock.is_connection_available(): # Проверить, если кто то пытается подключиться
|
||
var client: = sock.take_connection() # Принять соединение
|
||
sock.peerstream.set_stream_peer(client) # Привязать поток к новому клиенту
|
||
log.info('подключен: %s:%d к порту: %d' % [ client.get_connected_host(), client.get_connected_port(), client.get_local_port() ])
|
||
var peer = sock.peerstream.get_stream_peer()
|
||
if peer:
|
||
while true:
|
||
var peer_len_data: = peer.get_available_bytes()
|
||
if peer_len_data <= 0:
|
||
break
|
||
get_client_data(sock, peer, peer_len_data)
|
||
if sock.unit_name in units_tcp and sock.rx_all:
|
||
units_tcp[sock.unit_name].parse(sock.rx_data, tick)
|
||
return false
|
||
|
||
|
||
func get_client_data(sock: SocketTCP, peer: StreamPeer, peer_len_data: int) -> void:
|
||
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: StreamPeer, len_rx: int):
|
||
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:
|
||
tick = Time.get_ticks_msec()
|
||
RenderingServer.global_shader_parameter_set('tick_curent', tick)
|
||
for unit_name: StringName in units_udptx:
|
||
var unit_udptx: = units_udptx[unit_name]
|
||
if Error.OK == unit_udptx.process(tick):
|
||
var sock: = send_sockets[unit_name]
|
||
var tx_data: = unit_udptx.tx_data.slice(0, unit_udptx.tx_len)
|
||
var dst_port: = dst_ports[unit_name]
|
||
var dst_addr: = dst_addrs[unit_name]
|
||
sock.send_to([dst_addr, dst_port], tx_data)
|
||
for unit_name: StringName in units_udprx:
|
||
var unit_udprx: = units_udprx[unit_name]
|
||
unit_udprx.process(tick)
|
||
for unit_name: StringName in units_serial:
|
||
var unit_serial: = units_serial[unit_name]
|
||
var serial: = serials[unit_name]
|
||
if Error.OK == unit_serial.process(tick):
|
||
serial.send_to(unit_serial.tx_data)
|
||
for unit_name: StringName in units_modbus:
|
||
var unit_modbus: = units_modbus[unit_name]
|
||
unit_modbus.process(tick)
|
||
for sock_unit: Array in tcp_sockets:
|
||
var sock = sock_unit[0]
|
||
var unit_tcp = sock_unit[1]
|
||
if Error.OK == unit_tcp.process(tick):
|
||
sock.send_to(unit_tcp.tx_data)
|
||
poll_receive_tcp(sock)
|
||
poll_sockets.any(poll_receive_udp)
|
||
|
||
|
||
## [param unit_name] - Уникальное имя устройства[br]
|
||
func get_unit_instance(unit_name: StringName) -> unit.Unit:
|
||
tools.allways(unit_name in units, 'нет такого устройства: \"%s\"' % unit_name)
|
||
return units[unit_name]
|