Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add iBeacon example for Pico W #69

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ Each example in this repository contains build and deployment instructions, howe
| [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"> |
| [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"> |
| [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"> |
| [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"> |
| [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"> |
| [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"> |
| [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"> |
Expand Down
125 changes: 125 additions & 0 deletions pico-w-ibeacon-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-ibeacon-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
17 changes: 17 additions & 0 deletions pico-w-ibeacon-sdk/BridgingHeader.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
//===----------------------------------------------------------------------===//
//
// 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
//
//===----------------------------------------------------------------------===//

#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-ibeacon-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