Skip to content

Commit

Permalink
Enable Bluetooth iBeacon advertisement for Pico W
Browse files Browse the repository at this point in the history
  • Loading branch information
colemancda committed Nov 6, 2024
1 parent 60a648b commit 11d310d
Show file tree
Hide file tree
Showing 25 changed files with 2,392 additions and 15 deletions.
125 changes: 125 additions & 0 deletions pico-w-blink-sdk/Bluetooth.swift
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
}
}
160 changes: 160 additions & 0 deletions pico-w-blink-sdk/BluetoothAddress.swift
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
2 changes: 2 additions & 0 deletions pico-w-blink-sdk/BridgingHeader.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,7 @@

#pragma once

#include "btstack.h"
#include "pico/stdlib.h"
#include "pico/cyw43_arch.h"
#include "pico/btstack_cyw43.h"
72 changes: 72 additions & 0 deletions pico-w-blink-sdk/ByteSwap.swift
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
}
}
Loading

0 comments on commit 11d310d

Please sign in to comment.