/*  Данный пример демонстрирует вывод на ЦАП LTR35 в циклическом режиме.
    Для примера используется генерация синуса.
    Количество каналов задается через определение CFG_USED_DAC_CH_CNT, а параметры
    заданны в массивах sig_amps (Амплитуда в В), sig_offs (Смещение в В)
    и sig_freq_mul (количество периодов сигнала в циклическом буфере).
    Также если определение CFG_DIGOUT_ENABLE == TRUE, то включается вывод на
    цифровые линии (в примере используется вывод счетика).
    CFG_OUT_CH_PT_CNT определяет количество точек на канал в циклическом буфере.
    Следует учитывать ограничение внутреннего буфера модуля для загрузки
    циклического сигнала.

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

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

#ifdef _WIN32
#include <locale.h>
#include <conio.h>
#else
#include <signal.h>
#include <unistd.h>
#endif


#ifndef M_PI
    #define M_PI 3.1415926535897932384626433832795
#endif


#define CFG_OUT_FREQ                192000  /* Используемая частота генерации данных (стандартные 192КГц, 96КГц и 48 КГц) */
#define CFG_USED_DAC_CH_CNT         8       /* Используемое число разрешенных каналов ЦАП для потокового вывода */
#define CFG_DIGOUT_ENABLE           TRUE    /* разрешение вывода на цифровые линии */
#define CFG_FORMAT_24               TRUE    /* Использование 24-битного формата отчетов ЦАП (если FALSE - 20 битный)*/
#define CFG_OUT_CH_PT_CNT           48000   /* Количество точек на канал загружаемых за один вызов */


/* параметры выставляемых сигналов (используются тольк первые CFG_USED_DAC_CH_CNT элементов) */
static const double sig_amps[LTR35_DAC_CHANNEL_CNT]   = {5.,       2.,   0.,   0.,   0.,   0.,   0.,   0.};
static const double sig_offs[LTR35_DAC_CHANNEL_CNT]   = {5.,       5.,   0.,   0.,   0.,   0.,   0.,   0.};
static const int sig_freq_mul[LTR35_DAC_CHANNEL_CNT]  = {1,        2,    3,    4,    5,    6,    7,    8};



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 (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;
}


/* Генерация и передача ch_pt_cnt точек сигнала на канал в модуль.
 * в ch_cur_phases передается массив с фазами синусов по каждому каналу,
 * который изменяется по генерации */
static int f_send_data(TLTR35 *phltr35, DWORD ch_pt_cnt) {
    INT err = 0;
    INT ch;
    DWORD i;
    DWORD dac_pt_cnt = 0;
    DWORD dout_pt_cnt = 0;
    DWORD snd_wrds_cnt = 0;
    DWORD *wrds = NULL;
    double *dac_data = NULL;
#if CFG_DIGOUT_ENABLE
    DWORD *dout_data = NULL;
#endif


    /* Кол-во отсчетов данных ЦАП определяем по числу разрешенных каналов из RAM и отсчетам на канал */
    dac_pt_cnt = phltr35->State.SDRAMChCnt*ch_pt_cnt;
#if CFG_DIGOUT_ENABLE
    /* На цифровой вывод всегда идет один отсчет на все каналы */
    dout_pt_cnt = ch_pt_cnt;
#endif


    /* Для каждого отсчета используется 1 или 2 передаваемых слов в зависимости
     * от формата данных.  */
    snd_wrds_cnt = (dac_pt_cnt + dout_pt_cnt) * ((phltr35->Cfg.OutDataFmt == LTR35_OUTDATA_FORMAT_24) ? 2 : 1);

    /* выделяем массивы для формирования данных */
    wrds = (DWORD*)malloc(snd_wrds_cnt * sizeof(wrds[0]));
    dac_data = (double*)malloc(dac_pt_cnt*sizeof(dac_data[0]));
#if CFG_DIGOUT_ENABLE
    dout_data = (DWORD*)malloc(dout_pt_cnt*sizeof(dout_data[0]));
#endif

    if ((dac_data == NULL) || (wrds == NULL)
#if CFG_DIGOUT_ENABLE
            || (dout_data == NULL)
#endif
            ) {
        err = LTR_ERROR_MEMORY_ALLOC;
    } else {
        for (i=0; i < ch_pt_cnt; i++) {
            for (ch=0; ch < phltr35->State.SDRAMChCnt; ch++) {
                dac_data[phltr35->State.SDRAMChCnt*i + ch] = sig_amps[ch]*sin(2.*M_PI*i*sig_freq_mul[ch]/ch_pt_cnt) + sig_offs[ch];
            }
    #if CFG_DIGOUT_ENABLE
            dout_data[i] = i & 0xFFFF;
    #endif
        }

        err = LTR35_PrepareData(phltr35, dac_data, &dac_pt_cnt,
#if CFG_DIGOUT_ENABLE
                                   dout_data, &dout_pt_cnt,
#else
                                   NULL, 0,
#endif
                                   LTR35_PREP_FLAGS_VOLT, wrds, &snd_wrds_cnt);
        if (err!=LTR_OK) {
            fprintf(stderr, "Ошибка подготовки данных (%d) : %s!\n", err, LTR35_GetErrorString(err));
        }

        if (!err) {
            INT sent_size = 0;

            /* передаем массив, либо пока не передадим все данные, либо пока не
             * произойдет ошибка, либо не будет признака выхода.
             * Передача с небольшим таймаутом в цикле идет для того, чтобы можно было
             * быстрее среагировать на признак выхода. Иначе можно передавать
             * за один Send с достаточным таймаутом
             */
            while (!f_out && !err && (sent_size < (INT)snd_wrds_cnt)) {
                INT cur_sent = LTR35_Send(phltr35, &wrds[sent_size], snd_wrds_cnt - sent_size, 500);
                if (cur_sent < 0) {
                    err = cur_sent;
                    fprintf(stderr, "Ошибка передачи данных (%d) : %s!\n", err, LTR35_GetErrorString(err));
                } else {
                    sent_size += cur_sent;
                }
#ifdef _WIN32
                /* проверка нажатия клавиши для выхода */
                if (err==LTR_OK) {
                    if (_kbhit())
                        f_out = 1;
                }
#endif
            }
        }
    }
    free(wrds);
    free(dac_data);
#if CFG_DIGOUT_ENABLE
    free(dout_data);
#endif

    return err;
}





int main(int argc, char** argv) {
    t_open_param par;
    INT err = LTR_OK;

#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
    /* для вывода русских букв в консоле... */
    setlocale(LC_ALL, "");
#endif

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

    if (err == LTR_OK) {
        INT close_err;
        TLTR35 hltr35;
        LTR35_Init(&hltr35);
        /* Устанавливаем соединение с модулем */
        err = LTR35_Open(&hltr35, par.addr, LTRD_PORT_DEFAULT, par.serial, par.slot);
        if (err!=LTR_OK) {
            fprintf(stderr, "Не удалось установить связь с модулем. Ошибка %d (%s)\n",
                    err, LTR35_GetErrorString(err));
        } else {

            double fnd_freq;

            printf("Модуль открыт успешно!\n");
            printf("  Название модуля    = %s\n", hltr35.ModuleInfo.Name);
            printf("  Серийный номер     = %s\n", hltr35.ModuleInfo.Serial);
            printf("  Модификация        = LTR35-%d\n", hltr35.ModuleInfo.Modification);
            printf("  Версия PLD         = %d\n", hltr35.ModuleInfo.VerPLD);
            printf("  Версия ПЛИС        = %d\n", hltr35.ModuleInfo.VerFPGA);
            printf("  Выходов ЦАП        = %d\n", hltr35.ModuleInfo.DacChCnt);
            printf("  Цифровых выходов   = %d\n", hltr35.ModuleInfo.DoutLineCnt);

            fflush(stdout);

            /* настраиваем частоту генерации */
            err = LTR35_FillOutFreq(&hltr35.Cfg, CFG_OUT_FREQ, &fnd_freq);
            if (err==LTR_OK) {
                BYTE ch_num;
#if CFG_FORMAT_24
                hltr35.Cfg.OutDataFmt = LTR35_OUTDATA_FORMAT_24;
#else
                hltr35.Cfg.OutDataFmt = LTR35_OUTDATA_FORMAT_20;
#endif
                hltr35.Cfg.OutMode = LTR35_OUT_MODE_CYCLE;
                /* Резарешаем первые CFG_USED_DAC_CH_CNT каналов */
                for (ch_num = 0; ch_num < CFG_USED_DAC_CH_CNT; ch_num++) {
                    hltr35.Cfg.Ch[ch_num].Enabled = TRUE;
                    hltr35.Cfg.Ch[ch_num].Source = LTR35_CH_SRC_SDRAM;
                    hltr35.Cfg.Ch[ch_num].Output = LTR35_DAC_OUT_FULL_RANGE; /* выход 1:1 */
                }

                /* настраиваем параметры модуля */
                err = LTR35_Configure(&hltr35);
                if (err != LTR_OK) {
                    fprintf(stderr, "Не удалось настроить модуль. Ошибка %d (%s)\n",
                            err, LTR35_GetErrorString(err));
                }
            }

            /* вначале подгружаем часть данных в буфер, чтобы на момент запуска
               генерации данные были уже готовы к выдаче */
            if (err==LTR_OK) {                
                err = f_send_data(&hltr35, CFG_OUT_CH_PT_CNT);
            }

            if (err==LTR_OK) {
                /* запуск генерации данных переключением на первую страницу */
                err = LTR35_SwitchCyclePage(&hltr35, 0, 30000);
                if (err!=LTR_OK) {
                    fprintf(stderr, "Ошибка запуска потокового режима (%d) : %s!\n",
                            err, LTR35_GetErrorString(err));
                } else {
                    printf("Запущен потоковый вывод!\n"); fflush(stdout);
                }
            }

            if (err == LTR_OK) {
                printf("Запущена генерация данных. Для останова нажмите %s\n",
#ifdef _WIN32
                       "любую клавишу"
#else
                       "CTRL+C"
#endif
                       );
                fflush(stdout);
                /* Для циклической генерации не требуется выполнять каких-либо
                 * действий. Просто ждем нажатие кнопки завершения */
                while ((err==LTR_OK) && !f_out) {
                    LTRAPI_SLEEP_MS(100);
#ifdef _WIN32
                    /* проверка нажатия клавиши для выхода */
                    if (err==LTR_OK) {
                        if (_kbhit())
                            f_out = 1;
                    }
#endif
                }
            }

            /* останавливаем выдачу данных, если она была запущена */
            if (hltr35.State.Run) {
                INT stop_err = LTR35_Stop(&hltr35, 0);
                if (stop_err!=LTR_OK) {
                    fprintf(stderr, "Не удалось остановить циклический вывод. Ошибка %d:%s\n",
                            stop_err, LTR35_GetErrorString(stop_err));
                    if (err==LTR_OK)
                        err = stop_err;
                } else {
                    printf("Циклический вывод остановлен успешно.\n");
                }
            }
        }

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

    return err;
}
