Skip to content

Commit d226fce

Browse files
committed
Add ESP32 iBeacon example
1 parent 60a648b commit d226fce

30 files changed

+2419
-0
lines changed

esp32-ibeacon-sdk/CMakeLists.txt

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
cmake_minimum_required(VERSION 3.29)
2+
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
3+
project(main)

esp32-ibeacon-sdk/README.md

+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# esp32-ibeacon-sdk
2+
3+
This example demonstrates how to integrate with the ESP-IDF SDK via CMake and how to use the the SDK to advertise as a Bluetooth iBeacon from Swift. This example is specifically made for the RISC-V MCUs from ESP32 (the Xtensa MCUs are not currently supported by Swift).
4+
5+
## Requirements
6+
7+
- Set up the [ESP-IDF](https://docs.espressif.com/projects/esp-idf/en/stable/esp32/) development environment. Follow the steps in the [ESP32-C6 "Get Started" guide](https://docs.espressif.com/projects/esp-idf/en/v5.2/esp32c6/get-started/index.html).
8+
- Make sure you specifically set up development for the RISC-V ESP32-C6, and not the Xtensa based products.
9+
10+
- Before trying to use Swift with the ESP-IDF SDK, make sure your environment works and can build the provided C/C++ sample projects, in particular:
11+
- Try building and running the "get-started/blink" example from ESP-IDF written in C.
12+
13+
## Building
14+
15+
- Make sure you have a recent nightly Swift toolchain that has Embedded Swift support.
16+
- If needed, run export.sh to get access to the idf.py script from ESP-IDF.
17+
- Specify the nightly toolchain to be used via the `TOOLCHAINS` environment variable and the target board type by using `idf.py set-target`.
18+
``` console
19+
$ cd esp32-ibeacon-sdk
20+
$ export TOOLCHAINS=...
21+
$ . <path-to-esp-idf>/export.sh
22+
$ idf.py set-target esp32c6
23+
$ idf.py build
24+
```
25+
26+
## Running
27+
28+
- Connect the Esp32-C6-Bug board over a USB cable to your Mac. Alternatively you can just connect external LED to GPIO pin 8 on any other board.
29+
- Connect RX pin of USB-UART converter to TX0 pin of your board if you need serial ouput. You may also need to connect GND converter pin to the GND pin of the board.
30+
- Use `idf.py` to upload the firmware and to run it:
31+
32+
```console
33+
$ idf.py flash
34+
```
35+
36+
- Find the peripheral advertised as `ESP32-C6 XX:XX:XX:XX:XX:XX` in a Bluetooth scanner app like LightBlue or nRF Connect.
37+
38+
![LightBlue](assets/images/lightblue.jpg)
39+
![nRF Connect](assets/images/nrfconnect.jpg)
31.5 KB
Loading
Loading
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
+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
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+
#include <stdio.h>
13+
14+
#include "freertos/FreeRTOS.h"
15+
#include "freertos/task.h"
16+
#include "driver/gpio.h"
17+
#include "sdkconfig.h"
18+
#include "nimble/ble.h"
19+
#include "nimble/transport.h"
20+
#include "host/ble_hs.h"
21+
#include "host/ble_gap.h"
22+
#include "esp_bt.h"
23+
#include "esp_task.h"
24+
#include "esp_nimble_cfg.h"
25+
#include "esp_log.h"
26+
//#include "nvs_flash.h"
27+
#include "esp_bt.h"
28+
29+
#ifndef MYNEWT_VAL_BLE_LL_WHITELIST_SIZE
30+
#define MYNEWT_VAL_BLE_LL_WHITELIST_SIZE CONFIG_BT_NIMBLE_WHITELIST_SIZE
31+
#endif

esp32-ibeacon-sdk/main/ByteSwap.swift

+72
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+
}

esp32-ibeacon-sdk/main/CMakeLists.txt

+88
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
# Register the app as an IDF component
2+
idf_component_register(
3+
SRCS /dev/null # We don't have any C++ sources
4+
PRIV_INCLUDE_DIRS "."
5+
LDFRAGMENTS "linker.lf"
6+
REQUIRES bt driver
7+
)
8+
9+
idf_build_get_property(target IDF_TARGET)
10+
idf_build_get_property(arch IDF_TARGET_ARCH)
11+
12+
if("${arch}" STREQUAL "xtensa")
13+
message(FATAL_ERROR "Not supported target: ${target}")
14+
endif()
15+
16+
if(${target} STREQUAL "esp32c2" OR ${target} STREQUAL "esp32c3")
17+
set(march_flag "rv32imc_zicsr_zifencei")
18+
set(mabi_flag "ilp32")
19+
elseif(${target} STREQUAL "esp32p4")
20+
set(march_flag "rv32imafc_zicsr_zifencei")
21+
set(mabi_flag "ilp32f")
22+
else()
23+
set(march_flag "rv32imac_zicsr_zifencei")
24+
set(mabi_flag "ilp32")
25+
endif()
26+
27+
# Clear the default COMPILE_OPTIONS which include a lot of C/C++ specific compiler flags that the Swift compiler will not accept
28+
get_target_property(var ${COMPONENT_LIB} COMPILE_OPTIONS)
29+
set_target_properties(${COMPONENT_LIB} PROPERTIES COMPILE_OPTIONS "")
30+
31+
# Compute -Xcc flags to set up the C and C++ header search paths for Swift (for bridging header).
32+
set(SWIFT_INCLUDES)
33+
foreach(dir ${CMAKE_C_IMPLICIT_INCLUDE_DIRECTORIES})
34+
string(CONCAT SWIFT_INCLUDES ${SWIFT_INCLUDES} "-Xcc ")
35+
string(CONCAT SWIFT_INCLUDES ${SWIFT_INCLUDES} "-I${dir} ")
36+
endforeach()
37+
foreach(dir ${CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES})
38+
string(CONCAT SWIFT_INCLUDES ${SWIFT_INCLUDES} "-Xcc ")
39+
string(CONCAT SWIFT_INCLUDES ${SWIFT_INCLUDES} "-I${dir} ")
40+
endforeach()
41+
42+
# Swift compiler flags to build in Embedded Swift mode, optimize for size, choose the right ISA, ABI, etc.
43+
target_compile_options(${COMPONENT_LIB} PUBLIC "$<$<COMPILE_LANGUAGE:Swift>:SHELL:
44+
-target riscv32-none-none-eabi
45+
-Xfrontend -function-sections -enable-experimental-feature Embedded -wmo -parse-as-library -Osize
46+
-Xcc -march=${march_flag} -Xcc -mabi=${mabi_flag}
47+
48+
-pch-output-dir /tmp
49+
-Xfrontend -enable-single-module-llvm-emission
50+
51+
${SWIFT_INCLUDES}
52+
53+
-import-bridging-header ${CMAKE_CURRENT_LIST_DIR}/BridgingHeader.h
54+
>")
55+
56+
# Enable Swift support in CMake, force Whole Module builds (required by Embedded Swift), and use "CMAKE_Swift_COMPILER_WORKS" to
57+
# skip the trial compilations which don't (yet) correctly work when cross-compiling.
58+
set(CMAKE_Swift_COMPILER_WORKS YES)
59+
set(CMAKE_Swift_COMPILATION_MODE_DEFAULT wholemodule)
60+
set(CMAKE_Swift_COMPILATION_MODE wholemodule)
61+
enable_language(Swift)
62+
63+
# List of Swift source files to build.
64+
target_sources(${COMPONENT_LIB}
65+
PRIVATE
66+
Main.swift
67+
Error.swift
68+
NimBLE.swift
69+
Error.swift
70+
BluetoothAddress.swift
71+
ByteSwap.swift
72+
CompanyIdentifier.swift
73+
LowEnergyAddressType.swift
74+
LowEnergyAdvertisingData.swift
75+
Data.swift
76+
String.swift
77+
Hexadecimal.swift
78+
Encoder.swift
79+
GAPData.swift
80+
GAPDataType.swift
81+
GAPFlags.swift
82+
GAPShortLocalName.swift
83+
GAPManufacturerSpecificData.swift
84+
UInt128.swift
85+
UUID.swift
86+
iBeacon.swift
87+
Integer.swift
88+
)

0 commit comments

Comments
 (0)