Exemple #1
0
        /// <summary>
        /// Calculate the channel numbers and write them to the list of information on KP
        /// </summary>
        public static bool CalcCnlNums(Dictionary <string, Type> kpViewTypes, List <KPInfo> kpInfoList,
                                       Scada.Comm.AppDirs commDirs, List <int> inCnlNums, CnlNumParams inCnlNumParams,
                                       List <int> ctrlCnlNums, CnlNumParams ctrlCnlNumParams, out string errMsg)
        {
            if (kpViewTypes == null)
            {
                throw new ArgumentNullException(nameof(kpViewTypes));
            }
            if (kpInfoList == null)
            {
                throw new ArgumentNullException(nameof(kpInfoList));
            }
            if (inCnlNums == null)
            {
                throw new ArgumentNullException(nameof(inCnlNums));
            }
            if (inCnlNumParams == null)
            {
                throw new ArgumentNullException(nameof(inCnlNumParams));
            }
            if (ctrlCnlNums == null)
            {
                throw new ArgumentNullException(nameof(ctrlCnlNums));
            }
            if (ctrlCnlNumParams == null)
            {
                throw new ArgumentNullException(nameof(ctrlCnlNumParams));
            }

            var hasChannels = false;
            var hasErrors   = false;

            errMsg = "";

            try {
                // loading SCADA-Communicator settings
                Scada.Comm.Settings commSett = new Scada.Comm.Settings();
                if (!commSett.Load(commDirs.ConfigDir + Scada.Comm.Settings.DefFileName, out errMsg))
                {
                    throw new Exception(errMsg);
                }

                // filling the directory of KP properties
                Dictionary <int, KPView.KPProperties> kpPropsDict = new Dictionary <int, KPView.KPProperties>();
                foreach (Scada.Comm.Settings.CommLine commLine in commSett.CommLines)
                {
                    foreach (Scada.Comm.Settings.KP kp in commLine.ReqSequence)
                    {
                        if (!kpPropsDict.ContainsKey(kp.Number))
                        {
                            kpPropsDict.Add(kp.Number, new KPView.KPProperties(commLine.CustomParams, kp.CmdLine));
                        }
                    }
                }

                // determining the starting number of the input channel
                int inCnlsStart    = inCnlNumParams.Start;
                int inCnlsMultiple = inCnlNumParams.Multiple;
                int inCnlsSpace    = inCnlNumParams.Space;
                int remainder      = inCnlsStart % inCnlsMultiple;
                int curInCnlNum    = remainder > 0 ? inCnlsStart - remainder : inCnlsStart;
                curInCnlNum += inCnlNumParams.Shift;
                if (curInCnlNum < inCnlsStart)
                {
                    curInCnlNum += inCnlsMultiple;
                }

                // determination of the control channel start number
                int ctrlCnlsStart    = ctrlCnlNumParams.Start;
                int ctrlCnlsMultiple = ctrlCnlNumParams.Multiple;
                int ctrlCnlsSpace    = ctrlCnlNumParams.Space;
                remainder = ctrlCnlsStart % ctrlCnlNumParams.Multiple;
                int curCtrlCnlNum = remainder > 0 ? ctrlCnlsStart - remainder : ctrlCnlsStart;
                curCtrlCnlNum += ctrlCnlNumParams.Shift;
                if (curCtrlCnlNum < ctrlCnlNumParams.Start)
                {
                    curCtrlCnlNum += ctrlCnlNumParams.Multiple;
                }

                // calculation channel numbers KP
                var curInCnlInd    = 0;
                int inCnlNumsCnt   = inCnlNums.Count;
                var curCtrlCnlInd  = 0;
                int ctrlCnlNumsCnt = ctrlCnlNums.Count;

                foreach (var kpInfo in kpInfoList)
                {
                    if (kpInfo.Selected)
                    {
                        // getting interface type kp
                        if (!kpViewTypes.TryGetValue(kpInfo.DllFileName, out Type kpViewType))
                        {
                            continue;
                        }

                        // instantiating a KP interface class
                        KPView kpView = null;
                        try {
                            kpView = KPFactory.GetKPView(kpViewType, kpInfo.KPNum);
                            KPView.KPProperties kpProps;
                            if (kpPropsDict.TryGetValue(kpInfo.KPNum, out kpProps))
                            {
                                kpView.KPProps = kpProps;
                            }
                            kpView.AppDirs = commDirs;
                        } catch {
                            kpInfo.SetInCnlNums(true);
                            kpInfo.SetCtrlCnlNums(true);
                            continue;
                        }

                        // default prototype channel acquisition
                        try {
                            kpInfo.DefaultCnls = kpView.DefaultCnls;
                        } catch {
                            kpInfo.SetInCnlNums(true);
                            kpInfo.SetCtrlCnlNums(true);
                            continue;
                        }

                        // definition of numbers of input channels, taking into account the numbers occupied by existing channels
                        if (kpInfo.DefaultCnls != null && kpInfo.DefaultCnls.InCnls.Count > 0)
                        {
                            hasChannels = true;

                            int firstInCnlNum; // first input channel number
                            int lastInCnlNum;  // number of last input channel KP
                            int newInCnlInd;   // new index of the number of input channels
                            CalcFirstAndLastNums(curInCnlNum, curInCnlInd, inCnlNums, inCnlNumsCnt,
                                                 kpInfo.DefaultCnls.InCnls.Count, inCnlsSpace, inCnlsMultiple,
                                                 out firstInCnlNum, out lastInCnlNum, out newInCnlInd);

                            if (lastInCnlNum > ushort.MaxValue)
                            {
                                hasErrors = true;
                                kpInfo.SetInCnlNums(true);
                            }
                            else
                            {
                                kpInfo.SetInCnlNums(false, firstInCnlNum, lastInCnlNum);

                                curInCnlInd = newInCnlInd;
                                curInCnlNum = firstInCnlNum;
                                do
                                {
                                    curInCnlNum += inCnlsMultiple;
                                } while (curInCnlNum - lastInCnlNum <= inCnlsSpace);
                            }
                        }
                        else
                        {
                            // channel numbers are not assigned because KP does not support input channel creation
                            kpInfo.SetInCnlNums(false);
                        }

                        // identification of control channel numbers with regard to numbers occupied by existing channels
                        if (kpInfo.DefaultCnls != null && kpInfo.DefaultCnls.CtrlCnls.Count > 0)
                        {
                            hasChannels = true;

                            int firstCtrlCnlNum; // first control channel number
                            int lastCtrlCnlNum;  // control channel last control channel number
                            int newCtrlCnlInd;   // new index of the list of control channel numbers
                            CalcFirstAndLastNums(curCtrlCnlNum, curCtrlCnlInd, ctrlCnlNums, ctrlCnlNumsCnt,
                                                 kpInfo.DefaultCnls.CtrlCnls.Count, ctrlCnlsSpace, ctrlCnlsMultiple,
                                                 out firstCtrlCnlNum, out lastCtrlCnlNum, out newCtrlCnlInd);

                            if (lastCtrlCnlNum > ushort.MaxValue)
                            {
                                hasErrors = true;
                                kpInfo.SetCtrlCnlNums(true);
                            }
                            else
                            {
                                kpInfo.SetCtrlCnlNums(false, firstCtrlCnlNum, lastCtrlCnlNum);

                                curCtrlCnlInd = newCtrlCnlInd;
                                curCtrlCnlNum = firstCtrlCnlNum;
                                do
                                {
                                    curCtrlCnlNum += ctrlCnlsMultiple;
                                } while (curCtrlCnlNum - lastCtrlCnlNum <= ctrlCnlsSpace);
                            }
                        }
                        else
                        {
                            // channel numbers are not assigned because KP does not support the creation of control channels
                            kpInfo.SetCtrlCnlNums(false);
                        }
                    }
                    else
                    {
                        // channel numbers are not assigned because KP is not selected
                        kpInfo.SetInCnlNums(false, -1, -1);
                        kpInfo.SetCtrlCnlNums(false, -1, -1);
                    }
                }

                if (hasErrors)
                {
                    errMsg = AppPhrases.CalcCnlNumsErrors;
                }
                else if (!hasChannels)
                {
                    errMsg = AppPhrases.CreatedCnlsMissing;
                }
            } catch (Exception ex) {
                hasErrors = true;
                errMsg    = AppPhrases.CalcCnlNumsError + ":\r\n" + ex.Message;
            }

            return(hasChannels && !hasErrors);
        }
Exemple #2
0
        /// <summary>
        /// Расчитать номера каналов и записать их в список информации о КП
        /// </summary>
        public static bool CalcCnlNums(Dictionary <string, Type> kpViewTypes, List <KPInfo> kpInfoList,
                                       Scada.Comm.AppDirs commDirs, List <int> inCnlNums, CnlNumParams inCnlNumParams,
                                       List <int> ctrlCnlNums, CnlNumParams ctrlCnlNumParams, out string errMsg)
        {
            if (kpViewTypes == null)
            {
                throw new ArgumentNullException("kpViewTypes");
            }
            if (kpInfoList == null)
            {
                throw new ArgumentNullException("kpInfoList");
            }
            if (inCnlNums == null)
            {
                throw new ArgumentNullException("inCnlNums");
            }
            if (inCnlNumParams == null)
            {
                throw new ArgumentNullException("inCnlNumParams");
            }
            if (ctrlCnlNums == null)
            {
                throw new ArgumentNullException("ctrlCnlNums");
            }
            if (ctrlCnlNumParams == null)
            {
                throw new ArgumentNullException("ctrlCnlNumParams");
            }

            bool hasChannels = false;
            bool hasErrors   = false;

            errMsg = "";

            try
            {
                // загрузка настроек SCADA-Коммуникатора
                Scada.Comm.Settings commSett = new Scada.Comm.Settings();
                if (!commSett.Load(commDirs.ConfigDir + Scada.Comm.Settings.DefFileName, out errMsg))
                {
                    throw new Exception(errMsg);
                }

                // заполнение справочника свойств КП
                Dictionary <int, KPView.KPProperties> kpPropsDict = new Dictionary <int, KPView.KPProperties>();
                foreach (Scada.Comm.Settings.CommLine commLine in commSett.CommLines)
                {
                    foreach (Scada.Comm.Settings.KP kp in commLine.ReqSequence)
                    {
                        if (!kpPropsDict.ContainsKey(kp.Number))
                        {
                            kpPropsDict.Add(kp.Number, new KPView.KPProperties(commLine.CustomParams, kp.CmdLine));
                        }
                    }
                }

                // определение стартового номера входного канала
                int inCnlsStart    = inCnlNumParams.Start;
                int inCnlsMultiple = inCnlNumParams.Multiple;
                int inCnlsSpace    = inCnlNumParams.Space;
                int remainder      = inCnlsStart % inCnlsMultiple;
                int curInCnlNum    = remainder > 0 ? inCnlsStart - remainder : inCnlsStart;
                curInCnlNum += inCnlNumParams.Shift;
                if (curInCnlNum < inCnlsStart)
                {
                    curInCnlNum += inCnlsMultiple;
                }

                // определение стартового номера канала управления
                int ctrlCnlsStart    = ctrlCnlNumParams.Start;
                int ctrlCnlsMultiple = ctrlCnlNumParams.Multiple;
                int ctrlCnlsSpace    = ctrlCnlNumParams.Space;
                remainder = ctrlCnlsStart % ctrlCnlNumParams.Multiple;
                int curCtrlCnlNum = remainder > 0 ? ctrlCnlsStart - remainder : ctrlCnlsStart;
                curCtrlCnlNum += ctrlCnlNumParams.Shift;
                if (curCtrlCnlNum < ctrlCnlNumParams.Start)
                {
                    curCtrlCnlNum += ctrlCnlNumParams.Multiple;
                }

                // расчёт номеров каналов КП
                int curInCnlInd    = 0;
                int inCnlNumsCnt   = inCnlNums.Count;
                int curCtrlCnlInd  = 0;
                int ctrlCnlNumsCnt = ctrlCnlNums.Count;

                foreach (KPInfo kpInfo in kpInfoList)
                {
                    if (kpInfo.Selected)
                    {
                        // получение типа интерфейса КП
                        Type kpViewType;
                        if (!kpViewTypes.TryGetValue(kpInfo.DllFileName, out kpViewType))
                        {
                            continue;
                        }

                        // создание экземпляра класса интерфейса КП
                        KPView kpView = null;
                        try
                        {
                            kpView = KPFactory.GetKPView(kpViewType, kpInfo.KPNum);
                            KPView.KPProperties kpProps;
                            if (kpPropsDict.TryGetValue(kpInfo.KPNum, out kpProps))
                            {
                                kpView.KPProps = kpProps;
                            }
                            kpView.AppDirs = commDirs;
                        }
                        catch
                        {
                            kpInfo.SetInCnlNums(true);
                            kpInfo.SetCtrlCnlNums(true);
                            continue;
                        }

                        // получение прототипов каналов КП по умолчанию
                        try
                        {
                            kpInfo.DefaultCnls = kpView.DefaultCnls;
                        }
                        catch
                        {
                            kpInfo.SetInCnlNums(true);
                            kpInfo.SetCtrlCnlNums(true);
                            continue;
                        }

                        // определение номеров входных каналов с учётом занятых существующими каналами номеров
                        if (kpInfo.DefaultCnls != null && kpInfo.DefaultCnls.InCnls.Count > 0)
                        {
                            hasChannels = true;

                            int firstInCnlNum; // номер первого входного канала КП
                            int lastInCnlNum;  // номер последнего входного канала КП
                            int newInCnlInd;   // новый индекс списка номеров входных каналов
                            CalcFirstAndLastNums(curInCnlNum, curInCnlInd, inCnlNums, inCnlNumsCnt,
                                                 kpInfo.DefaultCnls.InCnls.Count, inCnlsSpace, inCnlsMultiple,
                                                 out firstInCnlNum, out lastInCnlNum, out newInCnlInd);

                            if (lastInCnlNum > ushort.MaxValue)
                            {
                                hasErrors = true;
                                kpInfo.SetInCnlNums(true);
                            }
                            else
                            {
                                kpInfo.SetInCnlNums(false, firstInCnlNum, lastInCnlNum);

                                curInCnlInd = newInCnlInd;
                                curInCnlNum = firstInCnlNum;
                                do
                                {
                                    curInCnlNum += inCnlsMultiple;
                                }while (curInCnlNum - lastInCnlNum <= inCnlsSpace);
                            }
                        }
                        else
                        {
                            // номера каналов не назначаются, т.к. КП не поддерживает создание входных каналов
                            kpInfo.SetInCnlNums(false);
                        }

                        // определение номеров каналов управления с учётом занятых существующими каналами номеров
                        if (kpInfo.DefaultCnls != null && kpInfo.DefaultCnls.CtrlCnls.Count > 0)
                        {
                            hasChannels = true;

                            int firstCtrlCnlNum; // номер первого канала управления КП
                            int lastCtrlCnlNum;  // номер последнего канала управления КП
                            int newCtrlCnlInd;   // новый индекс списка номеров каналов управления
                            CalcFirstAndLastNums(curCtrlCnlNum, curCtrlCnlInd, ctrlCnlNums, ctrlCnlNumsCnt,
                                                 kpInfo.DefaultCnls.CtrlCnls.Count, ctrlCnlsSpace, ctrlCnlsMultiple,
                                                 out firstCtrlCnlNum, out lastCtrlCnlNum, out newCtrlCnlInd);

                            if (lastCtrlCnlNum > ushort.MaxValue)
                            {
                                hasErrors = true;
                                kpInfo.SetCtrlCnlNums(true);
                            }
                            else
                            {
                                kpInfo.SetCtrlCnlNums(false, firstCtrlCnlNum, lastCtrlCnlNum);

                                curCtrlCnlInd = newCtrlCnlInd;
                                curCtrlCnlNum = firstCtrlCnlNum;
                                do
                                {
                                    curCtrlCnlNum += ctrlCnlsMultiple;
                                }while (curCtrlCnlNum - lastCtrlCnlNum <= ctrlCnlsSpace);
                            }
                        }
                        else
                        {
                            // номера каналов не назначаются, т.к. КП не поддерживает создание каналов управления
                            kpInfo.SetCtrlCnlNums(false);
                        }
                    }
                    else
                    {
                        // номера каналов не назначаются, т.к. КП не выбран
                        kpInfo.SetInCnlNums(false, -1, -1);
                        kpInfo.SetCtrlCnlNums(false, -1, -1);
                    }
                }

                if (hasErrors)
                {
                    errMsg = AppPhrases.CalcCnlNumsErrors;
                }
                else if (!hasChannels)
                {
                    errMsg = AppPhrases.CreatedCnlsMissing;
                }
            }
            catch (Exception ex)
            {
                hasErrors = true;
                errMsg    = AppPhrases.CalcCnlNumsError + ":\r\n" + ex.Message;
            }

            return(hasChannels && !hasErrors);
        }
Exemple #3
0
        /// <summary>
        /// Расчитать номера каналов и записать их в список информации о КП
        /// </summary>
        public static bool CalcCnlNums(Dictionary<string, Type> kpViewTypes, List<KPInfo> kpInfoList, 
            Scada.Comm.AppDirs commDirs, List<int> inCnlNums, CnlNumParams inCnlNumParams, 
            List<int> ctrlCnlNums, CnlNumParams ctrlCnlNumParams, out string errMsg)
        {
            if (kpViewTypes == null)
                throw new ArgumentNullException("kpViewTypes");
            if (kpInfoList == null)
                throw new ArgumentNullException("kpInfoList");
            if (inCnlNums == null)
                throw new ArgumentNullException("inCnlNums");
            if (inCnlNumParams == null)
                throw new ArgumentNullException("inCnlNumParams");
            if (ctrlCnlNums == null)
                throw new ArgumentNullException("ctrlCnlNums");
            if (ctrlCnlNumParams == null)
                throw new ArgumentNullException("ctrlCnlNumParams");

            bool hasChannels = false;
            bool hasErrors = false;
            errMsg = "";

            try
            {
                // загрузка настроек SCADA-Коммуникатора
                Scada.Comm.Settings commSett = new Scada.Comm.Settings();
                if (!commSett.Load(commDirs.ConfigDir + Scada.Comm.Settings.DefFileName, out errMsg))
                    throw new Exception(errMsg);

                // заполнение справочника свойств КП
                Dictionary<int, KPView.KPProperties> kpPropsDict = new Dictionary<int, KPView.KPProperties>();
                foreach (Scada.Comm.Settings.CommLine commLine in commSett.CommLines)
                {
                    foreach (Scada.Comm.Settings.KP kp in commLine.ReqSequence)
                    {
                        if (!kpPropsDict.ContainsKey(kp.Number))
                            kpPropsDict.Add(kp.Number, new KPView.KPProperties(commLine.CustomParams, kp.CmdLine));
                    }
                }

                // определение стартового номера входного канала
                int inCnlsStart = inCnlNumParams.Start;
                int inCnlsMultiple = inCnlNumParams.Multiple;
                int inCnlsSpace = inCnlNumParams.Space;
                int remainder = inCnlsStart % inCnlsMultiple;
                int curInCnlNum = remainder > 0 ? inCnlsStart - remainder : inCnlsStart;
                curInCnlNum += inCnlNumParams.Shift;
                if (curInCnlNum < inCnlsStart)
                    curInCnlNum += inCnlsMultiple;

                // определение стартового номера канала управления
                int ctrlCnlsStart = ctrlCnlNumParams.Start;
                int ctrlCnlsMultiple = ctrlCnlNumParams.Multiple;
                int ctrlCnlsSpace = ctrlCnlNumParams.Space;
                remainder = ctrlCnlsStart % ctrlCnlNumParams.Multiple;
                int curCtrlCnlNum = remainder > 0 ? ctrlCnlsStart - remainder : ctrlCnlsStart;
                curCtrlCnlNum += ctrlCnlNumParams.Shift;
                if (curCtrlCnlNum < ctrlCnlNumParams.Start)
                    curCtrlCnlNum += ctrlCnlNumParams.Multiple;

                // расчёт номеров каналов КП
                int curInCnlInd = 0;
                int inCnlNumsCnt = inCnlNums.Count;
                int curCtrlCnlInd = 0;
                int ctrlCnlNumsCnt = ctrlCnlNums.Count;

                foreach (KPInfo kpInfo in kpInfoList)
                {
                    if (kpInfo.Selected)
                    {
                        // получение типа интерфейса КП
                        Type kpViewType;
                        if (!kpViewTypes.TryGetValue(kpInfo.DllFileName, out kpViewType))
                            continue;

                        // создание экземпляра класса интерфейса КП
                        KPView kpView = null;
                        try
                        {
                            kpView = KPFactory.GetKPView(kpViewType, kpInfo.KPNum);
                            KPView.KPProperties kpProps;
                            if (kpPropsDict.TryGetValue(kpInfo.KPNum, out kpProps))
                                kpView.KPProps = kpProps;
                            kpView.AppDirs = commDirs;
                        }
                        catch
                        {
                            kpInfo.SetInCnlNums(true);
                            kpInfo.SetCtrlCnlNums(true);
                            continue;
                        }

                        // получение прототипов каналов КП по умолчанию
                        try
                        {
                            kpInfo.DefaultCnls = kpView.DefaultCnls;
                        }
                        catch
                        {
                            kpInfo.SetInCnlNums(true);
                            kpInfo.SetCtrlCnlNums(true);
                            continue;
                        }

                        // определение номеров входных каналов с учётом занятых существующими каналами номеров
                        if (kpInfo.DefaultCnls != null && kpInfo.DefaultCnls.InCnls.Count > 0)
                        {
                            hasChannels = true;

                            int firstInCnlNum; // номер первого входного канала КП
                            int lastInCnlNum;  // номер последнего входного канала КП
                            int newInCnlInd;   // новый индекс списка номеров входных каналов
                            CalcFirstAndLastNums(curInCnlNum, curInCnlInd, inCnlNums, inCnlNumsCnt,
                                kpInfo.DefaultCnls.InCnls.Count, inCnlsSpace, inCnlsMultiple,
                                out firstInCnlNum, out lastInCnlNum, out newInCnlInd);

                            if (lastInCnlNum > ushort.MaxValue)
                            {
                                hasErrors = true;
                                kpInfo.SetInCnlNums(true);
                            }
                            else
                            {
                                kpInfo.SetInCnlNums(false, firstInCnlNum, lastInCnlNum);

                                curInCnlInd = newInCnlInd;
                                curInCnlNum = firstInCnlNum;
                                do { curInCnlNum += inCnlsMultiple; }
                                while (curInCnlNum - lastInCnlNum <= inCnlsSpace);
                            }
                        }
                        else
                        {
                            // номера каналов не назначаются, т.к. КП не поддерживает создание входных каналов
                            kpInfo.SetInCnlNums(false);
                        }

                        // определение номеров каналов управления с учётом занятых существующими каналами номеров
                        if (kpInfo.DefaultCnls != null && kpInfo.DefaultCnls.CtrlCnls.Count > 0)
                        {
                            hasChannels = true;

                            int firstCtrlCnlNum; // номер первого канала управления КП
                            int lastCtrlCnlNum;  // номер последнего канала управления КП
                            int newCtrlCnlInd;   // новый индекс списка номеров каналов управления
                            CalcFirstAndLastNums(curCtrlCnlNum, curCtrlCnlInd, ctrlCnlNums, ctrlCnlNumsCnt,
                                kpInfo.DefaultCnls.CtrlCnls.Count, ctrlCnlsSpace, ctrlCnlsMultiple,
                                out firstCtrlCnlNum, out lastCtrlCnlNum, out newCtrlCnlInd);

                            if (lastCtrlCnlNum > ushort.MaxValue)
                            {
                                hasErrors = true;
                                kpInfo.SetCtrlCnlNums(true);
                            }
                            else
                            {
                                kpInfo.SetCtrlCnlNums(false, firstCtrlCnlNum, lastCtrlCnlNum);

                                curCtrlCnlInd = newCtrlCnlInd;
                                curCtrlCnlNum = firstCtrlCnlNum;
                                do { curCtrlCnlNum += ctrlCnlsMultiple; }
                                while (curCtrlCnlNum - lastCtrlCnlNum <= ctrlCnlsSpace);
                            }
                        }
                        else
                        {
                            // номера каналов не назначаются, т.к. КП не поддерживает создание каналов управления
                            kpInfo.SetCtrlCnlNums(false);
                        }
                    }
                    else
                    {
                        // номера каналов не назначаются, т.к. КП не выбран
                        kpInfo.SetInCnlNums(false, -1, -1);
                        kpInfo.SetCtrlCnlNums(false, -1, -1);
                    }
                }

                if (hasErrors)
                    errMsg = AppPhrases.CalcCnlNumsErrors;
                else if (!hasChannels)
                    errMsg = AppPhrases.CreatedCnlsMissing;
            }
            catch (Exception ex)
            {
                hasErrors = true;
                errMsg = AppPhrases.CalcCnlNumsError + ":\r\n" + ex.Message;
            }

            return hasChannels && !hasErrors;
        }