using System;
using ltrModulesNet;

/* Данный пример демонстрирует измерение одиночных интервалов между фронтами с пмощью 
   модуля LTR51 из программы на языке C#.
   Пример представляет собой консольную программу, которая устанавливает связь с модулем,
   выводит информацию о модуле, устанавливает настройки и собирает заданное кол-во 
   блоков данных, соответствующих по вермени установленному интервалу подсчета.
 
    Пример принимает данные от первых двух каналов и на основании принятых
    параметров M и N рассчитывает интервалы между заданным количеством фронтов.
    При появлении первого фронта для канала просто выводится сообщение, при
    появлении REQ_FRONT_CNT-ого фронта рассчитывается время с первого.
    При отсутствии заданного кол-ва фронтов за MAX_MEAS_INTERVAL_MS мс подсчет времени сбрасывается
    и снова ожидается первый фронт.
    Интервал между двумя фронтами должен быть не менее MIN_FRONT_INTERVAL_MS мс.

    Пользователь может изменить настройки на свои, поменяв заполнение полей
    структуры перед запуском сбора.
    
    Необходимо установить номер слота, в котором вставлен модуль (константа SLOT).
    Настройки сбора задаются в коде при конфигурации модуля.
 */

namespace ltr51_interval
{
    class ltr51_interval
    {
        /* Номер слота в крейте, где вставлен модуль */
        const int SLOT = 15;

        /* минимальный интервал между фронтами в мс */
        const double MIN_FRONT_INTERVAL_MS  = 2;
        /* максимальный интервал измерения в мс */
        const double MAX_MEAS_INTERVAL_MS   = 500000;
        /* количество фронтов, между которыми замеряется интервал */
        const int REQ_FRONT_CNT = 2;

        /* Количество блоков, которые нужно принять и выйти */
        const int RECV_BLOCK_CNT = 5000;

        struct FrontDetectInfo
        {  
            public uint fronts_cnt; /* признак, что был найден фронт */
            public uint dt_cnt; /* кол-во периодов дескретизации, которое длится текущий интервал */
        };
         

        static void Main(string[] args)
        {
            /* LTR51_Init() вызывается уже в конструкторе */
            ltr51api hltr51 = new ltr51api();
            /* отрываем модуль. Используем упрощенный вариант функции с указанием только слота.
             * (есть вариант как с только со слотом, так и с серийным крейта и слотом 
             *  + полный). Используем вариант функции без явного указания файла прошивки ПЛИС,
             *  который использует встроенный в .dll файл */
            _LTRNative.LTRERROR err = hltr51.Open(SLOT);
            if (err != _LTRNative.LTRERROR.OK)
            {
                Console.WriteLine("Не удалось открыть модуль. Ошибка {0}: {1}",
                                    err, ltr51api.GetErrorString(err));
            }
            else
            {
                /* выводим информацию из hltr51.ModuleInfo */
                Console.WriteLine("Информация о модуле: ");
                Console.WriteLine("  Название модуля        : {0}", hltr51.ModuleInfo.Name);
                Console.WriteLine("  Серийный номер         : {0}", hltr51.ModuleInfo.Serial);
                Console.WriteLine("  Версия прошивки        : {0}", hltr51.ModuleInfo.FirmwareVersion);
                Console.WriteLine("  Дата создания прошивки : {0}", hltr51.ModuleInfo.FirmwareDate);
                Console.WriteLine("  Версия прошивки ПЛИС   : {0}", hltr51.ModuleInfo.FPGA_Version);

                /* --------------- задание параметров работы модуля ------------ */                 
                 hltr51.AcqTime = 200;

                 /* Значения Base и FS задаем вручную */
                 hltr51.SetUserPars = true;
                 hltr51.Fs = ltr51api.LTR51_FS_MAX; /* устанавливаем макс. частоту для макс. разрешения
                                         500 KГц */
                 /* рассчитываем base, чтобы период измерения был хотя бы в 2 раза
                  * меньше минимального интервала следования фронтов, так как для
                  * измерения длительности одиночных интервалов нужно, чтобы
                  * каждый фронт был в своем интервале измерения */
                 hltr51.Base = (ushort)((MIN_FRONT_INTERVAL_MS * hltr51.Fs / 1000) / 2);


                 hltr51.LChQnt = 2;
                 
                double HighThreshold = 0.7;
                double LowThreshold = 0.3;
                /* для примера настраиваем первые два канала (первый мезанин).
                 * Первым параметром указываем номер заполняемого элемента в таблице (лог. канала) - считается от 0
                 * Вторым параметром - реально опрашиваемый канал - считается от 1
                 * ThresholdRange должен соответсвовать положению джампера на модуле */
                hltr51.FillLChannel(0, 1, HighThreshold, LowThreshold,
                                    ltr51api.ThresholdRange.Range_10V, ltr51api.EdgeMode.RISE);
                hltr51.FillLChannel(1, 2, HighThreshold, LowThreshold,
                                    ltr51api.ThresholdRange.Range_10V, ltr51api.EdgeMode.RISE);
    
                err = hltr51.Config();
                if (err != _LTRNative.LTRERROR.OK)
                    {
                        Console.WriteLine("Не удалось настроить модуль! Ошибка {0}: {1}",
                                        err, ltr51api.GetErrorString(err));     
                    } else 
                 {
                     Console.WriteLine("Установлены следующие параметры:");
                     Console.WriteLine("  Частота дискретизации         = {0} Гц", hltr51.Fs);
                     Console.WriteLine("  Счетчик BASE                  = {0}", hltr51.Base);
                     Console.WriteLine("  Время счета                   = {0} мс", hltr51.AcqTime);
                     Console.WriteLine("  Количество периодов измерения = {0}", hltr51.TbaseQnt);
                 }
            }


            if (err == _LTRNative.LTRERROR.OK)
            {
                /* всегда идет прием слов от всех каналов по два слова на канал.
                 * Для расчета частоты необходимо TbaseQnt кадров */
                uint recv_word_cnt = (uint)(2*ltr51api.LTR51_CHANNELS_CNT*hltr51.TbaseQnt);
                uint counters_cnt  = (uint)(hltr51.LChQnt * hltr51.TbaseQnt);
                uint[] rbuf = new uint[recv_word_cnt];
                uint[] counters = new uint[counters_cnt];                
                FrontDetectInfo[] detect_ch_info = new FrontDetectInfo[hltr51.LChQnt];
                /* рассчитываем кол-во периодов дескретизации, которое соответствует
                * максимальному отслеживаемому интервалу */
                uint dt_cnt_max = (uint)(MAX_MEAS_INTERVAL_MS * hltr51.Fs / 1000 + 0.5);

                /* таймаут на прием расчитывается библиотечной функцией */
                uint tout = hltr51.CalcTimeOut(hltr51.TbaseQnt);

                /* запуск сбора данных */
                err = hltr51.Start();
                if (err != _LTRNative.LTRERROR.OK)
                {
                    Console.WriteLine("Не удалось запустить сбор данных. Ошибка {0}: {1}",
                         err, ltr51api.GetErrorString(err));
                }
                else
                {
                    _LTRNative.LTRERROR stop_err;
                    for (uint block_num = 0; (block_num < RECV_BLOCK_CNT) &&
                            (err == _LTRNative.LTRERROR.OK); block_num++)
                    {
                        int rcv_cnt;
                        /* прием необработанных слов. есть варинант с tmark и без него для удобства */
                        rcv_cnt = hltr51.Recv(rbuf, (uint)rbuf.Length, tout);

                        /* значение меньше 0 => код ошибки */
                        if (rcv_cnt < 0)
                        {
                            err = (_LTRNative.LTRERROR)rcv_cnt;
                            Console.WriteLine("Ошибка приема данных. Ошибка {0}: {1}",
                                              err, ltr51api.GetErrorString(err));
                        }
                        else if (rcv_cnt != rbuf.Length)
                        {
                            err = _LTRNative.LTRERROR.ERROR_RECV_INSUFFICIENT_DATA;
                            Console.WriteLine("Приняли недостаточно данных: запрашивали {0}, приняли {1}",
                                              rbuf.Length, rcv_cnt);
                        }
                        else
                        {
                            /* ProcessData в LTR51 может расчитывать как частоту, так
                             * и значения N и M для каждого периода измерения.
                             * Для удобства введены 3 варианта ProcessData:
                             * c получением частоты, с получением N и M, и обоих значений.
                             * В данном случае используем первый. 
                             * В данном примере используем получение N и M счетчиков без частоты.
                             * Размер передается по ссылке, т.к. функция обновляет размер в
                             * соответствии с реально сохраненным количеством слов со счетчиками */                           uint proc_cnt = (uint)rcv_cnt;
                            err = hltr51.ProcessData(rbuf, counters, ref proc_cnt);
                            if (err != _LTRNative.LTRERROR.OK)
                            {
                                Console.WriteLine("Ошибка обработки данных. Ошибка {0}: {1}",
                                                   err, ltr51api.GetErrorString(err));
                            }
                            else
                            {
                                for (uint pt=0; pt < proc_cnt; pt+=(uint)hltr51.LChQnt) {
                                    for (int ch=0; ch < hltr51.LChQnt; ch++) {                                        
                                        bool check_drop = false;
                                        uint n = (counters[pt + ch] >>16) & 0xFFFF;
                                        uint m = (counters[pt+ch]&0xFFFF);
                                        if (n != 0) {
                                            if (n > 1) {
                                                Console.WriteLine("Канал {0}: {1} фронтов за один интервал измерения!! Неправильные настройки!", ch+1, n);
                                                /* если больше одного фронта,
                                                 * то точно определить можем только время последнего.
                                                 * если вели рассчеты, то эту информацию отбрасываем и
                                                 * начинаем рассчет нового периода с последнего фронта */
                                                detect_ch_info[ch].fronts_cnt = 0;
                                                detect_ch_info[ch].dt_cnt = m;
                                            } else {
                                                if (detect_ch_info[ch].fronts_cnt == 0) {
                                                    Console.WriteLine("Канал {0}: найден первый фронт", ch+1);
                                                    detect_ch_info[ch].fronts_cnt = 1;
                                                    detect_ch_info[ch].dt_cnt = m;
                                                } else if ((detect_ch_info[ch].fronts_cnt + 1) == (uint)(REQ_FRONT_CNT)) {
                                                    double period_ms;
                                                    detect_ch_info[ch].dt_cnt += (hltr51.Base - m);
                                                    period_ms = (double)1000*detect_ch_info[ch].dt_cnt/hltr51.Fs;
                                                    Console.WriteLine("Канал {0}: найден последний фронт. Интервал с первого: {1} мс",
                                                           ch+1, period_ms);
                                                    /* начинаем измерять время от этого фронта */
                                                    detect_ch_info[ch].dt_cnt = m;
                                                    detect_ch_info[ch].fronts_cnt = 1;
                                                } else {
                                                    /* для промежуточных фронтов просто
                                                     * прибавляем весь интервал
                                                     * и увеличиваем кол-во найденных фронтов */
                                                    detect_ch_info[ch].fronts_cnt++;
                                                    detect_ch_info[ch].dt_cnt+=hltr51.Base;
                                                    check_drop = true;
                                                    Console.WriteLine("Канал {0}: найден промежуточный фронт {1}",
                                                           ch+1, detect_ch_info[ch].fronts_cnt);
                                                }
                                            }
                                        } else {
                                            /* n==0 => hltr51.Base отсчетов
                                             * были без фронта => прибавляем
                                             * к текущему интервалу */
                                            if (detect_ch_info[ch].fronts_cnt > 0) {
                                                detect_ch_info[ch].dt_cnt+=hltr51.Base;
                                                check_drop = true;
                                            }                                                    
                                        }

                                        /* проверка, что превышен максимальный интервал отслеживания фронтов
                                         * и нужно сбросить подсчеты */
                                        if (check_drop && (detect_ch_info[ch].dt_cnt > dt_cnt_max)) {
                                            Console.WriteLine("Канал {0}: не было последующего фронта за заданный интервал", ch+1);
                                            detect_ch_info[ch].fronts_cnt = 0;
                                            detect_ch_info[ch].dt_cnt = 0;
                                        }
                                    }
                               }
                            }
                        }
                    }

                    /* останавливаем сбор данных */
                    stop_err = hltr51.Stop();
                    if (stop_err != _LTRNative.LTRERROR.OK)
                    {
                        Console.WriteLine("Не удалось остановить сбор данных. Ошибка {0}: {1}",
                             stop_err, ltr51api.GetErrorString(stop_err));
                        if (err == _LTRNative.LTRERROR.OK)
                            err = stop_err;
                    }
                }
            }

            hltr51.Close();
        }
    }
}
