echo("備忘録");

IT技術やプログラミング関連など、技術系の事を備忘録的にまとめています。

【Xamarin】ペアリング済のBluetoothデバイスの情報を取得する。

やりたいこと

  • (今も愛用中)のガラケー購入時にBluetoothスピーカーをもらったが、リモコンがなく、音量調整が面倒。

  • iTunesは音量調整出来るが、微妙な音量調節が難しい。(特に手が大きいと)

  • Androidでできるアプリを持ってない。→ならXamarinで作ってみようか。※出来るとは言ってない

ハマった?事

  • 最初は、下記の「Bluetooth LE Plugin for Xamarin」を使おうとしたが、「ペアリング済の機器情報」を取得する方法が分からなかった。(あったならすいません...)

  • Bluetoothの「UUID」は「機器ごと」に個別ではなく「サービス/プロファイル」ごとに個別だった。(同じ「スピーカー」ならUUIDは同じだが、それを知らずにハマった…いい勉強になりました。)

参考にした情報

ソース&結果

※すいません。時間の関係で、ソース&結果のみ表示します。
(詳細は後日追記します。)

■SearchDevicePage.xaml(VolChanger.View)

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="VolChanger.View.SearchDevicePage">
    <ContentPage.Content>
        <StackLayout>
            <ActivityIndicator x:Name="AILoading" IsVisible="{Binding IsSearching}" IsRunning="{Binding IsSearching}" />
            <Button x:Name="ButtonSearch" Text="Bluetoothデバイスを検出する" Command="{Binding GetDevicesCommand}" />
            <ListView x:Name="ListViewDevices" ItemsSource="{Binding DeviceList}">

                <!--ここに ItemTemplate を追加-->
                <ListView.ItemTemplate>
                    <DataTemplate>
                        <TextCell Text="{Binding Name}" Detail="{Binding Detail}"/>
                    </DataTemplate>
                </ListView.ItemTemplate>
            </ListView>
        </StackLayout>
    </ContentPage.Content>
</ContentPage>

■SearchDevicePage.xaml.cs(VolChanger.View)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Xamarin.Forms;
using Xamarin.Forms.Xaml;
using VolChanger.View;
using VolChanger.ViewModel;

using Plugin.BLE.Abstractions;
using Plugin.BLE.Abstractions.Contracts;
using Plugin.BLE.Abstractions.Exceptions;

// using Reactive.Bindings;
// using Reactive.Bindings.Extensions;

namespace VolChanger.View
{
    [XamlCompilation(XamlCompilationOptions.Compile)]
    public partial class SearchDevicePage : ContentPage
    {
        private readonly SearchDeviceViewModel vm;

        public SearchDevicePage ()
        {
            InitializeComponent ();
            this.vm = new SearchDeviceViewModel();
            BindingContext = vm;

            // ButtonSearch.Clicked += ButtonSearch_Clicked;
        }
    }
}

■SearchDeviceViewModel.cs(VolChanger.ViewModel)

using System;
using System.Collections.Generic;
using System.Text;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.Net.Http;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;

using VolChanger.View;
using VolChanger.ViewModel;
using VolChanger.Model;

using Plugin.BLE.Abstractions;
using Plugin.BLE.Abstractions.Contracts;
using Plugin.BLE.Abstractions.Exceptions;
using Xamarin.Forms;
using Plugin.BLE.Abstractions.EventArgs;

namespace VolChanger.ViewModel
{
    public class SearchDeviceViewModel : INotifyPropertyChanged
    {
        public ObservableCollection<BTDevice> DeviceList { get; set; } 
        public Command GetDevicesCommand { get; }
        public event PropertyChangedEventHandler PropertyChanged;
        private static PropertyChangedEventArgs SearchPropertyChangedEventArgs = new PropertyChangedEventArgs(nameof(IsSearching));

        bool isSearching = false;
        public bool IsSearching
        {
            get { return isSearching; }
            set
            {
                if(isSearching == value) { return; }

                isSearching = value;
                OnPropertyChanged(nameof(IsSearching));

                // CanExecute (このコマンドが実行可能かどうか)を更新
                GetDevicesCommand.ChangeCanExecute();
            }
        }

        public SearchDeviceViewModel()
        {
            DeviceList = new ObservableCollection<BTDevice>();

            GetDevicesCommand = new Command(
                () => GetDevices(), // 実行する処理
                () => !IsSearching); // このコマンドが実行可能の時にtrueを返す
        }

        private void GetDevices()
        {
            if (IsSearching)
                return;
            
            try
            {
                IsSearching = true;

                DeviceList.Clear();
                
                IBTManager manager = DependencyService.Get<IBTManager>();
                var list = manager.GetBondedDevices();

                // await Adapter.StartScanningForDevicesAsync();
                if(list != null && list.Count > 0)
                {
                    foreach(var dev in list)
                    {
                        DeviceList.Add(dev);
                    }
                }
                else
                { 
                    Application.Current.MainPage.DisplayAlert("VolChanger", "ペアリング済デバイスが見つかりませんでした。", "OK");
                }
            }
            catch (Exception e)
            {
                Application.Current.MainPage.DisplayAlert("Error!", e.Message, "OK");
            }
            finally
            {
                IsSearching = false;
            }
        }

        void OnPropertyChanged([CallerMemberName] string name = null) =>
            PropertyChanged?.Invoke(this, SearchPropertyChangedEventArgs);
    }
}

■BTDevice.cs(VolChanger.Model)

using System;
using System.Collections.Generic;
using System.Text;

namespace VolChanger.Model
{
    public class BTDevice
    {
        public string Name { get; set; }
        public string Uuid { get; set; }
        public string Adress { get; set; }
        public string Detail { get; set; }
    }
}

■IBTManager.cs(VolChanger.Model)

using System;
using System.Collections.Generic;
using System.Text;

using VolChanger.Model;

namespace VolChanger.Model
{
    public interface IBTManager
    {
        List<BTDevice> GetBondedDevices();
    }
}

■BTManager.cs(VolChanger.Android)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Android.App;
using Android.Content;
using Android.OS;
using Android.Runtime;
using Android.Views;
using Android.Widget;

using VolChanger.Model;
using Android.Bluetooth;

using Xamarin.Forms;

[assembly: Dependency(typeof(VolChanger.Droid.BTManager))]

namespace VolChanger.Droid
{
    public class BTManager: IBTManager
    {
        
        public List<BTDevice> GetBondedDevices()
        {
            // アダプター作成
            BluetoothAdapter adapter = BluetoothAdapter.DefaultAdapter; 

             // ペアリング済みデバイスの取得
            var listBondedDevices = adapter.BondedDevices;              
            var result = new List<BTDevice>();

            if (listBondedDevices != null && listBondedDevices.Count > 0)
            {
                foreach (var device in listBondedDevices)
                {
                    var btDevice = new BTDevice();
                    btDevice.Uuid = device.GetUuids().FirstOrDefault().ToString();

                    // Bluetoothスピーカーのみ取得する。
                    if (btDevice.Uuid.Substring(4, 4).ToUpper() == "110A" ||
                        btDevice.Uuid.Substring(4, 4).ToUpper() == "110B")
                    {
                        btDevice.Name = device.Name;
                        btDevice.Adress = device.Address;
                        btDevice.Detail = string.Format("MACアドレス:{0}", btDevice.Adress);
                        result.Add(btDevice);
                    }
                }
            }
            return result;
        }
    }
}

f:id:Makky12:20181201191328j:plain
結果(MACアドレスは一部編集で消してます)

あとは「音量の調整」なんですが、ここからが本当に難しいところでしょうね。

...まあ、それは旅行から帰ってきてからの続き、ということで。