FirstApp is a sample code that connects to the OPH-5000i's Bluetooth MFi and sends and receives data.

Download FirstApp

  1. Download the FirstApp zip file from the following link.
  2. Unzip the zip file and you will get the following files:
    FirstApp
    FirstApp.xcodeproj
  3. Open FirstApp.xcodeproj in Xcode.
  4. Connect your iOS device to your Mac and build FirstApp in Xcode.

FirstApp operation procedure

  1. Launch Settings on your iOS device and turn on Bluetooth.
  2. Read the iOS 12 digit Bluetooth address barcode with the OPH-5000i sample application.
  3. When the connection is established, start this sample (FirstApp).
  4. 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).
  5. 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