Merge branch 'car-rs' into 'master'

Replace swift implementation with rust

See merge request vato007/picar!10
This commit is contained in:
Michael Pivato
2023-01-22 04:38:46 +00:00
34 changed files with 1673 additions and 561 deletions

1
.gitignore vendored
View File

@@ -35,3 +35,4 @@ xcuserdata/
.classpath
.vscode/settings.json
**/target/**

1
.idea/.name generated Normal file
View File

@@ -0,0 +1 @@
CarController

6
.idea/compiler.xml generated Normal file
View 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
View 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
View File

@@ -4,9 +4,10 @@
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
<option name="testRunner" value="PLATFORM" />
<option name="testRunner" value="GRADLE" />
<option name="distributionType" value="DEFAULT_WRAPPED" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="gradleJvm" value="Embedded JDK" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />
@@ -17,7 +18,6 @@
<option value="$PROJECT_DIR$/pycar" />
</set>
</option>
<option name="resolveModulePerSourceSet" value="false" />
</GradleProjectSettings>
</option>
</component>

40
.idea/jarRepositories.xml generated Normal file
View 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
View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<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" />
</component>
<component name="ProjectType">

View File

@@ -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>

View File

@@ -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+.
See the individuals modules for more detailed READMEs, including build instructions.
See the individual modules for more detailed READMEs, including build instructions.
### Current Functinoality

View File

@@ -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"]

View File

@@ -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
}

View File

@@ -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"]),
]
)

View File

@@ -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)
}
}

View File

@@ -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())
}
}

View File

@@ -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
}
}

View File

@@ -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()
}

View File

@@ -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)
}
}
}

View File

@@ -1,7 +0,0 @@
import XCTest
import SwiftyCarTests
var tests = [XCTestCaseEntry]()
tests += SwiftyCarTests.allTests()
XCTMain(tests)

View File

@@ -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),
]
}

View File

@@ -1,9 +0,0 @@
import XCTest
#if !canImport(ObjectiveC)
public func allTests() -> [XCTestCaseEntry] {
return [
testCase(SwiftyCarTests.allTests),
]
}
#endif

View File

@@ -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'
}

View File

@@ -4,12 +4,11 @@ plugins{
}
android {
compileSdkVersion 30
buildToolsVersion "29.0.2"
compileSdkVersion 32
defaultConfig {
applicationId "org.vato.carcontroller"
minSdkVersion 26
targetSdkVersion 30
targetSdkVersion 32
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
@@ -27,24 +26,25 @@ android {
buildFeatures {
mlModelBinding true
}
namespace 'org.vato.carcontroller'
}
dependencies {
implementation project(':protobuf')
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
implementation 'com.google.android.material:material:1.3.0'
implementation 'androidx.preference:preference:1.1.1'
implementation 'androidx.appcompat:appcompat:1.4.2'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation 'com.google.android.material:material:1.6.1'
implementation 'androidx.preference:preference:1.2.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'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test:runner:1.3.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
implementation 'io.grpc:grpc-okhttp:1.29.0' // CURRENT_GRPC_VERSION
implementation 'io.grpc:grpc-protobuf-lite:1.29.0' // CURRENT_GRPC_VERSION
implementation 'io.grpc:grpc-stub:1.29.0' // CURRENT_GRPC_VERSION
implementation 'javax.annotation:javax.annotation-api:1.2'
androidTestImplementation 'androidx.test:runner:1.4.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
implementation 'io.grpc:grpc-okhttp:1.29.0'
implementation 'io.grpc:grpc-protobuf-lite:1.39.0'
implementation 'io.grpc:grpc-stub:1.39.0'
implementation 'javax.annotation:javax.annotation-api:1.3.2'
implementation 'org.zeromq:jeromq:0.5.2'
}

View File

@@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.vato.carcontroller">
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
@@ -12,7 +11,7 @@
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
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.SettingsActivity"
@@ -20,6 +19,7 @@
android:parentActivityName="org.vato.carcontroller.MainActivity" />
<activity android:name="org.vato.carcontroller.SimpleController" />
<activity
android:exported="true"
android:name="org.vato.carcontroller.MainActivity"
android:theme="@style/AppTheme.NoActionBar">
<intent-filter>

View File

@@ -17,12 +17,12 @@ public class PiLoader implements Runnable {
private Integer steeringValue = 50;
private Integer throttleValue = 50;
private ManagedChannel mChannel;
private final ManagedChannel mChannel;
private CarControlGrpc.CarControlBlockingStub stub;
private AtomicBoolean stop = new AtomicBoolean(false);
private final CarControlGrpc.CarControlBlockingStub stub;
private final AtomicBoolean stop = new AtomicBoolean(false);
private Thread piUpdaterThread;
private boolean useGrpcStream;
private final boolean useGrpcStream;
public PiLoader(String host, Integer port, boolean useGrpcStream) {
mChannel = ManagedChannelBuilder.forAddress(host, port).usePlaintext().build();
@@ -93,9 +93,9 @@ public class PiLoader implements Runnable {
private void doStreamUpdates() {
// Stream if user sets use_grpc_streams to true. This method is more efficient but less compatible.
final CountDownLatch finishLatch = new CountDownLatch(1);
StreamObserver<Empty> responseObserver = new StreamObserver<Empty>() {
StreamObserver<Vehicle2DResponse> responseObserver = new StreamObserver<Vehicle2DResponse>() {
@Override
public void onNext(Empty value) {
public void onNext(Vehicle2DResponse value) {
finishLatch.countDown();
Log.d("PiLoader", "Finished streaming");
}
@@ -141,12 +141,12 @@ public class PiLoader implements Runnable {
public void saveRecording() {
// 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) {
// 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());
}
}

View File

@@ -3,10 +3,10 @@
buildscript {
repositories {
google()
jcenter()
mavenCentral()
}
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'
// NOTE: Do not place your application dependencies here; they belong
@@ -17,8 +17,7 @@ buildscript {
allprojects {
repositories {
google()
jcenter()
mavenLocal()
mavenCentral()
}
}

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
View 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
View 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
View 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
View 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
View 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(())
}

View File

@@ -19,8 +19,8 @@ void setup()
void setupServos(uint8_t size, uint8_t *calibrationValues)
{
// We assume there are 3 bytes per servo. Ignore if there aren't.
if (size % 3 == 0)
// We assume there are 2 bytes per servo [channel number, pin number]. Ignore if there aren't.
if (size % 2 == 0)
{
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)
{
if(servos.count(channel) > 0){
if (servos.count(channel) > 0)
{
servos[channel]->write(newAngle);
}
}
void loop()
{
uint8_t *header = new uint8_t[2];
uint8_t header[2];
Serial.readBytes(header, 2);
if (header[0] == 0)
{
uint8_t *calibration = new uint8_t[2 * header[1]];
uint8_t calibration[2 * header[1]];
Serial.readBytes(calibration, header[1]);
setupServos(header[1], calibration);
delete [] calibration;
}
else
{
modifyServo(header[0], header[1]);
}
delete [] header;
}

View File

@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
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
zipStorePath=wrapper/dists

View File

@@ -7,8 +7,6 @@ option java_multiple_files = true;
option java_package = "org.vato.carcontroller";
option java_outer_classname = "MotorServiceProto";
import "google/protobuf/empty.proto";
message ThrottleRequest{
float throttle = 1;
}
@@ -38,10 +36,22 @@ message SaveRequest{
string file = 1;
}
message Vehicle2DResponse {
}
message RecordingResponse {
}
message SaveResponse {
}
service CarControl{
rpc set_throttle(ThrottleRequest) returns (ThrottleResponse){}
rpc set_steering(SteeringRequest) returns (SteeringResponse){}
rpc stream_vehicle_2d(stream Vehicle2DRequest) returns (google.protobuf.Empty){}
rpc record(RecordingReqeust) returns (google.protobuf.Empty) {}
rpc save_recorded_data(SaveRequest) returns (google.protobuf.Empty) {}
rpc stream_vehicle_2d(stream Vehicle2DRequest) returns (Vehicle2DResponse){}
rpc record(RecordingReqeust) returns (RecordingResponse) {}
rpc save_recorded_data(SaveRequest) returns (SaveResponse) {}
}