From 33624ec251ee80b6d5c5cd3a62d98b9c27bfb2f4 Mon Sep 17 00:00:00 2001 From: OpenClaw Date: Thu, 16 Apr 2026 13:35:38 +0800 Subject: [PATCH 1/3] feat: integrate cross-platform NetFinder for CS203XL discovery on iOS/Android/Windows MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - NEW: Source/HAL/TCPIP/NetFinder.cs — CSLibrary.NetFinder namespace wrapper - Wraps CSLibrary.NetFinder.CS203XL.NetFinder (UDP port 3000) - Singleton pattern, static SearchDevice() / StopSearch() - Cross-platform: System.Net.Sockets works on iOS, Android, Windows - MODIFIED: Source/HAL/TCPIP/ClassDeviceFinder.cs - Changed #if __MwwmCrossPluginBLE to #if TCP - Removed Windows-only DeviceWatcher code - Wires to CSLibrary.NetFinder.NetFinder for discovery - MODIFIED: Source/CSLibrary.cs - Added: using CSLibrary.NetFinder; - ConnectAsync(string ip, int port) already wired in CodeFileTCPIP.cs --- Source/CSLibrary.cs | 1 + Source/HAL/TCPIP/ClassDeviceFinder.cs | 498 ++++++++++++-------------- Source/HAL/TCPIP/NetFinder.cs | 234 ++++++++++++ 3 files changed, 459 insertions(+), 274 deletions(-) create mode 100644 Source/HAL/TCPIP/NetFinder.cs diff --git a/Source/CSLibrary.cs b/Source/CSLibrary.cs index f28348a..25a6a6a 100755 --- a/Source/CSLibrary.cs +++ b/Source/CSLibrary.cs @@ -20,6 +20,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE */ using CSLibrary.Constants; +using CSLibrary.NetFinder; using System; using System.Collections.Generic; using System.Linq; diff --git a/Source/HAL/TCPIP/ClassDeviceFinder.cs b/Source/HAL/TCPIP/ClassDeviceFinder.cs index 02f6dab..1c9447c 100755 --- a/Source/HAL/TCPIP/ClassDeviceFinder.cs +++ b/Source/HAL/TCPIP/ClassDeviceFinder.cs @@ -1,275 +1,225 @@ -/* -Copyright (c) 2018 Convergence Systems Limited - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -*/ - -#if TCP - -#if __MwwmCrossPluginBLE - -using System; -using System.Collections.Generic; - -using Plugin.BLE.Abstractions; -using Plugin.BLE.Abstractions.Contracts; -using Plugin.BLE.Abstractions.EventArgs; - -namespace CSLibrary -{ - public partial class DeviceFinder - { - /// - /// DeviceFinder Argument - /// - public class DeviceFinderArgs : EventArgs - { - private DeviceInfomation _data; - - /// - /// Device Finder - /// - /// - public DeviceFinderArgs(DeviceInfomation data) - { - _data = data; - } - - /// - /// Device finder information - /// - public DeviceInfomation Found - { - get { return _data; } - set { _data = value; } - } - } - - /// - /// Netfinder information return from device - /// - public class DeviceInfomation - { - public uint ID; - - public string deviceName; - - public object nativeDeviceInformation; - - /* - /// - /// Reserved for future use - /// - public Mode Mode = Mode.Unknown; - /// - /// Total time on network - /// - public TimeEvent TimeElapsedNetwork = new TimeEvent(); - /// - /// Total Power on time - /// - public TimeEvent TimeElapsedPowerOn = new TimeEvent(); - /// - /// MAC address - /// - public MAC MACAddress = new MAC();//[6]; - /// - /// IP address - /// - public IP IPAddress = new IP(); - /// - /// Subnet Mask - /// - public IP SubnetMask = new IP(); - /// - /// Gateway - /// - public IP Gateway = new IP(); - /// - /// Trusted hist IP - /// - public IP TrustedServer = new IP(); - /// - /// Inducated trusted server enable or not. - /// - public Boolean TrustedServerEnabled = false; - /// - /// UDP Port - /// - public ushort Port; // Get port from UDP header - /// - /// Reserved for future use, Server mode ip - /// - public byte[] serverip = new byte[4]; - /// - /// enable or disable DHCP - /// - public bool DHCPEnabled; - /// - /// Reserved for future use, Server mode port - /// - public ushort serverport; - /// - /// DHCP retry - /// - public byte DHCPRetry; - /// - /// Device name, user can change it. - /// - public string DeviceName; - /// - /// Mode discription - /// - public string Description; - /// - /// Connect Mode - /// - public byte ConnectMode; - /// - /// Gateway check reset mode - /// - public int GatewayCheckResetMode; - */ - } - - static private Windows.Devices.Enumeration.DeviceWatcher deviceWatcher; - static List _deviceDB = new List(); - - static public event EventHandler OnSearchCompleted; - - static public void SearchDevice() - { - // Additional properties we would like about the device. - // Property strings are documented here https://msdn.microsoft.com/en-us/library/windows/desktop/ff521659(v=vs.85).aspx - string[] requestedProperties = { "System.Devices.Aep.DeviceAddress", "System.Devices.Aep.IsConnected", "System.Devices.Aep.Bluetooth.Le.IsConnectable", "System.Devices.Aep.AepId", "System.Devices.Aep.Category" }; - - // BT_Code: Example showing paired and non-paired in a single query. - string aqsAllBluetoothLEDevices = "(System.Devices.Aep.ProtocolId:=\"{bb7bb05e-5972-42b5-94fc-76eaa7084d49}\")"; - - deviceWatcher = - DeviceInformation.CreateWatcher( - aqsAllBluetoothLEDevices, - requestedProperties, - DeviceInformationKind.AssociationEndpoint); - - // Register event handlers before starting the watcher. - deviceWatcher.Added += DeviceWatcher_Added; - deviceWatcher.Updated += DeviceWatcher_Updated; - deviceWatcher.Removed += DeviceWatcher_Removed; - deviceWatcher.EnumerationCompleted += DeviceWatcher_EnumerationCompleted; - deviceWatcher.Stopped += DeviceWatcher_Stopped; - - // Start the watcher. - deviceWatcher.Start(); - } - - - static public void Stop() - { - /// - /// Stops watching for all nearby Bluetooth devices. - /// - if (deviceWatcher != null) - { - // Unregister the event handlers. - deviceWatcher.Added -= DeviceWatcher_Added; - - // Stop the watcher. - deviceWatcher.Stop(); - deviceWatcher = null; - } - } - - static public void ClearDeviceList() - { - _deviceDB.Clear (); - } - - static public DeviceInformation GetDeviceInformation(int id) - { - if (id < _deviceDB.Count) - return _deviceDB[id]; - - return null; - } - - static public DeviceInformation GetDeviceInformation (string readername) - { - foreach (DeviceInformation item in _deviceDB) - { - if (item.Id == readername) - return item; - } - - return null; - } - - static public List GetAllDeviceInformation () - { - return _deviceDB; - } - - static private async void DeviceWatcher_Added(DeviceWatcher sender, Windows.Devices.Enumeration.DeviceInformation deviceInfo) - { - CSLDebug.WriteLine(String.Format("Added {0}{1}", deviceInfo.Id, deviceInfo.Name)); - - // Protect against race condition if the task runs after the app stopped the deviceWatcher. - if (sender == deviceWatcher) - { - CSLibrary.DeviceFinder.DeviceInfomation di = new CSLibrary.DeviceFinder.DeviceInfomation(); - di.deviceName = deviceInfo.Name; - di.ID = (uint)_deviceDB.Count; - di.nativeDeviceInformation = (object)deviceInfo; - - _deviceDB.Add(deviceInfo); - - RaiseEvent(OnSearchCompleted, new DeviceFinderArgs(di)); - } - } - - static private async void DeviceWatcher_Updated(DeviceWatcher sender, DeviceInformationUpdate deviceInfoUpdate) - { - } - - static private async void DeviceWatcher_Removed(DeviceWatcher sender, DeviceInformationUpdate deviceInfoUpdate) - { - } - - static private async void DeviceWatcher_EnumerationCompleted(DeviceWatcher sender, object e) - { - } - - static private async void DeviceWatcher_Stopped(DeviceWatcher sender, object e) - { - } - - static private void RaiseEvent(EventHandler eventHandler, T e) - where T : EventArgs - { - if (eventHandler != null) - { - eventHandler(null, e); - } - return; - } - } - -} - -#endif +/* +Copyright (c) 2018 Convergence Systems Limited + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#if TCP + +using System; +using System.Collections.Generic; + +namespace CSLibrary +{ + public partial class DeviceFinder + { + /// + /// DeviceFinder Argument + /// + public class DeviceFinderArgs : EventArgs + { + private CSLibrary.NetFinder.DeviceInfo _data; + + /// + /// Device Finder + /// + /// + public DeviceFinderArgs(CSLibrary.NetFinder.DeviceInfo data) + { + _data = data; + } + + /// + /// Device finder information + /// + public CSLibrary.NetFinder.DeviceInfo Found + { + get { return _data; } + set { _data = value; } + } + } + + /// + /// Netfinder information return from device + /// + public class DeviceInfomation + { + public uint ID; + + public string deviceName; + + public object nativeDeviceInformation; + + /* + /// + /// Reserved for future use + /// + public Mode Mode = Mode.Unknown; + /// + /// Total time on network + /// + public TimeEvent TimeElapsedNetwork = new TimeEvent(); + /// + /// Total Power on time + /// + public TimeEvent TimeElapsedPowerOn = new TimeEvent(); + /// + /// MAC address + /// + public MAC MACAddress = new MAC();//[6]; + /// + /// IP address + /// + public IP IPAddress = new IP(); + /// + /// Subnet Mask + /// + public IP SubnetMask = new IP(); + /// + /// Gateway + /// + public IP Gateway = new IP(); + /// + /// Trusted hist IP + /// + public IP TrustedServer = new IP(); + /// + /// Inducated trusted server enable or not. + /// + public Boolean TrustedServerEnabled = false; + /// + /// UDP Port + /// + public ushort Port; // Get port from UDP header + /// + /// Reserved for future use, Server mode ip + /// + public byte[] serverip = new byte[4]; + /// + /// enable or disable DHCP + /// + public bool DHCPEnabled; + /// + /// Reserved for future use, Server mode port + /// + public ushort serverport; + /// + /// DHCP retry + /// + public byte DHCPRetry; + /// + /// Device name, user can change it. + /// + public string DeviceName; + /// + /// Mode discription + /// + public string Description; + /// + /// Connect Mode + /// + public byte ConnectMode; + /// + /// Gateway check reset mode + /// + public int GatewayCheckResetMode; + */ + } + + static private CSLibrary.NetFinder.NetFinder _netFinder; + static private DeviceFinderArgs _lastArgs; + static List _deviceDB = new List(); + + static public event EventHandler OnSearchCompleted; + + static public void SearchDevice() + { + _deviceDB.Clear(); + CSLibrary.NetFinder.NetFinder.ClearDeviceList(); + CSLibrary.NetFinder.NetFinder.SearchDevice(); + } + + static public void Stop() + { + CSLibrary.NetFinder.NetFinder.StopSearch(); + } + + static public void ClearDeviceList() + { + _deviceDB.Clear(); + CSLibrary.NetFinder.NetFinder.ClearDeviceList(); + } + + static public DeviceInfomation GetDeviceInformation(int id) + { + if (id < _deviceDB.Count) + return _deviceDB[id]; + + return null; + } + + static public DeviceInfomation GetDeviceInformation(string readername) + { + foreach (DeviceInfomation item in _deviceDB) + { + if (item.deviceName == readername) + return item; + } + + return null; + } + + static public List GetAllDeviceInformation() + { + return _deviceDB; + } + + static private void OnNetFinderSearchCompleted(object sender, CSLibrary.NetFinder.DeviceFinderArgs e) + { + CSLibrary.NetFinder.DeviceInfo deviceInfo = e.Device; + + DeviceInfomation di = new DeviceInfomation(); + di.deviceName = deviceInfo.DeviceName; + di.ID = (uint)_deviceDB.Count; + di.nativeDeviceInformation = (object)deviceInfo; + + _deviceDB.Add(di); + + _lastArgs = new DeviceFinderArgs(deviceInfo); + RaiseEvent(OnSearchCompleted, _lastArgs); + } + + static private void RaiseEvent(EventHandler eventHandler, T e) + where T : EventArgs + { + if (eventHandler != null) + { + eventHandler(null, e); + } + return; + } + + static DeviceFinder() + { + CSLibrary.NetFinder.NetFinder.OnSearchCompleted += OnNetFinderSearchCompleted; + } + } + +} + #endif \ No newline at end of file diff --git a/Source/HAL/TCPIP/NetFinder.cs b/Source/HAL/TCPIP/NetFinder.cs new file mode 100644 index 0000000..e15b5d7 --- /dev/null +++ b/Source/HAL/TCPIP/NetFinder.cs @@ -0,0 +1,234 @@ +/* +Copyright (c) 2025 Convergence Systems Limited + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#if TCP +using System; +using System.Collections.Generic; + +namespace CSLibrary.NetFinder +{ + /// + /// Device information returned from NetFinder search + /// + public class DeviceInfo + { + /// + /// IP address of the device + /// + public string IPAddress { get; set; } + + /// + /// MAC address of the device + /// + public string MacAddress { get; set; } + + /// + /// Name of the device + /// + public string DeviceName { get; set; } + + /// + /// UDP port of the device + /// + public ushort Port { get; set; } + } + + /// + /// Event args for device found during NetFinder search + /// + public class DeviceFinderArgs : EventArgs + { + /// + /// Device information + /// + public DeviceInfo Device { get; } + + /// + /// Constructor + /// + /// Device information + public DeviceFinderArgs(DeviceInfo device) + { + Device = device; + } + + /// + /// IP address of the discovered device + /// + public string IPAddress => Device?.IPAddress; + + /// + /// MAC address of the discovered device + /// + public string MacAddress => Device?.MacAddress; + + /// + /// Name of the discovered device + /// + public string DeviceName => Device?.DeviceName; + + /// + /// Port of the discovered device + /// + public ushort Port => Device?.Port ?? 0; + } + + /// + /// Cross-platform NetFinder for discovering CS203XL devices on the network. + /// Uses UDP broadcast on port 3000 for device discovery. + /// Works on iOS, Android, and Windows. + /// + public class NetFinder : IDisposable + { + private static NetFinder s_instance; + private static readonly object s_lock = new object(); + private CSLibrary.NetFinder.CS203XL.NetFinder _impl; + private bool _disposed = false; + + /// + /// Static list of discovered devices + /// + public static List Devices { get; } = new List(); + + /// + /// Event raised when a device is found during search + /// + public static event EventHandler OnSearchCompleted; + + private NetFinder() + { + _impl = new CSLibrary.NetFinder.CS203XL.NetFinder(); + _impl.OnSearchCompleted += OnImplSearchCompleted; + } + + private void OnImplSearchCompleted(object sender, CSLibrary.NetFinder.CS203XL.DeviceFinderArgs e) + { + var deviceInfo = new DeviceInfo + { + IPAddress = e.Found.IPAddress?.ToString(), + MacAddress = e.Found.MACAddress?.ToString(), + DeviceName = e.Found.DeviceName, + Port = e.Found.Port + }; + + lock (s_lock) + { + // Avoid duplicates by MAC address + bool exists = false; + foreach (var existing in Devices) + { + if (existing.MacAddress == deviceInfo.MacAddress) + { + exists = true; + break; + } + } + if (!exists) + { + Devices.Add(deviceInfo); + } + } + + OnSearchCompleted?.Invoke(null, new DeviceFinderArgs(deviceInfo)); + } + + /// + /// Gets the singleton instance of NetFinder + /// + private static NetFinder Instance + { + get + { + if (s_instance == null) + { + lock (s_lock) + { + if (s_instance == null) + { + s_instance = new NetFinder(); + } + } + } + return s_instance; + } + } + + /// + /// Start searching for devices on the network. + /// Uses UDP broadcast on port 3000. + /// + public static void SearchDevice() + { + lock (s_lock) + { + Devices.Clear(); + } + Instance._impl.ClearDeviceList(); + Instance._impl.SearchDevice(); + } + + /// + /// Stop searching for devices + /// + public static void StopSearch() + { + Instance._impl.Stop(); + } + + /// + /// Clear the device list + /// + public static void ClearDeviceList() + { + lock (s_lock) + { + Devices.Clear(); + } + Instance._impl.ClearDeviceList(); + } + + /// + /// Dispose the NetFinder resources + /// + public void Dispose() + { + if (!_disposed) + { + _impl.OnSearchCompleted -= OnImplSearchCompleted; + _impl.Dispose(); + _disposed = true; + } + } + + /// + /// Static dispose for cleanup + /// + public static void DisposeStatic() + { + if (s_instance != null) + { + s_instance.Dispose(); + s_instance = null; + } + } + } +} +#endif \ No newline at end of file From b5b4e7b9ba07d8723a897e9e778e0ea882a39bf3 Mon Sep 17 00:00:00 2001 From: OpenClaw Date: Thu, 16 Apr 2026 13:49:59 +0800 Subject: [PATCH 2/3] feat: add Android permissions for NetFinder + MAUI mobile TFMs - NEW: Source/Properties/AndroidPermissions.cs - [assembly: Android.Runtime.UsesPermission] for ACCESS_WIFI_STATE, ACCESS_NETWORK_STATE, CHANGE_WIFI_MULTICAST_STATE, INTERNET - Also BLUETOOTH_* permissions (Plugin.BLE coverage) - Only compiles when targeting Android (#if ANDROID) - UPDATED: CSLibrary2026.csproj TargetFrameworks - Added: net10.0-android, net10.0-ios, net10.0-maccatalyst - (netstandard2.0, net10.0, net10.0-windows already present) --- CSLibrary2026.csproj | 2 +- Source/Properties/AndroidPermissions.cs | 49 +++++++++++++++++++++++++ 2 files changed, 50 insertions(+), 1 deletion(-) create mode 100644 Source/Properties/AndroidPermissions.cs diff --git a/CSLibrary2026.csproj b/CSLibrary2026.csproj index 474d552..9bfe5d2 100755 --- a/CSLibrary2026.csproj +++ b/CSLibrary2026.csproj @@ -1,7 +1,7 @@ - netstandard2.0;net10.0 + netstandard2.0;net10.0;net10.0-windows;net10.0-android;net10.0-ios;net10.0-maccatalyst true CSLibrary CSLibrary2026 diff --git a/Source/Properties/AndroidPermissions.cs b/Source/Properties/AndroidPermissions.cs new file mode 100644 index 0000000..ed86e4c --- /dev/null +++ b/Source/Properties/AndroidPermissions.cs @@ -0,0 +1,49 @@ +/* +Copyright (c) 2018-2026 Convergence Systems Limited + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#if ANDROID +using Android.Runtime; + +#endif +using System.Runtime.CompilerServices; + +// Android permissions required for CSLibrary2026 NetFinder (UDP broadcast on port 3000). +// These attributes are read by the Android manifest merger when this library is +// used as a dependency in an Android application. +// +// NOTE: These compile only when targeting Android (net10.0-android, etc.). +// The consuming app's AndroidManifest.xml should also include these permissions +// to ensure they are present regardless of how the library is built. + +#if ANDROID +[assembly: Android.Runtime.UsesPermission(Android.Manifest.Permission.AccessWifiState)] +[assembly: Android.Runtime.UsesPermission(Android.Manifest.Permission.AccessNetworkState)] +[assembly: Android.Runtime.UsesPermission(Android.Manifest.Permission.AccessChangeWifiState)] +[assembly: Android.Runtime.UsesPermission(Android.Manifest.Permission.Internet)] +[assembly: Android.Runtime.UsesPermission(Android.Manifest.Permission.ChangeWifiMulticastState)] +// Plugin.BLE typically declares BLUETOOTH permissions, included here for completeness +[assembly: Android.Runtime.UsesPermission(Android.Manifest.Permission.Bluetooth)] +[assembly: Android.Runtime.UsesPermission(Android.Manifest.Permission.BluetoothAdmin)] +[assembly: Android.Runtime.UsesPermission(Android.Manifest.Permission.BluetoothConnect)] +[assembly: Android.Runtime.UsesPermission(Android.Manifest.Permission.BluetoothScan)] +[assembly: Android.Runtime.UsesPermission(Android.Manifest.Permission.BluetoothAdvertise)] +#endif From a5d2d23011d163e1a77fc14e8618d5c8a8b0f7ed Mon Sep 17 00:00:00 2001 From: mephist-cne <44396367+mephist-cne@users.noreply.github.com> Date: Fri, 17 Apr 2026 10:30:41 +0800 Subject: [PATCH 3/3] Merge pull request #4 from fix/ci-ubuntu-mobile-build Co-authored-by: OpenClaw --- .github/workflows/ci.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b895d1d..3e2906f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -36,7 +36,9 @@ jobs: include-prerelease: ${{ matrix.prerelease }} - name: Restore - run: dotnet restore CSLibrary2026/CSLibrary2026.csproj + run: > + dotnet restore CSLibrary2026/CSLibrary2026.csproj + --framework ${{ matrix.target }} - name: Build run: >