一个用于与 VanMoof S3 & X3 自行车 🚲 通信的 Swift Package。
import VanMoofKit
let vanMoof = VanMoof()
try await vanMoof.login(
username: "knight.rider@vanmoof.com",
password: "********"
)
let bikes = try await vanMoof.bikes()
for bike in bikes {
try await bike.connect()
try await bike.playSound()
}
重要提示
VanMoofKit 不是 VanMoof B.V 的官方库。此 Swift Package 允许访问自行车的某些功能,但在某些司法管辖区使用这些功能可能是非法的。由于此库尚未达到官方稳定版本,因此某些功能可能尚未可用或可能无法按预期工作。
查看示例应用程序,了解 VanMoofKit 的实际应用。只需打开 Example/Example.xcodeproj
并运行 "Example" 方案。
VanMoofKit Swift Package 包含一个 CLI,它允许您轻松导出您的 VanMoof 帐户数据,包括自行车的加密密钥。
$ swift run vanmoof export --username "knight.rider@vanmoof.com" --password "********" --outputDirectory "~/Downloads"
注意
登录凭据不会被持久保存或记录,它们仅用于向 VanMoof API 进行身份验证。
--outputDirectory
参数是可选的。如果未指定,导出内容将自动保存在 ~/Desktop
。
要使用 Apple 的 Swift Package Manager 进行集成,请将以下内容作为依赖项添加到您的 Package.swift
中:
dependencies: [
.package(url: "https://github.com/SvenTiigi/VanMoofKit.git", from: "0.0.7")
]
或者,导航到您的 Xcode 项目,然后选择 Swift Packages
,单击“+”图标并搜索 VanMoofKit
。
由于 VanMoofKit 使用 CoreBluetooth
框架建立与自行车的 BLE 连接,因此需要将 NSBluetoothAlwaysUsageDescription
键添加到应用程序的 Info.plist 文件中。
<key>NSBluetoothAlwaysUsageDescription</key>
<string>Establishing a bluetooth connection to your VanMoof Bike.</string>
要检索 VanMoof 帐户的自行车,您首先需要初始化一个 VanMoof
实例。
let vanMoof = VanMoof()
要进行身份验证,只需在 VanMoof
对象的实例上调用 login
函数。
try await vanMoof.login(
username: "kight.rider@vanmoof.com",
password: "********"
)
注意
登录凭据不会被持久保存或记录,它们仅用于向 VanMoof API 进行身份验证。
利用 vanMoof.isAuthenticated
属性来检查用户是否已登录。
if vanMoof.isAuthenticated {
// ...
}
登录会生成一个 VanMoof.Token
,该令牌会自动存储在 VanMoofTokenStore
的实例中。您可以在创建 VanMoof
实例时配置令牌的持久性。
let vanMoof = VanMoof(
// Specify an instance which conforms to the `VanMoofTokenStore` protocol.
// Predefined implementations:
// - KeychainVanMoofTokenStore
// - LARightVanMoofTokenStore
// - NSUbiquitousVanMoofTokenStore
// - UserDefaultsVanMoofTokenStore
// - InMemoryVanMoofTokenStore
tokenStore: .keychain()
)
注意
默认情况下,将使用 UserDefaultsVanMoofTokenStore
来存储 VanMoof.Token
。
登录成功后,您可以检索用户个人资料和关联的自行车。
// Retrieve the user
let user: VanMoof.User = try await vanMoof.user()
print("Available Bikes", user.bikes)
// If you want to directly retrieve the bikes call:
let bikes: [VanMoof.Bike] = try vanMoof.bikes()
要注销当前用户,请调用
vanMoof.logout()
注意
注销用户不会影响任何可用的 VanMoof.Bike 实例。开发者有责任终止与 VanMoof.Bike 的任何打开的连接。
一些信息,例如自行车的名称、车架号等,无需与自行车建立有效连接即可获得。您可以通过 bike.details
属性访问这些信息。
let details: VanMoof.Bike.Details = bike.details
print(details.name)
print(details.macAddress)
print(details.frameNumber)
// Or access the details properties directly
// (powered by the @dynamicMemberLookup attribute)
print(bike.name, bike.macAddress, bike.frameNumber)
由于 VanMoof.Bike
符合 Codable
协议,您可以从本地 JSON 文件创建实例。
// Retrieve the JSON file url from the main bundle
let bikeJSONFileURL = Bundle.main.url(forResource: "Bike", withExtension: "json")!
// Try to load the contents of the JSON file
let bikeJSONData = try Data(contentsOf: bikeJSONFileURL)
// Try to decode VanMoof Bike from JSON data
let bike: VanMoof.Bike = try JSONDecoder().decode(VanMoof.Bike.self, from: bikeJSONData)
要建立与 VanMoof.Bike
的连接,请调用
// Try to connect to the bike
try await bike.connect()
您可以按以下方式检索连接的当前状态。
// Switch on connectionState
switch bike.connectionState {
case .disconnected:
print("Disconnected")
case .discovering:
print("Discovering")
case .connecting:
print("Connecting")
case .connected:
print("Connected")
case .disconnecting:
print("Disconnecting")
}
// Or make use of convience properties such as:
let isConnected: Bool = bike.isConnected
let isDisconnected: Bool = bike.isDisconnected
// Alternatively you can use a Publisher
bike.connectionStatePublisher
.sink { connectionState in
// ...
}
// Or a specialized publisher which only emits connection errors
bike.connectionErrorPublisher
.sink { connectionError in
// ...
}
此外,您可以通过以下方式监控连接的信号强度
// Retrieve the current signal strength
let signalStrength: VanMoof.Bike.SignalStrength = try await bike.signalStrength
// A Publisher that emits the current signal strength in a given update interval
bike.signalStrengthPublisher(
updateInterval: 5
).sink { signalStrength in
// ...
}
如果您希望终止连接,只需调用
// Disconnect from the bike
try await bike.disconnect()
switch try await bike.moduleState {
case .on:
break
case .off:
break
default:
break
}
bike.moduleStatePublisher.sink { moduleState in
// ...
}
try await bike.set(moduleState: .on)
// Alias for setting the module state to on
try await bike.wakeUp()
switch try await bike.lockState {
case .unlocked:
break
case .locked:
break
case .awaitingUnlock:
break
}
bike.lockStatePublisher.sink { lockState in
// ...
}
// Unlock Bike
try await bike.unlock()
let batteryLevel = try await bike.batteryLevel
bike.batteryLevelPublisher.sink { batteryLevel in
// ...
}
switch try await bike.batteryState {
case .notCharging;
break
case .charging:
break
}
bike.batteryStatePublisher.sink { batteryState in
// ...
}
switch try await bike.speedLimit {
case .europe:
print("Europe 25 km/h")
case .unitedStates:
print("United States 32 km/h")
case .japan:
print("Japan 24 km/h")
}
bike.speedLimitPublisher.sink { speedLimit in
// ...
}
警告
在某些司法管辖区更改速度限制可能是非法的。
try await bike.set(speedLimit: .unitedStates)
switch try await bike.powerLevel {
case .off:
break
case .one:
break
case .two:
break
case .three:
break
case .four:
break
}
bike.powerLevelPublisher.sink { powerLevel in
// ...
}
try await bike.set(powerLevel: .four)
switch try await bike.lightMode {
case .auto:
break
case .alwaysOn:
break
case .off:
break
}
bike.lightModePublisher.sink { lightMode in
// ...
}
try await bike.set(lightMode: .alwaysOn)
switch try await bike.bellSound {
case .sonar:
break
case .bell:
break
case .party:
break
case .foghorn:
break
}
bike.bellSoundPublisher.sink { bellSound in
// ...
}
try await bike.set(bellSound: .party)
try await bike.play(sound: .scrollingTone)
try await bike.play(sound: .beepPositive)
try await bike.play(sound: .alarmStageOne)
// An integer in the range from 0 to 100
let soundVolume: Int = try await bike.soundVolume
let totalDistance: VanMoof.Bike.Distance = try await bike.totalDistance
bike.totalDistancePublisher.sink { totalDistance in
// ...
}
switch try await bike.unitSystem {
case .metric:
break
case .imperial:
break
}
bike.unitSystemPublisher.sink { unitSystem in
// ...
}
try await bike.set(unitSystem: .metric)
let bikeFirmwareVersion: String = try await bike.firmwareVersion
let bleChipFirmwareVersion: String = try await bike.bleChipFirmwareVersion
let eShifterFirmwareVersion: String = try await bike.eShifterFirmwareVersion