Remove swift source, update esp32 usb servo to share serial port
This commit is contained in:
@@ -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'
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use serialport::SerialPort;
|
||||
|
||||
// TODO: Should be returning results in these traits
|
||||
@@ -89,14 +91,14 @@ pub mod rppal {
|
||||
}
|
||||
|
||||
pub struct Esp32SerialPwmServo<T: SerialPort> {
|
||||
serial_port: T,
|
||||
serial_port: Arc<Mutex<T>>,
|
||||
value: f64,
|
||||
channel: u8,
|
||||
pin: u8,
|
||||
}
|
||||
|
||||
impl<T: SerialPort> Esp32SerialPwmServo<T> {
|
||||
pub fn new(serial_port: T, channel: u8, pin: u8) -> Esp32SerialPwmServo<T> {
|
||||
pub fn new(serial_port: Arc<Mutex<T>>, channel: u8, pin: u8) -> Esp32SerialPwmServo<T> {
|
||||
Esp32SerialPwmServo {
|
||||
serial_port,
|
||||
value: 0.,
|
||||
@@ -108,7 +110,11 @@ impl<T: SerialPort> Esp32SerialPwmServo<T> {
|
||||
|
||||
impl<T: SerialPort> Esp32SerialPwmServo<T> {
|
||||
fn init_pwm(&mut self) {
|
||||
let bytes_written = self.serial_port.write(&[0, 1, self.channel, self.pin]);
|
||||
let bytes_written = self
|
||||
.serial_port
|
||||
.lock()
|
||||
.unwrap()
|
||||
.write(&[0, 1, self.channel, self.pin]);
|
||||
// TODO: Better error handling
|
||||
match bytes_written {
|
||||
Ok(size) => println!("{}", size),
|
||||
@@ -133,6 +139,8 @@ impl<T: SerialPort> Servo for Esp32SerialPwmServo<T> {
|
||||
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 {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use car_rs::{Esp32SerialPwmServo, ServoVehicle};
|
||||
use grpcserver::{
|
||||
motor_control_service::car_control_server::CarControlServer, MotorControlService,
|
||||
@@ -15,13 +17,11 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let serial_port = serialport::new("", 32400)
|
||||
.open_native()
|
||||
.expect("Could not open serial port");
|
||||
let throttle_port = serialport::new("", 32400)
|
||||
.open_native()
|
||||
.expect("Could not open serial port");
|
||||
let servo = Esp32SerialPwmServo::new(serial_port, 1, 12);
|
||||
let throttle_servo = Esp32SerialPwmServo::new(throttle_port, 2, 18);
|
||||
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(servo, throttle_servo));
|
||||
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?;
|
||||
|
||||
Reference in New Issue
Block a user