From b469c4ed6e53795b54cf39fdbcac18bd096aed55 Mon Sep 17 00:00:00 2001 From: BlubbFish Date: Fri, 6 Dec 2019 23:12:34 +0100 Subject: [PATCH] Codingstyle.... --- .../BluetoothErrorException.cs | 30 +- Unosquare.RaspberryIO/Camera/CameraColor.cs | 268 +++--- .../Camera/CameraController.cs | 380 ++++----- Unosquare.RaspberryIO/Camera/CameraRect.cs | 163 ++-- .../Camera/CameraSettingsBase.cs | 695 +++++++-------- .../Camera/CameraStillSettings.cs | 236 +++--- .../Camera/CameraVideoSettings.cs | 204 ++--- Unosquare.RaspberryIO/Camera/Enums.cs | 798 +++++++++--------- .../Computer/AudioSettings.cs | 211 +++-- Unosquare.RaspberryIO/Computer/AudioState.cs | 130 +-- Unosquare.RaspberryIO/Computer/Bluetooth.cs | 466 +++++----- Unosquare.RaspberryIO/Computer/DsiDisplay.cs | 128 ++- .../Computer/NetworkAdapterInfo.cs | 85 +- .../Computer/NetworkSettings.cs | 546 ++++++------ Unosquare.RaspberryIO/Computer/OsInfo.cs | 98 ++- Unosquare.RaspberryIO/Computer/PiVersion.cs | 760 +++++++++-------- Unosquare.RaspberryIO/Computer/SystemInfo.cs | 767 +++++++++-------- .../Computer/WirelessNetworkInfo.cs | 46 +- Unosquare.RaspberryIO/Native/Standard.cs | 31 +- Unosquare.RaspberryIO/Native/SystemName.cs | 87 +- Unosquare.RaspberryIO/Pi.cs | 273 +++--- .../Unosquare.RaspberryIO.csproj | 1 + 22 files changed, 3158 insertions(+), 3245 deletions(-) diff --git a/Unosquare.RaspberryIO/BluetoothErrorException.cs b/Unosquare.RaspberryIO/BluetoothErrorException.cs index 7f746a4..a8a6b78 100644 --- a/Unosquare.RaspberryIO/BluetoothErrorException.cs +++ b/Unosquare.RaspberryIO/BluetoothErrorException.cs @@ -1,20 +1,16 @@ -namespace Unosquare.RaspberryIO -{ - using System; - - /// +using System; + +namespace Unosquare.RaspberryIO { + /// + /// + /// Occurs when an exception is thrown in the Bluetooth component. + /// + public class BluetoothErrorException : Exception { /// - /// Occurs when an exception is thrown in the Bluetooth component. + /// Initializes a new instance of the class. /// - public class BluetoothErrorException : Exception - { - /// - /// Initializes a new instance of the class. - /// - /// The message. - public BluetoothErrorException(string message) - : base(message) - { - } - } + /// The message. + public BluetoothErrorException(String message) : base(message) { + } + } } diff --git a/Unosquare.RaspberryIO/Camera/CameraColor.cs b/Unosquare.RaspberryIO/Camera/CameraColor.cs index 4e06b34..bb6cc65 100644 --- a/Unosquare.RaspberryIO/Camera/CameraColor.cs +++ b/Unosquare.RaspberryIO/Camera/CameraColor.cs @@ -1,134 +1,140 @@ -namespace Unosquare.RaspberryIO.Camera -{ - using System; - using System.Linq; - using Swan; - +using System; +using System.Linq; + +using Swan; + +namespace Unosquare.RaspberryIO.Camera { + /// + /// A simple RGB color class to represent colors in RGB and YUV colorspaces. + /// + public class CameraColor { /// - /// A simple RGB color class to represent colors in RGB and YUV colorspaces. + /// Initializes a new instance of the class. /// - public class CameraColor - { - /// - /// Initializes a new instance of the class. - /// - /// The red. - /// The green. - /// The blue. - public CameraColor(int r, int g, int b) - : this(r, g, b, string.Empty) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The red. - /// The green. - /// The blue. - /// The well-known color name. - public CameraColor(int r, int g, int b, string name) - { - RGB = new[] { Convert.ToByte(r.Clamp(0, 255)), Convert.ToByte(g.Clamp(0, 255)), Convert.ToByte(b.Clamp(0, 255)) }; - - var y = (R * .299000f) + (G * .587000f) + (B * .114000f); - var u = (R * -.168736f) + (G * -.331264f) + (B * .500000f) + 128f; - var v = (R * .500000f) + (G * -.418688f) + (B * -.081312f) + 128f; - - YUV = new[] { (byte)y.Clamp(0, 255), (byte)u.Clamp(0, 255), (byte)v.Clamp(0, 255) }; - Name = name; - } - - #region Static Definitions - - /// - /// Gets the predefined white color. - /// - public static CameraColor White => new CameraColor(255, 255, 255, nameof(White)); - - /// - /// Gets the predefined red color. - /// - public static CameraColor Red => new CameraColor(255, 0, 0, nameof(Red)); - - /// - /// Gets the predefined green color. - /// - public static CameraColor Green => new CameraColor(0, 255, 0, nameof(Green)); - - /// - /// Gets the predefined blue color. - /// - public static CameraColor Blue => new CameraColor(0, 0, 255, nameof(Blue)); - - /// - /// Gets the predefined black color. - /// - public static CameraColor Black => new CameraColor(0, 0, 0, nameof(Black)); - - #endregion - - /// - /// Gets the well-known color name. - /// - public string Name { get; } - - /// - /// Gets the red byte. - /// - public byte R => RGB[0]; - - /// - /// Gets the green byte. - /// - public byte G => RGB[1]; - - /// - /// Gets the blue byte. - /// - public byte B => RGB[2]; - - /// - /// Gets the RGB byte array (3 bytes). - /// - public byte[] RGB { get; } - - /// - /// Gets the YUV byte array (3 bytes). - /// - public byte[] YUV { get; } - - /// - /// Returns a hexadecimal representation of the RGB byte array. - /// Preceded by 0x and all in lowercase. - /// - /// if set to true [reverse]. - /// A string. - public string ToRgbHex(bool reverse) - { - var data = RGB.ToArray(); - if (reverse) Array.Reverse(data); - return ToHex(data); - } - - /// - /// Returns a hexadecimal representation of the YUV byte array. - /// Preceded by 0x and all in lowercase. - /// - /// if set to true [reverse]. - /// A string. - public string ToYuvHex(bool reverse) - { - var data = YUV.ToArray(); - if (reverse) Array.Reverse(data); - return ToHex(data); - } - - /// - /// Returns a hexadecimal representation of the data byte array. - /// - /// The data. - /// A string. - private static string ToHex(byte[] data) => $"0x{BitConverter.ToString(data).Replace("-", string.Empty).ToLowerInvariant()}"; - } + /// The red. + /// The green. + /// The blue. + public CameraColor(Int32 r, Int32 g, Int32 b) : this(r, g, b, String.Empty) { + } + + /// + /// Initializes a new instance of the class. + /// + /// The red. + /// The green. + /// The blue. + /// The well-known color name. + public CameraColor(Int32 r, Int32 g, Int32 b, String name) { + this.RGB = new[] { Convert.ToByte(r.Clamp(0, 255)), Convert.ToByte(g.Clamp(0, 255)), Convert.ToByte(b.Clamp(0, 255)) }; + + Single y = this.R * .299000f + this.G * .587000f + this.B * .114000f; + Single u = this.R * -.168736f + this.G * -.331264f + this.B * .500000f + 128f; + Single v = this.R * .500000f + this.G * -.418688f + this.B * -.081312f + 128f; + + this.YUV = new[] { (Byte)y.Clamp(0, 255), (Byte)u.Clamp(0, 255), (Byte)v.Clamp(0, 255) }; + this.Name = name; + } + + #region Static Definitions + + /// + /// Gets the predefined white color. + /// + public static CameraColor White => new CameraColor(255, 255, 255, nameof(White)); + + /// + /// Gets the predefined red color. + /// + public static CameraColor Red => new CameraColor(255, 0, 0, nameof(Red)); + + /// + /// Gets the predefined green color. + /// + public static CameraColor Green => new CameraColor(0, 255, 0, nameof(Green)); + + /// + /// Gets the predefined blue color. + /// + public static CameraColor Blue => new CameraColor(0, 0, 255, nameof(Blue)); + + /// + /// Gets the predefined black color. + /// + public static CameraColor Black => new CameraColor(0, 0, 0, nameof(Black)); + + #endregion + + /// + /// Gets the well-known color name. + /// + public String Name { + get; + } + + /// + /// Gets the red byte. + /// + public Byte R => this.RGB[0]; + + /// + /// Gets the green byte. + /// + public Byte G => this.RGB[1]; + + /// + /// Gets the blue byte. + /// + public Byte B => this.RGB[2]; + + /// + /// Gets the RGB byte array (3 bytes). + /// + public Byte[] RGB { + get; + } + + /// + /// Gets the YUV byte array (3 bytes). + /// + public Byte[] YUV { + get; + } + + /// + /// Returns a hexadecimal representation of the RGB byte array. + /// Preceded by 0x and all in lowercase. + /// + /// if set to true [reverse]. + /// A string. + public String ToRgbHex(Boolean reverse) { + Byte[] data = this.RGB.ToArray(); + if(reverse) { + Array.Reverse(data); + } + + return ToHex(data); + } + + /// + /// Returns a hexadecimal representation of the YUV byte array. + /// Preceded by 0x and all in lowercase. + /// + /// if set to true [reverse]. + /// A string. + public String ToYuvHex(Boolean reverse) { + Byte[] data = this.YUV.ToArray(); + if(reverse) { + Array.Reverse(data); + } + + return ToHex(data); + } + + /// + /// Returns a hexadecimal representation of the data byte array. + /// + /// The data. + /// A string. + private static String ToHex(Byte[] data) => $"0x{BitConverter.ToString(data).Replace("-", String.Empty).ToLowerInvariant()}"; + } } \ No newline at end of file diff --git a/Unosquare.RaspberryIO/Camera/CameraController.cs b/Unosquare.RaspberryIO/Camera/CameraController.cs index 191535d..dad55b2 100644 --- a/Unosquare.RaspberryIO/Camera/CameraController.cs +++ b/Unosquare.RaspberryIO/Camera/CameraController.cs @@ -1,209 +1,177 @@ -namespace Unosquare.RaspberryIO.Camera -{ - using Swan; - using System; - using System.IO; - using System.Threading; - using System.Threading.Tasks; - +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; + +using Swan; + +namespace Unosquare.RaspberryIO.Camera { + /// + /// The Raspberry Pi's camera controller wrapping raspistill and raspivid programs. + /// This class is a singleton. + /// + public class CameraController : SingletonBase { + #region Private Declarations + + private static readonly ManualResetEventSlim OperationDone = new ManualResetEventSlim(true); + private static readonly Object SyncRoot = new Object(); + private static CancellationTokenSource _videoTokenSource = new CancellationTokenSource(); + private static Task? _videoStreamTask; + + #endregion + + #region Properties + /// - /// The Raspberry Pi's camera controller wrapping raspistill and raspivid programs. - /// This class is a singleton. + /// Gets a value indicating whether the camera module is busy. /// - public class CameraController : SingletonBase - { - #region Private Declarations - - private static readonly ManualResetEventSlim OperationDone = new ManualResetEventSlim(true); - private static readonly object SyncRoot = new object(); - private static CancellationTokenSource _videoTokenSource = new CancellationTokenSource(); - private static Task? _videoStreamTask; - - #endregion - - #region Properties - - /// - /// Gets a value indicating whether the camera module is busy. - /// - /// - /// true if this instance is busy; otherwise, false. - /// - public bool IsBusy => OperationDone.IsSet == false; - - #endregion - - #region Image Capture Methods - - /// - /// Captures an image asynchronously. - /// - /// The settings. - /// The ct. - /// The image bytes. - /// Cannot use camera module because it is currently busy. - public async Task CaptureImageAsync(CameraStillSettings settings, CancellationToken ct = default) - { - if (Instance.IsBusy) - throw new InvalidOperationException("Cannot use camera module because it is currently busy."); - - if (settings.CaptureTimeoutMilliseconds <= 0) - throw new ArgumentException($"{nameof(settings.CaptureTimeoutMilliseconds)} needs to be greater than 0"); - - try - { - OperationDone.Reset(); - - using var output = new MemoryStream(); - var exitCode = await ProcessRunner.RunProcessAsync( - settings.CommandName, - settings.CreateProcessArguments(), - (data, proc) => output.Write(data, 0, data.Length), - null, - true, - ct).ConfigureAwait(false); - - return exitCode != 0 ? Array.Empty() : output.ToArray(); - } - finally - { - OperationDone.Set(); - } - } - - /// - /// Captures an image. - /// - /// The settings. - /// The image bytes. - public byte[] CaptureImage(CameraStillSettings settings) => CaptureImageAsync(settings).GetAwaiter().GetResult(); - - /// - /// Captures a JPEG encoded image asynchronously at 90% quality. - /// - /// The width. - /// The height. - /// The ct. - /// The image bytes. - public Task CaptureImageJpegAsync(int width, int height, CancellationToken ct = default) - { - var settings = new CameraStillSettings - { - CaptureWidth = width, - CaptureHeight = height, - CaptureJpegQuality = 90, - CaptureTimeoutMilliseconds = 300, - }; - - return CaptureImageAsync(settings, ct); - } - - /// - /// Captures a JPEG encoded image at 90% quality. - /// - /// The width. - /// The height. - /// The image bytes. - public byte[] CaptureImageJpeg(int width, int height) => CaptureImageJpegAsync(width, height).GetAwaiter().GetResult(); - - #endregion - - #region Video Capture Methods - - /// - /// Opens the video stream with a timeout of 0 (running indefinitely) at 1080p resolution, variable bitrate and 25 FPS. - /// No preview is shown. - /// - /// The on data callback. - /// The on exit callback. - public void OpenVideoStream(Action onDataCallback, Action? onExitCallback = null) - { - var settings = new CameraVideoSettings - { - CaptureTimeoutMilliseconds = 0, - CaptureDisplayPreview = false, - CaptureWidth = 1920, - CaptureHeight = 1080, - }; - - OpenVideoStream(settings, onDataCallback, onExitCallback); - } - - /// - /// Opens the video stream with the supplied settings. Capture Timeout Milliseconds has to be 0 or greater. - /// - /// The settings. - /// The on data callback. - /// The on exit callback. - /// Cannot use camera module because it is currently busy. - /// CaptureTimeoutMilliseconds. - public void OpenVideoStream(CameraVideoSettings settings, Action onDataCallback, Action? onExitCallback = null) - { - if (Instance.IsBusy) - throw new InvalidOperationException("Cannot use camera module because it is currently busy."); - - if (settings.CaptureTimeoutMilliseconds < 0) - throw new ArgumentException($"{nameof(settings.CaptureTimeoutMilliseconds)} needs to be greater than or equal to 0"); - - try - { - OperationDone.Reset(); - _videoStreamTask = Task.Factory.StartNew(() => VideoWorkerDoWork(settings, onDataCallback, onExitCallback), _videoTokenSource.Token); - } - catch - { - OperationDone.Set(); - throw; - } - } - - /// - /// Closes the video stream of a video stream is open. - /// - public void CloseVideoStream() - { - lock (SyncRoot) - { - if (IsBusy == false) - return; - } - - if (_videoTokenSource.IsCancellationRequested == false) - { - _videoTokenSource.Cancel(); - _videoStreamTask?.Wait(); - } - - _videoTokenSource = new CancellationTokenSource(); - } - - private static async Task VideoWorkerDoWork( - CameraVideoSettings settings, - Action onDataCallback, - Action onExitCallback) - { - try - { - await ProcessRunner.RunProcessAsync( - settings.CommandName, - settings.CreateProcessArguments(), - (data, proc) => onDataCallback?.Invoke(data), - null, - true, - _videoTokenSource.Token).ConfigureAwait(false); - - onExitCallback?.Invoke(); - } - catch - { - // swallow - } - finally - { - Instance.CloseVideoStream(); - OperationDone.Set(); - } - } - #endregion - } + /// + /// true if this instance is busy; otherwise, false. + /// + public Boolean IsBusy => OperationDone.IsSet == false; + + #endregion + + #region Image Capture Methods + + /// + /// Captures an image asynchronously. + /// + /// The settings. + /// The ct. + /// The image bytes. + /// Cannot use camera module because it is currently busy. + public async Task CaptureImageAsync(CameraStillSettings settings, CancellationToken ct = default) { + if(Instance.IsBusy) { + throw new InvalidOperationException("Cannot use camera module because it is currently busy."); + } + + if(settings.CaptureTimeoutMilliseconds <= 0) { + throw new ArgumentException($"{nameof(settings.CaptureTimeoutMilliseconds)} needs to be greater than 0"); + } + + try { + OperationDone.Reset(); + + using MemoryStream output = new MemoryStream(); + Int32 exitCode = await ProcessRunner.RunProcessAsync(settings.CommandName, settings.CreateProcessArguments(), (data, proc) => output.Write(data, 0, data.Length), null, true, ct).ConfigureAwait(false); + + return exitCode != 0 ? Array.Empty() : output.ToArray(); + } finally { + OperationDone.Set(); + } + } + + /// + /// Captures an image. + /// + /// The settings. + /// The image bytes. + public Byte[] CaptureImage(CameraStillSettings settings) => this.CaptureImageAsync(settings).GetAwaiter().GetResult(); + + /// + /// Captures a JPEG encoded image asynchronously at 90% quality. + /// + /// The width. + /// The height. + /// The ct. + /// The image bytes. + public Task CaptureImageJpegAsync(Int32 width, Int32 height, CancellationToken ct = default) { + CameraStillSettings settings = new CameraStillSettings { + CaptureWidth = width, + CaptureHeight = height, + CaptureJpegQuality = 90, + CaptureTimeoutMilliseconds = 300, + }; + + return this.CaptureImageAsync(settings, ct); + } + + /// + /// Captures a JPEG encoded image at 90% quality. + /// + /// The width. + /// The height. + /// The image bytes. + public Byte[] CaptureImageJpeg(Int32 width, Int32 height) => this.CaptureImageJpegAsync(width, height).GetAwaiter().GetResult(); + + #endregion + + #region Video Capture Methods + + /// + /// Opens the video stream with a timeout of 0 (running indefinitely) at 1080p resolution, variable bitrate and 25 FPS. + /// No preview is shown. + /// + /// The on data callback. + /// The on exit callback. + public void OpenVideoStream(Action onDataCallback, Action? onExitCallback = null) { + CameraVideoSettings settings = new CameraVideoSettings { + CaptureTimeoutMilliseconds = 0, + CaptureDisplayPreview = false, + CaptureWidth = 1920, + CaptureHeight = 1080, + }; + + this.OpenVideoStream(settings, onDataCallback, onExitCallback); + } + + /// + /// Opens the video stream with the supplied settings. Capture Timeout Milliseconds has to be 0 or greater. + /// + /// The settings. + /// The on data callback. + /// The on exit callback. + /// Cannot use camera module because it is currently busy. + /// CaptureTimeoutMilliseconds. + public void OpenVideoStream(CameraVideoSettings settings, Action onDataCallback, Action? onExitCallback = null) { + if(Instance.IsBusy) { + throw new InvalidOperationException("Cannot use camera module because it is currently busy."); + } + + if(settings.CaptureTimeoutMilliseconds < 0) { + throw new ArgumentException($"{nameof(settings.CaptureTimeoutMilliseconds)} needs to be greater than or equal to 0"); + } + + try { + OperationDone.Reset(); + _videoStreamTask = Task.Factory.StartNew(() => VideoWorkerDoWork(settings, onDataCallback, onExitCallback), _videoTokenSource.Token); + } catch { + OperationDone.Set(); + throw; + } + } + + /// + /// Closes the video stream of a video stream is open. + /// + public void CloseVideoStream() { + lock(SyncRoot) { + if(this.IsBusy == false) { + return; + } + } + + if(_videoTokenSource.IsCancellationRequested == false) { + _videoTokenSource.Cancel(); + _videoStreamTask?.Wait(); + } + + _videoTokenSource = new CancellationTokenSource(); + } + + private static async Task VideoWorkerDoWork(CameraVideoSettings settings, Action onDataCallback, Action? onExitCallback) { + try { + _ = await ProcessRunner.RunProcessAsync(settings.CommandName, settings.CreateProcessArguments(), (data, proc) => onDataCallback?.Invoke(data), null, true, _videoTokenSource.Token).ConfigureAwait(false); + + onExitCallback?.Invoke(); + } catch { + // swallow + } finally { + Instance.CloseVideoStream(); + OperationDone.Set(); + } + } + #endregion + } } diff --git a/Unosquare.RaspberryIO/Camera/CameraRect.cs b/Unosquare.RaspberryIO/Camera/CameraRect.cs index 505cdd8..55a3479 100644 --- a/Unosquare.RaspberryIO/Camera/CameraRect.cs +++ b/Unosquare.RaspberryIO/Camera/CameraRect.cs @@ -1,82 +1,87 @@ -namespace Unosquare.RaspberryIO.Camera -{ - using System.Globalization; - using Swan; - +using System; +using System.Globalization; + +using Swan; + +namespace Unosquare.RaspberryIO.Camera { + /// + /// Defines the Raspberry Pi camera's sensor ROI (Region of Interest). + /// + public struct CameraRect { /// - /// Defines the Raspberry Pi camera's sensor ROI (Region of Interest). + /// The default ROI which is the entire area. /// - public struct CameraRect - { - /// - /// The default ROI which is the entire area. - /// - public static readonly CameraRect Default = new CameraRect { X = 0M, Y = 0M, W = 1.0M, H = 1.0M }; - - /// - /// Gets or sets the x in relative coordinates. (0.0 to 1.0). - /// - /// - /// The x. - /// - public decimal X { get; set; } - - /// - /// Gets or sets the y location in relative coordinates. (0.0 to 1.0). - /// - /// - /// The y. - /// - public decimal Y { get; set; } - - /// - /// Gets or sets the width in relative coordinates. (0.0 to 1.0). - /// - /// - /// The w. - /// - public decimal W { get; set; } - - /// - /// Gets or sets the height in relative coordinates. (0.0 to 1.0). - /// - /// - /// The h. - /// - public decimal H { get; set; } - - /// - /// Gets a value indicating whether this instance is equal to the default (The entire area). - /// - /// - /// true if this instance is default; otherwise, false. - /// - public bool IsDefault - { - get - { - Clamp(); - return X == Default.X && Y == Default.Y && W == Default.W && H == Default.H; - } - } - - /// - /// Clamps the members of this ROI to their minimum and maximum values. - /// - public void Clamp() - { - X = X.Clamp(0M, 1M); - Y = Y.Clamp(0M, 1M); - W = W.Clamp(0M, 1M - X); - H = H.Clamp(0M, 1M - Y); - } - - /// - /// Returns a that represents this instance. - /// - /// - /// A that represents this instance. - /// - public override string ToString() => $"{X.ToString(CultureInfo.InvariantCulture)},{Y.ToString(CultureInfo.InvariantCulture)},{W.ToString(CultureInfo.InvariantCulture)},{H.ToString(CultureInfo.InvariantCulture)}"; - } + public static readonly CameraRect Default = new CameraRect { X = 0M, Y = 0M, W = 1.0M, H = 1.0M }; + + /// + /// Gets or sets the x in relative coordinates. (0.0 to 1.0). + /// + /// + /// The x. + /// + public Decimal X { + get; set; + } + + /// + /// Gets or sets the y location in relative coordinates. (0.0 to 1.0). + /// + /// + /// The y. + /// + public Decimal Y { + get; set; + } + + /// + /// Gets or sets the width in relative coordinates. (0.0 to 1.0). + /// + /// + /// The w. + /// + public Decimal W { + get; set; + } + + /// + /// Gets or sets the height in relative coordinates. (0.0 to 1.0). + /// + /// + /// The h. + /// + public Decimal H { + get; set; + } + + /// + /// Gets a value indicating whether this instance is equal to the default (The entire area). + /// + /// + /// true if this instance is default; otherwise, false. + /// + public Boolean IsDefault { + get { + this.Clamp(); + return this.X == Default.X && this.Y == Default.Y && this.W == Default.W && this.H == Default.H; + } + } + + /// + /// Clamps the members of this ROI to their minimum and maximum values. + /// + public void Clamp() { + this.X = this.X.Clamp(0M, 1M); + this.Y = this.Y.Clamp(0M, 1M); + this.W = this.W.Clamp(0M, 1M - this.X); + this.H = this.H.Clamp(0M, 1M - this.Y); + } + + /// + /// Returns a that represents this instance. + /// + /// + /// A that represents this instance. + /// + public override String ToString() => $"{this.X.ToString(CultureInfo.InvariantCulture)},{this.Y.ToString(CultureInfo.InvariantCulture)},{this.W.ToString(CultureInfo.InvariantCulture)},{this.H.ToString(CultureInfo.InvariantCulture)}"; + } } diff --git a/Unosquare.RaspberryIO/Camera/CameraSettingsBase.cs b/Unosquare.RaspberryIO/Camera/CameraSettingsBase.cs index 0a5c456..40ecab5 100644 --- a/Unosquare.RaspberryIO/Camera/CameraSettingsBase.cs +++ b/Unosquare.RaspberryIO/Camera/CameraSettingsBase.cs @@ -1,340 +1,361 @@ -namespace Unosquare.RaspberryIO.Camera -{ - using System.Globalization; - using System.Text; - using Swan; - +using System; +using System.Globalization; +using System.Text; + +using Swan; + +namespace Unosquare.RaspberryIO.Camera { + /// + /// A base class to implement raspistill and raspivid wrappers + /// Full documentation available at + /// https://www.raspberrypi.org/documentation/raspbian/applications/camera.md. + /// + public abstract class CameraSettingsBase { /// - /// A base class to implement raspistill and raspivid wrappers - /// Full documentation available at - /// https://www.raspberrypi.org/documentation/raspbian/applications/camera.md. + /// The Invariant Culture shorthand. /// - public abstract class CameraSettingsBase - { - /// - /// The Invariant Culture shorthand. - /// - protected static readonly CultureInfo Ci = CultureInfo.InvariantCulture; - - #region Capture Settings - - /// - /// Gets or sets the timeout milliseconds. - /// Default value is 5000 - /// Recommended value is at least 300 in order to let the light collectors open. - /// - public int CaptureTimeoutMilliseconds { get; set; } = 5000; - - /// - /// Gets or sets a value indicating whether or not to show a preview window on the screen. - /// - public bool CaptureDisplayPreview { get; set; } = false; - - /// - /// Gets or sets a value indicating whether a preview window is shown in full screen mode if enabled. - /// - public bool CaptureDisplayPreviewInFullScreen { get; set; } = true; - - /// - /// Gets or sets a value indicating whether video stabilization should be enabled. - /// - public bool CaptureVideoStabilizationEnabled { get; set; } = false; - - /// - /// Gets or sets the display preview opacity only if the display preview property is enabled. - /// - public byte CaptureDisplayPreviewOpacity { get; set; } = 255; - - /// - /// Gets or sets the capture sensor region of interest in relative coordinates. - /// - public CameraRect CaptureSensorRoi { get; set; } = CameraRect.Default; - - /// - /// Gets or sets the capture shutter speed in microseconds. - /// Default -1, Range 0 to 6000000 (equivalent to 6 seconds). - /// - public int CaptureShutterSpeedMicroseconds { get; set; } = -1; - - /// - /// Gets or sets the exposure mode. - /// - public CameraExposureMode CaptureExposure { get; set; } = CameraExposureMode.Auto; - - /// - /// Gets or sets the picture EV compensation. Default is 0, Range is -10 to 10 - /// Camera exposure compensation is commonly stated in terms of EV units; - /// 1 EV is equal to one exposure step (or stop), corresponding to a doubling of exposure. - /// Exposure can be adjusted by changing either the lens f-number or the exposure time; - /// which one is changed usually depends on the camera's exposure mode. - /// - public int CaptureExposureCompensation { get; set; } = 0; - - /// - /// Gets or sets the capture metering mode. - /// - public CameraMeteringMode CaptureMeteringMode { get; set; } = CameraMeteringMode.Average; - - /// - /// Gets or sets the automatic white balance mode. By default it is set to Auto. - /// - public CameraWhiteBalanceMode CaptureWhiteBalanceControl { get; set; } = CameraWhiteBalanceMode.Auto; - - /// - /// Gets or sets the capture white balance gain on the blue channel. Example: 1.25 - /// Only takes effect if White balance control is set to off. - /// Default is 0. - /// - public decimal CaptureWhiteBalanceGainBlue { get; set; } = 0M; - - /// - /// Gets or sets the capture white balance gain on the red channel. Example: 1.75 - /// Only takes effect if White balance control is set to off. - /// Default is 0. - /// - public decimal CaptureWhiteBalanceGainRed { get; set; } = 0M; - - /// - /// Gets or sets the dynamic range compensation. - /// DRC changes the images by increasing the range of dark areas, and decreasing the brighter areas. This can improve the image in low light areas. - /// - public CameraDynamicRangeCompensation CaptureDynamicRangeCompensation { get; set; } = - CameraDynamicRangeCompensation.Off; - - #endregion - - #region Image Properties - - /// - /// Gets or sets the width of the picture to take. - /// Less than or equal to 0 in either width or height means maximum resolution available. - /// - public int CaptureWidth { get; set; } = 640; - - /// - /// Gets or sets the height of the picture to take. - /// Less than or equal to 0 in either width or height means maximum resolution available. - /// - public int CaptureHeight { get; set; } = 480; - - /// - /// Gets or sets the picture sharpness. Default is 0, Range form -100 to 100. - /// - public int ImageSharpness { get; set; } = 0; - - /// - /// Gets or sets the picture contrast. Default is 0, Range form -100 to 100. - /// - public int ImageContrast { get; set; } = 0; - - /// - /// Gets or sets the picture brightness. Default is 50, Range form 0 to 100. - /// - public int ImageBrightness { get; set; } = 50; // from 0 to 100 - - /// - /// Gets or sets the picture saturation. Default is 0, Range form -100 to 100. - /// - public int ImageSaturation { get; set; } = 0; - - /// - /// Gets or sets the picture ISO. Default is -1 Range is 100 to 800 - /// The higher the value, the more light the sensor absorbs. - /// - public int ImageIso { get; set; } = -1; - - /// - /// Gets or sets the image capture effect to be applied. - /// - public CameraImageEffect ImageEffect { get; set; } = CameraImageEffect.None; - - /// - /// Gets or sets the color effect U coordinates. - /// Default is -1, Range is 0 to 255 - /// 128:128 should be effectively a monochrome image. - /// - public int ImageColorEffectU { get; set; } = -1; // 0 to 255 - - /// - /// Gets or sets the color effect V coordinates. - /// Default is -1, Range is 0 to 255 - /// 128:128 should be effectively a monochrome image. - /// - public int ImageColorEffectV { get; set; } = -1; // 0 to 255 - - /// - /// Gets or sets the image rotation. Default is no rotation. - /// - public CameraImageRotation ImageRotation { get; set; } = CameraImageRotation.None; - - /// - /// Gets or sets a value indicating whether the image should be flipped horizontally. - /// - public bool ImageFlipHorizontally { get; set; } - - /// - /// Gets or sets a value indicating whether the image should be flipped vertically. - /// - public bool ImageFlipVertically { get; set; } - - /// - /// Gets or sets the image annotations using a bitmask (or flags) notation. - /// Apply a bitwise OR to the enumeration to include multiple annotations. - /// - public CameraAnnotation ImageAnnotations { get; set; } = CameraAnnotation.None; - - /// - /// Gets or sets the image annotations text. - /// Text may include date/time placeholders by using the '%' character, as used by strftime. - /// Example: ABC %Y-%m-%d %X will output ABC 2015-10-28 20:09:33. - /// - public string ImageAnnotationsText { get; set; } = string.Empty; - - /// - /// Gets or sets the font size of the text annotations - /// Default is -1, range is 6 to 160. - /// - public int ImageAnnotationFontSize { get; set; } = -1; - - /// - /// Gets or sets the color of the text annotations. - /// - /// - /// The color of the image annotation font. - /// - public CameraColor? ImageAnnotationFontColor { get; set; } = null; - - /// - /// Gets or sets the background color for text annotations. - /// - /// - /// The image annotation background. - /// - public CameraColor? ImageAnnotationBackground { get; set; } = null; - - #endregion - - #region Interface - - /// - /// Gets the command file executable. - /// - public abstract string CommandName { get; } - - /// - /// Creates the process arguments. - /// - /// The string that represents the process arguments. - public virtual string CreateProcessArguments() - { - var sb = new StringBuilder(); - sb.Append("-o -"); // output to standard output as opposed to a file. - sb.Append($" -t {(CaptureTimeoutMilliseconds < 0 ? "0" : CaptureTimeoutMilliseconds.ToString(Ci))}"); - - // Basic Width and height - if (CaptureWidth > 0 && CaptureHeight > 0) - { - sb.Append($" -w {CaptureWidth.ToString(Ci)}"); - sb.Append($" -h {CaptureHeight.ToString(Ci)}"); - } - - // Display Preview - if (CaptureDisplayPreview) - { - if (CaptureDisplayPreviewInFullScreen) - sb.Append(" -f"); - - if (CaptureDisplayPreviewOpacity != byte.MaxValue) - sb.Append($" -op {CaptureDisplayPreviewOpacity.ToString(Ci)}"); - } - else - { - sb.Append(" -n"); // no preview - } - - // Picture Settings - if (ImageSharpness != 0) - sb.Append($" -sh {ImageSharpness.Clamp(-100, 100).ToString(Ci)}"); - - if (ImageContrast != 0) - sb.Append($" -co {ImageContrast.Clamp(-100, 100).ToString(Ci)}"); - - if (ImageBrightness != 50) - sb.Append($" -br {ImageBrightness.Clamp(0, 100).ToString(Ci)}"); - - if (ImageSaturation != 0) - sb.Append($" -sa {ImageSaturation.Clamp(-100, 100).ToString(Ci)}"); - - if (ImageIso >= 100) - sb.Append($" -ISO {ImageIso.Clamp(100, 800).ToString(Ci)}"); - - if (CaptureVideoStabilizationEnabled) - sb.Append(" -vs"); - - if (CaptureExposureCompensation != 0) - sb.Append($" -ev {CaptureExposureCompensation.Clamp(-10, 10).ToString(Ci)}"); - - if (CaptureExposure != CameraExposureMode.Auto) - sb.Append($" -ex {CaptureExposure.ToString().ToLowerInvariant()}"); - - if (CaptureWhiteBalanceControl != CameraWhiteBalanceMode.Auto) - sb.Append($" -awb {CaptureWhiteBalanceControl.ToString().ToLowerInvariant()}"); - - if (ImageEffect != CameraImageEffect.None) - sb.Append($" -ifx {ImageEffect.ToString().ToLowerInvariant()}"); - - if (ImageColorEffectU >= 0 && ImageColorEffectV >= 0) - { - sb.Append( - $" -cfx {ImageColorEffectU.Clamp(0, 255).ToString(Ci)}:{ImageColorEffectV.Clamp(0, 255).ToString(Ci)}"); - } - - if (CaptureMeteringMode != CameraMeteringMode.Average) - sb.Append($" -mm {CaptureMeteringMode.ToString().ToLowerInvariant()}"); - - if (ImageRotation != CameraImageRotation.None) - sb.Append($" -rot {((int)ImageRotation).ToString(Ci)}"); - - if (ImageFlipHorizontally) - sb.Append(" -hf"); - - if (ImageFlipVertically) - sb.Append(" -vf"); - - if (CaptureSensorRoi.IsDefault == false) - sb.Append($" -roi {CaptureSensorRoi}"); - - if (CaptureShutterSpeedMicroseconds > 0) - sb.Append($" -ss {CaptureShutterSpeedMicroseconds.Clamp(0, 6000000).ToString(Ci)}"); - - if (CaptureDynamicRangeCompensation != CameraDynamicRangeCompensation.Off) - sb.Append($" -drc {CaptureDynamicRangeCompensation.ToString().ToLowerInvariant()}"); - - if (CaptureWhiteBalanceControl == CameraWhiteBalanceMode.Off && - (CaptureWhiteBalanceGainBlue != 0M || CaptureWhiteBalanceGainRed != 0M)) - sb.Append($" -awbg {CaptureWhiteBalanceGainBlue.ToString(Ci)},{CaptureWhiteBalanceGainRed.ToString(Ci)}"); - - if (ImageAnnotationFontSize > 0) - { - sb.Append($" -ae {ImageAnnotationFontSize.Clamp(6, 160).ToString(Ci)}"); - sb.Append($",{(ImageAnnotationFontColor == null ? "0xff" : ImageAnnotationFontColor.ToYuvHex(true))}"); - - if (ImageAnnotationBackground != null) - { - ImageAnnotations |= CameraAnnotation.SolidBackground; - sb.Append($",{ImageAnnotationBackground.ToYuvHex(true)}"); - } - } - - if (ImageAnnotations != CameraAnnotation.None) - sb.Append($" -a {((int)ImageAnnotations).ToString(Ci)}"); - - if (string.IsNullOrWhiteSpace(ImageAnnotationsText) == false) - sb.Append($" -a \"{ImageAnnotationsText.Replace("\"", "'")}\""); - - return sb.ToString(); - } - - #endregion - } + protected static readonly CultureInfo Ci = CultureInfo.InvariantCulture; + + #region Capture Settings + + /// + /// Gets or sets the timeout milliseconds. + /// Default value is 5000 + /// Recommended value is at least 300 in order to let the light collectors open. + /// + public Int32 CaptureTimeoutMilliseconds { get; set; } = 5000; + + /// + /// Gets or sets a value indicating whether or not to show a preview window on the screen. + /// + public Boolean CaptureDisplayPreview { get; set; } = false; + + /// + /// Gets or sets a value indicating whether a preview window is shown in full screen mode if enabled. + /// + public Boolean CaptureDisplayPreviewInFullScreen { get; set; } = true; + + /// + /// Gets or sets a value indicating whether video stabilization should be enabled. + /// + public Boolean CaptureVideoStabilizationEnabled { get; set; } = false; + + /// + /// Gets or sets the display preview opacity only if the display preview property is enabled. + /// + public Byte CaptureDisplayPreviewOpacity { get; set; } = 255; + + /// + /// Gets or sets the capture sensor region of interest in relative coordinates. + /// + public CameraRect CaptureSensorRoi { get; set; } = CameraRect.Default; + + /// + /// Gets or sets the capture shutter speed in microseconds. + /// Default -1, Range 0 to 6000000 (equivalent to 6 seconds). + /// + public Int32 CaptureShutterSpeedMicroseconds { get; set; } = -1; + + /// + /// Gets or sets the exposure mode. + /// + public CameraExposureMode CaptureExposure { get; set; } = CameraExposureMode.Auto; + + /// + /// Gets or sets the picture EV compensation. Default is 0, Range is -10 to 10 + /// Camera exposure compensation is commonly stated in terms of EV units; + /// 1 EV is equal to one exposure step (or stop), corresponding to a doubling of exposure. + /// Exposure can be adjusted by changing either the lens f-number or the exposure time; + /// which one is changed usually depends on the camera's exposure mode. + /// + public Int32 CaptureExposureCompensation { get; set; } = 0; + + /// + /// Gets or sets the capture metering mode. + /// + public CameraMeteringMode CaptureMeteringMode { get; set; } = CameraMeteringMode.Average; + + /// + /// Gets or sets the automatic white balance mode. By default it is set to Auto. + /// + public CameraWhiteBalanceMode CaptureWhiteBalanceControl { get; set; } = CameraWhiteBalanceMode.Auto; + + /// + /// Gets or sets the capture white balance gain on the blue channel. Example: 1.25 + /// Only takes effect if White balance control is set to off. + /// Default is 0. + /// + public Decimal CaptureWhiteBalanceGainBlue { get; set; } = 0M; + + /// + /// Gets or sets the capture white balance gain on the red channel. Example: 1.75 + /// Only takes effect if White balance control is set to off. + /// Default is 0. + /// + public Decimal CaptureWhiteBalanceGainRed { get; set; } = 0M; + + /// + /// Gets or sets the dynamic range compensation. + /// DRC changes the images by increasing the range of dark areas, and decreasing the brighter areas. This can improve the image in low light areas. + /// + public CameraDynamicRangeCompensation CaptureDynamicRangeCompensation { + get; set; + } = CameraDynamicRangeCompensation.Off; + + #endregion + + #region Image Properties + + /// + /// Gets or sets the width of the picture to take. + /// Less than or equal to 0 in either width or height means maximum resolution available. + /// + public Int32 CaptureWidth { get; set; } = 640; + + /// + /// Gets or sets the height of the picture to take. + /// Less than or equal to 0 in either width or height means maximum resolution available. + /// + public Int32 CaptureHeight { get; set; } = 480; + + /// + /// Gets or sets the picture sharpness. Default is 0, Range form -100 to 100. + /// + public Int32 ImageSharpness { get; set; } = 0; + + /// + /// Gets or sets the picture contrast. Default is 0, Range form -100 to 100. + /// + public Int32 ImageContrast { get; set; } = 0; + + /// + /// Gets or sets the picture brightness. Default is 50, Range form 0 to 100. + /// + public Int32 ImageBrightness { get; set; } = 50; // from 0 to 100 + + /// + /// Gets or sets the picture saturation. Default is 0, Range form -100 to 100. + /// + public Int32 ImageSaturation { get; set; } = 0; + + /// + /// Gets or sets the picture ISO. Default is -1 Range is 100 to 800 + /// The higher the value, the more light the sensor absorbs. + /// + public Int32 ImageIso { get; set; } = -1; + + /// + /// Gets or sets the image capture effect to be applied. + /// + public CameraImageEffect ImageEffect { get; set; } = CameraImageEffect.None; + + /// + /// Gets or sets the color effect U coordinates. + /// Default is -1, Range is 0 to 255 + /// 128:128 should be effectively a monochrome image. + /// + public Int32 ImageColorEffectU { get; set; } = -1; // 0 to 255 + + /// + /// Gets or sets the color effect V coordinates. + /// Default is -1, Range is 0 to 255 + /// 128:128 should be effectively a monochrome image. + /// + public Int32 ImageColorEffectV { get; set; } = -1; // 0 to 255 + + /// + /// Gets or sets the image rotation. Default is no rotation. + /// + public CameraImageRotation ImageRotation { get; set; } = CameraImageRotation.None; + + /// + /// Gets or sets a value indicating whether the image should be flipped horizontally. + /// + public Boolean ImageFlipHorizontally { + get; set; + } + + /// + /// Gets or sets a value indicating whether the image should be flipped vertically. + /// + public Boolean ImageFlipVertically { + get; set; + } + + /// + /// Gets or sets the image annotations using a bitmask (or flags) notation. + /// Apply a bitwise OR to the enumeration to include multiple annotations. + /// + public CameraAnnotation ImageAnnotations { get; set; } = CameraAnnotation.None; + + /// + /// Gets or sets the image annotations text. + /// Text may include date/time placeholders by using the '%' character, as used by strftime. + /// Example: ABC %Y-%m-%d %X will output ABC 2015-10-28 20:09:33. + /// + public String ImageAnnotationsText { get; set; } = String.Empty; + + /// + /// Gets or sets the font size of the text annotations + /// Default is -1, range is 6 to 160. + /// + public Int32 ImageAnnotationFontSize { get; set; } = -1; + + /// + /// Gets or sets the color of the text annotations. + /// + /// + /// The color of the image annotation font. + /// + public CameraColor? ImageAnnotationFontColor { get; set; } = null; + + /// + /// Gets or sets the background color for text annotations. + /// + /// + /// The image annotation background. + /// + public CameraColor? ImageAnnotationBackground { get; set; } = null; + + #endregion + + #region Interface + + /// + /// Gets the command file executable. + /// + public abstract String CommandName { + get; + } + + /// + /// Creates the process arguments. + /// + /// The string that represents the process arguments. + public virtual String CreateProcessArguments() { + StringBuilder sb = new StringBuilder(); + _ = sb.Append("-o -"); // output to standard output as opposed to a file. + _ = sb.Append($" -t {(this.CaptureTimeoutMilliseconds < 0 ? "0" : this.CaptureTimeoutMilliseconds.ToString(Ci))}"); + + // Basic Width and height + if(this.CaptureWidth > 0 && this.CaptureHeight > 0) { + _ = sb.Append($" -w {this.CaptureWidth.ToString(Ci)}"); + _ = sb.Append($" -h {this.CaptureHeight.ToString(Ci)}"); + } + + // Display Preview + if(this.CaptureDisplayPreview) { + if(this.CaptureDisplayPreviewInFullScreen) { + _ = sb.Append(" -f"); + } + + if(this.CaptureDisplayPreviewOpacity != Byte.MaxValue) { + _ = sb.Append($" -op {this.CaptureDisplayPreviewOpacity.ToString(Ci)}"); + } + } else { + _ = sb.Append(" -n"); // no preview + } + + // Picture Settings + if(this.ImageSharpness != 0) { + _ = sb.Append($" -sh {this.ImageSharpness.Clamp(-100, 100).ToString(Ci)}"); + } + + if(this.ImageContrast != 0) { + _ = sb.Append($" -co {this.ImageContrast.Clamp(-100, 100).ToString(Ci)}"); + } + + if(this.ImageBrightness != 50) { + _ = sb.Append($" -br {this.ImageBrightness.Clamp(0, 100).ToString(Ci)}"); + } + + if(this.ImageSaturation != 0) { + _ = sb.Append($" -sa {this.ImageSaturation.Clamp(-100, 100).ToString(Ci)}"); + } + + if(this.ImageIso >= 100) { + _ = sb.Append($" -ISO {this.ImageIso.Clamp(100, 800).ToString(Ci)}"); + } + + if(this.CaptureVideoStabilizationEnabled) { + _ = sb.Append(" -vs"); + } + + if(this.CaptureExposureCompensation != 0) { + _ = sb.Append($" -ev {this.CaptureExposureCompensation.Clamp(-10, 10).ToString(Ci)}"); + } + + if(this.CaptureExposure != CameraExposureMode.Auto) { + _ = sb.Append($" -ex {this.CaptureExposure.ToString().ToLowerInvariant()}"); + } + + if(this.CaptureWhiteBalanceControl != CameraWhiteBalanceMode.Auto) { + _ = sb.Append($" -awb {this.CaptureWhiteBalanceControl.ToString().ToLowerInvariant()}"); + } + + if(this.ImageEffect != CameraImageEffect.None) { + _ = sb.Append($" -ifx {this.ImageEffect.ToString().ToLowerInvariant()}"); + } + + if(this.ImageColorEffectU >= 0 && this.ImageColorEffectV >= 0) { + _ = sb.Append( + $" -cfx {this.ImageColorEffectU.Clamp(0, 255).ToString(Ci)}:{this.ImageColorEffectV.Clamp(0, 255).ToString(Ci)}"); + } + + if(this.CaptureMeteringMode != CameraMeteringMode.Average) { + _ = sb.Append($" -mm {this.CaptureMeteringMode.ToString().ToLowerInvariant()}"); + } + + if(this.ImageRotation != CameraImageRotation.None) { + _ = sb.Append($" -rot {((Int32)this.ImageRotation).ToString(Ci)}"); + } + + if(this.ImageFlipHorizontally) { + _ = sb.Append(" -hf"); + } + + if(this.ImageFlipVertically) { + _ = sb.Append(" -vf"); + } + + if(this.CaptureSensorRoi.IsDefault == false) { + _ = sb.Append($" -roi {this.CaptureSensorRoi}"); + } + + if(this.CaptureShutterSpeedMicroseconds > 0) { + _ = sb.Append($" -ss {this.CaptureShutterSpeedMicroseconds.Clamp(0, 6000000).ToString(Ci)}"); + } + + if(this.CaptureDynamicRangeCompensation != CameraDynamicRangeCompensation.Off) { + _ = sb.Append($" -drc {this.CaptureDynamicRangeCompensation.ToString().ToLowerInvariant()}"); + } + + if(this.CaptureWhiteBalanceControl == CameraWhiteBalanceMode.Off && + (this.CaptureWhiteBalanceGainBlue != 0M || this.CaptureWhiteBalanceGainRed != 0M)) { + _ = sb.Append($" -awbg {this.CaptureWhiteBalanceGainBlue.ToString(Ci)},{this.CaptureWhiteBalanceGainRed.ToString(Ci)}"); + } + + if(this.ImageAnnotationFontSize > 0) { + _ = sb.Append($" -ae {this.ImageAnnotationFontSize.Clamp(6, 160).ToString(Ci)}"); + _ = sb.Append($",{(this.ImageAnnotationFontColor == null ? "0xff" : this.ImageAnnotationFontColor.ToYuvHex(true))}"); + + if(this.ImageAnnotationBackground != null) { + this.ImageAnnotations |= CameraAnnotation.SolidBackground; + _ = sb.Append($",{this.ImageAnnotationBackground.ToYuvHex(true)}"); + } + } + + if(this.ImageAnnotations != CameraAnnotation.None) { + _ = sb.Append($" -a {((Int32)this.ImageAnnotations).ToString(Ci)}"); + } + + if(String.IsNullOrWhiteSpace(this.ImageAnnotationsText) == false) { + _ = sb.Append($" -a \"{this.ImageAnnotationsText.Replace("\"", "'")}\""); + } + + return sb.ToString(); + } + + #endregion + } } diff --git a/Unosquare.RaspberryIO/Camera/CameraStillSettings.cs b/Unosquare.RaspberryIO/Camera/CameraStillSettings.cs index f384831..582c9e6 100644 --- a/Unosquare.RaspberryIO/Camera/CameraStillSettings.cs +++ b/Unosquare.RaspberryIO/Camera/CameraStillSettings.cs @@ -1,120 +1,122 @@ -namespace Unosquare.RaspberryIO.Camera -{ - using System; - using System.Collections.Generic; - using System.Text; - using Swan; - +using System; +using System.Collections.Generic; +using System.Text; + +using Swan; + +namespace Unosquare.RaspberryIO.Camera { + /// + /// Defines a wrapper for the raspistill program and its settings (command-line arguments). + /// + /// + public class CameraStillSettings : CameraSettingsBase { + private Int32 _rotate; + + /// + public override String CommandName => "raspistill"; + /// - /// Defines a wrapper for the raspistill program and its settings (command-line arguments). + /// Gets or sets a value indicating whether the preview window (if enabled) uses native capture resolution + /// This may slow down preview FPS. /// - /// - public class CameraStillSettings : CameraSettingsBase - { - private int _rotate; - - /// - public override string CommandName => "raspistill"; - - /// - /// Gets or sets a value indicating whether the preview window (if enabled) uses native capture resolution - /// This may slow down preview FPS. - /// - public bool CaptureDisplayPreviewAtResolution { get; set; } = false; - - /// - /// Gets or sets the encoding format the hardware will use for the output. - /// - public CameraImageEncodingFormat CaptureEncoding { get; set; } = CameraImageEncodingFormat.Jpg; - - /// - /// Gets or sets the quality for JPEG only encoding mode. - /// Value ranges from 0 to 100. - /// - public int CaptureJpegQuality { get; set; } = 90; - - /// - /// Gets or sets a value indicating whether the JPEG encoder should add raw bayer metadata. - /// - public bool CaptureJpegIncludeRawBayerMetadata { get; set; } = false; - - /// - /// JPEG EXIF data - /// Keys and values must be already properly escaped. Otherwise the command will fail. - /// - public Dictionary CaptureJpegExtendedInfo { get; } = new Dictionary(); - - /// - /// Gets or sets a value indicating whether [horizontal flip]. - /// - /// - /// true if [horizontal flip]; otherwise, false. - /// - public bool HorizontalFlip { get; set; } = false; - - /// - /// Gets or sets a value indicating whether [vertical flip]. - /// - /// - /// true if [vertical flip]; otherwise, false. - /// - public bool VerticalFlip { get; set; } = false; - - /// - /// Gets or sets the rotation. - /// - /// Valid range 0-359. - public int Rotation - { - get => _rotate; - set - { - if (value < 0 || value > 359) - { - throw new ArgumentOutOfRangeException(nameof(value), "Valid range 0-359"); - } - - _rotate = value; - } - } - - /// - public override string CreateProcessArguments() - { - var sb = new StringBuilder(base.CreateProcessArguments()); - sb.Append($" -e {CaptureEncoding.ToString().ToLowerInvariant()}"); - - // JPEG Encoder specific arguments - if (CaptureEncoding == CameraImageEncodingFormat.Jpg) - { - sb.Append($" -q {CaptureJpegQuality.Clamp(0, 100).ToString(Ci)}"); - - if (CaptureJpegIncludeRawBayerMetadata) - sb.Append(" -r"); - - // JPEG EXIF data - if (CaptureJpegExtendedInfo.Count > 0) - { - foreach (var kvp in CaptureJpegExtendedInfo) - { - if (string.IsNullOrWhiteSpace(kvp.Key) || string.IsNullOrWhiteSpace(kvp.Value)) - continue; - - sb.Append($" -x \"{kvp.Key.Replace("\"", "'")}={kvp.Value.Replace("\"", "'")}\""); - } - } - } - - // Display preview settings - if (CaptureDisplayPreview && CaptureDisplayPreviewAtResolution) sb.Append(" -fp"); - - if (Rotation != 0) sb.Append($" -rot {Rotation}"); - - if (HorizontalFlip) sb.Append(" -hf"); - - if (VerticalFlip) sb.Append(" -vf"); - - return sb.ToString(); - } - } + public Boolean CaptureDisplayPreviewAtResolution { get; set; } = false; + + /// + /// Gets or sets the encoding format the hardware will use for the output. + /// + public CameraImageEncodingFormat CaptureEncoding { get; set; } = CameraImageEncodingFormat.Jpg; + + /// + /// Gets or sets the quality for JPEG only encoding mode. + /// Value ranges from 0 to 100. + /// + public Int32 CaptureJpegQuality { get; set; } = 90; + + /// + /// Gets or sets a value indicating whether the JPEG encoder should add raw bayer metadata. + /// + public Boolean CaptureJpegIncludeRawBayerMetadata { get; set; } = false; + + /// + /// JPEG EXIF data + /// Keys and values must be already properly escaped. Otherwise the command will fail. + /// + public Dictionary CaptureJpegExtendedInfo { get; } = new Dictionary(); + + /// + /// Gets or sets a value indicating whether [horizontal flip]. + /// + /// + /// true if [horizontal flip]; otherwise, false. + /// + public Boolean HorizontalFlip { get; set; } = false; + + /// + /// Gets or sets a value indicating whether [vertical flip]. + /// + /// + /// true if [vertical flip]; otherwise, false. + /// + public Boolean VerticalFlip { get; set; } = false; + + /// + /// Gets or sets the rotation. + /// + /// Valid range 0-359. + public Int32 Rotation { + get => this._rotate; + set { + if(value < 0 || value > 359) { + throw new ArgumentOutOfRangeException(nameof(value), "Valid range 0-359"); + } + + this._rotate = value; + } + } + + /// + public override String CreateProcessArguments() { + StringBuilder sb = new StringBuilder(base.CreateProcessArguments()); + _ = sb.Append($" -e {this.CaptureEncoding.ToString().ToLowerInvariant()}"); + + // JPEG Encoder specific arguments + if(this.CaptureEncoding == CameraImageEncodingFormat.Jpg) { + _ = sb.Append($" -q {this.CaptureJpegQuality.Clamp(0, 100).ToString(Ci)}"); + + if(this.CaptureJpegIncludeRawBayerMetadata) { + _ = sb.Append(" -r"); + } + + // JPEG EXIF data + if(this.CaptureJpegExtendedInfo.Count > 0) { + foreach(KeyValuePair kvp in this.CaptureJpegExtendedInfo) { + if(String.IsNullOrWhiteSpace(kvp.Key) || String.IsNullOrWhiteSpace(kvp.Value)) { + continue; + } + + _ = sb.Append($" -x \"{kvp.Key.Replace("\"", "'")}={kvp.Value.Replace("\"", "'")}\""); + } + } + } + + // Display preview settings + if(this.CaptureDisplayPreview && this.CaptureDisplayPreviewAtResolution) { + _ = sb.Append(" -fp"); + } + + if(this.Rotation != 0) { + _ = sb.Append($" -rot {this.Rotation}"); + } + + if(this.HorizontalFlip) { + _ = sb.Append(" -hf"); + } + + if(this.VerticalFlip) { + _ = sb.Append(" -vf"); + } + + return sb.ToString(); + } + } } \ No newline at end of file diff --git a/Unosquare.RaspberryIO/Camera/CameraVideoSettings.cs b/Unosquare.RaspberryIO/Camera/CameraVideoSettings.cs index c80c3ee..6a80899 100644 --- a/Unosquare.RaspberryIO/Camera/CameraVideoSettings.cs +++ b/Unosquare.RaspberryIO/Camera/CameraVideoSettings.cs @@ -1,104 +1,106 @@ -namespace Unosquare.RaspberryIO.Camera -{ - using System.Text; - +using System; +using System.Text; + +namespace Unosquare.RaspberryIO.Camera { + /// + /// Represents the raspivid camera settings for video capture functionality. + /// + /// + public class CameraVideoSettings : CameraSettingsBase { + private Int32 _length; + + /// + public override String CommandName => "raspivid"; + /// - /// Represents the raspivid camera settings for video capture functionality. + /// Use bits per second, so 10Mbits/s would be -b 10000000. For H264, 1080p30 a high quality bitrate would be 15Mbits/s or more. + /// Maximum bitrate is 25Mbits/s (-b 25000000), but much over 17Mbits/s won't show noticeable improvement at 1080p30. + /// Default -1. /// - /// - public class CameraVideoSettings : CameraSettingsBase - { - private int _length; - - /// - public override string CommandName => "raspivid"; - - /// - /// Use bits per second, so 10Mbits/s would be -b 10000000. For H264, 1080p30 a high quality bitrate would be 15Mbits/s or more. - /// Maximum bitrate is 25Mbits/s (-b 25000000), but much over 17Mbits/s won't show noticeable improvement at 1080p30. - /// Default -1. - /// - public int CaptureBitrate { get; set; } = -1; - - /// - /// Gets or sets the framerate. - /// Default 25, range 2 to 30. - /// - public int CaptureFramerate { get; set; } = 25; - - /// - /// Sets the intra refresh period (GoP) rate for the recorded video. H264 video uses a complete frame (I-frame) every intra - /// refresh period, from which subsequent frames are based. This option specifies the number of frames between each I-frame. - /// Larger numbers here will reduce the size of the resulting video, and smaller numbers make the stream less error-prone. - /// - public int CaptureKeyframeRate { get; set; } = 25; - - /// - /// Sets the initial quantisation parameter for the stream. Varies from approximately 10 to 40, and will greatly affect - /// the quality of the recording. Higher values reduce quality and decrease file size. Combine this setting with a - /// bitrate of 0 to set a completely variable bitrate. - /// - public int CaptureQuantisation { get; set; } = 23; - - /// - /// Gets or sets the profile. - /// Sets the H264 profile to be used for the encoding. - /// Default is Main mode. - /// - public CameraH264Profile CaptureProfile { get; set; } = CameraH264Profile.Main; - - /// - /// Forces the stream to include PPS and SPS headers on every I-frame. Needed for certain streaming cases - /// e.g. Apple HLS. These headers are small, so don't greatly increase the file size. - /// - /// - /// true if [interleave headers]; otherwise, false. - /// - public bool CaptureInterleaveHeaders { get; set; } = true; - - /// - /// Toggle fullscreen mode for video preview. - /// - public bool Fullscreen { get; set; } = false; - - /// - /// Specifies the path to save video files. - /// - public string VideoFileName { get; set; } - - /// - /// Video stream length in seconds. - /// - public int LengthInSeconds - { - get => _length; - set => _length = value * 1000; - } - - /// - /// Switch on an option to display the preview after compression. This will show any compression artefacts in the preview window. In normal operation, - /// the preview will show the camera output prior to being compressed. This option is not guaranteed to work in future releases. - /// - /// - /// true if [capture display preview encoded]; otherwise, false. - /// - public bool CaptureDisplayPreviewEncoded { get; set; } = false; - - /// - public override string CreateProcessArguments() - { - var sb = new StringBuilder(base.CreateProcessArguments()); - - if (Fullscreen) - sb.Append(" -f"); - - if (LengthInSeconds != 0) - sb.Append($" -t {LengthInSeconds}"); - - if (!string.IsNullOrEmpty(VideoFileName)) - sb.Append($" -o {VideoFileName}"); - - return sb.ToString(); - } - } + public Int32 CaptureBitrate { get; set; } = -1; + + /// + /// Gets or sets the framerate. + /// Default 25, range 2 to 30. + /// + public Int32 CaptureFramerate { get; set; } = 25; + + /// + /// Sets the intra refresh period (GoP) rate for the recorded video. H264 video uses a complete frame (I-frame) every intra + /// refresh period, from which subsequent frames are based. This option specifies the number of frames between each I-frame. + /// Larger numbers here will reduce the size of the resulting video, and smaller numbers make the stream less error-prone. + /// + public Int32 CaptureKeyframeRate { get; set; } = 25; + + /// + /// Sets the initial quantisation parameter for the stream. Varies from approximately 10 to 40, and will greatly affect + /// the quality of the recording. Higher values reduce quality and decrease file size. Combine this setting with a + /// bitrate of 0 to set a completely variable bitrate. + /// + public Int32 CaptureQuantisation { get; set; } = 23; + + /// + /// Gets or sets the profile. + /// Sets the H264 profile to be used for the encoding. + /// Default is Main mode. + /// + public CameraH264Profile CaptureProfile { get; set; } = CameraH264Profile.Main; + + /// + /// Forces the stream to include PPS and SPS headers on every I-frame. Needed for certain streaming cases + /// e.g. Apple HLS. These headers are small, so don't greatly increase the file size. + /// + /// + /// true if [interleave headers]; otherwise, false. + /// + public Boolean CaptureInterleaveHeaders { get; set; } = true; + + /// + /// Toggle fullscreen mode for video preview. + /// + public Boolean Fullscreen { get; set; } = false; + + /// + /// Specifies the path to save video files. + /// + public String? VideoFileName { + get; set; + } + + /// + /// Video stream length in seconds. + /// + public Int32 LengthInSeconds { + get => this._length; + set => this._length = value * 1000; + } + + /// + /// Switch on an option to display the preview after compression. This will show any compression artefacts in the preview window. In normal operation, + /// the preview will show the camera output prior to being compressed. This option is not guaranteed to work in future releases. + /// + /// + /// true if [capture display preview encoded]; otherwise, false. + /// + public Boolean CaptureDisplayPreviewEncoded { get; set; } = false; + + /// + public override String CreateProcessArguments() { + StringBuilder sb = new StringBuilder(base.CreateProcessArguments()); + + if(this.Fullscreen) { + _ = sb.Append(" -f"); + } + + if(this.LengthInSeconds != 0) { + _ = sb.Append($" -t {this.LengthInSeconds}"); + } + + if(!String.IsNullOrEmpty(this.VideoFileName)) { + _ = sb.Append($" -o {this.VideoFileName}"); + } + + return sb.ToString(); + } + } } \ No newline at end of file diff --git a/Unosquare.RaspberryIO/Camera/Enums.cs b/Unosquare.RaspberryIO/Camera/Enums.cs index f77ca6e..0c49868 100644 --- a/Unosquare.RaspberryIO/Camera/Enums.cs +++ b/Unosquare.RaspberryIO/Camera/Enums.cs @@ -1,423 +1,413 @@ -namespace Unosquare.RaspberryIO.Camera -{ - using System; - +namespace Unosquare.RaspberryIO.Camera { + using System; + + /// + /// Defines the available encoding formats for the Raspberry Pi camera module. + /// + public enum CameraImageEncodingFormat { /// - /// Defines the available encoding formats for the Raspberry Pi camera module. + /// The JPG /// - public enum CameraImageEncodingFormat - { - /// - /// The JPG - /// - Jpg, - - /// - /// The BMP - /// - Bmp, - - /// - /// The GIF - /// - Gif, - - /// - /// The PNG - /// - Png - } - + Jpg, + /// - /// Defines the different exposure modes for the Raspberry Pi's camera module. + /// The BMP /// - public enum CameraExposureMode - { - /// - /// The automatic - /// - Auto, - - /// - /// The night - /// - Night, - - /// - /// The night preview - /// - NightPreview, - - /// - /// The backlight - /// - Backlight, - - /// - /// The spotlight - /// - Spotlight, - - /// - /// The sports - /// - Sports, - - /// - /// The snow - /// - Snow, - - /// - /// The beach - /// - Beach, - - /// - /// The very long - /// - VeryLong, - - /// - /// The fixed FPS - /// - FixedFps, - - /// - /// The anti shake - /// - AntiShake, - - /// - /// The fireworks - /// - Fireworks - } - + Bmp, + /// - /// Defines the different AWB (Auto White Balance) modes for the Raspberry Pi's camera module. + /// The GIF /// - public enum CameraWhiteBalanceMode - { - /// - /// No white balance - /// - Off, - - /// - /// The automatic - /// - Auto, - - /// - /// The sun - /// - Sun, - - /// - /// The cloud - /// - Cloud, - - /// - /// The shade - /// - Shade, - - /// - /// The tungsten - /// - Tungsten, - - /// - /// The fluorescent - /// - Fluorescent, - - /// - /// The incandescent - /// - Incandescent, - - /// - /// The flash - /// - Flash, - - /// - /// The horizon - /// - Horizon - } - + Gif, + /// - /// Defines the available image effects for the Raspberry Pi's camera module. + /// The PNG /// - public enum CameraImageEffect - { - /// - /// No effect - /// - None, - - /// - /// The negative - /// - Negative, - - /// - /// The solarise - /// - Solarise, - - /// - /// The whiteboard - /// - Whiteboard, - - /// - /// The blackboard - /// - Blackboard, - - /// - /// The sketch - /// - Sketch, - - /// - /// The denoise - /// - Denoise, - - /// - /// The emboss - /// - Emboss, - - /// - /// The oil paint - /// - OilPaint, - - /// - /// The hatch - /// - Hatch, - - /// - /// Graphite Pen - /// - GPen, - - /// - /// The pastel - /// - Pastel, - - /// - /// The water colour - /// - WaterColour, - - /// - /// The film - /// - Film, - - /// - /// The blur - /// - Blur, - - /// - /// The saturation - /// - Saturation, - - /// - /// The solour swap - /// - SolourSwap, - - /// - /// The washed out - /// - WashedOut, - - /// - /// The colour point - /// - ColourPoint, - - /// - /// The colour balance - /// - ColourBalance, - - /// - /// The cartoon - /// - Cartoon - } - + Png + } + + /// + /// Defines the different exposure modes for the Raspberry Pi's camera module. + /// + public enum CameraExposureMode { /// - /// Defines the different metering modes for the Raspberry Pi's camera module. + /// The automatic /// - public enum CameraMeteringMode - { - /// - /// The average - /// - Average, - - /// - /// The spot - /// - Spot, - - /// - /// The backlit - /// - Backlit, - - /// - /// The matrix - /// - Matrix - } - + Auto, + /// - /// Defines the different image rotation modes for the Raspberry Pi's camera module. + /// The night /// - public enum CameraImageRotation - { - /// - /// No rerotation - /// - None = 0, - - /// - /// 90 Degrees - /// - Degrees90 = 90, - - /// - /// 180 Degrees - /// - Degrees180 = 180, - - /// - /// 270 degrees - /// - Degrees270 = 270 - } - + Night, + /// - /// Defines the different DRC (Dynamic Range Compensation) modes for the Raspberry Pi's camera module - /// Helpful for low light photos. + /// The night preview /// - public enum CameraDynamicRangeCompensation - { - /// - /// The off setting - /// - Off, - - /// - /// The low - /// - Low, - - /// - /// The medium - /// - Medium, - - /// - /// The high - /// - High - } - + NightPreview, + /// - /// Defines the bit-wise mask flags for the available annotation elements for the Raspberry Pi's camera module. + /// The backlight /// - [Flags] - public enum CameraAnnotation - { - /// - /// The none - /// - None = 0, - - /// - /// The time - /// - Time = 4, - - /// - /// The date - /// - Date = 8, - - /// - /// The shutter settings - /// - ShutterSettings = 16, - - /// - /// The caf settings - /// - CafSettings = 32, - - /// - /// The gain settings - /// - GainSettings = 64, - - /// - /// The lens settings - /// - LensSettings = 128, - - /// - /// The motion settings - /// - MotionSettings = 256, - - /// - /// The frame number - /// - FrameNumber = 512, - - /// - /// The solid background - /// - SolidBackground = 1024 - } - + Backlight, + /// - /// Defines the different H.264 encoding profiles to be used when capturing video. + /// The spotlight /// - public enum CameraH264Profile - { - /// - /// BP: Primarily for lower-cost applications with limited computing resources, - /// this profile is used widely in videoconferencing and mobile applications. - /// - Baseline, - - /// - /// MP: Originally intended as the mainstream consumer profile for broadcast - /// and storage applications, the importance of this profile faded when the High profile was developed for those applications. - /// - Main, - - /// - /// HiP: The primary profile for broadcast and disc storage applications, particularly - /// for high-definition television applications (this is the profile adopted into HD DVD and Blu-ray Disc, for example). - /// - High - } + Spotlight, + + /// + /// The sports + /// + Sports, + + /// + /// The snow + /// + Snow, + + /// + /// The beach + /// + Beach, + + /// + /// The very long + /// + VeryLong, + + /// + /// The fixed FPS + /// + FixedFps, + + /// + /// The anti shake + /// + AntiShake, + + /// + /// The fireworks + /// + Fireworks + } + + /// + /// Defines the different AWB (Auto White Balance) modes for the Raspberry Pi's camera module. + /// + public enum CameraWhiteBalanceMode { + /// + /// No white balance + /// + Off, + + /// + /// The automatic + /// + Auto, + + /// + /// The sun + /// + Sun, + + /// + /// The cloud + /// + Cloud, + + /// + /// The shade + /// + Shade, + + /// + /// The tungsten + /// + Tungsten, + + /// + /// The fluorescent + /// + Fluorescent, + + /// + /// The incandescent + /// + Incandescent, + + /// + /// The flash + /// + Flash, + + /// + /// The horizon + /// + Horizon + } + + /// + /// Defines the available image effects for the Raspberry Pi's camera module. + /// + public enum CameraImageEffect { + /// + /// No effect + /// + None, + + /// + /// The negative + /// + Negative, + + /// + /// The solarise + /// + Solarise, + + /// + /// The whiteboard + /// + Whiteboard, + + /// + /// The blackboard + /// + Blackboard, + + /// + /// The sketch + /// + Sketch, + + /// + /// The denoise + /// + Denoise, + + /// + /// The emboss + /// + Emboss, + + /// + /// The oil paint + /// + OilPaint, + + /// + /// The hatch + /// + Hatch, + + /// + /// Graphite Pen + /// + GPen, + + /// + /// The pastel + /// + Pastel, + + /// + /// The water colour + /// + WaterColour, + + /// + /// The film + /// + Film, + + /// + /// The blur + /// + Blur, + + /// + /// The saturation + /// + Saturation, + + /// + /// The solour swap + /// + SolourSwap, + + /// + /// The washed out + /// + WashedOut, + + /// + /// The colour point + /// + ColourPoint, + + /// + /// The colour balance + /// + ColourBalance, + + /// + /// The cartoon + /// + Cartoon + } + + /// + /// Defines the different metering modes for the Raspberry Pi's camera module. + /// + public enum CameraMeteringMode { + /// + /// The average + /// + Average, + + /// + /// The spot + /// + Spot, + + /// + /// The backlit + /// + Backlit, + + /// + /// The matrix + /// + Matrix + } + + /// + /// Defines the different image rotation modes for the Raspberry Pi's camera module. + /// + public enum CameraImageRotation { + /// + /// No rerotation + /// + None = 0, + + /// + /// 90 Degrees + /// + Degrees90 = 90, + + /// + /// 180 Degrees + /// + Degrees180 = 180, + + /// + /// 270 degrees + /// + Degrees270 = 270 + } + + /// + /// Defines the different DRC (Dynamic Range Compensation) modes for the Raspberry Pi's camera module + /// Helpful for low light photos. + /// + public enum CameraDynamicRangeCompensation { + /// + /// The off setting + /// + Off, + + /// + /// The low + /// + Low, + + /// + /// The medium + /// + Medium, + + /// + /// The high + /// + High + } + + /// + /// Defines the bit-wise mask flags for the available annotation elements for the Raspberry Pi's camera module. + /// + [Flags] + public enum CameraAnnotation { + /// + /// The none + /// + None = 0, + + /// + /// The time + /// + Time = 4, + + /// + /// The date + /// + Date = 8, + + /// + /// The shutter settings + /// + ShutterSettings = 16, + + /// + /// The caf settings + /// + CafSettings = 32, + + /// + /// The gain settings + /// + GainSettings = 64, + + /// + /// The lens settings + /// + LensSettings = 128, + + /// + /// The motion settings + /// + MotionSettings = 256, + + /// + /// The frame number + /// + FrameNumber = 512, + + /// + /// The solid background + /// + SolidBackground = 1024 + } + + /// + /// Defines the different H.264 encoding profiles to be used when capturing video. + /// + public enum CameraH264Profile { + /// + /// BP: Primarily for lower-cost applications with limited computing resources, + /// this profile is used widely in videoconferencing and mobile applications. + /// + Baseline, + + /// + /// MP: Originally intended as the mainstream consumer profile for broadcast + /// and storage applications, the importance of this profile faded when the High profile was developed for those applications. + /// + Main, + + /// + /// HiP: The primary profile for broadcast and disc storage applications, particularly + /// for high-definition television applications (this is the profile adopted into HD DVD and Blu-ray Disc, for example). + /// + High + } } diff --git a/Unosquare.RaspberryIO/Computer/AudioSettings.cs b/Unosquare.RaspberryIO/Computer/AudioSettings.cs index 206cd88..fb10e05 100644 --- a/Unosquare.RaspberryIO/Computer/AudioSettings.cs +++ b/Unosquare.RaspberryIO/Computer/AudioSettings.cs @@ -1,113 +1,104 @@ -namespace Unosquare.RaspberryIO.Computer -{ - using Swan; - using System; - using System.Linq; - using System.Threading.Tasks; - +using System; +using System.Linq; +using System.Threading.Tasks; + +using Swan; + +namespace Unosquare.RaspberryIO.Computer { + /// + /// Settings for audio device. + /// + public class AudioSettings : SingletonBase { + private const String DefaultControlName = "PCM"; + private const Int32 DefaultCardNumber = 0; + + private readonly String[] _errorMess = { "Invalid", "Unable" }; + /// - /// Settings for audio device. + /// Gets the current audio state. /// - public class AudioSettings : SingletonBase - { - private const string DefaultControlName = "PCM"; - private const int DefaultCardNumber = 0; - - private readonly string[] _errorMess = { "Invalid", "Unable" }; - - /// - /// Gets the current audio state. - /// - /// The card number. - /// Name of the control. - /// An object. - /// Invalid command, card number or control name. - public async Task GetState(int cardNumber = DefaultCardNumber, string controlName = DefaultControlName) - { - var volumeInfo = await ProcessRunner.GetProcessOutputAsync("amixer", $"-c {cardNumber} get {controlName}").ConfigureAwait(false); - - var lines = volumeInfo.Split(new[] { '\n' }, StringSplitOptions.RemoveEmptyEntries); - - if (!lines.Any()) - throw new InvalidOperationException("Invalid command."); - - if (_errorMess.Any(x => lines[0].Contains(x))) - throw new InvalidOperationException(lines[0]); - - var volumeLine = lines - .FirstOrDefault(x => x.Trim() - .StartsWith("Mono:", StringComparison.OrdinalIgnoreCase)); - - if (volumeLine == null) - throw new InvalidOperationException("Unexpected output from 'amixer'."); - - var sections = volumeLine.Split(new[] { ' ' }, - StringSplitOptions.RemoveEmptyEntries); - - var level = int.Parse(sections[3].Substring(1, sections[3].Length - 3), - System.Globalization.NumberFormatInfo.InvariantInfo); - - var decibels = float.Parse(sections[4].Substring(1, sections[4].Length - 4), - System.Globalization.NumberFormatInfo.InvariantInfo); - - var isMute = sections[5].Equals("[off]", - StringComparison.CurrentCultureIgnoreCase); - - return new AudioState(cardNumber, controlName, level, decibels, isMute); - } - - /// - /// Sets the volume percentage. - /// - /// The percentage level. - /// The card number. - /// Name of the control. - /// A representing the asynchronous operation. - /// Invalid card number or control name. - public Task SetVolumePercentage(int level, int cardNumber = DefaultCardNumber, string controlName = DefaultControlName) => - SetAudioCommand($"{level}%", cardNumber, controlName); - - /// - /// Sets the volume by decibels. - /// - /// The decibels. - /// The card number. - /// Name of the control. - /// A representing the asynchronous operation. - /// Invalid card number or control name. - public Task SetVolumeByDecibels(float decibels, int cardNumber = DefaultCardNumber, string controlName = DefaultControlName) => - SetAudioCommand($"{decibels}dB", cardNumber, controlName); - - /// - /// Increments the volume by decibels. - /// - /// The decibels to increment or decrement. - /// The card number. - /// Name of the control. - /// A representing the asynchronous operation. - /// Invalid card number or control name. - public Task IncrementVolume(float decibels, int cardNumber = DefaultCardNumber, string controlName = DefaultControlName) => - SetAudioCommand($"{decibels}dB{(decibels < 0 ? "-" : "+")}", cardNumber, controlName); - - /// - /// Toggles the mute state. - /// - /// if set to true, mutes the audio. - /// The card number. - /// Name of the control. - /// A representing the asynchronous operation. - /// Invalid card number or control name. - public Task ToggleMute(bool mute, int cardNumber = DefaultCardNumber, string controlName = DefaultControlName) => - SetAudioCommand(mute ? "mute" : "unmute", cardNumber, controlName); - - private static async Task SetAudioCommand(string command, int cardNumber = DefaultCardNumber, string controlName = DefaultControlName) - { - var taskResult = await ProcessRunner.GetProcessOutputAsync("amixer", $"-q -c {cardNumber} -- set {controlName} {command}").ConfigureAwait(false); - - if (!string.IsNullOrWhiteSpace(taskResult)) - throw new InvalidOperationException(taskResult.Split(new[] { '\n' }, StringSplitOptions.RemoveEmptyEntries).First()); - - return taskResult; - } - } + /// The card number. + /// Name of the control. + /// An object. + /// Invalid command, card number or control name. + public async Task GetState(Int32 cardNumber = DefaultCardNumber, String controlName = DefaultControlName) { + String volumeInfo = await ProcessRunner.GetProcessOutputAsync("amixer", $"-c {cardNumber} get {controlName}").ConfigureAwait(false); + + String[] lines = volumeInfo.Split(new[] { '\n' }, StringSplitOptions.RemoveEmptyEntries); + + if(!lines.Any()) { + throw new InvalidOperationException("Invalid command."); + } + + if(this._errorMess.Any(x => lines[0].Contains(x))) { + throw new InvalidOperationException(lines[0]); + } + + String volumeLine = lines.FirstOrDefault(x => x.Trim().StartsWith("Mono:", StringComparison.OrdinalIgnoreCase)); + + if(volumeLine == null) { + throw new InvalidOperationException("Unexpected output from 'amixer'."); + } + + String[] sections = volumeLine.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); + + Int32 level = Int32.Parse(sections[3][1..^2], System.Globalization.NumberFormatInfo.InvariantInfo); + + Single decibels = Single.Parse(sections[4][1..^3], System.Globalization.NumberFormatInfo.InvariantInfo); + + Boolean isMute = sections[5].Equals("[off]", StringComparison.CurrentCultureIgnoreCase); + + return new AudioState(cardNumber, controlName, level, decibels, isMute); + } + + /// + /// Sets the volume percentage. + /// + /// The percentage level. + /// The card number. + /// Name of the control. + /// A representing the asynchronous operation. + /// Invalid card number or control name. + public Task SetVolumePercentage(Int32 level, Int32 cardNumber = DefaultCardNumber, String controlName = DefaultControlName) => SetAudioCommand($"{level}%", cardNumber, controlName); + + /// + /// Sets the volume by decibels. + /// + /// The decibels. + /// The card number. + /// Name of the control. + /// A representing the asynchronous operation. + /// Invalid card number or control name. + public Task SetVolumeByDecibels(Single decibels, Int32 cardNumber = DefaultCardNumber, String controlName = DefaultControlName) => SetAudioCommand($"{decibels}dB", cardNumber, controlName); + + /// + /// Increments the volume by decibels. + /// + /// The decibels to increment or decrement. + /// The card number. + /// Name of the control. + /// A representing the asynchronous operation. + /// Invalid card number or control name. + public Task IncrementVolume(Single decibels, Int32 cardNumber = DefaultCardNumber, String controlName = DefaultControlName) => SetAudioCommand($"{decibels}dB{(decibels < 0 ? "-" : "+")}", cardNumber, controlName); + + /// + /// Toggles the mute state. + /// + /// if set to true, mutes the audio. + /// The card number. + /// Name of the control. + /// A representing the asynchronous operation. + /// Invalid card number or control name. + public Task ToggleMute(Boolean mute, Int32 cardNumber = DefaultCardNumber, String controlName = DefaultControlName) => SetAudioCommand(mute ? "mute" : "unmute", cardNumber, controlName); + + private static async Task SetAudioCommand(String command, Int32 cardNumber = DefaultCardNumber, String controlName = DefaultControlName) { + String taskResult = await ProcessRunner.GetProcessOutputAsync("amixer", $"-q -c {cardNumber} -- set {controlName} {command}").ConfigureAwait(false); + + if(!String.IsNullOrWhiteSpace(taskResult)) { + throw new InvalidOperationException(taskResult.Split(new[] { '\n' }, StringSplitOptions.RemoveEmptyEntries).First()); + } + + return taskResult; + } + } } diff --git a/Unosquare.RaspberryIO/Computer/AudioState.cs b/Unosquare.RaspberryIO/Computer/AudioState.cs index ab71777..62d302e 100644 --- a/Unosquare.RaspberryIO/Computer/AudioState.cs +++ b/Unosquare.RaspberryIO/Computer/AudioState.cs @@ -1,64 +1,72 @@ -namespace Unosquare.RaspberryIO.Computer -{ +using System; + +namespace Unosquare.RaspberryIO.Computer { + /// + /// Manage the volume of any sound device. + /// + public readonly struct AudioState { /// - /// Manage the volume of any sound device. + /// Initializes a new instance of the struct. /// - public readonly struct AudioState - { - /// - /// Initializes a new instance of the struct. - /// - /// The card number. - /// Name of the control. - /// The volume level in percentaje. - /// The volume level in decibels. - /// if set to true the audio is mute. - public AudioState(int cardNumber, string controlName, int level, float decibels, bool isMute) - { - CardNumber = cardNumber; - ControlName = controlName; - Level = level; - Decibels = decibels; - IsMute = isMute; - } - - /// - /// Gets the card number. - /// - public int CardNumber { get; } - - /// - /// Gets the name of the current control. - /// - public string ControlName { get; } - - /// - /// Gets the volume level in percentage. - /// - public int Level { get; } - - /// - /// Gets the volume level in decibels. - /// - public float Decibels { get; } - - /// - /// Gets a value indicating whether the audio is mute. - /// - public bool IsMute { get; } - - /// - /// Returns a that represents the audio state. - /// - /// - /// A that represents the audio state. - /// - public override string ToString() => - "Device information: \n" + - $">> Name: {ControlName}\n" + - $">> Card number: {CardNumber}\n" + - $">> Volume (%): {Level}%\n" + - $">> Volume (dB): {Decibels:0.00}dB\n" + - $">> Mute: [{(IsMute ? "Off" : "On")}]\n\n"; - } + /// The card number. + /// Name of the control. + /// The volume level in percentaje. + /// The volume level in decibels. + /// if set to true the audio is mute. + public AudioState(Int32 cardNumber, String controlName, Int32 level, Single decibels, Boolean isMute) { + this.CardNumber = cardNumber; + this.ControlName = controlName; + this.Level = level; + this.Decibels = decibels; + this.IsMute = isMute; + } + + /// + /// Gets the card number. + /// + public Int32 CardNumber { + get; + } + + /// + /// Gets the name of the current control. + /// + public String ControlName { + get; + } + + /// + /// Gets the volume level in percentage. + /// + public Int32 Level { + get; + } + + /// + /// Gets the volume level in decibels. + /// + public Single Decibels { + get; + } + + /// + /// Gets a value indicating whether the audio is mute. + /// + public Boolean IsMute { + get; + } + + /// + /// Returns a that represents the audio state. + /// + /// + /// A that represents the audio state. + /// + public override String ToString() => "Device information: \n" + + $">> Name: {this.ControlName}\n" + + $">> Card number: {this.CardNumber}\n" + + $">> Volume (%): {this.Level}%\n" + + $">> Volume (dB): {this.Decibels:0.00}dB\n" + + $">> Mute: [{(this.IsMute ? "Off" : "On")}]\n\n"; + } } \ No newline at end of file diff --git a/Unosquare.RaspberryIO/Computer/Bluetooth.cs b/Unosquare.RaspberryIO/Computer/Bluetooth.cs index beee700..8b6023e 100644 --- a/Unosquare.RaspberryIO/Computer/Bluetooth.cs +++ b/Unosquare.RaspberryIO/Computer/Bluetooth.cs @@ -1,271 +1,201 @@ -namespace Unosquare.RaspberryIO.Computer -{ - using System; - using System.Collections.Generic; - using System.Linq; - using System.Threading; - using System.Threading.Tasks; - using Swan; - +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +using Swan; + +namespace Unosquare.RaspberryIO.Computer { + /// + /// Represents the Bluetooth information. + /// + public class Bluetooth : SingletonBase { + private const String BcCommand = "bluetoothctl"; + /// - /// Represents the Bluetooth information. + /// Turns on the Bluetooth adapter. /// - public class Bluetooth : SingletonBase - { - private const string BcCommand = "bluetoothctl"; - - /// - /// Turns on the Bluetooth adapter. - /// - /// The cancellation token. - /// - /// Returns true or false depending if the controller was turned on. - /// - /// Failed to power on:. - public async Task PowerOn(CancellationToken cancellationToken = default) - { - try - { - var output = await ProcessRunner.GetProcessOutputAsync(BcCommand, "power on", null, cancellationToken) - .ConfigureAwait(false); - return output.Contains("succeeded"); - } - catch (Exception ex) - { - throw new BluetoothErrorException($"Failed to power on: {ex.Message}"); - } - } - - /// - /// Turns off the bluetooth adapter. - /// - /// The cancellation token. - /// - /// Returns true or false depending if the controller was turned off. - /// - /// Failed to power off:. - public async Task PowerOff(CancellationToken cancellationToken = default) - { - try - { - var output = await ProcessRunner.GetProcessOutputAsync(BcCommand, "power off", null, cancellationToken) - .ConfigureAwait(false); - return output.Contains("succeeded"); - } - catch (Exception ex) - { - throw new BluetoothErrorException($"Failed to power off: {ex.Message}"); - } - } - - /// - /// Gets the list of detected devices. - /// - /// The cancellation token. - /// - /// Returns the list of detected devices. - /// - /// Failed to retrieve devices:. - public async Task> ListDevices(CancellationToken cancellationToken = default) - { - try - { - using var cancellationTokenSource = new CancellationTokenSource(3000); - await ProcessRunner.GetProcessOutputAsync(BcCommand, "scan on", null, cancellationTokenSource.Token) - .ConfigureAwait(false); - await ProcessRunner.GetProcessOutputAsync(BcCommand, "scan off", null, cancellationToken) - .ConfigureAwait(false); - var devices = await ProcessRunner.GetProcessOutputAsync(BcCommand, "devices", null, cancellationToken) - .ConfigureAwait(false); - return devices.Trim().Split('\n').Select(x => x.Trim()); - } - catch (Exception ex) - { - throw new BluetoothErrorException($"Failed to retrieve devices: {ex.Message}"); - } - } - - /// - /// Gets the list of bluetooth controllers. - /// - /// The cancellation token. - /// - /// Returns the list of bluetooth controllers. - /// - /// Failed to retrieve controllers:. - public async Task> ListControllers(CancellationToken cancellationToken = default) - { - try - { - var controllers = await ProcessRunner.GetProcessOutputAsync(BcCommand, "list", null, cancellationToken) - .ConfigureAwait(false); - return controllers.Trim().Split('\n').Select(x => x.Trim()); - } - catch (Exception ex) - { - throw new BluetoothErrorException($"Failed to retrieve controllers: {ex.Message}"); - } - } - - /// - /// Pairs a specific device with a specific controller. - /// - /// The mac address of the controller that will be used to pair. - /// The mac address of the device that will be paired. - /// The cancellation token. - /// - /// Returns true or false if the pair was successfully. - /// - /// Failed to Pair:. - public async Task Pair( - string controllerAddress, - string deviceAddress, - CancellationToken cancellationToken = default) - { - try - { - // Selects the controller to pair. Once you select the controller, all controller-related commands will apply to it for three minutes. - await ProcessRunner - .GetProcessOutputAsync(BcCommand, $"select {controllerAddress}", null, cancellationToken) - .ConfigureAwait(false); - - // Makes the controller visible to other devices. - await ProcessRunner.GetProcessOutputAsync(BcCommand, "discoverable on", null, cancellationToken) - .ConfigureAwait(false); - - // Readies the controller for pairing. Remember that you have three minutes after running this command to pair. - await ProcessRunner.GetProcessOutputAsync(BcCommand, "pairable on", null, cancellationToken) - .ConfigureAwait(false); - - // Pairs the device with the controller. - var result = await ProcessRunner - .GetProcessOutputAsync(BcCommand, $"pair {deviceAddress}", null, cancellationToken) - .ConfigureAwait(false); - - // Hides the controller from other Bluetooth devices. Otherwise, any device that can detect it has access to it, leaving a major security hole. - await ProcessRunner.GetProcessOutputAsync(BcCommand, "discoverable off", null, cancellationToken) - .ConfigureAwait(false); - - return result.Contains("Paired: yes"); - } - catch (Exception ex) - { - throw new BluetoothErrorException($"Failed to Pair: {ex.Message}"); - } - } - - /// - /// Performs a connection of a given controller with a given device. - /// - /// The mac address of the controller that will be used to make the connection. - /// The mac address of the device that will be connected. - /// The cancellation token. - /// - /// Returns true or false if the connection was successfully. - /// - /// Failed to connect:. - public async Task Connect( - string controllerAddress, - string deviceAddress, - CancellationToken cancellationToken = default) - { - try - { - // Selects the controller to pair. Once you select the controller, all controller-related commands will apply to it for three minutes. - await ProcessRunner - .GetProcessOutputAsync(BcCommand, $"select {controllerAddress}", null, cancellationToken) - .ConfigureAwait(false); - - // Makes the controller visible to other devices. - await ProcessRunner.GetProcessOutputAsync(BcCommand, "discoverable on", null, cancellationToken) - .ConfigureAwait(false); - - // Readies the controller for pairing. Remember that you have three minutes after running this command to pair. - await ProcessRunner.GetProcessOutputAsync(BcCommand, "pairable on", null, cancellationToken) - .ConfigureAwait(false); - - // Readies the device for pairing. - var result = await ProcessRunner - .GetProcessOutputAsync(BcCommand, $"connect {deviceAddress}", null, cancellationToken) - .ConfigureAwait(false); - - // Hides the controller from other Bluetooth devices. Otherwise, any device that can detect it has access to it, leaving a major security hole. - await ProcessRunner.GetProcessOutputAsync(BcCommand, "discoverable off", null, cancellationToken) - .ConfigureAwait(false); - - return result.Contains("Connected: yes"); - } - catch (Exception ex) - { - throw new BluetoothErrorException($"Failed to connect: {ex.Message}"); - } - } - - /// - /// Sets the device to re-pair automatically when it is turned on, which eliminates the need to pair all over again. - /// - /// The mac address of the controller will be used. - /// The mac address of the device will be added to the trust list devices. - /// The cancellation token. - /// - /// Returns true or false if the operation was successful. - /// - /// Failed to add to trust devices list:. - public async Task Trust( - string controllerAddress, - string deviceAddress, - CancellationToken cancellationToken = default) - { - try - { - // Selects the controller to pair. Once you select the controller, all controller-related commands will apply to it for three minutes. - await ProcessRunner - .GetProcessOutputAsync(BcCommand, $"select {controllerAddress}", null, cancellationToken) - .ConfigureAwait(false); - - // Makes the controller visible to other devices. - await ProcessRunner.GetProcessOutputAsync(BcCommand, "discoverable on", null, cancellationToken) - .ConfigureAwait(false); - - // Readies the controller for pairing. Remember that you have three minutes after running this command to pair. - await ProcessRunner.GetProcessOutputAsync(BcCommand, "pairable on", null, cancellationToken) - .ConfigureAwait(false); - - // Sets the device to re-pair automatically when it is turned on, which eliminates the need to pair all over again. - var result = await ProcessRunner - .GetProcessOutputAsync(BcCommand, $"trust {deviceAddress}", null, cancellationToken) - .ConfigureAwait(false); - - // Hides the controller from other Bluetooth devices. Otherwise, any device that can detect it has access to it, leaving a major security hole. - await ProcessRunner.GetProcessOutputAsync(BcCommand, "discoverable off", null, cancellationToken) - .ConfigureAwait(false); - - return result.Contains("Trusted: yes"); - } - catch (Exception ex) - { - throw new BluetoothErrorException($"Failed to add to trust devices list: {ex.Message}"); - } - } - - /// - /// Displays information about a particular device. - /// - /// The mac address of the device which info will be retrieved. - /// The cancellation token. - /// - /// Returns the device info. - /// - /// Failed to retrieve info for {deviceAddress}. - public async Task DeviceInfo(string deviceAddress, CancellationToken cancellationToken = default) - { - var info = await ProcessRunner - .GetProcessOutputAsync(BcCommand, $"info {deviceAddress}", null, cancellationToken) - .ConfigureAwait(false); - - return !string.IsNullOrEmpty(info) - ? info - : throw new BluetoothErrorException($"Failed to retrieve info for {deviceAddress}"); - } - } + /// The cancellation token. + /// + /// Returns true or false depending if the controller was turned on. + /// + /// Failed to power on:. + public async Task PowerOn(CancellationToken cancellationToken = default) { + try { + String output = await ProcessRunner.GetProcessOutputAsync(BcCommand, "power on", null, cancellationToken).ConfigureAwait(false); + return output.Contains("succeeded"); + } catch(Exception ex) { + throw new BluetoothErrorException($"Failed to power on: {ex.Message}"); + } + } + + /// + /// Turns off the bluetooth adapter. + /// + /// The cancellation token. + /// + /// Returns true or false depending if the controller was turned off. + /// + /// Failed to power off:. + public async Task PowerOff(CancellationToken cancellationToken = default) { + try { + String output = await ProcessRunner.GetProcessOutputAsync(BcCommand, "power off", null, cancellationToken).ConfigureAwait(false); + return output.Contains("succeeded"); + } catch(Exception ex) { + throw new BluetoothErrorException($"Failed to power off: {ex.Message}"); + } + } + + /// + /// Gets the list of detected devices. + /// + /// The cancellation token. + /// + /// Returns the list of detected devices. + /// + /// Failed to retrieve devices:. + public async Task> ListDevices(CancellationToken cancellationToken = default) { + try { + using CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(3000); + _ = await ProcessRunner.GetProcessOutputAsync(BcCommand, "scan on", null, cancellationTokenSource.Token).ConfigureAwait(false); + _ = await ProcessRunner.GetProcessOutputAsync(BcCommand, "scan off", null, cancellationToken).ConfigureAwait(false); + String devices = await ProcessRunner.GetProcessOutputAsync(BcCommand, "devices", null, cancellationToken).ConfigureAwait(false); + return devices.Trim().Split('\n').Select(x => x.Trim()); + } catch(Exception ex) { + throw new BluetoothErrorException($"Failed to retrieve devices: {ex.Message}"); + } + } + + /// + /// Gets the list of bluetooth controllers. + /// + /// The cancellation token. + /// + /// Returns the list of bluetooth controllers. + /// + /// Failed to retrieve controllers:. + public async Task> ListControllers(CancellationToken cancellationToken = default) { + try { + String controllers = await ProcessRunner.GetProcessOutputAsync(BcCommand, "list", null, cancellationToken).ConfigureAwait(false); + return controllers.Trim().Split('\n').Select(x => x.Trim()); + } catch(Exception ex) { + throw new BluetoothErrorException($"Failed to retrieve controllers: {ex.Message}"); + } + } + + /// + /// Pairs a specific device with a specific controller. + /// + /// The mac address of the controller that will be used to pair. + /// The mac address of the device that will be paired. + /// The cancellation token. + /// + /// Returns true or false if the pair was successfully. + /// + /// Failed to Pair:. + public async Task Pair(String controllerAddress, String deviceAddress, CancellationToken cancellationToken = default) { + try { + // Selects the controller to pair. Once you select the controller, all controller-related commands will apply to it for three minutes. + _ = await ProcessRunner.GetProcessOutputAsync(BcCommand, $"select {controllerAddress}", null, cancellationToken).ConfigureAwait(false); + + // Makes the controller visible to other devices. + _ = await ProcessRunner.GetProcessOutputAsync(BcCommand, "discoverable on", null, cancellationToken).ConfigureAwait(false); + + // Readies the controller for pairing. Remember that you have three minutes after running this command to pair. + _ = await ProcessRunner.GetProcessOutputAsync(BcCommand, "pairable on", null, cancellationToken).ConfigureAwait(false); + + // Pairs the device with the controller. + String result = await ProcessRunner.GetProcessOutputAsync(BcCommand, $"pair {deviceAddress}", null, cancellationToken).ConfigureAwait(false); + + // Hides the controller from other Bluetooth devices. Otherwise, any device that can detect it has access to it, leaving a major security hole. + _ = await ProcessRunner.GetProcessOutputAsync(BcCommand, "discoverable off", null, cancellationToken).ConfigureAwait(false); + + return result.Contains("Paired: yes"); + } catch(Exception ex) { + throw new BluetoothErrorException($"Failed to Pair: {ex.Message}"); + } + } + + /// + /// Performs a connection of a given controller with a given device. + /// + /// The mac address of the controller that will be used to make the connection. + /// The mac address of the device that will be connected. + /// The cancellation token. + /// + /// Returns true or false if the connection was successfully. + /// + /// Failed to connect:. + public async Task Connect(String controllerAddress, String deviceAddress, CancellationToken cancellationToken = default) { + try { + // Selects the controller to pair. Once you select the controller, all controller-related commands will apply to it for three minutes. + _ = await ProcessRunner.GetProcessOutputAsync(BcCommand, $"select {controllerAddress}", null, cancellationToken).ConfigureAwait(false); + + // Makes the controller visible to other devices. + _ = await ProcessRunner.GetProcessOutputAsync(BcCommand, "discoverable on", null, cancellationToken).ConfigureAwait(false); + + // Readies the controller for pairing. Remember that you have three minutes after running this command to pair. + _ = await ProcessRunner.GetProcessOutputAsync(BcCommand, "pairable on", null, cancellationToken).ConfigureAwait(false); + + // Readies the device for pairing. + String result = await ProcessRunner.GetProcessOutputAsync(BcCommand, $"connect {deviceAddress}", null, cancellationToken).ConfigureAwait(false); + + // Hides the controller from other Bluetooth devices. Otherwise, any device that can detect it has access to it, leaving a major security hole. + _ = await ProcessRunner.GetProcessOutputAsync(BcCommand, "discoverable off", null, cancellationToken).ConfigureAwait(false); + + return result.Contains("Connected: yes"); + } catch(Exception ex) { + throw new BluetoothErrorException($"Failed to connect: {ex.Message}"); + } + } + + /// + /// Sets the device to re-pair automatically when it is turned on, which eliminates the need to pair all over again. + /// + /// The mac address of the controller will be used. + /// The mac address of the device will be added to the trust list devices. + /// The cancellation token. + /// + /// Returns true or false if the operation was successful. + /// + /// Failed to add to trust devices list:. + public async Task Trust(String controllerAddress, String deviceAddress, CancellationToken cancellationToken = default) { + try { + // Selects the controller to pair. Once you select the controller, all controller-related commands will apply to it for three minutes. + _ = await ProcessRunner.GetProcessOutputAsync(BcCommand, $"select {controllerAddress}", null, cancellationToken).ConfigureAwait(false); + + // Makes the controller visible to other devices. + _ = await ProcessRunner.GetProcessOutputAsync(BcCommand, "discoverable on", null, cancellationToken).ConfigureAwait(false); + + // Readies the controller for pairing. Remember that you have three minutes after running this command to pair. + _ = await ProcessRunner.GetProcessOutputAsync(BcCommand, "pairable on", null, cancellationToken).ConfigureAwait(false); + + // Sets the device to re-pair automatically when it is turned on, which eliminates the need to pair all over again. + String result = await ProcessRunner.GetProcessOutputAsync(BcCommand, $"trust {deviceAddress}", null, cancellationToken).ConfigureAwait(false); + + // Hides the controller from other Bluetooth devices. Otherwise, any device that can detect it has access to it, leaving a major security hole. + _ = await ProcessRunner.GetProcessOutputAsync(BcCommand, "discoverable off", null, cancellationToken).ConfigureAwait(false); + + return result.Contains("Trusted: yes"); + } catch(Exception ex) { + throw new BluetoothErrorException($"Failed to add to trust devices list: {ex.Message}"); + } + } + + /// + /// Displays information about a particular device. + /// + /// The mac address of the device which info will be retrieved. + /// The cancellation token. + /// + /// Returns the device info. + /// + /// Failed to retrieve info for {deviceAddress}. + public async Task DeviceInfo(String deviceAddress, CancellationToken cancellationToken = default) { + String info = await ProcessRunner.GetProcessOutputAsync(BcCommand, $"info {deviceAddress}", null, cancellationToken).ConfigureAwait(false); + + return !String.IsNullOrEmpty(info) ? info : throw new BluetoothErrorException($"Failed to retrieve info for {deviceAddress}"); + } + } } diff --git a/Unosquare.RaspberryIO/Computer/DsiDisplay.cs b/Unosquare.RaspberryIO/Computer/DsiDisplay.cs index cd49aaf..7583336 100644 --- a/Unosquare.RaspberryIO/Computer/DsiDisplay.cs +++ b/Unosquare.RaspberryIO/Computer/DsiDisplay.cs @@ -1,71 +1,63 @@ -namespace Unosquare.RaspberryIO.Computer -{ - using System.Globalization; - using System.IO; - using Swan; - +using System; +using System.Globalization; +using System.IO; + +using Swan; + +namespace Unosquare.RaspberryIO.Computer { + /// + /// The Official Raspberry Pi 7-inch touch display from the foundation + /// Some docs available here: + /// http://forums.pimoroni.com/t/official-7-raspberry-pi-touch-screen-faq/959. + /// + public class DsiDisplay : SingletonBase { + private const String BacklightFilename = "/sys/class/backlight/rpi_backlight/bl_power"; + private const String BrightnessFilename = "/sys/class/backlight/rpi_backlight/brightness"; + /// - /// The Official Raspberry Pi 7-inch touch display from the foundation - /// Some docs available here: - /// http://forums.pimoroni.com/t/official-7-raspberry-pi-touch-screen-faq/959. + /// Prevents a default instance of the class from being created. /// - public class DsiDisplay : SingletonBase - { - private const string BacklightFilename = "/sys/class/backlight/rpi_backlight/bl_power"; - private const string BrightnessFilename = "/sys/class/backlight/rpi_backlight/brightness"; - - /// - /// Prevents a default instance of the class from being created. - /// - private DsiDisplay() - { - // placeholder - } - - /// - /// Gets a value indicating whether the Pi Foundation Display files are present. - /// - /// - /// true if this instance is present; otherwise, false. - /// - public bool IsPresent => File.Exists(BrightnessFilename); - - /// - /// Gets or sets the brightness of the DSI display via filesystem. - /// - /// - /// The brightness. - /// - public byte Brightness - { - get => - IsPresent - ? byte.TryParse(File.ReadAllText(BrightnessFilename).Trim(), out var brightness) ? brightness : (byte)0 : - (byte)0; - set - { - if (IsPresent) - File.WriteAllText(BrightnessFilename, value.ToString(CultureInfo.InvariantCulture)); - } - } - - /// - /// Gets or sets a value indicating whether the backlight of the DSI display on. - /// This operation is performed via the file system. - /// - /// - /// true if this instance is backlight on; otherwise, false. - /// - public bool IsBacklightOn - { - get => - IsPresent && (int.TryParse(File.ReadAllText(BacklightFilename).Trim(), out var value) && - value == 0); - set - { - if (IsPresent) - File.WriteAllText(BacklightFilename, value ? "0" : "1"); - } - } - } + private DsiDisplay() { + // placeholder + } + + /// + /// Gets a value indicating whether the Pi Foundation Display files are present. + /// + /// + /// true if this instance is present; otherwise, false. + /// + public Boolean IsPresent => File.Exists(BrightnessFilename); + + /// + /// Gets or sets the brightness of the DSI display via filesystem. + /// + /// + /// The brightness. + /// + public Byte Brightness { + get => this.IsPresent ? System.Byte.TryParse(File.ReadAllText(BrightnessFilename).Trim(), out Byte brightness) ? brightness : (Byte)0 : (Byte)0; + set { + if(this.IsPresent) { + File.WriteAllText(BrightnessFilename, value.ToString(CultureInfo.InvariantCulture)); + } + } + } + + /// + /// Gets or sets a value indicating whether the backlight of the DSI display on. + /// This operation is performed via the file system. + /// + /// + /// true if this instance is backlight on; otherwise, false. + /// + public Boolean IsBacklightOn { + get => this.IsPresent && Int32.TryParse(File.ReadAllText(BacklightFilename).Trim(), out Int32 value) && value == 0; + set { + if(this.IsPresent) { + File.WriteAllText(BacklightFilename, value ? "0" : "1"); + } + } + } + } } diff --git a/Unosquare.RaspberryIO/Computer/NetworkAdapterInfo.cs b/Unosquare.RaspberryIO/Computer/NetworkAdapterInfo.cs index 661a357..7b1c801 100644 --- a/Unosquare.RaspberryIO/Computer/NetworkAdapterInfo.cs +++ b/Unosquare.RaspberryIO/Computer/NetworkAdapterInfo.cs @@ -1,40 +1,51 @@ -namespace Unosquare.RaspberryIO.Computer -{ - using System.Net; - +using System; +using System.Net; + +namespace Unosquare.RaspberryIO.Computer { + /// + /// Represents a Network Adapter. + /// + public class NetworkAdapterInfo { /// - /// Represents a Network Adapter. + /// Gets the name. /// - public class NetworkAdapterInfo - { - /// - /// Gets the name. - /// - public string Name { get; internal set; } - - /// - /// Gets the IP V4 address. - /// - public IPAddress IPv4 { get; internal set; } - - /// - /// Gets the IP V6 address. - /// - public IPAddress IPv6 { get; internal set; } - - /// - /// Gets the name of the access point. - /// - public string AccessPointName { get; internal set; } - - /// - /// Gets the MAC (Physical) address. - /// - public string MacAddress { get; internal set; } - - /// - /// Gets a value indicating whether this instance is wireless. - /// - public bool IsWireless { get; internal set; } - } + public String? Name { + get; internal set; + } + + /// + /// Gets the IP V4 address. + /// + public IPAddress? IPv4 { + get; internal set; + } + + /// + /// Gets the IP V6 address. + /// + public IPAddress? IPv6 { + get; internal set; + } + + /// + /// Gets the name of the access point. + /// + public String? AccessPointName { + get; internal set; + } + + /// + /// Gets the MAC (Physical) address. + /// + public String? MacAddress { + get; internal set; + } + + /// + /// Gets a value indicating whether this instance is wireless. + /// + public Boolean IsWireless { + get; internal set; + } + } } diff --git a/Unosquare.RaspberryIO/Computer/NetworkSettings.cs b/Unosquare.RaspberryIO/Computer/NetworkSettings.cs index 9d88ff7..00f4480 100644 --- a/Unosquare.RaspberryIO/Computer/NetworkSettings.cs +++ b/Unosquare.RaspberryIO/Computer/NetworkSettings.cs @@ -1,281 +1,271 @@ -namespace Unosquare.RaspberryIO.Computer -{ - using Swan; - using Swan.Logging; - using Swan.Net; - using System; - using System.Collections.Generic; - using System.IO; - using System.Linq; - using System.Net; - using System.Text; - using System.Threading.Tasks; - +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net; +using System.Text; +using System.Threading.Tasks; + +using Swan; +using Swan.Logging; +using Swan.Net; + +namespace Unosquare.RaspberryIO.Computer { + /// + /// Represents the network information. + /// + public class NetworkSettings : SingletonBase { + private const String EssidTag = "ESSID:"; + /// - /// Represents the network information. + /// Gets the local machine Host Name. /// - public class NetworkSettings : SingletonBase - { - private const string EssidTag = "ESSID:"; - - /// - /// Gets the local machine Host Name. - /// - public string HostName => Network.HostName; - - /// - /// Retrieves the wireless networks. - /// - /// The adapter. - /// A list of WiFi networks. - public Task> RetrieveWirelessNetworks(string adapter) => RetrieveWirelessNetworks(new[] { adapter }); - - /// - /// Retrieves the wireless networks. - /// - /// The adapters. - /// A list of WiFi networks. - public async Task> RetrieveWirelessNetworks(string[] adapters = null) - { - var result = new List(); - - foreach (var networkAdapter in adapters ?? (await RetrieveAdapters()).Where(x => x.IsWireless).Select(x => x.Name)) - { - var wirelessOutput = await ProcessRunner.GetProcessOutputAsync("iwlist", $"{networkAdapter} scanning").ConfigureAwait(false); - var outputLines = - wirelessOutput.Split('\n') - .Select(x => x.Trim()) - .Where(x => string.IsNullOrWhiteSpace(x) == false) - .ToArray(); - - for (var i = 0; i < outputLines.Length; i++) - { - var line = outputLines[i]; - - if (line.StartsWith(EssidTag) == false) continue; - - var network = new WirelessNetworkInfo - { - Name = line.Replace(EssidTag, string.Empty).Replace("\"", string.Empty) - }; - - while (true) - { - if (i + 1 >= outputLines.Length) break; - - // should look for two lines before the ESSID acording to the scan - line = outputLines[i - 2]; - - if (!line.StartsWith("Quality=")) continue; - network.Quality = line.Replace("Quality=", string.Empty); - break; - } - - while (true) - { - if (i + 1 >= outputLines.Length) break; - - // should look for a line before the ESSID acording to the scan - line = outputLines[i - 1]; - - if (!line.StartsWith("Encryption key:")) continue; - network.IsEncrypted = line.Replace("Encryption key:", string.Empty).Trim() == "on"; - break; - } - - if (result.Any(x => x.Name == network.Name) == false) - result.Add(network); - } - } - - return result - .OrderBy(x => x.Name) - .ToList(); - } - - /// - /// Setups the wireless network. - /// - /// Name of the adapter. - /// The network ssid. - /// The password (8 characters as minimum length). - /// The 2-letter country code in uppercase. Default is US. - /// True if successful. Otherwise, false. - public async Task SetupWirelessNetwork(string adapterName, string networkSsid, string password = null, string countryCode = "US") - { - // TODO: Get the country where the device is located to set 'country' param in payload var - var payload = $"country={countryCode}\nctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev\nupdate_config=1\n"; - - if (!string.IsNullOrWhiteSpace(password) && password.Length < 8) - throw new InvalidOperationException("The password must be at least 8 characters length."); - - payload += string.IsNullOrEmpty(password) - ? $"network={{\n\tssid=\"{networkSsid}\"\n\tkey_mgmt=NONE\n\t}}\n" - : $"network={{\n\tssid=\"{networkSsid}\"\n\tpsk=\"{password}\"\n\t}}\n"; - try - { - File.WriteAllText("/etc/wpa_supplicant/wpa_supplicant.conf", payload); - await ProcessRunner.GetProcessOutputAsync("pkill", "-f wpa_supplicant"); - await ProcessRunner.GetProcessOutputAsync("ifdown", adapterName); - await ProcessRunner.GetProcessOutputAsync("ifup", adapterName); - } - catch (Exception ex) - { - ex.Log(nameof(NetworkSettings)); - return false; - } - - return true; - } - - /// - /// Retrieves the network adapters. - /// - /// A list of network adapters. - public async Task> RetrieveAdapters() - { - const string hWaddr = "HWaddr "; - const string ether = "ether "; - - var result = new List(); - var interfacesOutput = await ProcessRunner.GetProcessOutputAsync("ifconfig"); - var wlanOutput = (await ProcessRunner.GetProcessOutputAsync("iwconfig")) - .Split('\n') - .Where(x => x.Contains("no wireless extensions.") == false) - .ToArray(); - - var outputLines = interfacesOutput.Split('\n').Where(x => string.IsNullOrWhiteSpace(x) == false).ToArray(); - - for (var i = 0; i < outputLines.Length; i++) - { - // grab the current line - var line = outputLines[i]; - - // skip if the line is indented - if (char.IsLetterOrDigit(line[0]) == false) - continue; - - // Read the line as an adapter - var adapter = new NetworkAdapterInfo - { - Name = line.Substring(0, line.IndexOf(' ')).TrimEnd(':') - }; - - // Parse the MAC address in old version of ifconfig; it comes in the first line - if (line.IndexOf(hWaddr, StringComparison.Ordinal) >= 0) - { - var startIndexHwd = line.IndexOf(hWaddr, StringComparison.Ordinal) + hWaddr.Length; - adapter.MacAddress = line.Substring(startIndexHwd, 17).Trim(); - } - - // Parse the info in lines other than the first - for (var j = i + 1; j < outputLines.Length; j++) - { - // Get the contents of the indented line - var indentedLine = outputLines[j]; - - // We have hit the next adapter info - if (char.IsLetterOrDigit(indentedLine[0])) - { - i = j - 1; - break; - } - - // Parse the MAC address in new versions of ifconfig; it no longer comes in the first line - if (indentedLine.IndexOf(ether, StringComparison.Ordinal) >= 0 && string.IsNullOrWhiteSpace(adapter.MacAddress)) - { - var startIndexHwd = indentedLine.IndexOf(ether, StringComparison.Ordinal) + ether.Length; - adapter.MacAddress = indentedLine.Substring(startIndexHwd, 17).Trim(); - } - - // Parse the IPv4 Address - GetIPv4(indentedLine, adapter); - - // Parse the IPv6 Address - GetIPv6(indentedLine, adapter); - - // we have hit the end of the output in an indented line - if (j >= outputLines.Length - 1) - i = outputLines.Length; - } - - // Retrieve the wireless LAN info - var wlanInfo = wlanOutput.FirstOrDefault(x => x.StartsWith(adapter.Name)); - - if (wlanInfo != null) - { - adapter.IsWireless = true; - var essidParts = wlanInfo.Split(new[] { EssidTag }, StringSplitOptions.RemoveEmptyEntries); - if (essidParts.Length >= 2) - { - adapter.AccessPointName = essidParts[1].Replace("\"", string.Empty).Trim(); - } - } - - // Add the current adapter to the result - result.Add(adapter); - } - - return result.OrderBy(x => x.Name).ToList(); - } - - /// - /// Retrieves the current network adapter. - /// - /// The name of the current network adapter. - public static async Task GetCurrentAdapterName() - { - var result = await ProcessRunner.GetProcessOutputAsync("route").ConfigureAwait(false); - var defaultLine = result.Split(new[] { '\n' }, StringSplitOptions.RemoveEmptyEntries) - .FirstOrDefault(l => l.StartsWith("default", StringComparison.OrdinalIgnoreCase)); - - return defaultLine?.Trim().Substring(defaultLine.LastIndexOf(" ", StringComparison.OrdinalIgnoreCase) + 1); - } - - /// - /// Retrieves current wireless connected network name. - /// - /// The connected network name. - public Task GetWirelessNetworkName() => ProcessRunner.GetProcessOutputAsync("iwgetid", "-r"); - - private static void GetIPv4(string indentedLine, NetworkAdapterInfo adapter) - { - var addressText = ParseOutputTagFromLine(indentedLine, "inet addr:") ?? - ParseOutputTagFromLine(indentedLine, "inet "); - - if (addressText == null) return; - if (IPAddress.TryParse(addressText, out var outValue)) - adapter.IPv4 = outValue; - } - - private static void GetIPv6(string indentedLine, NetworkAdapterInfo adapter) - { - var addressText = ParseOutputTagFromLine(indentedLine, "inet6 addr:") ?? - ParseOutputTagFromLine(indentedLine, "inet6 "); - - if (addressText == null) return; - - if (IPAddress.TryParse(addressText, out var outValue)) - adapter.IPv6 = outValue; - } - - private static string ParseOutputTagFromLine(string indentedLine, string tagName) - { - if (indentedLine.IndexOf(tagName, StringComparison.Ordinal) < 0) - return null; - - var startIndex = indentedLine.IndexOf(tagName, StringComparison.Ordinal) + tagName.Length; - var builder = new StringBuilder(1024); - for (var c = startIndex; c < indentedLine.Length; c++) - { - var currentChar = indentedLine[c]; - if (!char.IsPunctuation(currentChar) && !char.IsLetterOrDigit(currentChar)) - break; - - builder.Append(currentChar); - } - - return builder.ToString(); - } - } + public String HostName => Network.HostName; + + /// + /// Retrieves the wireless networks. + /// + /// The adapter. + /// A list of WiFi networks. + public Task> RetrieveWirelessNetworks(String adapter) => this.RetrieveWirelessNetworks(new[] { adapter }); + + /// + /// Retrieves the wireless networks. + /// + /// The adapters. + /// A list of WiFi networks. + public async Task> RetrieveWirelessNetworks(String[]? adapters = null) { + List result = new List(); + + foreach(String? networkAdapter in adapters ?? (await this.RetrieveAdapters()).Where(x => x.IsWireless).Select(x => x.Name)) { + String wirelessOutput = await ProcessRunner.GetProcessOutputAsync("iwlist", $"{networkAdapter} scanning").ConfigureAwait(false); + String[] outputLines = wirelessOutput.Split('\n').Select(x => x.Trim()).Where(x => String.IsNullOrWhiteSpace(x) == false).ToArray(); + + for(Int32 i = 0; i < outputLines.Length; i++) { + String line = outputLines[i]; + + if(line.StartsWith(EssidTag) == false) { + continue; + } + + WirelessNetworkInfo network = new WirelessNetworkInfo { + Name = line.Replace(EssidTag, String.Empty).Replace("\"", String.Empty) + }; + + while(true) { + if(i + 1 >= outputLines.Length) { + break; + } + + // should look for two lines before the ESSID acording to the scan + line = outputLines[i - 2]; + + if(!line.StartsWith("Quality=")) { + continue; + } + + network.Quality = line.Replace("Quality=", String.Empty); + break; + } + + while(true) { + if(i + 1 >= outputLines.Length) { + break; + } + + // should look for a line before the ESSID acording to the scan + line = outputLines[i - 1]; + + if(!line.StartsWith("Encryption key:")) { + continue; + } + + network.IsEncrypted = line.Replace("Encryption key:", String.Empty).Trim() == "on"; + break; + } + + if(result.Any(x => x.Name == network.Name) == false) { + result.Add(network); + } + } + } + + return result + .OrderBy(x => x.Name) + .ToList(); + } + + /// + /// Setups the wireless network. + /// + /// Name of the adapter. + /// The network ssid. + /// The password (8 characters as minimum length). + /// The 2-letter country code in uppercase. Default is US. + /// True if successful. Otherwise, false. + public async Task SetupWirelessNetwork(String adapterName, String networkSsid, String? password = null, String countryCode = "US") { + // TODO: Get the country where the device is located to set 'country' param in payload var + String payload = $"country={countryCode}\nctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev\nupdate_config=1\n"; + + if(!String.IsNullOrWhiteSpace(password) && password.Length < 8) { + throw new InvalidOperationException("The password must be at least 8 characters length."); + } + + payload += String.IsNullOrEmpty(password) + ? $"network={{\n\tssid=\"{networkSsid}\"\n\tkey_mgmt=NONE\n\t}}\n" + : $"network={{\n\tssid=\"{networkSsid}\"\n\tpsk=\"{password}\"\n\t}}\n"; + try { + File.WriteAllText("/etc/wpa_supplicant/wpa_supplicant.conf", payload); + _ = await ProcessRunner.GetProcessOutputAsync("pkill", "-f wpa_supplicant"); + _ = await ProcessRunner.GetProcessOutputAsync("ifdown", adapterName); + _ = await ProcessRunner.GetProcessOutputAsync("ifup", adapterName); + } catch(Exception ex) { + ex.Log(nameof(NetworkSettings)); + return false; + } + + return true; + } + + /// + /// Retrieves the network adapters. + /// + /// A list of network adapters. + public async Task> RetrieveAdapters() { + const String hWaddr = "HWaddr "; + const String ether = "ether "; + + List result = new List(); + String interfacesOutput = await ProcessRunner.GetProcessOutputAsync("ifconfig"); + String[] wlanOutput = (await ProcessRunner.GetProcessOutputAsync("iwconfig")).Split('\n').Where(x => x.Contains("no wireless extensions.") == false).ToArray(); + + String[] outputLines = interfacesOutput.Split('\n').Where(x => String.IsNullOrWhiteSpace(x) == false).ToArray(); + + for(Int32 i = 0; i < outputLines.Length; i++) { + // grab the current line + String line = outputLines[i]; + + // skip if the line is indented + if(Char.IsLetterOrDigit(line[0]) == false) { + continue; + } + + // Read the line as an adapter + NetworkAdapterInfo adapter = new NetworkAdapterInfo { + Name = line.Substring(0, line.IndexOf(' ')).TrimEnd(':') + }; + + // Parse the MAC address in old version of ifconfig; it comes in the first line + if(line.IndexOf(hWaddr, StringComparison.Ordinal) >= 0) { + Int32 startIndexHwd = line.IndexOf(hWaddr, StringComparison.Ordinal) + hWaddr.Length; + adapter.MacAddress = line.Substring(startIndexHwd, 17).Trim(); + } + + // Parse the info in lines other than the first + for(Int32 j = i + 1; j < outputLines.Length; j++) { + // Get the contents of the indented line + String indentedLine = outputLines[j]; + + // We have hit the next adapter info + if(Char.IsLetterOrDigit(indentedLine[0])) { + i = j - 1; + break; + } + + // Parse the MAC address in new versions of ifconfig; it no longer comes in the first line + if(indentedLine.IndexOf(ether, StringComparison.Ordinal) >= 0 && String.IsNullOrWhiteSpace(adapter.MacAddress)) { + Int32 startIndexHwd = indentedLine.IndexOf(ether, StringComparison.Ordinal) + ether.Length; + adapter.MacAddress = indentedLine.Substring(startIndexHwd, 17).Trim(); + } + + // Parse the IPv4 Address + GetIPv4(indentedLine, adapter); + + // Parse the IPv6 Address + GetIPv6(indentedLine, adapter); + + // we have hit the end of the output in an indented line + if(j >= outputLines.Length - 1) { + i = outputLines.Length; + } + } + + // Retrieve the wireless LAN info + String wlanInfo = wlanOutput.FirstOrDefault(x => x.StartsWith(adapter.Name)); + + if(wlanInfo != null) { + adapter.IsWireless = true; + String[] essidParts = wlanInfo.Split(new[] { EssidTag }, StringSplitOptions.RemoveEmptyEntries); + if(essidParts.Length >= 2) { + adapter.AccessPointName = essidParts[1].Replace("\"", String.Empty).Trim(); + } + } + + // Add the current adapter to the result + result.Add(adapter); + } + + return result.OrderBy(x => x.Name).ToList(); + } + + /// + /// Retrieves the current network adapter. + /// + /// The name of the current network adapter. + public static async Task GetCurrentAdapterName() { + String result = await ProcessRunner.GetProcessOutputAsync("route").ConfigureAwait(false); + String defaultLine = result.Split(new[] { '\n' }, StringSplitOptions.RemoveEmptyEntries).FirstOrDefault(l => l.StartsWith("default", StringComparison.OrdinalIgnoreCase)); + + return defaultLine?.Trim().Substring(defaultLine.LastIndexOf(" ", StringComparison.OrdinalIgnoreCase) + 1); + } + + /// + /// Retrieves current wireless connected network name. + /// + /// The connected network name. + public Task GetWirelessNetworkName() => ProcessRunner.GetProcessOutputAsync("iwgetid", "-r"); + + private static void GetIPv4(String indentedLine, NetworkAdapterInfo adapter) { + String? addressText = ParseOutputTagFromLine(indentedLine, "inet addr:") ?? ParseOutputTagFromLine(indentedLine, "inet "); + + if(addressText == null) { + return; + } + + if(IPAddress.TryParse(addressText, out IPAddress outValue)) { + adapter.IPv4 = outValue; + } + } + + private static void GetIPv6(String indentedLine, NetworkAdapterInfo adapter) { + String? addressText = ParseOutputTagFromLine(indentedLine, "inet6 addr:") ?? ParseOutputTagFromLine(indentedLine, "inet6 "); + + if(addressText == null) { + return; + } + + if(IPAddress.TryParse(addressText, out IPAddress outValue)) { + adapter.IPv6 = outValue; + } + } + + private static String? ParseOutputTagFromLine(String indentedLine, String tagName) { + if(indentedLine.IndexOf(tagName, StringComparison.Ordinal) < 0) { + return null; + } + + Int32 startIndex = indentedLine.IndexOf(tagName, StringComparison.Ordinal) + tagName.Length; + StringBuilder builder = new StringBuilder(1024); + for(Int32 c = startIndex; c < indentedLine.Length; c++) { + Char currentChar = indentedLine[c]; + if(!Char.IsPunctuation(currentChar) && !Char.IsLetterOrDigit(currentChar)) { + break; + } + + _ = builder.Append(currentChar); + } + + return builder.ToString(); + } + } } diff --git a/Unosquare.RaspberryIO/Computer/OsInfo.cs b/Unosquare.RaspberryIO/Computer/OsInfo.cs index d8d85cd..6675393 100644 --- a/Unosquare.RaspberryIO/Computer/OsInfo.cs +++ b/Unosquare.RaspberryIO/Computer/OsInfo.cs @@ -1,46 +1,58 @@ -namespace Unosquare.RaspberryIO.Computer -{ +using System; + +namespace Unosquare.RaspberryIO.Computer { + /// + /// Represents the OS Information. + /// + public class OsInfo { /// - /// Represents the OS Information. + /// System name. /// - public class OsInfo - { - /// - /// System name. - /// - public string SysName { get; set; } - - /// - /// Node name. - /// - public string NodeName { get; set; } - - /// - /// Release level. - /// - public string Release { get; set; } - - /// - /// Version level. - /// - public string Version { get; set; } - - /// - /// Hardware level. - /// - public string Machine { get; set; } - - /// - /// Domain name. - /// - public string DomainName { get; set; } - - /// - /// Returns a that represents this instance. - /// - /// - /// A that represents this instance. - /// - public override string ToString() => $"{SysName} {Release} {Version}"; - } + public String? SysName { + get; set; + } + + /// + /// Node name. + /// + public String? NodeName { + get; set; + } + + /// + /// Release level. + /// + public String? Release { + get; set; + } + + /// + /// Version level. + /// + public String? Version { + get; set; + } + + /// + /// Hardware level. + /// + public String? Machine { + get; set; + } + + /// + /// Domain name. + /// + public String? DomainName { + get; set; + } + + /// + /// Returns a that represents this instance. + /// + /// + /// A that represents this instance. + /// + public override String ToString() => $"{this.SysName} {this.Release} {this.Version}"; + } } diff --git a/Unosquare.RaspberryIO/Computer/PiVersion.cs b/Unosquare.RaspberryIO/Computer/PiVersion.cs index c748509..7c48f50 100644 --- a/Unosquare.RaspberryIO/Computer/PiVersion.cs +++ b/Unosquare.RaspberryIO/Computer/PiVersion.cs @@ -1,394 +1,388 @@ -namespace Unosquare.RaspberryIO.Computer -{ +namespace Unosquare.RaspberryIO.Computer { + /// + /// Defines the board revision codes of the different versions of the Raspberry Pi + /// http://www.raspberrypi-spy.co.uk/2012/09/checking-your-raspberry-pi-board-version/. + /// https://www.raspberrypi.org/documentation/hardware/raspberrypi/revision-codes/README.md. + /// + public enum PiVersion { /// - /// Defines the board revision codes of the different versions of the Raspberry Pi - /// http://www.raspberrypi-spy.co.uk/2012/09/checking-your-raspberry-pi-board-version/. - /// https://www.raspberrypi.org/documentation/hardware/raspberrypi/revision-codes/README.md. + /// The unknown version /// - public enum PiVersion - { - /// - /// The unknown version - /// - Unknown = 0, - - /// - /// The model B Rev1 - /// - ModelBRev1 = 0x0002, - - /// - /// The model B Rev1 ECN0001 - /// - ModelBRev1ECN0001 = 0x0003, - - /// - /// The model B Rev2 Sony - /// - ModelBRev2x04 = 0x0004, - - /// - /// The model B Rev2 Qisda - /// - ModelBRev2x05 = 0x0005, - - /// - /// The model B Rev2 Egoman - /// - ModelBRev2x06 = 0x0006, - - /// - /// The model A Rev2 Egoman - /// - ModelAx07 = 0x0007, - - /// - /// The model A Rev2 Sony - /// - ModelAx08 = 0x0008, - - /// - /// The model A Rev2 Qisda - /// - ModelAx09 = 0x0009, - - /// - /// The model B Rev2 (512MB) Egoman - /// - ModelBRev2x0d = 0x000d, - - /// - /// The model B Rev2 (512MB) Sony - /// - ModelBRev2x0e = 0x000e, - - /// - /// The model B Rev2 (512MB) Egoman - /// - ModelBRev2x0f = 0x000f, - - /// - /// The model B+ Rev1 Sony - /// - ModelBPlus0x10 = 0x0010, - - /// - /// The compute module 1 Sony - /// - ComputeModule0x11 = 0x0011, - - /// - /// The model A+ Rev1.1 Sony - /// - ModelAPlus0x12 = 0x0012, - - /// - /// The model B+ Rev1.2 Embest - /// - ModelBPlus0x13 = 0x0013, - - /// - /// The compute module 1 Embest - /// - ComputeModule0x14 = 0x0014, - - /// - /// The model A+ Rev1.1 Embest - /// - ModelAPlus0x15 = 0x0015, - - /// - /// The model A+ Rev1.1 (512MB) Sony - /// - ModelAPlus1v1Sony = 900021, - - /// - /// The model B+ Rev1.2 sony - /// - ModelBPlus1v2Sony = 900032, - - /// - /// The Pi Zero Rev1.2 Sony - /// - PiZero1v2 = 0x900092, - - /// - /// The Pi Zero Rev1.3 SOny - /// - PiZero1v3 = 0x900093, - - /// - /// The Pi Zero W Rev1.1 - /// - PiZeroW = 0x9000c1, - - /// - /// The Pi 3 model A+ Sony - /// - Pi3ModelAPlus = 0x9020e0, - - /// - /// The Pi Zero Rev1.2 Embest - /// - PiZero1v2Embest = 0x920092, - - /// - /// The Pi Zero Rev1.3 Embest - /// - PiZero1v3Embest = 0x920093, - - /// - /// The Pi 2 model B Rev1.0 Sony - /// - Pi2ModelB1v0Sony = 0xa01040, - - /// - /// The Pi 2 model B Rev1.1 Sony - /// - Pi2ModelB1v1Sony = 0xa01041, - - /// - /// The Pi 3 model B Rev1.2 Sony - /// - Pi3ModelBSony = 0xa02082, - - /// - /// The compute module 3 Rev1.0 Sony - /// - ComputeModule3Sony = 0xa020a0, - - /// - /// The Pi 3 model B+ Rev1.3 Sony - /// - Pi3ModelBPlusSony = 0xa020d3, - - /// - /// The Pi 2 model B Rev1.1 Embest - /// - Pi2ModelB1v1Embest = 0xa21041, - - /// - /// The Pi 2 model B Rev1.2 Embest - /// - Pi2ModelB1v2 = 0xa22042, - - /// - /// The Pi 3 model B Rev1.2 Embest - /// - Pi3ModelBEmbest = 0xa22082, - - /// - /// The compute module 3 Rev1.0 Embest - /// - ComputeModule3Embest = 0xa220a0, - - /// - /// The Pi 3 model B Rev1.2 Sony Japan - /// - Pi3ModelBSonyJapan = 0xa32082, - - /// - /// The Pi 3 model B Rev1.2 Stadium - /// - Pi3ModelBStadium = 0xa52082, - - /// - /// The compute module 3+ Rev 1.0 Sony - /// - ComputeModule3PlusSony = 0xa02100, - - /// - /// The Pi 4 model B 1GB, Sony - /// - Pi4ModelB1Gb = 0xa03111, - - /// - /// The Pi 4 model B 2GB, Sony - /// - Pi4ModelB2Gb = 0xb03111, - - /// - /// The Pi 4 model B 4GB, Sony - /// - Pi4ModelB4Gb = 0xc03111, - } - + Unknown = 0, + /// - /// Defines the board model accordingly to new-style revision codes. + /// The model B Rev1 /// - public enum BoardModel - { - /// - /// Model A - /// - ModelA = 0, - - /// - /// Model B - /// - ModelB = 1, - - /// - /// Model A+ - /// - ModelAPlus = 2, - - /// - /// Model B+ - /// - ModelBPlus = 3, - - /// - /// Model 2B - /// - Model2B = 4, - - /// - /// Alpha (early prototype) - /// - Alpha = 5, - - /// - /// Compute Module 1 - /// - CM1 = 6, - - /// - /// Model 3B - /// - Model3B = 8, - - /// - /// Model Zero - /// - Zero = 9, - - /// - /// Compute Module 3 - /// - CM3 = 0xa, - - /// - /// Model Zero W - /// - ZeroW = 0xc, - - /// - /// Model 3B+ - /// - Model3BPlus = 0xd, - - /// - /// Model 3A+ - /// - Model3APlus = 0xe, - - /// - /// Reserved (Internal use only) - /// - InternalUse = 0xf, - - /// - /// Compute Module 3+ - /// - CM3Plus = 0x10, - - /// - /// Model 4B - /// - Model4B = 0x11, - } - + ModelBRev1 = 0x0002, + /// - /// Defines the processor model accordingly to new-style revision codes. + /// The model B Rev1 ECN0001 /// - public enum ProcessorModel - { - /// - /// The BCMM2835 processor. - /// - BCM2835, - - /// - /// The BCMM2836 processor. - /// - BCM2836, - - /// - /// The BCMM2837 processor. - /// - BCM2837, - - /// - /// The BCM2711 processor. - /// - BCM2711, - } - + ModelBRev1ECN0001 = 0x0003, + /// - /// Defines the manufacturer accordingly to new-style revision codes. + /// The model B Rev2 Sony /// - public enum Manufacturer - { - /// - /// Sony UK - /// - SonyUK, - - /// - /// Egoman - /// - Egoman, - - /// - /// Embest - /// - Embest, - - /// - /// Sony Japan - /// - SonyJapan, - - /// - /// Embest - /// - Embest2, - - /// - /// Stadium - /// - Stadium, - } - + ModelBRev2x04 = 0x0004, + /// - /// Defines the memory size accordingly to new-style revision codes. + /// The model B Rev2 Qisda /// - public enum MemorySize - { - /// - /// 256 MB - /// - Memory256, - - /// - /// 512 MB - /// - Memory512, - - /// - /// 1 GB - /// - Memory1024, - - /// - /// 2 GB - /// - Memory2048, - - /// - /// 4 GB - /// - Memory4096, - } + ModelBRev2x05 = 0x0005, + + /// + /// The model B Rev2 Egoman + /// + ModelBRev2x06 = 0x0006, + + /// + /// The model A Rev2 Egoman + /// + ModelAx07 = 0x0007, + + /// + /// The model A Rev2 Sony + /// + ModelAx08 = 0x0008, + + /// + /// The model A Rev2 Qisda + /// + ModelAx09 = 0x0009, + + /// + /// The model B Rev2 (512MB) Egoman + /// + ModelBRev2x0d = 0x000d, + + /// + /// The model B Rev2 (512MB) Sony + /// + ModelBRev2x0e = 0x000e, + + /// + /// The model B Rev2 (512MB) Egoman + /// + ModelBRev2x0f = 0x000f, + + /// + /// The model B+ Rev1 Sony + /// + ModelBPlus0x10 = 0x0010, + + /// + /// The compute module 1 Sony + /// + ComputeModule0x11 = 0x0011, + + /// + /// The model A+ Rev1.1 Sony + /// + ModelAPlus0x12 = 0x0012, + + /// + /// The model B+ Rev1.2 Embest + /// + ModelBPlus0x13 = 0x0013, + + /// + /// The compute module 1 Embest + /// + ComputeModule0x14 = 0x0014, + + /// + /// The model A+ Rev1.1 Embest + /// + ModelAPlus0x15 = 0x0015, + + /// + /// The model A+ Rev1.1 (512MB) Sony + /// + ModelAPlus1v1Sony = 900021, + + /// + /// The model B+ Rev1.2 sony + /// + ModelBPlus1v2Sony = 900032, + + /// + /// The Pi Zero Rev1.2 Sony + /// + PiZero1v2 = 0x900092, + + /// + /// The Pi Zero Rev1.3 SOny + /// + PiZero1v3 = 0x900093, + + /// + /// The Pi Zero W Rev1.1 + /// + PiZeroW = 0x9000c1, + + /// + /// The Pi 3 model A+ Sony + /// + Pi3ModelAPlus = 0x9020e0, + + /// + /// The Pi Zero Rev1.2 Embest + /// + PiZero1v2Embest = 0x920092, + + /// + /// The Pi Zero Rev1.3 Embest + /// + PiZero1v3Embest = 0x920093, + + /// + /// The Pi 2 model B Rev1.0 Sony + /// + Pi2ModelB1v0Sony = 0xa01040, + + /// + /// The Pi 2 model B Rev1.1 Sony + /// + Pi2ModelB1v1Sony = 0xa01041, + + /// + /// The Pi 3 model B Rev1.2 Sony + /// + Pi3ModelBSony = 0xa02082, + + /// + /// The compute module 3 Rev1.0 Sony + /// + ComputeModule3Sony = 0xa020a0, + + /// + /// The Pi 3 model B+ Rev1.3 Sony + /// + Pi3ModelBPlusSony = 0xa020d3, + + /// + /// The Pi 2 model B Rev1.1 Embest + /// + Pi2ModelB1v1Embest = 0xa21041, + + /// + /// The Pi 2 model B Rev1.2 Embest + /// + Pi2ModelB1v2 = 0xa22042, + + /// + /// The Pi 3 model B Rev1.2 Embest + /// + Pi3ModelBEmbest = 0xa22082, + + /// + /// The compute module 3 Rev1.0 Embest + /// + ComputeModule3Embest = 0xa220a0, + + /// + /// The Pi 3 model B Rev1.2 Sony Japan + /// + Pi3ModelBSonyJapan = 0xa32082, + + /// + /// The Pi 3 model B Rev1.2 Stadium + /// + Pi3ModelBStadium = 0xa52082, + + /// + /// The compute module 3+ Rev 1.0 Sony + /// + ComputeModule3PlusSony = 0xa02100, + + /// + /// The Pi 4 model B 1GB, Sony + /// + Pi4ModelB1Gb = 0xa03111, + + /// + /// The Pi 4 model B 2GB, Sony + /// + Pi4ModelB2Gb = 0xb03111, + + /// + /// The Pi 4 model B 4GB, Sony + /// + Pi4ModelB4Gb = 0xc03111, + } + + /// + /// Defines the board model accordingly to new-style revision codes. + /// + public enum BoardModel { + /// + /// Model A + /// + ModelA = 0, + + /// + /// Model B + /// + ModelB = 1, + + /// + /// Model A+ + /// + ModelAPlus = 2, + + /// + /// Model B+ + /// + ModelBPlus = 3, + + /// + /// Model 2B + /// + Model2B = 4, + + /// + /// Alpha (early prototype) + /// + Alpha = 5, + + /// + /// Compute Module 1 + /// + CM1 = 6, + + /// + /// Model 3B + /// + Model3B = 8, + + /// + /// Model Zero + /// + Zero = 9, + + /// + /// Compute Module 3 + /// + CM3 = 0xa, + + /// + /// Model Zero W + /// + ZeroW = 0xc, + + /// + /// Model 3B+ + /// + Model3BPlus = 0xd, + + /// + /// Model 3A+ + /// + Model3APlus = 0xe, + + /// + /// Reserved (Internal use only) + /// + InternalUse = 0xf, + + /// + /// Compute Module 3+ + /// + CM3Plus = 0x10, + + /// + /// Model 4B + /// + Model4B = 0x11, + } + + /// + /// Defines the processor model accordingly to new-style revision codes. + /// + public enum ProcessorModel { + /// + /// The BCMM2835 processor. + /// + BCM2835, + + /// + /// The BCMM2836 processor. + /// + BCM2836, + + /// + /// The BCMM2837 processor. + /// + BCM2837, + + /// + /// The BCM2711 processor. + /// + BCM2711, + } + + /// + /// Defines the manufacturer accordingly to new-style revision codes. + /// + public enum Manufacturer { + /// + /// Sony UK + /// + SonyUK, + + /// + /// Egoman + /// + Egoman, + + /// + /// Embest + /// + Embest, + + /// + /// Sony Japan + /// + SonyJapan, + + /// + /// Embest + /// + Embest2, + + /// + /// Stadium + /// + Stadium, + } + + /// + /// Defines the memory size accordingly to new-style revision codes. + /// + public enum MemorySize { + /// + /// 256 MB + /// + Memory256, + + /// + /// 512 MB + /// + Memory512, + + /// + /// 1 GB + /// + Memory1024, + + /// + /// 2 GB + /// + Memory2048, + + /// + /// 4 GB + /// + Memory4096, + } } \ No newline at end of file diff --git a/Unosquare.RaspberryIO/Computer/SystemInfo.cs b/Unosquare.RaspberryIO/Computer/SystemInfo.cs index fdce8ba..0f65de5 100644 --- a/Unosquare.RaspberryIO/Computer/SystemInfo.cs +++ b/Unosquare.RaspberryIO/Computer/SystemInfo.cs @@ -1,390 +1,383 @@ -namespace Unosquare.RaspberryIO.Computer -{ - using System; - using System.Collections.Generic; - using System.Globalization; - using System.IO; - using System.Linq; - using System.Reflection; - using Abstractions; - using Native; - using Swan; - using Swan.DependencyInjection; - +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; + /// - /// Retrieves the RaspberryPI System Information. - /// - /// http://raspberry-pi-guide.readthedocs.io/en/latest/system.html. + /// Prevents a default instance of the class from being created. /// - 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 int 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 - - var 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(); - var propDictionary = new Dictionary(StringComparer.OrdinalIgnoreCase); - - foreach (var prop in properties) - { - propDictionary[prop.Name.Replace(" ", string.Empty).ToLowerInvariant().Trim()] = prop; - } - - #endregion - - #region Extract CPU information - - if (File.Exists(CpuInfoFilePath)) - { - var cpuInfoLines = File.ReadAllLines(CpuInfoFilePath); - - foreach (var line in cpuInfoLines) - { - var lineParts = line.Split(new[] { ':' }, 2); - if (lineParts.Length != 2) - continue; - - var propertyKey = lineParts[0].Trim().Replace(" ", string.Empty); - var propertyStringValue = lineParts[1].Trim(); - - if (!propDictionary.ContainsKey(propertyKey)) continue; - - var property = propDictionary[propertyKey]; - if (property.PropertyType == typeof(string)) - { - property.SetValue(this, propertyStringValue); - } - else if (property.PropertyType == typeof(string[])) - { - var propertyArrayValue = propertyStringValue.Split(' '); - property.SetValue(this, propertyArrayValue); - } - } - } - - #endregion - - ExtractMemoryInfo(); - ExtractBoardVersion(); - 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 int BoardRevision { get; set; } - - /// - /// Gets the number of processor cores. - /// - public int ProcessorCount => int.TryParse(Processor, out var outIndex) ? outIndex + 1 : 0; - - /// - /// Gets the installed ram in bytes. - /// - public int InstalledRam { get; private set; } - - /// - /// Gets a value indicating whether this CPU is little endian. - /// - public bool 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 int 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 => - NewStyleRevisionCodes ? - _boardModel : - throw new InvalidOperationException($"This board does not support new-style revision codes. Use {nameof(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 => - NewStyleRevisionCodes ? - _processorModel : - throw new InvalidOperationException($"This board does not support new-style revision codes. Use {nameof(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 => - NewStyleRevisionCodes ? - _manufacturer : - throw new InvalidOperationException($"This board does not support new-style revision codes. Use {nameof(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 => - NewStyleRevisionCodes ? - _memorySize : - throw new InvalidOperationException($"This board does not support new-style revision codes. Use {nameof(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; - var parts = File.ReadAllText(UptimeFilePath).Trim().Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); - - if (parts.Length >= 1 && float.TryParse(parts[0], out var result)) - return result; - } - catch - { - /* Ignore */ - } - - return 0; - } - } - - /// - /// Gets the uptime in TimeSpan. - /// - public TimeSpan UptimeTimeSpan => TimeSpan.FromSeconds(Uptime); - - /// - /// Indicates if the board uses the new-style revision codes. - /// - private bool 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() - { - var properties = typeof(SystemInfo).GetProperties(BindingFlags.Instance | BindingFlags.Public) - .Where(p => p.CanRead && ( - p.PropertyType == typeof(string) || - p.PropertyType == typeof(string[]) || - p.PropertyType == typeof(int) || - p.PropertyType == typeof(bool) || - p.PropertyType == typeof(TimeSpan))) - .ToArray(); - - var propertyValues2 = new List - { - "System Information", - $"\t{nameof(LibraryVersion),-22}: {LibraryVersion}", - $"\t{nameof(RaspberryPiVersion),-22}: {RaspberryPiVersion}", - }; - - foreach (var 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) - { - var 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 var unameInfo); - - OperatingSystem = new OsInfo - { - DomainName = unameInfo.DomainName, - Machine = unameInfo.Machine, - NodeName = unameInfo.NodeName, - Release = unameInfo.Release, - SysName = unameInfo.SysName, - Version = unameInfo.Version, - }; - } - catch - { - OperatingSystem = new OsInfo(); - } - } - - private void ExtractBoardVersion() - { - var hasSysInfo = DependencyContainer.Current.CanResolve(); - - try - { - if (string.IsNullOrWhiteSpace(Revision) == false && - int.TryParse( - Revision.ToUpperInvariant(), - NumberStyles.HexNumber, - CultureInfo.InvariantCulture, - out var boardVersion)) - { - RaspberryPiVersion = PiVersion.Unknown; - if (Enum.IsDefined(typeof(PiVersion), boardVersion)) - RaspberryPiVersion = (PiVersion)boardVersion; - - if ((boardVersion & NewStyleCodesMask) == NewStyleCodesMask) - { - NewStyleRevisionCodes = true; - RevisionNumber = boardVersion & 0xF; - _boardModel = (BoardModel)((boardVersion >> 4) & 0xFF); - _processorModel = (ProcessorModel)((boardVersion >> 12) & 0xF); - _manufacturer = (Manufacturer)((boardVersion >> 16) & 0xF); - _memorySize = (MemorySize)((boardVersion >> 20) & 0x7); - } - } - - if (hasSysInfo) - BoardRevision = (int)DependencyContainer.Current.Resolve().BoardRevision; - } - catch - { - /* Ignore */ - } - - if (hasSysInfo) - LibraryVersion = DependencyContainer.Current.Resolve().LibraryVersion; - } - - private void ExtractMemoryInfo() - { - if (!File.Exists(MemInfoFilePath)) return; - - var memInfoLines = File.ReadAllLines(MemInfoFilePath); - - foreach (var line in memInfoLines) - { - var lineParts = line.Split(new[] { ':' }, 2); - if (lineParts.Length != 2) - continue; - - if (lineParts[0].ToLowerInvariant().Trim().Equals("memtotal") == false) - continue; - - var memKb = lineParts[1].ToLowerInvariant().Trim().Replace("kb", string.Empty).Trim(); - - if (!int.TryParse(memKb, out var parsedMem)) continue; - InstalledRam = parsedMem * 1024; - break; - } - } - } + /// 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 != null ? 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; + } + } + } } \ No newline at end of file diff --git a/Unosquare.RaspberryIO/Computer/WirelessNetworkInfo.cs b/Unosquare.RaspberryIO/Computer/WirelessNetworkInfo.cs index 9e41d24..ca8659b 100644 --- a/Unosquare.RaspberryIO/Computer/WirelessNetworkInfo.cs +++ b/Unosquare.RaspberryIO/Computer/WirelessNetworkInfo.cs @@ -1,23 +1,29 @@ -namespace Unosquare.RaspberryIO.Computer -{ +using System; + +namespace Unosquare.RaspberryIO.Computer { + /// + /// Represents a wireless network information. + /// + public class WirelessNetworkInfo { /// - /// Represents a wireless network information. + /// Gets the ESSID of the Wireless network. /// - public class WirelessNetworkInfo - { - /// - /// Gets the ESSID of the Wireless network. - /// - public string Name { get; internal set; } - - /// - /// Gets the network quality. - /// - public string Quality { get; internal set; } - - /// - /// Gets a value indicating whether this instance is encrypted. - /// - public bool IsEncrypted { get; internal set; } - } + public String? Name { + get; internal set; + } + + /// + /// Gets the network quality. + /// + public String? Quality { + get; internal set; + } + + /// + /// Gets a value indicating whether this instance is encrypted. + /// + public Boolean IsEncrypted { + get; internal set; + } + } } diff --git a/Unosquare.RaspberryIO/Native/Standard.cs b/Unosquare.RaspberryIO/Native/Standard.cs index cd15d80..359e8a0 100644 --- a/Unosquare.RaspberryIO/Native/Standard.cs +++ b/Unosquare.RaspberryIO/Native/Standard.cs @@ -1,17 +1,16 @@ -namespace Unosquare.RaspberryIO.Native -{ - using System.Runtime.InteropServices; - - internal static class Standard - { - internal const string LibCLibrary = "libc"; - - /// - /// Fills in the structure with information about the system. - /// - /// The name. - /// The result. - [DllImport(LibCLibrary, EntryPoint = "uname", SetLastError = true)] - public static extern int Uname(out SystemName name); - } +using System; +using System.Runtime.InteropServices; + +namespace Unosquare.RaspberryIO.Native { + internal static class Standard { + internal const String LibCLibrary = "libc"; + + /// + /// Fills in the structure with information about the system. + /// + /// The name. + /// The result. + [DllImport(LibCLibrary, EntryPoint = "uname", SetLastError = true)] + public static extern Int32 Uname(out SystemName name); + } } diff --git a/Unosquare.RaspberryIO/Native/SystemName.cs b/Unosquare.RaspberryIO/Native/SystemName.cs index b9f5903..bd11a31 100644 --- a/Unosquare.RaspberryIO/Native/SystemName.cs +++ b/Unosquare.RaspberryIO/Native/SystemName.cs @@ -1,47 +1,46 @@ -namespace Unosquare.RaspberryIO.Native -{ - using System.Runtime.InteropServices; - +using System; +using System.Runtime.InteropServices; + +namespace Unosquare.RaspberryIO.Native { + /// + /// OS uname structure. + /// + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] + public struct SystemName { /// - /// OS uname structure. + /// System name. /// - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] - public struct SystemName - { - /// - /// System name. - /// - [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 65)] - public string SysName; - - /// - /// Node name. - /// - [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 65)] - public string NodeName; - - /// - /// Release level. - /// - [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 65)] - public string Release; - - /// - /// Version level. - /// - [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 65)] - public string Version; - - /// - /// Hardware level. - /// - [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 65)] - public string Machine; - - /// - /// Domain name. - /// - [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 65)] - public string DomainName; - } + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 65)] + public String SysName; + + /// + /// Node name. + /// + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 65)] + public String NodeName; + + /// + /// Release level. + /// + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 65)] + public String Release; + + /// + /// Version level. + /// + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 65)] + public String Version; + + /// + /// Hardware level. + /// + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 65)] + public String Machine; + + /// + /// Domain name. + /// + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 65)] + public String DomainName; + } } \ No newline at end of file diff --git a/Unosquare.RaspberryIO/Pi.cs b/Unosquare.RaspberryIO/Pi.cs index 2500924..11252f2 100644 --- a/Unosquare.RaspberryIO/Pi.cs +++ b/Unosquare.RaspberryIO/Pi.cs @@ -1,141 +1,138 @@ -namespace Unosquare.RaspberryIO -{ - using Abstractions; - using Camera; - using Computer; - using Swan; - using Swan.DependencyInjection; - using System; - using System.Threading.Tasks; - +using System; +using System.Threading.Tasks; + +using Swan; +using Swan.DependencyInjection; + +using Unosquare.RaspberryIO.Abstractions; +using Unosquare.RaspberryIO.Camera; +using Unosquare.RaspberryIO.Computer; + +namespace Unosquare.RaspberryIO { + /// + /// Our main character. Provides access to the Raspberry Pi's GPIO, system and board information and Camera. + /// + public static class Pi { + private const String MissingDependenciesMessage = "You need to load a valid assembly (WiringPi or PiGPIO)."; + private static readonly Object SyncLock = new Object(); + private static Boolean _isInit; + private static SystemInfo? _info; + /// - /// Our main character. Provides access to the Raspberry Pi's GPIO, system and board information and Camera. + /// Initializes static members of the class. /// - public static class Pi - { - private const string MissingDependenciesMessage = "You need to load a valid assembly (WiringPi or PiGPIO)."; - private static readonly object SyncLock = new object(); - private static bool _isInit; - private static SystemInfo _info; - - /// - /// Initializes static members of the class. - /// - static Pi() - { - lock (SyncLock) - { - Camera = CameraController.Instance; - PiDisplay = DsiDisplay.Instance; - Audio = AudioSettings.Instance; - Bluetooth = Bluetooth.Instance; - } - } - - /// - /// Provides information on this Raspberry Pi's CPU and form factor. - /// - public static SystemInfo Info => _info ??= SystemInfo.Instance; - - /// - /// Provides access to the Raspberry Pi's GPIO as a collection of GPIO Pins. - /// - public static IGpioController Gpio => - ResolveDependency(); - - /// - /// Provides access to the 2-channel SPI bus. - /// - public static ISpiBus Spi => - ResolveDependency(); - - /// - /// Provides access to the functionality of the i2c bus. - /// - public static II2CBus I2C => - ResolveDependency(); - - /// - /// Provides access to timing functionality. - /// - public static ITiming Timing => - ResolveDependency(); - - /// - /// Provides access to threading functionality. - /// - public static IThreading Threading => - ResolveDependency(); - - /// - /// Provides access to the official Raspberry Pi Camera. - /// - public static CameraController Camera { get; } - - /// - /// Provides access to the official Raspberry Pi 7-inch DSI Display. - /// - public static DsiDisplay PiDisplay { get; } - - /// - /// Provides access to Raspberry Pi ALSA sound card driver. - /// - public static AudioSettings Audio { get; } - - /// - /// Provides access to Raspberry Pi Bluetooth driver. - /// - public static Bluetooth Bluetooth { get; } - - /// - /// Restarts the Pi. Must be running as SU. - /// - /// The process result. - public static Task RestartAsync() => ProcessRunner.GetProcessResultAsync("reboot"); - - /// - /// Restarts the Pi. Must be running as SU. - /// - /// The process result. - public static ProcessResult Restart() => RestartAsync().GetAwaiter().GetResult(); - - /// - /// Halts the Pi. Must be running as SU. - /// - /// The process result. - public static Task ShutdownAsync() => ProcessRunner.GetProcessResultAsync("halt"); - - /// - /// Halts the Pi. Must be running as SU. - /// - /// The process result. - public static ProcessResult Shutdown() => ShutdownAsync().GetAwaiter().GetResult(); - - /// - /// Initializes an Abstractions implementation. - /// - /// An implementation of . - public static void Init() - where T : IBootstrap - { - lock (SyncLock) - { - if (_isInit) return; - - Activator.CreateInstance().Bootstrap(); - _isInit = true; - } - } - - private static T ResolveDependency() - where T : class - { - if (!_isInit) - throw new InvalidOperationException($"You must first initialize {nameof(Pi)} referencing a valid {nameof(IBootstrap)} implementation."); - - return DependencyContainer.Current.CanResolve() - ? DependencyContainer.Current.Resolve() - : throw new InvalidOperationException(MissingDependenciesMessage); - } - } + static Pi() { + lock(SyncLock) { + Camera = CameraController.Instance; + PiDisplay = DsiDisplay.Instance; + Audio = AudioSettings.Instance; + Bluetooth = Bluetooth.Instance; + } + } + + /// + /// Provides information on this Raspberry Pi's CPU and form factor. + /// + public static SystemInfo Info => _info ??= SystemInfo.Instance; + + /// + /// Provides access to the Raspberry Pi's GPIO as a collection of GPIO Pins. + /// + public static IGpioController Gpio => ResolveDependency(); + + /// + /// Provides access to the 2-channel SPI bus. + /// + public static ISpiBus Spi => ResolveDependency(); + + /// + /// Provides access to the functionality of the i2c bus. + /// + public static II2CBus I2C => ResolveDependency(); + + /// + /// Provides access to timing functionality. + /// + public static ITiming Timing => ResolveDependency(); + + /// + /// Provides access to threading functionality. + /// + public static IThreading Threading => ResolveDependency(); + + /// + /// Provides access to the official Raspberry Pi Camera. + /// + public static CameraController Camera { + get; + } + + /// + /// Provides access to the official Raspberry Pi 7-inch DSI Display. + /// + public static DsiDisplay PiDisplay { + get; + } + + /// + /// Provides access to Raspberry Pi ALSA sound card driver. + /// + public static AudioSettings Audio { + get; + } + + /// + /// Provides access to Raspberry Pi Bluetooth driver. + /// + public static Bluetooth Bluetooth { + get; + } + + /// + /// Restarts the Pi. Must be running as SU. + /// + /// The process result. + public static Task RestartAsync() => ProcessRunner.GetProcessResultAsync("reboot"); + + /// + /// Restarts the Pi. Must be running as SU. + /// + /// The process result. + public static ProcessResult Restart() => RestartAsync().GetAwaiter().GetResult(); + + /// + /// Halts the Pi. Must be running as SU. + /// + /// The process result. + public static Task ShutdownAsync() => ProcessRunner.GetProcessResultAsync("halt"); + + /// + /// Halts the Pi. Must be running as SU. + /// + /// The process result. + public static ProcessResult Shutdown() => ShutdownAsync().GetAwaiter().GetResult(); + + /// + /// Initializes an Abstractions implementation. + /// + /// An implementation of . + public static void Init() where T : IBootstrap { + lock(SyncLock) { + if(_isInit) { + return; + } + + Activator.CreateInstance().Bootstrap(); + _isInit = true; + } + } + + private static T ResolveDependency() where T : class { + if(!_isInit) { + throw new InvalidOperationException($"You must first initialize {nameof(Pi)} referencing a valid {nameof(IBootstrap)} implementation."); + } + + return DependencyContainer.Current.CanResolve() ? DependencyContainer.Current.Resolve() : throw new InvalidOperationException(MissingDependenciesMessage); + } + } } diff --git a/Unosquare.RaspberryIO/Unosquare.RaspberryIO.csproj b/Unosquare.RaspberryIO/Unosquare.RaspberryIO.csproj index 5638c85..d6d4e66 100644 --- a/Unosquare.RaspberryIO/Unosquare.RaspberryIO.csproj +++ b/Unosquare.RaspberryIO/Unosquare.RaspberryIO.csproj @@ -16,6 +16,7 @@ This library enables developers to use the various Raspberry Pi's hardware modul https://raw.githubusercontent.com/unosquare/raspberryio/master/LICENSE Raspberry Pi GPIO Camera SPI I2C Embedded IoT Mono C# .NET 8.0 + enable