Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Mac-to-bluetooth device file transfer, simple example?

I've spent two days googling and reading the Bluetooth programming guide while trying to piece together a small Mac app that will retrieve images from a drop folder and send any new files to a predetermined device over Bluetooth. There doesn't seem to be many good examples available.

I'm at the point where I'm able to spawn the Bluetooth Service Browser and select the device and its OBEX service, establishing a service and creating a connection, but then nothing more happens. Could anyone please point me in the direction of/show me a simple example that would work?

AppDelegate source code enclosed. Thanks for reading!

#import "AppDelegate.h"

@implementation AppDelegate

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {

    IOBluetoothServiceBrowserController *browser = [IOBluetoothServiceBrowserController serviceBrowserController:0];
    [browser runModal];

    //IOBluetoothSDPServiceRecord
    IOBluetoothSDPServiceRecord *result = [[browser getResults] objectAtIndex:0];
    [self describe:result];

    if ([[result.device.name substringToIndex:8] isEqualToString:@"Polaroid"]) {
        printer = result.device;
        serviceRecord = result;
        [self testPrint];
    }
    else {
        NSLog(@"%@ is not a valid device", result.device.name);
    }
}

- (void) testPrint {
     currentFilePath = @"/Users/oyvind/Desktop/_DSC8797.jpg";
    [self sendFile:currentFilePath];
}

- (void) sendFile:(NSString *)filePath {
    IOBluetoothOBEXSession *obexSession = [[IOBluetoothOBEXSession alloc] initWithSDPServiceRecord:serviceRecord];

    if( obexSession != nil )
    {
        NSLog(@"OBEX Session Established");

        OBEXFileTransferServices *fst = [OBEXFileTransferServices withOBEXSession:obexSession];
        OBEXDelegate *obxd = [[OBEXDelegate alloc] init];
        [obxd setFile:filePath];
        [fst setDelegate:obxd];

        OBEXError cnctResult = [fst connectToObjectPushService];

        if( cnctResult != kIOReturnSuccess ) {
            NSLog(@"Error creating connection");
            return;
        }
        else {
            NSLog(@"OBEX Session Created. Sending file: %@", filePath);
            [fst sendFile:filePath];
            [printer openConnection];
        }
    }
    else {
        NSLog(@"Error creating OBEX session");
        NSLog(@"Error sending file");
    }
}

@end
like image 868
nordhagen Avatar asked Nov 03 '22 19:11

nordhagen


1 Answers

OK; here's what ultimately became the core parts of the functionality. The application I made was a sort of print server for Polaroid instant printers that would only accept images over Object Push.

First, ensure watched folder exists.

/*

    Looks for a directory named PolaroidWatchFolder in the user's desktop directory
    and creates it if it does not exist.

 */

- (void) ensureWatchedFolderExists {
    NSFileManager *fileManager = [NSFileManager defaultManager];
    NSURL *url = [NSURL URLWithString:@"PolaroidWatchFolder" relativeToURL:[[fileManager URLsForDirectory:NSDesktopDirectory inDomains:NSUserDomainMask] objectAtIndex:0]];
    BOOL isDir;
    if ([fileManager fileExistsAtPath:[url path] isDirectory:&isDir] && isDir) {
        [self log:[NSString stringWithFormat:@"Watched folder exists at %@", [url absoluteURL]]];
        watchFolderPath = url;
    }
    else {
        NSError *theError = nil;
        if (![fileManager createDirectoryAtURL:url withIntermediateDirectories:NO attributes:nil error:&theError]) {
            [self log:[NSString stringWithFormat:@"Watched folder could not be created at %@", [url absoluteURL]]];
        }
        else {
            watchFolderPath = url;
            [self log:[NSString stringWithFormat:@"Watched folder created at %@", [url absoluteURL]]];
        }
    }
}

Then scan for available printers:

/*

    Loops through all paired Bluetooth devices and retrieves OBEX Object Push service records
    for each device who's name starts with "Polaroid".

 */

- (void) findPairedDevices {
    NSArray *pairedDevices = [IOBluetoothDevice pairedDevices];
    devicesTested = [NSMutableArray arrayWithCapacity:0];
    for (IOBluetoothDevice *device in pairedDevices)
    {
        if ([self deviceQualifiesForAddOrRenew:device.name])
        {
            BluetoothPushDevice *pushDevice = [[BluetoothPushDevice new] initWithDevice:device];
            if (pushDevice != nil)
            {
                [availableDevices addObject:pushDevice];
                [pushDevice testConnection];                
            }
        }
    }
}

That last function call is to the BluetoothPushDevice's built-in method to test the connection. Here is the delegate handler for the response:

- (void) deviceStatusHandler: (NSNotification *)notification {
    BluetoothPushDevice *device = [notification object];
    NSString *status = [[notification userInfo] objectForKey:@"message"];

    if ([devicesTested count] < [availableDevices count] && ![devicesTested containsObject:device.name]) {
        [devicesTested addObject:device.name];
    }
}

Upon server start, this method will run in response to a timer tick or manual scan:

- (void) checkWatchedFolder {
    NSError *error = nil;
    NSArray *properties = [NSArray arrayWithObjects: NSURLLocalizedNameKey, NSURLCreationDateKey, NSURLLocalizedTypeDescriptionKey, nil];

    NSArray *files = [[NSFileManager defaultManager]
                      contentsOfDirectoryAtURL:watchFolderPath
                      includingPropertiesForKeys:properties
                      options:(NSDirectoryEnumerationSkipsHiddenFiles)
                      error:&error];

    if (files == nil) {
        [self log:@"Error reading watched folder"];
        return;
    }

    if ([files count] > 0) {
        int newFileCount = 0;

        for (NSURL *url in files) {
            if (![filesInTransit containsObject:[url path]]) {
                NSLog(@"New file: %@", [url lastPathComponent]);
                [self sendFile:[url path]];
                newFileCount++;
            }
        }
    }
}

When new files are found, ww first need to find a device that is not busy recieving a file of printing it:

/*

 Loops through all discovered device service records and returns the a new OBEX session for
 the first it finds that is not connected (meaning it is not currently in use, connections are
 ad-hoc per print).

 */

- (BluetoothPushDevice*) getIdleDevice {
    for (BluetoothPushDevice *device in availableDevices) {
        if ([device.status isEqualToString:kBluetoothDeviceStatusReady]) {
            return device;
        }
    }
    return nil;
}

Then a file is sent with this method:

- (void) sendFile:(NSString *)filePath {
    BluetoothPushDevice *device = [self getIdleDevice];
        if( device != nil ) {
        NSLog(@"%@ is available", device.name);
        if ([device sendFile:filePath]) {
            [self log:[NSString stringWithFormat:@"Sending file: %@", filePath]];
            [filesInTransit addObject:filePath];
        }
        else {
            [self log:[NSString stringWithFormat:@"Error sending file: %@", filePath]];
        }
    }
    else {
        NSLog(@"No idle devices");
    }
}

Upon transfer complete, this delegate method is called:

/*

    Responds to BluetoothPushDevice's TransferComplete notification

 */

- (void) transferStatusHandler: (NSNotification *) notification {
    NSString *status = [[notification userInfo] objectForKey:@"message"];
    NSString *file = ((BluetoothPushDevice*)[notification object]).file;

    if ([status isEqualToString:kBluetoothTransferStatusComplete]) {
        if ([filesInTransit containsObject:file]) {
            NSFileManager *fileManager = [NSFileManager defaultManager];
            NSError *error = nil;
            [fileManager removeItemAtPath:file error:&error];
            if (error != nil) {
                [self log:[NSString stringWithFormat:@"**ERROR** File %@ could not be deleted (%@)", file, error.description]];
            }

            [self log:[NSString stringWithFormat:@"File deleted: %@", file]];
            [filesInTransit removeObject:file];
        }
        else {
            [self log:[NSString stringWithFormat:@"**ERROR** filesInTransit array does not contain file %@", file]];
        }
    }

    [self updateDeviceStatusDisplay];
}

I hope this helps someone!

like image 109
nordhagen Avatar answered Nov 15 '22 05:11

nordhagen