/***************************************************************************//**
  @file ltrsrv_crates.c
  @author Borisov Alexey <borisov@lcard.ru>
  @date   06.02.2012

  Файл содержит всю логику управления крейтами, которая не зависит (по крайней
  хочется надеяться) от интерфейса.
  Здесь происходит разбор данных от крейта, распределение этих данных по клиентам
  и обработка исключительных ситуаций от крейта, а так же синхрометок.

  Кроме того, модули могут иметь спец. функции по обработке данных, которые
  вызываются отсюда, а реализованы в modules/ltrsrv_mspec_ltrxxx.c
  (сейчас это LTR34 и LTR35 - функции для управления уровнем FIFO).

  Также выполняется прием данных от клиентов, установка в них номеров слотов,
    перемешивание и отправка крейту.
  Так же в данном файле реализованы функции для получения списка зарегистрированных
    крейтов, их поиска, а так же регистрации клиентов для модулей крейтов.

  Управление крейтом данным блоком начинается послет того, как интерфейс
    проинициализирует его и зарегистрирует в системе ltrsrv_crate_register().
    (До этого крейт не видим в сервере и им управляет только модуль интерфейса)

  Прием данных от крейта (bulk):

  Данные обрабатываются в темпе прихода от крейта. Если буферы модулей переполняются,
    то взводится флаг и данные теряются (так как если один клиент тормоз и его
    модуль переполнился, остальные не должны его ждать). Кода появится возможность,
    клиенту будет передан признак о том, что было переполнение.

  Когда часть потока данных от крейта была принята интерфейсом, он вызывает функцию
    ltrsrv_crate_rcv_done(), которой передает эти данные (интерфейс заботится, чтобы
    данные были кратны размеру слова), которая разбирает данные, обрабатывает
    команды, а данные для модулей раскладывает по буферам модулей и в конце
    пытается все их передать клиентам с помощью f_send_to_clients().

  При обработке исключительных ситуаций от крейта может быть необходимо принять
    прочитать или записать регистры крейта. Для этого введена очередь команд (на
    случай, если команды надо будет выполнять с болшей скоростью, чем они поступают),
    в которую сохраняются запросы на чтение/запись. При выполнении команды
    вызывается функция интерфейса для чтения/записи регистров, после завершения
    которой вызывается callback, который обрабатывает результаты и запускает
    следующую команду из очереди (если есть).

  Кроме того, через эту очередь реализуется чтение/запись регистров крейта со
    стороны клиента. В этом случае клиент вызывает функцию
    ltrsrv_crate_start_client_cmd(), передавая в нее помимо параметров команды и
    callback с данными, который будет вызван по завершению опирации.



  Передача:
    Когда у клиента появляются данные на передачу в модуль, он вызывает функцию
      ltrsrv_notify_client_data_rdy(), по которой проверяется, что есть место
      для новых данных. Если места нет, то прием от клиента будет остановлен
      с помощью ltrsrv_client_recv_stop() (и продолжен с помщью
      ltrsrv_client_recv_resume() только когда освободится место в буфере модуля).
    Если данные приняли, то они складываются в буфер модуля, а у крейта запрашивается
      готовность на прием. Когда крейт будет готов передать данные, его интерфейс
      вызывает callback (f_send_data()), по которому данные складываются из буферов
      модулей в общий буфер крейта и делается попытка их передать (если передались
      не все, то снова устанавливается ожидание готовности на передачу).
      Кроме того, вначале в буфер крейта на передачу добавляется последовательность
      STOP-RESET-STOP для всех новых найденных модулей.

  *****************************************************************************/

#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <time.h>
#include <math.h>

#include "ltrsrv_crates.h"
#include "ltrsrv_cfg.h"
#include "ltrsrv_defs.h"
#include "ltrsrv_log.h"
#include "ltrsrv_client.h"
#include "ltrsrv_wait.h"

#include "modules/ltr34/ltrsrv_mspec_ltr34.h"
#include "modules/ltr35/ltrsrv_mspec_ltr35.h"
#include "modules/ltr01/ltrsrv_mspec_ltr01.h"

/* размер очереди команд крейту */
#define LTRSRV_CRATE_CMD_QUEUE_SIZE  32
/* максимальный размер данных в команде */
#define LTRSRV_CRATE_CMD_DATA_SIZE   16

/* соотношение слов от быстрого и медленного модулей при смешивании */
#define LTRSRV_CRATE_SKIP_SLOW_COUNT  20

/* все описатели зарегестрированных крейтов */
static t_ltr_crate *f_crates[LTRSRV_MAX_CRATES_CNT];
/* количество зарегистрированных крейтов */
static int f_crates_cnt = 0;

static uint32_t f_module_snd_buf_size = LTRSRV_MODULE_SEND_BUF_DEFAULT_SIZE;
static uint32_t f_module_rcv_buf_size = LTRSRV_MODULE_RECV_BUF_DEFAULT_SIZE;



/* код типа метки, он же начальный номер последовательности слов
   интервал между значениями не менее 3 */
typedef enum {
    LTR_MARK_SEQ_NONE     = 0,
    LTR_MARK_SEQ_UNIXTIME = 10,
    LTR_MARK_SEQ_IRIG     = 20
} t_ltr_mark_seq_type;

typedef enum {
    LTR_MODULE_CLOSE_FLAG_SAVE_BAUD = 0x01 /** сохранить установленную скорость для сбрасываемого модуля */
} t_ltr_module_close_flags;


/** описание команды в очереди */
typedef struct {
    t_crate_cmd_code code;
    t_ltr_crate_cmd_reason reason;
    uint32_t req;
    uint32_t param;
    uint32_t snd_size;
    uint32_t rcv_size;
    uint32_t *recvd_size;
    uint8_t data[LTRSRV_CRATE_CMD_DATA_SIZE];
    t_ltr_crate_cmd_cb cb;
    void *cb_data;
    uint8_t* ext_buf;
    int flags; /* флаги из t_cmd_flags */
} t_ctl_cmd;



typedef struct  {
    uint16_t modules_mask;
    int16_t therm_vals[2];
    uint16_t res[128-3];
} t_ltr_crate_poll_state;


/** структура, описывающее состояние разбора данных модулей, создается при
    регистрации крейта и непрозрачна для остальных файлов */
typedef struct {
    int spec_cmd; /** признак, что при разборе потока  следующее слово
                      - исключительная ситуация */
    uint32_t slow_skip_cnt;
    t_ctl_cmd cmd_queue[LTRSRV_CRATE_CMD_QUEUE_SIZE]; /** очередь команд крейту */
    int cmd_cnt;   /** количество команд в очереди */
    int cmd_put_pos; /** позичия в очереди, куда будет добавлена следующая команда */
    int cmd_snd_pos; /** позиция выполняющейсая сейчас команды или следующей, если нет выполняющихся*/
    uint8_t* snd_buf; /** временный буфер на передачу в крейт */
    int snd_send_pos; /** номер байта, который будет передан в крейт следующим из snd_buf */
    int snd_def_size; /** количество не переданных байт в snd_buf */
    int snd_put_pos; /** номер байта в snd_buf, куда будет сохранен следующий байт
                          от модуля */
    int snd_buf_size; /** размер буфера snd_buf в байтах */
    uint16_t baud_rate; /** текущая маска скорости модулей */
#ifdef LTRD_BAND_STATISTIC_ENABLED
    t_lclock_ticks bw_last_check; /**< Время последней проверки скорости передачи */
    uint64_t bw_last_wrd_sent; /** Количество переданных слов при последней проверке скорости передачи */
    uint64_t bw_last_wrd_recv; /** Количество принятых слов при последней проверке скорости передачи */
#endif
    uint8_t mark_seq; /**< номер обрабатываемого слова unixtime-метки */
    uint32_t mark_wrds[3]; /**< массив для сохранения слов метки IRIG или UNIXTIME */


    t_ltr_crate_poll_state poll_state; /**< переменная для сохранения статуса опроса */
    uint32_t poll_recvd_size; /**< принятый размер данных в poll_state */

    uint16_t therm_show_msk; /**< маска показанных термометров в сообщении */
    float therm_show_vals[LTR_CRATE_THERM_MAX_CNT]; /**< значения термометров, показанные в последнем сообщении */
} t_proc_data;



#define _CRATE_CHECK_HND(hnd) (hnd) == NULL ? LTRSRV_ERR_CRATE_HANDLE : \
    (hnd)->proc_data == NULL ? LTRSRV_ERR_CRATE_UNREGISTRED : 0

static int f_cmd_cb(t_ltr_crate* crate, int cmd_err, void* prv_data);
static int f_module_init(t_ltr_crate* crate, int slot, uint16_t mid, uint32_t init_flags);
static int f_module_close(t_ltr_crate *crate, t_ltr_module *module, unsigned close_flags);
static int f_put_recv_word(t_ltr_crate* crate, int slot, uint32_t wrd);
static int f_send_to_clients(t_ltr_crate* crate);

static t_ltrsrv_str_tbl f_cmd_reason_str[] = {
    {LTR_CRATE_CMD_REASON_GET_MODULES, "Get modules configuration"},
    {LTR_CRATE_CMD_REASON_SYSFAULT,    "Read crate register after sysfault"},
    {LTR_CRATE_CMD_REASON_BAUD_CH,     "Write new baudrate mask"},
    {LTR_CRATE_CMD_REASON_CLIENT_REQ,  "Client request to crate"},
    {LTR_CRATE_CMD_REASON_SLOT_CFG,    "Get slots config"},
    {LTR_CRATE_CMD_REASON_CRATE_POLL,  "Crate status polling"}
};

static const t_ltr_crate_therm_limit f_ltr032_therm_limits = {-40.F, -30.F, 70.F, 85.F};

#define LOGSTR_CMDREASON(cmd_r) ltrsrv_log_get_str(cmd_r, f_cmd_reason_str, sizeof(f_cmd_reason_str)/sizeof(f_cmd_reason_str[0]))

#define PUT_CRATE_WRD(proc, wrd)  do {\
    *((uint32_t*)&proc->snd_buf[proc->snd_put_pos])=(wrd);\
    proc->snd_put_pos += sizeof(uint32_t);\
    if (proc->snd_put_pos  == proc->snd_buf_size)\
        proc->snd_put_pos = 0;\
    proc->snd_def_size += sizeof(uint32_t);\
    } while (0)


#define BCD_DECODE(bcd) ((((bcd) >> 4) & 0xF) * 10 + ((bcd) & 0xF))


#define CHECK_MODULE_SND_RDY(mod)  (!CYCLEBUF_IS_EMPTY(&(mod)->sndbuf) && \
    (!((mod)->flags & LTR_MODULE_FLAGS_USE_SND_FIFO_STATUS) || ((mod)->stat.hard_send_fifo_full!=(mod)->stat.hard_send_fifo_size)))

static int f_send_data(t_ltr_crate* crate);

static int f_cmd_ping_cb(struct st_ltr_crate* crate, int err, void* prv_data) {
    if (err) {
        ltrsrv_crate_close_req(crate, err);
        ltrsrv_log_rec(LTRSRV_LOGLVL_ERR, crate->log_str, err, "Crate ping failed");
    }
    return err;
}

static void f_snd_tmr_cb(t_socket sock, int event, void* data) {
    if (event==LTRSRV_EVENT_TOUT)
        f_send_data((t_ltr_crate*)data);
}




#ifdef HAVE_UINT64
/* преобразование struct tm в 64-битный unixtime
    (основана на http://www.2038bug.com/pivotal_gmtime_r.c.html) */
static const int days[4][13] = {
    {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
    {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
    {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365},
    {0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366},
};

#define LEAP_CHECK(n)   ((!(((n) + 1900) % 400) || (!(((n) + 1900) % 4) && (((n) + 1900) % 100))) != 0)

uint64_t mktime64 (struct tm * t) {
    int i, y;
    long day = 0;
    uint64_t r;
    if (t->tm_year < 70) {
        y = 69;
        do {
            day -= 365 + LEAP_CHECK (y);
            y--;
        } while (y >= t->tm_year);
    } else {
        y = 70;
        while (y < t->tm_year) {
            day += 365 + LEAP_CHECK (y);
            y++;
        }
    }
    for (i = 0; i < t->tm_mon; i++)
        day += days[LEAP_CHECK (t->tm_year)][i];
    day += t->tm_mday - 1;
    t->tm_wday = (int) ((day + 4) % 7);
    r = (uint64_t) day *86400;
    r += t->tm_hour * 3600;
    r += t->tm_min * 60;
    r += t->tm_sec;
    return r;
}
#endif


static void f_module_init_done_check_cb(t_socket sock, int event, void *data) {
    t_ltr_module *module = (t_ltr_module *)data;
    t_ltr_crate *crate = module->crate;
    int err = _CRATE_CHECK_HND(module->crate);
    if (!err && (module->state == LTR_MODULE_STATE_INIT)) {
        int slot = module - crate->modules;
        if (module->rst_retry_cnt < LTRSRV_MODULE_RST_RESTART_MAX_CNT) {
            ++module->rst_retry_cnt;
            module->state = LTR_MODULE_STATE_NEED_RST;

            ltrsrv_log_rec(LTRSRV_LOGLVL_DETAIL, crate->log_str, 0, "Module in slot %d initialization timeout. Retry module reset (%d/%d)",
                           slot + 1, module->rst_retry_cnt, LTRSRV_MODULE_RST_RESTART_MAX_CNT);
            f_send_data(crate);
        } else {
            ltrsrv_log_rec(LTRSRV_LOGLVL_ERR, crate->log_str, 0, "Module in slot %d initialization timeout. Retry limit exceeded. No more attempt until crate reconnect", slot + 1);
        }
    }
}


static void f_module_subid_done_check_cb(t_socket sock, int event, void *data) {
    t_ltr_module *module = (t_ltr_module *)data;
    t_ltr_crate *crate = module->crate;
    int err = _CRATE_CHECK_HND(module->crate);
    if (!err && (module->state == LTR_MODULE_STATE_WAIT_SUBID)) {
        int slot = module - crate->modules;
        if (module->subid_retry_cnt < LTRSRV_MODULE_SUBID_RESTART_MAX_CNT) {
            ++module->subid_retry_cnt;
            module->state = LTR_MODULE_STATE_NEED_GET_SUBID;

            ltrsrv_log_rec(LTRSRV_LOGLVL_DETAIL, crate->log_str, 0, "Module in slot %d get subid timeout. Retry (%d/%d)",
                           slot + 1, module->subid_retry_cnt, LTRSRV_MODULE_SUBID_RESTART_MAX_CNT);
            f_send_data(crate);
        } else {
            module->state = LTR_MODULE_STATE_WORK;
            ltrsrv_log_rec(LTRSRV_LOGLVL_WARN, crate->log_str, 0, "LTR01 module doesn't respond to subid request", slot + 1);

        }
    }
}

/** Попытка передать данные крейту. В общей буфер крейта складываются
     STOP-RESET-STOP для новых модулей и данные из буферов разных модулей
     и делается попытка передать все это (и то что осталось с прошлого раза
     не переданным */
static int f_send_data(t_ltr_crate *crate) {
    int err = _CRATE_CHECK_HND(crate);


    if (!err) {
        int sent;
        int slot;
        int fast_mask = 0, slow_mask = 0;
        int snd_wait = 0;

        t_proc_data *proc = (t_proc_data *)crate->proc_data;

        /* вначале проверяем, есть ли модули для которых нужно послать START-RESET-STOP */
        for (slot = 0; !err && (slot < crate->modules_cnt); ++slot) {
            t_ltr_module *module = &crate->modules[slot];
            if (module->state == LTR_MODULE_STATE_NEED_RST) {
                if ((proc->snd_buf_size - proc->snd_def_size) > 4*4) {
                    /* использую последовательность STOP-STOP-RESET-STOP, так как
                      некоторые модули (например LTR11), не отвечают на STOP-RESET-STOP,
                      если был запущен сбор данных */
                    PUT_CRATE_WRD(proc, LTR_CRATE_STREAM_SLOT(slot) | LTR_CRATE_STREAM_CMD_STOP);
                    PUT_CRATE_WRD(proc, LTR_CRATE_STREAM_SLOT(slot) | LTR_CRATE_STREAM_CMD_STOP);
                    PUT_CRATE_WRD(proc, LTR_CRATE_STREAM_SLOT(slot) | LTR_CRATE_STREAM_CMD_RESET);
                    PUT_CRATE_WRD(proc, LTR_CRATE_STREAM_SLOT(slot) | LTR_CRATE_STREAM_CMD_STOP);

                    module->crate = crate;
                    module->state = LTR_MODULE_STATE_INIT;
                    UINT64_ADD32(module->stat.wrd_sent, 4);

                    ltrsrv_wait_add_tmr(&module->rst_retry_cnt, LTRSRV_WAITFLG_AUTODEL,
                                        LTRSRV_MODULE_RST_DONE_CHECK_TIME,
                                        f_module_init_done_check_cb, module);
                }
            } else if (module->state == LTR_MODULE_STATE_NEED_GET_SUBID) {
                if ((proc->snd_buf_size - proc->snd_def_size) > 4) {
                    PUT_CRATE_WRD(proc, LTR_CRATE_STREAM_SLOT(slot) | LTR01_CMD_SUBID);
                    module->state = LTR_MODULE_STATE_WAIT_SUBID;
                    UINT64_ADD32(module->stat.wrd_sent, 1);

                    ltrsrv_wait_add_tmr(&module->subid_retry_cnt, LTRSRV_WAITFLG_AUTODEL,
                                        LTRSRV_MODULE_SUBID_DONE_CHECK_TIME,
                                        f_module_subid_done_check_cb, module);
                }
            }
        }

        /* смотрим, в каких модулях есть данные, и распределяем по скоростям */
        for (slot = 0; !err && (slot < crate->modules_cnt); ++slot){
            if (CHECK_MODULE_SND_RDY(&crate->modules[slot])) {
                //ltrsrv_log_rec(LTRSRV_LOGLVL_DBG_LOW, crate->log_str, 0, "Ready send data for slot %d. rdy size  %d",
                //               slot, crate->modules[slot].snd_wrd_cnt);
                if (crate->modules[slot].flags & LTR_MODULE_FLAGS_HIGH_BAUD)
                    fast_mask |= (1UL << slot);
                else
                    slow_mask |= (1UL << slot);
            }
        }



        /* Добавляем данные из модулей в общий буфер */
        if (!err) {
            int msk;

            while ((fast_mask || slow_mask) && (proc->snd_def_size!=proc->snd_buf_size)) {
                /* если есть быстрые модули - крутим счетчик пропуска медленных */
                if (fast_mask) {
                    if (!proc->slow_skip_cnt) {
                        proc->slow_skip_cnt = LTRSRV_CRATE_SKIP_SLOW_COUNT - 1;
                    } else {
                        proc->slow_skip_cnt--;
                    }
                } else {
                    // Иначе сбрасываем счетчик и разрешаем чтение из медленных
                    proc->slow_skip_cnt = 0;
                }

                for (slot=0, msk=1; (slot < crate->modules_cnt) &&
                     (proc->snd_def_size!=proc->snd_buf_size); slot++, msk<<=1) {
                    /* пытаемся взять данные от быстрго модуля всегда, а от медленного
                       только если счетчик попуска медленных в нуле */
                    if ((fast_mask & msk) || (!proc->slow_skip_cnt  && (msk&slow_mask))) {
                        t_ltr_module *module = &crate->modules[slot];
                        uint32_t wrd = *CYCLEBUF_GETPTR(&module->sndbuf);
                        /* устанавливаем поле слота */
                        wrd &= ~LTR_CRATE_STREAM_SLOT_Msk;
                        wrd |= LTR_CRATE_STREAM_SLOT(slot);

                        if ((module->func.proc_snd==NULL)
                                || (!module->func.proc_snd(crate, module, wrd))) {
                            PUT_CRATE_WRD(proc, wrd);
                            CYCLEBUF_GET_WORD_DONE(&module->sndbuf);
                            /* обновляем статистику */
                            UINT64_INC(module->stat.wrd_sent);
                        }



                        /* если больше данных нет на передачу, то снимаем маску
                           о наличии данных. можно снимать обе, так как установлена
                           всегда только одно - а проверять какую думаю дольше */
                        if (!CHECK_MODULE_SND_RDY(module)) {
                            slow_mask &= ~msk;
                            fast_mask &= ~msk;
                        }
                    }
                }
            }

            for (slot=0; slot < crate->modules_cnt; slot++) {
                /* если прием от клиентов был приостановлен из-за заполненности
                    буфера - то можем снова разрешить принимать данные от клиентов */
                if ((crate->modules[slot].state_flags & LTR_MODULE_STATE_FLAG_SND_SUSP)
                        && (!CYCLEBUF_IS_FULL(&crate->modules[slot].sndbuf))) {
                    int i;
                    crate->modules[slot].state_flags &= ~LTR_MODULE_STATE_FLAG_SND_SUSP;
                    for (i=crate->modules[slot].client_cnt-1; i >= 0; i--) {
                        int client_err = ltrsrv_client_recv_resume(crate->modules[slot].clients[i]);
                        if (client_err)
                            ltrsrv_client_close_req(crate->modules[slot].clients[i], client_err);
                    }
                }
            }
        }


        /* пробуем послать данные из буфера */
        if (!err && proc->snd_def_size) {
            int end_size = MIN(proc->snd_def_size, proc->snd_buf_size - proc->snd_send_pos);

            sent = crate->intf_func.send(crate, &proc->snd_buf[proc->snd_send_pos], end_size);
           //ltrsrv_log_rec(LTRSRV_LOGLVL_DBG_LOW, crate->log_str, 0, "Start send %d words, result %d, total rdy=%d", end_size, sent, proc->snd_def_size);

            if (sent < 0) {
                err = sent;
            } else {
                UINT64_ADD32(crate->stat.wrd_sent, (proc->snd_send_pos%4 + sent)/4);

                proc->snd_def_size -= sent;
                proc->snd_send_pos += sent;

                if (proc->snd_send_pos == proc->snd_buf_size)
                    proc->snd_send_pos = 0;

                if (sent < end_size) {
                    /* если не удалось все передать, то встаем на ожидание
                     * готоности к передаче */
                    err = crate->intf_func.wt_snd_rdy(crate, f_send_data);
                    if (!err) {
                        snd_wait = 1;
                    }
                }
            }
        }

        /* если есть данные передавали, что готовы, то пробуем передать еще раз
           (в windows нельзя вставать на ожидания готовности послать, так как
            send срабатывает только по фронту) */
        if (!err && !snd_wait && (proc->snd_def_size || slow_mask || fast_mask)) {
            ltrsrv_wait_add_tmr(crate, LTRSRV_WAITFLG_AUTODEL, 0, f_snd_tmr_cb, crate);
        }

        if (err) {
            ltrsrv_log_rec(LTRSRV_LOGLVL_ERR, crate->log_str, err, "Crate send error!");
        }
    }

    return err;
}

/* так как в windows у соектов готовность передавать срабатывает только по фронут,
 * то вставать на ожидание можно после неудачного send(). поэтому тут мы только
 * планируем немедленную попытку send, а уже ожидание только если не удастся передать */
int ltr_crate_wait_send_rdy(t_ltr_crate *crate) {
    return ltrsrv_wait_add_tmr(crate, LTRSRV_WAITFLG_AUTODEL, 0, f_snd_tmr_cb, crate);
}





/** Обработка очереди команд. Берется первая команда из очереди и
    передается запрос интерфейсу на выполнение соответствующей команды */
static void f_cmd_proc_queue(t_ltr_crate *crate) {
    int err = LTRSRV_ERR_SUCCESS;

    t_proc_data *proc = (t_proc_data *)crate->proc_data;
    if (proc->cmd_cnt) {
        t_ctl_cmd *cmd = &proc->cmd_queue[proc->cmd_snd_pos];
        switch (cmd->code) {
            case LTR_CRATE_CMD_CODE_READ:
                err = crate->intf_func.rd_regs(crate, cmd->param, cmd->rcv_size,
                                             cmd->flags & LTR_CRATE_CMD_FLAGS_EXTERNAL_BUF ? cmd->ext_buf : cmd->data,
                                             f_cmd_cb, cmd);
               break;
            case LTR_CRATE_CMD_CODE_WRITE:
                err = crate->intf_func.wr_regs(crate, cmd->param, cmd->snd_size,
                                             cmd->flags & LTR_CRATE_CMD_FLAGS_EXTERNAL_BUF ? cmd->ext_buf : cmd->data,
                                             f_cmd_cb, cmd);
                break;
            case LTR_CRATE_CMD_CODE_IOCTL:
                err = crate->intf_func.ioctl(crate, cmd->req, cmd->param,
                                           cmd->flags & LTR_CRATE_CMD_FLAGS_EXTERNAL_BUF ? cmd->ext_buf : cmd->data,
                                           cmd->snd_size,
                                           cmd->flags & LTR_CRATE_CMD_FLAGS_EXTERNAL_BUF ? cmd->ext_buf : cmd->data,
                                           cmd->recvd_size, f_cmd_cb, cmd);
                break;
            default:                
                err = LTRSRV_ERR_CRATE_INVALID_CMD_IN_QUEUE;
                break;
        }

        if (err)
            f_cmd_cb(crate, err, cmd);
    }
}

/* проверка, какому уровню соответствует указанное состояние термомтра */
static t_ltrsrv_log_level f_therm_check_lvl(t_ltr_crate* crate, float val) {
    t_ltrsrv_log_level lvl = LTRSRV_LOGLVL_DBG_LOW;
    if (crate->therm_limits!=NULL) {
        if ((val <= crate->therm_limits->low_err) || (val >= crate->therm_limits->high_err))  {
            lvl = LTRSRV_LOGLVL_ERR;
        } else  if ((val <= crate->therm_limits->low_warn) || (val >= crate->therm_limits->high_warn))  {
            lvl = LTRSRV_LOGLVL_WARN;
        }
    }
    return lvl;
}



/* проверка, следует ли посылать сообщение о изменении состояние показаний термомтра и с каким уровнем */
static int f_therm_check_req_lvl(t_ltr_crate* crate, int idx, float val, t_ltrsrv_log_level *lvl) {
    t_ltrsrv_log_level prev_lvl = LTRSRV_LOGLVL_DBG_LOW;
    t_ltrsrv_log_level new_lvl = f_therm_check_lvl(crate, val);
    t_proc_data *proc_data = (t_proc_data *)crate->proc_data;
    int req = 0;

    if (crate->stat.therm_mask & (1UL << idx)) {
        prev_lvl = f_therm_check_lvl(crate, crate->stat.therm_vals[idx]);
    }

    if (new_lvl != prev_lvl) {
        req = 1;
        /* сообщаем о возвращении в норму */
        if ((new_lvl > prev_lvl) || (new_lvl>LTRSRV_LOGLVL_WARN)) {
            new_lvl = LTRSRV_LOGLVL_INFO;
        }
    }


    if (!req) {
        /* если не было перехода через порог, то проверяем изменение
         * температуры с момента последнего отображения. Если оно
         * изменилось на заданную величину, то выводим сообщение
         * на уровне деталей */
        float therm_lim = new_lvl<=LTRSRV_LOGLVL_WARN ? 2.F : 3.F;

        if (!(proc_data->therm_show_msk & (1UL << idx)) ||
                (fabs(proc_data->therm_show_vals[idx]-val) >= therm_lim)) {
            req = 1;
            new_lvl = LTRSRV_LOGLVL_DETAIL;
        }
    }

    if (req && (lvl!=NULL))  {
        *lvl = new_lvl;
    }

    crate->stat.therm_vals[idx] = val;
    crate->stat.therm_mask |= (1UL << idx);

    if (req) {
        proc_data->therm_show_vals[idx] = val;
        proc_data->therm_show_msk |= (1UL << idx);
    }

    return req;
}

static int f_process_module_mask(t_ltr_crate *crate, uint16_t msk) {
    int err = 0;
    /* если прочитали новую конфигурацию модулей, то для новых
       обнаруженных модулей ставим флаг, что необходимо послать
       START-RESET-STOP.
       Закрываем модули, которые были удалены */
    int i, need_send = 0;
    //if (modules_cfg!=crate->o)
    //ltrsrv_log_rec(LTRSRV_LOGLVL_INFO, crate->log_str, 0,
    //               "New modules configuration 0x%04x", modules_cfg);

    for (i = 0; i < LTR_CRATE_MODULES_MAX_CNT; ++i) {
        t_ltr_module *module = &crate->modules[i];
        if ((msk & (1UL << i)) && (i >= crate->modules_cnt)) {
            ltrsrv_log_rec(LTRSRV_LOGLVL_INFO, crate->log_str, 0,
                           "Wrong slots count assumption. Slots count was increased to maximum");
            crate->modules_cnt = LTR_CRATE_MODULES_MAX_CNT;
        }

        if ((module->state == LTR_MODULE_STATE_OFF) && (msk & (1UL << i))) {
            /* если поддерживаем возможность сохраненных настроек,
             * то сперва нужно получить настройки, а потом уже
             * понимать, что делать с модулем */
            if (LTR_CRATE_SUPPORT_SLOTS_CONFIG(crate)) {
                err = ltrsrv_crate_add_cmd_to_queue(
                            crate, LTR_CRATE_CMD_CODE_READ, LTR_CRATE_CMD_REASON_SLOT_CFG, 0,
                            LTR_CRATE_ADDR_SLOT_HDR(i), 0,
                            LTR_CRATE_SLOT_HDR_SIZE,
                            NULL, NULL, 0, NULL, NULL);
                if (!err) {
                    module->state = LTR_MODULE_STATE_CHECK_CFG;
                }
            } else {
                /* появился новый модуль =>  устанавливаем флаг, что
                    нужен новый STOP-RESET-STOP */
                need_send = 1;
                module->state = LTR_MODULE_STATE_NEED_RST;
            }

            ltrsrv_log_rec(LTRSRV_LOGLVL_INFO, crate->log_str, 0,
                           "New module detected in slot %d", i+1);
        }

        if ((module->state != LTR_MODULE_STATE_OFF) && !(msk & (1UL << i))) {
            /* модуль был удален */
            ltrsrv_log_rec(LTRSRV_LOGLVL_DETAIL, crate->log_str, 0,
                           "Module is removed from slot %d", i + 1);
            f_module_close(crate, &crate->modules[i], 0);
        }
    }


    /* если мы пометили флагами модули, которым нужны STOP-RESET-STOP,
       то пробуем их послать */
    if (need_send) {
        f_send_data(crate);
    }
    return err;
}



/****************************************************************************//**
  Callback-функция на завершения выполнения управляющей команды
   (такие как чтение данных при приеме исключительной ситуации или смена скорости
    передачи)
   @param[in] crate     описатель крейта
   @param[in] cmd_err   код завершения команды
   @param[in] prv_data  должно указывать на описание первой команды из очереди
                        (команды выполняются по одной)
 ********************************************************************************/
static int f_cmd_cb(t_ltr_crate* crate, int cmd_err, void* prv_data) {
    int err = _CRATE_CHECK_HND(crate);
    t_proc_data *proc = NULL;
    t_ctl_cmd *cmd = (t_ctl_cmd *)prv_data;

    if (!err) {
        proc = (t_proc_data *)crate->proc_data;
        /* проверяем, что callback соответствует первой команде из очереди */
        if ((proc->cmd_cnt == 0) || (cmd != &proc->cmd_queue[proc->cmd_snd_pos])) {
            err = LTRSRV_ERR_CRATE_UNEXPECTED_CMD_CB;
            ltrsrv_log_rec(LTRSRV_LOGLVL_WARN, crate->log_str, err,"");
        }
    }


    if (!err) {
        /* удаляем команду из очереди */
        proc->cmd_cnt--;
        proc->cmd_snd_pos++;
        if (proc->cmd_snd_pos == LTRSRV_CRATE_CMD_QUEUE_SIZE)
            proc->cmd_snd_pos = 0;

        /* можем обработать следующую команду в очереди, если она была до
           обработки результатов предыдущей. То, что для обработки результатов,
           также важно, так как обработка может добавить свои команды в очередь,
           и если очередь пуста, то запустить их на выполнение и привести
           к повторному запуску...*/
        if (proc->cmd_cnt && !crate->close_req)
            f_cmd_proc_queue(crate);

        /* проверяем, что команда завершилась успешно */
        if (cmd_err)
            err = cmd_err;

        if (!err) {
            switch (cmd->reason) {
                case LTR_CRATE_CMD_REASON_GET_MODULES: {
                        err = f_process_module_mask(crate, ((uint16_t)cmd->data[1] << 8) | cmd->data[0]);
                    }
                    break;
                case LTR_CRATE_CMD_REASON_SYSFAULT: {
                        uint16_t *regs = (uint16_t *)cmd->data;
                        ltrsrv_log_rec(LTRSRV_LOGLVL_WARN, crate->log_str, 0,
                                       "crate sysfault exception! crate regs ="
                                       "%#04x,%#04x,%#04x,%#04x,%#04x,%#04x,%#04x,%#04x",
                                       regs[0],regs[1],regs[2],regs[3],regs[4],regs[5],regs[6],regs[7]);
                        crate->par.flags |= LTR_CRATE_FLAGS_HW_ERROR;
                    }
                    break;
                case LTR_CRATE_CMD_REASON_SLOT_CFG: {
                        uint32_t status = cmd->data[0] | ((uint32_t)cmd->data[1] << 8) |
                                ((uint32_t)cmd->data[2] << 16) | ((uint32_t)cmd->data[3] << 24);
                        uint8_t slot = (cmd->param >> 16) & 0xF;
                        if (crate->modules[slot].state == LTR_MODULE_STATE_CHECK_CFG) {
                            ltrsrv_log_rec(LTRSRV_LOGLVL_DBG_MED, crate->log_str, 0,
                                           "Get slots config. slot = %d, status = 0x%08X",
                                           slot+1, status);

                            /* если модуль используется, то берем заданный id модуля
                             * и сохраняем его, при этом сбрасывать модуль не нужно */
                            if (status & (1UL<<31)) {
                                uint16_t mid = cmd->data[4] | (cmd->data[5] << 8);
                                ltrsrv_log_rec(LTRSRV_LOGLVL_INFO, crate->log_str, 0,
                                               "Active config for slot = %d, mid = 0x%04X",
                                               slot+1, mid);
                                f_module_init(crate, slot, mid, LTR_MODULE_INIT_FLAGS_ON_CFG_RESTORE);
                            } else {
                                crate->modules[slot].state = LTR_MODULE_STATE_NEED_RST;
                                f_send_data(crate);
                            }
                        }
                    }
                    break;
                case LTR_CRATE_CMD_REASON_CRATE_POLL: {


                        t_proc_data *proc_data = (t_proc_data *)crate->proc_data;
                        if (proc_data->poll_recvd_size >= 2) {
                            err = f_process_module_mask(crate, proc_data->poll_state.modules_mask);
                        }

                        if (!err && (proc_data->poll_recvd_size >= 4)) {
                            t_ltrsrv_log_level therm_req_lvl=LTRSRV_LOGLVL_DBG_LOW;
                            int therm_req = 0;
                            uint16_t msk = 0x03;
                            unsigned therm_idx;
                            unsigned req_size = 4;

                            for (therm_idx = 0;
                                 (therm_idx < 2) && (proc_data->poll_recvd_size >= req_size );
                                 therm_idx++, req_size+=2) {

                                if (msk & (1UL << therm_idx)) {
                                    t_ltrsrv_log_level cur_lvl=LTRSRV_LOGLVL_DBG_LOW;
                                    int cur_req=0;

                                    cur_req = f_therm_check_req_lvl(crate, therm_idx, (float)proc_data->poll_state.therm_vals[therm_idx], &cur_lvl);
                                    if (cur_req) {
                                        therm_req = 1;
                                    }

                                    if (cur_lvl < therm_req_lvl) {
                                        therm_req_lvl = cur_lvl;
                                    }
                                }
                            }

                            if (therm_req) {
                                ltrsrv_log_rec(therm_req_lvl, crate->log_str, LTRSRV_ERR_CRATE_THERM_OUT_OF_RANGE,
                                               "%s: %0.1f, %0.1f",
                                               therm_req_lvl <= LTRSRV_LOGLVL_WARN ? "Thermometer values out of range" :
                                               therm_req_lvl == LTRSRV_LOGLVL_INFO ? "Thermometer values return to normal ranges" :
                                                                                     "Current themperature values",
                                               crate->stat.therm_vals[0], crate->stat.therm_vals[1]);
                            }
                        }
                    }
                    break;
                default:
                    //....

                    break;
            }
        }



        if (cmd->cb != NULL) {
            cmd->cb(crate, err, cmd->cb_data);
        } else if (err != 0){
            /* если есть callback на функцию, то сообщение об ошибке должно быть в нем.
               иначе выдаем сообщение об ошибке тут */
            ltrsrv_log_rec(LTRSRV_LOGLVL_ERR, crate->log_str, err,
                           "Can't handle crate control command: %s", LOGSTR_CMDREASON(cmd->reason));
        }
    }

    return err;
}

/****************************************************************************//**
    Добавляем команду крейту в очередь команд. Если команда единственная, то сразу
    и запускаем ее на выполнение.
    @param[in] hnd    Описатель крейта
    @param[in] code    Код команды
    @param[in] reason  Причина выполнения (смысл команды)
    @param[in] req     Код запроса (для команды IOCTL)
    @param[in] param   Параметр команды (адрес при командах чтении и записи)
    @param[in] size    Размер данных на прием/передачу
    @param[in] flags   Флаги из t_cmd_flags
    @param[in] cb      Callback-функция, вызываемая по звершению команды
    @param[in] cb_data Данные, которые будут переданы в callback
    @param[in] wr_data Данные на передачу (принятые данные сохраняются в буфере
    @return            Код ошибки
    ****************************************************************************/
int ltrsrv_crate_add_cmd_to_queue(t_ltr_crate *hnd, t_crate_cmd_code code,
                                  t_ltr_crate_cmd_reason reason,
                                  uint32_t req, uint32_t param,
                                  uint32_t snd_size, uint32_t rcv_size, uint32_t *recvd_size,
                                  uint8_t *data, int flags, t_ltr_crate_cmd_cb cb, void *cb_data) {
    t_proc_data* proc = (t_proc_data* )hnd->proc_data;   
    int err = 0;
    if (proc->cmd_cnt == LTRSRV_CRATE_CMD_QUEUE_SIZE) {
        err = LTRSRV_ERR_CRATE_CMD_QUEUE_FULL;
    } else {
        proc->cmd_queue[proc->cmd_put_pos].code = code;
        proc->cmd_queue[proc->cmd_put_pos].reason = reason;
        proc->cmd_queue[proc->cmd_put_pos].req = req;
        proc->cmd_queue[proc->cmd_put_pos].param = param;
        proc->cmd_queue[proc->cmd_put_pos].snd_size = snd_size;
        proc->cmd_queue[proc->cmd_put_pos].rcv_size = rcv_size;
        proc->cmd_queue[proc->cmd_put_pos].recvd_size = recvd_size;
        proc->cmd_queue[proc->cmd_put_pos].cb = cb;
        proc->cmd_queue[proc->cmd_put_pos].cb_data = cb_data;
        proc->cmd_queue[proc->cmd_put_pos].flags = flags;
        if (flags & LTR_CRATE_CMD_FLAGS_EXTERNAL_BUF) {
            proc->cmd_queue[proc->cmd_put_pos].ext_buf = data;
        } else {
            if (data)
                memcpy(proc->cmd_queue[proc->cmd_put_pos].data, data, snd_size);
            proc->cmd_queue[proc->cmd_put_pos].ext_buf = NULL;
        }
    }

    if (!err) {
        proc->cmd_put_pos++;
        if (proc->cmd_put_pos == LTRSRV_CRATE_CMD_QUEUE_SIZE)
            proc->cmd_put_pos = 0;
        proc->cmd_cnt++;

        /* если это единственная команда в очереди - то сразу ее начинаем выполнять */
        if (proc->cmd_cnt == 1)
            f_cmd_proc_queue(hnd);
    }
    return err;
}

/** обработка исключительных ситуций, индикацию которых крейт вставляет в
    поток данных
    @todo Доделать остальные исключительные ситуации
    @param[in] crate      Описатель крейта
    @param[in] exept      Код исключительной ситуации
    @return               Код ошибки */
static int f_process_crate_except(t_ltr_crate* crate, uint8_t exept) {
    int err = 0;
    switch (exept) {
        case LTR_CRATE_STREAM_EXCEPT_FAULT:
            err = ltrsrv_crate_add_cmd_to_queue(crate, LTR_CRATE_CMD_CODE_READ, LTR_CRATE_CMD_REASON_SYSFAULT, 0,
                                     LTR_CRATE_ADDR_SYSREGS, 0, LTR_CRATE_SYSREGS_SIZE, NULL, NULL, 0, NULL, NULL);
            break;
        case LTR_CRATE_STREAM_EXCEPT_MODULES_CH:
            err = ltrsrv_crate_add_cmd_to_queue(crate, LTR_CRATE_CMD_CODE_READ, LTR_CRATE_CMD_REASON_GET_MODULES, 0,
                                     LTR_CRATE_ADDR_MODULES, 0, 2, NULL, NULL,0, NULL, NULL);
            break;
        case LTR_CRATE_STREAM_EXCEPT_BUF_OVFL:
            ltrsrv_log_rec(LTRSRV_LOGLVL_WARN, crate->log_str, 0, "Crate buffer overflow signal!");
            crate->stat.internal_rbuf_ovfls++;
            break;
        case LTR_CRATE_STREAM_EXCEPT_BUF_MISS:
            ltrsrv_log_rec(LTRSRV_LOGLVL_DBG_HIGH, crate->log_str, 0, "Crate buffer miss signal!");
            UINT64_INC(crate->stat.internal_rbuf_miss);
            break;
        case LTR_CRATE_STREAM_EXCEPT_UNKNOWN_SYNC:
            ltrsrv_log_rec(LTRSRV_LOGLVL_WARN, crate->log_str, 0, "Crate unknown sync signal!");
            break;
        default:
            ltrsrv_log_rec(LTRSRV_LOGLVL_WARN, crate->log_str, 0, "Crate unknown signal!");
            break;
    }

    return err;
}


int ltrsrv_crate_init_spec(t_ltr_crate *crate) {
    int err = 0;
    /* по-умолчанию предполагаем, что тип крейта такой же, как имя устройства */
    strcpy(crate->descr.crate_type_name, crate->descr.devname);
    if (!strcmp(crate->descr.devname, "LTR030")) {
        crate->modules_cnt = 16;
        crate->type = LTR_CRATE_TYPE_LTR030;
        strcpy(crate->descr.crate_type_name, "LTR-EU-8/16");
    } else if (!strcmp(crate->descr.devname, "LTR031")) {
        crate->modules_cnt = 2;
        crate->type = LTR_CRATE_TYPE_LTR031;
        strcpy(crate->descr.crate_type_name, "LTR-EU-2");
    } else if (!strcmp(crate->descr.devname, "LTR032")) {        
        crate->type = LTR_CRATE_TYPE_LTR032;
        crate->therm_limits = &f_ltr032_therm_limits;
        /* количество модулей 7 или 15 определяется позже */
    } else if (!strcmp(crate->descr.devname, "LTR021")) {
        crate->modules_cnt = 2;
        crate->type = LTR_CRATE_TYPE_LTR021;
        strcpy(crate->descr.crate_type_name, "LTR-U-1");
    } else if (!strcmp(crate->descr.devname, "LTR010")) {
        crate->modules_cnt = 16;
        crate->type = LTR_CRATE_TYPE_LTR010;
        strcpy(crate->descr.crate_type_name, "LTR-U-8/16");
    } else if (!strcmp(crate->descr.devname, "LTR-CU-1")) {
        crate->modules_cnt = 1;
        crate->type = LTR_CRATE_TYPE_LTR_CU_1;
    } else if (!strcmp(crate->descr.devname, "LTR-CEU-1")) {
        crate->modules_cnt = 1;
        crate->type = LTR_CRATE_TYPE_LTR_CEU_1;
    } else {
        /** @todo а что возвращает тип bootloader??? */
        crate->type = LTR_CRATE_TYPE_UNKNOWN;
        crate->modules_cnt = 16;
        ltrsrv_log_rec(LTRSRV_LOGLVL_WARN, crate->log_str, 0, "Unkonwn crate %s",
                       crate->descr.devname);
    }

    if (crate->par.mode != LTR_CRATE_MODE_WORK) {
        crate->modules_cnt = 0;
    }
    return err;
}


/***************************************************************************//**
 Регистрация крейта в сервере. Выполняется после завершения инициализации
    крейста модулем интерфейса. После этого управление крейтом переходит серверу
    и крейт появляется в системе. Только после этого клиент сможет подключится
    к крейту и данные от крейта будет обрабатываться
    @param[in] crate       Описатель регистрируемого крейта
    @return                Код ошибки
*******************************************************************************/
int ltrsrv_crate_register(t_ltr_crate *crate) {
    int err = crate ? 0:  LTRSRV_ERR_CRATE_HANDLE;
    t_proc_data *proc = 0;

    if (!err) {
        /* Если уже есть крейт с тем же интерфейсом и серийным номером, то
         * отключаем его, чтобы для доступа остался только новый экземпляр.
         * Может возникнуть при переподключении крейта, когда новый крейт обаружен
         * раньше завершения отключения старого */
        t_ltr_crate *old_crate = ltrsrv_crate_find(crate->par.intf, crate->descr.serial);
        if (old_crate) {
            ltrsrv_crate_unregister(old_crate);
        }
    }

    if (!err && (f_crates_cnt == LTRSRV_MAX_CRATES_CNT))
        err = LTRSRV_ERR_NO_FREE_CRATE_SLOT;

    /* выделяем память под параметры обработки данных крейта */
    if (!err) {
        free(crate->proc_data);
        crate->proc_data = proc = (t_proc_data*)malloc(sizeof(t_proc_data));
        if (crate->proc_data==NULL)
            err = LTRSRV_ERR_MEMALLOC;
    }


    /* выделяем буфер крейта для передачи */
    if (!err) {
        memset(proc, 0, sizeof(t_proc_data));
        proc->snd_buf = (uint8_t*)malloc(LTRSRV_CRATE_SEND_BUF_SIZE);
        if (proc->snd_buf)
            proc->snd_buf_size = LTRSRV_CRATE_SEND_BUF_SIZE;
    }


    if (!err) {
#ifdef LTRD_BAND_STATISTIC_ENABLED
        proc->bw_last_check = clock();
#endif
        crate->par.state = LTR_CRATE_CONSTATE_WORK;
        crate->par.register_time = time(NULL);

        ltrsrv_log_make_crate_srcstr(crate);
        /* выводим информацию о крейте */
        ltrsrv_log_rec(LTRSRV_LOGLVL_INFO, crate->log_str, 0, "New crate initialized successfully: ");
        ltrsrv_log_rec(LTRSRV_LOGLVL_INFO, crate->log_str, 0, "   Location            = %s", crate->par.location);
        ltrsrv_log_rec(LTRSRV_LOGLVL_INFO, crate->log_str, 0, "   Crate type name     = %s", crate->descr.crate_type_name);
        ltrsrv_log_rec(LTRSRV_LOGLVL_INFO, crate->log_str, 0, "   Serial              = %s", crate->descr.serial);
        ltrsrv_log_rec(LTRSRV_LOGLVL_INFO, crate->log_str, 0, "   Firmware version    = %s", crate->descr.soft_ver);
        ltrsrv_log_rec(LTRSRV_LOGLVL_INFO, crate->log_str, 0, "   Bootloader version  = %s", crate->descr.bootloader_ver);
        ltrsrv_log_rec(LTRSRV_LOGLVL_INFO, crate->log_str, 0, "   CPU Type            = %s", crate->descr.cpu_type);
        ltrsrv_log_rec(LTRSRV_LOGLVL_INFO, crate->log_str, 0, "   Board revision      = %s", crate->descr.brd_revision);
        ltrsrv_log_rec(LTRSRV_LOGLVL_INFO, crate->log_str, 0, "   FPGA name           = %s", crate->descr.fpga_name);
        ltrsrv_log_rec(LTRSRV_LOGLVL_INFO, crate->log_str, 0, "   FPGA version        = %s", crate->descr.fpga_version);
        ltrsrv_log_rec(LTRSRV_LOGLVL_INFO, crate->log_str, 0, "   mode                = %d", crate->par.mode);
        if (LTR_CRATE_SUPPORT_PROTOCOL_VER(crate)) {
            ltrsrv_log_rec(LTRSRV_LOGLVL_INFO, crate->log_str, 0, "   Protocol version    = %d.%d",
                           crate->descr.protocol_ver_major, crate->descr.protocol_ver_minor);
        }
        if (LTR_CRATE_SUPPORT_SLOTS_CONFIG(crate)) {
            ltrsrv_log_rec(LTRSRV_LOGLVL_INFO, crate->log_str, 0, "   Slots config version = %d.%d",
                           crate->descr.slots_config_ver_major, crate->descr.slots_config_ver_minor);
        }

        memset(&crate->stat, 0, sizeof(crate->stat));

        f_crates[f_crates_cnt++] = crate;
    }

    /* устанавливаем медленную скорость по всем модулям */
    if (!err) {
        if (crate->par.mode == LTR_CRATE_MODE_WORK) {
            err = ltrsrv_crate_add_cmd_to_queue(crate, LTR_CRATE_CMD_CODE_WRITE, LTR_CRATE_CMD_REASON_BAUD_CH, 0,
                                     LTR_CRATE_ADDR_BAUDRATE, sizeof(proc->baud_rate),
                                     0, NULL, (uint8_t*)&proc->baud_rate, 0, NULL, NULL);
        }

        if (err) {
            ltrsrv_crate_unregister(crate);
        }
    }

    return err;
}


/***************************************************************************//**
 Удаление крейта из системы. Выполняется интерфейсом при закрытии крейта, который
    был зарегистрирован до этого с помощью ltrsrv_crate_register().
    @param[in] crate       Описатель удаляемого крейта
    @return                Код ошибки
*******************************************************************************/
int ltrsrv_crate_unregister(t_ltr_crate *crate) {
    int err = _CRATE_CHECK_HND(crate);
    t_proc_data *proc = NULL;
    if (!err) {
        int i;

        crate->par.state = LTR_CRATE_CONSTATE_CLOSED;

        ltrsrv_wait_remove_tmr(crate);

        /* закрываем все модули */
        for (i=0; i < crate->modules_cnt; i++) {
            f_module_close(crate, &crate->modules[i], 0);
        }

        /* оповещаем блок управления клиентами, чтобы в управляющих соединениях
           он отметил, что крейта больше нет */
        ltrsrv_client_notify_crate_closed(crate);

        proc = (t_proc_data *)crate->proc_data;
        free(proc->snd_buf);
        free(crate->proc_data);
        crate->proc_data = NULL;

        /* удаляем крейт из массива зарегестрированых крейтов */
        for (i=f_crates_cnt-1; i>=0 ; i--) {
            if (f_crates[i] == crate) {
                ltrsrv_log_rec(LTRSRV_LOGLVL_INFO, crate->log_str, 0, "Crate closed");
                if (i!=(f_crates_cnt-1))
                    memmove(&f_crates[i], &f_crates[i+1], (f_crates_cnt-i-1)*sizeof(f_crates[0]));
                f_crates_cnt--;
            }
        }
    }
    return err;
}




/** Функция вычисляет по модулям в крейте слово для установки скорости модулей.
    Если слово скорости не совпадает с установленными параметрами, то передается
    команда установки новой скорости */
static int f_check_snd_baudrate(t_ltr_crate *hnd) {
    int err = 0;
    t_proc_data *proc = (t_proc_data *)hnd->proc_data;
    uint16_t baud=0;
    int i;

    for (i = 0; i < hnd->modules_cnt; ++i) {
        if (hnd->modules[i].flags & LTR_MODULE_FLAGS_HIGH_BAUD)
            baud |= (1 << i);
    }

    if (baud != proc->baud_rate) {
        err = ltrsrv_crate_add_cmd_to_queue(hnd, LTR_CRATE_CMD_CODE_WRITE, LTR_CRATE_CMD_REASON_BAUD_CH, 0,
                                 LTR_CRATE_ADDR_BAUDRATE, sizeof(baud), 0, NULL,
                                 (uint8_t*)&baud, 0, NULL, NULL);

        if (!err) {
            ltrsrv_log_rec(LTRSRV_LOGLVL_DETAIL, hnd->log_str, 0, "Change baudrate to 0x%02X", baud);
            proc->baud_rate = baud;
        }
    }
    return err;
}


/** Функция закрытия модуля. Очищаются все ресурсы модуля и оповещаются
    все клиенты, которые установили связь с модулем, что модуля больше нет
    @param[in] crate       Описатель крейта, которому принадлежит модуль
    @param[in] module      Описатель самого модуля */
static int f_module_close(t_ltr_crate *crate, t_ltr_module *module, unsigned close_flags) {
    int err = 0;
    if (module->state != LTR_MODULE_STATE_OFF) {
        if (module->state == LTR_MODULE_STATE_WORK) {
            int i;
            /* оповещаем всех клиентов о закрытии модуля */
            for (i = module->client_cnt - 1; i >= 0; --i) {
                ltrsrv_client_notify_mod_closed(module->clients[i]);
            }
        }

        ltrsrv_wait_remove_tmr(&module->rst_retry_cnt);
        module->rst_retry_cnt = 0;

        ltrsrv_wait_remove_tmr(&module->subid_retry_cnt);
        module->subid_retry_cnt = 0;

        if (module->func.free != NULL) {
            module->func.free(crate, module);
        }

        /* сброс специализированных функций обработки */
        module->func.free = NULL;
        module->func.proc_rcv_cmd = NULL;
        module->func.proc_rcv_data = NULL;
        module->func.proc_snd = NULL;

        if (close_flags & LTR_MODULE_CLOSE_FLAG_SAVE_BAUD) {
            module->flags &= LTR_MODULE_FLAGS_HIGH_BAUD;
        } else {
            module->flags = 0;
        }


        CYCLEBUF_CLOSE(&module->sndbuf);
        CYCLEBUF_CLOSE(&module->rcvbuf);

        free(module->log_str); module->log_str = NULL;

        module->state = LTR_MODULE_STATE_OFF;
        if (crate->par.state == LTR_CRATE_CONSTATE_WORK) {
            err = f_check_snd_baudrate(crate);
        }
    }
    return err;
}

/****************************************************************************//**
 Инициализация новго модуля и перевод его в рабочее состояние.
 Заполняются все поля модуля. Только в этом месте должен быть код, зависимый
    от номера модуля!!!!
 ******************************************************************************/
static int f_module_init(t_ltr_crate *crate, int slot, uint16_t mid, uint32_t init_flags) {
    int err = 0;
    t_ltr_module *module = &crate->modules[slot];
    ltrsrv_wait_remove_tmr(&module->rst_retry_cnt);
    ltrsrv_wait_remove_tmr(&module->subid_retry_cnt);

    memset(module, 0, sizeof(t_ltr_module));
    module->crate = crate;
    module->state = mid == LTR_MID_INVALID ? LTR_MODULE_STATE_NEED_RST : LTR_MODULE_STATE_WORK;
    module->mid = mid;


    CYCLEBUF_INIT(&module->sndbuf, f_module_snd_buf_size);
    CYCLEBUF_INIT(&module->rcvbuf, f_module_rcv_buf_size);

    if (!CYCLEBUF_IS_VALID(&module->sndbuf) || !CYCLEBUF_IS_VALID(&module->rcvbuf)) {
        err = LTRSRV_ERR_MEMALLOC;
    }

    if (!err) {
        sprintf(module->name, LTR_MODULE_NAME "%d", mid & 0xFF);
    }

    if (!err) {
        if (mid == LTR_MID_LTR34) {
            err = ltrsrv_ltr34_init(module);
        } else if (mid == LTR_MID_LTR35) {
            err = ltrsrv_ltr35_init(module, init_flags);
        } else if (mid == LTR_MID_LTR01) {
            err = ltrsrv_ltr01_init(module, init_flags);
        } else {
            if (mid == LTR_MID_LTR24) {
                module->flags |= LTR_MODULE_FLAGS_HIGH_BAUD;
                /* модификацию модуля LTR24 можно определить по бит
                 * поддержки ICP в ответе на сброс */
                if (init_flags & LTR_MODULE_INIT_FLAGS_ON_RESET) {
                    sprintf(module->name, LTR_MODULE_NAME "24-%d", init_flags & 0x20 ? 2 : 1);
                }
            }

            if ((mid == LTR_MID_LTR210) ||
                ((mid==LTR_MID_LTR25) && (crate->type!=LTR_CRATE_TYPE_LTR021))) {
                module->flags |= LTR_MODULE_FLAGS_HIGH_BAUD;
            }

            if ((mid == LTR_MID_LTR41) || (mid==LTR_MID_LTR42) || (mid==LTR_MID_LTR43)
                || (mid == LTR_MID_LTR21)) {
                module->flags |= LTR_MODULE_FLAGS_USE_SYNC_MARK;
            }            
        }
    }

    if (!err) {
        err = ltrsrv_log_make_module_srcstr(crate, slot);
    }

    if (!err && (module->state == LTR_MODULE_STATE_WORK)) {
        ltrsrv_log_rec(LTRSRV_LOGLVL_INFO, module->log_str, 0,
                       "Initialized successfully");
    }



    if (err) {
        CYCLEBUF_CLOSE(&module->sndbuf);
        CYCLEBUF_CLOSE(&module->rcvbuf);

        ltrsrv_log_rec(LTRSRV_LOGLVL_INFO, crate->log_str, 0,
                       "can't initialize module in slot %d", slot);
    }

    return err;
}




/****************************************************************************//**
  Функция используется, чтобы оповестить крейт, что есть новые данные от клиента для
    модуля. По этой функции блоком управления потоком крейта будет сделана попытка
    прочитать данные от клиентов в свой внутренний буфер модуля
  @param[in] client   Описатель клиента
  @param[in] crate    Описатель крейта, с которым соединен клиент
  @param[in] slot     Номер слота модуля, с которым соединен клиент
  @param[in] by_event Если 0, то просто попытка принять данные, если 1, значит
  ******************************************************************************/
int ltrsrv_notify_client_data_rdy(struct st_ltr_client *client, t_ltr_crate *crate, int slot) {
    int err = _CRATE_CHECK_HND(crate);
    if (!err) {
        if (crate->modules[slot].state != LTR_MODULE_STATE_WORK) {
            err = LTRSRV_ERR_CRATE_SLOT_EMPTY;
        } else {
            t_ltr_module *module = &crate->modules[slot];
            /* если буфер на передачу для модуля уже заполнен - то останавливаем
               прием данных для этого клиента */
            if (CYCLEBUF_IS_FULL(&module->sndbuf)) {
                ltrsrv_client_recv_stop(client);
                module->state_flags |= LTR_MODULE_STATE_FLAG_SND_SUSP;
            } else {
                /* смотрим, сколько можем принять. если свободное переходят границу
                   буфера, то будем принимать за два раза - сперва до конца буфера,
                   а затем, когда клиент опять сообщит, что есть данные - заполним
                   начало */
                int rcv_size, received;

                rcv_size = (int)CYCLEBUF_PUT_CONT_FREE_SIZE(&module->sndbuf);
                  /* пробуем принять все */
                received = ltrsrv_client_get_words(client, CYCLEBUF_PUTPTR(&module->sndbuf), rcv_size);
                if (received > 0) {
                    UINT64_ADD32(module->stat.wrd_recv_from_client, received);
                    CYCLEBUF_PUT_CONT_DONE(&module->sndbuf, received);

                    if (CYCLEBUF_FILL_SIZE(&module->sndbuf) > module->stat.send_srvbuf_full_max)
                        module->stat.send_srvbuf_full_max = CYCLEBUF_FILL_SIZE(&module->sndbuf);

                    /* Пробуем отослать данные вне данной функции. Следует учитывать,
                       что в Windows мы не можем ждать события готовности к записи,
                       так как оно срабатывает по фронту а не уровню для WSA-сокетов */
                    ltrsrv_wait_add_tmr(crate, LTRSRV_WAITFLG_AUTODEL | LTRSRV_WAITFLG_TIMER_ONLY_EARLIER,
                                        LTRSRV_CRATE_SEND_WAIT_TIME, f_snd_tmr_cb, crate);
                } else if (received<0) {
                    err = received;
                }
            }
        }
    }

    return err;
}


static int f_client_send_rdy(struct st_ltr_client *client) {
    return f_send_to_clients(ltrsrv_client_crate(client));
}

/****************************************************************************//**
   Функция пытается передать данные из приемных буферов модуля крейта клиентам
    @param[in] crate     Описатель крейта
    @return              Код ошибки
    ***************************************************************************/
static int f_send_to_clients(t_ltr_crate *crate) {
    int slot;
    for (slot = 0; slot < crate->modules_cnt; ++slot) {
        t_ltr_module *module = &crate->modules[slot];
        int send_size = 0,  max_sent=0;

        /* если данные на передачу выходят за границу буфера (т.е. часть в конце,
           часть в начале), то передаем за два раза, именного для этого
           тут такой while */
        while (!CYCLEBUF_IS_EMPTY(&module->rcvbuf) && (send_size == max_sent)) {
            int client, max_i=0;
            int sent[LTRSRV_CLIENT_PER_MODULE_MAX];

            max_sent = 0;

            send_size = (int)CYCLEBUF_GET_CONT_RDY_SIZE(&module->rcvbuf);

            /* передаем всем клиентам блок данных */
            for (client = 0; client < module->client_cnt; client++) {
                sent[client] = ltrsrv_client_put_words(module->clients[client],
                                 CYCLEBUF_GETPTR(&module->rcvbuf), send_size);
                if (sent[client] > max_sent) {
                    max_sent = sent[client] ;
                    max_i = client;
                }
            }

            /* если клиентов больше одного, то проверяем, все ли забрали одинаково
               данных. Если нет, то ставим неудачнику флаг и пишим сообщение
               в лог */
            if (module->client_cnt > 1) {
                for (client = 0; client < module->client_cnt; client++) {
                    if (sent[client] < max_sent) {
                        if (!module->client_ovfl[client]) {
                            ltrsrv_client_sndbuf_ov(module->clients[client]);
                            module->client_ovfl[client] = 1;
                        }
                    }
                }
            }

            UINT64_ADD32(module->stat.wrd_sent_to_client, max_sent);

            CYCLEBUF_GET_CONT_DONE(&module->rcvbuf, max_sent);

            /* если не смогли все передать клиенту, то будем ждать, пока он станет
               доступен для записи, чтобы попытаться еще раз передать данные */
            if ((max_sent!=send_size) && (module->client_cnt)) {
                ltrsrv_client_wait_snd_rdy(module->clients[max_i], f_client_send_rdy);
            }
        }
    }
    return 0;
}




/***************************************************************************//**
  Функция кладет заданное количество слов данных, в буфер соответствующего
    модуля. Операция выполняется только если есть место для всех слов из массива
    и только если есть хотя бы один клиент.
    @param[in] crate   Описатель крейта
    @param[in] slot    Номер слота - модуля, от которого пришли данные
    @param[in] wrds    Указатель на массив слов, которые нужно положить в буфер
    @param[in] cnt     Количество слов
    @return            Код ошибки
    ***************************************************************************/
static int f_put_recv_words(t_ltr_crate *crate, int slot, uint32_t *wrds, uint32_t cnt) {
    t_ltr_module *module = &crate->modules[slot];
    /* передаем слова клиенту только когда модуль уже в рабочем состоянии */
    if (module->state == LTR_MODULE_STATE_WORK) {
        if (module->client_cnt != 0) {
            /* если буфер модуля уже занят - пробуем отослать блок данных */
            if ((CYCLEBUF_FREE_SIZE(&module->rcvbuf) < cnt)
                    && !(module->state_flags & LTR_MODULE_STATE_FLAG_RCV_OV)) {
                f_send_to_clients(crate);
            }

            /* если все равно занят - переполнение */
            if (CYCLEBUF_FREE_SIZE(&module->rcvbuf) < cnt) {
                if (!(module->state_flags & LTR_MODULE_STATE_FLAG_RCV_OV)) {
                    module->state_flags |= LTR_MODULE_STATE_FLAG_RCV_OV;
                    ltrsrv_log_rec(LTRSRV_LOGLVL_WARN, crate->log_str, 0,
                               "Receive buffer overflow for slot %d", slot+1);
                    module->stat.rbuf_ovfls++;
                    crate->stat.rbuf_ovfls++;
                }
                UINT64_ADD32(module->stat.wrd_recv_drop, cnt);
            } else if (module->state_flags & LTR_MODULE_STATE_FLAG_RCV_OV) {
                module->state_flags &= ~LTR_MODULE_STATE_FLAG_RCV_OV;
                /* так как флаг снят и есть место на хотя бы одно слово, то
                   в бесконечный цикл в рекурсии мы не уйдем. а так это
                   позволяет удобно еще раз проверить, что есть
                   место не только для признака переполнения, но и для слова данных */
                f_put_recv_word(crate, slot, LTR_CLIENT_STREAM_SIG_RBUF_OVF);
                f_put_recv_words(crate, slot, wrds, cnt);
            } else {
                /* если есть место, то сохраняем слова */
                uint32_t i;

                for (i = 0; i < cnt; ++i) {
                    CYCLEBUF_PUT_WORD(&module->rcvbuf, wrds[i]);
                }

                if (CYCLEBUF_FILL_SIZE(&module->rcvbuf) > module->stat.recv_srvbuf_full_max) {
                    module->stat.recv_srvbuf_full_max = CYCLEBUF_FILL_SIZE(&module->rcvbuf);
                }
            }
        }
    }
    return 0;
}

/***************************************************************************//**
  Функция кладет слово данных, принятое из крейта в буфер соответствующего
    модуля (но только если там есть место и есть хотя бы один клиент, которому
    они нужны
    @param[in] crate   Описатель крейта
    @param[in] slot    Номер слота - модуля, от которого пришли данные
    @param[in] wrd     Слово данных, которое нужно положить в буфер
    @return            Код ошибки
    ***************************************************************************/
static int f_put_recv_word(t_ltr_crate *crate, int slot, uint32_t wrd) {
    return f_put_recv_words(crate, slot, &wrd, 1);
}

#define LTR_CRATE_TMARK(crate) ((uint32_t)(crate->stat.total_start_marks & 0xFFFF) << 16) \
                                | (crate->stat.total_sec_marks & 0xFFFF)

/* заполнение сигнала unixtime для заданного крейта. в wrds должно
 * быть место для LTR_SIG_STAMP_EX_SIZE слов */
static int f_fill_unixtime(t_ltr_crate *crate, uint32_t *wrds) {
    int out_pos = 0;
    wrds[out_pos++] = LTR_SIGNAL(LTR_SIG_TYPE_TSTAMP_EX, LTR_SIG_STAMP_EX_SIZE-1);
    wrds[out_pos++] = LTR_CRATE_TMARK(crate);
    wrds[out_pos++] = UINT64_LOW(crate->stat.crate_unixtime);
    wrds[out_pos++] = UINT64_HIGH(crate->stat.crate_unixtime);
    return out_pos;
}





/** Обработка слова с данными синхрометок (старт или секунда)
    Обновляется статистика и каждому модулю передается слово с новыми параметрами
    синхрометок
    @param[in] crate      Крейт, для которого пришла синхрометка
    @param[in] wrd        Слово, которое считается синхрометкой
    @param[in] slot       0 - синхрометка от самого крейта, 1-16 - от модуля в слоте slot
    @return               Признак, что слово обработано и его не нужно пересылать дальше */
static int f_proc_marks(t_ltr_crate *crate, uint32_t wrd, int slot) {
    /* simple (LTR4x,LTRT13):
            00ts xxxx xxxx xxxx 1000 MMMM 0000 0000
       simple crate (old style) mark:
            00ts xxxx xxxx xxxx 1111 1111 0001 0000
       36-bit unixtime mark:
            011s TTTT TTTT TTTT 1111 1111 0001 0000,    T = unixtime[11:0]
            1000 TTTT TTTT TTTT 1111 1111 0001 0000,    T = unixtime[23:12]
            1100 TTTT TTTT TTTT 1111 1111 0001 0000,    T = unixtime[35:24]
       IRIG-B mark:
            011s 0000 0MMM MMMM 1111 1110 0SSS SSSS,    M = minute BCD, S = second BCD
            1000 00DD DDDD DDDD 1111 1110 00HH HHHH,    D = day-of-year BCD (1..366), H = hour BCD
            1100 000B 0000 0000 1111 1110 YYYY YYYY,    B = ??, Y = year BCD (00..99)

       t = second
       s = start
     */
    uint16_t mark_val = (wrd >> 16) & 0xFFFF;
    uint8_t  seq = LTR_CRATE_STREAM_MARK_GET_SEQ(mark_val);
    t_proc_data *proc = (t_proc_data *)crate->proc_data;
    uint32_t out_pos = 0;
    uint32_t out_words[LTR_SIG_STAMP_EX_SIZE];
    uint8_t mark_type = LTR_MARK_SEQ_NONE;

    switch ((wrd >> 8) & 0xFF) {
        case 0xFF: mark_type = LTR_MARK_SEQ_UNIXTIME; break;
        case 0xFE: mark_type = LTR_MARK_SEQ_IRIG; break;
    }

    if (mark_val & LTR_CRATE_STREAM_MARK_START) {
        crate->stat.total_start_marks++;
        if (slot) {
            crate->modules[slot-1].stat.start_mark++;
        } else {
            crate->stat.crate_start_marks++;
        }
    }

    if (mark_val & LTR_CRATE_STREAM_MARK_SECOND) {
        crate->stat.total_sec_marks++;
        if (slot) {
            crate->modules[slot-1].stat.sec_mark++;
        } else {
            crate->stat.crate_sec_marks++;
        }
    }

    switch (seq) {
        case 0:
            //старый формат меток (unixtime)
            if (mark_val & (LTR_CRATE_STREAM_MARK_START | LTR_CRATE_STREAM_MARK_SECOND)) {
                out_words[out_pos++] = LTR_CLIENT_STREAM_SIG_TIMESTAMP;
                out_words[out_pos++] = LTR_CRATE_TMARK(crate);
            }
            break;
        case 1:
            proc->mark_seq = mark_type + 1;
            proc->mark_wrds[0] = wrd;


            break;
        case 2:
            if (++proc->mark_seq== (mark_type + 2)) {
                UINT64_OR(crate->stat.crate_unixtime, 0, ((uint32_t)(mark_val & 0xFFF)) << 12);
                proc->mark_wrds[1] = wrd;
            } else {
                proc->mark_seq = 0;
            }
            break;
        case 3:
            if (++proc->mark_seq==(mark_type + 3)) {
                switch (mark_type) {
                    case LTR_MARK_SEQ_UNIXTIME:
                        UINT64_ASSIGN32(crate->stat.crate_unixtime,
                                        ((proc->mark_wrds[0]>>16) & 0x00000FFF) |
                                        ((proc->mark_wrds[1]>>4)  & 0x00FFF000) |
                                        ((wrd<<8)  & 0xFF000000));
                        UINT64_OR(crate->stat.crate_unixtime, (wrd >> 24) & 0xF,0);
                        break;
                    case LTR_MARK_SEQ_IRIG: {
                            struct tm tm;
                            uint32_t app_y;
                            memset(&tm, 0, sizeof(tm));
                            tm.tm_year = 100 + BCD_DECODE(wrd & 0xFF);
                            tm.tm_hour = BCD_DECODE(proc->mark_wrds[1] & 0x3F);
                            tm.tm_min  = BCD_DECODE((proc->mark_wrds[0] >> 16) & 0x7F);
                            tm.tm_sec  = BCD_DECODE(proc->mark_wrds[0] & 0x7F);
                            tm.tm_mday = 1;
                            tm.tm_mon  = 0;
                            tm.tm_wday = 0;
                            tm.tm_yday = BCD_DECODE((proc->mark_wrds[1] >> 16) & 0xFF)
                                         + ((proc->mark_wrds[1] >> 24) & 3) * 100 - 1;
                            app_y = 86400 * tm.tm_yday;

#ifdef HAVE_UINT64
                            crate->stat.crate_unixtime = mktime64(&tm);
#else
                            UINT64_ASSIGN32(crate->stat.crate_unixtime, mktime(&tm));
#endif
                            UINT64_ADD32(crate->stat.crate_unixtime, app_y);
                        }
                        break;
                }
                out_pos = f_fill_unixtime(crate, out_words);
            }
            proc->mark_seq = 0;
            break;
    }

    if (out_pos) {
        int i;
        for (i=0; i < crate->modules_cnt; i++) {
            if (crate->modules[i].client_cnt > 0) {
                f_put_recv_words(crate, i, out_words, out_pos);
            }
        }
        /** @todo для RawData здесь нужно положить метки тоже */
    }

    return 0;
}



/***************************************************************************//**
    Функция оповещает сервер, что пришли новые данные от крейта и передает их
        серверу. Вызывается интерфейсом крейта.
    Функция обрабатывает исключительные ситуации от крейта, синхрометки, а данные
      для модулей сохраняет в буфера модулей
    @param[in] hnd    Описатель крейта
    @param[in] buf    Буфер принятыми данными от крейста
    @param[in] size   Размер данных в словах
    @return           Код ошибки
    ***************************************************************************/
int ltrsrv_crate_rcv_done(t_ltr_crate *crate, uint32_t *buf, int size) {
    t_proc_data *proc = NULL;
    int i, fnd_new_modules = 0;
    int need_send_to_crate = 0;
    int err = _CRATE_CHECK_HND(crate);
    if (!err) {
        proc = (t_proc_data *)crate->proc_data;
        UINT64_ADD32(crate->stat.wrd_recv, size);
    }

    /*  просмотр потока на предмет сигналов крейт-контроллера -> {0xFFFFFFFF, 0xFFFFAAXX}
        и сортировка данных по модулям */
    for (i=0; (i < size) && !err; i++) {
        uint32_t wrd = buf[i];

        /* Обработка специальных случаев */
        if (proc->spec_cmd && ((wrd & LTR_CRATE_STREAM_EXCEPT_SIGN2_Msk)
                               == LTR_CRATE_STREAM_EXCEPT_SIGN2)) {
            err = f_process_crate_except(crate, wrd&LTR_CRATE_STREAM_EXCEPT_CODE_Msk);
            proc->spec_cmd = 0;
        } else {
            uint32_t wrd_type = wrd & LTR_CRATE_STREAM_TYPE_Msk;
            int slot = LTR_CRATE_STREAM_GET_SLOT(wrd);
            t_ltr_module *module = &crate->modules[slot];
            int processed = 0;

            switch (wrd_type) {
                case LTR_CRATE_STREAM_TYPE_CRATE_CMD:
                    UINT64_INC(crate->stat.crate_wrd_recv);

                    /* команда от крейта
                        В формате команд крейт-контроллера в настоящее время приходят:
                          - информация об ошибках и особых случаях (в т.ч. анонсы новых модулей)
                            ФОРМАТ: одно слово FFFFFFFF, потом FFFFAAxx
                            Второе слово из-за 0xAA & 0xC0 == 0x80 попадает в блок команд от модулей
                            (так исторически сложилось); определяется по флагу special_code
                          - синхрометки СТАРТ и СЕКУНДА
                            ФОРМАТ: x000FF10, бит 28 = start, бит 29 = 1sec */
                     if (wrd == LTR_CRATE_STREAM_EXCEPT_SIGN) {
                         proc->spec_cmd = 1;
                     } else if (((wrd & 0xFFFF) == LTR_CRATE_STREAM_CRATE_CMD_MARK)
                        || ((wrd & 0xFF00)==LTR_CRATE_STREAM_CRATE_CMD_IRIG)) {
                         f_proc_marks(crate, wrd, 0);
                     } else {
                         ltrsrv_log_rec(LTRSRV_LOGLVL_WARN, crate->log_str,0,
                                        "Unknown crate command (data=0x%08X)\n", wrd);
                     }
                     break;
                 case LTR_CRATE_STREAM_TYPE_CMD:
                     /* команда от модуля */

                     /* в команде сбороса проверяем тип модуля */
                    if ((wrd & LTR_CRATE_STREAM_CMD_Msk) == LTR_CRATE_STREAM_CMD_RESET) {
                         uint16_t new_mid = LTR_CRATE_STREAM_GET_CMD_DATA(wrd);
                         int new_module = 0;

                         /* проверяем правильность mid - совпадение старшей и
                            младшей частей */
                         if ((new_mid == LTR_MID_EMPTY) || (new_mid == LTR_MID_IDENTIFYING)
                                 || (new_mid & 0xFF) != ((new_mid >> 8) & 0xFF)) {
                             ltrsrv_log_rec(LTRSRV_LOGLVL_ERR, crate->log_str, LTRSRV_ERR_MODULE_MID,
                                            "Bad module (slot = %d, mid= 0x%04X)", slot+1, new_mid);
                             if (!((module->mid == LTR_MID_INVALID) && (module->state == LTR_MODULE_STATE_WORK))) {
                                new_module = 1;
                             } else {
                                module->state = LTR_MODULE_STATE_NEED_RST;
                                need_send_to_crate = 1;
                             }
                             new_mid = LTR_MID_INVALID;
                         } else if (module->state != LTR_MODULE_STATE_WORK) {                             
                             new_module = 1;
                         } else if (module->mid != new_mid) {
                             new_module = 1;
                             ltrsrv_log_rec(LTRSRV_LOGLVL_INFO, crate->log_str, 0,
                                            "Module mid in slot %d changed from 0x%04x to 0x%04x",
                                            slot+1, module->mid, new_mid);
                         } else {
                             /* пришел сброс модулю, который не менялся */
                             if (module->flags & LTR_MODULE_FLAGS_USE_SND_FIFO_STATUS) {
                                 /* для модулей с контрлем заполненности очереди
                                  * сбрасываем состояние заполненности */
                                 module->stat.hard_send_fifo_full = 0;
                                 module->stat.hard_send_fifo_overrun = 0;
                                 module->stat.hard_send_fifo_underrun = 0;
                             }
                         }

                         if (new_module) {
                             fnd_new_modules = 1;
                             processed = 1;
                             f_module_close(crate, module, 0);
                             err = f_module_init(crate, slot, new_mid,
                                                 LTR_MODULE_INIT_FLAGS_ON_RESET |
                                                 (wrd & LTR_MODULE_INIT_FLAGS_RST_HBYTE_CMD_MSK));
                             if ((module->state == LTR_MODULE_STATE_NEED_GET_SUBID) ||
                                 (module->state_flags == LTR_MODULE_STATE_NEED_RST)) {
                                 need_send_to_crate = 1;
                             }
                         }
                     }

                     /* если пришла синхронная метка от модуля */
                     if ((module->flags & LTR_MODULE_FLAGS_USE_SYNC_MARK)
                             && ((wrd & LTR_CRATE_STREAM_CMD_CODE_Msk)==
                                 LTR_CRATE_STREAM_CMD_CODE_MARK)) {
                         f_proc_marks(crate, wrd, slot+1);
                         processed = 1;
                     }

                     if (!processed && (module->func.proc_rcv_cmd != NULL)) {
                         processed = module->func.proc_rcv_cmd(crate, module, wrd);
                     }

                     if (!processed)
                         f_put_recv_word(crate, slot, wrd);
                     UINT64_INC(module->stat.wrd_recv);
                     break;
                 case LTR_CRATE_STREAM_TYPE_DATA:
                    if (module->func.proc_rcv_data!=NULL) {
                        processed = module->func.proc_rcv_data(crate, module, wrd);
                    }

                    if (!processed) {
                        f_put_recv_word(crate, slot, wrd);
                    }

                    UINT64_INC(module->stat.wrd_recv);
                    break;
             } /* switch (wrd & LTR_CRATE_STREAM_TYPE_Msk) */
        }


    }

    /* если были найдены новые модули - то проверяем, не нужно ли
       сменить скорость */
    if (!err && fnd_new_modules) {
        err = f_check_snd_baudrate(crate);
    }

    if (!err) {
        /* пробуем передать клиентам все что приняли в их буфер*/
        err = f_send_to_clients(crate);
    }

    if (!err && need_send_to_crate) {
        f_send_data(crate);
    }

    return err;
}

/***************************************************************************//**
    Поиск зарегистрированного крейста по серийному номеру
    @param[in] serial  Серийный номер (если NULL, возвращается первый
                        зарегистрированный крейт)
    @return            Описатель найденного крейта или NULL, если крейт не найден
    ***************************************************************************/
t_ltr_crate *ltrsrv_crate_find(t_ltr_crate_interface intf, const char *serial) {
    t_ltr_crate *hnd = NULL, *tmp_hnd = NULL;
    int i;
    for (i = 0; (i < f_crates_cnt) && (hnd == NULL); ++i) {
        if ((intf == f_crates[i]->par.intf) || (intf == LTR_CRATE_IFACE_UNKNOWN)) {
            if ((serial == NULL) || (serial[0] == 0) ||
                    !strcmp(serial, f_crates[i]->descr.serial)) {
                /* Для произвольного интерфейса приоритет должны иметь крейты,
                 * находящиеся в рабочем режиме. Если крейт в другом режиме,
                 * то сохраняем его описатель во временную переменную и вернем
                 * его только если не найдем в рабочем режиме*/
                if ((intf == LTR_CRATE_IFACE_UNKNOWN) &&
                        (f_crates[i]->par.mode != LTR_CRATE_MODE_WORK)) {
                    tmp_hnd = f_crates[i];
                } else {
                    hnd = f_crates[i];
                }
            }
        }
    }

    if (hnd == NULL)
        hnd = tmp_hnd;

    return hnd;
}

/***************************************************************************//**
  Регистрация клиента для модуля LTR. Вызывается только для соединений с модулем,
    (для управляющего канала не нужна).
    После этого, все данные, пришедшие от модуля, будут передаваться этому клиенту.
    @param[in] crate    Описатель крейта, с модулем которого устанавливает соединение клиент
    @param[in] slot     Номер слота (0-15), с которым устанавливает соединение клиент
    @param[in] client   Описатель клиента, которого нужно зарегистрировать
    @return             Код ошибки
    ***************************************************************************/
int ltrsrv_crate_register_client(t_ltr_crate *crate, int slot, struct st_ltr_client *client) {
    int err = _CRATE_CHECK_HND(crate);
    if (!err && (slot >= crate->modules_cnt)) {
        err = LTRSRV_ERR_CRATE_INVALID_SLOT_NUM;
    }

    if (!err) {
        t_ltr_module *module = &crate->modules[slot];
        if (module->state == LTR_MODULE_STATE_WORK) {
            if (module->client_cnt < LTRSRV_CLIENT_PER_MODULE_MAX) {
                if (module->client_cnt)
                    err = LTRSRV_ERR_MODULE_IN_USE;
                module->clients[module->client_cnt++] = client;
            } else {
                err = LTRSRV_ERR_MODULE_EXCEED_MAX_CLIENT;
            }
        } else {
            err = LTRSRV_ERR_CRATE_SLOT_EMPTY;
        }
    }
    return err;
}

/* Инициализация клиента после завершения установления связи с начальной командой */
int ltrsrv_crate_client_init(t_ltr_crate *crate, int slot, struct st_ltr_client *client) {
    int err = _CRATE_CHECK_HND(crate);
    /* если есть unixtime, то сразу посылаем метку, чтобы быть уверенным,
     * что первые семплы придут верно */
    if (!UINT64_IS_ZERO(crate->stat.crate_unixtime)) {
        uint32_t wrds[LTR_SIG_STAMP_EX_SIZE];
        f_fill_unixtime(crate, wrds);
        f_put_recv_words(crate, slot, wrds, LTR_SIG_STAMP_EX_SIZE);
    }
    return err;
}

/****************************************************************************//**
 Функция отменяет регистрацию соединения клиента с модулем.
    Удаляет указатель на клиента у модуля и, если клиент последний, зачищает
    буфер модуля на прием.
    @param[in] crate    Описатель крейта, с модулем которого соединен клиент
    @param[in] slot     Номер слота (0-15), с которым был содинен клиент
    @param[in] client   Описатель клиента, которого нужно удалить
    @return             Код ошибки
    ***************************************************************************/
int ltrsrv_crate_unregister_client(t_ltr_crate *crate, int slot, struct st_ltr_client *client) {
    int err = _CRATE_CHECK_HND(crate);
    if (!err) {
        t_ltr_module *module = &crate->modules[slot];
        int i, fnd;
        for (i = module->client_cnt-1, fnd = 0; (i >= 0) && !fnd; i--) {
            if (module->clients[i] == client) {
                if (i!=(module->client_cnt-1)) {
                    memmove(&module->clients[i], &module->clients[i+1],
                            (module->client_cnt-i-1)*sizeof(module->clients[0]));
                }
                module->client_cnt--;
                fnd = 1;
            }
        }

        /* если больше клиентов нет, то зачищаем буфер на прием данных от модуля
         * и сбрасываем флаги, так как для нового соединения они уже не нужны */
        if (module->client_cnt==0) {
            module->state_flags = 0;
            CYCLEBUF_CLEAR(&module->rcvbuf);
        }
    }
    return err;
}

int ltrsrv_crate_reset_module(t_ltr_crate *crate, int slot, int flags) {
    int err = _CRATE_CHECK_HND(crate);
    if (!err && (slot >= crate->modules_cnt)) {
        err = LTRSRV_ERR_CRATE_INVALID_SLOT_NUM;
    }

    if (!err) {
        t_ltr_module *module = &crate->modules[slot];
        if (module->state == LTR_MODULE_STATE_OFF) {
            err = LTRSRV_ERR_CRATE_SLOT_EMPTY;
        }

        if (!err) {
            err = f_module_close(crate, module, LTR_MODULE_CLOSE_FLAG_SAVE_BAUD);
            if (!err) {
                module->state = LTR_MODULE_STATE_NEED_RST;
                ltrsrv_log_rec(LTRSRV_LOGLVL_INFO, crate->log_str, 0,
                                              "Reset module in slot %d", slot+1);
                f_send_data(crate);
            }
        }
    }
    return err;
}

/****************************************************************************//**
  Получение списка серийников крейтов.
    @param[out] serial_list  Массив, в которые сохраняются серийные номера.
                             Сохраняется min(max_cnt, *fnd_cnt) серийных номеров,
                             остальное остается нетронутым
    @param[in]  max_cnt      На сколько серийных номеров выделен массив
    @param[out] fnd_cnt      Возвращает сколько всего крейтов в системе
    @return                  Код ошибки
  *****************************************************************************/
int ltrsrv_get_crates(char serial_list[][LTR_CRATE_SERIAL_SIZE], int max_cnt, int *fnd_cnt) {
    int i, total_cnt = 0;
    if (serial_list == NULL)
        max_cnt = 0;

    for (i = 0; i < f_crates_cnt; ++i) {
        int j, fnd;
        /* проверяем, что этот серийный номер уже не встречался раньше */
        for (j = 0, fnd = 0; (j < i) && !fnd; ++j) {
            if (!strcmp(f_crates[j]->descr.serial, f_crates[i]->descr.serial)) {
                fnd = 1;
            }
        }

        if (!fnd) {
            if (total_cnt < max_cnt) {
                memcpy(serial_list[total_cnt], f_crates[i]->descr.serial, LTR_CRATE_SERIAL_SIZE);
            }

            ++total_cnt;
        }
    }

    if (fnd_cnt != NULL)
        *fnd_cnt = total_cnt;
    return 0;
}


int ltrsrv_get_crates_info(t_crate_info_entry *info_list, int max_cnt, int flags, int *fnd_cnt) {
    int i;
    int cur_fnd_cnt = 0;
    for (i = 0; (i < f_crates_cnt); ++i) {
        if (!((flags & LTR_GETCRATES_FLAGS_WORKMODE_ONLY) && (f_crates[i]->par.mode != LTR_CRATE_MODE_WORK))) {
            if (info_list && (cur_fnd_cnt < max_cnt)) {
                memcpy(info_list[cur_fnd_cnt].serial, f_crates[i]->descr.serial, LTR_CRATE_SERIAL_SIZE);
                info_list[cur_fnd_cnt].type = f_crates[i]->type;
                info_list[cur_fnd_cnt].intf = f_crates[i]->par.intf;
                memset(info_list[cur_fnd_cnt].res, 0, sizeof(info_list[0].res));
            }
            ++cur_fnd_cnt;
        }
    }
    if (fnd_cnt)
        *fnd_cnt = cur_fnd_cnt;
    return 0;
}

/***************************************************************************//**
  Получение списка ID модулей в крейте. Если модуля нет в слоте, то id устанавливается
  в 0, если в слоте модуль находится в стадии инициализации, то устанавливается
  значение 0xFFFF
  @param[in] crate    Описатель крейта
  @param[out] mids    Массив, в который сохраняются ID модулей
  @param[in] max_mids Определяет на сколько ID выделен массив mids
  @return             Код ошибки
  *****************************************************************************/
int ltrsrv_get_crate_modules(t_ltr_crate *crate, uint16_t *mids, int max_mids) {
    int err = _CRATE_CHECK_HND(crate);
    if (!err) {
        int i;
        for (i = 0; i < max_mids; ++i) {
            if (i < crate->modules_cnt) {
                mids[i] = CRATE_GET_MODULE_MID(crate, i);
            } else {
                mids[i] = 0;
            }
        }
    }
    return err;
}

int ltrsrv_crate_start_client_cmd(t_ltr_crate *crate, t_crate_cmd_code cmd, uint32_t req, uint32_t param,
                                  uint8_t *buf, uint32_t snd_size, uint32_t *recvd_size,
                                  t_ltr_crate_cmd_cb cb, void *cb_data) {
    int err = _CRATE_CHECK_HND(crate);
    if (!err) {
        err = ltrsrv_crate_add_cmd_to_queue(crate, cmd, LTR_CRATE_CMD_REASON_CLIENT_REQ, req, param,
                                 snd_size, recvd_size ? *recvd_size : 0, recvd_size,
                                 buf, LTR_CRATE_CMD_FLAGS_EXTERNAL_BUF, cb, cb_data);
    }
    return err;
}


static LINLINE double f_update_bw_state(uint64_t *cnt, uint64_t *last_cnt, t_lclock_ticks proc_time) {
#ifdef HAVE_UINT64
    uint64_t wrds = *cnt - *last_cnt;
    *last_cnt = *cnt;
#else
    uint32_t wrds = cnt->l - last_cnt->l;
    last_cnt->l = cnt->l;
#endif
    return ((double)wrds / proc_time) * LCLOCK_TICKS_PER_SECOND;
}


static void f_crates_check_cb(t_socket sock, int event, void *data) {
    if (event == LTRSRV_EVENT_TOUT) {
        int i;
        for (i = 0; i < f_crates_cnt; ++i) {
#ifdef LTRD_BAND_STATISTIC_ENABLED
            t_lclock_ticks cur_clock = lclock_get_ticks();
            t_proc_data *proc = (t_proc_data *)f_crates[i]->proc_data;
            t_lclock_ticks prog_time = cur_clock - proc->bw_last_check;

            if (prog_time > 100) {
                int slot;
                proc->bw_last_check = cur_clock;
                f_crates[i]->stat.bw_send = f_update_bw_state(&f_crates[i]->stat.wrd_sent,
                                  &proc->bw_last_wrd_sent, prog_time);
                f_crates[i]->stat.bw_recv = f_update_bw_state(&f_crates[i]->stat.wrd_recv,
                                  &proc->bw_last_wrd_recv, prog_time);

                for (slot = 0; slot < f_crates[i]->modules_cnt; ++slot) {
                    t_ltr_module *module = &f_crates[i]->modules[slot];

                    if (module->state==LTR_MODULE_STATE_WORK) {
                        module->stat.bw_send = f_update_bw_state(
                                    &module->stat.wrd_sent,
                                    &module->bw_last_wrd_sent, prog_time);
                        module->stat.bw_recv = f_update_bw_state(
                                    &module->stat.wrd_recv,
                                    &module->bw_last_wrd_recv, prog_time);
                    } else {
                        module->stat.bw_send = 0;
                        module->stat.bw_recv = 0;
                    }
                }
            }
#endif

            if (f_crates[i]->intf_func.poll_time && ltimer_expired(&f_crates[i]->intf_func.poll_timer)) {
                int32_t poll_err = 0;
                /* если крейт поддерживает специальную команду для опроса статуса -
                 * используем ее. Иначе используем команду для чтения маски
                 * модулей, чтобы проверить их наличие (так как исключение на изменение
                 * модулей не работалов при работе по ethernet для LTR-EU) */
                if (f_crates[i]->descr_flags & LTR_CRATE_DESCR_FLAGS_SUPPORT_POLL_CMD) {
                    t_proc_data *proc_data = (t_proc_data *)f_crates[i]->proc_data;
                    proc_data->poll_recvd_size = sizeof(proc_data->poll_state);

                    poll_err = ltrsrv_crate_add_cmd_to_queue(f_crates[i], LTR_CRATE_CMD_CODE_IOCTL, LTR_CRATE_CMD_REASON_CRATE_POLL,
                                                  f_crates[i]->intf_func.poll_ioctl, 0, 0,
                                                  0, &proc_data->poll_recvd_size,
                                                  (uint8_t*)&proc_data->poll_state,
                                                  LTR_CRATE_CMD_FLAGS_EXTERNAL_BUF, f_cmd_ping_cb, NULL
                                                  );
                } else {
                    poll_err = ltrsrv_crate_add_cmd_to_queue(f_crates[i], LTR_CRATE_CMD_CODE_READ, LTR_CRATE_CMD_REASON_GET_MODULES,
                                                  0, LTR_CRATE_ADDR_MODULES, 0, 2, NULL, NULL,0, f_cmd_ping_cb, NULL);
                }

                if (!poll_err) {
                    ltimer_restart(&f_crates[i]->intf_func.poll_timer);
                }
            }

            ltrsrv_crate_check_close_req(f_crates[i]);
        }
    }
}

int ltrsrv_crates_init(void) {
    return ltrsrv_wait_add_tmr(&f_crates, 0, LTRSRV_BW_CHECK_TIME, f_crates_check_cb, NULL);
}


/** Закрытие всех зарегистрированных крейтов в системе */
void ltrsrv_crates_close(void) {
    int i;
    for (i = f_crates_cnt - 1; i >= 0; --i) {
        f_crates[i]->intf_func.close(f_crates[i], 0);
    }
    ltrsrv_wait_remove_tmr(&f_crates);
}



/* Обработка стандартных ответов на GET_ARRAY при инициализации крейта */
int ltrsrv_crate_init_process_data(t_ltr_crate *crate, uint32_t addr,
                                   const uint8_t *data, uint32_t size) {
    int err = 0;
    switch(addr) {
        case LTR_CRATE_ADDR_MODULE_DESCR:
            if (size < sizeof(t_ltr_device_raw_descr)) {
                err = LTRSRV_ERR_CRATE_RAW_DESRC_SIZE;
            } else {
                const t_ltr_device_raw_descr *descr = (const t_ltr_device_raw_descr *)data;
                /* используем только серийник и, на всякий случай,
                   тип процессора. не очень понятно,
                   зачем все остальное нужно */
                memcpy(crate->descr.serial, descr->SerialNumber,
                       MIN(sizeof(descr->SerialNumber), LTR_CRATE_SERIAL_SIZE));
                memcpy(crate->descr.cpu_type, descr->CpuType,
                       MIN(sizeof(descr->CpuType), LTR_CRATE_CPUTYPE_SIZE));
            }
            break;
        case LTR_CRATE_ADDR_BOOTLOADER_VER:
            if (size < LTR_CRATE_REG_BOOTVER_VER_SIZE) {
                err = LTRSRV_ERR_CRATE_BOOTLOADER_VER_SIZE;
            } else {
                sprintf(crate->descr.bootloader_ver, "%d.%d.%d.%d",
                        data[3], data[2], data[1], data[0]);
            }
            break;
        case LTR_CRATE_ADDR_FIRMWARE_VER:
            if (size < LTR_CRATE_REG_FIRMWARE_VER_SIZE) {
                err = LTRSRV_ERR_CRATE_FIRMVARE_VER_SIZE;
            } else {
                sprintf(crate->descr.soft_ver, "%d.%d.%d.%d",
                        data[3], data[2], data[1], data[0]);
                crate->firm_ver = ((uint32_t)data[3] << 24) |
                                  ((uint32_t)data[2] << 16) |
                                  ((uint32_t)data[1] << 8) |
                                  data[0];

                /* В LTR-EU ревизия поддерживается только в прошивках с версии 2.0.0.0 */
                if ((crate->type == LTR_CRATE_TYPE_LTR032) ||
                        (((crate->type == LTR_CRATE_TYPE_LTR030) ||
                          (crate->type == LTR_CRATE_TYPE_LTR031))
                            && (crate->firm_ver >= 0x2000000))) {
                    crate->descr_flags |= LTR_CRATE_DESCR_FLAGS_SUPPORT_BOARD_REV;
                }

                /* для LTREU крейт возвращает неверную версию загрузчика, поэтому
                   просто ее обнуляем, чтобы не вводить в заблуждение */
                if ((crate->type == LTR_CRATE_TYPE_LTR030) || (crate->type == LTR_CRATE_TYPE_LTR031)) {
                    memset(crate->descr.bootloader_ver, 0, sizeof(crate->descr.bootloader_ver));
                }

                if (((crate->type == LTR_CRATE_TYPE_LTR030) || (crate->type == LTR_CRATE_TYPE_LTR031))
                       && (crate->firm_ver <= 0x2000000)) {
                    crate->descr_flags |= LTR_CRATE_DESCR_FLAGS_HAS_DATA_CLOSE_BUG;
                }



                if ((crate->type == LTR_CRATE_TYPE_LTR032) &&
                    (crate->firm_ver >= 0x02020100)) {
                    crate->descr_flags |= LTR_CRATE_DESCR_FLAGS_SUPPORT_POLL_CMD;
                }

                if ((crate->type == LTR_CRATE_TYPE_LTR_CU_1) ||
                        (crate->type == LTR_CRATE_TYPE_LTR_CEU_1)) {
                    crate->descr_flags |= LTR_CRATE_DESCR_FLAGS_SUPPORT_BOARD_REV |
                            LTR_CRATE_DESCR_FLAGS_SUPPORT_GET_PRIMARY_IFACE;
                }
            }
            break;
        case LTR_CRATE_ADDR_BOARD_REV:
            if (size < LTR_CRATE_REG_BOARD_REV_SIZE) {
                err = LTRSRV_ERR_CRATE_BOARD_REV_SIZE;
            } else  {
                sprintf(crate->descr.brd_revision, "%d", data[0]);
            }
            break;
        case LTR_CRATE_ADDR_SLOT_CONFIG_VER:
            if (size < LTR_CRATE_REG_SLOT_CONFIG_VER_SIZE) {
                err = LTRSRV_ERR_CRATE_SLOTS_CONFIG_VER_SIZE;
            } else {
                crate->descr.slots_config_ver_major = data[0];
                crate->descr.slots_config_ver_minor = data[1];
            }
            break;
        case LTR_CRATE_ADDR_FPGA_STATE:
            /* Для ltr032 можно понять количество используемых слотов по биту регистра */
            if ((size >= 1) && (crate->type==LTR_CRATE_TYPE_LTR032)) {
                if (data[0] &  LTR_CRATE_REG_BITMSK_SLOTS16) {
                    crate->modules_cnt = 16;
                    strcpy(crate->descr.crate_type_name, "KDR-B/F/H-15");
                } else {
                    crate->modules_cnt = 8;
                    strcpy(crate->descr.crate_type_name, "KDR-B/F/H-7");
                }
            } else {
                strcpy(crate->descr.crate_type_name, "KDR-B/F/H-7/15");
                crate->modules_cnt = 16;
            }
            break;
    }
    return err;
}

/***************************************************************************//**
    Функция разберает принятую от FPGA информацию и заполняет нужные поля
    структуры t_ltr_crate_fpga_info. На данный момент это - Name, Version и Comment
    @todo Разбор несколько упращен, не проверяется что после <key> до {} не было
    другого ключа. Но в случае если формат более менее верен, то все ок...

    @param[in] data   Данные от FPGA в виде строки
    @param[out] info  В данной структуре будет сохранена информация о FPGA
    @return           Код ошибки
    ***************************************************************************/
int ltrsrv_crate_init_parse_fpga_data(const char *data, t_ltr_crate_descr *descr) {
    const char *ptag, *p1, *p2;
    ptag=strstr(data, "<Name>");
    if(ptag && (p1=strchr(ptag, '{'))!=0 && (p2=strchr(p1, '}'))!=0) {
        strncpy(descr->fpga_name, p1+1, MIN((int)sizeof(descr->fpga_name), p2-p1-1));
        descr->fpga_name[sizeof(descr->fpga_name)-1] = 0;
    }

    // Version
    ptag=strstr(data, "<Version>");
    if(ptag && (p1=strchr(ptag, '{'))!=0 && (p2=strchr(p1, '}'))!=0) {
        strncpy(descr->fpga_version, p1+1, MIN((int)sizeof(descr->fpga_version), p2-p1-1));
        descr->fpga_version[sizeof(descr->fpga_version)-1] = 0;
    }

    // Comment
    /*
    ptag=strstr(data, "<Comments>");
    if(ptag && (p1=strchr(ptag, '{'))!=0 && (p2=strchr(p1, '}'))!=0) {
        strncpy(info->comment, p1+1, MIN((int)sizeof(info->comment), p2-p1-1));
        info->comment[sizeof(info->comment)-1] = 0;
    }
    */

    return 0;
}


/* установка флага, что крейт нужно будет закрыть */
void ltrsrv_crate_close_req(t_ltr_crate *crate, int err) {
    crate->close_req = 1;
    crate->close_req_err = err;
}

/* закрытие крейта, если был установлен флаг с помощью ltrsrv_crate_close_req() */
void ltrsrv_crate_check_close_req(t_ltr_crate *crate) {
    if (crate->close_req) {
        crate->intf_func.close(crate, crate->close_req_err);
    }
}

int ltrsrv_crate_reopen(t_ltr_crate *crate) {
    int err = _CRATE_CHECK_HND(crate);
    if (!err) {
        if (crate->intf_func.reopen != NULL)
            crate->intf_func.reopen(crate);
    }
    return err;
}

/* Функция вызывается в случае, если канал данных с крейтом был переоткрыт.
   В ней мы проверяем все модули, по которым послали сброс, но не дождались
   ответа, снова в состояние, чтобы по следующему send послать сброс, т.к.
   ответа на старый мы не дождемся по предыдущему каналу */
int ltrsrv_crate_data_channel_reset(t_ltr_crate *crate) {
    int err = _CRATE_CHECK_HND(crate);
    if (!err) {
        int send_req = 0;
        int module_idx;
        for (module_idx = 0; module_idx < crate->modules_cnt; module_idx++) {
            if ((crate->modules[module_idx].state == LTR_MODULE_STATE_INIT) ||
                (crate->modules[module_idx].state == LTR_MODULE_STATE_WAIT_SUBID)) {
                crate->modules[module_idx].state = LTR_MODULE_STATE_NEED_RST;
                send_req = 1;
            }
        }

        if (send_req) {
            ltrsrv_wait_add_tmr(crate, LTRSRV_WAITFLG_AUTODEL, 0, f_snd_tmr_cb, crate);
        }
    }
    return err;
}




int ltrsrv_crate_set_param(t_ltrd_params param, const void *val, uint32_t size) {
    int err = 0;
    uint32_t uintval;
    switch (param) {
        case LTRD_PARAM_MODULE_SEND_BUF_SIZE:
            err = ltrsrv_param_get_uint32(val, size, &uintval);
            if (!err) {
                if (uintval < LTRSRV_MODULE_SEND_BUF_MIN_SIZE) {
                    uintval = LTRSRV_MODULE_SEND_BUF_MIN_SIZE;
                }

                if (uintval != f_module_snd_buf_size) {
                    int crate_idx;

                    f_module_snd_buf_size = uintval;
                    ltrsrv_settings_mark_modified();

                    for (crate_idx = 0; (crate_idx < f_crates_cnt) && !err; ++crate_idx) {
                        t_ltr_crate *crate = f_crates[crate_idx];
                        int slot_idx;
                        for (slot_idx = 0; (slot_idx < crate->modules_cnt) && !err; ++slot_idx) {
                            if (CYCLEBUF_IS_VALID(&crate->modules[slot_idx].sndbuf)) {
                                CYCLEBUF_REALLOC(&crate->modules[slot_idx].sndbuf, f_module_snd_buf_size);
                                if (CYCLEBUF_SIZE(&crate->modules[slot_idx].sndbuf) != f_module_snd_buf_size)  {
                                    err = LTRSRV_ERR_MEMALLOC;
                                }
                            }
                        }
                    }
                }
            }
            break;
        case LTRD_PARAM_MODULE_RECV_BUF_SIZE:
            err = ltrsrv_param_get_uint32(val, size, &uintval);
            if (!err) {
                if (uintval < LTRSRV_MODULE_RECV_BUF_MIN_SIZE) {
                    uintval = LTRSRV_MODULE_RECV_BUF_MIN_SIZE;
                }

                if (uintval != f_module_rcv_buf_size) {
                    int crate_idx;
                    f_module_rcv_buf_size = uintval;
                    ltrsrv_settings_mark_modified();

                    for (crate_idx = 0; (crate_idx < f_crates_cnt) && !err; ++crate_idx) {
                        t_ltr_crate *crate = f_crates[crate_idx];
                        int slot_idx;
                        for (slot_idx = 0; (slot_idx < crate->modules_cnt) && !err; ++slot_idx) {
                            if (CYCLEBUF_IS_VALID(&crate->modules[slot_idx].rcvbuf)) {
                                CYCLEBUF_REALLOC(&crate->modules[slot_idx].rcvbuf, f_module_rcv_buf_size);
                                if (CYCLEBUF_SIZE(&crate->modules[slot_idx].rcvbuf) != f_module_rcv_buf_size)  {
                                    err = LTRSRV_ERR_MEMALLOC;
                                }
                            }
                        }
                    }
                }
            }
            break;
        default:
            err = LTRSRV_ERR_INVALID_PARAM;
            break;
    }

    return err;
}

int ltrsrv_crate_get_param(t_ltrd_params param, void *val, uint32_t *size) {
    int err = 0;
    switch (param) {
        case LTRD_PARAM_MODULE_SEND_BUF_SIZE:
            err = ltrsrv_param_put_uint32(val, size, f_module_snd_buf_size);
            break;
        case LTRD_PARAM_MODULE_RECV_BUF_SIZE:
            err = ltrsrv_param_put_uint32(val, size, f_module_rcv_buf_size);
            break;
        default:
            err = LTRSRV_ERR_INVALID_PARAM;
            break;
    }

    return err;
}

int ltrsrv_crates_exec_for_all(int (*exec_func)(t_ltr_crate *, void *), void *data) {
    int err = 0;
    int i;
    for (i = 0; (i < f_crates_cnt) && !err; ++i) {
        err = exec_func(f_crates[i], data);
    }
    return err;
}
