/// <summary> /// Add a configuration to the dictionary. This will create an AdcpSubsystemConfig based /// off the Subsystem and CEPO index given. It will then add it to the dictionary. It will /// then return the created AdcpSubsystemConfig. If the AdcpSubsystemConfig could not be /// created, null will be returned. /// </summary> /// <param name="ss">Subsystem to add a configuration.</param> /// <param name="cepoIndex">CEPO index.</param> /// <returns>AdcpSubsystemConfig created with the given settings or null if one could not be created.</returns> private AdcpSubsystemConfig AddConfig(Subsystem ss, int cepoIndex) { AdcpSubsystemConfig asConfig = null; // If a bad Subsystem is given, we cannot use it if (!ss.IsEmpty()) { // Determine how many of the given subsystem have been added to the dictionary // We need to generate SubsystemConfiguration index value. // SubsystemConfiguration index is based off the number of SubsystemConfigurations already // in the dictionary before this configuration is added. ushort ssCount = 0; foreach (AdcpSubsystemConfig configuration in SubsystemConfigDict.Values) { // If the subsystems are the same, then increment the value if (configuration.SubsystemConfig.SubSystem.Code == ss.Code) { ssCount++; } } // Create all the subsystem configurations and add to the dictionary SubsystemConfiguration ssConfig = new SubsystemConfiguration(ss, (byte)cepoIndex, (byte)ssCount); // SubsystemConfiguration with the Index of the SubsystemConfiguration asConfig = new AdcpSubsystemConfig(ssConfig); // AdcpSubsystemConfig with the Subsystem, SubsystemConfig and CEPO index if (!SubsystemConfigDict.ContainsKey(asConfig.ToString())) { SubsystemConfigDict.Add(asConfig.ToString(), asConfig); } } return(asConfig); }
/// <summary> /// Get the CEPO configuration character and index within the CEPO command. /// The configuration character is the Subsystem code for the configuration. It /// represents the system type. The Index represents where in teh CEPO command /// the configuration was located. This determines the ping order of the configurations. /// It also make the configuration unique for a SubsystemConfiguration for a Subsystem. /// Get the Subsystem using the serial number and subsystem code. /// /// SubsystemConfiguaration CommandSetup: Index of the Configuration within Subsystem. (Based off counting configurations for a subsystem) /// index: Location in CEPO for the Subsystem configuration. /// </summary> /// <param name="ssCode">Subsystem Code from the CEPO command.</param> /// <param name="cepoIndex">Location in the CEPO command of the Subsystem Code.</param> /// <param name="serial">Serial number for the ADCP.</param> /// <returns>Return the AdcpSubsystemConfig created or null if one could not be created.</returns> private AdcpSubsystemConfig AddConfig(byte ssCode, int cepoIndex, SerialNumber serial) { AdcpSubsystemConfig asConfig = null; // Get the Subsystem index from the serial number // If it cannot be found in the serial number, then it is // a bad Subsystem and we can not use the command. Subsystem ss = serial.GetSubsystem(ssCode); if (!ss.IsEmpty()) { // Determine how many of the given subsystem have been added to the dictionary // We need to generate SubsystemConfiguration index value. // SubsystemConfiguration index is based off the number of SubsystemConfigurations already // in the dictionary before this configuration is added. ushort ssCount = 0; foreach (AdcpSubsystemConfig configuration in SubsystemConfigDict.Values) { // If the subsystems are the same, then increment the value if (configuration.SubsystemConfig.SubSystem.Code == ssCode) { ssCount++; } } // Create all the subsystem configurations and add to the dictionary SubsystemConfiguration ssConfig = new SubsystemConfiguration(ss, (byte)cepoIndex, (byte)ssCount); // SubsystemConfiguration with the CEPO index and Index of the SubsystemConfiguration asConfig = new AdcpSubsystemConfig(ssConfig); // AdcpSubsystemConfig with the Subsystem, SubsystemConfig and CEPO index SubsystemConfigDict.Add(asConfig.ToString(), asConfig); // Add to the dictionary the configuration with the string as the key } return(asConfig); }
/// <summary> /// Add a given AdcpSubsystemConfig to the dictionary. This will determine /// how many of the given subsystem type have already been added to the dictionary /// and generate a new index value. It will then set the index value to the SubsysteConfiguration. /// It will then add the AdcpSubsystemConfig to the dictionary. /// </summary> /// <param name="asConfig">AdcpSubsystemConfig to add to the dictionary.</param> private void AddConfig(AdcpSubsystemConfig asConfig) { // Determine how many of the given subsystem have been added to the dictionary // We need to generate SubsystemConfiguration index value. // SubsystemConfiguration index is based off the number of SubsystemConfigurations already // in the dictionary before this configuration is added. ushort ssCount = 0; foreach (AdcpSubsystemConfig configuration in SubsystemConfigDict.Values) { // If the subsystems are the same, then increment the value if (configuration.SubsystemConfig.SubSystem.Code == asConfig.SubsystemConfig.SubSystem.Code) { ssCount++; } } // Set the new Configuration index asConfig.SubsystemConfig.SubsystemConfigIndex = (byte)ssCount; // Set the new CEPO index // This is the last index for the CEPO command asConfig.SubsystemConfig.CepoIndex = Convert.ToByte(SubsystemConfigDict.Values.Count); // Add it to the dictionary SubsystemConfigDict.Add(asConfig.ToString(), asConfig); }
/// <summary> /// Initialize the view model. /// </summary> /// <param name="config">AdcpSubsystemConfig for this view model.</param> /// <param name="configVM">Adcp Configuration view model.</param> public AdcpSubsystemConfigurationViewModel(AdcpSubsystemConfig config, AdcpConfigurationViewModel configVM) : base(string.Format("Subsystem Configuration {0}", config.ToString())) { ConfigKey = config.ToString(); ConfigVM = configVM; // Initialize values _events = IoC.Get <IEventAggregator>(); _pm = IoC.Get <PulseManager>(); // Get the predictor from the selected project subsystem configuration // Create the VM //Predictor = config.Predictor as AdcpPredictor; //Predictor = _pm.SelectedProject.Configuration.SubsystemConfigDict[ConfigKey].Predictor as AdcpPredictor; CalcPrediction(); RangeVM = new AdcpRangePlannerViewModel(PredictedProfileRange, PredictedBottomRange); // Update the properties with the latest values UpdateProperties(); // Remove configuration command RemoveCommand = ReactiveCommand.Create(); RemoveCommand.Subscribe(_ => OnRemoveCommand()); // Edit the configuration command EditCommand = ReactiveCommand.Create(); EditCommand.Subscribe(param => OnEditCommand(param)); }
/// <summary> /// Add the configuration and then move to the next display. /// </summary> private void OnNextCommand() { if (_SelectedSubsystem != null) { AdcpSubsystemConfig ssConfig = null; _pm.SelectedProject.Configuration.AddConfiguration(_SelectedSubsystem, out ssConfig); _pm.SelectedProject.Save(); _events.PublishOnUIThread(new ViewNavEvent(ViewNavEvent.ViewId.BroadbandModeView, ssConfig.ToString())); } }
/// <summary> /// Determine if the given object is equal to this /// object. This will check the Subsystem, SubsystemConfiguration and Index /// are all the same. /// </summary> /// <param name="obj">Object to compare with this object.</param> /// <returns>TRUE = AdcpSubsystemConfig are the same.</returns> public override bool Equals(object obj) { //Check for null and compare run-time types. if (obj == null || GetType() != obj.GetType()) { return(false); } AdcpSubsystemConfig p = (AdcpSubsystemConfig)obj; return(SubsystemConfig == p.SubsystemConfig); }
/// <summary> /// Remove the subsystem configuration from the project and /// from the UI. /// </summary> private void OnRemoveCommand() { // Ensure a project is selected and if the key exist in the dictionary if (_pm.IsProjectSelected && _pm.SelectedProject.Configuration.SubsystemConfigDict.ContainsKey(ConfigKey)) { AdcpSubsystemConfig ssConfig = _pm.SelectedProject.Configuration.SubsystemConfigDict[ConfigKey]; _pm.SelectedProject.Configuration.RemoveAdcpSubsystemConfig(ssConfig); ConfigVM.RemoveConfiguration(this); } }
/// <summary> /// Add a configuration. This will take a Subsystem as a parameter. /// It will then create a new configuration for the given Subsystem. /// It will update the CEPO command and it will add the new Configuration /// to the dictionary. /// This will add a specific configuration but only update the CEPO command /// if the /// </summary> /// <param name="ss">Subsystem for the configuration.</param> /// <param name="asConfig">Return the AdcpSubsystemConfig created.</param> /// <param name="cepoIndex">The CEPO Index to use.</param> /// <returns>TRUE = Configuration Added. / FALSE = Configuration could not be added.</returns> public bool AddConfiguration(Subsystem ss, out AdcpSubsystemConfig asConfig, int cepoIndex) { // Initialize the AdcpSubsystemConfig to null asConfig = null; // Generate a new CEPO //string cepo = CEPO + Convert.ToChar(ss.Code); //string cepo = Commands.CEPO + Convert.ToChar(ss.Code); string cepo = AddConfigToCepo(ss.Code, cepoIndex); // Validate the new CEPO // If it pass, then add the new configuration to the dictionary if (ValidateCEPO(cepo, SerialNumber)) { // Set the CEPO //CEPO = cepo; Commands.CEPO = cepo; // Get the CEPO index // The index will be the last character in the CEPO command // Subtract 1 because it is 0 based //int cepoIndex = Commands.CEPO.Length - 1; // Add the configuration to the dictionary // Set the AdcpSubsystemConfig to give to the user asConfig = AddConfig(ss, cepoIndex); return(true); } // If the data is DVL data, add the config if (SerialNumber == SerialNumber.DVL) { // Set the CEPO //CEPO = cepo; Commands.CEPO = cepo; // Get the CEPO index // The index will be the last character in the CEPO command // Subtract 1 because it is 0 based //int cepoIndex = Commands.CEPO.Length - 1; // Add the configuration to the dictionary // Set the AdcpSubsystemConfig to give to the user asConfig = AddConfig(ss, cepoIndex); return(true); } return(false); }
/// <summary> /// Update the serial number of the serial number generater updates the number. /// </summary> private void SerialNumberGeneratorVM_UpdateEvent() { AdcpSerialNumber = SerialNumberGeneratorVM.SerialNumber.ToString(); _pm.SelectedProject.SerialNumber = SerialNumberGeneratorVM.SerialNumber; AdcpSerialNumber += "\n"; AdcpSerialNumber += _pm.SelectedProject.SerialNumber.GetSerialNumberDescString(true); // Add a default configuration for the new subsystem added if (SerialNumberGeneratorVM.SerialNumber.SubSystemsList.Count > 0) { AdcpSubsystemConfig config = null; _pm.SelectedProject.Configuration.AddConfiguration(SerialNumberGeneratorVM.SerialNumber.SubSystemsList.Last(), out config); } }
public void TestConstructor() { Subsystem ss = new Subsystem(Subsystem.SUB_1_2MHZ_4BEAM_20DEG_PISTON_2, 0); // 1200kHz SubsystemConfiguration ssConfig = new SubsystemConfiguration(ss, 0, 0); // 0 Config int cepoIndex = 0; AdcpSubsystemConfig asConfig = new AdcpSubsystemConfig(ssConfig); string asConfigStr = AdcpSubsystemConfig.GetString(ssConfig); Assert.AreEqual(asConfig.SubsystemConfig.CepoIndex, cepoIndex, "CepoIndex is incorrect."); //Assert.AreEqual(asConfig.Commands.CepoIndex, cepoIndex, "Commands CEPO index is incorrect."); Assert.AreEqual(asConfig.SubsystemConfig.SubSystem, ss, "Subsystem is incorrect."); Assert.AreEqual(asConfig.SubsystemConfig, ssConfig, "SubsystemConfiguration is incorrect."); Assert.AreEqual(asConfig.ToString(), "[0] 1.2 MHz 4 beam 20 degree piston", "ToString is incorrect."); Assert.AreEqual(asConfigStr, "[0] 1.2 MHz 4 beam 20 degree piston", "GetString is incorrect."); }
/// <summary> /// Initialize the view model. /// </summary> /// <param name="config">Subsystem configuration.</param> /// <param name="dvlSetupVM">DVL Setup VM.</param> public DvlSubsystemConfigurationViewModel(ref AdcpSubsystemConfig config, DvlSetupViewModel dvlSetupVM) : base("DVL Subsystem Configuration") { // Initialize values _dvlSetupVM = dvlSetupVM; AdcpSubConfig = config; if (config != null) { Desc = config.ToString(); } Init(); // Add Subsystem RemoveSubsystemCommand = ReactiveCommand.Create(); RemoveSubsystemCommand.Subscribe(_ => RemoveSubsystem()); }
public void TestJSON1() { Subsystem ss = new Subsystem(Subsystem.SUB_1_2MHZ_VERT_PISTON_A, 0); // 1200kHz SubsystemConfiguration ssConfig = new SubsystemConfiguration(ss, 3, 3); // 3 Config int cepoIndex = 3; AdcpSubsystemConfig asConfig = new AdcpSubsystemConfig(ssConfig); string json = Newtonsoft.Json.JsonConvert.SerializeObject(asConfig); // Serialize object to JSON AdcpSubsystemConfig newConfig = Newtonsoft.Json.JsonConvert.DeserializeObject <AdcpSubsystemConfig>(json); // Deserialize the JSON Assert.AreEqual(asConfig.SubsystemConfig.CepoIndex, cepoIndex, "CepoIndex is incorrect."); Assert.AreEqual(asConfig.SubsystemConfig.SubSystem, ss, "Subsystem is incorrect."); Assert.AreEqual(asConfig.SubsystemConfig, ssConfig, "SubsystemConfiguration is incorrect."); Assert.AreEqual(asConfig.ToString(), "[3] 1.2 MHz vertical beam piston", "ToString is incorrect."); Assert.AreEqual(newConfig.SubsystemConfig.CepoIndex, cepoIndex, "CepoIndex is incorrect."); Assert.AreEqual(newConfig.SubsystemConfig.SubSystem, ss, "Subsystem is incorrect."); Assert.AreEqual(newConfig.SubsystemConfig, ssConfig, "SubsystemConfiguration is incorrect."); Assert.AreEqual(newConfig.ToString(), "[3] 1.2 MHz vertical beam piston", "ToString is incorrect."); }
/// <summary> /// Get the AdcpSubsystemConfig from the dictionary if it exist. If it does not /// exist in the dictionary, null will be returned. /// </summary> /// <param name="ssConfig">SubsystemConfiguration.</param> /// <returns>If the AdcpSubystemConfig is found, it will return the AdcpSubsystemConfig. If it is not found, it will return null.</returns> public AdcpSubsystemConfig GetAdcpSubsystemConfig(SubsystemConfiguration ssConfig) { // Check for null if (ssConfig == null) { return(null); } // Generate the key for the Subsystem and SubsystemConfiguration string key = AdcpSubsystemConfig.GetString(ssConfig); // If the key exist, return the object if (SubsystemConfigDict.ContainsKey(key)) { return(SubsystemConfigDict[key]); } // The key did not exist so return null return(null); }
/// <summary> /// Remove the AdcpSubsystemConfig from the dictionary. If it exist in the /// dictionary, the AdcpSubsystemConfigs in the dictionary have to reordered. /// If it did not exist in the dictionary, do nothing and return false. /// /// Create a temporary list of all the configurations in the order /// of the CEPO command. Then add the configurations back to the dictonary /// and create a new CEPO command. /// </summary> /// <param name="config">AdcpSubsystemConfig to remove.</param> /// <returns>TRUE = Config removed / FALSE = Config did not exist in the dictonary. Nothing done.</returns> public bool RemoveAdcpSubsystemConfig(AdcpSubsystemConfig config) { if (config != null) { // Remove the AdcpSubsystemConfig from the dict // If it is removed, all the remaining configurations needed // to be renumbered if (SubsystemConfigDict.Remove(config.ToString())) { // Remove all the configurations from the dictionary and put in an sorted list by CEPO index SortedList <int, AdcpSubsystemConfig> tempList = new SortedList <int, AdcpSubsystemConfig>(); //List<AdcpSubsystemConfig> tempList = new List<AdcpSubsystemConfig>(); foreach (AdcpSubsystemConfig asConfig in SubsystemConfigDict.Values) { tempList.Add(asConfig.SubsystemConfig.CepoIndex, asConfig); //tempList.Add(asConfig); } // Clear the dictionary and the CEPO command //CEPO = ""; Commands.CEPO = AdcpCommands.DEFAULT_CEPO; SubsystemConfigDict.Clear(); // Redo the CEPO command // and add the configuration back to the dictionary for (int x = 0; x < tempList.Count; x++) { // Redo the cepo value Commands.CEPO += Convert.ToChar(tempList.Values[x].SubsystemConfig.SubSystem.Code); // Add config to the dictionary AddConfig(tempList.Values[x]); } return(true); } } // Config was not in the list so the configuration was not reordered return(false); }
public void TestJSON() { Subsystem ss = new Subsystem(Subsystem.SUB_1_2MHZ_4BEAM_20DEG_PISTON_2, 0); // 1200kHz SubsystemConfiguration ssConfig = new SubsystemConfiguration(ss, 0, 0); // 0 Config int cepoIndex = 0; AdcpSubsystemConfig asConfig = new AdcpSubsystemConfig(ssConfig); string json = Newtonsoft.Json.JsonConvert.SerializeObject(asConfig); // Serialize object to JSON AdcpSubsystemConfig newConfig = Newtonsoft.Json.JsonConvert.DeserializeObject <AdcpSubsystemConfig>(json); // Deserialize the JSON Assert.AreEqual(asConfig.SubsystemConfig.CepoIndex, cepoIndex, "CepoIndex is incorrect."); //Assert.AreEqual(asConfig.Commands.CepoIndex, cepoIndex, "Commands CEPO index is incorrect."); Assert.AreEqual(asConfig.SubsystemConfig.SubSystem, ss, "Subsystem is incorrect."); Assert.AreEqual(asConfig.SubsystemConfig, ssConfig, "SubsystemConfiguration is incorrect."); Assert.AreEqual(asConfig.ToString(), "[0] 1.2 MHz 4 beam 20 degree piston", "ToString is incorrect."); Assert.AreEqual(newConfig.SubsystemConfig.CepoIndex, cepoIndex, "CepoIndex is incorrect."); //Assert.AreEqual(newConfig.Commands.CepoIndex, cepoIndex, "Commands CEPO index is incorrect."); Assert.AreEqual(newConfig.SubsystemConfig.SubSystem, ss, "Subsystem is incorrect."); Assert.AreEqual(newConfig.SubsystemConfig, ssConfig, "SubsystemConfiguration is incorrect."); Assert.AreEqual(newConfig.ToString(), "[0] 1.2 MHz 4 beam 20 degree piston", "ToString is incorrect."); }
/// <summary> /// Add a configuration to the canvas. Give the subsystem configuration and /// add it to the list of configurations. This will add the ping time and the /// CEI timing. /// </summary> /// <param name="ssConfig">Subsystem configuration.</param> /// <param name="numConfigs">Number of configurations.</param> /// <param name="CEI">CEI time in seconds.</param> /// <param name="pixelsPerSecond">Number of pixels per second.</param> /// <param name="startPixel">Start pixel.</param> /// <returns>Total time included CEI.</returns> private double AddConfigCanvas(AdcpSubsystemConfig ssConfig, int numConfigs, double CEI, double pixelsPerSecond, double startPixel) { // Configuration number to know the location to put the model // and to label the model int configNum = ssConfig.SubsystemConfig.CepoIndex; // Get the CWPP and CWPTBP time double cwppTime = ssConfig.Commands.CWPTBP * ssConfig.Commands.CWPP; // Time to ping // Calculate the time for a single ping based off the depth profiling and the 1.5ms per meter //double pingTime = 0.1; double pingTime = (1.5 / 1000) * (ssConfig.Commands.CWPBL + (ssConfig.Commands.CWPBS * ssConfig.Commands.CWPBN)); // 1.5ms per meter // Check if the CWPP and CWPTBP exceeds the CEI time if (ssConfig.Commands.CWPP > 1) { pingTime = cwppTime; // If more than one ping is in the ensemble } double ensembleLength = (pingTime * pixelsPerSecond); // CEI length or CBI Length double ceiLength = (CEI * pixelsPerSecond) - ensembleLength; if (ceiLength < 0) { ceiLength = 0; } // Calculate the ping time double ceiTimeLeft = CEI - pingTime; if (ceiTimeLeft < 0) { ceiTimeLeft = 0; // Set 0 as the smallest } // If Burst Mode is used, replace the values if (ssConfig.Commands.CBI_BurstInterval.ToSecondsD() > 0) { // Burst Pinging pingTime = ssConfig.Commands.CBI_NumEnsembles * CEI; ensembleLength = (pingTime * pixelsPerSecond); // Burst Length ceiLength = (ssConfig.Commands.CBI_BurstInterval.ToSecondsD() * pixelsPerSecond) - ensembleLength; // Time Remaining ceiTimeLeft = ssConfig.Commands.CBI_BurstInterval.ToSecondsD() - pingTime; } // Width // Based off the number of configurations double startWidth = startPixel; // Height // Start at top and go down half way double startHeight = (PingModelCanvas.Height / numConfigs) * configNum;; double endHeight = (PingModelCanvas.Height / numConfigs); // Ensemble double widthEnsStart = startWidth; double heightEnsStart = startHeight; double widthEnsEnd = widthEnsStart + ensembleLength; double heightEnsEnd = endHeight; // CEI double widthCeiStart = widthEnsEnd; double heightCeiStart = startHeight; double widthCeiEnd = widthCeiStart + ceiLength; double heightCeiEnd = endHeight; // Gradient LinearGradientBrush linGrBrush = new LinearGradientBrush(Color.FromArgb(255, 29, 29, 29), Color.FromArgb(255, 14, 14, 14), 90); // Add Ensemble System.Windows.Shapes.Rectangle rectEns; rectEns = new System.Windows.Shapes.Rectangle(); // Create a rectangle rectEns.Stroke = new SolidColorBrush(Colors.Black); // Border color rectEns.StrokeThickness = 10; rectEns.Fill = linGrBrush; rectEns.Width = ensembleLength; // Ensemble Length rectEns.Height = endHeight; // Ensemble Height Canvas.SetLeft(rectEns, widthEnsStart); // Location of the rectangle Width Canvas.SetTop(rectEns, heightEnsStart); // Location of the rectangle Height PingTimingCanvas.Children.Add(rectEns); // Add the rectangle to the canvas // Add CEI System.Windows.Shapes.Rectangle rectCEI; rectCEI = new System.Windows.Shapes.Rectangle(); // Create a rectangle rectCEI.Stroke = new SolidColorBrush(Colors.Gray); // Border color rectCEI.StrokeThickness = 10; // Thicker border rectCEI.Fill = new SolidColorBrush(Colors.Transparent); // No fill color rectCEI.Width = ceiLength; // CEI Length rectCEI.Height = endHeight; // CEI height Canvas.SetLeft(rectCEI, widthCeiStart); // Location of the rectangle Width Canvas.SetTop(rectCEI, heightCeiStart); // Location of the rectangle Height PingTimingCanvas.Children.Add(rectCEI); // Add the rectangle to the canvas // Add the Ensemble Length labels TextBlock textBlockEns = new TextBlock(); if (ssConfig.Commands.CBI_BurstInterval.ToSecondsD() <= 0 && pingTime > CEI) { textBlockEns.Foreground = Brushes.Red; // Give a warning that the ping time exceeds CEI } textBlockEns.Text = "[" + ssConfig.SubsystemConfig.CepoIndex + "] " + pingTime.ToString("0.000") + " sec"; textBlockEns.FontSize = 40; // Font size Canvas.SetLeft(textBlockEns, widthEnsStart + (ensembleLength / 2)); // Put in the middle of the box Width Canvas.SetTop(textBlockEns, heightEnsStart + (endHeight)); // Put in the middle of the box Height PingTimingCanvas.Children.Add(textBlockEns); // Add the text to the canvas // Add the CEI Length labels TextBlock textBlockCei = new TextBlock(); textBlockCei.Text = ceiTimeLeft.ToString("0.000") + " sec"; textBlockCei.FontSize = 40; // Font size Canvas.SetLeft(textBlockCei, widthCeiStart + (ceiLength / 2)); // Put in the middle of the box Width Canvas.SetTop(textBlockCei, heightCeiStart + (endHeight)); // Put in the middle of the box Height PingTimingCanvas.Children.Add(textBlockCei); // Add the text to the canvas // If burst mode, return the burst length if (ssConfig.Commands.CBI_BurstInterval.ToSecondsD() > 0) { return(ssConfig.Commands.CBI_BurstInterval.ToSecondsD()); } // IF the CWPP and CWPTBP exceeds CEI // Then return the larger time if (cwppTime > CEI) { return(cwppTime); } // Return CEI return(CEI); }