/* -*-mode: C; fill-column: 77; c-basic-offset: 4; compile-command: "make ARCH=68K_ONLY MEMDEBUG=TRUE"; -*- */ /* * Copyright 2006 by Eric House (xwords@eehouse.org). All rights reserved. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifdef XWFEATURE_BLUETOOTH #include "xptypes.h" #include "palmbt.h" #include "strutils.h" # include # include #define L2CAPSOCKETMTU 500 #define SOCK_INVAL 0XFFFF typedef struct PalmBTStuff { DataCb cb; PalmAppGlobals* globals; XP_U16 btLibRefNum; XP_U16 lenOut; XP_UCHAR bufOut[L2CAPSOCKETMTU]; /* what's the mmu? */ BtLibSocketRef dataSocket; XP_Bool sendInProgress; XP_Bool sendPending; XP_Bool amMaster; union { struct { BtLibDeviceAddressType masterAddr; XP_Bool addrSet; } slave; struct { BtLibSocketRef listenSocket; } master; } u; BtLibSdpRecordHandle sdpRecordH; } PalmBTStuff; #define LOG_ERR(f,e) palm_bt_log( #f, __FUNCTION__, e ) #define CALL_ERR(e,f,...) \ e = f(__VA_ARGS__); \ LOG_ERR(f,e) /* WHAT SHOULD THIS BE? Copied from Whiteboard.... PENDING */ static const BtLibSdpUuidType XWORDS_UUID = { btLibUuidSize128, { 0x83, 0xe0, 0x87, 0xae, 0x4e, 0x18, 0x46, 0xbe, 0x83, 0xe0, 0x7b, 0x3d, 0xe6, 0xa1, 0xc3, 0x3b } }; static Err initBTStuff( PalmAppGlobals* globals, DataCb cb, XP_Bool amMaster ); static void palm_bt_log( const char* btfunc, const char* func, Err err ); static void pbt_connect_slave( PalmBTStuff* btStuff ); static Err bpd_discover( PalmBTStuff* btStuff, BtLibDeviceAddressType* addr ); static void pbt_setup_slave( PalmBTStuff* btStuff, const CommsAddrRec* addr ); #ifdef DEBUG static const char* btErrToStr( Err err ); static const char* btEvtToStr( BtLibSocketEventEnum evt ); static const char* mgmtEvtToStr( BtLibManagementEventEnum event ); #else # define btErrToStr( err ) "" # define btEvtToStr( evt ) "" # define mgmtEvtToStr( evt ) "" #endif /* callbacks */ static void libMgmtCallback( BtLibManagementEventType* mEvent, UInt32 refCon ); static void l2SocketCallback( BtLibSocketEventType* sEvent, UInt32 refCon ); Err palm_bt_init( PalmAppGlobals* globals, DataCb cb, XP_Bool amMaster ) { XP_LOGF( "%s(amMaster=%d)", __FUNCTION__, (XP_U16)amMaster ); return initBTStuff( globals, cb, amMaster ); } void palm_bt_close( PalmAppGlobals* globals ) { PalmBTStuff* btStuff = globals->btStuff; if ( !!btStuff ) { XP_U16 btLibRefNum = btStuff->btLibRefNum; if ( btLibRefNum != 0 ) { Err err; /* Need to unregister callbacks */ (void)BtLibUnregisterManagementNotification( btLibRefNum, libMgmtCallback ); if ( btStuff->amMaster && btStuff->u.master.listenSocket != SOCK_INVAL ) { CALL_ERR( err, BtLibSocketClose, btLibRefNum, btStuff->u.master.listenSocket ); /* btStuff->u.master.listenSocket = SOCK_INVAL; */ } if ( btStuff->dataSocket != SOCK_INVAL ) { CALL_ERR( err, BtLibSocketClose, btLibRefNum, btStuff->u.master.listenSocket ); /* btStuff->dataSocket = SOCK_INVAL; */ } if ( !!btStuff->sdpRecordH ) { CALL_ERR( err, BtLibSdpServiceRecordDestroy, btLibRefNum, btStuff->sdpRecordH ); } CALL_ERR( err, BtLibClose, btLibRefNum ); XP_ASSERT( errNone == err ); } XP_FREE( globals->mpool, btStuff ); globals->btStuff = NULL; } } /* palm_bt_close */ XP_Bool palm_bt_browse_device( PalmAppGlobals* globals, XP_BtAddr* btAddr, XP_UCHAR* out, XP_U16 len ) { Err err; PalmBTStuff* btStuff; err = initBTStuff( globals, NULL, XP_FALSE ); if ( errNone == err ) { UInt16 index; BtLibDeviceAddressType addr; btStuff = globals->btStuff; err = bpd_discover( btStuff, &addr ); CALL_ERR( err, BtLibSecurityFindTrustedDeviceRecord, btStuff->btLibRefNum, &addr, &index ); CALL_ERR( err, BtLibSecurityGetTrustedDeviceRecordInfo, btStuff->btLibRefNum, index, NULL, out, len, NULL, NULL, NULL ); XP_ASSERT( sizeof(*btAddr) >= sizeof(addr) ); XP_MEMCPY( btAddr, &addr, sizeof(addr) ); LOG_HEX( &addr, sizeof(addr), __FUNCTION__ ); /* err = BtLibGetRemoteDeviceName( btStuff->btLibRefNum, */ /* BtLibDeviceAddressTypePtr remoteDeviceP, */ /* BtLibFriendlyNameType* nameP, */ /* BtLibGetNameEnum retrievalMethod ); */ /* err = BtLibAddrBtdToA( btStuff->btLibRefNum, &btStuff->u.slave.masterAddr, */ /* out, len ); */ } else { XP_WARNF( "%s: err = %s", __FUNCTION__, btErrToStr(err) ); } return errNone == err; } /* palm_bt_browse_device */ XP_Bool btSocketIsOpen( PalmAppGlobals* globals ) { return (NULL != globals->btStuff) && (globals->btStuff->dataSocket != 0); } static void pbt_send_pending( PalmBTStuff* btStuff, const CommsAddrRec* addr ) { Err err; LOG_FUNC(); if ( btStuff->sendPending && !btStuff->sendInProgress ) { if ( btStuff->dataSocket != SOCK_INVAL ) { if ( btStuff->lenOut > 0 ) { /* hack: zero-len send to cause connect */ CALL_ERR( err, BtLibSocketSend, btStuff->btLibRefNum, btStuff->dataSocket, btStuff->bufOut, btStuff->lenOut ); if ( err == errNone ) { // clear on receipt of btLibSocketEventSendComplete btStuff->sendInProgress = XP_TRUE; } } else { btStuff->sendPending = XP_FALSE; } } else { /* No data socket? */ if ( !btStuff->amMaster ) { pbt_setup_slave( btStuff, addr ); } } } } /* pbt_send_pending */ static void pbt_check_socket( PalmBTStuff* btStuff, const CommsAddrRec* addr ) { LOG_FUNC(); if ( btStuff->dataSocket == SOCK_INVAL ) { if ( btStuff->amMaster ) { XP_ASSERT(0); /* pbt_setup_master( btStuff ); */ } else { pbt_setup_slave( btStuff, addr ); } } } XP_S16 palm_bt_send( const XP_U8* buf, XP_U16 len, const CommsAddrRec* addr, PalmAppGlobals* globals ) { XP_S16 nSent = -1; PalmBTStuff* btStuff; CommsAddrRec remoteAddr; XP_LOGF( "%s(len=%d)", __FUNCTION__, len ); if ( !addr ) { comms_getAddr( globals->game.comms, &remoteAddr ); addr = &remoteAddr; } XP_ASSERT( !!addr ); btStuff = globals->btStuff; if ( !!btStuff ) { pbt_check_socket( btStuff, addr ); if ( !!btStuff ) { if ( !btStuff->sendInProgress ) { if ( len > sizeof( btStuff->bufOut ) ) { len = sizeof( btStuff->bufOut ); } XP_MEMCPY( btStuff->bufOut, buf, len ); btStuff->lenOut = len; btStuff->sendPending = XP_TRUE; pbt_send_pending( btStuff, addr ); nSent = len; } else { XP_LOGF( "%s: send ALREADY in progress", __FUNCTION__ ); } } } else { XP_LOGF( "%s: btStuff null", __FUNCTION__ ); } LOG_RETURNF( "%d", nSent ); return nSent; } /* palm_bt_send */ static void pbt_connect_slave( PalmBTStuff* btStuff ) { Err err; XP_ASSERT( !btStuff->amMaster ); CALL_ERR( err, BtLibSocketCreate, btStuff->btLibRefNum, &btStuff->dataSocket, l2SocketCallback, (UInt32)btStuff, btLibL2CapProtocol ); if ( btLibErrNoError == err ) { BtLibSocketConnectInfoType connInfo; connInfo.data.L2Cap.remotePsm = XW_PSM; connInfo.data.L2Cap.localMtu = L2CAPSOCKETMTU; connInfo.data.L2Cap.minRemoteMtu = L2CAPSOCKETMTU; connInfo.remoteDeviceP = &btStuff->u.slave.masterAddr; /* sends btLibManagementEventACLConnectOutbound */ CALL_ERR( err, BtLibSocketConnect, btStuff->btLibRefNum, btStuff->dataSocket, &connInfo ); } } static void pbt_setup_master( PalmBTStuff* btStuff ) { /* Will eventually want to create a piconet here for more than two devices to play.... */ Err err; BtLibSocketListenInfoType listenInfo; btStuff->u.master.listenSocket = SOCK_INVAL; /* 1. BtLibSocketCreate: create an L2CAP socket. */ CALL_ERR( err, BtLibSocketCreate, btStuff->btLibRefNum, &btStuff->u.master.listenSocket, l2SocketCallback, (UInt32)btStuff, btLibL2CapProtocol ); /* 2. BtLibSocketListen: set up an L2CAP socket as a listener. */ XP_MEMSET( &listenInfo, 0, sizeof(listenInfo) ); listenInfo.data.L2Cap.localPsm = XW_PSM; // BT_L2CAP_RANDOM_PSM; listenInfo.data.L2Cap.localMtu = L2CAPSOCKETMTU; listenInfo.data.L2Cap.minRemoteMtu = L2CAPSOCKETMTU; CALL_ERR( err, BtLibSocketListen, btStuff->btLibRefNum, btStuff->u.master.listenSocket, &listenInfo ); /* 3. BtLibSdpServiceRecordCreate: allocate a memory chunk that represents an SDP service record. */ CALL_ERR( err, BtLibSdpServiceRecordCreate, btStuff->btLibRefNum, &btStuff->sdpRecordH ); /* 4. BtLibSdpServiceRecordSetAttributesForSocket: initialize an SDP memory record so it can represent the newly-created L2CAP listener socket as a service */ CALL_ERR( err, BtLibSdpServiceRecordSetAttributesForSocket, btStuff->btLibRefNum, btStuff->u.master.listenSocket, (BtLibSdpUuidType*)&XWORDS_UUID, 1, APPNAME, StrLen(APPNAME), btStuff->sdpRecordH ); /* 5. BtLibSdpServiceRecordStartAdvertising: make an SDP memory record representing a local SDP service record visible to remote devices. */ CALL_ERR( err, BtLibSdpServiceRecordStartAdvertising, btStuff->btLibRefNum, btStuff->sdpRecordH ); } /* pbt_setup_master */ static Err bpd_discover( PalmBTStuff* btStuff, BtLibDeviceAddressType* addr ) { Err err; static const BtLibClassOfDeviceType deviceFilter = btLibCOD_ServiceAny | btLibCOD_Major_Any // btLibCOD_Major_Computer | btLibCOD_Minor_Comp_Any; //btLibCOD_Minor_Comp_Palm; CALL_ERR( err, BtLibDiscoverSingleDevice, btStuff->btLibRefNum, "Crosswords host", (BtLibClassOfDeviceType*)&deviceFilter, 1, addr, false, false ); LOG_RETURNF( "%s", btErrToStr(err) ); return err; } /* bpd_discover */ static void pbt_setup_slave( PalmBTStuff* btStuff, const CommsAddrRec* addr ) { LOG_FUNC(); XP_ASSERT( !btStuff->amMaster ); if ( !!addr ) { char buf[64]; if ( errNone == BtLibAddrBtdToA( btStuff->btLibRefNum, (BtLibDeviceAddressType*)&addr->u.bt.btAddr, buf, sizeof(buf) ) ) { XP_LOGF( "%s(%s)", __FUNCTION__, buf ); } } else { XP_LOGF( "null addr" ); } if ( !btStuff->u.slave.addrSet && !!addr ) { Err err; /* Our xp type better be big enough */ XP_ASSERT( sizeof(addr->u.bt.btAddr) >= sizeof(btStuff->u.slave.masterAddr) ); XP_MEMCPY( &btStuff->u.slave.masterAddr, addr->u.bt.btAddr, sizeof(btStuff->u.slave.masterAddr) ); btStuff->u.slave.addrSet = XP_TRUE; /* sends btLibManagementEventACLConnectOutbound */ CALL_ERR( err, BtLibLinkConnect, btStuff->btLibRefNum, &btStuff->u.slave.masterAddr ); if ( btLibErrAlreadyConnected == err ) { pbt_connect_slave( btStuff ); } XP_ASSERT( err == btLibErrPending ); } else { XP_LOGF( "%s: doing nothing", __FUNCTION__ ); } } /* pbt_setup_slave */ static Err initBTStuff( PalmAppGlobals* globals, DataCb cb, XP_Bool amMaster ) { PalmBTStuff* btStuff; Err err; LOG_FUNC(); btStuff = globals->btStuff; if ( btStuff != NULL ) { if ( NULL != cb ) { btStuff->cb = cb; } if ( btStuff->amMaster == amMaster ) { /* nothing to do */ } else { /* role change. Adapt... */ XP_ASSERT( 0 ); } err = errNone; } else { XP_U16 btLibRefNum; CALL_ERR( err, SysLibFind, btLibName, &btLibRefNum ); if ( errNone == err ) { btStuff = XP_MALLOC( globals->mpool, sizeof(*btStuff) ); XP_ASSERT( !!btStuff ); XP_MEMSET( btStuff, 0, sizeof(*btStuff) ); globals->btStuff = btStuff; btStuff->globals = globals; btStuff->cb = cb; btStuff->amMaster = amMaster; btStuff->dataSocket = SOCK_INVAL; btStuff->btLibRefNum = btLibRefNum; CALL_ERR( err, BtLibOpen, btLibRefNum, false ); XP_ASSERT( errNone == err ); CALL_ERR( err, BtLibRegisterManagementNotification, btLibRefNum, libMgmtCallback, (UInt32)btStuff ); if ( btStuff->amMaster ) { pbt_setup_master( btStuff ); } else { /* Can't set up b/c don't have address yet. */ /* pbt_setup_slave( btStuff, addr ); */ } } } LOG_RETURNF( "%s", btErrToStr(err) ); return err; } /* initBTStuff */ static void l2SocketCallback( BtLibSocketEventType* sEvent, UInt32 refCon ) { PalmBTStuff* btStuff = (PalmBTStuff*)refCon; BtLibSocketEventEnum event = sEvent->event; Err err; XP_LOGF( "%s(%s)", __FUNCTION__, btEvtToStr(event) ); switch( event ) { case btLibSocketEventConnectRequest: CALL_ERR( err, BtLibSocketRespondToConnection, btStuff->btLibRefNum, sEvent->socket, true ); break; case btLibSocketEventConnectedInbound: if ( sEvent->status == errNone ) { btStuff->dataSocket = sEvent->eventData.newSocket; XP_LOGF( "we have a data socket!!!" ); pbt_send_pending( btStuff, NULL ); } else { XP_LOGF( "%s: status = %d(%s)", __FUNCTION__, sEvent->status, btErrToStr(sEvent->status) ); } break; case btLibSocketEventConnectedOutbound: pbt_send_pending( btStuff, NULL ); break; case btLibSocketEventData: XP_ASSERT( sEvent->status == errNone ); XP_ASSERT( sEvent->socket == btStuff->dataSocket ); XP_ASSERT( !!btStuff->cb ); (*btStuff->cb)( btStuff->globals, sEvent->eventData.data.data, sEvent->eventData.data.dataLen ); break; default: break; } } /* l2SocketCallback */ /*********************************************************************** * Callbacks ***********************************************************************/ static void libMgmtCallback( BtLibManagementEventType* mEvent, UInt32 refCon ) { PalmBTStuff* btStuff = (PalmBTStuff*)refCon; BtLibManagementEventEnum event = mEvent->event; XP_LOGF( "%s(%s)", __FUNCTION__, mgmtEvtToStr(event) ); switch( event ) { case btLibManagementEventACLConnectOutbound: if ( btLibErrNoError == mEvent->status ) { XP_LOGF( "successful ACL connection to master!" ); pbt_connect_slave( btStuff ); } break; case btLibManagementEventACLConnectInbound: if ( btLibErrNoError == mEvent->status ) { XP_LOGF( "successful ACL connection!" ); } break; default: break; } } /* libMgmtCallback */ /*********************************************************************** * Debug helpers for verbose logging ***********************************************************************/ #ifdef DEBUG # define CASESTR(e) case(e): return #e static const char* btEvtToStr( BtLibSocketEventEnum evt ) { switch( evt ) { CASESTR(btLibSocketEventConnectRequest); CASESTR(btLibSocketEventConnectedOutbound); CASESTR(btLibSocketEventConnectedInbound); CASESTR(btLibSocketEventDisconnected); CASESTR(btLibSocketEventData); CASESTR(btLibSocketEventSendComplete); CASESTR(btLibSocketEventSdpServiceRecordHandle); CASESTR(btLibSocketEventSdpGetAttribute); CASESTR(btLibSocketEventSdpGetStringLen); CASESTR(btLibSocketEventSdpGetNumListEntries); CASESTR(btLibSocketEventSdpGetNumLists); CASESTR(btLibSocketEventSdpGetRawAttribute); CASESTR(btLibSocketEventSdpGetRawAttributeSize); CASESTR(btLibSocketEventSdpGetServerChannelByUuid); CASESTR(btLibSocketEventSdpGetPsmByUuid); default: XP_ASSERT(0); return ""; } } /* btEvtToStr */ static const char* mgmtEvtToStr( BtLibManagementEventEnum event ) { switch( event ) { CASESTR(btLibManagementEventRadioState); CASESTR(btLibManagementEventInquiryResult); CASESTR(btLibManagementEventInquiryComplete); CASESTR(btLibManagementEventInquiryCanceled); CASESTR(btLibManagementEventACLDisconnect); CASESTR(btLibManagementEventACLConnectInbound); CASESTR(btLibManagementEventACLConnectOutbound); CASESTR(btLibManagementEventPiconetCreated); CASESTR(btLibManagementEventPiconetDestroyed); CASESTR(btLibManagementEventModeChange); CASESTR(btLibManagementEventAccessibilityChange); CASESTR(btLibManagementEventEncryptionChange); CASESTR(btLibManagementEventRoleChange); CASESTR(btLibManagementEventNameResult); CASESTR(btLibManagementEventLocalNameChange); CASESTR(btLibManagementEventAuthenticationComplete); CASESTR(btLibManagementEventPasskeyRequest); CASESTR(btLibManagementEventPasskeyRequestComplete); CASESTR(btLibManagementEventPairingComplete); default: XP_ASSERT(0); return "unknown"; } } /* mgmtEvtToStr */ static const char* btErrToStr( Err err ) { switch ( err ) { CASESTR(btLibErrNoError); CASESTR(btLibErrError); CASESTR(btLibErrNotOpen); CASESTR(btLibErrBluetoothOff); CASESTR(btLibErrNoPrefs); CASESTR(btLibErrAlreadyOpen); CASESTR(btLibErrOutOfMemory); CASESTR(btLibErrFailed); CASESTR(btLibErrInProgress); CASESTR(btLibErrParamError); CASESTR(btLibErrTooMany); CASESTR(btLibErrPending); CASESTR(btLibErrNotInProgress); CASESTR(btLibErrRadioInitFailed); CASESTR(btLibErrRadioFatal); CASESTR(btLibErrRadioInitialized); CASESTR(btLibErrRadioSleepWake); CASESTR(btLibErrNoConnection); CASESTR(btLibErrAlreadyRegistered); CASESTR(btLibErrNoAclLink); CASESTR(btLibErrSdpRemoteRecord); CASESTR(btLibErrSdpAdvertised); CASESTR(btLibErrSdpFormat); CASESTR(btLibErrSdpNotAdvertised); CASESTR(btLibErrSdpQueryVersion); CASESTR(btLibErrSdpQueryHandle); CASESTR(btLibErrSdpQuerySyntax); CASESTR(btLibErrSdpQueryPduSize); CASESTR(btLibErrSdpQueryContinuation); CASESTR(btLibErrSdpQueryResources); CASESTR(btLibErrSdpQueryDisconnect); CASESTR(btLibErrSdpInvalidResponse); CASESTR(btLibErrSdpAttributeNotSet); CASESTR(btLibErrSdpMapped); CASESTR(btLibErrSocket); CASESTR(btLibErrSocketProtocol); CASESTR(btLibErrSocketRole); CASESTR(btLibErrSocketPsmUnavailable); CASESTR(btLibErrSocketChannelUnavailable); CASESTR(btLibErrSocketUserDisconnect); CASESTR(btLibErrCanceled); CASESTR(btLibErrBusy); CASESTR(btLibMeStatusUnknownHciCommand); CASESTR(btLibMeStatusNoConnection); CASESTR(btLibMeStatusHardwareFailure); CASESTR(btLibMeStatusPageTimeout); CASESTR(btLibMeStatusAuthenticateFailure); CASESTR(btLibMeStatusMissingKey); CASESTR(btLibMeStatusMemoryFull); CASESTR(btLibMeStatusConnnectionTimeout); CASESTR(btLibMeStatusMaxConnections); CASESTR(btLibMeStatusMaxScoConnections); CASESTR(btLibMeStatusMaxAclConnections); CASESTR(btLibMeStatusCommandDisallowed); CASESTR(btLibMeStatusLimitedResources); CASESTR(btLibMeStatusSecurityError); CASESTR(btLibMeStatusPersonalDevice); CASESTR(btLibMeStatusHostTimeout); CASESTR(btLibMeStatusUnsupportedFeature); CASESTR(btLibMeStatusInvalidHciParam); CASESTR(btLibMeStatusUserTerminated); CASESTR(btLibMeStatusLowResources); CASESTR(btLibMeStatusPowerOff); CASESTR(btLibMeStatusLocalTerminated); CASESTR(btLibMeStatusRepeatedAttempts); CASESTR(btLibMeStatusPairingNotAllowed); CASESTR(btLibMeStatusUnknownLmpPDU); CASESTR(btLibMeStatusUnsupportedRemote); CASESTR(btLibMeStatusScoOffsetRejected); CASESTR(btLibMeStatusScoIntervalRejected); CASESTR(btLibMeStatusScoAirModeRejected); CASESTR(btLibMeStatusInvalidLmpParam); CASESTR(btLibMeStatusUnspecifiedError); CASESTR(btLibMeStatusUnsupportedLmpParam); CASESTR(btLibMeStatusRoleChangeNotAllowed); CASESTR(btLibMeStatusLmpResponseTimeout); CASESTR(btLibMeStatusLmpTransdCollision); CASESTR(btLibMeStatusLmpPduNotAllowed); CASESTR(btLibL2DiscReasonUnknown); CASESTR(btLibL2DiscUserRequest); CASESTR(btLibL2DiscRequestTimeout); CASESTR(btLibL2DiscLinkDisc); CASESTR(btLibL2DiscQosViolation); CASESTR(btLibL2DiscSecurityBlock); CASESTR(btLibL2DiscConnPsmUnsupported); CASESTR(btLibL2DiscConnSecurityBlock); CASESTR(btLibL2DiscConnNoResources); CASESTR(btLibL2DiscConfigUnacceptable); CASESTR(btLibL2DiscConfigReject); CASESTR(btLibL2DiscConfigOptions); CASESTR(btLibServiceShutdownAppUse); CASESTR(btLibServiceShutdownPowerCycled); CASESTR(btLibServiceShutdownAclDrop); CASESTR(btLibServiceShutdownTimeout); CASESTR(btLibServiceShutdownDetached); CASESTR(btLibErrInUseByService); CASESTR(btLibErrNoPiconet); CASESTR(btLibErrRoleChange); CASESTR(btLibErrSdpNotMapped); CASESTR(btLibErrAlreadyConnected); CASESTR(btLibErrStackNotOpen); CASESTR(btLibErrBatteryTooLow); CASESTR(btLibErrNotFound); CASESTR(btLibNotYetSupported); default: return "unknown err"; } } /* btErrToStr */ static void palm_bt_log( const char* btfunc, const char* func, Err err ) { /* if ( errNone != err ) { */ XP_LOGF( "%s from %s called in %s", btErrToStr(err), btfunc, func ); /* } */ } #endif /* DEBUG */ /* use piconet? With that, HOST sets it up and clients join. That establishes ACL links that can then be used to open sockets. I think. How to get from piconet advertising to clients connecting? See http://www.palmos.com/dev/support/docs/palmos/BTCompanion.html NOTE: I've read conflicting reports on whether a listening socket is good for accepting more than one inbound connection. Confirm. Or just do a piconet. */ #endif /* #ifdef XWFEATURE_BLUETOOTH */