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 { /// /// Retrieves the RaspberryPI System Information. /// /// http://raspberry-pi-guide.readthedocs.io/en/latest/system.html. /// public sealed class SystemInfo : SingletonBase { 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; /// /// Prevents a default instance of the class from being created. /// /// Could not initialize the GPIO controller. 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 propDictionary = new Dictionary(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(); } /// /// Gets the library version. /// public Version LibraryVersion { get; private set; } /// /// Gets the OS information. /// /// /// The os information. /// public OsInfo OperatingSystem { get; set; } /// /// Gets the Raspberry Pi version. /// public PiVersion RaspberryPiVersion { get; set; } /// /// Gets the board revision (1 or 2). /// /// /// The wiring pi board revision. /// public Int32 BoardRevision { get; set; } /// /// Gets the number of processor cores. /// public Int32 ProcessorCount => Int32.TryParse(this.Processor, out Int32 outIndex) ? outIndex + 1 : 0; /// /// Gets the installed ram in bytes. /// public Int32 InstalledRam { get; private set; } /// /// Gets a value indicating whether this CPU is little endian. /// public Boolean IsLittleEndian => BitConverter.IsLittleEndian; /// /// Gets the CPU model name. /// public String ModelName { get; private set; } /// /// Gets a list of supported CPU features. /// public String[] Features { get; private set; } /// /// Gets the CPU implementer hex code. /// public String CpuImplementer { get; private set; } /// /// Gets the CPU architecture code. /// public String CpuArchitecture { get; private set; } /// /// Gets the CPU variant code. /// public String CpuVariant { get; private set; } /// /// Gets the CPU part code. /// public String CpuPart { get; private set; } /// /// Gets the CPU revision code. /// public String CpuRevision { get; private set; } /// /// Gets the hardware model number. /// public String Hardware { get; private set; } /// /// Gets the hardware revision number. /// public String Revision { get; private set; } /// /// Gets the revision number (accordingly to new-style revision codes). /// public Int32 RevisionNumber { get; set; } /// /// Gets the board model (accordingly to new-style revision codes). /// /// /// This board does not support new-style revision codes. Use {nameof(RaspberryPiVersion)}. 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."); /// /// Gets processor model (accordingly to new-style revision codes). /// /// /// This board does not support new-style revision codes. Use {nameof(RaspberryPiVersion)}. 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."); /// /// Gets the manufacturer of the board (accordingly to new-style revision codes). /// /// This board does not support new-style revision codes. Use {nameof(RaspberryPiVersion)}. 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."); /// /// Gets the size of the memory (accordingly to new-style revision codes). /// /// This board does not support new-style revision codes. Use {nameof(RaspberryPiVersion)}. 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."); /// /// Gets the serial number. /// public String Serial { get; private set; } /// /// Gets the system up-time (in seconds). /// 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; } } /// /// Gets the uptime in TimeSpan. /// public TimeSpan UptimeTimeSpan => TimeSpan.FromSeconds(this.Uptime); /// /// Indicates if the board uses the new-style revision codes. /// private Boolean NewStyleRevisionCodes { get; set; } /// /// Placeholder for processor index. /// private String Processor { get; set; } /// /// Returns a that represents this instance. /// /// /// A that represents this instance. /// 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 propertyValues2 = new List { "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(); 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().BoardRevision; } } catch { /* Ignore */ } if(hasSysInfo) { this.LibraryVersion = DependencyContainer.Current.Resolve().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; } } } }