ExternalAccessory на iOS в Xamarin

Кто-нибудь знает, как использовать API ExternalAccessory в Xamarin.iOS?

Моя версия Xamarin Studio — 4.0.12 (сборка 3), Xamarin.Android — версия 4.8.1, Xamarin.iOS — версия 6.4.5.0, а Xcode — версия 5.0 (5A1413), и я пытался использовать iPad/iPhone 6.1 и 7.0.

Я ходил по Интернету, и там не так много документации. Даже в документах MonoTouch есть неработающие ссылки.

Я хочу перечислить подключенные устройства Bluetooth, получить одно из них по имени, а затем подключиться к нему, чтобы я мог открыть сокет и начать отправлять на него данные. Это устройство, использующее последовательную связь, и да, оно имеет идентификатор внешнего аксессуара Apple.

Я пробовал это:

var am = EAAccessoryManager.SharedAccessoryManager;

Он просто выдает мне исключение InvaidCastException.

Любые подсказки?

Спасибо! Я очень ценю помощь.

PS: сведения о Xamarin

Xamarin Studio
Version 4.0.12 (build 3)
Installation UUID: 7348d641-ed6d-4c8a-b59a-116674e06dfd
Runtime:
    Mono 3.2.0 ((no/7c7fcc7)
    GTK 2.24.20
    GTK# (2.12.0.0)
    Package version: 302000000

[...]

Apple Developer Tools
Xcode 5.0 (3332.25)
Build 5A1413

[...]

Xamarin.iOS
Version: 6.4.5.0 (Trial Edition)
Hash: 1336a36
Branch: 
Build date: 2013-10-09 11:14:45-0400

Build Information
Release ID: 400120003
Git revision: 593d7acb1cb78ceeeb482d5133cf1fe514467e39
Build date: 2013-08-07 20:30:53+0000
Xamarin addins: 25a0858b281923e666b09259ad4746b774e0a873

Operating System
Mac OS X 10.8.5
Darwin Gutembergs-MacBook-Pro.local 12.5.0 Darwin Kernel Version 12.5.0
    Mon Jul 29 16:33:49 PDT 2013
    root:xnu-2050.48.11~1/RELEASE_X86_64 x86_64

person Gutemberg Ribeiro    schedule 18.09.2013    source источник
comment
Какую версию Xamarin.iOS вы используете? и какая версия iOS? симулятор или устройства? Вышеописанное отлично работает с только что выпущенной версией 7.0 (и iOS 7.0). Документация по платформе скудна (она не используется многими людьми) даже от Apple, но ее легко применить к Xamarin.iOS.   -  person poupou    schedule 19.09.2013
comment
Я использую последнюю версию Xamarin для iOS и ориентируюсь на iOS 6.1 (с последней версией xcode, которая поддерживает iOS 7, но мы все еще используем предыдущую версию).   -  person Gutemberg Ribeiro    schedule 19.09.2013
comment
К сожалению, последний означает почти ничего... еще меньше через несколько дней, месяцев, лет, когда вопрос все еще будет на stackoverflow.com. Самый простой способ получить точную информацию о версии — использовать меню Xamarin Studio, пункт «О Xamarin Studio», кнопку «Показать подробности» и скопировать/вставить информацию о версии (можно использовать кнопку «Копировать информацию»). Вы можете отредактировать свой вопрос, чтобы включить это (это не будет читаться как комментарий).   -  person poupou    schedule 19.09.2013
comment
Я не могу дублировать это исключение. Можете ли вы отправить отчет об ошибке (bugzilla.xamarin.com) и приложить небольшой автономный тестовый пример? Мое единственное предположение заключается в том, что что-то в ваших параметрах (например, в настройках сборки) может вызывать косвенную проблему.   -  person poupou    schedule 20.09.2013
comment
@poupou Спасибо за ответ. Я вчера обновился до последних битов, и ошибка прекратилась и ничего нового не сделал :)   -  person Gutemberg Ribeiro    schedule 20.09.2013


Ответы (2)


Хотя кажется, что вы с этим справились, я решил показать несколько фрагментов кода, которые показывают основы (в данном случае подключение к Sphero и его зеленый цвет):

EAAccessoryManager mgr = EAAccessoryManager.SharedAccessoryManager;
var accessories = mgr.ConnectedAccessories;
foreach(var accessory in accessories)
{
    myLabel.Text = "Got me an accessory";
    Console.WriteLine(accessory.ToString());
    Console.WriteLine(accessory.Name);
    var protocol = "com.orbotix.robotprotocol";

    if(accessory.ProtocolStrings.Where(s => s == protocol).Any())
    {
        myLabel.Text = "Got me a Sphero";

        var session = new EASession(accessory, protocol);
        var outputStream = session.OutputStream;
        outputStream.Delegate = new MyOutputStreamDelegate(myLabel);
        outputStream.Schedule(NSRunLoop.Current, "kCFRunLoopDefaultMode");
        outputStream.Open();
    }
}

а также

public class MyOutputStreamDelegate : NSStreamDelegate
{
    UILabel label;
    bool hasWritten = false;

    public MyOutputStreamDelegate(UILabel label)
    {
        this.label = label;
    }
    public override void HandleEvent(NSStream theStream, NSStreamEvent streamEvent)
    {
        if(streamEvent == NSStreamEvent.HasSpaceAvailable && ! hasWritten)
        {
            //Set the color of the Sphero
            var written = ((NSOutputStream)theStream).Write(new byte[] {0xFF, 0xFF, 0x02, 0x20, 0x0e, 0x05, 0x1F, 0xFF, 0x1B, 0x00, 0x91}, 11);
            if(written == 11)
            {
                label.Text = "Sphero should be green";
            }
            hasWritten = true;
        }
    }
}
person Larry OBrien    schedule 20.09.2013
comment
Спасибо! Это действительно помогло! - person Gutemberg Ribeiro; 21.09.2013

Я знаю, что вы специально спрашивали о записи данных на устройство Bluetooth, но это просто расширение чтения данных, а также общее использование API внешних аксессуаров для Xamarin.iOS, потому что там не так много документации или примеров Xamarin. Это свободное преобразование из Образец Apple, выполненный с помощью Objective-C. Моим аксессуаром был сертифицированный MFi считыватель микрочипов. Я добавил только функцию «чтения», так как мне это нужно было только для моего приложения.

Создайте класс SessionController, наследуемый от NSStreamDelegate, и это сделает большую часть работы. Открывает, закрывает сеансы, обрабатывает события с устройства и считывает данные. Я думаю, вы бы добавили сюда и свои методы записи.

public class EASessionController : NSStreamDelegate
{

    NSString SessionDataReceivedNotification = (NSString)"SessionDataReceivedNotification";

    public static EAAccessory _accessory;
    public static string _protocolString;

    EASession _session;
    NSMutableData _readData;

    public static EASessionController SharedController()
    {
        EASessionController sessionController = null;

        if (sessionController == null)
        {
            sessionController = new EASessionController();
        }

        return sessionController;

    }

    public void SetupController(EAAccessory accessory, string protocolString)
    {

        _accessory = accessory;
        _protocolString = protocolString;

    }

    public bool OpenSession()
    {

        Console.WriteLine("opening new session");

        _accessory.WeakDelegate = this;

        if (_session == null)
            _session = new EASession(_accessory, _protocolString);

        // Open both input and output streams even if the device only makes use of one of them

        _session.InputStream.Delegate = this;
        _session.InputStream.Schedule(NSRunLoop.Current, NSRunLoopMode.Default);
        _session.InputStream.Open();

        _session.OutputStream.Delegate = this;
        _session.OutputStream.Schedule(NSRunLoop.Current, NSRunLoopMode.Default);
        _session.OutputStream.Open();

        return (_session != null);

    }

    public void CloseSession()
    {
        _session.InputStream.Unschedule(NSRunLoop.Current, NSRunLoopMode.Default);
        _session.InputStream.Delegate = null;
        _session.InputStream.Close();

        _session.OutputStream.Unschedule(NSRunLoop.Current, NSRunLoopMode.Default);
        _session.OutputStream.Delegate = null;
        _session.OutputStream.Close();

        _session = null;

    }


    /// <summary>
    /// Get Number of bytes to read into local buffer
    /// </summary>
    /// <returns></returns>
    public nuint ReadBytesAvailable()
    {
        return _readData.Length;
    }



    /// <summary>
    /// High level read method
    /// </summary>
    /// <param name="bytesToRead"></param>
    /// <returns></returns>
    public NSData ReadData(nuint bytesToRead)
    {

        NSData data = null;

        if (_readData.Length >= bytesToRead)
        {
            NSRange range = new NSRange(0, (nint)bytesToRead);
            data = _readData.Subdata(range);
            _readData.ReplaceBytes(range, IntPtr.Zero, 0);
        }

        return data;

    }

    /// <summary>
    /// Low level read method - read data while there is data and space in input buffer, then post notification to observer
    /// </summary>
    void ReadData()
    {

        nuint bufferSize = 128;
        byte[] buffer = new byte[bufferSize];

        while (_session.InputStream.HasBytesAvailable())
        {
            nint bytesRead = _session.InputStream.Read(buffer, bufferSize);

            if (_readData == null)
            {
                _readData = new NSMutableData(); 
            }
            _readData.AppendBytes(buffer, 0, bytesRead);
            Console.WriteLine(buffer);


        }

        // We now have our data from the device (stored in _readData), so post the notification for an observer to do something with the data

        NSNotificationCenter.DefaultCenter.PostNotificationName(SessionDataReceivedNotification, this);

    }


    /// <summary>
    /// Handle the events occurring with the external accessory
    /// </summary>
    /// <param name="theStream"></param>
    /// <param name="streamEvent"></param>
    public override void HandleEvent(NSStream theStream, NSStreamEvent streamEvent)
    {

        switch (streamEvent)
        {

            case NSStreamEvent.None:
                Console.WriteLine("StreamEventNone");
                break;
            case NSStreamEvent.HasBytesAvailable:
                Console.WriteLine("StreamEventHasBytesAvailable");
                ReadData();
                break;
            case NSStreamEvent.HasSpaceAvailable:
                Console.WriteLine("StreamEventHasSpaceAvailable");
                // Do write operations to the device here
                break;
            case NSStreamEvent.OpenCompleted:
                Console.WriteLine("StreamEventOpenCompleted");
                break;
            case NSStreamEvent.ErrorOccurred:
                Console.WriteLine("StreamEventErroOccurred");
                break;
            case NSStreamEvent.EndEncountered:
                Console.WriteLine("StreamEventEndEncountered");
                break;
            default:
                Console.WriteLine("Stream present but no event");
                break;

        }
    }

}

В моем ViewController, который будет отображать данные, которые я только что прочитал из внешнего аксессуара, мы все это подключаем. В ViewDidLoad создайте наблюдателей, чтобы представление знало, когда событие было запущено устройством. Также убедитесь, что мы подключены к правильному аксессуару, и откройте сеанс.

  public EASessionController _EASessionController;
  EAAccessory[] _accessoryList;
  EAAccessory _selectedAccessory;
  NSString SessionDataReceivedNotification = (NSString)"SessionDataReceivedNotification";
  string myDeviceProtocol = "com.my-microchip-reader.1234";

  public override void ViewDidLoad()
    {
        base.ViewDidLoad();

        NSNotificationCenter.DefaultCenter.AddObserver(EAAccessoryManager.DidConnectNotification, EADidConnect);
        NSNotificationCenter.DefaultCenter.AddObserver(EAAccessoryManager.DidDisconnectNotification, EADidDisconnect);
        NSNotificationCenter.DefaultCenter.AddObserver(SessionDataReceivedNotification, SessionDataReceived);
        EAAccessoryManager.SharedAccessoryManager.RegisterForLocalNotifications();



        _EASessionController = EASessionController.SharedController();
        _accessoryList = EAAccessoryManager.SharedAccessoryManager.ConnectedAccessories;

        foreach (EAAccessory acc in _accessoryList)
        {
            if (acc.ProtocolStrings.Contains(myDeviceProtocol))
            {
                // Connected to the correct accessory
                _selectedAccessory = acc;
                _EASessionController.SetupController(acc, myDeviceProtocol);
                _EASessionController.OpenSession();
                lblEAConnectionStatus.Text = acc.Name;

                Console.WriteLine("Already connected via bluetooth");

            }
            else
            {
                // Not connected
            }
        }

}

Создайте методы DidConnect, DidDisconnect и SessionDataReceived. Имя устройства просто обновляется на некоторых ярлыках при подключении/отключении, и я отображаю данные в текстовом поле.

void EADidConnect(NSNotification notification)
    {
        EAAccessory connectedAccessory = (EAAccessory)notification.UserInfo.ObjectForKey((NSString)"EAAccessoryKey");
        Console.WriteLine("I did connect!!");
        _accessoryList = EAAccessoryManager.SharedAccessoryManager.ConnectedAccessories;

        // Reconnect and open the session in case the device was disconnected
        foreach (EAAccessory acc in _accessoryList)
        {
            if (acc.ProtocolStrings.Contains(myDeviceProtocol))
            {
                // Connected to the correct accessory
                _selectedAccessory = acc;
                Console.WriteLine(_selectedAccessory.ProtocolStrings);

                _EASessionController.SetupController(acc, myDeviceProtocol);
                _EASessionController.OpenSession();

            }
            else
            {
                // Not connected
            }
        }

        Console.WriteLine(connectedAccessory.Name);

        // Update a label to show it's connected
        lblEAConnectionStatus.Text = connectedAccessory.Name;

    }

    void EADidDisconnect(NSNotification notification)
    {

        Console.WriteLine("Accessory disconnected");
        _EASessionController.CloseSession();
        lblEAConnectionStatus.Text = string.Empty;

    }


    /// <summary>
    /// Data receieved from accessory
    /// </summary>
    /// <param name="notification"></param>
    void SessionDataReceived(NSNotification notification)
    {

        EASessionController sessionController = (EASessionController)notification.Object;

        nuint bytesAvailable = 0;


        while ((bytesAvailable = sessionController.ReadBytesAvailable()) > 0)
        {

            // read the data as a string

            NSData data = sessionController.ReadData(bytesAvailable);
            NSString chipNumber = new NSString(data, NSStringEncoding.UTF8);

           // Displaying the data
            txtMircochipNumber.Text = chipNumber;
           }


        }
person Tahari    schedule 13.07.2017