Merge branch 'SwiftyCarStarter' into 'master'
Swifty car starter See merge request vato007/picar!3
This commit is contained in:
14
.gitignore
vendored
14
.gitignore
vendored
@@ -20,4 +20,16 @@ build
|
|||||||
__pycache__
|
__pycache__
|
||||||
|
|
||||||
Pods
|
Pods
|
||||||
CarControlleriOS/**/car
|
CarControlleriOS/**/car
|
||||||
|
car/.idea
|
||||||
|
|
||||||
|
.build
|
||||||
|
SwiftyCar/Packages
|
||||||
|
SwiftyCar/*.xcodeproj
|
||||||
|
SwiftyCar/Sources/Generated
|
||||||
|
SwiftyCar/xcuserdata/
|
||||||
|
SwiftyCar/Sources/SwiftyCar/car
|
||||||
|
xcuserdata/
|
||||||
|
.settings
|
||||||
|
.project
|
||||||
|
.classpath
|
||||||
79
SwiftyCar/Package.resolved
Normal file
79
SwiftyCar/Package.resolved
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
{
|
||||||
|
"object": {
|
||||||
|
"pins": [
|
||||||
|
{
|
||||||
|
"package": "grpc-swift",
|
||||||
|
"repositoryURL": "https://github.com/grpc/grpc-swift.git",
|
||||||
|
"state": {
|
||||||
|
"branch": null,
|
||||||
|
"revision": "b83ee1ee2caa0660eb02444977b9b6e353c2adbf",
|
||||||
|
"version": "1.0.0-alpha.12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"package": "swift-log",
|
||||||
|
"repositoryURL": "https://github.com/apple/swift-log.git",
|
||||||
|
"state": {
|
||||||
|
"branch": null,
|
||||||
|
"revision": "74d7b91ceebc85daf387ebb206003f78813f71aa",
|
||||||
|
"version": "1.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"package": "swift-nio",
|
||||||
|
"repositoryURL": "https://github.com/apple/swift-nio.git",
|
||||||
|
"state": {
|
||||||
|
"branch": null,
|
||||||
|
"revision": "40bdad80882d307abe2c0bb36cf3bd4d3e03fe04",
|
||||||
|
"version": "2.16.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"package": "swift-nio-http2",
|
||||||
|
"repositoryURL": "https://github.com/apple/swift-nio-http2.git",
|
||||||
|
"state": {
|
||||||
|
"branch": null,
|
||||||
|
"revision": "82eb3fa0974b838358ee46bc6c5381e5ae5de6b9",
|
||||||
|
"version": "1.11.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"package": "swift-nio-ssl",
|
||||||
|
"repositoryURL": "https://github.com/apple/swift-nio-ssl.git",
|
||||||
|
"state": {
|
||||||
|
"branch": null,
|
||||||
|
"revision": "ae213938e151964aa691f0e902462fbe06baeeb6",
|
||||||
|
"version": "2.7.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"package": "swift-nio-transport-services",
|
||||||
|
"repositoryURL": "https://github.com/apple/swift-nio-transport-services.git",
|
||||||
|
"state": {
|
||||||
|
"branch": null,
|
||||||
|
"revision": "85a67aea7caf5396ed599543dd23cffeb6dbbf96",
|
||||||
|
"version": "1.5.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"package": "SwiftProtobuf",
|
||||||
|
"repositoryURL": "https://github.com/apple/swift-protobuf.git",
|
||||||
|
"state": {
|
||||||
|
"branch": null,
|
||||||
|
"revision": "7790acf0a81d08429cb20375bf42a8c7d279c5fe",
|
||||||
|
"version": "1.8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"package": "SwiftyGPIO",
|
||||||
|
"repositoryURL": "https://github.com/uraimo/SwiftyGPIO.git",
|
||||||
|
"state": {
|
||||||
|
"branch": null,
|
||||||
|
"revision": "4127ff9dd5c6aa8acb6be34b7ce2af2f6e0b942d",
|
||||||
|
"version": "1.1.14"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"version": 1
|
||||||
|
}
|
||||||
27
SwiftyCar/Package.swift
Normal file
27
SwiftyCar/Package.swift
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
// swift-tools-version:5.1
|
||||||
|
// The swift-tools-version declares the minimum version of Swift required to build this package.
|
||||||
|
|
||||||
|
import PackageDescription
|
||||||
|
|
||||||
|
let package = Package(
|
||||||
|
name: "SwiftyCar",
|
||||||
|
products: [
|
||||||
|
// Products define the executables and libraries produced by a package, and make them visible to other packages.
|
||||||
|
],
|
||||||
|
dependencies: [
|
||||||
|
// Dependencies declare other packages that this package depends on.
|
||||||
|
// .package(url: /* package url */, from: "1.0.0"),
|
||||||
|
.package(url: "https://github.com/grpc/grpc-swift.git", from: "1.0.0-alpha.12"),
|
||||||
|
.package(url: "https://github.com/uraimo/SwiftyGPIO.git", from: "1.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 which this package depends on.
|
||||||
|
.target(
|
||||||
|
name: "SwiftyCar",
|
||||||
|
dependencies: ["SwiftyGPIO", .product(name: "GRPC", package: "grpc-swift")]),
|
||||||
|
.testTarget(
|
||||||
|
name: "SwiftyCarTests",
|
||||||
|
dependencies: ["SwiftyCar"]),
|
||||||
|
]
|
||||||
|
)
|
||||||
57
SwiftyCar/Sources/SwiftyCar/MotorProvider.swift
Normal file
57
SwiftyCar/Sources/SwiftyCar/MotorProvider.swift
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
//
|
||||||
|
// MotorProvider.swift
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// Created by Michael Pivato on 13/5/20.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import GRPC
|
||||||
|
import NIO
|
||||||
|
import SwiftProtobuf
|
||||||
|
|
||||||
|
class MotorProvider: MotorControl_CarControlProvider{
|
||||||
|
private var vehicle: Vehicle2D
|
||||||
|
|
||||||
|
init(vehicle: Vehicle2D){
|
||||||
|
self.vehicle = vehicle
|
||||||
|
}
|
||||||
|
|
||||||
|
func set_throttle(request: MotorControl_ThrottleRequest, context: StatusOnlyCallContext) -> EventLoopFuture<MotorControl_ThrottleResponse> {
|
||||||
|
self.vehicle.throttle = request.throttle
|
||||||
|
return context.eventLoop.makeSucceededFuture(.with{
|
||||||
|
$0.throttleSet = true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func set_steering(request: MotorControl_SteeringRequest, context: StatusOnlyCallContext) -> EventLoopFuture<MotorControl_SteeringResponse> {
|
||||||
|
self.vehicle.steering = request.steering
|
||||||
|
return context.eventLoop.makeSucceededFuture(.with{
|
||||||
|
$0.steeringSet = true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func stream_vehicle_2d(context: UnaryResponseCallContext<Google_Protobuf_Empty>) -> EventLoopFuture<(StreamEvent<MotorControl_Vehicle2DRequest>) -> Void> {
|
||||||
|
return context.eventLoop.makeSucceededFuture({event in
|
||||||
|
switch event{
|
||||||
|
case .message(let movement):
|
||||||
|
self.vehicle.throttle = movement.throttle.throttle
|
||||||
|
self.vehicle.steering = movement.steering.steering
|
||||||
|
case .end:
|
||||||
|
context.responsePromise.succeed(Google_Protobuf_Empty())
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func record(request: MotorControl_RecordingReqeust, context: StatusOnlyCallContext) -> EventLoopFuture<Google_Protobuf_Empty> {
|
||||||
|
// TODO: Recording...
|
||||||
|
return context.eventLoop.makeSucceededFuture(Google_Protobuf_Empty())
|
||||||
|
}
|
||||||
|
|
||||||
|
func save_recorded_data(request: MotorControl_SaveRequest, context: StatusOnlyCallContext) -> EventLoopFuture<Google_Protobuf_Empty> {
|
||||||
|
// TODO Recording...
|
||||||
|
return context.eventLoop.makeSucceededFuture(Google_Protobuf_Empty())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
104
SwiftyCar/Sources/SwiftyCar/PwmWrappers.swift
Normal file
104
SwiftyCar/Sources/SwiftyCar/PwmWrappers.swift
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
//
|
||||||
|
// PwmWrappers.swift
|
||||||
|
//
|
||||||
|
// Simple wrappers for PwmOutput I'll create as I go along, to match gpioZero's implementation I'm currently using.
|
||||||
|
// See gpiozero code here: https://github.com/gpiozero/gpiozero/blob/master/gpiozero/output_devices.py
|
||||||
|
//
|
||||||
|
// Created by Michael Pivato on 9/5/20.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import SwiftyGPIO
|
||||||
|
|
||||||
|
enum ServoError: Error{
|
||||||
|
case invalidMinPulseWidth
|
||||||
|
case invalidMaxPulseWidth
|
||||||
|
}
|
||||||
|
|
||||||
|
class Servo{
|
||||||
|
private (set) var frameWidth : Float
|
||||||
|
private var minDc: Float
|
||||||
|
private var dcRange: Float
|
||||||
|
private var minValue: Float
|
||||||
|
private var valueRange: Float
|
||||||
|
private var internalValue: Float
|
||||||
|
|
||||||
|
// TODO: Use a factory to provide the correct pwm rather than passing the pin in.
|
||||||
|
private var device: PWMOutput
|
||||||
|
private var currentPulseWIdth: Float
|
||||||
|
|
||||||
|
var minPulseWidth: Float {
|
||||||
|
get{
|
||||||
|
return self.minDc * self.frameWidth
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var maxPulseWidth: Float{
|
||||||
|
get{
|
||||||
|
return (self.dcRange * self.frameWidth) + self.minPulseWidth
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Also store the pulse width (needs to be within this class)
|
||||||
|
// var pulseWidth: Float{
|
||||||
|
// get{
|
||||||
|
// return self.device//self.pwm_device.pin.state * self.frame_width
|
||||||
|
// }
|
||||||
|
// set (value){
|
||||||
|
//
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
var value: Float{
|
||||||
|
get{
|
||||||
|
internalValue
|
||||||
|
}
|
||||||
|
set(newValue){
|
||||||
|
self.internalValue = newValue
|
||||||
|
self.device.startPWM(period: Int(self.frameWidth),
|
||||||
|
// Multiple by 100 to get into percentage. I don't like that...
|
||||||
|
duty: self.minDc + self.dcRange * ((internalValue - self.minValue) / self.valueRange) * 100)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Change this to nanoseconds to match PWMOutput
|
||||||
|
init?(forPin pin: PWMOutput, startingAt initialValue: Float = 0, withMinPulseWidth minPulseWidth: Float = 1000000,
|
||||||
|
withMaxPulseWidth maxPulseWidth: Float = 2000000, withFrameWidth frameWidth: Float = 20000000) throws {
|
||||||
|
if(minPulseWidth >= maxPulseWidth){
|
||||||
|
throw ServoError.invalidMinPulseWidth
|
||||||
|
}
|
||||||
|
|
||||||
|
if(maxPulseWidth >= frameWidth){
|
||||||
|
throw ServoError.invalidMaxPulseWidth
|
||||||
|
}
|
||||||
|
self.frameWidth = frameWidth
|
||||||
|
self.minDc = minPulseWidth / frameWidth
|
||||||
|
self.dcRange = (maxPulseWidth - minPulseWidth) / frameWidth
|
||||||
|
self.minValue = -1
|
||||||
|
self.valueRange = 2
|
||||||
|
|
||||||
|
self.currentPulseWIdth = 0
|
||||||
|
|
||||||
|
// Initialise pin immediately.
|
||||||
|
self.device = pin
|
||||||
|
self.device.initPWM()
|
||||||
|
self.internalValue = initialValue
|
||||||
|
self.device.startPWM(period: Int(self.frameWidth), duty: self.minDc + self.dcRange * ((internalValue - self.minValue) / self.valueRange) * 100)
|
||||||
|
}
|
||||||
|
|
||||||
|
func min(){
|
||||||
|
self.value = -1
|
||||||
|
}
|
||||||
|
|
||||||
|
func mid(){
|
||||||
|
self.value = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func max() {
|
||||||
|
self.value = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
func detach(){
|
||||||
|
self.device.stopPWM()
|
||||||
|
}
|
||||||
|
}
|
||||||
65
SwiftyCar/Sources/SwiftyCar/Vehicle.swift
Normal file
65
SwiftyCar/Sources/SwiftyCar/Vehicle.swift
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
//
|
||||||
|
// Vehicle.swift
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// Created by Michael Pivato on 8/5/20.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import SwiftyGPIO
|
||||||
|
|
||||||
|
protocol Vehicle{
|
||||||
|
mutating func move2D(magnitude: Float, angle: Float)
|
||||||
|
}
|
||||||
|
|
||||||
|
protocol Vehicle2D: Vehicle{
|
||||||
|
var throttle: Float {get set}
|
||||||
|
var steering: Float {get set}
|
||||||
|
}
|
||||||
|
|
||||||
|
class MockVehicle: Vehicle2D{
|
||||||
|
var throttle: Float = 0
|
||||||
|
var steering: Float = 0
|
||||||
|
|
||||||
|
func move2D(magnitude: Float, angle: Float) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class RPiVehicle2D: Vehicle2D{
|
||||||
|
public let pwmThrottle: Servo
|
||||||
|
public let pwmSteering: Servo
|
||||||
|
|
||||||
|
var throttle: Float{
|
||||||
|
get{
|
||||||
|
return pwmThrottle.value
|
||||||
|
}
|
||||||
|
set(value){
|
||||||
|
pwmThrottle.value = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var steering: Float{
|
||||||
|
get{
|
||||||
|
return pwmSteering.value
|
||||||
|
}
|
||||||
|
set(value){
|
||||||
|
pwmSteering.value = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
init(withThrottlePin: Servo, withSteeringPin: Servo){
|
||||||
|
pwmThrottle = withThrottlePin
|
||||||
|
pwmSteering = withSteeringPin
|
||||||
|
}
|
||||||
|
|
||||||
|
func move2D(magnitude: Float, angle: Float) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func stop(){
|
||||||
|
pwmThrottle.detach()
|
||||||
|
pwmSteering.detach()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
27
SwiftyCar/Sources/SwiftyCar/VehicleFactory.swift
Normal file
27
SwiftyCar/Sources/SwiftyCar/VehicleFactory.swift
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
//
|
||||||
|
// File.swift
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// Created by Michael Pivato on 20/5/20.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import SwiftyGPIO
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
func getVehicle2D() throws -> Vehicle2D {
|
||||||
|
if let value = ProcessInfo.processInfo.environment["CAR_VEHICLE"] {
|
||||||
|
switch value{
|
||||||
|
case "CAR_2D":
|
||||||
|
// Get car for rpi.
|
||||||
|
let pwms = SwiftyGPIO.hardwarePWMs(for:.RaspberryPi3)!
|
||||||
|
|
||||||
|
// Read the feature database.
|
||||||
|
return try RPiVehicle2D(withThrottlePin: Servo(forPin: (pwms[0]?[.P18])!)!, withSteeringPin:Servo(forPin: (pwms[1]?[.P19])!)!)
|
||||||
|
default:
|
||||||
|
return MockVehicle()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return MockVehicle()
|
||||||
|
}
|
||||||
47
SwiftyCar/Sources/SwiftyCar/main.swift
Normal file
47
SwiftyCar/Sources/SwiftyCar/main.swift
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
//
|
||||||
|
// main.swift
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// Created by Michael Pivato on 8/5/20.
|
||||||
|
//
|
||||||
|
|
||||||
|
import NIO
|
||||||
|
import GRPC
|
||||||
|
|
||||||
|
func doServer() throws {
|
||||||
|
// Copied from examples
|
||||||
|
// Create an event loop group for the server to run on.
|
||||||
|
let group = MultiThreadedEventLoopGroup(numberOfThreads: System.coreCount)
|
||||||
|
defer {
|
||||||
|
try! group.syncShutdownGracefully()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Create a provider using the features we read.
|
||||||
|
let provider = try MotorProvider(vehicle: getVehicle2D())
|
||||||
|
|
||||||
|
// Start the server and print its address once it has started.
|
||||||
|
let server = Server.insecure(group: group)
|
||||||
|
.withServiceProviders([provider])
|
||||||
|
.bind(host: "localhost", port: 0)
|
||||||
|
|
||||||
|
server.map {
|
||||||
|
$0.channel.localAddress
|
||||||
|
}.whenSuccess { address in
|
||||||
|
print("server started on port \(address!.port!)")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait on the server's `onClose` future to stop the program from exiting.
|
||||||
|
_ = try server.flatMap {
|
||||||
|
$0.onClose
|
||||||
|
}.wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Entry-Point to the Swift Car Controller
|
||||||
|
print("Starting Server")
|
||||||
|
do{
|
||||||
|
try doServer()
|
||||||
|
}
|
||||||
|
catch{
|
||||||
|
print("Server failed")
|
||||||
|
}
|
||||||
7
SwiftyCar/Tests/LinuxMain.swift
Normal file
7
SwiftyCar/Tests/LinuxMain.swift
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import XCTest
|
||||||
|
|
||||||
|
import SwiftyCarTests
|
||||||
|
|
||||||
|
var tests = [XCTestCaseEntry]()
|
||||||
|
tests += SwiftyCarTests.allTests()
|
||||||
|
XCTMain(tests)
|
||||||
15
SwiftyCar/Tests/SwiftyCarTests/SwiftyCarTests.swift
Normal file
15
SwiftyCar/Tests/SwiftyCarTests/SwiftyCarTests.swift
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import XCTest
|
||||||
|
@testable import SwiftyCar
|
||||||
|
|
||||||
|
final class SwiftyCarTests: XCTestCase {
|
||||||
|
func testExample() {
|
||||||
|
// This is an example of a functional test case.
|
||||||
|
// Use XCTAssert and related functions to verify your tests produce the correct
|
||||||
|
// results.
|
||||||
|
// XCTAssertEqual(SwiftyCar().text, "Hello, World!")
|
||||||
|
}
|
||||||
|
|
||||||
|
static var allTests = [
|
||||||
|
("testExample", testExample),
|
||||||
|
]
|
||||||
|
}
|
||||||
9
SwiftyCar/Tests/SwiftyCarTests/XCTestManifests.swift
Normal file
9
SwiftyCar/Tests/SwiftyCarTests/XCTestManifests.swift
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import XCTest
|
||||||
|
|
||||||
|
#if !canImport(ObjectiveC)
|
||||||
|
public func allTests() -> [XCTestCaseEntry] {
|
||||||
|
return [
|
||||||
|
testCase(SwiftyCarTests.allTests),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
#endif
|
||||||
16
SwiftyCar/build.gradle
Normal file
16
SwiftyCar/build.gradle
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
configurations{
|
||||||
|
swift {
|
||||||
|
canBeConsumed = false
|
||||||
|
canBeResolved = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
swift project(path: ':protobuf', configuration: 'swift')
|
||||||
|
}
|
||||||
|
|
||||||
|
task copySwiftCode(type: Copy, dependsOn: configurations.swift) {
|
||||||
|
// Copy python protobuf code from proto project.
|
||||||
|
from zipTree(configurations.swift.asPath)
|
||||||
|
into './Sources/SwiftyCar'
|
||||||
|
}
|
||||||
@@ -2,4 +2,5 @@ include ':app'
|
|||||||
include ':protobuf'
|
include ':protobuf'
|
||||||
include 'car'
|
include 'car'
|
||||||
include 'CarControlleriOS'
|
include 'CarControlleriOS'
|
||||||
|
include 'SwiftyCar'
|
||||||
rootProject.name='CarController'
|
rootProject.name='CarController'
|
||||||
|
|||||||
Reference in New Issue
Block a user