// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. // If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. // Copyright (C) LibreHardwareMonitor and Contributors. // Partial Copyright (C) Michael Möller and Contributors. // All Rights Reserved. using System; using System.Collections.Generic; using System.Globalization; using System.IO; using System.IO.Compression; namespace LibreHardwareMonitor.Hardware; internal class Sensor : ISensor { private readonly string _defaultName; private readonly Hardware _hardware; private readonly ISettings _settings; private readonly bool _trackMinMax; private readonly List _values = new(); private int _count; private float? _currentValue; private string _name; private float _sum; private TimeSpan _valuesTimeWindow = TimeSpan.FromDays(1.0); public Sensor(string name, int index, SensorType sensorType, Hardware hardware, ISettings settings) : this(name, index, sensorType, hardware, null, settings) { } public Sensor(string name, int index, SensorType sensorType, Hardware hardware, ParameterDescription[] parameterDescriptions, ISettings settings) : this(name, index, false, sensorType, hardware, parameterDescriptions, settings) { } public Sensor ( string name, int index, bool defaultHidden, SensorType sensorType, Hardware hardware, ParameterDescription[] parameterDescriptions, ISettings settings, bool disableHistory = false) { Index = index; IsDefaultHidden = defaultHidden; SensorType = sensorType; _hardware = hardware; Parameter[] parameters = new Parameter[parameterDescriptions?.Length ?? 0]; for (int i = 0; i < parameters.Length; i++) { if (parameterDescriptions != null) parameters[i] = new Parameter(parameterDescriptions[i], this, settings); } Parameters = parameters; _settings = settings; _defaultName = name; _name = settings.GetValue(new Identifier(Identifier, "name").ToString(), name); _trackMinMax = !disableHistory; if (disableHistory) { _valuesTimeWindow = TimeSpan.Zero; } GetSensorValuesFromSettings(); hardware.Closing += delegate { SetSensorValuesToSettings(); }; } public IControl Control { get; internal set; } public IHardware Hardware { get { return _hardware; } } public Identifier Identifier { get { return new Identifier(_hardware.Identifier, SensorType.ToString().ToLowerInvariant(), Index.ToString(CultureInfo.InvariantCulture)); } } public int Index { get; } public bool IsDefaultHidden { get; } public float? Max { get; private set; } public float? Min { get; private set; } public string Name { get { return _name; } set { _name = !string.IsNullOrEmpty(value) ? value : _defaultName; _settings.SetValue(new Identifier(Identifier, "name").ToString(), _name); } } public IReadOnlyList Parameters { get; } public SensorType SensorType { get; } public virtual float? Value { get { return _currentValue; } set { if (_valuesTimeWindow != TimeSpan.Zero) { DateTime now = DateTime.UtcNow; while (_values.Count > 0 && now - _values[0].Time > _valuesTimeWindow) _values.RemoveAt(0); if (value.HasValue) { _sum += value.Value; _count++; if (_count == 4) { AppendValue(_sum / _count, now); _sum = 0; _count = 0; } } } _currentValue = value; if (_trackMinMax) { if (!Min.HasValue || Min > value) Min = value; if (!Max.HasValue || Max < value) Max = value; } } } public IEnumerable Values { get { return _values; } } public TimeSpan ValuesTimeWindow { get { return _valuesTimeWindow; } set { _valuesTimeWindow = value; if (value == TimeSpan.Zero) _values.Clear(); } } public void ResetMin() { Min = null; } public void ResetMax() { Max = null; } public void ClearValues() { _values.Clear(); } public void Accept(IVisitor visitor) { if (visitor == null) throw new ArgumentNullException(nameof(visitor)); visitor.VisitSensor(this); } public void Traverse(IVisitor visitor) { foreach (IParameter parameter in Parameters) parameter.Accept(visitor); } private void SetSensorValuesToSettings() { using MemoryStream memoryStream = new(); using GZipStream gZipStream = new(memoryStream, CompressionMode.Compress); using BufferedStream outputStream = new(gZipStream, 65536); using (BinaryWriter binaryWriter = new(outputStream)) { long t = 0; foreach (SensorValue sensorValue in _values) { long v = sensorValue.Time.ToBinary(); binaryWriter.Write(v - t); t = v; binaryWriter.Write(sensorValue.Value); } binaryWriter.Flush(); } _settings.SetValue(new Identifier(Identifier, "values").ToString(), Convert.ToBase64String(memoryStream.ToArray())); } private void GetSensorValuesFromSettings() { string name = new Identifier(Identifier, "values").ToString(); string s = _settings.GetValue(name, null); if (!string.IsNullOrEmpty(s)) { try { byte[] array = Convert.FromBase64String(s); DateTime now = DateTime.UtcNow; using MemoryStream memoryStream = new(array); using GZipStream gZipStream = new(memoryStream, CompressionMode.Decompress); using MemoryStream destination = new(); gZipStream.CopyTo(destination); destination.Seek(0, SeekOrigin.Begin); using BinaryReader reader = new(destination); try { long t = 0; long readLen = reader.BaseStream.Length - reader.BaseStream.Position; while (readLen > 0) { t += reader.ReadInt64(); DateTime time = DateTime.FromBinary(t); if (time > now) break; float value = reader.ReadSingle(); AppendValue(value, time); readLen = reader.BaseStream.Length - reader.BaseStream.Position; } } catch (EndOfStreamException) { } } catch { // Ignored. } } if (_values.Count > 0) AppendValue(float.NaN, DateTime.UtcNow); //remove the value string from the settings to reduce memory usage _settings.Remove(name); } private void AppendValue(float value, DateTime time) { if (_values.Count >= 2 && _values[_values.Count - 1].Value == value && _values[_values.Count - 2].Value == value) { _values[_values.Count - 1] = new SensorValue(value, time); return; } _values.Add(new SensorValue(value, time)); } }