Net2HassMqtt provides Home Assistant (HASS) integration to .NET applications via MQTT. It not a MQTT transport layer. It provides a Home Assistant centric fluent configuration interface to map your application's models to Home Assistant entities.
The devices and entities you configure automatically appear fully configured in Home Assistant (Home Assistant MQTT Discovery). Configure once, use twice & no YAML! :-)
You do not need to code any MQTT publish calls, value conversions, subscriptions, or connection management. It just all happens. Net2HassMqtt layers over MQTT so you do not have to. It is designed to let you work with the "what" (Home Assistant) not the "how" (MQTT).
Use by adding NuGet package NoeticTools.Net2HassMqtt to your project.
As a quick example, here is a Net2HassMqtt configration code fragment that connects a application's model
to Home Assistant.
// Create a device to hold the entities
var device = new DeviceBuilder().WithFriendlyName("Net2HassMqtt Quick Start Device 1")
.WithId("net2hassmqtt_quick_start_device_01");
// Map model property to an entity
device.HasBatteryChargingBinarySensor(config => config.OnModel(model)
.WithStatusProperty(nameof(QuickStartDemoModel.BatteryChaging))
.WithUnitOfMeasurement(BatteryChargingBinarySensorUoM.None)
.WithFriendlyName("Battery Charging Status")
.WithNodeId("battery_1_charging"));
// Build bridge
var bridge = new BridgeConfiguration().WithMqttOptions(mqttClientOptions)
.HasDevice(device)
.Build();
// Run
await bridge.StartAsync();
A device with entity then appears in Home Assistant as shown below:
Tip
After Net2HassMqtt adds a new device in Home Assistant, the first time you browse to its Device Info dialog it will probably only show one entity. To see all entities it is necessary to refresh the brower's page. This only happens first time viewed after the device is added.
To get started you will need an MQTT Broker and Home Assistant. Using MQTT with Home Assistant is common and there are many sites and videos to help with installing both. Have a look at:
Net2HassMqtt has been tested using the Home Assistant Mosquitto add-on connected to a stand-alone Eclipse Mosquitto broker.
MQTT Explorer is highly recommended. It is a simple GUI tool that can connect to an MQTT broker and browse what is on the server. It is great for debugging any MQTT broker connection issues or to confirm last value sent.
A walkthough creating a simple demonstration console application that creats device and entity in home assistant. This code can be found at QuickStartDemoApp Program.cs).
The application requires the NuGet packages:
- NoeticTools.Net2HassMqtt (the sample app in the repository uses project reference instead)
- CommunityToolkit.Mvvm
Then add a class that will be our application's entity model:
public class QuickStartDemoModel : ObservableObject
{
private bool _batteryCharging;
public bool BatteryCharging
{
get => _batteryCharging;
set
{
if (SetProperty(ref _batteryChaging, value))
{
Console.WriteLine($" Battery is charging: {BatteryChaging}");
}
}
}
}
This code creates a class that supports property changed notification.
Each time LightSwitch
changes state (different value) a property changed notification is sent.
This version of the code is verbose and is used to make the notifying state property LightSwitch
visible.
Tip
An easier approach of declaring LightSwitch
property is:
public partial class MyDemoModel : ObservableObject
{
[ObservableProperty] private bool _lightSwitch;
}
The console application's Program.cs
code looks like this:
internal class Program
{
private static async Task Main(string[] args)
{
Console.WriteLine("""
Net2HassMqtt Quick Start Demo"
Press:
'x' to exit
'1' to toggle the state property
""");
var appConfig = new ConfigurationBuilder().AddUserSecrets<Program>().Build();
// Create a sample application model
var model = new QuickStartDemoModel
{
BatteryCharging = true
};
// Configure a Home Assistant device
var device = new DeviceBuilder().WithFriendlyName("Net2HassMqtt Quick Start Device 1")
.WithId("net2hassmqtt_quick_start_device_01");
// Map model property to a Home Assistant entity
device.HasBatteryChargingBinarySensor(config => config.OnModel(model)
.WithStatusProperty(nameof(QuickStartDemoModel.BatteryChaging))
.WithFriendlyName("Battery Charging Status")
.WithNodeId("battery_1_charging"));
// Build the MQTT bridge
var mqttOptions = HassMqttClientFactory.CreateQuickStartOptions("net2hassmqtt_quick_start", appConfig);
var bridge = new BridgeConfiguration()
.WithMqttOptions(mqttOptions)
.HasDevice(device)
.Build();
// Run!
await bridge.StartAsync();
await Run(model);
await bridge.StopAsync();
Console.WriteLine("Finished");
}
private static async Task Run(QuickStartDemoModel model)
{
while (true)
{
await Task.Delay(100.Milliseconds());
if (Console.KeyAvailable)
{
var key = Console.ReadKey();
if (key.KeyChar == 'x')
{
break;
}
if (key.KeyChar == '1')
{
model.BatteryCharging = !model.BatteryCharging;
}
}
}
}
}
Breaking this down ...
Firstly a Home Assistant device is configured by the line:
var device = new DeviceBuilder().WithFriendlyName("Net2HassMqtt Quick Start Device 1")
.WithId("net2hassmqtt_quick_start_device_01");
This device is given the name Net2HassMqtt Quick Start Device 1
and device ID net2hassmqtt_quick_start_device_01
.
The device ID will be used as a prefix to all entity IDs (object_id
). The device ID must not change.
Then a property on the application's model is mapped to a Home Assistant entity:
device.HasBatteryChargingBinarySensor(config => config.OnModel(model)
.WithStatusProperty(nameof(QuickStartDemoModel.BatteryChaging))
.WithFriendlyName("Battery Charging Status")
.WithNodeId("battery_1_charging"));
The entity's node ID must not change. It is used with the device ID to build the entities unique ID. This entity's ID will be:
binary_sensor.net2hassmqtt_quick_start_device_01_attery_1_charging
.
The Net2HassMqtt bridge built with the lines:
var mqttOptions = HassMqttClientFactory.CreateQuickStartOptions("net2hassmqtt_quick_start", appConfig);
var bridge = new BridgeConfiguration()
.WithMqttOptions(mqttOptions)
.HasDevice(device)
.Build();
Important
The quick start MQTT options require the MQTT broker's address, username, and password secrets. The code obtains these from a store using the .NET project secrets in development tool.
To setup these secrets see Developer Secrets or run the application and read the exception message instructions.
The bridge starts/stops with the lines:
await bridge.StartAsync();
:
// your could be run application here
:
await bridge.StopAsync();
Run the console application and it will connect to the MQTT broker!
Tip
If it is unable to connect to the MQTT it will keep retrying every few seconds. Use MQTT Explorer to check the connection detials and if the MQTT broker is running. It is must faster doing this in MQTT Explorer's UI.
On connection it sends the new device and entity configuration to the MQTT broker and they will appear automatically in Home Assistant.
Tip
If viewing the Home Assistant Device Info dialog first time after the device is added to Home Assistant, remember to refresh the browser page.
Try these quick tests:
- Press '1' and you will see the light entity toggle state in Home Assistant.
- Press 'x' or close the console and the entity will immediately become "unavailable" in Home Assistant.
- Restart the application and the entity will immediately return to on-line operation.
- Shutdown the application, then delete the device in Home Assistant (yes really!). Now restart the application and the device reappears in Home Assistant.
Entities are central to Home Assistant they can have a status (value) and or a command action such as turn on or set level. An entity status can optionally have attributes which are like labelled sub values. Attributes provide additional information related to the status.
An entity has a domain and a device class. Each device class has a set of units of measurement. The are all modelled in Net2HassMqtt's fluid configuration framework.
Supported Home Assistant entity domains are:
- BinarySensor
- Button (not tested)
- Cover (open/close only, proportional covers not yet supported)
- Event
- Number
- Sensor
- Switch
- Valve (open/close only, proportional valves not yet supported)
For each domain there is a set of device classes like Duration
, Gas
, or AtmosphericPressure
.
Net2HassMqtt uses "entity type" names using the format <domain_class><domain>
like GasSensor
or DurationSensor
.
There are 100+ of these entity types like:
- GasSensor
- CabonMonoxideSensor
- DurationSensor
- GasValve
- WaterValve
- ...
Screenshot showing intellisense selection of these entity types:
Entity type have one or more units of measurement like kg, oz, meters, ft, or hours.
Screenshot showing intellisense selection of Measurement (UoM) when a SensorGas
entity is being configured:
For more information see:
Net2HassMqtt is based on MQTTnet which uses a MqttClientOptionsBuilder
class to configure the connection.
MqttClientOptionsBuilder
code examples can be founder here.
Net2HassMqtt includes a bundled quick start MQTTnet options that is known to work well on a local Eclipse Mosquitto broker.
Example code using the bundled MQTT options:
var appConfig = new ConfigurationBuilder().AddUserSecrets<Program>().Build();
var mqttOptions = HassMqttClientFactory.CreateQuickStartOptions("net2hassmqtt_my_app_client", appConfig);
var bridge = new BridgeConfiguration()
.WithMqttOptions(mqttOptions)
.HasDevice(device)
.Build();
Important
The quick start MQTT options require the MQTT broker's address, username, and password secrets. The code obtains these from a store using the .NET project secrets in development tool.
To setup these secrets see Developer Secrets or run the application and read the exception message instructions.
An entity model is an application supplied model that provides at least one of:
- An entity status like on/off or temperature.
- An entity command handler to handle and entity's commands like start/stop or set temperature.
Note
For entity status the model provides a property getter. This called a status property.
For entity command handling the model provides a method. This called a command method.
Each entity model must implement INotifyPropertyChanged
(or inherit from ObservableObject
).
An example model:
public class MyValveModel : ObservableObject
{
public bool IsOpen
{
get{ /* observable property implementation */ }
}
public void Operate(string command)
{
// code here to handle "open" or "close" command.
}
}
Tip
An easier approach of declaring IsOpen
property is:
public class MyValveModel : ObservableObject
{
private [ObservableProperty] bool _iIsOpen
:
}
A single model can provide status properties and/or command methods for multiple entities.
Net2HassMqtt will update and entity'a status on the MQTT broker (and hence Home Assistant):
- On connecting to the MQTT broker.
- When the application model sends a property changed notification.
Valid model property value .NET types:
Entity type | Device class | .NET type |
---|---|---|
Sensor | Duration | TimeSpan |
int double |
||
any other | ||
Humidifer | any | |
Number | ||
BinarySensor | bool | |
Switch | ||
Valve |
Model command methods are called when Home Assistant attempt to operate (like 'open', 'close', or 'turn off') one of the model'sentities.
For example, for a valve command method may look like:
public void Operate(string command)
{
if (command == "open")
{
// do stuff here to open the valve.
}
else if (command == "close")
{
// do stuff here to close the vavle.
}
}
The methods return value is not used. The method must have one argument. Valid command argument types and values are:
Entity type | Device class | Valid .NET arg |
---|---|---|
Sensor | Duration | TimeSpan |
any other | double string |
|
Humidifer | any | |
Number | ||
BinarySensor | bool true: set on false: set off |
|
string "ON": set on "OFF": set off |
||
Switch | bool true: close switch false: open switch | |
string "ON": close switch "OFF": open switch | ||
Valve | bool true: close valve false: open valve | |
string "close": close valve "open": open valve |
Event entities are intended to trigger an event in Home Assistant and subscribe to an event on the model.
For example, an event on model may look like:
public enum VolumeEventTypes
{
Mute,
LowerVolume,
RaiseVolume
}
public event EventHandler<HassEventArgs>? VolumeEvent;
// Example model method firing event
public void OnKeyPressed(char keyChar)
{
if (keyChar == '+')
{
EfEvent?.Invoke(this, HassEventArgs.From(VolumeEventTypes.RaiseVolume));
}
if (keyChar == '-')
{
EfEvent?.Invoke(this, HassEventArgs.From(VolumeEventTypes.LowerVolume));
}
if (keyChar == 'm')
{
EfEvent?.Invoke(this, HassEventArgs.From(VolumeEventTypes.Mute));
}
}
The Net2HassMqtt configuration will look something like:
device.HasEvent(config => config.OnModel(model)
.WithEvent(nameof(MyModel.VolumeEvent))
.WithEventTypes<VolumeEventTypes>()
.WithEventTypeToSendAfterEachEvent("Clear")
.WithFriendlyName("Volume control event")
.WithNodeId("volume_event"));
Home Assistant's Event entity keeps the last received event as a state. When a device disconnects it shows a state of "unavailable" (Correct) and when the device reconnects it shows the last received event state again (OK). The problem is that it retriggers the last received event each time the device reconnects and possibly when Home Assistant starts. In the above example this may cause the volume to change everytime the device connects.
The WithEventTypeToSendAfterEachEvent("Clear")
option, seen in the example, is a workaround to achieve desired event behaviour:
- Do not resend last received event on startup or on reconnecting.
- Treat every event as new. Allow the same event to be fired repeatedly. e.g: Multiple raise volume events.
This option causes Net2HassMqtt to, if necessary, add an event type (in the example "Clear") and publish this event type immediately after publishing any other event type. Hence Home Assistant's Event sees every event as a changed state. It will still resent the "Clear" event but that should be ignored.
The example defined used an enum event to define event types. Names as strings may also be used as shown below:
public event EventHandler<HassEventArgs>? VolumeEvent;
// Example model method firing event
public void OnKeyPressed(char keyChar)
{
if (keyChar == '+')
{
EfEvent?.Invoke(this, new HassEventArgs("increase"));
}
if (keyChar == '-')
{
EfEvent?.Invoke(this, new HassEventArgs("decrease"));
}
if (keyChar == 'm')
{
EfEvent?.Invoke(this, new HassEventArgs("mute"));
}
}
With the configuration:
device.HasEvent(config => config.OnModel(model)
.WithEvent(nameof(MyModel.VolumeEvent))
.WithEventTypes("increase", "decrease", "mute")
.WithEventTypeToSendAfterEachEvent("clear")
.WithFriendlyName("Volume control event")
.WithNodeId("volume_event"));
A Home Assistant device is a collection of entities. There can be any number of devices.
Devices are added by the HasDevice
method as shown in the code fragement below.
var device = new DeviceBuilder().WithFriendlyName("Net2HassMqtt Quick Start Device 1")
.WithId("net2hassmqtt_quick_start_device_01");
var config = new BridgeConfiguration(mqttClientOptions)
.HasDevice(deice)
:
The device's name appears in Home Assistant while its ID is used as the start of entity IDs. Keep the device ID unique for all devices on the MQTT client and also as the first part of entity IDs in Home Assistant.
Often the device is ID is something like random hexadecimal value string (e.g: 0x00198d1007eff345) but that can make debugging challenging.
Each entity is given a name. This is a user friendly name that is shown in the Home Assistant UI the entity's name. The name can be changed in Home Assistant and, after the entity is created, changing the name in Net2HassMqtt will have no effect.
Recommendations:
- Keep it reasonable unique. It does not need to be unique but is is helpful if it is. The name may be used when searching for the entity.
- The Home Assistant UI often has limited space for names. Short names are good. Try to keep names under 20 characters.
Important
Entity IDs must be unique and must not change between application restarts.
In Home Assistant entities are referenced by their entity ID (object_Id
).
Examples are:
switch.garage_door_opener_open_switch
sensor.garage_door_opener_temperature
sensor.lounge_env_sensor_temperature
sensor.lounge_env_sensor_humidity
The portion to the left of the '.' is the domain. What is to the right of the '.' must be unique within its domain (sensor, switch, etc).
Net2HassMqtt builds the entity ID using the domain, device ID and an enity's node ID. You provide the device ID when configuring a device and the entity node ID when you declare an entity.
The entity ID format is:
<domain>.<device ID>_<entity node ID>
For example, say we configuring (mapping) application models to entities with the naming structure:
Device - "Demo Home Environment Sensor"
- Entity Model
- Has a sensor entity: "Lounge Temperature"
- Has a sensor entity: "Lounge Humidity"
The IDs provided during configuration may look like:
"my_lounge_sensor"
-
- sensed state: "lounge_temperature"
- sensed state: "lounge_humidity"
Which would result in the entity IDs:
sensor.my_lounge_sensor_lounge_temperature
sensor.my_lounge_sensor_lounge_humidity
When the entities first appears in Home Assistant they will have these entity IDs but they can be edited in Home Assistant.
The entity IDs will remain fixed on the MQTT broker (as object_id
) and Home Assistant will map this ID to the configured entity ID.
Note
Net2HassMqtt requires device ID, entity node IDs, and names. The two often look similar, but names may change while the entity ID must not change.
An entity can have any number of attributes. Net2HassMqtt will send attributes to Home Assistant as part of an entity's status update. Attbitute value changes do not trigger a status updates.
Any number of Home Assistant attributes can be added to an entity. The are like labelled values.
An example of adding attributes to an entity:
device.HasBinarySensor(config => config.OnModel(irrigationProgram)
:
.WithDurationAttribute(nameof(prog.Duration), "Duration (Minutes)", DurationSensorUoM.Minutes)
.WithAttribute(nameof(prog.ZoneIds), "Zone IDs"))
Attribute values are read from attribute properties just like state properties. The attribute properties must be on the same model as the entity's status property.
An attribute property type may also be a list of values like:
IReadOnlyList<int> ZoneIds { get; }
The Home Assistant device UI lists entities is the categories:
- State
- Sensor
- Switch
- ...
- Diagnostics
- Configuration
By default an entity is in the State group. An entity can be assigned to a category by using the InCategory
method.
For example:
device.HasBinarySensor(config => config.OnModel(irrigationProgram)
:
.InCategory(EntityCategory.Configuration)
By default Home assisstant provides an entity icon depending on the entity's device class.
A specific icon can set by using the WithIcon
method.
For example:
:
device.HasBinarySensor(config => config.OnModel(irrigationValve)
:
.WithIcon("mdi:valve")
:
The icon name must have a mdi:
prefex. The name is a Material Design Icon name that can be found here.
Important
Many lists of Material Design Icons include aliases. An alias name will not work in Home Assistant.
For example mdi:done
is an alias for mdi:check
. mdi:done
will not work.
The project uses the GitHub Flow branching strategy.
The project uses Semmantic Versioning where a release is defined as a public release.
For more information on this project's versioning see: Versioning.
The a few .NET projects that providing Home Assistant via MQTT. These are generally MQTT transport layers. Most help with Home Asssistant MQTT discovery and provide C# entity models.
They include:
- MBW.HassMQTT - Has a wide range of C# entity models.
- SimpleHA
- Sholo.HomeAssistant
- smarthome.net
This project uses MQTTnet for its MQTT transport layer.
Net2HassMqtt
uses the MIT license.
This project uses the following tools and libraries. Many thanks to those who created and manage them.
- CommunityToolkit.Mvvm
- FluentDateTime
- GitVersion
- MQTTnet
- Scriban
- Serilog
- Brain icons created by Freepik - Flaticon
Special thanks to the Home Assistant team for the excellent Home Assistant MQTT Discovery feature. Great example of a feature that lets magic happen.