class_name sch_3 extends Node

const ONLINE_TIMEOUT = 5000


## Представление прибора Щ3
class Sch3 extends unit.Unit:
    enum Sch3Fsm {
    READ_INPUT,
    READ_HOLDING,
    WRITE,
    CONNECT,
    IDLE,
    DISCONNECT }

    var step_fsm: Sch3Fsm = Sch3Fsm.CONNECT
    var base_input_address: int = 0
    var regs_input_count: int = 5
    var base_holding_address: int = 0
    var regs_holding_count: int = 0
    var request_period: int = 300
    var reconnect_period: int = ONLINE_TIMEOUT
    var reconnect_failed: bool = false
    var write_stack: Array
    var tick_fsm: int = 0
    var mbc: = ModbusClientRtu.new()
    var tick_fsm_delta: = reconnect_period
    var port: String
    var params: Array


    func try_disconnect():
        mbc.thread_stop()
        mbc.queue_clear()
        mbc.flush()
        mbc.close()

    func try_connect() -> bool:
        var rc: = mbc.open(port, params[0], params[1], params[2], params[3], params[4])
        if rc != Error.OK:  # TODO: убрать это отсюда (неуместный вывод сообщения)
            if not reconnect_failed:
                log.error('%s: невозможно открыть \"%s\" %s' % [name, port, params])
                reconnect_failed = true
            return false
        if reconnect_failed:  # TODO: убрать это отсюда (неуместный вывод сообщения)
            log.warning('%s: открыто после сбоя \"%s\" %s' % [name, port, params])
            reconnect_failed = 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:
        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.WRITE
        elif step_fsm == Sch3Fsm.CONNECT:
            try_disconnect()
            try_connect()
            mbc.request_read_input(base_input_address, regs_input_count)
            cmd_state = CmdState.WAIT
            tx_tick = tick
        elif step_fsm == Sch3Fsm.DISCONNECT:
            try_disconnect()
            step_fsm = Sch3Fsm.IDLE
        elif step_fsm == Sch3Fsm.IDLE:
            pass


    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(unit_name: String):
        name = unit_name
        mbc.connect('read', on_read)
        mbc.connect('read_input', on_read_input)
        mbc.connect('write', on_write)
        ProjectSettings.connect('settings_changed', _on_settings_changed)

    func _on_settings_changed():
        var external_ctl = ProjectSettings.get_setting('application/config/Внешнее управление', false)
        if external_ctl and (step_fsm not in [Sch3Fsm.DISCONNECT, Sch3Fsm.IDLE]):
            step_fsm = Sch3Fsm.DISCONNECT
        elif (not external_ctl) and (step_fsm in [Sch3Fsm.DISCONNECT, Sch3Fsm.IDLE]):
            var unit_sch3 = network.get_unit_instance('уарэп-щ3')
            var unit_prof = ProjectSettings.get_setting('application/config/%s' % unit_sch3.name)
            if unit_prof:
                unit_sch3.port = unit_prof[0]
                unit_sch3.params = unit_prof.slice(1, -1)
            step_fsm = Sch3Fsm.CONNECT


    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('command_done', self)
            emit_signal(code, base_addr, data)
            if not online:
                online = true
                emit_signal('line_changed', self)
                step_fsm = Sch3Fsm.READ_INPUT
                tick_fsm_delta = request_period
        else:
            if cmd_state == CmdState.WAIT:
                cmd_state = CmdState.FAIL
                if online:
                    emit_signal('command_fail', self)
            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:
            if (tick - rx_tick) > ONLINE_TIMEOUT:
                online = false
                if cmd_state == CmdState.WAIT:
                    cmd_state = CmdState.FAIL
                    emit_signal('command_fail', self)
                emit_signal('line_changed', self)
                if step_fsm != Sch3Fsm.IDLE:
                    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]
    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]
    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]
    func on_write(return_code: Error, base_addr: int, data: Array):
        parse(return_code, base_addr, data, 'write')
