/*
    Данный пример демонстрирует работу с модулем LTR25.
    По умолчанию идет работа с первым слотом первого крейта, но это можно изменить
        через параметры командной строки:
    ltr25_recv  slot crate_serial srvip
    где:
        slot         - номер слота (от 1 до 16)
        crate_serial - серийный номер крейта (если крейтов больше одного)
        srvip        - ip-адрес программы ltrd или LtrServer (если он запущен на другой машине)
    Параметры опциональны: можно указать только слот, слот и серийный номер крейта или все

    Пример принимает данные от первых трех каналов на максимальной частоте сбора.
    На экране отображается значение пик-пик (максимум - минимум) по принятому блоку
    по каждому каналу.

    Пользователь может изменить настройки на свои, поменяв заполнение полей
    структуры перед запуском сбора.

    Сбор идет до нажатия любой клавиши на Windows или  CTRL+C на Linux

    Сборка в VisualStudio:
    Для того чтобы собрать проект в Visual Studio, измените путь к заголовочным файлам
    (Проект (Project) -> Свойства (Properties) -> Свойства конфигурации (Configuration Properties)
    -> С/С++ -> Общие (General) -> Дополнительные каталоги включения (Additional Include Directories))
    на нужный в зависимаости от пути установки библиотек (ltr25api.h  и остальные заголовочные
    файлы должны находится в поддиректории ltr/include относительно указанного пути)
    и измените путь к .lib файлам на <путь установки библиотек>/lib/msvc
    (Проект (Project) -> Свойства (Properties) -> Свойства конфигурации (Configuration Properties) ->
    Компоновщик (Linker) -> Общие (General) -> Дополнительные каталоги библиотек (Additional Library Directories)).

    Внимание!: Если Вы собираете проект под Visual Studio и у Вас некорректно
    отображается вывод русских букв, то нужно изменить кодировку:
    выберите Файл (File) -> Дополнительные параметры сохранения (Advanced Save Options)...
    и в поле Кодировка (Encoding) выберите Юникод (UTF8, с сигнатурой)/Unicode (UTF-8 with signature)
    и сохраните изменения в файле.
*/

#include "ltr/include/ltr216api.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifdef _WIN32
#include <locale.h>
#include <conio.h>
#else
#include <signal.h>
#include <unistd.h>
#endif

LTR216API_DllExport(INT) LTR216_WriteInfo(TLTR216 *hnd);


#define LCH_CNT             (1)
/* количество отсчетов на канал, принмаемых за раз */
#define RECV_BLOCK_CH_SIZE  (100/LCH_CNT)
/* таймаут на ожидание данных при приеме */
#define RECV_TOUT           (1000)


#define  LTABLE_SW_WORD(sw1, sw2, sw3) ((((sw1) & 0xF) << 28) | \
    (((sw2) & 0xF) << 24) | \
    (((sw3) & 1) << 23))


#define LTABLE_SW_MAIN          LTABLE_SW_WORD(1,0,1)
#define LTABLE_SW_OFFS          LTABLE_SW_WORD(1,1,0)
#define LTABLE_SW_UREF          LTABLE_SW_WORD(4,1,0)
#define LTABLE_SW_UREF_BOUT     LTABLE_SW_WORD(2,1,0)
#define LTABLE_SW_UREF_OFFS     LTABLE_SW_WORD(4,2,0)
#define LTABLE_SW_U             LTABLE_SW_WORD(4,0,1)
#define LTABLE_SW_U_BOUT        LTABLE_SW_WORD(2,0,1)
#define LTABLE_SW_VADJ          LTABLE_SW_WORD(2,4,0)
#define LTABLE_SW_UNEG          LTABLE_SW_WORD(2,2,0)






#define LTABLE_WORD(sw, ch, gain, offs, bout) (sw | \
    (((ch) & 0xF) << 19) | \
    (((gain) & 0x7) << 16) | \
    ((offs) & 0x3FFF) | ((bout ? 1 : 0) << 14))

#define LTABLE_WORD_BG    (1 << 15)

#define LTR216_RANGE_1200 2

static const double ranges[] = {0.035, 0.070, 1.2};

static const struct {
    BYTE ku;
    double full_range;
    double user_range;
    INT range;
} f_gains[] = {
    {128,   9.77/1000,      9./1000,  -1},
    {64,   19.5/1000,      18./1000,  -1},
    {32,   39.0625/1000,   35./1000,  LTR216_RANGE_35},
    {16,   78.125/1000,    70./1000,  LTR216_RANGE_70},
    {8,   156./1000,      150./1000, -1},
    {4,   312./1000,      300./1000, -1},
    {2,   625./1000,      600./1000, -1},
    {1,  1250./1000,     1200./1000, LTR216_RANGE_1200}
};


static BYTE get_gain(DWORD range) {
    BYTE gain = 0;
    unsigned i;
    for (i = 0; i < sizeof(f_gains)/sizeof(f_gains[0]); i++) {
        if (range == (BYTE)f_gains[i].range) {
            gain = i;
        }
    }
    return gain;
}

static double cbr_dac(const TLTR216_CBR_VALUE *cbr, double val) {
    return cbr->Scale * val + cbr->Offset;
}

static WORD get_dac_code(const TLTR216_MODULE_INFO *minfo, DWORD range, double dac_val) {

    BYTE gain = get_gain(range);
    double dac_range = 2.5/f_gains[gain].ku;


    /* Используем 16384, а не 16383, чтобы ноль был ровным кодом и не вносить доп. погрешности
     * при нулевом значении ЦАП */
    double dac_code = (dac_val + dac_range) * 16384 / (2. *dac_range);
    if (dac_code < 0)
        dac_code = 0;
    if (dac_code > 16383)
        dac_code = 16383;

    dac_code = cbr_dac(&minfo->FabricCbr.Dac[range], dac_code);

    return (WORD)(dac_code + 0.5);
}




static DWORD ltable_fill_word(const TLTR216_MODULE_INFO *minfo, DWORD sw, BYTE ch, DWORD range, double dac_val, BOOLEAN bout) {
    BYTE gain = get_gain(range);
    WORD dac_code = get_dac_code(minfo, range, dac_val);
    return LTABLE_WORD(sw, ch, gain, dac_code, bout);
}




typedef struct {
    int slot;
    const char *serial;
    DWORD addr;
} t_open_param;


/* признак необходимости завершить сбор данных */
static int f_out = 0;

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


/* Разбор параметров командной строки. Если указано меньше, то используются
 * значения по умолчанию:
 * 1 параметр - номер слота (от 1 до 16)
 * 2 параметр - серийный номер крейта
 * 3 параметр - ip-адрес сервера */
static int f_get_params(int argc, char** argv, t_open_param* par) {
    int err = 0;
    par->slot = LTR_CC_CHNUM_MODULE1;
    par->serial = "";
    par->addr = LTRD_ADDR_DEFAULT;


    if (argc > 1)
        par->slot = atoi(argv[1]);
#if 0
    if (argc > 2)
        par->serial = argv[2];
    if (argc > 3) {
        int a[4],i;
        if (sscanf(argv[3], "%d.%d.%d.%d", &a[0], &a[1], &a[2], &a[3])!=4) {
            fprintf(stderr, "Неверный формат IP-адреса!!\n");
            err = -1;
        }

        for (i=0; (i < 4) && !err; i++) {
            if ((a[i]<0) || (a[i] > 255)) {
                fprintf(stderr, "Недействительный IP-адрес!!\n");
                err = -1;
            }
        }

        if (!err) {
            par->addr = (a[0] << 24) | (a[1]<<16) | (a[2]<<8) | a[3];
        }
    }
#endif
    return err;
}

int main(int argc, char** argv) {
    INT err = LTR_OK;
    TLTR216 hltr216;
    t_open_param par;
#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

#ifdef _WIN32
    /* для вывода русских букв в консоль для ОС Windows*/
    setlocale(LC_ALL, "");
#endif

    err = f_get_params(argc, argv, &par);

    if (err==LTR_OK) {
        LTR216_Init(&hltr216);

        /* Устанавливаем соединение с модулем */
        err = LTR216_Open(&hltr216, par.addr, LTRD_PORT_DEFAULT, par.serial, par.slot);
        if (err!=LTR_OK) {
            fprintf(stderr, "Не удалось установить связь с модулем. Ошибка %d (%s)\n",
                    err, LTR216_GetErrorString(err));
        } else {
            INT close_err;
#if LCH_CNT > 1
            BYTE ch_idx;
#endif
            double adcFreq;

            printf("Успешно установлена связь с модулем. Версия FPGA = %d\n", (int)hltr216.ModuleInfo.VerFPGA);

            LTR216_FillISrcCode(&hltr216, 25, NULL);
            hltr216.Cfg.FilterType = LTR216_FILTER_SINC5_1;
            hltr216.Cfg.AdcOdrCode = 0;
            hltr216.Cfg.Ch16ForUref = TRUE;
            hltr216.Cfg.TareEnabled = FALSE;
            LTR216_FillSyncFreqDiv(&hltr216, 1000, &adcFreq);
#if LCH_CNT == 1
            hltr216.Cfg.AdcSwMode = LTR216_ADC_SWMODE_SIGNLECH_CONT;
#else
            hltr216.Cfg.AdcSwMode = LTR216_ADC_SWMODE_MULTICH_SYNC;
#endif
            hltr216.Cfg.BgMeas = LTR216_BG_MEASGROUP_ALL;
            hltr216.Cfg.LChCnt = LCH_CNT;

#if LCH_CNT > 1
            for (ch_idx = 0; ch_idx < hltr216.Cfg.LChCnt; ch_idx++) {
                hltr216.Cfg.LChTbl[ch_idx].PhyChannel = ch_idx;//i % 16;
                hltr216.Cfg.LChTbl[ch_idx].Range = LTR216_RANGE_35;
            }
            //hltr216.Cfg.RawTables.LChTbl[hltr216.Cfg.RawTables.LChCnt - 1] = (1 << 15);

            //err = LTR216_WriteInfo(&hltr216);
#else
            hltr216.Cfg.LChTbl[0].PhyChannel = 14;
            hltr216.Cfg.LChTbl[0].Range = LTR216_RANGE_35;


#endif
            err = LTR216_SetADC(&hltr216);
            if (err != LTR_OK) {
                fprintf(stderr, "Не удалось установить код потенциометра. Ошибка %d (%s)\n",
                        err, LTR216_GetErrorString(err));
            } else {
                printf("Настройки АЦП установленны успешно.\n Частота АЦП = %.2f Гц\n Частота кадра %.2f\n Размер кадра: %d\n",
                       hltr216.State.AdcFreq, hltr216.State.FrameFreq, hltr216.State.FrameWordsCount);
            }
#if 1
            if (err == LTR_OK) {
                LTR216_InitMeasParams(&hltr216, LTR216_INIT_MEAS_ALL, 0, NULL);//LTR216_INIT_MEAS_ALL);
            }
#endif
            if (err == LTR_OK) {
                DWORD ch_ok;
                err = LTR216_TareOffset(&hltr216, 0xFFFF, &ch_ok);
            }

            if (err == LTR_OK) {
                hltr216.Cfg.Flags = LTR216_CONFIG_FLAG_RAWTABLE;
                TLTR216_RAWCONFIG *rawcfg = &hltr216.RawCfg;
                rawcfg->UserChCnt = rawcfg->ProcChCnt = rawcfg->MainLChCnt = 1;
                rawcfg->BgLChCnt = rawcfg->BgFrameStepCnt = rawcfg->BgBoutLChCnt = 0;
                rawcfg->BgStepSize = 1;
                rawcfg->FrameSize = 1;
                rawcfg->MainLChTbl[0] = ltable_fill_word(&hltr216.ModuleInfo,
                                                         LTABLE_SW_UREF, 0, LTR216_RANGE_1200,
                                                         0, FALSE);
                err = LTR216_SetADC(&hltr216);
            }

#if 0
            if (err == LTR_OK) {
                DWORD cbr_mask = 0;
                DWORD ch;
                err = LTR216_TareOffset(&hltr216, LTR216_CHANNEL_MASK_ALL, &cbr_mask);
                for (ch = 0; ch < LTR216_CHANNELS_CNT; ch++)
                    hltr216.ModuleInfo.Tare[ch].ScaleValid = FALSE;

                if (err == LTR_OK) {
                    err = LTR216_WriteTareInfo(&hltr216, LTR216_CHANNEL_MASK_ALL);
                }
            }
#endif

            if (err==LTR_OK) {
                DWORD recvd_blocks=0;
                INT recv_data_cnt = RECV_BLOCK_CH_SIZE*hltr216.Cfg.LChCnt;
                INT   recv_wrd_cnt = RECV_BLOCK_CH_SIZE*hltr216.State.FrameWordsCount;
                DWORD  *rbuf = (DWORD*)malloc(recv_wrd_cnt*sizeof(rbuf[0]));
                double *data = (double *)malloc(recv_data_cnt*sizeof(data[0]));
                TLTR216_DATA_STATUS status;


                if ((rbuf==NULL) || (data==NULL)) {
                    fprintf(stderr, "Ошибка выделения памяти!\n");
                    err = LTR_ERROR_MEMORY_ALLOC;
                }

                do {
                    recvd_blocks = 0;
                    if (err==LTR_OK) {
                        /* Запуск сбора данных */
                        err=LTR216_Start(&hltr216);
                        if (err!=LTR_OK) {
                            fprintf(stderr, "Не удалось запустить сбор данных! Ошибка %d (%s)\n",
                                    err, LTR216_GetErrorString(err));
                        }
                    }

                    if (err==LTR_OK) {
                        printf("Сбор данных запущен. Для останова нажмите %s\n",
    #ifdef _WIN32
                               "любую клавишу"
    #else
                               "CTRL+C"
    #endif
                               );
                        fflush(stdout);
                    }


                    /* ведем сбор данных до возникновения ошибки или до
                     * запроса на завершение */
                    while (!f_out && (err==LTR_OK) && (recvd_blocks < 10)) {
                        INT recvd;
                        /* В таймауте учитываем время сбора запрашиваемого числа отсчетов */
                        DWORD tout = RECV_TOUT + (DWORD)(1000.*RECV_BLOCK_CH_SIZE/
                                                         hltr216.State.FrameFreq + 1);
                        TLTR216_DATA_CH_STATUS ch_statuses[LCH_CNT];

                        /* Прием данных от модуля.  */
                        recvd = LTR216_Recv(&hltr216, rbuf, NULL, recv_wrd_cnt, tout);

                        /* Значение меньше нуля соответствуют коду ошибки */
                        if (recvd<0) {
                            err = recvd;
                            fprintf(stderr, "Ошибка приема данных. Ошибка %d:%s\n",
                                    err, LTR216_GetErrorString(err));
                        } else if (recvd!=recv_wrd_cnt) {
                            fprintf(stderr, "Принято недостаточно данных. Запрашивали %d, приняли %d\n",
                                    recv_wrd_cnt, recvd);
                            err = LTR_ERROR_RECV_INSUFFICIENT_DATA;
                        } else {
                            err = LTR216_ProcessData(&hltr216, rbuf, data, &recvd,
                                                     LTR216_PROC_FLAG_MEAS_UDIFF,
                                                     &status, ch_statuses);
                            if (err != LTR_OK) {
                                fprintf(stderr, "Ошибка обработки данных. Ошибка %d:%s\n",
                                        err, LTR216_GetErrorString(err));
                            } else {
                                DWORD ch;
                                DWORD show_cnt = hltr216.Cfg.LChCnt;
                                if (show_cnt > 16)
                                    show_cnt = 16;
                                recvd_blocks++;
                                printf("Блок %d (размер = %d, uref = %.2f):", recvd_blocks, recvd, 1000*status.Uref.Value);
                                for (ch=0; (ch < show_cnt) ; ch++) {
                                    printf("%8.2f", 1000.*data[ch]);

                                    if (ch==(show_cnt-1)) {
                                        printf("\n");
                                    } else {
                                        printf(",  ");
                                    }
                                }
                                fflush(stdout);
                            }
                        }

    #ifdef _WIN32
                        /* проверка нажатия клавиши для выхода */
                        if (err==LTR_OK) {
                            if (_kbhit())
                                f_out = 1;
                        }
    #endif
                    } //while (!f_out && (err==LTR_OK))

                    /* по завершению останавливаем сбор, если был запущен */
                    if (hltr216.State.Run) {
                        INT stop_err = LTR216_Stop(&hltr216);
                        if (stop_err!=LTR_OK) {
                            fprintf(stderr, "Не удалось остановить сбор данных. Ошибка %d:%s\n",
                                    stop_err, LTR216_GetErrorString(stop_err));
                            if (err==LTR_OK)
                                err = stop_err;
                        } else {
                            printf("Сбор остановлен успешно.\n");
                        }
                    }
#if 0
                    BYTE byte;
                    LTR216_WriteInfo(&hltr216);

                    LTR216_FlashWriteEnable(&hltr216);
                    LTR216_FlashRead(&hltr216, 0, &byte, 1);
                    LTR216_FlashWriteDisable(&hltr216);
#endif

                } while (!f_out);

                free(rbuf);
                free(data);
            }



            /* закрываем связь с модулем */
            close_err = LTR216_Close(&hltr216);
            if (close_err!=LTR_OK) {
                fprintf(stderr, "Не удалось закрыть связь с модулем. Ошибка %d:%s\n",
                        close_err, LTR216_GetErrorString(close_err));
                if (err==LTR_OK)
                    err = close_err;
            } else {
                printf("Связь с модулем успешно закрыта.\n");
            }
        }
    }

    return err;
}
