├── doc └── assets │ ├── FanControl.HomeAssistant_Settings1.png │ ├── FanControl.HomeAssistant_Settings2.png │ ├── FanControl.HomeAssistant_Trouble_DLL1.png │ ├── FanControl.HomeAssistant_Trouble_DLL2.png │ ├── FanControl.HomeAssistant_plugin_install.gif │ ├── FanControl.HomeAssistant_Trouble_Entities.png │ ├── FanControl.HomeAssistant_AmbientMixSensorExample.png │ └── FanControl.HomeAssistant_AmbientMixSensorExample2.png ├── test_http └── get-sensor-id-state.http ├── FanControl.HomeAssistant ├── FanControl.HomeAssistant.csproj ├── HomeAssistantConfig.cs ├── HomeAssistantPlugin.cs └── HomeAssistantSensor.cs ├── .github ├── FUNDING.yml └── workflows │ └── pipeline.yml ├── DEVELOPMENT.md ├── update-fancontrol-api.ps1 ├── LICENSE ├── FanControl.HomeAssistant.sln ├── README.md └── .gitignore /doc/assets/FanControl.HomeAssistant_Settings1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hgross/FanControl.HomeAssistant/HEAD/doc/assets/FanControl.HomeAssistant_Settings1.png -------------------------------------------------------------------------------- /doc/assets/FanControl.HomeAssistant_Settings2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hgross/FanControl.HomeAssistant/HEAD/doc/assets/FanControl.HomeAssistant_Settings2.png -------------------------------------------------------------------------------- /doc/assets/FanControl.HomeAssistant_Trouble_DLL1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hgross/FanControl.HomeAssistant/HEAD/doc/assets/FanControl.HomeAssistant_Trouble_DLL1.png -------------------------------------------------------------------------------- /doc/assets/FanControl.HomeAssistant_Trouble_DLL2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hgross/FanControl.HomeAssistant/HEAD/doc/assets/FanControl.HomeAssistant_Trouble_DLL2.png -------------------------------------------------------------------------------- /doc/assets/FanControl.HomeAssistant_plugin_install.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hgross/FanControl.HomeAssistant/HEAD/doc/assets/FanControl.HomeAssistant_plugin_install.gif -------------------------------------------------------------------------------- /doc/assets/FanControl.HomeAssistant_Trouble_Entities.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hgross/FanControl.HomeAssistant/HEAD/doc/assets/FanControl.HomeAssistant_Trouble_Entities.png -------------------------------------------------------------------------------- /doc/assets/FanControl.HomeAssistant_AmbientMixSensorExample.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hgross/FanControl.HomeAssistant/HEAD/doc/assets/FanControl.HomeAssistant_AmbientMixSensorExample.png -------------------------------------------------------------------------------- /doc/assets/FanControl.HomeAssistant_AmbientMixSensorExample2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hgross/FanControl.HomeAssistant/HEAD/doc/assets/FanControl.HomeAssistant_AmbientMixSensorExample2.png -------------------------------------------------------------------------------- /test_http/get-sensor-id-state.http: -------------------------------------------------------------------------------- 1 | GET http://smarthome-infrastructure.home.arpa:8123/api/states/sensor.office_hue_motion_temperature HTTP/1.1 2 | Content-Type: application/json 3 | Authorization: Bearer xxxinsertherexxx 4 | 5 | GET http://smarthome-infrastructure.home.arpa:8123/api/states/sensor.thermostat_office_actual_temperature HTTP/1.1 6 | Content-Type: application/json 7 | Authorization: Bearer xxx 8 | -------------------------------------------------------------------------------- /FanControl.HomeAssistant/FanControl.HomeAssistant.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0;net48;net7.0;net8.0 5 | 6 | 7 | 8 | 9 | ..\lib\FanControl.Plugins.dll 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | github: [hgross] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 3 | patreon: # Replace with a single Patreon username 4 | open_collective: # Replace with a single Open Collective username 5 | ko_fi: # Replace with a single Ko-fi username 6 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 7 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 8 | liberapay: # Replace with a single Liberapay username 9 | issuehunt: # Replace with a single IssueHunt username 10 | otechie: # Replace with a single Otechie username 11 | custom: ["https://paypal.me/GrossH", "https://www.buymeacoffee.com/HenningGross"] # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 12 | -------------------------------------------------------------------------------- /.github/workflows/pipeline.yml: -------------------------------------------------------------------------------- 1 | name: dotnet package 2 | 3 | permissions: 4 | contents: write 5 | 6 | on: 7 | push: 8 | tags: 9 | - "v*.*.*" 10 | 11 | jobs: 12 | build: 13 | 14 | runs-on: windows-latest 15 | strategy: 16 | matrix: 17 | dotnet-version: [ '6.0.x' ] 18 | 19 | steps: 20 | - uses: actions/checkout@v3 21 | - name: Setup .NET Core SDK ${{ matrix.dotnet-version }} 22 | uses: actions/setup-dotnet@v3 23 | with: 24 | dotnet-version: ${{ matrix.dotnet-version }} 25 | - name: Download FC dependencies 26 | shell: pwsh 27 | run: | 28 | .\update-fancontrol-api.ps1 29 | - name: Build 30 | shell: pwsh 31 | run: | 32 | .\build.ps1 33 | - name: Release 34 | uses: softprops/action-gh-release@v1 35 | if: startsWith(github.ref, 'refs/tags/') 36 | with: 37 | files: out/release_package/*.zip -------------------------------------------------------------------------------- /DEVELOPMENT.md: -------------------------------------------------------------------------------- 1 | 2 | # Development 3 | ## Build, package, run 4 | From within the root directory in a PowerShell 5 | ```powershell 6 | # Download dependencies (once) 7 | .\update-fancontrol-api.ps1 8 | 9 | # Clean & Build everything (per dev cycle) 10 | .\build.ps1 11 | 12 | # Start pre-packaged FanControl instance. 13 | .\out\FanControlDevBuild\FanControl.exe 14 | ``` 15 | 16 | Pick up the release build from the `out/FanControl.HomeAssistant` directory and place it within your FanControl installation's plugin folder (`FanControl/Plugins`). 17 | Alternatively the build script builds a readily usable portable FanControl configuration within the `out/FanControlDevBuild` directory (for dev purposes). 18 | 19 | ## Tools 20 | - `update-fancontrol-api.ps1` downloads the latest FanControl master to extract the FanControl Plugins API dll to `.\lib` 21 | - `build.ps1`cleans, builds, packages the plugin and a pre-configured FanControl instance ready to be started. Check the `out` directory 22 | -------------------------------------------------------------------------------- /update-fancontrol-api.ps1: -------------------------------------------------------------------------------- 1 | # Source, Dest, Unpack folder 2 | $url = "https://github.com/Rem0o/FanControl.Releases/raw/master/FanControl.zip" 3 | $dl_zip_dest = ".\download\FanControl.zip" 4 | $dl_folder = ".\download" 5 | $temp_unzip_folder = ".\temp\FanControl" 6 | $dll_temp_path = ".\temp\FanControl\FanControl.Plugins.dll" 7 | $lib_folder = ".\lib" 8 | $fc_folder = ".\devbuild\FanControl" 9 | 10 | # ensure folders 11 | New-Item -Force -Path $lib_folder -ItemType Directory 12 | New-Item -Force -Path $dl_folder -ItemType Directory 13 | New-Item -Force -Path $temp_unzip_folder -ItemType Directory 14 | New-Item -Force -Path $fc_folder -ItemType Directory 15 | 16 | # Download file 17 | Invoke-WebRequest -Uri $url -OutFile $dl_zip_dest 18 | 19 | # unzip fancontrol to temp dir 20 | Expand-Archive -Path $dl_zip_dest -DestinationPath $temp_unzip_folder 21 | 22 | # unzip FanControl to devbuild dir 23 | Expand-Archive -Path $dl_zip_dest -DestinationPath $fc_folder 24 | 25 | # copy dll to lib folder 26 | Copy-Item $dll_temp_path -Destination $lib_folder 27 | 28 | # cleanup 29 | Remove-Item $dl_folder -Recurse 30 | Remove-Item $temp_unzip_folder -Recurse 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Fan Control License Agreement 2 | 3 | © [2023] [Henning Groß]. All rights reserved. 4 | 5 | This software is provided by [Henning Groß] ("the Licensor") for personal and non-commercial use only. You may download, install, and use this software on any device that you own or control. 6 | 7 | You may not modify, reverse engineer, decompile, disassemble, or otherwise attempt to discover the source code of this software. You may not distribute, sublicense, rent, lease, lend or transfer this software or any part thereof to any third party without the prior consent of the Licensor. 8 | 9 | You acknowledge and agree that this software is the proprietary and confidential property of the Licensor and that all intellectual property rights in this software belong to the Licensor. You agree to respect and protect these rights and not to infringe or violate them in any way. 10 | 11 | The Licensor makes no warranties or representations of any kind with respect to this software. This software is provided "as is" and "as available" without any express or implied warranty of any kind. The Licensor disclaims all liability for any damages or losses arising from your use of or inability to use this software. 12 | 13 | By downloading, installing, or using this software, you agree to be bound by the terms and conditions of this license. -------------------------------------------------------------------------------- /FanControl.HomeAssistant.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.0.31903.59 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FanControl.HomeAssistant", "FanControl.HomeAssistant\FanControl.HomeAssistant.csproj", "{5E6DB1EB-FD71-4833-991A-8B94A61A19FD}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(SolutionProperties) = preSolution 14 | HideSolutionNode = FALSE 15 | EndGlobalSection 16 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 17 | {5E6DB1EB-FD71-4833-991A-8B94A61A19FD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 18 | {5E6DB1EB-FD71-4833-991A-8B94A61A19FD}.Debug|Any CPU.Build.0 = Debug|Any CPU 19 | {5E6DB1EB-FD71-4833-991A-8B94A61A19FD}.Release|Any CPU.ActiveCfg = Release|Any CPU 20 | {5E6DB1EB-FD71-4833-991A-8B94A61A19FD}.Release|Any CPU.Build.0 = Release|Any CPU 21 | {76606ECE-D556-40D5-88F4-6D37CD419266}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 22 | {76606ECE-D556-40D5-88F4-6D37CD419266}.Debug|Any CPU.Build.0 = Debug|Any CPU 23 | {76606ECE-D556-40D5-88F4-6D37CD419266}.Release|Any CPU.ActiveCfg = Release|Any CPU 24 | {76606ECE-D556-40D5-88F4-6D37CD419266}.Release|Any CPU.Build.0 = Release|Any CPU 25 | EndGlobalSection 26 | EndGlobal 27 | -------------------------------------------------------------------------------- /FanControl.HomeAssistant/HomeAssistantConfig.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace FanControl.HomeAssistant 4 | { 5 | public class HomeAssistantConfig { 6 | /// 7 | /// The auth token used to retrieve sensor data. 8 | /// Create one from the HomeAssistant UI https://www.home-assistant.io/docs/authentication/#your-account-profile 9 | /// 10 | /// 11 | public string HomeAssistantAuthToken {get; set;} 12 | 13 | /// 14 | /// The Home Assistant URL of your installation including protocol and port. 15 | /// Example: http://smarthome-infrastructure.home.arpa:8123 16 | /// 17 | /// 18 | public string HomeAssistantURL {get; set;} 19 | 20 | /// 21 | /// List of sensors you want to periodically retrieve. 22 | /// 23 | /// 24 | public List sensors {get; set;} = new List(); 25 | } 26 | 27 | public class HomeAssistantSensorConfig 28 | { 29 | /// 30 | /// Used when the configured interval is not plausible. 31 | /// 32 | public static int POLLING_INTERVAL_DEFAULT = 30; 33 | /// 34 | /// The default fallback temperature a sensor uses before it has been retrieved state data once. 35 | /// 36 | public static float DEFAULT_FALLBACK_VALUE = 20.0f; 37 | 38 | /// 39 | /// The home assistant entity id to resolve. Must be a tempearture sensor. 40 | /// Example: sensor.office_hue_motion_temperature 41 | /// 42 | /// 43 | public string EntityId { get; set;} 44 | 45 | /// 46 | /// The interval in seconds to poll data from HomeAssistant. 47 | /// 48 | /// 49 | public int PollingInterval {get; set;} = POLLING_INTERVAL_DEFAULT; 50 | 51 | /// 52 | /// An initial value that is used to initialize the sensor. 53 | /// Will be overridden on first successful retrieval from HomeAssistant. 54 | /// Note that this value is used when home assistant is down or can't be reached initially. 55 | /// 56 | /// 57 | public float InitialFallbackValue {get; set;} = DEFAULT_FALLBACK_VALUE; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [!["Buy Me A Coffee"](https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png)](https://www.buymeacoffee.com/HenningGross) 2 | 3 | # FanControl.HomeAssistant 4 | [![dotnet package](https://github.com/hgross/FanControl.HomeAssistant/actions/workflows/pipeline.yml/badge.svg)](https://github.com/hgross/FanControl.HomeAssistant/actions/workflows/pipeline.yml) 5 | ![GitHub all releases](https://img.shields.io/github/downloads/hgross/FanControl.HomeAssistant/total) 6 | 7 | [FanControl](https://github.com/Rem0o/FanControl.Releases)-Plugin for [HomeAssistant](https://www.home-assistant.io/) sensors. 8 | 9 | - Integrates HomeAssistant temperature sensors into FanControl 10 | - HomeAssistant for example provides Philips Hue Ambient temperature sensors or HomeMatic thermostat data as temperature sensors 11 | - Initial use case: Implemented to be used as an ambient temperature sensor for a custom loop. 12 | 13 | 14 |

15 | 16 | 17 | 18 | 19 |

20 | 21 |

22 | 23 | 24 |

25 | 26 | ## Installation 27 | Download the latest release and drop the `.dll`- files into FanControl's `Plugins` folder. Make sure to have the latest [FanControl](https://github.com/Rem0o/FanControl.Releases) release installed. 28 |

29 | 30 | 31 | 32 |

33 | 34 | 35 | ## Configuration 36 | The Plugin Expects a file `FanControl.HomeAssistant.json` within FanControl's `Configurations` directory (next to your default `userConfig.json`). 37 | If this file is not found, a non-working default configuration file will be created. Check the error log (accessible from the FanControl menu). 38 | The default configuration file will look like this 39 | ```json 40 | { 41 | "HomeAssistantAuthToken": "your_long_lived_access_token_created_from_home_assistants_user_configuration", 42 | "HomeAssistantURL": "http://example.com:8123", 43 | "sensors": [ 44 | { 45 | "EntityId": "sensors.your_temp_sensor_entity_id", 46 | "PollingInterval": 10, 47 | "InitialFallbackValue": 10.0 48 | } 49 | ] 50 | } 51 | ``` 52 | 53 | After each configuration change, you have to restart FanControl for your changes to have any effect. 54 | 55 | |Config entry|Description| 56 | |-|-| 57 | |`HomeAssistantAuthToken` (mandatory)|The auth token used to retrieve sensor data. Create one from the [HomeAssistant UI](https://www.home-assistant.io/docs/authentication/#your-account-profile)| 58 | |`HomeAssistantURL` (mandatory)|The Home Assistant URL of your installation including protocol and port.| 59 | |`sensors`|A list of home assistant (temperature) sensors that you want to have accessible within FanControl. Check how to define a sensors below.| 60 | 61 | Sensors can be configured with these attribtues 62 | 63 | |Sensor Config entry|Description|Default| 64 | |-|-|-| 65 | |`EntityId` (mandatory)|The home assistant entity id to resolve. Must be a tempearture sensor.|-| 66 | |`PollingInterval` (optional)|The interval in seconds to poll data from HomeAssistant.|30| 67 | |`InitialFallbackValue` (optional)|An initial value that is used to initialize the sensor. Will be overridden on first successful retrieval from HomeAssistant. Note that this value is used when home assistant is down or can't be reached initially.|20| 68 | 69 | 70 | ## Troubleshooting 71 | 72 | |Error|Solution|| 73 | |-|-|-| 74 | |`Failed to load plugin FanControl.HomeAssistant.dll. Make sure the plugin's dll is unblocked in its properties.`

| | Right click all DLL's in FanControl's `Plugins`-directory and allow access by checking the box visible in the screenshot to the left | 75 | |My sensor is not updating.| | Make sure you have configured the Auth Token and sensors in the `FanControl.HomeAssistant.json` file within FanControl's `Configurations` directory according to the documentation above. Check FanControl's error log for hints. | 76 | |I edited the `FanControl.HomeAssistat.json` without effect. ||After each change, you have to restart FanControl for your changes to have any effect.| 77 | 78 | 79 | 80 | ## Development 81 | See [development docs](./DEVELOPMENT.md) -------------------------------------------------------------------------------- /FanControl.HomeAssistant/HomeAssistantPlugin.cs: -------------------------------------------------------------------------------- 1 | using FanControl.Plugins; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using Newtonsoft.Json; 5 | using System; 6 | 7 | namespace FanControl.HomeAssistant 8 | { 9 | /// 10 | /// FanControl Plugin API implementation for HomeAssistant temperature sensors. 11 | /// 12 | public class HomeAssistantPlugin : IPlugin2 13 | { 14 | public static readonly string LOG_PREFIX = "[FanControl.HomeAssistant] "; 15 | public string Name => "HomeAssistant"; 16 | private readonly IPluginLogger _logger; 17 | private readonly IPluginDialog _dialog; 18 | private HomeAssistantConfig _hassConfig; 19 | private readonly List _sensors; 20 | 21 | 22 | public HomeAssistantPlugin(IPluginLogger logger, IPluginDialog dialog) 23 | { 24 | _logger = logger; 25 | _dialog = dialog; 26 | _sensors = new List(); 27 | } 28 | 29 | public void Close() 30 | { 31 | // nothing to do 32 | } 33 | 34 | public void Initialize() 35 | { 36 | string cwd_path = Directory.GetCurrentDirectory(); 37 | var config_directory_path = Path.Combine(cwd_path, "Configurations"); 38 | var config_json_path = Path.Combine(config_directory_path, "FanControl.HomeAssistant.json"); 39 | 40 | // check existance 41 | if(!File.Exists(config_json_path)) { 42 | // create default config and write to file, then print error 43 | var default_config = new HomeAssistantConfig(); 44 | default_config.HomeAssistantURL = "http://example.com:8123"; 45 | default_config.HomeAssistantAuthToken = "your_long_lived_access_token_created_from_home_assistants_user_configuration"; 46 | default_config.sensors.Add(new HomeAssistantSensorConfig{ EntityId="sensors.your_temp_sensor_entity_id", InitialFallbackValue=10.0f, PollingInterval=10 }); 47 | var serialized_default_config = JsonConvert.SerializeObject(default_config); 48 | File.WriteAllText(config_json_path, serialized_default_config); 49 | 50 | // log & show dialog 51 | var error_message = $"Error: Could not find a config file for the FanControl.HomeAssistant plugin at {config_directory_path}. Creating {config_json_path}. Please edit it to your needs and restart FanControl."; 52 | Log(error_message); 53 | _dialog.ShowMessageDialog(error_message); 54 | } 55 | 56 | // Read config 57 | try 58 | { 59 | string hassConfigRaw = File.ReadAllText(config_json_path); 60 | var hassConfig = JsonConvert.DeserializeObject(hassConfigRaw); 61 | _hassConfig = hassConfig; 62 | 63 | // TODO: some additional validation - nice to have. 64 | } 65 | catch (System.Exception e) 66 | { 67 | var error_message = $"Error: Could not parse configuration for the FanControl.HomeAssistant plugin at {config_json_path}. Please edit it to be valid and restart FanControl. You can delete {config_json_path} and restart FanControl to get back the default config."; 68 | Log(error_message + Environment.NewLine + "Not loading any FanControl.HomeAssistant sensors." + Environment.NewLine + $"Parsing exception hint: {e.Message}" + Environment.NewLine + e.StackTrace); 69 | _dialog.ShowMessageDialog(error_message); 70 | } 71 | } 72 | 73 | public void Load(IPluginSensorsContainer container) 74 | { 75 | // Create plugin sensors from config 76 | foreach (var hassSensorConfig in _hassConfig.sensors) 77 | { 78 | _sensors.Add(new HomeAssistantSensor(hassSensorConfig, _hassConfig, _logger)); 79 | } 80 | 81 | // hand over sensor instances to FanControl 82 | foreach (var sensor in this._sensors) 83 | { 84 | container.TempSensors.Add(sensor); 85 | } 86 | } 87 | 88 | // Called every 1Hz to update all sensors 89 | public void Update() 90 | { 91 | // unused - implemented in the individual sensor instances 92 | } 93 | 94 | /// 95 | /// Logs exceptions to FanControl's log file. 96 | /// Sucks up any exceptions that might occur during logging, since FanControl V176+ does not handle multiple log messages in one cycle very well (file locks). 97 | /// 98 | /// The log message 99 | private void Log(string msg) 100 | { 101 | try 102 | { 103 | _logger.Log(LOG_PREFIX + msg); 104 | } 105 | catch (System.Exception) 106 | { 107 | // ignore, FanControl V176+ does not handle multiple log messages in one cycle very well (file locks). 108 | } 109 | } 110 | } 111 | 112 | } 113 | -------------------------------------------------------------------------------- /FanControl.HomeAssistant/HomeAssistantSensor.cs: -------------------------------------------------------------------------------- 1 | using FanControl.Plugins; 2 | using Newtonsoft.Json; 3 | using System.Net.Http; 4 | 5 | namespace FanControl.HomeAssistant 6 | { 7 | /// 8 | /// HASS implementation of a sensor. 9 | /// Periodically polls the state of a sensor entity from Home Assistant. 10 | /// 11 | public class HomeAssistantSensor : IPluginSensor 12 | { 13 | private HomeAssistantConfig _hassConfig; 14 | private HomeAssistantSensorConfig _hassSensorConfig; 15 | private IPluginLogger _logger; 16 | private long _cycle = 0; 17 | private HttpClient _httpClient; 18 | 19 | internal HomeAssistantSensor(HomeAssistantSensorConfig hassSensorConfig, HomeAssistantConfig hassConfig, IPluginLogger logger) 20 | { 21 | _hassConfig = hassConfig; 22 | _hassSensorConfig = hassSensorConfig; 23 | _logger = logger; 24 | 25 | Id = _hassSensorConfig.EntityId; 26 | Name = _hassSensorConfig.EntityId; ; // temporary, will be overwritten on first update 27 | Value = _hassSensorConfig.InitialFallbackValue; // initial value to avoid NaN 28 | 29 | if (_hassSensorConfig.PollingInterval < 1) 30 | { 31 | Log($"Error: Configured polling interval of {_hassSensorConfig.PollingInterval} seconds for sensor {_hassSensorConfig.EntityId} is invalid. Falling back to default value of {HomeAssistantSensorConfig.POLLING_INTERVAL_DEFAULT}."); 32 | _hassSensorConfig.PollingInterval = HomeAssistantSensorConfig.POLLING_INTERVAL_DEFAULT; 33 | } 34 | 35 | _httpClient = new HttpClient(); 36 | _httpClient.DefaultRequestHeaders.Add("Authorization", "Bearer " + _hassConfig.HomeAssistantAuthToken); 37 | _httpClient.BaseAddress = new System.Uri(_hassConfig.HomeAssistantURL); 38 | } 39 | 40 | public string Name { get; } 41 | 42 | public float? Value { get; internal set; } 43 | 44 | public string Id { get; } 45 | 46 | // https://developers.home-assistant.io/docs/api/rest 47 | public void Update() 48 | { 49 | // called in 1 hz cycles by FC 50 | _cycle += 1; 51 | bool is_first_cycle = _cycle == 1; 52 | 53 | // only update every pollingIntevall-th cycle 54 | if (!is_first_cycle && (_cycle % _hassSensorConfig.PollingInterval != 0)) 55 | { 56 | return; 57 | } 58 | 59 | _poll_data(); 60 | } 61 | 62 | /// 63 | /// HomeAssistant REST API Response structure 64 | /// 65 | private class HassEntityAttributes 66 | { 67 | public string state_class { get; set; } 68 | public string unit_of_measurement { get; set; } 69 | public string device_class { get; set; } 70 | public string friendly_name { get; set; } 71 | } 72 | 73 | /// 74 | /// HomeAssistant REST API Response structure 75 | /// 76 | private class HassTemperatureSensorResponse 77 | { 78 | public float state { get; set; } 79 | public string entity_id { get; set; } 80 | public string last_changed { get; set; } 81 | public string last_updated { get; set; } 82 | public HassEntityAttributes attributes { get; set; } 83 | public override string ToString() 84 | { 85 | return $"SensorData for {this.attributes.friendly_name} ({this.entity_id}): {this.state} {this.attributes.unit_of_measurement}"; 86 | } 87 | } 88 | 89 | /// 90 | /// Asynchronously fetches the state of the entity id from the HomeAssistant REST API. 91 | /// 92 | /// 93 | private async void _poll_data() 94 | { 95 | try 96 | { 97 | // Expected format is this 98 | /* HTTP/1.1 200 OK 99 | Content-Type: application/json 100 | Content-Length: 265 101 | Content-Encoding: deflate 102 | Date: Thu, 06 Apr 2023 20:35:28 GMT 103 | Server: Python/3.10 aiohttp/3.8.4 104 | Connection: close 105 | 106 | { 107 | "entity_id": "sensor.office_hue_motion_temperature", 108 | "state": "21.2", 109 | "attributes": { 110 | "state_class": "measurement", 111 | "unit_of_measurement": "°C", 112 | "device_class": "temperature", 113 | "friendly_name": "Sensor Office Temperature" 114 | }, 115 | "last_changed": "2023-04-06T20:27:27.542739+00:00", 116 | "last_updated": "2023-04-06T20:27:27.542739+00:00", 117 | "context": { 118 | "id": "01GXC41CDPMV245AW4X5ADKF4X", 119 | "parent_id": null, 120 | "user_id": null 121 | } 122 | } 123 | */ 124 | 125 | HttpResponseMessage response = await _httpClient.GetAsync($"api/states/{Id}"); 126 | var resp_code = (int)response.StatusCode; 127 | 128 | // Handle response based on code. 129 | if (resp_code == 200) 130 | { 131 | string rawResponse = await response.Content.ReadAsStringAsync(); 132 | HassTemperatureSensorResponse sensorData = JsonConvert.DeserializeObject(rawResponse); 133 | Value = sensorData.state; 134 | } 135 | else 136 | { 137 | // todo: maybe some error logic for 404s / invalid configs. Nice to have. 138 | Log($"Error retrieving state for sensor {Id} with status code {resp_code}"); 139 | } 140 | } 141 | catch (System.Exception e) 142 | { 143 | try 144 | { 145 | Log($"Error polling state of {Id} -> {e.Message}" + System.Environment.NewLine + e.ToString()); 146 | } 147 | catch (System.Exception) 148 | { 149 | // ignore, FanControl V176+ does not handle multiple log messages in one cycle very well (file locks). 150 | } 151 | } 152 | 153 | } 154 | 155 | /// 156 | /// Logs exceptions to FanControl's log file. 157 | /// Sucks up any exceptions that might occur during logging, since FanControl V176+ does not handle multiple log messages in one cycle very well (file locks). 158 | /// 159 | /// The log message 160 | private void Log(string msg) 161 | { 162 | try 163 | { 164 | _logger.Log(HomeAssistantPlugin.LOG_PREFIX + msg); 165 | } 166 | catch (System.Exception) 167 | { 168 | // ignore, FanControl V176+ does not handle multiple log messages in one cycle very well (file locks). 169 | } 170 | } 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | temp/ 2 | download/ 3 | lib/ 4 | devbuild/ 5 | out/ 6 | 7 | ## Ignore Visual Studio temporary files, build results, and 8 | ## files generated by popular Visual Studio add-ons. 9 | ## 10 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 11 | 12 | # User-specific files 13 | *.rsuser 14 | *.suo 15 | *.user 16 | *.userosscache 17 | *.sln.docstates 18 | 19 | # User-specific files (MonoDevelop/Xamarin Studio) 20 | *.userprefs 21 | 22 | # Mono auto generated files 23 | mono_crash.* 24 | 25 | # Build results 26 | [Dd]ebug/ 27 | [Dd]ebugPublic/ 28 | [Rr]elease/ 29 | [Rr]eleases/ 30 | x64/ 31 | x86/ 32 | [Aa][Rr][Mm]/ 33 | [Aa][Rr][Mm]64/ 34 | bld/ 35 | [Bb]in/ 36 | [Oo]bj/ 37 | [Ll]og/ 38 | [Ll]ogs/ 39 | 40 | # Visual Studio 2015/2017 cache/options directory 41 | .vs/ 42 | # Uncomment if you have tasks that create the project's static files in wwwroot 43 | #wwwroot/ 44 | 45 | # Visual Studio 2017 auto generated files 46 | Generated\ Files/ 47 | 48 | # MSTest test Results 49 | [Tt]est[Rr]esult*/ 50 | [Bb]uild[Ll]og.* 51 | 52 | # NUnit 53 | *.VisualState.xml 54 | TestResult.xml 55 | nunit-*.xml 56 | 57 | # Build Results of an ATL Project 58 | [Dd]ebugPS/ 59 | [Rr]eleasePS/ 60 | dlldata.c 61 | 62 | # Benchmark Results 63 | BenchmarkDotNet.Artifacts/ 64 | 65 | # .NET Core 66 | project.lock.json 67 | project.fragment.lock.json 68 | artifacts/ 69 | 70 | # StyleCop 71 | StyleCopReport.xml 72 | 73 | # Files built by Visual Studio 74 | *_i.c 75 | *_p.c 76 | *_h.h 77 | *.ilk 78 | *.meta 79 | *.obj 80 | *.iobj 81 | *.pch 82 | *.pdb 83 | *.ipdb 84 | *.pgc 85 | *.pgd 86 | *.rsp 87 | *.sbr 88 | *.tlb 89 | *.tli 90 | *.tlh 91 | *.tmp 92 | *.tmp_proj 93 | *_wpftmp.csproj 94 | *.log 95 | *.vspscc 96 | *.vssscc 97 | .builds 98 | *.pidb 99 | *.svclog 100 | *.scc 101 | 102 | # Chutzpah Test files 103 | _Chutzpah* 104 | 105 | # Visual C++ cache files 106 | ipch/ 107 | *.aps 108 | *.ncb 109 | *.opendb 110 | *.opensdf 111 | *.sdf 112 | *.cachefile 113 | *.VC.db 114 | *.VC.VC.opendb 115 | 116 | # Visual Studio profiler 117 | *.psess 118 | *.vsp 119 | *.vspx 120 | *.sap 121 | 122 | # Visual Studio Trace Files 123 | *.e2e 124 | 125 | # TFS 2012 Local Workspace 126 | $tf/ 127 | 128 | # Guidance Automation Toolkit 129 | *.gpState 130 | 131 | # ReSharper is a .NET coding add-in 132 | _ReSharper*/ 133 | *.[Rr]e[Ss]harper 134 | *.DotSettings.user 135 | 136 | # TeamCity is a build add-in 137 | _TeamCity* 138 | 139 | # DotCover is a Code Coverage Tool 140 | *.dotCover 141 | 142 | # AxoCover is a Code Coverage Tool 143 | .axoCover/* 144 | !.axoCover/settings.json 145 | 146 | # Visual Studio code coverage results 147 | *.coverage 148 | *.coveragexml 149 | 150 | # NCrunch 151 | _NCrunch_* 152 | .*crunch*.local.xml 153 | nCrunchTemp_* 154 | 155 | # MightyMoose 156 | *.mm.* 157 | AutoTest.Net/ 158 | 159 | # Web workbench (sass) 160 | .sass-cache/ 161 | 162 | # Installshield output folder 163 | [Ee]xpress/ 164 | 165 | # DocProject is a documentation generator add-in 166 | DocProject/buildhelp/ 167 | DocProject/Help/*.HxT 168 | DocProject/Help/*.HxC 169 | DocProject/Help/*.hhc 170 | DocProject/Help/*.hhk 171 | DocProject/Help/*.hhp 172 | DocProject/Help/Html2 173 | DocProject/Help/html 174 | 175 | # Click-Once directory 176 | publish/ 177 | 178 | # Publish Web Output 179 | *.[Pp]ublish.xml 180 | *.azurePubxml 181 | # Note: Comment the next line if you want to checkin your web deploy settings, 182 | # but database connection strings (with potential passwords) will be unencrypted 183 | *.pubxml 184 | *.publishproj 185 | 186 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 187 | # checkin your Azure Web App publish settings, but sensitive information contained 188 | # in these scripts will be unencrypted 189 | PublishScripts/ 190 | 191 | # NuGet Packages 192 | *.nupkg 193 | # NuGet Symbol Packages 194 | *.snupkg 195 | # The packages folder can be ignored because of Package Restore 196 | **/[Pp]ackages/* 197 | # except build/, which is used as an MSBuild target. 198 | !**/[Pp]ackages/build/ 199 | # Uncomment if necessary however generally it will be regenerated when needed 200 | #!**/[Pp]ackages/repositories.config 201 | # NuGet v3's project.json files produces more ignorable files 202 | *.nuget.props 203 | *.nuget.targets 204 | 205 | # Microsoft Azure Build Output 206 | csx/ 207 | *.build.csdef 208 | 209 | # Microsoft Azure Emulator 210 | ecf/ 211 | rcf/ 212 | 213 | # Windows Store app package directories and files 214 | AppPackages/ 215 | BundleArtifacts/ 216 | Package.StoreAssociation.xml 217 | _pkginfo.txt 218 | *.appx 219 | *.appxbundle 220 | *.appxupload 221 | 222 | # Visual Studio cache files 223 | # files ending in .cache can be ignored 224 | *.[Cc]ache 225 | # but keep track of directories ending in .cache 226 | !?*.[Cc]ache/ 227 | 228 | # Others 229 | ClientBin/ 230 | ~$* 231 | *~ 232 | *.dbmdl 233 | *.dbproj.schemaview 234 | *.jfm 235 | *.pfx 236 | *.publishsettings 237 | orleans.codegen.cs 238 | 239 | # Including strong name files can present a security risk 240 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 241 | #*.snk 242 | 243 | # Since there are multiple workflows, uncomment next line to ignore bower_components 244 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 245 | #bower_components/ 246 | 247 | # RIA/Silverlight projects 248 | Generated_Code/ 249 | 250 | # Backup & report files from converting an old project file 251 | # to a newer Visual Studio version. Backup files are not needed, 252 | # because we have git ;-) 253 | _UpgradeReport_Files/ 254 | Backup*/ 255 | UpgradeLog*.XML 256 | UpgradeLog*.htm 257 | ServiceFabricBackup/ 258 | *.rptproj.bak 259 | 260 | # SQL Server files 261 | *.mdf 262 | *.ldf 263 | *.ndf 264 | 265 | # Business Intelligence projects 266 | *.rdl.data 267 | *.bim.layout 268 | *.bim_*.settings 269 | *.rptproj.rsuser 270 | *- [Bb]ackup.rdl 271 | *- [Bb]ackup ([0-9]).rdl 272 | *- [Bb]ackup ([0-9][0-9]).rdl 273 | 274 | # Microsoft Fakes 275 | FakesAssemblies/ 276 | 277 | # GhostDoc plugin setting file 278 | *.GhostDoc.xml 279 | 280 | # Node.js Tools for Visual Studio 281 | .ntvs_analysis.dat 282 | node_modules/ 283 | 284 | # Visual Studio 6 build log 285 | *.plg 286 | 287 | # Visual Studio 6 workspace options file 288 | *.opt 289 | 290 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 291 | *.vbw 292 | 293 | # Visual Studio LightSwitch build output 294 | **/*.HTMLClient/GeneratedArtifacts 295 | **/*.DesktopClient/GeneratedArtifacts 296 | **/*.DesktopClient/ModelManifest.xml 297 | **/*.Server/GeneratedArtifacts 298 | **/*.Server/ModelManifest.xml 299 | _Pvt_Extensions 300 | 301 | # Paket dependency manager 302 | .paket/paket.exe 303 | paket-files/ 304 | 305 | # FAKE - F# Make 306 | .fake/ 307 | 308 | # CodeRush personal settings 309 | .cr/personal 310 | 311 | # Python Tools for Visual Studio (PTVS) 312 | __pycache__/ 313 | *.pyc 314 | 315 | # Cake - Uncomment if you are using it 316 | # tools/** 317 | # !tools/packages.config 318 | 319 | # Tabs Studio 320 | *.tss 321 | 322 | # Telerik's JustMock configuration file 323 | *.jmconfig 324 | 325 | # BizTalk build output 326 | *.btp.cs 327 | *.btm.cs 328 | *.odx.cs 329 | *.xsd.cs 330 | 331 | # OpenCover UI analysis results 332 | OpenCover/ 333 | 334 | # Azure Stream Analytics local run output 335 | ASALocalRun/ 336 | 337 | # MSBuild Binary and Structured Log 338 | *.binlog 339 | 340 | # NVidia Nsight GPU debugger configuration file 341 | *.nvuser 342 | 343 | # MFractors (Xamarin productivity tool) working folder 344 | .mfractor/ 345 | 346 | # Local History for Visual Studio 347 | .localhistory/ 348 | 349 | # BeatPulse healthcheck temp database 350 | healthchecksdb 351 | 352 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 353 | MigrationBackup/ 354 | 355 | # Ionide (cross platform F# VS Code tools) working folder 356 | .ionide/ 357 | --------------------------------------------------------------------------------