using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using Iot.Device.Common;
using Iot.Device.HardwareMonitor;
using Iot.Device.HardwareMonitor.JsonSchema;
using Microsoft.Extensions.Logging;
namespace Iot.Device.HardwareMonitor
{
internal sealed class OpenHardwareMonitorHttp : IOpenHardwareMonitorInternal
{
private readonly string _host;
private readonly int _port;
private readonly Uri _uri;
private readonly ILogger _logger;
private readonly object _lock = new object();
private HttpClient _httpClient;
private string? _version;
private List<OpenHardwareMonitor.Hardware> _hardware;
private List<SensorHttp> _sensors;
private DateTime _lastUpdateTime;
public OpenHardwareMonitorHttp(string host, int port)
{
_host = host;
_port = port;
_lastUpdateTime = DateTime.MinValue;
_hardware = new List<OpenHardwareMonitor.Hardware>();
_sensors = new List<SensorHttp>();
_uri = new Uri("http://" + host + ":" + port + "/");
_httpClient = new HttpClient();
_logger = this.GetCurrentClassLogger();
TryConnectToApi();
}
public SensorUpdateStrategy UpdateStrategy
{
get;
set;
}
public TimeSpan UpdateInterval
{
get;
set;
}
public void UpdateSensors(bool refreshSensorList)
{
GetFullDataAndDecode(refreshSensorList);
}
public bool TryConnectToApi()
{
if (!string.IsNullOrEmpty(_version))
{
return true;
}
_version = string.Empty;
try
{
_version = _httpClient.GetStringAsync(_uri + "api/version").Result;
}
catch (AggregateException ex)
{
_logger.LogWarning(ex, $"Unable to read version from OpenHardwareMonitor REST api: {ex.Message}");
_version = String.Empty;
}
return !string.IsNullOrEmpty(_version);
}
public bool HasHardware()
{
return !string.IsNullOrEmpty(_version);
}
private void GetFullDataAndDecode(bool refreshSensorList)
{
if (!TryConnectToApi())
{
return;
}
try
{
string fullJson = _httpClient.GetStringAsync(_uri + "api/rootnode").Result;
fullJson = fullJson.Replace("\"Value\": NaN", "\"Value\": 0");
lock (_lock)
{
ComputerJson? rootNode = JsonSerializer.Deserialize<ComputerJson>(fullJson);
if (rootNode == null)
{
return;
}
if (_hardware.Count == 0 || _sensors.Count == 0 || refreshSensorList)
{
var (newHardware, newSensors) = CreateSensorTree(rootNode);
_hardware = newHardware;
_sensors = newSensors;
}
else
{
List<SensorJson> sensorsFromJson = new();
foreach (var hw in rootNode.Hardware)
{
sensorsFromJson.AddRange(hw.Sensors);
}
foreach (var sensor in sensorsFromJson)
{
var sensorToUpdate = _sensors.FirstOrDefault(x => x.Identifier == sensor.NodeId);
if (sensorToUpdate != null)
{
sensorToUpdate.Value = sensor.Value;
}
}
}
_lastUpdateTime = DateTime.UtcNow;
}
}
catch (AggregateException ex)
{
_logger.LogWarning(ex, $"Unable to read /api/rootnode: {ex.Message}");
if ((DateTime.UtcNow - _lastUpdateTime).Duration().TotalSeconds > UpdateInterval.TotalSeconds * 10)
{
_hardware = new List<OpenHardwareMonitor.Hardware>();
_sensors = new List<SensorHttp>();
}
}
}
private (List<OpenHardwareMonitor.Hardware> Hardware, List<SensorHttp> Sensors) CreateSensorTree(ComputerJson rootNode)
{
var newHardware = new List<OpenHardwareMonitor.Hardware>();
var newSensors = new List<SensorHttp>();
foreach (var hardware in rootNode.Hardware)
{
var hw = new OpenHardwareMonitor.Hardware(hardware.Name, hardware.NodeId, hardware.Parent, hardware.HardwareType);
newHardware.Add(hw);
foreach (var sensor in hardware.Sensors)
{
SensorType sensorType;
if (Enum.TryParse<SensorType>(sensor.Type, true, out sensorType))
{
var s = new SensorHttp(this, sensor.Name, sensor.NodeId, sensor.Parent, sensorType, sensor.Value);
newSensors.Add(s);
}
}
}
return (newHardware, newSensors);
}
public IList<OpenHardwareMonitor.Sensor> GetSensorList()
{
if (!_sensors.Any())
{
GetFullDataAndDecode(false);
}
return _sensors.Cast<OpenHardwareMonitor.Sensor>().ToList();
}
public IList<OpenHardwareMonitor.Hardware> GetHardwareComponents()
{
if (!_hardware.Any())
{
GetFullDataAndDecode(false);
}
return _hardware;
}
public void Dispose()
{
_version = string.Empty;
_httpClient.Dispose();
}
private Uri Combine(Uri root, params String[] moreParts)
{
if (!moreParts.Any())
{
return root;
}
Uri combined = root;
foreach (var part in moreParts)
{
Uri? temp;
var trimmed = part.TrimStart('/');
if (!Uri.TryCreate(combined, trimmed, out temp))
{
throw new InvalidOperationException($"{part} is not a valid URL component");
}
combined = temp;
}
return combined;
}
private bool TryUpdateSensor(SensorHttp sensor, out double value)
{
value = 0;
if (UpdateStrategy == SensorUpdateStrategy.PerSensor || UpdateStrategy == SensorUpdateStrategy.Unspecified)
{
try
{
var apiUrl = Combine(_uri, "/api/nodes/", sensor.Identifier);
string fullJson = _httpClient.GetStringAsync(apiUrl).Result;
lock (_lock)
{
SensorJson? rootNode = JsonSerializer.Deserialize<SensorJson>(fullJson);
if (rootNode == null)
{
_logger.LogWarning($"Unable to parse json: {fullJson}");
return false;
}
value = rootNode.Value;
return true;
}
}
catch (AggregateException ex)
{
_logger.LogWarning(ex, $"Unable to read update value for node {sensor.Identifier}: {ex.Message}");
return false;
}
}
else if (UpdateStrategy == SensorUpdateStrategy.SynchronousAfterTimeout && (DateTime.UtcNow - _lastUpdateTime).Duration() > UpdateInterval)
{
UpdateSensors(false);
}
value = sensor.Value;
return true;
}
private sealed class SensorHttp : OpenHardwareMonitor.Sensor
{
private readonly OpenHardwareMonitorHttp _monitor;
public SensorHttp(OpenHardwareMonitorHttp monitor, string name, string identifier, string? parent, SensorType typeEnum, double initialValue)
: base(name, identifier, parent, typeEnum)
{
_monitor = monitor;
Value = initialValue;
}
protected override bool UpdateValue(out double value)
{
return _monitor.TryUpdateSensor(this, out value);
}
}
}
}