Skip to content

Commit fcb79b8

Browse files
committed
Add Bluetooth iBeacon example for Pico W
1 parent 60a648b commit fcb79b8

28 files changed

+2541
-0
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ Each example in this repository contains build and deployment instructions, howe
3939
| [pico-blink](./pico-blink) | Raspberry Pi Pico | None | Blink an LED repeatedly. | <img width="300" src="https://github.com/apple/swift-embedded-examples/assets/1186214/f2c45c18-f9a4-48b4-a941-1298ecc942cb"> |
4040
| [pico-blink-sdk](./pico-blink-sdk) | Raspberry Pi Pico, Pico 2 | Pico SDK | Blink an LED repeatedly with Swift & the Pico SDK. | <img width="300" src="https://github.com/apple/swift-embedded-examples/assets/1186214/f2c45c18-f9a4-48b4-a941-1298ecc942cb"> |
4141
| [pico-w-blink-sdk](./pico-w-blink-sdk) | Raspberry Pi Pico W | Pico SDK | Blink an LED to signal 'SOS' in Morse code repeatedly with Swift & the Pico SDK. | <img width="300" src="https://github.com/apple/swift-embedded-examples/assets/26223064/a4949a2e-1887-4325-8f5f-a681963c93d7"> |
42+
| [pico-w-ibeacon-sdk](./pico-w-ibeacon-sdk) | Raspberry Pi Pico W | Pico SDK | Advertise as an iBeacon via Bluetooth LE with Swift & the Pico SDK. | <img width="300" src="https://github.com/apple/swift-embedded-examples/assets/26223064/a4949a2e-1887-4325-8f5f-a681963c93d7"> |
4243
| [pico2-neopixel](./pico2-neopixel) | Raspberry Pi Pico 2 | None | Control Neopixel LEDs using the RP2350 PIO. | <img width="300" src="pico2-neopixel/assets/images/example.jpg"> |
4344
| [nrfx-blink-sdk](./nrfx-blink-sdk) | nRF52840-DK | Zephyr SDK | Blink an LED repeatedly with Swift & Zephyr. | <img width="300" src="https://github.com/apple/swift-embedded-examples/assets/1186214/ae3ff153-dd33-4460-8a08-4eac442bf7b0"> |
4445
| [esp32-led-strip-sdk](./esp32-led-strip-sdk) | ESP32-C6-DevKitC-1 | ESP-IDF SDK | Control NeoPixel LEDs with Swift & the ESP-IDF. | <img width="300" src="https://github.com/apple/swift-embedded-examples/assets/1186214/15f8a3e0-953e-426d-ad2d-3902baf859be"> |

pico-w-ibeacon-sdk/Bluetooth.swift

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift open source project
4+
//
5+
// Copyright (c) 2024 Apple Inc. and the Swift project authors.
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
//
10+
//===----------------------------------------------------------------------===//
11+
12+
extension CYW43 {
13+
14+
final class Bluetooth {
15+
16+
static let shared = Bluetooth()
17+
18+
private var callbackRegistration = btstack_packet_callback_registration_t()
19+
20+
fileprivate(set) var state: State = .off
21+
22+
private(set) var address: BluetoothAddress = .zero
23+
24+
var advertisement = LowEnergyAdvertisingData() {
25+
didSet {
26+
let length = advertisement.length
27+
advertisementBuffer = [UInt8](advertisement)
28+
// data is not copied, pointer has to stay valid
29+
gap_advertisements_set_data(length, &advertisementBuffer)
30+
}
31+
}
32+
33+
private var advertisementBuffer = [UInt8]()
34+
35+
var scanResponse = LowEnergyAdvertisingData() {
36+
didSet {
37+
let length = scanResponse.length
38+
scanResponseBuffer = [UInt8](scanResponse)
39+
// data is not copied, pointer has to stay valid
40+
gap_scan_response_set_data(length, &scanResponseBuffer)
41+
}
42+
}
43+
44+
private var scanResponseBuffer = [UInt8]()
45+
46+
var isAdvertising = false {
47+
didSet {
48+
gap_advertisements_enable(isAdvertising ? 1 : 0)
49+
}
50+
}
51+
52+
private init() {
53+
// register for callbacks
54+
callbackRegistration.callback = _bluetooth_packet_handler
55+
hci_add_event_handler(&callbackRegistration)
56+
}
57+
58+
deinit {
59+
hci_remove_event_handler(&callbackRegistration)
60+
}
61+
}
62+
}
63+
64+
extension CYW43.Bluetooth {
65+
66+
func setPower(_ state: PowerState) {
67+
hci_power_control(.init(rawValue: state.rawValue))
68+
}
69+
70+
func setAdvertisementParameters(
71+
advIntMin: UInt16 = 0x0030,
72+
advIntMax: UInt16 = 0x0030,
73+
advType: UInt8 = 0,
74+
directAddressType: UInt8 = 0,
75+
directAddress: inout BluetoothAddress,
76+
channelMap: UInt8 = 0x07,
77+
filterPolicy: UInt8 = 0x00
78+
) {
79+
withUnsafeMutablePointer(to: &directAddress.bytes) {
80+
gap_advertisements_set_params(advIntMin, advIntMax, advType, directAddressType, $0, channelMap, filterPolicy)
81+
}
82+
}
83+
}
84+
85+
extension CYW43.Bluetooth {
86+
87+
enum PowerState: UInt8, Sendable {
88+
89+
case off = 0
90+
case on = 1
91+
case sleep = 2
92+
}
93+
94+
enum State: UInt8 {
95+
96+
case off = 0
97+
case initializing = 1
98+
case on = 2
99+
case halting = 3
100+
case sleeping = 4
101+
case fallingAsleep = 5
102+
}
103+
}
104+
105+
// packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size)
106+
@_documentation(visibility: internal)
107+
@_cdecl("bluetooth_packet_handler")
108+
internal func _bluetooth_packet_handler(packetType: UInt8, channel: UInt16, packetPointer: UnsafeMutablePointer<UInt8>?, packetSize: UInt16) {
109+
110+
switch packetType {
111+
case UInt8(HCI_EVENT_PACKET):
112+
switch hci_event_packet_get_type(packetPointer) {
113+
case UInt8(BTSTACK_EVENT_STATE):
114+
let rawState = btstack_event_state_get_state(packetPointer)
115+
let newValue = CYW43.Bluetooth.State(rawValue: rawState) ?? .off
116+
CYW43.Bluetooth.shared.state = newValue
117+
case UInt8(HCI_EVENT_VENDOR_SPECIFIC):
118+
break
119+
default:
120+
break
121+
}
122+
default:
123+
break
124+
}
125+
}
Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift open source project
4+
//
5+
// Copyright (c) 2024 Apple Inc. and the Swift project authors.
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
//
10+
//===----------------------------------------------------------------------===//
11+
12+
/// Bluetooth address.
13+
public struct BluetoothAddress: Sendable {
14+
15+
// MARK: - Properties
16+
17+
/// Underlying address bytes (host endianess).
18+
public var bytes: ByteValue
19+
20+
// MARK: - Initialization
21+
22+
/// Initialize with the specifed bytes (in host endianess).
23+
public init(bytes: ByteValue = (0, 0, 0, 0, 0, 0)) {
24+
self.bytes = bytes
25+
}
26+
}
27+
28+
public extension BluetoothAddress {
29+
30+
/// The minimum representable value in this type.
31+
static var min: BluetoothAddress { return BluetoothAddress(bytes: (.min, .min, .min, .min, .min, .min)) }
32+
33+
/// The maximum representable value in this type.
34+
static var max: BluetoothAddress { return BluetoothAddress(bytes: (.max, .max, .max, .max, .max, .max)) }
35+
36+
/// A zero address.
37+
static var zero: BluetoothAddress { return .min }
38+
}
39+
40+
// MARK: - ByteValue
41+
42+
extension BluetoothAddress {
43+
44+
/// Raw Bluetooth Address 6 byte (48 bit) value.
45+
public typealias ByteValue = (UInt8, UInt8, UInt8, UInt8, UInt8, UInt8)
46+
47+
public static var bitWidth: Int { return 48 }
48+
49+
public static var length: Int { return 6 }
50+
}
51+
52+
// MARK: - Equatable
53+
54+
extension BluetoothAddress: Equatable {
55+
56+
public static func == (lhs: BluetoothAddress, rhs: BluetoothAddress) -> Bool {
57+
return lhs.bytes.0 == rhs.bytes.0
58+
&& lhs.bytes.1 == rhs.bytes.1
59+
&& lhs.bytes.2 == rhs.bytes.2
60+
&& lhs.bytes.3 == rhs.bytes.3
61+
&& lhs.bytes.4 == rhs.bytes.4
62+
&& lhs.bytes.5 == rhs.bytes.5
63+
}
64+
}
65+
66+
// MARK: - Hashable
67+
68+
extension BluetoothAddress: Hashable {
69+
70+
public func hash(into hasher: inout Hasher) {
71+
withUnsafeBytes(of: bytes) { hasher.combine(bytes: $0) }
72+
}
73+
}
74+
75+
// MARK: - Byte Swap
76+
77+
extension BluetoothAddress: ByteSwap {
78+
79+
/// A representation of this address with the byte order swapped.
80+
public var byteSwapped: BluetoothAddress {
81+
return BluetoothAddress(bytes: (bytes.5, bytes.4, bytes.3, bytes.2, bytes.1, bytes.0))
82+
}
83+
}
84+
85+
// MARK: - RawRepresentable
86+
87+
extension BluetoothAddress: RawRepresentable {
88+
89+
/// Initialize a Bluetooth Address from its big endian string representation (e.g. `00:1A:7D:DA:71:13`).
90+
public init?(rawValue: String) {
91+
self.init(rawValue)
92+
}
93+
94+
/// Initialize a Bluetooth Address from its big endian string representation (e.g. `00:1A:7D:DA:71:13`).
95+
internal init?<S: StringProtocol>(_ rawValue: S) {
96+
97+
// verify string length
98+
let characters = rawValue.utf8
99+
guard characters.count == 17,
100+
let separator = ":".utf8.first
101+
else { return nil }
102+
103+
var bytes: ByteValue = (0, 0, 0, 0, 0, 0)
104+
105+
let components = characters.split(whereSeparator: { $0 == separator })
106+
107+
guard components.count == 6
108+
else { return nil }
109+
110+
for (index, subsequence) in components.enumerated() {
111+
112+
guard subsequence.count == 2,
113+
let byte = UInt8(hexadecimal: subsequence)
114+
else { return nil }
115+
116+
withUnsafeMutablePointer(to: &bytes) {
117+
$0.withMemoryRebound(to: UInt8.self, capacity: 6) {
118+
$0.advanced(by: index).pointee = byte
119+
}
120+
}
121+
}
122+
123+
self.init(bigEndian: BluetoothAddress(bytes: bytes))
124+
}
125+
126+
/// Convert a Bluetooth Address to its big endian string representation (e.g. `00:1A:7D:DA:71:13`).
127+
public var rawValue: String {
128+
let bytes = self.bigEndian.bytes
129+
return bytes.0.toHexadecimal()
130+
+ ":" + bytes.1.toHexadecimal()
131+
+ ":" + bytes.2.toHexadecimal()
132+
+ ":" + bytes.3.toHexadecimal()
133+
+ ":" + bytes.4.toHexadecimal()
134+
+ ":" + bytes.5.toHexadecimal()
135+
}
136+
}
137+
138+
// MARK: - CustomStringConvertible
139+
140+
extension BluetoothAddress: CustomStringConvertible {
141+
142+
public var description: String { rawValue }
143+
}
144+
145+
// MARK: - Data
146+
147+
public extension BluetoothAddress {
148+
149+
init?<Data: DataContainer>(data: Data) {
150+
guard data.count == type(of: self).length
151+
else { return nil }
152+
self.bytes = (data[0], data[1], data[2], data[3], data[4], data[5])
153+
}
154+
}
155+
156+
// MARK: - Codable
157+
158+
#if !hasFeature(Embedded)
159+
extension BluetoothAddress: Codable { }
160+
#endif

pico-w-ibeacon-sdk/BridgingHeader.h

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift open source project
4+
//
5+
// Copyright (c) 2024 Apple Inc. and the Swift project authors.
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
//
10+
//===----------------------------------------------------------------------===//
11+
12+
#pragma once
13+
14+
#include "btstack.h"
15+
#include "pico/stdlib.h"
16+
#include "pico/cyw43_arch.h"
17+
#include "pico/btstack_cyw43.h"

pico-w-ibeacon-sdk/ByteSwap.swift

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift open source project
4+
//
5+
// Copyright (c) 2024 Apple Inc. and the Swift project authors.
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
//
10+
//===----------------------------------------------------------------------===//
11+
12+
/// A Bluetooth value that is stored in the CPU native endianess format.
13+
public protocol ByteSwap {
14+
15+
/// A representation of this integer with the byte order swapped.
16+
var byteSwapped: Self { get }
17+
}
18+
19+
public extension ByteSwap {
20+
21+
/// Creates an instance from its little-endian representation, changing the
22+
/// byte order if necessary.
23+
///
24+
/// - Parameter value: A value to use as the little-endian representation of
25+
/// the new instance.
26+
init(littleEndian value: Self) {
27+
#if _endian(little)
28+
self = value
29+
#else
30+
self = value.byteSwapped
31+
#endif
32+
}
33+
34+
/// Creates an instance from its big-endian representation, changing the byte
35+
/// order if necessary.
36+
///
37+
/// - Parameter value: A value to use as the big-endian representation of the
38+
/// new instance.
39+
init(bigEndian value: Self) {
40+
#if _endian(big)
41+
self = value
42+
#else
43+
self = value.byteSwapped
44+
#endif
45+
}
46+
47+
/// The little-endian representation of this value.
48+
///
49+
/// If necessary, the byte order of this value is reversed from the typical
50+
/// byte order of this address. On a little-endian platform, for any
51+
/// address `x`, `x == x.littleEndian`.
52+
var littleEndian: Self {
53+
#if _endian(little)
54+
return self
55+
#else
56+
return byteSwapped
57+
#endif
58+
}
59+
60+
/// The big-endian representation of this value.
61+
///
62+
/// If necessary, the byte order of this value is reversed from the typical
63+
/// byte order of this address. On a big-endian platform, for any
64+
/// address `x`, `x == x.bigEndian`.
65+
var bigEndian: Self {
66+
#if _endian(big)
67+
return self
68+
#else
69+
return byteSwapped
70+
#endif
71+
}
72+
}

0 commit comments

Comments
 (0)