├── .devcontainer ├── Dockerfile ├── devcontainer.json └── library-scripts │ ├── common-debian.sh │ └── node-debian.sh ├── .editorconfig ├── .github ├── FUNDING.yml ├── pull_request_template.md └── workflows │ ├── swift-arm.yml │ ├── swift.yml │ └── swiftlint.yml ├── .gitignore ├── .gitlab-ci.yml ├── .swift-format ├── .swiftlint.yml ├── Assets ├── ATTExchangeMTU.png ├── AdvertisingInterval.png ├── DiscoverAllCharacteristicDescriptors.png ├── DiscoverAllCharacteristics.png ├── DiscoverAllPrimaryServices.png ├── DiscoverCharacteristicsByUUID.png ├── DiscoverPrimaryServiceByServiceUUID.png ├── ExchangeMTU.png ├── FindIncludedServices.png ├── LowEnergyAdvertisingDataExample1.png ├── Notifications.png ├── PaintCode.pcvd ├── PureSwiftBluetooth.png ├── ReadCharacteristicValue.png ├── ReadLongCharacteristicValues .png ├── ReadMultipleCharacteristicValues.png ├── ReadUsingCharacteristicUUID.png ├── ReliableWrites.png ├── SignedWriteWithoutResponse.png ├── WriteCharacteristicValue.png └── WriteLongCharacteristicValues.png ├── LICENSE ├── Package.swift ├── Plugins └── GenerateBluetoothDefinitions │ ├── BluetoothUUID.swift │ ├── CompanyIdentifier.swift │ ├── Plugin.swift │ └── UnitIdentifier.swift ├── README.md ├── Sources ├── Bluetooth │ ├── Address.swift │ ├── AsyncIndefiniteStream.swift │ ├── BitMaskOption.swift │ ├── Bluetooth.docc │ │ └── Bluetooth.md │ ├── BluetoothUUID.swift │ ├── BluetoothUUIDMetadata.swift │ ├── ByteSwap.swift │ ├── ByteValue.swift │ ├── ClassOfDevice.swift │ ├── CompanyIdentifier.swift │ ├── CompanyIdentifierMetadata.swift │ ├── Data.swift │ ├── Extensions │ │ ├── Hexadecimal.swift │ │ ├── Integer.swift │ │ ├── String.swift │ │ ├── System.swift │ │ └── UUID.swift │ ├── L2CAPSocket.swift │ ├── LowEnergyAdvertisingData.swift │ ├── LowEnergyScanTimeInterval.swift │ ├── RSSI.swift │ ├── SFloat.swift │ ├── SecurityLevel.swift │ ├── UInt128.swift │ ├── UInt24.swift │ ├── UInt256.swift │ ├── UInt40.swift │ ├── UInt48.swift │ ├── UInt512.swift │ ├── Unit.swift │ ├── UnitIdentifier.swift │ ├── UnitIdentifierMetadata.swift │ └── iBeacon.swift ├── BluetoothGAP │ ├── Decoder.swift │ ├── Encoder.swift │ ├── Extensions │ │ ├── Hexadecimal.swift │ │ ├── Integer.swift │ │ ├── OptionSet.swift │ │ ├── String.swift │ │ └── System.swift │ ├── GAP3DInformation.swift │ ├── GAPAdvertisingInterval.swift │ ├── GAPAppearance.swift │ ├── GAPAppearanceData.swift │ ├── GAPChannelMapUpdateIndication.swift │ ├── GAPClassOfDevice.swift │ ├── GAPCompleteListOf128BitServiceClassUUIDs.swift │ ├── GAPCompleteListOf16BitServiceClassUUIDs.swift │ ├── GAPCompleteListOf32BitServiceClassUUIDs.swift │ ├── GAPCompleteLocalName.swift │ ├── GAPData.swift │ ├── GAPDataType.swift │ ├── GAPFlags.swift │ ├── GAPIncompleteListOf128BitServiceClassUUIDs.swift │ ├── GAPIncompleteListOf16BitServiceClassUUIDs.swift │ ├── GAPIncompleteListOf32BitServiceClassUUIDs.swift │ ├── GAPIndoorPositioning.swift │ ├── GAPLEDeviceAddress.swift │ ├── GAPLERole.swift │ ├── GAPLESecureConnectionsConfirmation.swift │ ├── GAPLESecureConnectionsRandom.swift │ ├── GAPLESupportedFeatures.swift │ ├── GAPListOf128BitServiceSolicitationUUIDs.swift │ ├── GAPListOf16BitServiceSolicitationUUIDs.swift │ ├── GAPListOf32BitServiceSolicitationUUIDs.swift │ ├── GAPManufacturerSpecificData.swift │ ├── GAPMeshBeacon.swift │ ├── GAPMeshMessage.swift │ ├── GAPPBADV.swift │ ├── GAPPublicTargetAddress.swift │ ├── GAPRandomTargetAddress.swift │ ├── GAPSecurityManagerOOBFlags.swift │ ├── GAPSecurityManagerTKValue.swift │ ├── GAPServiceData128BitUUID.swift │ ├── GAPServiceData16BitUUID.swift │ ├── GAPServiceData32BitUUID.swift │ ├── GAPShortLocalName.swift │ ├── GAPSimplePairingHashC.swift │ ├── GAPSimplePairingRandomizerR.swift │ ├── GAPSlaveConnectionIntervalRange.swift │ ├── GAPTransportDiscoveryData.swift │ ├── GAPTxPowerLevel.swift │ ├── GAPURI.swift │ ├── GAPUUIDList.swift │ └── iBeacon.swift ├── BluetoothGATT │ ├── ATTAttributePermissions.swift │ ├── ATTConnection.swift │ ├── ATTError.swift │ ├── ATTErrorResponse.swift │ ├── ATTExecuteWriteRequest.swift │ ├── ATTExecuteWriteResponse.swift │ ├── ATTFindByTypeRequest.swift │ ├── ATTFindByTypeResponse.swift │ ├── ATTFindInformationRequest.swift │ ├── ATTFindInformationResponse.swift │ ├── ATTHandleValueConfirmation.swift │ ├── ATTHandleValueIndication.swift │ ├── ATTHandleValueNotification.swift │ ├── ATTMaximumTransmissionUnit.swift │ ├── ATTMaximumTransmissionUnitRequest.swift │ ├── ATTMaximumTransmissionUnitResponse.swift │ ├── ATTOpcode.swift │ ├── ATTPrepareWriteRequest.swift │ ├── ATTPrepareWriteResponse.swift │ ├── ATTProtocolDataUnit.swift │ ├── ATTReadBlobRequest.swift │ ├── ATTReadBlobResponse.swift │ ├── ATTReadByGroupTypeRequest.swift │ ├── ATTReadByGroupTypeResponse.swift │ ├── ATTReadByTypeRequest.swift │ ├── ATTReadByTypeResponse.swift │ ├── ATTReadMultipleRequest.swift │ ├── ATTReadMultipleResponse.swift │ ├── ATTReadRequest.swift │ ├── ATTReadResponse.swift │ ├── ATTSignedWriteCommand.swift │ ├── ATTWriteCommand.swift │ ├── ATTWriteRequest.swift │ ├── ATTWriteResponse.swift │ ├── Extensions │ │ ├── Array.swift │ │ ├── Bool.swift │ │ ├── Data.swift │ │ ├── Hexadecimal.swift │ │ ├── Integer.swift │ │ ├── OptionSet.swift │ │ ├── String.swift │ │ └── System.swift │ ├── GATT.swift │ ├── GATTAerobicHeartRateLowerLimit.swift │ ├── GATTAerobicHeartRateUpperLimit.swift │ ├── GATTAerobicThreshold.swift │ ├── GATTAge.swift │ ├── GATTAggregateFormatDescriptor.swift │ ├── GATTAlertCategory.swift │ ├── GATTAlertCategoryBitMask.swift │ ├── GATTAlertLevel.swift │ ├── GATTAlertNotificationControlPoint.swift │ ├── GATTAlertNotificationService.swift │ ├── GATTAlertStatus.swift │ ├── GATTAltitude.swift │ ├── GATTAnaerobicHeartRateLowerLimit.swift │ ├── GATTAnaerobicHeartRateUpperLimit.swift │ ├── GATTAttributes.swift │ ├── GATTBarometricPressureTrend.swift │ ├── GATTBatteryLevel.swift │ ├── GATTBatteryPowerState.swift │ ├── GATTBatteryService.swift │ ├── GATTBloodPressureFeature.swift │ ├── GATTBloodPressureManagement.swift │ ├── GATTBloodPressureService.swift │ ├── GATTBodyCompositionMeasurement.swift │ ├── GATTBodySensorLocation.swift │ ├── GATTBootKeyboardInputReport.swift │ ├── GATTBootKeyboardOutputReport.swift │ ├── GATTBootMouseInputReport.swift │ ├── GATTCGMSessionRunTime.swift │ ├── GATTCentralAddressResolution.swift │ ├── GATTCharacteristic.swift │ ├── GATTCharacteristicExtendedProperties.swift │ ├── GATTCharacteristicFormatType.swift │ ├── GATTCharacteristicProperties.swift │ ├── GATTClient.swift │ ├── GATTClientCharacteristicConfiguration.swift │ ├── GATTCrossTrainerData.swift │ ├── GATTCurrentTime.swift │ ├── GATTDatabase.swift │ ├── GATTDateTime.swift │ ├── GATTDateUTC.swift │ ├── GATTDayDateTime.swift │ ├── GATTDayOfWeek.swift │ ├── GATTDescriptor.swift │ ├── GATTDstOffset.swift │ ├── GATTExactTime256.swift │ ├── GATTExternalReportReference.swift │ ├── GATTFirmwareRevisionString.swift │ ├── GATTFloorNumber.swift │ ├── GATTFormatDescriptor.swift │ ├── GATTHardwareRevisionString.swift │ ├── GATTIndoorPositioningConfiguration.swift │ ├── GATTLatitude.swift │ ├── GATTLocalEastCoordinate.swift │ ├── GATTLocalNorthCoordinate.swift │ ├── GATTLocalTimeInformation.swift │ ├── GATTLocationName.swift │ ├── GATTLongitude.swift │ ├── GATTManufacturerNameString.swift │ ├── GATTModelNumber.swift │ ├── GATTNewAlert.swift │ ├── GATTNumberOfDigitals.swift │ ├── GATTObjectID.swift │ ├── GATTObjectName.swift │ ├── GATTObjectSize.swift │ ├── GATTObjectType.swift │ ├── GATTPnPID.swift │ ├── GATTProfile.swift │ ├── GATTReferenceTimeInformation.swift │ ├── GATTReportReference.swift │ ├── GATTScanIntervalWindow.swift │ ├── GATTScanRefresh.swift │ ├── GATTSerialNumberString.swift │ ├── GATTServer.swift │ ├── GATTServerCharacteristicConfiguration.swift │ ├── GATTService.swift │ ├── GATTSoftwareRevisionString.swift │ ├── GATTSupportedNewAlertCategory.swift │ ├── GATTSupportedUnreadAlertCategory.swift │ ├── GATTSystemID.swift │ ├── GATTTimeAccuracy.swift │ ├── GATTTimeSource.swift │ ├── GATTTimeTriggerSetting.swift │ ├── GATTTimeUpdateControlPoint.swift │ ├── GATTTimeUpdateState.swift │ ├── GATTTimeWithDst.swift │ ├── GATTTimeZone.swift │ ├── GATTUUIDList.swift │ ├── GATTUncertainty.swift │ ├── GATTUnits.swift │ ├── GATTUnreadAlertStatus.swift │ └── GATTUserDescription.swift ├── BluetoothHCI │ ├── AdvertisingChannelHeader.swift │ ├── AdvertisingInterval.swift │ ├── BluetoothHostController.swift │ ├── ChannelIdentifier.swift │ ├── ConnectionAcceptTimeout.swift │ ├── Extensions │ │ ├── Bool.swift │ │ ├── Hexadecimal.swift │ │ ├── Integer.swift │ │ ├── String.swift │ │ └── System.swift │ ├── HCI.swift │ ├── HCIAcceptConnectionRequest.swift │ ├── HCIAuthenticationComplete.swift │ ├── HCIAuthenticationRequested.swift │ ├── HCIChangeConnectionPacketType.swift │ ├── HCICommand.swift │ ├── HCICommandComplete.swift │ ├── HCICommandStatus.swift │ ├── HCICommandTimeout.swift │ ├── HCIConnectionComplete.swift │ ├── HCIConnectionPacketTypeChange.swift │ ├── HCIConnectionRequest.swift │ ├── HCICreateConnection.swift │ ├── HCICreateConnectionCancel.swift │ ├── HCIDeleteStoredLinkKey.swift │ ├── HCIDisconnect.swift │ ├── HCIDisconnectionComplete.swift │ ├── HCIEncryptionChange.swift │ ├── HCIEncryptionKeyRefreshComplete.swift │ ├── HCIError.swift │ ├── HCIEvent.swift │ ├── HCIExitPeriodicInquiryMode.swift │ ├── HCIGeneralEvent.swift │ ├── HCIHoldMode.swift │ ├── HCIIOCapabilityRequest.swift │ ├── HCIIOCapabilityRequestReply.swift │ ├── HCIIOCapabilityResponse.swift │ ├── HCIInquiry.swift │ ├── HCIInquiryCancel.swift │ ├── HCIInquiryComplete.swift │ ├── HCIInquiryResult.swift │ ├── HCILEAddDeviceToPeriodicAdvertiserList.swift │ ├── HCILEAddDeviceToResolvingList.swift │ ├── HCILEAddDeviceToWhiteList.swift │ ├── HCILEAdvertisingReport.swift │ ├── HCILEAdvertisingSetTerminated.swift │ ├── HCILEChannelSelectionAlgorithm.swift │ ├── HCILEConnectionComplete.swift │ ├── HCILEConnectionUpdateComplete.swift │ ├── HCILECreateConnection.swift │ ├── HCILEDataLengthChange.swift │ ├── HCILEDirectedAdvertisingReport.swift │ ├── HCILEEncrypt.swift │ ├── HCILEEnhancedConnectionComplete.swift │ ├── HCILEEnhancedReceiverTest.swift │ ├── HCILEEnhancedTransmitterTest.swift │ ├── HCILEExtendedAdvertisingReport.swift │ ├── HCILEExtendedCreateConnection.swift │ ├── HCILEGenerateDHKey.swift │ ├── HCILEGenerateDHKeyComplete.swift │ ├── HCILELongTermKeyRequest.swift │ ├── HCILELongTermKeyRequestNegativeReply.swift │ ├── HCILELongTermKeyRequestReply.swift │ ├── HCILEPeriodicAdvertisingCreateSync.swift │ ├── HCILEPeriodicAdvertisingReport.swift │ ├── HCILEPeriodicAdvertisingSyncEstablished.swift │ ├── HCILEPeriodicAdvertisingSyncLost.swift │ ├── HCILEPeriodicAdvertisingTerminateSync.swift │ ├── HCILEPhyUpdateComplete.swift │ ├── HCILERandom.swift │ ├── HCILEReadAdvertisingChannelTxPower.swift │ ├── HCILEReadBufferSize.swift │ ├── HCILEReadChannelMap.swift │ ├── HCILEReadLocalP256PublicKeyComplete.swift │ ├── HCILEReadLocalResolvableAddressReturn.swift │ ├── HCILEReadLocalSupportedFeatures.swift │ ├── HCILEReadMaximumAdvertisingDataLength.swift │ ├── HCILEReadMaximumDataLength.swift │ ├── HCILEReadNumberOfSupportedAdvertisingSets.swift │ ├── HCILEReadPeerResolvableAddressReturn.swift │ ├── HCILEReadPeriodicAdvertisingListSize.swift │ ├── HCILEReadPhy.swift │ ├── HCILEReadRemoteUsedFeatures.swift │ ├── HCILEReadRemoteUsedFeaturesComplete.swift │ ├── HCILEReadResolvingListSize.swift │ ├── HCILEReadRfPathCompensation.swift │ ├── HCILEReadSuggestedDefaultDataLength.swift │ ├── HCILEReadSupportedStates.swift │ ├── HCILEReadTransmitPower.swift │ ├── HCILEReadWhiteListSize.swift │ ├── HCILEReceiverTest.swift │ ├── HCILERemoteConnectionParameterRequest.swift │ ├── HCILERemoteConnectionParameterRequestNegativeReply.swift │ ├── HCILERemoteConnectionParameterRequestReply.swift │ ├── HCILERemoveAdvertisingSet.swift │ ├── HCILERemoveDeviceFromResolvingList.swift │ ├── HCILERemoveDeviceFromWhiteList.swift │ ├── HCILERemoveDeviceToPeriodicAdvertiserList.swift │ ├── HCILEScanRequestReceived.swift │ ├── HCILESetAddressResolutionEnable.swift │ ├── HCILESetAdvertiseEnable.swift │ ├── HCILESetAdvertisingData.swift │ ├── HCILESetAdvertisingParameters.swift │ ├── HCILESetAdvertisingSetRandomAddress.swift │ ├── HCILESetDataLength.swift │ ├── HCILESetDefaultPhy.swift │ ├── HCILESetEventMask.swift │ ├── HCILESetExtendedAdvertisingData.swift │ ├── HCILESetExtendedAdvertisingParameters.swift │ ├── HCILESetExtendedScanEnable.swift │ ├── HCILESetExtendedScanParameters.swift │ ├── HCILESetExtendedScanResponseData.swift │ ├── HCILESetHostChannelClassification.swift │ ├── HCILESetPeriodicAdvertisingData.swift │ ├── HCILESetPeriodicAdvertisingEnable.swift │ ├── HCILESetPeriodicAdvertisingParameters.swift │ ├── HCILESetPhy.swift │ ├── HCILESetPrivacyMode.swift │ ├── HCILESetRandomAddress.swift │ ├── HCILESetResolvablePrivateAddressTimeout.swift │ ├── HCILESetScanEnable.swift │ ├── HCILESetScanParameters.swift │ ├── HCILESetScanResponseData.swift │ ├── HCILEStartEncryption.swift │ ├── HCILETestEnd.swift │ ├── HCILETransmitterTest.swift │ ├── HCILEUpdateConnection.swift │ ├── HCILEWriteRfPathCompensation.swift │ ├── HCILEWriteSuggestedDefaultDataLength.swift │ ├── HCILinkKeyNotification.swift │ ├── HCILinkKeyRequest.swift │ ├── HCILinkKeyRequestNegativeReply.swift │ ├── HCILinkKeyRequestReply.swift │ ├── HCILowEnergyMetaEvent.swift │ ├── HCIMaxSlotsChange.swift │ ├── HCIModeChange.swift │ ├── HCINumberOfCompletedPackets.swift │ ├── HCIPINCodeRequest.swift │ ├── HCIPINCodeRequestReply.swift │ ├── HCIPacketHeader.swift │ ├── HCIPageScanRepetitionMode.swift │ ├── HCIPeriodicInquiryMode.swift │ ├── HCIQoSSetup.swift │ ├── HCIQoSSetupComplete.swift │ ├── HCIReadClassOfDevice.swift │ ├── HCIReadClockOffset.swift │ ├── HCIReadClockOffsetComplete.swift │ ├── HCIReadConnectionAcceptTimeout.swift │ ├── HCIReadDataBlockSize.swift │ ├── HCIReadDeviceAddress.swift │ ├── HCIReadLMPHandle.swift │ ├── HCIReadLocalName.swift │ ├── HCIReadLocalSupportedFeatures.swift │ ├── HCIReadLocalVersionInformation.swift │ ├── HCIReadPageTimeout.swift │ ├── HCIReadRemoteExtendedFeatures.swift │ ├── HCIReadRemoteExtendedFeaturesComplete.swift │ ├── HCIReadRemoteFeaturesComplete.swift │ ├── HCIReadRemoteSupportedFeatures.swift │ ├── HCIReadRemoteVersionInformation.swift │ ├── HCIReadRemoteVersionInformationComplete.swift │ ├── HCIReadStoredLinkKey.swift │ ├── HCIRejectConnectionRequest.swift │ ├── HCIRemoteNameRequest.swift │ ├── HCIRemoteNameRequestComplete.swift │ ├── HCIReset.swift │ ├── HCISetConnectionEncryption.swift │ ├── HCISetEventFilter.swift │ ├── HCISimplePairingComplete.swift │ ├── HCIUserConfirmationRequest.swift │ ├── HCIUserConfirmationRequestReply.swift │ ├── HCIVersion.swift │ ├── HCIWriteClassOfDevice.swift │ ├── HCIWriteConnectionAcceptTimeout.swift │ ├── HCIWriteLinkPolicySettings.swift │ ├── HCIWriteLinkSupervisionTimeout.swift │ ├── HCIWriteLocalName.swift │ ├── HCIWritePageScanActivity.swift │ ├── HCIWritePageScanType.swift │ ├── HCIWritePageTimeout.swift │ ├── HCIWriteScanEnable.swift │ ├── HostControllerBasebandCommand.swift │ ├── InformationalCommand.swift │ ├── LMPFeature.swift │ ├── LinkControlCommand.swift │ ├── LinkPolicyCommand.swift │ ├── LowEnergyAddressType.swift │ ├── LowEnergyAdvertiserAddressType.swift │ ├── LowEnergyAdvertising.swift │ ├── LowEnergyAllPhys.swift │ ├── LowEnergyChannelMap.swift │ ├── LowEnergyClockAccuracy.swift │ ├── LowEnergyCommand.swift │ ├── LowEnergyConnection.swift │ ├── LowEnergyConnectionInterval.swift │ ├── LowEnergyConnectionIntervalRange.swift │ ├── LowEnergyConnectionLatency.swift │ ├── LowEnergyConnectionLength.swift │ ├── LowEnergyEvent.swift │ ├── LowEnergyFeature.swift │ ├── LowEnergyFragmentPreference.swift │ ├── LowEnergyMaxTxOctets.swift │ ├── LowEnergyMaxTxTime.swift │ ├── LowEnergyPacketPayload.swift │ ├── LowEnergyPeerIdentifyAddressType.swift │ ├── LowEnergyPhyOptions.swift │ ├── LowEnergyResolvingList.swift │ ├── LowEnergyRfRxPathCompensationValue.swift │ ├── LowEnergyRfTxPathCompensationValue.swift │ ├── LowEnergyRole.swift │ ├── LowEnergyRxChannel.swift │ ├── LowEnergyRxPhy.swift │ ├── LowEnergyRxPhys.swift │ ├── LowEnergyScanInterval.swift │ ├── LowEnergyState.swift │ ├── LowEnergySupervisionTimeout.swift │ ├── LowEnergyTxChannel.swift │ ├── LowEnergyTxPhy.swift │ ├── LowEnergyTxPhys.swift │ ├── LowEnergyTxPower.swift │ ├── LowEnergyWhiteList.swift │ ├── LowEnergyWhiteListDevice.swift │ ├── PacketType.swift │ ├── ProtocolServiceMultiplexer.swift │ ├── StatusParametersCommand.swift │ ├── VendorCommand.swift │ └── iBeacon.swift ├── BluetoothMacros │ ├── BluetoothAddress.swift │ ├── BluetoothUUID.swift │ ├── Extensions │ │ └── Hexadecimal.swift │ └── Plugins.swift ├── BluetoothMetadata │ ├── BluetoothMetadata.swift │ ├── BluetoothUUID.swift │ ├── CompanyIdentifier.swift │ └── Resources │ │ ├── CharacteristicUUID.json │ │ ├── CompanyIdentifier.json │ │ ├── DeclarationUUID.json │ │ ├── DescriptorUUID.json │ │ ├── MemberUUID.json │ │ ├── ServiceUUID.json │ │ └── UnitIdentifier.json └── GenerateBluetooth │ ├── BluetoothUUID.swift │ ├── CompanyIdentifier.swift │ ├── Extensions │ ├── Hexadecimal.swift │ └── String.swift │ ├── Generate.swift │ └── UnitIdentifier.swift └── Tests └── BluetoothTests ├── AddressTests.swift ├── AttributeProtocolTests.swift ├── BluetoothTests.swift ├── BluetoothUUIDTests.swift ├── DarwinTests.swift ├── GAPTests.swift ├── GATTCharacteristicTests.swift ├── GATTDatabaseTests.swift ├── GATTDescriptorTests.swift ├── GATTTests.swift ├── HCITests.swift ├── HostController.swift ├── IntegerTests.swift ├── L2CAPSocket.swift ├── TestProfile.swift ├── UInt128Tests.swift ├── UInt24Tests.swift ├── UInt256Tests.swift ├── UInt40Tests.swift ├── UInt48Tests.swift ├── UInt512Tests.swift ├── UUIDTests.swift └── iBeaconTests.swift /.devcontainer/Dockerfile: -------------------------------------------------------------------------------- 1 | # [Choice] Swift version: 2 | ARG VARIANT=6.0.3-jammy 3 | FROM swiftlang/swift:${VARIANT} 4 | 5 | # [Option] Install zsh 6 | ARG INSTALL_ZSH="true" 7 | # [Option] Upgrade OS packages to their latest versions 8 | ARG UPGRADE_PACKAGES="false" 9 | 10 | # Install needed packages and setup non-root user. Use a separate RUN statement to add your own dependencies. 11 | ARG USERNAME=vscode 12 | ARG USER_UID=1000 13 | ARG USER_GID=$USER_UID 14 | COPY library-scripts/common-debian.sh /tmp/library-scripts/ 15 | RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ 16 | && /bin/bash /tmp/library-scripts/common-debian.sh "${INSTALL_ZSH}" "${USERNAME}" "${USER_UID}" "${USER_GID}" "${UPGRADE_PACKAGES}" "true" "true" \ 17 | && apt-get autoremove -y && apt-get clean -y && rm -rf /var/lib/apt/lists/* && rm -rf /tmp/library-scripts 18 | 19 | # [Choice] Node.js version: none, lts/*, 16, 14, 12, 10 20 | ARG NODE_VERSION="none" 21 | ENV NVM_DIR=/usr/local/share/nvm 22 | ENV NVM_SYMLINK_CURRENT=true \ 23 | PATH=${NVM_DIR}/current/bin:${PATH} 24 | COPY library-scripts/node-debian.sh /tmp/library-scripts/ 25 | RUN bash /tmp/library-scripts/node-debian.sh "${NVM_DIR}" "${NODE_VERSION}" "${USERNAME}" \ 26 | && rm -rf /var/lib/apt/lists/* /tmp/library-scripts 27 | 28 | -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | // For format details, see https://aka.ms/devcontainer.json. For config options, see the README at: 2 | // https://github.com/microsoft/vscode-dev-containers/tree/v0.245.2/containers/swift 3 | { 4 | "name": "Swift", 5 | "build": { 6 | "dockerfile": "Dockerfile", 7 | "args": { 8 | // Update the VARIANT arg to pick a Swift version 9 | "VARIANT": "nightly-jammy", 10 | // Options 11 | "NODE_VERSION": "lts/*", 12 | "UPGRADE_PACKAGES": "true" 13 | } 14 | }, 15 | "runArgs": [ 16 | "--cap-add=SYS_PTRACE", 17 | "--security-opt", 18 | "seccomp=unconfined" 19 | ], 20 | 21 | // Configure tool-specific properties. 22 | "customizations": { 23 | // Configure properties specific to VS Code. 24 | "vscode": { 25 | // Set *default* container specific settings.json values on container create. 26 | "settings": { 27 | "lldb.library": "/usr/lib/liblldb.so" 28 | }, 29 | 30 | // Add the IDs of extensions you want installed when the container is created. 31 | "extensions": [ 32 | "sswg.swift-lang" 33 | ] 34 | } 35 | }, 36 | 37 | // Use 'forwardPorts' to make a list of ports inside the container available locally. 38 | // "forwardPorts": [], 39 | 40 | // Use 'postCreateCommand' to run commands after the container is created. 41 | // "postCreateCommand": "", 42 | 43 | // Comment out to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root. 44 | "remoteUser": "vscode" 45 | } 46 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | indent_style = space 7 | indent_size = 4 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | ko_fi: colemancda 3 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | **Issue** 2 | 3 | Fixes #1. 4 | 5 | **What does this PR Do?** 6 | 7 | Description of the changes in this pull request. 8 | 9 | **Where should the reviewer start?** 10 | 11 | `main.swift` 12 | 13 | **Sweet giphy showing how you feel about this PR** 14 | 15 | ![Giphy](https://media.giphy.com/media/rkDXJA9GoWR2/giphy.gif) 16 | -------------------------------------------------------------------------------- /.github/workflows/swift.yml: -------------------------------------------------------------------------------- 1 | name: Swift 2 | on: [push] 3 | jobs: 4 | 5 | macos: 6 | name: macOS 7 | runs-on: macos-15 8 | steps: 9 | - name: Checkout 10 | uses: actions/checkout@v4 11 | - name: Swift Version 12 | run: swift --version 13 | - name: Build (Debug) 14 | run: swift build -c debug 15 | - name: Build (Release) 16 | run: swift build -c release 17 | - name: Test (Debug) 18 | run: swift test -c debug 19 | 20 | linux: 21 | name: Linux 22 | strategy: 23 | matrix: 24 | container: ["swift:6.0.3", "swiftlang/swift:nightly"] 25 | runs-on: ubuntu-latest 26 | container: ${{ matrix.container }}-jammy 27 | steps: 28 | - name: Checkout 29 | uses: actions/checkout@v4 30 | - name: Swift Version 31 | run: swift --version 32 | - name: Build (Debug) 33 | run: swift build -c debug 34 | - name: Build (Release) 35 | run: swift build -c release 36 | - name: Test (Debug) 37 | run: swift test -c debug 38 | -------------------------------------------------------------------------------- /.github/workflows/swiftlint.yml: -------------------------------------------------------------------------------- 1 | name: Swift Lint 2 | on: [pull_request] 3 | jobs: 4 | swift-format-lint: 5 | name: swift-format lint 6 | runs-on: ubuntu-latest 7 | container: swift:6.0.3 8 | steps: 9 | - name: Checkout 10 | uses: actions/checkout@v4 11 | - name: Swift Version 12 | run: swift --version 13 | - name: Lint 14 | run: swift format lint -r ./ --configuration .swift-format 15 | 16 | swiftlint: 17 | name: swiftlint 18 | runs-on: macos-15 19 | steps: 20 | - name: Checkout 21 | uses: actions/checkout@v4 22 | - name: Swift Version 23 | run: swift --version 24 | - name: Install SwiftLint 25 | run: brew install swiftlint 26 | - name: Lint 27 | run: swiftlint 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData/ 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata/ 19 | 20 | ## Other 21 | *.moved-aside 22 | *.xccheckout 23 | *.xcscmblueprint 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | *.ipa 28 | *.dSYM.zip 29 | *.dSYM 30 | 31 | ## Playgrounds 32 | timeline.xctimeline 33 | playground.xcworkspace 34 | 35 | # Swift Package Manager 36 | # 37 | Packages/ 38 | Package.pins 39 | Package.resolved 40 | .build/ 41 | Bluetooth.xcodeproj/* 42 | .swiftpm 43 | 44 | # CocoaPods 45 | # 46 | # We recommend against adding the Pods directory to your .gitignore. However 47 | # you should judge for yourself, the pros and cons are mentioned at: 48 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 49 | # 50 | # Pods/ 51 | 52 | # Carthage 53 | # 54 | 55 | Carthage/* 56 | 57 | # fastlane 58 | # 59 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 60 | # screenshots whenever they are needed. 61 | # For more information about the recommended setup visit: 62 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 63 | 64 | fastlane/report.xml 65 | fastlane/Preview.html 66 | fastlane/screenshots 67 | fastlane/test_output 68 | 69 | # Jazzy 70 | docs 71 | 72 | # VS Code 73 | .vscode 74 | 75 | # Finder 76 | .DS_Store 77 | -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | stages: 2 | - build 3 | - test 4 | 5 | build-macos: 6 | stage: build 7 | script: swift build 8 | tags: 9 | - osx 10 | - swift5 11 | 12 | test-macos: 13 | stage: test 14 | script: swift test 15 | tags: 16 | - osx 17 | - swift5 18 | -------------------------------------------------------------------------------- /Assets/ATTExchangeMTU.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PureSwift/Bluetooth/3db3287b6e99a1815e4056aa03b8f5b170540e44/Assets/ATTExchangeMTU.png -------------------------------------------------------------------------------- /Assets/AdvertisingInterval.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PureSwift/Bluetooth/3db3287b6e99a1815e4056aa03b8f5b170540e44/Assets/AdvertisingInterval.png -------------------------------------------------------------------------------- /Assets/DiscoverAllCharacteristicDescriptors.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PureSwift/Bluetooth/3db3287b6e99a1815e4056aa03b8f5b170540e44/Assets/DiscoverAllCharacteristicDescriptors.png -------------------------------------------------------------------------------- /Assets/DiscoverAllCharacteristics.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PureSwift/Bluetooth/3db3287b6e99a1815e4056aa03b8f5b170540e44/Assets/DiscoverAllCharacteristics.png -------------------------------------------------------------------------------- /Assets/DiscoverAllPrimaryServices.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PureSwift/Bluetooth/3db3287b6e99a1815e4056aa03b8f5b170540e44/Assets/DiscoverAllPrimaryServices.png -------------------------------------------------------------------------------- /Assets/DiscoverCharacteristicsByUUID.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PureSwift/Bluetooth/3db3287b6e99a1815e4056aa03b8f5b170540e44/Assets/DiscoverCharacteristicsByUUID.png -------------------------------------------------------------------------------- /Assets/DiscoverPrimaryServiceByServiceUUID.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PureSwift/Bluetooth/3db3287b6e99a1815e4056aa03b8f5b170540e44/Assets/DiscoverPrimaryServiceByServiceUUID.png -------------------------------------------------------------------------------- /Assets/ExchangeMTU.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PureSwift/Bluetooth/3db3287b6e99a1815e4056aa03b8f5b170540e44/Assets/ExchangeMTU.png -------------------------------------------------------------------------------- /Assets/FindIncludedServices.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PureSwift/Bluetooth/3db3287b6e99a1815e4056aa03b8f5b170540e44/Assets/FindIncludedServices.png -------------------------------------------------------------------------------- /Assets/LowEnergyAdvertisingDataExample1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PureSwift/Bluetooth/3db3287b6e99a1815e4056aa03b8f5b170540e44/Assets/LowEnergyAdvertisingDataExample1.png -------------------------------------------------------------------------------- /Assets/Notifications.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PureSwift/Bluetooth/3db3287b6e99a1815e4056aa03b8f5b170540e44/Assets/Notifications.png -------------------------------------------------------------------------------- /Assets/PaintCode.pcvd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PureSwift/Bluetooth/3db3287b6e99a1815e4056aa03b8f5b170540e44/Assets/PaintCode.pcvd -------------------------------------------------------------------------------- /Assets/PureSwiftBluetooth.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PureSwift/Bluetooth/3db3287b6e99a1815e4056aa03b8f5b170540e44/Assets/PureSwiftBluetooth.png -------------------------------------------------------------------------------- /Assets/ReadCharacteristicValue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PureSwift/Bluetooth/3db3287b6e99a1815e4056aa03b8f5b170540e44/Assets/ReadCharacteristicValue.png -------------------------------------------------------------------------------- /Assets/ReadLongCharacteristicValues .png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PureSwift/Bluetooth/3db3287b6e99a1815e4056aa03b8f5b170540e44/Assets/ReadLongCharacteristicValues .png -------------------------------------------------------------------------------- /Assets/ReadMultipleCharacteristicValues.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PureSwift/Bluetooth/3db3287b6e99a1815e4056aa03b8f5b170540e44/Assets/ReadMultipleCharacteristicValues.png -------------------------------------------------------------------------------- /Assets/ReadUsingCharacteristicUUID.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PureSwift/Bluetooth/3db3287b6e99a1815e4056aa03b8f5b170540e44/Assets/ReadUsingCharacteristicUUID.png -------------------------------------------------------------------------------- /Assets/ReliableWrites.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PureSwift/Bluetooth/3db3287b6e99a1815e4056aa03b8f5b170540e44/Assets/ReliableWrites.png -------------------------------------------------------------------------------- /Assets/SignedWriteWithoutResponse.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PureSwift/Bluetooth/3db3287b6e99a1815e4056aa03b8f5b170540e44/Assets/SignedWriteWithoutResponse.png -------------------------------------------------------------------------------- /Assets/WriteCharacteristicValue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PureSwift/Bluetooth/3db3287b6e99a1815e4056aa03b8f5b170540e44/Assets/WriteCharacteristicValue.png -------------------------------------------------------------------------------- /Assets/WriteLongCharacteristicValues.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PureSwift/Bluetooth/3db3287b6e99a1815e4056aa03b8f5b170540e44/Assets/WriteLongCharacteristicValues.png -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Plugins/GenerateBluetoothDefinitions/BluetoothUUID.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BluetoothUUID.swift 3 | // Bluetooth 4 | // 5 | // Created by Alsey Coleman Miller on 1/11/25. 6 | // 7 | 8 | import Foundation 9 | import PackagePlugin 10 | 11 | extension GenerateBluetoothDefinitionsPlugin { 12 | 13 | func bluetoothUUIDBuildCommands( 14 | type: String, 15 | fileName: String, 16 | for context: PluginContext, 17 | target: SwiftSourceModuleTarget, 18 | commands: inout [Command] 19 | ) throws { 20 | guard target.name == "Bluetooth" else { 21 | return 22 | } 23 | // Generate Bluetooth UUID Definitions 24 | let outputDirectory = context.pluginWorkDirectoryURL 25 | let outputURLs = [ 26 | outputDirectory.appending(component: fileName + ".swift") 27 | ] 28 | let arguments = 29 | ["uuid", type] 30 | + outputURLs.map { $0.path() } 31 | let command = Command.buildCommand( 32 | displayName: "Generate Bluetooth \(type) UUID Definitions", 33 | executable: try context.tool(named: "GenerateBluetooth").url, 34 | arguments: arguments, 35 | inputFiles: [], 36 | outputFiles: outputURLs 37 | ) 38 | assert(arguments.count == 3) 39 | commands.append(command) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Sources/Bluetooth/Bluetooth.docc/Bluetooth.md: -------------------------------------------------------------------------------- 1 | # ``Bluetooth`` 2 | 3 | Pure Swift Bluetooth Definitions. 4 | 5 | ## Usage 6 | 7 | ```swift 8 | import Bluetooth 9 | 10 | let uuid128bit = BluetoothUUID(rawValue: "60F14FE2-F972-11E5-B84F-23E070D5A8C7") 11 | let uuid16bit = BluetoothUUID(rawValue: "FEA9") 12 | let address = BluetoothAddress(rawValue: "00:1A:7D:DA:71:13") 13 | ``` 14 | 15 | ## Topics 16 | 17 | ### Bluetooth Types 18 | 19 | - ``BluetoothAddress`` 20 | - ``BluetoothUUID`` 21 | - ``LowEnergyAdvertisingData`` 22 | - ``AppleBeacon`` 23 | 24 | ### Identifiers 25 | 26 | - ``CompanyIdentifier`` 27 | - ``UnitIdentifier`` 28 | - ``ClassOfDevice`` 29 | 30 | ### Socket 31 | 32 | - ``L2CAPSocket`` 33 | - ``L2CAPSocketEvent`` 34 | - ``L2CAPSocketEventStream`` 35 | 36 | ### Integer Types 37 | 38 | - ``UInt24`` 39 | - ``UInt40`` 40 | - ``UInt48`` 41 | - ``UInt128`` 42 | - ``UInt256`` 43 | - ``UInt512`` 44 | - ``SFloat`` 45 | 46 | ### Range of Values 47 | 48 | - ``LowEnergyScanTimeInterval`` 49 | - ``RSSI`` 50 | -------------------------------------------------------------------------------- /Sources/Bluetooth/BluetoothUUIDMetadata.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BluetoothUUIDMetadata.swift 3 | // Bluetooth 4 | // 5 | // Created by Alsey Coleman Miller on 1/12/25. 6 | // 7 | 8 | #if canImport(Foundation) && canImport(BluetoothMetadata) && !os(WASI) && !hasFeature(Embedded) 9 | import Foundation 10 | import BluetoothMetadata 11 | 12 | public extension BluetoothUUID { 13 | 14 | /// Fetch the metadata for the UUID. 15 | var metadata: BluetoothMetadata.BluetoothUUID? { 16 | guard case let .bit16(rawValue) = self else { 17 | return nil 18 | } 19 | for file in files.values { 20 | if let metadata = file[rawValue] { 21 | return metadata 22 | } 23 | } 24 | return nil 25 | } 26 | } 27 | 28 | internal let files: [BluetoothMetadata.BluetoothUUID.Category: BluetoothMetadata.BluetoothUUID.File] = { 29 | do { 30 | return try BluetoothMetadata.BluetoothUUID.File.load() 31 | } catch { 32 | assertionFailure("Unable to load metadata: \(error)") 33 | return [:] 34 | } 35 | }() 36 | 37 | #endif 38 | -------------------------------------------------------------------------------- /Sources/Bluetooth/CompanyIdentifierMetadata.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CompanyIdentifierMetadata.swift 3 | // Bluetooth 4 | // 5 | // Created by Alsey Coleman Miller on 1/12/25. 6 | // 7 | 8 | #if canImport(Foundation) && canImport(BluetoothMetadata) && !os(WASI) && !hasFeature(Embedded) 9 | import Foundation 10 | import BluetoothMetadata 11 | 12 | public extension CompanyIdentifier { 13 | 14 | /// Bluetooth Company name. 15 | /// 16 | /// - SeeAlso: [Company Identifiers](https://www.bluetooth.com/specifications/assigned-numbers/company-identifiers) 17 | var name: String? { 18 | companyIdentifiers[rawValue] 19 | } 20 | } 21 | 22 | internal let companyIdentifiers: [UInt16: String] = { 23 | do { 24 | let file = try BluetoothMetadata.CompanyIdentifier.File.load() 25 | var companyIdentifiers = [UInt16: String]() 26 | for element in file.companyIdentifiers { 27 | companyIdentifiers[element.id] = element.name 28 | } 29 | assert(companyIdentifiers.count == file.companyIdentifiers.count) 30 | return companyIdentifiers 31 | } catch { 32 | assertionFailure("Unable to load metadata: \(error)") 33 | return [:] 34 | } 35 | }() 36 | #endif 37 | -------------------------------------------------------------------------------- /Sources/Bluetooth/Extensions/String.swift: -------------------------------------------------------------------------------- 1 | // 2 | // String.swift 3 | // Bluetooth 4 | // 5 | // Created by Alsey Coleman Miller on 11/4/24. 6 | // 7 | 8 | #if canImport(Darwin) 9 | import Darwin 10 | #endif 11 | 12 | internal extension String { 13 | 14 | /// Initialize from UTF8 data. 15 | init?(utf8 data: Data) { 16 | #if canImport(Darwin) 17 | // Newer Darwin and other platforms use StdLib parsing 18 | if #available(macOS 15, iOS 18, watchOS 11, tvOS 18, visionOS 2, *) { 19 | self.init(validating: data, as: UTF8.self) 20 | } else { 21 | // Older Darwin uses Foundation 22 | self.init(bytes: data, encoding: .utf8) 23 | } 24 | #else 25 | self.init(validating: data, as: UTF8.self) 26 | #endif 27 | } 28 | 29 | #if hasFeature(Embedded) 30 | // Can't use `CVarArg` in Embedded Swift 31 | init?(format: String, length: Int, _ value: UInt8) { 32 | var cString: [CChar] = .init(repeating: 0, count: length + 1) 33 | guard _snprintf_uint8_t(&cString, cString.count, format, value) >= 0 else { 34 | return nil 35 | } 36 | self.init(cString: cString) 37 | } 38 | #elseif canImport(Darwin) 39 | init?(format: String, length: Int, _ value: T) { 40 | var cString: [CChar] = .init(repeating: 0, count: length + 1) 41 | guard snprintf(ptr: &cString, cString.count, format, value) >= 0 else { 42 | return nil 43 | } 44 | self.init(cString: cString) 45 | } 46 | #endif 47 | } 48 | -------------------------------------------------------------------------------- /Sources/Bluetooth/Extensions/System.swift: -------------------------------------------------------------------------------- 1 | // 2 | // System.swift 3 | // Bluetooth 4 | // 5 | // Created by Alsey Coleman Miller on 11/7/24. 6 | // 7 | 8 | #if canImport(Darwin) 9 | import Darwin 10 | #elseif os(Windows) 11 | import ucrt 12 | #elseif canImport(Glibc) 13 | import Glibc 14 | #elseif canImport(Musl) 15 | import Musl 16 | #elseif canImport(WASILibc) 17 | import WASILibc 18 | #elseif canImport(Bionic) 19 | import Bionic 20 | #endif 21 | 22 | // Declares the required C functions 23 | #if hasFeature(Embedded) 24 | @_silgen_name("memcmp") 25 | internal func _memcmp( 26 | _ p1: UnsafeRawPointer?, 27 | _ p2: UnsafeRawPointer?, 28 | _ size: Int 29 | ) -> Int32 30 | #else 31 | internal func _memcmp( 32 | _ p1: UnsafeRawPointer, 33 | _ p2: UnsafeRawPointer, 34 | _ size: Int 35 | ) -> Int32 { 36 | memcmp(p1, p2, size) 37 | } 38 | #endif 39 | 40 | #if hasFeature(Embedded) 41 | @_silgen_name("snprintf") 42 | internal func _snprintf_uint8_t( 43 | _ pointer: UnsafeMutablePointer, 44 | _ length: Int, 45 | _ format: UnsafePointer, 46 | _ arg: UInt8 47 | ) -> Int32 48 | #endif 49 | -------------------------------------------------------------------------------- /Sources/Bluetooth/LowEnergyScanTimeInterval.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LowEnergyScanTimeInterval.swift 3 | // Bluetooth 4 | // 5 | // Created by Alsey Coleman Miller on 6/14/18. 6 | // Copyright © 2018 PureSwift. All rights reserved. 7 | // 8 | 9 | /// LE Scan Time Interval 10 | /// 11 | /// Range: 0x0004 to 0x4000 12 | /// Time = N * 0.625 msec 13 | /// Time Range: 2.5 msec to 10240 msec 14 | @frozen 15 | public struct LowEnergyScanTimeInterval: RawRepresentable, Equatable, Comparable, Hashable, Sendable { 16 | 17 | /// 2.5 msec 18 | public static var min: LowEnergyScanTimeInterval { LowEnergyScanTimeInterval(0x0004) } 19 | 20 | /// 10.24 seconds 21 | public static var max: LowEnergyScanTimeInterval { LowEnergyScanTimeInterval(0x4000) } 22 | 23 | public let rawValue: UInt16 24 | 25 | public init?(rawValue: UInt16) { 26 | 27 | guard rawValue >= LowEnergyScanTimeInterval.min.rawValue, 28 | rawValue <= LowEnergyScanTimeInterval.max.rawValue 29 | else { return nil } 30 | 31 | self.rawValue = rawValue 32 | } 33 | 34 | /// Time = N * 0.625 msec 35 | public var miliseconds: Double { 36 | 37 | return Double(rawValue) * 0.625 38 | } 39 | 40 | // Private, unsafe 41 | init(_ rawValue: UInt16) { 42 | self.rawValue = rawValue 43 | } 44 | 45 | // Comparable 46 | public static func < (lhs: LowEnergyScanTimeInterval, rhs: LowEnergyScanTimeInterval) -> Bool { 47 | 48 | return lhs.rawValue < rhs.rawValue 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Sources/Bluetooth/RSSI.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RSSI.swift 3 | // Bluetooth 4 | // 5 | // Created by Alsey Coleman Miller on 4/11/18. 6 | // Copyright © 2018 PureSwift. All rights reserved. 7 | // 8 | 9 | /// RSSI 10 | /// 11 | /// Size: 1 Octet (signed integer) 12 | /// Range: -127 ≤ N ≤ +20 13 | /// Units: dBm 14 | @frozen 15 | public struct RSSI: RawRepresentable, Equatable, Hashable, Sendable { 16 | 17 | /// Units: dBm 18 | public let rawValue: Int8 19 | 20 | public init?(rawValue: Int8) { 21 | 22 | guard -127 <= rawValue, 23 | rawValue <= +20 24 | else { return nil } 25 | 26 | self.rawValue = rawValue 27 | } 28 | } 29 | 30 | #if !hasFeature(Embedded) 31 | extension RSSI: Codable {} 32 | #endif 33 | 34 | // MARK: - CustomStringConvertible 35 | 36 | extension RSSI: CustomStringConvertible { 37 | 38 | public var description: String { 39 | return "\(rawValue) dBm" 40 | } 41 | } 42 | 43 | // MARK: - Comparable 44 | 45 | extension RSSI: Comparable { 46 | 47 | public static func < (lhs: RSSI, rhs: RSSI) -> Bool { 48 | return lhs.rawValue < rhs.rawValue 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Sources/Bluetooth/SecurityLevel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SecurityType.swift 3 | // Bluetooth 4 | // 5 | // Created by Alsey Coleman Miller on 2/28/16. 6 | // Copyright © 2016 PureSwift. All rights reserved. 7 | // 8 | 9 | /// Bluetooth security level. 10 | public enum SecurityLevel: UInt8, Sendable { 11 | 12 | case sdp = 0 13 | case low = 1 14 | case medium = 2 15 | case high = 3 16 | case fips = 4 17 | 18 | public init() { self = .sdp } 19 | } 20 | 21 | // MARK: - Comparable 22 | 23 | extension SecurityLevel: Comparable { 24 | 25 | public static func < (lhs: SecurityLevel, rhs: SecurityLevel) -> Bool { 26 | return lhs.rawValue < rhs.rawValue 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Sources/Bluetooth/Unit.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Units.swift 3 | // Bluetooth 4 | // 5 | // Created by Carlos Duclos on 6/6/18. 6 | // Copyright © 2018 PureSwift. All rights reserved. 7 | // 8 | 9 | /// Type that represents a unit of measurement defined by Bluetooth. 10 | public protocol BluetoothUnit: RawRepresentable { 11 | 12 | /// The unit of measurement type. 13 | static var unitType: UnitIdentifier { get } 14 | } 15 | -------------------------------------------------------------------------------- /Sources/Bluetooth/UnitIdentifier.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UnitIdentifier.swift 3 | // Bluetooth 4 | // 5 | // Created by Carlos Duclos on 6/6/18. 6 | // Copyright © 2018 PureSwift. All rights reserved. 7 | // 8 | 9 | @frozen 10 | public struct UnitIdentifier: RawRepresentable, Equatable, Hashable, Sendable { 11 | 12 | public var rawValue: UInt16 13 | 14 | public init(rawValue: UInt16) { 15 | self.rawValue = rawValue 16 | } 17 | } 18 | 19 | // MARK: - ExpressibleByIntegerLiteral 20 | 21 | extension UnitIdentifier: ExpressibleByIntegerLiteral { 22 | 23 | public init(integerLiteral value: UInt16) { 24 | self.init(rawValue: value) 25 | } 26 | } 27 | 28 | // MARK: - CustomStringConvertible 29 | 30 | extension UnitIdentifier: CustomStringConvertible { 31 | 32 | public var description: String { 33 | #if canImport(Foundation) && canImport(BluetoothMetadata) && !os(WASI) && !hasFeature(Embedded) 34 | return self.name ?? rawValueDescription 35 | #else 36 | return rawValueDescription 37 | #endif 38 | } 39 | 40 | internal var rawValueDescription: String { 41 | "0x" + rawValue.toHexadecimal() 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Sources/Bluetooth/UnitIdentifierMetadata.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UnitIdentifierMetadata.swift 3 | // Bluetooth 4 | // 5 | // Created by Alsey Coleman Miller on 1/12/25. 6 | // 7 | 8 | #if canImport(Foundation) && canImport(BluetoothMetadata) && !os(WASI) && !hasFeature(Embedded) 9 | import Foundation 10 | import BluetoothMetadata 11 | 12 | public extension UnitIdentifier { 13 | 14 | /// The name of the unit. 15 | var name: String? { 16 | BluetoothUUID.bit16(rawValue).metadata?.name 17 | } 18 | 19 | /// The Bluetooth type namespace of the unit. 20 | var type: String? { 21 | BluetoothUUID.bit16(rawValue).metadata?.type 22 | } 23 | } 24 | #endif 25 | -------------------------------------------------------------------------------- /Sources/BluetoothGAP/Extensions/OptionSet.swift: -------------------------------------------------------------------------------- 1 | // 2 | // OptionSet.swift 3 | // Bluetooth 4 | // 5 | // Created by Alsey Coleman Miller on 11/5/24. 6 | // 7 | 8 | extension OptionSet { 9 | @inline(never) 10 | internal func buildDescription( 11 | _ descriptions: [(Element, StaticString)] 12 | ) -> String { 13 | var copy = self 14 | var result = "[" 15 | 16 | for (option, name) in descriptions { 17 | if _slowPath(copy.contains(option)) { 18 | result += name.description 19 | copy.remove(option) 20 | if !copy.isEmpty { result += ", " } 21 | } 22 | } 23 | 24 | if _slowPath(!copy.isEmpty) { 25 | result += "\(Self.self)(rawValue: \(copy.rawValue))" 26 | } 27 | result += "]" 28 | return result 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Sources/BluetoothGAP/Extensions/String.swift: -------------------------------------------------------------------------------- 1 | // 2 | // String.swift 3 | // Bluetooth 4 | // 5 | // Created by Alsey Coleman Miller on 11/4/24. 6 | // 7 | 8 | #if canImport(Darwin) 9 | import Darwin 10 | #endif 11 | 12 | internal extension String { 13 | 14 | /// Initialize from UTF8 data. 15 | init?(utf8 data: Data) { 16 | #if canImport(Darwin) 17 | // Newer Darwin and other platforms use StdLib parsing 18 | if #available(macOS 15, iOS 18, watchOS 11, tvOS 18, visionOS 2, *) { 19 | self.init(validating: data, as: UTF8.self) 20 | } else { 21 | // Older Darwin uses Foundation 22 | self.init(bytes: data, encoding: .utf8) 23 | } 24 | #else 25 | self.init(validating: data, as: UTF8.self) 26 | #endif 27 | } 28 | 29 | #if hasFeature(Embedded) 30 | // Can't use `CVarArg` in Embedded Swift 31 | init?(format: String, length: Int, _ value: UInt8) { 32 | var cString: [CChar] = .init(repeating: 0, count: length + 1) 33 | guard _snprintf_uint8_t(&cString, cString.count, format, value) >= 0 else { 34 | return nil 35 | } 36 | self.init(cString: cString) 37 | } 38 | #elseif canImport(Darwin) 39 | init?(format: String, length: Int, _ value: T) { 40 | var cString: [CChar] = .init(repeating: 0, count: length + 1) 41 | guard snprintf(ptr: &cString, cString.count, format, value) >= 0 else { 42 | return nil 43 | } 44 | self.init(cString: cString) 45 | } 46 | #endif 47 | } 48 | -------------------------------------------------------------------------------- /Sources/BluetoothGAP/Extensions/System.swift: -------------------------------------------------------------------------------- 1 | // 2 | // System.swift 3 | // Bluetooth 4 | // 5 | // Created by Alsey Coleman Miller on 11/7/24. 6 | // 7 | 8 | #if canImport(Darwin) 9 | import Darwin 10 | #elseif os(Windows) 11 | import ucrt 12 | #elseif canImport(Glibc) 13 | import Glibc 14 | #elseif canImport(Musl) 15 | import Musl 16 | #elseif canImport(WASILibc) 17 | import WASILibc 18 | #elseif canImport(Bionic) 19 | import Bionic 20 | #endif 21 | 22 | // Declares the required C functions 23 | #if hasFeature(Embedded) 24 | @_silgen_name("memcmp") 25 | internal func _memcmp( 26 | _ p1: UnsafeRawPointer?, 27 | _ p2: UnsafeRawPointer?, 28 | _ size: Int 29 | ) -> Int32 30 | #else 31 | internal func _memcmp( 32 | _ p1: UnsafeRawPointer, 33 | _ p2: UnsafeRawPointer, 34 | _ size: Int 35 | ) -> Int32 { 36 | memcmp(p1, p2, size) 37 | } 38 | #endif 39 | 40 | #if hasFeature(Embedded) 41 | @_silgen_name("snprintf") 42 | internal func _snprintf_uint8_t( 43 | _ pointer: UnsafeMutablePointer, 44 | _ length: Int, 45 | _ format: UnsafePointer, 46 | _ arg: UInt8 47 | ) -> Int32 48 | #endif 49 | -------------------------------------------------------------------------------- /Sources/BluetoothGAP/GAPAppearanceData.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GAPAppearanceData.swift 3 | // Bluetooth 4 | // 5 | // Created by Carlos Duclos on 6/13/18. 6 | // Copyright © 2018 PureSwift. All rights reserved. 7 | // 8 | 9 | #if canImport(Foundation) 10 | import Foundation 11 | #endif 12 | import Bluetooth 13 | 14 | /// The Appearance data type defines the external appearance of the device. 15 | /// This value shall be the same as the Appearance characteristic, as defined in Vol. 3, Part C, Section 12.2. 16 | @frozen 17 | public struct GAPAppearanceData: GAPData, Equatable { 18 | 19 | public static let dataType: GAPDataType = .appearance 20 | 21 | public let appearance: GAPAppearance 22 | 23 | public init(appearance: GAPAppearance) { 24 | 25 | self.appearance = appearance 26 | } 27 | } 28 | 29 | public extension GAPAppearanceData { 30 | 31 | init?(data: Data) { 32 | 33 | guard data.count == 2 34 | else { return nil } 35 | 36 | let appearance = GAPAppearance(rawValue: UInt16(littleEndian: UInt16(bytes: (data[0], data[1])))) 37 | 38 | self.init(appearance: appearance) 39 | } 40 | 41 | func append(to data: inout Data) { 42 | 43 | data += appearance.rawValue.littleEndian 44 | } 45 | 46 | var dataLength: Int { 47 | 48 | return 2 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Sources/BluetoothGAP/GAPData.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GenericAccessProfile.swift 3 | // Bluetooth 4 | // 5 | // Created by Alsey Coleman Miller on 4/26/18. 6 | // Copyright © 2018 PureSwift. All rights reserved. 7 | // 8 | 9 | @_exported import Bluetooth 10 | 11 | // MARK: - Generic Access Profile Data 12 | 13 | /// Generic Access Profile 14 | /// 15 | /// - SeeAlso: 16 | /// [Generic Access Profile](https://www.bluetooth.com/specifications/assigned-numbers/generic-access-profile) 17 | public protocol GAPData: DataConvertible { 18 | 19 | /// Generic Access Profile data type. 20 | static var dataType: GAPDataType { get } 21 | } 22 | -------------------------------------------------------------------------------- /Sources/BluetoothGAP/GAPLESupportedFeatures.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GAPLESupportedFeatures.swift 3 | // Bluetooth 4 | // 5 | // Created by Carlos Duclos on 6/14/18. 6 | // Copyright © 2018 PureSwift. All rights reserved. 7 | // 8 | 9 | #if canImport(Foundation) 10 | import Foundation 11 | #endif 12 | import Bluetooth 13 | 14 | /* 15 | /// The LE Supported Features data type defines the LE features supported by the device. 16 | /// All 0x00 octets after the last non-zero octet shall be omitted from the value transmitted. 17 | /// 18 | /// The LE Supported Features data type size is zero or more octets long. 19 | /// This allows the LE Supported Features to be represented while using 20 | /// the minimum number of octets within the data packet. 21 | public struct GAPLESupportedFeatures: GAPData { 22 | 23 | public static var dataType: GAPDataType { return .lowEnergySupportedFeatures } 24 | } 25 | 26 | public extension GAPLESupportedFeatures { 27 | 28 | 29 | } 30 | */ 31 | -------------------------------------------------------------------------------- /Sources/BluetoothGAP/GAPURI.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GAPURI.swift 3 | // Bluetooth 4 | // 5 | // Created by Carlos Duclos on 6/14/18. 6 | // Copyright © 2018 PureSwift. All rights reserved. 7 | // 8 | 9 | #if canImport(Foundation) 10 | import Foundation 11 | #endif 12 | import Bluetooth 13 | 14 | /// GAP URI 15 | @frozen 16 | public struct GAPURI: GAPData, Equatable, Hashable, Sendable { 17 | 18 | public static var dataType: GAPDataType { .uri } 19 | 20 | public var uri: String 21 | 22 | public init(uri: String) { 23 | self.uri = uri 24 | } 25 | } 26 | 27 | public extension GAPURI { 28 | 29 | init?(data: Data) { 30 | 31 | guard let string = String(utf8: data) 32 | else { return nil } 33 | 34 | self.uri = string 35 | } 36 | 37 | func append(to data: inout Data) { 38 | data += uri.utf8 39 | } 40 | 41 | var dataLength: Int { 42 | return uri.utf8.count 43 | } 44 | } 45 | 46 | // MARK: - CustomStringConvertible 47 | 48 | extension GAPURI: CustomStringConvertible { 49 | 50 | public var description: String { 51 | return uri 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Sources/BluetoothGATT/ATTExecuteWriteRequest.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ATTExecuteWriteRequest.swift 3 | // Bluetooth 4 | // 5 | // Created by Alsey Coleman Miller on 6/14/18. 6 | // Copyright © 2018 PureSwift. All rights reserved. 7 | // 8 | 9 | import Bluetooth 10 | 11 | /// Execute Write Request 12 | /// 13 | /// The *Execute Write Request* is used to request the server to write or cancel the write 14 | /// of all the prepared values currently held in the prepare queue from this client. 15 | /// This request shall be handled by the server as an atomic operation. 16 | @frozen 17 | public enum ATTExecuteWriteRequest: UInt8, ATTProtocolDataUnit, Sendable, CaseIterable { 18 | 19 | public static var attributeOpcode: ATTOpcode { .executeWriteRequest } 20 | 21 | /// Cancel all prepared writes. 22 | case cancel = 0x00 23 | 24 | /// Immediately write all pending prepared values. 25 | case write = 0x01 26 | } 27 | 28 | extension ATTExecuteWriteRequest: DataConvertible { 29 | 30 | public static var length: Int { 2 } 31 | 32 | public init?(data: Data) { 33 | 34 | guard data.count == 2, 35 | Self.validateOpcode(data) 36 | else { return nil } 37 | 38 | self.init(rawValue: data[1]) 39 | } 40 | 41 | public func append(to data: inout Data) where Data: DataContainer { 42 | data += [Self.attributeOpcode.rawValue, rawValue] 43 | } 44 | 45 | public var dataLength: Int { 46 | Self.length 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Sources/BluetoothGATT/ATTExecuteWriteResponse.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ATTExecuteWriteResponse.swift 3 | // Bluetooth 4 | // 5 | // Created by Alsey Coleman Miller on 6/14/18. 6 | // Copyright © 2018 PureSwift. All rights reserved. 7 | // 8 | 9 | import Bluetooth 10 | 11 | /// The *Execute Write Response* is sent in response to a received *Execute Write Request*. 12 | @frozen 13 | public struct ATTExecuteWriteResponse: ATTProtocolDataUnit, Sendable { 14 | 15 | public static var attributeOpcode: ATTOpcode { .executeWriteResponse } 16 | 17 | public init() {} 18 | } 19 | 20 | extension ATTExecuteWriteResponse: DataConvertible { 21 | 22 | public init?(data: Data) { 23 | 24 | guard data.count == 1, 25 | Self.validateOpcode(data) 26 | else { return nil } 27 | } 28 | 29 | public func append(to data: inout Data) where Data: DataContainer { 30 | data += Self.attributeOpcode.rawValue 31 | } 32 | 33 | public var dataLength: Int { 34 | 1 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Sources/BluetoothGATT/ATTHandleValueConfirmation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ATTHandleValueConfirmation.swift 3 | // Bluetooth 4 | // 5 | // Created by Alsey Coleman Miller on 6/14/18. 6 | // Copyright © 2018 PureSwift. All rights reserved. 7 | // 8 | 9 | import Bluetooth 10 | 11 | /// Handle Value Confirmation 12 | /// 13 | /// The *Handle Value Confirmation* is sent in response to a received *Handle Value Indication* 14 | /// and confirms that the client has received an indication of the given attribute. 15 | @frozen 16 | public struct ATTHandleValueConfirmation: ATTProtocolDataUnit, Sendable { 17 | 18 | public static var attributeOpcode: ATTOpcode { .handleValueConfirmation } 19 | 20 | public init() {} 21 | } 22 | 23 | extension ATTHandleValueConfirmation: DataConvertible { 24 | 25 | public init?(data: Data) { 26 | 27 | guard data.count == 1, 28 | Self.validateOpcode(data) 29 | else { return nil } 30 | } 31 | 32 | public func append(to data: inout Data) where Data: DataContainer { 33 | data += Self.attributeOpcode.rawValue 34 | } 35 | 36 | public var dataLength: Int { 1 } 37 | } 38 | -------------------------------------------------------------------------------- /Sources/BluetoothGATT/ATTMaximumTransmissionUnitResponse.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ATTMaximumTransmissionUnitResponse.swift 3 | // Bluetooth 4 | // 5 | // Created by Alsey Coleman Miller on 6/13/18. 6 | // Copyright © 2018 PureSwift. All rights reserved. 7 | // 8 | 9 | import Bluetooth 10 | 11 | /// Exchange MTU Response 12 | /// 13 | /// The *Exchange MTU Response* is sent in reply to a received *Exchange MTU Request*. 14 | @frozen 15 | public struct ATTMaximumTransmissionUnitResponse: ATTProtocolDataUnit, Equatable, Hashable, Sendable { 16 | 17 | /// 0x03 = Exchange MTU Response 18 | public static var attributeOpcode: ATTOpcode { return .maximumTransmissionUnitResponse } 19 | 20 | /// Server Rx MTU 21 | /// 22 | /// Attribute server receive MTU size 23 | public var serverMTU: UInt16 24 | 25 | public init(serverMTU: UInt16) { 26 | self.serverMTU = serverMTU 27 | } 28 | } 29 | 30 | // MARK: - DataConvertible 31 | 32 | extension ATTMaximumTransmissionUnitResponse: DataConvertible { 33 | 34 | public static var length: Int { 3 } 35 | 36 | public init?(data: Data) { 37 | 38 | guard data.count == Self.length, 39 | Self.validateOpcode(data) 40 | else { return nil } 41 | 42 | self.serverMTU = UInt16(littleEndian: UInt16(bytes: (data[1], data[2]))) 43 | } 44 | 45 | public func append(to data: inout Data) where Data: DataContainer { 46 | data += Self.attributeOpcode.rawValue 47 | data += self.serverMTU.littleEndian 48 | } 49 | 50 | public var dataLength: Int { 51 | Self.length 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Sources/BluetoothGATT/ATTReadBlobResponse.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ATTReadBlobResponse.swift 3 | // Bluetooth 4 | // 5 | // Created by Alsey Coleman Miller on 6/14/18. 6 | // Copyright © 2018 PureSwift. All rights reserved. 7 | // 8 | 9 | import Bluetooth 10 | 11 | /// Read Blob Response 12 | /// 13 | /// The *Read Blob Response* is sent in reply to a received *Read Blob Request* 14 | /// and contains part of the value of the attribute that has been read. 15 | @frozen 16 | public struct ATTReadBlobResponse: ATTProtocolDataUnit, Equatable, Hashable, Sendable { 17 | 18 | public static var attributeOpcode: ATTOpcode { .readBlobResponse } 19 | 20 | /// Part of the value of the attribute with the handle given. 21 | /// 22 | /// 23 | /// The part attribute value shall be set to part of the value of the attribute identified 24 | /// by the attribute handle and the value offset in the request. 25 | public var partAttributeValue: Value 26 | 27 | public init(partAttributeValue: Value) { 28 | self.partAttributeValue = partAttributeValue 29 | } 30 | } 31 | 32 | // MARK: - DataConvertible 33 | 34 | extension ATTReadBlobResponse: DataConvertible { 35 | 36 | public init?(data: Data) { 37 | guard data.count >= 1, 38 | Self.validateOpcode(data) 39 | else { return nil } 40 | self.partAttributeValue = data.suffixCheckingBounds(from: 1) 41 | } 42 | 43 | public func append(to data: inout Data) where Data: DataContainer { 44 | data += Self.attributeOpcode.rawValue 45 | data += self.partAttributeValue 46 | } 47 | 48 | public var dataLength: Int { 49 | 1 + partAttributeValue.count 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /Sources/BluetoothGATT/ATTReadMultipleResponse.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ATTReadMultipleResponse.swift 3 | // Bluetooth 4 | // 5 | // Created by Alsey Coleman Miller on 6/14/18. 6 | // Copyright © 2018 PureSwift. All rights reserved. 7 | // 8 | 9 | import Bluetooth 10 | 11 | /// Read Multiple Response 12 | /// 13 | /// The read response is sent in reply to a received *Read Multiple Request* and 14 | /// contains the values of the attributes that have been read. 15 | @frozen 16 | public struct ATTReadMultipleResponse: ATTProtocolDataUnit, Equatable, Hashable, Sendable { 17 | 18 | public static var attributeOpcode: ATTOpcode { return .readMultipleResponse } 19 | 20 | public var values: Values 21 | 22 | public init(values: Values) { 23 | self.values = values 24 | } 25 | } 26 | 27 | extension ATTReadMultipleResponse: DataConvertible { 28 | 29 | public init?(data: Data) { 30 | guard Self.validateOpcode(data) 31 | else { return nil } 32 | self.values = data.suffixCheckingBounds(from: 1) 33 | } 34 | 35 | public func append(to data: inout Data) where Data: DataContainer { 36 | data += Self.attributeOpcode.rawValue 37 | data += self.values 38 | } 39 | 40 | public var dataLength: Int { 41 | 1 + values.count 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Sources/BluetoothGATT/ATTReadRequest.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ATTReadRequest.swift 3 | // Bluetooth 4 | // 5 | // Created by Alsey Coleman Miller on 6/14/18. 6 | // Copyright © 2018 PureSwift. All rights reserved. 7 | // 8 | 9 | import Bluetooth 10 | 11 | /// Read Request 12 | /// 13 | /// The *Read Request* is used to request the server to read the value of an attribute 14 | /// and return its value in a *Read Response*. 15 | @frozen 16 | public struct ATTReadRequest: ATTProtocolDataUnit, Equatable, Hashable, Sendable { 17 | 18 | public static var attributeOpcode: ATTOpcode { .readRequest } 19 | 20 | /// The handle of the attribute to read. 21 | public var handle: UInt16 22 | 23 | public init(handle: UInt16) { 24 | self.handle = handle 25 | } 26 | } 27 | 28 | // MARK: - DataConvertible 29 | 30 | extension ATTReadRequest: DataConvertible { 31 | 32 | public static var length: Int { 3 } 33 | 34 | public init?(data: Data) { 35 | 36 | guard data.count == Self.length, 37 | Self.validateOpcode(data) 38 | else { return nil } 39 | 40 | self.handle = UInt16(littleEndian: UInt16(bytes: (data[1], data[2]))) 41 | } 42 | 43 | public func append(to data: inout Data) where Data: DataContainer { 44 | data += Self.attributeOpcode.rawValue 45 | data += self.handle.littleEndian 46 | } 47 | 48 | public var dataLength: Int { 49 | Self.length 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /Sources/BluetoothGATT/ATTReadResponse.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ATTReadResponse.swift 3 | // Bluetooth 4 | // 5 | // Created by Alsey Coleman Miller on 6/14/18. 6 | // Copyright © 2018 PureSwift. All rights reserved. 7 | // 8 | 9 | import Bluetooth 10 | 11 | /// Read Response 12 | /// 13 | /// The *Read Response* is sent in reply to a received *Read Request* and contains 14 | /// the value of the attribute that has been read. 15 | /// 16 | /// - Note: The *Read Blob Request* would be used to read the remaining octets of a long attribute value. 17 | @frozen 18 | public struct ATTReadResponse: ATTProtocolDataUnit, Equatable { 19 | 20 | public static var attributeOpcode: ATTOpcode { return .readResponse } 21 | 22 | /// The value of the attribute with the handle given. 23 | public var attributeValue: Value 24 | 25 | public init(attributeValue: Value) { 26 | self.attributeValue = attributeValue 27 | } 28 | } 29 | 30 | // MARK: - DataConvertible 31 | 32 | extension ATTReadResponse: DataConvertible { 33 | 34 | public init?(data: Data) { 35 | guard data.count >= 1, 36 | Self.validateOpcode(data) 37 | else { return nil } 38 | self.attributeValue = data.suffixCheckingBounds(from: 1) 39 | } 40 | 41 | public func append(to data: inout Data) where Data: DataContainer { 42 | data += Self.attributeOpcode.rawValue 43 | data += self.attributeValue 44 | } 45 | 46 | public var dataLength: Int { 47 | return 1 + attributeValue.count 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Sources/BluetoothGATT/ATTWriteCommand.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ATTWriteCommand.swift 3 | // Bluetooth 4 | // 5 | // Created by Alsey Coleman Miller on 6/14/18. 6 | // Copyright © 2018 PureSwift. All rights reserved. 7 | // 8 | 9 | import Bluetooth 10 | 11 | /// Write Command 12 | /// 13 | /// The *Write Command* is used to request the server to write the value of an attribute, typically into a control-point attribute. 14 | @frozen 15 | public struct ATTWriteCommand: ATTProtocolDataUnit, Equatable, Hashable, Sendable { 16 | 17 | public static var attributeOpcode: ATTOpcode { .writeCommand } 18 | 19 | /// The handle of the attribute to be set. 20 | public var handle: UInt16 21 | 22 | /// The value of be written to the attribute. 23 | public var value: Value 24 | 25 | public init( 26 | handle: UInt16, 27 | value: Value 28 | ) { 29 | self.handle = handle 30 | self.value = value 31 | } 32 | } 33 | 34 | // MARK: - DataConvertible 35 | 36 | extension ATTWriteCommand: DataConvertible { 37 | 38 | public init?(data: Data) { 39 | 40 | guard data.count >= 3, 41 | Self.validateOpcode(data) 42 | else { return nil } 43 | 44 | self.handle = UInt16(littleEndian: UInt16(bytes: (data[1], data[2]))) 45 | self.value = data.suffixCheckingBounds(from: 3) 46 | } 47 | 48 | public func append(to data: inout Data) where Data: DataContainer { 49 | data += Self.attributeOpcode.rawValue 50 | data += self.handle.littleEndian 51 | data += self.value 52 | } 53 | 54 | public var dataLength: Int { 55 | return 3 + value.count 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Sources/BluetoothGATT/ATTWriteRequest.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ATTWriteRequest.swift 3 | // Bluetooth 4 | // 5 | // Created by Alsey Coleman Miller on 6/14/18. 6 | // Copyright © 2018 PureSwift. All rights reserved. 7 | // 8 | 9 | import Bluetooth 10 | 11 | /// Write Request 12 | /// 13 | /// The *Write Request* is used to request the server to write the value of an attribute 14 | /// and acknowledge that this has been achieved in a *Write Response*. 15 | @frozen 16 | public struct ATTWriteRequest: ATTProtocolDataUnit, Equatable, Hashable, Sendable { 17 | 18 | public static var attributeOpcode: ATTOpcode { .writeRequest } 19 | 20 | /// The handle of the attribute to be written. 21 | public var handle: UInt16 22 | 23 | /// The value to be written to the attribute. 24 | public var value: Value 25 | 26 | public init( 27 | handle: UInt16, 28 | value: Value 29 | ) { 30 | self.handle = handle 31 | self.value = value 32 | } 33 | } 34 | 35 | extension ATTWriteRequest: DataConvertible { 36 | 37 | public init?(data: Data) { 38 | 39 | guard data.count >= 3, 40 | Self.validateOpcode(data) 41 | else { return nil } 42 | 43 | self.handle = UInt16(littleEndian: UInt16(bytes: (data[1], data[2]))) 44 | self.value = data.suffixCheckingBounds(from: 3) 45 | } 46 | 47 | public var dataLength: Int { 48 | return 3 + value.count 49 | } 50 | 51 | public func append(to data: inout Data) { 52 | data += Self.attributeOpcode.rawValue 53 | data += self.handle.littleEndian 54 | data += self.value 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Sources/BluetoothGATT/ATTWriteResponse.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ATTWriteResponse.swift 3 | // Bluetooth 4 | // 5 | // Created by Alsey Coleman Miller on 6/14/18. 6 | // Copyright © 2018 PureSwift. All rights reserved. 7 | // 8 | 9 | import Bluetooth 10 | 11 | /// Write Response 12 | /// 13 | /// The *Write Response* is sent in reply to a valid *Write Request* 14 | /// and acknowledges that the attribute has been successfully written. 15 | @frozen 16 | public struct ATTWriteResponse: ATTProtocolDataUnit, Sendable { 17 | 18 | public static var attributeOpcode: ATTOpcode { .writeResponse } 19 | 20 | public init() {} 21 | } 22 | 23 | extension ATTWriteResponse: DataConvertible { 24 | 25 | public init?(data: Data) { 26 | 27 | guard data.count == 1, 28 | Self.validateOpcode(data) 29 | else { return nil } 30 | } 31 | 32 | public func append(to data: inout Data) where Data: DataContainer { 33 | data += Self.attributeOpcode.rawValue 34 | } 35 | 36 | public var dataLength: Int { 1 } 37 | } 38 | -------------------------------------------------------------------------------- /Sources/BluetoothGATT/Extensions/Array.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Array.swift 3 | // Bluetooth 4 | // 5 | // Created by Alsey Coleman Miller on 3/31/18. 6 | // Copyright © 2018 PureSwift. All rights reserved. 7 | // 8 | 9 | internal extension Array { 10 | 11 | mutating func popFirst() -> Element? { 12 | guard let first = self.first else { return nil } 13 | self.removeFirst() 14 | return first 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Sources/BluetoothGATT/Extensions/Bool.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Bool.swift 3 | // Bluetooth 4 | // 5 | // Created by Alsey Coleman Miller on 4/17/18. 6 | // Copyright © 2018 PureSwift. All rights reserved. 7 | // 8 | 9 | internal extension Bool { 10 | 11 | init?(byteValue: UInt8) { 12 | 13 | switch byteValue { 14 | case 0x00: self = false 15 | case 0x01: self = true 16 | default: return nil 17 | } 18 | } 19 | 20 | var byteValue: UInt8 { 21 | 22 | return self ? 0x01 : 0x00 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Sources/BluetoothGATT/Extensions/Data.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Data.swift 3 | // Bluetooth 4 | // 5 | // Created by Alsey Coleman Miller on 5/30/18. 6 | // Copyright © 2018 PureSwift. All rights reserved. 7 | // 8 | 9 | internal extension DataContainer { 10 | 11 | @usableFromInline 12 | func suffixCheckingBounds(from start: Int) -> Data { 13 | if count > start { 14 | return Data(suffix(from: start)) 15 | } else { 16 | return Data() 17 | } 18 | } 19 | 20 | static func += (data: inout Self, bytes: (UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8)) { 21 | let length = MemoryLayout<(UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8)>.size 22 | withUnsafePointer(to: bytes) { 23 | $0.withMemoryRebound(to: UInt8.self, capacity: length) { 24 | data.append($0, count: length) 25 | } 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Sources/BluetoothGATT/Extensions/OptionSet.swift: -------------------------------------------------------------------------------- 1 | // 2 | // OptionSet.swift 3 | // Bluetooth 4 | // 5 | // Created by Alsey Coleman Miller on 11/5/24. 6 | // 7 | 8 | #if !hasFeature(Embedded) 9 | extension OptionSet { 10 | @inline(never) 11 | internal func buildDescription( 12 | _ descriptions: [(Element, StaticString)] 13 | ) -> String { 14 | var copy = self 15 | var result = "[" 16 | 17 | for (option, name) in descriptions { 18 | if _slowPath(copy.contains(option)) { 19 | result += name.description 20 | copy.remove(option) 21 | if !copy.isEmpty { result += ", " } 22 | } 23 | } 24 | 25 | if _slowPath(!copy.isEmpty) { 26 | result += "\(Self.self)(rawValue: \(copy.rawValue))" 27 | } 28 | result += "]" 29 | return result 30 | } 31 | } 32 | #endif 33 | -------------------------------------------------------------------------------- /Sources/BluetoothGATT/Extensions/String.swift: -------------------------------------------------------------------------------- 1 | // 2 | // String.swift 3 | // Bluetooth 4 | // 5 | // Created by Alsey Coleman Miller on 11/4/24. 6 | // 7 | 8 | #if canImport(Darwin) 9 | import Darwin 10 | #endif 11 | 12 | internal extension String { 13 | 14 | /// Initialize from UTF8 data. 15 | init?(utf8 data: Data) { 16 | #if canImport(Darwin) 17 | // Newer Darwin and other platforms use StdLib parsing 18 | if #available(macOS 15, iOS 18, watchOS 11, tvOS 18, visionOS 2, *) { 19 | self.init(validating: data, as: UTF8.self) 20 | } else { 21 | // Older Darwin uses Foundation 22 | self.init(bytes: data, encoding: .utf8) 23 | } 24 | #else 25 | self.init(validating: data, as: UTF8.self) 26 | #endif 27 | } 28 | 29 | #if hasFeature(Embedded) 30 | // Can't use `CVarArg` in Embedded Swift 31 | init?(format: String, length: Int, _ value: UInt8) { 32 | var cString: [CChar] = .init(repeating: 0, count: length + 1) 33 | guard _snprintf_uint8_t(&cString, cString.count, format, value) >= 0 else { 34 | return nil 35 | } 36 | self.init(cString: cString) 37 | } 38 | #elseif canImport(Darwin) 39 | init?(format: String, length: Int, _ value: T) { 40 | var cString: [CChar] = .init(repeating: 0, count: length + 1) 41 | guard snprintf(ptr: &cString, cString.count, format, value) >= 0 else { 42 | return nil 43 | } 44 | self.init(cString: cString) 45 | } 46 | #endif 47 | } 48 | -------------------------------------------------------------------------------- /Sources/BluetoothGATT/Extensions/System.swift: -------------------------------------------------------------------------------- 1 | // 2 | // System.swift 3 | // Bluetooth 4 | // 5 | // Created by Alsey Coleman Miller on 11/7/24. 6 | // 7 | 8 | #if canImport(Darwin) 9 | import Darwin 10 | #elseif os(Windows) 11 | import ucrt 12 | #elseif canImport(Glibc) 13 | import Glibc 14 | #elseif canImport(Musl) 15 | import Musl 16 | #elseif canImport(WASILibc) 17 | import WASILibc 18 | #elseif canImport(Bionic) 19 | import Bionic 20 | #endif 21 | 22 | // Declares the required C functions 23 | #if hasFeature(Embedded) 24 | @_silgen_name("memcmp") 25 | internal func _memcmp( 26 | _ p1: UnsafeRawPointer?, 27 | _ p2: UnsafeRawPointer?, 28 | _ size: Int 29 | ) -> Int32 30 | #else 31 | internal func _memcmp( 32 | _ p1: UnsafeRawPointer, 33 | _ p2: UnsafeRawPointer, 34 | _ size: Int 35 | ) -> Int32 { 36 | memcmp(p1, p2, size) 37 | } 38 | #endif 39 | 40 | #if hasFeature(Embedded) 41 | @_silgen_name("snprintf") 42 | internal func _snprintf_uint8_t( 43 | _ pointer: UnsafeMutablePointer, 44 | _ length: Int, 45 | _ format: UnsafePointer, 46 | _ arg: UInt8 47 | ) -> Int32 48 | #endif 49 | -------------------------------------------------------------------------------- /Sources/BluetoothGATT/GATT.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GATT.swift 3 | // Bluetooth 4 | // 5 | // Created by Alsey Coleman Miller on 2/29/16. 6 | // Copyright © 2016 PureSwift. All rights reserved. 7 | // 8 | 9 | @_exported import Bluetooth 10 | 11 | /// The Generic Attributes (GATT) define a hierarchical data structure that is exposed to connected Bluetooth Low Energy (LE) devices. 12 | /// 13 | /// GATT profiles enable extensive innovation while still maintaining full interoperability with other Bluetooth® devices. The profile describes a use case, roles and general behaviors based on the GATT functionality. Services are collections of characteristics and relationships to other services that encapsulate the behavior of part of a device. This also includes hierarchy of services, characteristics and attributes used in the attribute server. 14 | /// 15 | /// GATT is built on top of the Attribute Protocol (ATT) (see Bluetooth Core System Architecture for block diagram and explanations), which uses GATT data to define the way that two Bluetooth Low Energy devices send and receive standard messages. Note that GATT is not used in Bluetooth BR/EDR implementations, which use only adopted profiles. 16 | internal enum GATTUUID: UInt16 { 17 | 18 | case primaryService = 0x2800 19 | case secondaryService = 0x2801 20 | case include = 0x2802 21 | case characteristic = 0x2803 22 | 23 | /// Initializes a GATT UUID for service type. 24 | public init(primaryService: Bool) { 25 | self = primaryService ? .primaryService : .secondaryService 26 | } 27 | 28 | /// Returns a Bluetooth UUID initialized with the `rawValue` of this GATT UUID. 29 | public var uuid: BluetoothUUID { 30 | return .bit16(rawValue) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Sources/BluetoothGATT/GATTAerobicHeartRateLowerLimit.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GATTAerobicHeartRateLowerLimit.swift 3 | // Bluetooth 4 | // 5 | // Created by Carlos Duclos on 6/13/18. 6 | // Copyright © 2018 PureSwift. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Bluetooth 11 | 12 | /// Aerobic Heart Rate Lower Limit 13 | /// 14 | /// Lower limit of the heart rate where the user enhances his endurance while exercising 15 | /// 16 | /// - SeeAlso: [Aerobic Heart Rate Lower Limit](https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.aerobic_heart_rate_lower_limit.xml) 17 | @frozen 18 | public struct GATTAerobicHeartRateLowerLimit: GATTCharacteristic, Equatable, Hashable, Sendable { 19 | 20 | public typealias BeatsPerMinute = GATTBeatsPerMinute.Byte 21 | 22 | internal static let length = MemoryLayout.size 23 | 24 | public static var uuid: BluetoothUUID { BluetoothUUID.Characteristic.aerobicHeartRateLowerLimit } 25 | 26 | public var beats: BeatsPerMinute 27 | 28 | public init(beats: BeatsPerMinute) { 29 | 30 | self.beats = beats 31 | } 32 | 33 | public init?(data: Data) { 34 | 35 | guard data.count == Self.length 36 | else { return nil } 37 | 38 | let beats = BeatsPerMinute(rawValue: data[0]) 39 | 40 | self.init(beats: beats) 41 | } 42 | 43 | public var data: Data { 44 | 45 | return Data([beats.rawValue]) 46 | } 47 | 48 | } 49 | 50 | extension GATTAerobicHeartRateLowerLimit: CustomStringConvertible { 51 | 52 | public var description: String { 53 | 54 | return beats.description 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Sources/BluetoothGATT/GATTAerobicThreshold.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GATTAerobicThreshold.swift 3 | // Bluetooth 4 | // 5 | // Created by Carlos Duclos on 6/13/18. 6 | // Copyright © 2018 PureSwift. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Bluetooth 11 | 12 | /// Aerobic Threshold 13 | /// 14 | /// First metabolic threshold. 15 | /// 16 | /// - SeeAlso: [Aerobic Threshold](https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.aerobic_threshold.xml) 17 | @frozen 18 | public struct GATTAerobicThreshold: GATTCharacteristic, Equatable, Hashable, Sendable { 19 | 20 | public typealias BeatsPerMinute = GATTBeatsPerMinute.Byte 21 | 22 | internal static let length = MemoryLayout.size 23 | 24 | public static var uuid: BluetoothUUID { BluetoothUUID.Characteristic.aerobicThreshold } 25 | 26 | public var beats: BeatsPerMinute 27 | 28 | public init(beats: BeatsPerMinute) { 29 | self.beats = beats 30 | } 31 | 32 | public init?(data: Data) { 33 | 34 | guard data.count == Self.length 35 | else { return nil } 36 | 37 | let beats = BeatsPerMinute(rawValue: data[0]) 38 | 39 | self.init(beats: beats) 40 | } 41 | 42 | public func append(to data: inout Data) where Data: DataContainer { 43 | data.append(beats.rawValue) 44 | } 45 | } 46 | 47 | extension GATTAerobicThreshold: CustomStringConvertible { 48 | 49 | public var description: String { 50 | return beats.description 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Sources/BluetoothGATT/GATTAlertLevel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GATTAlertLevel.swift 3 | // Bluetooth 4 | // 5 | // Created by Carlos Duclos on 6/13/18. 6 | // Copyright © 2018 PureSwift. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Bluetooth 11 | 12 | /// Alert Level 13 | /// 14 | /// The level of an alert a device is to sound. If this level is changed while the alert is being sounded, the new level should take effect. 15 | /// 16 | /// The value of the characteristic shall be an unsigned 8 bit integer that has a fixed point exponent of 0. The Alert Level characteristic defines the level of alert, and is one of the following three values: 17 | /// 18 | /// • Value 0, meaning “No Alert” 19 | /// 20 | /// • Value 1, meaning “Mild Alert” 21 | /// 22 | /// • EValue 2, meaning “High Alert” 23 | /// 24 | /// • Example: 25 | /// 26 | /// The value 0x01 is interpreted as “Mild Alert” 27 | /// 28 | /// - SeeAlso: [Alert Level](https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.alert_level.xml) 29 | @frozen 30 | public enum GATTAlertLevel: UInt8, GATTCharacteristic { 31 | 32 | public static var uuid: BluetoothUUID { BluetoothUUID.Characteristic.alertLevel } 33 | 34 | internal static let length = MemoryLayout.size 35 | 36 | /// No alert. 37 | case none = 0x00 38 | 39 | /// Mild alert. 40 | case mild = 0x01 41 | 42 | /// High alert. 43 | case high = 0x02 44 | 45 | public init?(data: Data) { 46 | 47 | guard data.count == Self.length 48 | else { return nil } 49 | 50 | self.init(rawValue: data[0]) 51 | } 52 | 53 | public var data: Data { 54 | 55 | return Data([rawValue]) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Sources/BluetoothGATT/GATTAlertNotificationService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GATTAlertNotificationService.swift 3 | // Bluetooth 4 | // 5 | // Created by Alsey Coleman Miller on 6/13/18. 6 | // Copyright © 2018 PureSwift. All rights reserved. 7 | // 8 | 9 | /// Alert Notification service exposes: 10 | /// 11 | /// • The different types of alerts with the short text messages. 12 | /// • The information how many count of new alert messages. 13 | /// • The information how many count of unread alerts. 14 | /// 15 | /// The Alert Notification service exposes alert information in a device. This information includes the following: 16 | /// 17 | /// - Type of alert occuring in a device. 18 | /// - Additional text information such as caller ID or sender ID 19 | /// - Count of new alerts. 20 | /// - Count of unread alert items. 21 | @frozen 22 | public struct GATTAlertNotificationService: GATTService { 23 | 24 | public static var uuid: BluetoothUUID { BluetoothUUID.Service.alertNotification } 25 | } 26 | -------------------------------------------------------------------------------- /Sources/BluetoothGATT/GATTAltitude.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GATTAltitude.swift 3 | // Bluetooth 4 | // 5 | // Created by Carlos Duclos on 6/15/18. 6 | // Copyright © 2018 PureSwift. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Bluetooth 11 | 12 | /// Altitude 13 | /// 14 | /// The Altitude characteristic describes the altitude of the device. 15 | /// 16 | /// - SeeAlso: [Altitude](https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.altitude.xml) 17 | @frozen 18 | public struct GATTAltitude: RawRepresentable, GATTCharacteristic, Equatable, Hashable { 19 | 20 | internal static let length = MemoryLayout.size 21 | 22 | public static var uuid: BluetoothUUID { BluetoothUUID.Characteristic.altitude } 23 | 24 | public let rawValue: UInt16 25 | 26 | public init(rawValue: UInt16) { 27 | 28 | self.rawValue = rawValue 29 | } 30 | 31 | public init?(data: Data) { 32 | 33 | guard data.count == Self.length 34 | else { return nil } 35 | 36 | self.init(rawValue: UInt16(littleEndian: UInt16(bytes: (data[0], data[1])))) 37 | } 38 | 39 | public var data: Data { 40 | 41 | let bytes = rawValue.littleEndian.bytes 42 | return Data([bytes.0, bytes.1]) 43 | } 44 | } 45 | 46 | extension GATTAltitude: CustomStringConvertible { 47 | 48 | public var description: String { 49 | 50 | return rawValue.description 51 | } 52 | } 53 | 54 | extension GATTAltitude: ExpressibleByIntegerLiteral { 55 | 56 | public init(integerLiteral value: UInt16) { 57 | 58 | self.init(rawValue: value) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /Sources/BluetoothGATT/GATTAnaerobicHeartRateLowerLimit.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GATTAnaerobicHeartRateLowerLimit.swift 3 | // Bluetooth 4 | // 5 | // Created by Carlos Duclos on 6/15/18. 6 | // Copyright © 2018 PureSwift. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Bluetooth 11 | 12 | /// Anaerobic Heart Rate Lower Limit 13 | /// 14 | /// Lower limit of the heart rate where the user enhances his anaerobic tolerance while exercising. 15 | /// 16 | /// - SeeAlso: [Anaerobic Heart Rate Lower Limit](https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.anaerobic_heart_rate_lower_limit.xml) 17 | @frozen 18 | public struct GATTAnaerobicHeartRateLowerLimit: GATTCharacteristic, Equatable, Hashable { 19 | 20 | public typealias BeatsPerMinute = GATTBeatsPerMinute.Byte 21 | 22 | internal static let length = MemoryLayout.size 23 | 24 | public static var uuid: BluetoothUUID { BluetoothUUID.Characteristic.anaerobicHeartRateLowerLimit } 25 | 26 | public var beats: BeatsPerMinute 27 | 28 | public init(beats: BeatsPerMinute) { 29 | 30 | self.beats = beats 31 | } 32 | 33 | public init?(data: Data) { 34 | 35 | guard data.count == Self.length 36 | else { return nil } 37 | 38 | let beats = BeatsPerMinute(rawValue: data[0]) 39 | 40 | self.init(beats: beats) 41 | } 42 | 43 | public var data: Data { 44 | 45 | return Data([beats.rawValue]) 46 | } 47 | 48 | } 49 | 50 | extension GATTAnaerobicHeartRateLowerLimit: CustomStringConvertible { 51 | 52 | public var description: String { 53 | 54 | return beats.description 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Sources/BluetoothGATT/GATTAnaerobicHeartRateUpperLimit.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GATTAnaerobicHeartRateUpperLimit.swift 3 | // Bluetooth 4 | // 5 | // Created by Carlos Duclos on 6/15/18. 6 | // Copyright © 2018 PureSwift. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Bluetooth 11 | 12 | /// Anaerobic Heart Rate Upper Limit 13 | /// 14 | /// Upper limit of the heart rate where the user enhances his anaerobic tolerance while exercising. 15 | /// 16 | /// - SeeAlso: [Anaerobic Heart Rate Upper Limit](https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.anaerobic_heart_rate_upper_limit.xml) 17 | @frozen 18 | public struct GATTAnaerobicHeartRateUpperLimit: GATTCharacteristic, Equatable, Hashable { 19 | 20 | public typealias BeatsPerMinute = GATTBeatsPerMinute.Byte 21 | 22 | internal static let length = MemoryLayout.size 23 | 24 | public static var uuid: BluetoothUUID { BluetoothUUID.Characteristic.anaerobicHeartRateUpperLimit } 25 | 26 | public var beats: BeatsPerMinute 27 | 28 | public init(beats: BeatsPerMinute) { 29 | 30 | self.beats = beats 31 | } 32 | 33 | public init?(data: Data) { 34 | 35 | guard data.count == Self.length 36 | else { return nil } 37 | 38 | let beats = BeatsPerMinute(rawValue: data[0]) 39 | 40 | self.init(beats: beats) 41 | } 42 | 43 | public var data: Data { 44 | 45 | return Data([beats.rawValue]) 46 | } 47 | 48 | } 49 | 50 | extension GATTAnaerobicHeartRateUpperLimit: CustomStringConvertible { 51 | 52 | public var description: String { 53 | 54 | return beats.description 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Sources/BluetoothGATT/GATTBatteryLevel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GATTBatteryLevel.swift 3 | // Bluetooth 4 | // 5 | // Created by Alsey Coleman Miller on 6/13/18. 6 | // Copyright © 2018 PureSwift. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Bluetooth 11 | 12 | /// Battery Level 13 | /// 14 | /// The current charge level of a battery. 100% represents fully charged while 0% represents fully discharged. 15 | /// 16 | /// - SeeAlso: [Battery Level](https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.battery_level.xml) 17 | @frozen 18 | public struct GATTBatteryLevel: GATTCharacteristic, Equatable, Hashable { 19 | 20 | public typealias Percentage = GATTBatteryPercentage 21 | 22 | internal static let length = 1 23 | 24 | public static var uuid: BluetoothUUID { BluetoothUUID.Characteristic.batteryLevel } 25 | 26 | public var level: Percentage 27 | 28 | public init(level: Percentage) { 29 | 30 | self.level = level 31 | } 32 | } 33 | 34 | public extension GATTBatteryLevel { 35 | 36 | init?(data: Data) { 37 | 38 | guard data.count == Self.length 39 | else { return nil } 40 | 41 | guard let level = Percentage(rawValue: data[0]) 42 | else { return nil } 43 | 44 | self.init(level: level) 45 | } 46 | 47 | var data: Data { 48 | 49 | return Data([level.rawValue]) 50 | } 51 | } 52 | 53 | // MARK: - CustomStringConvertible 54 | 55 | extension GATTBatteryLevel: CustomStringConvertible { 56 | 57 | public var description: String { 58 | 59 | return level.description 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /Sources/BluetoothGATT/GATTBatteryService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GATTBatteryService.swift 3 | // Bluetooth 4 | // 5 | // Created by Alsey Coleman Miller on 6/13/18. 6 | // Copyright © 2018 PureSwift. All rights reserved. 7 | // 8 | 9 | /// The Battery Service exposes the state of a battery within a device. 10 | /// 11 | /// The Battery Service exposes the Battery State and Battery Level of a single battery or set of batteries in a device. 12 | /// 13 | /// This service has no dependencies on other GATT-based services. 14 | @frozen 15 | public struct GATTBatteryService: GATTService { 16 | 17 | public static var uuid: BluetoothUUID { BluetoothUUID.Service.battery } 18 | } 19 | -------------------------------------------------------------------------------- /Sources/BluetoothGATT/GATTBloodPressureService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GATTBloodPressureService.swift 3 | // Bluetooth 4 | // 5 | // Created by Alsey Coleman Miller on 6/13/18. 6 | // Copyright © 2018 PureSwift. All rights reserved. 7 | // 8 | 9 | /// Blood Pressure Service 10 | /// 11 | /// This service exposes blood pressure and other data from a blood pressure monitor for use in consumer and professional healthcare applications. 12 | @frozen 13 | public struct GATTBloodPressureService: GATTService { 14 | 15 | public static var uuid: BluetoothUUID { BluetoothUUID.Service.bloodPressure } 16 | } 17 | -------------------------------------------------------------------------------- /Sources/BluetoothGATT/GATTCharacteristic.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GATTCharacteristic.swift 3 | // Bluetooth 4 | // 5 | // Created by Alsey Coleman Miller on 6/10/18. 6 | // Copyright © 2018 PureSwift. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Bluetooth 11 | import Foundation 12 | 13 | /// GATT Characteristic protocol. 14 | /// 15 | /// Describes a type that can encode / decode data to a characteristic type. 16 | public protocol GATTCharacteristic: DeprecatedGATTCharacteristic { 17 | 18 | /// The Bluetooth UUID of the characteristic. 19 | static var uuid: BluetoothUUID { get } 20 | 21 | /// Initialize from data. 22 | init?(data: Data) 23 | 24 | /// Append data representation into buffer. 25 | func append(to data: inout Data) 26 | } 27 | 28 | public extension DataContainer { 29 | 30 | /// Initialize data with contents of value. 31 | init(_ value: T) { 32 | self.init() 33 | value.append(to: &self) 34 | } 35 | } 36 | 37 | public protocol DeprecatedGATTCharacteristic { 38 | 39 | init?(data: Data) 40 | 41 | var data: Data { get } 42 | } 43 | 44 | public extension GATTCharacteristic { 45 | 46 | // TODO: Remove Foundation.Data Usage 47 | var data: Data { 48 | Data(self) 49 | } 50 | 51 | // TODO: Should implement at least one of these 52 | func append(to data: inout Data) { 53 | data += self.data 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Sources/BluetoothGATT/GATTDayDateTime.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GATTDayDateTime.swift 3 | // Bluetooth 4 | // 5 | // Created by Carlos Duclos on 7/5/18. 6 | // Copyright © 2018 PureSwift. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Bluetooth 11 | 12 | /// Day Date Time 13 | /// 14 | /// - SeeAlso: [Day Date Time](https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.day_date_time.xml) 15 | @frozen 16 | public struct GATTDayDateTime: GATTCharacteristic { 17 | 18 | internal static let length = GATTDateTime.length + GATTDayOfWeek.length 19 | 20 | public static var uuid: BluetoothUUID { BluetoothUUID.Characteristic.dayDateTime } 21 | 22 | public var dateTime: GATTDateTime 23 | 24 | public var dayOfWeek: GATTDayOfWeek 25 | 26 | public init(dateTime: GATTDateTime, dayOfWeek: GATTDayOfWeek) { 27 | 28 | self.dateTime = dateTime 29 | self.dayOfWeek = dayOfWeek 30 | } 31 | 32 | public init?(data: Data) { 33 | 34 | guard data.count == Self.length 35 | else { return nil } 36 | 37 | guard let dateTime = GATTDateTime(data: data.subdata(in: (0..<7))) 38 | else { return nil } 39 | 40 | guard let dayOfWeek = GATTDayOfWeek(data: data.subdata(in: (7..<8))) 41 | else { return nil } 42 | 43 | self.init(dateTime: dateTime, dayOfWeek: dayOfWeek) 44 | } 45 | 46 | public var data: Data { 47 | 48 | return dateTime.data + dayOfWeek.data 49 | } 50 | } 51 | 52 | extension GATTDayDateTime: Equatable { 53 | 54 | public static func == (lhs: GATTDayDateTime, rhs: GATTDayDateTime) -> Bool { 55 | 56 | return lhs.dateTime == rhs.dateTime && lhs.dayOfWeek == rhs.dayOfWeek 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Sources/BluetoothGATT/GATTDescriptor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CharacteristicDescriptor.swift 3 | // Bluetooth 4 | // 5 | // Created by Alsey Coleman Miller on 4/17/18. 6 | // Copyright © 2018 PureSwift. All rights reserved. 7 | // 8 | 9 | import Bluetooth 10 | 11 | /// GATT Characteristic Descriptor 12 | public protocol GATTDescriptor: DataConvertible { 13 | 14 | /// Bluetooth UUID of the descriptor. 15 | static var uuid: BluetoothUUID { get } 16 | 17 | /// Decode from data. 18 | init?(data: Data) 19 | 20 | /// Encode to data. 21 | func append(to data: inout Data) 22 | } 23 | 24 | public extension GATTAttribute.Descriptor { 25 | 26 | init( 27 | _ descriptor: Descriptor, 28 | permissions: ATTAttributePermissions = [.read] 29 | ) { 30 | self.init( 31 | uuid: Descriptor.uuid, 32 | value: Data(descriptor), 33 | permissions: permissions 34 | ) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Sources/BluetoothGATT/GATTDstOffset.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GATTDstOffset.swift 3 | // Bluetooth 4 | // 5 | // Created by Carlos Duclos on 7/6/18. 6 | // Copyright © 2018 PureSwift. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Bluetooth 11 | 12 | /// DST Offset 13 | /// 14 | /// - SeeAlso: [DST Offset](https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.dst_offset.xml) 15 | @frozen 16 | public enum GATTDstOffset: UInt8, GATTCharacteristic { 17 | 18 | internal static let length = MemoryLayout.size 19 | 20 | public static var uuid: BluetoothUUID { BluetoothUUID.Characteristic.dstOffset } 21 | 22 | /// Standard Time 23 | case standardTime = 0 24 | 25 | /// Half An Hour Daylight Time (+0.5h) 26 | case halfAndHourDaylightTime = 2 27 | 28 | /// Daylight Time (+1h) 29 | case daylightTime = 4 30 | 31 | /// Double Daylight Time (+2h) 32 | case doubleDaylightTime = 8 33 | 34 | /// DST is not known 35 | case unknown = 255 36 | 37 | public init?(data: Data) { 38 | 39 | guard data.count == Self.length 40 | else { return nil } 41 | 42 | self.init(rawValue: data[0]) 43 | } 44 | 45 | public var data: Data { 46 | 47 | return Data([rawValue]) 48 | } 49 | 50 | } 51 | 52 | extension GATTDstOffset: CustomStringConvertible { 53 | 54 | public var description: String { 55 | 56 | return rawValue.description 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Sources/BluetoothGATT/GATTExternalReportReference.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GATTExternalReportReference.swift 3 | // Bluetooth 4 | // 5 | // Created by Carlos Duclos on 6/14/18. 6 | // Copyright © 2018 PureSwift. All rights reserved. 7 | // 8 | 9 | import Bluetooth 10 | 11 | /// GATT External Report Reference Descriptor 12 | /// 13 | /// The External Report Reference characteristic descriptor allows a HID Host to map information from the Report Map characteristic value for Input Report, Output Report or Feature Report data to the Characteristic UUID of external service characteristics used to transfer the associated data. 14 | @frozen 15 | public struct GATTExternalReportReference: GATTDescriptor, Hashable, Sendable { 16 | 17 | public static var uuid: BluetoothUUID { BluetoothUUID.Descriptor.externalReportReference } 18 | 19 | public var uuid: BluetoothUUID 20 | 21 | public init(uuid: BluetoothUUID) { 22 | self.uuid = uuid 23 | } 24 | } 25 | 26 | extension GATTExternalReportReference: DataConvertible { 27 | 28 | public init?(data: Data) { 29 | guard let uuid = BluetoothUUID(data: data) 30 | else { return nil } 31 | self.init(uuid: BluetoothUUID(littleEndian: uuid)) 32 | } 33 | 34 | public func append(to data: inout Data) where Data: DataContainer { 35 | data += uuid 36 | } 37 | 38 | public var dataLength: Int { 39 | uuid.dataLength 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Sources/BluetoothGATT/GATTFirmwareRevisionString.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GATTFirmwareRevisionString.swift 3 | // Bluetooth 4 | // 5 | // Created by Carlos Duclos on 6/21/18. 6 | // Copyright © 2018 PureSwift. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Bluetooth 11 | 12 | /*+ 13 | Firmware Revision String 14 | 15 | [Firmware Revision String](https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.firmware_revision_string.xml) 16 | 17 | The value of this characteristic is a UTF-8 string representing the firmware revision for the firmware within the device. 18 | */ 19 | @frozen 20 | public struct GATTFirmwareRevisionString: RawRepresentable, GATTCharacteristic, Equatable, Hashable, Sendable { 21 | 22 | public static var uuid: BluetoothUUID { BluetoothUUID.Characteristic.firmwareRevisionString } 23 | 24 | public let rawValue: String 25 | 26 | public init(rawValue: String) { 27 | self.rawValue = rawValue 28 | } 29 | 30 | public init?(data: Data) { 31 | 32 | guard let rawValue = String(utf8: data) 33 | else { return nil } 34 | 35 | self.init(rawValue: rawValue) 36 | } 37 | 38 | public func append(to data: inout Data) where Data: DataContainer { 39 | data += rawValue.utf8 40 | } 41 | } 42 | 43 | extension GATTFirmwareRevisionString: CustomStringConvertible { 44 | 45 | public var description: String { 46 | return rawValue 47 | } 48 | } 49 | 50 | extension GATTFirmwareRevisionString: ExpressibleByStringLiteral { 51 | 52 | public init(stringLiteral value: String) { 53 | self.init(rawValue: value) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Sources/BluetoothGATT/GATTFloorNumber.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GATTFloorNumber.swift 3 | // Bluetooth 4 | // 5 | // Created by Carlos Duclos on 7/4/18. 6 | // Copyright © 2018 PureSwift. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Bluetooth 11 | 12 | /// Floor Number 13 | /// 14 | /// The Floor Number characteristic describes in which floor the device is installed. 15 | /// 16 | /// - SeeAlso: [Floor Number](https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.floor_number.xml) 17 | @frozen 18 | public struct GATTFloorNumber: RawRepresentable, GATTCharacteristic { 19 | 20 | internal static let length = MemoryLayout.size 21 | 22 | public static var uuid: BluetoothUUID { BluetoothUUID.Characteristic.floorNumber } 23 | 24 | public let rawValue: UInt8 25 | 26 | public init(rawValue: UInt8) { 27 | 28 | self.rawValue = rawValue 29 | } 30 | 31 | public init?(data: Data) { 32 | 33 | guard data.count == Self.length 34 | else { return nil } 35 | 36 | self.init(rawValue: data[0]) 37 | } 38 | 39 | public var data: Data { 40 | 41 | return Data([rawValue]) 42 | } 43 | } 44 | 45 | extension GATTFloorNumber: Equatable { 46 | 47 | public static func == (lhs: GATTFloorNumber, rhs: GATTFloorNumber) -> Bool { 48 | 49 | return lhs.rawValue == rhs.rawValue 50 | } 51 | } 52 | 53 | extension GATTFloorNumber: CustomStringConvertible { 54 | 55 | public var description: String { 56 | 57 | return rawValue.description 58 | } 59 | } 60 | 61 | extension GATTFloorNumber: ExpressibleByIntegerLiteral { 62 | 63 | public init(integerLiteral value: UInt8) { 64 | 65 | self.init(rawValue: value) 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /Sources/BluetoothGATT/GATTHardwareRevisionString.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GATTHardwareRevisionString.swift 3 | // Bluetooth 4 | // 5 | // Created by Carlos Duclos on 6/27/18. 6 | // Copyright © 2018 PureSwift. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Bluetooth 11 | 12 | /// Hardware Revision String 13 | /// 14 | /// [Hardware Revision String](https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.hardware_revision_string.xml) 15 | /// 16 | /// The value of this characteristic is a UTF-8 string representing the hardware revision for the hardware within the device. 17 | @frozen 18 | public struct GATTHardwareRevisionString: RawRepresentable, Equatable, Hashable, Sendable, GATTCharacteristic { 19 | 20 | public static var uuid: BluetoothUUID { BluetoothUUID.Characteristic.hardwareRevisionString } 21 | 22 | public let rawValue: String 23 | 24 | public init(rawValue: String) { 25 | self.rawValue = rawValue 26 | } 27 | 28 | public init?(data: Data) { 29 | 30 | guard let rawValue = String(utf8: data) 31 | else { return nil } 32 | 33 | self.init(rawValue: rawValue) 34 | } 35 | 36 | public func append(to data: inout Data) where Data: DataContainer { 37 | data += rawValue.utf8 38 | } 39 | } 40 | 41 | extension GATTHardwareRevisionString: CustomStringConvertible { 42 | 43 | public var description: String { 44 | return rawValue 45 | } 46 | } 47 | 48 | extension GATTHardwareRevisionString: ExpressibleByStringLiteral { 49 | 50 | public init(stringLiteral value: String) { 51 | self.init(rawValue: value) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Sources/BluetoothGATT/GATTLocalTimeInformation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GATTLocalTimeInformation.swift 3 | // Bluetooth 4 | // 5 | // Created by Carlos Duclos on 7/6/18. 6 | // Copyright © 2018 PureSwift. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Bluetooth 11 | 12 | /// Local Time Information 13 | /// 14 | /// - SeeAlso: [Local Time Information](https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.local_time_information.xml) 15 | @frozen 16 | public struct GATTLocalTimeInformation: GATTCharacteristic, Equatable { 17 | 18 | internal static let length = GATTTimeZone.length + GATTDstOffset.length 19 | 20 | public static var uuid: BluetoothUUID { BluetoothUUID.Characteristic.localTimeInformation } 21 | 22 | public var timeZone: GATTTimeZone 23 | 24 | public var dstOffset: GATTDstOffset 25 | 26 | public init(timeZone: GATTTimeZone, dstOffset: GATTDstOffset) { 27 | 28 | self.timeZone = timeZone 29 | self.dstOffset = dstOffset 30 | } 31 | 32 | public init?(data: Data) { 33 | 34 | guard data.count == Self.length 35 | else { return nil } 36 | 37 | guard let timeZone = GATTTimeZone(data: data.subdata(in: (0..(data: Data) { 28 | guard let rawValue = String(utf8: data) 29 | else { return nil } 30 | self.init(rawValue: rawValue) 31 | } 32 | 33 | public func append(to data: inout Data) where Data: DataContainer { 34 | data += rawValue.utf8 35 | } 36 | } 37 | 38 | extension GATTManufacturerNameString: CustomStringConvertible { 39 | 40 | public var description: String { 41 | return rawValue 42 | } 43 | } 44 | 45 | extension GATTManufacturerNameString: ExpressibleByStringLiteral { 46 | 47 | public init(stringLiteral value: String) { 48 | self.init(rawValue: value) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Sources/BluetoothGATT/GATTNumberOfDigitals.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GATTNumberOfDigitals.swift 3 | // Bluetooth 4 | // 5 | // Created by Carlos Duclos on 6/14/18. 6 | // Copyright © 2018 PureSwift. All rights reserved. 7 | // 8 | 9 | import Bluetooth 10 | 11 | /// GATT Number of Digitals Descriptor 12 | /// 13 | /// The Characteristic Number of Digitals descriptor is used for defining the number of digitals in a characteristic. 14 | @frozen 15 | public struct GATTNumberOfDigitals: GATTDescriptor, RawRepresentable, Equatable, Hashable, Sendable { 16 | 17 | public static var uuid: BluetoothUUID { BluetoothUUID.Descriptor.numberOfDigitals } 18 | 19 | public var rawValue: UInt8 20 | 21 | public init(rawValue: UInt8) { 22 | self.rawValue = rawValue 23 | } 24 | } 25 | 26 | // MARK: - DataConvertible 27 | 28 | extension GATTNumberOfDigitals: DataConvertible { 29 | 30 | public static var length: Int { 1 } 31 | 32 | public init?(data: Data) { 33 | guard data.count == Self.length 34 | else { return nil } 35 | self.init(rawValue: data[0]) 36 | } 37 | 38 | public func append(to data: inout Data) where Data: DataContainer { 39 | data += rawValue 40 | } 41 | 42 | public var dataLength: Int { Self.length } 43 | } 44 | 45 | // MARK: - CustomStringConvertible 46 | 47 | extension GATTNumberOfDigitals: CustomStringConvertible { 48 | 49 | public var description: String { 50 | return rawValue.description 51 | } 52 | } 53 | 54 | // MARK: - ExpressibleByIntegerLiteral 55 | 56 | extension GATTNumberOfDigitals: ExpressibleByIntegerLiteral { 57 | 58 | public init(integerLiteral value: UInt8) { 59 | self.init(rawValue: value) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /Sources/BluetoothGATT/GATTObjectID.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GATTObjectID.swift 3 | // Bluetooth 4 | // 5 | // Created by Carlos Duclos on 7/11/18. 6 | // Copyright © 2018 PureSwift. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Bluetooth 11 | 12 | /// Object ID 13 | /// 14 | /// - SeeAlso: [Object ID](https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.object_id.xml) 15 | @frozen 16 | public struct GATTObjectID: Equatable, RawRepresentable, GATTCharacteristic { 17 | 18 | internal static let length = MemoryLayout.size 19 | 20 | public static var uuid: BluetoothUUID { BluetoothUUID.Characteristic.objectId } 21 | 22 | internal static var min: UInt48 { UInt48(256) } 23 | 24 | internal static var max: UInt48 { UInt48(281_474_976_710_655) } 25 | 26 | public let rawValue: UInt48 27 | 28 | public init?(rawValue: UInt48) { 29 | 30 | guard rawValue <= GATTObjectID.max, 31 | rawValue >= GATTObjectID.min 32 | else { return nil } 33 | 34 | self.rawValue = rawValue 35 | } 36 | 37 | public init?(data: Data) { 38 | 39 | guard data.count == Self.length 40 | else { return nil } 41 | 42 | let rawValue = UInt48(littleEndian: UInt48(bytes: (data[0], data[1], data[2], data[3], data[4], data[5]))) 43 | 44 | self.init(rawValue: rawValue) 45 | } 46 | 47 | public var data: Data { 48 | 49 | let bytes = rawValue.littleEndian.bytes 50 | 51 | return Data([bytes.0, bytes.1, bytes.2, bytes.3, bytes.4, bytes.5]) 52 | } 53 | } 54 | 55 | extension GATTObjectID: CustomStringConvertible { 56 | 57 | public var description: String { 58 | return rawValue.description 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /Sources/BluetoothGATT/GATTObjectType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GATTObjectType.swift 3 | // Bluetooth 4 | // 5 | // Created by Carlos Duclos on 7/11/18. 6 | // Copyright © 2018 PureSwift. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Bluetooth 11 | 12 | /// Object Type 13 | /// 14 | /// Description: Unspecified Object Type, 16-bit UUID: << Unspecified Object Type >> 15 | /// 16 | /// - SeeAlso: [Object Type](https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.object_type.xml) 17 | @frozen 18 | public struct GATTObjectType: Equatable, Hashable, RawRepresentable, GATTCharacteristic { 19 | 20 | internal static let length = MemoryLayout.size 21 | 22 | public static var uuid: BluetoothUUID { BluetoothUUID.Characteristic.objectType } 23 | 24 | public let rawValue: UInt16 25 | 26 | public init(rawValue: UInt16) { 27 | 28 | self.rawValue = rawValue 29 | } 30 | 31 | public init?(data: Data) { 32 | 33 | guard data.count == Self.length 34 | else { return nil } 35 | 36 | let rawValue = UInt16(littleEndian: UInt16(bytes: (data[0], data[1]))) 37 | 38 | self.init(rawValue: rawValue) 39 | } 40 | 41 | public var data: Data { 42 | 43 | let bytes = rawValue.littleEndian.bytes 44 | 45 | return Data([bytes.0, bytes.1]) 46 | } 47 | } 48 | 49 | extension GATTObjectType: CustomStringConvertible { 50 | 51 | public var description: String { 52 | 53 | return BluetoothUUID.bit16(rawValue).description 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Sources/BluetoothGATT/GATTProfile.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GATTProfile.swift 3 | // Bluetooth 4 | // 5 | // Created by Carlos Duclos on 6/4/18. 6 | // Copyright © 2018 PureSwift. All rights reserved. 7 | // 8 | 9 | protocol GATTProfile { 10 | 11 | static var services: [GATTService.Type] { get } 12 | } 13 | -------------------------------------------------------------------------------- /Sources/BluetoothGATT/GATTScanRefresh.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GATTScanRefresh.swift 3 | // Bluetooth 4 | // 5 | // Created by Carlos Duclos on 7/11/18. 6 | // Copyright © 2018 PureSwift. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Bluetooth 11 | 12 | /// Scan Refresh 13 | /// 14 | /// The Scan Refresh characteristic is used to notify the Client that the Server requires the Scan Interval Window characteristic to be written with the latest values upon notification. 15 | /// 16 | /// - SeeAlso: [Scan Refresh](https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.scan_refresh.xml) 17 | @frozen 18 | public enum GATTScanRefresh: UInt8, GATTCharacteristic { 19 | 20 | internal static let length = MemoryLayout.size 21 | 22 | public static var uuid: BluetoothUUID { BluetoothUUID.Characteristic.scanRefresh } 23 | 24 | case serverRequiredRefresh = 0 25 | 26 | public init?(data: Data) { 27 | 28 | guard data.count == Self.length 29 | else { return nil } 30 | 31 | self.init(rawValue: data[0]) 32 | } 33 | 34 | public var data: Data { 35 | 36 | return Data([rawValue]) 37 | } 38 | 39 | } 40 | 41 | extension GATTScanRefresh: CustomStringConvertible { 42 | 43 | public var description: String { 44 | 45 | return rawValue.description 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Sources/BluetoothGATT/GATTService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GATTService.swift 3 | // Bluetooth 4 | // 5 | // Created by Alsey Coleman Miller on 6/10/18. 6 | // Copyright © 2018 PureSwift. All rights reserved. 7 | // 8 | 9 | public protocol GATTService { 10 | 11 | static var uuid: BluetoothUUID { get } 12 | } 13 | -------------------------------------------------------------------------------- /Sources/BluetoothGATT/GATTTimeSource.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GATTTimeSource.swift 3 | // Bluetooth 4 | // 5 | // Created by Carlos Duclos on 7/6/18. 6 | // Copyright © 2018 PureSwift. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Bluetooth 11 | 12 | /// Time Source 13 | /// 14 | /// - SeeAlso: [Time Source](https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.time_source.xml) 15 | @frozen 16 | public enum GATTTimeSource: UInt8, GATTCharacteristic { 17 | 18 | internal static let length = MemoryLayout.size 19 | 20 | public static var uuid: BluetoothUUID { BluetoothUUID.Characteristic.timeSource } 21 | 22 | /// Unknown 23 | case unknown = 0 24 | 25 | /// Network Time Protocol 26 | case networkTimeProtocol = 1 27 | 28 | /// GPS 29 | case gps = 2 30 | 31 | /// Radio Time Signal 32 | case radioTimeSignal = 3 33 | 34 | /// Manual 35 | case manual = 4 36 | 37 | /// Atomic Clock 38 | case atomicClock = 5 39 | 40 | /// Cellular Network 41 | case cellularNetwork = 6 42 | 43 | public init?(data: Data) { 44 | 45 | guard data.count == Self.length 46 | else { return nil } 47 | 48 | self.init(rawValue: data[0]) 49 | } 50 | 51 | public var data: Data { 52 | 53 | return Data([rawValue]) 54 | } 55 | 56 | } 57 | 58 | extension GATTTimeSource: Equatable { 59 | 60 | public static func == (lhs: GATTTimeSource, rhs: GATTTimeSource) -> Bool { 61 | 62 | return lhs.rawValue == rhs.rawValue 63 | } 64 | } 65 | 66 | extension GATTTimeSource: CustomStringConvertible { 67 | 68 | public var description: String { 69 | 70 | return rawValue.description 71 | } 72 | 73 | } 74 | -------------------------------------------------------------------------------- /Sources/BluetoothGATT/GATTTimeUpdateControlPoint.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GATTTimeUpdateControlPoint.swift 3 | // Bluetooth 4 | // 5 | // Created by Carlos Duclos on 7/10/18. 6 | // Copyright © 2018 PureSwift. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Bluetooth 11 | 12 | /// Time Update Control Point 13 | /// 14 | /// - SeeAlso: [Time Update Control Point](https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.time_update_control_point.xml) 15 | @frozen 16 | public enum GATTTimeUpdateControlPoint: UInt8, GATTCharacteristic { 17 | 18 | internal static let length = MemoryLayout.size 19 | 20 | public static var uuid: BluetoothUUID { BluetoothUUID.Characteristic.timeUpdateControlPoint } 21 | 22 | /// Get Reference Update 23 | case getReferenceUpdate = 1 24 | 25 | /// Cancel Reference Update 26 | case cancelReferenceUpdate = 2 27 | 28 | public init?(data: Data) { 29 | 30 | guard data.count == Self.length 31 | else { return nil } 32 | 33 | self.init(rawValue: data[0]) 34 | } 35 | 36 | public var data: Data { 37 | 38 | return Data([rawValue]) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Sources/BluetoothGATT/GATTTimeWithDst.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GATTTimeWithDst.swift 3 | // Bluetooth 4 | // 5 | // Created by Carlos Duclos on 7/10/18. 6 | // Copyright © 2018 PureSwift. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Bluetooth 11 | 12 | /// Time with DST 13 | /// 14 | /// - SeeAlso: [Time with DST](https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.time_with_dst.xml) 15 | @frozen 16 | public struct GATTTimeWithDst: GATTCharacteristic, Equatable { 17 | 18 | internal static let length = GATTDateTime.length + GATTDstOffset.length 19 | 20 | public static var uuid: BluetoothUUID { BluetoothUUID.Characteristic.timeWithDst } 21 | 22 | public var datetime: GATTDateTime 23 | 24 | public var dstOffset: GATTDstOffset 25 | 26 | public init(datetime: GATTDateTime, dstOffset: GATTDstOffset) { 27 | 28 | self.datetime = datetime 29 | self.dstOffset = dstOffset 30 | } 31 | 32 | public init?(data: Data) { 33 | 34 | guard data.count == Self.length 35 | else { return nil } 36 | 37 | guard let datetime = GATTDateTime(data: data.subdata(in: (0..(data: Data) { 45 | 46 | guard let rawValue = String(utf8: data) 47 | else { return nil } 48 | 49 | self.init(rawValue: rawValue) 50 | } 51 | 52 | public func append(to data: inout Data) where Data: DataContainer { 53 | data += rawValue.utf8 54 | } 55 | 56 | public var dataLength: Int { 57 | rawValue.utf8.count 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /Sources/BluetoothHCI/AdvertisingChannelHeader.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AdvertisingChannelHeader.swift 3 | // Bluetooth 4 | // 5 | // Created by Alsey Coleman Miller on 11/28/17. 6 | // Copyright © 2017 PureSwift. All rights reserved. 7 | // 8 | 9 | /// Advertising channel PDU Header’s PDU Type 10 | /// 11 | /// - Note: The actual value is 4 bits. 12 | /// 13 | /// - SeeAlso: BLUETOOTH SPECIFICATION Version 4.0 [Vol 6] p39 14 | @frozen 15 | public enum AdvertisingChannelHeader: UInt8 { 16 | 17 | /// Connectable undirected advertising event 18 | case undirectedAdvertising = 0b0000 // ADV_IND 19 | 20 | /// Connectable directed advertising event 21 | case directedAdvertising = 0b0001 // ADV_DIRECT_IND 22 | 23 | /// Non-connectable undirected advertising event 24 | case nonConnectableAdvertising = 0b0010 // ADV_NONCONN_IND 25 | 26 | /// Scan Request 27 | case scanRequest = 0b0011 // SCAN_REQ 28 | 29 | /// Scan Response 30 | case scanResponse = 0b0100 // SCAN_RSP 31 | 32 | /// Connection Request 33 | case connectionRequest = 0b0101 // CONNECT_REQ 34 | 35 | /// Scannable undirected advertising event 36 | case scannableAdvertising = 0b0110 // ADV_SCAN_IND 37 | 38 | // 0111-1111 reserved 39 | 40 | /// Default value. 41 | public init() { self = .undirectedAdvertising } 42 | } 43 | -------------------------------------------------------------------------------- /Sources/BluetoothHCI/ChannelIdentifier.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Channel.swift 3 | // Bluetooth 4 | // 5 | // Created by Alsey Coleman Miller on 12/2/18. 6 | // Copyright © 2018 PureSwift. All rights reserved. 7 | // 8 | 9 | /// Bluetooth Channel Identifier 10 | @frozen 11 | public struct ChannelIdentifier: RawRepresentable, Equatable, Hashable { 12 | 13 | public var rawValue: UInt16 14 | 15 | public init(rawValue: UInt16) { 16 | 17 | self.rawValue = rawValue 18 | } 19 | } 20 | 21 | public extension ChannelIdentifier { 22 | 23 | static var att: ChannelIdentifier { return 4 } 24 | } 25 | 26 | // MARK: - ExpressibleByIntegerLiteral 27 | 28 | extension ChannelIdentifier: ExpressibleByIntegerLiteral { 29 | 30 | public init(integerLiteral value: UInt16) { 31 | 32 | self.init(rawValue: value) 33 | } 34 | } 35 | 36 | // MARK: - CustomStringConvertible 37 | 38 | extension ChannelIdentifier: CustomStringConvertible { 39 | 40 | public var description: String { 41 | 42 | return rawValue.description 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Sources/BluetoothHCI/ConnectionAcceptTimeout.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ConnectionAcceptTimeout.swift 3 | // Bluetooth 4 | // 5 | // Created by Alsey Coleman Miller on 6/14/18. 6 | // Copyright © 2018 PureSwift. All rights reserved. 7 | // 8 | 9 | /// Connection Accept Timeout measured in Number of BR/EDR Baseband slots. 10 | /// 11 | /// - Note: Interval Length = N * 0.625 ms (1 Baseband slot) 12 | /// 13 | /// Range for N: 0x0001 – 0xB540 14 | /// 15 | /// Time Range: 0.625 ms - 29 s 16 | @frozen 17 | public struct ConnectionAcceptTimeout: RawRepresentable, Equatable, Hashable { 18 | 19 | public static let length = MemoryLayout.size 20 | 21 | /// 0.625 ms 22 | public static var min: Self { ConnectionAcceptTimeout(0x0001) } 23 | 24 | /// 29 seconds 25 | public static var max: Self { ConnectionAcceptTimeout(0xB540) } 26 | 27 | public let rawValue: UInt16 28 | 29 | public init?(rawValue: UInt16) { 30 | 31 | guard rawValue >= Self.min.rawValue, 32 | rawValue <= Self.max.rawValue 33 | else { return nil } 34 | 35 | self.rawValue = rawValue 36 | 37 | assert((Self.min.rawValue...Self.max.rawValue).contains(rawValue)) 38 | } 39 | 40 | /// Time = N * 0.625 ms 41 | /// 42 | /// Time Range: 0.625 ms - 29 s 43 | public var miliseconds: Double { 44 | 45 | return Double(rawValue) * 0.625 46 | } 47 | 48 | // Private, unsafe 49 | private init(_ rawValue: UInt16) { 50 | self.rawValue = rawValue 51 | } 52 | } 53 | 54 | extension ConnectionAcceptTimeout: Comparable { 55 | 56 | public static func < (lhs: ConnectionAcceptTimeout, rhs: ConnectionAcceptTimeout) -> Bool { 57 | 58 | return lhs.rawValue < rhs.rawValue 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /Sources/BluetoothHCI/Extensions/Bool.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Bool.swift 3 | // Bluetooth 4 | // 5 | // Created by Alsey Coleman Miller on 4/17/18. 6 | // Copyright © 2018 PureSwift. All rights reserved. 7 | // 8 | 9 | internal extension Bool { 10 | 11 | init?(byteValue: UInt8) { 12 | 13 | switch byteValue { 14 | case 0x00: self = false 15 | case 0x01: self = true 16 | default: return nil 17 | } 18 | } 19 | 20 | var byteValue: UInt8 { 21 | 22 | return self ? 0x01 : 0x00 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Sources/BluetoothHCI/Extensions/String.swift: -------------------------------------------------------------------------------- 1 | // 2 | // String.swift 3 | // Bluetooth 4 | // 5 | // Created by Alsey Coleman Miller on 11/4/24. 6 | // 7 | 8 | #if canImport(Darwin) 9 | import Darwin 10 | #endif 11 | 12 | internal extension String { 13 | 14 | /// Initialize from UTF8 data. 15 | init?(utf8 data: Data) { 16 | #if canImport(Darwin) 17 | // Newer Darwin and other platforms use StdLib parsing 18 | if #available(macOS 15, iOS 18, watchOS 11, tvOS 18, visionOS 2, *) { 19 | self.init(validating: data, as: UTF8.self) 20 | } else { 21 | // Older Darwin uses Foundation 22 | self.init(bytes: data, encoding: .utf8) 23 | } 24 | #else 25 | self.init(validating: data, as: UTF8.self) 26 | #endif 27 | } 28 | 29 | #if hasFeature(Embedded) 30 | // Can't use `CVarArg` in Embedded Swift 31 | init?(format: String, length: Int, _ value: UInt8) { 32 | var cString: [CChar] = .init(repeating: 0, count: length + 1) 33 | guard _snprintf_uint8_t(&cString, cString.count, format, value) >= 0 else { 34 | return nil 35 | } 36 | self.init(cString: cString) 37 | } 38 | #elseif canImport(Darwin) 39 | init?(format: String, length: Int, _ value: T) { 40 | var cString: [CChar] = .init(repeating: 0, count: length + 1) 41 | guard snprintf(ptr: &cString, cString.count, format, value) >= 0 else { 42 | return nil 43 | } 44 | self.init(cString: cString) 45 | } 46 | #endif 47 | } 48 | -------------------------------------------------------------------------------- /Sources/BluetoothHCI/Extensions/System.swift: -------------------------------------------------------------------------------- 1 | // 2 | // System.swift 3 | // Bluetooth 4 | // 5 | // Created by Alsey Coleman Miller on 11/7/24. 6 | // 7 | 8 | #if canImport(Darwin) 9 | import Darwin 10 | #elseif os(Windows) 11 | import ucrt 12 | #elseif canImport(Glibc) 13 | import Glibc 14 | #elseif canImport(Musl) 15 | import Musl 16 | #elseif canImport(WASILibc) 17 | import WASILibc 18 | #elseif canImport(Bionic) 19 | import Bionic 20 | #endif 21 | 22 | // Declares the required C functions 23 | #if hasFeature(Embedded) 24 | @_silgen_name("memcmp") 25 | internal func _memcmp( 26 | _ p1: UnsafeRawPointer?, 27 | _ p2: UnsafeRawPointer?, 28 | _ size: Int 29 | ) -> Int32 30 | #else 31 | internal func _memcmp( 32 | _ p1: UnsafeRawPointer, 33 | _ p2: UnsafeRawPointer, 34 | _ size: Int 35 | ) -> Int32 { 36 | memcmp(p1, p2, size) 37 | } 38 | #endif 39 | 40 | #if hasFeature(Embedded) 41 | @_silgen_name("snprintf") 42 | internal func _snprintf_uint8_t( 43 | _ pointer: UnsafeMutablePointer, 44 | _ length: Int, 45 | _ format: UnsafePointer, 46 | _ arg: UInt8 47 | ) -> Int32 48 | #endif 49 | -------------------------------------------------------------------------------- /Sources/BluetoothHCI/HCIAuthenticationComplete.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HCIAuthenticationComplete.swift 3 | // Bluetooth 4 | // 5 | // Created by Carlos Duclos on 8/2/18. 6 | // Copyright © 2018 PureSwift. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /// Authentication Complete Event 12 | /// 13 | /// The Authentication Complete event occurs when authentication has been completed for the specified connection. The Connection_Handle must be a Connection_Handle for an ACL connection. 14 | @frozen 15 | public struct HCIAuthenticationComplete: HCIEventParameter { 16 | 17 | public static let event = HCIGeneralEvent.authenticationComplete 18 | 19 | public static let length: Int = 3 20 | 21 | public let status: HCIStatus 22 | 23 | /// Connection_Handle for which Authentication has been performed. 24 | /// Range: 0x0000-0x0EFF (0x0F00 - 0x0FFF Reserved for future use) 25 | public let handle: UInt16 26 | 27 | public init?(data: Data) { 28 | 29 | guard data.count == Self.length 30 | else { return nil } 31 | 32 | guard let status = HCIStatus(rawValue: data[0]) 33 | else { return nil } 34 | 35 | let handle = UInt16(littleEndian: UInt16(bytes: (data[1], data[2]))) 36 | 37 | self.status = status 38 | self.handle = handle 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /Sources/BluetoothHCI/HCICommandComplete.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HCICommandComplete.swift 3 | // Bluetooth 4 | // 5 | // Created by Alsey Coleman Miller on 6/14/18. 6 | // Copyright © 2018 PureSwift. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /// HCI Command Complete 12 | @frozen 13 | public struct HCICommandComplete: HCIEventParameter { 14 | 15 | public static let event = HCIGeneralEvent.commandComplete 16 | public static let length = 3 17 | 18 | /// The Number of HCI command packets which are allowed to be sent to the Controller from the Host. 19 | public var numberOfCommandPackets: UInt8 20 | public var opcode: UInt16 21 | 22 | public init?(data: Data) { 23 | 24 | guard data.count == Self.length 25 | else { return nil } 26 | 27 | self.numberOfCommandPackets = data[0] 28 | self.opcode = UInt16(littleEndian: UInt16(bytes: (data[1], data[2]))) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Sources/BluetoothHCI/HCICommandStatus.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HCICommandStatus.swift 3 | // Bluetooth 4 | // 5 | // Created by Alsey Coleman Miller on 6/14/18. 6 | // Copyright © 2018 PureSwift. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /// HCI Command Status Event 12 | @frozen 13 | public struct HCICommandStatus: HCIEventParameter { 14 | 15 | public static let event = HCIGeneralEvent.commandStatus 16 | public static let length = 4 17 | 18 | public var status: HCIStatus 19 | public var ncmd: UInt8 20 | public var opcode: UInt16 21 | 22 | public init?(data: Data) { 23 | 24 | guard data.count == Self.length 25 | else { return nil } 26 | 27 | let statusByte = data[0] 28 | 29 | guard let status = HCIStatus(rawValue: statusByte) 30 | else { return nil } 31 | 32 | self.status = status 33 | self.ncmd = data[1] 34 | self.opcode = UInt16(littleEndian: UInt16(bytes: (data[2], data[3]))) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Sources/BluetoothHCI/HCICommandTimeout.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HCICommandTimeout.swift 3 | // Bluetooth 4 | // 5 | // Created by Alsey Coleman Miller on 3/31/18. 6 | // Copyright © 2018 PureSwift. All rights reserved. 7 | // 8 | 9 | #if canImport(Foundation) 10 | import Foundation 11 | #endif 12 | 13 | /// The duration of an HCI command. 14 | @frozen 15 | public struct HCICommandTimeout: RawRepresentable, Equatable, Hashable, Sendable { 16 | 17 | /// The duration of the timeout in miliseconds. 18 | public var rawValue: UInt 19 | 20 | public init(rawValue: UInt) { 21 | self.rawValue = rawValue 22 | } 23 | 24 | /// Default timeout of HCI commands in miliseconds. 25 | public static var `default`: HCICommandTimeout { 1000 } 26 | } 27 | 28 | public extension HCICommandTimeout { 29 | 30 | /// Duration in seconds. 31 | var duration: TimeInterval { 32 | return TimeInterval(rawValue) / 1000.0 33 | } 34 | } 35 | 36 | // MARK: - ExpressibleByIntegerLiteral 37 | 38 | extension HCICommandTimeout: ExpressibleByIntegerLiteral { 39 | 40 | public init(integerLiteral value: UInt) { 41 | self.init(rawValue: value) 42 | } 43 | } 44 | 45 | // MARK: - CustomStringConvertible 46 | 47 | extension HCICommandTimeout: CustomStringConvertible { 48 | 49 | public var description: String { 50 | return "\(duration)s" 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Sources/BluetoothHCI/HCIConnectionPacketTypeChange.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HCIConnectionPacketTypeChange.swift 3 | // Bluetooth 4 | // 5 | // Created by Carlos Duclos on 8/17/18. 6 | // Copyright © 2018 PureSwift. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /// The Connection Packet Type Changed event is used to indicate that the process has completed of the Link Manager changing which packet types can be used for the connection. This allows current connections to be dynamically modified to support different types of user data. The Packet_Type event parameter specifies which packet types the Link Manager can use for the connection identified by the Connection_Handle event parameter for sending L2CAP data or voice. The Packet_Type event parameter does not decide which packet types the LM is allowed to use for sending LMP PDUs. 12 | @frozen 13 | public struct HCIConnectionPacketTypeChange: HCIEventParameter { 14 | 15 | public static let event = HCIGeneralEvent.connectionPacketTypeChanged 16 | 17 | public static let length: Int = 5 18 | 19 | public let status: HCIStatus 20 | 21 | public let connectionHandle: UInt16 22 | 23 | public let packetType: UInt16 24 | 25 | public init?(data: Data) { 26 | 27 | guard data.count == Self.length 28 | else { return nil } 29 | 30 | guard let status = HCIStatus(rawValue: data[0]) 31 | else { return nil } 32 | 33 | let connectionHandle = UInt16(littleEndian: UInt16(bytes: (data[1], data[2]))) 34 | 35 | let packetType = UInt16(littleEndian: UInt16(bytes: (data[3], data[4]))) 36 | 37 | self.status = status 38 | self.connectionHandle = connectionHandle 39 | self.packetType = packetType 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Sources/BluetoothHCI/HCIEvent.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HCIEvent.swift 3 | // Bluetooth 4 | // 5 | // Created by Alsey Coleman Miller on 1/3/16. 6 | // Copyright © 2016 PureSwift. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /// HCI Event Opcode 12 | public protocol HCIEvent: RawRepresentable, Hashable, CustomStringConvertible { 13 | 14 | init?(rawValue: UInt8) 15 | 16 | var rawValue: UInt8 { get } 17 | 18 | var name: String { get } 19 | } 20 | 21 | public extension HCIEvent { 22 | 23 | var description: String { 24 | 25 | return name 26 | } 27 | } 28 | 29 | public protocol HCIEventParameter { 30 | 31 | associatedtype HCIEventType: HCIEvent 32 | 33 | /// Event Opcode 34 | static var event: HCIEventType { get } 35 | 36 | /// Length of the event parameter when encoded to data. 37 | static var length: Int { get } 38 | 39 | /// Attempt to initialize event parameter from data. 40 | init?(data: Data) 41 | } 42 | -------------------------------------------------------------------------------- /Sources/BluetoothHCI/HCIExitPeriodicInquiryMode.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HCIExitPeriodicInquiryMode.swift 3 | // Bluetooth 4 | // 5 | // Created by Carlos Duclos on 7/26/18. 6 | // Copyright © 2018 PureSwift. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | // MARK: - Method 12 | 13 | public extension BluetoothHostControllerInterface { 14 | 15 | /// Exit Periodic Inquiry Mode Command 16 | /// 17 | /// The Exit_Periodic_Inquiry_Mode command is used to end the Periodic Inquiry mode when the local device is in Periodic Inquiry Mode. If the BR/EDR Control- ler is currently in an Inquiry process, the Inquiry process shall be stopped directly and the BR/EDR Controller shall no longer perform periodic inquiries until the Periodic Inquiry Mode command is reissued. 18 | func exitPeriodicInquiry(timeout: HCICommandTimeout = .default) async throws { 19 | 20 | try await deviceRequest(LinkControlCommand.exitPeriodicInquiry, timeout: timeout) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Sources/BluetoothHCI/HCIIOCapabilityRequest.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HCIIOCapabilityRequest.swift 3 | // Bluetooth 4 | // 5 | // Created by Carlos Duclos on 8/16/18. 6 | // Copyright © 2018 PureSwift. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /// The IO Capability Request event is used to indicate that the IO capabilities of the Host are required for a simple pairing process. The Host shall respond with an IO_Capability_Request_Reply command. This event shall only be generated if simple pairing has been enabled with the Write_Simple_Pairing_Mode command. 12 | @frozen 13 | public struct HCIIOCapabilityRequest: HCIEventParameter { 14 | 15 | public static let event = HCIGeneralEvent.ioCapabilityRequest 16 | 17 | public static let length: Int = 6 18 | 19 | public let address: BluetoothAddress 20 | 21 | public init?(data: Data) { 22 | 23 | guard data.count == Self.length 24 | else { return nil } 25 | 26 | let address = BluetoothAddress(littleEndian: BluetoothAddress(bytes: (data[0], data[1], data[2], data[3], data[4], data[5]))) 27 | 28 | self.address = address 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Sources/BluetoothHCI/HCIInquiryCancel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HCIInquiryCancel.swift 3 | // Bluetooth 4 | // 5 | // Created by Carlos Duclos on 7/25/18. 6 | // Copyright © 2018 PureSwift. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | // MARK: - Method 12 | 13 | public extension BluetoothHostControllerInterface { 14 | 15 | /// Inquiry Cancel Command 16 | /// 17 | /// This command is used to start a test where the DUT receives test reference packets at a fixed interval. 18 | /// The tester generates the test reference packets. 19 | func inquiryCancel(timeout: HCICommandTimeout = .default) async throws { 20 | 21 | try await deviceRequest(LinkControlCommand.inquiryCancel, timeout: timeout) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Sources/BluetoothHCI/HCIInquiryComplete.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HCIInquiryComplete.swift 3 | // Bluetooth 4 | // 5 | // Created by Carlos Duclos on 7/30/18. 6 | // Copyright © 2018 PureSwift. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /// Inquiry Complete Event 12 | /// 13 | /// The Inquiry Complete event indicates that the Inquiry is finished. This event contains a Status parameter, which is used to indicate if the Inquiry completed successfully or if the Inquiry was not completed. 14 | @frozen 15 | public struct HCIInquiryComplete: HCIEventParameter { 16 | 17 | public static let event = HCIGeneralEvent.inquiryComplete 18 | 19 | public static let length: Int = 1 20 | 21 | public let status: HCIStatus 22 | 23 | public init?(data: Data) { 24 | 25 | guard data.count == Self.length 26 | else { return nil } 27 | 28 | guard let status = HCIStatus(rawValue: data[0]) 29 | else { return nil } 30 | 31 | self.status = status 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Sources/BluetoothHCI/HCILEAdvertisingSetTerminated.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HCILEAdvertisingSetTerminated.swift 3 | // Bluetooth 4 | // 5 | // Created by Alsey Coleman Miller on 6/15/18. 6 | // Copyright © 2018 PureSwift. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /// LE Advertising Set Terminated Event 12 | /// 13 | /// The event indicates that the Controller has terminated advertising in the advertising sets specified by the Advertising_Handle parameter. 14 | @frozen 15 | public struct HCILEAdvertisingSetTerminated: HCIEventParameter { 16 | 17 | public static let event = LowEnergyEvent.advertisingSetTerminated // 0x12 18 | 19 | public static let length: Int = 5 20 | 21 | public let status: HCIStatus 22 | 23 | public let advertisingHandle: UInt8 24 | 25 | public let connectionHandle: UInt16 // Connection_Handle 26 | 27 | public let numCompletedExtendedAdvertisingEvents: UInt8 28 | 29 | public init?(data: Data) { 30 | 31 | guard data.count == Self.length 32 | else { return nil } 33 | 34 | guard let status = HCIStatus(rawValue: data[0]) 35 | else { return nil } 36 | 37 | let advertisingHandle = data[1] 38 | 39 | let connectionHandle = UInt16(littleEndian: UInt16(bytes: (data[2], data[3]))) 40 | 41 | let numCompletedExtendedAdvertisingEvents = data[4] 42 | 43 | self.status = status 44 | self.advertisingHandle = advertisingHandle 45 | self.connectionHandle = connectionHandle 46 | self.numCompletedExtendedAdvertisingEvents = numCompletedExtendedAdvertisingEvents 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Sources/BluetoothHCI/HCILEChannelSelectionAlgorithm.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HCILEChannelSelectionAlgorithm.swift 3 | // Bluetooth 4 | // 5 | // Created by Alsey Coleman Miller on 6/15/18. 6 | // Copyright © 2018 PureSwift. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /// LE Channel Selection Algorithm Event 12 | /// 13 | /// The LE Channel Selection Algorithm Event indicates which channel selection algorithm is used on a data channel connection. 14 | @frozen 15 | public struct HCILEChannelSelectionAlgorithm: HCIEventParameter { 16 | 17 | public static let event = LowEnergyEvent.channelSelectionAlgorithm // 0x14 18 | 19 | public static let length: Int = 3 20 | 21 | public let connectionHandle: UInt16 // Connection_Handle 22 | 23 | public let channelSelectionAlgorithm: ChannelSelectionAlgorithm 24 | 25 | public init?(data: Data) { 26 | guard data.count == Self.length 27 | else { return nil } 28 | 29 | let connectionHandle = UInt16(littleEndian: UInt16(bytes: (data[0], data[1]))) 30 | 31 | guard let channelSelectionAlgorithm = ChannelSelectionAlgorithm.init(rawValue: data[2]) 32 | else { return nil } 33 | 34 | self.connectionHandle = connectionHandle 35 | self.channelSelectionAlgorithm = channelSelectionAlgorithm 36 | } 37 | 38 | public enum ChannelSelectionAlgorithm: UInt8 { // Channel_Selection_Algorithm 39 | 40 | /// LE Channel Selection Algorithm #1 is used 41 | case algorithm1 = 0x00 42 | 43 | /// LE Channel Selection Algorithm #2 is used 44 | case algorithm2 = 0x01 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Sources/BluetoothHCI/HCILEGenerateDHKeyComplete.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HCILEGenerateDHKeyComplete.swift 3 | // Bluetooth 4 | // 5 | // Created by Alsey Coleman Miller on 6/15/18. 6 | // Copyright © 2018 PureSwift. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /// LE Generate DHKey Complete Event 12 | /// 13 | /// This event indicates that LE Diffie Hellman key generation has been completed by the Controller. 14 | @frozen 15 | public struct HCILEGenerateDHKeyComplete: HCIEventParameter { 16 | 17 | public static let event = LowEnergyEvent.generateDHKeyComplete // 0x09 18 | 19 | public static let length: Int = 33 20 | 21 | public let status: HCIStatus 22 | 23 | public let dhKey: UInt256 24 | 25 | public init?(data: Data) { 26 | 27 | guard data.count == Self.length 28 | else { return nil } 29 | 30 | let statusByte = data[0] 31 | 32 | guard let status = HCIStatus(rawValue: statusByte) 33 | else { return nil } 34 | 35 | let dhKey = UInt256(littleEndian: UInt256(bytes: ((data[1], data[2], data[3], data[4], data[5], data[6], data[7], data[8], data[9], data[10], data[11], data[12], data[13], data[14], data[15], data[16], data[17], data[18], data[19], data[20], data[21], data[22], data[23], data[24], data[25], data[26], data[27], data[28], data[29], data[30], data[31], data[32])))) 36 | 37 | self.status = status 38 | self.dhKey = dhKey 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Sources/BluetoothHCI/HCILELongTermKeyRequest.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HCILELongTermKeyRequest.swift 3 | // Bluetooth 4 | // 5 | // Created by Alsey Coleman Miller on 6/14/18. 6 | // Copyright © 2018 PureSwift. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /// LE Long Term Key Request Event 12 | /// 13 | /// The LE Long Term Key Request event indicates that the master device is attempting 14 | /// to encrypt or re-encrypt the link and is requesting the Long Term Key from the Host. 15 | @frozen 16 | public struct HCILELongTermKeyRequest: HCIEventParameter { 17 | 18 | public static let event = LowEnergyEvent.longTermKeyRequest // 0x05 19 | 20 | public static let length: Int = 12 21 | 22 | public let handle: UInt16 // Connection_Handle 23 | 24 | /// Random Number 25 | public let randomNumber: UInt64 //Random_Number 26 | 27 | public let encryptedDiversifier: UInt16 28 | 29 | public init?(data: Data) { 30 | 31 | guard data.count == Self.length 32 | else { return nil } 33 | 34 | let handle = UInt16(littleEndian: UInt16(bytes: (data[0], data[1]))) 35 | 36 | let randomNumber = UInt64(littleEndian: UInt64(bytes: ((data[2], data[3], data[4], data[5], data[6], data[7], data[8], data[9])))) 37 | 38 | let encryptedDiversifier = UInt16(littleEndian: UInt16(bytes: (data[10], data[11]))) 39 | 40 | self.handle = handle 41 | self.randomNumber = randomNumber 42 | self.encryptedDiversifier = encryptedDiversifier 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Sources/BluetoothHCI/HCILEPeriodicAdvertisingSyncLost.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HCILEPeriodicAdvertisingSyncLost.swift 3 | // Bluetooth 4 | // 5 | // Created by Alsey Coleman Miller on 6/15/18. 6 | // Copyright © 2018 PureSwift. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /// LE Periodic Advertising Sync Lost Event 12 | /// 13 | /// The event indicates that the Controller has not received a Periodic Advertising packet identified 14 | /// by Sync_Handle within the timeout period. 15 | @frozen 16 | public struct HCILEPeriodicAdvertisingSyncLost: HCIEventParameter { 17 | 18 | public static let event = LowEnergyEvent.periodicAdvertisingSyncLost // 0x10 19 | 20 | public static let length = 2 21 | 22 | public let syncHandle: UInt16 // Sync_Handle 23 | 24 | public init?(data: Data) { 25 | 26 | guard data.count == Self.length 27 | else { return nil } 28 | 29 | let syncHandle = UInt16(littleEndian: UInt16(bytes: (data[0], data[1]))) 30 | 31 | self.syncHandle = syncHandle 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Sources/BluetoothHCI/HCILERandom.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HCILERandomReturn.swift 3 | // Bluetooth 4 | // 5 | // Created by Alsey Coleman Miller on 6/15/18. 6 | // Copyright © 2018 PureSwift. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | // MARK: - Method 12 | 13 | public extension BluetoothHostControllerInterface { 14 | 15 | /// LE Rand Command 16 | /// 17 | /// The command is used to request the Controller to generate 8 octets of random data to be sent to the Host. 18 | func lowEnergyRandom(timeout: HCICommandTimeout = .default) async throws -> UInt64 { 19 | 20 | let returnParameters = try await deviceRequest(HCILERandom.self, timeout: timeout) 21 | 22 | return returnParameters.randomNumber 23 | } 24 | } 25 | 26 | // MARK: - Return parameter 27 | 28 | /// LE Rand Command 29 | /// 30 | /// The command is used to request the Controller to generate 8 octets of random data to be sent to the Host. 31 | @frozen 32 | public struct HCILERandom: HCICommandReturnParameter { // HCI_LE_Rand 33 | 34 | public static let command = HCILowEnergyCommand.random //0x0018 35 | 36 | public static let length: Int = 8 37 | 38 | /// Random Number 39 | public let randomNumber: UInt64 //Random_Number 40 | 41 | public init?(data: Data) { 42 | 43 | guard data.count == Self.length 44 | else { return nil } 45 | 46 | self.randomNumber = UInt64(littleEndian: UInt64(bytes: ((data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7])))) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Sources/BluetoothHCI/HCILEReadBufferSize.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HCILEReadBufferSizeReturn.swift 3 | // Bluetooth 4 | // 5 | // Created by Alsey Coleman Miller on 6/15/18. 6 | // Copyright © 2018 PureSwift. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | // MARK: - Method 12 | 13 | public extension BluetoothHostControllerInterface { 14 | 15 | /// LE Read Buffer Size Command 16 | /// 17 | /// The command is used to read the maximum size of the data portion of HCI LE ACL Data Packets sent from the Host to the Controller. 18 | func readBufferSize(timeout: HCICommandTimeout = .default) async throws -> HCILEReadBufferSize { 19 | 20 | let bufferSizeReturnParameter = try await deviceRequest(HCILEReadBufferSize.self, timeout: timeout) 21 | 22 | return bufferSizeReturnParameter 23 | } 24 | } 25 | 26 | // MARK: - Return parameters 27 | 28 | /// LE Read Buffer Size Command 29 | /// 30 | /// The command is used to read the maximum size of the data portion of HCI LE ACL Data Packets sent from the Host to the Controller. 31 | @frozen 32 | public struct HCILEReadBufferSize: HCICommandReturnParameter { 33 | 34 | public static let command = HCILowEnergyCommand.readBufferSize //0x0002 35 | public static let length = 3 36 | 37 | public let dataPacketLength: UInt16 38 | public let dataPacket: UInt8 39 | 40 | public init?(data: Data) { 41 | 42 | guard data.count == Self.length 43 | else { return nil } 44 | 45 | let dataPacketLength = UInt16(littleEndian: UInt16(bytes: (data[0], data[1]))) 46 | 47 | self.dataPacketLength = dataPacketLength 48 | self.dataPacket = data[2] 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Sources/BluetoothHCI/HCILEReadPeriodicAdvertisingListSize.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HCILEReadPeriodicAdvertisingListSize.swift 3 | // Bluetooth 4 | // 5 | // Created by Alsey Coleman Miller on 6/15/18. 6 | // Copyright © 2018 PureSwift. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | // MARK: - Method 12 | 13 | public extension BluetoothHostControllerInterface { 14 | 15 | /// LE Read Periodic Advertiser List Size Command 16 | /// 17 | /// The command is used to read the total number of Periodic Advertiser list entries that can be stored in the Controller. 18 | func lowEnergyReadPeriodicAdvertisingListSize(timeout: HCICommandTimeout = .default) async throws -> UInt8 { 19 | 20 | let value = try await deviceRequest( 21 | HCILEReadPeriodicAdvertisingListSize.self, 22 | timeout: timeout) 23 | 24 | return value.periodicAdvertiserListSize 25 | } 26 | } 27 | 28 | // MARK: - Return parameter 29 | 30 | /// LE Read Periodic Advertiser List Size Command 31 | /// 32 | /// The command is used to read the total number of Periodic Advertiser list entries that can be stored in the Controller. 33 | @frozen 34 | public struct HCILEReadPeriodicAdvertisingListSize: HCICommandReturnParameter { 35 | 36 | public static let command = HCILowEnergyCommand.readPeriodicAdvertiserListSize //0x004A 37 | 38 | public static let length: Int = 1 39 | 40 | /// Total number of Periodic Advertiser list entries that can be stored in the Controller 41 | public let periodicAdvertiserListSize: UInt8 42 | 43 | public init?(data: Data) { 44 | guard data.count == Self.length 45 | else { return nil } 46 | 47 | periodicAdvertiserListSize = data[0] 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Sources/BluetoothHCI/HCILEReadSupportedStates.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HCILEReadSupportedStates.swift 3 | // Bluetooth 4 | // 5 | // Created by Alsey Coleman Miller on 6/15/18. 6 | // Copyright © 2018 PureSwift. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | // MARK: - Method 12 | 13 | public extension BluetoothHostControllerInterface { 14 | 15 | /// LE Read Supported States 16 | /// 17 | /// The LE_Read_Supported_States command reads the states and state combinations that the link layer supports. 18 | func readSupportedStates(timeout: HCICommandTimeout = .default) async throws -> LowEnergyStateSet { 19 | 20 | let returValue = try await deviceRequest(HCILEReadSupportedStates.self, timeout: timeout) 21 | 22 | return returValue.state 23 | } 24 | } 25 | 26 | // MARK: - Return parameter 27 | 28 | /// LE Read Supported States 29 | /// 30 | /// The LE_Read_Supported_States command reads the states and state combinations that the link layer supports. 31 | @frozen 32 | public struct HCILEReadSupportedStates: HCICommandReturnParameter { 33 | 34 | public static let command = HCILowEnergyCommand.readSupportedStates //0x001C 35 | 36 | public static let length: Int = 8 37 | 38 | public let state: LowEnergyStateSet 39 | 40 | public init?(data: Data) { 41 | 42 | guard data.count == Self.length 43 | else { return nil } 44 | 45 | let stateRawValue = UInt64(littleEndian: UInt64(bytes: (data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7]))) 46 | 47 | guard let state = LowEnergyStateSet(rawValue: stateRawValue) 48 | else { return nil } 49 | 50 | self.state = state 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Sources/BluetoothHCI/HCILEReadWhiteListSize.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HCILEReadWhiteListSizeReturn.swift 3 | // Bluetooth 4 | // 5 | // Created by Alsey Coleman Miller on 6/15/18. 6 | // Copyright © 2018 PureSwift. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | // MARK: - Method 12 | 13 | public extension BluetoothHostControllerInterface { 14 | 15 | /// LE Read White List Size Command 16 | /// 17 | /// Used to read the total number of white list entries that can be stored in the Controller. 18 | func lowEnergyReadWhiteListSize(timeout: HCICommandTimeout = .default) async throws -> Int { 19 | 20 | let sizeReturnParameter = try await deviceRequest(HCILEReadWhiteListSize.self, timeout: timeout) 21 | 22 | return Int(sizeReturnParameter.size) 23 | } 24 | } 25 | 26 | // MARK: - Return parameter 27 | 28 | /// LE Read White List Size 29 | /// 30 | /// The command is used to read the total number of white list entries that can be stored in the Controller. 31 | @frozen 32 | public struct HCILEReadWhiteListSize: HCICommandReturnParameter { // HCI_LE_Read_White_List_Size 33 | 34 | public static let command = HCILowEnergyCommand.readWhiteListSize //0x000F 35 | public static let length = 1 36 | 37 | /// The white list size. 38 | public let size: UInt8 // White_List_Size 39 | 40 | public init?(data: Data) { 41 | 42 | guard data.count == Self.length 43 | else { return nil } 44 | 45 | self.size = data[0] 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Sources/BluetoothHCI/HCILEReceiverTest.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HCILEReceiverTest.swift 3 | // Bluetooth 4 | // 5 | // Created by Alsey Coleman Miller on 6/14/18. 6 | // Copyright © 2018 PureSwift. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | // MARK: - Method 12 | 13 | public extension BluetoothHostControllerInterface { 14 | 15 | /// LE Receiver Test Command 16 | /// 17 | /// This command is used to start a test where the DUT receives test reference packets at a fixed interval. 18 | /// The tester generates the test reference packets. 19 | func lowEnergyReceiverTest(rxChannel: LowEnergyRxChannel, timeout: HCICommandTimeout = .default) async throws { 20 | 21 | let parameters = HCILEReceiverTest(rxChannel: rxChannel) 22 | 23 | try await deviceRequest(parameters, timeout: timeout) 24 | } 25 | } 26 | 27 | // MARK: - Command 28 | 29 | /// LE Receiver Test Command 30 | /// 31 | /// This command is used to start a test where the DUT receives test reference 32 | /// packets at a fixed interval. The tester generates the test reference packets. 33 | @frozen 34 | public struct HCILEReceiverTest: HCICommandParameter { 35 | 36 | public static let command = HCILowEnergyCommand.receiverTest //0x001D 37 | 38 | /// N = (F – 2402) / 2 39 | /// Range: 0x00 – 0x27. Frequency Range : 2402 MHz to 2480 MHz 40 | public let rxChannel: LowEnergyRxChannel //RX_Channel 41 | 42 | public init(rxChannel: LowEnergyRxChannel) { 43 | 44 | self.rxChannel = rxChannel 45 | } 46 | 47 | public var data: Data { 48 | 49 | return Data([rxChannel.rawValue]) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /Sources/BluetoothHCI/HCILEScanRequestReceived.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HCILEScanRequestReceived.swift 3 | // Bluetooth 4 | // 5 | // Created by Alsey Coleman Miller on 6/15/18. 6 | // Copyright © 2018 PureSwift. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /// LE Scan Request Received Event 12 | /// 13 | /// The vent indicates that a SCAN_REQ PDU or an AUX_SCAN_REQ PDU has been received by the advertiser. 14 | /// The request contains a device address from a scanner that is allowed by the advertising filter policy. 15 | /// The advertising set is identified by Advertising_Handle. 16 | @frozen 17 | public struct HCILEScanRequestReceived: HCIEventParameter { 18 | 19 | public static let event = LowEnergyEvent.scanRequestReceived // 0x13 20 | 21 | public static let length: Int = 8 22 | 23 | public let advertisingHandle: UInt8 24 | 25 | public let scannerAddressType: LowEnergyAddressType 26 | 27 | public let scannerAddress: BluetoothAddress 28 | 29 | public init?(data: Data) { 30 | 31 | guard data.count == Self.length 32 | else { return nil } 33 | 34 | let advertisingHandle = data[0] 35 | 36 | guard let scannerAddressType = LowEnergyAddressType(rawValue: data[1]) 37 | else { return nil } 38 | 39 | let scannerAddress = BluetoothAddress(littleEndian: BluetoothAddress(bytes: (data[2], data[3], data[4], data[5], data[6], data[7]))) 40 | 41 | self.advertisingHandle = advertisingHandle 42 | self.scannerAddressType = scannerAddressType 43 | self.scannerAddress = scannerAddress 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Sources/BluetoothHCI/HCILESetHostChannelClassification.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HCISetHostChannelClassification.swift 3 | // Bluetooth 4 | // 5 | // Created by Alsey Coleman Miller on 6/14/18. 6 | // Copyright © 2018 PureSwift. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | // MARK: - Command 12 | 13 | /// LE Set Host Channel Classification Command 14 | /// 15 | /// command allows the Host to specify a channel classification for data channels based 16 | /// on its “local information”. This classification persists until overwritten with a subsequent LE_Set_Host_Channel_Classification command or 17 | /// until the Controller is reset using the Reset command 18 | @frozen 19 | public struct HCILESetHostChannelClassification: HCICommandParameter { // HCI_LE_Set_Host_Channel_Classification 20 | 21 | public static let command = HCILowEnergyCommand.setHostChannelClassification //0x0014 22 | 23 | /// This parameter contains 37 1-bit fields. 24 | /// The nth such field (in the range 0 to 36) contains the value for the link layer channel index n. 25 | /// Channel n is bad = 0. Channel n is unknown = 1. 26 | /// The most significant bits are reserved and shall be set to 0for future use. 27 | /// At least one channel shall be marked as unknown. 28 | public let channelMap: LowEnergyChannelMap //Channel_Map 29 | 30 | public init(channelMap: LowEnergyChannelMap) { 31 | self.channelMap = channelMap 32 | } 33 | 34 | public var data: Data { 35 | return Data([ 36 | channelMap.0, 37 | channelMap.1, 38 | channelMap.2, 39 | channelMap.3, 40 | channelMap.4 41 | ]) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Sources/BluetoothHCI/HCILETestEnd.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HCILETestEnd.swift 3 | // Bluetooth 4 | // 5 | // Created by Alsey Coleman Miller on 6/15/18. 6 | // Copyright © 2018 PureSwift. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | // MARK: - Method 12 | 13 | public extension BluetoothHostControllerInterface { 14 | 15 | /// LE Test End Command 16 | /// 17 | /// This command is used to stop any test which is in progress. 18 | func lowEnergyTestEnd(timeout: HCICommandTimeout = .default) async throws -> UInt16 { 19 | 20 | let value = try await deviceRequest( 21 | HCILETestEnd.self, 22 | timeout: timeout) 23 | 24 | return value.numberOfPackets 25 | } 26 | } 27 | 28 | // MARK: - Return parameter 29 | 30 | /// LE Test End Command 31 | /// 32 | /// This command is used to stop any test which is in progress. The Number_Of_Packets 33 | /// for a transmitter test shall be reported as 0x0000. The Number_Of_Packets is an unsigned number 34 | /// and contains the number of received packets. 35 | @frozen 36 | public struct HCILETestEnd: HCICommandReturnParameter { 37 | 38 | public static let command = HCILowEnergyCommand.testEnd //0x001F 39 | 40 | public static let length: Int = 2 41 | 42 | public let numberOfPackets: UInt16 43 | 44 | public init?(data: Data) { 45 | 46 | guard data.count == Self.length 47 | else { return nil } 48 | 49 | numberOfPackets = UInt16(littleEndian: UInt16(bytes: (data[0], data[1]))) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /Sources/BluetoothHCI/HCILowEnergyMetaEvent.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HCILowEnergyMetaEvent.swift 3 | // Bluetooth 4 | // 5 | // Created by Alsey Coleman Miller on 6/14/18. 6 | // Copyright © 2018 PureSwift. All rights reserved. 7 | // 8 | 9 | import Bluetooth 10 | 11 | /// HCI Low Energy Meta Event 12 | @frozen 13 | public struct HCILowEnergyMetaEvent: HCIEventParameter { 14 | 15 | public static var event: HCIGeneralEvent { .lowEnergyMeta } 16 | 17 | public static var length: Int { 1 } // 1 ... HCI.maximumEventSize 18 | 19 | public let subevent: LowEnergyEvent 20 | public let eventData: EventData 21 | 22 | public init?(data: Data) { 23 | 24 | guard data.count >= HCILowEnergyMetaEvent.length, 25 | let subevent = LowEnergyEvent(rawValue: data[0]) 26 | else { return nil } 27 | 28 | self.subevent = subevent 29 | 30 | if data.count > 1 { 31 | 32 | self.eventData = EventData(data.suffix(from: 1)) 33 | 34 | } else { 35 | 36 | self.eventData = EventData() 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Sources/BluetoothHCI/HCIMaxSlotsChange.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HCIMaxSlotsChange.swift 3 | // Bluetooth 4 | // 5 | // Created by Carlos Duclos on 8/17/18. 6 | // Copyright © 2018 PureSwift. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /// Max Slots Change Event 12 | /// 13 | /// This event is used to notify the Host about the LMP_Max_Slots parameter when the value of this parameter changes. It shall be sent each time the maximum allowed length, in number of slots, for baseband packets transmitted by the local device, changes. The Connection_Handle will be a Connection_Handle for an ACL connection. 14 | @frozen 15 | public struct HCIMaxSlotsChange: HCIEventParameter { 16 | 17 | public static let event = HCIGeneralEvent.maxSlotsChange 18 | 19 | public static let length: Int = 3 20 | 21 | public let connectionHandle: UInt16 22 | 23 | public let maxSlotsLMP: UInt8 24 | 25 | public init?(data: Data) { 26 | 27 | guard data.count == Self.length 28 | else { return nil } 29 | 30 | let connectionHandle = UInt16(littleEndian: UInt16(bytes: (data[0], data[1]))) 31 | 32 | self.connectionHandle = connectionHandle 33 | self.maxSlotsLMP = data[2] 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Sources/BluetoothHCI/HCIPINCodeRequest.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HCIPINCodeRequest.swift 3 | // Bluetooth 4 | // 5 | // Created by Carlos Duclos on 8/10/18. 6 | // Copyright © 2018 PureSwift. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /// The PIN Code Request event is used to indicate that a PIN code is required to create a new link key. The Host shall respond using either the PIN_Code_Request_Reply or the PIN_Code_Request_Negative_Reply command, depending on whether the Host can provide the Controller with a PIN code or not. 12 | /// 13 | /// - Note: If the PIN Code Request event is masked away, then the BR/EDR Controller will assume that the Host has no PIN Code. 14 | /// When the BR/EDR Controller generates a PIN Code Request event in order for the local Link Manager to respond to the request from the remote Link Manager (as a result of a Create_Connection or Authentication_Requested com- mand from the remote Host), the local Host must respond with either a PIN_Code_Request_Reply or PIN_Code_Request_Negative_Reply com- mand before the remote Link Manager detects LMP response timeout 15 | @frozen 16 | public struct HCIPINCodeRequest: HCIEventParameter { 17 | 18 | public static let event = HCIGeneralEvent.pinCodeRequest 19 | 20 | public static let length: Int = 6 21 | 22 | public let address: BluetoothAddress 23 | 24 | public init?(data: Data) { 25 | 26 | guard data.count == Self.length 27 | else { return nil } 28 | 29 | let address = BluetoothAddress(littleEndian: BluetoothAddress(bytes: (data[0], data[1], data[2], data[3], data[4], data[5]))) 30 | 31 | self.address = address 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Sources/BluetoothHCI/HCIPageScanRepetitionMode.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HCIPageScanRepetitionMode.swift 3 | // Bluetooth 4 | // 5 | // Created by Carlos Duclos on 7/30/18. 6 | // Copyright © 2018 PureSwift. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /// The Page_Scan_Repetition_Mode parameter specifies the page scan repetition mode supported by the remote device with the BD_ADDR. This is the information that was acquired during the inquiry process. 12 | @frozen 13 | public struct PageScanRepetitionMode: RawRepresentable { 14 | 15 | public static let r1 = PageScanRepetitionMode(0x00) 16 | 17 | public static let r2 = PageScanRepetitionMode(0x01) 18 | 19 | public static let r3 = PageScanRepetitionMode(0x02) 20 | 21 | public var rawValue: UInt8 22 | 23 | public init(rawValue: UInt8) { 24 | 25 | self.rawValue = rawValue 26 | } 27 | 28 | private init(_ unsafe: UInt8) { 29 | 30 | self.rawValue = unsafe 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Sources/BluetoothHCI/HCIReadClassOfDevice.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HCIReadClassOfDevice.swift 3 | // Bluetooth 4 | // 5 | // Created by Carlos Duclos on 8/15/18. 6 | // Copyright © 2018 PureSwift. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | // MARK: - Method 12 | 13 | public extension BluetoothHostControllerInterface { 14 | 15 | /// Read Class of Device Command 16 | /// 17 | /// This command writes the value for the Class_of_Device parameter. 18 | func readClassOfDevice(timeout: HCICommandTimeout = .default) async throws -> ClassOfDevice { 19 | 20 | return try await deviceRequest(HCIReadClassOfDeviceReturn.self, timeout: timeout).classOfDevice 21 | } 22 | } 23 | 24 | // MARK: - Return Parameter 25 | 26 | /// Read Class of Device Command 27 | /// 28 | /// This command writes the value for the Class_of_Device parameter. 29 | @frozen 30 | public struct HCIReadClassOfDeviceReturn: HCICommandReturnParameter { 31 | 32 | public static let command = HostControllerBasebandCommand.readClassOfDevice 33 | 34 | public static let length: Int = MemoryLayout.size 35 | 36 | public var classOfDevice: ClassOfDevice 37 | 38 | public init?(data: Data) { 39 | 40 | guard data.count == HCIReadClassOfDeviceReturn.length 41 | else { return nil } 42 | 43 | guard let classOfDevice = ClassOfDevice(data: data) 44 | else { return nil } 45 | 46 | self.classOfDevice = classOfDevice 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Sources/BluetoothHCI/HCIReadConnectionAcceptTimeout.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HCIReadConnectionAcceptTimeout.swift 3 | // Bluetooth 4 | // 5 | // Created by Alsey Coleman Miller on 6/14/18. 6 | // Copyright © 2018 PureSwift. All rights reserved. 7 | // 8 | 9 | // MARK: - Command 10 | 11 | import Foundation 12 | 13 | /// Read Connection Accept Timeout Command 14 | /// 15 | /// This command reads the value for the Connection Accept Timeout configuration parameter. 16 | @frozen 17 | public struct HCIReadConnectionAcceptTimeout: HCICommandReturnParameter { 18 | 19 | public static let command = HostControllerBasebandCommand.readConnectionAcceptTimeout 20 | public static let length = ConnectionAcceptTimeout.length 21 | 22 | public let timeout: ConnectionAcceptTimeout 23 | 24 | public init?(data: Data) { 25 | 26 | guard data.count == Self.length 27 | else { return nil } 28 | 29 | let rawValue = UInt16(littleEndian: UInt16(bytes: (data[0], data[1]))) 30 | 31 | guard let timeout = ConnectionAcceptTimeout(rawValue: rawValue) 32 | else { return nil } 33 | 34 | self.timeout = timeout 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Sources/BluetoothHCI/HCIReadDeviceAddress.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HCIReadDeviceAddress.swift 3 | // Bluetooth 4 | // 5 | // Created by Alsey Coleman Miller on 6/14/18. 6 | // Copyright © 2018 PureSwift. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | // MARK: - Method 12 | 13 | public extension BluetoothHostControllerInterface { 14 | 15 | /// Read Device Address 16 | func readDeviceAddress(timeout: HCICommandTimeout = .default) async throws -> BluetoothAddress { 17 | 18 | return try await deviceRequest(HCIReadDeviceAddress.self, timeout: timeout).address 19 | } 20 | } 21 | 22 | // MARK: - Return Parameter 23 | 24 | /// Read Device Address 25 | /// 26 | /// On a BR/EDR Controller, this command reads the Bluetooth Controller address (BD_ADDR). 27 | /// 28 | /// On an LE Controller, this command shall read the Public Device Address. 29 | /// If this Controller does not have a Public Device Address, the value 0x000000000000 shall be returned. 30 | /// 31 | /// - Note: On a BR/EDR/LE Controller, the public address shall be the same as the `BD_ADDR`. 32 | @frozen 33 | public struct HCIReadDeviceAddress: HCICommandReturnParameter { 34 | 35 | public static let command = InformationalCommand.readDeviceAddress 36 | 37 | public static let length = 6 38 | 39 | /// The Bluetooth address of the device. 40 | public let address: BluetoothAddress 41 | 42 | public init?(data: Data) { 43 | 44 | guard data.count == Self.length 45 | else { return nil } 46 | 47 | self.address = BluetoothAddress(littleEndian: BluetoothAddress(bytes: (data[0], data[1], data[2], data[3], data[4], data[5]))) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Sources/BluetoothHCI/HCIReadLocalName.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HCIReadLocalName.swift 3 | // Bluetooth 4 | // 5 | // Created by Alsey Coleman Miller on 6/14/18. 6 | // Copyright © 2018 PureSwift. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | // MARK: - Method 12 | 13 | public extension BluetoothHostControllerInterface { 14 | 15 | /// Write Local Name Command 16 | /// 17 | /// Provides the ability to modify the user- friendly name for the BR/EDR Controller. 18 | func readLocalName(timeout: HCICommandTimeout = .default) async throws -> String { 19 | 20 | let value = try await deviceRequest( 21 | HCIReadLocalName.self, 22 | timeout: timeout) 23 | 24 | return value.localName 25 | } 26 | } 27 | 28 | // MARK: - Command 29 | 30 | /// Read Local Name Command 31 | /// 32 | /// The Read Local Name command provides the ability to read the stored user-friendly name 33 | /// for the BR/EDR Controller. 34 | @frozen 35 | public struct HCIReadLocalName: HCICommandReturnParameter { 36 | 37 | public static let command = HostControllerBasebandCommand.readLocalName 38 | 39 | public static let length = HCI.maximumNameLength //248 40 | 41 | public let localName: String 42 | 43 | public init?(data: Data) { 44 | 45 | var data = unsafeBitCast([UInt8](data), to: [Int8].self) 46 | 47 | guard let localName = String(validatingUTF8: &data) 48 | else { return nil } 49 | 50 | self.localName = localName 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Sources/BluetoothHCI/HCIReadRemoteFeaturesComplete.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HCIReadRemoteFeaturesComplete.swift 3 | // Bluetooth 4 | // 5 | // Created by Carlos Duclos on 8/6/18. 6 | // Copyright © 2018 PureSwift. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /// Read Remote Supported Features Complete Event 12 | /// 13 | /// The Read Remote Supported Features Complete event is used to indicate the completion of the process of the Link Manager obtaining the supported features of the remote BR/EDR Controller specified by the Connection_Handle event parameter. The Connection_Handle will be a Connection_Handle for an ACL connection. The event parameters include a list of LMP features 14 | @frozen 15 | public struct HCIReadRemoteSupportedFeaturesComplete: HCIEventParameter { 16 | 17 | public static let event = HCIGeneralEvent.readRemoteSupportedFeaturesComplete 18 | 19 | public static let length: Int = 11 20 | 21 | public let status: HCIStatus 22 | 23 | public let handle: UInt16 24 | 25 | public let features: BitMaskOptionSet 26 | 27 | public init?(data: Data) { 28 | 29 | guard data.count == Self.length 30 | else { return nil } 31 | 32 | guard let status = HCIStatus(rawValue: data[0]) 33 | else { return nil } 34 | 35 | let handle = UInt16(littleEndian: UInt16(bytes: (data[1], data[2]))) 36 | 37 | let featuresValue = UInt64(littleEndian: UInt64(bytes: (data[3], data[4], data[5], data[6], data[7], data[8], data[9], data[10]))) 38 | 39 | let features = BitMaskOptionSet(rawValue: featuresValue) 40 | 41 | self.status = status 42 | self.handle = handle 43 | self.features = features 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Sources/BluetoothHCI/HCIRemoteNameRequestComplete.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HCIRemoteNameRequestComplete.swift 3 | // Bluetooth 4 | // 5 | // Created by Alsey Coleman Miller on 6/14/18. 6 | // Copyright © 2018 PureSwift. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /// HCI Remote Name Request Complete Event 12 | @frozen 13 | public struct HCIRemoteNameRequestComplete: HCIEventParameter { 14 | 15 | public static let event = HCIGeneralEvent.remoteNameRequestComplete 16 | public static let length = 255 17 | 18 | public var status: HCIStatus 19 | public var address: BluetoothAddress 20 | public var name: String 21 | 22 | public init?(data: Data) { 23 | 24 | guard data.count == Self.length 25 | else { return nil } 26 | 27 | let statusByte = data[0] 28 | 29 | guard let status = HCIStatus(rawValue: statusByte) 30 | else { return nil } 31 | 32 | self.status = status 33 | self.address = BluetoothAddress(littleEndian: BluetoothAddress(bytes: (data[1], data[2], data[3], data[4], data[5], data[6]))) 34 | 35 | guard let name = String(utf8: data.subdata(in: 7..(data: Data) { 26 | 27 | guard data.count == Self.length 28 | else { return nil } 29 | 30 | guard let status = HCIStatus(rawValue: data[0]) 31 | else { return nil } 32 | 33 | let address = BluetoothAddress(littleEndian: BluetoothAddress(bytes: (data[1], data[2], data[3], data[4], data[5], data[6]))) 34 | 35 | self.status = status 36 | self.address = address 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Sources/BluetoothHCI/HCIWriteClassOfDevice.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HCIWriteClassOfDevice.swift 3 | // Bluetooth 4 | // 5 | // Created by Carlos Duclos on 8/15/18. 6 | // Copyright © 2018 PureSwift. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | // MARK: - Method 12 | 13 | public extension BluetoothHostControllerInterface { 14 | 15 | /// Write Class of Device Command 16 | /// 17 | /// This command writes the value for the Class_of_Device parameter. 18 | func writeClassOfDevice( 19 | classOfDevice: ClassOfDevice, 20 | timeout: HCICommandTimeout = .default 21 | ) async throws { 22 | 23 | let command = HCIWriteClassOfDevice(classOfDevice: classOfDevice) 24 | 25 | return try await deviceRequest(command, timeout: timeout) 26 | } 27 | } 28 | 29 | // MARK: - Command 30 | 31 | /// Write Class of Device Command 32 | /// 33 | /// This command writes the value for the Class_of_Device parameter. 34 | @frozen 35 | public struct HCIWriteClassOfDevice: HCICommandParameter { 36 | 37 | public static let command = HostControllerBasebandCommand.writeClassOfDevice 38 | 39 | public var classOfDevice: ClassOfDevice 40 | 41 | public init(classOfDevice: ClassOfDevice) { 42 | 43 | self.classOfDevice = classOfDevice 44 | } 45 | 46 | public var data: Data { 47 | 48 | return Data(classOfDevice) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Sources/BluetoothHCI/HCIWriteConnectionAcceptTimeout.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HCIWriteConnectionAcceptTimeout.swift 3 | // Bluetooth 4 | // 5 | // Created by Alsey Coleman Miller on 6/14/18. 6 | // Copyright © 2018 PureSwift. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /// Write Connection Accept Timeout Command 12 | /// 13 | /// This command writes the value for the Connection Accept Timeout configuration parameter. 14 | @frozen 15 | public struct HCIWriteConnectionAcceptTimeout: HCICommandParameter { 16 | 17 | public static let command = HostControllerBasebandCommand.writeConnectionAcceptTimeout 18 | 19 | public var timeout: ConnectionAcceptTimeout 20 | 21 | public init(timeout: ConnectionAcceptTimeout) { 22 | 23 | self.timeout = timeout 24 | } 25 | 26 | public var data: Data { 27 | 28 | let timeoutBytes = timeout.rawValue.littleEndian.bytes 29 | 30 | return Data([timeoutBytes.0, timeoutBytes.1]) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Sources/BluetoothHCI/HCIWritePageScanType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HCIWritePageScanType.swift 3 | // Bluetooth 4 | // 5 | // Created by Carlos Duclos on 8/16/18. 6 | // Copyright © 2018 PureSwift. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | // MARK: - Method 12 | 13 | public extension BluetoothHostControllerInterface { 14 | 15 | /// Write Page Scan Type Command 16 | /// 17 | /// This command writes the Page Scan Type configuration parameter of the local BR/EDR Controller. 18 | func writePageScanType( 19 | pageScanType: HCIWritePageScanType.PageScanType, 20 | timeout: HCICommandTimeout = .default 21 | ) async throws { 22 | 23 | let command = HCIWritePageScanType(pageScanType: pageScanType) 24 | 25 | return try await deviceRequest(command, timeout: timeout) 26 | } 27 | } 28 | 29 | // MARK: - Command 30 | 31 | /// Write Page Scan Type Command 32 | /// 33 | /// This command writes the Page Scan Type configuration parameter of the local BR/EDR Controller. 34 | @frozen 35 | public struct HCIWritePageScanType: HCICommandParameter { 36 | 37 | public static let command = HostControllerBasebandCommand.writePageScanType 38 | 39 | public var pageScanType: PageScanType 40 | 41 | public init(pageScanType: PageScanType) { 42 | 43 | self.pageScanType = pageScanType 44 | } 45 | 46 | public var data: Data { 47 | 48 | return Data([pageScanType.rawValue]) 49 | } 50 | } 51 | 52 | extension HCIWritePageScanType { 53 | 54 | public enum PageScanType: UInt8 { 55 | 56 | /// Mandatory: Standard Scan (default) 57 | case mandatory = 0x00 58 | 59 | /// Optional: Interlaced Scan 60 | case optional = 0x01 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /Sources/BluetoothHCI/LowEnergyAddressType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LowEnergyAddressType.swift 3 | // Bluetooth 4 | // 5 | // Created by Alsey Coleman Miller on 11/28/17. 6 | // Copyright © 2017 PureSwift. All rights reserved. 7 | // 8 | 9 | /// Bluetooth Low Energy Address type 10 | @frozen 11 | public enum LowEnergyAddressType: UInt8 { 12 | 13 | /// Public Device Address 14 | case `public` = 0x00 15 | 16 | /// Random Device Address 17 | case random = 0x01 18 | 19 | /// Public Identity Address (Corresponds to peer’s Resolvable Private Address). 20 | /// 21 | /// This value shall only be used by the Host if either the Host or the 22 | /// Controller does not support the LE Set Privacy Mode command. 23 | /// 24 | /// - Note: Requires Bluetooth 5.0 25 | case publicIdentity = 0x02 26 | 27 | /// Random (static) Identity Address (Corresponds to peer’s Resolvable Private Address). 28 | /// 29 | /// This value shall only be used by a Host if either the Host or the Controller does 30 | /// not support the LE Set Privacy Mode command. 31 | /// 32 | /// - Note: Requires Bluetooth 5.0 33 | case randomIdentity = 0x03 34 | 35 | /// Default Low Energy Address type (`.public`). 36 | public init() { self = .public } 37 | } 38 | 39 | // MARK: - HCIVersioned 40 | 41 | extension LowEnergyAddressType: HCIVersioned { 42 | 43 | public func isCompatible(with version: HCIVersion) -> Bool { 44 | 45 | switch self { 46 | 47 | case .public, 48 | .random: 49 | return version >= .v4_0 50 | 51 | case .publicIdentity, 52 | .randomIdentity: 53 | return version >= .v5_0 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Sources/BluetoothHCI/LowEnergyAdvertiserAddressType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LowEnergyAdvertiserAddressType.swift 3 | // Bluetooth 4 | // 5 | // Created by Alsey Coleman Miller on 6/14/18. 6 | // Copyright © 2018 PureSwift. All rights reserved. 7 | // 8 | 9 | /// Bluetooth LE Advertiser Address Type 10 | @frozen 11 | public enum LowEnergyAdvertiserAddressType: UInt8 { //Advertiser_Address_Type 12 | 13 | /// Public Device Address or Public Identity Address 14 | case `public` = 0x00 15 | 16 | /// Random Device Address or Random (static) Identity Address 17 | case random = 0x01 18 | } 19 | -------------------------------------------------------------------------------- /Sources/BluetoothHCI/LowEnergyAllPhys.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LowEnergyAllPhys.swift 3 | // Bluetooth 4 | // 5 | // Created by Alsey Coleman Miller on 6/14/18. 6 | // Copyright © 2018 PureSwift. All rights reserved. 7 | // 8 | 9 | /// The ALL_PHYS parameter is a bit field that allows the Host to specify, for each direction, 10 | /// whether it has no preference among the PHYs that the Controller supports in a given direction 11 | /// or whether it has specified particular PHYs that it prefers in the TX_PHYS or RX_PHYS parameter. 12 | @frozen 13 | public enum LowEnergyAllPhys: UInt8, BitMaskOption { 14 | 15 | /// The Host has no preference among the transmitter PHYs supported by the Controller 16 | case hostHasNoPreferenceAmongTheTransmitterPhy = 0b01 17 | 18 | /// The Host has no preference among the receiver PHYs supported by the Controller 19 | case hostHasNoPreferenceAmongTheReceiverPhy = 0b10 20 | 21 | public static let allCases: [LowEnergyAllPhys] = [ 22 | .hostHasNoPreferenceAmongTheTransmitterPhy, 23 | .hostHasNoPreferenceAmongTheReceiverPhy 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /Sources/BluetoothHCI/LowEnergyChannelMap.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LowEnergyChannelMap.swift 3 | // Bluetooth 4 | // 5 | // Created by Marco Estrella on 3/29/18. 6 | // Copyright © 2018 PureSwift. All rights reserved. 7 | // 8 | 9 | public typealias LowEnergyChannelMap = (UInt8, UInt8, UInt8, UInt8, UInt8) 10 | -------------------------------------------------------------------------------- /Sources/BluetoothHCI/LowEnergyClockAccuracy.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LowEnergyClockAccuracy.swift 3 | // Bluetooth 4 | // 5 | // Created by Alsey Coleman Miller on 6/14/18. 6 | // Copyright © 2018 PureSwift. All rights reserved. 7 | // 8 | 9 | /// Bluetooth LE Clock Accuracy 10 | @frozen 11 | public enum LowEnergyClockAccuracy: UInt8 { 12 | 13 | case ppm500 = 0x00 14 | case ppm250 = 0x01 15 | case ppm150 = 0x02 16 | case ppm100 = 0x03 17 | case ppm75 = 0x04 18 | case ppm50 = 0x05 19 | case ppm30 = 0x06 20 | case ppm20 = 0x07 21 | } 22 | 23 | public extension LowEnergyClockAccuracy { 24 | 25 | var ppm: UInt { 26 | 27 | switch self { 28 | case .ppm500: return 500 29 | case .ppm250: return 250 30 | case .ppm150: return 150 31 | case .ppm100: return 100 32 | case .ppm75: return 75 33 | case .ppm50: return 50 34 | case .ppm30: return 30 35 | case .ppm20: return 20 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Sources/BluetoothHCI/LowEnergyConnection.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LowEnergyConnection.swift 3 | // Bluetooth 4 | // 5 | // Created by Alsey Coleman Miller on 11/29/17. 6 | // Copyright © 2017 PureSwift. All rights reserved. 7 | // 8 | 9 | public extension BluetoothHostControllerInterface { 10 | 11 | /// LE Create Connection Cancel Command 12 | /// 13 | /// The LE_Create_Connection_Cancel command is used to cancel the LE_Create_Connection command. 14 | /// This command shall only be issued after the LE_Create_Connection command has been issued, 15 | /// a Command Status event has been received for the LE Create Connection command and before 16 | /// the LE Connection Complete event. 17 | func lowEnergyCreateConnectionCancel(timeout: HCICommandTimeout = .default) async throws { 18 | 19 | // cancel connection 20 | try await deviceRequest(HCILowEnergyCommand.createConnectionCancel, timeout: timeout) 21 | } 22 | 23 | /// LE Read Local P-256 Public Key Command 24 | /// 25 | /// This command is used to return the local P-256 public key from the Controller. 26 | func lowEnergyReadLocalP256PublicKey(timeout: HCICommandTimeout = .default) async throws -> UInt512 { 27 | 28 | let event = try await deviceRequest( 29 | HCILowEnergyCommand.readLocalP256PublicKeyCommand, 30 | HCILEReadLocalP256PublicKeyComplete.self, 31 | timeout: timeout) 32 | 33 | switch event.status { 34 | 35 | case let .error(error): 36 | throw error 37 | 38 | case .success: 39 | 40 | return event.localP256PublicKey 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Sources/BluetoothHCI/LowEnergyConnectionInterval.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LowEnergyConnectionInterval.swift 3 | // Bluetooth 4 | // 5 | // Created by Alsey Coleman Miller on 6/14/18. 6 | // Copyright © 2018 PureSwift. All rights reserved. 7 | // 8 | 9 | /// Connection interval / latency used on this connection. 10 | /// 11 | /// Range: 0x0006 to 0x0C80 12 | /// Time = N * 1.25 msec 13 | /// Time Range: 7.5 msec to 4000 msec. 14 | @frozen 15 | public struct LowEnergyConnectionInterval: RawRepresentable, Equatable, Hashable, Comparable { 16 | 17 | /// 7.5 msec 18 | public static let min = LowEnergyConnectionInterval(0x0006) 19 | 20 | /// 4000 msec 21 | public static let max = LowEnergyConnectionInterval(0x0C80) 22 | 23 | public let rawValue: UInt16 24 | 25 | public init(rawValue: UInt16) { 26 | 27 | self.rawValue = rawValue 28 | } 29 | 30 | /// Time = N * 1.25 msec 31 | public var miliseconds: Double { 32 | 33 | return Double(rawValue) * 1.25 34 | } 35 | 36 | // Private, unsafe 37 | private init(_ rawValue: UInt16) { 38 | self.rawValue = rawValue 39 | } 40 | 41 | // Comparable 42 | public static func < (lhs: LowEnergyConnectionInterval, rhs: LowEnergyConnectionInterval) -> Bool { 43 | 44 | return lhs.rawValue < rhs.rawValue 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Sources/BluetoothHCI/LowEnergyConnectionLatency.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LowEnergyConnectionLatency.swift 3 | // Bluetooth 4 | // 5 | // Created by Alsey Coleman Miller on 6/14/18. 6 | // Copyright © 2018 PureSwift. All rights reserved. 7 | // 8 | 9 | /// Slave latency for the connection in number of connection events. 10 | /// 11 | /// Range: 0x0000 to 0x01F3 12 | @frozen 13 | public struct LowEnergyConnectionLatency: RawRepresentable, Equatable, Hashable, Comparable { 14 | 15 | public static var zero: LowEnergyConnectionLatency { return LowEnergyConnectionLatency() } 16 | 17 | public let rawValue: UInt16 18 | 19 | public init() { 20 | 21 | self.rawValue = 0 22 | } 23 | 24 | public init?(rawValue: UInt16) { 25 | 26 | guard rawValue <= 0x01F3 27 | else { return nil } 28 | 29 | self.rawValue = rawValue 30 | } 31 | 32 | // Comparable 33 | public static func < (lhs: LowEnergyConnectionLatency, rhs: LowEnergyConnectionLatency) -> Bool { 34 | 35 | return lhs.rawValue < rhs.rawValue 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Sources/BluetoothHCI/LowEnergyConnectionLength.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LowEnergyConnectionLength.swift 3 | // Bluetooth 4 | // 5 | // Created by Alsey Coleman Miller on 6/14/18. 6 | // Copyright © 2018 PureSwift. All rights reserved. 7 | // 8 | 9 | /// Information parameter about the minimum length of connection needed for this LE connection. 10 | /// 11 | /// Range: 0x0000 – 0xFFFF 12 | /// Time = N * 0.625 msec. 13 | @frozen 14 | public struct LowEnergyConnectionLength: RawRepresentable, Equatable { 15 | 16 | public typealias RawValue = CountableClosedRange 17 | 18 | /// Maximum interval range. 19 | public static let full = LowEnergyConnectionLength(rawValue: .min ... .max) 20 | 21 | public let rawValue: RawValue 22 | 23 | public init(rawValue: RawValue) { 24 | 25 | self.rawValue = rawValue 26 | } 27 | 28 | /// Time = N * 0.625 msec. 29 | public var miliseconds: ClosedRange { 30 | 31 | let miliseconds = Double(0.625) 32 | 33 | let min = Double(rawValue.lowerBound) * miliseconds 34 | 35 | let max = Double(rawValue.upperBound) * miliseconds 36 | 37 | return min...max 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Sources/BluetoothHCI/LowEnergyFragmentPreference.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LowEnergyFragmentPreference.swift 3 | // Bluetooth 4 | // 5 | // Created by Alsey Coleman Miller on 6/14/18. 6 | // Copyright © 2018 PureSwift. All rights reserved. 7 | // 8 | 9 | /// Bluetooth LE Fragment Preference 10 | @frozen 11 | public enum LowEnergyFragmentPreference: UInt8 { //Fragment_Preference 12 | 13 | /// The Controller may fragment all Host advertising data 14 | case fragmentAllHostAdvertisingData = 0x00 15 | 16 | /// The Controller should not fragment or should minimize fragmentation of Host advertising data 17 | case shouldNotFragmentHostAdvertisingData = 0x01 18 | } 19 | -------------------------------------------------------------------------------- /Sources/BluetoothHCI/LowEnergyMaxTxOctets.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LowEnergyMaxTxOctets.swift 3 | // Bluetooth 4 | // 5 | // Created by Alsey Coleman Miller on 6/14/18. 6 | // Copyright © 2018 PureSwift. All rights reserved. 7 | // 8 | 9 | /// Number of microseconds that the local Controller should use to transmit 10 | /// a single Link Layer packet on this connection. 11 | /// 12 | /// - Note: Range 0x001B-0x00FB 13 | @frozen 14 | public struct LowEnergyMaxTxOctets: RawRepresentable, Equatable, Hashable, Comparable { 15 | 16 | public static let min = LowEnergyMaxTxOctets(0x001B) 17 | 18 | public static let max = LowEnergyMaxTxOctets(0x00FB) 19 | 20 | public let rawValue: UInt16 21 | 22 | public init?(rawValue: UInt16 = 0x0148) { 23 | 24 | guard rawValue >= LowEnergyMaxTxOctets.min.rawValue, 25 | rawValue <= LowEnergyMaxTxOctets.max.rawValue 26 | else { return nil } 27 | 28 | assert((LowEnergyMaxTxOctets.min.rawValue...LowEnergyMaxTxOctets.max.rawValue).contains(rawValue)) 29 | 30 | self.rawValue = rawValue 31 | } 32 | 33 | // Private, unsafe 34 | private init(_ rawValue: UInt16) { 35 | self.rawValue = rawValue 36 | } 37 | 38 | // Comparable 39 | public static func < (lhs: LowEnergyMaxTxOctets, rhs: LowEnergyMaxTxOctets) -> Bool { 40 | 41 | return lhs.rawValue < rhs.rawValue 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Sources/BluetoothHCI/LowEnergyMaxTxTime.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LowEnergyMaxTxTime.swift 3 | // Bluetooth 4 | // 5 | // Created by Alsey Coleman Miller on 6/14/18. 6 | // Copyright © 2018 PureSwift. All rights reserved. 7 | // 8 | 9 | /// Number of payload octets that the local Controller should include in a single Link Layer 10 | /// packet on this connection. 11 | /// 12 | /// - Note: Range 0x0148-0x4290 13 | @frozen 14 | public struct LowEnergyMaxTxTime: RawRepresentable, Equatable, Hashable, Comparable { 15 | 16 | public static let min = LowEnergyMaxTxTime(0x0148) 17 | 18 | public static let max = LowEnergyMaxTxTime(0x4290) 19 | 20 | public let rawValue: UInt16 21 | 22 | public init?(rawValue: UInt16 = 0x001B) { 23 | 24 | guard rawValue >= LowEnergyMaxTxTime.min.rawValue, 25 | rawValue <= LowEnergyMaxTxTime.max.rawValue 26 | else { return nil } 27 | 28 | assert((LowEnergyMaxTxTime.min.rawValue...LowEnergyMaxTxTime.max.rawValue).contains(rawValue)) 29 | 30 | self.rawValue = rawValue 31 | } 32 | 33 | // Private, unsafe 34 | private init(_ rawValue: UInt16) { 35 | self.rawValue = rawValue 36 | } 37 | 38 | // Comparable 39 | public static func < (lhs: LowEnergyMaxTxTime, rhs: LowEnergyMaxTxTime) -> Bool { 40 | 41 | return lhs.rawValue < rhs.rawValue 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Sources/BluetoothHCI/LowEnergyPacketPayload.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LowEnergyPacketPayload.swift 3 | // Bluetooth 4 | // 5 | // Created by Alsey Coleman Miller on 6/14/18. 6 | // Copyright © 2018 PureSwift. All rights reserved. 7 | // 8 | 9 | /// Bluetooth LE Packet Payload format 10 | @frozen 11 | public enum LowEnergyPacketPayload: UInt8 { // Packet_Payload 12 | 13 | case prb29Sequence = 0x00 14 | case repeated11110000 = 0x01 15 | case repeated10101010 = 0x02 16 | case prbs15Sequence = 0x03 17 | case repeated11111111 = 0x04 18 | case repeated00000000 = 0x05 19 | case repeated00001111 = 0x06 20 | case repeated01010101 = 0x07 21 | } 22 | -------------------------------------------------------------------------------- /Sources/BluetoothHCI/LowEnergyPeerIdentifyAddressType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LowEnergyPeerIdentifyAddressType.swift 3 | // Bluetooth 4 | // 5 | // Created by Alsey Coleman Miller on 6/14/18. 6 | // Copyright © 2018 PureSwift. All rights reserved. 7 | // 8 | 9 | /// Bluetooth LE Peer Identify Address Type 10 | @frozen 11 | public enum LowEnergyPeerIdentifyAddressType: UInt8 { //Peer_Identity_Address_Type 12 | 13 | case `public` = 0x00 14 | case `random` = 0x01 15 | } 16 | -------------------------------------------------------------------------------- /Sources/BluetoothHCI/LowEnergyPhyOptions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LowEnergyPhyOptions.swift 3 | // Bluetooth 4 | // 5 | // Created by Alsey Coleman Miller on 6/14/18. 6 | // Copyright © 2018 PureSwift. All rights reserved. 7 | // 8 | 9 | /// The PHY_options parameter is a bit field that allows the Host to specify options for PHYs. 10 | /// The default value for a new connection shall be all zero bits. The Controller may override 11 | /// any preferred coding for transmitting on the LE Coded PHY. 12 | @frozen 13 | public enum LowEnergyPhyOptions: UInt16, BitMaskOption { 14 | 15 | /// The Host has no preferred coding when transmitting on the LE Coded PHY 16 | case host = 0b01 17 | 18 | /// The Host prefers that S=2 coding be used when transmitting on the LE Coded PHY 19 | case s2 = 0b10 20 | 21 | /// The Host prefers that S=8 coding be used when transmitting on the LE Coded PHY 22 | case s3 = 0b100 23 | 24 | public static let allCases: [LowEnergyPhyOptions] = [ 25 | .host, 26 | .s2, 27 | .s3 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /Sources/BluetoothHCI/LowEnergyResolvingList.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LowEnergyResolvingList.swift 3 | // Bluetooth 4 | // 5 | // Created by Marco Estrella on 4/5/18. 6 | // Copyright © 2018 PureSwift. All rights reserved. 7 | // 8 | 9 | public extension BluetoothHostControllerInterface { 10 | 11 | /// LE Clear Resolving List Command 12 | /// 13 | /// The command is used to remove all devices from the list of address translations 14 | /// used to resolve Resolvable Private Addresses in the Controller. 15 | func lowEnergyClearResolvingList(timeout: HCICommandTimeout = .default) async throws { 16 | try await deviceRequest(HCILowEnergyCommand.clearResolvedList, timeout: timeout) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Sources/BluetoothHCI/LowEnergyRfRxPathCompensationValue.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LowEnergyRfRxPathCompensationValue.swift 3 | // Bluetooth 4 | // 5 | // Created by Alsey Coleman Miller on 6/14/18. 6 | // Copyright © 2018 PureSwift. All rights reserved. 7 | // 8 | 9 | /// RF_Rx_Path_Compensation_Value 10 | /// 11 | /// Size: 2 Octets (signed integer) 12 | /// Range: -128.0 dB (0xFB00) ≤ N ≤ 128.0 dB (0x0500) 13 | /// Units: 0.1 dB 14 | @frozen 15 | public struct LowEnergyRfRxPathCompensationValue: RawRepresentable, Equatable, Hashable, Comparable { 16 | 17 | public static let min = LowEnergyRfRxPathCompensationValue(-128) 18 | 19 | public static let max = LowEnergyRfRxPathCompensationValue(128) 20 | 21 | public let rawValue: Int16 22 | 23 | public init?(rawValue: Int16) { 24 | 25 | guard rawValue >= LowEnergyRfRxPathCompensationValue.min.rawValue, 26 | rawValue <= LowEnergyRfRxPathCompensationValue.max.rawValue 27 | else { return nil } 28 | 29 | assert((LowEnergyRfRxPathCompensationValue.min.rawValue...LowEnergyRfRxPathCompensationValue.max.rawValue).contains(rawValue)) 30 | 31 | self.rawValue = rawValue 32 | } 33 | 34 | // Private, unsafe 35 | private init(_ rawValue: Int16) { 36 | self.rawValue = rawValue 37 | } 38 | 39 | // Comparable 40 | public static func < (lhs: LowEnergyRfRxPathCompensationValue, rhs: LowEnergyRfRxPathCompensationValue) -> Bool { 41 | 42 | return lhs.rawValue < rhs.rawValue 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Sources/BluetoothHCI/LowEnergyRfTxPathCompensationValue.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LowEnergyRfTxPathCompensationValue.swift 3 | // Bluetooth 4 | // 5 | // Created by Alsey Coleman Miller on 6/14/18. 6 | // Copyright © 2018 PureSwift. All rights reserved. 7 | // 8 | 9 | /// RF_Tx_Path_Compensation_Value 10 | /// 11 | /// Size: 2 Octets (signed integer) 12 | /// Range: -128.0 dB (0xFB00) ≤ N ≤ 128.0 dB (0x0500) 13 | /// Units: 0.1 dB 14 | @frozen 15 | public struct LowEnergyRfTxPathCompensationValue: RawRepresentable, Equatable, Hashable, Comparable { 16 | 17 | public static let min = LowEnergyRfTxPathCompensationValue(-128) 18 | 19 | public static let max = LowEnergyRfTxPathCompensationValue(128) 20 | 21 | public let rawValue: Int16 22 | 23 | public init?(rawValue: Int16) { 24 | 25 | guard rawValue >= LowEnergyRfTxPathCompensationValue.min.rawValue, 26 | rawValue <= LowEnergyRfTxPathCompensationValue.max.rawValue 27 | else { return nil } 28 | 29 | assert((LowEnergyRfTxPathCompensationValue.min.rawValue...LowEnergyRfTxPathCompensationValue.max.rawValue).contains(rawValue)) 30 | 31 | self.rawValue = rawValue 32 | } 33 | 34 | // Private, unsafe 35 | private init(_ rawValue: Int16) { 36 | self.rawValue = rawValue 37 | } 38 | 39 | // Comparable 40 | public static func < (lhs: LowEnergyRfTxPathCompensationValue, rhs: LowEnergyRfTxPathCompensationValue) -> Bool { 41 | 42 | return lhs.rawValue < rhs.rawValue 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Sources/BluetoothHCI/LowEnergyRole.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LowEnergyRole.swift 3 | // Bluetooth 4 | // 5 | // Created by Alsey Coleman Miller on 6/14/18. 6 | // Copyright © 2018 PureSwift. All rights reserved. 7 | // 8 | 9 | /// Bluetooth LE Connection role 10 | @frozen 11 | public enum LowEnergyRole: UInt8 { 12 | 13 | /// Connection is master. 14 | case master = 0x00 15 | 16 | /// Connection is slave 17 | case slave = 0x01 18 | } 19 | -------------------------------------------------------------------------------- /Sources/BluetoothHCI/LowEnergyRxChannel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LowEnergyRxChannel.swift 3 | // Bluetooth 4 | // 5 | // Created by Alsey Coleman Miller on 6/14/18. 6 | // Copyright © 2018 PureSwift. All rights reserved. 7 | // 8 | 9 | /// Bluetooth LE Rx Channel 10 | @frozen 11 | public struct LowEnergyRxChannel: RawRepresentable, Equatable, Hashable, Comparable { 12 | 13 | /// 100 msec 14 | public static let min = LowEnergyRxChannel(0x00) 15 | 16 | /// 32 seconds 17 | public static let max = LowEnergyRxChannel(0x27) 18 | 19 | public let rawValue: UInt8 20 | 21 | public init?(rawValue: UInt8) { 22 | guard rawValue >= LowEnergyRxChannel.min.rawValue, 23 | rawValue <= LowEnergyRxChannel.max.rawValue 24 | else { return nil } 25 | 26 | assert((LowEnergyRxChannel.min.rawValue...LowEnergyRxChannel.max.rawValue).contains(rawValue)) 27 | 28 | self.rawValue = rawValue 29 | } 30 | 31 | // Private, unsafe 32 | private init(_ rawValue: UInt8) { 33 | self.rawValue = rawValue 34 | } 35 | 36 | // Comparable 37 | public static func < (lhs: LowEnergyRxChannel, rhs: LowEnergyRxChannel) -> Bool { 38 | 39 | return lhs.rawValue < rhs.rawValue 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Sources/BluetoothHCI/LowEnergyRxPhy.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LowEnergyRxPhy.swift 3 | // Bluetooth 4 | // 5 | // Created by Alsey Coleman Miller on 6/14/18. 6 | // Copyright © 2018 PureSwift. All rights reserved. 7 | // 8 | 9 | /// Bluetooth LE Rx Phy 10 | @frozen 11 | public enum LowEnergyRxPhy: UInt8 { //RX_PHY 12 | 13 | /// The receiver PHY for the connection is LE 1M 14 | case le1m = 0x01 15 | 16 | /// The receiver PHY for the connection is LE 2M 17 | case le2m = 0x02 18 | 19 | /// The receiver PHY for the connection is LE Coded 20 | case leCoded = 0x03 21 | } 22 | -------------------------------------------------------------------------------- /Sources/BluetoothHCI/LowEnergyRxPhys.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LowEnergyRxPhys.swift 3 | // Bluetooth 4 | // 5 | // Created by Alsey Coleman Miller on 6/14/18. 6 | // Copyright © 2018 PureSwift. All rights reserved. 7 | // 8 | 9 | /// The RX_PHYS parameter is a bit field that indicates the receiver PHYs that the Host prefers 10 | /// the Controller to use. If the ALL_PHYS parameter specifies that the Host has no preference, 11 | /// the RX_PHYS parameter is ignored; otherwise at least one bit shall be set to 1. 12 | @frozen 13 | public enum LowEnergyRxPhys: UInt8, BitMaskOption { 14 | 15 | /// The Host prefers to use the LE 1M receiver PHY (possibly among others) 16 | case hostUseLe1MReceiverPhy = 0b001 17 | 18 | /// The Host prefers to use the LE 2M receiver PHY (possibly among others) 19 | case hostUseLe2MReceiverPhy = 0b010 20 | 21 | /// The Host prefers to use the LE Coded receiver PHY (possibly among others) 22 | case hostUseLeCodedReceiverPhy = 0b100 23 | 24 | public static let allCases: [LowEnergyRxPhys] = [ 25 | .hostUseLe1MReceiverPhy, 26 | .hostUseLe2MReceiverPhy, 27 | .hostUseLeCodedReceiverPhy 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /Sources/BluetoothHCI/LowEnergyScanInterval.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LowEnergyScanInterval.swift 3 | // Bluetooth 4 | // 5 | // Created by Alsey Coleman Miller on 6/14/18. 6 | // Copyright © 2018 PureSwift. All rights reserved. 7 | // 8 | 9 | /// Time interval from when the Controller started its last scan until it begins 10 | /// the subsequent scan on the primary advertising channel. 11 | @frozen 12 | public struct LowEnergyScanInterval: RawRepresentable, Equatable, Comparable, Hashable { 13 | 14 | /// 2.5 msec 15 | public static let min = LowEnergyScanInterval(0x0004) 16 | 17 | /// 40.959375 seconds 18 | public static let max = LowEnergyScanInterval(0xFFFF) 19 | 20 | public let rawValue: UInt16 21 | 22 | public init?(rawValue: UInt16) { 23 | 24 | guard rawValue >= LowEnergyScanInterval.min.rawValue, 25 | rawValue <= LowEnergyScanInterval.max.rawValue 26 | else { return nil } 27 | 28 | self.rawValue = rawValue 29 | } 30 | 31 | /// Time = N * 0.625 msec 32 | public var miliseconds: Double { 33 | 34 | return Double(rawValue) * 0.625 35 | } 36 | 37 | // Private, unsafe 38 | fileprivate init(_ rawValue: UInt16) { 39 | self.rawValue = rawValue 40 | } 41 | 42 | // Comparable 43 | public static func < (lhs: LowEnergyScanInterval, rhs: LowEnergyScanInterval) -> Bool { 44 | 45 | return lhs.rawValue < rhs.rawValue 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Sources/BluetoothHCI/LowEnergySupervisionTimeout.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LowEnergySupervisionTimeout.swift 3 | // Bluetooth 4 | // 5 | // Created by Alsey Coleman Miller on 6/14/18. 6 | // Copyright © 2018 PureSwift. All rights reserved. 7 | // 8 | 9 | /// Supervision Timeout 10 | /// 11 | /// Supervision timeout for the LE Link. 12 | /// Range: 0x000A to 0x0C80 13 | /// Time = N * 10 msec 14 | /// Time Range: 100 msec to 32 seconds 15 | /// 16 | /// - SeeAlso: [Vol 6] Part B, Section 4.5.2 17 | @frozen 18 | public struct LowEnergySupervisionTimeout: RawRepresentable, Equatable, Hashable, Comparable { 19 | 20 | /// 100 msec 21 | public static let min = LowEnergySupervisionTimeout(0x000A) 22 | 23 | /// 32 seconds 24 | public static let max = LowEnergySupervisionTimeout(0x0C80) 25 | 26 | public let rawValue: UInt16 27 | 28 | public init?(rawValue: UInt16) { 29 | 30 | guard rawValue >= LowEnergySupervisionTimeout.min.rawValue, 31 | rawValue <= LowEnergySupervisionTimeout.max.rawValue 32 | else { return nil } 33 | 34 | assert((LowEnergySupervisionTimeout.min.rawValue...LowEnergySupervisionTimeout.max.rawValue).contains(rawValue)) 35 | 36 | self.rawValue = rawValue 37 | } 38 | 39 | /// Time = N * 10 msec 40 | public var miliseconds: Double { 41 | 42 | return Double(rawValue) * 10 43 | } 44 | 45 | // Private, unsafe 46 | private init(_ rawValue: UInt16) { 47 | self.rawValue = rawValue 48 | } 49 | 50 | // Comparable 51 | public static func < (lhs: LowEnergySupervisionTimeout, rhs: LowEnergySupervisionTimeout) -> Bool { 52 | 53 | return lhs.rawValue < rhs.rawValue 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Sources/BluetoothHCI/LowEnergyTxChannel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LowEnergyTxChannel.swift 3 | // Bluetooth 4 | // 5 | // Created by Alsey Coleman Miller on 6/14/18. 6 | // Copyright © 2018 PureSwift. All rights reserved. 7 | // 8 | 9 | /// Bluetooth LE Tx Power Channel 10 | @frozen 11 | public struct LowEnergyTxChannel: RawRepresentable, Equatable, Hashable, Comparable { 12 | 13 | /// 100 msec 14 | public static let min = LowEnergyTxChannel(0x00) 15 | 16 | /// 32 seconds 17 | public static let max = LowEnergyTxChannel(0x27) 18 | 19 | public let rawValue: UInt8 20 | 21 | public init?(rawValue: UInt8) { 22 | guard rawValue >= LowEnergyTxChannel.min.rawValue, 23 | rawValue <= LowEnergyTxChannel.max.rawValue 24 | else { return nil } 25 | 26 | assert((LowEnergyTxChannel.min.rawValue...LowEnergyTxChannel.max.rawValue).contains(rawValue)) 27 | 28 | self.rawValue = rawValue 29 | } 30 | 31 | // Private, unsafe 32 | private init(_ rawValue: UInt8) { 33 | self.rawValue = rawValue 34 | } 35 | 36 | // Comparable 37 | public static func < (lhs: LowEnergyTxChannel, rhs: LowEnergyTxChannel) -> Bool { 38 | 39 | return lhs.rawValue < rhs.rawValue 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Sources/BluetoothHCI/LowEnergyTxPhy.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LowEnergyTxPhy.swift 3 | // Bluetooth 4 | // 5 | // Created by Alsey Coleman Miller on 6/14/18. 6 | // Copyright © 2018 PureSwift. All rights reserved. 7 | // 8 | 9 | /// Bluetooth LE Tx Phy 10 | @frozen 11 | public enum LowEnergyTxPhy: UInt8 { //TX_PHY 12 | 13 | /// The transmitter PHY for the connection is LE 1M 14 | case le1m = 0x01 15 | 16 | /// The transmitter PHY for the connection is LE 2M 17 | case le2m = 0x02 18 | 19 | /// The transmitter PHY for the connection is LE Coded 20 | case leCoded = 0x03 21 | } 22 | -------------------------------------------------------------------------------- /Sources/BluetoothHCI/LowEnergyTxPhys.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LowEnergyTxPhys.swift 3 | // Bluetooth 4 | // 5 | // Created by Alsey Coleman Miller on 6/14/18. 6 | // Copyright © 2018 PureSwift. All rights reserved. 7 | // 8 | 9 | /// The TX_PHYS parameter is a bit field that indicates the transmitter PHYs that the Host prefers 10 | /// the Controller to use. If the ALL_PHYS parameter specifies that the Host has no preference, 11 | //// the TX_PHYS parameter is ignored; otherwise at least one bit shall be set to 1. 12 | @frozen 13 | public enum LowEnergyTxPhys: UInt8, BitMaskOption { 14 | 15 | /// The Host prefers to use the LE 1M transmitter PHY (possibly among others) 16 | case hostUseLe1MTransmitterPhy = 0b001 17 | 18 | /// The Host prefers to use the LE 2M transmitter PHY (possibly among others) 19 | case hostUseLe2MTransmitterPhy = 0b010 20 | 21 | /// The Host prefers to use the LE Coded transmitter PHY (possibly among others) 22 | case hostUseLeCodedTransmitterPhy = 0b100 23 | 24 | public static let allCases: [LowEnergyTxPhys] = [ 25 | .hostUseLe1MTransmitterPhy, 26 | .hostUseLe2MTransmitterPhy, 27 | .hostUseLeCodedTransmitterPhy 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /Sources/BluetoothHCI/LowEnergyTxPower.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LowEnergyTxPower.swift 3 | // Bluetooth 4 | // 5 | // Created by Alsey Coleman Miller on 6/14/18. 6 | // Copyright © 2018 PureSwift. All rights reserved. 7 | // 8 | 9 | /// Bluetooth LE Tx Power 10 | /// 11 | /// Units: dBm 12 | /// 127 Host has no preference 13 | @frozen 14 | public struct LowEnergyTxPower: RawRepresentable, Equatable, Hashable, Comparable { 15 | 16 | public static let min = LowEnergyTxPower(-127) 17 | 18 | public static let max = LowEnergyTxPower(126) 19 | 20 | public let rawValue: Int8 21 | 22 | public init?(rawValue: Int8) { 23 | 24 | guard rawValue >= LowEnergyTxPower.min.rawValue, 25 | rawValue <= LowEnergyTxPower.max.rawValue 26 | else { return nil } 27 | 28 | assert((LowEnergyTxPower.min.rawValue...LowEnergyTxPower.max.rawValue).contains(rawValue)) 29 | 30 | self.rawValue = rawValue 31 | } 32 | 33 | // Private, unsafe 34 | private init(_ rawValue: Int8) { 35 | self.rawValue = rawValue 36 | } 37 | 38 | // Comparable 39 | public static func < (lhs: LowEnergyTxPower, rhs: LowEnergyTxPower) -> Bool { 40 | 41 | return lhs.rawValue < rhs.rawValue 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Sources/BluetoothHCI/LowEnergyWhiteList.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LowEnergyWhiteList.swift 3 | // Bluetooth 4 | // 5 | // Created by Alsey Coleman Miller on 11/29/17. 6 | // Copyright © 2017 PureSwift. All rights reserved. 7 | // 8 | 9 | public extension BluetoothHostControllerInterface { 10 | 11 | /// LE Clear White List Command 12 | /// 13 | /// Used to clear the White List stored in the Controller. 14 | func lowEnergyClearWhiteList(timeout: HCICommandTimeout = .default) async throws { 15 | 16 | // clear white list 17 | try await deviceRequest(HCILowEnergyCommand.clearWhiteList, timeout: timeout) 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /Sources/BluetoothHCI/LowEnergyWhiteListDevice.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LowEnergyWhiteListDevice.swift 3 | // Bluetooth 4 | // 5 | // Created by Alsey Coleman Miller on 6/14/18. 6 | // Copyright © 2018 PureSwift. All rights reserved. 7 | // 8 | 9 | /// LE White List Address Type 10 | @frozen 11 | public enum LowEnergyWhiteListAddressType: UInt8 { 12 | 13 | /// Public Device Address 14 | case `public` = 0x00 15 | 16 | /// Random Device Address 17 | case random = 0x01 18 | 19 | /// Devices sending anonymous advertisements 20 | case anonymous = 0xFF 21 | } 22 | 23 | /// LE White List Device Entry 24 | @frozen 25 | public enum LowEnergyWhiteListDevice { 26 | 27 | case `public`(BluetoothAddress) 28 | case random(BluetoothAddress) 29 | case anonymous 30 | } 31 | 32 | public extension LowEnergyWhiteListDevice { 33 | 34 | var addressType: LowEnergyWhiteListAddressType { 35 | switch self { 36 | case .public: return .public 37 | case .random: return .random 38 | case .anonymous: return .anonymous 39 | } 40 | } 41 | 42 | var address: BluetoothAddress? { 43 | switch self { 44 | case let .public(address): return address 45 | case let .random(address): return address 46 | case .anonymous: return nil 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Sources/BluetoothHCI/ProtocolServiceMultiplexer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PSM.swift 3 | // Bluetooth 4 | // 5 | // Created by Alsey Coleman Miller on 3/1/16. 6 | // Copyright © 2016 PureSwift. All rights reserved. 7 | // 8 | 9 | /// Protocol/Service Multiplexer (PSM). 10 | public enum ProtocolServiceMultiplexer: UInt8, Sendable { 11 | 12 | /// SDP 13 | case sdp = 0x0001 14 | 15 | /// RFCOMM 16 | case rfcomm = 0x0003 17 | 18 | /// TCS 19 | case tcs = 0x0005 20 | 21 | /// TCS-BIN-CORDLESS 22 | case ctp = 0x0007 23 | 24 | /// BNEP 25 | case bnep = 0x000F 26 | 27 | /// HID Control 28 | case hidc = 0x0011 29 | 30 | /// HID Interrupt 31 | case hidi = 0x0013 32 | 33 | /// UPnP 34 | case upnp = 0x0015 35 | 36 | /// AVCTP 37 | case avctp = 0x0017 38 | 39 | /// AVDTP 40 | case avdtp = 0x0019 41 | 42 | /// Advanced Control - Browsing 43 | case avctpBrowsing = 0x001B 44 | 45 | /// Unrestricted Digital Information Profile C-Plane 46 | case udicp = 0x001D 47 | 48 | /// Attribute Protocol 49 | case att = 0x001F 50 | 51 | /// 3DSP 52 | case _3dsp = 0x0021 53 | 54 | /// IPSP 55 | case ipsp = 0x0023 56 | 57 | /// OTS 58 | case ots = 0x0025 59 | 60 | /// Extended ATT 61 | case eatt = 0x0027 62 | } 63 | -------------------------------------------------------------------------------- /Sources/BluetoothHCI/VendorCommand.swift: -------------------------------------------------------------------------------- 1 | // 2 | // VendorCommand.swift 3 | // Bluetooth 4 | // 5 | // Created by Alsey Coleman Miller on 3/23/18. 6 | // Copyright © 2018 PureSwift. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | @frozen 12 | public struct VendorCommand: HCICommand, Equatable, Hashable { 13 | 14 | public static let opcodeGroupField = HCIOpcodeGroupField.vendor 15 | 16 | public let rawValue: HCIOpcodeCommandField 17 | 18 | public init(rawValue: HCIOpcodeCommandField) { 19 | self.rawValue = rawValue 20 | } 21 | } 22 | 23 | // MARK: - Name 24 | 25 | public extension VendorCommand { 26 | 27 | /// The names of the registered vendor commands. 28 | nonisolated(unsafe) static var names = [VendorCommand: String]() 29 | 30 | var name: String { 31 | return Self.names[self] ?? rawValue.toHexadecimal() 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Sources/BluetoothHCI/iBeacon.swift: -------------------------------------------------------------------------------- 1 | // 2 | // iBeacon.swift 3 | // 4 | // 5 | // Created by Alsey Coleman Miller on 10/22/20. 6 | // 7 | 8 | import Foundation 9 | import Bluetooth 10 | import BluetoothGAP 11 | 12 | public extension BluetoothHostControllerInterface { 13 | 14 | /// Enable iBeacon functionality. 15 | func iBeacon( 16 | _ beacon: AppleBeacon, 17 | flags: GAPFlags = [.lowEnergyGeneralDiscoverableMode, .notSupportedBREDR], 18 | interval: AdvertisingInterval = .default, 19 | timeout: HCICommandTimeout = .default 20 | ) async throws { 21 | 22 | // stop advertising 23 | do { try await enableLowEnergyAdvertising(false, timeout: timeout) } catch HCIError.commandDisallowed { /* ignore, means already turned off */ } 24 | 25 | // set advertising parameters 26 | let advertisingParameters = HCILESetAdvertisingParameters(interval: (min: interval, max: interval)) 27 | 28 | try await deviceRequest(advertisingParameters, timeout: timeout) 29 | 30 | // start advertising 31 | do { try await enableLowEnergyAdvertising(timeout: timeout) } catch HCIError.commandDisallowed { /* ignore, means already turned on */ } 32 | 33 | // set iBeacon data 34 | let advertisingDataCommand = HCILESetAdvertisingData(advertisingData: LowEnergyAdvertisingData(beacon: beacon, flags: flags)) 35 | 36 | try await deviceRequest(advertisingDataCommand, timeout: timeout) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Sources/BluetoothMacros/Plugins.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Plugins.swift 3 | // Bluetooth 4 | // 5 | // Created by Alsey Coleman Miller on 1/9/25. 6 | // 7 | 8 | import Foundation 9 | import SwiftCompilerPlugin 10 | import SwiftSyntaxMacros 11 | 12 | @main 13 | struct Plugins: CompilerPlugin { 14 | let providingMacros: [Macro.Type] = [ 15 | BluetoothAddressMacro.self, 16 | BluetoothUUIDMacro.self 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /Sources/BluetoothMetadata/BluetoothMetadata.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BluetoothSIG.swift 3 | // Bluetooth 4 | // 5 | // Created by Alsey Coleman Miller on 1/11/25. 6 | // 7 | 8 | import Foundation 9 | 10 | public protocol BluetoothMetadataFile: Codable { 11 | 12 | init(from data: Data) throws 13 | } 14 | 15 | public extension BluetoothMetadataFile { 16 | 17 | init(url: URL) throws { 18 | let data = try Data(contentsOf: url, options: [.mappedIfSafe]) 19 | try self.init(from: data) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Sources/BluetoothMetadata/CompanyIdentifier.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CompanyIdentifier.swift 3 | // Bluetooth 4 | // 5 | // Created by Alsey Coleman Miller on 1/12/25. 6 | // 7 | 8 | import Foundation 9 | 10 | /// Bluetooth SIG Company Identifier Metadata 11 | public struct CompanyIdentifier: Equatable, Hashable, Codable, Sendable, Identifiable { 12 | 13 | public var id: UInt16 { value } 14 | 15 | public let value: UInt16 16 | 17 | public let name: String 18 | } 19 | 20 | public extension CompanyIdentifier { 21 | 22 | struct File: BluetoothMetadataFile, Equatable, Hashable, Codable, Sendable { 23 | 24 | public let companyIdentifiers: [CompanyIdentifier] 25 | 26 | public init(from data: Data) throws { 27 | self = try Self.decoder.decode(File.self, from: data) 28 | } 29 | } 30 | } 31 | 32 | public extension CompanyIdentifier.File { 33 | 34 | static var fileName: String { "CompanyIdentifier" } 35 | 36 | static var fileExtension: String { 37 | "json" 38 | } 39 | 40 | static var decoder: JSONDecoder { 41 | .init() 42 | } 43 | 44 | static func load() throws -> Self { 45 | guard 46 | let fileURL = Bundle.module.url( 47 | forResource: fileName, 48 | withExtension: fileExtension 49 | ) 50 | else { 51 | throw CocoaError(.fileNoSuchFile) 52 | } 53 | return try self.init(url: fileURL) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Sources/BluetoothMetadata/Resources/DeclarationUUID.json: -------------------------------------------------------------------------------- 1 | { 2 | "uuids": [ 3 | { 4 | "uuid": 10240, 5 | "name": "Primary Service", 6 | "id": "org.bluetooth.attribute.gatt.primary_service_declaration" 7 | }, 8 | { 9 | "uuid": 10241, 10 | "name": "Secondary Service", 11 | "id": "org.bluetooth.attribute.gatt.secondary_service_declaration" 12 | }, 13 | { 14 | "uuid": 10242, 15 | "name": "Include", 16 | "id": "org.bluetooth.attribute.gatt.include_declaration" 17 | }, 18 | { 19 | "uuid": 10243, 20 | "name": "Characteristic", 21 | "id": "org.bluetooth.attribute.gatt.characteristic_declaration" 22 | } 23 | ] 24 | } -------------------------------------------------------------------------------- /Sources/GenerateBluetooth/Extensions/Hexadecimal.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Hexadecimal.swift 3 | // Bluetooth 4 | // 5 | // Created by Alsey Coleman Miller on 3/2/16. 6 | // Copyright © 2016 PureSwift. All rights reserved. 7 | // 8 | 9 | internal extension FixedWidthInteger { 10 | 11 | func toHexadecimal() -> String { 12 | 13 | var string = String(self, radix: 16) 14 | while string.utf8.count < (MemoryLayout.size * 2) { 15 | string = "0" + string 16 | } 17 | return string.uppercased() 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Tests/BluetoothTests/GATTDatabaseTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GATTDatabaseTests.swift 3 | // Bluetooth 4 | // 5 | // Created by Alsey Coleman Miller on 4/17/18. 6 | // Copyright © 2018 PureSwift. All rights reserved. 7 | // 8 | 9 | #if canImport(BluetoothGATT) 10 | import Testing 11 | import Foundation 12 | import Bluetooth 13 | @testable import BluetoothGATT 14 | 15 | @Suite struct GATTDatabaseTests { 16 | 17 | @Test func testProfile() { 18 | 19 | var database = GATTDatabase(services: TestProfile.services) 20 | database.dump() 21 | 22 | #expect(database.attributeGroups.count == TestProfile.services.count) 23 | #expect(database.attributes[0x01].uuid == BluetoothUUID.Declaration.characteristic) 24 | #expect(Array(database) == database.attributes) 25 | 26 | database.removeAll() 27 | 28 | #expect(database.isEmpty) 29 | #expect(database.attributes.isEmpty) 30 | #expect(database.attributeGroups.isEmpty) 31 | } 32 | } 33 | 34 | extension GATTDatabase { 35 | 36 | func dump() { 37 | 38 | print("GATT Database:") 39 | 40 | for attribute in self { 41 | 42 | let value: String = 43 | BluetoothUUID(data: attribute.value)?.littleEndian.description 44 | ?? ((attribute.value.count > 1) ? String(utf8: attribute.value) : attribute.value.toHexadecimal()) 45 | ?? attribute.value.toHexadecimal() 46 | 47 | print("\(attribute.handle) - \(attribute.uuid)") 48 | print("Permissions: \(attribute.permissions)") 49 | print("Value: \(value)") 50 | } 51 | } 52 | } 53 | #endif 54 | -------------------------------------------------------------------------------- /Tests/BluetoothTests/IntegerTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // IntegerTests.swift 3 | // BluetoothTests 4 | // 5 | // Created by Alsey Coleman Miller on 6/19/18. 6 | // Copyright © 2018 PureSwift. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Testing 11 | @testable import Bluetooth 12 | 13 | @Suite struct IntegerTests { 14 | 15 | @Test func test2Bit() { 16 | 17 | let bit2Range = UInt8(0)...UInt8(3) 18 | 19 | for enum1 in bit2Range { 20 | 21 | for enum2 in bit2Range { 22 | 23 | for enum3 in bit2Range { 24 | 25 | for enum4 in bit2Range { 26 | 27 | let packedByte = UInt8.bit2(enum1, enum2, enum3, enum4) 28 | let bit2Values = packedByte.bit2() 29 | 30 | #expect(bit2Values.0 == enum1) 31 | #expect(bit2Values.1 == enum2) 32 | #expect(bit2Values.2 == enum3) 33 | #expect(bit2Values.3 == enum4) 34 | } 35 | } 36 | } 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Tests/BluetoothTests/UInt256Tests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UInt256Tests.swift 3 | // Bluetooth 4 | // 5 | // Created by Marco Estrella on 4/21/18. 6 | // Copyright © 2018 PureSwift. All rights reserved. 7 | // 8 | 9 | import Testing 10 | import Foundation 11 | @testable import Bluetooth 12 | 13 | @Suite struct UInt256Tests { 14 | 15 | @Test func bitWidth() { 16 | 17 | #expect(UInt256.bitWidth == MemoryLayout.size * 8) 18 | #expect(UInt256.bitWidth == 256) 19 | } 20 | 21 | @Test func hashable() { 22 | 23 | #expect(UInt256.max.hashValue != 0) 24 | } 25 | 26 | @Test func expressibleByIntegerLiteral() { 27 | 28 | let values: [(UInt256, String)] = [ 29 | (UInt256.zero, "0x0000000000000000000000000000000000000000000000000000000000000000"), 30 | (0x0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000, "0x0000000000000000000000000000000000000000000000000000000000000000"), 31 | (0x0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0001, "0x0000000000000000000000000000000000000000000000000000000000000001"), 32 | (0x0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0020, "0x0000000000000000000000000000000000000000000000000000000000000020"), 33 | (0x0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_DCBA_BEBA_AFDE_0001, "0x000000000000000000000000000000000000000000000000DCBABEBAAFDE0001") 34 | ] 35 | 36 | values.forEach { #expect($0.description == $1) } 37 | 38 | #expect(UInt256.zero == 0) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Tests/BluetoothTests/UInt40Tests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UInt40Tests.swift 3 | // Bluetooth 4 | // 5 | // Created by Alsey Coleman Miller on 6/28/18. 6 | // Copyright © 2018 PureSwift. All rights reserved. 7 | // 8 | 9 | import Testing 10 | import Foundation 11 | @testable import Bluetooth 12 | 13 | @Suite struct UInt40Tests { 14 | 15 | @Test func bitWidth() { 16 | 17 | #expect(UInt40.bitWidth == MemoryLayout.size * 8) 18 | #expect(UInt40.bitWidth == 40) 19 | } 20 | 21 | @Test func equatable() { 22 | 23 | #expect(UInt40.zero == 0) 24 | #expect(UInt40.min == 0) 25 | #expect(UInt40.max == 1_099_511_627_775) 26 | #expect(UInt40.max == 0xFF_FFFF_FFFF) 27 | } 28 | 29 | @Test func hashable() { 30 | 31 | #expect(UInt40.max.hashValue != UInt64.max.hashValue) 32 | } 33 | 34 | @Test func expressibleByIntegerLiteral() { 35 | 36 | let values: [(UInt40, String)] = [ 37 | (UInt40.zero, "0"), 38 | (0x00_0000_0000, "0"), 39 | (0x00_0000_0001, "1"), 40 | (0x00_0000_0020, "32"), 41 | (12_345_678, "12345678"), 42 | (1_099_511_627_775, "1099511627775"), 43 | (0xFF_FFFF_FFFF, "1099511627775") 44 | ] 45 | 46 | values.forEach { #expect($0.description == $1) } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Tests/BluetoothTests/UInt48Tests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UInt48.swift 3 | // BluetoothTests 4 | // 5 | // Created by Carlos Duclos on 7/11/18. 6 | // Copyright © 2018 PureSwift. All rights reserved. 7 | // 8 | 9 | import Testing 10 | import Foundation 11 | @testable import Bluetooth 12 | 13 | @Suite struct UInt48Tests { 14 | 15 | @Test func bitWidth() { 16 | 17 | #expect(UInt48.bitWidth == MemoryLayout.size * 8) 18 | #expect(UInt48.bitWidth == 48) 19 | } 20 | 21 | @Test func comparable() { 22 | 23 | #expect(UInt48.zero == 0) 24 | #expect(UInt48.min == 0) 25 | #expect(UInt48.max == 281_474_976_710_655) 26 | #expect(UInt48.max == 0xFFFF_FFFF_FFFF) 27 | #expect(0xFFFF_FFFF_FFFF <= UInt48.max) 28 | #expect(0xFFFF_FFFF_FFFE < UInt48.max) 29 | #expect(0xFFFF_FFFF_FFFF >= UInt48.max) 30 | #expect(UInt48.max > 0xFFFF_FFFF_FFFE) 31 | } 32 | 33 | @Test func hashable() { 34 | 35 | #expect(UInt48.max.hashValue != 0) 36 | } 37 | 38 | @Test func expressibleByIntegerLiteral() { 39 | 40 | let values: [(UInt48, String)] = [ 41 | (.zero, "0"), 42 | (0x00_0000_0000, "0"), 43 | (0x00_0000_0001, "1"), 44 | (0x00_0000_0020, "32"), 45 | (123_456_789, "123456789"), 46 | (281_474_976_710_655, "281474976710655"), 47 | (0xFFFF_FFFF_FFFF, "281474976710655") 48 | ] 49 | 50 | values.forEach { #expect($0.description == $1) } 51 | } 52 | 53 | @Test func data() { 54 | 55 | let data = Data([0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF]) 56 | 57 | #expect(UInt48(data: data) == 281_474_976_710_655) 58 | #expect(Data(UInt48.max) == data) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /Tests/BluetoothTests/UInt512Tests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UInt512Tests.swift 3 | // Bluetooth 4 | // 5 | // Created by Marco Estrella on 4/21/18. 6 | // Copyright © 2018 PureSwift. All rights reserved. 7 | // 8 | 9 | import Testing 10 | import Foundation 11 | @testable import Bluetooth 12 | 13 | @Suite struct UInt512Tests { 14 | 15 | @Test func bitWidth() { 16 | 17 | #expect(UInt512.bitWidth == MemoryLayout.size * 8) 18 | #expect(UInt512.bitWidth == 512) 19 | } 20 | 21 | @Test func hashable() { 22 | 23 | #expect(UInt512.max.hashValue != 0) 24 | } 25 | 26 | @Test func expressibleByIntegerLiteral() { 27 | 28 | let values: [(UInt512, String)] = [ 29 | (UInt512.zero, "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"), 30 | (0x0, "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"), 31 | (0x1, "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001"), 32 | (0x20, "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020"), 33 | (0xDCBA_BEBA_AFDE_0001, "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000DCBABEBAAFDE0001") 34 | ] 35 | 36 | values.forEach { #expect($0.description == $1) } 37 | 38 | #expect(UInt512.zero == 0) 39 | } 40 | } 41 | --------------------------------------------------------------------------------