/* Данный пример демонстрирует использование некотрых дополнительных
   спецчифчных для модуля E20-10 режимов:
     - передача тестовой последовательности вместо данных АЦП для проверки
       непрерывности и корректности передаваемого потока данных
     - проверка информации о переполнении буфера модуля во время сбора
     - вывод информации о прошивке и загрузчике AVR, темп. диапазон модуля
   Пример также подсчитывает и выводит дополнительную статистику по завершению
   сбора данных.
     */



#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <dlfcn.h>
#include <pthread.h>
#include <stdint.h>

#include <iomanip>
#include <iostream>
#include <fstream>

#include <math.h>

using namespace std;

#define INITGUID

#include "lcomp/e2010cmd.h"
#include "lcomp/ifc_ldev.h"
#include <errno.h>

#include <signal.h>
#include <unistd.h>
#include <getopt.h>

typedef IDaqLDevice* (*CREATEFUNCPTR)(ULONG Slot);





/* количество слов, сохраняемых во временном буфере для вывода данных при
   возникновении ошибок */
#define SAVE_BUF_SIZE  (1024*1024)
/* максимальное количество ошибок, инфомация о которых будет сохранена */
#define SAVE_ERRS_CNT  64

/* через сколько обработанных слов выводить признак прогресса */
#define PROGR_PRINT_SIZE (1024*1024)






/* дополнительные определения, специфичные для модуля E20-10 */
#pragma pack(1)

#define E2010_IRQ_STEP_MAX       (1024*1024)

/* флаг разрешения индикации светодиодами */
#define E2010_NV_FLAG_LED_EN      (1UL << 0)

#define E2010_PLD_INFO_SIZE                  510
#define E2010_PLD_VERSION_STR_MAX_SIZE       17

/* модуль счетчика из тестовой последовательности E20-10 */
#define E2010_TEST_COUNTER_MODULE 65535


#define E2010_DATA_STATUS_BUF_OVF     0x01 /* признак переполнения внутреннего буфера АЦП */
#define E2010_DATA_STATUS_CHS_OVL     0x08  /* общий признак перегрузки входа для всех каналов АЦП */
#define E2010_DATA_STATUS_CH_OVL(ch)  (0x10 << ch) /* признак перегрузки входа указанного канала АЦП */

/* статистика о состоянии буфера модуля, читаемая по адресу DATA_STATE_ADDRESS */
typedef struct {
    UCHAR  status; /* набор флагов из E2010B_DATA_STATUS_xxx */
    ULONG32 cur_buffer_filling; /* текущая заполненность вутренного буфера модуля в словах */
    ULONG32 max_buf_filling;    /* максимальная заполненность бфера модуля в словах */
    ULONG32 buf_size; /* размер буфера модуля в словах */
} t_e2010_data_state;

#define E2010_CHECK_MCU_VER(vh, vl, req_h, req_l) (((vh) > (req_h)) || (((vh) == (req_h)) && ((vl) >= (req_l))))
#define E2010_CHECK_MCU_VER2(vh, vl, req_h1, req_l1, req_h2, req_l2) \
    (((vh) == (req_h1)) ? E2010_CHECK_MCU_VER(vh, vl, req_h1, req_l1) : \
                         E2010_CHECK_MCU_VER(vh, vl, req_h2, req_l2))


/* Синхронный ввод поддерживается с версии AVR 2.4
 * (также должна быть прошивка версии yt yb;t 2.0.13) */
#define E2010_MCU_CHECK_DIG_SYNCIN(vh, vl)  E2010_CHECK_MCU_VER(vh,  vl, 2, 4)
/* чтение информации о темп. диапазоне модуля доступно только для
   прошивок AVR 2.4 и выше. Если версия прошивки ниже, то это в любом
   случае коммерческий диапазон */
#define E2010_MCU_CHECK_TG_GRADE(vh, vl)  E2010_CHECK_MCU_VER(vh,  vl, 2, 4)
/* чтение/запись флагов в EEPROM доступна для AVR версий: 2.6 или выше для rev.B
 *                                                        3.2 или выше для rev.C */
#define E2010_MCU_CHECK_NV_FLAGS(vh, vl)  E2010_CHECK_MCU_VER2(vh, vl, 2, 5, 3, 2)




typedef struct {
    CHAR McuName[25];   // название микроконтроллера
    CHAR Version[10];   // версия драйвера микроконтроллера
    CHAR Created[14];   // дата сборки драйвера микроконтроллера
    CHAR Manufacturer[25];  // производитель драйвера микроконтроллера
    CHAR Author[25];    // автор драйвера микроконтроллера
    CHAR Comment[128];  // строка комментария
} t_e2010_mcu_descriptor;

typedef struct {
    CHAR McuName[20];   // название микроконтроллера
    CHAR Version[10];   // версия загрузчика микроконтроллера
    CHAR Created[14];   // дата сборки загрузчика микроконтроллера
    CHAR Manufacturer[15];  // производитель загрузчика микроконтроллера
    CHAR Author[20];    // автор загрузчика микроконтроллера
} t_e2010_bootloader_descriptor;


typedef struct {
    CHAR  Text[E2010_PLD_VERSION_STR_MAX_SIZE];
    union {
        struct {
            CHAR TGrade;
            UCHAR Low;
            UCHAR Middle;
            UCHAR High;
        };
        ULONG Value;
    };
} t_e2010_pld_version_info;

#pragma pack()


unsigned short *p;
volatile unsigned int *pp;

/* признак необходимости завершить сбор данных */
static int f_out = 0;
#ifndef _WIN32
/* Обработчик сигнала завершения для Linux */
static void f_abort_handler(int sig) {
    (void)sig;
    f_out = 1;
}
#endif

/* временный буфер с сохранением последних слов, принятых от модуля.
   используется для вывода подробной информации о принятых данных
   в случае возникновения ошибки */
static uint16_t tmp_buf[SAVE_BUF_SIZE];
static unsigned tmp_buf_pos = 0;
static unsigned tmp_buf_size = 0;

/* информация о одной ошибке при несовпадении счетчика */
struct t_err_info {
    unsigned tmp_buf_pos; /* позиция в tmb_buf слова с ошибкой */
    unsigned lcomp_buf_pos; /* позиция слова с ошикой в буере pp из драйвера lcomp */
    unsigned proc_buf_pos; /* позиция слова в текущим обрабатываемом блоке данных */
    unsigned proc_buf_len; /* размер обрабатываемого блока данных в котором была данная ошика */
    uint64_t start_block_dtime; /* время, прошедшее с момента начала обработки прошлого
                                   блока до начала обработки блока с ошибкой в мкс */
    uint64_t err_point_dtime; /* время от начала обработки блока до нахождения
                                 ошибки в мкс */
    uint64_t word_num; /* номер слова с начала сбора */
    uint16_t rcv_wrd; /* слово в принятом потоке от модуля */
    uint16_t exp_wrd; /* слово, которое ожидалось принять */
};


/* информация о состоянии сбора данных */
static struct {
    t_err_info errs[SAVE_ERRS_CNT]; /* массив с информацией о первых ошибоках (до SAVE_ERRS_CNT штук).
                                       количество действительных элементов определяется как min(errs_cnt, SAVE_ERRS_CNT). */
    uint64_t errs_cnt; /* общее количество возникших ошибок за все время сбора (может быть больше SAVE_ERRS_CNT)  */

    uint64_t total_samples; /* общее число принятых отсчетов от модуля с момента начала сбора */
    uint64_t total_time;    /* общее время в мкс с момента запуска сбора данных */
    uint64_t max_block_time; /* максимальное время между обработкой блоков за весь сбор данных в мкс */
    unsigned max_block_len; /* максимальный размер блока обрабатываемых данных в мкс */
    unsigned first_block_len; /* размер первого обработанного блока */

    struct {
        int      ov_cnt;   /* признак обнаружения переполнения */
        uint32_t max_words; /* макс. заполненность буфера модуля в словах */
        uint32_t buf_size; /* размер буфера модуля в словах */
        uint64_t max_req_dtime; /* макс. время выполнения запроса чтения состояния буфера в мкс */
    } module_buf;
} acq_stat;





/*  --------------------------------Определение опций программы -------------  */
#define OPT_HELP            'h'
#define OPT_DEVSLOT         's'
#define OPT_TESTMODE        't'
#define OPT_DIG_CH_EN       'd'
#define OPT_ADC_FREQ        'f'
#define OPT_SETLEDEN        0x100
#define OPT_FW_FILE         0x101
#define OPT_OV_CHECK        0x102
#define OPT_STOP_ON_ERR     0x103
#define OPT_IRQ_STEP        0x104
#define OPT_LBUF_SIZE       0x105



static const struct option f_long_opt[] = {
    {"help",            no_argument,       0, OPT_HELP},
    {"adc-freq",        required_argument, 0, OPT_ADC_FREQ},
    {"dig-chs-en",      optional_argument, 0, OPT_DIG_CH_EN},
    {"fw-file",         required_argument, 0, OPT_FW_FILE},
    {"irq-step",        required_argument, 0, OPT_IRQ_STEP},
    {"lcomp-buf-size",  required_argument, 0, OPT_LBUF_SIZE},
    {"ov-check",        optional_argument, 0, OPT_OV_CHECK},
    {"set-led-en",      required_argument, 0, OPT_SETLEDEN},
    {"slot",            required_argument, 0, OPT_DEVSLOT},
    {"stop-on-err",     optional_argument, 0, OPT_STOP_ON_ERR},
    {"test-mode",       optional_argument, 0, OPT_TESTMODE},
    {0,0,0,0}
};
static const char* f_opt_str = "hf:s:t::d::";

static const char* f_usage_descr = \
"\nUsage: lcomp_e2010_test [OPTIONS]\n\n";


static const char* f_options_descr =
"Options:\n" \
"-h, --help                 - Print this help and exit.\n"
"-f, --adc-freq=freq[k|M]   - Adc frequency in HZ. Suffixes k for kHZ and \n"
"                             M for MHZ are allowed.\n"
"    --dig_chs-en[=on|off]  - Enable digital channels in logical table.\n"
"    --fw-file=file         - Load module firmware (bios) from specified file\n"
"                             rather than from default location.\n"
"    --irq-step=count[k|M]  - Size of USB data transfer in samples (max 1M).\n"
"    --lcomp-buf=count[k|M] - Size of circular buffer in devicer in samples.\n"
"    --ov-check[=on|off]    - Check module buffer overflow during data.\n"
"                             acquisition (on) or on stop (off).\n"
"    --set-led-en=on|off    - Enable (on) or disable (off) module LED\n"
"                             indication. No action if this option is not\n"
"                             specified.\n"
"-s, --slot                 - Device slot number (from 0 to 255).\n"
"    --stop-on-err[=on|off] - Stop data acquisition on first error.\n"
"-t, --test-mode[=on|off]   - Enable or disable test counter mode.\n"
;

/* Значения опций, управляющих работой программы */
typedef struct {
    int devslot; /* номер слота устройства (от 0) */
    int test_mode; /* признак включения проверки тестового счетчика */
    int dig_chs_en; /* разрешение цифровых каналов в логической таблице */
    int ov_check_en; /* проверка переполнения буфера при сборе (если 0 - то только после останова) */
    int stop_on_err; /* признак, что нужно остановить сбор по первой ошибке */
    double adc_freq_khz; /* частота сбора в КГц */
    int irq_step; /* размер одного обмена в отсчетах */
    int lbuf_size; /* размер кольцевого буфера в драйвере для приема данных */
    int pages_cnt; /* количество страниц в кольцевом буфере (lbuf_size/irq_step) */
    const char *fw_filename; /* имя файла с прошивкой (bios) PLD. Если NULL - используется
                                прошивка по умолчанию */
    struct {
        int cmd_req;  /* признак, что была запрошена данная команда */
        int enable;   /* указывает, необходимо разрешить или запретить индикацию */
    } set_led_indication; /* команда изменения разрешения индикации светодиодом */
} t_app_options;
static t_app_options f_app_opts;

static void  f_print_usage(void) {
    cout << f_usage_descr << endl << endl << f_options_descr << endl;
}

static int f_check_onof_opt(const char *str) {
    return !strcmp(str, "on") || !strcmp(str, "yes");
}

static int f_parse_options(t_app_options *opts, int argc, char **argv, int *out) {
    int err = 0;
    int opt = 0;

    *out = 0;
    memset(opts, 0, sizeof(t_app_options));
    /* установка значений по умолчанию */
    opts->adc_freq_khz = 10000;
    opts->irq_step = E2010_IRQ_STEP_MAX;
    opts->lbuf_size = 32 * 1024 * 1024;

    /************************** разбор опций *****************************/
    while ((opt!=-1) && !err && !*out) {
        opt = getopt_long(argc, argv, f_opt_str,
                          f_long_opt, 0);
        switch (opt) {
            case -1:
                break;
            case OPT_HELP:
                f_print_usage();
                *out = 1;
                break;
            case OPT_FW_FILE:
                opts->fw_filename = optarg;
                break;
            case OPT_SETLEDEN: {
                    opts->set_led_indication.cmd_req = 1;
                    opts->set_led_indication.enable = f_check_onof_opt(optarg);
                }
                break;
            case OPT_DEVSLOT: {
                    int slot = atoi(optarg);
                    if ((slot < 0) || (slot > 255)) {
                        cout << "invalid device slot number"  << endl;
                        err = L_ERROR;
                    } else {
                        opts->devslot = slot;
                    }
                }
                break;
            case OPT_TESTMODE:
                if (!optarg || f_check_onof_opt(optarg)) {
                    opts->test_mode = 1;
                } else {
                    opts->test_mode = 0;
                }
                break;
            case OPT_DIG_CH_EN:
                if (!optarg || f_check_onof_opt(optarg)) {
                    opts->dig_chs_en = 1;
                } else {
                    opts->dig_chs_en = 0;
                }
                break;
            case OPT_OV_CHECK:
                if (!optarg || f_check_onof_opt(optarg)) {
                    opts->ov_check_en = 1;
                } else {
                    opts->ov_check_en = 0;
                }
                break;
            case OPT_STOP_ON_ERR:
                if (!optarg || f_check_onof_opt(optarg)) {
                    opts->stop_on_err = 1;
                } else {
                    opts->stop_on_err = 0;
                }
                break;
            case OPT_ADC_FREQ: {
                    float freq_hz;
                    char mult;
                    int res = sscanf(optarg, "%f%c", &freq_hz, &mult);
                    if (res < 1) {
                        cout << "invalid adc frequency format" << endl;
                        err = L_ERROR;
                    } else if (res == 2){
                        if  ((mult == 'k') || (mult == 'K')) {
                            freq_hz *= 1000;
                        } else if (mult == 'M') {
                            freq_hz *= 1000000;
                        } else {
                            cout << "invalid adc frequency format" << endl;
                            err = L_ERROR;
                        }
                    }

                    if (err == L_SUCCESS) {
                        if ((freq_hz > 10e6) || (freq_hz < 1e6)) {
                            cout << "invalid adc frequency value" << endl;
                            err = L_ERROR;
                        } else {
                            opts->adc_freq_khz = freq_hz/1000;
                        }
                    }

                }
                break;
            case OPT_IRQ_STEP: {
                    int step;
                    char mult;
                    int res = sscanf(optarg, "%d%c", &step, &mult);
                    if (res < 1) {
                        cout << "invalid irq step format" << endl;
                    } else if (res == 2) {
                        if  ((mult == 'k') || (mult == 'K')) {
                            step *= 1024;
                        } else if (mult == 'M') {
                            step *= 1024 * 1024;
                        } else {
                            cout << "invalid irq step format" << endl;
                            err = L_ERROR;
                        }
                    }

                    if (err == L_SUCCESS) {
                        /* шаг не может превышать E2010_IRQ_STEP_MAX и для текущей
                         * версии должен быть кратен странице (2048 слов) */
                        if ((step > E2010_IRQ_STEP_MAX) || (step < 2048) || ((step % 2048) != 0))  {
                            cout << "invalid irq step" << endl;
                            err = L_ERROR;
                        } else {
                            opts->irq_step = step;
                        }
                    }
                }
                break;
            case OPT_LBUF_SIZE: {
                    int buf_size;
                    char mult;
                    int res = sscanf(optarg, "%d%c", &buf_size, &mult);
                    if (res < 1) {
                        cout << "invalid lcomp buf size format" << endl;
                    } else if (res == 2) {
                        if  ((mult == 'k') || (mult == 'K')) {
                            buf_size *= 1024;
                        } else if (mult == 'M') {
                            buf_size *= 1024 * 1024;
                        } else {
                            cout << "invalid lcomp buf size format" << endl;
                            err = L_ERROR;
                        }
                    }

                    if (err == L_SUCCESS) {
                        if (buf_size < 4096) {
                            cout << "invalid lcomp buf size" << endl;
                            err = L_ERROR;
                        } else {
                            opts->lbuf_size = buf_size;
                        }
                    }
                }
                break;
            default:
                cout << "unknown option!";
                err = L_ERROR;
                break;
        }
    }

    if ((err == L_SUCCESS) && !*out) {
        if (optind != argc) {
            cout << "unknown application parameters" << endl;
            err = L_ERROR;
        } else {
            /* вычисления количества страниц по размеру буфера и шагу и корректировка
             * размера буфера для кретного деления на страницы */
            opts->pages_cnt = (opts->lbuf_size + opts->irq_step-1)/opts->irq_step;
            opts->lbuf_size = opts->pages_cnt * opts->irq_step;
        }
    }
    return err;
}

static int e2010_parse_pld_info(const UCHAR *pld_info, t_e2010_pld_version_info *pversion) {
    char *pTag, *p1, *p2;
    int fnd = 0;

    pTag = strstr((char*)pld_info, "<Version>");
    if(pTag && ((p1 = strchr(pTag, '{')) != NULL) && ((p2 = strchr(pTag, ' ')) != NULL)) {
        size_t versize = p2-p1-0x1;
        if (versize < E2010_PLD_VERSION_STR_MAX_SIZE) {
            int v1, v2, v3;
            char tg;
            int res;

            memcpy(pversion->Text, (char *)(p1 + 0x1), versize);
            memset(&pversion->Text[versize], 0, E2010_PLD_VERSION_STR_MAX_SIZE - versize);

            res = sscanf(pversion->Text, "%d.%d.%d%c", &v1, &v2, &v3, &tg);
            if (res >= 3) {
                pversion->Low = v3 & 0xFF;
                pversion->Middle = v2 & 0xFF;
                pversion->High = v1 & 0xFF;
                fnd = 1;
            }
            if (res >= 4) {
                pversion->TGrade = tg;
            } else {
                pversion->TGrade = 'c';
            }
        }
    }
    return fnd ? L_SUCCESS : L_ERROR;
}

/* расчет разницы между двумя отсчетами времени, предатвленными типом timespec, в мкс */
static uint64_t timespec_diff_mks(timespec start, timespec end) {
    timespec temp;
    if ((end.tv_nsec-start.tv_nsec)<0) {
        temp.tv_sec = end.tv_sec-start.tv_sec-1;
        temp.tv_nsec = 1000000000+end.tv_nsec-start.tv_nsec;
    } else {
        temp.tv_sec = end.tv_sec-start.tv_sec;
        temp.tv_nsec = end.tv_nsec-start.tv_nsec;
    }
    return temp.tv_sec * 1000000 + temp.tv_nsec/1000;
}

/* обработка данных из буфера драйвера lcomp с позиции from до позиции to */
static void process_data(unsigned int from, unsigned to) {
    static unsigned tmp_buf_stop_pos;
    static int      tmp_buf_stop_req = 0;
    static uint64_t tst_cur_cntr = 0;
    unsigned proc_len = to - from;
    if (proc_len > 0) {
        static int tmp_buf_prev_time_valid = 0;
        static timespec tmp_buf_prev_time;
        uint64_t block_time = 0;
        /* обновление статситки по времени между блоками и размерам блока обработки */
        timespec block_start_time;
        clock_gettime(CLOCK_MONOTONIC, &block_start_time);
        if (tmp_buf_prev_time_valid) {
            block_time = timespec_diff_mks(tmp_buf_prev_time, block_start_time);
            if (block_time > acq_stat.max_block_time)
                acq_stat.max_block_time = block_time;
            acq_stat.total_time += block_time;
        } else {
            tmp_buf_prev_time_valid = 1;
            acq_stat.first_block_len = proc_len;
        }
        tmp_buf_prev_time = block_start_time;

        if (proc_len > acq_stat.max_block_len)
            acq_stat.max_block_len = proc_len;

        /* обработка данных по словам */
        for (unsigned int idx = from; idx < to; idx++) {
            unsigned short wrd = p[idx];


            /* проверка тестового счетчика */
            if (f_app_opts.test_mode) {
                /* учет сдвига тестового счетчика для неготорых частот. В случае
                   исправления в ПЛИС и обновления прошивки это условие можно убрать */
                uint16_t exp_wrd;
                /* для частот 5000, 6000 и 7500 идет некорректная
                   тестовая последовательность со сдвигом на байт */
                if ((f_app_opts.adc_freq_khz < 8750) && (f_app_opts.adc_freq_khz > 4650)) {
                    exp_wrd = acq_stat.total_samples == 0 ? 0 :
                              (((tst_cur_cntr % E2010_TEST_COUNTER_MODULE) & 0xFF) << 8) |
                              ((((tst_cur_cntr + 1) % E2010_TEST_COUNTER_MODULE) >> 8) & 0xFF);
                } else {
                    exp_wrd = (tst_cur_cntr % E2010_TEST_COUNTER_MODULE);
                }

                if (wrd != exp_wrd) {
                    /* при несовпадении счетчика, если есть место для сохранения
                       информации о ошибке, то добавяем ее подробную информацию
                       о произошедшей ошике */
                    if (acq_stat.errs_cnt < SAVE_ERRS_CNT) {
                        timespec err_time;
                        clock_gettime(CLOCK_MONOTONIC, &err_time);
                        t_err_info *perr_info = &acq_stat.errs[acq_stat.errs_cnt];

                        perr_info->tmp_buf_pos = tmp_buf_pos;
                        perr_info->lcomp_buf_pos = idx;
                        perr_info->proc_buf_pos = idx - from;
                        perr_info->proc_buf_len = proc_len;
                        perr_info->start_block_dtime = block_time;
                        perr_info->err_point_dtime = timespec_diff_mks(block_start_time, err_time);
                        perr_info->word_num = acq_stat.total_samples;
                        perr_info->rcv_wrd = wrd;
                        perr_info->exp_wrd = exp_wrd;

                        /* при первой ошибке планируем завершение приема через пол сохраняемого буфера от возникшей ошибки,
                           чтобы сбойное слово было посередине кольцевого буфера и иметь контекст как до,
                           так и после ошики */
                        if (acq_stat.errs_cnt == 0) {
                            tmp_buf_stop_pos = (tmp_buf_pos + SAVE_BUF_SIZE/2) % SAVE_BUF_SIZE;
                            if (f_app_opts.stop_on_err)
                                tmp_buf_stop_req = 1;
                        }
                    }
                    acq_stat.errs_cnt++;
                    tst_cur_cntr = wrd;
                }


                /* для цифровых каналов тестовое слово не передается
                   в интерфейс. в пример используется попеременно
                   цифровые и аналогвые каналы и из-за отстутсвия
                   слов цифровых счетчик будет инкрементироваться
                   на 2, а не 1 */
                tst_cur_cntr += f_app_opts.dig_chs_en ? 2 : 1;
            }

            /* сохранение последних слов во верменном буфера для вывода при ошибках */
            tmp_buf[tmp_buf_pos] = wrd;

            if (++tmp_buf_pos == SAVE_BUF_SIZE) {
                tmp_buf_pos = 0;
            }
            /* увеличиваем счетчик действительных элеметов во временном буфере,
               если уже буфер ен был заполнен хоть раз */
            if (tmp_buf_size < SAVE_BUF_SIZE)
                tmp_buf_size++;


            /* если был запланирован останов по ошибке и дошли до запланированной
               позиции останов, то завершаем сор и обработку */
            if (tmp_buf_stop_req && (tmp_buf_pos == tmp_buf_stop_pos)) {
                f_out = 1;
                break;
            }

            acq_stat.total_samples++;
#ifdef PROGR_PRINT_SIZE
            /* вывод прогресса каждые PROGR_PRINT_SIZE слов.
               если не было ошибок выводим точки, иначе - ! */
            if ((acq_stat.total_samples % PROGR_PRINT_SIZE) == 0) {
                cout <<  (acq_stat.errs_cnt == 0 ? "." : "!") << flush;
            }
#endif
        }
    }
}

/* чтение информации о состоянии буфера модуля и обновление информации
   в acq_stat (должно вызываться до останова сбора данных!) */
int check_data_state(IDaqLDevice *pI) {
    /* Читаем из памяти модуля статистику модуля по передаче данных.
       используем inmbyte для чтения данных нужного размера */
    t_e2010_data_state dstate;
    timespec req_start_time;
    clock_gettime(CLOCK_MONOTONIC, &req_start_time);
    memset(&dstate, 0, sizeof(dstate));

    int err = pI->inmbyte(DATA_STATE_ADDRESS, reinterpret_cast<PUCHAR>(&dstate), sizeof(dstate));
    if (err != L_SUCCESS) {
        cout << endl << "get data state error " << err << endl;
    } else {
        uint64_t req_exec_dtime;
        timespec req_finish_time;
        clock_gettime(CLOCK_MONOTONIC, &req_finish_time);
        req_exec_dtime = timespec_diff_mks(req_start_time, req_finish_time);
        if (req_exec_dtime > acq_stat.module_buf.max_req_dtime) {
            acq_stat.module_buf.max_req_dtime = req_exec_dtime;
        }

        if (dstate.status & E2010_DATA_STATUS_BUF_OVF) {
            acq_stat.module_buf.ov_cnt++;
            err = -10;
            cout << endl << "module buffer overflow detected!" << endl;
        }

        if (dstate.max_buf_filling > acq_stat.module_buf.max_words) {
            acq_stat.module_buf.max_words = dstate.max_buf_filling;
        }
        acq_stat.module_buf.buf_size = dstate.buf_size;
    }
    return err;
}

/* Поток сора данных. В качестве аргумента передается ссылка на устройство */
void *thread_func(void *arg) {
    unsigned int lbuf_pos=0, lbuf_prev_pos=0;
    unsigned int lbuf_size = f_app_opts.lbuf_size;
    IDaqLDevice *pI = static_cast<IDaqLDevice *>(arg);

    cout << "data thread started..." << endl;

    while (!f_out) {
        usleep(1000);

        lbuf_pos=*pp;
        lbuf_pos-=lbuf_pos%4; // выравниваем по кадру (в кадре 4 канала)

        if(lbuf_pos != lbuf_prev_pos)  {
            if(lbuf_prev_pos < lbuf_pos) {
                process_data(lbuf_prev_pos, lbuf_pos);
            } else if(lbuf_prev_pos>lbuf_pos) {
                // переход через границу кольцевого буфера
                process_data(lbuf_prev_pos, lbuf_size);
                process_data(0, lbuf_pos);
            }

            lbuf_prev_pos = lbuf_pos;

            if (f_app_opts.ov_check_en) {
                if (check_data_state(pI) != L_SUCCESS) {
                    f_out = 1;
                }
            }
        }
    }

    /* после завершения сбора до его останова проверяем статус внутреннего буфера
       модуля */
    check_data_state(pI);

    cout << endl << "data thread finished..." << endl;

    return nullptr;
}


//Att. for board slot numbers!!!!

int main(int argc, char **argv) {
    int err = L_SUCCESS;
#ifndef _WIN32
    struct sigaction sa;
    memset(&sa, 0, sizeof(sa));
    /* В ОС Linux устанавливаем свой обработчик на сигнал закрытия,
       чтобы завершить сбор корректно */
    sa.sa_handler = f_abort_handler;
    sigaction(SIGTERM, &sa, NULL);
    sigaction(SIGINT, &sa, NULL);
    sigaction(SIGABRT, &sa, NULL);
#endif

    err = f_parse_options(&f_app_opts, argc, argv, &f_out);
    if ((err == L_SUCCESS) && !f_out) {
        void *handle = NULL;
        CREATEFUNCPTR CreateInstance = NULL;
        IDaqLDevice* pI = NULL;
        PLATA_DESCR_U2 pd;
        SLOT_PAR sl;
        ADC_PAR adcPar;
        ULONG size;

        unsigned mcu_ver_h = 0, mcu_ver_l = 0;

        if (err == L_SUCCESS) {
            handle = dlopen("liblcomp.so", RTLD_LAZY);
            if(!handle) {
                cout << "error open dll!! " << dlerror() << endl;
                err = -2;
            }
        }

        if (err == L_SUCCESS) {
            CreateInstance = (CREATEFUNCPTR) dlsym(handle,"CreateInstance");
            if (!CreateInstance) {
                cout << "load symbol CreateInstance error " << dlerror() << endl;
                err = -3;
            }
        }


        if (err == L_SUCCESS) {
            LUnknown* pIUnknown = CreateInstance(f_app_opts.devslot);
            if(pIUnknown == NULL) {
                cout << "Call CreateInstance failed " << errno << endl;
                err = -4;
            } else {
                cout << "Get IDaqLDevice interface" << endl;

                HRESULT hr = pIUnknown->QueryInterface(IID_ILDEV,(void**)&pI);
                if (hr != S_OK) {
                    cout << "Get IDaqLDevice failed" << endl;
                    err = -5;
                } else {
                    printf("IDaqLDevice get success \n");
                }
            }

            if (pIUnknown) {
                pIUnknown->Release();
                cout << "Free IUnknown" << endl;
            }
        }

        if (err == L_SUCCESS) {
            if (pI->OpenLDevice() == INVALID_HANDLE_VALUE) {
                cout << "open device error" << endl;
                err = -6;
            } else {
                err = pI->GetSlotParam(&sl);
                if (err != L_SUCCESS) {
                    cout << "get slot param error: " << err << endl;
                }
            }
        }


        if (err == L_SUCCESS) {
            cout << endl << "Read FLASH" << endl;

            err = pI->ReadPlataDescr(&pd);
            if (err != L_SUCCESS) {
                cout << "Read plata descr failed: " << err << endl;
            } else {
                if (sl.BoardType == E2010B) {
                    cout << "Module description:" << endl;
                    cout << "  SerNum       " << pd.t6.SerNum << endl;
                    cout << "  BrdName      " << pd.t6.BrdName << endl;
                    cout << "  Rev          " << pd.t6.Rev << endl;
                    cout << "  DspType      " << pd.t6.DspType << endl;
                    cout << "  IsDacPresent " << (pd.t6.IsDacPresent ? "Yes" : "No") << endl;
                    cout << "  Quartz       " << dec << pd.t6.Quartz << endl;
                    cout << "  Modification " << hex << (unsigned)(pd.t6.Modification & 0x3) << dec << endl;
                } else {
                    cout << "unsupported board type: " << sl.BoardType << endl;
                    err = -7;
                }
            }
        }


        if (err == L_SUCCESS) {
            t_e2010_mcu_descriptor mcu_descr;
            bool industrial = false;
            /* чтение информации о прошивке AVR модуля */
            /* расширенные функции, не входящие в базовый интерфейс, могут выполняться
               через доступ к памяти модуля через функции inmbyte, inmword, inmdword
               (первая используется также для доступа к массиву произвольной длины) */
            err = pI->inmbyte(FIRMWARE_DESCRIPTOR_ADDRESS, reinterpret_cast<UCHAR*>(&mcu_descr),
                             sizeof(mcu_descr));
            if (err != L_SUCCESS) {
                cout << "Read mcu descr failed: " << err << endl;
            } else {
                cout << "MCU Firmware info:" << endl;
                cout << "  Version:   " <<  mcu_descr.Version << endl;
                cout << "  Date:      " <<  mcu_descr.Created << endl;
                sscanf(mcu_descr.Version, "%d.%d", &mcu_ver_h, &mcu_ver_l);


                if (E2010_MCU_CHECK_TG_GRADE(mcu_ver_h, mcu_ver_l)) {
                    UCHAR tg_info;
                    err = pI->inmbyte(SEL_MODULE_TG_DATA, &tg_info, 1);
                    if (err != L_SUCCESS) {
                        cout << "Read temperature grade failed: " << err << endl;
                    } else {
                        industrial = tg_info & 1;
                    }
                }

                if (err == L_SUCCESS) {
                    cout << "Module temperature grade: " << (industrial ? "industrial" : "commercial") << endl;
                }

                if (E2010_MCU_CHECK_NV_FLAGS(mcu_ver_h, mcu_ver_l)) {
                    ULONG nv_flags = 0;
                    err = pI->inmdword(NV_FLAGS_REG_ADDRESS, &nv_flags);
                    if (err == L_SUCCESS) {
                        cout << "Nonvolatile flags:   0x" << hex << nv_flags << dec << endl;
                        cout << "   LED enabled:      " << ((nv_flags & E2010_NV_FLAG_LED_EN) ? "Yes" : "No") << endl;
                    } else {
                        cout << "Get nonvolatile flags failed: " << err << endl;
                    }
                }
            }
        }


        if (err == L_SUCCESS) {
            /* вывод информации о загрузчике AVR */
            t_e2010_bootloader_descriptor bootldr_descr;
            err = pI->inmbyte(BOOT_LOADER_DESCRIPTOR_ADDRESS, reinterpret_cast<UCHAR*>(&bootldr_descr),
                             sizeof(bootldr_descr));
            if (err != L_SUCCESS) {
                cout << "Read bootloader descr failed: " << err << endl;
            } else {
                cout << "Bootloader info:" << endl;
                cout << "  Version:   " <<  bootldr_descr.Version << endl;
                cout << "  Date:      " <<  bootldr_descr.Created << endl;
            }
        }

        if (err == L_SUCCESS) {
            UCHAR pld_info[E2010_PLD_INFO_SIZE];
            timespec load_start_time, load_end_time;
            clock_gettime(CLOCK_MONOTONIC, &load_start_time);
            /* загрузка прошивки ПЛИС. Если передан нулевой указатель или пустая строка,
               то библиотека берет предустановленную версию в зависимости от типа модуля */
            err = pI->LoadBios(f_app_opts.fw_filename);
            clock_gettime(CLOCK_MONOTONIC, &load_end_time);
            if (err != L_SUCCESS) {
                cout << "Load PLD failed: " << err << endl;
            } else {
                cout << "Load PLD successfully. Time (ms): " << timespec_diff_mks(load_start_time, load_end_time)/1000 << endl;

                err = pI->inmbyte(PLD_INFO_ADDRESS, pld_info, sizeof(pld_info));
                if (err != L_SUCCESS) {
                    cout << "Read PLD info failed: " << err << endl;
                } else {
                    t_e2010_pld_version_info pldver;
                    pld_info[E2010_PLD_INFO_SIZE-1] = '\0';
                    err = e2010_parse_pld_info(pld_info, &pldver);
                    if (err == L_SUCCESS) {
                        cout << "  PLD Version:  " << pldver.Text << endl;
                        cout << "    high:       " << (unsigned)pldver.High << endl;
                        cout << "    middle:     " << (unsigned)pldver.Middle << endl;
                        cout << "    low:        " << (unsigned)pldver.Low << endl;
                        cout << "    tgrade:     " << pldver.TGrade << endl;
                    } else {
                        cout << "PLD info parse error! pld info: " << pld_info;
                    }
                }
            }
        }

        if (err == L_SUCCESS) {
            err =  pI->PlataTest();
            if (err != L_SUCCESS) {
                cout << "Plate test failed: " << err << endl;
            }
        }


        /* Выполнение команды изменения состояния разрешения индикации светодиодом,
         * если эта опция была указана */
        if ((err == L_SUCCESS) && f_app_opts.set_led_indication.cmd_req) {
            if (!E2010_MCU_CHECK_NV_FLAGS(mcu_ver_h, mcu_ver_l)) {
                cout << "AVR firmware version doesn't support nonvolatile flags change operation!" << endl;
                err = L_ERROR;
            } else {
                ULONG nv_flags = 0;
                err = pI->inmdword(NV_FLAGS_REG_ADDRESS, &nv_flags);
                if (err != L_SUCCESS) {
                    cout << "Get nonvolatile flags failed: " << err << endl;
                } else {
                    if (f_app_opts.set_led_indication.enable) {
                        nv_flags |= E2010_NV_FLAG_LED_EN;
                    } else {
                        nv_flags &= ~E2010_NV_FLAG_LED_EN;
                    }
                    err = pI->outmdword(NV_FLAGS_REG_ADDRESS, &nv_flags);
                    if (err != L_SUCCESS) {
                        cout << "Set nonvolatile flags failed: " << err << endl;
                    } else {
                        cout << "Set led indication "
                             << (f_app_opts.set_led_indication.enable ? "enabled" : "disabled")
                             << " successfully" << endl;
                    }
                }
            }
        }


        if (err == L_SUCCESS) {
            size = f_app_opts.lbuf_size;
            err = pI->RequestBufferStream(&size);
            if (err != L_SUCCESS) {
                cout << "request buffer stream error: " << err << endl;
            }
        }

        if (err == L_SUCCESS) {
            if (sl.BoardType == E2010B) {
                adcPar.t2.s_Type = L_ADC_PARAM;
                adcPar.t2.AutoInit = 1;
                adcPar.t2.dRate = f_app_opts.adc_freq_khz;
                adcPar.t2.dKadr = 0.000;

                adcPar.t2.SynchroType = INT_START_TRANS;
                adcPar.t2.SynchroSrc = INT_CLK_TRANS;
                adcPar.t2.AdcIMask = SIG_0|SIG_1|SIG_2|SIG_3;
                adcPar.t2.DigRate = 0;

                adcPar.t2.NCh = 4;
                if (f_app_opts.dig_chs_en) {
                    adcPar.t2.Chn[0] = 2; // BNC3 (координата Y)
                    adcPar.t2.Chn[1] = 6; // SYNC
                    adcPar.t2.Chn[2] = 1; // BNC2 (координата X)
                    adcPar.t2.Chn[3] = 6; // SYNC
                } else {
                    adcPar.t2.Chn[0] = 2; // BNC3 (координата Y)
                    adcPar.t2.Chn[1] = 0; //
                    adcPar.t2.Chn[2] = 1; // BNC2 (координата X)
                    adcPar.t2.Chn[3] = 3; //
                }

                adcPar.t2.FIFO = f_app_opts.irq_step;
                adcPar.t2.IrqStep = f_app_opts.irq_step;
                adcPar.t2.Pages = f_app_opts.pages_cnt;
                adcPar.t2.IrqEna = 1;
                adcPar.t2.AdcEna = 1;

                // extra sync mode
                adcPar.t2.StartCnt = 0;
                adcPar.t2.StopCnt = 0;
                adcPar.t2.DM_Ena = 0;
                adcPar.t2.SynchroMode = 0;
                adcPar.t2.AdPorog = 0;


                err = pI->FillDAQparameters(&adcPar.t2);
                if (err != L_SUCCESS) {
                    cout << "fill daq parameters error: " << err << endl;
                }

                if (err == L_SUCCESS) {
                    err = pI->SetParametersStream(&adcPar.t2, &size, (void **)&p, (void **)&pp,L_STREAM_ADC);
                    if (err != L_SUCCESS) {
                        cout << "set daq parameters error: " << err << endl;
                    }
                }

                if (err == L_SUCCESS) {
                    cout << "Buffer size(word): " << size << endl;
                    cout << "Pages:             " << adcPar.t2.Pages << endl;
                    cout << "IrqStep:           " << adcPar.t2.IrqStep << endl;
                    cout << "FIFO:              " << adcPar.t2.FIFO << endl;
                    cout << "Rate:              " << adcPar.t2.dRate << " KHz" << endl;
                    cout << "Kadr:              " << adcPar.t2.dKadr << " ms" << endl;

                    cout << "Use dig channels   " << (f_app_opts.dig_chs_en ?  "Yes" : "No") << endl;
                    cout << "Use test sequence  " << (f_app_opts.test_mode ? "Yes" : "No") << endl;
                    cout << "Dev buf ovf check  " << (f_app_opts.ov_check_en ? "Yes" : "No") << endl;
                }
            }

            if (err == L_SUCCESS) {
                err = pI->EnableCorrection();
                if (err != L_SUCCESS)
                    cout << "Enabled correction error: " << err << endl;
            }

            if (err == L_SUCCESS) {
                USHORT tst_wrd = f_app_opts.test_mode ? 1 : 0;
                err = pI->outmword(SEL_TEST_MODE, &tst_wrd);
                if (err != L_SUCCESS)
                    cout << "set test mode error: " << err << endl;
            }


            if (err == L_SUCCESS) {
                err = pI->InitStartLDevice();
                if (err != L_SUCCESS)
                    cout << "init start error: " << err << endl;
            }

            if (err == L_SUCCESS) {
                pthread_t thread1;
                if (err == L_SUCCESS) {
                    pthread_create(&thread1, NULL, thread_func, pI);
                    err = pI->StartLDevice();
                    if (err != L_SUCCESS) {
                        cout << "start device error: " << err << endl;
                    }
                }

                if (err == L_SUCCESS) {
                    cout << "Data acquisition started. Press CTRL+C for stop..." << endl;
                    while (!f_out) {
                        usleep(40000);
                    }
                } else {
                    f_out = 1;
                }

                pthread_join(thread1,NULL);
                pI->StopLDevice();

                if (err == L_SUCCESS) {
                    /* вывод общией статистики по сбору */
                    cout << "Data acqusition finished..."  << endl;
                    cout << " Total samples processed: " << acq_stat.total_samples  << endl;
                    cout << " Total time (mks):        " << acq_stat.total_time  << endl;
                    cout << " Max block len:           " << acq_stat.max_block_len  << endl;
                    cout << " Max block time (mks):    " << acq_stat.max_block_time << endl;
                    if (acq_stat.total_time > 0) {
                        cout << " Avg receive freq         " <<
                                (double)((uint64_t)1000000 *
                                         (acq_stat.total_samples - acq_stat.first_block_len)
                                         / acq_stat.total_time)/1000 << " KHz" << endl;
                    }


                    cout << " Module buffer state:" << endl;
                    cout << "   overflow cntr:         " << acq_stat.module_buf.ov_cnt << endl;
                    cout << "   max words:             " << acq_stat.module_buf.max_words << " of " << acq_stat.module_buf.buf_size << endl;
                    cout << "   max request dtime:     " << acq_stat.module_buf.max_req_dtime << endl;


                    cout << " Total errors:            " << acq_stat.errs_cnt << endl;

                    if (acq_stat.errs_cnt > 0) {
                        for (uint64_t err_idx = 0; (err_idx < acq_stat.errs_cnt) && (err_idx < SAVE_ERRS_CNT); err_idx++) {
                            cout << " Error " << err_idx + 1 << ":" << endl;
                            const t_err_info *perr_info = &acq_stat.errs[err_idx];
                            if (err_idx != 0) {
                                cout << "   words from prev err    " << perr_info->word_num - acq_stat.errs[err_idx-1].word_num << endl;
                            }
                            cout << "   lcomp buf pos:         " << perr_info->lcomp_buf_pos << endl;
                            cout << "   proc block pos:        " << perr_info->proc_buf_pos  << endl;
                            cout << "   proc block len:        " << perr_info->proc_buf_len  << endl;
                            cout << "   block start dtime:     " << perr_info->start_block_dtime  << endl;
                            cout << "   error point dtime:     " << perr_info->err_point_dtime  << endl;
                            cout << "   receive word:          " << perr_info->rcv_wrd  << endl;
                            cout << "   expected word:         " << perr_info->exp_wrd  << endl;
                        }


                        cout << "dump last data buffer to file..." << endl;
                        /* сохранение кольцевого буфера в текстовый файл */
                        ofstream dumpfile;
                        dumpfile.open ("dump.txt");

                        unsigned cur_buf_pos = (tmp_buf_pos + SAVE_BUF_SIZE - tmp_buf_size) % SAVE_BUF_SIZE;
                        unsigned first_err_pos = acq_stat.errs[0].tmp_buf_pos;

                        /* номер индекса относительно первой ошибки */
                        int delta_idx = -(first_err_pos > cur_buf_pos ?
                                              first_err_pos - cur_buf_pos :
                                              first_err_pos - cur_buf_pos + tmp_buf_size);

                        for (unsigned i = 0; i < tmp_buf_size; i++) {
                            dumpfile << "   " << setw(6) << delta_idx <<  " : " << setw(5) << tmp_buf[cur_buf_pos] << endl;
                            delta_idx++;
                            cur_buf_pos++;
                            if (cur_buf_pos == SAVE_BUF_SIZE)
                                cur_buf_pos = 0;
                        }
                        dumpfile.close();
                        cout << "dump finished" << endl;
                    }
                }
            }
        }

        if (pI) {
            pI->CloseLDevice();
            pI->Release();
        }

        if(handle)
            dlclose(handle);
    }
    return err;
}
