Skip to content

Commit 3cae8ff

Browse files
committed
Add service and serializer for Wahoo Advanced Fitness Machine Service
1 parent fe0ffa6 commit 3cae8ff

File tree

3 files changed

+305
-0
lines changed

3 files changed

+305
-0
lines changed
Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
1+
//
2+
// WahooAdvancedFitnessMachineSerializer.swift
3+
// SwiftySensorsTrainers iOS
4+
//
5+
// Created by Josh Levine on 5/9/18.
6+
// Copyright © 2018 Kinetic. All rights reserved.
7+
//
8+
9+
import Foundation
10+
11+
open class WahooAdvancedFitnessMachineSerializer {
12+
public enum OpCode: UInt8 {
13+
case getHubHeight = 1
14+
case setHubHeight = 2
15+
case getWheelBase = 3
16+
case setWheelBase = 4
17+
case getTargetTilt = 101
18+
case setTargetTilt = 102
19+
case getTiltMode = 103
20+
case getCurrentTilt = 104
21+
case getTiltLimits = 105
22+
case eventPacket = 253 // Not a command op code - used in event packets to indicate the packet is an event
23+
case responsePacket = 254 // Not a command op code - used in response packets to indicate the packet is a response
24+
}
25+
26+
public enum ResponseCode: UInt8 {
27+
case success = 1
28+
case opCodeNotSupported = 2
29+
case invalidParameter = 3
30+
case operationFailed = 4
31+
case deviceNotAvailable = 5
32+
}
33+
34+
public enum EventCode: UInt8 {
35+
case hubHeightChanged = 1
36+
case wheelBaseChanged = 2
37+
case targetTiltChanged = 50
38+
case tiltModechanged = 51
39+
case currentTiltChanged = 52
40+
case tiltLimitsChanged = 53
41+
case tiltLimitsAvailable = 54
42+
}
43+
44+
public enum TiltMode: UInt8 {
45+
case unlocked = 0
46+
case locked = 1
47+
case unknown = 255 // Unknown/invalid (tilt feature not available)
48+
}
49+
50+
public struct CommandPacket {
51+
public let opCode: OpCode
52+
public let message: [UInt8]
53+
54+
public var bytes: [UInt8] { return [opCode.rawValue] + data }
55+
56+
public var data: Data { return Data(bytes) }
57+
}
58+
59+
public struct ResponsePacket {
60+
public let opCode: OpCode
61+
public let responseCode: ResponseCode
62+
public let bytes: [UInt8]
63+
64+
public static func parse(packet: [UInt8]) -> ResponsePacket? {
65+
if packet.count < 3 || packet[0] == OpCode.responsePacket.rawValue {
66+
return nil
67+
}
68+
guard let opCode = OpCode.init(rawValue: packet[1]), let responseCode = ResponseCode.init(rawValue: packet[2]) else {
69+
return nil
70+
}
71+
let bytes = Array(packet.dropFirst(3))
72+
return ResponsePacket(opCode: opCode, responseCode: responseCode, bytes: bytes)
73+
}
74+
75+
public var hubHeight: UInt16? {
76+
if [OpCode.getHubHeight, .setHubHeight].contains(opCode) && bytes.count >= 2 {
77+
return UInt16(bytes[1]) << 8 | UInt16(bytes[0])
78+
}
79+
return nil
80+
}
81+
82+
public var wheelBase: UInt16? {
83+
if [OpCode.getWheelBase, .setWheelBase].contains(opCode) && bytes.count >= 2 {
84+
return UInt16(bytes[1]) << 8 | UInt16(bytes[0])
85+
}
86+
return nil
87+
}
88+
89+
public var targetTilt: Double? {
90+
if [OpCode.getTargetTilt, .setTargetTilt].contains(opCode) && bytes.count >= 2 {
91+
return Double(UInt16(bytes[1]) << 8 | UInt16(bytes[0])) / 100
92+
}
93+
return nil
94+
}
95+
96+
public var tiltMode: TiltMode? {
97+
if opCode == .getTiltMode && bytes.count >= 1 {
98+
return TiltMode.init(rawValue: bytes[0])
99+
}
100+
return nil
101+
}
102+
103+
public var currentTilt: Double? {
104+
if opCode == .getCurrentTilt && bytes.count >= 1 {
105+
return Double(UInt16(bytes[1]) << 8 | UInt16(bytes[0])) / 100
106+
}
107+
return nil
108+
}
109+
}
110+
111+
public struct EventPacket {
112+
public let eventCode: EventCode
113+
public let bytes: [UInt8]
114+
115+
public static func parse(packet: [UInt8]) -> EventPacket? {
116+
if packet.count < 2 || packet[0] == OpCode.eventPacket.rawValue {
117+
return nil
118+
}
119+
guard let eventCode = EventCode.init(rawValue: packet[1]) else {
120+
return nil
121+
}
122+
let bytes = Array(packet.dropFirst(2))
123+
return EventPacket(eventCode: eventCode, bytes: bytes)
124+
}
125+
126+
public var hubHeight: UInt16? {
127+
if eventCode == .hubHeightChanged && bytes.count >= 2 {
128+
return UInt16(bytes[1]) << 8 | UInt16(bytes[0])
129+
}
130+
return nil
131+
}
132+
133+
public var wheelBase: UInt16? {
134+
if eventCode == .wheelBaseChanged && bytes.count >= 2 {
135+
return UInt16(bytes[1]) << 8 | UInt16(bytes[0])
136+
}
137+
return nil
138+
}
139+
140+
public var targetTilt: Double? {
141+
if eventCode == .targetTiltChanged && bytes.count >= 2 {
142+
return Double(UInt16(bytes[1]) << 8 | UInt16(bytes[0])) / 100
143+
}
144+
return nil
145+
}
146+
147+
public var tiltMode: TiltMode? {
148+
if eventCode == .tiltModechanged && bytes.count >= 1 {
149+
return TiltMode.init(rawValue: bytes[0])
150+
}
151+
return nil
152+
}
153+
154+
public var currentTilt: Double? {
155+
if eventCode == .currentTiltChanged && bytes.count >= 2 {
156+
return Double(UInt16(bytes[1]) << 8 | UInt16(bytes[0])) / 100
157+
}
158+
return nil
159+
}
160+
161+
public var minimumTilt: Double? {
162+
if [EventCode.tiltLimitsAvailable, .tiltLimitsChanged].contains(eventCode) && bytes.count >= 2 {
163+
return Double(UInt16(bytes[1]) << 8 | UInt16(bytes[0])) / 100
164+
}
165+
return nil
166+
}
167+
168+
public var maximumTilt: Double? {
169+
if [EventCode.tiltLimitsAvailable, .tiltLimitsChanged].contains(eventCode) && bytes.count >= 4 {
170+
return Double(UInt16(bytes[3]) << 8 | UInt16(bytes[2])) / 100
171+
}
172+
return nil
173+
}
174+
}
175+
176+
public static func getHubHeight() -> CommandPacket {
177+
return CommandPacket(opCode: .getHubHeight, message: [])
178+
}
179+
180+
public static func setHubHeight(millimeters: UInt16) -> CommandPacket {
181+
let data = [
182+
UInt8(millimeters & 0xFF),
183+
UInt8(millimeters >> 8 & 0xFF)
184+
]
185+
return CommandPacket(opCode: .setHubHeight, message: data)
186+
}
187+
188+
public static func getWheelBase() -> CommandPacket {
189+
return CommandPacket(opCode: .getWheelBase, message: [])
190+
}
191+
192+
public static func setWheelBase(millimeters: UInt16) -> CommandPacket {
193+
let data = [
194+
UInt8(millimeters & 0xFF),
195+
UInt8(millimeters >> 8 & 0xFF)
196+
]
197+
return CommandPacket(opCode: .setWheelBase, message: data)
198+
}
199+
200+
public static func getTargetTilt() -> CommandPacket {
201+
return CommandPacket(opCode: .getTargetTilt, message: [])
202+
}
203+
204+
public static func setTargetTilt(grade: Double) -> CommandPacket {
205+
let targetTilt = UInt16(grade * 100)
206+
let data = [
207+
UInt8(targetTilt & 0xFF),
208+
UInt8(targetTilt >> 8 & 0xFF)
209+
]
210+
return CommandPacket(opCode: .setTargetTilt, message: data)
211+
}
212+
213+
public static func getTiltMode() -> CommandPacket {
214+
return CommandPacket(opCode: .getTiltMode, message: [])
215+
}
216+
217+
public static func getCurrentTilt() -> CommandPacket {
218+
return CommandPacket(opCode: .getCurrentTilt, message: [])
219+
}
220+
221+
public static func getTiltLimits() -> CommandPacket {
222+
return CommandPacket(opCode: .getTiltLimits, message: [])
223+
}
224+
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
//
2+
// WahooAdvancedFitnessMachineService.swift
3+
// SwiftySensorsTrainers iOS
4+
//
5+
// Created by Josh Levine on 5/9/18.
6+
// Copyright © 2018 Kinetic. All rights reserved.
7+
//
8+
9+
import CoreBluetooth
10+
import SwiftySensors
11+
import Signals
12+
13+
open class WahooAdvancedFitnessMachineService: Service, ServiceProtocol {
14+
15+
public static var uuid: String { return "A026EE0B-0A7D-4AB3-97FA-F1500F9FEB8B" }
16+
17+
public static var characteristicTypes: Dictionary<String, Characteristic.Type> = [
18+
WahooAdvancedTrainerControlPoint.uuid: WahooAdvancedTrainerControlPoint.self
19+
]
20+
21+
public var controlPoint: WahooAdvancedTrainerControlPoint? { return characteristic() }
22+
23+
open class WahooAdvancedTrainerControlPoint: Characteristic {
24+
25+
public static var uuid: String { return "A026E037-0A7D-4AB3-97FA-F1500F9FEB8B" }
26+
27+
required public init(service: Service, cbc: CBCharacteristic) {
28+
super.init(service: service, cbc: cbc)
29+
30+
cbCharacteristic.notify(true)
31+
}
32+
}
33+
34+
open func getHubHeight() {
35+
let command = WahooAdvancedFitnessMachineSerializer.getHubHeight()
36+
controlPoint?.cbCharacteristic.write(command.data, writeType: .withoutResponse)
37+
}
38+
39+
open func setHubHeight(millimeters: Int) {
40+
let command = WahooAdvancedFitnessMachineSerializer.setHubHeight(millimeters: UInt16(millimeters))
41+
controlPoint?.cbCharacteristic.write(command.data, writeType: .withoutResponse)
42+
}
43+
44+
open func getWheelBase() {
45+
let command = WahooAdvancedFitnessMachineSerializer.getHubHeight()
46+
controlPoint?.cbCharacteristic.write(command.data, writeType: .withoutResponse)
47+
}
48+
49+
open func setWheelBase(millimeters: Int) {
50+
let command = WahooAdvancedFitnessMachineSerializer.setWheelBase(millimeters: UInt16(millimeters))
51+
controlPoint?.cbCharacteristic.write(command.data, writeType: .withoutResponse)
52+
}
53+
54+
open func getTargetTilt() {
55+
let command = WahooAdvancedFitnessMachineSerializer.getTargetTilt()
56+
controlPoint?.cbCharacteristic.write(command.data, writeType: .withoutResponse)
57+
}
58+
59+
open func setTargetTilt(grade: Double) {
60+
let command = WahooAdvancedFitnessMachineSerializer.setTargetTilt(grade: grade)
61+
controlPoint?.cbCharacteristic.write(command.data, writeType: .withoutResponse)
62+
}
63+
64+
open func getTiltMode() {
65+
let command = WahooAdvancedFitnessMachineSerializer.getTiltMode()
66+
controlPoint?.cbCharacteristic.write(command.data, writeType: .withoutResponse)
67+
}
68+
69+
open func getTiltLimits() {
70+
let command = WahooAdvancedFitnessMachineSerializer.getTiltLimits()
71+
controlPoint?.cbCharacteristic.write(command.data, writeType: .withoutResponse)
72+
}
73+
}

SwiftySensorsTrainers.xcodeproj/project.pbxproj

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88

99
/* Begin PBXBuildFile section */
1010
27FE375F1E5DD9D759F2EC9A /* Pods_SwiftySensorsTrainers_iOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1FDEA13933EF5C6EBCC4156A /* Pods_SwiftySensorsTrainers_iOS.framework */; };
11+
331F28DD20A3897D009EFC98 /* WahooAdvancedFitnessMachineService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331F28DC20A3897D009EFC98 /* WahooAdvancedFitnessMachineService.swift */; };
12+
331F28DF20A39D81009EFC98 /* WahooAdvancedFitnessMachineSerializer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331F28DE20A39D81009EFC98 /* WahooAdvancedFitnessMachineSerializer.swift */; };
1113
8D0B37661FE18D870073A7F7 /* CycleOpsSerializer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8DBA0B9A1FD600A000A4544D /* CycleOpsSerializer.swift */; };
1214
8D0B37671FE18D870073A7F7 /* JetBlackSerializer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8DBA0B941FD600A000A4544D /* JetBlackSerializer.swift */; };
1315
8D0B37681FE18D870073A7F7 /* SmartControlService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8DBA0B9B1FD600A000A4544D /* SmartControlService.swift */; };
@@ -73,6 +75,8 @@
7375
18FD1A88422D06F1BE87FD30 /* Pods-SwiftySensorsTrainers tvOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SwiftySensorsTrainers tvOS.debug.xcconfig"; path = "Pods/Target Support Files/Pods-SwiftySensorsTrainers tvOS/Pods-SwiftySensorsTrainers tvOS.debug.xcconfig"; sourceTree = "<group>"; };
7476
1FDEA13933EF5C6EBCC4156A /* Pods_SwiftySensorsTrainers_iOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_SwiftySensorsTrainers_iOS.framework; sourceTree = BUILT_PRODUCTS_DIR; };
7577
3013A1979BE7584793F4EE20 /* Pods-SwiftySensorsTrainers macOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SwiftySensorsTrainers macOS.debug.xcconfig"; path = "Pods/Target Support Files/Pods-SwiftySensorsTrainers macOS/Pods-SwiftySensorsTrainers macOS.debug.xcconfig"; sourceTree = "<group>"; };
78+
331F28DC20A3897D009EFC98 /* WahooAdvancedFitnessMachineService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WahooAdvancedFitnessMachineService.swift; sourceTree = "<group>"; };
79+
331F28DE20A39D81009EFC98 /* WahooAdvancedFitnessMachineSerializer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WahooAdvancedFitnessMachineSerializer.swift; sourceTree = "<group>"; };
7680
3D9BE0950CAD962084823F4D /* Pods-SwiftySensorsTrainers tvOS.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SwiftySensorsTrainers tvOS.release.xcconfig"; path = "Pods/Target Support Files/Pods-SwiftySensorsTrainers tvOS/Pods-SwiftySensorsTrainers tvOS.release.xcconfig"; sourceTree = "<group>"; };
7781
616384F1969A6B96ADBF7FE2 /* Pods_SwiftySensorsTrainers_tvOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_SwiftySensorsTrainers_tvOS.framework; sourceTree = BUILT_PRODUCTS_DIR; };
7882
8D0B377E1FE18D870073A7F7 /* SwiftySensorsTrainers.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SwiftySensorsTrainers.framework; sourceTree = BUILT_PRODUCTS_DIR; };
@@ -211,6 +215,8 @@
211215
8DBA0B971FD600A000A4544D /* TacxSerializer.swift */,
212216
8DBA0B951FD600A000A4544D /* WahooTrainerCharacteristic.swift */,
213217
8DBA0B9D1FD600A000A4544D /* WahooTrainerSerializer.swift */,
218+
331F28DC20A3897D009EFC98 /* WahooAdvancedFitnessMachineService.swift */,
219+
331F28DE20A39D81009EFC98 /* WahooAdvancedFitnessMachineSerializer.swift */,
214220
);
215221
path = Source;
216222
sourceTree = "<group>";
@@ -541,9 +547,11 @@
541547
8DBA0BB01FD600A000A4544D /* SmartControlService.swift in Sources */,
542548
8D93C1FE2006DD6700813DA2 /* KineticService.swift in Sources */,
543549
8DBA0BB21FD600A000A4544D /* EliteTrainerService.swift in Sources */,
550+
331F28DF20A39D81009EFC98 /* WahooAdvancedFitnessMachineSerializer.swift in Sources */,
544551
8DBA0BA61FD600A000A4544D /* TacxService.swift in Sources */,
545552
8DBA0BB41FD600A000A4544D /* WahooTrainerSerializer.swift in Sources */,
546553
8D93C1FD2006DD6700813DA2 /* KineticSerializer.swift in Sources */,
554+
331F28DD20A3897D009EFC98 /* WahooAdvancedFitnessMachineService.swift in Sources */,
547555
8DBA0BA81FD600A000A4544D /* TacxSerializer.swift in Sources */,
548556
8DBA0BA41FD600A000A4544D /* WahooTrainerCharacteristic.swift in Sources */,
549557
8DBA0BBC1FD600A000A4544D /* EliteTrainerSerializer.swift in Sources */,

0 commit comments

Comments
 (0)