Codingstyle....

This commit is contained in:
BlubbFish 2019-12-06 23:12:34 +01:00
parent a7a7278eda
commit b469c4ed6e
22 changed files with 3158 additions and 3245 deletions

View File

@ -1,20 +1,16 @@
namespace Unosquare.RaspberryIO using System;
{
using System;
namespace Unosquare.RaspberryIO {
/// <inheritdoc /> /// <inheritdoc />
/// <summary> /// <summary>
/// Occurs when an exception is thrown in the <c>Bluetooth</c> component. /// Occurs when an exception is thrown in the <c>Bluetooth</c> component.
/// </summary> /// </summary>
public class BluetoothErrorException : Exception public class BluetoothErrorException : Exception {
{
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="BluetoothErrorException"/> class. /// Initializes a new instance of the <see cref="BluetoothErrorException"/> class.
/// </summary> /// </summary>
/// <param name="message">The message.</param> /// <param name="message">The message.</param>
public BluetoothErrorException(string message) public BluetoothErrorException(String message) : base(message) {
: base(message)
{
} }
} }
} }

View File

@ -1,23 +1,20 @@
namespace Unosquare.RaspberryIO.Camera using System;
{ using System.Linq;
using System;
using System.Linq;
using Swan;
using Swan;
namespace Unosquare.RaspberryIO.Camera {
/// <summary> /// <summary>
/// A simple RGB color class to represent colors in RGB and YUV colorspaces. /// A simple RGB color class to represent colors in RGB and YUV colorspaces.
/// </summary> /// </summary>
public class CameraColor public class CameraColor {
{
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="CameraColor"/> class. /// Initializes a new instance of the <see cref="CameraColor"/> class.
/// </summary> /// </summary>
/// <param name="r">The red.</param> /// <param name="r">The red.</param>
/// <param name="g">The green.</param> /// <param name="g">The green.</param>
/// <param name="b">The blue.</param> /// <param name="b">The blue.</param>
public CameraColor(int r, int g, int b) public CameraColor(Int32 r, Int32 g, Int32 b) : this(r, g, b, String.Empty) {
: this(r, g, b, string.Empty)
{
} }
/// <summary> /// <summary>
@ -27,16 +24,15 @@
/// <param name="g">The green.</param> /// <param name="g">The green.</param>
/// <param name="b">The blue.</param> /// <param name="b">The blue.</param>
/// <param name="name">The well-known color name.</param> /// <param name="name">The well-known color name.</param>
public CameraColor(int r, int g, int b, string 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)) };
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); Single y = this.R * .299000f + this.G * .587000f + this.B * .114000f;
var u = (R * -.168736f) + (G * -.331264f) + (B * .500000f) + 128f; Single u = this.R * -.168736f + this.G * -.331264f + this.B * .500000f + 128f;
var v = (R * .500000f) + (G * -.418688f) + (B * -.081312f) + 128f; Single v = this.R * .500000f + this.G * -.418688f + this.B * -.081312f + 128f;
YUV = new[] { (byte)y.Clamp(0, 255), (byte)u.Clamp(0, 255), (byte)v.Clamp(0, 255) }; this.YUV = new[] { (Byte)y.Clamp(0, 255), (Byte)u.Clamp(0, 255), (Byte)v.Clamp(0, 255) };
Name = name; this.Name = name;
} }
#region Static Definitions #region Static Definitions
@ -71,32 +67,38 @@
/// <summary> /// <summary>
/// Gets the well-known color name. /// Gets the well-known color name.
/// </summary> /// </summary>
public string Name { get; } public String Name {
get;
}
/// <summary> /// <summary>
/// Gets the red byte. /// Gets the red byte.
/// </summary> /// </summary>
public byte R => RGB[0]; public Byte R => this.RGB[0];
/// <summary> /// <summary>
/// Gets the green byte. /// Gets the green byte.
/// </summary> /// </summary>
public byte G => RGB[1]; public Byte G => this.RGB[1];
/// <summary> /// <summary>
/// Gets the blue byte. /// Gets the blue byte.
/// </summary> /// </summary>
public byte B => RGB[2]; public Byte B => this.RGB[2];
/// <summary> /// <summary>
/// Gets the RGB byte array (3 bytes). /// Gets the RGB byte array (3 bytes).
/// </summary> /// </summary>
public byte[] RGB { get; } public Byte[] RGB {
get;
}
/// <summary> /// <summary>
/// Gets the YUV byte array (3 bytes). /// Gets the YUV byte array (3 bytes).
/// </summary> /// </summary>
public byte[] YUV { get; } public Byte[] YUV {
get;
}
/// <summary> /// <summary>
/// Returns a hexadecimal representation of the RGB byte array. /// Returns a hexadecimal representation of the RGB byte array.
@ -104,10 +106,12 @@
/// </summary> /// </summary>
/// <param name="reverse">if set to <c>true</c> [reverse].</param> /// <param name="reverse">if set to <c>true</c> [reverse].</param>
/// <returns>A string.</returns> /// <returns>A string.</returns>
public string ToRgbHex(bool reverse) public String ToRgbHex(Boolean reverse) {
{ Byte[] data = this.RGB.ToArray();
var data = RGB.ToArray(); if(reverse) {
if (reverse) Array.Reverse(data); Array.Reverse(data);
}
return ToHex(data); return ToHex(data);
} }
@ -117,10 +121,12 @@
/// </summary> /// </summary>
/// <param name="reverse">if set to <c>true</c> [reverse].</param> /// <param name="reverse">if set to <c>true</c> [reverse].</param>
/// <returns>A string.</returns> /// <returns>A string.</returns>
public string ToYuvHex(bool reverse) public String ToYuvHex(Boolean reverse) {
{ Byte[] data = this.YUV.ToArray();
var data = YUV.ToArray(); if(reverse) {
if (reverse) Array.Reverse(data); Array.Reverse(data);
}
return ToHex(data); return ToHex(data);
} }
@ -129,6 +135,6 @@
/// </summary> /// </summary>
/// <param name="data">The data.</param> /// <param name="data">The data.</param>
/// <returns>A string.</returns> /// <returns>A string.</returns>
private static string ToHex(byte[] data) => $"0x{BitConverter.ToString(data).Replace("-", string.Empty).ToLowerInvariant()}"; private static String ToHex(Byte[] data) => $"0x{BitConverter.ToString(data).Replace("-", String.Empty).ToLowerInvariant()}";
} }
} }

View File

@ -1,21 +1,20 @@
namespace Unosquare.RaspberryIO.Camera using System;
{ using System.IO;
using Swan; using System.Threading;
using System; using System.Threading.Tasks;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Swan;
namespace Unosquare.RaspberryIO.Camera {
/// <summary> /// <summary>
/// The Raspberry Pi's camera controller wrapping raspistill and raspivid programs. /// The Raspberry Pi's camera controller wrapping raspistill and raspivid programs.
/// This class is a singleton. /// This class is a singleton.
/// </summary> /// </summary>
public class CameraController : SingletonBase<CameraController> public class CameraController : SingletonBase<CameraController> {
{
#region Private Declarations #region Private Declarations
private static readonly ManualResetEventSlim OperationDone = new ManualResetEventSlim(true); private static readonly ManualResetEventSlim OperationDone = new ManualResetEventSlim(true);
private static readonly object SyncRoot = new object(); private static readonly Object SyncRoot = new Object();
private static CancellationTokenSource _videoTokenSource = new CancellationTokenSource(); private static CancellationTokenSource _videoTokenSource = new CancellationTokenSource();
private static Task<Task>? _videoStreamTask; private static Task<Task>? _videoStreamTask;
@ -29,7 +28,7 @@ namespace Unosquare.RaspberryIO.Camera
/// <value> /// <value>
/// <c>true</c> if this instance is busy; otherwise, <c>false</c>. /// <c>true</c> if this instance is busy; otherwise, <c>false</c>.
/// </value> /// </value>
public bool IsBusy => OperationDone.IsSet == false; public Boolean IsBusy => OperationDone.IsSet == false;
#endregion #endregion
@ -42,31 +41,23 @@ namespace Unosquare.RaspberryIO.Camera
/// <param name="ct">The ct.</param> /// <param name="ct">The ct.</param>
/// <returns>The image bytes.</returns> /// <returns>The image bytes.</returns>
/// <exception cref="InvalidOperationException">Cannot use camera module because it is currently busy.</exception> /// <exception cref="InvalidOperationException">Cannot use camera module because it is currently busy.</exception>
public async Task<byte[]> CaptureImageAsync(CameraStillSettings settings, CancellationToken ct = default) public async Task<Byte[]> CaptureImageAsync(CameraStillSettings settings, CancellationToken ct = default) {
{ if(Instance.IsBusy) {
if (Instance.IsBusy)
throw new InvalidOperationException("Cannot use camera module because it is currently busy."); throw new InvalidOperationException("Cannot use camera module because it is currently busy.");
}
if (settings.CaptureTimeoutMilliseconds <= 0) if(settings.CaptureTimeoutMilliseconds <= 0) {
throw new ArgumentException($"{nameof(settings.CaptureTimeoutMilliseconds)} needs to be greater than 0"); throw new ArgumentException($"{nameof(settings.CaptureTimeoutMilliseconds)} needs to be greater than 0");
}
try try {
{
OperationDone.Reset(); OperationDone.Reset();
using var output = new MemoryStream(); using MemoryStream output = new MemoryStream();
var exitCode = await ProcessRunner.RunProcessAsync( Int32 exitCode = await ProcessRunner.RunProcessAsync(settings.CommandName, settings.CreateProcessArguments(), (data, proc) => output.Write(data, 0, data.Length), null, true, ct).ConfigureAwait(false);
settings.CommandName,
settings.CreateProcessArguments(),
(data, proc) => output.Write(data, 0, data.Length),
null,
true,
ct).ConfigureAwait(false);
return exitCode != 0 ? Array.Empty<byte>() : output.ToArray(); return exitCode != 0 ? Array.Empty<Byte>() : output.ToArray();
} } finally {
finally
{
OperationDone.Set(); OperationDone.Set();
} }
} }
@ -76,7 +67,7 @@ namespace Unosquare.RaspberryIO.Camera
/// </summary> /// </summary>
/// <param name="settings">The settings.</param> /// <param name="settings">The settings.</param>
/// <returns>The image bytes.</returns> /// <returns>The image bytes.</returns>
public byte[] CaptureImage(CameraStillSettings settings) => CaptureImageAsync(settings).GetAwaiter().GetResult(); public Byte[] CaptureImage(CameraStillSettings settings) => this.CaptureImageAsync(settings).GetAwaiter().GetResult();
/// <summary> /// <summary>
/// Captures a JPEG encoded image asynchronously at 90% quality. /// Captures a JPEG encoded image asynchronously at 90% quality.
@ -85,17 +76,15 @@ namespace Unosquare.RaspberryIO.Camera
/// <param name="height">The height.</param> /// <param name="height">The height.</param>
/// <param name="ct">The ct.</param> /// <param name="ct">The ct.</param>
/// <returns>The image bytes.</returns> /// <returns>The image bytes.</returns>
public Task<byte[]> CaptureImageJpegAsync(int width, int height, CancellationToken ct = default) public Task<Byte[]> CaptureImageJpegAsync(Int32 width, Int32 height, CancellationToken ct = default) {
{ CameraStillSettings settings = new CameraStillSettings {
var settings = new CameraStillSettings
{
CaptureWidth = width, CaptureWidth = width,
CaptureHeight = height, CaptureHeight = height,
CaptureJpegQuality = 90, CaptureJpegQuality = 90,
CaptureTimeoutMilliseconds = 300, CaptureTimeoutMilliseconds = 300,
}; };
return CaptureImageAsync(settings, ct); return this.CaptureImageAsync(settings, ct);
} }
/// <summary> /// <summary>
@ -104,7 +93,7 @@ namespace Unosquare.RaspberryIO.Camera
/// <param name="width">The width.</param> /// <param name="width">The width.</param>
/// <param name="height">The height.</param> /// <param name="height">The height.</param>
/// <returns>The image bytes.</returns> /// <returns>The image bytes.</returns>
public byte[] CaptureImageJpeg(int width, int height) => CaptureImageJpegAsync(width, height).GetAwaiter().GetResult(); public Byte[] CaptureImageJpeg(Int32 width, Int32 height) => this.CaptureImageJpegAsync(width, height).GetAwaiter().GetResult();
#endregion #endregion
@ -116,17 +105,15 @@ namespace Unosquare.RaspberryIO.Camera
/// </summary> /// </summary>
/// <param name="onDataCallback">The on data callback.</param> /// <param name="onDataCallback">The on data callback.</param>
/// <param name="onExitCallback">The on exit callback.</param> /// <param name="onExitCallback">The on exit callback.</param>
public void OpenVideoStream(Action<byte[]> onDataCallback, Action? onExitCallback = null) public void OpenVideoStream(Action<Byte[]> onDataCallback, Action? onExitCallback = null) {
{ CameraVideoSettings settings = new CameraVideoSettings {
var settings = new CameraVideoSettings
{
CaptureTimeoutMilliseconds = 0, CaptureTimeoutMilliseconds = 0,
CaptureDisplayPreview = false, CaptureDisplayPreview = false,
CaptureWidth = 1920, CaptureWidth = 1920,
CaptureHeight = 1080, CaptureHeight = 1080,
}; };
OpenVideoStream(settings, onDataCallback, onExitCallback); this.OpenVideoStream(settings, onDataCallback, onExitCallback);
} }
/// <summary> /// <summary>
@ -137,21 +124,19 @@ namespace Unosquare.RaspberryIO.Camera
/// <param name="onExitCallback">The on exit callback.</param> /// <param name="onExitCallback">The on exit callback.</param>
/// <exception cref="InvalidOperationException">Cannot use camera module because it is currently busy.</exception> /// <exception cref="InvalidOperationException">Cannot use camera module because it is currently busy.</exception>
/// <exception cref="ArgumentException">CaptureTimeoutMilliseconds.</exception> /// <exception cref="ArgumentException">CaptureTimeoutMilliseconds.</exception>
public void OpenVideoStream(CameraVideoSettings settings, Action<byte[]> onDataCallback, Action? onExitCallback = null) public void OpenVideoStream(CameraVideoSettings settings, Action<Byte[]> onDataCallback, Action? onExitCallback = null) {
{ if(Instance.IsBusy) {
if (Instance.IsBusy)
throw new InvalidOperationException("Cannot use camera module because it is currently busy."); throw new InvalidOperationException("Cannot use camera module because it is currently busy.");
}
if (settings.CaptureTimeoutMilliseconds < 0) if(settings.CaptureTimeoutMilliseconds < 0) {
throw new ArgumentException($"{nameof(settings.CaptureTimeoutMilliseconds)} needs to be greater than or equal to 0"); throw new ArgumentException($"{nameof(settings.CaptureTimeoutMilliseconds)} needs to be greater than or equal to 0");
}
try try {
{
OperationDone.Reset(); OperationDone.Reset();
_videoStreamTask = Task.Factory.StartNew(() => VideoWorkerDoWork(settings, onDataCallback, onExitCallback), _videoTokenSource.Token); _videoStreamTask = Task.Factory.StartNew(() => VideoWorkerDoWork(settings, onDataCallback, onExitCallback), _videoTokenSource.Token);
} } catch {
catch
{
OperationDone.Set(); OperationDone.Set();
throw; throw;
} }
@ -160,16 +145,14 @@ namespace Unosquare.RaspberryIO.Camera
/// <summary> /// <summary>
/// Closes the video stream of a video stream is open. /// Closes the video stream of a video stream is open.
/// </summary> /// </summary>
public void CloseVideoStream() public void CloseVideoStream() {
{ lock(SyncRoot) {
lock (SyncRoot) if(this.IsBusy == false) {
{
if (IsBusy == false)
return; return;
} }
}
if (_videoTokenSource.IsCancellationRequested == false) if(_videoTokenSource.IsCancellationRequested == false) {
{
_videoTokenSource.Cancel(); _videoTokenSource.Cancel();
_videoStreamTask?.Wait(); _videoStreamTask?.Wait();
} }
@ -177,29 +160,14 @@ namespace Unosquare.RaspberryIO.Camera
_videoTokenSource = new CancellationTokenSource(); _videoTokenSource = new CancellationTokenSource();
} }
private static async Task VideoWorkerDoWork( private static async Task VideoWorkerDoWork(CameraVideoSettings settings, Action<Byte[]> onDataCallback, Action? onExitCallback) {
CameraVideoSettings settings, try {
Action<byte[]> onDataCallback, _ = await ProcessRunner.RunProcessAsync(settings.CommandName, settings.CreateProcessArguments(), (data, proc) => onDataCallback?.Invoke(data), null, true, _videoTokenSource.Token).ConfigureAwait(false);
Action onExitCallback)
{
try
{
await ProcessRunner.RunProcessAsync(
settings.CommandName,
settings.CreateProcessArguments(),
(data, proc) => onDataCallback?.Invoke(data),
null,
true,
_videoTokenSource.Token).ConfigureAwait(false);
onExitCallback?.Invoke(); onExitCallback?.Invoke();
} } catch {
catch
{
// swallow // swallow
} } finally {
finally
{
Instance.CloseVideoStream(); Instance.CloseVideoStream();
OperationDone.Set(); OperationDone.Set();
} }

View File

@ -1,13 +1,13 @@
namespace Unosquare.RaspberryIO.Camera using System;
{ using System.Globalization;
using System.Globalization;
using Swan;
using Swan;
namespace Unosquare.RaspberryIO.Camera {
/// <summary> /// <summary>
/// Defines the Raspberry Pi camera's sensor ROI (Region of Interest). /// Defines the Raspberry Pi camera's sensor ROI (Region of Interest).
/// </summary> /// </summary>
public struct CameraRect public struct CameraRect {
{
/// <summary> /// <summary>
/// The default ROI which is the entire area. /// The default ROI which is the entire area.
/// </summary> /// </summary>
@ -19,7 +19,9 @@
/// <value> /// <value>
/// The x. /// The x.
/// </value> /// </value>
public decimal X { get; set; } public Decimal X {
get; set;
}
/// <summary> /// <summary>
/// Gets or sets the y location in relative coordinates. (0.0 to 1.0). /// Gets or sets the y location in relative coordinates. (0.0 to 1.0).
@ -27,7 +29,9 @@
/// <value> /// <value>
/// The y. /// The y.
/// </value> /// </value>
public decimal Y { get; set; } public Decimal Y {
get; set;
}
/// <summary> /// <summary>
/// Gets or sets the width in relative coordinates. (0.0 to 1.0). /// Gets or sets the width in relative coordinates. (0.0 to 1.0).
@ -35,7 +39,9 @@
/// <value> /// <value>
/// The w. /// The w.
/// </value> /// </value>
public decimal W { get; set; } public Decimal W {
get; set;
}
/// <summary> /// <summary>
/// Gets or sets the height in relative coordinates. (0.0 to 1.0). /// Gets or sets the height in relative coordinates. (0.0 to 1.0).
@ -43,7 +49,9 @@
/// <value> /// <value>
/// The h. /// The h.
/// </value> /// </value>
public decimal H { get; set; } public Decimal H {
get; set;
}
/// <summary> /// <summary>
/// Gets a value indicating whether this instance is equal to the default (The entire area). /// Gets a value indicating whether this instance is equal to the default (The entire area).
@ -51,32 +59,29 @@
/// <value> /// <value>
/// <c>true</c> if this instance is default; otherwise, <c>false</c>. /// <c>true</c> if this instance is default; otherwise, <c>false</c>.
/// </value> /// </value>
public bool IsDefault public Boolean IsDefault {
{ get {
get this.Clamp();
{ return this.X == Default.X && this.Y == Default.Y && this.W == Default.W && this.H == Default.H;
Clamp();
return X == Default.X && Y == Default.Y && W == Default.W && H == Default.H;
} }
} }
/// <summary> /// <summary>
/// Clamps the members of this ROI to their minimum and maximum values. /// Clamps the members of this ROI to their minimum and maximum values.
/// </summary> /// </summary>
public void Clamp() public void Clamp() {
{ this.X = this.X.Clamp(0M, 1M);
X = X.Clamp(0M, 1M); this.Y = this.Y.Clamp(0M, 1M);
Y = Y.Clamp(0M, 1M); this.W = this.W.Clamp(0M, 1M - this.X);
W = W.Clamp(0M, 1M - X); this.H = this.H.Clamp(0M, 1M - this.Y);
H = H.Clamp(0M, 1M - Y);
} }
/// <summary> /// <summary>
/// Returns a <see cref="string" /> that represents this instance. /// Returns a <see cref="String" /> that represents this instance.
/// </summary> /// </summary>
/// <returns> /// <returns>
/// A <see cref="string" /> that represents this instance. /// A <see cref="String" /> that represents this instance.
/// </returns> /// </returns>
public override string ToString() => $"{X.ToString(CultureInfo.InvariantCulture)},{Y.ToString(CultureInfo.InvariantCulture)},{W.ToString(CultureInfo.InvariantCulture)},{H.ToString(CultureInfo.InvariantCulture)}"; public override String ToString() => $"{this.X.ToString(CultureInfo.InvariantCulture)},{this.Y.ToString(CultureInfo.InvariantCulture)},{this.W.ToString(CultureInfo.InvariantCulture)},{this.H.ToString(CultureInfo.InvariantCulture)}";
} }
} }

View File

@ -1,16 +1,16 @@
namespace Unosquare.RaspberryIO.Camera using System;
{ using System.Globalization;
using System.Globalization; using System.Text;
using System.Text;
using Swan;
using Swan;
namespace Unosquare.RaspberryIO.Camera {
/// <summary> /// <summary>
/// A base class to implement raspistill and raspivid wrappers /// A base class to implement raspistill and raspivid wrappers
/// Full documentation available at /// Full documentation available at
/// https://www.raspberrypi.org/documentation/raspbian/applications/camera.md. /// https://www.raspberrypi.org/documentation/raspbian/applications/camera.md.
/// </summary> /// </summary>
public abstract class CameraSettingsBase public abstract class CameraSettingsBase {
{
/// <summary> /// <summary>
/// The Invariant Culture shorthand. /// The Invariant Culture shorthand.
/// </summary> /// </summary>
@ -23,27 +23,27 @@ namespace Unosquare.RaspberryIO.Camera
/// Default value is 5000 /// Default value is 5000
/// Recommended value is at least 300 in order to let the light collectors open. /// Recommended value is at least 300 in order to let the light collectors open.
/// </summary> /// </summary>
public int CaptureTimeoutMilliseconds { get; set; } = 5000; public Int32 CaptureTimeoutMilliseconds { get; set; } = 5000;
/// <summary> /// <summary>
/// Gets or sets a value indicating whether or not to show a preview window on the screen. /// Gets or sets a value indicating whether or not to show a preview window on the screen.
/// </summary> /// </summary>
public bool CaptureDisplayPreview { get; set; } = false; public Boolean CaptureDisplayPreview { get; set; } = false;
/// <summary> /// <summary>
/// Gets or sets a value indicating whether a preview window is shown in full screen mode if enabled. /// Gets or sets a value indicating whether a preview window is shown in full screen mode if enabled.
/// </summary> /// </summary>
public bool CaptureDisplayPreviewInFullScreen { get; set; } = true; public Boolean CaptureDisplayPreviewInFullScreen { get; set; } = true;
/// <summary> /// <summary>
/// Gets or sets a value indicating whether video stabilization should be enabled. /// Gets or sets a value indicating whether video stabilization should be enabled.
/// </summary> /// </summary>
public bool CaptureVideoStabilizationEnabled { get; set; } = false; public Boolean CaptureVideoStabilizationEnabled { get; set; } = false;
/// <summary> /// <summary>
/// Gets or sets the display preview opacity only if the display preview property is enabled. /// Gets or sets the display preview opacity only if the display preview property is enabled.
/// </summary> /// </summary>
public byte CaptureDisplayPreviewOpacity { get; set; } = 255; public Byte CaptureDisplayPreviewOpacity { get; set; } = 255;
/// <summary> /// <summary>
/// Gets or sets the capture sensor region of interest in relative coordinates. /// Gets or sets the capture sensor region of interest in relative coordinates.
@ -54,7 +54,7 @@ namespace Unosquare.RaspberryIO.Camera
/// Gets or sets the capture shutter speed in microseconds. /// Gets or sets the capture shutter speed in microseconds.
/// Default -1, Range 0 to 6000000 (equivalent to 6 seconds). /// Default -1, Range 0 to 6000000 (equivalent to 6 seconds).
/// </summary> /// </summary>
public int CaptureShutterSpeedMicroseconds { get; set; } = -1; public Int32 CaptureShutterSpeedMicroseconds { get; set; } = -1;
/// <summary> /// <summary>
/// Gets or sets the exposure mode. /// Gets or sets the exposure mode.
@ -68,7 +68,7 @@ namespace Unosquare.RaspberryIO.Camera
/// Exposure can be adjusted by changing either the lens f-number or the exposure time; /// 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. /// which one is changed usually depends on the camera's exposure mode.
/// </summary> /// </summary>
public int CaptureExposureCompensation { get; set; } = 0; public Int32 CaptureExposureCompensation { get; set; } = 0;
/// <summary> /// <summary>
/// Gets or sets the capture metering mode. /// Gets or sets the capture metering mode.
@ -85,21 +85,22 @@ namespace Unosquare.RaspberryIO.Camera
/// Only takes effect if White balance control is set to off. /// Only takes effect if White balance control is set to off.
/// Default is 0. /// Default is 0.
/// </summary> /// </summary>
public decimal CaptureWhiteBalanceGainBlue { get; set; } = 0M; public Decimal CaptureWhiteBalanceGainBlue { get; set; } = 0M;
/// <summary> /// <summary>
/// Gets or sets the capture white balance gain on the red channel. Example: 1.75 /// 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. /// Only takes effect if White balance control is set to off.
/// Default is 0. /// Default is 0.
/// </summary> /// </summary>
public decimal CaptureWhiteBalanceGainRed { get; set; } = 0M; public Decimal CaptureWhiteBalanceGainRed { get; set; } = 0M;
/// <summary> /// <summary>
/// Gets or sets the dynamic range compensation. /// 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. /// 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.
/// </summary> /// </summary>
public CameraDynamicRangeCompensation CaptureDynamicRangeCompensation { get; set; } = public CameraDynamicRangeCompensation CaptureDynamicRangeCompensation {
CameraDynamicRangeCompensation.Off; get; set;
} = CameraDynamicRangeCompensation.Off;
#endregion #endregion
@ -109,39 +110,39 @@ namespace Unosquare.RaspberryIO.Camera
/// Gets or sets the width of the picture to take. /// 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. /// Less than or equal to 0 in either width or height means maximum resolution available.
/// </summary> /// </summary>
public int CaptureWidth { get; set; } = 640; public Int32 CaptureWidth { get; set; } = 640;
/// <summary> /// <summary>
/// Gets or sets the height of the picture to take. /// 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. /// Less than or equal to 0 in either width or height means maximum resolution available.
/// </summary> /// </summary>
public int CaptureHeight { get; set; } = 480; public Int32 CaptureHeight { get; set; } = 480;
/// <summary> /// <summary>
/// Gets or sets the picture sharpness. Default is 0, Range form -100 to 100. /// Gets or sets the picture sharpness. Default is 0, Range form -100 to 100.
/// </summary> /// </summary>
public int ImageSharpness { get; set; } = 0; public Int32 ImageSharpness { get; set; } = 0;
/// <summary> /// <summary>
/// Gets or sets the picture contrast. Default is 0, Range form -100 to 100. /// Gets or sets the picture contrast. Default is 0, Range form -100 to 100.
/// </summary> /// </summary>
public int ImageContrast { get; set; } = 0; public Int32 ImageContrast { get; set; } = 0;
/// <summary> /// <summary>
/// Gets or sets the picture brightness. Default is 50, Range form 0 to 100. /// Gets or sets the picture brightness. Default is 50, Range form 0 to 100.
/// </summary> /// </summary>
public int ImageBrightness { get; set; } = 50; // from 0 to 100 public Int32 ImageBrightness { get; set; } = 50; // from 0 to 100
/// <summary> /// <summary>
/// Gets or sets the picture saturation. Default is 0, Range form -100 to 100. /// Gets or sets the picture saturation. Default is 0, Range form -100 to 100.
/// </summary> /// </summary>
public int ImageSaturation { get; set; } = 0; public Int32 ImageSaturation { get; set; } = 0;
/// <summary> /// <summary>
/// Gets or sets the picture ISO. Default is -1 Range is 100 to 800 /// Gets or sets the picture ISO. Default is -1 Range is 100 to 800
/// The higher the value, the more light the sensor absorbs. /// The higher the value, the more light the sensor absorbs.
/// </summary> /// </summary>
public int ImageIso { get; set; } = -1; public Int32 ImageIso { get; set; } = -1;
/// <summary> /// <summary>
/// Gets or sets the image capture effect to be applied. /// Gets or sets the image capture effect to be applied.
@ -153,14 +154,14 @@ namespace Unosquare.RaspberryIO.Camera
/// Default is -1, Range is 0 to 255 /// Default is -1, Range is 0 to 255
/// 128:128 should be effectively a monochrome image. /// 128:128 should be effectively a monochrome image.
/// </summary> /// </summary>
public int ImageColorEffectU { get; set; } = -1; // 0 to 255 public Int32 ImageColorEffectU { get; set; } = -1; // 0 to 255
/// <summary> /// <summary>
/// Gets or sets the color effect V coordinates. /// Gets or sets the color effect V coordinates.
/// Default is -1, Range is 0 to 255 /// Default is -1, Range is 0 to 255
/// 128:128 should be effectively a monochrome image. /// 128:128 should be effectively a monochrome image.
/// </summary> /// </summary>
public int ImageColorEffectV { get; set; } = -1; // 0 to 255 public Int32 ImageColorEffectV { get; set; } = -1; // 0 to 255
/// <summary> /// <summary>
/// Gets or sets the image rotation. Default is no rotation. /// Gets or sets the image rotation. Default is no rotation.
@ -170,12 +171,16 @@ namespace Unosquare.RaspberryIO.Camera
/// <summary> /// <summary>
/// Gets or sets a value indicating whether the image should be flipped horizontally. /// Gets or sets a value indicating whether the image should be flipped horizontally.
/// </summary> /// </summary>
public bool ImageFlipHorizontally { get; set; } public Boolean ImageFlipHorizontally {
get; set;
}
/// <summary> /// <summary>
/// Gets or sets a value indicating whether the image should be flipped vertically. /// Gets or sets a value indicating whether the image should be flipped vertically.
/// </summary> /// </summary>
public bool ImageFlipVertically { get; set; } public Boolean ImageFlipVertically {
get; set;
}
/// <summary> /// <summary>
/// Gets or sets the image annotations using a bitmask (or flags) notation. /// Gets or sets the image annotations using a bitmask (or flags) notation.
@ -188,13 +193,13 @@ namespace Unosquare.RaspberryIO.Camera
/// Text may include date/time placeholders by using the '%' character, as used by strftime. /// 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. /// Example: ABC %Y-%m-%d %X will output ABC 2015-10-28 20:09:33.
/// </summary> /// </summary>
public string ImageAnnotationsText { get; set; } = string.Empty; public String ImageAnnotationsText { get; set; } = String.Empty;
/// <summary> /// <summary>
/// Gets or sets the font size of the text annotations /// Gets or sets the font size of the text annotations
/// Default is -1, range is 6 to 160. /// Default is -1, range is 6 to 160.
/// </summary> /// </summary>
public int ImageAnnotationFontSize { get; set; } = -1; public Int32 ImageAnnotationFontSize { get; set; } = -1;
/// <summary> /// <summary>
/// Gets or sets the color of the text annotations. /// Gets or sets the color of the text annotations.
@ -219,118 +224,134 @@ namespace Unosquare.RaspberryIO.Camera
/// <summary> /// <summary>
/// Gets the command file executable. /// Gets the command file executable.
/// </summary> /// </summary>
public abstract string CommandName { get; } public abstract String CommandName {
get;
}
/// <summary> /// <summary>
/// Creates the process arguments. /// Creates the process arguments.
/// </summary> /// </summary>
/// <returns>The string that represents the process arguments.</returns> /// <returns>The string that represents the process arguments.</returns>
public virtual string CreateProcessArguments() public virtual String CreateProcessArguments() {
{ StringBuilder sb = new StringBuilder();
var sb = new StringBuilder(); _ = sb.Append("-o -"); // output to standard output as opposed to a file.
sb.Append("-o -"); // output to standard output as opposed to a file. _ = sb.Append($" -t {(this.CaptureTimeoutMilliseconds < 0 ? "0" : this.CaptureTimeoutMilliseconds.ToString(Ci))}");
sb.Append($" -t {(CaptureTimeoutMilliseconds < 0 ? "0" : CaptureTimeoutMilliseconds.ToString(Ci))}");
// Basic Width and height // Basic Width and height
if (CaptureWidth > 0 && CaptureHeight > 0) if(this.CaptureWidth > 0 && this.CaptureHeight > 0) {
{ _ = sb.Append($" -w {this.CaptureWidth.ToString(Ci)}");
sb.Append($" -w {CaptureWidth.ToString(Ci)}"); _ = sb.Append($" -h {this.CaptureHeight.ToString(Ci)}");
sb.Append($" -h {CaptureHeight.ToString(Ci)}");
} }
// Display Preview // Display Preview
if (CaptureDisplayPreview) if(this.CaptureDisplayPreview) {
{ if(this.CaptureDisplayPreviewInFullScreen) {
if (CaptureDisplayPreviewInFullScreen) _ = sb.Append(" -f");
sb.Append(" -f");
if (CaptureDisplayPreviewOpacity != byte.MaxValue)
sb.Append($" -op {CaptureDisplayPreviewOpacity.ToString(Ci)}");
} }
else
{ if(this.CaptureDisplayPreviewOpacity != Byte.MaxValue) {
sb.Append(" -n"); // no preview _ = sb.Append($" -op {this.CaptureDisplayPreviewOpacity.ToString(Ci)}");
}
} else {
_ = sb.Append(" -n"); // no preview
} }
// Picture Settings // Picture Settings
if (ImageSharpness != 0) if(this.ImageSharpness != 0) {
sb.Append($" -sh {ImageSharpness.Clamp(-100, 100).ToString(Ci)}"); _ = sb.Append($" -sh {this.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) if(this.ImageContrast != 0) {
sb.Append($" -mm {CaptureMeteringMode.ToString().ToLowerInvariant()}"); _ = sb.Append($" -co {this.ImageContrast.Clamp(-100, 100).ToString(Ci)}");
}
if (ImageRotation != CameraImageRotation.None) if(this.ImageBrightness != 50) {
sb.Append($" -rot {((int)ImageRotation).ToString(Ci)}"); _ = sb.Append($" -br {this.ImageBrightness.Clamp(0, 100).ToString(Ci)}");
}
if (ImageFlipHorizontally) if(this.ImageSaturation != 0) {
sb.Append(" -hf"); _ = sb.Append($" -sa {this.ImageSaturation.Clamp(-100, 100).ToString(Ci)}");
}
if (ImageFlipVertically) if(this.ImageIso >= 100) {
sb.Append(" -vf"); _ = sb.Append($" -ISO {this.ImageIso.Clamp(100, 800).ToString(Ci)}");
}
if (CaptureSensorRoi.IsDefault == false) if(this.CaptureVideoStabilizationEnabled) {
sb.Append($" -roi {CaptureSensorRoi}"); _ = sb.Append(" -vs");
}
if (CaptureShutterSpeedMicroseconds > 0) if(this.CaptureExposureCompensation != 0) {
sb.Append($" -ss {CaptureShutterSpeedMicroseconds.Clamp(0, 6000000).ToString(Ci)}"); _ = sb.Append($" -ev {this.CaptureExposureCompensation.Clamp(-10, 10).ToString(Ci)}");
}
if (CaptureDynamicRangeCompensation != CameraDynamicRangeCompensation.Off) if(this.CaptureExposure != CameraExposureMode.Auto) {
sb.Append($" -drc {CaptureDynamicRangeCompensation.ToString().ToLowerInvariant()}"); _ = sb.Append($" -ex {this.CaptureExposure.ToString().ToLowerInvariant()}");
}
if (CaptureWhiteBalanceControl == CameraWhiteBalanceMode.Off && if(this.CaptureWhiteBalanceControl != CameraWhiteBalanceMode.Auto) {
(CaptureWhiteBalanceGainBlue != 0M || CaptureWhiteBalanceGainRed != 0M)) _ = sb.Append($" -awb {this.CaptureWhiteBalanceControl.ToString().ToLowerInvariant()}");
sb.Append($" -awbg {CaptureWhiteBalanceGainBlue.ToString(Ci)},{CaptureWhiteBalanceGainRed.ToString(Ci)}"); }
if (ImageAnnotationFontSize > 0) if(this.ImageEffect != CameraImageEffect.None) {
{ _ = sb.Append($" -ifx {this.ImageEffect.ToString().ToLowerInvariant()}");
sb.Append($" -ae {ImageAnnotationFontSize.Clamp(6, 160).ToString(Ci)}"); }
sb.Append($",{(ImageAnnotationFontColor == null ? "0xff" : ImageAnnotationFontColor.ToYuvHex(true))}");
if (ImageAnnotationBackground != null) if(this.ImageColorEffectU >= 0 && this.ImageColorEffectV >= 0) {
{ _ = sb.Append(
ImageAnnotations |= CameraAnnotation.SolidBackground; $" -cfx {this.ImageColorEffectU.Clamp(0, 255).ToString(Ci)}:{this.ImageColorEffectV.Clamp(0, 255).ToString(Ci)}");
sb.Append($",{ImageAnnotationBackground.ToYuvHex(true)}"); }
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 (ImageAnnotations != CameraAnnotation.None) if(this.ImageAnnotations != CameraAnnotation.None) {
sb.Append($" -a {((int)ImageAnnotations).ToString(Ci)}"); _ = sb.Append($" -a {((Int32)this.ImageAnnotations).ToString(Ci)}");
}
if (string.IsNullOrWhiteSpace(ImageAnnotationsText) == false) if(String.IsNullOrWhiteSpace(this.ImageAnnotationsText) == false) {
sb.Append($" -a \"{ImageAnnotationsText.Replace("\"", "'")}\""); _ = sb.Append($" -a \"{this.ImageAnnotationsText.Replace("\"", "'")}\"");
}
return sb.ToString(); return sb.ToString();
} }

View File

@ -1,26 +1,25 @@
namespace Unosquare.RaspberryIO.Camera using System;
{ using System.Collections.Generic;
using System; using System.Text;
using System.Collections.Generic;
using System.Text;
using Swan;
using Swan;
namespace Unosquare.RaspberryIO.Camera {
/// <summary> /// <summary>
/// Defines a wrapper for the raspistill program and its settings (command-line arguments). /// Defines a wrapper for the raspistill program and its settings (command-line arguments).
/// </summary> /// </summary>
/// <seealso cref="CameraSettingsBase" /> /// <seealso cref="CameraSettingsBase" />
public class CameraStillSettings : CameraSettingsBase public class CameraStillSettings : CameraSettingsBase {
{ private Int32 _rotate;
private int _rotate;
/// <inheritdoc /> /// <inheritdoc />
public override string CommandName => "raspistill"; public override String CommandName => "raspistill";
/// <summary> /// <summary>
/// Gets or sets a value indicating whether the preview window (if enabled) uses native capture resolution /// Gets or sets a value indicating whether the preview window (if enabled) uses native capture resolution
/// This may slow down preview FPS. /// This may slow down preview FPS.
/// </summary> /// </summary>
public bool CaptureDisplayPreviewAtResolution { get; set; } = false; public Boolean CaptureDisplayPreviewAtResolution { get; set; } = false;
/// <summary> /// <summary>
/// Gets or sets the encoding format the hardware will use for the output. /// Gets or sets the encoding format the hardware will use for the output.
@ -31,18 +30,18 @@
/// Gets or sets the quality for JPEG only encoding mode. /// Gets or sets the quality for JPEG only encoding mode.
/// Value ranges from 0 to 100. /// Value ranges from 0 to 100.
/// </summary> /// </summary>
public int CaptureJpegQuality { get; set; } = 90; public Int32 CaptureJpegQuality { get; set; } = 90;
/// <summary> /// <summary>
/// Gets or sets a value indicating whether the JPEG encoder should add raw bayer metadata. /// Gets or sets a value indicating whether the JPEG encoder should add raw bayer metadata.
/// </summary> /// </summary>
public bool CaptureJpegIncludeRawBayerMetadata { get; set; } = false; public Boolean CaptureJpegIncludeRawBayerMetadata { get; set; } = false;
/// <summary> /// <summary>
/// JPEG EXIF data /// JPEG EXIF data
/// Keys and values must be already properly escaped. Otherwise the command will fail. /// Keys and values must be already properly escaped. Otherwise the command will fail.
/// </summary> /// </summary>
public Dictionary<string, string> CaptureJpegExtendedInfo { get; } = new Dictionary<string, string>(); public Dictionary<String, String> CaptureJpegExtendedInfo { get; } = new Dictionary<String, String>();
/// <summary> /// <summary>
/// Gets or sets a value indicating whether [horizontal flip]. /// Gets or sets a value indicating whether [horizontal flip].
@ -50,7 +49,7 @@
/// <value> /// <value>
/// <c>true</c> if [horizontal flip]; otherwise, <c>false</c>. /// <c>true</c> if [horizontal flip]; otherwise, <c>false</c>.
/// </value> /// </value>
public bool HorizontalFlip { get; set; } = false; public Boolean HorizontalFlip { get; set; } = false;
/// <summary> /// <summary>
/// Gets or sets a value indicating whether [vertical flip]. /// Gets or sets a value indicating whether [vertical flip].
@ -58,61 +57,64 @@
/// <value> /// <value>
/// <c>true</c> if [vertical flip]; otherwise, <c>false</c>. /// <c>true</c> if [vertical flip]; otherwise, <c>false</c>.
/// </value> /// </value>
public bool VerticalFlip { get; set; } = false; public Boolean VerticalFlip { get; set; } = false;
/// <summary> /// <summary>
/// Gets or sets the rotation. /// Gets or sets the rotation.
/// </summary> /// </summary>
/// <exception cref="ArgumentOutOfRangeException">Valid range 0-359.</exception> /// <exception cref="ArgumentOutOfRangeException">Valid range 0-359.</exception>
public int Rotation public Int32 Rotation {
{ get => this._rotate;
get => _rotate; set {
set if(value < 0 || value > 359) {
{
if (value < 0 || value > 359)
{
throw new ArgumentOutOfRangeException(nameof(value), "Valid range 0-359"); throw new ArgumentOutOfRangeException(nameof(value), "Valid range 0-359");
} }
_rotate = value; this._rotate = value;
} }
} }
/// <inheritdoc /> /// <inheritdoc />
public override string CreateProcessArguments() public override String CreateProcessArguments() {
{ StringBuilder sb = new StringBuilder(base.CreateProcessArguments());
var sb = new StringBuilder(base.CreateProcessArguments()); _ = sb.Append($" -e {this.CaptureEncoding.ToString().ToLowerInvariant()}");
sb.Append($" -e {CaptureEncoding.ToString().ToLowerInvariant()}");
// JPEG Encoder specific arguments // JPEG Encoder specific arguments
if (CaptureEncoding == CameraImageEncodingFormat.Jpg) if(this.CaptureEncoding == CameraImageEncodingFormat.Jpg) {
{ _ = sb.Append($" -q {this.CaptureJpegQuality.Clamp(0, 100).ToString(Ci)}");
sb.Append($" -q {CaptureJpegQuality.Clamp(0, 100).ToString(Ci)}");
if (CaptureJpegIncludeRawBayerMetadata) if(this.CaptureJpegIncludeRawBayerMetadata) {
sb.Append(" -r"); _ = sb.Append(" -r");
}
// JPEG EXIF data // JPEG EXIF data
if (CaptureJpegExtendedInfo.Count > 0) if(this.CaptureJpegExtendedInfo.Count > 0) {
{ foreach(KeyValuePair<String, String> kvp in this.CaptureJpegExtendedInfo) {
foreach (var kvp in CaptureJpegExtendedInfo) if(String.IsNullOrWhiteSpace(kvp.Key) || String.IsNullOrWhiteSpace(kvp.Value)) {
{
if (string.IsNullOrWhiteSpace(kvp.Key) || string.IsNullOrWhiteSpace(kvp.Value))
continue; continue;
}
sb.Append($" -x \"{kvp.Key.Replace("\"", "'")}={kvp.Value.Replace("\"", "'")}\""); _ = sb.Append($" -x \"{kvp.Key.Replace("\"", "'")}={kvp.Value.Replace("\"", "'")}\"");
} }
} }
} }
// Display preview settings // Display preview settings
if (CaptureDisplayPreview && CaptureDisplayPreviewAtResolution) sb.Append(" -fp"); if(this.CaptureDisplayPreview && this.CaptureDisplayPreviewAtResolution) {
_ = sb.Append(" -fp");
}
if (Rotation != 0) sb.Append($" -rot {Rotation}"); if(this.Rotation != 0) {
_ = sb.Append($" -rot {this.Rotation}");
}
if (HorizontalFlip) sb.Append(" -hf"); if(this.HorizontalFlip) {
_ = sb.Append(" -hf");
}
if (VerticalFlip) sb.Append(" -vf"); if(this.VerticalFlip) {
_ = sb.Append(" -vf");
}
return sb.ToString(); return sb.ToString();
} }

View File

@ -1,44 +1,43 @@
namespace Unosquare.RaspberryIO.Camera using System;
{ using System.Text;
using System.Text;
namespace Unosquare.RaspberryIO.Camera {
/// <summary> /// <summary>
/// Represents the raspivid camera settings for video capture functionality. /// Represents the raspivid camera settings for video capture functionality.
/// </summary> /// </summary>
/// <seealso cref="CameraSettingsBase" /> /// <seealso cref="CameraSettingsBase" />
public class CameraVideoSettings : CameraSettingsBase public class CameraVideoSettings : CameraSettingsBase {
{ private Int32 _length;
private int _length;
/// <inheritdoc /> /// <inheritdoc />
public override string CommandName => "raspivid"; public override String CommandName => "raspivid";
/// <summary> /// <summary>
/// Use bits per second, so 10Mbits/s would be -b 10000000. For H264, 1080p30 a high quality bitrate would be 15Mbits/s or more. /// 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. /// Maximum bitrate is 25Mbits/s (-b 25000000), but much over 17Mbits/s won't show noticeable improvement at 1080p30.
/// Default -1. /// Default -1.
/// </summary> /// </summary>
public int CaptureBitrate { get; set; } = -1; public Int32 CaptureBitrate { get; set; } = -1;
/// <summary> /// <summary>
/// Gets or sets the framerate. /// Gets or sets the framerate.
/// Default 25, range 2 to 30. /// Default 25, range 2 to 30.
/// </summary> /// </summary>
public int CaptureFramerate { get; set; } = 25; public Int32 CaptureFramerate { get; set; } = 25;
/// <summary> /// <summary>
/// Sets the intra refresh period (GoP) rate for the recorded video. H264 video uses a complete frame (I-frame) every intra /// 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. /// 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. /// Larger numbers here will reduce the size of the resulting video, and smaller numbers make the stream less error-prone.
/// </summary> /// </summary>
public int CaptureKeyframeRate { get; set; } = 25; public Int32 CaptureKeyframeRate { get; set; } = 25;
/// <summary> /// <summary>
/// Sets the initial quantisation parameter for the stream. Varies from approximately 10 to 40, and will greatly affect /// 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 /// 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. /// bitrate of 0 to set a completely variable bitrate.
/// </summary> /// </summary>
public int CaptureQuantisation { get; set; } = 23; public Int32 CaptureQuantisation { get; set; } = 23;
/// <summary> /// <summary>
/// Gets or sets the profile. /// Gets or sets the profile.
@ -54,25 +53,26 @@
/// <value> /// <value>
/// <c>true</c> if [interleave headers]; otherwise, <c>false</c>. /// <c>true</c> if [interleave headers]; otherwise, <c>false</c>.
/// </value> /// </value>
public bool CaptureInterleaveHeaders { get; set; } = true; public Boolean CaptureInterleaveHeaders { get; set; } = true;
/// <summary> /// <summary>
/// Toggle fullscreen mode for video preview. /// Toggle fullscreen mode for video preview.
/// </summary> /// </summary>
public bool Fullscreen { get; set; } = false; public Boolean Fullscreen { get; set; } = false;
/// <summary> /// <summary>
/// Specifies the path to save video files. /// Specifies the path to save video files.
/// </summary> /// </summary>
public string VideoFileName { get; set; } public String? VideoFileName {
get; set;
}
/// <summary> /// <summary>
/// Video stream length in seconds. /// Video stream length in seconds.
/// </summary> /// </summary>
public int LengthInSeconds public Int32 LengthInSeconds {
{ get => this._length;
get => _length; set => this._length = value * 1000;
set => _length = value * 1000;
} }
/// <summary> /// <summary>
@ -82,21 +82,23 @@
/// <value> /// <value>
/// <c>true</c> if [capture display preview encoded]; otherwise, <c>false</c>. /// <c>true</c> if [capture display preview encoded]; otherwise, <c>false</c>.
/// </value> /// </value>
public bool CaptureDisplayPreviewEncoded { get; set; } = false; public Boolean CaptureDisplayPreviewEncoded { get; set; } = false;
/// <inheritdoc /> /// <inheritdoc />
public override string CreateProcessArguments() public override String CreateProcessArguments() {
{ StringBuilder sb = new StringBuilder(base.CreateProcessArguments());
var sb = new StringBuilder(base.CreateProcessArguments());
if (Fullscreen) if(this.Fullscreen) {
sb.Append(" -f"); _ = sb.Append(" -f");
}
if (LengthInSeconds != 0) if(this.LengthInSeconds != 0) {
sb.Append($" -t {LengthInSeconds}"); _ = sb.Append($" -t {this.LengthInSeconds}");
}
if (!string.IsNullOrEmpty(VideoFileName)) if(!String.IsNullOrEmpty(this.VideoFileName)) {
sb.Append($" -o {VideoFileName}"); _ = sb.Append($" -o {this.VideoFileName}");
}
return sb.ToString(); return sb.ToString();
} }

View File

@ -1,12 +1,10 @@
namespace Unosquare.RaspberryIO.Camera namespace Unosquare.RaspberryIO.Camera {
{
using System; using System;
/// <summary> /// <summary>
/// Defines the available encoding formats for the Raspberry Pi camera module. /// Defines the available encoding formats for the Raspberry Pi camera module.
/// </summary> /// </summary>
public enum CameraImageEncodingFormat public enum CameraImageEncodingFormat {
{
/// <summary> /// <summary>
/// The JPG /// The JPG
/// </summary> /// </summary>
@ -31,8 +29,7 @@
/// <summary> /// <summary>
/// Defines the different exposure modes for the Raspberry Pi's camera module. /// Defines the different exposure modes for the Raspberry Pi's camera module.
/// </summary> /// </summary>
public enum CameraExposureMode public enum CameraExposureMode {
{
/// <summary> /// <summary>
/// The automatic /// The automatic
/// </summary> /// </summary>
@ -97,8 +94,7 @@
/// <summary> /// <summary>
/// Defines the different AWB (Auto White Balance) modes for the Raspberry Pi's camera module. /// Defines the different AWB (Auto White Balance) modes for the Raspberry Pi's camera module.
/// </summary> /// </summary>
public enum CameraWhiteBalanceMode public enum CameraWhiteBalanceMode {
{
/// <summary> /// <summary>
/// No white balance /// No white balance
/// </summary> /// </summary>
@ -153,8 +149,7 @@
/// <summary> /// <summary>
/// Defines the available image effects for the Raspberry Pi's camera module. /// Defines the available image effects for the Raspberry Pi's camera module.
/// </summary> /// </summary>
public enum CameraImageEffect public enum CameraImageEffect {
{
/// <summary> /// <summary>
/// No effect /// No effect
/// </summary> /// </summary>
@ -264,8 +259,7 @@
/// <summary> /// <summary>
/// Defines the different metering modes for the Raspberry Pi's camera module. /// Defines the different metering modes for the Raspberry Pi's camera module.
/// </summary> /// </summary>
public enum CameraMeteringMode public enum CameraMeteringMode {
{
/// <summary> /// <summary>
/// The average /// The average
/// </summary> /// </summary>
@ -290,8 +284,7 @@
/// <summary> /// <summary>
/// Defines the different image rotation modes for the Raspberry Pi's camera module. /// Defines the different image rotation modes for the Raspberry Pi's camera module.
/// </summary> /// </summary>
public enum CameraImageRotation public enum CameraImageRotation {
{
/// <summary> /// <summary>
/// No rerotation /// No rerotation
/// </summary> /// </summary>
@ -317,8 +310,7 @@
/// Defines the different DRC (Dynamic Range Compensation) modes for the Raspberry Pi's camera module /// Defines the different DRC (Dynamic Range Compensation) modes for the Raspberry Pi's camera module
/// Helpful for low light photos. /// Helpful for low light photos.
/// </summary> /// </summary>
public enum CameraDynamicRangeCompensation public enum CameraDynamicRangeCompensation {
{
/// <summary> /// <summary>
/// The off setting /// The off setting
/// </summary> /// </summary>
@ -344,8 +336,7 @@
/// Defines the bit-wise mask flags for the available annotation elements for the Raspberry Pi's camera module. /// Defines the bit-wise mask flags for the available annotation elements for the Raspberry Pi's camera module.
/// </summary> /// </summary>
[Flags] [Flags]
public enum CameraAnnotation public enum CameraAnnotation {
{
/// <summary> /// <summary>
/// The none /// The none
/// </summary> /// </summary>
@ -400,8 +391,7 @@
/// <summary> /// <summary>
/// Defines the different H.264 encoding profiles to be used when capturing video. /// Defines the different H.264 encoding profiles to be used when capturing video.
/// </summary> /// </summary>
public enum CameraH264Profile public enum CameraH264Profile {
{
/// <summary> /// <summary>
/// BP: Primarily for lower-cost applications with limited computing resources, /// BP: Primarily for lower-cost applications with limited computing resources,
/// this profile is used widely in videoconferencing and mobile applications. /// this profile is used widely in videoconferencing and mobile applications.

View File

@ -1,19 +1,18 @@
namespace Unosquare.RaspberryIO.Computer using System;
{ using System.Linq;
using Swan; using System.Threading.Tasks;
using System;
using System.Linq;
using System.Threading.Tasks;
using Swan;
namespace Unosquare.RaspberryIO.Computer {
/// <summary> /// <summary>
/// Settings for audio device. /// Settings for audio device.
/// </summary> /// </summary>
public class AudioSettings : SingletonBase<AudioSettings> public class AudioSettings : SingletonBase<AudioSettings> {
{ private const String DefaultControlName = "PCM";
private const string DefaultControlName = "PCM"; private const Int32 DefaultCardNumber = 0;
private const int DefaultCardNumber = 0;
private readonly string[] _errorMess = { "Invalid", "Unable" }; private readonly String[] _errorMess = { "Invalid", "Unable" };
/// <summary> /// <summary>
/// Gets the current audio state. /// Gets the current audio state.
@ -22,36 +21,32 @@
/// <param name="controlName">Name of the control.</param> /// <param name="controlName">Name of the control.</param>
/// <returns>An <see cref="AudioState"/> object.</returns> /// <returns>An <see cref="AudioState"/> object.</returns>
/// <exception cref="InvalidOperationException">Invalid command, card number or control name.</exception> /// <exception cref="InvalidOperationException">Invalid command, card number or control name.</exception>
public async Task<AudioState> GetState(int cardNumber = DefaultCardNumber, string controlName = DefaultControlName) public async Task<AudioState> GetState(Int32 cardNumber = DefaultCardNumber, String controlName = DefaultControlName) {
{ String volumeInfo = await ProcessRunner.GetProcessOutputAsync("amixer", $"-c {cardNumber} get {controlName}").ConfigureAwait(false);
var volumeInfo = await ProcessRunner.GetProcessOutputAsync("amixer", $"-c {cardNumber} get {controlName}").ConfigureAwait(false);
var lines = volumeInfo.Split(new[] { '\n' }, StringSplitOptions.RemoveEmptyEntries); String[] lines = volumeInfo.Split(new[] { '\n' }, StringSplitOptions.RemoveEmptyEntries);
if (!lines.Any()) if(!lines.Any()) {
throw new InvalidOperationException("Invalid command."); throw new InvalidOperationException("Invalid command.");
}
if (_errorMess.Any(x => lines[0].Contains(x))) if(this._errorMess.Any(x => lines[0].Contains(x))) {
throw new InvalidOperationException(lines[0]); throw new InvalidOperationException(lines[0]);
}
var volumeLine = lines String volumeLine = lines.FirstOrDefault(x => x.Trim().StartsWith("Mono:", StringComparison.OrdinalIgnoreCase));
.FirstOrDefault(x => x.Trim()
.StartsWith("Mono:", StringComparison.OrdinalIgnoreCase));
if (volumeLine == null) if(volumeLine == null) {
throw new InvalidOperationException("Unexpected output from 'amixer'."); throw new InvalidOperationException("Unexpected output from 'amixer'.");
}
var sections = volumeLine.Split(new[] { ' ' }, String[] sections = volumeLine.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
StringSplitOptions.RemoveEmptyEntries);
var level = int.Parse(sections[3].Substring(1, sections[3].Length - 3), Int32 level = Int32.Parse(sections[3][1..^2], System.Globalization.NumberFormatInfo.InvariantInfo);
System.Globalization.NumberFormatInfo.InvariantInfo);
var decibels = float.Parse(sections[4].Substring(1, sections[4].Length - 4), Single decibels = Single.Parse(sections[4][1..^3], System.Globalization.NumberFormatInfo.InvariantInfo);
System.Globalization.NumberFormatInfo.InvariantInfo);
var isMute = sections[5].Equals("[off]", Boolean isMute = sections[5].Equals("[off]", StringComparison.CurrentCultureIgnoreCase);
StringComparison.CurrentCultureIgnoreCase);
return new AudioState(cardNumber, controlName, level, decibels, isMute); return new AudioState(cardNumber, controlName, level, decibels, isMute);
} }
@ -64,8 +59,7 @@
/// <param name="controlName">Name of the control.</param> /// <param name="controlName">Name of the control.</param>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns> /// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
/// <exception cref="InvalidOperationException">Invalid card number or control name.</exception> /// <exception cref="InvalidOperationException">Invalid card number or control name.</exception>
public Task SetVolumePercentage(int level, int cardNumber = DefaultCardNumber, string controlName = DefaultControlName) => public Task SetVolumePercentage(Int32 level, Int32 cardNumber = DefaultCardNumber, String controlName = DefaultControlName) => SetAudioCommand($"{level}%", cardNumber, controlName);
SetAudioCommand($"{level}%", cardNumber, controlName);
/// <summary> /// <summary>
/// Sets the volume by decibels. /// Sets the volume by decibels.
@ -75,8 +69,7 @@
/// <param name="controlName">Name of the control.</param> /// <param name="controlName">Name of the control.</param>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns> /// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
/// <exception cref="InvalidOperationException">Invalid card number or control name.</exception> /// <exception cref="InvalidOperationException">Invalid card number or control name.</exception>
public Task SetVolumeByDecibels(float decibels, int cardNumber = DefaultCardNumber, string controlName = DefaultControlName) => public Task SetVolumeByDecibels(Single decibels, Int32 cardNumber = DefaultCardNumber, String controlName = DefaultControlName) => SetAudioCommand($"{decibels}dB", cardNumber, controlName);
SetAudioCommand($"{decibels}dB", cardNumber, controlName);
/// <summary> /// <summary>
/// Increments the volume by decibels. /// Increments the volume by decibels.
@ -86,8 +79,7 @@
/// <param name="controlName">Name of the control.</param> /// <param name="controlName">Name of the control.</param>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns> /// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
/// <exception cref="InvalidOperationException">Invalid card number or control name.</exception> /// <exception cref="InvalidOperationException">Invalid card number or control name.</exception>
public Task IncrementVolume(float decibels, int cardNumber = DefaultCardNumber, string controlName = DefaultControlName) => public Task IncrementVolume(Single decibels, Int32 cardNumber = DefaultCardNumber, String controlName = DefaultControlName) => SetAudioCommand($"{decibels}dB{(decibels < 0 ? "-" : "+")}", cardNumber, controlName);
SetAudioCommand($"{decibels}dB{(decibels < 0 ? "-" : "+")}", cardNumber, controlName);
/// <summary> /// <summary>
/// Toggles the mute state. /// Toggles the mute state.
@ -97,15 +89,14 @@
/// <param name="controlName">Name of the control.</param> /// <param name="controlName">Name of the control.</param>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns> /// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
/// <exception cref="InvalidOperationException">Invalid card number or control name.</exception> /// <exception cref="InvalidOperationException">Invalid card number or control name.</exception>
public Task ToggleMute(bool mute, int cardNumber = DefaultCardNumber, string controlName = DefaultControlName) => public Task ToggleMute(Boolean mute, Int32 cardNumber = DefaultCardNumber, String controlName = DefaultControlName) => SetAudioCommand(mute ? "mute" : "unmute", cardNumber, controlName);
SetAudioCommand(mute ? "mute" : "unmute", cardNumber, controlName);
private static async Task<string> SetAudioCommand(string command, int cardNumber = DefaultCardNumber, string controlName = DefaultControlName) private static async Task<String> SetAudioCommand(String command, Int32 cardNumber = DefaultCardNumber, String controlName = DefaultControlName) {
{ String taskResult = await ProcessRunner.GetProcessOutputAsync("amixer", $"-q -c {cardNumber} -- set {controlName} {command}").ConfigureAwait(false);
var taskResult = await ProcessRunner.GetProcessOutputAsync("amixer", $"-q -c {cardNumber} -- set {controlName} {command}").ConfigureAwait(false);
if (!string.IsNullOrWhiteSpace(taskResult)) if(!String.IsNullOrWhiteSpace(taskResult)) {
throw new InvalidOperationException(taskResult.Split(new[] { '\n' }, StringSplitOptions.RemoveEmptyEntries).First()); throw new InvalidOperationException(taskResult.Split(new[] { '\n' }, StringSplitOptions.RemoveEmptyEntries).First());
}
return taskResult; return taskResult;
} }

View File

@ -1,10 +1,10 @@
namespace Unosquare.RaspberryIO.Computer using System;
{
namespace Unosquare.RaspberryIO.Computer {
/// <summary> /// <summary>
/// Manage the volume of any sound device. /// Manage the volume of any sound device.
/// </summary> /// </summary>
public readonly struct AudioState public readonly struct AudioState {
{
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="AudioState"/> struct. /// Initializes a new instance of the <see cref="AudioState"/> struct.
/// </summary> /// </summary>
@ -13,52 +13,60 @@
/// <param name="level">The volume level in percentaje.</param> /// <param name="level">The volume level in percentaje.</param>
/// <param name="decibels">The volume level in decibels.</param> /// <param name="decibels">The volume level in decibels.</param>
/// <param name="isMute">if set to <c>true</c> the audio is mute.</param> /// <param name="isMute">if set to <c>true</c> the audio is mute.</param>
public AudioState(int cardNumber, string controlName, int level, float decibels, bool isMute) public AudioState(Int32 cardNumber, String controlName, Int32 level, Single decibels, Boolean isMute) {
{ this.CardNumber = cardNumber;
CardNumber = cardNumber; this.ControlName = controlName;
ControlName = controlName; this.Level = level;
Level = level; this.Decibels = decibels;
Decibels = decibels; this.IsMute = isMute;
IsMute = isMute;
} }
/// <summary> /// <summary>
/// Gets the card number. /// Gets the card number.
/// </summary> /// </summary>
public int CardNumber { get; } public Int32 CardNumber {
get;
}
/// <summary> /// <summary>
/// Gets the name of the current control. /// Gets the name of the current control.
/// </summary> /// </summary>
public string ControlName { get; } public String ControlName {
get;
}
/// <summary> /// <summary>
/// Gets the volume level in percentage. /// Gets the volume level in percentage.
/// </summary> /// </summary>
public int Level { get; } public Int32 Level {
get;
}
/// <summary> /// <summary>
/// Gets the volume level in decibels. /// Gets the volume level in decibels.
/// </summary> /// </summary>
public float Decibels { get; } public Single Decibels {
get;
}
/// <summary> /// <summary>
/// Gets a value indicating whether the audio is mute. /// Gets a value indicating whether the audio is mute.
/// </summary> /// </summary>
public bool IsMute { get; } public Boolean IsMute {
get;
}
/// <summary> /// <summary>
/// Returns a <see cref="string" /> that represents the audio state. /// Returns a <see cref="String" /> that represents the audio state.
/// </summary> /// </summary>
/// <returns> /// <returns>
/// A <see cref="string" /> that represents the audio state. /// A <see cref="String" /> that represents the audio state.
/// </returns> /// </returns>
public override string ToString() => public override String ToString() => "Device information: \n" +
"Device information: \n" + $">> Name: {this.ControlName}\n" +
$">> Name: {ControlName}\n" + $">> Card number: {this.CardNumber}\n" +
$">> Card number: {CardNumber}\n" + $">> Volume (%): {this.Level}%\n" +
$">> Volume (%): {Level}%\n" + $">> Volume (dB): {this.Decibels:0.00}dB\n" +
$">> Volume (dB): {Decibels:0.00}dB\n" + $">> Mute: [{(this.IsMute ? "Off" : "On")}]\n\n";
$">> Mute: [{(IsMute ? "Off" : "On")}]\n\n";
} }
} }

View File

@ -1,18 +1,17 @@
namespace Unosquare.RaspberryIO.Computer using System;
{ using System.Collections.Generic;
using System; using System.Linq;
using System.Collections.Generic; using System.Threading;
using System.Linq; using System.Threading.Tasks;
using System.Threading;
using System.Threading.Tasks;
using Swan;
using Swan;
namespace Unosquare.RaspberryIO.Computer {
/// <summary> /// <summary>
/// Represents the Bluetooth information. /// Represents the Bluetooth information.
/// </summary> /// </summary>
public class Bluetooth : SingletonBase<Bluetooth> public class Bluetooth : SingletonBase<Bluetooth> {
{ private const String BcCommand = "bluetoothctl";
private const string BcCommand = "bluetoothctl";
/// <summary> /// <summary>
/// Turns on the Bluetooth adapter. /// Turns on the Bluetooth adapter.
@ -22,16 +21,11 @@ namespace Unosquare.RaspberryIO.Computer
/// Returns true or false depending if the controller was turned on. /// Returns true or false depending if the controller was turned on.
/// </returns> /// </returns>
/// <exception cref="BluetoothErrorException">Failed to power on:.</exception> /// <exception cref="BluetoothErrorException">Failed to power on:.</exception>
public async Task<bool> PowerOn(CancellationToken cancellationToken = default) public async Task<Boolean> PowerOn(CancellationToken cancellationToken = default) {
{ try {
try String output = await ProcessRunner.GetProcessOutputAsync(BcCommand, "power on", null, cancellationToken).ConfigureAwait(false);
{
var output = await ProcessRunner.GetProcessOutputAsync(BcCommand, "power on", null, cancellationToken)
.ConfigureAwait(false);
return output.Contains("succeeded"); return output.Contains("succeeded");
} } catch(Exception ex) {
catch (Exception ex)
{
throw new BluetoothErrorException($"Failed to power on: {ex.Message}"); throw new BluetoothErrorException($"Failed to power on: {ex.Message}");
} }
} }
@ -44,16 +38,11 @@ namespace Unosquare.RaspberryIO.Computer
/// Returns true or false depending if the controller was turned off. /// Returns true or false depending if the controller was turned off.
/// </returns> /// </returns>
/// <exception cref="BluetoothErrorException">Failed to power off:.</exception> /// <exception cref="BluetoothErrorException">Failed to power off:.</exception>
public async Task<bool> PowerOff(CancellationToken cancellationToken = default) public async Task<Boolean> PowerOff(CancellationToken cancellationToken = default) {
{ try {
try String output = await ProcessRunner.GetProcessOutputAsync(BcCommand, "power off", null, cancellationToken).ConfigureAwait(false);
{
var output = await ProcessRunner.GetProcessOutputAsync(BcCommand, "power off", null, cancellationToken)
.ConfigureAwait(false);
return output.Contains("succeeded"); return output.Contains("succeeded");
} } catch(Exception ex) {
catch (Exception ex)
{
throw new BluetoothErrorException($"Failed to power off: {ex.Message}"); throw new BluetoothErrorException($"Failed to power off: {ex.Message}");
} }
} }
@ -66,21 +55,14 @@ namespace Unosquare.RaspberryIO.Computer
/// Returns the list of detected devices. /// Returns the list of detected devices.
/// </returns> /// </returns>
/// <exception cref="BluetoothErrorException">Failed to retrieve devices:.</exception> /// <exception cref="BluetoothErrorException">Failed to retrieve devices:.</exception>
public async Task<IEnumerable<string>> ListDevices(CancellationToken cancellationToken = default) public async Task<IEnumerable<String>> ListDevices(CancellationToken cancellationToken = default) {
{ try {
try using CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(3000);
{ _ = await ProcessRunner.GetProcessOutputAsync(BcCommand, "scan on", null, cancellationTokenSource.Token).ConfigureAwait(false);
using var cancellationTokenSource = new CancellationTokenSource(3000); _ = await ProcessRunner.GetProcessOutputAsync(BcCommand, "scan off", null, cancellationToken).ConfigureAwait(false);
await ProcessRunner.GetProcessOutputAsync(BcCommand, "scan on", null, cancellationTokenSource.Token) String devices = await ProcessRunner.GetProcessOutputAsync(BcCommand, "devices", null, cancellationToken).ConfigureAwait(false);
.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()); return devices.Trim().Split('\n').Select(x => x.Trim());
} } catch(Exception ex) {
catch (Exception ex)
{
throw new BluetoothErrorException($"Failed to retrieve devices: {ex.Message}"); throw new BluetoothErrorException($"Failed to retrieve devices: {ex.Message}");
} }
} }
@ -93,16 +75,11 @@ namespace Unosquare.RaspberryIO.Computer
/// Returns the list of bluetooth controllers. /// Returns the list of bluetooth controllers.
/// </returns> /// </returns>
/// <exception cref="BluetoothErrorException">Failed to retrieve controllers:.</exception> /// <exception cref="BluetoothErrorException">Failed to retrieve controllers:.</exception>
public async Task<IEnumerable<string>> ListControllers(CancellationToken cancellationToken = default) public async Task<IEnumerable<String>> ListControllers(CancellationToken cancellationToken = default) {
{ try {
try String controllers = await ProcessRunner.GetProcessOutputAsync(BcCommand, "list", null, cancellationToken).ConfigureAwait(false);
{
var controllers = await ProcessRunner.GetProcessOutputAsync(BcCommand, "list", null, cancellationToken)
.ConfigureAwait(false);
return controllers.Trim().Split('\n').Select(x => x.Trim()); return controllers.Trim().Split('\n').Select(x => x.Trim());
} } catch(Exception ex) {
catch (Exception ex)
{
throw new BluetoothErrorException($"Failed to retrieve controllers: {ex.Message}"); throw new BluetoothErrorException($"Failed to retrieve controllers: {ex.Message}");
} }
} }
@ -117,39 +94,25 @@ namespace Unosquare.RaspberryIO.Computer
/// Returns true or false if the pair was successfully. /// Returns true or false if the pair was successfully.
/// </returns> /// </returns>
/// <exception cref="BluetoothErrorException">Failed to Pair:.</exception> /// <exception cref="BluetoothErrorException">Failed to Pair:.</exception>
public async Task<bool> Pair( public async Task<Boolean> Pair(String controllerAddress, String deviceAddress, CancellationToken cancellationToken = default) {
string controllerAddress, try {
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. // Selects the controller to pair. Once you select the controller, all controller-related commands will apply to it for three minutes.
await ProcessRunner _ = await ProcessRunner.GetProcessOutputAsync(BcCommand, $"select {controllerAddress}", null, cancellationToken).ConfigureAwait(false);
.GetProcessOutputAsync(BcCommand, $"select {controllerAddress}", null, cancellationToken)
.ConfigureAwait(false);
// Makes the controller visible to other devices. // Makes the controller visible to other devices.
await ProcessRunner.GetProcessOutputAsync(BcCommand, "discoverable on", null, cancellationToken) _ = await ProcessRunner.GetProcessOutputAsync(BcCommand, "discoverable on", null, cancellationToken).ConfigureAwait(false);
.ConfigureAwait(false);
// Readies the controller for pairing. Remember that you have three minutes after running this command to pair. // 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) _ = await ProcessRunner.GetProcessOutputAsync(BcCommand, "pairable on", null, cancellationToken).ConfigureAwait(false);
.ConfigureAwait(false);
// Pairs the device with the controller. // Pairs the device with the controller.
var result = await ProcessRunner String result = await ProcessRunner.GetProcessOutputAsync(BcCommand, $"pair {deviceAddress}", null, cancellationToken).ConfigureAwait(false);
.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. // 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) _ = await ProcessRunner.GetProcessOutputAsync(BcCommand, "discoverable off", null, cancellationToken).ConfigureAwait(false);
.ConfigureAwait(false);
return result.Contains("Paired: yes"); return result.Contains("Paired: yes");
} } catch(Exception ex) {
catch (Exception ex)
{
throw new BluetoothErrorException($"Failed to Pair: {ex.Message}"); throw new BluetoothErrorException($"Failed to Pair: {ex.Message}");
} }
} }
@ -164,39 +127,25 @@ namespace Unosquare.RaspberryIO.Computer
/// Returns true or false if the connection was successfully. /// Returns true or false if the connection was successfully.
/// </returns> /// </returns>
/// <exception cref="BluetoothErrorException">Failed to connect:.</exception> /// <exception cref="BluetoothErrorException">Failed to connect:.</exception>
public async Task<bool> Connect( public async Task<Boolean> Connect(String controllerAddress, String deviceAddress, CancellationToken cancellationToken = default) {
string controllerAddress, try {
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. // Selects the controller to pair. Once you select the controller, all controller-related commands will apply to it for three minutes.
await ProcessRunner _ = await ProcessRunner.GetProcessOutputAsync(BcCommand, $"select {controllerAddress}", null, cancellationToken).ConfigureAwait(false);
.GetProcessOutputAsync(BcCommand, $"select {controllerAddress}", null, cancellationToken)
.ConfigureAwait(false);
// Makes the controller visible to other devices. // Makes the controller visible to other devices.
await ProcessRunner.GetProcessOutputAsync(BcCommand, "discoverable on", null, cancellationToken) _ = await ProcessRunner.GetProcessOutputAsync(BcCommand, "discoverable on", null, cancellationToken).ConfigureAwait(false);
.ConfigureAwait(false);
// Readies the controller for pairing. Remember that you have three minutes after running this command to pair. // 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) _ = await ProcessRunner.GetProcessOutputAsync(BcCommand, "pairable on", null, cancellationToken).ConfigureAwait(false);
.ConfigureAwait(false);
// Readies the device for pairing. // Readies the device for pairing.
var result = await ProcessRunner String result = await ProcessRunner.GetProcessOutputAsync(BcCommand, $"connect {deviceAddress}", null, cancellationToken).ConfigureAwait(false);
.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. // 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) _ = await ProcessRunner.GetProcessOutputAsync(BcCommand, "discoverable off", null, cancellationToken).ConfigureAwait(false);
.ConfigureAwait(false);
return result.Contains("Connected: yes"); return result.Contains("Connected: yes");
} } catch(Exception ex) {
catch (Exception ex)
{
throw new BluetoothErrorException($"Failed to connect: {ex.Message}"); throw new BluetoothErrorException($"Failed to connect: {ex.Message}");
} }
} }
@ -211,39 +160,25 @@ namespace Unosquare.RaspberryIO.Computer
/// Returns true or false if the operation was successful. /// Returns true or false if the operation was successful.
/// </returns> /// </returns>
/// <exception cref="BluetoothErrorException">Failed to add to trust devices list:.</exception> /// <exception cref="BluetoothErrorException">Failed to add to trust devices list:.</exception>
public async Task<bool> Trust( public async Task<Boolean> Trust(String controllerAddress, String deviceAddress, CancellationToken cancellationToken = default) {
string controllerAddress, try {
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. // Selects the controller to pair. Once you select the controller, all controller-related commands will apply to it for three minutes.
await ProcessRunner _ = await ProcessRunner.GetProcessOutputAsync(BcCommand, $"select {controllerAddress}", null, cancellationToken).ConfigureAwait(false);
.GetProcessOutputAsync(BcCommand, $"select {controllerAddress}", null, cancellationToken)
.ConfigureAwait(false);
// Makes the controller visible to other devices. // Makes the controller visible to other devices.
await ProcessRunner.GetProcessOutputAsync(BcCommand, "discoverable on", null, cancellationToken) _ = await ProcessRunner.GetProcessOutputAsync(BcCommand, "discoverable on", null, cancellationToken).ConfigureAwait(false);
.ConfigureAwait(false);
// Readies the controller for pairing. Remember that you have three minutes after running this command to pair. // 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) _ = await ProcessRunner.GetProcessOutputAsync(BcCommand, "pairable on", null, cancellationToken).ConfigureAwait(false);
.ConfigureAwait(false);
// Sets the device to re-pair automatically when it is turned on, which eliminates the need to pair all over again. // 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 String result = await ProcessRunner.GetProcessOutputAsync(BcCommand, $"trust {deviceAddress}", null, cancellationToken).ConfigureAwait(false);
.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. // 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) _ = await ProcessRunner.GetProcessOutputAsync(BcCommand, "discoverable off", null, cancellationToken).ConfigureAwait(false);
.ConfigureAwait(false);
return result.Contains("Trusted: yes"); return result.Contains("Trusted: yes");
} } catch(Exception ex) {
catch (Exception ex)
{
throw new BluetoothErrorException($"Failed to add to trust devices list: {ex.Message}"); throw new BluetoothErrorException($"Failed to add to trust devices list: {ex.Message}");
} }
} }
@ -257,15 +192,10 @@ namespace Unosquare.RaspberryIO.Computer
/// Returns the device info. /// Returns the device info.
/// </returns> /// </returns>
/// <exception cref="BluetoothErrorException">Failed to retrieve info for {deviceAddress}.</exception> /// <exception cref="BluetoothErrorException">Failed to retrieve info for {deviceAddress}.</exception>
public async Task<string> DeviceInfo(string deviceAddress, CancellationToken cancellationToken = default) public async Task<String> DeviceInfo(String deviceAddress, CancellationToken cancellationToken = default) {
{ String info = await ProcessRunner.GetProcessOutputAsync(BcCommand, $"info {deviceAddress}", null, cancellationToken).ConfigureAwait(false);
var info = await ProcessRunner
.GetProcessOutputAsync(BcCommand, $"info {deviceAddress}", null, cancellationToken)
.ConfigureAwait(false);
return !string.IsNullOrEmpty(info) return !String.IsNullOrEmpty(info) ? info : throw new BluetoothErrorException($"Failed to retrieve info for {deviceAddress}");
? info
: throw new BluetoothErrorException($"Failed to retrieve info for {deviceAddress}");
} }
} }
} }

View File

@ -1,24 +1,23 @@
namespace Unosquare.RaspberryIO.Computer using System;
{ using System.Globalization;
using System.Globalization; using System.IO;
using System.IO;
using Swan;
using Swan;
namespace Unosquare.RaspberryIO.Computer {
/// <summary> /// <summary>
/// The Official Raspberry Pi 7-inch touch display from the foundation /// The Official Raspberry Pi 7-inch touch display from the foundation
/// Some docs available here: /// Some docs available here:
/// http://forums.pimoroni.com/t/official-7-raspberry-pi-touch-screen-faq/959. /// http://forums.pimoroni.com/t/official-7-raspberry-pi-touch-screen-faq/959.
/// </summary> /// </summary>
public class DsiDisplay : SingletonBase<DsiDisplay> public class DsiDisplay : SingletonBase<DsiDisplay> {
{ private const String BacklightFilename = "/sys/class/backlight/rpi_backlight/bl_power";
private const string BacklightFilename = "/sys/class/backlight/rpi_backlight/bl_power"; private const String BrightnessFilename = "/sys/class/backlight/rpi_backlight/brightness";
private const string BrightnessFilename = "/sys/class/backlight/rpi_backlight/brightness";
/// <summary> /// <summary>
/// Prevents a default instance of the <see cref="DsiDisplay"/> class from being created. /// Prevents a default instance of the <see cref="DsiDisplay"/> class from being created.
/// </summary> /// </summary>
private DsiDisplay() private DsiDisplay() {
{
// placeholder // placeholder
} }
@ -28,7 +27,7 @@
/// <value> /// <value>
/// <c>true</c> if this instance is present; otherwise, <c>false</c>. /// <c>true</c> if this instance is present; otherwise, <c>false</c>.
/// </value> /// </value>
public bool IsPresent => File.Exists(BrightnessFilename); public Boolean IsPresent => File.Exists(BrightnessFilename);
/// <summary> /// <summary>
/// Gets or sets the brightness of the DSI display via filesystem. /// Gets or sets the brightness of the DSI display via filesystem.
@ -36,18 +35,14 @@
/// <value> /// <value>
/// The brightness. /// The brightness.
/// </value> /// </value>
public byte Brightness public Byte Brightness {
{ get => this.IsPresent ? System.Byte.TryParse(File.ReadAllText(BrightnessFilename).Trim(), out Byte brightness) ? brightness : (Byte)0 : (Byte)0;
get => set {
IsPresent if(this.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)); File.WriteAllText(BrightnessFilename, value.ToString(CultureInfo.InvariantCulture));
} }
} }
}
/// <summary> /// <summary>
/// Gets or sets a value indicating whether the backlight of the DSI display on. /// Gets or sets a value indicating whether the backlight of the DSI display on.
@ -56,16 +51,13 @@
/// <value> /// <value>
/// <c>true</c> if this instance is backlight on; otherwise, <c>false</c>. /// <c>true</c> if this instance is backlight on; otherwise, <c>false</c>.
/// </value> /// </value>
public bool IsBacklightOn public Boolean IsBacklightOn {
{ get => this.IsPresent && Int32.TryParse(File.ReadAllText(BacklightFilename).Trim(), out Int32 value) && value == 0;
get => set {
IsPresent && (int.TryParse(File.ReadAllText(BacklightFilename).Trim(), out var value) && if(this.IsPresent) {
value == 0);
set
{
if (IsPresent)
File.WriteAllText(BacklightFilename, value ? "0" : "1"); File.WriteAllText(BacklightFilename, value ? "0" : "1");
} }
} }
} }
}
} }

View File

@ -1,40 +1,51 @@
namespace Unosquare.RaspberryIO.Computer using System;
{ using System.Net;
using System.Net;
namespace Unosquare.RaspberryIO.Computer {
/// <summary> /// <summary>
/// Represents a Network Adapter. /// Represents a Network Adapter.
/// </summary> /// </summary>
public class NetworkAdapterInfo public class NetworkAdapterInfo {
{
/// <summary> /// <summary>
/// Gets the name. /// Gets the name.
/// </summary> /// </summary>
public string Name { get; internal set; } public String? Name {
get; internal set;
}
/// <summary> /// <summary>
/// Gets the IP V4 address. /// Gets the IP V4 address.
/// </summary> /// </summary>
public IPAddress IPv4 { get; internal set; } public IPAddress? IPv4 {
get; internal set;
}
/// <summary> /// <summary>
/// Gets the IP V6 address. /// Gets the IP V6 address.
/// </summary> /// </summary>
public IPAddress IPv6 { get; internal set; } public IPAddress? IPv6 {
get; internal set;
}
/// <summary> /// <summary>
/// Gets the name of the access point. /// Gets the name of the access point.
/// </summary> /// </summary>
public string AccessPointName { get; internal set; } public String? AccessPointName {
get; internal set;
}
/// <summary> /// <summary>
/// Gets the MAC (Physical) address. /// Gets the MAC (Physical) address.
/// </summary> /// </summary>
public string MacAddress { get; internal set; } public String? MacAddress {
get; internal set;
}
/// <summary> /// <summary>
/// Gets a value indicating whether this instance is wireless. /// Gets a value indicating whether this instance is wireless.
/// </summary> /// </summary>
public bool IsWireless { get; internal set; } public Boolean IsWireless {
get; internal set;
}
} }
} }

View File

@ -1,92 +1,94 @@
namespace Unosquare.RaspberryIO.Computer using System;
{ using System.Collections.Generic;
using Swan; using System.IO;
using Swan.Logging; using System.Linq;
using Swan.Net; using System.Net;
using System; using System.Text;
using System.Collections.Generic; using System.Threading.Tasks;
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 {
/// <summary> /// <summary>
/// Represents the network information. /// Represents the network information.
/// </summary> /// </summary>
public class NetworkSettings : SingletonBase<NetworkSettings> public class NetworkSettings : SingletonBase<NetworkSettings> {
{ private const String EssidTag = "ESSID:";
private const string EssidTag = "ESSID:";
/// <summary> /// <summary>
/// Gets the local machine Host Name. /// Gets the local machine Host Name.
/// </summary> /// </summary>
public string HostName => Network.HostName; public String HostName => Network.HostName;
/// <summary> /// <summary>
/// Retrieves the wireless networks. /// Retrieves the wireless networks.
/// </summary> /// </summary>
/// <param name="adapter">The adapter.</param> /// <param name="adapter">The adapter.</param>
/// <returns>A list of WiFi networks.</returns> /// <returns>A list of WiFi networks.</returns>
public Task<List<WirelessNetworkInfo>> RetrieveWirelessNetworks(string adapter) => RetrieveWirelessNetworks(new[] { adapter }); public Task<List<WirelessNetworkInfo>> RetrieveWirelessNetworks(String adapter) => this.RetrieveWirelessNetworks(new[] { adapter });
/// <summary> /// <summary>
/// Retrieves the wireless networks. /// Retrieves the wireless networks.
/// </summary> /// </summary>
/// <param name="adapters">The adapters.</param> /// <param name="adapters">The adapters.</param>
/// <returns>A list of WiFi networks.</returns> /// <returns>A list of WiFi networks.</returns>
public async Task<List<WirelessNetworkInfo>> RetrieveWirelessNetworks(string[] adapters = null) public async Task<List<WirelessNetworkInfo>> RetrieveWirelessNetworks(String[]? adapters = null) {
{ List<WirelessNetworkInfo> result = new List<WirelessNetworkInfo>();
var result = new List<WirelessNetworkInfo>();
foreach (var networkAdapter in adapters ?? (await RetrieveAdapters()).Where(x => x.IsWireless).Select(x => x.Name)) 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);
var 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();
var outputLines =
wirelessOutput.Split('\n')
.Select(x => x.Trim())
.Where(x => string.IsNullOrWhiteSpace(x) == false)
.ToArray();
for (var i = 0; i < outputLines.Length; i++) for(Int32 i = 0; i < outputLines.Length; i++) {
{ String line = outputLines[i];
var line = outputLines[i];
if (line.StartsWith(EssidTag) == false) continue; if(line.StartsWith(EssidTag) == false) {
continue;
}
var network = new WirelessNetworkInfo WirelessNetworkInfo network = new WirelessNetworkInfo {
{ Name = line.Replace(EssidTag, String.Empty).Replace("\"", String.Empty)
Name = line.Replace(EssidTag, string.Empty).Replace("\"", string.Empty)
}; };
while (true) while(true) {
{ if(i + 1 >= outputLines.Length) {
if (i + 1 >= outputLines.Length) break; break;
}
// should look for two lines before the ESSID acording to the scan // should look for two lines before the ESSID acording to the scan
line = outputLines[i - 2]; line = outputLines[i - 2];
if (!line.StartsWith("Quality=")) continue; if(!line.StartsWith("Quality=")) {
network.Quality = line.Replace("Quality=", string.Empty); continue;
}
network.Quality = line.Replace("Quality=", String.Empty);
break; break;
} }
while (true) while(true) {
{ if(i + 1 >= outputLines.Length) {
if (i + 1 >= outputLines.Length) break; break;
}
// should look for a line before the ESSID acording to the scan // should look for a line before the ESSID acording to the scan
line = outputLines[i - 1]; line = outputLines[i - 1];
if (!line.StartsWith("Encryption key:")) continue; if(!line.StartsWith("Encryption key:")) {
network.IsEncrypted = line.Replace("Encryption key:", string.Empty).Trim() == "on"; continue;
}
network.IsEncrypted = line.Replace("Encryption key:", String.Empty).Trim() == "on";
break; break;
} }
if (result.Any(x => x.Name == network.Name) == false) if(result.Any(x => x.Name == network.Name) == false) {
result.Add(network); result.Add(network);
} }
} }
}
return result return result
.OrderBy(x => x.Name) .OrderBy(x => x.Name)
@ -101,26 +103,23 @@
/// <param name="password">The password (8 characters as minimum length).</param> /// <param name="password">The password (8 characters as minimum length).</param>
/// <param name="countryCode">The 2-letter country code in uppercase. Default is US.</param> /// <param name="countryCode">The 2-letter country code in uppercase. Default is US.</param>
/// <returns>True if successful. Otherwise, false.</returns> /// <returns>True if successful. Otherwise, false.</returns>
public async Task<bool> SetupWirelessNetwork(string adapterName, string networkSsid, string password = null, string countryCode = "US") public async Task<Boolean> 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 // 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"; String payload = $"country={countryCode}\nctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev\nupdate_config=1\n";
if (!string.IsNullOrWhiteSpace(password) && password.Length < 8) if(!String.IsNullOrWhiteSpace(password) && password.Length < 8) {
throw new InvalidOperationException("The password must be at least 8 characters length."); throw new InvalidOperationException("The password must be at least 8 characters length.");
}
payload += string.IsNullOrEmpty(password) payload += String.IsNullOrEmpty(password)
? $"network={{\n\tssid=\"{networkSsid}\"\n\tkey_mgmt=NONE\n\t}}\n" ? $"network={{\n\tssid=\"{networkSsid}\"\n\tkey_mgmt=NONE\n\t}}\n"
: $"network={{\n\tssid=\"{networkSsid}\"\n\tpsk=\"{password}\"\n\t}}\n"; : $"network={{\n\tssid=\"{networkSsid}\"\n\tpsk=\"{password}\"\n\t}}\n";
try try {
{
File.WriteAllText("/etc/wpa_supplicant/wpa_supplicant.conf", payload); File.WriteAllText("/etc/wpa_supplicant/wpa_supplicant.conf", payload);
await ProcessRunner.GetProcessOutputAsync("pkill", "-f wpa_supplicant"); _ = await ProcessRunner.GetProcessOutputAsync("pkill", "-f wpa_supplicant");
await ProcessRunner.GetProcessOutputAsync("ifdown", adapterName); _ = await ProcessRunner.GetProcessOutputAsync("ifdown", adapterName);
await ProcessRunner.GetProcessOutputAsync("ifup", adapterName); _ = await ProcessRunner.GetProcessOutputAsync("ifup", adapterName);
} } catch(Exception ex) {
catch (Exception ex)
{
ex.Log(nameof(NetworkSettings)); ex.Log(nameof(NetworkSettings));
return false; return false;
} }
@ -132,59 +131,50 @@
/// Retrieves the network adapters. /// Retrieves the network adapters.
/// </summary> /// </summary>
/// <returns>A list of network adapters.</returns> /// <returns>A list of network adapters.</returns>
public async Task<List<NetworkAdapterInfo>> RetrieveAdapters() public async Task<List<NetworkAdapterInfo>> RetrieveAdapters() {
{ const String hWaddr = "HWaddr ";
const string hWaddr = "HWaddr "; const String ether = "ether ";
const string ether = "ether ";
var result = new List<NetworkAdapterInfo>(); List<NetworkAdapterInfo> result = new List<NetworkAdapterInfo>();
var interfacesOutput = await ProcessRunner.GetProcessOutputAsync("ifconfig"); String interfacesOutput = await ProcessRunner.GetProcessOutputAsync("ifconfig");
var wlanOutput = (await ProcessRunner.GetProcessOutputAsync("iwconfig")) String[] wlanOutput = (await ProcessRunner.GetProcessOutputAsync("iwconfig")).Split('\n').Where(x => x.Contains("no wireless extensions.") == false).ToArray();
.Split('\n')
.Where(x => x.Contains("no wireless extensions.") == false)
.ToArray();
var outputLines = interfacesOutput.Split('\n').Where(x => string.IsNullOrWhiteSpace(x) == false).ToArray(); String[] outputLines = interfacesOutput.Split('\n').Where(x => String.IsNullOrWhiteSpace(x) == false).ToArray();
for (var i = 0; i < outputLines.Length; i++) for(Int32 i = 0; i < outputLines.Length; i++) {
{
// grab the current line // grab the current line
var line = outputLines[i]; String line = outputLines[i];
// skip if the line is indented // skip if the line is indented
if (char.IsLetterOrDigit(line[0]) == false) if(Char.IsLetterOrDigit(line[0]) == false) {
continue; continue;
}
// Read the line as an adapter // Read the line as an adapter
var adapter = new NetworkAdapterInfo NetworkAdapterInfo adapter = new NetworkAdapterInfo {
{
Name = line.Substring(0, line.IndexOf(' ')).TrimEnd(':') Name = line.Substring(0, line.IndexOf(' ')).TrimEnd(':')
}; };
// Parse the MAC address in old version of ifconfig; it comes in the first line // Parse the MAC address in old version of ifconfig; it comes in the first line
if (line.IndexOf(hWaddr, StringComparison.Ordinal) >= 0) if(line.IndexOf(hWaddr, StringComparison.Ordinal) >= 0) {
{ Int32 startIndexHwd = line.IndexOf(hWaddr, StringComparison.Ordinal) + hWaddr.Length;
var startIndexHwd = line.IndexOf(hWaddr, StringComparison.Ordinal) + hWaddr.Length;
adapter.MacAddress = line.Substring(startIndexHwd, 17).Trim(); adapter.MacAddress = line.Substring(startIndexHwd, 17).Trim();
} }
// Parse the info in lines other than the first // Parse the info in lines other than the first
for (var j = i + 1; j < outputLines.Length; j++) for(Int32 j = i + 1; j < outputLines.Length; j++) {
{
// Get the contents of the indented line // Get the contents of the indented line
var indentedLine = outputLines[j]; String indentedLine = outputLines[j];
// We have hit the next adapter info // We have hit the next adapter info
if (char.IsLetterOrDigit(indentedLine[0])) if(Char.IsLetterOrDigit(indentedLine[0])) {
{
i = j - 1; i = j - 1;
break; break;
} }
// Parse the MAC address in new versions of ifconfig; it no longer comes in the first line // 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)) if(indentedLine.IndexOf(ether, StringComparison.Ordinal) >= 0 && String.IsNullOrWhiteSpace(adapter.MacAddress)) {
{ Int32 startIndexHwd = indentedLine.IndexOf(ether, StringComparison.Ordinal) + ether.Length;
var startIndexHwd = indentedLine.IndexOf(ether, StringComparison.Ordinal) + ether.Length;
adapter.MacAddress = indentedLine.Substring(startIndexHwd, 17).Trim(); adapter.MacAddress = indentedLine.Substring(startIndexHwd, 17).Trim();
} }
@ -195,20 +185,19 @@
GetIPv6(indentedLine, adapter); GetIPv6(indentedLine, adapter);
// we have hit the end of the output in an indented line // we have hit the end of the output in an indented line
if (j >= outputLines.Length - 1) if(j >= outputLines.Length - 1) {
i = outputLines.Length; i = outputLines.Length;
} }
}
// Retrieve the wireless LAN info // Retrieve the wireless LAN info
var wlanInfo = wlanOutput.FirstOrDefault(x => x.StartsWith(adapter.Name)); String wlanInfo = wlanOutput.FirstOrDefault(x => x.StartsWith(adapter.Name));
if (wlanInfo != null) if(wlanInfo != null) {
{
adapter.IsWireless = true; adapter.IsWireless = true;
var essidParts = wlanInfo.Split(new[] { EssidTag }, StringSplitOptions.RemoveEmptyEntries); String[] essidParts = wlanInfo.Split(new[] { EssidTag }, StringSplitOptions.RemoveEmptyEntries);
if (essidParts.Length >= 2) if(essidParts.Length >= 2) {
{ adapter.AccessPointName = essidParts[1].Replace("\"", String.Empty).Trim();
adapter.AccessPointName = essidParts[1].Replace("\"", string.Empty).Trim();
} }
} }
@ -223,11 +212,9 @@
/// Retrieves the current network adapter. /// Retrieves the current network adapter.
/// </summary> /// </summary>
/// <returns>The name of the current network adapter.</returns> /// <returns>The name of the current network adapter.</returns>
public static async Task<string> GetCurrentAdapterName() public static async Task<String?> GetCurrentAdapterName() {
{ String result = await ProcessRunner.GetProcessOutputAsync("route").ConfigureAwait(false);
var result = await ProcessRunner.GetProcessOutputAsync("route").ConfigureAwait(false); String defaultLine = result.Split(new[] { '\n' }, StringSplitOptions.RemoveEmptyEntries).FirstOrDefault(l => l.StartsWith("default", StringComparison.OrdinalIgnoreCase));
var defaultLine = result.Split(new[] { '\n' }, StringSplitOptions.RemoveEmptyEntries)
.FirstOrDefault(l => l.StartsWith("default", StringComparison.OrdinalIgnoreCase));
return defaultLine?.Trim().Substring(defaultLine.LastIndexOf(" ", StringComparison.OrdinalIgnoreCase) + 1); return defaultLine?.Trim().Substring(defaultLine.LastIndexOf(" ", StringComparison.OrdinalIgnoreCase) + 1);
} }
@ -236,43 +223,46 @@
/// Retrieves current wireless connected network name. /// Retrieves current wireless connected network name.
/// </summary> /// </summary>
/// <returns>The connected network name.</returns> /// <returns>The connected network name.</returns>
public Task<string> GetWirelessNetworkName() => ProcessRunner.GetProcessOutputAsync("iwgetid", "-r"); public Task<String> GetWirelessNetworkName() => ProcessRunner.GetProcessOutputAsync("iwgetid", "-r");
private static void GetIPv4(string indentedLine, NetworkAdapterInfo adapter) private static void GetIPv4(String indentedLine, NetworkAdapterInfo adapter) {
{ String? addressText = ParseOutputTagFromLine(indentedLine, "inet addr:") ?? ParseOutputTagFromLine(indentedLine, "inet ");
var addressText = ParseOutputTagFromLine(indentedLine, "inet addr:") ??
ParseOutputTagFromLine(indentedLine, "inet ");
if (addressText == null) return; if(addressText == null) {
if (IPAddress.TryParse(addressText, out var outValue)) return;
}
if(IPAddress.TryParse(addressText, out IPAddress outValue)) {
adapter.IPv4 = 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) private static void GetIPv6(String indentedLine, NetworkAdapterInfo adapter) {
{ String? addressText = ParseOutputTagFromLine(indentedLine, "inet6 addr:") ?? ParseOutputTagFromLine(indentedLine, "inet6 ");
if (indentedLine.IndexOf(tagName, StringComparison.Ordinal) < 0)
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; return null;
}
var startIndex = indentedLine.IndexOf(tagName, StringComparison.Ordinal) + tagName.Length; Int32 startIndex = indentedLine.IndexOf(tagName, StringComparison.Ordinal) + tagName.Length;
var builder = new StringBuilder(1024); StringBuilder builder = new StringBuilder(1024);
for (var c = startIndex; c < indentedLine.Length; c++) for(Int32 c = startIndex; c < indentedLine.Length; c++) {
{ Char currentChar = indentedLine[c];
var currentChar = indentedLine[c]; if(!Char.IsPunctuation(currentChar) && !Char.IsLetterOrDigit(currentChar)) {
if (!char.IsPunctuation(currentChar) && !char.IsLetterOrDigit(currentChar))
break; break;
}
builder.Append(currentChar); _ = builder.Append(currentChar);
} }
return builder.ToString(); return builder.ToString();

View File

@ -1,46 +1,58 @@
namespace Unosquare.RaspberryIO.Computer using System;
{
namespace Unosquare.RaspberryIO.Computer {
/// <summary> /// <summary>
/// Represents the OS Information. /// Represents the OS Information.
/// </summary> /// </summary>
public class OsInfo public class OsInfo {
{
/// <summary> /// <summary>
/// System name. /// System name.
/// </summary> /// </summary>
public string SysName { get; set; } public String? SysName {
get; set;
}
/// <summary> /// <summary>
/// Node name. /// Node name.
/// </summary> /// </summary>
public string NodeName { get; set; } public String? NodeName {
get; set;
}
/// <summary> /// <summary>
/// Release level. /// Release level.
/// </summary> /// </summary>
public string Release { get; set; } public String? Release {
get; set;
}
/// <summary> /// <summary>
/// Version level. /// Version level.
/// </summary> /// </summary>
public string Version { get; set; } public String? Version {
get; set;
}
/// <summary> /// <summary>
/// Hardware level. /// Hardware level.
/// </summary> /// </summary>
public string Machine { get; set; } public String? Machine {
get; set;
}
/// <summary> /// <summary>
/// Domain name. /// Domain name.
/// </summary> /// </summary>
public string DomainName { get; set; } public String? DomainName {
get; set;
}
/// <summary> /// <summary>
/// Returns a <see cref="string" /> that represents this instance. /// Returns a <see cref="String" /> that represents this instance.
/// </summary> /// </summary>
/// <returns> /// <returns>
/// A <see cref="string" /> that represents this instance. /// A <see cref="String" /> that represents this instance.
/// </returns> /// </returns>
public override string ToString() => $"{SysName} {Release} {Version}"; public override String ToString() => $"{this.SysName} {this.Release} {this.Version}";
} }
} }

View File

@ -1,12 +1,10 @@
namespace Unosquare.RaspberryIO.Computer namespace Unosquare.RaspberryIO.Computer {
{
/// <summary> /// <summary>
/// Defines the board revision codes of the different versions of the Raspberry Pi /// 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/. /// 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. /// https://www.raspberrypi.org/documentation/hardware/raspberrypi/revision-codes/README.md.
/// </summary> /// </summary>
public enum PiVersion public enum PiVersion {
{
/// <summary> /// <summary>
/// The unknown version /// The unknown version
/// </summary> /// </summary>
@ -216,8 +214,7 @@
/// <summary> /// <summary>
/// Defines the board model accordingly to new-style revision codes. /// Defines the board model accordingly to new-style revision codes.
/// </summary> /// </summary>
public enum BoardModel public enum BoardModel {
{
/// <summary> /// <summary>
/// Model A /// Model A
/// </summary> /// </summary>
@ -302,8 +299,7 @@
/// <summary> /// <summary>
/// Defines the processor model accordingly to new-style revision codes. /// Defines the processor model accordingly to new-style revision codes.
/// </summary> /// </summary>
public enum ProcessorModel public enum ProcessorModel {
{
/// <summary> /// <summary>
/// The BCMM2835 processor. /// The BCMM2835 processor.
/// </summary> /// </summary>
@ -328,8 +324,7 @@
/// <summary> /// <summary>
/// Defines the manufacturer accordingly to new-style revision codes. /// Defines the manufacturer accordingly to new-style revision codes.
/// </summary> /// </summary>
public enum Manufacturer public enum Manufacturer {
{
/// <summary> /// <summary>
/// Sony UK /// Sony UK
/// </summary> /// </summary>
@ -364,8 +359,7 @@
/// <summary> /// <summary>
/// Defines the memory size accordingly to new-style revision codes. /// Defines the memory size accordingly to new-style revision codes.
/// </summary> /// </summary>
public enum MemorySize public enum MemorySize {
{
/// <summary> /// <summary>
/// 256 MB /// 256 MB
/// </summary> /// </summary>

View File

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

View File

@ -1,23 +1,29 @@
namespace Unosquare.RaspberryIO.Computer using System;
{
namespace Unosquare.RaspberryIO.Computer {
/// <summary> /// <summary>
/// Represents a wireless network information. /// Represents a wireless network information.
/// </summary> /// </summary>
public class WirelessNetworkInfo public class WirelessNetworkInfo {
{
/// <summary> /// <summary>
/// Gets the ESSID of the Wireless network. /// Gets the ESSID of the Wireless network.
/// </summary> /// </summary>
public string Name { get; internal set; } public String? Name {
get; internal set;
}
/// <summary> /// <summary>
/// Gets the network quality. /// Gets the network quality.
/// </summary> /// </summary>
public string Quality { get; internal set; } public String? Quality {
get; internal set;
}
/// <summary> /// <summary>
/// Gets a value indicating whether this instance is encrypted. /// Gets a value indicating whether this instance is encrypted.
/// </summary> /// </summary>
public bool IsEncrypted { get; internal set; } public Boolean IsEncrypted {
get; internal set;
}
} }
} }

View File

@ -1,10 +1,9 @@
namespace Unosquare.RaspberryIO.Native using System;
{ using System.Runtime.InteropServices;
using System.Runtime.InteropServices;
internal static class Standard namespace Unosquare.RaspberryIO.Native {
{ internal static class Standard {
internal const string LibCLibrary = "libc"; internal const String LibCLibrary = "libc";
/// <summary> /// <summary>
/// Fills in the structure with information about the system. /// Fills in the structure with information about the system.
@ -12,6 +11,6 @@
/// <param name="name">The name.</param> /// <param name="name">The name.</param>
/// <returns>The result.</returns> /// <returns>The result.</returns>
[DllImport(LibCLibrary, EntryPoint = "uname", SetLastError = true)] [DllImport(LibCLibrary, EntryPoint = "uname", SetLastError = true)]
public static extern int Uname(out SystemName name); public static extern Int32 Uname(out SystemName name);
} }
} }

View File

@ -1,47 +1,46 @@
namespace Unosquare.RaspberryIO.Native using System;
{ using System.Runtime.InteropServices;
using System.Runtime.InteropServices;
namespace Unosquare.RaspberryIO.Native {
/// <summary> /// <summary>
/// OS uname structure. /// OS uname structure.
/// </summary> /// </summary>
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct SystemName public struct SystemName {
{
/// <summary> /// <summary>
/// System name. /// System name.
/// </summary> /// </summary>
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 65)] [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 65)]
public string SysName; public String SysName;
/// <summary> /// <summary>
/// Node name. /// Node name.
/// </summary> /// </summary>
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 65)] [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 65)]
public string NodeName; public String NodeName;
/// <summary> /// <summary>
/// Release level. /// Release level.
/// </summary> /// </summary>
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 65)] [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 65)]
public string Release; public String Release;
/// <summary> /// <summary>
/// Version level. /// Version level.
/// </summary> /// </summary>
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 65)] [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 65)]
public string Version; public String Version;
/// <summary> /// <summary>
/// Hardware level. /// Hardware level.
/// </summary> /// </summary>
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 65)] [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 65)]
public string Machine; public String Machine;
/// <summary> /// <summary>
/// Domain name. /// Domain name.
/// </summary> /// </summary>
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 65)] [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 65)]
public string DomainName; public String DomainName;
} }
} }

View File

@ -1,30 +1,28 @@
namespace Unosquare.RaspberryIO using System;
{ using System.Threading.Tasks;
using Abstractions;
using Camera;
using Computer;
using Swan;
using Swan.DependencyInjection;
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 {
/// <summary> /// <summary>
/// Our main character. Provides access to the Raspberry Pi's GPIO, system and board information and Camera. /// Our main character. Provides access to the Raspberry Pi's GPIO, system and board information and Camera.
/// </summary> /// </summary>
public static class Pi public static class Pi {
{ private const String MissingDependenciesMessage = "You need to load a valid assembly (WiringPi or PiGPIO).";
private const string MissingDependenciesMessage = "You need to load a valid assembly (WiringPi or PiGPIO)."; private static readonly Object SyncLock = new Object();
private static readonly object SyncLock = new object(); private static Boolean _isInit;
private static bool _isInit; private static SystemInfo? _info;
private static SystemInfo _info;
/// <summary> /// <summary>
/// Initializes static members of the <see cref="Pi" /> class. /// Initializes static members of the <see cref="Pi" /> class.
/// </summary> /// </summary>
static Pi() static Pi() {
{ lock(SyncLock) {
lock (SyncLock)
{
Camera = CameraController.Instance; Camera = CameraController.Instance;
PiDisplay = DsiDisplay.Instance; PiDisplay = DsiDisplay.Instance;
Audio = AudioSettings.Instance; Audio = AudioSettings.Instance;
@ -40,52 +38,55 @@ namespace Unosquare.RaspberryIO
/// <summary> /// <summary>
/// Provides access to the Raspberry Pi's GPIO as a collection of GPIO Pins. /// Provides access to the Raspberry Pi's GPIO as a collection of GPIO Pins.
/// </summary> /// </summary>
public static IGpioController Gpio => public static IGpioController Gpio => ResolveDependency<IGpioController>();
ResolveDependency<IGpioController>();
/// <summary> /// <summary>
/// Provides access to the 2-channel SPI bus. /// Provides access to the 2-channel SPI bus.
/// </summary> /// </summary>
public static ISpiBus Spi => public static ISpiBus Spi => ResolveDependency<ISpiBus>();
ResolveDependency<ISpiBus>();
/// <summary> /// <summary>
/// Provides access to the functionality of the i2c bus. /// Provides access to the functionality of the i2c bus.
/// </summary> /// </summary>
public static II2CBus I2C => public static II2CBus I2C => ResolveDependency<II2CBus>();
ResolveDependency<II2CBus>();
/// <summary> /// <summary>
/// Provides access to timing functionality. /// Provides access to timing functionality.
/// </summary> /// </summary>
public static ITiming Timing => public static ITiming Timing => ResolveDependency<ITiming>();
ResolveDependency<ITiming>();
/// <summary> /// <summary>
/// Provides access to threading functionality. /// Provides access to threading functionality.
/// </summary> /// </summary>
public static IThreading Threading => public static IThreading Threading => ResolveDependency<IThreading>();
ResolveDependency<IThreading>();
/// <summary> /// <summary>
/// Provides access to the official Raspberry Pi Camera. /// Provides access to the official Raspberry Pi Camera.
/// </summary> /// </summary>
public static CameraController Camera { get; } public static CameraController Camera {
get;
}
/// <summary> /// <summary>
/// Provides access to the official Raspberry Pi 7-inch DSI Display. /// Provides access to the official Raspberry Pi 7-inch DSI Display.
/// </summary> /// </summary>
public static DsiDisplay PiDisplay { get; } public static DsiDisplay PiDisplay {
get;
}
/// <summary> /// <summary>
/// Provides access to Raspberry Pi ALSA sound card driver. /// Provides access to Raspberry Pi ALSA sound card driver.
/// </summary> /// </summary>
public static AudioSettings Audio { get; } public static AudioSettings Audio {
get;
}
/// <summary> /// <summary>
/// Provides access to Raspberry Pi Bluetooth driver. /// Provides access to Raspberry Pi Bluetooth driver.
/// </summary> /// </summary>
public static Bluetooth Bluetooth { get; } public static Bluetooth Bluetooth {
get;
}
/// <summary> /// <summary>
/// Restarts the Pi. Must be running as SU. /// Restarts the Pi. Must be running as SU.
@ -115,27 +116,23 @@ namespace Unosquare.RaspberryIO
/// Initializes an Abstractions implementation. /// Initializes an Abstractions implementation.
/// </summary> /// </summary>
/// <typeparam name="T">An implementation of <see cref="IBootstrap"/>.</typeparam> /// <typeparam name="T">An implementation of <see cref="IBootstrap"/>.</typeparam>
public static void Init<T>() public static void Init<T>() where T : IBootstrap {
where T : IBootstrap lock(SyncLock) {
{ if(_isInit) {
lock (SyncLock) return;
{ }
if (_isInit) return;
Activator.CreateInstance<T>().Bootstrap(); Activator.CreateInstance<T>().Bootstrap();
_isInit = true; _isInit = true;
} }
} }
private static T ResolveDependency<T>() private static T ResolveDependency<T>() where T : class {
where T : class if(!_isInit) {
{
if (!_isInit)
throw new InvalidOperationException($"You must first initialize {nameof(Pi)} referencing a valid {nameof(IBootstrap)} implementation."); throw new InvalidOperationException($"You must first initialize {nameof(Pi)} referencing a valid {nameof(IBootstrap)} implementation.");
}
return DependencyContainer.Current.CanResolve<T>() return DependencyContainer.Current.CanResolve<T>() ? DependencyContainer.Current.Resolve<T>() : throw new InvalidOperationException(MissingDependenciesMessage);
? DependencyContainer.Current.Resolve<T>()
: throw new InvalidOperationException(MissingDependenciesMessage);
} }
} }
} }

View File

@ -16,6 +16,7 @@ This library enables developers to use the various Raspberry Pi's hardware modul
<PackageLicenseUrl>https://raw.githubusercontent.com/unosquare/raspberryio/master/LICENSE</PackageLicenseUrl> <PackageLicenseUrl>https://raw.githubusercontent.com/unosquare/raspberryio/master/LICENSE</PackageLicenseUrl>
<PackageTags>Raspberry Pi GPIO Camera SPI I2C Embedded IoT Mono C# .NET</PackageTags> <PackageTags>Raspberry Pi GPIO Camera SPI I2C Embedded IoT Mono C# .NET</PackageTags>
<LangVersion>8.0</LangVersion> <LangVersion>8.0</LangVersion>
<Nullable>enable</Nullable>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>