2019-12-06 23:12:34 +01:00
using System ;
using System.Collections.Generic ;
using System.Globalization ;
using System.IO ;
using System.Linq ;
using System.Reflection ;
using Swan ;
2019-12-08 12:34:43 +01:00
using Swan.DependencyInjection ;
2019-12-06 23:12:34 +01:00
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 ;
2019-12-04 18:57:18 +01:00
/// <summary>
2019-12-06 23:12:34 +01:00
/// Prevents a default instance of the <see cref="SystemInfo"/> class from being created.
2019-12-04 18:57:18 +01:00
/// </summary>
2019-12-06 23:12:34 +01:00
/// <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>
2019-12-08 12:34:43 +01:00
public Version LibraryVersion {
2019-12-06 23:12:34 +01:00
get ; private set ;
}
/// <summary>
/// Gets the OS information.
/// </summary>
/// <value>
/// The os information.
/// </value>
2019-12-08 12:34:43 +01:00
public OsInfo OperatingSystem {
2019-12-06 23:12:34 +01:00
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>
2019-12-08 12:34:43 +01:00
public String ModelName {
2019-12-06 23:12:34 +01:00
get ; private set ;
}
/// <summary>
/// Gets a list of supported CPU features.
/// </summary>
2019-12-08 12:34:43 +01:00
public String [ ] Features {
2019-12-06 23:12:34 +01:00
get ; private set ;
}
/// <summary>
/// Gets the CPU implementer hex code.
/// </summary>
2019-12-08 12:34:43 +01:00
public String CpuImplementer {
2019-12-06 23:12:34 +01:00
get ; private set ;
}
/// <summary>
/// Gets the CPU architecture code.
/// </summary>
2019-12-08 12:34:43 +01:00
public String CpuArchitecture {
2019-12-06 23:12:34 +01:00
get ; private set ;
}
/// <summary>
/// Gets the CPU variant code.
/// </summary>
2019-12-08 12:34:43 +01:00
public String CpuVariant {
2019-12-06 23:12:34 +01:00
get ; private set ;
}
/// <summary>
/// Gets the CPU part code.
/// </summary>
2019-12-08 12:34:43 +01:00
public String CpuPart {
2019-12-06 23:12:34 +01:00
get ; private set ;
}
/// <summary>
/// Gets the CPU revision code.
/// </summary>
2019-12-08 12:34:43 +01:00
public String CpuRevision {
2019-12-06 23:12:34 +01:00
get ; private set ;
}
/// <summary>
/// Gets the hardware model number.
/// </summary>
2019-12-08 12:34:43 +01:00
public String Hardware {
2019-12-06 23:12:34 +01:00
get ; private set ;
}
/// <summary>
/// Gets the hardware revision number.
/// </summary>
2019-12-08 12:34:43 +01:00
public String Revision {
2019-12-06 23:12:34 +01:00
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>
2019-12-08 12:34:43 +01:00
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." ) ;
2019-12-06 23:12:34 +01:00
/// <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>
2019-12-08 12:34:43 +01:00
public String Serial {
2019-12-06 23:12:34 +01:00
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>
2019-12-08 12:34:43 +01:00
private String Processor {
2019-12-06 23:12:34 +01:00
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}" ,
2019-12-08 12:34:43 +01:00
$"\t{nameof(this.RaspberryPiVersion),-22}: {this.RaspberryPiVersion}" ,
2019-12-06 23:12:34 +01:00
} ;
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 {
2019-12-08 12:34:43 +01:00
if ( String . IsNullOrWhiteSpace ( this . Revision ) = = false & & Int32 . TryParse ( this . Revision . ToUpperInvariant ( ) , NumberStyles . HexNumber , CultureInfo . InvariantCulture , out Int32 boardVersion ) ) {
2019-12-06 23:12:34 +01:00
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 ;
}
}
}
2019-12-04 18:57:18 +01:00
}