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 上的“完成”文本
在 iOS 上的 Xamarin.Forms.Maps 中使用 Xamarin.Essentials Geolocation 获取设备位置的奇怪问题