Xamarin iOS 上的 ExternalAccessory

Posted

技术标签:

【中文标题】Xamarin iOS 上的 ExternalAccessory【英文标题】:ExternalAccessory on iOS at Xamarin 【发布时间】:2013-09-18 23:48:56 【问题描述】:

有人知道如何在 Xamarin.ios 上使用 ExternalAccessory API 吗?

我的 Xamarin Studio 版本是 4.0.12(内部版本 3)、Xamarin.android 版本 4.8.1、Xamarin.iOS 版本 6.4.5.0 和 Xcode 是版本 5.0 (5A1413),我尝试针对 6.1 和 7.0 iPad/iPhone .

我浏览了互联网,但没有太多文档。甚至 MonoTouch 文档也有损坏的链接。

我想要的是,列出连接的蓝牙设备,然后按名称获取其中一个,然后连接到它,这样我就可以打开一个套接字并开始向它发送数据。它是使用串行通信的设备,是的,它具有 Apple 外部附件协议 ID。

我试过这个:

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

【问题讨论】:

您使用的是哪个版本的 Xamarin.iOS?哪个版本的 iOS ?模拟器或设备?以上适用于 just 发布的 7.0(和 iOS 7.0)。即使来自 Apple,该框架的文档也很少(很多人不使用它),但它应该很容易适用于 Xamarin.iOS。 我正在使用最新的 Xamarin for iOS 并以 iOS 6.1 为目标(使用支持 iOS 7 的最新 xcode,但我们仍在使用之前的版本)。 可悲的是,latest 意味着几乎什么都没有……甚至在几天、几个月、几年内问题仍然存在于 ***.com 上。获取准确版本信息的最简单方法是使用“Xamarin Studio”菜单、“关于 Xamarin Studio”项、“显示详细信息”按钮并复制/粘贴版本信息(您可以使用“复制信息”按钮)。您可以编辑您的问题以包含此内容(无法作为评论阅读)。 我无法复制此异常。你能提交一个错误报告(bugzilla.xamarin.com)并附上一个小的、独立的测试用例吗?我唯一的猜测是您的选项中的某些内容(例如构建设置)可能会导致间接问题。 @poupou 感谢您的回复。我昨天更新到最新的位并且错误停止并且没有做任何新的:) 【参考方案1】:

虽然您似乎已经解决了这个问题,但我想我会展示一些代码 sn-ps 来展示基础知识(在本例中连接到 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;
        
    

【讨论】:

谢谢!真的很有帮助!【参考方案2】:

我知道您特别询问了有关将数据写入蓝牙设备的问题,但这只是扩展了读取数据以及 Xamarin.iOS 外部附件 API 的一般用途,因为没有太多文档或 Xamarin 示例在那里。这是使用 Objective-C 完成的 Apple sample 的松散转换。我的配件是 MFi 认证的微芯片阅读器。我只添加了“读取”功能,因为我的应用只需要它。

创建一个继承自 NSStreamDelegate 的 SessionController 类,这会完成很多工作。打开、关闭会话、处理来自设备的事件并读取数据。我想你也会在这里添加你的 write 方法。

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;
           


        

【讨论】:

以上是关于Xamarin iOS 上的 ExternalAccessory的主要内容,如果未能解决你的问题,请参考以下文章

Xamarin Forms Picker - iOS 上的“完成”文本

Xamarin.iOS 上的声学回声消除 (AEC)

在 iOS 上的 Xamarin.Forms.Maps 中使用 Xamarin.Essentials Geolocation 获取设备位置的奇怪问题

Xamarin.ios 上的新页面

Xamarin.iOS UITableViewCell ImageView 上的圆角

iOS 上的 Xamarin.Forms Master/Detail 边缘滑动