OpenNetty aims at providing an advanced solution for implementing OpenWebNet support in .NET 8.0+ applications.
OpenWebNet is a protocol developed by BTicino and Legrand around 2000 to manage electrical networks. While it uses a very basic wire format initially designed to be usable over PSTN phone lines, the OpenWebNet protocol is actually fairly complex to implement properly but also quite powerful.
To this date, 3 variants of OpenWebNet have been developed by the two companies:
- OpenWebNet, used to integrate with the SCS-based "MyHome" products.
- OpenWebNet/Nitoo, used to integrate with "In One by Legrand" products (powerline and radio).
- OpenWebNet/Zigbee, used to integrate with the Zigbee-based "MyHome Play" products.
Note
OpenNetty is currently the only library that supports the 3 OpenWebNet variants.
OpenNetty offers both low-level primitives to represent OpenWebNet messages and communicate with OpenWebNet gateways and a higher-level MQTT integration that can be directly used with home automation software like Home Assistant, Jeedom or FHEM.
Important
An OpenWebNet gateway is required by OpenNetty to be able to interact with BTicino and Legrand devices:
- For In One by Legrand devices, a Legrand 88213 powerline/USB gateway is required. To communicate with In One by Legrand radio devices, a Legrand 03606 interface must also be present in the installation.
- For MyHome/MyHome Up devices, both BTicino F454 and MH202 SCS/Ethernet gateways are currently supported.
- For MyHome Play devices, a BTicino 3578 or Legrand 88328 Zigbee/USB gateway is required.
Since the "In One by Legrand" and "MyHome Play" products are no longer manufactured, buying the corresponding gateway is generally not easy (or cheap!), so this should probably only be considered for large existing installations.
The following Legrand and BTicino products are partially or fully supported by OpenNetty:
Product series | Product collection | Legrand reference | BTicino reference | Remarks |
---|---|---|---|---|
In One by Legrand | Lexic | 03600 | ||
In One by Legrand | Lexic | 03648 | ||
In One by Legrand | Lexic | 03809 | ||
In One by Legrand | 43214 | |||
In One by Legrand | Céliane | 67201 | ||
In One by Legrand | Céliane | 67202 | ||
In One by Legrand | Céliane | 67203 | ||
In One by Legrand | Céliane | 67204 | ||
In One by Legrand | Céliane | 67208 | ||
In One by Legrand | Céliane | 67210 | ||
In One by Legrand | Céliane | 67212 | ||
In One by Legrand | Céliane | 67214 | ||
In One by Legrand | Céliane | 67215 | ||
In One by Legrand | Céliane | 67220 | ||
In One by Legrand | Céliane | 67222 | ||
In One by Legrand | Céliane | 67280 | ||
In One by Legrand | Céliane | 67290 | ||
In One by Legrand | Céliane | 67445 | ||
In One by Legrand | Céliane | 67448 | ||
In One by Legrand | Plexo | 69510 | ||
In One by Legrand | Sagane | 84520 | ||
In One by Legrand | Sagane | 84522 | ||
In One by Legrand | Sagane | 84523 | ||
In One by Legrand | Sagane | 84524 | ||
In One by Legrand | Sagane | 84525 | ||
In One by Legrand | Sagane | 84529 | ||
In One by Legrand | Sagane | 84530 | ||
In One by Legrand | Sagane | 84531 | ||
In One by Legrand | Sagane | 84542 | ||
In One by Legrand | 88205 | |||
In One by Legrand | 88213 | |||
MyHome Up | 03847 | F411U1 | ||
MyHome Up | 03848 | F411U2 | Shutter mode is not yet supported | |
MyHome Up | 03651 | F418U2 | ||
MyHome Up | 03598 | F454 | ||
MyHome Up | 03535 | MH202 | ||
MyHome Play | Céliane | 67223 | ||
MyHome Play | 88328 | 3578 | ||
MyHome Play | 88337 |
Note
Support for additional devices will be progressively added depending on the demand.
OpenNetty ships with an OpenNetty.Daemon
executable that can be directly used as an OpenWebNet/MQTT
gateway on any x64, ARM32 or ARM64 Linux distribution that supports .NET 8.0 and uses systemd.
Compiled binaries can be found in the opennetty-resources repository, under the releases folder.
Note
These binaries are self-contained .NET applications that embed all the required dependencies so you don't have to install any global package on the machine on which OpenNetty is deployed.
Tip
Make sure you select the correct architecture when downloading the compiled binaries:
- x64: typically used for bare metal and virtual machines.
- ARM32: compatible with Single Board Computers (like Raspberry PIs) that don't support 64 bits.
- ARM64: best used for Single Board Computers that support 64 bits (e.g Raspberry PIs 3+ on which Raspbian 64 bits is installed).
First, you'll need to create a folder on the machine that will contain all the files required by OpenNetty: while it can
be deployed anywhere, a folder under /usr/local/bin
(e.g /usr/local/bin/opennetty
) is probably the best option.
The recommended option to deploy the daemon is to use an SSH/SFTP client (like Bitvise SSH client) and create the folder via SSH:
sudo mkdir /usr/local/bin/opennetty
sudo chmod 777 /usr/local/bin/opennetty
Once created, you can copy the daemon files under /usr/local/bin/opennetty
. You'll need to make the opennetty-daemon
file executable:
sudo chmod +x /usr/local/bin/opennetty/opennetty-daemon
To ensure the OpenNetty daemon is started and tracked by the operating system, a systemd service must be created under /etc/systemd/systemd
(e.g /etc/systemd/system/opennetty.service
). For that, you can use your SSH client to create the necessary file:
sudo nano /etc/systemd/system/opennetty.service
[Unit]
Description=OpenWebNet/MQTT gateway
After=network.target
[Service]
Type=notify
User=root
WorkingDirectory=/usr/local/bin/opennetty
ExecStart=/usr/local/bin/opennetty/opennetty-daemon
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
Once the file is saved, use systemctl enable
to register the OpenNetty service:
sudo systemctl enable opennetty
Tip
At this point, do not start OpenNetty yet, as you'll first need to add the configuration file required to communicate with the OpenWebNet gateways and the MQTT broker.
The OpenNetty daemon relies on a configuration file to locate the OpenWebNet gateways and devices. For that, create
a new XML file named OpenNettyConfiguration.xml
locally with the following content and replace the
server/port/username/password attributes to match the values used by your MQTT broker:
<Configuration>
<Mqtt Server="192.168.5.1" Port="1883" Username="jeedom" Password="koIiuhTFGtrdRkjLKhYGvgfFSDr" />
</Configuration>
Tip
Using a code editor like Visual Studio Code greatly simplifies writing the configuration file.
OpenNetty requires listing the gateways in the configuration file.
For that, you need to a Device
node with the correct brand/model attributes for each gateway present in the installation
and a Gateway
node containing the gateway details, including its unique name and whether OpenNetty will use a serial or TCP
socket to initiate OpenWebNet sessions:
<Configuration>
<Mqtt Server="192.168.5.1" Port="1883" Username="jeedom" Password="koIiuhTFGtrdRkjLKhYGvgfFSDr" />
<!-- In One by Legrand gateway -->
<Device Brand="Legrand" Model="88213">
<Gateway Name="OPEN-Nitoo gateway" Type="Serial" Port="/dev/serial/by-id/usb-Btcino_Terraneo_Mod._SFERA_Tele_Loop-if00" />
</Device>
<!-- MyHome Play gateway -->
<Device Brand="Legrand" Model="88328">
<Gateway Name="OPEN-Zigbee gateway" Type="Serial" Port="/dev/serial/by-id/usb-Silicon_Labs_CP2102_USB_to_UART_Bridge_Controller_0001-if00-port0" />
</Device>
<!-- MyHome Up gateway -->
<Device Brand="BTicino" Model="F454">
<Gateway Name="OPEN-SCS gateway" Type="Tcp" Server="192.168.5.10" Password="aJhYiBHk8" />
</Device>
</Configuration>
To be able to communicate with "In One by Legrand", "MyHome Play" and "MyHome Up" devices, OpenNetty requires listing them in the configuration file.
For that, you need to a Device
node with the correct brand/model attributes for each device present in the installation:
- The serial number is required for In One by Legrand and MyHome Play devices and optional for MyHome Up devices.
- The unit node is not used for MyHome Up devices but is generally required for In One by Legrand and MyHome Play devices.
- The unit must match one of the unit identifiers offered by the specific device. If you're unsure what identifier should be used,
you can see
OpenNettyDevices.xml
for a list of all the supported devices and the units they expose. - For MyHome Up devices, the area/point attributes must match the values assigned via MyHome Suite.
- The endpoint name must be chosen carefully as it will be used to infer the MQTT topic used for the endpoint (e.g state changes dispatched
by an endpoint named
Bedroom/Wall light
will be posted under theopennetty/bedroom/wall light
MQTT topic).
<Configuration>
<Mqtt Server="192.168.5.1" Port="1883" Username="jeedom" Password="koIiuhTFGtrdRkjLKhYGvgfFSDr" />
<!-- In One by Legrand gateway -->
<Device Brand="Legrand" Model="88213">
<Gateway Name="OPEN-Nitoo gateway" Type="Serial" Port="/dev/serial/by-id/usb-Btcino_Terraneo_Mod._SFERA_Tele_Loop-if00" />
</Device>
<!-- MyHome Play gateway -->
<Device Brand="Legrand" Model="88328">
<Gateway Name="OPEN-Zigbee gateway" Type="Serial" Port="/dev/serial/by-id/usb-Silicon_Labs_CP2102_USB_to_UART_Bridge_Controller_0001-if00-port0" />
</Device>
<!-- MyHome Up gateway -->
<Device Brand="BTicino" Model="F454">
<Gateway Name="OPEN-SCS gateway" Type="Tcp" Server="192.168.5.10" Password="aJhYiBHk8" />
</Device>
<!-- In One by Legrand one-gang PLC switch -->
<Device Brand="Legrand" Model="67201" SerialNumber="597132">
<Unit Id="2">
<Endpoint Name="Bedroom/Wall light" />
</Unit>
</Device>
<!-- In One by Legrand two-gang PLC switch -->
<Device Brand="Legrand" Model="67202" SerialNumber="479632">
<Unit Id="3">
<Endpoint Name="Bedroom/Bedside lamp 1" />
</Unit>
<Unit Id="4">
<Endpoint Name="Bedroom/Bedside lamp 2" />
</Unit>
</Device>
<!-- MyHome Play one-gang wireless command -->
<Device Brand="Legrand" Model="67223" SerialNumber="0014AC87">
<Unit Id="1">
<Endpoint Name="Bedroom/Wireless command/Short press" />
</Unit>
</Device>
<!-- MyHome Up two-way dimmer -->
<Device Brand="BTicino" Model="F418U2" SerialNumber="00B582A5">
<Endpoint Name="Living room/Wall light 1" Area="1" Point="1" />
<Endpoint Name="Living room/Wall light 2" Area="1" Point="2" />
</Device>
</Configuration>
Once your configuration file is ready, copy it to the folder you created to host OpenNetty's files and start the daemon:
sudo service opennetty start
Tip
You can use sudo service opennetty status
to determine if the daemon is correctly running.
You can also use MQTT Explorer to ensure state changes are correctly posted to MQTT and trigger commands that will be executed by the In One by Legrand/MyHome Play/MyHome Up devices.
For instance, to turn the Bedroom/Wall light
on, post the ON
value under the opennetty/bedroom/wall light/switch_state/set
topic: if the command was correctly executed by the device, the ON
value will be posted back by OpenNetty under the
opennetty/bedroom/wall light/switch_state
topic.
You can also send an empty opennetty/bedroom/wall light/switch_state/get
message to get the current switch state of the endpoint.
Tip
The complete list of supported MQTT attributes can be found in the OpenNettyMqttAttributes.cs
file.
Ready-to-use templates for Jeedom's jMQTT plugin can be found in the opennetty-resources repository.
To represent raw OpenWebNet frames, OpenNetty exposes 3 low-level structures – OpenNettyFrame
, OpenNettyField
and OpenNettyParameter
– and
one high-level primitive – OpenNettyMessage
– that can be used to represent any message type supported by the 3 OpenWebNet specifications:
- Bus commands.
- Dimension requests.
- Dimension reads.
- Dimension sets.
- Status requests.
var message = OpenNettyMessage.CreateCommand(
protocol: OpenNettyProtocol.Nitoo,
command : OpenNettyCommands.Lighting.On,
address : OpenNettyAddress.FromNitooAddress(identifier: 487932, unit: 2),
medium : OpenNettyMedium.Powerline,
mode : OpenNettyMode.Unicast);
var message = OpenNettyMessage.CreateFromFrame(OpenNettyProtocol.Nitoo, "*1*1*7806914##");
// (487932, 2)
var (identifier, unit) = OpenNettyAddress.ToNitooAddress(message.Address!.Value);
The OpenNettySession
class is the main entry point for manually communicating with an OpenWebNet gateway: it takes care
of initializing the connection and negotiates the desired OpenWebNet session type automatically. If authentication is
required by the remote gateway, it also takes care of the authentication dance in a completely transparent way.
OpenNettySession
implements IAsyncObservable<OpenNettyMessage>
and can be natively used with any of the extensions
provided by the System.Reactive.Async package to filter and
observe the messages sent by the OpenWebNet gateway.
var gateway = OpenNettyGateway.Create(
name : "SCS-Ethernet gateway",
brand : OpenNettyBrand.BTicino,
model : "F454",
endpoint: IPEndPoint.Parse("192.168.5.10:20000"),
password: "aJhYiBHk8");
await using var session = await OpenNettySession.CreateAsync(gateway, OpenNettySessionType.Event);
await using var subscription = await session.SubscribeAsync(message => Console.WriteLine(message.ToString()));
await using var connection = await session.ConnectAsync();
await Task.Delay(-1);
Tip
For advanced scenarios that only involve sequential processing, the OpenNettyConnection
class can also be directly
used to send and/or receive OpenWebNet frames from a gateway using either a TCP connection or a serial port:
using var port = new SerialPort(
portName: "/dev/serial/by-id/usb-Silicon_Labs_CP2102_USB_to_UART_Bridge_Controller_0001-if00-port0",
baudRate: 19_200,
parity : Parity.None,
dataBits: 8,
stopBits: StopBits.One);
using var source = new CancellationTokenSource();
source.CancelAfter(TimeSpan.FromSeconds(10));
await using var connection = await OpenNettyConnection.CreateSerialConnectionAsync(port, source.Token);
var message = OpenNettyMessage.CreateCommand(
protocol: OpenNettyProtocol.Zigbee,
command : OpenNettyCommands.Lighting.On,
address : OpenNettyAddress.FromHexadecimalZigbeeAddress(identifier: "0065ACAC", unit: 1),
medium : OpenNettyMedium.Radio,
mode : OpenNettyMode.Unicast);
await connection.SendAsync(message.Frame, source.Token);
if (await connection.ReceiveAsync(source.Token) != OpenNettyFrames.Acknowledgement)
{
throw new ApplicationException("The frame was not acknowledged by the gateway.");
}
While sessions and connections can be directly used to communicate with an OpenWebNet gateway, it is not the recommended approach.
Instead, users are strongly encouraged to leverage OpenNetty's .NET Generic Host integration: it will register a worker for each configured gateway and will dynamically manage sessions, process incoming messages and dispatch outgoing messages. It also automatically retransmit failed outgoing messages using a retry policy defined by OpenNetty based on the type of gateway.
Once the OpenNetty services are registered using the dedicated .AddOpenNetty()
extension, the low-level IOpenNettyService
interface can be leveraged to execute any arbitrary bus command, dimension request, dimension set or status request
and extract the corresponding response returned by the gateway, if applicable:
var builder = Host.CreateApplicationBuilder();
builder.Services.AddOpenNetty(options =>
{
// Register the SCS gateway used to communicate with MyHome devices.
options.AddGateway(OpenNettyGateway.Create(
name : "F454 gateway",
brand : OpenNettyBrand.BTicino,
model : "F454",
endpoint: IPEndPoint.Parse("192.168.5.10:20000"),
password: "aJhYiBHk8"));
});
var app = builder.Build();
await app.StartAsync();
// Send a WHO=13/DIMENSION=19 request and extract the raw values resolved from the response returned by the gateway.
var service = app.Services.GetRequiredService<IOpenNettyService>();
var values = await service.GetDimensionAsync(OpenNettyProtocol.Scs, OpenNettyDimensions.Management.Uptime);
await app.StopAsync();
Tip
While the IOpenNettyService
service can be very useful to send arbitrary messages or observe specific incoming messages, defining endpoints
as shown in the next section and using the strongly-typed APIs offered by OpenNettyController
when possible is strongly recommended.
For all its high-level operations, OpenNetty relies on the OpenNettyEndpoint
class:
- An endpoint generally has an address associated (but it's not always true, as gateway endpoints don't have an address attached).
- In most cases, an endpoint has a device definition attached from which it resolves the supported functions (like switching on or off a connected load or controlling the brightness level), but it's possible to create endpoints that don't have a device attached, which allows supporting non-device-specific addresses like SCS point-of-light area or group addresses.
- Nitoo and Zigbee endpoints often have a unit definition attached, but non-unit-specific endpoints can also be created to perform actions that don't target a specific Nitoo or Zigbee unit (e.g Nitoo device descriptions).
- When no unit or device definition is attached, a list of capabilities must be attached
to the endpoint before being able to perform actions using the
OpenNettyController
class.
var builder = Host.CreateApplicationBuilder();
builder.Services.AddOpenNetty(options =>
{
options.AddGateway(OpenNettyGateway.Create(
name : "F454 gateway",
brand : OpenNettyBrand.BTicino,
model : "F454",
endpoint: IPEndPoint.Parse("192.168.5.10:20000"),
password: "aJhYiBHk8"));
options.AddEndpoint(new OpenNettyEndpoint
{
Address = OpenNettyAddress.FromScsLightPointPointToPointAddress(area: 1, point: 3),
Device = new OpenNettyDevice
{
Definition = OpenNettyDevices.GetDeviceByModel(OpenNettyBrand.BTicino, "F418U2")
?? throw new InvalidOperationException("The specified gateway model is not supported.")
},
Name = "Bathroom/Recessed light",
Protocol = OpenNettyProtocol.Scs
});
options.AddEndpoint(new OpenNettyEndpoint
{
Address = OpenNettyAddress.FromScsLightPointAreaAddress(area: 1),
Capabilities = [OpenNettyCapabilities.OnOffSwitching],
Name = "Bathroom/All lights",
Protocol = OpenNettyProtocol.Scs
});
});
var app = builder.Build();
await app.StartAsync();
var manager = app.Services.GetRequiredService<OpenNettyManager>();
var controller = app.Services.GetRequiredService<OpenNettyController>();
// Resolve the brightness of the dimmable recessed light in area 1.
var brightness = await controller.GetBrightnessAsync(
await manager.FindEndpointByNameAsync("Bathroom/Recessed light")
?? throw new InvalidOperationException("The endpoint couldn't be resolved."));
// Switch off all the lights located in area 1.
await controller.SwitchOffAsync(
await manager.FindEndpointByNameAsync("Bathroom/All lights")
?? throw new InvalidOperationException("The endpoint couldn't be resolved."));
await app.StopAsync();
To infer high-level state changes affecting registered endpoints, OpenNetty includes a built-in OpenNettyCoordinator
service that monitors
all the incoming messages sent by the configured gateways and invokes the corresponding events exposed by the OpenNettyEvents
class.
By implementing the IOpenNettyHandler
interface, it is possible to subscribe to any event before incoming frames start being processed:
var builder = Host.CreateApplicationBuilder();
builder.Services.AddOpenNetty(options =>
{
var file = builder.Environment.ContentRootFileProvider.GetFileInfo("OpenNettyConfiguration.xml");
options.ImportFromXmlConfiguration(file);
});
builder.Services.AddSingleton<IOpenNettyHandler, MyEventHandler>();
var app = builder.Build();
await app.RunAsync();
class MyEventHandler(OpenNettyEvents events) : IOpenNettyHandler
{
public async ValueTask<IAsyncDisposable> SubscribeAsync() => StableCompositeAsyncDisposable.Create(
[
await events.BrightnessReported
.Where(args => !string.IsNullOrEmpty(args.Endpoint.Name))
.SubscribeAsync(args => Console.WriteLine($"Brightness on endpoint {args.Endpoint.Name}: {args.Level}.")),
await events.SwitchStateReported
.Where(args => !string.IsNullOrEmpty(args.Endpoint.Name))
.SubscribeAsync(args => Console.WriteLine($"Switch state on endpoint {args.Endpoint.Name}:" +
(args.State is OpenNettyModels.Lighting.SwitchState.On ? "on" : "off")))
]);
}
Security issues and bugs should be reported privately by emailing contact@kevinchalet.com. You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message.
OpenNetty is actively maintained by Kévin Chalet. Contributions are welcome and can be submitted using pull requests.
This project is licensed under the Apache License. This means that you can use, modify and distribute it freely. See http://www.apache.org/licenses/LICENSE-2.0.html for more details.