/// <summary> /// passed a sector build a folder containing the sector and all sm's attached to the sector. /// </summary> /// <param name="ap"></param> private Folder generateSector(AccessPointRadioInfo ap, CnLocation location) { // Create this sectors folder to hold all subscribers var sectorFolder = new Folder() { Name = ap.Name }; // Tracker to know if we should show this sector by default (users are connected to it with bad signal) bool showSector = false; // Fetch all subscribers for this specific sector. var sectorSubscribers = _subscribers.Where(sm => sm.Latitude != 0 && sm.Longitude != 0 && sm.APName == ap.Name); sectorFolder.Description = CreateDescriptionFromObject(ap); // Loop through and create all the placemarks for these sector subscribers. if (settings.showSubscribers) { foreach (var sm in sectorSubscribers) { // Check if the sm coordinates are realistic (within our configured range of the sector) if (sm.DistanceGeoM <= settings.SmInvalidationRangeM) { var smPlacemark = generateSmPlacemark(sm); if (smPlacemark.Visibility == true) { showSector = true; } sectorFolder.AddFeature(smPlacemark); } } } if (ap.Azimuth != 999) { // Generate the plot to show the coverage based on the sectors azimuth and distance var sectorPlot = generateSectorPlot((double)location.coordinates[1], (double)location.coordinates[0], ap.Azimuth, 500, 90); var plotPlacemark = new Placemark() { Name = ap.Name + " Coverage", Geometry = sectorPlot, StyleUrl = new Uri($"#plot_{ap.Channel.ToString()}", UriKind.Relative) }; // We chose in settings to always show sector plots if (settings.alwaysShowSectorPlot == true) { showSector = true; } ; plotPlacemark.Visibility = showSector; sectorFolder.AddFeature(plotPlacemark); } else { Console.WriteLine($"AP Missing Azimuth: {ap.Name}"); } return(sectorFolder); }
static AccessPointRadioInfo generateAccessPoint(CnStatistics ap, string ip, string sysContact) { var apRI = new AccessPointRadioInfo() { Name = ap.name, Esn = ap.mac, IP = ip, ConnectedSMs = ap.connected_sms, Lan = ap.lan_status, Channel = Double.Parse(ap.radio.frequency), ColorCode = Byte.Parse(ap.radio.color_code), SyncState = ap.radio.sync_state, TxPower = ap.radio.tx_power ?? 0, Tower = ap.tower, Azimuth = 0, Downtilt = 0, Uptime = TimeSpan.FromSeconds(Double.Parse(ap.status_time)) }; // Parse the sysContact into Azimuth and Downtilt var azdtMatch = Regex.Match(sysContact, @"\[(?<azimuth>\d*)AZ\s(?<downtilt>\d*)DT\]"); if (azdtMatch.Success) { // If we can parse the azimuth and downtilt save it to the AP if we can't set it to an invalid value so we know it wasn't good (0 would be a valid value so would -1) var goodAzimuth = Int32.TryParse(azdtMatch.Groups["azimuth"].ToString(), out int azimuth); var goodDowntilt = Int32.TryParse(azdtMatch.Groups["downtilt"].ToString(), out int downtilt); apRI.Azimuth = goodAzimuth ? azimuth : 999; apRI.Downtilt = goodDowntilt ? downtilt : 999; } return(apRI); }
/// <summary> /// Generate our unified SM View based on the AP & SM Device and Statistics from cnMaestro, /// as well as snmp we had to pull direct, and return a clean object. /// </summary> /// <param name="apDevice"></param> /// <param name="apInfo"></param> /// <param name="smDevice"></param> /// <param name="smStats"></param> /// <param name="smSnmp"></param> /// <returns></returns> public static SubscriberRadioInfo GenerateSmRadioInfo(CnDevice apDevice, AccessPointRadioInfo apInfo, CnDevice smDevice, CnStatistics smStats, IDictionary <string, string> smSnmp) { Double.TryParse(smSnmp[SNMP.OIDs.smFrequencyHz], out double smFrequencyHz); Int32.TryParse(smSnmp[SNMP.OIDs.smAirDelayNs], out int smAirDelayNs); double smDistanceM = RFCalc.MetersFromAirDelay(smAirDelayNs, smFrequencyHz, false); // If we have smGain from cnMaestro let's use it if not fall back to our configured value. Int32.TryParse(smStats.gain, out int smGain); if (smGain <= 0) { smGain = cambiumRadios.SM[smDevice.product].AntennaGain; } // Odd irregularity where cnMaestro sends a -30 let's assume max Tx since it's obviously transmitting as we have a SM to calculate on the panel. var apTx = apInfo.TxPower; if (apTx <= 0) { apTx = cambiumRadios.AP[apDevice.product].MaxTransmit; } // smEPL === The power transmitted from the AP and what we expect to see on the SM var smEPL = RFCalc.EstimatedPowerLevel( smDistanceM, smFrequencyHz, 0, Tx: cambiumRadios.AP[apDevice.product].Radio(apTx), Rx: cambiumRadios.SM[smDevice.product].Radio(smStats.radio.tx_power, smGain)); // apEPL === The power transmitted from the SM and what we expect to see on the AP var apEPL = RFCalc.EstimatedPowerLevel( smDistanceM, smFrequencyHz, 0, Tx: cambiumRadios.SM[smDevice.product].Radio(smStats.radio.tx_power, smGain), Rx: cambiumRadios.AP[apDevice.product].Radio(apTx)); Console.WriteLine($"Generated SM DeviceInfo: {smDevice.name}"); //TODO: change coordinate system to use a strongly typed lat/long not the coordinates from cnLocation that are just an array as its confusing. var GeoDistance = GeoCalc.GeoDistance((double)smDevice.location.coordinates[1], (double)smDevice.location.coordinates[0], (double)apDevice.location.coordinates[1], (double)apDevice.location.coordinates[0]); return(new SubscriberRadioInfo() { Name = smDevice.name, Esn = smDevice.mac, Tower = apDevice.tower, Firmware = smDevice.software_version, Latitude = smDevice.location.coordinates[1], Longitude = smDevice.location.coordinates[0], SmGain = smGain, APName = apDevice.name, DistanceM = (int)smDistanceM, DistanceGeoM = (int)GeoDistance, IP = smDevice.ip, Model = smDevice.product, SmEPL = Math.Round(smEPL, 2), SmAPL = smStats.radio.dl_rssi ?? -1, SmSNRH = smStats.radio.dl_snr_h ?? -1, SmSNRV = smStats.radio.dl_snr_v ?? -1, ApSNRH = smStats.radio.ul_snr_h ?? -1, ApSNRV = smStats.radio.ul_snr_v ?? -1, SmImbalance = smStats.radio.dl_rssi_imbalance ?? 0, ApModel = apDevice.product, ApEPL = Math.Round(apEPL, 2), ApAPL = smStats.radio.ul_rssi ?? -1, ApTxPower = apTx, SmTxPower = smStats.radio.tx_power ?? cambiumRadios.SM[smDevice.product].MaxTransmit, SmMaxTxPower = cambiumRadios.SM[smDevice.product].MaxTransmit, }); }