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

    Данный пример использует факт, что в бибилотеках 1.30.8 и выше можно передать
    пустую строку либо NULL в качестве имени bio-файла для загурузки и будет взят
    файл из ресурса библиотеки (Windows) или по стандартному пути (Linux).

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

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

    После завершения калибровки (если пользователь ввел, что следующий этап не
    нужен), выполняется сбор данных.
    Сбор идет до нажатия любой клавиши на Windows или  CTRL+C на Linux

    Сборка в VisualStudio:
    Для того чтобы собрать проект в Visual Studio, измените путь к заголовочным файлам
    (Проект (Project) -> Свойства (Properties) -> Свойства конфигурации (Configuration Properties)
    -> С/С++ -> Общие (General) -> Дополнительные каталоги включения (Additional Include Directories))
    на нужный в зависимаости от пути установки библиотек (ltr212api.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/ltr212api.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>
#include <termios.h>
#endif


#ifdef _MSC_VER
    /* отключаем предупреждение на использование getch */
    #pragma warning( disable : 4996)
#endif


/* кол-во отсчетов на канал, принимаемых за один раз */
#define RECV_CH_SIZE       4

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

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


/* Разбор параметров командной строки. Если указано меньше, то используются
 * значения по умолчанию:
 * 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 (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];
        }
    }
    return err;
}



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


int getch(void) {
     struct termios oldt,
     newt;
     int ch;
     tcgetattr( STDIN_FILENO, &oldt );
     newt = oldt;
     newt.c_lflag &= ~( ICANON | ECHO );
     tcsetattr( STDIN_FILENO, TCSANOW, &newt );
     ch = getchar();
     tcsetattr( STDIN_FILENO, TCSANOW, &oldt );
     return ch;
}
#endif

/* получение ответа y или n с клавиатуры */
int get_yesno(void) {
    int ch;
    do {
        ch = getch();
    } while ((ch!='y') && (ch!='Y') && (ch!='n') && (ch!='N'));
    return (ch=='y') || (ch=='Y');
}

/* строки с названиями модификаций модуля */
static const char* f_module_type_str[] = {
    "LTR212/LTR212-M3",
    "LTR212-M1",
    "LTR212-M2"
};

/* строки с описанием вариантов калибровки */
static const char* f_cbr_names[] = {
    "Внутренняя калибровка нуля",
    "Внутренняя калибровка шкалы",
    "Внутренняя калибровка нуля и шкалы",
    "Внешняя тарировка нуля",
    "Внешняя тарировка шкалы",
    "Внешняя тарировка нуля с внутр. калибровкой шкалы",
    "Внешняя тарировка шкалы (2-ой этап полной)",
    "Внешняя тарировка нуля с сохранением коэф. шкалы",
};

#define CBR_MODES_CNT sizeof(f_cbr_names)/sizeof(f_cbr_names[0])

/* вывод списка каналов по маске */
static void f_print_ch_msk(BYTE lch_msk) {
    INT ch;
    for (ch=0; lch_msk!=0; ch++) {
        if (lch_msk & (1<<ch)) {
            printf("%d", ch+1);
            lch_msk &= ~(1<<ch);
            if (lch_msk)
                printf(", ");
        }
    }
}



int main(int argc, char** argv) {
    INT err = LTR_OK;
    TLTR212 hltr212;
    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) {
        LTR212_Init(&hltr212);
        /* Устанавливаем соединение с модулем. Используем встроенную в библиотеку
            прошивку DSP*/
        err = LTR212_Open(&hltr212, par.addr, LTRD_PORT_DEFAULT, par.serial, par.slot, NULL);
        if (err != LTR_OK) {
            fprintf(stderr, "Не удалось установить связь с модулем. Ошибка %d (%s)\n",
                    err, LTR212_GetErrorString(err));
        } else {
            /* после открытия модуля доступна информация, кроме версии прошивки ПЛИС */
            printf("Модуль открыт успешно!\n");
            printf("  Название модуля    = %s\n", hltr212.ModuleInfo.Name);
            printf("  Серийный номер     = %s\n", hltr212.ModuleInfo.Serial);
            printf("  Тип модуля         = %s\n", hltr212.ModuleInfo.Type <
                   sizeof(f_module_type_str)/sizeof(f_module_type_str[0]) ?
                        f_module_type_str[hltr212.ModuleInfo.Type] : "Неизвествный тип");
            printf("  Версия прошивки    = %s\n", hltr212.ModuleInfo.BiosVersion);
            printf("  Дата прошивки    = %s\n",   hltr212.ModuleInfo.BiosDate);
            fflush(stdout);

            /* Проверка контрольной суммы EEPROM */
            err = LTR212_TestEEPROM(&hltr212);
            if (err!=LTR_OK) {
                fprintf(stderr, "Ошибка проверки контрольной суммы EEPROM: %d (%s)\n",
                        err, LTR212_GetErrorString(err));
            }


            if (err==LTR_OK) {
                INT ch;
                /* ------------- Установка настроек модуля --------------- */
                hltr212.AcqMode = LTR212_FOUR_CHANNELS_WITH_HIGH_RESOLUTION;

                hltr212.LChQnt = 4;
                hltr212.REF = LTR212_REF_5V;
                hltr212.AC = FALSE;

                for (ch=0; ch < hltr212.LChQnt; ch++) {
                    hltr212.LChTbl[ch]=LTR212_CreateLChannel(ch+1, LTR212_SCALE_B_10);
                }
            }


            if (err==LTR_OK) {
                printf("\nИспользовать пользовательские тарировочные коэффициенты (y) или заводские калибровки (n)?:");
                if (!get_yesno()) {
                    hltr212.UseClb = FALSE;
                    hltr212.UseFabricClb = TRUE;
                    printf("\n");
                } else {
                    int done = 0;

                    hltr212.UseClb = TRUE;
                    hltr212.UseFabricClb = FALSE;

                    while (!done) {
                        printf("\nВыполнить очередной этап калибровки сейчас? (y/n): ");
                        if (!get_yesno()) {
                            done = 1;
                            printf("\n");
                        } else {
                            int answer;
                            unsigned i;
                            printf("\nВозможны следующие варинты калибровки:\n");
                            for (i=0; i < CBR_MODES_CNT; i++) {
                                printf(" %d - %s\n", i, f_cbr_names[i]);
                            }
                            printf("Введите номер проводимого этапа калибровки:\n");
                            answer = getch();
                            if ((answer >= '0')  && (answer< (char)('0'+CBR_MODES_CNT))) {
                                INT mode = answer - '0';
                                INT reset;
                                BYTE lch_msk, lch_msk_orig;
                                INT cbr_err;
                                printf("\nВыполнять сброс АЦП (y/n)?");
                                reset = get_yesno();

                                printf("\nВыполнение этапа калибровки \"%s\" %s\n",
                                       f_cbr_names[mode], reset ? "со сбросом АЦП" : "без сброса АЦП");

                                /* все каналы */
                                lch_msk = lch_msk_orig = (1 << hltr212.LChQnt) - 1;
                                cbr_err = LTR212_Calibrate(&hltr212, &lch_msk, mode, reset);

                                if (cbr_err == LTR212_ERR_CANT_CALIBRATE) {
                                    /* если часть каналов не удалось откалибровать, то
                                       lch_msk указывает, какие все же откалиброваны*/
                                    printf("Часть каналов не удалось откалибровать: ");
                                    f_print_ch_msk(lch_msk_orig & ~lch_msk);
                                    printf("\n");
                                    /* удаляем биты из оригинальной маски, чтобы
                                       получить, какие каналы были успешны */                                    
                                    if (lch_msk) {
                                        printf("Успешно откалиброваны следующие каналы: ");
                                        f_print_ch_msk(lch_msk);
                                        printf("\n");
                                    } else {
                                        printf("Успешно не удалось откалибровать ни один канал!\n");
                                    }
                                } else if (cbr_err!=LTR_OK) {
                                    fprintf(stderr, "Не удалось выполнить калибровку! Ошибка %d (%s)\n",
                                            cbr_err, LTR212_GetErrorString(cbr_err));
                                } else {
                                    printf("Калибровка выполнена успешно!\n");
                                }

                            } else {
                                printf("\n Неверный номер этапа калибровки");
                            }
                        }
                    }
                }
            }




            if (err==LTR_OK) {
                err = LTR212_SetADC(&hltr212);
                if (err!=LTR_OK) {
                    fprintf(stderr, "Не удалось установить настройки АЦП: Ошибка %d (%s)\n",
                        err, LTR212_GetErrorString(err));
                }
            }

            if (err==LTR_OK) {
                DWORD *wrds=NULL;
                double *data=NULL;
                INT recv_data_cnt = hltr212.LChQnt * RECV_CH_SIZE;
                //каждый отсчет состоит из 2-х слов
                INT recv_wrds_cnt = 2*recv_data_cnt;
                DWORD tout;
                INT Run = 0;
                INT frame_num = 0;

                tout = LTR212_CalcTimeOut(&hltr212, RECV_CH_SIZE);

                /* выделяем память под принимаемые и обработанные слова */
                wrds = (DWORD*)malloc(recv_wrds_cnt*sizeof(wrds[0]));
                data = (double*)malloc(recv_data_cnt*sizeof(data[0]));

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

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

                while (!f_out && (err==LTR_OK)) {
                    INT recv_cnt=0;
                    /* Прием данных за рассчитанный таймаут */
                    recv_cnt = LTR212_Recv(&hltr212, wrds, NULL, recv_wrds_cnt, tout);
                    /* Значение меньше нуля соответствуют коду ошибки */
                    if (recv_cnt<0) {
                        err = recv_cnt;
                        fprintf(stderr, "Ошибка при приеме кадра: Ошибка %d (%s)\n",
                                err, LTR212_GetErrorString(err));
                    }
                    else if (recv_cnt < recv_wrds_cnt) {
                        fprintf(stderr, "Принято меньше слов, чем было в кадре! запрашивали %d, приняли %d\n",
                                recv_wrds_cnt, recv_cnt);
                        err = LTR_ERROR_RECV_INSUFFICIENT_DATA;
                    } else {

                        /* переводим данные в Вольты */
                        err = LTR212_ProcessData(&hltr212, wrds, data, (DWORD*)&recv_cnt, TRUE);
                        if (err!=LTR_OK) {
                            printf("Ошибка обработки данных! Ошибка %d (%s)\n",
                                   err, LTR212_GetErrorString(err));
                        } else {
                            int i;
                            frame_num++;
                            /* на экран выводим первые два отсчета,а
                             * полностью кадр сохраняем в файл */
                            printf("Успешно приняли кадр %d: первые отсчеты: ", frame_num);
                            for (i=0; (i < hltr212.LChQnt) && (i < recv_cnt); i++)
                                printf(" %.4f мВ", data[i]*1000);
                            printf("\n");
                            fflush(stdout);
                        }
                    }

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

                /* останавливаем запись данных модулем, если она была запущена */
                if (Run) {
                    INT stoperr = LTR212_Stop(&hltr212);
                    if (stoperr==LTR_OK) {
                        printf("Сбор остановлен успешно!\n");
                    } else {
                        fprintf(stderr, "Сбор остановлен с ошибкой. Ошибка %d (%s)\n",
                                err, LTR212_GetErrorString(err));
                        if (err==LTR_OK)
                            err = stoperr;
                    }
                }

                free(wrds);
                free(data);
            }
        }
        LTR212_Close(&hltr212);
    }


    return err;
}
