├── .gitignore
├── .gitmodules
├── .swiftpm
└── xcode
│ └── package.xcworkspace
│ └── contents.xcworkspacedata
├── .travis.yml
├── LICENSE
├── Package.resolved
├── Package.swift
├── QuadKit.png
├── README.md
├── Sources
├── QuadKit Examples
│ ├── AppDelegate.swift
│ ├── Assets.xcassets
│ │ └── AppIcon.appiconset
│ │ │ └── Contents.json
│ ├── Base.lproj
│ │ ├── LaunchScreen.storyboard
│ │ └── Main.storyboard
│ └── ExampleView.swift
└── QuadKit
│ ├── BindingSupport.swift
│ ├── EachineE10W.swift
│ ├── InputController.swift
│ ├── InputState.swift
│ └── QuadClient.swift
└── Tests
├── QuadKit.xctestplan
└── QuadKitTests
└── InputStateTests.swift
/.gitignore:
--------------------------------------------------------------------------------
1 | # Xcode
2 | #
3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
4 |
5 | ## Build generated
6 | build/
7 | DerivedData/
8 |
9 | ## Various settings
10 | *.pbxuser
11 | !default.pbxuser
12 | *.mode1v3
13 | !default.mode1v3
14 | *.mode2v3
15 | !default.mode2v3
16 | *.perspectivev3
17 | !default.perspectivev3
18 | xcuserdata/
19 |
20 | ## Other
21 | *.moved-aside
22 | *.xccheckout
23 | *.xcscmblueprint
24 |
25 | .build/
26 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gabrieloc/QuadKit/aa514a734b7bcc26c74a5f4765f737f2e149a7ce/.gitmodules
--------------------------------------------------------------------------------
/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: objective-c
2 | osx_image: xcode8.2
3 | xcode_project: QuadKit.xcodeproj
4 | xcode_scheme: QuadKit_iOS #4
5 | xcode_sdk: iphonesimulator10.2 #5
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | BSD 3-Clause License
2 |
3 | Copyright (c) 2017 GABRIEL O'FLAHERTY-CHAN
4 | All rights reserved.
5 |
6 | Redistribution and use in source and binary forms, with or without
7 | modification, are permitted provided that the following conditions are met:
8 |
9 | * Redistributions of source code must retain the above copyright notice, this
10 | list of conditions and the following disclaimer.
11 |
12 | * Redistributions in binary form must reproduce the above copyright notice,
13 | this list of conditions and the following disclaimer in the documentation
14 | and/or other materials provided with the distribution.
15 |
16 | * Neither the name of the copyright holder nor the names of its
17 | contributors may be used to endorse or promote products derived from
18 | this software without specific prior written permission.
19 |
20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 |
--------------------------------------------------------------------------------
/Package.resolved:
--------------------------------------------------------------------------------
1 | {
2 | "pins" : [
3 | {
4 | "identity" : "swiftsocket",
5 | "kind" : "remoteSourceControl",
6 | "location" : "https://github.com/trisbee/SwiftSocket",
7 | "state" : {
8 | "revision" : "a7e958beb73c5a87cc6315b8a29b67e98138967f",
9 | "version" : "2.2.0"
10 | }
11 | }
12 | ],
13 | "version" : 2
14 | }
15 |
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version: 5.8
2 |
3 | import PackageDescription
4 |
5 | let package = Package(
6 | name: "QuadKit",
7 | products: [
8 | .library(
9 | name: "QuadKit",
10 | targets: ["QuadKit"]),
11 | ],
12 | dependencies: [
13 | .package(url: "https://github.com/trisbee/SwiftSocket", from: "2.2.0"),
14 | ],
15 | targets: [
16 | .target(
17 | name: "QuadKit",
18 | dependencies: ["SwiftSocket"]
19 | ),
20 | .testTarget(
21 | name: "QuadKitTests",
22 | dependencies: ["QuadKit"]
23 | ),
24 | ]
25 | )
26 |
--------------------------------------------------------------------------------
/QuadKit.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gabrieloc/QuadKit/aa514a734b7bcc26c74a5f4765f737f2e149a7ce/QuadKit.png
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | **A simple set of interfaces for communicating with your Wi-Fi enabled quadcopter**.
4 |
5 | QuadKit powers [SCARAB](https://itunes.apple.com/us/app/scarab-rc-controller-for-quadcopters/id1205279859), an RC controller for iOS. Read about the making of QuadKit: [Building a Quadcopter Controller for iOS and Open-Sourcing the Internals](https://medium.com/@_gabrieloc/building-a-quadcopter-controller-for-ios-and-open-sourcing-the-internals-3bbc7f526ed2#.8c7aaatsv)
6 |
7 | [](https://github.com/Carthage/Carthage)
8 |
9 | ## Installation
10 |
11 | Add this to your Cartfile:
12 | `github "gabrieloc/QuadKit"`
13 |
14 | To test the examples, make sure [Carthage](https://github.com/Carthage/Carthage#installing-carthage) is installed, then run:
15 |
16 | `carthage update`
17 |
18 |
19 | ## Integration
20 |
21 | Ensure any project using this framework includes `QuadKit.framework` and `SwiftSocket.framework` as embedded binaries.
22 |
23 | To connect to your quadcopter, create an instance of `QuadClient` and call `-connect:`. Ensure that your device has been connected to the Wi-Fi network created by your quadcopter, before attempting to connect. Once a connection has been established, the client expects to be passed an `InputState` object via `-updateInput:`, in response to user interaction.
24 |
25 | ### Example
26 |
27 | ``` Swift
28 |
29 | let client = QuadClient()
30 | let inputState = InputState()
31 | let thrustControl = UIControl()
32 |
33 | func connectClient() {
34 | if let error = client.connect() {
35 | // Error connecting
36 | }
37 | thrustControl.enabled = true
38 | }
39 |
40 | func thrustControlTouched(_ thrustControl: UIControl) {
41 | inputState.thrust = thrustControl.selected ? 1.0 : 0.0
42 | client.updateInput(with inputState)
43 | }
44 | ```
45 |
46 | iOS and macOS samples can be found inside the `Examples/` directory.
47 |
48 |
49 | ## Contributing
50 |
51 | While the format for input data appears to be generic across models (needs to be verified), it's possible that handshake data isn't. For this reason, `BindingSupport` defines a protocol `QuadcopterModel`, which individual models can use for providing this data.
52 |
53 | Handshake data can be found by running a tool like `tcpdump` or `Wireshark` to get a packet trace, and collecting the first few packets your device sends your quadcopter in the software provided by it's manufacturer.
54 |
55 | ### Example
56 |
57 | 1. Connect your device via USB and create a RVI: `$ rvictl -s (DEVICE UDID)`
58 | 2. Ensure the remote interface was created by running: `$ ifconfig -l` and verifying `rvi0` exists
59 | 3. Turn on your quadcopter and join it's Wi-Fi network
60 | 4. In a tool like [Wireshark](https://www.wireshark.org), begin capturing `rvi0`
61 | 5. Open the software provided by your quadcopter's manufacturer and access the input controls
62 | 6. Stop capturing and find what looks like identification data. It should consistently look the same every time you try to connect the app, and will appear before you begin receiving any form of input or video data
63 | 7. Create a new class in `QuadKit` representing your quadcopter model, and have it adopt `QuadcopterModel`
64 | 8. In your class' implementation of `-identification`, return the data your recorded in the form of byte arrays
65 |
66 |
67 | ## Reference
68 |
69 | Apple provides step by step instructions for creating using various network debugging tools: [Technical Q&A QA1176: Getting a Packet Trace](https://developer.apple.com/library/content/qa/qa1176/_index.html).
70 |
71 | For more information on the protocol, refer to Jim Hung's [Hubsan X4 H107L Quadcopter Control Protocol Specification](http://www.jimhung.co.uk/wp-content/uploads/2014/11/HubsanX4_ProtocolSpec_v1.txt), or his excellent reverse engineering series, [Reverse Engineering a Hubsan X4 Quadcopter](http://www.jimhung.co.uk/?p=1424).
72 |
73 |
--------------------------------------------------------------------------------
/Sources/QuadKit Examples/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // QuadKit iOS Example
4 | //
5 | // Copyright (c) <2017>, Gabriel O'Flaherty-Chan
6 | // All rights reserved.
7 | //
8 | // Redistribution and use in source and binary forms, with or without
9 | // modification, are permitted provided that the following conditions are met:
10 | // 1. Redistributions of source code must retain the above copyright
11 | // notice, this list of conditions and the following disclaimer.
12 | // 2. Redistributions in binary form must reproduce the above copyright
13 | // notice, this list of conditions and the following disclaimer in the
14 | // documentation and/or other materials provided with the distribution.
15 | // 3. All advertising materials mentioning features or use of this software
16 | // must display the following acknowledgement:
17 | // This product includes software developed by skysent.
18 | // 4. Neither the name of the skysent nor the
19 | // names of its contributors may be used to endorse or promote products
20 | // derived from this software without specific prior written permission.
21 | //
22 | // THIS SOFTWARE IS PROVIDED BY skysent ''AS IS'' AND ANY
23 | // EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
24 | // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
25 | // DISCLAIMED. IN NO EVENT SHALL skysent BE LIABLE FOR ANY
26 | // DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
27 | // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
28 | // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
29 | // ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
30 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
31 | // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32 | //
33 |
34 | import UIKit
35 |
36 | @UIApplicationMain
37 | class AppDelegate: UIResponder, UIApplicationDelegate {
38 |
39 | var window: UIWindow?
40 |
41 |
42 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
43 | // Override point for customization after application launch.
44 | return true
45 | }
46 |
47 | func applicationWillResignActive(_ application: UIApplication) {
48 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
49 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game.
50 | }
51 |
52 | func applicationDidEnterBackground(_ application: UIApplication) {
53 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
54 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
55 | }
56 |
57 | func applicationWillEnterForeground(_ application: UIApplication) {
58 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
59 | }
60 |
61 | func applicationDidBecomeActive(_ application: UIApplication) {
62 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
63 | }
64 |
65 | func applicationWillTerminate(_ application: UIApplication) {
66 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
67 | }
68 |
69 |
70 | }
71 |
72 |
--------------------------------------------------------------------------------
/Sources/QuadKit Examples/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "size" : "29x29",
6 | "scale" : "2x"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "size" : "29x29",
11 | "scale" : "3x"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "size" : "40x40",
16 | "scale" : "2x"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "size" : "40x40",
21 | "scale" : "3x"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "size" : "60x60",
26 | "scale" : "2x"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "size" : "60x60",
31 | "scale" : "3x"
32 | },
33 | {
34 | "idiom" : "ipad",
35 | "size" : "29x29",
36 | "scale" : "1x"
37 | },
38 | {
39 | "idiom" : "ipad",
40 | "size" : "29x29",
41 | "scale" : "2x"
42 | },
43 | {
44 | "idiom" : "ipad",
45 | "size" : "40x40",
46 | "scale" : "1x"
47 | },
48 | {
49 | "idiom" : "ipad",
50 | "size" : "40x40",
51 | "scale" : "2x"
52 | },
53 | {
54 | "idiom" : "ipad",
55 | "size" : "76x76",
56 | "scale" : "1x"
57 | },
58 | {
59 | "idiom" : "ipad",
60 | "size" : "76x76",
61 | "scale" : "2x"
62 | }
63 | ],
64 | "info" : {
65 | "version" : 1,
66 | "author" : "xcode"
67 | }
68 | }
--------------------------------------------------------------------------------
/Sources/QuadKit Examples/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/Sources/QuadKit Examples/Base.lproj/Main.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
47 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
--------------------------------------------------------------------------------
/Sources/QuadKit Examples/ExampleView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ExampleView.swift
3 | // QuadKit
4 | //
5 | // Copyright (c) <2017>, Gabriel O'Flaherty-Chan
6 | // All rights reserved.
7 | //
8 | // Redistribution and use in source and binary forms, with or without
9 | // modification, are permitted provided that the following conditions are met:
10 | // 1. Redistributions of source code must retain the above copyright
11 | // notice, this list of conditions and the following disclaimer.
12 | // 2. Redistributions in binary form must reproduce the above copyright
13 | // notice, this list of conditions and the following disclaimer in the
14 | // documentation and/or other materials provided with the distribution.
15 | // 3. All advertising materials mentioning features or use of this software
16 | // must display the following acknowledgement:
17 | // This product includes software developed by skysent.
18 | // 4. Neither the name of the skysent nor the
19 | // names of its contributors may be used to endorse or promote products
20 | // derived from this software without specific prior written permission.
21 | //
22 | // THIS SOFTWARE IS PROVIDED BY skysent ''AS IS'' AND ANY
23 | // EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
24 | // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
25 | // DISCLAIMED. IN NO EVENT SHALL skysent BE LIABLE FOR ANY
26 | // DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
27 | // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
28 | // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
29 | // ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
30 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
31 | // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32 | //
33 |
34 | import UIKit
35 |
36 | class ExampleView: UIView, ControlView {
37 |
38 | @IBOutlet weak var thrust: UIButton!
39 | @IBOutlet weak var connectButton: UIButton!
40 |
41 | public var inputHandler: InputHandler?
42 |
43 | override func awakeFromNib() {
44 | super.awakeFromNib()
45 |
46 | thrust.isEnabled = false
47 | }
48 |
49 | func connectionUpdated(_ connected: Bool) {
50 | connectButton.isEnabled = !connected
51 | let connectTitle = connected ? "Connected" : "Reconnect"
52 | connectButton.setTitle(connectTitle, for: .normal)
53 | thrust.isEnabled = connected
54 | }
55 |
56 | @IBAction func beginThrust(_ sender: Any) {
57 | inputHandler?.inputUpdated(.thrust, selected: true)
58 | }
59 |
60 | @IBAction func endThrust(_ sender: Any) {
61 | inputHandler?.inputUpdated(.thrust, selected: false)
62 | }
63 |
64 | @IBAction func connectSelected(_ sender: Any) {
65 | inputHandler?.connectClient()
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/Sources/QuadKit/BindingSupport.swift:
--------------------------------------------------------------------------------
1 | //
2 | // BindingSupport.swift
3 | // QuadKit
4 | //
5 | // Copyright (c) <2017>, Gabriel O'Flaherty-Chan
6 | // All rights reserved.
7 | //
8 | // Redistribution and use in source and binary forms, with or without
9 | // modification, are permitted provided that the following conditions are met:
10 | // 1. Redistributions of source code must retain the above copyright
11 | // notice, this list of conditions and the following disclaimer.
12 | // 2. Redistributions in binary form must reproduce the above copyright
13 | // notice, this list of conditions and the following disclaimer in the
14 | // documentation and/or other materials provided with the distribution.
15 | // 3. All advertising materials mentioning features or use of this software
16 | // must display the following acknowledgement:
17 | // This product includes software developed by skysent.
18 | // 4. Neither the name of the skysent nor the
19 | // names of its contributors may be used to endorse or promote products
20 | // derived from this software without specific prior written permission.
21 | //
22 | // THIS SOFTWARE IS PROVIDED BY skysent ''AS IS'' AND ANY
23 | // EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
24 | // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
25 | // DISCLAIMED. IN NO EVENT SHALL skysent BE LIABLE FOR ANY
26 | // DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
27 | // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
28 | // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
29 | // ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
30 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
31 | // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32 | //
33 |
34 | import Foundation
35 |
36 | typealias Byte = UInt8
37 | typealias Packet = [Byte]
38 |
39 | struct Conversation {
40 |
41 | let topic: String
42 | let packets: [Packet]
43 |
44 | init(topic: String, packets: [Packet]) {
45 | self.topic = topic
46 | self.packets = packets
47 | }
48 | }
49 |
50 | protocol QuadcopterModel {
51 |
52 | var identification: Conversation { get }
53 | }
54 |
55 | extension QuadClient {
56 |
57 | var allIdentificationConversations: [Conversation] {
58 |
59 | // This list should grow over time
60 | let supportedModels = [
61 | EachineE10W()
62 | ]
63 |
64 | return supportedModels.map { $0.identification }
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/Sources/QuadKit/EachineE10W.swift:
--------------------------------------------------------------------------------
1 | //
2 | // EachineE10W.swift
3 | // QuadKit
4 | //
5 | // Copyright (c) <2017>, Gabriel O'Flaherty-Chan
6 | // All rights reserved.
7 | //
8 | // Redistribution and use in source and binary forms, with or without
9 | // modification, are permitted provided that the following conditions are met:
10 | // 1. Redistributions of source code must retain the above copyright
11 | // notice, this list of conditions and the following disclaimer.
12 | // 2. Redistributions in binary form must reproduce the above copyright
13 | // notice, this list of conditions and the following disclaimer in the
14 | // documentation and/or other materials provided with the distribution.
15 | // 3. All advertising materials mentioning features or use of this software
16 | // must display the following acknowledgement:
17 | // This product includes software developed by skysent.
18 | // 4. Neither the name of the skysent nor the
19 | // names of its contributors may be used to endorse or promote products
20 | // derived from this software without specific prior written permission.
21 | //
22 | // THIS SOFTWARE IS PROVIDED BY skysent ''AS IS'' AND ANY
23 | // EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
24 | // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
25 | // DISCLAIMED. IN NO EVENT SHALL skysent BE LIABLE FOR ANY
26 | // DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
27 | // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
28 | // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
29 | // ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
30 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
31 | // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32 | //
33 |
34 | import Foundation
35 |
36 | struct EachineE10W: QuadcopterModel {
37 |
38 | var identification: Conversation {
39 | let packets = identification1 + [identification2] + [inputIdentification]
40 | return Conversation(topic: "Eachine E10W Identification", packets: packets)
41 | }
42 |
43 | let identification1: [Packet] = [
44 | [
45 | 0x49, 0x54, 0x64, 0x00, 0x00, 0x00, 0x52, 0x00,
46 | 0x00, 0x00, 0x05, 0xa7, 0xa9, 0x0f, 0xb3, 0x6e,
47 | 0xcd, 0x3f, 0xa2, 0xca, 0x7e, 0xc4, 0x8c, 0xa3,
48 | 0x60, 0x04, 0xac, 0xef, 0x63, 0xf7, 0x71, 0x57,
49 | 0xab, 0x2f, 0x53, 0xe3, 0xf7, 0x68, 0xec, 0xd9,
50 | 0xe1, 0x85, 0x47, 0xb8, 0xc2, 0x2e, 0x21, 0xd0,
51 | 0x1b, 0xfb, 0x6b, 0x3d, 0xe3, 0x25, 0xa2, 0x7b,
52 | 0x8f, 0xb3, 0xac, 0xef, 0x63, 0xf7, 0x71, 0x57,
53 | 0xab, 0x2f, 0x53, 0xe3, 0xf7, 0x68, 0xec, 0xd9,
54 | 0xe1, 0x85, 0xb7, 0x33, 0x0f, 0xb7, 0xc9, 0x57,
55 | 0x82, 0xfc, 0x3d, 0x67, 0xe7, 0xc3, 0xa6, 0x67,
56 | 0x28, 0xda, 0xd8, 0xb5, 0x98, 0x48, 0xc7, 0x67,
57 | 0x0c, 0x94, 0xb2, 0x9b, 0x54, 0xd2, 0x37, 0x9e,
58 | 0x2e, 0x7a
59 | ],
60 | [
61 | 0x49, 0x54, 0x64, 0x00, 0x00, 0x00, 0x52, 0x00,
62 | 0x00, 0x00, 0x21, 0xe0, 0xc4, 0x77, 0xc7, 0x73,
63 | 0x94, 0xe8, 0x5d, 0x66, 0xa9, 0x8c, 0x2c, 0x92,
64 | 0x2c, 0xc5, 0xac, 0xef, 0x63, 0xf7, 0x71, 0x57,
65 | 0xab, 0x2f, 0x53, 0xe3, 0xf7, 0x68, 0xec, 0xd9,
66 | 0xe1, 0x85, 0x47, 0xb8, 0xc2, 0x2e, 0x21, 0xd0,
67 | 0x1b, 0xfb, 0x6b, 0x3d, 0xe3, 0x25, 0xa2, 0x7b,
68 | 0x8f, 0xb3, 0xac, 0xef, 0x63, 0xf7, 0x71, 0x57,
69 | 0xab, 0x2f, 0x53, 0xe3, 0xf7, 0x68, 0xec, 0xd9,
70 | 0xe1, 0x85, 0xb7, 0x33, 0x0f, 0xb7, 0xc9, 0x57,
71 | 0x82, 0xfc, 0x3d, 0x67, 0xe7, 0xc3, 0xa6, 0x67,
72 | 0x28, 0xda, 0xd8, 0xb5, 0x98, 0x48, 0xc7, 0x67,
73 | 0x0c, 0x94, 0xb2, 0x9b, 0x54, 0xd2, 0x37, 0x9e,
74 | 0x2e, 0x7a
75 | ],
76 | [
77 | 0x49, 0x54, 0x64, 0x00, 0x00, 0x00, 0x56, 0x00,
78 | 0x00, 0x00, 0x54, 0x69, 0x6d, 0x47, 0xa5, 0x41,
79 | 0x85, 0x86, 0x00, 0x72, 0x9e, 0x0a, 0x5b, 0xa1,
80 | 0x90, 0x37, 0xac, 0xef, 0x63, 0xf7, 0x71, 0x57,
81 | 0xab, 0x2f, 0x53, 0xe3, 0xf7, 0x68, 0xec, 0xd9,
82 | 0xe1, 0x85, 0x47, 0xb8, 0xc2, 0x2e, 0x21, 0xd0,
83 | 0x1b, 0xfb, 0x6b, 0x3d, 0xe3, 0x25, 0xa2, 0x7b,
84 | 0x8f, 0xb3, 0xac, 0xef, 0x63, 0xf7, 0x71, 0x57,
85 | 0xab, 0x2f, 0x53, 0xe3, 0xf7, 0x68, 0xec, 0xd9,
86 | 0xe1, 0x85, 0x3b, 0x30, 0x70, 0x85, 0xef, 0x35,
87 | 0xf4, 0x3e, 0xaf, 0x44, 0xcf, 0x6f, 0xc7, 0x9c,
88 | 0x5d, 0x1e, 0x38, 0x27, 0x5b, 0x07, 0xf2, 0xd7,
89 | 0x3f, 0xc0, 0xa7, 0x38, 0xdc, 0x49, 0xd6, 0x0f,
90 | 0xfe, 0x9f
91 | ],
92 | [
93 | 0x49, 0x54, 0x64, 0x00, 0x00, 0x00, 0x52, 0x00,
94 | 0x00, 0x00, 0x2f, 0x2e, 0xe9, 0x40, 0x93, 0x91,
95 | 0x9e, 0x15, 0x36, 0x09, 0x66, 0x26, 0x02, 0x81,
96 | 0xef, 0xa5, 0xac, 0xef, 0x63, 0xf7, 0x71, 0x57,
97 | 0xab, 0x2f, 0x53, 0xe3, 0xf7, 0x68, 0xec, 0xd9,
98 | 0xe1, 0x85, 0x47, 0xb8, 0xc2, 0x2e, 0x21, 0xd0,
99 | 0x1b, 0xfb, 0x6b, 0x3d, 0xe3, 0x25, 0xa2, 0x7b,
100 | 0x8f, 0xb3, 0xac, 0xef, 0x63, 0xf7, 0x71, 0x57,
101 | 0xab, 0x2f, 0x53, 0xe3, 0xf7, 0x68, 0xec, 0xd9,
102 | 0xe1, 0x85, 0xb7, 0x33, 0x0f, 0xb7, 0xc9, 0x57,
103 | 0x82, 0xfc, 0x3d, 0x67, 0xe7, 0xc3, 0xa6, 0x67,
104 | 0x28, 0xda, 0xd8, 0xb5, 0x98, 0x48, 0xc7, 0x67,
105 | 0x0c, 0x94, 0xb2, 0x9b, 0x54, 0xd2, 0x37, 0x9e,
106 | 0x2e, 0x7a
107 | ],
108 | [
109 | 0x49, 0x54, 0x64, 0x00, 0x00, 0x00, 0x52, 0x00,
110 | 0x00, 0x00, 0x09, 0x2d, 0x16, 0x6d, 0xae, 0xbe,
111 | 0x5e, 0xc5, 0xdb, 0x47, 0x35, 0x07, 0x76, 0x7f,
112 | 0x88, 0x9b, 0xac, 0xef, 0x63, 0xf7, 0x71, 0x57,
113 | 0xab, 0x2f, 0x53, 0xe3, 0xf7, 0x68, 0xec, 0xd9,
114 | 0xe1, 0x85, 0x47, 0xb8, 0xc2, 0x2e, 0x21, 0xd0,
115 | 0x1b, 0xfb, 0x6b, 0x3d, 0xe3, 0x25, 0xa2, 0x7b,
116 | 0x8f, 0xb3, 0xac, 0xef, 0x63, 0xf7, 0x71, 0x57,
117 | 0xab, 0x2f, 0x53, 0xe3, 0xf7, 0x68, 0xec, 0xd9,
118 | 0xe1, 0x85, 0xb7, 0x33, 0x0f, 0xb7, 0xc9, 0x57,
119 | 0x82, 0xfc, 0x3d, 0x67, 0xe7, 0xc3, 0xa6, 0x67,
120 | 0x28, 0xda, 0xd8, 0xb5, 0x98, 0x48, 0xc7, 0x67,
121 | 0x0c, 0x94, 0xb2, 0x9b, 0x54, 0xd2, 0x37, 0x9e,
122 | 0x2e, 0x7a
123 | ],
124 | [
125 | 0x49, 0x54, 0x64, 0x00, 0x00, 0x00, 0x52, 0x00,
126 | 0x00, 0x00, 0x0d, 0xcf, 0x53, 0x3a, 0x42, 0x51,
127 | 0x63, 0x8c, 0x31, 0x25, 0x94, 0x8b, 0x99, 0x09,
128 | 0xf1, 0xbd, 0xac, 0xef, 0x63, 0xf7, 0x71, 0x57,
129 | 0xab, 0x2f, 0x53, 0xe3, 0xf7, 0x68, 0xec, 0xd9,
130 | 0xe1, 0x85, 0x47, 0xb8, 0xc2, 0x2e, 0x21, 0xd0,
131 | 0x1b, 0xfb, 0x6b, 0x3d, 0xe3, 0x25, 0xa2, 0x7b,
132 | 0x8f, 0xb3, 0xac, 0xef, 0x63, 0xf7, 0x71, 0x57,
133 | 0xab, 0x2f, 0x53, 0xe3, 0xf7, 0x68, 0xec, 0xd9,
134 | 0xe1, 0x85, 0xb7, 0x33, 0x0f, 0xb7, 0xc9, 0x57,
135 | 0x82, 0xfc, 0x3d, 0x67, 0xe7, 0xc3, 0xa6, 0x67,
136 | 0x28, 0xda, 0xd8, 0xb5, 0x98, 0x48, 0xc7, 0x67,
137 | 0x0c, 0x94, 0xb2, 0x9b, 0x54, 0xd2, 0x37, 0x9e,
138 | 0x2e, 0x7a
139 | ],
140 | [
141 | 0x49, 0x54, 0x64, 0x00, 0x00, 0x00, 0x5d, 0x00,
142 | 0x00, 0x00, 0xb5, 0x4f, 0x38, 0xc7, 0x5c, 0xe4,
143 | 0x9a, 0xca, 0xa9, 0x0a, 0x73, 0x7d, 0x7d, 0x84,
144 | 0xdb, 0xdc, 0xac, 0xef, 0x63, 0xf7, 0x71, 0x57,
145 | 0xab, 0x2f, 0x53, 0xe3, 0xf7, 0x68, 0xec, 0xd9,
146 | 0xe1, 0x85, 0x47, 0xb8, 0xc2, 0x2e, 0x21, 0xd0,
147 | 0x1b, 0xfb, 0x6b, 0x3d, 0xe3, 0x25, 0xa2, 0x7b,
148 | 0x8f, 0xb3, 0xac, 0xef, 0x63, 0xf7, 0x71, 0x57,
149 | 0xab, 0x2f, 0x53, 0xe3, 0xf7, 0x68, 0xec, 0xd9,
150 | 0xe1, 0x85, 0xee, 0x2e, 0x09, 0xa3, 0x9b, 0xdd,
151 | 0x05, 0xc8, 0x30, 0xa2, 0x81, 0xc8, 0x2a, 0x9e,
152 | 0xda, 0x7f, 0x49, 0x5c, 0xf8, 0xc7, 0x21, 0x31,
153 | 0xd4, 0x89, 0xa8, 0xcf, 0x63, 0x6e, 0xab, 0xbd,
154 | 0x64, 0x3c
155 | ]
156 | ]
157 | let identification2: Packet = [
158 | 0x49, 0x54, 0x64, 0x00, 0x00, 0x00, 0x52, 0x00,
159 | 0x00, 0x00, 0x72, 0x98, 0xc0, 0x38, 0x9b, 0xc3,
160 | 0x72, 0xa7, 0x1a, 0x17, 0x4b, 0xd1, 0xb5, 0x14,
161 | 0xb3, 0xad, 0xac, 0xef, 0x63, 0xf7, 0x71, 0x57,
162 | 0xab, 0x2f, 0x53, 0xe3, 0xf7, 0x68, 0xec, 0xd9,
163 | 0xe1, 0x85, 0x47, 0xb8, 0xc2, 0x2e, 0x21, 0xd0,
164 | 0x1b, 0xfb, 0x6b, 0x3d, 0xe3, 0x25, 0xa2, 0x7b,
165 | 0x8f, 0xb3, 0xac, 0xef, 0x63, 0xf7, 0x71, 0x57,
166 | 0xab, 0x2f, 0x53, 0xe3, 0xf7, 0x68, 0xec, 0xd9,
167 | 0xe1, 0x85, 0xb7, 0x33, 0x0f, 0xb7, 0xc9, 0x57,
168 | 0x82, 0xfc, 0x3d, 0x67, 0xe7, 0xc3, 0xa6, 0x67,
169 | 0x28, 0xda, 0xd8, 0xb5, 0x98, 0x48, 0xc7, 0x67,
170 | 0x0c, 0x94, 0xb2, 0x9b, 0x54, 0xd2, 0x37, 0x9e,
171 | 0x2e, 0x7a
172 | ]
173 |
174 | let inputIdentification: Packet = [
175 | 0x00, 0x00, 0x01, 0xa0, 0x00, 0x4f, 0x00, 0x03,
176 | 0xe6, 0x0b, 0x00, 0x00, 0x38, 0x24, 0xad, 0x49,
177 | 0x01, 0x5d, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00,
178 | 0xd0, 0x02, 0x40, 0x02, 0x00, 0x02, 0x00, 0x00,
179 | 0xe6, 0x0b, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00
180 | ]
181 | }
182 |
--------------------------------------------------------------------------------
/Sources/QuadKit/InputController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // QuadControl.swift
3 | // QuadKit
4 | //
5 | // Copyright (c) <2017>, Gabriel O'Flaherty-Chan
6 | // All rights reserved.
7 | //
8 | // Redistribution and use in source and binary forms, with or without
9 | // modification, are permitted provided that the following conditions are met:
10 | // 1. Redistributions of source code must retain the above copyright
11 | // notice, this list of conditions and the following disclaimer.
12 | // 2. Redistributions in binary form must reproduce the above copyright
13 | // notice, this list of conditions and the following disclaimer in the
14 | // documentation and/or other materials provided with the distribution.
15 | // 3. All advertising materials mentioning features or use of this software
16 | // must display the following acknowledgement:
17 | // This product includes software developed by skysent.
18 | // 4. Neither the name of the skysent nor the
19 | // names of its contributors may be used to endorse or promote products
20 | // derived from this software without specific prior written permission.
21 | //
22 | // THIS SOFTWARE IS PROVIDED BY skysent ''AS IS'' AND ANY
23 | // EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
24 | // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
25 | // DISCLAIMED. IN NO EVENT SHALL skysent BE LIABLE FOR ANY
26 | // DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
27 | // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
28 | // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
29 | // ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
30 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
31 | // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32 | //
33 |
34 | import Foundation
35 | import SwiftSocket
36 |
37 | // Represents the data (in bytes) sent to the quadcopter
38 | struct InputData {
39 |
40 | let start: (Byte, Byte) = (0x66, 0xCC)
41 | let end: (Byte, Byte) = (0x99, 0x33)
42 | let data: (Byte, Byte, Byte, Byte)
43 |
44 | init(_ data: (Byte, Byte, Byte, Byte)) {
45 | self.data = data
46 | }
47 |
48 | func createChecksum(_ bytes: [Byte]) -> Byte {
49 | return bytes.reduce ( 0, { $0 ^ $1 }) & 0xFF
50 | }
51 |
52 | public var formatted: [Byte] {
53 | let bytes = [data.0, data.1, data.2, data.3, 0x0]
54 | let checksum = createChecksum(bytes)
55 | return [start.0] + bytes + [checksum, end.0, start.1] + bytes + [checksum, end.1]
56 | }
57 | }
58 |
59 | struct InputController {
60 |
61 | var state = InputState()
62 |
63 | var data: [Byte] {
64 |
65 | var yaw: Byte = 0
66 | var roll: Byte = 0
67 | var pitch: Byte = 0
68 | var thrust: Byte = 0
69 | state.getFinalValues(roll: &roll,
70 | pitch: &pitch,
71 | thrust: &thrust,
72 | yaw: &yaw)
73 |
74 | // NOTE: This format may vary by model. Consider refactoring if this is the case.
75 | let data = InputData((roll, pitch, thrust, yaw))
76 | return data.formatted
77 | }
78 |
79 | var prettyData: String {
80 |
81 | let hexData = data.map { "0x\(String(format: "%X", $0))" }
82 | return hexData.joined(separator: ", ")
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/Sources/QuadKit/InputState.swift:
--------------------------------------------------------------------------------
1 | //
2 | // InputState.swift
3 | // QuadKit
4 | //
5 | // Created by Gabriel O'Flaherty-Chan on 2017-03-01.
6 | // Copyright © 2017 Gabrieloc. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | func clamp(_ x: Double, min: Double, max: Double) -> Double {
12 | return Swift.min(max, Swift.max(min, x))
13 | }
14 |
15 | func clamp(_ x: Int, min: Int, max: Int) -> Int {
16 | return Swift.min(max, Swift.max(min, x))
17 | }
18 |
19 | extension Double {
20 | var byteValue: Byte {
21 | return Byte(clamp(self, min: 0.0, max: 1.0) * 0xFF)
22 | }
23 | }
24 |
25 | public struct InputState {
26 |
27 | public var trimPitch = 0.0 // Z Movement Calibration
28 | public var trimRoll = 0.0 // X Movement Calibration
29 |
30 | // Y Movement
31 | public var thrust: Double {
32 | get { return _trimPitch }
33 | set { _trimPitch = clamp(newValue, min: 0, max: 1) }
34 | }
35 |
36 | // X Movement
37 | public var pitch: Double {
38 | get { return _pitch }
39 | set { _pitch = clamp(newValue, min: -1, max: 1) }
40 | }
41 |
42 | // Z Movement
43 | public var roll: Double {
44 | get { return _roll }
45 | set { _roll = clamp(newValue, min: -1, max: 1) }
46 | }
47 |
48 | // Y Rotation
49 | public var yaw: Double {
50 | get { return _yaw }
51 | set { _yaw = clamp(newValue, min: -1, max: 1) }
52 | }
53 |
54 | private var _trimPitch = 0.0
55 | private var _trimRoll = 0.0
56 | private var _thrust = 0.0
57 | private var _pitch = 0.0
58 | private var _roll = 0.0
59 | private var _yaw = 0.0
60 |
61 | private enum InputType: Int {
62 | case thrust, pitch, roll, yaw
63 | }
64 |
65 | public init() {}
66 |
67 | func normalized() -> InputState {
68 |
69 | var n = InputState()
70 |
71 | n.trimPitch = trimPitch
72 | n.trimRoll = trimRoll
73 |
74 | n.thrust = thrust
75 | n.pitch = (pitch + 1.0) * 0.5
76 | n.roll = (roll + 1.0) * 0.5
77 | n.yaw = (yaw + 1.0) * 0.5
78 |
79 | return n
80 | }
81 |
82 | func getFinalValues(roll: inout Byte, pitch: inout Byte, thrust: inout Byte, yaw: inout Byte) {
83 |
84 | let normalized = self.normalized()
85 |
86 | let calibratedRoll = Int(normalized.roll.byteValue) + Int(normalized.trimRoll)
87 | let calibratedPitch = Int(normalized.pitch.byteValue) + Int(normalized.trimPitch)
88 |
89 | roll = Byte(clamp(calibratedRoll, min: 0, max: Int(Byte.max)))
90 | pitch = Byte(clamp(calibratedPitch, min: 0, max: Int(Byte.max)))
91 | thrust = normalized.thrust.byteValue
92 | yaw = normalized.yaw.byteValue
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/Sources/QuadKit/QuadClient.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Client.swift
3 | // QuadKit
4 | //
5 | // Copyright (c) <2017>, Gabriel O'Flaherty-Chan
6 | // All rights reserved.
7 | //
8 | // Redistribution and use in source and binary forms, with or without
9 | // modification, are permitted provided that the following conditions are met:
10 | // 1. Redistributions of source code must retain the above copyright
11 | // notice, this list of conditions and the following disclaimer.
12 | // 2. Redistributions in binary form must reproduce the above copyright
13 | // notice, this list of conditions and the following disclaimer in the
14 | // documentation and/or other materials provided with the distribution.
15 | // 3. All advertising materials mentioning features or use of this software
16 | // must display the following acknowledgement:
17 | // This product includes software developed by skysent.
18 | // 4. Neither the name of the skysent nor the
19 | // names of its contributors may be used to endorse or promote products
20 | // derived from this software without specific prior written permission.
21 | //
22 | // THIS SOFTWARE IS PROVIDED BY skysent ''AS IS'' AND ANY
23 | // EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
24 | // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
25 | // DISCLAIMED. IN NO EVENT SHALL skysent BE LIABLE FOR ANY
26 | // DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
27 | // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
28 | // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
29 | // ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
30 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
31 | // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32 | //
33 |
34 | import Foundation
35 | import SwiftSocket
36 |
37 |
38 | public class QuadClient {
39 |
40 | let udpClient = UDPClient(address: "172.16.10.1", port: 8895)
41 | let tcpClient = TCPClient(address: "172.16.10.1", port: 8888)
42 | var controller = InputController()
43 |
44 | var verbose = false
45 |
46 | public struct QuadClientError: Error {
47 | enum ErrorKind {
48 | case connectionTimeout
49 | case bindingFailed
50 | }
51 | let kind: ErrorKind
52 | let reason: String
53 | }
54 |
55 | func print(_ items: Any) {
56 | if verbose {
57 | Swift.print(items)
58 | }
59 | }
60 |
61 | public init() {}
62 |
63 | public func connect(withTimeout timeout: Int = 10, verbose: Bool = false) -> QuadClientError? {
64 |
65 | self.verbose = verbose
66 |
67 | if let connectionError = connectClient(withTimeout: timeout) {
68 | print("Couldn't connect: \(connectionError.reason)")
69 | return connectionError
70 | }
71 |
72 | if let bindingError = performBindingChoreography() {
73 | return bindingError
74 | }
75 |
76 | beginBroadcasting()
77 |
78 | return nil
79 | }
80 |
81 | func connectClient(withTimeout timeout: Int) -> QuadClientError? {
82 |
83 | switch tcpClient.connect(timeout: timeout) {
84 | case .failure(let socketError):
85 | return QuadClientError(kind: .connectionTimeout, reason: socketError.localizedDescription)
86 | default:
87 | return nil
88 | }
89 | }
90 |
91 | func performBindingChoreography() -> QuadClientError? {
92 |
93 | func isConversationValid(_ conversation: Conversation) -> Bool {
94 |
95 | let description = conversation.topic
96 |
97 | return conversation.packets.reduce(false, { (identified, message) -> Bool in
98 |
99 | print("\(description): Sending message \(message)")
100 | switch tcpClient.send(data: message) {
101 | case .success:
102 | let response = tcpClient.read(1024*10, timeout: 5) ?? []
103 | print("\(description): Received response \(response.count) bytes long")
104 | return true
105 | case .failure(let error):
106 | print("\(description): Failed: \(error)")
107 | return false
108 | }
109 | })
110 | }
111 |
112 | let identified = allIdentificationConversations.reduce(true, {
113 | $0 && isConversationValid($1)
114 | })
115 |
116 | return identified ? nil : QuadClientError(kind: .bindingFailed, reason: "One or more binding errors")
117 | }
118 |
119 | // MARK: Input
120 |
121 | static let InputSyncInterval: TimeInterval = 0.05
122 |
123 | func beginBroadcasting() {
124 |
125 | udpClient.enableBroadcast()
126 | Timer.scheduledTimer(timeInterval: QuadClient.InputSyncInterval,
127 | target: self,
128 | selector: #selector(sendInputData),
129 | userInfo: nil,
130 | repeats: true)
131 | }
132 |
133 | public func updateInput(with state: InputState) {
134 | controller.state = state
135 | }
136 |
137 | @objc func sendInputData() {
138 |
139 | let inputData = controller.data
140 | switch udpClient.send(data: inputData) {
141 | case .success:
142 | print(controller.prettyData)
143 | break
144 | case .failure(let error):
145 | print("Failed to send input: \(error)")
146 | }
147 | }
148 | }
149 |
--------------------------------------------------------------------------------
/Tests/QuadKit.xctestplan:
--------------------------------------------------------------------------------
1 | {
2 | "configurations" : [
3 | {
4 | "id" : "63F5E43C-2354-4343-A2B7-317E58D42EC7",
5 | "name" : "Configuration 1",
6 | "options" : {
7 |
8 | }
9 | }
10 | ],
11 | "defaultOptions" : {
12 |
13 | },
14 | "testTargets" : [
15 |
16 | ],
17 | "version" : 1
18 | }
19 |
--------------------------------------------------------------------------------
/Tests/QuadKitTests/InputStateTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // InputStateTests.swift
3 | // QuadKit
4 | //
5 | // Copyright (c) <2017>, Gabriel O'Flaherty-Chan
6 | // All rights reserved.
7 | //
8 | // Redistribution and use in source and binary forms, with or without
9 | // modification, are permitted provided that the following conditions are met:
10 | // 1. Redistributions of source code must retain the above copyright
11 | // notice, this list of conditions and the following disclaimer.
12 | // 2. Redistributions in binary form must reproduce the above copyright
13 | // notice, this list of conditions and the following disclaimer in the
14 | // documentation and/or other materials provided with the distribution.
15 | // 3. All advertising materials mentioning features or use of this software
16 | // must display the following acknowledgement:
17 | // This product includes software developed by skysent.
18 | // 4. Neither the name of the skysent nor the
19 | // names of its contributors may be used to endorse or promote products
20 | // derived from this software without specific prior written permission.
21 | //
22 | // THIS SOFTWARE IS PROVIDED BY skysent ''AS IS'' AND ANY
23 | // EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
24 | // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
25 | // DISCLAIMED. IN NO EVENT SHALL skysent BE LIABLE FOR ANY
26 | // DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
27 | // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
28 | // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
29 | // ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
30 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
31 | // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32 | //
33 |
34 | import XCTest
35 | @testable import QuadKit
36 |
37 | class InputStateTests: XCTestCase {
38 |
39 | func createInputStateMax() -> InputState {
40 | var inputState = InputState()
41 | inputState.thrust = Double.greatestFiniteMagnitude
42 | inputState.pitch = Double.greatestFiniteMagnitude
43 | inputState.roll = Double.greatestFiniteMagnitude
44 | inputState.yaw = Double.greatestFiniteMagnitude
45 | return inputState
46 | }
47 |
48 | func createInputStateMin() -> InputState {
49 | var inputState = InputState()
50 | inputState.thrust = -Double.greatestFiniteMagnitude
51 | inputState.pitch = -Double.greatestFiniteMagnitude
52 | inputState.roll = -Double.greatestFiniteMagnitude
53 | inputState.yaw = -Double.greatestFiniteMagnitude
54 | return inputState
55 | }
56 |
57 | func testInputStateMaxValues() {
58 |
59 | let inputState = createInputStateMax()
60 | XCTAssertEqual(inputState.thrust, 1)
61 | XCTAssertEqual(inputState.pitch, 1)
62 | XCTAssertEqual(inputState.roll, 1)
63 | XCTAssertEqual(inputState.yaw, 1)
64 | }
65 |
66 | func testInputStateMinValues() {
67 |
68 | let inputState = createInputStateMin()
69 | XCTAssertEqual(inputState.thrust, 0)
70 | XCTAssertEqual(inputState.pitch, -1)
71 | XCTAssertEqual(inputState.roll, -1)
72 | XCTAssertEqual(inputState.yaw, -1)
73 | }
74 |
75 | func testInputStateNormalizedValues() {
76 |
77 | let normalized = InputState().normalized()
78 |
79 | XCTAssertEqual(normalized.thrust, 0)
80 | XCTAssertEqual(normalized.pitch, 0.5)
81 | XCTAssertEqual(normalized.roll, 0.5)
82 | XCTAssertEqual(normalized.yaw, 0.5)
83 | }
84 |
85 | func testInputStateMaxNormalizedValues() {
86 |
87 | let inputState = createInputStateMax()
88 | let normalized = inputState.normalized()
89 |
90 | XCTAssertEqual(normalized.thrust, 1)
91 | XCTAssertEqual(normalized.pitch, 1)
92 | XCTAssertEqual(normalized.roll, 1)
93 | XCTAssertEqual(normalized.yaw, 1)
94 | }
95 |
96 | func testInputStateMinNormalizedValues() {
97 |
98 | let inputState = createInputStateMin()
99 | let normalized = inputState.normalized()
100 |
101 | XCTAssertEqual(normalized.thrust, 0)
102 | XCTAssertEqual(normalized.pitch, 0)
103 | XCTAssertEqual(normalized.roll, 0)
104 | XCTAssertEqual(normalized.yaw, 0)
105 | }
106 |
107 | func testInputStateMaxByteValues() {
108 |
109 | let inputState = createInputStateMax()
110 |
111 | var thrust = Byte()
112 | var pitch = Byte()
113 | var roll = Byte()
114 | var yaw = Byte()
115 | inputState.getFinalValues(roll: &roll, pitch: &pitch, thrust: &thrust, yaw: &yaw)
116 |
117 | XCTAssertEqual(thrust, Byte.max)
118 | XCTAssertEqual(pitch, Byte.max)
119 | XCTAssertEqual(roll, Byte.max)
120 | XCTAssertEqual(yaw, Byte.max)
121 | }
122 |
123 | func testInputStateMinByteValues() {
124 |
125 | let inputState = createInputStateMin()
126 |
127 | var thrust = Byte()
128 | var pitch = Byte()
129 | var roll = Byte()
130 | var yaw = Byte()
131 | inputState.getFinalValues(roll: &roll, pitch: &pitch, thrust: &thrust, yaw: &yaw)
132 |
133 | XCTAssertEqual(thrust, Byte.min)
134 | XCTAssertEqual(pitch, Byte.min)
135 | XCTAssertEqual(roll, Byte.min)
136 | XCTAssertEqual(yaw, Byte.min)
137 | }
138 | }
139 |
--------------------------------------------------------------------------------