Download FirstApp
- Download the FirstApp zip file from the following link.
- Unzip the zip file and you will get the following files:
FirstApp
FirstApp.xcodeproj - Open FirstApp.xcodeproj in Xcode.
- Connect your iOS device to your Mac and build FirstApp in Xcode.
FirstApp operation procedure
- Launch Settings on your iOS device and turn on Bluetooth.
- Read the iOS 12 digit Bluetooth address barcode with the OPH-5000i sample application.
- When the connection is established, start this sample (FirstApp).
- Receive data
Read the barcode with the OPH-5000i sample application.
The data sent from OPH-5000i is displayed in the text view of this sample (First App). - Send the data.
Click the "Sent Test String" button to send the "Test String" string to OPH-5000i.
OPHBluetoothService class is a core class that performs Bluetooth communication with OPH-5000i and is in charge of session management, accessory holding, I / O processing, etc.
OPHBluetoothService is implemented with GoF's Singleton pattern. There is only one instance of the OPHBluetoothService class running.
In the OPHBluetoothService class, ExternalAccessory.framework is used for communication with external accessories.
Sample: OPHBluetoothService.m
//
// OPHBluetoothService.m
//
// Copyright (c) 2021 OPTOELECTRONICS CO., LTD. All rights reserved.
//
#import "OPHBluetoothService.h"
/** OPHBluetoothService always keeps the current accessories, protocols, sessions, and read / write buffers.
* setupControllerForAccessory Accessory, protocol initialization (discard if session, buffer is not destroyed)
* openSession Session, buffer generation (disabled if accessories and protocols are not initialized)
* closeSession Session, discard buffer (disabled if session, buffer has not been created)
*/
@ interface OPHBluetoothService () {
EAAccessory *__weak _accessory; // Currently connected EAAccessory
EASession *_currentSession; // Currently connected session
NSString *_protocolString; // Currently connected protocolString
NSMutableData *_writeData; // Write data buffer
NSMutableData *_readData; // Read data buffer
NSMutableArray *_sessions; // Generated 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 close Session];
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
{
// Argument check
if (accessory_ == nil) {
NSLog (@ "arguments error: accessory is nil.");
return ;
}
if (protocolString == nil) {
NSLog (@ "arguments error: protocolString is nil.");
return ;
}
// If both accessories and sessions are present
if (_accessory && _currentSession) {
// If the connection ID of the given accessory is the same as the current one, do nothing.
if (accessory_.connectionID == _currentSession.accessory.connectionID) {
NSLog (@ "session is already connected.");
return ;
}
// If the connection ID is different, close the current session.
else {
[self close Session];
}
}
_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);
// For ModelNumbe OPH-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;
}
// Are there any sessions generated so far?
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 there is no current session, or if there is a session and it has not been created so far
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
// ------------------------------------------------------------------------------------------------ -------------------------------------------------- --------------------------------------------------
/ ** Called when EAAccessory is disconnected.
*/
-(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 event handler.
* @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 // Private Methods
// ------------------------------------------------------------------------------------------------ -------------------------------------------------- --------------------------------------------------
/ ** Write data to the session.
* When there is space to write in the outputStream of _session and there is data in the write buffer (_writeData)
* Continue writing to _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];
}
}
}
/** Load session data.
* If there is data in the inputStream of _session, continue reading to the read buffer (_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];
// Sleep and wait to store in the inputStream buffer
[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 ;
// It is necessary to reply ACK to OPH-5000i after receiving outputSteam.
[self write ACK];
if ([ delegate respondsToSelector: @selector (bluetoothService: receivedData :)]) {
[ delegate bluetoothService: self receivedData: readdata];
}
[self performSelector: @selector (clearReadData)];
}
-(void)clearReadData
{
[_readData setLength: 0];
}
/** Get the connected EAAccessory from EAAccessoryManager and set it in the property.
*/
-(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
Related matters
Last updated: 2021/11/18