Merge branch 'car-rs' into 'master'
Replace swift implementation with rust See merge request vato007/picar!10
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -35,3 +35,4 @@ xcuserdata/
|
|||||||
.classpath
|
.classpath
|
||||||
|
|
||||||
.vscode/settings.json
|
.vscode/settings.json
|
||||||
|
**/target/**
|
||||||
1
.idea/.name
generated
Normal file
1
.idea/.name
generated
Normal file
@@ -0,0 +1 @@
|
|||||||
|
CarController
|
||||||
6
.idea/compiler.xml
generated
Normal file
6
.idea/compiler.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="CompilerConfiguration">
|
||||||
|
<bytecodeTargetLevel target="11" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
17
.idea/deploymentTargetDropDown.xml
generated
Normal file
17
.idea/deploymentTargetDropDown.xml
generated
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="deploymentTargetDropDown">
|
||||||
|
<targetSelectedWithDropDown>
|
||||||
|
<Target>
|
||||||
|
<type value="QUICK_BOOT_TARGET" />
|
||||||
|
<deviceKey>
|
||||||
|
<Key>
|
||||||
|
<type value="VIRTUAL_DEVICE_PATH" />
|
||||||
|
<value value="$USER_HOME$/.android/avd/Pixel_3a_API_33_x86_64.avd" />
|
||||||
|
</Key>
|
||||||
|
</deviceKey>
|
||||||
|
</Target>
|
||||||
|
</targetSelectedWithDropDown>
|
||||||
|
<timeTargetWasSelectedWithDropDown value="2022-10-15T03:44:49.580415Z" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
4
.idea/gradle.xml
generated
4
.idea/gradle.xml
generated
@@ -4,9 +4,10 @@
|
|||||||
<component name="GradleSettings">
|
<component name="GradleSettings">
|
||||||
<option name="linkedExternalProjectsSettings">
|
<option name="linkedExternalProjectsSettings">
|
||||||
<GradleProjectSettings>
|
<GradleProjectSettings>
|
||||||
<option name="testRunner" value="PLATFORM" />
|
<option name="testRunner" value="GRADLE" />
|
||||||
<option name="distributionType" value="DEFAULT_WRAPPED" />
|
<option name="distributionType" value="DEFAULT_WRAPPED" />
|
||||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||||
|
<option name="gradleJvm" value="Embedded JDK" />
|
||||||
<option name="modules">
|
<option name="modules">
|
||||||
<set>
|
<set>
|
||||||
<option value="$PROJECT_DIR$" />
|
<option value="$PROJECT_DIR$" />
|
||||||
@@ -17,7 +18,6 @@
|
|||||||
<option value="$PROJECT_DIR$/pycar" />
|
<option value="$PROJECT_DIR$/pycar" />
|
||||||
</set>
|
</set>
|
||||||
</option>
|
</option>
|
||||||
<option name="resolveModulePerSourceSet" value="false" />
|
|
||||||
</GradleProjectSettings>
|
</GradleProjectSettings>
|
||||||
</option>
|
</option>
|
||||||
</component>
|
</component>
|
||||||
|
|||||||
40
.idea/jarRepositories.xml
generated
Normal file
40
.idea/jarRepositories.xml
generated
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="RemoteRepositoriesConfiguration">
|
||||||
|
<remote-repository>
|
||||||
|
<option name="id" value="central" />
|
||||||
|
<option name="name" value="Maven Central repository" />
|
||||||
|
<option name="url" value="https://repo1.maven.org/maven2" />
|
||||||
|
</remote-repository>
|
||||||
|
<remote-repository>
|
||||||
|
<option name="id" value="jboss.community" />
|
||||||
|
<option name="name" value="JBoss Community repository" />
|
||||||
|
<option name="url" value="https://repository.jboss.org/nexus/content/repositories/public/" />
|
||||||
|
</remote-repository>
|
||||||
|
<remote-repository>
|
||||||
|
<option name="id" value="MavenLocal" />
|
||||||
|
<option name="name" value="MavenLocal" />
|
||||||
|
<option name="url" value="file:$USER_HOME$/.m2/repository" />
|
||||||
|
</remote-repository>
|
||||||
|
<remote-repository>
|
||||||
|
<option name="id" value="BintrayJCenter" />
|
||||||
|
<option name="name" value="BintrayJCenter" />
|
||||||
|
<option name="url" value="https://jcenter.bintray.com/" />
|
||||||
|
</remote-repository>
|
||||||
|
<remote-repository>
|
||||||
|
<option name="id" value="Google" />
|
||||||
|
<option name="name" value="Google" />
|
||||||
|
<option name="url" value="https://dl.google.com/dl/android/maven2/" />
|
||||||
|
</remote-repository>
|
||||||
|
<remote-repository>
|
||||||
|
<option name="id" value="MavenLocal" />
|
||||||
|
<option name="name" value="MavenLocal" />
|
||||||
|
<option name="url" value="file:$USER_HOME$/.m2/repository/" />
|
||||||
|
</remote-repository>
|
||||||
|
<remote-repository>
|
||||||
|
<option name="id" value="MavenRepo" />
|
||||||
|
<option name="name" value="MavenRepo" />
|
||||||
|
<option name="url" value="https://repo.maven.apache.org/maven2/" />
|
||||||
|
</remote-repository>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
2
.idea/misc.xml
generated
2
.idea/misc.xml
generated
@@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" project-jdk-name="1.8" project-jdk-type="JavaSDK">
|
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="11" project-jdk-type="JavaSDK">
|
||||||
<output url="file://$PROJECT_DIR$/build/classes" />
|
<output url="file://$PROJECT_DIR$/build/classes" />
|
||||||
</component>
|
</component>
|
||||||
<component name="ProjectType">
|
<component name="ProjectType">
|
||||||
|
|||||||
12
.idea/runConfigurations.xml
generated
12
.idea/runConfigurations.xml
generated
@@ -1,12 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="RunConfigurationProducerService">
|
|
||||||
<option name="ignoredProducers">
|
|
||||||
<set>
|
|
||||||
<option value="org.jetbrains.plugins.gradle.execution.test.runner.AllInPackageGradleConfigurationProducer" />
|
|
||||||
<option value="org.jetbrains.plugins.gradle.execution.test.runner.TestClassGradleConfigurationProducer" />
|
|
||||||
<option value="org.jetbrains.plugins.gradle.execution.test.runner.TestMethodGradleConfigurationProducer" />
|
|
||||||
</set>
|
|
||||||
</option>
|
|
||||||
</component>
|
|
||||||
</project>
|
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
This software allows for the control of an RC Car using a linux system. Currently it's been tested to work on a Traxxas Slash and raspberry pi 3b+.
|
This software allows for the control of an RC Car using a linux system. Currently it's been tested to work on a Traxxas Slash and raspberry pi 3b+.
|
||||||
|
|
||||||
See the individuals modules for more detailed READMEs, including build instructions.
|
See the individual modules for more detailed READMEs, including build instructions.
|
||||||
|
|
||||||
### Current Functinoality
|
### Current Functinoality
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +0,0 @@
|
|||||||
FROM vato.ddns.net:8083/swift:latest as builder
|
|
||||||
WORKDIR /root
|
|
||||||
COPY . .
|
|
||||||
RUN swift build -c release
|
|
||||||
|
|
||||||
FROM vato.ddns.net:8083/swift:slim
|
|
||||||
WORKDIR /root
|
|
||||||
COPY --from=builder /root .
|
|
||||||
CMD [".build/x86_64-unknown-linux/release/docker-test"]
|
|
||||||
@@ -1,106 +0,0 @@
|
|||||||
{
|
|
||||||
"object": {
|
|
||||||
"pins": [
|
|
||||||
{
|
|
||||||
"package": "grpc-swift",
|
|
||||||
"repositoryURL": "https://github.com/grpc/grpc-swift.git",
|
|
||||||
"state": {
|
|
||||||
"branch": null,
|
|
||||||
"revision": "640b0ef1d0be63bda0ada86786cfda678ab2aae9",
|
|
||||||
"version": "1.0.0-alpha.19"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"package": "swift-log",
|
|
||||||
"repositoryURL": "https://github.com/apple/swift-log.git",
|
|
||||||
"state": {
|
|
||||||
"branch": null,
|
|
||||||
"revision": "173f567a2dfec11d74588eea82cecea555bdc0bc",
|
|
||||||
"version": "1.4.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"package": "swift-nio",
|
|
||||||
"repositoryURL": "https://github.com/apple/swift-nio.git",
|
|
||||||
"state": {
|
|
||||||
"branch": null,
|
|
||||||
"revision": "5fc24345f92ec4c274121776c215ab0aa1ed4d50",
|
|
||||||
"version": "2.22.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"package": "swift-nio-http2",
|
|
||||||
"repositoryURL": "https://github.com/apple/swift-nio-http2.git",
|
|
||||||
"state": {
|
|
||||||
"branch": null,
|
|
||||||
"revision": "1e68e51752be0b43c5a0ef35818c1dd24d13e77c",
|
|
||||||
"version": "1.14.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"package": "swift-nio-ssl",
|
|
||||||
"repositoryURL": "https://github.com/apple/swift-nio-ssl.git",
|
|
||||||
"state": {
|
|
||||||
"branch": null,
|
|
||||||
"revision": "ea1dfd64193bf5af4490635a4a44c4fb43b1e1ae",
|
|
||||||
"version": "2.9.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"package": "swift-nio-transport-services",
|
|
||||||
"repositoryURL": "https://github.com/apple/swift-nio-transport-services.git",
|
|
||||||
"state": {
|
|
||||||
"branch": null,
|
|
||||||
"revision": "bb56586c4cab9a79dce6ec4738baddb5802c5de7",
|
|
||||||
"version": "1.9.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"package": "SwiftProtobuf",
|
|
||||||
"repositoryURL": "https://github.com/apple/swift-protobuf.git",
|
|
||||||
"state": {
|
|
||||||
"branch": null,
|
|
||||||
"revision": "0279688c9fc5a40028e1b5bb0cb56534a45a6020",
|
|
||||||
"version": "1.12.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"package": "Swift2dCar",
|
|
||||||
"repositoryURL": "https://vato.ddns.net/gitlab/vato007/swift2dcar.git",
|
|
||||||
"state": {
|
|
||||||
"branch": "master",
|
|
||||||
"revision": "970aac902531408614db0a37a7300e9373dafb50",
|
|
||||||
"version": null
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"package": "SwiftRPLidar",
|
|
||||||
"repositoryURL": "https://vato.ddns.net/gitlab/vato007/swiftrplidar.git",
|
|
||||||
"state": {
|
|
||||||
"branch": "master",
|
|
||||||
"revision": "761eb0bc1a00b4627a7870ffac121a542ff0cd6b",
|
|
||||||
"version": null
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"package": "SwiftSerial",
|
|
||||||
"repositoryURL": "https://vato.ddns.net/gitlab/vato007/SwiftSerial.git",
|
|
||||||
"state": {
|
|
||||||
"branch": "master",
|
|
||||||
"revision": "27a5d92aa00f6e91581389485994364e16bed2c5",
|
|
||||||
"version": null
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"package": "SwiftyGPIO",
|
|
||||||
"repositoryURL": "https://github.com/uraimo/SwiftyGPIO.git",
|
|
||||||
"state": {
|
|
||||||
"branch": null,
|
|
||||||
"revision": "2038228e020cf12a62012b1ebe36bb9b8e6fdb6a",
|
|
||||||
"version": "1.2.5"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"version": 1
|
|
||||||
}
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
// 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.19"),
|
|
||||||
.package(url: "https://github.com/uraimo/SwiftyGPIO.git", from: "1.0.0"),
|
|
||||||
.package(url: "https://vato.ddns.net/gitlab/vato007/swiftrplidar.git", .branch("master")),
|
|
||||||
.package(url: "https://vato.ddns.net/gitlab/vato007/SwiftSerial.git", .branch("master")),
|
|
||||||
.package(url: "https://vato.ddns.net/gitlab/vato007/swift2dcar.git", .branch("master"))
|
|
||||||
],
|
|
||||||
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"),
|
|
||||||
"SwiftRPLidar",
|
|
||||||
"SwiftSerial",
|
|
||||||
"Swift2dCar"]),
|
|
||||||
.testTarget(
|
|
||||||
name: "SwiftyCarTests",
|
|
||||||
dependencies: ["SwiftyCar"]),
|
|
||||||
]
|
|
||||||
)
|
|
||||||
@@ -1,61 +0,0 @@
|
|||||||
//
|
|
||||||
// LidarProvider.swift
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// Created by Michael Pivato on 10/7/20.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
import GRPC
|
|
||||||
import NIO
|
|
||||||
import SwiftProtobuf
|
|
||||||
import SwiftRPLidar
|
|
||||||
|
|
||||||
class LidarProvider: Persontracking_PersonTrackingProvider {
|
|
||||||
|
|
||||||
private let lidar: SwiftRPLidar
|
|
||||||
private var shouldScan: Bool = false
|
|
||||||
|
|
||||||
init(lidar: SwiftRPLidar) {
|
|
||||||
self.lidar = lidar
|
|
||||||
}
|
|
||||||
|
|
||||||
func set_tracking_group(request: Persontracking_Int32Value, context: StatusOnlyCallContext) -> EventLoopFuture<Google_Protobuf_Empty> {
|
|
||||||
return context.eventLoop.makeSucceededFuture(Google_Protobuf_Empty())
|
|
||||||
}
|
|
||||||
|
|
||||||
func stop_tracking(request: Google_Protobuf_Empty, context: StatusOnlyCallContext) -> EventLoopFuture<Google_Protobuf_Empty> {
|
|
||||||
shouldScan = false
|
|
||||||
return context.eventLoop.makeSucceededFuture(Google_Protobuf_Empty())
|
|
||||||
}
|
|
||||||
|
|
||||||
func start_tracking(request: Google_Protobuf_Empty, context: StatusOnlyCallContext) -> EventLoopFuture<Google_Protobuf_Empty> {
|
|
||||||
return context.eventLoop.makeSucceededFuture(Google_Protobuf_Empty())
|
|
||||||
}
|
|
||||||
|
|
||||||
func record(request: Google_Protobuf_BoolValue, context: StatusOnlyCallContext) -> EventLoopFuture<Google_Protobuf_Empty> {
|
|
||||||
return context.eventLoop.makeSucceededFuture(Google_Protobuf_Empty())
|
|
||||||
}
|
|
||||||
|
|
||||||
func save_lidar(request: MotorControl_SaveRequest, context: StatusOnlyCallContext) -> EventLoopFuture<Google_Protobuf_Empty> {
|
|
||||||
return context.eventLoop.makeSucceededFuture(Google_Protobuf_Empty())
|
|
||||||
}
|
|
||||||
|
|
||||||
func lidar_stream(request: Persontracking_StreamMessage, context: StreamingResponseCallContext<Persontracking_PointScan>) -> EventLoopFuture<GRPCStatus> {
|
|
||||||
shouldScan = true
|
|
||||||
try! lidar.iterScans{scan in
|
|
||||||
_ = context.sendResponse(.with{protoScan in
|
|
||||||
protoScan.points = scan.map{ point in
|
|
||||||
Persontracking_Point.with{ protoPoint in
|
|
||||||
protoPoint.angle = Double(point.angle)
|
|
||||||
protoPoint.distance = Double(point.distance)
|
|
||||||
// Placeholder group number.
|
|
||||||
protoPoint.groupNumber = 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return shouldScan
|
|
||||||
}
|
|
||||||
return context.eventLoop.makeSucceededFuture(.ok)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
//
|
|
||||||
// MotorProvider.swift
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// Created by Michael Pivato on 13/5/20.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
import GRPC
|
|
||||||
import NIO
|
|
||||||
import SwiftProtobuf
|
|
||||||
import Swift2dCar
|
|
||||||
|
|
||||||
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{ throttle in
|
|
||||||
throttle.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())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
//
|
|
||||||
// Vehicle.swift
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// Created by Michael Pivato on 8/5/20.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
import Swift2dCar
|
|
||||||
|
|
||||||
public typealias ThrottleHandler = (_ magnitude: Float) -> Float
|
|
||||||
public typealias SteeringHandler = (_ angle: Float) -> Float
|
|
||||||
|
|
||||||
|
|
||||||
public class IntelligentPiCar : RPiVehicle2D {
|
|
||||||
|
|
||||||
private var vehicleLength: Float?
|
|
||||||
private var throttleFunc: ThrottleHandler?
|
|
||||||
private var steeringFunc: SteeringHandler?
|
|
||||||
|
|
||||||
/**:
|
|
||||||
Calibration function for how the car moves (acoording to a bicycle model) for a given throttle/steering angle. This sets the way the
|
|
||||||
- Parameters
|
|
||||||
- carLength
|
|
||||||
*/
|
|
||||||
func calibrate(vehicleLength: Float, throttleFunc: @escaping ThrottleHandler, steeringFunc: @escaping SteeringHandler){
|
|
||||||
// Define a function that indicates how the throttle/steering should be set for given magnitude/angle to move.
|
|
||||||
self.vehicleLength = vehicleLength
|
|
||||||
self.throttleFunc = throttleFunc
|
|
||||||
self.steeringFunc = steeringFunc
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
Move the car by the given magnitude and angle, depending on how the is known to move with apriori throttle/steering.
|
|
||||||
*/
|
|
||||||
func move2D(magnitude: Float, angle: Float) {
|
|
||||||
if let throttleFunc = self.throttleFunc {
|
|
||||||
// self.pwmThrottle.value = throttleFunc(magnitude)
|
|
||||||
}
|
|
||||||
|
|
||||||
if let steeringFunc = steeringFunc {
|
|
||||||
// self.pwmSteering.value = steeringFunc(angle)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
Move to the coordinates relative to the car. You must first calibrate the car. A bicycle model is assumed.
|
|
||||||
*/
|
|
||||||
func moveRelativeTo2D(x: Float, y: Float) {
|
|
||||||
// TODO: This function, has a lot of edge cases. Also is really
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
//
|
|
||||||
// VehicleFactory.swift
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// Created by Michael Pivato on 20/5/20.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
import SwiftyGPIO
|
|
||||||
import Swift2dCar
|
|
||||||
import SwiftSerial
|
|
||||||
|
|
||||||
func getVehicle2D() throws -> Vehicle2D? {
|
|
||||||
// TODO: Clean up this factory, or see if we can get dependency injection working.
|
|
||||||
if let value = ProcessInfo.processInfo.environment["CAR_VEHICLE"] {
|
|
||||||
switch value{
|
|
||||||
case "VEHICLE_2D":
|
|
||||||
// Get car for rpi.
|
|
||||||
let pwms = SwiftyGPIO.hardwarePWMs(for:.RaspberryPi3)!
|
|
||||||
return try RPiVehicle2D(withThrottlePin: PWMHardwareServo(forPin: (pwms[0]?[.P18])!)!, withSteeringPin:PWMHardwareServo(forPin: (pwms[1]?[.P19])!)!)
|
|
||||||
case "VEHICLE_SERIAL":
|
|
||||||
// TODO: Get from environment variable. tty won't work in macos anyway.
|
|
||||||
// We share the serialport object, as cu will block on macOS (required by SwiftSerial), so can't open 2 of the same port.
|
|
||||||
let serialPort = SerialPort(path: "/dev/ttyUSB0")
|
|
||||||
|
|
||||||
// The port does not open/initialise inside of the ESP32ServoOutputs, as on macOS /dev/cu.* blocks.
|
|
||||||
try serialPort.openPort()
|
|
||||||
serialPort.setSettings(receiveRate: .baud115200, transmitRate: .baud115200, minimumBytesToRead: 1)
|
|
||||||
guard let throttlePin = Esp32ServoOutput(forChannel: 1, forPin: 14, onPort: serialPort) else {
|
|
||||||
print("Failed to create throttle pin.")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
guard let steeringPin = Esp32ServoOutput(forChannel: 2, forPin: 12, onPort: serialPort) else {
|
|
||||||
print("Failed to create steering pin.")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return try RPiVehicle2D(withThrottlePin: PWMHardwareServo(forPin: throttlePin)!, withSteeringPin: PWMHardwareServo(forPin: steeringPin)!)
|
|
||||||
default:
|
|
||||||
return MockVehicle()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return MockVehicle()
|
|
||||||
}
|
|
||||||
@@ -1,70 +0,0 @@
|
|||||||
//
|
|
||||||
// main.swift
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// Created by Michael Pivato on 8/5/20.
|
|
||||||
//
|
|
||||||
|
|
||||||
import NIO
|
|
||||||
import GRPC
|
|
||||||
import SwiftRPLidar
|
|
||||||
import SwiftSerial
|
|
||||||
|
|
||||||
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()
|
|
||||||
}
|
|
||||||
|
|
||||||
let lidar = createLidar()
|
|
||||||
try lidar.iterMeasurements{measruement in
|
|
||||||
print(measruement.quality)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
// Create a provider using the features we read.
|
|
||||||
let provider = try MotorProvider(vehicle: getVehicle2D()!)
|
|
||||||
let trackingProvider = LidarProvider(lidar: lidar)
|
|
||||||
|
|
||||||
// Start the server and print its address once it has started.
|
|
||||||
let server = Server.insecure(group: group)
|
|
||||||
.withServiceProviders([provider, trackingProvider])
|
|
||||||
.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()
|
|
||||||
}
|
|
||||||
|
|
||||||
func createLidar() -> SwiftRPLidar{
|
|
||||||
return try! SwiftRPLidar(onPort: SerialPort(path: "/dev/cu.usbserial0001"))
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Entry-Point to the Swift Car Controller
|
|
||||||
print("Starting Server")
|
|
||||||
do{
|
|
||||||
try doServer()
|
|
||||||
}
|
|
||||||
catch{
|
|
||||||
print("Server failed")
|
|
||||||
}
|
|
||||||
|
|
||||||
extension SerialPort: LidarSerial{
|
|
||||||
public func setBaudrate(baudrate: Int) {
|
|
||||||
// TODO: handle different baudrates. Only need this for now.
|
|
||||||
switch baudrate{
|
|
||||||
default:
|
|
||||||
setSettings(receiveRate: .baud115200, transmitRate: .baud115200, minimumBytesToRead: 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
import XCTest
|
|
||||||
|
|
||||||
import SwiftyCarTests
|
|
||||||
|
|
||||||
var tests = [XCTestCaseEntry]()
|
|
||||||
tests += SwiftyCarTests.allTests()
|
|
||||||
XCTMain(tests)
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
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),
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
import XCTest
|
|
||||||
|
|
||||||
#if !canImport(ObjectiveC)
|
|
||||||
public func allTests() -> [XCTestCaseEntry] {
|
|
||||||
return [
|
|
||||||
testCase(SwiftyCarTests.allTests),
|
|
||||||
]
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
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'
|
|
||||||
}
|
|
||||||
@@ -4,12 +4,11 @@ plugins{
|
|||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion 30
|
compileSdkVersion 32
|
||||||
buildToolsVersion "29.0.2"
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId "org.vato.carcontroller"
|
applicationId "org.vato.carcontroller"
|
||||||
minSdkVersion 26
|
minSdkVersion 26
|
||||||
targetSdkVersion 30
|
targetSdkVersion 32
|
||||||
versionCode 1
|
versionCode 1
|
||||||
versionName "1.0"
|
versionName "1.0"
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
@@ -27,24 +26,25 @@ android {
|
|||||||
buildFeatures {
|
buildFeatures {
|
||||||
mlModelBinding true
|
mlModelBinding true
|
||||||
}
|
}
|
||||||
|
namespace 'org.vato.carcontroller'
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation project(':protobuf')
|
implementation project(':protobuf')
|
||||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||||
implementation 'androidx.appcompat:appcompat:1.2.0'
|
implementation 'androidx.appcompat:appcompat:1.4.2'
|
||||||
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
|
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
|
||||||
implementation 'com.google.android.material:material:1.3.0'
|
implementation 'com.google.android.material:material:1.6.1'
|
||||||
implementation 'androidx.preference:preference:1.1.1'
|
implementation 'androidx.preference:preference:1.2.0'
|
||||||
implementation 'org.tensorflow:tensorflow-lite-support:0.1.0'
|
implementation 'org.tensorflow:tensorflow-lite-support:0.1.0'
|
||||||
implementation 'org.tensorflow:tensorflow-lite-metadata:0.1.0-rc1'
|
implementation 'org.tensorflow:tensorflow-lite-metadata:0.1.0-rc2'
|
||||||
implementation 'org.tensorflow:tensorflow-lite-gpu:2.3.0'
|
implementation 'org.tensorflow:tensorflow-lite-gpu:2.3.0'
|
||||||
testImplementation 'junit:junit:4.13.2'
|
testImplementation 'junit:junit:4.13.2'
|
||||||
androidTestImplementation 'androidx.test:runner:1.3.0'
|
androidTestImplementation 'androidx.test:runner:1.4.0'
|
||||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
|
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
|
||||||
implementation 'io.grpc:grpc-okhttp:1.29.0' // CURRENT_GRPC_VERSION
|
implementation 'io.grpc:grpc-okhttp:1.29.0'
|
||||||
implementation 'io.grpc:grpc-protobuf-lite:1.29.0' // CURRENT_GRPC_VERSION
|
implementation 'io.grpc:grpc-protobuf-lite:1.39.0'
|
||||||
implementation 'io.grpc:grpc-stub:1.29.0' // CURRENT_GRPC_VERSION
|
implementation 'io.grpc:grpc-stub:1.39.0'
|
||||||
implementation 'javax.annotation:javax.annotation-api:1.2'
|
implementation 'javax.annotation:javax.annotation-api:1.3.2'
|
||||||
implementation 'org.zeromq:jeromq:0.5.2'
|
implementation 'org.zeromq:jeromq:0.5.2'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
package="org.vato.carcontroller">
|
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||||
@@ -12,7 +11,7 @@
|
|||||||
android:roundIcon="@mipmap/ic_launcher_round"
|
android:roundIcon="@mipmap/ic_launcher_round"
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
android:theme="@style/AppTheme">
|
android:theme="@style/AppTheme">
|
||||||
<activity android:name="org.vato.carcontroller.SLAM.SlamController"></activity>
|
<activity android:name="org.vato.carcontroller.SLAM.SlamController" />
|
||||||
<activity android:name="org.vato.carcontroller.LIDAR.LidarTrackingController" />
|
<activity android:name="org.vato.carcontroller.LIDAR.LidarTrackingController" />
|
||||||
<activity
|
<activity
|
||||||
android:name="org.vato.carcontroller.SettingsActivity"
|
android:name="org.vato.carcontroller.SettingsActivity"
|
||||||
@@ -20,6 +19,7 @@
|
|||||||
android:parentActivityName="org.vato.carcontroller.MainActivity" />
|
android:parentActivityName="org.vato.carcontroller.MainActivity" />
|
||||||
<activity android:name="org.vato.carcontroller.SimpleController" />
|
<activity android:name="org.vato.carcontroller.SimpleController" />
|
||||||
<activity
|
<activity
|
||||||
|
android:exported="true"
|
||||||
android:name="org.vato.carcontroller.MainActivity"
|
android:name="org.vato.carcontroller.MainActivity"
|
||||||
android:theme="@style/AppTheme.NoActionBar">
|
android:theme="@style/AppTheme.NoActionBar">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
|
|||||||
@@ -17,12 +17,12 @@ public class PiLoader implements Runnable {
|
|||||||
|
|
||||||
private Integer steeringValue = 50;
|
private Integer steeringValue = 50;
|
||||||
private Integer throttleValue = 50;
|
private Integer throttleValue = 50;
|
||||||
private ManagedChannel mChannel;
|
private final ManagedChannel mChannel;
|
||||||
|
|
||||||
private CarControlGrpc.CarControlBlockingStub stub;
|
private final CarControlGrpc.CarControlBlockingStub stub;
|
||||||
private AtomicBoolean stop = new AtomicBoolean(false);
|
private final AtomicBoolean stop = new AtomicBoolean(false);
|
||||||
private Thread piUpdaterThread;
|
private Thread piUpdaterThread;
|
||||||
private boolean useGrpcStream;
|
private final boolean useGrpcStream;
|
||||||
|
|
||||||
public PiLoader(String host, Integer port, boolean useGrpcStream) {
|
public PiLoader(String host, Integer port, boolean useGrpcStream) {
|
||||||
mChannel = ManagedChannelBuilder.forAddress(host, port).usePlaintext().build();
|
mChannel = ManagedChannelBuilder.forAddress(host, port).usePlaintext().build();
|
||||||
@@ -72,10 +72,10 @@ public class PiLoader implements Runnable {
|
|||||||
try {
|
try {
|
||||||
SteeringResponse steeringResponse = stub.setSteering(
|
SteeringResponse steeringResponse = stub.setSteering(
|
||||||
SteeringRequest.newBuilder().setSteering((float) steeringValue / 50f - 1)
|
SteeringRequest.newBuilder().setSteering((float) steeringValue / 50f - 1)
|
||||||
.build());
|
.build());
|
||||||
ThrottleResponse throttleResponse = stub.setThrottle(
|
ThrottleResponse throttleResponse = stub.setThrottle(
|
||||||
ThrottleRequest.newBuilder().setThrottle((float) throttleValue / 50f - 1)
|
ThrottleRequest.newBuilder().setThrottle((float) throttleValue / 50f - 1)
|
||||||
.build());
|
.build());
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
System.out.println("Error");
|
System.out.println("Error");
|
||||||
stop();
|
stop();
|
||||||
@@ -93,9 +93,9 @@ public class PiLoader implements Runnable {
|
|||||||
private void doStreamUpdates() {
|
private void doStreamUpdates() {
|
||||||
// Stream if user sets use_grpc_streams to true. This method is more efficient but less compatible.
|
// Stream if user sets use_grpc_streams to true. This method is more efficient but less compatible.
|
||||||
final CountDownLatch finishLatch = new CountDownLatch(1);
|
final CountDownLatch finishLatch = new CountDownLatch(1);
|
||||||
StreamObserver<Empty> responseObserver = new StreamObserver<Empty>() {
|
StreamObserver<Vehicle2DResponse> responseObserver = new StreamObserver<Vehicle2DResponse>() {
|
||||||
@Override
|
@Override
|
||||||
public void onNext(Empty value) {
|
public void onNext(Vehicle2DResponse value) {
|
||||||
finishLatch.countDown();
|
finishLatch.countDown();
|
||||||
Log.d("PiLoader", "Finished streaming");
|
Log.d("PiLoader", "Finished streaming");
|
||||||
}
|
}
|
||||||
@@ -113,23 +113,23 @@ public class PiLoader implements Runnable {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
StreamObserver<Vehicle2DRequest> requestStreamObserver = CarControlGrpc.newStub(mChannel)
|
StreamObserver<Vehicle2DRequest> requestStreamObserver = CarControlGrpc.newStub(mChannel)
|
||||||
.streamVehicle2d(
|
.streamVehicle2d(
|
||||||
responseObserver);
|
responseObserver);
|
||||||
while (!stop.get() && !Thread.interrupted() && finishLatch.getCount() > 0) {
|
while (!stop.get() && !Thread.interrupted() && finishLatch.getCount() > 0) {
|
||||||
requestStreamObserver.onNext(Vehicle2DRequest.newBuilder()
|
requestStreamObserver.onNext(Vehicle2DRequest.newBuilder()
|
||||||
.setThrottle(ThrottleRequest.newBuilder()
|
.setThrottle(ThrottleRequest.newBuilder()
|
||||||
.setThrottle(
|
.setThrottle(
|
||||||
(float) throttleValue /
|
(float) throttleValue /
|
||||||
50f -
|
50f -
|
||||||
1)
|
1)
|
||||||
.build())
|
.build())
|
||||||
.setSteering(SteeringRequest.newBuilder()
|
.setSteering(SteeringRequest.newBuilder()
|
||||||
.setSteering(
|
.setSteering(
|
||||||
(float) steeringValue /
|
(float) steeringValue /
|
||||||
50f -
|
50f -
|
||||||
1)
|
1)
|
||||||
.build())
|
.build())
|
||||||
.build());
|
.build());
|
||||||
try {
|
try {
|
||||||
// Use the same update rate as a typical screen refresh rate.
|
// Use the same update rate as a typical screen refresh rate.
|
||||||
TimeUnit.MILLISECONDS.sleep(200);
|
TimeUnit.MILLISECONDS.sleep(200);
|
||||||
@@ -141,12 +141,12 @@ public class PiLoader implements Runnable {
|
|||||||
|
|
||||||
public void saveRecording() {
|
public void saveRecording() {
|
||||||
// Ideally don't want to use a blocking stub here, android may complain.
|
// Ideally don't want to use a blocking stub here, android may complain.
|
||||||
Empty done = stub.saveRecordedData(SaveRequest.newBuilder().setFile("Test").build());
|
SaveResponse done = stub.saveRecordedData(SaveRequest.newBuilder().setFile("Test").build());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void record(boolean record) {
|
public void record(boolean record) {
|
||||||
// Ideally don't want to use a blocking stub here, android may complain.
|
// Ideally don't want to use a blocking stub here, android may complain.
|
||||||
Empty done = stub.record(RecordingReqeust.newBuilder().setRecord(record).build());
|
RecordingResponse done = stub.record(RecordingReqeust.newBuilder().setRecord(record).build());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,10 +3,10 @@
|
|||||||
buildscript {
|
buildscript {
|
||||||
repositories {
|
repositories {
|
||||||
google()
|
google()
|
||||||
jcenter()
|
mavenCentral()
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath 'com.android.tools.build:gradle:4.1.3'
|
classpath 'com.android.tools.build:gradle:7.3.1'
|
||||||
classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.16'
|
classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.16'
|
||||||
|
|
||||||
// NOTE: Do not place your application dependencies here; they belong
|
// NOTE: Do not place your application dependencies here; they belong
|
||||||
@@ -17,8 +17,7 @@ buildscript {
|
|||||||
allprojects {
|
allprojects {
|
||||||
repositories {
|
repositories {
|
||||||
google()
|
google()
|
||||||
jcenter()
|
mavenCentral()
|
||||||
mavenLocal()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
1173
car-rs/Cargo.lock
generated
Normal file
1173
car-rs/Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
26
car-rs/Cargo.toml
Normal file
26
car-rs/Cargo.toml
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
[package]
|
||||||
|
name = "car-rs"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
# https://github.com/golemparts/rppal
|
||||||
|
rppal = { version = "0.13.1", optional = true }
|
||||||
|
futures-core = "0.3"
|
||||||
|
futures-util = "0.3"
|
||||||
|
tokio = { version = "1", features = ["full"] }
|
||||||
|
prost = "0.11"
|
||||||
|
|
||||||
|
# https://github.com/hyperium/tonic
|
||||||
|
tonic = "0.8.0"
|
||||||
|
|
||||||
|
# https://docs.rs/serialport/4.0.1/serialport/index.html
|
||||||
|
serialport = "4.0.1"
|
||||||
|
|
||||||
|
[build-dependencies]
|
||||||
|
tonic-build = "0.8.0"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
rppal = ["dep:rppal"]
|
||||||
7
car-rs/build.rs
Normal file
7
car-rs/build.rs
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
tonic_build::configure().compile(
|
||||||
|
&["../protobuf/src/main/proto/car/control/motorService.proto"],
|
||||||
|
&["../protobuf/src/main/proto"],
|
||||||
|
)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
89
car-rs/src/grpcserver.rs
Normal file
89
car-rs/src/grpcserver.rs
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
pub mod motor_control_service {
|
||||||
|
tonic::include_proto!("motor_control");
|
||||||
|
}
|
||||||
|
|
||||||
|
use std::{sync::Mutex, time::Duration};
|
||||||
|
|
||||||
|
use car_rs::{Servo, Vehicle};
|
||||||
|
use futures_util::StreamExt;
|
||||||
|
use motor_control_service::car_control_server::CarControl;
|
||||||
|
use tokio::time;
|
||||||
|
use tonic::{Request, Response, Status, Streaming};
|
||||||
|
|
||||||
|
use self::motor_control_service::{
|
||||||
|
RecordingReqeust, RecordingResponse, SaveRequest, SaveResponse, SteeringRequest,
|
||||||
|
SteeringResponse, ThrottleRequest, ThrottleResponse, Vehicle2DRequest, Vehicle2DResponse,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct MotorControlService<T>
|
||||||
|
where
|
||||||
|
T: Vehicle,
|
||||||
|
{
|
||||||
|
// TODO: Any better way than mutex? need it over refcell to implement send, and need a smart pointer
|
||||||
|
// for interior mutability (since the generated protobuf functions aren't mut)
|
||||||
|
vehicle: Mutex<T>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Vehicle> MotorControlService<T> {
|
||||||
|
pub fn new(vehicle: T) -> MotorControlService<T> {
|
||||||
|
MotorControlService {
|
||||||
|
vehicle: Mutex::new(vehicle),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tonic::async_trait]
|
||||||
|
impl<T: Vehicle + Send + Sync + 'static> CarControl for MotorControlService<T> {
|
||||||
|
async fn set_throttle(
|
||||||
|
&self,
|
||||||
|
_request: Request<ThrottleRequest>,
|
||||||
|
) -> Result<Response<ThrottleResponse>, Status> {
|
||||||
|
self.vehicle
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.set_throttle(_request.into_inner().throttle as f64);
|
||||||
|
Ok(Response::new(ThrottleResponse { throttle_set: true }))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn set_steering(
|
||||||
|
&self,
|
||||||
|
_request: Request<SteeringRequest>,
|
||||||
|
) -> Result<Response<SteeringResponse>, Status> {
|
||||||
|
self.vehicle
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.set_steering(_request.into_inner().steering as f64);
|
||||||
|
Ok(Response::new(SteeringResponse { steering_set: true }))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn stream_vehicle_2d(
|
||||||
|
&self,
|
||||||
|
request: Request<Streaming<Vehicle2DRequest>>,
|
||||||
|
) -> Result<Response<Vehicle2DResponse>, Status> {
|
||||||
|
let mut stream = request.into_inner();
|
||||||
|
while let Ok(Some(Ok(req))) = time::timeout(Duration::from_secs(3), stream.next()).await {
|
||||||
|
let mut vehicle = self.vehicle.lock().unwrap();
|
||||||
|
vehicle.set_throttle(req.throttle.unwrap().throttle as f64);
|
||||||
|
vehicle.set_steering(req.steering.unwrap().steering as f64);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.vehicle.lock().unwrap().set_throttle(0.);
|
||||||
|
|
||||||
|
Ok(Response::new(Vehicle2DResponse {}))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn record(
|
||||||
|
&self,
|
||||||
|
_request: Request<RecordingReqeust>,
|
||||||
|
) -> Result<Response<RecordingResponse>, Status> {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn save_recorded_data(
|
||||||
|
&self,
|
||||||
|
_request: Request<SaveRequest>,
|
||||||
|
) -> Result<Response<SaveResponse>, Status> {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
}
|
||||||
211
car-rs/src/lib.rs
Normal file
211
car-rs/src/lib.rs
Normal file
@@ -0,0 +1,211 @@
|
|||||||
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
|
use serialport::SerialPort;
|
||||||
|
|
||||||
|
// TODO: Should be returning results in these traits
|
||||||
|
pub trait Servo {
|
||||||
|
fn get_value(&self) -> f64;
|
||||||
|
|
||||||
|
fn set_value(&mut self, value: f64);
|
||||||
|
|
||||||
|
fn min(&mut self) {
|
||||||
|
self.set_value(-1.);
|
||||||
|
}
|
||||||
|
fn mid(&mut self) {
|
||||||
|
self.set_value(0.);
|
||||||
|
}
|
||||||
|
fn max(&mut self) {
|
||||||
|
self.set_value(1.);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait Vehicle {
|
||||||
|
fn get_throttle(&self) -> f64;
|
||||||
|
fn set_throttle(&mut self, throttle: f64);
|
||||||
|
fn get_steering(&self) -> f64;
|
||||||
|
fn set_steering(&mut self, steering: f64);
|
||||||
|
}
|
||||||
|
#[cfg(feature = "rppal")]
|
||||||
|
pub mod rppal {
|
||||||
|
use rppal::pwm::{Channel, Pwm};
|
||||||
|
|
||||||
|
pub struct RpiPwmServo {
|
||||||
|
pwm: Pwm,
|
||||||
|
min_duty_cycle: f64,
|
||||||
|
duty_cycle_range: f64,
|
||||||
|
value: f64,
|
||||||
|
frame_width: f64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RpiPwmServo {
|
||||||
|
pub fn new(pwm: Pwm) -> RpiPwmServo {
|
||||||
|
RpiPwmServo::new(pwm, 1000000)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new(pwm: Pwm, min_pulse_width: f64) -> RpiPwmServo {}
|
||||||
|
|
||||||
|
pub fn new(
|
||||||
|
pwm: Pwm,
|
||||||
|
min_pulse_width: f64,
|
||||||
|
max_pulse_width: f64,
|
||||||
|
frame_width: f64,
|
||||||
|
) -> RpiPwmServo {
|
||||||
|
RpiPwmServo {
|
||||||
|
pwm,
|
||||||
|
min_duty_cycle: min_pulse_width / frame_width,
|
||||||
|
duty_cycle_range: (max_pulse_width - min_pulse_width) / frame_width,
|
||||||
|
frame_width,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for RpiPwmServo {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
pwm: Pwm::new(Channel::Pwm0).expect("Failed to initialise Pwm servo"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Servo for RpiPwmServo {
|
||||||
|
fn get_duty_cycle(&self) -> f64 {
|
||||||
|
self.pwm.duty_cycle().unwrap_or(0.)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_duty_cycle(&self, pwm: f64) {
|
||||||
|
self.pwm
|
||||||
|
.set_duty_cycle(pwm)
|
||||||
|
.expect("Failed to write duty cycle");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_frequency(&self) -> f64 {
|
||||||
|
self.pwm.duty_cycle().unwrap_or(0.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_frequency(&self, frequency: f64) {
|
||||||
|
self.pwm
|
||||||
|
.set_frequency(frequency, self.get_duty_cycle())
|
||||||
|
.expect("Failed to set Frequency")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Esp32SerialPwmServo<T: SerialPort> {
|
||||||
|
serial_port: Arc<Mutex<T>>,
|
||||||
|
value: f64,
|
||||||
|
channel: u8,
|
||||||
|
pin: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: SerialPort> Esp32SerialPwmServo<T> {
|
||||||
|
pub fn new(serial_port: Arc<Mutex<T>>, channel: u8, pin: u8) -> Esp32SerialPwmServo<T> {
|
||||||
|
let mut servo = Esp32SerialPwmServo {
|
||||||
|
serial_port,
|
||||||
|
value: 0.,
|
||||||
|
channel,
|
||||||
|
pin,
|
||||||
|
};
|
||||||
|
servo.init_pwm();
|
||||||
|
servo
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: SerialPort> Esp32SerialPwmServo<T> {
|
||||||
|
fn init_pwm(&mut self) {
|
||||||
|
let bytes_written = self
|
||||||
|
.serial_port
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.write(&[0, 1, self.channel, self.pin]);
|
||||||
|
// TODO: Better error handling (even anyhow would be better)
|
||||||
|
match bytes_written {
|
||||||
|
Ok(size) => println!("{}", size),
|
||||||
|
Err(err) => eprintln!("{}", err),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: SerialPort> Servo for Esp32SerialPwmServo<T> {
|
||||||
|
fn get_value(&self) -> f64 {
|
||||||
|
self.value
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_value(&mut self, value: f64) {
|
||||||
|
let mut temp_value = value;
|
||||||
|
// TODO: Panic when out of bounds?
|
||||||
|
if temp_value < -1. {
|
||||||
|
temp_value = -1.;
|
||||||
|
} else if temp_value > 1. {
|
||||||
|
temp_value = 1.;
|
||||||
|
}
|
||||||
|
self.value = temp_value;
|
||||||
|
let bytes_written = self
|
||||||
|
.serial_port
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.write(&[self.channel, ((value + 1.) / 2. * 255.) as u8]);
|
||||||
|
// TODO: Better error handling
|
||||||
|
match bytes_written {
|
||||||
|
Ok(size) => println!("{}", size),
|
||||||
|
Err(err) => eprintln!("{}", err),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ServoVehicle<T: Servo> {
|
||||||
|
steering_servo: T,
|
||||||
|
throttle_servo: T,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Servo> ServoVehicle<T> {
|
||||||
|
pub fn new(steering_servo: T, throttle_servo: T) -> ServoVehicle<T> {
|
||||||
|
ServoVehicle {
|
||||||
|
steering_servo,
|
||||||
|
throttle_servo,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Servo> Vehicle for ServoVehicle<T> {
|
||||||
|
fn get_throttle(&self) -> f64 {
|
||||||
|
self.throttle_servo.get_value()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_throttle(&mut self, throttle: f64) {
|
||||||
|
self.throttle_servo.set_value(throttle);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_steering(&self) -> f64 {
|
||||||
|
self.steering_servo.get_value()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_steering(&mut self, steering: f64) {
|
||||||
|
self.steering_servo.set_value(steering);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default, Debug)]
|
||||||
|
pub struct PrintVehicle {
|
||||||
|
throttle: f64,
|
||||||
|
steering: f64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Vehicle for PrintVehicle {
|
||||||
|
fn get_throttle(&self) -> f64 {
|
||||||
|
self.throttle
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_throttle(&mut self, throttle: f64) {
|
||||||
|
println!("New Throttle: {}", throttle);
|
||||||
|
self.throttle = throttle;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_steering(&self) -> f64 {
|
||||||
|
self.steering
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_steering(&mut self, steering: f64) {
|
||||||
|
println!("New Steering: {}", steering);
|
||||||
|
self.steering = steering;
|
||||||
|
}
|
||||||
|
}
|
||||||
29
car-rs/src/main.rs
Normal file
29
car-rs/src/main.rs
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
|
use car_rs::{Esp32SerialPwmServo, ServoVehicle};
|
||||||
|
use grpcserver::{
|
||||||
|
motor_control_service::car_control_server::CarControlServer, MotorControlService,
|
||||||
|
};
|
||||||
|
|
||||||
|
use tonic::transport::Server;
|
||||||
|
|
||||||
|
mod grpcserver;
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let addr = "[::1]:10000".parse().unwrap();
|
||||||
|
|
||||||
|
let serial_port = serialport::new("", 32400)
|
||||||
|
.open_native()
|
||||||
|
.expect("Could not open serial port");
|
||||||
|
let serial_servo = Arc::new(Mutex::new(serial_port));
|
||||||
|
let steering_servo = Esp32SerialPwmServo::new(serial_servo.clone(), 1, 12);
|
||||||
|
let throttle_servo = Esp32SerialPwmServo::new(serial_servo.clone(), 2, 18);
|
||||||
|
|
||||||
|
let motor_control = MotorControlService::new(ServoVehicle::new(steering_servo, throttle_servo));
|
||||||
|
|
||||||
|
let svc = CarControlServer::new(motor_control);
|
||||||
|
Server::builder().add_service(svc).serve(addr).await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -19,8 +19,8 @@ void setup()
|
|||||||
|
|
||||||
void setupServos(uint8_t size, uint8_t *calibrationValues)
|
void setupServos(uint8_t size, uint8_t *calibrationValues)
|
||||||
{
|
{
|
||||||
// We assume there are 3 bytes per servo. Ignore if there aren't.
|
// We assume there are 2 bytes per servo [channel number, pin number]. Ignore if there aren't.
|
||||||
if (size % 3 == 0)
|
if (size % 2 == 0)
|
||||||
{
|
{
|
||||||
for (int i = 0; i < size; i += 2)
|
for (int i = 0; i < size; i += 2)
|
||||||
{
|
{
|
||||||
@@ -33,27 +33,25 @@ void setupServos(uint8_t size, uint8_t *calibrationValues)
|
|||||||
|
|
||||||
void modifyServo(uint8_t channel, uint8_t newAngle)
|
void modifyServo(uint8_t channel, uint8_t newAngle)
|
||||||
{
|
{
|
||||||
if(servos.count(channel) > 0){
|
if (servos.count(channel) > 0)
|
||||||
|
{
|
||||||
servos[channel]->write(newAngle);
|
servos[channel]->write(newAngle);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void loop()
|
void loop()
|
||||||
{
|
{
|
||||||
uint8_t *header = new uint8_t[2];
|
uint8_t header[2];
|
||||||
Serial.readBytes(header, 2);
|
Serial.readBytes(header, 2);
|
||||||
|
|
||||||
if (header[0] == 0)
|
if (header[0] == 0)
|
||||||
{
|
{
|
||||||
uint8_t *calibration = new uint8_t[2 * header[1]];
|
uint8_t calibration[2 * header[1]];
|
||||||
Serial.readBytes(calibration, header[1]);
|
Serial.readBytes(calibration, header[1]);
|
||||||
setupServos(header[1], calibration);
|
setupServos(header[1], calibration);
|
||||||
delete [] calibration;
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
modifyServo(header[0], header[1]);
|
modifyServo(header[0], header[1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
delete [] header;
|
|
||||||
}
|
}
|
||||||
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,5 +1,5 @@
|
|||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
|||||||
@@ -7,8 +7,6 @@ option java_multiple_files = true;
|
|||||||
option java_package = "org.vato.carcontroller";
|
option java_package = "org.vato.carcontroller";
|
||||||
option java_outer_classname = "MotorServiceProto";
|
option java_outer_classname = "MotorServiceProto";
|
||||||
|
|
||||||
import "google/protobuf/empty.proto";
|
|
||||||
|
|
||||||
message ThrottleRequest{
|
message ThrottleRequest{
|
||||||
float throttle = 1;
|
float throttle = 1;
|
||||||
}
|
}
|
||||||
@@ -38,10 +36,22 @@ message SaveRequest{
|
|||||||
string file = 1;
|
string file = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message Vehicle2DResponse {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
message RecordingResponse {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
message SaveResponse {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
service CarControl{
|
service CarControl{
|
||||||
rpc set_throttle(ThrottleRequest) returns (ThrottleResponse){}
|
rpc set_throttle(ThrottleRequest) returns (ThrottleResponse){}
|
||||||
rpc set_steering(SteeringRequest) returns (SteeringResponse){}
|
rpc set_steering(SteeringRequest) returns (SteeringResponse){}
|
||||||
rpc stream_vehicle_2d(stream Vehicle2DRequest) returns (google.protobuf.Empty){}
|
rpc stream_vehicle_2d(stream Vehicle2DRequest) returns (Vehicle2DResponse){}
|
||||||
rpc record(RecordingReqeust) returns (google.protobuf.Empty) {}
|
rpc record(RecordingReqeust) returns (RecordingResponse) {}
|
||||||
rpc save_recorded_data(SaveRequest) returns (google.protobuf.Empty) {}
|
rpc save_recorded_data(SaveRequest) returns (SaveResponse) {}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user