How to build commands for use with writeAsync over RFCOMMChannel? - cocoa

I have to communicate over bluetooth with a device, the device expects commands to be separated by carriage return + linefeed.
Connection is established using RFCOMMChannel.
Atm it seems that my code is not working since I am expecting a reply from the device, which it does when I send it commands using a simpel terminal program.
This code is run after the connection is established (this is definately working since I can log data coming in from the external device)
NSString *clockRequest = #"C\r\n";
void *clockRequestData = (__bridge void *)([clockRequest dataUsingEncoding:NSASCIIStringEncoding]);
NSLog(#"Data buffer to write: %#", clockRequestData);
[rfcommChannel writeAsync: clockRequestData length:100 refcon:NULL];
//writing data from rfcomm
- (void)rfcommChannelWriteComplete:(IOBluetoothRFCOMMChannel*)rfcommChannel refcon:(void*)refcon status:(IOReturn)error {
NSLog(#"Macbook wrote to Timecube, status: %d", error);
}
The code for establishing a connection was taken and adjusted from
https://gist.github.com/crazycoder1999/3139668
thx in advance

Add category NSStringHexToBytes to Your project:
NSString+NSStringHexToBytes.h
#import <Foundation/Foundation.h>
#interface NSString (NSStringHexToBytes)
+ (NSData *)dataWithString:(NSString *)string;
#end
NSString+NSStringHexToBytes.m
#import "NSString+NSStringHexToBytes.h"
#implementation NSString (NSStringHexToBytes)
+ (NSData *)dataWithString:(NSString *)string
{
//string = [string stringByReplacingOccurrencesOfString:#"0x" withString:#""];
//NSCharacterSet *notAllowedCharacters = [[NSCharacterSet characterSetWithCharactersInString:#"abcdefABCDEF1234567890"] invertedSet];
//string = [[string componentsSeparatedByCharactersInSet:notAllowedCharacters] componentsJoinedByString:#""];
const char *cString = [string cStringUsingEncoding:NSASCIIStringEncoding];
const char *idx = cString;
unsigned char result[[string length] / 2];
size_t count = 0;
for(count = 0; count < sizeof(result)/sizeof(result[0]); count++)
{
sscanf(idx, "%2hhx", &result[count]);
idx += 2 * sizeof(char);
}
return [[NSData alloc] initWithBytes:result length:sizeof(result)];
}
#end
In Your implementation file import NSString+NSStringHexToBytes.h and add method
-(void)sendMessage:(NSData *)data
{
[rfcommChannel writeSync:(void*)data.bytes length:data.length];
}
and then:
NSString* clockRequest = #"C\r\n";
NSData* data = [NSString dataWithString:clockRequest];
[rfcommChannel sendMessage:data];

Related

Error with 'getProfilePicture'

I have a profile view controller where the user can set or change his or her profile picture but am getting constant errors that do not make sense to me.
-(void)getProfilePicture
{
PFUser *user = [PFUser currentUser];
NSLog(#"file--%#",[user objectForKey:PF_USER_PICTURE]);
userName = user[PF_USER_USERNAME];
status = user[PF_USER_STATUS];
if ([user objectForKey:PF_USER_PICTURE]) {
[imageUser setFile:[user objectForKey:PF_USER_PICTURE]];
[imageUser loadInBackground];
}
else{
imageUser.image = [UIImage imageNamed:#"blank_profile#2x.png"];
}
// fieldName.text = user[PF_USER_FULLNAME];
}
#pragma mark - UIActionSheetDelegate
I receive the following errors portrayed in my ProfileViewController.m (I can provide/add .h if needed):
"No visible #interface for 'UIImageView' declares the selector 'setFile:'
"No visible #interface for 'UIImageView' declares the selector 'loadInBackground'
Any help would be much appreciated or any supporting code thats needed, thanks.
Try this and see what it does for you:
-(void)getProfilePicture
{
PFUser *user = [PFUser currentUser];
NSLog(#"file--%#",[user objectForKey:PF_USER_PICTURE]);
userName = user[PF_USER_USERNAME];
status = user[PF_USER_STATUS];
if ([user objectForKey:PF_USER_PICTURE]) {
userImage.file = [user objectForKey:PF_USER_PICTURE];
[userImage loadInBackground:^(UIImage *image, NSError *error) {
if (!error) {
imageUser.image = image;
[imageUser setNeedsDisplay];
} else {
NSLog(#"%#", error);
}
}];
}
else{
imageUser.image = [UIImage imageNamed:#"blank_profile#2x.png"];
}
// fieldName.text = user[PF_USER_FULLNAME];
}
#pragma mark - UIActionSheetDelegate
This is assuming your userImage is a PFImageView and [user objectForKey:PF_USER_PICTURE] is a PFFile.
EDIT
Actually, looking at your error it seems that userImage is just a UIImageView not a PFImageView, which might be the source of all your problems. Try making the userImage a PFImageView

Obtain Model Identifier string on OS X

Every Mac has a model identifier, for example "Macmini5,1". (These are shown in the System Information app.)
How can I programatically obtain this model identifier string?
Swift 4+ using IOKit
import IOKit
func getModelIdentifier() -> String? {
let service = IOServiceGetMatchingService(kIOMasterPortDefault,
IOServiceMatching("IOPlatformExpertDevice"))
var modelIdentifier: String?
if let modelData = IORegistryEntryCreateCFProperty(service, "model" as CFString, kCFAllocatorDefault, 0).takeRetainedValue() as? Data {
modelIdentifier = String(data: modelData, encoding: .utf8)?.trimmingCharacters(in: .controlCharacters)
}
IOObjectRelease(service)
return modelIdentifier
}
You can use sysctl
#import <Foundation/Foundation.h>
#import <sys/sysctl.h>
NSString *ModelIdentifier()
{
NSString *result=#"Unknown Mac";
size_t len=0;
sysctlbyname("hw.model", NULL, &len, NULL, 0);
if (len) {
NSMutableData *data=[NSMutableData dataWithLength:len];
sysctlbyname("hw.model", [data mutableBytes], &len, NULL, 0);
result=[NSString stringWithUTF8String:[data bytes]];
}
return result;
}
You can also use IOKit.framework. I think it's best choice.
This simple code example shows how to read model identifier from I/O Kit registry to NSString:
- (NSString *)modelIdentifier {
io_service_t service = IOServiceGetMatchingService(kIOMasterPortDefault,
IOServiceMatching("IOPlatformExpertDevice"));
CFStringRef model = IORegistryEntryCreateCFProperty(service,
CFSTR("model"),
kCFAllocatorDefault,
0);
NSString *modelIdentifier = [[NSString alloc] initWithData:(__bridge NSData *)model
encoding:NSUTF8StringEncoding];
CFRelease(model);
IOObjectRelease(service);
return modelIdentifier;
}
Strings "IOPlatformExpertDevice" and "model" in code above is used to read model identifier from I/O Kit registry. ioreg command line tool is your friend, when you want to find information from I/O Kit registry. This image shows those strings in ioreg output:
I hope this helps to use IOKit.framework.
Answer from Ryan H is correct except improper conversion from null-terminated string to Swift String, giving result with \0 symbol in the end, which you may not expect, performing full match. This is corrected version:
static private func modelIdentifier() -> String? {
let service = IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching("IOPlatformExpertDevice"))
defer { IOObjectRelease(service) }
if let modelData = IORegistryEntryCreateCFProperty(service, "model" as CFString, kCFAllocatorDefault, 0).takeRetainedValue() as? Data {
return modelData.withUnsafeBytes { (cString: UnsafePointer<UInt8>) -> String in
return String(cString: cString)
}
}
return nil
}
You can get the same output from the system_profiler command. It has an -xml option that you can use. NSTask can run the command for you and you can parse the result.
Sample code:
#import <Foundation/Foundation.h>
NSString *ModelIdentifier() {
NSPipe *pipe=[NSPipe pipe];
NSTask *task=[[NSTask alloc] init];
[task setLaunchPath:#"/usr/sbin/system_profiler"];
[task setArguments:#[#"-xml", #"SPHardwareDataType"]];
[task setStandardOutput:pipe];
[task launch];
NSData *outData=[[pipe fileHandleForReading] readDataToEndOfFile];
NSString *outString=[[NSString alloc] initWithData:outData encoding:NSUTF8StringEncoding];
return [outString propertyList][0][#"_items"][0][#"machine_model"];
}
CFStringRef model = IORegistryEntryCreateCFProperty(service,
CFSTR("model"),
kCFAllocatorDefault,
0); ? type is ok ?
I think code maybe like this:
CFSDataRef model = IORegistryEntryCreateCFProperty(service,
CFSTR("model"),
kCFAllocatorDefault,
0);
With iOS 16 apps that also build to Mac Catalyst any solution here that uses kIOMasterPortDefault (such as Ryan H's) will generate a build error:
'kIOMasterPortDefault' is unavailable in Mac Catalyst
Attempting to switch kIOMasterPortDefault to kIOMainPortDefault may also give a build error depending on which versions of Catalyst you are targetting – and I found trying to use #available to get around that didn't work either.
If you run into that situation then try the following, which is a reformulation of Parag Bafna's answer into Swift:
var modelIdentifier: String {
#if targetEnvironment(macCatalyst)
var size = 0
sysctlbyname("hw.model", nil, &size, nil, 0)
var modelIdentifier: [CChar] = Array(repeating: 0, count: size)
sysctlbyname("hw.model", &modelIdentifier, &size, nil, 0)
return String(cString: modelIdentifier)
#else
// Handle iOS
#endif
}
Swift version of Parag Bafna excellent answer
var deviceName: String {
var str = "Unknown Device"
var len = 0
sysctlbyname("hw.model", nil, &len, nil, 0)
if len > 0 {
var data = Data(count: len)
sysctlbyname("hw.model", &data, &len, nil, 0)
if let s = String(bytes: data, encoding: .utf8) {
str = s
}
}
return str
}

NSSharingService - Remove facebook and twitter and add Print

I've just implemented a share-button, that has a share menu:
[_shareButton sendActionOn:NSLeftMouseDownMask];
And has this action connected:
-(IBAction)share:(id)sender {
NSArray *shareArray = #[#"testShare"];
NSSharingServicePicker *sharingServicePicker = [[NSSharingServicePicker alloc] initWithItems:shareArray];
sharingServicePicker.delegate = self;
[sharingServicePicker showRelativeToRect:[sender bounds]
ofView:sender
preferredEdge:NSMinYEdge];
}
Now to my question, I don't want Facebook and Twitter to be an option in the menu. I only want E-Mail and Messages to be available. Also I would like to add "Print", but don't know if I can do that.
Is that possible?
Thanks
(Don't have enough rep points to add 'NSSharingService' as a tag)
Solved it by using proposedSharingServices.
- (NSArray *)sharingServicePicker:(NSSharingServicePicker *)sharingServicePicker sharingServicesForItems:(NSArray *)items proposedSharingServices:(NSArray *)proposedServices{
// Find and the services you want
NSMutableArray *newProposedServices = [[NSMutableArray alloc] initWithCapacity:5];
for (NSSharingService *sharingService in proposedServices) {
if ([[sharingService title] isEqualToString:#"Email"] || [[sharingService title] isEqualToString:#"Message"]) {
[newProposedServices addObject:sharingService];
}
}
NSArray *services = newProposedServices;
NSSharingService *customService = [[NSSharingService alloc] initWithTitle:#"Print" image:[NSImage imageNamed:#"PrintImage"] alternateImage:nil handler:^{
// Do whatever
}];
services = [services arrayByAddingObject:customService];
return services;
}
Comparing a proposed service to a new named instance works. Here's a trivial Swift code from my project:
let excludedNames = [
NSSharingServiceNamePostOnFacebook,
NSSharingServiceNamePostOnTwitter,
]
var excludedServices = [NSSharingService]()
for name in excludedNames {
if let service = NSSharingService(named: name) {
excludedServices += [service]
}
}
return proposedServices.filter {
!excludedServices.contains($0)
}
No need to use a private name property.
Rather then trying to say what you don't want simply return a list of what you do want.
- (NSArray<NSSharingService *> *)sharingServicePicker:(NSSharingServicePicker *)sharingServicePicker sharingServicesForItems:(NSArray *)items proposedSharingServices:(NSArray<NSSharingService *> *)proposedServices
{
NSArray *result = #[[NSSharingService sharingServiceNamed:NSSharingServiceNameComposeEmail], [NSSharingService sharingServiceNamed:NSSharingServiceNameComposeMessage]];
return result;
}
A slightly different approach via proposedSharingServices:
  
- (NSArray*)sharingServicePicker:(NSSharingServicePicker *)sharingServicePicker sharingServicesForItems:(NSArray *)items proposedSharingServices:(NSArray *)proposedServices {
NSArray *excludedServices = #[NSSharingServiceNamePostOnFacebook,
NSSharingServiceNamePostOnTwitter];
NSArray *sharingServices = [proposedServices filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:#"NOT (name IN %#)", excludedServices]];
return sharingServices;
}
Here's a better way - no private API access required.
NSArray *excludedServices = #[NSSharingServiceNamePostOnFacebook,
NSSharingServiceNamePostOnTwitter];
NSMutableArray *includedServices = [[NSMutableArray alloc] init];
for (NSSharingService *service in proposedServices) {
if ([excludedServices indexOfObject:service] == NSNotFound) {
[includedServices addObject:service];
}
}
return includedServices;

sqlite error parsing query

I'm receiving a strange error querying a database with sqlite:
SQLlite error: near "SE": syntax error
I don't really get why he doesn't like the query string I'm sending to it. Here's my code:
-(Wallet*)loadDataFromSQL{
sqlite3 *database;
NSLog(#"opening..");
if (sqlite3_open([[NSString stringWithFormat:#"mywallet.sqlite3"] UTF8String], &database) == SQLITE_OK) {
NSLog(#"opened..");
const char *query = [[NSString stringWithFormat:#"SELECT * FROM 'Transaction';"] UTF8String]; // "insert into \"Transaction\" values (\"2013-01-01\",\"tipo\",\"cat\",1)";
sqlite3_stmt *selectstmt;
NSLog(#"preparing stmnt..");
if(sqlite3_prepare_v2(database, query, SQLITE_OPEN_READWRITE, &selectstmt, nil) == SQLITE_OK) {
NSLog(#"Prepared..");
while(sqlite3_step(selectstmt) == SQLITE_ROW) {
NSLog(#"row..");
NSString *data = [NSString stringWithUTF8String: (char*)sqlite3_column_text(selectstmt, 0)];
NSString *type = [NSString stringWithUTF8String: (char*)sqlite3_column_text(selectstmt, 1)];
NSString *category = [NSString stringWithUTF8String: (char*)sqlite3_column_text(selectstmt, 2)];
float amount = (float)sqlite3_column_double(selectstmt, 0);
Transaction *t = [[Transaction alloc]init:data transactionType:type transactionCategory:category transactionAmount:[NSString stringWithFormat:#"%f",amount]];
NSLog(#"%#",t);
}
}else{
NSLog([NSString stringWithFormat:#"SQLlite error: %s\n", sqlite3_errmsg(database)]);
}
}
sqlite3_close(database); //Even though the open call failed, close the database connection to release all the memory.
return nil;
}
Nothing too fancy.. what's wrong with it?
Thanks!
The third parameter of sqlite3_prepare_v2 must be the length of the query string, or just -1.
sqlite3_prepare_v2(database, query, SQLITE_OPEN_READWRITE, &selectstmt, nil)
With SQLite complaining about "SE", I'd guess that the value of SQLITE_OPEN_READWRITE is 2. :-)

Get the Username(s) stored in Keychain, using only the ServiceName? OR: Where are you supposed to store the Username?

So the OS X Keychain has three pieces of information:
ServiceName (the name of my app)
Username
Password
I obviously always know the ServiceName. Is there a way to find any saved Username(s) for that ServiceName? (Finding the password is easy once you know the Username.)
I would much prefer to use a nice Cocoa wrapper such as EMKeychain to do this. But EMKeychain requires the UserName to get any keychain item!
+ (EMGenericKeychainItem *)genericKeychainItemForService:(NSString *)serviceNameString withUsername:(NSString *)usernameString;
How are you expected to fully utilize saving credentials in the Keychain, if you need the Username to find the credentials? Is the best practice to save the Username in the .plist file or something?
SecKeychainFindGenericPassword only returns a single keychain item. To find all generic passwords for a specific service, you need to run a query on the keychain. There are several ways to do this, based on what version of OS X you target.
If you need to run on 10.5 or below, you'll need to use SecKeychainSearchCreateFromAttributes. It's a rather horrible API. Here is a rough cut of a method that returns a dictionary mapping usernames to passwords.
- (NSDictionary *)genericPasswordsWithService:(NSString *)service {
OSStatus status;
// Construct a query.
const char *utf8Service = [service UTF8String];
SecKeychainAttribute attr = { .tag = kSecServiceItemAttr,
.length = strlen(utf8Service),
.data = (void *)utf8Service };
SecKeychainAttribute attrList = { .count = 1, .attr = &attr };
SecKeychainSearchRef *search = NULL;
status = SecKeychainSearchCreateFromAttributes(NULL, kSecGenericPasswordItemClass, &attrList, &search);
if (status) {
report(status);
return nil;
}
// Enumerate results.
NSMutableDictionary *result = [NSMutableDictionary dictionary];
while (1) {
SecKeychainItemRef item = NULL;
status = SecKeychainSearchCopyNext(search, &item);
if (status)
break;
// Find 'account' attribute and password value.
UInt32 tag = kSecAccountItemAttr;
UInt32 format = CSSM_DB_ATTRIBUTE_FORMAT_STRING;
SecKeychainAttributeInfo info = { .count = 1, .tag = &tag, .format = &format };
SecKeychainAttributeList *attrList = NULL;
UInt32 length = 0;
void *data = NULL;
status = SecKeychainItemCopyAttributesAndData(item, &info, NULL, &attrList, &length, &data);
if (status) {
CFRelease(item);
continue;
}
NSAssert(attrList->count == 1 && attrList->attr[0].tag == kSecAccountItemAttr, #"SecKeychainItemCopyAttributesAndData is messing with us");
NSString *account = [[[NSString alloc] initWithBytes:attrList->attr[0].data length:attrList->attr[0].length encoding:NSUTF8StringEncoding] autorelease];
NSString *password = [[[NSString alloc] initWithBytes:data length:length encoding:NSUTF8StringEncoding] autorelease];
[result setObject:password forKey:account];
SecKeychainItemFreeAttributesAndData(attrList, data);
CFRelease(item);
}
CFRelease(search);
return result;
}
For 10.6 and later, you can use the somewhat less inconvenient SecItemCopyMatching API:
- (NSDictionary *)genericPasswordsWithService:(NSString *)service {
NSDictionary *query = [NSDictionary dictionaryWithObjectsAndKeys:
kSecClassGenericPassword, kSecClass,
(id)kCFBooleanTrue, kSecReturnData,
(id)kCFBooleanTrue, kSecReturnAttributes,
kSecMatchLimitAll, kSecMatchLimit,
service, kSecAttrService,
nil];
NSArray *itemDicts = nil;
OSStatus status = SecItemCopyMatching((CFDictionaryRef)q, (CFTypeRef *)&itemDicts);
if (status) {
report(status);
return nil;
}
NSMutableDictionary *result = [NSMutableDictionary dictionary];
for (NSDictionary *itemDict in itemDicts) {
NSData *data = [itemDict objectForKey:kSecValueData];
NSString *password = [[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] autorelease];
NSString *account = [itemDict objectForKey:kSecAttrAccount];
[result setObject:password forKey:account];
}
[itemDicts release];
return result;
}
For 10.7 or later, you can use my wonderful LKKeychain framework (PLUG!). It doesn't support building attribute-based queries, but you can simply list all passwords and filter out the ones you don't need.
- (NSDictionary *)genericPasswordsWithService:(NSString *)service {
LKKCKeychain *keychain = [LKKCKeychain defaultKeychain];
NSMutableDictionary *result = [NSMutableDictionary dictionary];
for (LKKCGenericPassword *item in [keychain genericPasswords]) {
if ([service isEqualToString:item.service]) {
[result setObject:item.password forKey:item.account];
}
}
return result;
}
(I didn't try running, or even compiling any of the above code samples; sorry for any typos.)
You don't need the username. You do with EMKeychain, but that's an artificial distinction that that class imposes; the underlying Keychain Services function does not require a username to find a keychain item.
When using SecKeychainFindGenericPassword directly, pass 0 and NULL for the username parameters. It will return a keychain item that exists on that service.
However, that will return only one item. If the user has multiple keychain items on the same service, you won't know that, or which one you got (the documentation says it returns the “first” matching item, with no specification of what it considers “first”). If you want any and all items for that service, you should create a search and use that.
Generic passwords have a unique key of the service name and the username. Thus, to fetch a single generic keychain entry, you will need to provide both. However, you can iterate over all generic keychain entries for your given service using the SecKeychainFindGenericPassword function.
(Disclaimer: I don't know anything about doing this in EMKeychain.)

Resources