C# for Home Assistant

Hi everyone,

since my favorite programming language is C#, i wanted to write my automations in C# with all the tooling support like Visual Studio and fully integrated in Home Assistant.

This is why i created C# for Home Assistant.

C# for Home Assistant is an hass.io addon and framework that allows to easily write automations that run in your hass.io installation.

Key feature:

  • Automations run automatically when a entity used in the automation changes
  • Entities are generated as classes which allows to check their existence at compile time
  • Easy debugging of automations on your machine with your favorite tools
  • UI to view running automations, logs and exceptions

To try it out you can check the wiki: Home · anhaehne/hhnl.HomeAssistantNet Wiki (github.com)

Any feedback is appreciated!

Screenshots:


Example automation


Another example automation


UI overview


Single automation

17 Likes

Nice project! I love using C# automating my home. I am one of the maintainers for a similar project netdaemon. Hope it is ok to contact you here. Please let me know if you’re interested to work together in this space?

4 Likes

This looks awesome!!

As a .NET developer, I love you!

I’m really only just getting into automations after a long time testing various devices and am about to roll out a load of them in my house. This looks really useful to me.

Thanks!

What does this offer that the official integration NetDaemon doesn’t?

Netdaemon isn’t an official integration either.

As many are asking for the differences between NetDeamon and this project i tried to come up with a list. This is based on my limited knowledge of NetDeamon so please correct me if i’m wrong.

Automations run on entity change

Previously all my automations were done in Node Red and i eventually realized that i was constructing the same pattern over and over again:

when EntityA changes or EntityB changes EntityC changes
then EntityD = EntityA + EntityB + EntityC 

So basically you not only have to write your condition, but you also have to specify the automation to run when any of the entities changes. In C# for Home Assistant you only have to request the entities and the underlying framework will make sure the automation gets executed when any entity changes.

NetDeamon relies heavily on the Reactive pattern and have to setup the the triggers for each entity. This would result in the same pattern as in NodeRed.

You just develop an application

When writing automations in C# for Home Assistant there are no config files for automations. Everything is just code (except for the initial setup to make sure you dev environment can talk to Home Assistant).
When you press ‘Build & Deploy’ in the UI the very same application just gets deployed and run inside the Add-on container.

Generated entities as foundation

I actually only noticed that NetDeamon also supports generated entities, very late in my development progress. In C# for Home Assistant the generated entities are the main way to interact with Home Assistant. This allows to verify the existence of the entities at compile time. My most frequent mistake in Node Red was to mistype an entity id leading to an error at runtime.

Rooted in the .net ecosphere

This point is up to debate and only my point of view. NetDeamon is inspired by AppDeamon and for me some of the mechanisms/concepts don’t feel natural. C# for Home Assistant is build on top of Microsoft.Extensions.Hosting and relies heavily on DependencyInjection. This is just more the way i write .net applications nowadays.

In the end, if you are satisfied with NetDeamon there is really no reason to switch. I would describe C# for Home Assistant as a different paradigm on how to write automations with the same benefits of being part of the .net ecosystem.

@helto4real I was looking at NetDeamon from the start but wasn’t sure i could achieve my goals within that framework. I would love to work together if we can figure it out.
I like that you first instinct is to collaborate, great attitude :+1:.

1 Like

@Limeray thanks! It’s opens source and the choice, sharing ideas and code benefits all. I was exited to see some other solutions out there for the .NET community to use. I was actually considering the attribute approach too but found the Reactive a natural choice to start with and may add attributes later too. Good job and good luck. Now the users in the .Net space have two models and may they choose what is best fit for them. All winners.

1 Like

The idea seems really good.
Is there any plans to support installations that don’t support add-ons (like Docker)?

You should be able to run this in docker only even though it is not documented right now.

Just build it from the Dockerfile and set the environment variables HOME_ASSISTANT_API=http://<your-home-assistant-ip>:8123/ and SUPERVISOR_TOKEN=<long-lived-access-token>. Mount the /config path to access the source code and forward port 20777.

Once i set up the infrastructure to distribute prebuild docker images i will document the process in more detail.

1 Like

I love this idea, however the secret’s section of the documentation doesn’t explain where to find these secrets. It’s not building with a tremendous number of errors. The HASS IP is obvious, but the token and Supervisor IP are not. I’ve also assumed the secrets can be added via the UI, for those that don’t have access to the command line with-in HASS.

There are two types of secrets.

Secrets you use in you automations like passwords to access web apis and such. These can be set in the UI but have also to be set when developing locally. You won’t need command line access in HASS.

Also you have to configure the HomeAssistant endpoint to develop locally (this is done in UserSecrets; UserSecrets are a .net concept to store secrets during development). When you see a bunch of error, there has probably been a problem with your local config.

To create an access token, go to your Home Assistant profile, scroll all the way down to ‘Long-Lived Access Token’ and click ‘Create Token’. (I’ve added this to the documentation)

The supervisor IP is the same as your homeassistant IP. It is just a different port.

If this still won’t work i would recommend opening a github issue so we can discuss this issue further.

1 Like

I’ve just tried this steps and it didn’t seem to work.
I can see some logging:

netassistant     | dbug: Microsoft.AspNetCore.Http.Connections.Internal.HttpConnectionDispatcher[4]
netassistant     |       Establishing new connection.
netassistant     | dbug: Microsoft.AspNetCore.SignalR.HubConnectionHandler[5]
netassistant     |       OnConnectedAsync started.
netassistant     | dbug: Microsoft.AspNetCore.Http.Connections.Internal.Transports.WebSocketsTransport[1]
netassistant     |       Socket opened using Sub-Protocol: '(null)'.
netassistant     | dbug: Microsoft.AspNetCore.SignalR.Internal.DefaultHubProtocolResolver[2]
netassistant     |       Found protocol implementation for requested protocol: json.
netassistant     | dbug: Microsoft.AspNetCore.SignalR.HubConnectionContext[1]
netassistant     |       Completed connection handshake. Using HubProtocol 'json'.

Then I open the website, a access token popup is shown, I paste the same access token and then the spinner keeps spinning forever with message Not connected - Waiting for connection ...

I’ve tried to reproduce the scenario and it seem there is a target framework mismatch. Try going to the cs4h.csproj file and change the TargetFramework to net6.0

Andre,

This is terrific. I love having the typical OOP paradigms and the syntactical sugar I’ve grown to love in the .NET eco system. It works very well.

I’m thinking now about abstraction and how that might work within this framework. With the daemon solutions, abstraction is basically built in through configuration. I’m thinking of, for example, a motion lighting program that handles multiple rooms separately. Any examples of you have approached similar concepts? Maybe generics?

EDIT: Nope, generics don’t seem to be supported.

Can you elaborate? Like writing a single automation and reusing it with different entities?

Exactly like that, yes. My instinct was to go with generics, but that didn’t work. Wondering your thoughts, as you’re the author of the engine.

You could extract the logic into a method and call that from different automations.

/// <summary>
/// Handles the lighting in living bathroom.
/// </summary>
[Automation]
public Task LivingRoomLighting(Sensors.HueMotionSensor1LightLevel livingRoomLightLevelSensor, [NoTrack] Lights.Wohnzimmer livingRoomLight)
    => HandleLighting(livingRoomLightLevelSensor, livingRoomLight, 5.3);

/// <summary>
/// Handles the lighting in the bathroom.
/// </summary>
[Automation]
public Task BathRoomLighting(Sensors.HueMotionSensor2LightLevel bathRoomLightLevelSensor, [NoTrack] Lights.Badezimmer bathRoomLight)
    => HandleLighting(bathRoomLightLevelSensor, bathRoomLight, 4.5);

/// <summary>
/// Turns on the light if the ligth level is lower than the provided threshold.
/// </summary>
/// <param name="lightLevelSensor">The sensor to check the light level on.</param>
/// <param name="light">The light to turn on when the light level is low.</param>
/// <param name="minLightLevel">The minium light level.</param>
private async Task HandleLighting(Sensor lightLevelSensor, Light light, double minLightLevel)
{
    if(light.IsOff && lightLevelSensor.GetValue<float>() < minLightLevel)
    {
        await light.TurnOnAsync();
    }
}

Good ole brute force abstraction.

I would like to inquire, however, if you know why generics don’t work and if there is any plan to implement them? They would GREATLY simplify complexity.

Can you share some source code how you would like it to work?