private async Task <ITopologyElement> CreateNoScadaMeasurementAsync(ITopologyElement element)
        {
            string verboseMessage = $"{baseLogString} entering CreateNoScadaMeasurement method.";

            Logger.LogVerbose(verboseMessage);

            DMSType dMSType = GetDMSTypeOfTopologyElement(element.Id);

            if (dMSType == DMSType.LOADBREAKSWITCH ||
                dMSType == DMSType.BREAKER ||
                dMSType == DMSType.FUSE ||
                dMSType == DMSType.DISCONNECTOR)
            {
                ArtificalDiscreteMeasurement measurement = GetNoScadaDiscreteMeasurement();
                element.Measurements.Add(measurement.Id, "SWITCH_STATUS");
                measurement.ElementId = element.Id;
                var measurementProviderClient = MeasurementProviderClient.CreateClient();
                await measurementProviderClient.AddDiscreteMeasurement(measurement);

                measurementProviderClient = MeasurementProviderClient.CreateClient();
                await measurementProviderClient.AddMeasurementElementPair(measurement.Id, element.Id);
            }

            return(element);
        }
예제 #2
0
        private async Task TurnOffAllMeasurement(Dictionary <long, string> measurements)
        {
            string verboseMessage = $"{baseLogString} TurnOffAllMeasurement method called => Measurements count: {measurements.Count}.";

            Logger.LogVerbose(verboseMessage);

            List <AnalogMeasurement> analogMeasurements = await GetMeasurements(measurements);

            var commands   = new Dictionary <long, float>();
            var modbusData = new Dictionary <long, AnalogModbusData>();

            foreach (var meas in analogMeasurements)
            {
                Logger.LogDebug($"{baseLogString} TurnOffAllMeasurement => Adding Command method from measurement provider. Measurement GID {meas.Id:X16}.");
                commands.Add(meas.Id, 0);
                modbusData.Add(meas.Id, new AnalogModbusData(0, AlarmType.NO_ALARM, meas.Id, CommandOriginType.CE_COMMAND));
            }

            var measurementProviderClient = MeasurementProviderClient.CreateClient();

            await measurementProviderClient.SendMultipleAnalogCommand(commands, CommandOriginType.CE_COMMAND);

            Logger.LogDebug($"{baseLogString} TurnOffAllMeasurement => SendAnalogCommand method from measurement provider has been called successfully.");

            Logger.LogDebug($"{baseLogString} TurnOffAllMeasurement => Calling UpdateAnalogMeasurement method from measurement provider.");
            await measurementProviderClient.UpdateAnalogMeasurement(modbusData);

            Logger.LogDebug($"{baseLogString} TurnOffAllMeasurement => UpdateAnalogMeasurement method from measurement provider has been called successfully.");
        }
예제 #3
0
        private async Task TurnOnAllMeasurement(Dictionary <long, string> measurements)
        {
            string verboseMessage = $"{baseLogString} TurnOnAllMeasurement method called. Measurements count: {measurements.Count}";

            Logger.LogVerbose(verboseMessage);

            List <AnalogMeasurement> analogMeasurements = await GetMeasurements(measurements);

            var commands   = new Dictionary <long, float>();
            var modbusData = new Dictionary <long, AnalogModbusData>();

            foreach (var meas in analogMeasurements)
            {
                commands.Add(meas.Id, meas.NormalValue);
                modbusData.Add(meas.Id, new AnalogModbusData(meas.NormalValue, AlarmType.NO_ALARM, meas.Id, CommandOriginType.CE_COMMAND));
            }

            var measurementProviderClient = MeasurementProviderClient.CreateClient();
            await measurementProviderClient.SendMultipleAnalogCommand(commands, CommandOriginType.CE_COMMAND);

            Logger.LogDebug($"{baseLogString} TurnOnAllMeasurement => Calling update analog measurement method from measurement provider.");
            await measurementProviderClient.UpdateAnalogMeasurement(modbusData);

            Logger.LogDebug($"{baseLogString} TurnOnAllMeasurement => Update analog measurement method from measurement provider successfully finished.");
        }
예제 #4
0
        private async Task <List <AnalogMeasurement> > GetMeasurements(Dictionary <long, string> measurements)
        {
            string verboseMessage = $"{baseLogString} GetMeasurements method called.";

            Logger.LogVerbose(verboseMessage);

            List <AnalogMeasurement> analogMeasurements = new List <AnalogMeasurement>();

            foreach (var measurement in measurements)
            {
                DMSType type = (DMSType)ModelCodeHelper.ExtractTypeFromGlobalId(measurement.Key);

                if (type == DMSType.ANALOG)
                {
                    Logger.LogDebug($"{baseLogString} GetMeasurements => Calling GetAnalogMeasurement for GID {measurement.Key:X16} from measurement provider.");
                    var measurementProviderClient       = MeasurementProviderClient.CreateClient();
                    AnalogMeasurement analogMeasurement = await measurementProviderClient.GetAnalogMeasurement(measurement.Key);

                    Logger.LogDebug($"{baseLogString} GetMeasurements => GetAnalogMeasurement method from measurement provider has been called successfully.");

                    if (analogMeasurement == null)
                    {
                        string message = $"{baseLogString} GetMeasurements => GetAnalogMeasurement from measurement provider returned null for measurement GID {measurement.Key:X16}";
                        Logger.LogWarning(message);
                        continue;
                    }

                    if (measurement.Value.Equals(AnalogMeasurementType.POWER.ToString()))
                    {
                        analogMeasurements.Add(analogMeasurement);
                    }
                    else if (measurement.Value.Equals(AnalogMeasurementType.VOLTAGE.ToString()))
                    {
                        analogMeasurements.Add(analogMeasurement);
                    }
                    else if (measurement.Value.Equals(AnalogMeasurementType.CURRENT.ToString()))
                    {
                        analogMeasurements.Add(analogMeasurement);
                    }
                    else if (measurement.Value.Equals(AnalogMeasurementType.FEEDER_CURRENT.ToString()))
                    {
                        analogMeasurements.Add(analogMeasurement);
                    }
                    else
                    {
                        Logger.LogWarning($"{baseLogString} GetMeasurements => Unknown type [{measurement.Value}] of measurement with GID {measurement.Key:X16}.");
                    }
                }
            }

            return(analogMeasurements);
        }
        public async Task <bool> SendMultipleScadaCommandAsync(Dictionary <long, DiscreteCommandingType> elementGidCommandMap, Dictionary <long, CommandedElement> commandedElements, CommandOriginType commandOriginType)
        {
            var measurementMapClient    = MeasurementMapClient.CreateClient();
            var elementToMeasurementMap = await measurementMapClient.GetElementToMeasurementMap();

            var commands = new Dictionary <long, int>();

            foreach (var elementGid in elementGidCommandMap.Keys)
            {
                var discreteCommandingType = elementGidCommandMap[elementGid];

                int reTryCount = 30;
                while (commandedElements.ContainsKey(elementGid) && commandedElements[elementGid].CommandingType == discreteCommandingType)
                {
                    Logger.LogDebug($"{baseLogString} SendMultipleScadaCommandAsync => Trying to send duplicate command. Entering delay for 1000 ms and retrying the call.");

                    await Task.Delay(1000);

                    if (--reTryCount <= 0)
                    {
                        Logger.LogError($"{baseLogString} SendMultipleScadaCommandAsync => Trying to send duplicate command. ReTryCount reached 60 calls.");
                        return(false);
                    }
                }

                if (!elementToMeasurementMap.TryGetValue(elementGid, out List <long> measurementGids) || measurementGids.Count == 0)
                {
                    Logger.LogWarning($"{baseLogString} SendMultipleScadaCommandAsync => Element with gid: 0x{elementGid:X16} has no measurements.");
                    return(false);
                }

                var measurementGid = measurementGids.FirstOrDefault();
                if (measurementGid == 0)
                {
                    Logger.LogWarning($"{baseLogString} SendMultipleScadaCommandAsync => Measurement gid is 0.");
                    return(false);
                }

                commands.Add(measurementGid, (int)elementGidCommandMap[elementGid]);
            }

            var measurementProviderClient = MeasurementProviderClient.CreateClient();

            return(await measurementProviderClient.SendMultipleDiscreteCommand(commands, commandOriginType));
        }
예제 #6
0
        public async Task Notify(IPublishableMessage message, string publisherName)
        {
            Logger.LogDebug($"{baseLogString} Notify method invoked.");

            if (message is SingleAnalogValueSCADAMessage singleAnalog)
            {
                Dictionary <long, AnalogModbusData> data = new Dictionary <long, AnalogModbusData>(1)
                {
                    { singleAnalog.AnalogModbusData.MeasurementGid, singleAnalog.AnalogModbusData },
                };

                Logger.LogDebug($"{baseLogString} Calling Update analog measurement from measurement provider.");
                var measurementProviderClient = MeasurementProviderClient.CreateClient();
                await measurementProviderClient.UpdateAnalogMeasurement(data);
            }
            else if (message is MultipleAnalogValueSCADAMessage multipleAnalog)
            {
                Logger.LogDebug($"{baseLogString} Calling Update analog measurement from measurement provider.");
                var measurementProviderClient = MeasurementProviderClient.CreateClient();
                await measurementProviderClient.UpdateAnalogMeasurement(multipleAnalog.Data);
            }
            else if (message is SingleDiscreteValueSCADAMessage singleDiscrete)
            {
                Dictionary <long, DiscreteModbusData> data = new Dictionary <long, DiscreteModbusData>(1)
                {
                    { singleDiscrete.DiscreteModbusData.MeasurementGid, singleDiscrete.DiscreteModbusData },
                };

                Logger.LogDebug($"{baseLogString} Calling Update discrete measurement from measurement provider.");
                var measurementProviderClient = MeasurementProviderClient.CreateClient();
                await measurementProviderClient.UpdateDiscreteMeasurement(data);
            }
            else if (message is MultipleDiscreteValueSCADAMessage multipleDiscrete)
            {
                Logger.LogDebug($"{baseLogString} Calling Update discrete measurement from measurement provider.");
                var measurementProviderClient = MeasurementProviderClient.CreateClient();
                await measurementProviderClient.UpdateDiscreteMeasurement(multipleDiscrete.Data);
            }
            else
            {
                Logger.LogError($"{baseLogString} ERROR Message has unsupported type [{message.GetType().ToString()}].");
            }
        }
        public async Task <Dictionary <long, long> > GetMeasurementToElementMap()
        {
            string verboseMessage = $"{baseLogString} entering GetMeasurementToElementMap method.";

            Logger.LogVerbose(verboseMessage);

            try
            {
                var measurementProviderClient = MeasurementProviderClient.CreateClient();
                return(await measurementProviderClient.GetMeasurementToElementMap());
            }
            catch (Exception e)
            {
                string message = $"{baseLogString} GetMeasurementToElementMap => " +
                                 $"Failed to get measurement to element map from measurement provider client." +
                                 $"{Environment.NewLine} Exception message: {e.Message}";
                Logger.LogError(message);
                throw;
            }
        }
        public async Task <List <long> > GetMeasurementsOfElement(long elementId)
        {
            string verboseMessage = $"{baseLogString} entering GetMeasurementsOfElement method.";

            Logger.LogVerbose(verboseMessage);

            try
            {
                var measurementProviderClient = MeasurementProviderClient.CreateClient();
                return(await measurementProviderClient.GetMeasurementsOfElement(elementId));
            }
            catch (Exception e)
            {
                string message = $"{baseLogString} GetMeasurementsOfElement => " +
                                 $"Failed to get measurements of element with GID {elementId:X16} from measurement provider client." +
                                 $"{Environment.NewLine} Exception message: {e.Message}";
                Logger.LogError(message);
                throw;
            }
        }
예제 #9
0
        private async Task CommandToRecloser(long measurementGid, int value, CommandOriginType originType, ITopologyElement recloser)
        {
            string verboseMessage = $"{baseLogString} CommandToRecloser method called.";

            Logger.LogVerbose(verboseMessage);

            if (recloser == null)
            {
                string message = $"{baseLogString} CommandToRecloser => NULL value has been passed instead of element.";
                Logger.LogError(message);
                throw new Exception(message);
            }

            if (!(recloser is Recloser))
            {
                string message = $"{baseLogString} CommandToRecloser => Element with GID {recloser.Id:X16} is not a recloser.";
                Logger.LogError(message);
                throw new Exception(message);
            }

            Logger.LogDebug($"{baseLogString} CommandToRecloser => Enetring in sleep for 20 seconds.");
            await Task.Delay(recloserInterval);

            Logger.LogDebug($"{baseLogString} CommandToRecloser => Waking up after 20 seconds.");

            var topologyProviderClient = TopologyProviderClient.CreateClient();
            int counter = await topologyProviderClient.GetRecloserCount(recloser.Id);

            if (((Recloser)recloser).MaxNumberOfTries > counter)
            {
                topologyProviderClient = TopologyProviderClient.CreateClient();
                await topologyProviderClient.RecloserOpened(recloser.Id);

                Logger.LogDebug($"{baseLogString} CommandToRecloser => Calling SendDiscreteCommand method from measurement provider. Measurement GID: {measurementGid:X16}, Value: {value}, OriginType {originType}.");
                var measurementProviderClient = MeasurementProviderClient.CreateClient();
                await measurementProviderClient.SendSingleDiscreteCommand(measurementGid, value, originType);

                Logger.LogDebug($"{baseLogString} CommandToRecloser => SendDiscreteCommand method has been successfully called.");
            }
        }
        public async Task <bool> SendCloseCommand(long gid)
        {
            string verboseMessage = $"{baseLogString} entering SendCloseCommand method for GID {gid:X16}.";

            Logger.LogVerbose(verboseMessage);

            try
            {
                var measurementProviderClient = MeasurementProviderClient.CreateClient();
                await measurementProviderClient.SendSingleDiscreteCommand(gid, (int)DiscreteCommandingType.CLOSE, CommandOriginType.USER_COMMAND);

                return(true);
            }
            catch (Exception e)
            {
                string message = $"{baseLogString} SendCloseCommand => " +
                                 $"Failed to call send discrete command method measurement provider client." +
                                 $"{Environment.NewLine} Exception message: {e.Message}";
                Logger.LogError(message);
                return(false);
            }
        }
        public async Task <UIModel> ConvertTopologyToUIModel(TopologyModel topology)
        {
            string verboseMessage = $"{baseLogString} ConvertTopologyToUIModel method called.";

            Logger.LogVerbose(verboseMessage);

            if (topology == null)
            {
                string message = $"{baseLogString} ConvertTopologyToUIModel => Provider topology is null.";
                Logger.LogError(message);
                throw new Exception(message);
            }

            UIModel      uIModel = new UIModel();
            Stack <long> stack   = new Stack <long>();

            Logger.LogDebug($"{baseLogString} ConvertTopologyToUIModel => Calling GetReclosers method from model provider client.");
            var modelProviderClient = CeModelProviderClient.CreateClient();
            var reclosers           = await modelProviderClient.GetReclosers();

            Logger.LogDebug($"{baseLogString} ConvertTopologyToUIModel => GetReclosers method from model provider client has been called successfully.");

            uIModel.FirstNode = topology.FirstNode;
            stack.Push(topology.FirstNode);
            long nextElementGid;

            while (stack.Count > 0)
            {
                nextElementGid = stack.Pop();
                if (topology.GetElementByGid(nextElementGid, out ITopologyElement element))
                {
                    if (!reclosers.Contains(nextElementGid))
                    {
                        foreach (var child in element.SecondEnd)
                        {
                            long nextElement = child.Id;
                            if (ModelCodeHelper.ExtractTypeFromGlobalId(child.Id) == 0)
                            {
                                if (child is Field field && field.Members.Count > 0)
                                {
                                    nextElement = field.Members.First().Id;
                                }
                                else
                                {
                                    string message = $"{baseLogString} ConvertTopologyToUIModel => Error while getting field in Topology to UIModel convert. Element is not field or field is empty.";
                                    Logger.LogError(message);
                                    throw new Exception(message);
                                }
                            }

                            uIModel.AddRelation(element.Id, nextElement);
                            stack.Push(nextElement);
                        }
                    }

                    List <UIMeasurement> measurements = new List <UIMeasurement>();
                    foreach (var measurementGid in element.Measurements.Keys)
                    {
                        DMSType type = (DMSType)ModelCodeHelper.ExtractTypeFromGlobalId(measurementGid);

                        if (type == DMSType.ANALOG)
                        {
                            Logger.LogDebug($"{baseLogString} ConvertTopologyToUIModel => Calling GetAnalogMeasurement method from measurement provider client for measurement GID {measurementGid:X16}.");
                            var measurementProviderClient       = MeasurementProviderClient.CreateClient();
                            AnalogMeasurement analogMeasurement = await measurementProviderClient.GetAnalogMeasurement(measurementGid);

                            Logger.LogDebug($"{baseLogString} ConvertTopologyToUIModel => GetAnalogMeasurement method from measurement provider client has been called successfully.");

                            measurements.Add(new UIMeasurement()
                            {
                                Gid   = analogMeasurement.Id,
                                Type  = analogMeasurement.GetMeasurementType(),
                                Value = analogMeasurement.GetCurrentValue()
                            });
                        }
                        else if (type == DMSType.DISCRETE)
                        {
                            Logger.LogDebug($"{baseLogString} ConvertTopologyToUIModel => Calling GetDiscreteMeasurement method from measurement provider client for measurement GID {measurementGid:X16}.");
                            var measurementProviderClient           = MeasurementProviderClient.CreateClient();
                            DiscreteMeasurement discreteMeasurement = await measurementProviderClient.GetDiscreteMeasurement(measurementGid);

                            Logger.LogDebug($"{baseLogString} ConvertTopologyToUIModel => GetDiscreteMeasurement method from measurement provider client has been called successfully.");

                            measurements.Add(new UIMeasurement()
                            {
                                Gid   = discreteMeasurement.Id,
                                Type  = discreteMeasurement.GetMeasurementType(),
                                Value = discreteMeasurement.GetCurrentValue()
                            });
                        }
                    }


                    if (!uIModel.Nodes.ContainsKey(element.Id))
                    {
                        UINode newUINode = new UINode()
                        {
                            Id             = element.Id,
                            Name           = element.Name,
                            Mrid           = element.Mrid,
                            Description    = element.Description,
                            DMSType        = element.DmsType,
                            NominalVoltage = element.NominalVoltage,
                            Measurements   = measurements,
                            IsActive       = element.IsActive,
                            IsRemote       = element.IsRemote,
                            NoReclosing    = element.NoReclosing
                        };
                        uIModel.AddNode(newUINode);
                    }
                }
예제 #12
0
        private async Task <Tuple <bool, float> > IsElementEnergized(ITopologyElement element)
        {
            string verboseMessage = $"{baseLogString} IsElementEnergized method called => Element: {element?.Id:X16}.";

            Logger.LogVerbose(verboseMessage);

            float load      = 0;
            bool  pastState = element.IsActive;

            element.IsActive = true;

            foreach (var measurement in element.Measurements)
            {
                if ((DMSType)ModelCodeHelper.ExtractTypeFromGlobalId(measurement.Key) == DMSType.DISCRETE ||
                    measurement.Key < 10000)
                {
                    Logger.LogDebug($"{baseLogString} IsElementEnergized => Getting discrete value of {measurement.Key:X16} from measurement provider.");
                    var  measurementProviderClient = MeasurementProviderClient.CreateClient();
                    bool isOpen = await measurementProviderClient.GetDiscreteValue(measurement.Key);

                    Logger.LogDebug($"{baseLogString} IsElementEnergized => Discrete value of {measurement.Key:X16} has been delivered successfully. Result is [{isOpen}].");

                    // Value je true ako je prekidac otvoren, tada je element neaktivan
                    element.IsActive = !isOpen;
                    break;
                }
            }

            if (element.IsActive)
            {
                if (!pastState)
                {
                    await TurnOnAllMeasurement(element.Measurements);
                }

                List <AnalogMeasurement> analogMeasurements = await GetMeasurements(element.Measurements);

                if (element.DmsType.Equals(DMSType.SYNCHRONOUSMACHINE.ToString()))
                {
                    if (!syncMachines.ContainsKey(element.Id))
                    {
                        syncMachines.Add(element.Id, element);
                    }
                }
                else
                {
                    if (element.IsRemote)
                    {
                        float power   = 0;
                        float voltage = 0;
                        foreach (var analogMeasurement in analogMeasurements)
                        {
                            if (analogMeasurement.GetMeasurementType().Equals(AnalogMeasurementType.POWER.ToString()))
                            {
                                power = analogMeasurement.GetCurrentValue();
                            }
                            else if (analogMeasurement.GetMeasurementType().Equals(AnalogMeasurementType.VOLTAGE.ToString()))
                            {
                                voltage = analogMeasurement.GetCurrentValue();
                            }
                        }

                        if (power != 0 && voltage != 0)
                        {
                            load = (float)Math.Round(power / voltage);
                        }
                    }
                    else if (element is EnergyConsumer consumer)
                    {
                        if (dailyCurves.TryGetValue(EnergyConsumerTypeToDailyCurveConverter.GetDailyCurveType(consumer.Type), out DailyCurve curve))
                        {
                            load = (float)Math.Round(curve.GetValue((short)DateTime.Now.Hour) * 1000 / 220);
                        }
                    }
                }
            }

            return(new Tuple <bool, float>(element.IsActive, load));
        }
예제 #13
0
        public async Task <IModelDelta> TryGetAllModelEntitiesAsync()
        {
            string verboseMessage = $"{baseLogString} entering TryGetAllModelEntities method.";

            Logger.LogVerbose(verboseMessage);

            while (!ReliableDictionariesInitialized)
            {
                await Task.Delay(1000);
            }

            ModelDelta modelDelta = new ModelDelta();

            try
            {
                //var clearTasks = new List<Task>
                //{
                await TopologyElements.ClearAsync();

                await Measurements.ClearAsync();

                await EnergySources.ClearAsync();

                await ElementConnections.ClearAsync();

                await MeasurementToConnectedTerminalMap.ClearAsync();

                await TerminalToConnectedElementsMap.ClearAsync();

                await BaseVoltages.ClearAsync();

                await Reclosers.ClearAsync();

                //};

                //Task.WaitAll(clearTasks.ToArray());

                await energySources.SetAsync(ReliableDictionaryNames.EnergySources, new List <long>());

                await reclosers.SetAsync(ReliableDictionaryNames.Reclosers, new HashSet <long>());

                Logger.LogDebug($"{baseLogString} TryGetAllModelEntities => Getting all network model elements and converting them.");

                await GetBaseVoltagesAsync();

                foreach (var model in ConcreteModels)
                {
                    if (model != ModelCode.BASEVOLTAGE)
                    {
                        List <ModelCode> properties = modelResourcesDesc.GetAllPropertyIds(model);
                        var elements = await networkModelGda.GetExtentValuesAsync(model, properties);

                        foreach (var element in elements)
                        {
                            try
                            {
                                await TransformToTopologyElementAsync(element);
                            }
                            catch (Exception e)
                            {
                                Logger.LogError($"{baseLogString} TryGetAllModelEntitiesAsync failed." +
                                                $" {Environment.NewLine} {e.Message} " +
                                                $"{Environment.NewLine} {e.StackTrace}");
                            }
                        }
                    }
                }


                //Parallel.For(0, ConcreteModels.Count, async (i) =>
                //{
                //	var model = ConcreteModels.ElementAt(i);
                //	if (model != ModelCode.BASEVOLTAGE)
                //	{
                //		List<ModelCode> properties = modelResourcesDesc.GetAllPropertyIds(model);
                //		var elements = await networkModelGda.GetExtentValuesAsync(model, properties);
                //		foreach (var element in elements)
                //		{
                //			try
                //			{
                //				await TransformToTopologyElementAsync(element);
                //			}
                //			catch (Exception e)
                //			{
                //				Logger.LogError($"{baseLogString} TryGetAllModelEntitiesAsync failed." +
                //					$" {Environment.NewLine} {e.Message} " +
                //					$"{Environment.NewLine} {e.StackTrace}");
                //			}
                //		}
                //	}
                //});

                var enumerableMeasurements = await Measurements.GetEnumerableDictionaryAsync();

                List <IMeasurement> updatedMeasurements = new List <IMeasurement>(enumerableMeasurements.Count);
                foreach (var measurement in enumerableMeasurements.Values)
                {
                    var elementId = await PutMeasurementsInElements(measurement);

                    var measurementProviderClient = MeasurementProviderClient.CreateClient();
                    await measurementProviderClient.AddMeasurementElementPair(measurement.Id, elementId);

                    updatedMeasurements.Add(measurement);
                }

                foreach (var updatedMeas in updatedMeasurements)
                {
                    await Measurements.SetAsync(updatedMeas.Id, updatedMeas);
                }

                var enumerableTopologyElements = await TopologyElements.GetEnumerableDictionaryAsync();

                List <ITopologyElement> updatedElements = new List <ITopologyElement>(enumerableTopologyElements.Count);
                foreach (var element in enumerableTopologyElements.Values)
                {
                    if (element.Measurements.Count == 0)
                    {
                        await CreateNoScadaMeasurementAsync(element);

                        updatedElements.Add(element);
                    }
                }

                foreach (var updatedEl in updatedElements)
                {
                    await TopologyElements.SetAsync(updatedEl.Id, updatedEl);
                }

                modelDelta.TopologyElements   = enumerableTopologyElements;
                modelDelta.ElementConnections = await ElementConnections.GetEnumerableDictionaryAsync();

                var reclosersResult = await Reclosers.TryGetValueAsync(ReliableDictionaryNames.Reclosers);

                if (reclosersResult.HasValue)
                {
                    modelDelta.Reclosers = reclosersResult.Value;
                }
                else
                {
                    Logger.LogWarning($"{baseLogString} Reliable collection '{ReliableDictionaryNames.Reclosers}' was not defined yet. Handling...");
                    await Reclosers.SetAsync(ReliableDictionaryNames.Reclosers, new HashSet <long>());

                    modelDelta.Reclosers = new HashSet <long>();
                }

                var enegySourcesResult = await EnergySources.TryGetValueAsync(ReliableDictionaryNames.EnergySources);

                if (reclosersResult.HasValue)
                {
                    modelDelta.EnergySources = enegySourcesResult.Value;
                }
                else
                {
                    Logger.LogWarning($"{baseLogString} Reliable collection '{ReliableDictionaryNames.EnergySources}' was not defined yet. Handling...");
                    await EnergySources.SetAsync(ReliableDictionaryNames.EnergySources, new List <long>());

                    modelDelta.EnergySources = new List <long>();
                }
            }
            catch (Exception e)
            {
                string message = $"{baseLogString} TryGetAllModelEntities => Failed in get all network model elements." +
                                 $"{Environment.NewLine} Exception message: {e.Message}" +
                                 $"{Environment.NewLine} Stack trace: {e.StackTrace}";
                Logger.LogError(message);
                throw new Exception(message);
            }

            return(modelDelta);
        }
        public async Task Notify(IPublishableMessage message, string publisherName)
        {
            Logger.LogDebug($"{baseLogString} Notify method started.");

            while (!ReliableDictionariesInitialized)
            {
                await Task.Delay(1000);
            }

            try
            {
                if (message is MultipleDiscreteValueSCADAMessage multipleDiscreteValueSCADAMessage)
                {
                    Logger.LogDebug($"{baseLogString} MultipleDiscreteValueSCADAMessage received.");
                    var discreteData = multipleDiscreteValueSCADAMessage.Data;

                    #region HeadBreakers
                    var enumerableHeadBreakerMeasurements = await MonitoredHeadBreakerMeasurements.GetEnumerableDictionaryAsync();

                    foreach (var headMeasurementGid in enumerableHeadBreakerMeasurements.Keys)
                    {
                        if (!discreteData.ContainsKey(headMeasurementGid))
                        {
                            continue;
                        }

                        await MonitoredHeadBreakerMeasurements.SetAsync(headMeasurementGid, discreteData[headMeasurementGid]);
                    }
                    #endregion HeadBreakers

                    #region CommandedElements
                    var measurementProviderClient   = MeasurementProviderClient.CreateClient();
                    var enumerableCommandedElements = await CommandedElements.GetEnumerableDictionaryAsync();

                    foreach (var commandedElementGid in enumerableCommandedElements.Keys)
                    {
                        var measurementGid = (await measurementProviderClient.GetMeasurementsOfElement(commandedElementGid)).FirstOrDefault();
                        var measurement    = await measurementProviderClient.GetDiscreteMeasurement(measurementGid);

                        if (measurement is ArtificalDiscreteMeasurement)
                        {
                            await CommandedElements.TryRemoveAsync(commandedElementGid);

                            Logger.LogInformation($"{baseLogString} Notify => Command on element 0x{commandedElementGid:X16} executed (ArtificalDiscreteMeasurement). New value: {measurement.CurrentOpen}");
                            continue;
                        }

                        if (!discreteData.ContainsKey(measurementGid))
                        {
                            continue;
                        }

                        if (discreteData[measurementGid].Value == (ushort)enumerableCommandedElements[commandedElementGid].CommandingType)
                        {
                            if ((await CommandedElements.TryRemoveAsync(commandedElementGid)).HasValue)
                            {
                                Logger.LogInformation($"{baseLogString} Notify => Command on element 0x{commandedElementGid:X16} executed. New value: {discreteData[measurementGid].Value}");
                            }
                        }
                    }
                    #endregion CommandedElements
                }
                else if (message is OMSModelMessage omsModelMessage)
                {
                    Logger.LogDebug($"{baseLogString} OMSModelMessage received. Count {omsModelMessage.OutageTopologyModel.OutageTopology.Count}");

                    OutageTopologyModel topology = omsModelMessage.OutageTopologyModel;
                    await OutageTopologyModel.SetAsync(ReliableDictionaryNames.OutageTopologyModel, topology);

                    var reportingOutageClient = PotentialOutageReportingClient.CreateClient();

                    while (true)
                    {
                        var result = await PotentialOutagesQueue.TryDequeueAsync();

                        if (!result.HasValue)
                        {
                            break;
                        }

                        var command = result.Value;

                        await reportingOutageClient.ReportPotentialOutage(command.ElementGid, command.CommandOriginType, command.NetworkType);

                        Logger.LogInformation($"{baseLogString} PotentianOutageCommand executed. ElementGid: 0x{command.ElementGid:X16}, OriginType: {command.CommandOriginType}");
                    }
                }
                else
                {
                    Logger.LogWarning($"{baseLogString} Notify => unexpected type of message: {message.GetType()}");
                    return;
                }
            }
            catch (Exception e)
            {
                string errorMessage = $"{baseLogString} Notify => Exception: {e.Message}";
                Logger.LogError(errorMessage, e);
            }
        }
예제 #15
0
        private async Task <long> PutMeasurementsInElements(IMeasurement measurement)
        {
            string verboseMessage = $"{baseLogString} entering PutMeasurementsInElements method. Measurement GID {measurement?.Id:X16}.";

            Logger.LogVerbose(verboseMessage);

            var enumerableMeasurementToConnectedTerminalMap = await MeasurementToConnectedTerminalMap.GetEnumerableDictionaryAsync();

            if (enumerableMeasurementToConnectedTerminalMap.TryGetValue(measurement.Id, out long terminalId))
            {
                var enumerableTerminalToConnectedElementsMap = await TerminalToConnectedElementsMap.GetEnumerableDictionaryAsync();

                if (enumerableTerminalToConnectedElementsMap.TryGetValue(terminalId, out List <long> connectedElements))
                {
                    try
                    {
                        var elementId = connectedElements.Find(
                            e => GetDMSTypeOfTopologyElement(e) != DMSType.CONNECTIVITYNODE &&
                            GetDMSTypeOfTopologyElement(e) != DMSType.ANALOG);

                        var enumerableTopologyElements = await TopologyElements.GetEnumerableDictionaryAsync();

                        if (enumerableTopologyElements.TryGetValue(elementId, out ITopologyElement element))
                        {
                            if (!element.Measurements.ContainsKey(measurement.Id))
                            {
                                element.Measurements.Add(measurement.Id, measurement.GetMeasurementType());
                            }
                            else
                            {
                                Logger.LogWarning($"{baseLogString} PutMeasurementsInElements => element.Measurements contains key: 0x{measurement.Id:X16}");
                            }

                            measurement.ElementId = elementId;

                            if (measurement is DiscreteMeasurement)
                            {
                                var measurementProviderClient = MeasurementProviderClient.CreateClient();
                                await measurementProviderClient.AddDiscreteMeasurement((DiscreteMeasurement)measurement);
                            }

                            if (measurement.GetMeasurementType().Equals(AnalogMeasurementType.FEEDER_CURRENT.ToString()))
                            {
                                await TopologyElements.SetAsync(elementId, new Feeder(element));
                            }
                            else
                            {
                                await TopologyElements.SetAsync(elementId, element);
                            }
                        }
                        else
                        {
                            Logger.LogError($"{baseLogString} PutMeasurementsInElement => Element with GID 0x{elementId:16X} does not exist in elements dictionary.");
                        }
                    }
                    catch (Exception e)
                    {
                        Logger.LogError($"{baseLogString} PutMeasurementsInElement =>  {e.Message} {Environment.NewLine} {e.StackTrace}");
                        //Logger.LogError($"{baseLogString} PutMeasurementsInElement =>  Failed to find appropriate element for mesuremnt with GID {measurement.Id:16X}. There is no conducting equipment connected to common terminal.");
                    }
                }
                else
                {
                    Logger.LogError($"{baseLogString} PutMeasurementsInElement => Terminal with GID 0x{terminalId:X16} does not exist in terminal to element map.");
                }
            }
            else
            {
                Logger.LogError($"{baseLogString} PutMeasurementsInElement => Measurement with GID {measurement.Id:X16} does not exist in mesurement to terminal map.");
            }
            return(measurement.ElementId);
        }
예제 #16
0
        private async Task TransformToTopologyElementAsync(ResourceDescription modelEntity)
        {
            string verboseMessage = $"{baseLogString} entering TransformToTopologyElement method.";

            Logger.LogVerbose(verboseMessage);

            DMSType dmsType;

            dmsType = GetDMSTypeOfTopologyElement(modelEntity.Id);

            if (dmsType == DMSType.DISCRETE)
            {
                Measurement newDiscrete = await GetPopulatedDiscreteMeasurement(modelEntity);

                if (!await Measurements.ContainsKeyAsync(newDiscrete.Id))
                {
                    await Measurements.SetAsync(newDiscrete.Id, newDiscrete);                     //contains moze da bude false, a da kad doje ova linija na red, da vrednost bude popunjena, zato SetAsync, ali onda je sam if suvisan (ne znam da li je kljucan za neku logiku...)
                }
                var measurementProviderClient = MeasurementProviderClient.CreateClient();
                await measurementProviderClient.AddDiscreteMeasurement(newDiscrete as DiscreteMeasurement);
            }
            else if (dmsType == DMSType.ANALOG)
            {
                Measurement newAnalog = await GetPopulatedAnalogMeasurement(modelEntity);

                if (!await Measurements.ContainsKeyAsync(newAnalog.Id))
                {
                    await Measurements.SetAsync(newAnalog.Id, newAnalog);                     //contains moze da bude false, a da kad doje ova linija na red, da vrednost bude popunjena, zato SetAsync, ali onda je sam if suvisan (ne znam da li je kljucan za neku logiku...)
                }
                var measurementProviderClient = MeasurementProviderClient.CreateClient();
                await measurementProviderClient.AddAnalogMeasurement(newAnalog as AnalogMeasurement);
            }
            else if (dmsType != DMSType.MASK_TYPE && dmsType != DMSType.BASEVOLTAGE)
            {
                ITopologyElement newElement = await GetPopulatedElement(modelEntity);

                //lock (syncObj)
                //{
                if (!await TopologyElements.ContainsKeyAsync(newElement.Id))
                {
                    await TopologyElements.SetAsync(newElement.Id, newElement);                     //contains moze da bude false, a da kad doje ova linija na red, da vrednost bude popunjena, zato SetAsync, ali onda je sam if suvisan (ne znam da li je kljucan za neku logiku...)
                }
                else
                {
                    Logger.LogDebug($"{baseLogString} TransformToTopologyElementAsync => TopologyElements contain key {newElement.Id:X16}");
                }
                //}

                if (dmsType == DMSType.ENERGYSOURCE)
                {
                    var energySourcesResult = await EnergySources.TryGetValueAsync(ReliableDictionaryNames.EnergySources);

                    if (energySourcesResult.HasValue)
                    {
                        var energySources = energySourcesResult.Value;
                        energySources.Add(newElement.Id);

                        await EnergySources.SetAsync(ReliableDictionaryNames.EnergySources, energySources);
                    }
                    else
                    {
                        Logger.LogWarning($"{baseLogString} Reliable collection '{ReliableDictionaryNames.EnergySources}' was not defined yet. Handling...");
                        await EnergySources.SetAsync(ReliableDictionaryNames.EnergySources, new List <long>() { newElement.Id });
                    }
                }

                //lock (syncObj)
                //{
                if (!await ElementConnections.ContainsKeyAsync(modelEntity.Id))
                {
                    await ElementConnections.SetAsync(modelEntity.Id, await GetAllReferencedElements(modelEntity));                     //contains moze da bude false, a da kad doje ova linija na red, da vrednost bude popunjena, zato SetAsync, ali onda je sam if suvisan (ne znam da li je kljucan za neku logiku...)
                }
                else
                {
                    Logger.LogDebug($"{baseLogString} TransformToTopologyElementAsync => ElementConnections contain key {modelEntity.Id:X16}");
                }
                //}
            }
        }
예제 #17
0
        private async Task SyncMachine(ITopologyElement element, Dictionary <long, float> loadOfFeeders)
        {
            string verboseMessage = $"{baseLogString} SyncMachine method called. Element with GID {element?.Id:X16}";

            Logger.LogVerbose(verboseMessage);

            if (element == null)
            {
                string message = $"{baseLogString} UpdateLoadFlow => Element is null.";
                Logger.LogError(message);
                throw new Exception(message);
            }

            if (!(element is SynchronousMachine))
            {
                string message = $"{baseLogString} UpdateLoadFlow => Element is not SynchronousMachine.";
                Logger.LogError(message);
                throw new Exception(message);
            }

            AnalogMeasurement powerMeasurement   = null;
            AnalogMeasurement voltageMeasurement = null;

            if (element.Feeder != null)
            {
                if (loadOfFeeders.TryGetValue(element.Feeder.Id, out float feederLoad))
                {
                    float machineCurrentChange;
                    if (feederLoad > 36)
                    {
                        float improvementFactor = feederLoad - 36;

                        machineCurrentChange = (((SynchronousMachine)element).Capacity >= improvementFactor)
                                                    ? improvementFactor
                                                    : ((SynchronousMachine)element).Capacity;
                    }
                    else
                    {
                        machineCurrentChange = 0;
                    }

                    foreach (var meas in element.Measurements)
                    {
                        if (meas.Value.Equals(AnalogMeasurementType.POWER.ToString()))
                        {
                            Logger.LogDebug($"{baseLogString} UpdateLoadFlow => Calling GetAnalogMeasurement method from measurement provider. Measurement GID {meas.Key:X16}.");
                            var measurementProviderClient = MeasurementProviderClient.CreateClient();
                            powerMeasurement = await measurementProviderClient.GetAnalogMeasurement(meas.Key);

                            Logger.LogDebug($"{baseLogString} UpdateLoadFlow => GetAnalogMeasurement method called successfully.");

                            if (powerMeasurement == null)
                            {
                                Logger.LogError($"{baseLogString} UpdateLoadFlow => Synchronous machine with GID {element.Id:X16} does not have POWER measurement.");
                            }
                        }

                        if (meas.Value.Equals(AnalogMeasurementType.VOLTAGE.ToString()))
                        {
                            Logger.LogDebug($"{baseLogString} UpdateLoadFlow => Calling GetAnalogMeasurement method from measurement provider. Measurement GID {meas.Key:X16}.");
                            var measurementProviderClient = MeasurementProviderClient.CreateClient();
                            voltageMeasurement = await measurementProviderClient.GetAnalogMeasurement(meas.Key);

                            Logger.LogDebug($"{baseLogString} UpdateLoadFlow => GetAnalogMeasurement method called successfully.");

                            if (voltageMeasurement == null)
                            {
                                Logger.LogError($"{baseLogString} UpdateLoadFlow => Synchronous machine with GID {element.Id:X16} does not have VOLTAGE measurement.");
                            }
                        }
                    }

                    if (powerMeasurement != null && voltageMeasurement != null)
                    {
                        float newNeededPower = machineCurrentChange * voltageMeasurement.GetCurrentValue();
                        float newSMPower     = (((SynchronousMachine)element).Capacity >= newNeededPower)
                                                    ? newNeededPower
                                                    : ((SynchronousMachine)element).Capacity;

                        Logger.LogDebug($"{baseLogString} UpdateLoadFlow => Calling SendAnalogCommand method from measurement provider. Measurement GID {powerMeasurement.Id:X16}, Value {newSMPower}.");
                        var measurementProviderClient = MeasurementProviderClient.CreateClient();
                        await measurementProviderClient.SendSingleAnalogCommand(powerMeasurement.Id, newSMPower, CommandOriginType.CE_COMMAND);

                        Logger.LogDebug($"{baseLogString} UpdateLoadFlow => SendAnalogCommand method called successfully.");

                        Dictionary <long, AnalogModbusData> data = new Dictionary <long, AnalogModbusData>(1)
                        {
                            { powerMeasurement.Id, new AnalogModbusData(newSMPower, AlarmType.NO_ALARM, powerMeasurement.Id, CommandOriginType.CE_COMMAND) }
                        };

                        Logger.LogDebug($"{baseLogString} UpdateLoadFlow => Calling UpdateAnalogMeasurement method from measurement provider.");
                        measurementProviderClient = MeasurementProviderClient.CreateClient();
                        await measurementProviderClient.UpdateAnalogMeasurement(data);

                        Logger.LogDebug($"{baseLogString} UpdateLoadFlow => UpdateAnalogMeasurement method called successfully.");

                        loadOfFeeders[element.Feeder.Id] -= newSMPower / voltageMeasurement.GetCurrentValue();
                    }
                    else
                    {
                        Logger.LogError($"{baseLogString} UpdateLoadFlow => Synchronous machine with GID {element.Id:X16} does not have measurements for calculating CURRENT.");
                    }
                }
            }
            else
            {
                Logger.LogError($"{baseLogString} UpdateLoadFlow => Synchronous machine with GID {element.Id:X16} does not belond to any feeder.");
            }
        }
예제 #18
0
        public async Task <TopologyModel> UpdateLoadFlow(TopologyModel inputTopology)
        {
            string verboseMessage = $"{baseLogString} UpdateLoadFlow method called.";

            Logger.LogVerbose(verboseMessage);

            TopologyModel topology = inputTopology;

            try
            {
                Dictionary <long, float> loadOfFeeders = new Dictionary <long, float>();
                feeders      = new Dictionary <long, ITopologyElement>();
                syncMachines = new Dictionary <long, ITopologyElement>();
                dailyCurves  = DailyCurveReader.ReadDailyCurves();

                Logger.LogDebug($"{baseLogString} UpdateLoadFlow => Getting reclosers from model provider.");
                var modelProviderClient = CeModelProviderClient.CreateClient();
                reclosers = await modelProviderClient.GetReclosers();

                Logger.LogDebug($"{baseLogString} UpdateLoadFlow => Reclosers were delivered successfully.");


                if (topology == null)
                {
                    string message = $"{baseLogString} UpdateLoadFlow => Topology is null.";
                    Logger.LogWarning(message);
                    //throw new Exception(message);
                    return(topology);
                }

                await CalculateLoadFlow(topology, loadOfFeeders);

                await UpdateLoadFlowFromRecloser(topology, loadOfFeeders);

                foreach (var syncMachine in syncMachines.Values)
                {
                    await SyncMachine(syncMachine, loadOfFeeders);
                }

                var commands   = new Dictionary <long, float>();
                var modbusData = new Dictionary <long, AnalogModbusData>();

                foreach (var loadFeeder in loadOfFeeders)
                {
                    if (feeders.TryGetValue(loadFeeder.Key, out ITopologyElement feeder))
                    {
                        long signalGid = 0;
                        foreach (var measurement in feeder.Measurements)
                        {
                            if (measurement.Value.Equals(AnalogMeasurementType.FEEDER_CURRENT.ToString()))
                            {
                                signalGid = measurement.Key;
                            }
                        }

                        if (signalGid != 0)
                        {
                            Logger.LogDebug($"{baseLogString} UpdateLoadFlow => Calling SendAnalogCommand method from measurement provider. Measurement GID {signalGid:X16}, Value {loadFeeder.Value}.");
                            commands.Add(signalGid, loadFeeder.Value);

                            AlarmType alarmType = (loadFeeder.Value >= 36) ? AlarmType.HIGH_ALARM : AlarmType.NO_ALARM;
                            modbusData.Add(signalGid, new AnalogModbusData(loadFeeder.Value, alarmType, signalGid, CommandOriginType.CE_COMMAND));
                        }
                        else
                        {
                            Logger.LogWarning($"{baseLogString} UpdateLoadFlow => Feeder with GID 0x{feeder.Id:X16} does not have FEEDER_CURRENT measurement.");
                        }
                    }
                }

                var measurementProviderClient = MeasurementProviderClient.CreateClient();

                await measurementProviderClient.SendMultipleAnalogCommand(commands, CommandOriginType.CE_COMMAND);

                Logger.LogDebug($"{baseLogString} UpdateLoadFlow => SendAnalogCommand method from measurement provider successfully finished.");

                Logger.LogDebug($"{baseLogString} UpdateLoadFlow => Calling update analog measurement method from measurement provider.");
                await measurementProviderClient.UpdateAnalogMeasurement(modbusData);

                Logger.LogDebug($"{baseLogString} UpdateLoadFlow => Update analog measurement method from measurement provider successfully finished.");
            }
            catch (Exception e)
            {
                string errorMessage = $"{baseLogString} UpdateLoadFlow => Exception: {e.Message}";
                Logger.LogError(errorMessage, e);
            }

            return(topology);
        }
예제 #19
0
        private async Task <TopologyModel> CalculateLoadFlowFromRecloser(ITopologyElement recloser, TopologyModel topology, Dictionary <long, float> loadOfFeeders)
        {
            string verboseMessage = $"{baseLogString} CalculateLoadFlowFromRecloser method called. Element with GID {recloser?.Id:X16}.";

            Logger.LogVerbose(verboseMessage);

            if (recloser == null)
            {
                string message = $"{baseLogString} CalculateLoadFlowUpsideDown => NULL value has been passed instead of recloser.";
                Logger.LogError(message);
                throw new Exception(message);
            }

            if (topology == null)
            {
                string message = $"{baseLogString} CalculateLoadFlowUpsideDown => NULL value has been passed instead of topology.";
                Logger.LogError(message);
                throw new Exception(message);
            }

            long measurementGid = 0;

            if (recloser.Measurements.Count == 1)
            {
                //dogovor je da prekidaci imaju samo discrete measurement
                measurementGid = recloser.Measurements.First().Key;
            }
            else
            {
                Logger.LogWarning($"{baseLogString} CalculateLoadFlowUpsideDown => Recloser with GID {recloser.Id:X16} does not have proper measurements.");
            }


            bool isEnergized = false;

            if (recloser.FirstEnd != null && recloser.SecondEnd.Count == 1)
            {
                if (recloser.FirstEnd.IsActive && !recloser.SecondEnd.First().IsActive)
                {
                    Tuple <bool, float> tuple = await IsElementEnergized(recloser);

                    isEnergized = tuple.Item1;
                    float load = tuple.Item2;
                    if (isEnergized)
                    {
                        await CalculateLoadFlowUpsideDown(recloser.SecondEnd.First(), recloser.Id, recloser.FirstEnd.Feeder, loadOfFeeders);
                    }
                    else if (!recloser.IsActive)
                    {
                        Thread thread = new Thread(async() => await CommandToRecloser(measurementGid, (int)DiscreteCommandingType.CLOSE, CommandOriginType.CE_COMMAND, recloser));
                        thread.Start();
                    }
                }
                else if (!recloser.FirstEnd.IsActive && recloser.SecondEnd.First().IsActive)
                {
                    Tuple <bool, float> tuple = await IsElementEnergized(recloser);

                    isEnergized = tuple.Item1;
                    float load = tuple.Item2;

                    if (isEnergized)
                    {
                        await CalculateLoadFlowUpsideDown(recloser.FirstEnd, recloser.Id, recloser.SecondEnd.First().Feeder, loadOfFeeders);
                    }
                    else if (!recloser.IsActive)
                    {
                        Thread thread = new Thread(async() => await CommandToRecloser(measurementGid, (int)DiscreteCommandingType.CLOSE, CommandOriginType.CE_COMMAND, recloser));
                        thread.Start();
                    }
                }
                else if (recloser.IsActive)
                {
                    Logger.LogDebug($"{baseLogString} TurnOnAllMeasurement => Calling SendDiscreteCommand method from measurement provider. Measurement GID {measurementGid:X16}, Value 1.");
                    var measurementProviderClient = MeasurementProviderClient.CreateClient();
                    await measurementProviderClient.SendSingleDiscreteCommand(measurementGid, (int)DiscreteCommandingType.OPEN, CommandOriginType.CE_COMMAND);

                    Logger.LogDebug($"{baseLogString} TurnOnAllMeasurement => SendDiscreteCommand method from measurement provider successfully finished.");
                    recloser.IsActive = false;
                }
            }
            else
            {
                Logger.LogDebug($"{baseLogString} TurnOnAllMeasurement =>  Recloser with GID {recloser.Id:X16} does not have both ends, or have more than one on one end.");
            }
            return(topology);
        }