FirstAppのダウンロード
- 次のリンクからFirstAppのzipファイルをダウンロードします。
- zipファイルを解凍すると次のファイルが得られます。
FirstApp
FirstApp.xcodeproj - XcodeでFirstApp.xcodeprojを開きます。
- iOSデバイスとMac接続し、XcodeでFirstAppをビルドします。
FirstAppの動作手順
- iOS デバイスの[設定]を起動し、Bluetooth をオンにします。
- OPH-5000iサンプルアプリケーションでiOS12桁のBluetoothアドレスバーコードを読み取ります。
- 接続済みの状態になりましたら、本サンプル(FirstApp)を起動します。
- データを受信します
OPH-5000iサンプルアプリケーションでバーコードを読み取ります。
本サンプル(FirstApp)のテキストビューにOPH-5000iから送信したデータを表示されます。 - データを送信します。
「Sent TestString」ボタンを押すと「TestString」文字列をOPH-5000iに送信します。
OPHBluetoothServiceクラスは、OPH-5000iとBluetooth通信を行い、セッション管理、アクセサリ保持、I/O処理等を担当するコアクラスです。
OPHBluetoothServiceは、GoFのSingletonパターンで実装しています。OPHBluetoothServiceクラスのインスタンスは、実行中ただ1つしか存在しません。
OPHBluetoothServiceクラスでは、外部アクセサリとの通信にExternalAccessory.frameworkを利用します。
サンプル:OPHBluetoothService.m
//
// OPHBluetoothService.m
//
// Copyright (c) 2021年OPTOELECTRONICS CO., LTD. All rights reserved.
//
#import "OPHBluetoothService.h"
/** OPHBluetoothServiceは常に現在のアクセサリ、プロトコル、セッション、読み書きバッファをつずつ保持します。
* setupControllerForAccessory アクセサリ、プロトコルの初期化(セッション、バッファが破棄されていない場合は破棄)
* openSession セッション、バッファの生成(アクセサリ、プロトコルが初期化されていない場合は無効)
* closeSession セッション、バッファの破棄(セッション、バッファが生成されていない場合は無効)
*/
@interface OPHBluetoothService() {
EAAccessory *__weak _accessory; // 現在接続しているEAAccessory
EASession *_currentSession; // 現在接続しているセッション
NSString *_protocolString; // 現在接続しているprotocolString
NSMutableData *_writeData; // 書込データのバッファ
NSMutableData *_readData; // 読込データのバッファ
NSMutableArray *_sessions; // 生成したセッション
}
@end
static NSString * const OPH5000iProtocolString = @"jp.opto.opnprotocol";
static NSString * const OPH5000iAccessoryModelNumber = @"OPH-5000i";
//**************************************************************************************************
// @name OPHBluetoothService
//**************************************************************************************************
@implementation OPHBluetoothService
- (id)init
{
self = [super init];
if (self) {
_sessions = [[NSMutableArray alloc] init];
}
return self;
}
#pragma mark - Memory Management
//--------------------------------------------------------------------------------------------------
// Memory Management
//--------------------------------------------------------------------------------------------------
- (void)dealloc
{
[self closeSession];
RELEASE_TO_NIL(_currentSession);
RELEASE_TO_NIL(_writeData);
RELEASE_TO_NIL(_readData);
RELEASE_TO_NIL(_protocolString);
RELEASE_TO_NIL(_accessory);
RELEASE_TO_NIL(_sessions);
}
//--------------------------------------------------------------------------------------------------
// Public Methods
//--------------------------------------------------------------------------------------------------
- (void)setupControllerForAccessory:(EAAccessory *)accessory_ withProtocolString:(NSString *)protocolString
{
// 引数チェック
if (accessory_ == nil) {
NSLog(@"arguments error: accessory is nil.");
return;
}
if (protocolString == nil) {
NSLog(@"arguments error: protocolString is nil.");
return;
}
// アクセサリ、セッションの両方が存在する場合
if (_accessory && _currentSession) {
// 与えられたアクセサリの接続IDが現在と同じ場合、何もしない。
if (accessory_.connectionID == _currentSession.accessory.connectionID) {
NSLog(@"session is already connected.");
return;
}
// 接続IDが異なる場合は、現在のセッションをクローズする。
else {
[self closeSession];
}
}
_accessory = nil;
_accessory = accessory_;
_accessory.delegate = self;
RELEASE_TO_NIL(_protocolString);
_protocolString = [protocolString copy];
NSLog(@"accessory changed. modelNumber:%@, connectionID:%lu", _accessory.modelNumber, (unsigned long)_accessory.connectionID);
}
- (BOOL)openSession
{
if (_accessory == nil) {
[self _initAccessoryAndProtocolString];
if (_accessory == nil) {
NSLog(@"accessory not found.");
return NO;
}
_accessory.delegate = self;
}
NSLog(@"accessory found. modelNumber:%@, connectionID:%lu", _accessory.modelNumber, (unsigned long)_accessory.connectionID);
// ModelNumbeOPH-5000iの場合openSession
if (![_accessory.modelNumber isEqualToString:OPH5000iAccessoryModelNumber]){
NSLog(@"ModelNumber is not OPH-5000i.");
return NO;
}
if ( _currentSession && _currentSession.accessory.connectionID == _accessory.connectionID ) {
NSLog(@"session is already opened.");
return YES;
}
// これまで生成されたセッションがあるか
BOOL exists = NO;
for (EASession *s in _sessions) {
if (s.accessory.connectionID == _accessory.connectionID) {
_currentSession = s;
exists = YES;
NSLog(@"session found already opened in past.");
break;
}
}
// 現在のセッションがない場合、または、セッションありかつこれまで生成されていない場合、
if (_currentSession == nil || !exists) {
EASession *session = [[EASession alloc] initWithAccessory:_accessory
forProtocol:OPH5000iProtocolString];
if (!session) {
NSLog(@"create session failed. session is aleary exists?");
return NO;
}
[_sessions addObject:session];
_currentSession = session;
NSLog(@"new session created.");
[[_currentSession inputStream] setDelegate:self];
[[_currentSession inputStream] scheduleInRunLoop:[NSRunLoop currentRunLoop]
forMode:NSDefaultRunLoopMode];
[[_currentSession inputStream] open];
[[_currentSession outputStream] setDelegate:self];
[[_currentSession outputStream] scheduleInRunLoop:[NSRunLoop currentRunLoop]
forMode:NSDefaultRunLoopMode];
[[_currentSession outputStream] open];
NSLog(@"created session:%@", _currentSession);
return YES;
}
else {
NSLog(@"create session fail by accessory. modelNumber:%@, connectionID:%lu", _accessory.modelNumber, (unsigned long)_accessory.connectionID);
return NO;
}
}
- (void)closeSession
{
if (_currentSession) {
if (_writeData) {
[_writeData setLength:0];
}
if (_readData) {
[_readData setLength:0];
}
RELEASE_TO_NIL(_writeData);
RELEASE_TO_NIL(_readData);
NSLog(@"current session closed.");
}
else {
NSLog(@"current session already closed.");
}
}
- (BOOL)writeData:(NSData *)data
{
RELEASE_TO_NIL(_writeData)
if (_writeData == nil) {
_writeData = [[NSMutableData alloc] init];
}
#define MAX_OPH_RECEIVE_SIZE 2036
if (data.length > MAX_OPH_RECEIVE_SIZE){
NSLog(@"over size error.");
return NO;
}
[_writeData appendData:data];
[self _writeData];
return YES;
}
- (void)writeACK
{
RELEASE_TO_NIL(_writeData)
_writeData = [[NSMutableData alloc] init];
uint8_t ack[1] = {0x06};
[self writeData:[NSData dataWithBytes:ack length:sizeof(ack)]];
}
- (void)writeNAK
{
_writeData = [[NSMutableData alloc] init];
uint8_t ack[1] = {0x15};
[self writeData:[NSData dataWithBytes:ack length:sizeof(ack)]];
}
#pragma mark - EAAccessoryDelegate
//--------------------------------------------------------------------------------------------------
// EAAccessoryDelegate
//--------------------------------------------------------------------------------------------------
/** EAAccessoryが切断された場合に呼び出されます。
*/
- (void)accessoryDidDisconnect:(EAAccessory *)accessory_
{
EASession *removableSession = nil;
for (EASession *session in _sessions) {
if (session.accessory.connectionID == accessory_.connectionID) {
removableSession = session;
break;
}
}
if (removableSession) {
[_sessions removeObject:removableSession];
if ([removableSession isEqual:_currentSession]) {
[[_currentSession inputStream] close];
[[_currentSession outputStream] close];
_currentSession = nil;
}
NSLog(@"session closed and removed when accessory did disconnected.");
}
}
#pragma mark - NSStreamDelegateEventExtensions
//--------------------------------------------------------------------------------------------------
// NSStreamDelegateEventExtensions
//--------------------------------------------------------------------------------------------------
/** NSStreamのイベントハンドラです。
* @param aStream
* @param eventCode
*/
- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode
{
switch (eventCode) {
case NSStreamEventNone:
break;
case NSStreamEventOpenCompleted:
break;
case NSStreamEventHasBytesAvailable:
[self _readData];
break;
case NSStreamEventHasSpaceAvailable:
[self _writeData];
break;
case NSStreamEventErrorOccurred:
break;
case NSStreamEventEndEncountered:
break;
default:
break;
}
}
#pragma mark - Private Methods
//--------------------------------------------------------------------------------------------------
// Private Methods
//--------------------------------------------------------------------------------------------------
/** セッションにデータを書き込みます。
* _sessionのoutputStreamに書き込めるスペースがある場合、かつ、書込バッファ(_writeData)にデータが存在する場合に
* _sessionへの書込を続けます。
*/
- (void)_writeData
{
while (([[_currentSession outputStream] hasSpaceAvailable]) && ([_writeData length] > 0)) {
NSInteger bytesWritten = [[_currentSession outputStream] write:[_writeData bytes]
maxLength:[_writeData length]];
NSLog(@"Written Data : %@ , bytesWritten = %ld", _writeData, (long)bytesWritten);
if (bytesWritten == -1) {
NSLog(@"Write error");
break;
}
else if (bytesWritten > 0) {
[_writeData replaceBytesInRange:NSMakeRange(0, bytesWritten) withBytes:NULL length:0];
}
}
}
/** セッションのデータを読み込みます。
* _sessionのinputStreamにデータが存在する場合、読込バッファ(_readData)への読込を続けます。
*/
- (void)_readData
{
#define EAD_INPUT_BUFFER_SIZE 2048
uint8_t buf[EAD_INPUT_BUFFER_SIZE] = {0};
while ([[_currentSession inputStream] hasBytesAvailable]) {
NSInteger bytesRead = [[_currentSession inputStream] read:buf maxLength:EAD_INPUT_BUFFER_SIZE];
if (_readData == nil) {
_readData = [[NSMutableData alloc] init];
}
[_readData appendBytes:(void *)buf length:bytesRead];
// inputStream のバッファに蓄積させるためにスリープして待機する
[NSThread sleepForTimeInterval:0.1];
}
if ([_readData length] != 0) {
[self receivedData:_readData];
}
}
- (void)receivedData:(NSData *)readdata
{
NSLog(@"receivedData : _readData : %@",_readData);
id<OPHBluetoothServiceDelegate> delegate =
(id<OPHBluetoothServiceDelegate>)self.delegate;
//outputSteamの受信完了後、OPH-5000iにACKを返信する必要がある
[self writeACK];
if ([delegate respondsToSelector:@selector(bluetoothService:receivedData:)]) {
[delegate bluetoothService:self receivedData:readdata];
}
[self performSelector:@selector(clearReadData)];
}
- (void)clearReadData
{
[_readData setLength:0];
}
/** EAAccessoryManager から接続中のEAAccessoryを取得し、プロパティに設定します。
*/
- (void)_initAccessoryAndProtocolString
{
NSArray *connectedAccessories =
[[NSMutableArray alloc] initWithArray:
[[EAAccessoryManager sharedAccessoryManager] connectedAccessories]];
if (connectedAccessories == nil || connectedAccessories.count == 0) {
return;
}
for (EAAccessory *accessory in connectedAccessories) {
NSArray *protocolStrings = [accessory protocolStrings];
if ([protocolStrings count] > 0) {
NSString *key = accessory.serialNumber;
if ([key length] > 0) {
NSUserDefaults *userDefault = [NSUserDefaults standardUserDefaults];
NSString *termName = [userDefault objectForKey:key];
if (termName == nil) {
[userDefault setObject:[accessory modelNumber] forKey:key];
}
NSString *protocol = protocolStrings[0];
if (([protocol isEqualToString:OPH5000iProtocolString]) && ([termName isEqualToString:OPH5000iAccessoryModelNumber])){
[self setupControllerForAccessory:accessory
withProtocolString:protocol];
break;
}
}
}
}
}
+ (OPHBluetoothService *)sharedController
{
static OPHBluetoothService *sessionController = nil;
if (sessionController == nil) {
sessionController = [[OPHBluetoothService alloc] init];
}
return sessionController;
}
@end
関連事項
最終更新日:2021/10/29