Skip to content

Commit

Permalink
Feature/initial setup (#1)
Browse files Browse the repository at this point in the history
* Added Swift Package Manager information.

Commented out a bunch of debug statements.

* Added minimum iOS version for package.

Also fixed several warnings.

* Updated company name in copyright notices.

* Added comments.

* More documentation updates.

* Incorporated Mac address calculation changes.

* Updated naming of internal contents of package.

* Another tweak to the package.

* Marked classes public.

* Marked additional classes public.

* Marked a number of other classes and members public.

* Marked additional fields public.

* Marked accessor public.

* Marked additional items public.

* Marked additional class public.

* Added some additional documentation.

* Fixed more non-public items.

* Added MIT license to header of all files.

* Refactored DeviceManager to differentiate between Device and Probe.

* Gave DeviceManager.probes getter/setter.

* Marked a number of properties 'published'

* Added framework documentation.
  • Loading branch information
jmaha authored Dec 10, 2021
1 parent fea09e1 commit a86339a
Show file tree
Hide file tree
Showing 20 changed files with 1,729 additions and 89 deletions.
96 changes: 10 additions & 86 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,90 +1,14 @@
# Xcode
#
# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore

## User settings
.DS_Store
/.build
/Packages
/*.xcodeproj
xcuserdata/

## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9)
*.xcscmblueprint
*.xccheckout

## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4)
build/
DerivedData/
*.moved-aside
*.pbxuser
!default.pbxuser
*.mode1v3
!default.mode1v3
*.mode2v3
!default.mode2v3
*.perspectivev3
!default.perspectivev3

## Obj-C/Swift specific
*.hmap

## App packaging
*.ipa
*.dSYM.zip
*.dSYM

## Playgrounds
timeline.xctimeline
playground.xcworkspace

# Swift Package Manager
#
# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
# Packages/
# Package.pins
# Package.resolved
# *.xcodeproj
#
# Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata
# hence it is not needed unless you have added a package configuration file to your project
# .swiftpm

.build/

# CocoaPods
#
# We recommend against adding the Pods directory to your .gitignore. However
# you should judge for yourself, the pros and cons are mentioned at:
# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
#
# Pods/
#
# Add this line if you want to avoid checking in source code from the Xcode workspace
# *.xcworkspace

# Carthage
#
# Add this line if you want to avoid checking in source code from Carthage dependencies.
# Carthage/Checkouts

Carthage/Build/

# Accio dependency management
Dependencies/
.accio/

# fastlane
#
# It is recommended to not store the screenshots in the git repo.
# Instead, use fastlane to re-generate the screenshots whenever they are needed.
# For more information about the recommended setup visit:
# https://docs.fastlane.tools/best-practices/source-control/#source-control

fastlane/report.xml
fastlane/Preview.html
fastlane/screenshots/**/*.png
fastlane/test_output
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata

# Code Injection
#
# After new code Injection tools there's a generated folder /iOSInjectionProject
# https://github.com/johnno1962/injectionforxcode
# Swift package manager items
Package.resolved

iOSInjectionProject/
# Don't need this for this project as there are no shared
# schemes for the library.
.swiftpm
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
MIT License

Copyright (c) 2021 Combustion Inc
Copyright (c) 2021 Combustion Inc.

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
34 changes: 34 additions & 0 deletions Package.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// swift-tools-version:5.5
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription

let package = Package(
name: "combustion-ios-ble",
platforms: [
.iOS(.v13)
],
products: [
// Products define the executables and libraries a package produces, and make them visible to other packages.
.library(name: "CombustionBLE", targets: ["CombustionBLE"]),
],
dependencies: [
// Dependencies declare other packages that this package depends on.
.package(url: "https://github.com/apple/swift-collections", "1.0.0"..<"2.0.0")
],
targets: [
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
// Targets can depend on other targets in this package, and on products in packages this package depends on.
.target(
name: "CombustionBLE",
dependencies: [
.product(name: "Collections", package: "swift-collections", condition: nil)
],
path: "Sources/CombustionBLE"),
/*
.testTarget(
name: "combustion-ios-bleTests",
dependencies: ["combustion-ios-ble"]),
*/
]
)
147 changes: 145 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,145 @@
# combustion-ios-ble
Bluetooth Low Energy framework for communicating with Combustion, Inc. probe.
# Combustion Inc. Temperature Probe BLE Library

## Overview

This package enables communication with Combustion Inc. temperature probes probes. It uses Apple's Combine framework, subclassing `ObservableObject` to enable reactive UI development in SwiftUI (and is compatible with Storyboard approaches as well).

## Example project

An example iOS app illustrating the use of this framework is available in the [combustion-ios-example](https://github.com/combustion-inc/combustion-ios-example) repository.

## Usage information

### Swift Package Manager

Simply add this [Github repository](https://github.com/combustion-inc/combustion-ios-ble) to your project via [Swift Package Manager](https://developer.apple.com/documentation/swift_packages/adding_package_dependencies_to_your_app).

### Target capabilities

The following Capabilities need to be added to your Target (Signing & Capabilities tab in Target settings):

- Background BLE services
- Enable `Uses Bluetooth LE Accessories`

### Info.plist settings

Additionally, the following entries must be added to your project's `Info.plist`:

- Key: `Privacy - Bluetooth Always Usage Description`

- Value: Description of reason for Bluetooth access, e.g. "Bluetooth is used to communicate with hardware products."

- Key: `Privacy - Bluetooth Peripheral Usage Description`

- Value: Description of reason for Bluetooth access, e.g. "Bluetooth is used to communicate with hardware products."

## Important Classes

The following classes provide key functionality to apps incorporating this framework.

### `DeviceManager`

`DeviceManager` is an observable singleton class that maintains a dictionary of `Probe` objects that have been discovered over BLE.

#### Important members

- `probes` - Observable dictionary of probes (key is a `String` BLE UUID identifier, value is the `Probe` object)
- `getProbes()` - Function that returns array representation of the `Probe` objects in the `probes` dictionary.

### `Probe` (subclass of `Device`)

An instance of the `Probe` class representes an individual temperature probe that has been discovered via its advertising data. These are retrieved from the `DeviceManager.shared.probes` dictionary.

### Important members

- `serialNumber` - The Probe's unique serial number
- `name` - String format of probe serial number
- `macAddress` - Probe's MAC address
- `macAddressString` - String representation of Probe's MAC address
- `batteryLevel` - Battery level as reported by probe *NOTE: This is not yet implemented in probe firmware and will likely change to a boolean 'battery low' flag in the near future.*
- `currentTemperatures` - `ProbeTemperatures` struct containing the most recent temperatures read by the Probe.
- `currentTemperatures.values` - Array of these temperatures, in celsius, where `values[0]` is temperature sensor T1, and `values[7]` is temperature sensor T8.
- T1 - High-precision temperature sensor in tip of probe
- T2 - High-precision temperature sensor
- T3 - MCU temperature sensor
- T4 - High-precision temperature sensor
- T5 - High-temperature thermistor
- T6 - High-temperature thermistor
- T7 - High-temperature thermistor
- T8 - High-temperature thermistor on handle tip measuring ambient

- `id` - iOS-provided UUID for the device

- `rssi` - Signal strength between Probe and iOS device

- `maintainingConnection` - Whether the app is currently attempting to maintain a connection with the `Probe`, as directed by the `connect()` and `disconnect()` methods.

- `connect()` - Attempts to connect to device, and instructs framework to attempt to maintain a connection to this probe if it is lost.

- `disconnect()` - Instruct framework to disconnect from this probe, and to no longer attempt to maintain a connection to it.

- `stale` - `true` if no advertising data or notifications have been received from the Probe within the "staleness timeout" (15 seconds), or `false` if recent data has been received.

- `status` - `DeviceStatus` struct containing device status information.
- `minSequenceNumber` - Minimum sequence number of log records stored on the probe
- `maxSequenceNumber` - Maximum sequence number of log records stored on the probe

- `logsUpToDate` - Boolean value that indicates whether all log sequence numbers contained in the probe (determined by the `status` sequence number range) have been successfully retrieved and stored in the app's memory.

- `temperatureLog` - `ProbeTemperatureLog` class instance containing all logged temperatures that have been retrieved from the device, and logic that coordinates automatically retrieving all past records when connected to a Probe.
- Individual logged temperatures are provided in the `temperatureLog.dataPoints` array. These are instances of the struct `LoggedProbeDataPoint`, which contains the point's sequence number and corresponding `ProbeTemperatures` struct as explained above.

## Useful functions

The framework also provides `celsius()` and `fahrenheit()` functions that convert temperatures between these two formats.

## Common usage examples

### Importing this framework

To use the Combustion BLE framework in your own Swift file, import it:

```swift
import CombustionBLE
```

### Rendering list of probes

In SwiftUI, a list of probes can be rendered like so:

```swift
struct EngineeringProbeList: View {
@ObservedObject var deviceManager = DeviceManager.shared

var body: some View {
NavigationView {
List {
ForEach(deviceManager.probes.keys.sorted(), id: \.self) { key in
if let probe = deviceManager.probes[key] {
NavigationLink(destination: EngineeringProbeDetails(probe: probe)) {
EngineeringProbeRow(probe: probe)
}
}
}
}
.navigationTitle("Probes")
}
}
}
```

## Framework features coming soon

The following features are planned for near-term development but are not yet implemented in this version of the Combustion BLE Framework.

### Set ring color

The framework will provide functions allowing a probe's identifying silicone ring color to be configured by the user (colors TBA).

### Set numeric ID

The framework will provide functions allowing a Probe's numeric ID (1-8) to be configured by the user.

### Firmware update

The framework will provide methods for updating a Probe's firmware with a signed firmware image.
96 changes: 96 additions & 0 deletions Sources/CombustionBLE/AdvertisingData.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
// AdvertisingData.swift

/*--
MIT License

Copyright (c) 2021 Combustion Inc.

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
--*/

import Foundation

/// Enumeration of Combustion, Inc. product types.
public enum CombustionProductType: UInt8 {
case UNKNOWN = 0x00
case PROBE = 0x01
case NODE = 0x02
}


/// Struct containing advertising data received from device.
public struct AdvertisingData {
/// Type of Combustion product
public let type: CombustionProductType
/// Product serial number
public let serialNumber: UInt32
/// Latest temperatures read by device
public let temperatures: ProbeTemperatures

private enum Constants {
// Locations of data in advertising packets
static let PRODUCT_TYPE_RANGE = 2..<3
static let SERIAL_RANGE = 3..<7
static let TEMPERATURE_RANGE = 7..<20
}
}

extension AdvertisingData {
init?(fromData : Data?) {
guard let data = fromData else { return nil }
guard data.count >= 19 else { return nil }

// Product type (1 byte)
let rawType = data.subdata(in: Constants.PRODUCT_TYPE_RANGE)
let typeByte = [UInt8](rawType)
type = CombustionProductType(rawValue: typeByte[0]) ?? .UNKNOWN

// Device Serial number (4 bytes)
// Reverse the byte order (this is a little-endian packed bitfield)
let rawSerial = data.subdata(in: Constants.SERIAL_RANGE)
var revSerial : [UInt8] = []
for byte in rawSerial {
revSerial.insert(byte as UInt8, at: 0)
}

let serialArray = [UInt8](revSerial)
var value: UInt32 = 0
for byte in serialArray {
value = value << 8
value = value | UInt32(byte)
}

serialNumber = value


// Temperatures (8 13-bit) values
let tempData = data.subdata(in: Constants.TEMPERATURE_RANGE)
temperatures = ProbeTemperatures.fromRawData(data: tempData)
}
}


extension AdvertisingData {
// Fake data initializer for previews
public init?(fakeSerial: UInt32) {
type = .PROBE
temperatures = ProbeTemperatures.withFakeData()
serialNumber = fakeSerial
}
}
Loading

0 comments on commit a86339a

Please sign in to comment.