-
Notifications
You must be signed in to change notification settings - Fork 52
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Enable Bluetooth iBeacon advertisement for Pico W
- Loading branch information
1 parent
60a648b
commit 11d310d
Showing
25 changed files
with
2,392 additions
and
15 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,125 @@ | ||
//===----------------------------------------------------------------------===// | ||
// | ||
// This source file is part of the Swift open source project | ||
// | ||
// Copyright (c) 2024 Apple Inc. and the Swift project authors. | ||
// Licensed under Apache License v2.0 with Runtime Library Exception | ||
// | ||
// See https://swift.org/LICENSE.txt for license information | ||
// | ||
//===----------------------------------------------------------------------===// | ||
|
||
extension CYW43 { | ||
|
||
final class Bluetooth { | ||
|
||
static let shared = Bluetooth() | ||
|
||
private var callbackRegistration = btstack_packet_callback_registration_t() | ||
|
||
fileprivate(set) var state: State = .off | ||
|
||
private(set) var address: BluetoothAddress = .zero | ||
|
||
var advertisement = LowEnergyAdvertisingData() { | ||
didSet { | ||
let length = advertisement.length | ||
advertisementBuffer = [UInt8](advertisement) | ||
// data is not copied, pointer has to stay valid | ||
gap_advertisements_set_data(length, &advertisementBuffer) | ||
} | ||
} | ||
|
||
private var advertisementBuffer = [UInt8]() | ||
|
||
var scanResponse = LowEnergyAdvertisingData() { | ||
didSet { | ||
let length = scanResponse.length | ||
scanResponseBuffer = [UInt8](scanResponse) | ||
// data is not copied, pointer has to stay valid | ||
gap_scan_response_set_data(length, &scanResponseBuffer) | ||
} | ||
} | ||
|
||
private var scanResponseBuffer = [UInt8]() | ||
|
||
var isAdvertising = false { | ||
didSet { | ||
gap_advertisements_enable(isAdvertising ? 1 : 0) | ||
} | ||
} | ||
|
||
private init() { | ||
// register for callbacks | ||
callbackRegistration.callback = _bluetooth_packet_handler | ||
hci_add_event_handler(&callbackRegistration) | ||
} | ||
|
||
deinit { | ||
hci_remove_event_handler(&callbackRegistration) | ||
} | ||
} | ||
} | ||
|
||
extension CYW43.Bluetooth { | ||
|
||
func setPower(_ state: PowerState) { | ||
hci_power_control(.init(rawValue: state.rawValue)) | ||
} | ||
|
||
func setAdvertisementParameters( | ||
advIntMin: UInt16 = 0x0030, | ||
advIntMax: UInt16 = 0x0030, | ||
advType: UInt8 = 0, | ||
directAddressType: UInt8 = 0, | ||
directAddress: inout BluetoothAddress, | ||
channelMap: UInt8 = 0x07, | ||
filterPolicy: UInt8 = 0x00 | ||
) { | ||
withUnsafeMutablePointer(to: &directAddress.bytes) { | ||
gap_advertisements_set_params(advIntMin, advIntMax, advType, directAddressType, $0, channelMap, filterPolicy) | ||
} | ||
} | ||
} | ||
|
||
extension CYW43.Bluetooth { | ||
|
||
enum PowerState: UInt8, Sendable { | ||
|
||
case off = 0 | ||
case on = 1 | ||
case sleep = 2 | ||
} | ||
|
||
enum State: UInt8 { | ||
|
||
case off = 0 | ||
case initializing = 1 | ||
case on = 2 | ||
case halting = 3 | ||
case sleeping = 4 | ||
case fallingAsleep = 5 | ||
} | ||
} | ||
|
||
// packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size) | ||
@_documentation(visibility: internal) | ||
@_cdecl("bluetooth_packet_handler") | ||
internal func _bluetooth_packet_handler(packetType: UInt8, channel: UInt16, packetPointer: UnsafeMutablePointer<UInt8>?, packetSize: UInt16) { | ||
|
||
switch packetType { | ||
case UInt8(HCI_EVENT_PACKET): | ||
switch hci_event_packet_get_type(packetPointer) { | ||
case UInt8(BTSTACK_EVENT_STATE): | ||
let rawState = btstack_event_state_get_state(packetPointer) | ||
let newValue = CYW43.Bluetooth.State(rawValue: rawState) ?? .off | ||
CYW43.Bluetooth.shared.state = newValue | ||
case UInt8(HCI_EVENT_VENDOR_SPECIFIC): | ||
break | ||
default: | ||
break | ||
} | ||
default: | ||
break | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,160 @@ | ||
//===----------------------------------------------------------------------===// | ||
// | ||
// This source file is part of the Swift open source project | ||
// | ||
// Copyright (c) 2024 Apple Inc. and the Swift project authors. | ||
// Licensed under Apache License v2.0 with Runtime Library Exception | ||
// | ||
// See https://swift.org/LICENSE.txt for license information | ||
// | ||
//===----------------------------------------------------------------------===// | ||
|
||
/// Bluetooth address. | ||
public struct BluetoothAddress: Sendable { | ||
|
||
// MARK: - Properties | ||
|
||
/// Underlying address bytes (host endianess). | ||
public var bytes: ByteValue | ||
|
||
// MARK: - Initialization | ||
|
||
/// Initialize with the specifed bytes (in host endianess). | ||
public init(bytes: ByteValue = (0, 0, 0, 0, 0, 0)) { | ||
self.bytes = bytes | ||
} | ||
} | ||
|
||
public extension BluetoothAddress { | ||
|
||
/// The minimum representable value in this type. | ||
static var min: BluetoothAddress { return BluetoothAddress(bytes: (.min, .min, .min, .min, .min, .min)) } | ||
|
||
/// The maximum representable value in this type. | ||
static var max: BluetoothAddress { return BluetoothAddress(bytes: (.max, .max, .max, .max, .max, .max)) } | ||
|
||
/// A zero address. | ||
static var zero: BluetoothAddress { return .min } | ||
} | ||
|
||
// MARK: - ByteValue | ||
|
||
extension BluetoothAddress { | ||
|
||
/// Raw Bluetooth Address 6 byte (48 bit) value. | ||
public typealias ByteValue = (UInt8, UInt8, UInt8, UInt8, UInt8, UInt8) | ||
|
||
public static var bitWidth: Int { return 48 } | ||
|
||
public static var length: Int { return 6 } | ||
} | ||
|
||
// MARK: - Equatable | ||
|
||
extension BluetoothAddress: Equatable { | ||
|
||
public static func == (lhs: BluetoothAddress, rhs: BluetoothAddress) -> Bool { | ||
return lhs.bytes.0 == rhs.bytes.0 | ||
&& lhs.bytes.1 == rhs.bytes.1 | ||
&& lhs.bytes.2 == rhs.bytes.2 | ||
&& lhs.bytes.3 == rhs.bytes.3 | ||
&& lhs.bytes.4 == rhs.bytes.4 | ||
&& lhs.bytes.5 == rhs.bytes.5 | ||
} | ||
} | ||
|
||
// MARK: - Hashable | ||
|
||
extension BluetoothAddress: Hashable { | ||
|
||
public func hash(into hasher: inout Hasher) { | ||
withUnsafeBytes(of: bytes) { hasher.combine(bytes: $0) } | ||
} | ||
} | ||
|
||
// MARK: - Byte Swap | ||
|
||
extension BluetoothAddress: ByteSwap { | ||
|
||
/// A representation of this address with the byte order swapped. | ||
public var byteSwapped: BluetoothAddress { | ||
return BluetoothAddress(bytes: (bytes.5, bytes.4, bytes.3, bytes.2, bytes.1, bytes.0)) | ||
} | ||
} | ||
|
||
// MARK: - RawRepresentable | ||
|
||
extension BluetoothAddress: RawRepresentable { | ||
|
||
/// Initialize a Bluetooth Address from its big endian string representation (e.g. `00:1A:7D:DA:71:13`). | ||
public init?(rawValue: String) { | ||
self.init(rawValue) | ||
} | ||
|
||
/// Initialize a Bluetooth Address from its big endian string representation (e.g. `00:1A:7D:DA:71:13`). | ||
internal init?<S: StringProtocol>(_ rawValue: S) { | ||
|
||
// verify string length | ||
let characters = rawValue.utf8 | ||
guard characters.count == 17, | ||
let separator = ":".utf8.first | ||
else { return nil } | ||
|
||
var bytes: ByteValue = (0, 0, 0, 0, 0, 0) | ||
|
||
let components = characters.split(whereSeparator: { $0 == separator }) | ||
|
||
guard components.count == 6 | ||
else { return nil } | ||
|
||
for (index, subsequence) in components.enumerated() { | ||
|
||
guard subsequence.count == 2, | ||
let byte = UInt8(hexadecimal: subsequence) | ||
else { return nil } | ||
|
||
withUnsafeMutablePointer(to: &bytes) { | ||
$0.withMemoryRebound(to: UInt8.self, capacity: 6) { | ||
$0.advanced(by: index).pointee = byte | ||
} | ||
} | ||
} | ||
|
||
self.init(bigEndian: BluetoothAddress(bytes: bytes)) | ||
} | ||
|
||
/// Convert a Bluetooth Address to its big endian string representation (e.g. `00:1A:7D:DA:71:13`). | ||
public var rawValue: String { | ||
let bytes = self.bigEndian.bytes | ||
return bytes.0.toHexadecimal() | ||
+ ":" + bytes.1.toHexadecimal() | ||
+ ":" + bytes.2.toHexadecimal() | ||
+ ":" + bytes.3.toHexadecimal() | ||
+ ":" + bytes.4.toHexadecimal() | ||
+ ":" + bytes.5.toHexadecimal() | ||
} | ||
} | ||
|
||
// MARK: - CustomStringConvertible | ||
|
||
extension BluetoothAddress: CustomStringConvertible { | ||
|
||
public var description: String { rawValue } | ||
} | ||
|
||
// MARK: - Data | ||
|
||
public extension BluetoothAddress { | ||
|
||
init?<Data: DataContainer>(data: Data) { | ||
guard data.count == type(of: self).length | ||
else { return nil } | ||
self.bytes = (data[0], data[1], data[2], data[3], data[4], data[5]) | ||
} | ||
} | ||
|
||
// MARK: - Codable | ||
|
||
#if !hasFeature(Embedded) | ||
extension BluetoothAddress: Codable { } | ||
#endif |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
//===----------------------------------------------------------------------===// | ||
// | ||
// This source file is part of the Swift open source project | ||
// | ||
// Copyright (c) 2024 Apple Inc. and the Swift project authors. | ||
// Licensed under Apache License v2.0 with Runtime Library Exception | ||
// | ||
// See https://swift.org/LICENSE.txt for license information | ||
// | ||
//===----------------------------------------------------------------------===// | ||
|
||
/// A Bluetooth value that is stored in the CPU native endianess format. | ||
public protocol ByteSwap { | ||
|
||
/// A representation of this integer with the byte order swapped. | ||
var byteSwapped: Self { get } | ||
} | ||
|
||
public extension ByteSwap { | ||
|
||
/// Creates an instance from its little-endian representation, changing the | ||
/// byte order if necessary. | ||
/// | ||
/// - Parameter value: A value to use as the little-endian representation of | ||
/// the new instance. | ||
init(littleEndian value: Self) { | ||
#if _endian(little) | ||
self = value | ||
#else | ||
self = value.byteSwapped | ||
#endif | ||
} | ||
|
||
/// Creates an instance from its big-endian representation, changing the byte | ||
/// order if necessary. | ||
/// | ||
/// - Parameter value: A value to use as the big-endian representation of the | ||
/// new instance. | ||
init(bigEndian value: Self) { | ||
#if _endian(big) | ||
self = value | ||
#else | ||
self = value.byteSwapped | ||
#endif | ||
} | ||
|
||
/// The little-endian representation of this value. | ||
/// | ||
/// If necessary, the byte order of this value is reversed from the typical | ||
/// byte order of this address. On a little-endian platform, for any | ||
/// address `x`, `x == x.littleEndian`. | ||
var littleEndian: Self { | ||
#if _endian(little) | ||
return self | ||
#else | ||
return byteSwapped | ||
#endif | ||
} | ||
|
||
/// The big-endian representation of this value. | ||
/// | ||
/// If necessary, the byte order of this value is reversed from the typical | ||
/// byte order of this address. On a big-endian platform, for any | ||
/// address `x`, `x == x.bigEndian`. | ||
var bigEndian: Self { | ||
#if _endian(big) | ||
return self | ||
#else | ||
return byteSwapped | ||
#endif | ||
} | ||
} |
Oops, something went wrong.