Tested on GitHub Actions GitHub GitHub issues Static Badge Mastodon Follow

OldMoofKit

一个用于与旧款 VanMoof 自行车(如 SmartBike、SmartS/X、Electrified S/X 或 S/X2)通信的 Swift 包

import OldMoofKit

let bike = try await Bike(username: "Johnny Mnemonic", password: "swordfish") // queries the vanmoof web api
try await bike.connect()
try await bike.playSound(.bell)
try await bike.set(lock: .unlocked)
bike.disconnect()

免责声明

OldMoofKit 不是 VanMoof B.V. 的官方库。此 Swift 包尚未达到官方稳定版本,因此某些功能可能无法按预期工作。您需要自行承担使用此库的风险。

功能特性

支持的自行车

型号 支持 已测试 替代方案
SmartBike vanbike-lib
SmartS/X vanbike-lib
Electrified S/X vanbike-lib
S/X 2 vanbike-lib
S/X 3 VanMoofKit, PyMoof

未测试 意味着理论上应该可以工作,但由于我无法接触到这样的自行车,这仍然是一个未知的领域。如果您拥有 SmartBike 或 Electrified S/X,我将不胜感激您能确认 OldMoofKit 是否工作,或者帮助我调试问题。

安装

Swift Package Manager

要使用 Apple 的 Swift Package Manager 进行集成,请将以下内容作为依赖项添加到您的 Package.swift 文件中

dependencies: [
    .package(url: "https://github.com/Jegge/OldMoofKit.git", from: "0.0.3")
]

或者导航到您的 Xcode 项目,然后选择 Swift Packages,点击“+”图标并搜索 OldMoofKit

Info.plist

由于 OldMoofKit 使用 CoreBluetooth 框架来建立与自行车的 BLE 连接,因此需要将 NSBluetoothAlwaysUsageDescription 键添加到您的应用程序的 Info.plist 文件中。

<key>NSBluetoothAlwaysUsageDescription</key>
<string>Establishing a bluetooth connection to your VanMoof Bike.</string>

如何获取自行车

从 VanMoof Web API

要首次获取自行车,请连接到 VanMoof Web API 并检索第一辆自行车。

let bike = try await Bike(username: "Johnny Mnemonic", password: "swordfish")

如果您拥有多辆自行车,则需要单独下载详细信息。然后,您可以通过蓝牙扫描周围环境,查找与这些详细信息匹配的自行车。

var api = VanMoof(apiUrl: VanMoof.Api.url, apiKey: VanMoof.Api.key)
try await api.authenticate(username: "Johnny Mnemonic", password: "swordfish")
let allDetails = try await api.bikeDetails()
let details = allDetails.first! // select one element from allDetails
let bike = try await Bike(scanningForBikeMatchingDetails: details)

从详细信息手动获取

如果您已经拥有自行车的详细信息,例如因为您之前已从 VanMoof 网站下载了它们,则可以手动构建自行车详细信息。然后,您可以通过蓝牙扫描周围环境,查找与这些详细信息匹配的自行车。

let details = try BikeDetails(bleProfile: .smartbike2016, macAddress: "12:34:56:78:9A:BC", encryptionKey: "00112233445566778899aabbccddeeff")
let bike = try await Bike(scanningForBikeMatchingDetails: details)

注意:请确保您的 bleProfilemacAddressencryptionKey 正确,否则将无法建立连接。其他参数仅为描述性文本。

注意:MAC 地址必须以 MAC-48 格式输入。

注意:加密密钥必须正好是 16 个字节长,并且必须以十六进制字符串形式输入。

Codable

Bikes 实现了 Codable,因此可以在需要时进行序列化/反序列化。

// store a bike as data
let data = try? JSONEncoder().encode(bike)

// read another bike back from data
let otherBike = JSONDecoder().decode(Bike.self, from: data)

连接

连接自行车非常简单,只需调用 connect 方法即可。

try await bike.connect()

只要您不手动断开连接,自行车将保持连接状态(实际上会自动重新建立断开的连接)。

bike.disconnect()

要检索当前连接状态,请查询自行车的 state

let state = bike.state
switch state {
    case .connected:
        // do something when connection get (re-)established
    case .disconnected:
        // do something when connection drops or closes
}

您还可以订阅 statePublisher,并在当前状态更改时收到通知。

let subscription: AnyCancellable = bike.statePublisher.receive(on: RunLoop.main).sink { state in
    // react to state ...
}

subscription.cancel()

注意:请确保在正确的线程上接收状态更改。

注意:断开连接时,请不要忘记取消您的订阅。

错误

自行车有一个专用的 errorPublisher,您可以订阅它以获取错误消息。

let subscription: AnyCancellable = bike.errorPublisher.receive(on: RunLoop.main).sink { error in
    // react to the error
    print("Error: \(error))
}

subscription.cancel()

注意:请确保在正确的线程上接收状态更改。

注意:断开连接时,请不要忘记取消您的订阅。

获取、观察和设置自行车属性

自行车具有各种属性,代表自行车的当前已知状态,例如

let lighting = bike.lighting

注意:如果您的自行车不支持某个属性,则其值将为 nil

对于每个属性,都有一个关联的 Publisher,允许监视值的变化。

let subscription: AnyCancellable = bike.lightingPublisher.receive(on: RunLoop.main).sink { state in
    // do something when lighting changed ...
}

subscription.cancel()

注意:请确保在正确的线程上接收状态更改。

注意:断开连接时,请不要忘记取消您的订阅。

每个属性都辅以一个 setter。调用此 setter 会将值直接传输到自行车。然后,自行车将发送通知,并且在收到该通知后,相应的属性将得到更新。如果您的自行车不支持某个属性,则调用 setter 将被忽略。

try await bike.set(lighting: .alwaysOn)

注意:将您的电动自行车的区域设置为与您所在国家/地区不符的值在某些司法管辖区可能是非法的。使用风险自负。

其他功能

播放声音

try await bike.playSound(.bell, 3) // play the bell sound thrice

设置备用代码

try await bike.set(backupCode: 123) // sets 123 as new backup code

唤醒自行车

有时,自行车可能不会立即对配置更改做出反应,因为它的智能模块处于休眠状态。为了确保您的命令即使在自行车进入休眠状态后也能执行,您可以再次唤醒它。当 wakeup 返回时,命令已发送到自行车。此时它可能仍处于 .standby 状态。为了确保自行车已唤醒,请考虑监听 moduleStatePublisher

try await bike.wakeup()

致谢与灵感来源

许可证

MIT 许可证

版权所有 (c) 2023 Sebastian Boettcher

特此授予任何人免费复制和分发本软件和相关文档文件(以下简称“软件”)的许可,包括但不限于使用、复制、修改、合并、发布、发行、再许可和/或销售软件副本的权利,并允许向已获得软件的人员提供软件,但须遵守以下条件:

上述版权声明和本许可声明应包含在软件的所有副本或主要部分中。

本软件按“现状”提供,不提供任何形式的明示或暗示保证,包括但不限于适销性、特定用途适用性和非侵权保证。在任何情况下,作者或版权持有者均不对任何索赔、损害或其他责任负责,无论是在合同诉讼、侵权诉讼或其他诉讼中,由软件或软件的使用或其他交易引起、产生或与之相关的责任。