├── 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 | [](https://www.buymeacoffee.com/HenningGross)
2 |
3 | # FanControl.HomeAssistant
4 | [](https://github.com/hgross/FanControl.HomeAssistant/actions/workflows/pipeline.yml)
5 | 
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 |
--------------------------------------------------------------------------------