Codingstyle....

This commit is contained in:
2019-12-06 23:12:34 +01:00
parent a7a7278eda
commit b469c4ed6e
22 changed files with 3158 additions and 3245 deletions
+101 -110
View File
@@ -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;
}
}
}
+69 -61
View File
@@ -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";
}
}
+198 -268
View File
@@ -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}");
}
}
}
+60 -68
View File
@@ -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;
}
}
}
+268 -278
View File
@@ -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();
}
}
}
+55 -43
View File
@@ -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}";
}
}
+377 -383
View File
@@ -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,
}
}
+380 -387
View File
@@ -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;
}
}
}