/// <summary>
        /// Проверяем время на корректность данных
        /// Время может быть задано в секундах и в долях секунд
        /// 0; 0.230; 33.675.
        /// Миллисекунды должны быть от 0 до 999.
        /// Значение секунд не должно быть больше 65535
        /// 
        /// TODO: избавиться от дубликата проверки секунд
        /// </summary>
        /// <param name="timeStr">распознанное время из команды</param>
        private void tryParseTimeToken(string timeStr)
        {
            string[] timeTokens = timeStr.Split('.');   // timeTokens[0] - значение секунд, timeTokens[1] - значение мс, если задано

            // дробное значение не задано, считаем, что заданы секунды
            if (timeTokens.Length == 1)
            {
                try
                {
                    int tempSec = Int32.Parse(timeTokens[0]);
                    if ((tempSec < 0) || (tempSec > MAX_SECONDS_VALUE))
                    {
                        CyclogramParsingException exc = new CyclogramParsingException("Ошибка преобразования времени выполнения команды: " + timeStr + ". Секунды должны быть заданы от 0 до " + MAX_SECONDS_VALUE.ToString());
                        throw exc;
                    }
                    _curCommand.DelayMs = tempSec * 1000;
                    _curCommand.DelayOriginal = _curCommand.DelayMs;
                }
                catch (FormatException)
                {
                    CyclogramParsingException exc = new CyclogramParsingException("Ошибка преобразования времени выполнения команды: " + timeStr);
                    throw exc;
                }
            }
            // задано и дробное значение
            else if (timeTokens.Length == 2)
            {
                try
                {
                    int tempSec = Int32.Parse(timeTokens[0]);
                    if ((tempSec < 0) || (tempSec > MAX_SECONDS_VALUE))
                    {
                        CyclogramParsingException exc = new CyclogramParsingException("Ошибка преобразования времени выполнения команды: " + timeStr + ". Секунды должны быть заданы от 0 до " + MAX_SECONDS_VALUE.ToString());
                        throw exc;
                    }

                    int ms = Int32.Parse(timeTokens[1]);
                    if ((ms < 0) && (ms > 999))
                    {
                        CyclogramParsingException exc = new CyclogramParsingException("Ошибка преобразованя времени выполнения команды: " + timeStr + ". Милисекунды должны быть от 0 до 999.");
                        throw exc;
                    }

                    _curCommand.DelayMs = tempSec * 1000 + ms;
                    _curCommand.DelayOriginal = _curCommand.DelayMs;
                }
                catch (FormatException)
                {
                    CyclogramParsingException exc = new CyclogramParsingException("Ошибка преобразованя времени выполнения команды: " + timeStr);
                    throw exc;
                }
            }
            // ошибка задания времени (время разделено более одной точкой)
            else
            {
                CyclogramParsingException exc = new CyclogramParsingException("Ошибка преобразованя времени выполнения команды: " + timeStr);
                throw exc;
            }
        }
        /// <summary>
        /// Проверяем, что команда существует в списке доступных команд
        /// Если команда не находится в списке, вызывается исключение CyclogramParsingException
        /// Если команда в списке находится, переменной _curCommand присваиваются значения id, execFunction и testFunction
        /// Выставляется флаг cmdExists
        /// </summary>
        /// <param name="cmdStr">Название команды</param>
        private void tryParseCmdToken(string cmdStr)
        {
            Debug.Assert(_availableCommands != null, "Список доступных команд циклограммы пуст!");

            bool cmdExists = false;

            foreach (var cmd in _availableCommands)
            {
                if (cmd.Key == cmdStr)
                {
                    cmdExists = true;
                    _curCommand.Id = cmd.Value.Id;
                    _curCommand.ExecFunction = cmd.Value.ExecFunction;
                    _curCommand.TestFunction = cmd.Value.TestFunction;

                    break;
                }
            }
            if (!cmdExists)
            {
                CyclogramParsingException exc = new CyclogramParsingException("Команды " + cmdStr + " нет в списке поддерживаемых команд!");
                throw exc;
            }
        }
        /// <summary>
        /// загружаем файл циклограмм
        /// Даже в случае ошибки, команда добавляется в список команд, но выставляется признак наличия ошибки
        /// </summary>
        /// <param name="cycFName">Путь к файлу циклограмм</param>
        /// <param name="availableCommands">Список доступных команд</param>
        public void TryLoad(string cycFName, CyclogramCommands availableCommands)
        {
            Debug.Assert(availableCommands != null, "Список доступных команд циклограммы пуст!");
            string cycLine;
            int curLineNum = 0;

            _availableCommands = availableCommands;
            _wasError = false;
            if (!File.Exists(cycFName))
            {
                _wasError = true;
                CyclogramParsingException exc = new CyclogramParsingException("Файл " + cycFName + " не существует!");
                throw exc;
            }

            FileName = cycFName;
            commands.Clear();
            using (StreamReader sr = new StreamReader(cycFName))
            {
                // читам файл по строкам
                while (sr.Peek() >= 0)
                {
                    cycLine = sr.ReadLine();
                    try
                    {
                        TryParseString(cycLine);
                    }
                    catch (CyclogramParsingException e)
                    {
                        _wasError = true;
                        _curCommand.ErrorInCommand += e.Message + "\t";
                    }
                    catch
                    {
                        _wasError = true;
                        _curCommand.ErrorInCommand += " Общая ошибка проверки команды.";
                    }
                    finally
                    {
                        _curCommand.Line = curLineNum;
                        commands.Add(_curCommand);

                        // начинаем "собирать" новую команду
                        _curCommand = new CyclogramLine();
                    }
                    curLineNum++;
                }
            }
        }
        /// <summary>
        /// Функция парсинга строки
        /// Работаем на исключениях, в вызывающей функции, необходимо проверять исключения от этой функции
        /// </summary>
        /// <param name="cycStr">Строка циклограммы</param>
        public void TryParseString(string cycStr)
        {
            _curCommand.Reset();
            // убираем лишнее пробелы из строки
            cycStr.Trim();
            // сохраняем на случай, если эта строка - только комментарий
            _curCommand.Str = cycStr;
            // убираем комментарии, сохраняя их в специальной переменной
            _curCommand.Comments += TakeComments(ref cycStr) + "\t";
            // вся строка - комментарий, выходим, нам здесь больше делать нечего
            if (cycStr == String.Empty) return;

            // кроме комментариев есть еще что-то, пытаемся понять, что, но в любом случае. считаем что должна быть команда
            _curCommand.IsCommand = true;
            // разделяем строку по словам (разделитель - пробел)
            string[] strTokens = cycStr.Split(default(string[]), StringSplitOptions.RemoveEmptyEntries);
            // если нашли только одно слово, то генерим исключение об ошибке
            if (strTokens.Length <= 1)
            {
                CyclogramParsingException exc = new CyclogramParsingException("Ошибка в строке " + cycStr + ". Формат задания команд: ВРЕМЯ КОМАНДА ПАРАМЕТРЫ #КОММЕНТАРИИ");
                throw exc;
            }
            // пытаемся разобрать время
            tryParseTimeToken(strTokens[0]);
            // пытаемся разобрать команду
            tryParseCmdToken(strTokens[1]);
            // составляем строку команды для отображения в циклограмме (выкидываем из исходной строки время, так как оно у нас в отдельном столбце)
            _curCommand.Str = strTokens[1];
            // копируем параметры, если они есть в массив параметров команды
            if (strTokens.Length - 2 > 0)
            {
                _curCommand.Parameters = new string[strTokens.Length - 2];
                System.Array.Copy(strTokens, 2, _curCommand.Parameters, 0, _curCommand.Parameters.Length);
                for (UInt16 i = 2; i < strTokens.Length; i++)
                {
                    _curCommand.Str += " " + strTokens[i];
                }
            }
            // добавляем комментарии к строке, если они есть
            _curCommand.Str += " " + _curCommand.Comments;
            // выполняем функцию тестирования параметров команды
            if (!_curCommand.RunTestFunction())
            {
                CyclogramParsingException exc = new CyclogramParsingException(); // "Ошибка при проверке команды " + _curCommand.CmdName);
                throw exc;
            }
        }
 /// <summary>
 /// Загружаем циклограмму
 /// В случае ошмибки загрузки циклограммы, генерируем исключение
 /// </summary>
 /// <param name="fName">Путь к файлу циклограмм</param>
 /// <param name="availableCommands">Список доступных команд</param>
 public void Load(string fName, CyclogramCommands availableCommands)
 {
     try
     {
         CycFile.TryLoad(fName, availableCommands);
         if (CycFile.WasError)
         {
             CyclogramParsingException exc = new CyclogramParsingException("Ошибки в циклограмме");
             throw exc;
         }
         else
         {
             ChangeState(CurState.csLoaded);
             CycFile.CalcAbsoluteTime();
             _cPos.SetToLine(0, true);
         }
     }
     catch
     {
         ChangeState(CurState.csLoadedWithErrors);
         // throw;
     }
 }