Рефактор. Чистка от udp-json протокола на python-драйвере modbus

This commit is contained in:
sasha80
2025-03-20 16:03:54 +03:00
parent d2cfe4fe8e
commit 9775290848
3 changed files with 170 additions and 266 deletions

View File

@@ -8,132 +8,88 @@ extends 'res://scenes/контроль/элемент-я.gd'
## информационного взаимодействия изделия 5П-28
## с аппаратурой УА-РЭП на кораблях проекта 23900
@export var base_input_address: = 0 ## Базовый адрес регистров входов
@export var base_holding_address: = 0 ## Базовый адрес регистров хранения
@export var regs_input_count: = 5 ## Количество регистров входов
@export var regs_holding_count: = 4 ## Количество регистров хранения
@export var modbus_address: = 7 ## Адрес устройства на шине Modbus
@export var request_period: = 0.2 ## Период в секундах отправки команд на шину Modbus
@export var reconnect_period: = 3.0 ## Период в секундах
@export var base_input_address: = 0 ## Базовый адрес регистров входов
@export var base_holding_address: = 0 ## Базовый адрес регистров хранения
@export var regs_input_count: = 5 ## Количество регистров входов
@export var regs_holding_count: = 4 ## Количество регистров хранения
@export var modbus_address: = 7 ## Адрес устройства на шине Modbus
@export var request_period: = 200 ## Период в секундах отправки команд на шину Modbus
@export var reconnect_period: = 3000 ## Период переподключения в секундах
var regs_input: = {} ## Регистры входные
var regs_holding: = {} ## Регистры хранения
var write_stack: = [] ## Стек команд для записи
var switches_sync: = false
var timer = Timer.new()
var timer_reconnect = Timer.new()
var step_fsm: = Sch3Fsm.CONNECT
var regs_input: = {} ## Регистры входные
var regs_holding: = {} ## Регистры хранения
var switches_sync: = false ## Флаг. Если установлен, то можно переключать
enum Sch3Fsm {
READ_INPUT,
READ_HOLDING,
WRITE,
CONNECT }
func on_error(msg): push_error(msg)
class StackItem:
var addr: = 0
var data = null
func _init(a: int, d): addr = a; data = d
func _to_string() -> String: return '%s: %s' % [addr, data]
func on_data_received(_data: PackedByteArray): pass
func _on_timeout(unit_sch3, mbc: ModbusClientRtu) -> void:
push_warning(step_fsm)
if step_fsm == Sch3Fsm.WRITE:
timer.wait_time = request_period
if write_stack.size():
var ad = write_stack.pop_front()
mbc.write(ad.addr, ad.data)
step_fsm = Sch3Fsm.READ_INPUT
elif step_fsm == Sch3Fsm.READ_INPUT:
unit_sch3.read_input(base_input_address, regs_input_count)
elif step_fsm == Sch3Fsm.READ_HOLDING:
unit_sch3.read_holding(base_holding_address, regs_holding_count)
elif step_fsm == Sch3Fsm.CONNECT:
timer.wait_time = reconnect_period
mbc.queue_clear()
mbc.flush()
mbc.try_reconnect()
mbc.request_read(base_input_address, regs_input_count)
## Вызывается, когда состояния прибора Щ3 "на связи" изменяется[br]
## [param unit_sch3] - Экземпляр представления прибора Щ3
func on_line_changed_sch3(online: bool):
for item in get_tree().get_nodes_in_group('bits_items_good'):
item.texture = textures[STATE_VAL.NONE]
if not online:
for item in get_tree().get_nodes_in_group('bits_items_switch'):
item.disabled = true
regs_holding.clear()
regs_input.clear()
switches_sync = false
$margin/vbox/grid1/lbl_freq.text = '<нет данных>'
$margin/vbox/grid1/state_freq.texture = textures[STATE_VAL.NONE]
$margin/vbox/grid/state.texture = textures[STATE_VAL.NONE]
## Вызывается при изменении состояния выключателя[br]
## [param val] - Состояние выключателя[br]
## [param meta] - Адреса регистров и индексы битов назначенных выключателю[br]
func on_toggled_switch(val: bool, meta: Array):
func on_toggled_switch(val: bool, meta: Array, unit_sch3):
var on_addr = meta[0]
var on_bit = meta[1]
var off_addr = meta[2]
var off_bit = meta[3]
if regs_holding.has(on_addr) and regs_holding.has(off_addr):
regs_holding[on_addr] = tools.set_bit(regs_holding[on_addr], on_bit, val)
regs_holding[on_addr] = tools.set_bit(regs_holding[on_addr], on_bit, val)
regs_holding[off_addr] = tools.set_bit(regs_holding[off_addr], off_bit, not val)
push_write_holding()
unit_sch3.push_write_holding(regs_holding)
## Заносит в очередь команду чтения регистров хранения
func push_write_holding():
var regs_data: = []
var keys = regs_holding.keys()
keys.sort() # Ключи в словаре хранятся не упорядочено
for addr in keys:
regs_data.append(regs_holding[addr])
write_stack.push_back(StackItem.new(keys[0], regs_data))
func _ready() -> void:
func _enter_tree() -> void:
super._enter_tree()
if Engine.is_editor_hint(): return
var unit_key = settings.get_unit_key(unit_name)
var unit_sch3 = network.units[unit_key]
unit_sch3.mb_addr = modbus_address
var items = get_tree().get_nodes_in_group('bits_items_switch')
for item in items:
var meta = item.get_meta('bits')
item.connect('toggled', on_toggled_switch.bind(meta))
item.connect('toggled', on_toggled_switch.bind(meta, unit_sch3))
item.disabled = true
unit_sch3.connect('resp_input', on_resp_input)
unit_sch3.connect('resp_holding', on_resp_holding)
unit_sch3.connect('command_fail', on_command_fail)
var mbc = network.mbcs[unit_key]
timer.connect('timeout', _on_timeout.bind(unit_sch3, mbc))
add_child(timer)
timer.start(request_period)
unit_sch3.base_holding_address = base_holding_address
unit_sch3.regs_holding_count = regs_holding_count
unit_sch3.base_input_address = base_input_address
unit_sch3.regs_input_count = regs_input_count
unit_sch3.request_period = request_period
unit_sch3.reconnect_period = reconnect_period
unit_sch3.connect('read', on_read.bind(unit_sch3))
unit_sch3.connect('read_input', on_input)
unit_sch3.connect('error', on_error)
unit_sch3.connect('line_changed', on_line_changed_sch3)
func on_command_fail(unit_sch3):
unit_sch3.cmd_state = unit.Unit.CmdState.DONE
## Вызывается, когда состояния прибора Щ3 "на связи" изменяется[br]
## [param unit_sch3] - Экземпляр представления прибора Щ3
func on_line_changed(unit_sch3: sch_3.Sch3):
for item in get_tree().get_nodes_in_group('bits_items_good'):
item.texture = textures[STATE_VAL.NONE]
if not unit_sch3.online:
for item in get_tree().get_nodes_in_group('bits_items_switch'):
item.disabled = true
regs_holding.clear()
regs_input.clear()
write_stack.clear()
switches_sync = false
$margin/vbox/grid1/lbl_freq.text = '<нет данных>'
$margin/vbox/grid1/state_freq.texture = textures[STATE_VAL.NONE]
$margin/vbox/grid/state.texture = textures[STATE_VAL.NONE]
step_fsm = Sch3Fsm.CONNECT
else:
timer.wait_time = request_period
func _exit_tree() -> void:
if Engine.is_editor_hint(): return
var unit_key = settings.get_unit_key(unit_name)
var unit_sch3 = network.units[unit_key]
unit_sch3.disconnect('read', on_read.bind(unit_sch3))
unit_sch3.disconnect('read_input', on_input.bind(unit_sch3))
unit_sch3.disconnect('error', on_error)
unit_sch3.disconnect('line_changed', on_line_changed_sch3)
## Вызывается по сигналу "приняты данные чтения регистров хранения"[br]
## [param base_addr] - Начальный адрес регистров.[br]
## [param data] - Массив значений прочитанных из регистров.
func on_resp_holding(base_addr: int, data: Array):
step_fsm = Sch3Fsm.WRITE
func on_read(base_addr: int, data: Array, unit_sch3):
if (base_addr != base_holding_address) or (data.size() < regs_holding_count):
return
for i in data.size():
@@ -151,14 +107,13 @@ func on_resp_holding(base_addr: int, data: Array):
item.button_pressed = val0
item.disabled = false
regs_holding[byte1] = tools.set_bit(regs_holding[byte1], bit1, not val0)
push_write_holding()
unit_sch3.push_write_holding(regs_holding)
## Вызывается по сигналу "приняты данные чтения регистров входов".[br]
## [param base_addr] - Начальный адрес регистров.[br]
## [param data] - Массив значений прочитанных из регистров.[br]
func on_resp_input(base_addr: int, data: Array):
step_fsm = Sch3Fsm.READ_HOLDING
func on_input(base_addr: int, data: Array):
if !is_inside_tree():
return
if (base_addr != base_input_address) or (data.size() < regs_input_count):

View File

@@ -48,32 +48,6 @@ class SocketTCP extends TCPServer:
peer.put_data(data)
class Modbus extends ModbusClientRtu:
var unit_name
func send_to(data):
if data['code'] == 'write':
var addr = data['data'].pop_at(0)
request_write(addr, data['data'])
elif data['code'] == 'read_holding':
request_read(data['data'][0], data['data'][1])
elif data['code'] == 'read_input':
request_read_input(data['data'][0], data['data'][1])
func try_reconnect():
var port = settings.UnitProfiles[unit_name][1]
var params = settings.UnitProfiles[unit_name][2]
thread_stop()
close()
var rc = open(port, params[0], params[1], params[2], params[3], params[4])
if rc != Error.OK:
push_error('%s:%s %s' % [unit_name, port, params])
return false
set_response_timeout(params[5])
set_indication_timeout(params[5])
thread_run()
return true
var poll_sockets: Array[Socket]
var tcp_sockets: Array
var units: Dictionary
@@ -127,15 +101,9 @@ func create_serial(nm) -> Serial:
return sp
func create_modbus(nm, mbc_unit) -> Modbus:
var mbc: = Modbus.new()
mbc.connect('read', mbc_unit.on_read)
mbc.connect('read_input', mbc_unit.on_read_input)
mbc.connect('write', mbc_unit.on_write)
mbc.unit_name = nm
mbc.try_reconnect()
mbc.thread_run()
return mbc
func create_modbus(nm) -> sch_3.Sch3:
var unit_sch3: = sch_3.Sch3.new(nm)
return unit_sch3
func _ready() -> void:
@@ -193,10 +161,8 @@ func _ready() -> void:
if logger_page:
unit_modbus.connect('line_changed', Callable(logger_page, 'on_line_changed').bind(unit_key))
unit_modbus.connect('command_fail', Callable(logger_page, 'on_command_fail').bind(unit_key))
var mbc = create_modbus(unit_name, unit_modbus)
units_modbus[unit_key] = unit_modbus
units[unit_key] = unit_modbus
mbcs[unit_key] = mbc
if proto in SERIAL_PROTO:
var unit_serial = SERIAL_PROTO[proto].new(unit_name)
@@ -299,10 +265,7 @@ func _process(_delta: float) -> void:
Error.FAILED: emit_signal('socket_error', [unit_serial, serial])
for key in units_modbus:
var unit_modbus = units_modbus[key]
var mbc = mbcs[key]
match unit_modbus.process(tick):
Error.OK: mbc.send_to(unit_modbus.mb_data)
Error.FAILED: emit_signal('socket_error', [unit_modbus, mbc])
unit_modbus.process(tick)
for sock_unit in tcp_sockets:
var sock = sock_unit[0]
var unit_tcp = sock_unit[1]

View File

@@ -1,162 +1,148 @@
class_name sch_3 extends Node
## Модуль Прибора Щ3
const ONLINE_TIMEOUT: int = 5000 ## Время ожидания данных
const REQUEST_PERIOD: int = 1000 ## Переиод запроса, мс
const ONLINE_TIMEOUT = 5000
## Представление прибора Щ3
class Sch3 extends unit.Unit:
enum Sch3Fsm {
READ_INPUT,
READ_HOLDING,
WRITE,
CONNECT }
var mb_addr: = 0 ## Адрес устройства Modbus.
var cmd_state: = CmdState.DONE ## Состояние выполнения текущей команды.
var mb_data: Dictionary = {} ## Буфер для записи в регистры модбас
var step_fsm: Sch3Fsm = Sch3Fsm.CONNECT
var base_input_address: int = 0
var regs_input_count: int = 0
var base_holding_address: int = 0
var regs_holding_count: int = 0
var request_period: int = 300
var reconnect_period: int = ONLINE_TIMEOUT
var write_stack: Array
var tick_fsm: int = 0
var mbc: = ModbusClientRtu.new()
var tick_fsm_delta: = reconnect_period
static var json_conv: = JSON.new()
signal resp_input(addr: int, data: Array) ## Вызывается при получении сообщения c данными из регистров входов.
signal resp_holding(addr: int, data: Array) ## Вызывается при получении сообщения c данными из регистров хранения.
signal resp_write() ## Вызывается при получении подтверждения, что команда записи выполнена
signal info_received(msg: String) ## Вызывается при получении информационного сообщения.
signal except_received(msg: String) ## Вызывается при получении сообщения об исключительной ситуации.
func _init(nm: String, u: int = 0):
mb_addr = u
func try_connect() -> bool:
var port = settings.UnitProfiles[name][1]
var params = settings.UnitProfiles[name][2]
mbc.thread_stop()
mbc.queue_clear()
mbc.flush()
mbc.close()
var rc = mbc.open(port, params[0], params[1], params[2], params[3], params[4])
if rc != Error.OK:
push_error('%s:%s %s' % [name, port, params])
return false
mbc.set_response_timeout(params[5])
mbc.thread_run()
return true
func push_write_holding(regs: Dictionary):
var keys = regs.keys()
keys.sort()
var data: = []
for k in keys:
data.append(regs[k])
write_stack.append([keys[0], data])
func _on_timeout(tick: int) -> void:
push_warning(tick_fsm_delta)
if step_fsm == Sch3Fsm.WRITE:
while write_stack.size():
var write_command = write_stack.pop_front()
mbc.request_write(write_command[0], write_command[1])
cmd_state = CmdState.WAIT
tx_tick = tick
step_fsm = Sch3Fsm.READ_INPUT
elif step_fsm == Sch3Fsm.READ_INPUT:
mbc.request_read_input(base_input_address, regs_input_count)
cmd_state = CmdState.WAIT
tx_tick = tick
step_fsm = Sch3Fsm.READ_HOLDING
elif step_fsm == Sch3Fsm.READ_HOLDING:
mbc.request_read(base_holding_address, regs_holding_count)
cmd_state = CmdState.WAIT
tx_tick = tick
step_fsm = Sch3Fsm.WRITE
elif step_fsm == Sch3Fsm.CONNECT:
try_connect()
mbc.request_read(base_input_address, regs_input_count)
cmd_state = CmdState.WAIT
tx_tick = tick
signal write (addr: int, data: Array) ## Вызывается при получении сообщения подтверждения записи
signal read (addr: int, data: Array) ## Вызывается при получении сообщения c данными из регистров входов.
signal read_input (addr: int, data: Array) ## Вызывается при получении сообщения c данными из регистров хранения.
signal error(msg: String) ## Вызывается при получении сообщения об исключительной ситуации.
func _init(nm: String):
name = nm
mbc.connect('read', on_read)
mbc.connect('read_input', on_read_input)
mbc.connect('write', on_write)
## Изменяет состояние. Вызывать периодически.[br]
func parse(return_code: Error, base_addr: int, data: Array, code: String):
if return_code == Error.OK:
rx_tick = Time.get_ticks_msec()
if cmd_state == CmdState.WAIT:
cmd_state = CmdState.DONE
emit_signal(code, base_addr, data)
if not online:
online = true
emit_signal('line_changed', online)
step_fsm = Sch3Fsm.WRITE
tick_fsm_delta = request_period
else:
if cmd_state == CmdState.WAIT:
cmd_state = CmdState.FAIL
emit_signal('error', [code, return_code, base_addr, data])
## Изменяет состояние. Вызывается периодически.[br]
## [param tick] - Текущщее время в мс.[br]
## Возвращает [b]Error.OK[/b] - если данные готовы для отправки.[br]
## Возвращает [b]Error.ERR_BUSY[/b] - если данных для отправки нет.[br]
func process(tick: int) -> Error:
if online and ((tick - rx_tick) > ONLINE_TIMEOUT):
online = false
emit_signal('line_changed', self)
if cmd_state == CmdState.WAIT:
if (tick - tx_tick) > ONLINE_TIMEOUT:
cmd_state = CmdState.FAIL
emit_signal('command_fail', self)
var rc = Error.ERR_BUSY
if cmd_state == CmdState.SEND:
tx_tick = tick
cmd_state = CmdState.WAIT
rc = Error.OK
return rc
## Производит разбор принятых данных. Вызывать при приёме данных на сокете.[br]
## [param data] - Данные принятые на сокете.[br]
## [param tick] - Текущщее время в мс.[br]
func parse(message: Dictionary) -> bool:
if not online and (message['code'] != 'exception'):
online = true
emit_signal('line_changed', self)
if message.has('code') and message.has('data'):
var rc: = true
var message_code = message['code']
var message_data = message['data']
match message_code:
'resp_holding', 'resp_input':
var addr = int(message_data[0])
var regs = message_data.slice(1)
for i in regs.size():
regs[i] = int(regs[i])
emit_signal(message_code, addr, regs)
'resp_write':
emit_signal(message_code)
'info':
var msg = message['data']
emit_signal('info_received', msg)
'exception':
rc = false
_:
var msg: String = 'код команды \"%s\" - не распознан' % message_code
if online:
if (tick - rx_tick) > ONLINE_TIMEOUT:
online = false
if cmd_state == CmdState.WAIT:
cmd_state = CmdState.FAIL
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', self)
return true
## Отправляет команду "записать в регистры".[br]
## [param addr] - Начальный адрес регистров.[br]
## [param count] - Данные для записи в регистры от начального адреса.[br]
func write(addr: int, data: Array) -> bool:
if cmd_state != CmdState.DONE: return false
mb_data = {'code': 'write', 'data': [addr] + data, 'unit': mb_addr}
cmd_state = CmdState.SEND
return true
## Отправляет команду "читать регистры хранения".[br]
## [param addr] - Начальный адрес регистров.[br]
## [param count] - Количество регистров от начального адреса.[br]
func read_holding(addr: int, count: int = 1) -> bool:
if cmd_state != CmdState.DONE: return false
mb_data = {'code': 'read_holding', 'data': [addr, count], 'unit': mb_addr}
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
mb_data = {'code': 'read_input', 'data': [addr, count], 'unit': mb_addr}
cmd_state = CmdState.SEND
return true
emit_signal('command_fail', name)
emit_signal('line_changed', online)
step_fsm = Sch3Fsm.CONNECT
tick_fsm_delta = reconnect_period
if (tick - tick_fsm) > tick_fsm_delta:
tick_fsm = tick
_on_timeout(tick)
return Error.ERR_BUSY
## Принимает результат команды "читать регистры хранения".[br]
## [param return_code] - Код выполнения команды.[br]
## [param base_addr] - Начальный адрес регистров.[br]
## [param data] - Данные прочитанные из регистров.[br]
## [param tick] - Время приёма пакета.[br]
func on_read(return_code, base_addr, data):
var msg: Dictionary
if return_code == Error.OK:
rx_tick = Time.get_ticks_msec()
msg['code'] = 'resp_holding'
msg['data'] = [base_addr] + data
else:
msg['code'] = 'exception'
msg['data'] = 'операция чтения входных регистров не выполнена (%d)' % base_addr
parse(msg)
func on_read(return_code: Error, base_addr: int, data: Array):
parse(return_code, base_addr, data, 'read')
## Принимает результат команды "читать регистры входов".[br]
## [param return_code] - Код выполнения команды.[br]
## [param base_addr] - Начальный адрес регистров.[br]
## [param data] - Данные прочитанные из регистров.[br]
## [param tick] - Время приёма пакета.[br]
func on_read_input(return_code, base_addr, data):
var msg: Dictionary
if return_code == Error.OK:
rx_tick = Time.get_ticks_msec()
msg['code'] = 'resp_input'
msg['data'] = [base_addr] + data
else:
msg['code'] = 'exception'
msg['data'] = 'операция чтения входных регистров не выполнена (%d)' % base_addr
parse(msg)
func on_read_input(return_code: Error, base_addr: int, data: Array):
parse(return_code, base_addr, data, 'read_input')
## Принимает результат команды "записать в регистры".[br]
## [param return_code] - Код выполнения команды.[br]
## [param base_addr] - Начальный адрес регистров.[br]
## [param data] - Данные прочитанные из регистров.[br]
## [param tick] - Время приёма пакета.[br]
func on_write(return_code, base_addr, data):
var msg: Dictionary
if return_code == Error.OK:
rx_tick = Time.get_ticks_msec()
msg['data'] = []
msg['code'] = 'resp_write'
else:
msg['data'] = 'операция записи в регистры не выполнена (%d, %s)' % [base_addr, data]
msg['code'] = 'except_received'
parse(msg)
func on_write(return_code: Error, base_addr: int, data: Array):
parse(return_code, base_addr, data, 'write')