using System; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using Swan; using Swan.DependencyInjection; using Unosquare.RaspberryIO.Abstractions; using Unosquare.RaspberryIO.Native; namespace Unosquare.RaspberryIO.Computer { /// <summary> /// Retrieves the RaspberryPI System Information. /// /// http://raspberry-pi-guide.readthedocs.io/en/latest/system.html. /// </summary> public sealed class SystemInfo : SingletonBase<SystemInfo> { private const String CpuInfoFilePath = "/proc/cpuinfo"; private const String MemInfoFilePath = "/proc/meminfo"; private const String UptimeFilePath = "/proc/uptime"; private const Int32 NewStyleCodesMask = 0x800000; private BoardModel _boardModel; private ProcessorModel _processorModel; private Manufacturer _manufacturer; private MemorySize _memorySize; /// <summary> /// Prevents a default instance of the <see cref="SystemInfo"/> class from being created. /// </summary> /// <exception cref="NotSupportedException">Could not initialize the GPIO controller.</exception> private SystemInfo() { #region Obtain and format a property dictionary PropertyInfo[] properties = typeof(SystemInfo).GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).Where( p => p.CanWrite && p.CanRead && (p.PropertyType == typeof(String) || p.PropertyType == typeof(String[]))).ToArray(); Dictionary<String, PropertyInfo> propDictionary = new Dictionary<String, PropertyInfo>(StringComparer.OrdinalIgnoreCase); foreach(PropertyInfo prop in properties) { propDictionary[prop.Name.Replace(" ", String.Empty).ToLowerInvariant().Trim()] = prop; } #endregion #region Extract CPU information if(File.Exists(CpuInfoFilePath)) { String[] cpuInfoLines = File.ReadAllLines(CpuInfoFilePath); foreach(String line in cpuInfoLines) { String[] lineParts = line.Split(new[] { ':' }, 2); if(lineParts.Length != 2) { continue; } String propertyKey = lineParts[0].Trim().Replace(" ", String.Empty); String propertyStringValue = lineParts[1].Trim(); if(!propDictionary.ContainsKey(propertyKey)) { continue; } PropertyInfo property = propDictionary[propertyKey]; if(property.PropertyType == typeof(String)) { property.SetValue(this, propertyStringValue); } else if(property.PropertyType == typeof(String[])) { String[] propertyArrayValue = propertyStringValue.Split(' '); property.SetValue(this, propertyArrayValue); } } } #endregion this.ExtractMemoryInfo(); this.ExtractBoardVersion(); this.ExtractOS(); } /// <summary> /// Gets the library version. /// </summary> public Version LibraryVersion { get; private set; } /// <summary> /// Gets the OS information. /// </summary> /// <value> /// The os information. /// </value> public OsInfo OperatingSystem { get; set; } /// <summary> /// Gets the Raspberry Pi version. /// </summary> public PiVersion RaspberryPiVersion { get; set; } /// <summary> /// Gets the board revision (1 or 2). /// </summary> /// <value> /// The wiring pi board revision. /// </value> public Int32 BoardRevision { get; set; } /// <summary> /// Gets the number of processor cores. /// </summary> public Int32 ProcessorCount => Int32.TryParse(this.Processor, out Int32 outIndex) ? outIndex + 1 : 0; /// <summary> /// Gets the installed ram in bytes. /// </summary> public Int32 InstalledRam { get; private set; } /// <summary> /// Gets a value indicating whether this CPU is little endian. /// </summary> public Boolean IsLittleEndian => BitConverter.IsLittleEndian; /// <summary> /// Gets the CPU model name. /// </summary> public String ModelName { get; private set; } /// <summary> /// Gets a list of supported CPU features. /// </summary> public String[] Features { get; private set; } /// <summary> /// Gets the CPU implementer hex code. /// </summary> public String CpuImplementer { get; private set; } /// <summary> /// Gets the CPU architecture code. /// </summary> public String CpuArchitecture { get; private set; } /// <summary> /// Gets the CPU variant code. /// </summary> public String CpuVariant { get; private set; } /// <summary> /// Gets the CPU part code. /// </summary> public String CpuPart { get; private set; } /// <summary> /// Gets the CPU revision code. /// </summary> public String CpuRevision { get; private set; } /// <summary> /// Gets the hardware model number. /// </summary> public String Hardware { get; private set; } /// <summary> /// Gets the hardware revision number. /// </summary> public String Revision { get; private set; } /// <summary> /// Gets the revision number (accordingly to new-style revision codes). /// </summary> public Int32 RevisionNumber { get; set; } /// <summary> /// Gets the board model (accordingly to new-style revision codes). /// </summary> /// /// <exception cref="InvalidOperationException">This board does not support new-style revision codes. Use {nameof(RaspberryPiVersion)}.</exception> public BoardModel BoardModel => this.NewStyleRevisionCodes ? this._boardModel : throw new InvalidOperationException($"This board does not support new-style revision codes. Use {nameof(this.RaspberryPiVersion)} property instead."); /// <summary> /// Gets processor model (accordingly to new-style revision codes). /// </summary> /// /// <exception cref="InvalidOperationException">This board does not support new-style revision codes. Use {nameof(RaspberryPiVersion)}.</exception> public ProcessorModel ProcessorModel => this.NewStyleRevisionCodes ? this._processorModel : throw new InvalidOperationException($"This board does not support new-style revision codes. Use {nameof(this.RaspberryPiVersion)} property instead."); /// <summary> /// Gets the manufacturer of the board (accordingly to new-style revision codes). /// </summary> /// <exception cref="InvalidOperationException">This board does not support new-style revision codes. Use {nameof(RaspberryPiVersion)}.</exception> public Manufacturer Manufacturer => this.NewStyleRevisionCodes ? this._manufacturer : throw new InvalidOperationException($"This board does not support new-style revision codes. Use {nameof(this.RaspberryPiVersion)} property instead."); /// <summary> /// Gets the size of the memory (accordingly to new-style revision codes). /// </summary> /// <exception cref="InvalidOperationException">This board does not support new-style revision codes. Use {nameof(RaspberryPiVersion)}.</exception> public MemorySize MemorySize => this.NewStyleRevisionCodes ? this._memorySize : throw new InvalidOperationException($"This board does not support new-style revision codes. Use {nameof(this.RaspberryPiVersion)} property instead."); /// <summary> /// Gets the serial number. /// </summary> public String Serial { get; private set; } /// <summary> /// Gets the system up-time (in seconds). /// </summary> public Double Uptime { get { try { if(File.Exists(UptimeFilePath) == false) { return 0; } String[] parts = File.ReadAllText(UptimeFilePath).Trim().Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); if(parts.Length >= 1 && Single.TryParse(parts[0], out Single result)) { return result; } } catch { /* Ignore */ } return 0; } } /// <summary> /// Gets the uptime in TimeSpan. /// </summary> public TimeSpan UptimeTimeSpan => TimeSpan.FromSeconds(this.Uptime); /// <summary> /// Indicates if the board uses the new-style revision codes. /// </summary> private Boolean NewStyleRevisionCodes { get; set; } /// <summary> /// Placeholder for processor index. /// </summary> private String Processor { get; set; } /// <summary> /// Returns a <see cref="String" /> that represents this instance. /// </summary> /// <returns> /// A <see cref="String" /> that represents this instance. /// </returns> public override String ToString() { PropertyInfo[] properties = typeof(SystemInfo).GetProperties(BindingFlags.Instance | BindingFlags.Public).Where( p => p.CanRead && (p.PropertyType == typeof(String) || p.PropertyType == typeof(String[]) || p.PropertyType == typeof(Int32) || p.PropertyType == typeof(Boolean) || p.PropertyType == typeof(TimeSpan))).ToArray(); List<String> propertyValues2 = new List<String> { "System Information", $"\t{nameof(this.LibraryVersion),-22}: {this.LibraryVersion}", $"\t{nameof(this.RaspberryPiVersion),-22}: {this.RaspberryPiVersion}", }; foreach(PropertyInfo property in properties) { if(property.PropertyType != typeof(String[])) { propertyValues2.Add($"\t{property.Name,-22}: {property.GetValue(this)}"); } else if(property.GetValue(this) is String[] allValues) { String concatValues = String.Join(" ", allValues); propertyValues2.Add($"\t{property.Name,-22}: {concatValues}"); } } return String.Join(Environment.NewLine, propertyValues2.ToArray()); } private void ExtractOS() { try { _ = Standard.Uname(out SystemName unameInfo); this.OperatingSystem = new OsInfo { DomainName = unameInfo.DomainName, Machine = unameInfo.Machine, NodeName = unameInfo.NodeName, Release = unameInfo.Release, SysName = unameInfo.SysName, Version = unameInfo.Version, }; } catch { this.OperatingSystem = new OsInfo(); } } private void ExtractBoardVersion() { Boolean hasSysInfo = DependencyContainer.Current.CanResolve<ISystemInfo>(); try { if(String.IsNullOrWhiteSpace(this.Revision) == false && Int32.TryParse(this.Revision.ToUpperInvariant(), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out Int32 boardVersion)) { this.RaspberryPiVersion = PiVersion.Unknown; if(Enum.IsDefined(typeof(PiVersion), boardVersion)) { this.RaspberryPiVersion = (PiVersion)boardVersion; } if((boardVersion & NewStyleCodesMask) == NewStyleCodesMask) { this.NewStyleRevisionCodes = true; this.RevisionNumber = boardVersion & 0xF; this._boardModel = (BoardModel)((boardVersion >> 4) & 0xFF); this._processorModel = (ProcessorModel)((boardVersion >> 12) & 0xF); this._manufacturer = (Manufacturer)((boardVersion >> 16) & 0xF); this._memorySize = (MemorySize)((boardVersion >> 20) & 0x7); } } if(hasSysInfo) { this.BoardRevision = (Int32)DependencyContainer.Current.Resolve<ISystemInfo>().BoardRevision; } } catch { /* Ignore */ } if(hasSysInfo) { this.LibraryVersion = DependencyContainer.Current.Resolve<ISystemInfo>().LibraryVersion; } } private void ExtractMemoryInfo() { if(!File.Exists(MemInfoFilePath)) { return; } String[] memInfoLines = File.ReadAllLines(MemInfoFilePath); foreach(String line in memInfoLines) { String[] lineParts = line.Split(new[] { ':' }, 2); if(lineParts.Length != 2) { continue; } if(lineParts[0].ToLowerInvariant().Trim().Equals("memtotal") == false) { continue; } String memKb = lineParts[1].ToLowerInvariant().Trim().Replace("kb", String.Empty).Trim(); if(!Int32.TryParse(memKb, out Int32 parsedMem)) { continue; } this.InstalledRam = parsedMem * 1024; break; } } } }