Skip to content

Commit ea45910

Browse files
authored
Support per vendor device discovery (imurvai#128)
1 parent 58b0827 commit ea45910

30 files changed

Lines changed: 803 additions & 149 deletions

BrickController2/BrickController2.Android/PlatformServices/BluetoothLE/ScanRecordProcessor.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ namespace BrickController2.Droid.PlatformServices.BluetoothLE
44
{
55
public static class ScanRecordProcessor
66
{
7-
public static IDictionary<byte, byte[]> GetAdvertismentData(byte[] scanRecord)
7+
public static Dictionary<byte, byte[]> GetAdvertismentData(byte[] scanRecord)
88
{
99
var advertismentData = new Dictionary<byte, byte[]>();
1010

BrickController2/BrickController2.Tests/BrickController2.Tests.csproj

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,13 @@
22

33
<PropertyGroup>
44
<TargetFramework>net9.0</TargetFramework>
5-
<IsPackable>false</IsPackable>
6-
<IsTestProject>true</IsTestProject>
75
</PropertyGroup>
86

97
<ItemGroup>
108
<PackageReference Include="coverlet.collector" />
11-
<PackageReference Include="FluentAssertions" />
9+
<PackageReference Include="FluentAssertions" />
1210
<PackageReference Include="Microsoft.NET.Test.Sdk" />
11+
<PackageReference Include="Moq" />
1312
<PackageReference Include="xunit" />
1413
<PackageReference Include="xunit.runner.visualstudio" />
1514
</ItemGroup>
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Text;
4+
using BrickController2.DeviceManagement;
5+
using BrickController2.DeviceManagement.BuWizz;
6+
using FluentAssertions;
7+
using Xunit;
8+
9+
namespace BrickController2.Tests.DeviceManagement.BuWizz;
10+
11+
public class BuWizzDeviceManagerTests : DeviceManagerTestBase<BuWizzDeviceManager>
12+
{
13+
[Fact]
14+
public void TryGetDevice_BuWizz1ManufacturerId_BuWizz1DeviceReturned()
15+
{
16+
var scanResult = CreateScanResult("BuWizz-1-ByManufacturerId-0x484d", manufacturerData: [0x48, 0x4d, 0x98, 0x76]);
17+
18+
var result = _manager.TryGetDevice(scanResult, out var device);
19+
20+
result.Should().BeTrue();
21+
device.Should().BeEquivalentTo(new FoundDevice()
22+
{
23+
DeviceAddress = scanResult.DeviceAddress,
24+
DeviceName = scanResult.DeviceName,
25+
DeviceType = DeviceType.BuWizz,
26+
ManufacturerData = [0x48, 0x4d, 0x98, 0x76]
27+
});
28+
}
29+
30+
[Theory]
31+
[InlineData(0x054e, "BuWizz", DeviceType.BuWizz2)]
32+
[InlineData(0x4505, "BuWizz2", DeviceType.BuWizz2)]
33+
[InlineData(0x054e, "BuWizz3", DeviceType.BuWizz3)]
34+
public void TryGetDevice_BuWizzManufacturerIdWithLocalName_ReturnsProperBuWizzDevice(ushort manufacturerId, string localName, DeviceType deviceType)
35+
{
36+
var scanResult = CreateScanResult("BuWizz", new Dictionary<byte, byte[]>
37+
{
38+
{ 0xff, BitConverter.GetBytes(manufacturerId) },
39+
{ 0x09, Encoding.ASCII.GetBytes(localName) }
40+
});
41+
42+
var result = _manager.TryGetDevice(scanResult, out var device);
43+
44+
result.Should().BeTrue();
45+
device.Should().BeEquivalentTo(new FoundDevice()
46+
{
47+
DeviceAddress = scanResult.DeviceAddress,
48+
DeviceName = scanResult.DeviceName,
49+
DeviceType = deviceType,
50+
ManufacturerData = BitConverter.GetBytes(manufacturerId)
51+
});
52+
}
53+
}
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
using BrickController2.DeviceManagement;
2+
using BrickController2.DeviceManagement.CaDA;
3+
using BrickController2.PlatformServices.BluetoothLE;
4+
using BrickController2.UI.Services.Preferences;
5+
using FluentAssertions;
6+
using Moq;
7+
using System.Collections.Generic;
8+
using Xunit;
9+
10+
namespace BrickController2.Tests.DeviceManagement.CaDA;
11+
12+
public class CaDADeviceManagerTests
13+
{
14+
private readonly CaDADeviceManager _manager;
15+
private readonly Mock<IPreferencesService> _preferencesService = new(MockBehavior.Strict);
16+
17+
public CaDADeviceManagerTests()
18+
{
19+
_preferencesService.Setup(x => x.ContainsKey("AppID", "CaDA")).Returns(true);
20+
_preferencesService.Setup(x => x.Get("AppID", "", "CaDA")).Returns("YWJj");
21+
22+
_manager = new CaDADeviceManager(_preferencesService.Object);
23+
}
24+
25+
[Fact]
26+
public void TryGetDevice_CadaCarWithMatchingAppId_ReturnsCaDaRaceCarDevice()
27+
{
28+
byte[] manufacturerData =
29+
{
30+
// manufacturerId
31+
0xf0, 0xff,
32+
// CADA RaceCar
33+
0x75, 0x49,
34+
// 3 bytes identifying the device
35+
0x01, 0x05, 0x94,
36+
// 3 bytes AppID
37+
0x61, 0x62, 0x63,
38+
// other data
39+
0x00, 0x00, 0x81, 0x82, 0x00, 0x00, 0x00, 0x00
40+
};
41+
42+
var scanResult = new ScanResult("RaceCar", "1234", new Dictionary<byte, byte[]>()
43+
{
44+
{ 0xFF, manufacturerData }
45+
});
46+
47+
var result = _manager.TryGetDevice(scanResult, out var device);
48+
49+
result.Should().BeTrue();
50+
device.Should().BeEquivalentTo(new FoundDevice()
51+
{
52+
DeviceAddress = "01-05-94",
53+
DeviceName = "CaDA 01-05-94",
54+
DeviceType = DeviceType.CaDA_RaceCar,
55+
ManufacturerData = manufacturerData
56+
});
57+
}
58+
59+
[Fact]
60+
public void TryGetDevice_CadaCarWithDifferentAppId_ReturnsCaDaRaceCarDevice()
61+
{
62+
byte[] manufacturerData =
63+
{
64+
// manufacturerId
65+
0xf0, 0xff,
66+
// CADA RaceCar
67+
0x75, 0x40,
68+
// 3 bytes identifying the device
69+
0x01, 0x02, 0x03,
70+
// 3 bytes AppID
71+
0x63, 0x62, 0x61,
72+
// other data
73+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
74+
};
75+
76+
var scanResult = new ScanResult("RaceCar", "1234", new Dictionary<byte, byte[]>()
77+
{
78+
{ 0xFF, manufacturerData }
79+
});
80+
81+
var result = _manager.TryGetDevice(scanResult, out var device);
82+
83+
result.Should().BeFalse();
84+
device.DeviceType.Should().Be(DeviceType.Unknown);
85+
}
86+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
using BrickController2.DeviceManagement;
2+
using FluentAssertions;
3+
using System;
4+
using System.Collections.Generic;
5+
using Xunit;
6+
7+
namespace BrickController2.Tests.DeviceManagement;
8+
9+
public class CircuitCubeDeviceManagerTests : DeviceManagerTestBase<CircuitCubeDeviceManager>
10+
{
11+
[Fact]
12+
public void TryGetDevice_CircuitCubeServiceUuid_CircuitCubeDeviceReturned()
13+
{
14+
var scanResult = CreateScanResult(deviceName: default, serviceUuid: new Guid("6e400001-b5a3-f393-e0a9-e50e24dcca9e"));
15+
16+
var result = _manager.TryGetDevice(scanResult, out var device);
17+
18+
result.Should().BeTrue();
19+
device.Should().BeEquivalentTo(new FoundDevice()
20+
{
21+
DeviceAddress = scanResult.DeviceAddress,
22+
DeviceName = scanResult.DeviceName,
23+
DeviceType = DeviceType.CircuitCubes,
24+
ManufacturerData = null
25+
});
26+
}
27+
28+
[Fact]
29+
public void TryGetDevice_ServiceGuidDoesNotMatch_ReturnsFalse()
30+
{
31+
var scanResult = CreateScanResult("Wrong-ServiceUuid", new Dictionary<byte, byte[]>
32+
{
33+
{ 0x06, new Guid("6e400001-b5a3-f393-e0a9-e50e24dcca9e").ToByteArray() }
34+
});
35+
36+
var result = _manager.TryGetDevice(scanResult, out var device);
37+
38+
result.Should().BeFalse();
39+
device.DeviceType.Should().Be(DeviceType.Unknown);
40+
}
41+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
using BrickController2.PlatformServices.BluetoothLE;
2+
using BrickController2.Protocols;
3+
using System;
4+
using System.Collections.Generic;
5+
6+
namespace BrickController2.Tests.DeviceManagement;
7+
8+
public abstract class DeviceManagerTestBase<TManager> where TManager : IBluetoothLEDeviceManager, new()
9+
{
10+
protected readonly TManager _manager = new();
11+
12+
protected static ScanResult CreateScanResult(string? deviceName, Guid serviceUuid)
13+
=> CreateScanResult(deviceName, new Dictionary<byte, byte[]>()
14+
{
15+
{ 0x06, serviceUuid.To128BitByteArray() }
16+
});
17+
18+
protected static ScanResult CreateScanResult(string? deviceName, byte[] manufacturerData)
19+
=> CreateScanResult(deviceName, new Dictionary<byte, byte[]>()
20+
{
21+
{ 0xFF, manufacturerData }
22+
});
23+
24+
protected static ScanResult CreateScanResult(string? deviceName)
25+
=> CreateScanResult(deviceName, advertismentData:default);
26+
27+
protected static ScanResult CreateScanResult(string? deviceName, Dictionary<byte, byte[]>? advertismentData = default)
28+
=> new(deviceName ?? typeof(TManager).Name, Random.Shared.Next().ToString("X"), advertismentData ?? []);
29+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
using BrickController2.DeviceManagement;
2+
using BrickController2.DeviceManagement.Lego;
3+
using FluentAssertions;
4+
using Xunit;
5+
6+
namespace BrickController2.Tests.DeviceManagement.Lego;
7+
8+
public class LegoDeviceManagerTests : DeviceManagerTestBase<LegoDeviceManager>
9+
{
10+
[Fact]
11+
public void TryGetDevice_WeDoServiceUuid_WeDo2DeviceReturned()
12+
{
13+
// 128bit UUID 00001523-1212-efde-1523-785feabcd123
14+
var scanResult = CreateScanResult("WeDo2", advertismentData: new() { { 0x06, [0x23, 0xd1, 0xbc, 0xea, 0x5f, 0x78, 0x23, 0x15, 0xde, 0xef, 0x12, 0x12, 0x23, 0x15, 0x00, 0x00] } });
15+
16+
var result = _manager.TryGetDevice(scanResult, out var device);
17+
18+
result.Should().BeTrue();
19+
device.Should().BeEquivalentTo(new FoundDevice()
20+
{
21+
DeviceAddress = scanResult.DeviceAddress,
22+
DeviceName = scanResult.DeviceName,
23+
DeviceType = DeviceType.WeDo2
24+
});
25+
}
26+
27+
[Theory]
28+
[InlineData(0x20, DeviceType.DuploTrainHub)]
29+
[InlineData(0x40, DeviceType.Boost)]
30+
[InlineData(0x41, DeviceType.PoweredUp)]
31+
[InlineData(0x80, DeviceType.TechnicHub)]
32+
[InlineData(0x84, DeviceType.TechnicMove)]
33+
public void TryGetDevice_LegoManufacturerIdWithDeviceId_ReturnsProperLegoDevice(byte deviceId, DeviceType deviceType)
34+
{
35+
byte[] manufacturerData = [0x97, 0x03, 0x00, deviceId];
36+
var scanResult = CreateScanResult(deviceName: default, manufacturerData: manufacturerData);
37+
38+
var result = _manager.TryGetDevice(scanResult, out var device);
39+
40+
result.Should().BeTrue();
41+
device.Should().BeEquivalentTo(new FoundDevice()
42+
{
43+
DeviceAddress = scanResult.DeviceAddress,
44+
DeviceName = scanResult.DeviceName,
45+
DeviceType = deviceType,
46+
ManufacturerData = manufacturerData
47+
});
48+
}
49+
50+
[Fact]
51+
public void TryGetDevice_UnknownLegoDeviceId_ReturnsFalse()
52+
{
53+
var scanResult = CreateScanResult("UnknownLegoDevice", manufacturerData: [0x97, 0x03, 0x00, 0xFF]);
54+
55+
var result = _manager.TryGetDevice(scanResult, out var device);
56+
57+
result.Should().BeFalse();
58+
device.DeviceType.Should().Be(DeviceType.Unknown);
59+
}
60+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
using BrickController2.DeviceManagement;
2+
using BrickController2.DeviceManagement.MouldKing;
3+
using FluentAssertions;
4+
using Xunit;
5+
6+
namespace BrickController2.Tests.DeviceManagement.MouldKing;
7+
8+
public class MouldKingDeviceManagerTests : DeviceManagerTestBase<MouldKingDeviceManager>
9+
{
10+
[Fact]
11+
public void TryGetDevice_MouldKingManufacturerId_ReturnsMouldKingDiyDevice()
12+
{
13+
byte[] manufacturerData = [0x33, 0xac];
14+
var scanResult = CreateScanResult(deviceName: default, manufacturerData: manufacturerData);
15+
16+
var result = _manager.TryGetDevice(scanResult, out var device);
17+
18+
result.Should().BeTrue();
19+
device.Should().BeEquivalentTo(new FoundDevice()
20+
{
21+
DeviceAddress = scanResult.DeviceAddress,
22+
DeviceName = scanResult.DeviceName,
23+
DeviceType = DeviceType.MK_DIY,
24+
ManufacturerData = manufacturerData
25+
});
26+
}
27+
28+
[Fact]
29+
public void TryGetDevice_WrongManufacturerId_ReturnsFalse()
30+
{
31+
var scanResult = CreateScanResult("WrongManufacturerId", manufacturerData: [0x0f, 0xff]);
32+
33+
var result = _manager.TryGetDevice(scanResult, out var device);
34+
35+
result.Should().BeFalse();
36+
device.DeviceType.Should().Be(DeviceType.Unknown);
37+
}
38+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
using BrickController2.DeviceManagement;
2+
using BrickController2.DeviceManagement.Lego;
3+
using FluentAssertions;
4+
using Xunit;
5+
6+
namespace BrickController2.Tests.DeviceManagement;
7+
8+
public class SBrickDeviceManagerTests : DeviceManagerTestBase<SBrickDeviceManager>
9+
{
10+
[Fact]
11+
public void TryGetDevice_VengitManufacturerId_ReturnsSBrickDevice()
12+
{
13+
byte[] manufacturerData = [0x98, 0x01];
14+
var scanResult = CreateScanResult(deviceName: default, manufacturerData: manufacturerData);
15+
16+
var result = _manager.TryGetDevice(scanResult, out var device);
17+
18+
result.Should().BeTrue();
19+
device.Should().BeEquivalentTo(new FoundDevice()
20+
{
21+
DeviceAddress = scanResult.DeviceAddress,
22+
DeviceName = scanResult.DeviceName,
23+
DeviceType = DeviceType.SBrick,
24+
ManufacturerData = manufacturerData
25+
});
26+
}
27+
28+
[Fact]
29+
public void TryGetDevice_WrongManufacturerId_ReturnsFalse()
30+
{
31+
var scanResult = CreateScanResult("WrongManufacturerId", manufacturerData: [0x01, 0x98]);
32+
33+
var result = _manager.TryGetDevice(scanResult, out var device);
34+
35+
result.Should().BeFalse();
36+
device.DeviceType.Should().Be(DeviceType.Unknown);
37+
}
38+
}

0 commit comments

Comments
 (0)