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

#include "ltr27api.h"

#include "crc.h"
#include "ltrmodule.h"
#include "ltimer.h"
#ifdef LTRAPI_USE_KD_STORESLOTS
#include "ltrslot.h"
#endif



/*================================================================================================*/
#define LTR27CMD_ECHO                         (LTR010CMD_INSTR|0x00)
#define LTR27CMD_ADC_STOP                     (LTR010CMD_INSTR|0x02)
#define LTR27CMD_ADC_START                    (LTR010CMD_INSTR|0x03)
#define LTR27CMD_EEPROM_WRITE_ENABLE_DISABLE  (LTR010CMD_INSTR|0x07)
#define LTR27CMD_READ_BYTE_AVR                (LTR010CMD_INSTR|0x08)
#define LTR27CMD_READ_BYTE_AVR0               (LTR010CMD_INSTR|0x08)
#define LTR27CMD_READ_BYTE_AVR1               (LTR010CMD_INSTR|0x09)
#define LTR27CMD_READ_BYTE_AVR2               (LTR010CMD_INSTR|0x0A)
#define LTR27CMD_READ_BYTE_AVR3               (LTR010CMD_INSTR|0x0B)
#define LTR27CMD_WRITE_BYTE_AVR               (LTR010CMD_INSTR|0x0C)
#define LTR27CMD_READ_BYTE_SUB                (LTR010CMD_INSTR|0x10)
#define LTR27CMD_WRITE_BYTE_SUB               (LTR010CMD_INSTR|0x18)
#define AVR_VAR_BASE  (0x60)
/* максимальный размер пакета команд обрабатываемых модулем */
#define LTR27_PACKET_SIZE   (118)
/* тайм-аут приема ответа на посланую команду */
#define TIMEOUT_CMD_SEND                                2000
#define TIMEOUT_CMD_RECIEVE                             5000

#define LTR27_EXCHANGE_TOUT                             5000

/* число элементов в массиве MezzanineInfo */
#define MINFO_SIZE    (sizeof(f_mezzanine_info)/sizeof(f_mezzanine_info[0]))
/* идентификаторы мезонинов */
#define H27__U01      9
#define H27__U10      10
#define H27__U20      11
#define H27__U30      12
#define H27__U300     13
#define H27__U100     14
#define H27__I5       20
#define H27__I10      21
#define H27__I20      22
#define H27__R100     31
#define H27__R250     32
#define H27__T        40
#define H27__EMPTY   255

#define MODULE_EEPROM_SIZE         (sizeof(TLTR27_DeviceDescription))
#define MEZZANINE_EEPROM_SIZE      (sizeof(TLTR27_MezzanineDescription))

#define ECHO_SIZE (LTR27_PACKET_SIZE)

#define SBUF_SIZE   (sizeof(sbuf)/sizeof(sbuf[0]))
#define RBUF_SIZE   (sizeof(rbuf)/sizeof(rbuf[0]))
#define LTR27_GET_DESCR_DATA_SIZE_MAX (MODULE_EEPROM_SIZE+LTR27_MEZZANINE_CNT*MEZZANINE_EEPROM_SIZE)


/*================================================================================================*/
/* описание мезонинов */
typedef struct {
    WORD ID;                           /* идентификатор субмодуля */
    CHAR Name[16];                     /* название модуля */
    CHAR Unit[16];                     /* измеряемая субмодулем физ.величина */
    double ConvCoeff[2];               /* масштаб и смещение для пересчета кода в физ.величину */
} TMezzanineInfo;

#pragma pack(1)
/* описание модуля в упакованном формате (128 байт) */
typedef struct {
    BYTE  CompanyName[16];
    BYTE  DeviceName[16];
    BYTE  SerialNumber[16];
    BYTE  CpuName[16];
    DWORD CpuClockRate;
    DWORD FirmwareVersion;
    BYTE  DeviceRevision;
    BYTE  Comment[53];
    WORD Crc;
} TLTR27_DeviceDescription;
/* описание мезонина в упакованном формате (128 байт) */
typedef struct {
    WORD  Version;
    WORD  ID;
    WORD  Crc;
    BYTE  SerialNumber[6];
    WORD  CalibrCoef[4];
    BYTE  Comment[108];
} TLTR27_MezzanineDescription;
#pragma pack()

#pragma pack(4)
struct LTR27Config {
    BYTE freq_div;
    struct {
        BYTE company_name[16];
        BYTE dev_name[16];
        BYTE serial[16];
        BYTE rev;
    } card;
    struct {
        BYTE valid;
        BYTE name[16];
        double clk;
        DWORD firmware_ver;
    } cpu;
    struct {
        BYTE valid;
        WORD id;
        BYTE serial[16];
        BYTE rev;
        double user_calibr[4];
        double factory_calibr[4];
    } subcard[LTR27_MEZZANINE_CNT];
};
#pragma pack()


/*================================================================================================*/
static INT f_check_resp(DWORD ack, DWORD cmd);
static int f_exchange(TLTR27 *module, DWORD *src_data, DWORD *des_data, DWORD size);
#ifdef LTRAPI_USE_KD_STORESLOTS
static void hltr27_to_ltr27cfg(const TLTR27 *h_ltr27, struct LTR27Config *cfg);
static void ltr27cfg_to_hltr27(const struct LTR27Config *cfg, TLTR27 *h_ltr27);
static unsigned submodule_id(const char name[16]);
static size_t submodule_ndescr(unsigned id);
#endif

/*================================================================================================*/
/* перечислены все известные h27-мезонины и их параметры */
static const TMezzanineInfo f_mezzanine_info[] = {
    {H27__U01  , "U01\0" , "В\0" , {   2.0/0x8000,  -1.0}},
    {H27__U10  , "U10\0" , "В\0" , {  20.0/0x8000, -10.0}},
    {H27__U20  , "U20\0" , "В\0" , {  20.0/0x8000,   0.0}},
    {H27__U30  , "U30\0" , "В\0" , {  30.0/0x8000,   0.0}},
    {H27__U100 , "U100\0", "В\0" , { 100.0/0x8000,   0.0}},
    {H27__U300 , "U300\0", "В\0" , { 300.0/0x8000,   0.0}},
    {H27__I5   , "I5\0"  , "мА\0", {   5.0/0x8000,   0.0}},
    {H27__I10  , "I10\0" , "мА\0", {  20.0/0x8000, -10.0}},
    {H27__I20  , "I20\0" , "мА\0", {  20.0/0x8000,   0.0}},
    {H27__R100 , "R100\0", "Ом\0", { 100.0/0x8000,   0.0}},
    {H27__R250 , "R250\0", "Ом\0", { 250.0/0x8000,   0.0}},
    {H27__T    , "T\0"   , "мВ\0", { 100.0/0x8000, -25.0}},
    {H27__EMPTY, "EMPTY\0" , "\0", { 100.0/0x8000,   0.0}},
    {H27__EMPTY, "UDEF\0" , "\0" , { 100.0/0x8000,   0.0}}
};

/*================================================================================================*/
/*------------------------------------------------------------------------------------------------*/
#ifdef _WIN32
int WINAPI DllEntryPoint(HINSTANCE hinst, unsigned long reason, void* lpReserved) {
    return 1;
}
#endif

/*------------------------------------------------------------------------------------------------*/
#ifdef _WIN32
BOOL APIENTRY DllMain(HMODULE hModule, DWORD  ul_reason_for_call, LPVOID lpReserved) {
    switch (ul_reason_for_call) {
    case DLL_PROCESS_ATTACH:
    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:
        break;
    }
    return TRUE;
}
#endif

/*------------------------------------------------------------------------------------------------*/
static INT f_check_resp(DWORD ack, DWORD cmd) {
    return (ack & 0xFFFF00DF) == (cmd & 0xFFFF00DF) ? LTR_OK : LTR_ERROR_INVALID_CMD_RESPONSE;
}

/*------------------------------------------------------------------------------------------------*/
static int f_exchange(TLTR27 *h_ltr27, DWORD *src_data, DWORD *des_data, DWORD size) {
    /* Обмен командами с модулем. Размер ответа равен размеру посылки.
     * Функция передает данные блоками размером не больше LTR27_PACKET_SIZE
     */
    unsigned i;
    DWORD rbuf[LTR27_PACKET_SIZE];
    t_ltimer tmr;
    DWORD proc_sz = 0;
    int res = LTR_OK;

    ltimer_set(&tmr, LTIMER_MS_TO_CLOCK_TICKS(LTR27_EXCHANGE_TOUT));

    while ((res == LTR_OK) && (proc_sz < size)) {
        DWORD block_size = MIN(size - proc_sz, LTR27_PACKET_SIZE);
        res = ltr_module_send_cmd(&h_ltr27->Channel, src_data + proc_sz, block_size);
        if (res == LTR_OK) {
            while ((res == LTR_OK) && block_size) {
                INT received = LTR_Recv(&h_ltr27->Channel, rbuf, NULL, block_size, 100);
                if (received < 0)
                    res = received;

                for (i = 0; (res == LTR_OK) && (i < (DWORD)received); ++i) {
                    if ((rbuf[i] & 0xC000) == 0x8000) {
                        res = ltr_module_check_parity(rbuf[i]);
                        if (res == LTR_OK) {
                            des_data[proc_sz++] = rbuf[i];
                            block_size--;
                        }
                    }
                }

                if (block_size && (ltimer_expired(&tmr)))
                    res = LTR_ERROR_RECV_INSUFFICIENT_DATA;
            }
        }
    }
    return res;
}

#ifdef LTRAPI_USE_KD_STORESLOTS
/*------------------------------------------------------------------------------------------------*/
static void hltr27_to_ltr27cfg(const TLTR27 *h_ltr27, struct LTR27Config *cfg) {
    size_t i;
    cfg->freq_div = h_ltr27->FrequencyDivisor;
    memcpy(cfg->card.company_name, h_ltr27->ModuleInfo.Module.CompanyName,
        sizeof cfg->card.company_name);
    memcpy(cfg->card.dev_name, h_ltr27->ModuleInfo.Module.DeviceName, sizeof cfg->card.dev_name);
    memcpy(cfg->card.serial, h_ltr27->ModuleInfo.Module.SerialNumber, sizeof cfg->card.serial);
    cfg->card.rev = h_ltr27->ModuleInfo.Module.Revision;
    cfg->cpu.valid = h_ltr27->ModuleInfo.Cpu.Active;
    memcpy(cfg->cpu.name, h_ltr27->ModuleInfo.Cpu.Name, sizeof cfg->cpu.name);
    cfg->cpu.clk = h_ltr27->ModuleInfo.Cpu.ClockRate;
    cfg->cpu.firmware_ver = h_ltr27->ModuleInfo.Cpu.FirmwareVersion;
    for (i = 0; (i < LTR27_MEZZANINE_CNT); i++) {
        cfg->subcard[i].valid = h_ltr27->ModuleInfo.Mezzanine[i].Active;
        cfg->subcard[i].id = submodule_id((const char *)h_ltr27->ModuleInfo.Mezzanine[i].Name);
        memcpy(cfg->subcard[i].serial, h_ltr27->ModuleInfo.Mezzanine[i].SerialNumber,
            sizeof cfg->subcard[i].serial);
        cfg->subcard[i].rev = h_ltr27->ModuleInfo.Mezzanine[i].Revision;
        memcpy(cfg->subcard[i].user_calibr, h_ltr27->Mezzanine[i].CalibrCoeff,
            sizeof cfg->subcard[i].user_calibr);
        memcpy(cfg->subcard[i].factory_calibr, h_ltr27->ModuleInfo.Mezzanine[i].Calibration,
            sizeof cfg->subcard[i].factory_calibr);
    }
}
#endif

/*------------------------------------------------------------------------------------------------*/
LTR27API_DllExport(INT) LTR27_ADCStart(TLTR27 *h_ltr27) {
    DWORD dst_data;
    DWORD src_data;
    INT ret_val = LTR27_IsOpened(h_ltr27);

    if (ret_val == LTR_OK)
        ret_val = LTR27_ADCStop(h_ltr27);

    if (ret_val == LTR_OK) {
        src_data = ltr_module_fill_cmd_parity(LTR27CMD_ADC_START, 0);
        ret_val = f_exchange(h_ltr27, &src_data, &dst_data, 1);
    }

    if (ret_val == LTR_OK)
        ret_val = f_check_resp(dst_data, src_data);

    if (ret_val == LTR_OK)
        h_ltr27->subchannel = 0;
#ifdef LTRAPI_USE_KD_STORESLOTS
    if (ret_val == LTR_OK) {
        t_ltrslot_conn h_slot_conn = LTRSLOT_CONN_INVAL;
        int issupported_slots;
        ret_val = ltrslot_replicate_connection(&h_ltr27->Channel, &h_slot_conn);

        if (ret_val == LTR_OK)
            ret_val = ltrslot_check_support(h_slot_conn, &issupported_slots);

        if ((ret_val == LTR_OK) && issupported_slots) {
            struct LTRSlotInfo *h_slot_info = malloc(sizeof(struct LTRSlotInfo));
            struct LTR27Config *cfg = malloc(sizeof(struct LTR27Config));
            if ((h_slot_info == NULL) || (cfg == NULL))
                ret_val = LTR_ERROR_MEMORY_ALLOC;

            if (ret_val == LTR_OK) {
                hltr27_to_ltr27cfg(h_ltr27, cfg);
                ret_val = ltrslot_put_config(h_slot_conn, sizeof(struct LTR27Config), cfg);
            }
            if (ret_val == LTR_OK) {
                h_slot_info->status = LTRSLOT_STATUSFLG_RUN | LTRSLOT_STATUSFLG_CONFIG;
                h_slot_info->card_id = LTR_MID_LTR27;
                h_slot_info->start_mode = LTR_CARD_START_OFF;
                ret_val = ltrslot_put_info(h_slot_conn, h_slot_info);
            }

            free(cfg);
            free(h_slot_info);
        }

        ltrslot_destroy_connection(h_slot_conn);
    }
#endif

    return ret_val;
}

/*------------------------------------------------------------------------------------------------*/
LTR27API_DllExport(INT) LTR27_ADCStop(TLTR27 *h_ltr27) {
    INT ret_val = LTR27_IsOpened(h_ltr27);

    if (ret_val == LTR_OK) {
        DWORD cmd = ltr_module_fill_cmd_parity(LTR27CMD_ADC_STOP, 0);
        ret_val = ltr_module_stop(&h_ltr27->Channel, &cmd, 1, LTR27CMD_ADC_STOP,
                                  LTR_MSTOP_FLAGS_CHECK_PARITY, 0, NULL);
    }
#ifdef LTRAPI_USE_KD_STORESLOTS
    if (ret_val == LTR_OK)
        ltrslot_stop(&h_ltr27->Channel);
#endif

    return ret_val;
}

/*------------------------------------------------------------------------------------------------*/
LTR27API_DllExport(INT) LTR27_Close(TLTR27 *h_ltr27) {
    INT res = (h_ltr27 == NULL) ? LTR_ERROR_INVALID_MODULE_DESCR : LTR_OK;
    if (res == LTR_OK)
        res = LTR_Close(&h_ltr27->Channel);
    return res;
}

/*------------------------------------------------------------------------------------------------*/
LTR27API_DllExport(INT) LTR27_Echo(TLTR27 *h_ltr27) {
    INT res = LTR27_IsOpened(h_ltr27);
    if (res == LTR_OK) {
        DWORD des_data[ECHO_SIZE];
        DWORD src_data[ECHO_SIZE];
        unsigned i;


        for (i = 0; i < ECHO_SIZE; ++i) {
            src_data[i] = ltr_module_fill_cmd_parity(LTR27CMD_ECHO, rand());
        }

        res = f_exchange(h_ltr27, src_data, des_data, ECHO_SIZE);

        if (res == LTR_OK) {
            for (i = 0; (res == LTR_OK) && (i < ECHO_SIZE); ++i) {
                res = f_check_resp(des_data[i], src_data[i]);
            }
        }
    }
    return res;
}

/*------------------------------------------------------------------------------------------------*/
LTR27API_DllExport(INT) LTR27_GetConfig(TLTR27 *h_ltr27) {
    INT res = LTR27_IsOpened(h_ltr27);
    if (res == LTR_OK) {
        DWORD des_data[1 + LTR27_MEZZANINE_CNT];
        DWORD src_data[1 + LTR27_MEZZANINE_CNT];
        WORD i;

        /* читаем значение делителя частоты из памяти AVR и ID-мезанинов из их памяти */
        src_data[0] = LTR_MODULE_FILL_CMD_PARITY_BYTES(LTR27CMD_READ_BYTE_AVR, AVR_VAR_BASE, 0);
        for (i = 0; i < LTR27_MEZZANINE_CNT; ++i) {
            src_data[i+1] = LTR_MODULE_FILL_CMD_PARITY_BYTES(LTR27CMD_READ_BYTE_SUB + i, 2, 0);
        }
        res = f_exchange(h_ltr27, src_data, des_data, 1+LTR27_MEZZANINE_CNT);

        if (res == LTR_OK) {
            for (i = 0; (res == LTR_OK) && (i < (1 + LTR27_MEZZANINE_CNT)); ++i) {
                if ((src_data[i] & 0xFF0000DF) != (des_data[i] & 0xFF0000DF))
                    res = LTR_ERROR_INVALID_CMD_RESPONSE;
            }
        }

        if (res == LTR_OK) {
            h_ltr27->FrequencyDivisor = (BYTE)((des_data[0] >> 16) & 0xFF);
            for (i = 0; i < LTR27_MEZZANINE_CNT; ++i) {
                int fnd;
                BYTE j;
                BYTE id = (BYTE)((des_data[i+1] >> 16) & 0xFF);

                /* ищем по полученному id информацию о модуле из таблицы и заполняем поля */
                for (j = 0, fnd = 0; !fnd && (j < (MINFO_SIZE - 1)); ++j) {
                    if (f_mezzanine_info[j].ID == id) {
                        fnd = 1;
                        strcpy(h_ltr27->Mezzanine[i].Name, f_mezzanine_info[j].Name);
                        strcpy(h_ltr27->Mezzanine[i].Unit, f_mezzanine_info[j].Unit);
                        h_ltr27->Mezzanine[i].ConvCoeff[0] = f_mezzanine_info[j].ConvCoeff[0];
                        h_ltr27->Mezzanine[i].ConvCoeff[1] = f_mezzanine_info[j].ConvCoeff[1];
                    }
                }
            }
        }
    }
    return res;
}

/*------------------------------------------------------------------------------------------------*/
LTR27API_DllExport(INT) LTR27_GetDescription(TLTR27 *h_ltr27, WORD flags) {
    WORD i;
    DWORD des_data[LTR27_GET_DESCR_DATA_SIZE_MAX];
    DWORD src_data[LTR27_GET_DESCR_DATA_SIZE_MAX];
    DWORD cmd_cnt = 0;
    INT res = LTR27_IsOpened(h_ltr27);

    if (res == LTR_OK) {
        /* формируем блок команд */
        if (flags & LTR27_MODULE_DESCRIPTION) {
            for (i = 0; i < MODULE_EEPROM_SIZE; ++i) {
                src_data[i] = LTR_MODULE_FILL_CMD_PARITY_BYTES(LTR27CMD_READ_BYTE_AVR3, 0x80 + i, 0);
            }
            cmd_cnt += MODULE_EEPROM_SIZE;
            /* признак версии PLD сохраняем неизменным */
            memset(&h_ltr27->ModuleInfo.Module, 0, sizeof(TLTR_DESCRIPTION_MODULE) - 2);
            memset(&h_ltr27->ModuleInfo.Cpu, 0, sizeof(TLTR_DESCRIPTION_CPU));
        }
        for (i = 0; i < LTR27_MEZZANINE_CNT; ++i) {
            if (flags & (1 << (i + 1))) {
                WORD j;
                for (j = 0; j < MEZZANINE_EEPROM_SIZE; ++j) {
                    src_data[cmd_cnt+j] = LTR_MODULE_FILL_CMD_PARITY_BYTES(
                                LTR27CMD_READ_BYTE_SUB + i, j, 0);
                }
                cmd_cnt += MEZZANINE_EEPROM_SIZE;
                memset(&h_ltr27->ModuleInfo.Mezzanine[i], 0, sizeof(TLTR_DESCRIPTION_MEZZANINE));
            }
        }
        /* отсылаем блок команд и принимаем ответ */
        res = f_exchange(h_ltr27, src_data, des_data, cmd_cnt);
    }

    if (res == LTR_OK) {
        /* проверка полей адреса и команд */
        for (i = 0; (res == LTR_OK) && (i < cmd_cnt); ++i) {
            if ((src_data[i] & 0xFF0000DF) != (des_data[i] & 0xFF0000DF))
                res = LTR_ERROR_INVALID_CMD_RESPONSE;
        }
        cmd_cnt = 0;
        /* распаковка полей структуры */
        if (res == LTR_OK) {
            /* распаковываем описание модуля */
            if (flags & LTR27_MODULE_DESCRIPTION) {
                WORD Crc;
                TLTR27_DeviceDescription ddescr;

                for (i = 0; i < MODULE_EEPROM_SIZE; ++i) {
                    ((BYTE*)&ddescr)[i] = (BYTE)(des_data[i] >> 16);
                }
                /* проверяем кс */
                Crc = eval_crc16(0, (BYTE*)&ddescr, MODULE_EEPROM_SIZE-sizeof(WORD));
                if (ddescr.Crc == Crc) {
                    /* module module->ModuleInfo */
                    memcpy(h_ltr27->ModuleInfo.Module.CompanyName, ddescr.CompanyName,
                           MIN(sizeof(h_ltr27->ModuleInfo.Module.CompanyName),
                           sizeof(ddescr.CompanyName)));
                    memcpy(h_ltr27->ModuleInfo.Module.DeviceName, ddescr.DeviceName,
                           MIN(sizeof(h_ltr27->ModuleInfo.Module.DeviceName),
                           sizeof(ddescr.DeviceName)));
                    memcpy(h_ltr27->ModuleInfo.Module.SerialNumber, ddescr.SerialNumber,
                           MIN(sizeof(h_ltr27->ModuleInfo.Module.SerialNumber),
                           sizeof(ddescr.SerialNumber)));
                    h_ltr27->ModuleInfo.Module.Revision = ddescr.DeviceRevision;
                    /* cpu module->ModuleInfo */
                    h_ltr27->ModuleInfo.Cpu.Active = 1;
                    memcpy(h_ltr27->ModuleInfo.Cpu.Name, ddescr.CpuName,
                           MIN(sizeof(h_ltr27->ModuleInfo.Cpu.Name), sizeof(ddescr.CpuName)));
                    h_ltr27->ModuleInfo.Cpu.ClockRate = ddescr.CpuClockRate;
                    h_ltr27->ModuleInfo.Cpu.FirmwareVersion = ddescr.FirmwareVersion;
                    memcpy(h_ltr27->ModuleInfo.Cpu.Comment, ddescr.Comment,
                           MIN(sizeof(h_ltr27->ModuleInfo.Cpu.Comment), sizeof(ddescr.Comment)));
                } else {
                    strcpy((char *)h_ltr27->ModuleInfo.Module.CompanyName, "L-CARD Inc.");
                    strcpy((char *)h_ltr27->ModuleInfo.Module.DeviceName, "LTR27");
                    strcpy((char *)h_ltr27->ModuleInfo.Module.SerialNumber, "EMPTY");
                }
                cmd_cnt += MODULE_EEPROM_SIZE;
            }
            /* распаковываем описание мезонинов */
            for (i = 0; i < LTR27_MEZZANINE_CNT; ++i) {
                if (flags & (1 << (i + 1))) {
                    WORD Crc;
                    unsigned j;
                    TLTR27_MezzanineDescription mdescr;

                    for (j = 0; j < MEZZANINE_EEPROM_SIZE; ++j) {
                        ((BYTE*)&mdescr)[j] = (BYTE)(des_data[cmd_cnt + j] >> 16);
                    }
                    /* проверяем кс */
                    Crc = eval_crc16(0, ((BYTE *)&mdescr)+0, 4);
                    Crc = eval_crc16(Crc, ((BYTE *)&mdescr)+6, MEZZANINE_EEPROM_SIZE-6);
                    if (mdescr.Crc == Crc) {                        
                        int fnd;

                        size_t len;
                        size_t lenData;
                        BYTE RR;

                        h_ltr27->ModuleInfo.Mezzanine[i].Active = 1;

                        /* ищем описание мезанина */
                        for (j = 0, fnd = 0; !fnd && (j < (MINFO_SIZE - 1)); ++j) {
                            if (f_mezzanine_info[j].ID == (mdescr.ID & 0xFF)) {
                                fnd = 1;
                                strcpy((char *)h_ltr27->ModuleInfo.Mezzanine[i].Name,
                                       f_mezzanine_info[j].Name);
                                RR = mdescr.SerialNumber[5] & 0xF8;
                                if (RR == 0xA8) {
                                    char sernum_text[9];
                                    char zero_padding[9];
                                    DWORD ser = mdescr.SerialNumber[2] |
                                        ((DWORD)mdescr.SerialNumber[3] << 8) |
                                        ((DWORD)mdescr.SerialNumber[4] << 16);
                                    /* читаем с учётом длины серийника */
                                    len = mdescr.SerialNumber[5] & 0x07;
                                    sprintf(sernum_text, "%d", ser);
                                    lenData = strlen(sernum_text);
                                    zero_padding[0] = 0;
                                    if (lenData < len) {
                                        for(j = 0; j < (len - lenData); ++j) {
                                            zero_padding[j] = 48;
                                            zero_padding[j+1] = 0;
                                        }
                                    }
                                    sprintf((char *)h_ltr27->ModuleInfo.Mezzanine[i].SerialNumber,
                                            "%i%c%s%s", mdescr.SerialNumber[0], mdescr.SerialNumber[1],
                                            zero_padding, sernum_text);
                                } else {
                                    /* читаем по старому */
                                    DWORD ser = mdescr.SerialNumber[2] |
                                            ((DWORD)mdescr.SerialNumber[3] << 8) |
                                            ((DWORD)mdescr.SerialNumber[4] << 16);
                                    sprintf((char *)h_ltr27->ModuleInfo.Mezzanine[i].SerialNumber,
                                        "%i%c%d", mdescr.SerialNumber[0], mdescr.SerialNumber[1],
                                        ser);
                                }
                            }
                        }

                        for (j = 2; j < 8; ++j) {
                            if (h_ltr27->ModuleInfo.Mezzanine[i].SerialNumber[j] == 32)
                                h_ltr27->ModuleInfo.Mezzanine[i].SerialNumber[j] = 48;
                        }

                        h_ltr27->ModuleInfo.Mezzanine[i].Revision = '0' + mdescr.Version;
                        for (j = 0; j < 4; j+=2) {
                            WORD offset = mdescr.CalibrCoef[j+1];
                            WORD scale = mdescr.CalibrCoef[j+0];
                            h_ltr27->ModuleInfo.Mezzanine[i].Calibration[j+0] = (double)scale /
                                0x8000;
                            if (offset < 0xC000) {
                                h_ltr27->ModuleInfo.Mezzanine[i].Calibration[j+1] = (double)offset;
                            } else {
                                h_ltr27->ModuleInfo.Mezzanine[i].Calibration[j+1] =
                                    (double)(*(SHORT *)&offset);
                            }
                        }
                        memcpy(h_ltr27->ModuleInfo.Mezzanine[i].Comment, mdescr.Comment,
                               MIN(sizeof(h_ltr27->ModuleInfo.Mezzanine[i].Comment),
                               sizeof(mdescr.Comment)));
                    } /* if (mdescr.Crc == Crc)*/
                    cmd_cnt += MEZZANINE_EEPROM_SIZE;
                } /*if (flags & (1 << (i + 1)))*/
            } /*for (i = 0; (i < LTR27_MEZZANINE_CNT); i++)*/
        } /*if (res == LTR_OK)*/
    } /*if (res == LTR_OK)*/

    return res;
}

/*------------------------------------------------------------------------------------------------*/
LTR27API_DllExport(LPCSTR) LTR27_GetErrorString(INT err) {
    return LTR_GetErrorString(err);
}


static void f_info_init(TLTR27 *hnd) {
    unsigned i;

    memset(&hnd->ModuleInfo, 0, sizeof(hnd->ModuleInfo));
    strcpy((CHAR*)hnd->ModuleInfo.Module.DeviceName, "LTR27");

    for (i = 0; i < LTR27_MEZZANINE_CNT; ++i) {
        strcpy(hnd->Mezzanine[i].Name, f_mezzanine_info[MINFO_SIZE-1].Name);
        strcpy(hnd->Mezzanine[i].Unit, f_mezzanine_info[MINFO_SIZE-1].Unit);
        hnd->Mezzanine[i].ConvCoeff[0] = f_mezzanine_info[MINFO_SIZE-1].ConvCoeff[0];
        hnd->Mezzanine[i].ConvCoeff[0] = f_mezzanine_info[MINFO_SIZE-1].ConvCoeff[1];
        hnd->Mezzanine[i].CalibrCoeff[0] = 1.0;
        hnd->Mezzanine[i].CalibrCoeff[1] = 0.0;
        hnd->Mezzanine[i].CalibrCoeff[2] = 1.0;
        hnd->Mezzanine[i].CalibrCoeff[3] = 0.0;
    }
}


/*------------------------------------------------------------------------------------------------*/
LTR27API_DllExport(INT) LTR27_Init(TLTR27 *h_ltr27) {
    INT res = (h_ltr27 == NULL) ? LTR_ERROR_INVALID_MODULE_DESCR : LTR_OK;
    if (res == LTR_OK) {
        memset(h_ltr27, 0, sizeof(TLTR27));
        h_ltr27->size = sizeof(TLTR27);
        res = LTR_Init(&h_ltr27->Channel);
        if (res == LTR_OK) {
            f_info_init(h_ltr27);
            h_ltr27->subchannel = 0;
            h_ltr27->FrequencyDivisor = 0;
        }
    } else {
        res = LTR_ERROR_PARAMETRS;
    }
    return res;
}

/*------------------------------------------------------------------------------------------------*/
LTR27API_DllExport(INT) LTR27_IsOpened(TLTR27 *h_ltr27) {
    return (h_ltr27 == NULL) ? LTR_ERROR_INVALID_MODULE_DESCR : LTR_IsOpened(&h_ltr27->Channel);
}

/*------------------------------------------------------------------------------------------------*/
LTR27API_DllExport(INT) LTR27_Open(TLTR27 *hnd, DWORD net_addr, WORD net_port,
                                   const CHAR *crate_sn, WORD slot) {
    INT warning;
    DWORD open_flags = 0;
    INT err = (hnd == NULL) ? LTR_ERROR_INVALID_MODULE_DESCR : LTR_OK;
    if (err == LTR_OK) {
        DWORD ack;
        if (LTR27_IsOpened(hnd) == LTR_OK) {
            LTR27_Close(hnd);
        }
        if (err == LTR_OK) {
            err = ltr_module_open(&hnd->Channel, net_addr, net_port, crate_sn, slot,
                                  LTR_MID_LTR27, &open_flags, &ack, &warning);
        }

        if (err == LTR_OK) {
            f_info_init(hnd);
            hnd->ModuleInfo.Module.VerPLD = ack & 0x3F;
            hnd->ModuleInfo.Module.VerPLDIsValid = 1;
        }
    }

    if ((err == LTR_OK) && !(open_flags & LTR_MOPEN_OUTFLAGS_DONT_INIT)) {
        LTRAPI_SLEEP_MS(500);
    }

    return (err == LTR_OK) ? warning : err;
}

/*------------------------------------------------------------------------------------------------*/
LTR27API_DllExport(INT) LTR27_OpenEx(TLTR27 *h_ltr27, DWORD net_addr, WORD net_port,
                                     const CHAR *crate_sn, WORD slot, DWORD in_flags,
                                     DWORD *out_flags) {
    DWORD open_flags = 0;
    DWORD out_flg = 0;
    INT ret_val = (h_ltr27 == NULL) ? LTR_ERROR_INVALID_MODULE_DESCR : LTR_OK;
    INT warning = LTR_OK;

    if (in_flags & LTR_OPENINFLG_REOPEN) {
#ifdef LTRAPI_USE_KD_STORESLOTS
        int issupported_slots = 0;
        struct LTRSlotInfo *h_slot_info = NULL;
        t_ltrslot_conn h_slot_conn = LTRSLOT_CONN_INVAL;
        if (ret_val == LTR_OK)
            ret_val = ltrslot_create_connection(net_addr, net_port, crate_sn, slot, &h_slot_conn);

        if (ret_val == LTR_OK)
            ret_val = ltrslot_check_support(h_slot_conn, &issupported_slots);

        if (issupported_slots) {
            if (ret_val == LTR_OK) {
                if ((h_slot_info = malloc(sizeof(struct LTRSlotInfo))) == NULL)
                    ret_val = LTR_ERROR_MEMORY_ALLOC;
            }

            if (ret_val == LTR_OK)
                ret_val = ltrslot_get_info(h_slot_conn, h_slot_info);

            if (ret_val == LTR_OK) {
                if (h_slot_info->status & LTRSLOT_STATUSFLG_RUN) {
                    struct LTR27Config *cfg = malloc(sizeof(struct LTR27Config));

                    if (cfg == NULL)
                        ret_val = LTR_ERROR_MEMORY_ALLOC;
                    if (ret_val == LTR_OK)
                        ret_val = ltrslot_get_config(h_slot_conn, sizeof(struct LTR27Config), cfg);
                    if (ret_val == LTR_OK) {
                        ltr27cfg_to_hltr27(cfg, h_ltr27);
                        out_flg = LTR_OPENOUTFLG_REOPEN;
                    }
                    free(cfg);
                }
            }
        }

        ltrslot_destroy_connection(h_slot_conn);
        free(h_slot_info);
#else
        ret_val = LTR_ERROR_NOT_IMPLEMENTED;
#endif
    }

    if (ret_val == LTR_OK) {
        if (out_flg & LTR_OPENOUTFLG_REOPEN)
            open_flags |= LTR_MOPEN_INFLAGS_DONT_RESET;
        ret_val = ltr_module_open(&h_ltr27->Channel, net_addr, net_port, crate_sn,
                                  slot, LTR_MID_LTR27, &open_flags, NULL, &warning);

        if (ret_val == LTR_OK) {
            f_info_init(h_ltr27);
        }
    }



    if ((ret_val == LTR_OK) && !(open_flags & LTR_MOPEN_OUTFLAGS_DONT_INIT))
        LTRAPI_SLEEP_MS(20);

    if (out_flags != NULL)
        *out_flags = out_flg;


    return (ret_val == LTR_OK) ? warning : ret_val;
}

/*------------------------------------------------------------------------------------------------*/
LTR27API_DllExport(INT) LTR27_ProcessData(TLTR27 *h_ltr27, const DWORD *src_data, double *dst_data,
                                          DWORD *size, BOOL calibr, BOOL value) {
    DWORD flags = 0;
    if (calibr)
        flags |= LTR27_PROC_FLAG_CALIBR;
    if (value)
        flags |= LTR27_PROC_FLAG_CONV_VALUE;

    return LTR27_ProcessDataEx(h_ltr27, src_data, dst_data, size, flags, 0xFFFF, NULL);
}

LTR27API_DllExport(INT) LTR27_ProcessDataEx(TLTR27 *h_ltr27, const DWORD *src_data, double *dst_data,
                                            DWORD *size, DWORD flags, DWORD ch_mask, void *reserved) {
    INT res = h_ltr27 == NULL ? LTR_ERROR_INVALID_MODULE_DESCR : LTR_OK;
    if ((res == LTR_OK) && (src_data == NULL))
        res = LTR_ERROR_PARAMETERS;
    if (res == LTR_OK) {
        /* подготовка коэффициентов */
        DWORD ch_num;
        DWORD chn;
        DWORD i;
        double offset1[LTR27_CHANNEL_CNT];
        double offset2[LTR27_CHANNEL_CNT];
        double scale1[LTR27_CHANNEL_CNT];
        double scale2[LTR27_CHANNEL_CNT];
        BOOLEAN ch_en[LTR27_CHANNEL_CNT];
        DWORD put_pos = 0;
        DWORD proc_size = *size;

        for(chn = 0; chn < LTR27_CHANNEL_CNT; ++chn) {
            ch_en[chn] = (ch_mask & (1 << chn)) ? TRUE : FALSE;
            if (ch_en[chn]) {
                /* калибровочные коэффициенты */
                if (flags & LTR27_PROC_FLAG_CALIBR) {
                    scale1[chn] = h_ltr27->Mezzanine[chn>>1].CalibrCoeff[((chn & 1) << 1) + 0];
                    offset1[chn] = h_ltr27->Mezzanine[chn>>1].CalibrCoeff[((chn & 1) << 1) + 1];
                } else {
                    scale1[chn] = 1.0;
                    offset1[chn] = 0.0;
                }
                /* пересчет масштаба для выбраной частоты дискретизации */
                scale1[chn] = scale1[chn] * 0x7FFF / 250 / (h_ltr27->FrequencyDivisor + 1);
                /* коэффициенты для пересчета в физические величины */
                if (flags & LTR27_PROC_FLAG_CONV_VALUE) {
                    scale2[chn] = h_ltr27->Mezzanine[chn>>1].ConvCoeff[0];
                    offset2[chn] = h_ltr27->Mezzanine[chn>>1].ConvCoeff[1];
                } else {
                    scale2[chn] = 1.0;
                    offset2[chn] = 0.0;
                }
            }
        }

        for (i = 0, ch_num = src_data[0] & 0x0F; (res == LTR_OK) && (i < proc_size); ++i) {
            double data;
            DWORD dw = src_data[i];

            res = ltr_module_check_parity(dw);
            if (res == LTR_OK) {
                chn = dw & 0x0F;
                if (ch_num != chn) {
                    res = LTR_ERROR_PROCDATA_CHNUM;
                }
            }

            if (res == LTR_OK) {
                if (ch_en[chn]) {
                    /* корректировка данных */
                    dw = (DWORD)((dw >> 16) * scale1[chn] + offset1[chn]);
                    if (dw < 0xC000) {
                        data = (WORD)dw;
                    } else {
                        data = (SHORT)dw;
                    }
                    /* перевод в физ.величины */
                    dst_data[put_pos++] = scale2[chn] * data + offset2[chn];
                }

                if (++ch_num > 15) {
                    ch_num = 0;
                }
            }
        }
        *size = put_pos;
    } /*if (res == LTR_OK)*/

    return res;
}


/*------------------------------------------------------------------------------------------------*/
LTR27API_DllExport(INT) LTR27_Recv(TLTR27 *h_ltr27, DWORD *data, DWORD *tstamp, DWORD size,
    DWORD timeout) {
    INT res = (h_ltr27 == NULL) ? LTR_ERROR_INVALID_MODULE_DESCR : LTR_OK;
    if (res == LTR_OK) {
        res = LTR_Recv(&h_ltr27->Channel, data, tstamp, size, timeout);
        /* помимо обычного Recv выполняется проверка флага переполнения
         * и индексов данных от субмодулей
         */
        if ((res >= 0) && (h_ltr27->Channel.flags & LTR_FLAG_RBUF_OVF))
            res = LTR_ERROR_RECV_OVERFLOW;

        if (res > 0)
            h_ltr27->subchannel = data[res-1] & 0x0F;
    }
    return res;
}

/*------------------------------------------------------------------------------------------------*/
LTR27API_DllExport(INT) LTR27_SearchFirstFrame(TLTR27 *h_ltr27, const DWORD *data, DWORD size,
    DWORD *frame_idx) {
    INT ret_val = LTR_OK;

    if ((h_ltr27 == NULL) || (data == NULL) || (frame_idx == NULL))
        ret_val = LTR_ERROR_PARAMETERS;

    if (ret_val == LTR_OK) {
        DWORD i;
        DWORD idx_first;
        int isfound_frame = 0;
        const DWORD *pdata = data;
        for (i = 0; i < size; ++i) {
            if ((isfound_frame = ((*pdata++ & 0x0F) == 0))) {
                idx_first = i;
                break;
            }
        }
        if (isfound_frame) {
            *frame_idx = idx_first;
        } else {
            ret_val = LTR_ERROR_FIRSTFRAME_NOTFOUND;
        }
    }

    return ret_val;
}

/*------------------------------------------------------------------------------------------------*/
LTR27API_DllExport(INT) LTR27_SetConfig(TLTR27 *h_ltr27) {
    INT ret_code = LTR_OK;

    DWORD dst_data;
    DWORD src_data;


    ret_code = LTR27_IsOpened(h_ltr27);

    if (ret_code == LTR_OK) {
        src_data = LTR_MODULE_FILL_CMD_PARITY_BYTES(LTR27CMD_WRITE_BYTE_AVR, AVR_VAR_BASE,
            h_ltr27->FrequencyDivisor);
        ret_code = f_exchange(h_ltr27, &src_data, &dst_data, 1);
    }

    if (ret_code == LTR_OK)
        ret_code = f_check_resp(dst_data, src_data);

    return ret_code;
}

/*------------------------------------------------------------------------------------------------*/
LTR27API_DllExport(INT) LTR27_StoreConfig(TLTR27 *h_ltr27, TLTR_CARD_START_MODE start_mode) {
#ifdef LTRAPI_USE_KD_STORESLOTS
    int slots_supported;
    t_ltrslot_conn h_slot_conn = LTRSLOT_CONN_INVAL;
    struct LTRSlotInfo *h_slot_info = NULL;
    INT ret_code = LTR27_IsOpened(h_ltr27);


    if (ret_code == LTR_OK)
        ret_code = ltrslot_replicate_connection(&h_ltr27->Channel, &h_slot_conn);

    if (ret_code == LTR_OK)
        ret_code = ltrslot_check_support(h_slot_conn, &slots_supported);

    if (ret_code == LTR_OK) {
        if (!slots_supported)
            ret_code = LTR_ERROR_CARDSCONFIG_UNSUPPORTED;
    }

    if (ret_code == LTR_OK) {
        if ((h_slot_info = malloc(sizeof(struct LTRSlotInfo))) == NULL)
            ret_code = LTR_ERROR_MEMORY_ALLOC;
    }

    if (ret_code == LTR_OK)
        ret_code = ltrslot_get_info(h_slot_conn, h_slot_info);

    if (ret_code == LTR_OK) {
        if (!(h_slot_info->status & LTRSLOT_STATUSFLG_CONFIG)) {
            // Put config before saving
            struct LTR27Config *cfg = malloc(sizeof(struct LTR27Config));
            if (cfg == NULL)
                ret_code = LTR_ERROR_MEMORY_ALLOC;
            if (ret_code == LTR_OK) {
                hltr27_to_ltr27cfg(h_ltr27, cfg);
                ret_code = ltrslot_put_config(h_slot_conn, sizeof(struct LTR27Config), cfg);
            }
            free(cfg);
        }
    }

    if (ret_code == LTR_OK) {
        h_slot_info->status |= LTRSLOT_STATUSFLG_CONFIG;
        h_slot_info->card_id = LTR_MID_LTR27;
        h_slot_info->start_mode = start_mode;
        ret_code = ltrslot_put_info(h_slot_conn, h_slot_info);
    }

    if (ret_code == LTR_OK)
        ret_code = ltrslot_save_settings(h_slot_conn);


    ltrslot_destroy_connection(h_slot_conn);

    free(h_slot_info);

    return ret_code;
#else
    return LTR_ERROR_NOT_IMPLEMENTED;
#endif
}

/*------------------------------------------------------------------------------------------------*/
LTR27API_DllExport(INT) LTR27_WriteMezzanineDescr(TLTR27 *h_ltr27, BYTE mn) {
    size_t len;
    TLTR27_MezzanineDescription mdescr;
    char MSN[16];
    INT res = (mn >= LTR27_MEZZANINE_CNT) ? LTR_ERROR_PARAMETERS : LTR27_IsOpened(h_ltr27);

    if (res == LTR_OK) {
        WORD Crc;
        DWORD des_data[MEZZANINE_EEPROM_SIZE+2];
        int fnd;
        unsigned i;
        DWORD ser;
        DWORD src_data[MEZZANINE_EEPROM_SIZE+2];

        /* упаковка дескриптора */
        mdescr.SerialNumber[0] = atoi((char *)&h_ltr27->ModuleInfo.Mezzanine[mn].SerialNumber[0]);
        mdescr.SerialNumber[1] = h_ltr27->ModuleInfo.Mezzanine[mn].SerialNumber[1];
        ser = atoi((char *)&h_ltr27->ModuleInfo.Mezzanine[mn].SerialNumber[2]);
        mdescr.SerialNumber[2] = ser & 0xFF;
        mdescr.SerialNumber[3] = (ser >> 8) & 0xFF;
        mdescr.SerialNumber[4] = (ser >> 16) & 0xFF;
        mdescr.SerialNumber[5] = (ser >> 24) & 0xFF;

        memcpy(MSN, h_ltr27->ModuleInfo.Mezzanine[mn].SerialNumber, 16);
        len = strlen(MSN);        /* длина серийного номера (-2, т.к. первые символы есть всегда) */
        if (len <= 8) {
            len = 0xA8 + (len - 2);
        }

        /* не используется в старых субмодулях, а в новых будем писать длину серийника
         * 0xFF - значит старый модуль, 0xA8 - признак что тут есть длина;
         * длина в последних 3х битах
         */
        mdescr.SerialNumber[5] = (BYTE)len;

        mdescr.CalibrCoef[0] = (WORD)(h_ltr27->ModuleInfo.Mezzanine[mn].Calibration[0] * 0x8000);
        mdescr.CalibrCoef[1] = (SHORT)(h_ltr27->ModuleInfo.Mezzanine[mn].Calibration[1]);
        mdescr.CalibrCoef[2] = (WORD)(h_ltr27->ModuleInfo.Mezzanine[mn].Calibration[2] * 0x8000);
        mdescr.CalibrCoef[3] = (SHORT)(h_ltr27->ModuleInfo.Mezzanine[mn].Calibration[3]);

        memcpy(&mdescr.Comment, &h_ltr27->ModuleInfo.Mezzanine[mn].Comment,
               MIN(sizeof(mdescr.Comment),
                   sizeof(h_ltr27->ModuleInfo.Mezzanine[mn].Comment)));

        mdescr.Version = h_ltr27->ModuleInfo.Mezzanine[mn].Revision - '0';

        for (fnd = 0, i = 0; !fnd && (i < MINFO_SIZE); ++i) {
            int isequal_name = strcmp((char *)h_ltr27->ModuleInfo.Mezzanine[mn].Name,
                               f_mezzanine_info[i].Name) == 0;
            if (isequal_name) {
                fnd = 1;
                mdescr.ID = f_mezzanine_info[i].ID;
            }
        }

        /* подсчет кс */
        Crc = eval_crc16(0, ((BYTE*)&mdescr)+0, 4);
        Crc = eval_crc16(Crc, ((BYTE*)&mdescr)+6, MEZZANINE_EEPROM_SIZE-6);
        mdescr.Crc = Crc;

        /* формируем блок команд */
        src_data[0] = LTR_MODULE_FILL_CMD_PARITY_BYTES(LTR27CMD_EEPROM_WRITE_ENABLE_DISABLE, mn, 1);
        for (i = 0; i < MEZZANINE_EEPROM_SIZE; ++i) {
            src_data[i+1] = LTR_MODULE_FILL_CMD_PARITY_BYTES(LTR27CMD_WRITE_BYTE_SUB+mn, i,
                                                             ((BYTE *)&mdescr)[i]);
        }
        src_data[MEZZANINE_EEPROM_SIZE+1] =
            LTR_MODULE_FILL_CMD_PARITY_BYTES(LTR27CMD_EEPROM_WRITE_ENABLE_DISABLE, mn, 0);
        /* отсылаем блок команд и принимаем ответ
         * не более 110 команд - за раз, поскольку будет переполнение буфера команд в модуле
         * а MEZZANINE_EEPROM_SIZE == 128
         */
        res = f_exchange(h_ltr27, src_data, des_data, MEZZANINE_EEPROM_SIZE + 2 - 30);
        if (res == LTR_OK) {
            res = f_exchange(h_ltr27, &src_data[MEZZANINE_EEPROM_SIZE + 2 - 30],
                    &des_data[MEZZANINE_EEPROM_SIZE+2-30], 30);
            if (res == LTR_OK) {
                /* проверка полей адреса и команд */
                for (i = 0; (res == LTR_OK) && (i < (MEZZANINE_EEPROM_SIZE + 2)); ++i) {
                    if((src_data[i] & 0xFF0000DF) != (des_data[i] & 0xFF0000DF)) {
                        res = LTR_ERROR_INVALID_CMD_RESPONSE;
                    }
                }
            }
        }
    } /*if (res == LTR_OK)*/

    return res;
}

/*------------------------------------------------------------------------------------------------*/
#ifdef LTRAPI_USE_KD_STORESLOTS
static void ltr27cfg_to_hltr27(const struct LTR27Config *cfg, TLTR27 *h_ltr27) {
    size_t i;

    h_ltr27->FrequencyDivisor = cfg->freq_div;

    strncpy((char *)h_ltr27->ModuleInfo.Module.CompanyName, (const char *)cfg->card.company_name,
        sizeof h_ltr27->ModuleInfo.Module.CompanyName /
        sizeof h_ltr27->ModuleInfo.Module.CompanyName[0]);
    strncpy((char *)h_ltr27->ModuleInfo.Module.DeviceName, (const char *)cfg->card.dev_name,
        sizeof h_ltr27->ModuleInfo.Module.DeviceName /
        sizeof h_ltr27->ModuleInfo.Module.DeviceName[0]);
    strncpy((char *)h_ltr27->ModuleInfo.Module.SerialNumber, (const char *)cfg->card.serial,
        sizeof h_ltr27->ModuleInfo.Module.SerialNumber /
        sizeof h_ltr27->ModuleInfo.Module.SerialNumber[0]);
    h_ltr27->ModuleInfo.Module.Revision = cfg->card.rev;

    h_ltr27->ModuleInfo.Cpu.Active = cfg->cpu.valid;
    strncpy((char *)h_ltr27->ModuleInfo.Cpu.Name, (const char *)cfg->cpu.name,
        sizeof h_ltr27->ModuleInfo.Cpu.Name / sizeof h_ltr27->ModuleInfo.Cpu.Name[0]);
    h_ltr27->ModuleInfo.Cpu.ClockRate = cfg->cpu.clk;
    h_ltr27->ModuleInfo.Cpu.FirmwareVersion = cfg->cpu.firmware_ver;

    for (i = 0; (i < LTR27_MEZZANINE_CNT); i++) {
        size_t n = submodule_ndescr(cfg->subcard[i].id);
        if (n < MINFO_SIZE) {
            strncpy(h_ltr27->Mezzanine[i].Name, f_mezzanine_info[n].Name,
                sizeof h_ltr27->Mezzanine[i].Name / sizeof h_ltr27->Mezzanine[i].Name[0]);
            strncpy(h_ltr27->Mezzanine[i].Unit, f_mezzanine_info[n].Unit,
                sizeof h_ltr27->Mezzanine[i].Unit / sizeof h_ltr27->Mezzanine[i].Unit[0]);
            memcpy(h_ltr27->Mezzanine[i].ConvCoeff, f_mezzanine_info[n].ConvCoeff,
                sizeof h_ltr27->Mezzanine[i].ConvCoeff);
            strncpy((char *)h_ltr27->ModuleInfo.Mezzanine[i].Name, f_mezzanine_info[n].Name,
                sizeof h_ltr27->ModuleInfo.Mezzanine[i].Name /
                sizeof h_ltr27->ModuleInfo.Mezzanine[i].Name[0]);
        }
        memcpy(h_ltr27->Mezzanine[i].CalibrCoeff, cfg->subcard[i].user_calibr,
            sizeof h_ltr27->Mezzanine[i].CalibrCoeff);
        h_ltr27->ModuleInfo.Mezzanine[i].Active = cfg->subcard[i].valid;
        strncpy((char *)h_ltr27->ModuleInfo.Mezzanine[i].SerialNumber,
            (const char *)cfg->subcard[i].serial,
            sizeof h_ltr27->ModuleInfo.Mezzanine[i].SerialNumber /
            sizeof h_ltr27->ModuleInfo.Mezzanine[i].SerialNumber[0]);
        h_ltr27->ModuleInfo.Mezzanine[i].Revision = cfg->subcard[i].rev;
        memcpy(h_ltr27->ModuleInfo.Mezzanine[i].Calibration, cfg->subcard[i].factory_calibr,
            sizeof h_ltr27->ModuleInfo.Mezzanine[i].Calibration);
    }
}


/*------------------------------------------------------------------------------------------------*/
static unsigned submodule_id(const char name[16]) {
    size_t i;
    for (i = 0; (i < MINFO_SIZE); i++) {
        if (strncmp(f_mezzanine_info[i].Name, name, 16) == 0)
            return f_mezzanine_info[i].ID;
    }

    return H27__EMPTY;
}

/*------------------------------------------------------------------------------------------------*/
static size_t submodule_ndescr(unsigned id) {
    size_t n;
    for (n = 0; (n < MINFO_SIZE); n++) {
        if (f_mezzanine_info[n].ID == id)
            break;
    }
    return n;
}
#endif

LTR27API_DllExport(INT) LTR27_FindAdcFreqParams(double adcFreq, BYTE *divisor, double *resultAdcFreq) {
    if (adcFreq < 1)
        adcFreq = 1;

    INT cur_divisor = (INT)(1000./adcFreq - 0.5);
    if (cur_divisor < 0)
        cur_divisor = 0;
    if (cur_divisor > 255)
        cur_divisor = 255;

    if (divisor != NULL)
        *divisor = (BYTE)cur_divisor;
    if (resultAdcFreq != NULL)
        *resultAdcFreq = 1000./(cur_divisor + 1);
    return LTR_OK;
}

