Skip to content

Commit

Permalink
Support for bluetooth le
Browse files Browse the repository at this point in the history
  • Loading branch information
jdomnitz committed Jan 12, 2025
1 parent 267b816 commit b78fdd1
Show file tree
Hide file tree
Showing 15 changed files with 730 additions and 76 deletions.
2 changes: 1 addition & 1 deletion ExampleConsole/ExampleConsole.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<TargetFrameworks>net8.0-windows10.0.19041.0; net8.0</TargetFrameworks>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
Expand Down
32 changes: 26 additions & 6 deletions MatterDotNet/Entities/Node.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
using MatterDotNet.Clusters.Utility;
using MatterDotNet.OperationalDiscovery;
using MatterDotNet.PKI;
using MatterDotNet.Protocol.Connection;
using MatterDotNet.Protocol.Sessions;
using MatterDotNet.Protocol.Subprotocols;
using System.Net;
Expand Down Expand Up @@ -56,15 +57,23 @@ private Node(ODNode connection, Fabric fabric, OperationalCertificate noc)
/// <exception cref="IOException"></exception>
public async Task<SecureSession> GetCASESession()
{
using (SessionContext session = SessionManager.GetUnsecureSession(new IPEndPoint(connection.IPAddress!, connection.Port), true))
return await GetCASESession(session);
if (connection.IPAddress != null)
{
using (SessionContext session = SessionManager.GetUnsecureSession(new IPEndPoint(connection.IPAddress!, connection.Port), true))
return await GetCASESession(session);
}
else
{
using (SessionContext session = SessionManager.GetUnsecureSession(new BLEEndPoint(connection.BTAddress), true))
return await GetCASESession(session);
}
}
/// <summary>
/// Get a secure session for the node
/// </summary>
/// <returns></returns>
/// <exception cref="IOException"></exception>
public async Task<SecureSession> GetCASESession(SessionContext session)
internal async Task<SecureSession> GetCASESession(SessionContext session)
{
CASE caseProtocol = new CASE(session);
//TODO - Use OD session params
Expand All @@ -74,15 +83,18 @@ public async Task<SecureSession> GetCASESession(SessionContext session)
return caseSession;
}

internal static Node CreateTemp(OperationalCertificate noc, Fabric fabric, ODNode opInfo)
internal static Node CreateTemp(OperationalCertificate noc, Fabric fabric, ODNode opInfo, EndPoint rootNode)
{
return new Node(opInfo, fabric, noc);
Node node = new Node(opInfo, fabric, noc);
rootNode.SetNode(node);
node.root = rootNode;
return node;
}

internal static async Task<Node?> Enumerate(OperationalCertificate noc, Fabric fabric)
{
string operationalInstanceName = $"{Convert.ToHexString(fabric.CompressedFabricID)}-{noc.NodeID:X16}";
ODNode? opInfo = await DiscoveryService.Shared.Find(operationalInstanceName);
ODNode? opInfo = await IPDiscoveryService.Shared.Find(operationalInstanceName);
if (opInfo == null)
return null;
return await Enumerate(noc, fabric, opInfo);
Expand Down Expand Up @@ -112,6 +124,14 @@ internal static async Task Populate(SecureSession session, Node node)
await node.Root.EnumerateClusters(session);
}

internal string OperationalInstanceName
{
get
{
return $"{Convert.ToHexString(fabric.CompressedFabricID)}-{noc.NodeID:X16}";
}
}

/// <inheritdoc />
public override string ToString()
{
Expand Down
39 changes: 23 additions & 16 deletions MatterDotNet/MatterDotNet.csproj
Original file line number Diff line number Diff line change
@@ -1,24 +1,30 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<Version>0.1.0</Version>
<Authors>jdomnitz</Authors>
<Company>SmartHomeOS and Contributors</Company>
<PackageLicenseExpression>AGPL-3.0-or-later</PackageLicenseExpression>
<Title>MatterDotNet</Title>
<Description>A C# implementation of the Matter 1.3 Standard (Formally known as project chip)</Description>
<Copyright>Copyright MatterDotNet Contributors</Copyright>
<PackageReadmeFile>README.md</PackageReadmeFile>
<RepositoryUrl>https://github.com/SmartHomeOS/MatterDotNet/</RepositoryUrl>
<PackageTags>matter; matter-controller; smarthome; project-chip; dotnet;</PackageTags>
<PackageReleaseNotes>Library is not yet functional. See README for details.</PackageReleaseNotes>
<PackageIcon>logo.png</PackageIcon>
<GenerateDocumentationFile>True</GenerateDocumentationFile>
</PropertyGroup>

<PropertyGroup Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::Windows)))' == 'true'">
<TargetFrameworks>net8.0-windows10.0.19041.0; net9.0-windows10.0.19041.0; net8.0</TargetFrameworks>
</PropertyGroup>
<PropertyGroup Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::Linux)))' == 'true'">
<TargetFrameworks>net8.0; net9.0</TargetFrameworks>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<Version>0.1.0</Version>
<Authors>jdomnitz</Authors>
<Company>SmartHomeOS and Contributors</Company>
<PackageLicenseExpression>AGPL-3.0-or-later</PackageLicenseExpression>
<Title>MatterDotNet</Title>
<Description>A C# implementation of the Matter 1.3 Standard (Formally known as project chip)</Description>
<Copyright>Copyright MatterDotNet Contributors</Copyright>
<PackageReadmeFile>README.md</PackageReadmeFile>
<RepositoryUrl>https://github.com/SmartHomeOS/MatterDotNet/</RepositoryUrl>
<PackageTags>matter; matter-controller; smarthome; project-chip; dotnet;</PackageTags>
<PackageReleaseNotes>Library is not yet functional. See README for details.</PackageReleaseNotes>
<PackageIcon>logo.png</PackageIcon>
<GenerateDocumentationFile>True</GenerateDocumentationFile>
</PropertyGroup>

<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|net8.0|AnyCPU'">
<IsTrimmable>True</IsTrimmable>
<IsAotCompatible>True</IsAotCompatible>
Expand All @@ -40,6 +46,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="InTheHand.BluetoothLE" Version="4.0.37" />
<PackageReference Include="TinyDNS" Version="0.9.0" />
</ItemGroup>

Expand Down
116 changes: 116 additions & 0 deletions MatterDotNet/OperationalDiscovery/BTDiscoveryService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
// MatterDotNet Copyright (C) 2025
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY, without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
// See the GNU Affero General Public License for more details.
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.

using InTheHand.Bluetooth;
using MatterDotNet.Protocol.Connection;
using System.Buffers.Binary;

namespace MatterDotNet.OperationalDiscovery
{
/// <summary>
/// Bluetooth LE Operational Discovery Service
/// </summary>
public static class BTDiscoveryService
{
/// <summary>
/// Generate commissionable node info from BLE advertisement
/// </summary>
/// <param name="id"></param>
/// <param name="name"></param>
/// <param name="bleAdvertisement"></param>
/// <returns></returns>
private static ODNode FromAdvertisement(string id, string name, ReadOnlySpan<byte> bleAdvertisement)
{
ODNode ret = new ODNode();
if (bleAdvertisement[0] == 0x0)
ret.CommissioningMode = CommissioningMode.Basic;
ret.Discriminator = (ushort)(BinaryPrimitives.ReadUInt16LittleEndian(bleAdvertisement.Slice(1, 2)) & 0xFFF);
ret.Vendor = BinaryPrimitives.ReadUInt16LittleEndian(bleAdvertisement.Slice(3, 2));
ret.Product = BinaryPrimitives.ReadUInt16LittleEndian(bleAdvertisement.Slice(5, 2));
ret.DeviceName = name;
ret.BTAddress = id;
return ret;
}

/// <summary>
/// Discover all matter devices commissionable using Bluetooth LE
/// </summary>
/// <returns></returns>
/// <exception cref="PlatformNotSupportedException"></exception>
public static async Task<ODNode[]> ScanAll()
{
Dictionary<string, ODNode> discoveredDevices = new Dictionary<string, ODNode>();
void Bluetooth_AdvertisementReceived(object? sender, BluetoothAdvertisingEvent e)
{
if (e.ServiceData.ContainsKey(BTPConnection.MATTER_UUID) && e.Device != null && !discoveredDevices.ContainsKey(e.Device.Id))
discoveredDevices.Add(e.Device.Id, FromAdvertisement(e.Device.Id, e.Device?.Name ?? e.Name, e.ServiceData[BTPConnection.MATTER_UUID]));
}
if (await Bluetooth.GetAvailabilityAsync() == false)
throw new PlatformNotSupportedException("No Bluetooth Adapter Found");
BluetoothLEScanOptions opts = new BluetoothLEScanOptions();
BluetoothLEScanFilter filter = new BluetoothLEScanFilter();
filter.Services.Add(BTPConnection.MATTER_UUID);
opts.Filters.Add(filter);
opts.AcceptAllAdvertisements = false;
opts.KeepRepeatedDevices = false;
Bluetooth.AdvertisementReceived += Bluetooth_AdvertisementReceived;
BluetoothLEScan scan = await Bluetooth.RequestLEScanAsync(opts);
await Task.Delay(3000);
scan.Stop();
Bluetooth.AdvertisementReceived -= Bluetooth_AdvertisementReceived;
return discoveredDevices.Values.ToArray();
}

/// <summary>
/// Find a Bluetooth device that matches the provided payload
/// </summary>
/// <param name="payload"></param>
/// <returns></returns>
/// <exception cref="InvalidOperationException"></exception>
public static async Task<ODNode?> Find(CommissioningPayload payload)
{
SemaphoreSlim findLock = new SemaphoreSlim(0, 1);
ODNode? result = null;
void Bluetooth_AdvertisementReceived(object? sender, BluetoothAdvertisingEvent e)
{
if (e.ServiceData.ContainsKey(BTPConnection.MATTER_UUID))
{
ODNode found = FromAdvertisement(e.Device.Id, e.Device?.Name ?? e.Name, e.ServiceData[BTPConnection.MATTER_UUID]);
if (found.Vendor != payload.VendorID && payload.VendorID != 0 && found.Vendor != 0)
return;
if (found.Product != payload.ProductID && payload.ProductID != 0 && found.Product != 0)
return;
if (payload.LongDiscriminator && found.Discriminator != payload.Discriminator)
return;
if (!payload.LongDiscriminator && (found.Discriminator >> 8) != payload.Discriminator)
return;
result = found;
findLock.Release();
}
}
if (await Bluetooth.GetAvailabilityAsync() == false)
throw new InvalidOperationException("No Bluetooth");
BluetoothLEScanOptions opts = new BluetoothLEScanOptions();
BluetoothLEScanFilter filter = new BluetoothLEScanFilter();
filter.Services.Add(BTPConnection.MATTER_UUID);
opts.Filters.Add(filter);
opts.AcceptAllAdvertisements = false;
opts.KeepRepeatedDevices = false;
Bluetooth.AdvertisementReceived += Bluetooth_AdvertisementReceived;
BluetoothLEScan scan = await Bluetooth.RequestLEScanAsync(opts);
await findLock.WaitAsync(10000);
scan.Stop();
Bluetooth.AdvertisementReceived -= Bluetooth_AdvertisementReceived;
return result;
}
}
}
46 changes: 46 additions & 0 deletions MatterDotNet/OperationalDiscovery/FabricInterface.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// MatterDotNet Copyright (C) 2025
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY, without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
// See the GNU Affero General Public License for more details.
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.

namespace MatterDotNet.OperationalDiscovery
{
/// <summary>
/// Connectivity method between a node and a controller
/// </summary>
[Flags]
public enum FabricInterface
{
/// <summary>
/// No connectivity Type
/// </summary>
None = 0x0,
/// <summary>
/// WiFi Connection
/// </summary>
WiFi = 0x1,
/// <summary>
/// Thread 802.11.4
/// </summary>
Thread = 0x2,
/// <summary>
/// Ethernet
/// </summary>
Ethernet = 0x4,
/// <summary>
/// One or more IP protocols (aka anything but bluetooth)
/// </summary>
IP = 0x8,
/// <summary>
/// Bluetooth LE
/// </summary>
Bluetooth = 0x10
}
}
Loading

0 comments on commit b78fdd1

Please sign in to comment.