Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
f36d9be
refactor: organize imports
francWhite Nov 18, 2023
644d333
chore: add micro_ros project (platformIO)
francWhite Nov 18, 2023
55959ef
chore(micro_ros): add RoboStepper library
francWhite Nov 18, 2023
81cb929
feat(navigation): add micro_ros node to control stepper motors
francWhite Nov 19, 2023
c14c722
feat(navigation): add automatic reconnect if connection to micro_ros …
francWhite Nov 19, 2023
d0be357
refactor(navigation): remove RoboStepper and implement ability to con…
francWhite Nov 19, 2023
2eaf8c2
fix(navigation): spin stop_executor in main loop to consume potential…
francWhite Nov 19, 2023
4048916
feat(navigation): add action to move forward/backward
francWhite Nov 19, 2023
4d86f7c
feat(navigation): add endpoint and action_api implementation for Move…
francWhite Nov 19, 2023
5849512
refactor(client-lib): make internal records private
francWhite Nov 20, 2023
8917141
feat(navigation): add stop service and endpoint
francWhite Nov 20, 2023
9b024bf
feat(navigation): add client implementation to move forward/backward
francWhite Nov 21, 2023
2b17ee0
feat(client): add example procedure
francWhite Nov 21, 2023
e1d0c6f
feat(navigation): add client implementation to rotate left/right
francWhite Nov 21, 2023
f247acb
feat(navigation): add endpoint for rotation
francWhite Nov 21, 2023
646f96a
refactor(navigation): add dedicated navigation module and split compo…
francWhite Nov 21, 2023
9495ff3
refactor(navigation): create dedicated message type for direction
francWhite Nov 21, 2023
3905095
refactor(action_api): create ActionHandler do avoid code redundancies
francWhite Nov 21, 2023
a45e5b5
feat(navigation): add action to rotate
francWhite Nov 21, 2023
1fc8a47
feat(client): add rotation to procedure example
francWhite Nov 21, 2023
3e63954
feat(navigation): add action to turn
francWhite Dec 14, 2023
67e0f19
feat(navigation): add client implementation for turn
francWhite Dec 14, 2023
b404a60
Merge branch 'main' into feature/navigation
francWhite Dec 14, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions apps/api/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { buzzerRouter } from './routes/buzzer.routes';
import { actionRouter } from './routes/action.routes';
import { colorRouter } from './routes/color.routes';
import { distanceRouter } from './routes/distance.routes';
import { navigationRouter } from './routes/navigation.routes';

dotenv.config();

Expand All @@ -20,6 +21,7 @@ app.use('/api/buzzer', buzzerRouter);
app.use('/api/actions', actionRouter);
app.use('/api/color', colorRouter);
app.use('/api/distance', distanceRouter);
app.use('/api/navigation', navigationRouter);

rosService.connect();

Expand Down
1 change: 1 addition & 0 deletions apps/api/src/ros.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ class RosService {
topic.publish(msg);
}

// TODO add timeout
callService<TResponse>(
name: string,
serviceType: string,
Expand Down
2 changes: 0 additions & 2 deletions apps/api/src/routes/action.routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@ actionRouter.use(express.json());
actionRouter.get(
'/goals/:goalId',
async (req: Request<{ goalId: string }, undefined, ActionCompletedResult>, res: Response) => {
console.log(req.originalUrl, req.body);

const requestData = { goal_id: req.params.goalId };
rosService.callService(
'/raros/action_api/action_completed',
Expand Down
20 changes: 12 additions & 8 deletions apps/api/src/routes/color.routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,18 @@ colorRouter.use(express.json());
colorRouter.get('/', async (req: Request<undefined, undefined, undefined>, res: Response) => {
console.log(req.originalUrl);

rosService.readSingleMessageFromTopic('/raros/color_sensor/color', 'std_msgs/msg/ColorRGBA', (message: ColorRGBA) =>
res
.json({
red: message.r,
green: message.g,
blue: message.b,
})
.send(),
rosService.readSingleMessageFromTopic(
'/raros/color_sensor/color',
'std_msgs/msg/ColorRGBA',
(message: ColorRGBA) =>
res
.json({
red: message.r,
green: message.g,
blue: message.b,
})
.send(),
(error) => res.status(500).send(error),
);
});

Expand Down
85 changes: 85 additions & 0 deletions apps/api/src/routes/navigation.routes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import express from 'express';
import { Request, Response } from 'express';
import { rosService } from '../ros.service';
import { decode } from 'uuid-base64-ts';
import { ActionInvocationResult } from '../ros.model';

export const navigationRouter = express.Router();
navigationRouter.use(express.json());

navigationRouter.post('/stop', async (req: Request<undefined, undefined, undefined>, res: Response) => {
console.log(req.originalUrl);

rosService.callService(
'/raros/navigation/stop',
'std_srvs/srv/Empty',
{},
() => res.send(),
(error) => res.status(500).send(error),
);
});

navigationRouter.post('/move', async (req: Request<undefined, undefined, MoveRequest>, res: Response) => {
console.log(req.originalUrl, req.body);

const requestData = { ...req.body, direction: { value: req.body.direction } };
rosService.callService(
'/raros/action_api/navigation/move',
'raros_interfaces/srv/ActionMove',
requestData,
(response: ActionInvocationResult) => {
const uidString = decode(response.goal_id.uuid);
res.json({ goal_id: uidString }).send();
},
(error) => res.status(500).send(error),
);
});

navigationRouter.post('/rotate', async (req: Request<undefined, undefined, RotateRequest>, res: Response) => {
console.log(req.originalUrl, req.body);

const requestData = { ...req.body, direction: { value: req.body.direction } };
rosService.callService(
'/raros/action_api/navigation/rotate',
'raros_interfaces/srv/ActionRotate',
requestData,
(response: ActionInvocationResult) => {
const uidString = decode(response.goal_id.uuid);
res.json({ goal_id: uidString }).send();
},
(error) => res.status(500).send(error),
);
});

navigationRouter.post('/turn', async (req: Request<undefined, undefined, TurnRequest>, res: Response) => {
console.log(req.originalUrl, req.body);

const requestData = { ...req.body, direction: { value: req.body.direction } };
rosService.callService(
'/raros/action_api/navigation/turn',
'raros_interfaces/srv/ActionTurn',
requestData,
(response: ActionInvocationResult) => {
const uidString = decode(response.goal_id.uuid);
res.json({ goal_id: uidString }).send();
},
(error) => res.status(500).send(error),
);
});

type MoveRequest = {
distance: number;
speed: number;
direction: number;
};

type RotateRequest = {
angle: number;
direction: number;
};

type TurnRequest = {
angle: number;
radius: number;
direction: number;
};
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@
import java.util.concurrent.CompletableFuture;

public class ActionApiService implements ActionService {
private record Goal(UUID goalId, Boolean completed) {
}

private final URI apiUri;

public ActionApiService(URI apiBaseUri) {
Expand All @@ -30,7 +33,4 @@ public CompletableFuture<Boolean> IsActionCompleted(UUID goalId) {
.thenApply(Goal::completed);
}
}
}

record Goal(UUID goalId, Boolean completed) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@
import java.net.http.HttpResponse;
import java.util.concurrent.CompletableFuture;

record Tone(int frequency, int duration) {
}

class BuzzerApiService implements BuzzerService {
private record Tone(int frequency, int duration) {
}

private final URI apiUri;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package ch.hslu.raros.client.connector;

import com.fasterxml.jackson.annotation.JsonValue;

public enum Direction {
Forward(1),
Backward(2),
Left(3),
Right(4);

private final int value;

private Direction(int value) {
this.value = value;
}

@JsonValue
public int getValue() {
return value;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@
import java.util.concurrent.CompletableFuture;

class DistanceApiService implements DistanceService {
private record Rotation(int angle) {
}

private final URI apiUri;

public DistanceApiService(URI apiBaseUri) {
Expand Down Expand Up @@ -41,7 +44,4 @@ public CompletableFuture<Void> RotateSensor(int angle) {
.thenApply(HttpResponse::body);
}
}
}

record Rotation(int angle) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,9 @@
import java.net.http.HttpResponse;
import java.util.concurrent.CompletableFuture;

record MagnetState(Boolean active) {
}

class MagnetApiService implements MagnetService {
private record MagnetState(Boolean active) {
}

private final URI apiUri;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
package ch.hslu.raros.client.connector;

import ch.hslu.raros.client.util.HttpRequestBuilder;
import ch.hslu.raros.client.util.JsonSerializer;

import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.concurrent.CompletableFuture;

class NavigationApiService implements NavigationService {
private record MoveRequest(double distance, double speed, Direction direction) {
}

private record RotateRequest(double angle, Direction direction) {
}

private record TurnRequest(double angle, double radius, Direction direction) {
}

private final URI apiUri;

public NavigationApiService(URI apiBaseUri) {
this.apiUri = apiBaseUri.resolve("/api/navigation/");
}

@Override
public CompletableFuture<Void> Stop() {
var request = HttpRequest.newBuilder(apiUri.resolve("./stop"))
.POST(HttpRequest.BodyPublishers.noBody())
.build();

try (var httpClient = HttpClient.newHttpClient()) {
return httpClient
.sendAsync(request, HttpResponse.BodyHandlers.discarding())
.thenApply(HttpResponse::body);
}
}

@Override
public CompletableFuture<ActionInvocationResult> Move(Direction direction) {
return Move(new MoveRequest(Math.pow(10, 4), -1d, direction));
}

@Override
public CompletableFuture<ActionInvocationResult> Move(double distance, Direction direction) {
return Move(new MoveRequest(distance, -1d, direction));
}

@Override
public CompletableFuture<ActionInvocationResult> Move(double distance, double speed, Direction direction) {
return Move(new MoveRequest(distance, speed, direction));
}

@Override
public CompletableFuture<ActionInvocationResult> Rotate(double angle, Direction direction) {
if (angle < 0 || angle > 180)
throw new IllegalArgumentException("The angle must be between 0° and 180°.");

if (direction != Direction.Left && direction != Direction.Right)
throw new IllegalArgumentException("Only left and right are supported as directions.");

var request = HttpRequestBuilder.buildJsonPOST(apiUri.resolve("./rotate"), new RotateRequest(angle, direction));

try (var httpClient = HttpClient.newHttpClient()) {
return httpClient
.sendAsync(request, HttpResponse.BodyHandlers.ofString())
.thenApply(HttpResponse::body)
.thenApply(s -> JsonSerializer.deserialize(s, ActionInvocationResult.class));
}
}

@Override
public CompletableFuture<ActionInvocationResult> Turn(double angle, double radius, Direction direction) {
if (angle < 0 || angle > 180)
throw new IllegalArgumentException("The angle must be between 0° and 180°.");

if (direction != Direction.Left && direction != Direction.Right)
throw new IllegalArgumentException("Only left and right are supported as directions.");

var request = HttpRequestBuilder.buildJsonPOST(apiUri.resolve("./turn"), new TurnRequest(angle, radius, direction));

try (var httpClient = HttpClient.newHttpClient()) {
return httpClient
.sendAsync(request, HttpResponse.BodyHandlers.ofString())
.thenApply(HttpResponse::body)
.thenApply(s -> JsonSerializer.deserialize(s, ActionInvocationResult.class));
}
}

private CompletableFuture<ActionInvocationResult> Move(MoveRequest moveRequest) {
if (moveRequest.direction != Direction.Forward && moveRequest.direction != Direction.Backward)
throw new IllegalArgumentException("Only forward and backward are supported as directions.");

var request = HttpRequestBuilder.buildJsonPOST(apiUri.resolve("./move"), moveRequest);

try (var httpClient = HttpClient.newHttpClient()) {
return httpClient
.sendAsync(request, HttpResponse.BodyHandlers.ofString())
.thenApply(HttpResponse::body)
.thenApply(s -> JsonSerializer.deserialize(s, ActionInvocationResult.class));
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package ch.hslu.raros.client.connector;

import java.util.concurrent.CompletableFuture;

public interface NavigationService {

/**
* Stops the robot.
*
* @return CompletableFuture<Void>
*/
CompletableFuture<Void> Stop();

/**
* Moves the robot forward indefinitely.
*
* @param direction The direction to move in (only forward and backward are possible)
* @return CompletableFuture<ActionInvocationResult> The result of the action invocation.
*/
CompletableFuture<ActionInvocationResult> Move(Direction direction);

/**
* Moves the robot forward for the given distance.
*
* @param distance The distance to move in meters.
* @param direction The direction to move in (only forward and backward are possible)
* @return CompletableFuture<ActionInvocationResult> The result of the action invocation.
*/
CompletableFuture<ActionInvocationResult> Move(double distance, Direction direction);

/**
* Moves the robot forward for the given distance.
*
* @param distance The distance to move in meters.
* @param speed The speed to move in meters per second.
* @param direction The direction to move in (only forward and backward are possible)
* @return CompletableFuture<ActionInvocationResult> The result of the action invocation.
*/
CompletableFuture<ActionInvocationResult> Move(double distance, double speed, Direction direction);

/**
* Rotates the robot by the given angle.
*
* @param angle The angle to rotate in degrees. The angle must be between 0° and 180°.
* @param direction The direction to rotate in (only left and right are possible)
* @return CompletableFuture<ActionInvocationResult> The result of the action invocation.
*/
CompletableFuture<ActionInvocationResult> Rotate(double angle, Direction direction);

/**
* Turns the robot by the given angle and radius.
*
* @param angle The angle to turn in degrees. The angle must be between 0° and 180°.
* @param radius The radius to turn in meters.
* @param direction The direction to turn in (only left and right are possible)
* @return CompletableFuture<ActionInvocationResult> The result of the action invocation.
*/
CompletableFuture<ActionInvocationResult> Turn(double angle, double radius, Direction direction);
}

Loading