Initial commit
This commit is contained in:
@@ -0,0 +1,376 @@
|
||||
# XLOC Manual Control API Guide
|
||||
|
||||
Now you have **FULL MANUAL CONTROL** over XLOC SLAM operations! Control via:
|
||||
1. C# Service Methods
|
||||
2. REST API Endpoints
|
||||
3. SignalR Hub Methods
|
||||
|
||||
---
|
||||
|
||||
## 1. C# Service Methods (Direct)
|
||||
|
||||
Inject `XlocIntegrationService` into your service:
|
||||
|
||||
```csharp
|
||||
public class MyNavigationService
|
||||
{
|
||||
private readonly XlocIntegrationService _xloc;
|
||||
|
||||
public MyNavigationService(XlocIntegrationService xloc)
|
||||
{
|
||||
_xloc = xloc;
|
||||
}
|
||||
|
||||
public void StartLocalizationWithMap()
|
||||
{
|
||||
// Activate map
|
||||
if (_xloc.ActivateMap("/maps/factory_floor.pbstream"))
|
||||
{
|
||||
// Start localization
|
||||
_xloc.StartLocalization();
|
||||
}
|
||||
}
|
||||
|
||||
public void BeginMapping()
|
||||
{
|
||||
_xloc.StartMapping();
|
||||
}
|
||||
|
||||
public void SaveAndStopMapping()
|
||||
{
|
||||
_xloc.StopMapping("/maps/new_map.pbstream");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. REST API Endpoints
|
||||
|
||||
### Activate Map
|
||||
```bash
|
||||
curl -X POST "https://localhost:7002/api/xloc/activate-map?mapPath=/maps/factory.pbstream"
|
||||
```
|
||||
|
||||
Response:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "Map activated"
|
||||
}
|
||||
```
|
||||
|
||||
### Start Mapping
|
||||
```bash
|
||||
dotnet build
|
||||
# Restart app
|
||||
pkill -f dotnet
|
||||
./run-quiet.sh
|
||||
# Test mapping
|
||||
curl -k -X POST https://127.0.0.1:7002/api/motion/ps5/enable
|
||||
curl -k -X POST https://localhost:7002/api/xloc/mapping/start
|
||||
# ... di chuyển robot ...
|
||||
|
||||
```
|
||||
|
||||
### Stop Mapping
|
||||
```bash
|
||||
curl -k -X POST https://localhost:7002/api/xloc/mapping/stop \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"map_file_path": "test14"}'
|
||||
```
|
||||
### Active Map
|
||||
```bash
|
||||
curl -k -X POST https://localhost:7002/api/xloc/map/activate \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"map_file_path": "test10"}'
|
||||
```
|
||||
### Start Localization
|
||||
```bash
|
||||
curl -k -X POST https://localhost:7002/api/xloc/localization/start
|
||||
```
|
||||
### Stop Localization
|
||||
```bash
|
||||
curl -k -X POST https://localhost:7002/api/xloc/localization/stop
|
||||
```
|
||||
|
||||
### Reset SLAM State
|
||||
```bash
|
||||
# Reset SLAM error state (automatically called before start mapping/localization)
|
||||
# Useful if you manually need to clear previous trajectory state
|
||||
curl -k -X POST https://localhost:7002/api/xloc/slam/reset
|
||||
```
|
||||
|
||||
**Note:** `StartMapping()` and `StartLocalization()` now automatically call reset before starting, so you typically don't need to call this manually.
|
||||
|
||||
### Stop Mapping & Save
|
||||
|
||||
**Option 1: Manual (will crash, but map is saved)**
|
||||
```bash
|
||||
# Save with timestamp
|
||||
MAP_NAME="map_$(date +%Y%m%d_%H%M%S).pbstream"
|
||||
curl -k -X POST "https://localhost:7002/api/xloc/stop-mapping?savePath=/home/robotics/sonvh/RobotNet10/srcs/RobotNet10/RobotApp/RobotNet10.RobotApp/Xloc/map/$MAP_NAME"
|
||||
|
||||
# Or save with custom name (MUST include .pbstream extension!)
|
||||
curl -k -X POST "https://localhost:7002/api/xloc/stop-mapping?savePath=/home/robotics/sonvh/RobotNet10/srcs/RobotNet10/RobotApp/RobotNet10.RobotApp/Xloc/map/my_map.pbstream"
|
||||
|
||||
cd Xloc
|
||||
./map-and-save.sh my_office_map.pbstream
|
||||
|
||||
|
||||
# Note: Application will crash due to XLOC Cairo bug, but map is saved successfully
|
||||
# Restart with: ./run-quiet.sh
|
||||
```
|
||||
|
||||
**Option 2: Automated script (recommended)**
|
||||
```bash
|
||||
# Use automated script that handles crash and restart
|
||||
cd Xloc
|
||||
./map-and-save.sh my_map.pbstream
|
||||
|
||||
# Script will:
|
||||
# 1. Wait for you to drive robot
|
||||
# 2. Save map on ENTER
|
||||
# 3. Handle crash gracefully
|
||||
# 4. Auto-restart application
|
||||
```
|
||||
|
||||
### Get Current Pose
|
||||
```bash
|
||||
curl "https://localhost:7002/api/xloc/pose"
|
||||
```
|
||||
|
||||
Response:
|
||||
```json
|
||||
{
|
||||
"x": 1.234,
|
||||
"y": 5.678,
|
||||
"yaw": 1.57,
|
||||
"yawDegrees": 90.0
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. SignalR Hub Methods
|
||||
|
||||
### TypeScript/JavaScript Client
|
||||
|
||||
```typescript
|
||||
import * as signalR from "@microsoft/signalr";
|
||||
|
||||
const connection = new signalR.HubConnectionBuilder()
|
||||
.withUrl("https://localhost:7002/hubs/xloc/pose")
|
||||
.build();
|
||||
|
||||
// Subscribe to pose updates (realtime streaming)
|
||||
connection.on("ReceivePose", (pose) => {
|
||||
console.log(`Position: (${pose.x}, ${pose.y}), Heading: ${pose.yawDegrees}°`);
|
||||
});
|
||||
|
||||
await connection.start();
|
||||
|
||||
// Control SLAM manually
|
||||
async function startLocalization() {
|
||||
const success = await connection.invoke("StartLocalization");
|
||||
console.log("Localization started:", success);
|
||||
}
|
||||
|
||||
async function activateMap(mapPath: string) {
|
||||
const success = await connection.invoke("ActivateMap", mapPath);
|
||||
console.log("Map activated:", success);
|
||||
}
|
||||
|
||||
async function startMapping() {
|
||||
const success = await connection.invoke("StartMapping");
|
||||
console.log("Mapping started:", success);
|
||||
}
|
||||
|
||||
async function stopMapping(savePath: string) {
|
||||
const success = await connection.invoke("StopMapping", savePath);
|
||||
console.log("Map saved:", success);
|
||||
}
|
||||
|
||||
async function getCurrentPose() {
|
||||
const pose = await connection.invoke("GetCurrentPose2D");
|
||||
console.log("Current pose:", pose);
|
||||
// { x: 1.234, y: 5.678, yaw: 1.57, yawDegrees: 90.0 }
|
||||
}
|
||||
```
|
||||
|
||||
### React Example
|
||||
|
||||
```tsx
|
||||
import { HubConnectionBuilder } from '@microsoft/signalr';
|
||||
import { useState, useEffect } from 'react';
|
||||
|
||||
export function XlocControl() {
|
||||
const [connection, setConnection] = useState(null);
|
||||
const [pose, setPose] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
const conn = new HubConnectionBuilder()
|
||||
.withUrl("https://localhost:7002/hubs/xloc/pose")
|
||||
.build();
|
||||
|
||||
conn.on("ReceivePose", (data) => {
|
||||
setPose(data);
|
||||
});
|
||||
|
||||
conn.start();
|
||||
setConnection(conn);
|
||||
|
||||
return () => conn.stop();
|
||||
}, []);
|
||||
|
||||
const handleStartLocalization = async () => {
|
||||
const result = await connection.invoke("StartLocalization");
|
||||
console.log("Started:", result);
|
||||
};
|
||||
|
||||
const handleStartMapping = async () => {
|
||||
const result = await connection.invoke("StartMapping");
|
||||
console.log("Mapping started:", result);
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h2>XLOC Control Panel</h2>
|
||||
|
||||
{pose && (
|
||||
<div>
|
||||
<p>X: {pose.x.toFixed(2)}m</p>
|
||||
<p>Y: {pose.y.toFixed(2)}m</p>
|
||||
<p>Heading: {pose.yawDegrees.toFixed(1)}°</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<button onClick={handleStartLocalization}>
|
||||
Start Localization
|
||||
</button>
|
||||
<button onClick={handleStartMapping}>
|
||||
Start Mapping
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Typical Workflows
|
||||
|
||||
### Workflow 1: Localization (Using Existing Map)
|
||||
|
||||
```bash
|
||||
# 1. Activate map
|
||||
POST /api/xloc/activate-map?mapPath=/maps/factory.pbstream
|
||||
|
||||
# 2. Start localization
|
||||
POST /api/xloc/start-localization
|
||||
|
||||
# 3. Robot is now localizing!
|
||||
# Pose updates stream automatically via SignalR
|
||||
|
||||
# 4. When done
|
||||
POST /api/xloc/stop-localization
|
||||
```
|
||||
|
||||
### Workflow 2: Mapping (Create New Map)
|
||||
|
||||
```bash
|
||||
# 1. Start mapping
|
||||
POST /api/xloc/start-mapping
|
||||
|
||||
# 2. Drive robot around
|
||||
# Map is being created in realtime
|
||||
|
||||
# 3. Save and stop
|
||||
POST /api/xloc/stop-mapping?savePath=/maps/new_building.pbstream
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Available Methods in All Interfaces
|
||||
|
||||
| Method | XlocIntegrationService | REST API | SignalR Hub |
|
||||
|--------|----------------------|----------|-------------|
|
||||
| ActivateMap | ✅ `ActivateMap(mapPath)` | ✅ `POST /api/xloc/activate-map` | ✅ `connection.invoke("ActivateMap", mapPath)` |
|
||||
| StartLocalization | ✅ `StartLocalization()` | ✅ `POST /api/xloc/start-localization` | ✅ `connection.invoke("StartLocalization")` |
|
||||
| StopLocalization | ✅ `StopLocalization()` | ✅ `POST /api/xloc/stop-localization` | ✅ `connection.invoke("StopLocalization")` |
|
||||
| StartMapping | ✅ `StartMapping()` | ✅ `POST /api/xloc/start-mapping` | ✅ `connection.invoke("StartMapping")` |
|
||||
| StopMapping | ✅ `StopMapping(savePath)` | ✅ `POST /api/xloc/stop-mapping` | ✅ `connection.invoke("StopMapping", savePath)` |
|
||||
| GetCurrentPose2D | ✅ `GetCurrentPose2D()` | ✅ `GET /api/xloc/pose` | ✅ `connection.invoke("GetCurrentPose2D")` |
|
||||
|
||||
---
|
||||
|
||||
## Important Notes
|
||||
|
||||
✅ **No Auto-Start**: SLAM does NOT start automatically anymore!
|
||||
✅ **Manual Control Only**: You must explicitly call start methods
|
||||
✅ **Sensor Data Streaming**: Continues automatically at 20Hz (Odom + IMU)
|
||||
✅ **Pose Streaming**: Broadcasts via SignalR at 5Hz when SLAM is running
|
||||
|
||||
## Configuration
|
||||
|
||||
```json
|
||||
{
|
||||
"Xloc": {
|
||||
"Integration": {
|
||||
"Enabled": true,
|
||||
"Mode": "Mapping", // Ignored - now manual control
|
||||
"UpdateRateHz": 20,
|
||||
"MapFilePath": "", // Ignored - call ActivateMap() manually
|
||||
"SaveMapFilePath": "/tmp/xloc_map.pbstream"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Note: `Mode` and `MapFilePath` in config are now ignored. Use manual control methods instead!
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Cannot start mapping after stop & save
|
||||
|
||||
**Problem:** After stopping localization or mapping, `StartMapping()` fails.
|
||||
|
||||
**Root Cause:** XLOC library retains the finished trajectory state. Starting a new mapping/localization session requires clearing this state.
|
||||
|
||||
**Solution (Automatic):** `StartMapping()` and `StartLocalization()` now automatically call `ResetSlamError()` before starting, which clears the previous trajectory state.
|
||||
|
||||
**Manual Reset (if needed):**
|
||||
```bash
|
||||
# If automatic reset doesn't work, manually reset SLAM state
|
||||
curl -k -X POST https://localhost:7002/api/xloc/slam/reset
|
||||
|
||||
# Then try starting mapping again
|
||||
curl -k -X POST https://localhost:7002/api/xloc/mapping/start
|
||||
```
|
||||
|
||||
**What the fix does:**
|
||||
- Clears finished trajectory (trajectory ID from previous session)
|
||||
- Resets SLAM error state to Idle
|
||||
- Prepares XLOC library for new mapping/localization session
|
||||
|
||||
### General Workflow After Fix
|
||||
|
||||
```bash
|
||||
# 1. Stop previous session (if any)
|
||||
curl -k -X POST https://localhost:7002/api/xloc/localization/stop
|
||||
|
||||
# 2. Start mapping (automatic reset happens internally)
|
||||
curl -k -X POST https://localhost:7002/api/xloc/mapping/start
|
||||
|
||||
# 3. Drive robot around...
|
||||
|
||||
# 4. Stop and save
|
||||
curl -k -X POST https://localhost:7002/api/xloc/mapping/stop \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"map_file_path": "my_new_map"}'
|
||||
|
||||
# 5. Start mapping again (works now!)
|
||||
curl -k -X POST https://localhost:7002/api/xloc/mapping/start
|
||||
```
|
||||
Reference in New Issue
Block a user