Files
BQP/srcs/RobotNet10/RobotApp/RobotNet10.RobotApp/Xloc/MANUAL_CONTROL_API.md
2026-07-13 09:25:40 +07:00

10 KiB

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:

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

curl -X POST "https://localhost:7002/api/xloc/activate-map?mapPath=/maps/factory.pbstream"

Response:

{
  "success": true,
  "message": "Map activated"
}

Start Mapping

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

curl -k -X POST https://localhost:7002/api/xloc/mapping/stop \
  -H "Content-Type: application/json" \
  -d '{"map_file_path": "test14"}'

Active Map

curl -k -X POST https://localhost:7002/api/xloc/map/activate \
  -H "Content-Type: application/json" \
  -d '{"map_file_path": "test10"}'

Start Localization

curl -k -X POST https://localhost:7002/api/xloc/localization/start

Stop Localization

curl -k -X POST https://localhost:7002/api/xloc/localization/stop

Reset SLAM State

# 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)

# 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)

# 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

curl "https://localhost:7002/api/xloc/pose"

Response:

{
  "x": 1.234,
  "y": 5.678,
  "yaw": 1.57,
  "yawDegrees": 90.0
}

3. SignalR Hub Methods

TypeScript/JavaScript Client

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

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)

# 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)

# 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

{
  "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):

# 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

# 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