From cfdf803a7b8fdf27b2c372ac8cc3ec642db312f3 Mon Sep 17 00:00:00 2001 From: sleevezipper Date: Sun, 14 Mar 2021 18:03:20 +0100 Subject: [PATCH] gpu sensors --- README.md | 12 +++- UserInterface/Views/AddSensorDialog.axaml.cs | 14 +++++ UserInterface/Views/SensorSettings.axaml.cs | 7 ++- .../InterProcessApi.cs | 11 +++- .../ServiceContractInterfaces.cs | 2 +- .../ServiceContractModels.cs | 4 +- .../Data/ConfigurationService.cs | 18 ++++++ .../Data/IConfigurationService.cs | 1 + .../Domain/Sensors/GpuLoadSensor.cs | 62 ++++++++++++++++++ .../Domain/Sensors/GpuTemperatureSensor.cs | 63 +++++++++++++++++++ .../hass-workstation-service.csproj | 1 + 11 files changed, 188 insertions(+), 7 deletions(-) create mode 100644 hass-workstation-service/Domain/Sensors/GpuLoadSensor.cs create mode 100644 hass-workstation-service/Domain/Sensors/GpuTemperatureSensor.cs diff --git a/README.md b/README.md index d2a95dc..49d4117 100644 --- a/README.md +++ b/README.md @@ -90,6 +90,14 @@ This sensor shows if the microphone is currently being used. It uses the Windows This sensor checks the current CPU load. It averages the load on all logical cores every second and rounds the output to two decimals. +### GPULoad + +This sensor returns the current GPU load. This should work for both NVidia and AMD GPU's. + +### GPUTemperature + +This sensor returns the current temperature of the GPU in °C. This should work for both NVidia and AMD GPU's. + ### UsedMemory This sensor calculates the percentage of used memory. @@ -196,7 +204,7 @@ Sends a keystroke with the specified key. You can pick [any of these](https://do ### Media Commands -There's several media commands available which are very self exlanatory. +There's several media commands available which are very self exlanatory. - Play/Pause - Next @@ -211,4 +219,4 @@ This project depends on work done by others and they should at least get a menti ### [CoreAudio](https://github.com/morphx666/CoreAudio) -CoreAudio was used to check the current volume of playing audio. +CoreAudio was used to check the current volume of playing audio. diff --git a/UserInterface/Views/AddSensorDialog.axaml.cs b/UserInterface/Views/AddSensorDialog.axaml.cs index 1f0f785..e51a34e 100644 --- a/UserInterface/Views/AddSensorDialog.axaml.cs +++ b/UserInterface/Views/AddSensorDialog.axaml.cs @@ -163,6 +163,20 @@ namespace UserInterface.Views item.ShowWindowNameInput = false; item.UpdateInterval = 5; break; + case AvailableSensors.GPUTemperatureSensor: + item.Description = "This sensor returns the current temperature of the GPU in °C."; + item.MoreInfoLink = "https://github.com/sleevezipper/hass-workstation-service#gputemperature"; + item.ShowQueryInput = false; + item.ShowWindowNameInput = false; + item.UpdateInterval = 5; + break; + case AvailableSensors.GPULoadSensor: + item.Description = "This sensor returns the current GPU load."; + item.MoreInfoLink = "https://github.com/sleevezipper/hass-workstation-service#gpuload"; + item.ShowQueryInput = false; + item.ShowWindowNameInput = false; + item.UpdateInterval = 5; + break; default: item.Description = null; item.MoreInfoLink = null; diff --git a/UserInterface/Views/SensorSettings.axaml.cs b/UserInterface/Views/SensorSettings.axaml.cs index 6619e06..532006c 100644 --- a/UserInterface/Views/SensorSettings.axaml.cs +++ b/UserInterface/Views/SensorSettings.axaml.cs @@ -50,12 +50,17 @@ namespace UserInterface.Views sensorsNeedToRefresh = false; List status = await this.client.InvokeAsync(x => x.GetConfiguredSensors()); - ((SensorSettingsViewModel)this.DataContext).ConfiguredSensors = status.Select(s => new SensorViewModel() { Name = s.Name, Type = s.Type, Value = s.Value, Id = s.Id , UpdateInterval = s.UpdateInterval, UnitOfMeasurement = s.UnitOfMeasurement}).ToList(); + ((SensorSettingsViewModel)this.DataContext).ConfiguredSensors = status.Select(s => new SensorViewModel() { Name = s.Name, Type = s.Type, Value = s.Value, Id = s.Id, UpdateInterval = s.UpdateInterval, UnitOfMeasurement = s.UnitOfMeasurement }).ToList(); while (!sensorsNeedToRefresh) { await Task.Delay(1000); List statusUpdated = await this.client.InvokeAsync(x => x.GetConfiguredSensors()); var configuredSensors = ((SensorSettingsViewModel)this.DataContext).ConfiguredSensors; + // this is a workaround for the list showing before it has been completely loaded in the service + if (statusUpdated.Count != configuredSensors.Count) { + sensorsNeedToRefresh = true; + GetConfiguredSensors(); + } statusUpdated.ForEach(s => { var configuredSensor = configuredSensors.FirstOrDefault(cs => cs.Id == s.Id); diff --git a/hass-workstation-service/Communication/InterProcesCommunication/InterProcessApi.cs b/hass-workstation-service/Communication/InterProcesCommunication/InterProcessApi.cs index 25eff46..e78985b 100644 --- a/hass-workstation-service/Communication/InterProcesCommunication/InterProcessApi.cs +++ b/hass-workstation-service/Communication/InterProcesCommunication/InterProcessApi.cs @@ -73,9 +73,10 @@ namespace hass_workstation_service.Communication.InterProcesCommunication return this._configurationService.IsAutoStartEnabled(); } - public List GetConfiguredSensors() + public async Task> GetConfiguredSensors() { - return this._configurationService.ConfiguredSensors.Select(s => new ConfiguredSensorModel() { Name = s.Name, Type = s.GetType().Name, Value = s.PreviousPublishedState, Id = s.Id, UpdateInterval = s.UpdateInterval, UnitOfMeasurement = ((SensorDiscoveryConfigModel)s.GetAutoDiscoveryConfig()).Unit_of_measurement }).ToList(); + var sensors = await this._configurationService.GetSensorsAfterLoadingAsync(); + return sensors.Select(s => new ConfiguredSensorModel() { Name = s.Name, Type = s.GetType().Name, Value = s.PreviousPublishedState, Id = s.Id, UpdateInterval = s.UpdateInterval, UnitOfMeasurement = ((SensorDiscoveryConfigModel)s.GetAutoDiscoveryConfig()).Unit_of_measurement }).ToList(); } public List GetConfiguredCommands() @@ -150,6 +151,12 @@ namespace hass_workstation_service.Communication.InterProcesCommunication case AvailableSensors.CurrentVolumeSensor: sensorToCreate = new CurrentVolumeSensor(this._publisher, (int)model.UpdateInterval, model.Name); break; + case AvailableSensors.GPUTemperatureSensor: + sensorToCreate = new GpuTemperatureSensor(this._publisher, (int)model.UpdateInterval, model.Name); + break; + case AvailableSensors.GPULoadSensor: + sensorToCreate = new GpuLoadSensor(this._publisher, (int)model.UpdateInterval, model.Name); + break; default: Log.Logger.Error("Unknown sensortype"); break; diff --git a/hass-workstation-service/Communication/InterProcesCommunication/ServiceContractInterfaces.cs b/hass-workstation-service/Communication/InterProcesCommunication/ServiceContractInterfaces.cs index 189bd5c..b7f9dc3 100644 --- a/hass-workstation-service/Communication/InterProcesCommunication/ServiceContractInterfaces.cs +++ b/hass-workstation-service/Communication/InterProcesCommunication/ServiceContractInterfaces.cs @@ -14,7 +14,7 @@ namespace hass_workstation_service.Communication.NamedPipe MqqtClientStatus GetMqqtClientStatus(); void EnableAutostart(bool enable); bool IsAutoStartEnabled(); - List GetConfiguredSensors(); + Task> GetConfiguredSensors(); void RemoveSensorById(Guid id); void AddSensor(AvailableSensors sensorType, string json); void RemoveCommandById(Guid id); diff --git a/hass-workstation-service/Communication/InterProcesCommunication/ServiceContractModels.cs b/hass-workstation-service/Communication/InterProcesCommunication/ServiceContractModels.cs index 768f5e8..1f87979 100644 --- a/hass-workstation-service/Communication/InterProcesCommunication/ServiceContractModels.cs +++ b/hass-workstation-service/Communication/InterProcesCommunication/ServiceContractModels.cs @@ -51,7 +51,9 @@ namespace hass_workstation_service.Communication.InterProcesCommunication.Models LastActiveSensor, LastBootSensor, SessionStateSensor, - CurrentVolumeSensor + CurrentVolumeSensor, + GPUTemperatureSensor, + GPULoadSensor } public enum AvailableCommands diff --git a/hass-workstation-service/Data/ConfigurationService.cs b/hass-workstation-service/Data/ConfigurationService.cs index e58d95e..db11c32 100644 --- a/hass-workstation-service/Data/ConfigurationService.cs +++ b/hass-workstation-service/Data/ConfigurationService.cs @@ -32,6 +32,7 @@ namespace hass_workstation_service.Data private bool BrokerSettingsFileLocked { get; set; } private bool SensorsSettingsFileLocked { get; set; } private bool CommandSettingsFileLocked { get; set; } + private bool _sensorsLoading { get; set; } private readonly string path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "Hass Workstation Service"); @@ -59,6 +60,7 @@ namespace hass_workstation_service.Data public async void ReadSensorSettings(MqttPublisher publisher) { + this._sensorsLoading = true; while (this.SensorsSettingsFileLocked) { await Task.Delay(500); @@ -123,6 +125,12 @@ namespace hass_workstation_service.Data case "CurrentVolumeSensor": sensor = new CurrentVolumeSensor(publisher, configuredSensor.UpdateInterval, configuredSensor.Name, configuredSensor.Id); break; + case "GpuTemperatureSensor": + sensor = new GpuTemperatureSensor(publisher, configuredSensor.UpdateInterval, configuredSensor.Name, configuredSensor.Id); + break; + case "GpuLoadSensor": + sensor = new GpuLoadSensor(publisher, configuredSensor.UpdateInterval, configuredSensor.Name, configuredSensor.Id); + break; // keep this one last! case "WMIQuerySensor": sensor = new WMIQuerySensor(publisher, configuredSensor.Query, configuredSensor.UpdateInterval, configuredSensor.Name, configuredSensor.Id); @@ -135,6 +143,7 @@ namespace hass_workstation_service.Data { this.ConfiguredSensors.Add(sensor); } + this._sensorsLoading = false; } } @@ -474,5 +483,14 @@ namespace hass_workstation_service.Data RegistryKey rkApp = Registry.CurrentUser.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Run", true); return rkApp.GetValue("hass-workstation-service") != null; } + + public async Task> GetSensorsAfterLoadingAsync() + { + while (this._sensorsLoading) + { + await Task.Delay(500); + } + return this.ConfiguredSensors; + } } } \ No newline at end of file diff --git a/hass-workstation-service/Data/IConfigurationService.cs b/hass-workstation-service/Data/IConfigurationService.cs index ba0d4d7..43d81dc 100644 --- a/hass-workstation-service/Data/IConfigurationService.cs +++ b/hass-workstation-service/Data/IConfigurationService.cs @@ -31,5 +31,6 @@ namespace hass_workstation_service.Data void DeleteConfiguredCommand(Guid id); void WriteCommandSettingsAsync(); void ReadCommandSettings(MqttPublisher publisher); + Task> GetSensorsAfterLoadingAsync(); } } \ No newline at end of file diff --git a/hass-workstation-service/Domain/Sensors/GpuLoadSensor.cs b/hass-workstation-service/Domain/Sensors/GpuLoadSensor.cs new file mode 100644 index 0000000..9231075 --- /dev/null +++ b/hass-workstation-service/Domain/Sensors/GpuLoadSensor.cs @@ -0,0 +1,62 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using hass_workstation_service.Communication; +using LibreHardwareMonitor.Hardware; + +namespace hass_workstation_service.Domain.Sensors +{ + public class GpuLoadSensor : AbstractSensor + { + private Computer _computer; + private IHardware _gpu; + public GpuLoadSensor(MqttPublisher publisher, int? updateInterval = null, string name = "GPULoad", Guid id = default(Guid)) : base(publisher, name ?? "GPULoad", updateInterval ?? 10, id) + { + _computer = new Computer + { + IsCpuEnabled = true, + IsGpuEnabled = true, + IsMemoryEnabled = true, + IsMotherboardEnabled = true, + IsControllerEnabled = true, + IsNetworkEnabled = true, + IsStorageEnabled = true + }; + + _computer.Open(); + this._gpu = _computer.Hardware.FirstOrDefault(h => h.HardwareType == HardwareType.GpuAmd || h.HardwareType == HardwareType.GpuNvidia); + } + + public override DiscoveryConfigModel GetAutoDiscoveryConfig() + { + return this._autoDiscoveryConfigModel ?? SetAutoDiscoveryConfigModel(new SensorDiscoveryConfigModel() + { + Name = this.Name, + Unique_id = this.Id.ToString(), + Device = this.Publisher.DeviceConfigModel, + State_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/{this.Name}/state", + Unit_of_measurement = "%", + Availability_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/availability" + }); + } + + public override string GetState() + { + if (_gpu == null) + { + return "NotSupported"; + } + _gpu.Update(); + var sensor = _gpu.Sensors.FirstOrDefault(s => s.SensorType == SensorType.Load); + if (sensor == null) + { + return "NotSupported"; + } + + return sensor.Value.HasValue ? sensor.Value.Value.ToString("#.##", CultureInfo.InvariantCulture) : "Unknown"; + } + } +} diff --git a/hass-workstation-service/Domain/Sensors/GpuTemperatureSensor.cs b/hass-workstation-service/Domain/Sensors/GpuTemperatureSensor.cs new file mode 100644 index 0000000..27eada6 --- /dev/null +++ b/hass-workstation-service/Domain/Sensors/GpuTemperatureSensor.cs @@ -0,0 +1,63 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using hass_workstation_service.Communication; +using LibreHardwareMonitor.Hardware; + +namespace hass_workstation_service.Domain.Sensors +{ + public class GpuTemperatureSensor : AbstractSensor + { + private Computer _computer; + private IHardware _gpu; + public GpuTemperatureSensor(MqttPublisher publisher, int? updateInterval = null, string name = "GPUTemperature", Guid id = default(Guid)) : base(publisher, name ?? "GPUTemperature", updateInterval ?? 10, id) + { + _computer = new Computer + { + IsCpuEnabled = true, + IsGpuEnabled = true, + IsMemoryEnabled = true, + IsMotherboardEnabled = true, + IsControllerEnabled = true, + IsNetworkEnabled = true, + IsStorageEnabled = true + }; + + _computer.Open(); + this._gpu = _computer.Hardware.FirstOrDefault(h => h.HardwareType == HardwareType.GpuAmd || h.HardwareType == HardwareType.GpuNvidia); + } + + public override DiscoveryConfigModel GetAutoDiscoveryConfig() + { + return this._autoDiscoveryConfigModel ?? SetAutoDiscoveryConfigModel(new SensorDiscoveryConfigModel() + { + Name = this.Name, + Unique_id = this.Id.ToString(), + Device = this.Publisher.DeviceConfigModel, + State_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/{this.Name}/state", + Device_class = "temperature", + Unit_of_measurement = "°C", + Availability_topic = $"homeassistant/{this.Domain}/{Publisher.DeviceConfigModel.Name}/availability" + }); + } + + public override string GetState() + { + if (_gpu == null) + { + return "NotSupported"; + } + _gpu.Update(); + var sensor = _gpu.Sensors.FirstOrDefault(s => s.SensorType == SensorType.Temperature); + if (sensor == null) + { + return "NotSupported"; + } + + return sensor.Value.HasValue ? sensor.Value.Value.ToString("#.##", CultureInfo.InvariantCulture) : "Unknown"; + } + } +} diff --git a/hass-workstation-service/hass-workstation-service.csproj b/hass-workstation-service/hass-workstation-service.csproj index 675b076..3dc5f99 100644 --- a/hass-workstation-service/hass-workstation-service.csproj +++ b/hass-workstation-service/hass-workstation-service.csproj @@ -43,6 +43,7 @@ +