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