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 ESP32 #70

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
3 changes: 3 additions & 0 deletions esp32-ibeacon-sdk/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
cmake_minimum_required(VERSION 3.29)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(main)
39 changes: 39 additions & 0 deletions esp32-ibeacon-sdk/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# esp32-ibeacon-sdk

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).

## Requirements

- 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).
- Make sure you specifically set up development for the RISC-V ESP32-C6, and not the Xtensa based products.

- 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:
- Try building and running the "get-started/blink" example from ESP-IDF written in C.

## Building

- Make sure you have a recent nightly Swift toolchain that has Embedded Swift support.
- If needed, run export.sh to get access to the idf.py script from ESP-IDF.
- Specify the nightly toolchain to be used via the `TOOLCHAINS` environment variable and the target board type by using `idf.py set-target`.
``` console
$ cd esp32-ibeacon-sdk
$ export TOOLCHAINS=...
$ . <path-to-esp-idf>/export.sh
$ idf.py set-target esp32c6
$ idf.py build
```

## Running

- 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.
- 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.
- Use `idf.py` to upload the firmware and to run it:

```console
$ idf.py flash
```

- Find the peripheral advertised as `ESP32-C6 XX:XX:XX:XX:XX:XX` in a Bluetooth scanner app like LightBlue or nRF Connect.

![LightBlue](assets/images/lightblue.jpg)
![nRF Connect](assets/images/nrfconnect.jpg)
Binary file added esp32-ibeacon-sdk/assets/images/lightblue.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
160 changes: 160 additions & 0 deletions esp32-ibeacon-sdk/main/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
31 changes: 31 additions & 0 deletions esp32-ibeacon-sdk/main/BridgingHeader.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
//===----------------------------------------------------------------------===//
//
// 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
//
//===----------------------------------------------------------------------===//

#include <stdio.h>

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/gpio.h"
#include "sdkconfig.h"
#include "nimble/ble.h"
#include "nimble/transport.h"
#include "host/ble_hs.h"
#include "host/ble_gap.h"
#include "esp_bt.h"
#include "esp_task.h"
#include "esp_nimble_cfg.h"
#include "esp_log.h"
//#include "nvs_flash.h"
#include "esp_bt.h"

#ifndef MYNEWT_VAL_BLE_LL_WHITELIST_SIZE
#define MYNEWT_VAL_BLE_LL_WHITELIST_SIZE CONFIG_BT_NIMBLE_WHITELIST_SIZE
#endif
72 changes: 72 additions & 0 deletions esp32-ibeacon-sdk/main/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
}
}
88 changes: 88 additions & 0 deletions esp32-ibeacon-sdk/main/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# Register the app as an IDF component
idf_component_register(
SRCS /dev/null # We don't have any C++ sources
PRIV_INCLUDE_DIRS "."
LDFRAGMENTS "linker.lf"
REQUIRES bt driver
)

idf_build_get_property(target IDF_TARGET)
idf_build_get_property(arch IDF_TARGET_ARCH)

if("${arch}" STREQUAL "xtensa")
message(FATAL_ERROR "Not supported target: ${target}")
endif()

if(${target} STREQUAL "esp32c2" OR ${target} STREQUAL "esp32c3")
set(march_flag "rv32imc_zicsr_zifencei")
set(mabi_flag "ilp32")
elseif(${target} STREQUAL "esp32p4")
set(march_flag "rv32imafc_zicsr_zifencei")
set(mabi_flag "ilp32f")
else()
set(march_flag "rv32imac_zicsr_zifencei")
set(mabi_flag "ilp32")
endif()

# Clear the default COMPILE_OPTIONS which include a lot of C/C++ specific compiler flags that the Swift compiler will not accept
get_target_property(var ${COMPONENT_LIB} COMPILE_OPTIONS)
set_target_properties(${COMPONENT_LIB} PROPERTIES COMPILE_OPTIONS "")

# Compute -Xcc flags to set up the C and C++ header search paths for Swift (for bridging header).
set(SWIFT_INCLUDES)
foreach(dir ${CMAKE_C_IMPLICIT_INCLUDE_DIRECTORIES})
string(CONCAT SWIFT_INCLUDES ${SWIFT_INCLUDES} "-Xcc ")
string(CONCAT SWIFT_INCLUDES ${SWIFT_INCLUDES} "-I${dir} ")
endforeach()
foreach(dir ${CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES})
string(CONCAT SWIFT_INCLUDES ${SWIFT_INCLUDES} "-Xcc ")
string(CONCAT SWIFT_INCLUDES ${SWIFT_INCLUDES} "-I${dir} ")
endforeach()

# Swift compiler flags to build in Embedded Swift mode, optimize for size, choose the right ISA, ABI, etc.
target_compile_options(${COMPONENT_LIB} PUBLIC "$<$<COMPILE_LANGUAGE:Swift>:SHELL:
-target riscv32-none-none-eabi
-Xfrontend -function-sections -enable-experimental-feature Embedded -wmo -parse-as-library -Osize
-Xcc -march=${march_flag} -Xcc -mabi=${mabi_flag}

-pch-output-dir /tmp
-Xfrontend -enable-single-module-llvm-emission

${SWIFT_INCLUDES}

-import-bridging-header ${CMAKE_CURRENT_LIST_DIR}/BridgingHeader.h
>")

# Enable Swift support in CMake, force Whole Module builds (required by Embedded Swift), and use "CMAKE_Swift_COMPILER_WORKS" to
# skip the trial compilations which don't (yet) correctly work when cross-compiling.
set(CMAKE_Swift_COMPILER_WORKS YES)
set(CMAKE_Swift_COMPILATION_MODE_DEFAULT wholemodule)
set(CMAKE_Swift_COMPILATION_MODE wholemodule)
enable_language(Swift)

# List of Swift source files to build.
target_sources(${COMPONENT_LIB}
PRIVATE
Main.swift
Error.swift
NimBLE.swift
Error.swift
BluetoothAddress.swift
ByteSwap.swift
CompanyIdentifier.swift
LowEnergyAddressType.swift
LowEnergyAdvertisingData.swift
Data.swift
String.swift
Hexadecimal.swift
Encoder.swift
GAPData.swift
GAPDataType.swift
GAPFlags.swift
GAPShortLocalName.swift
GAPManufacturerSpecificData.swift
UInt128.swift
UUID.swift
iBeacon.swift
Integer.swift
)
Loading