Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
12 changes: 12 additions & 0 deletions docs/contents/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ Connectors should subclass `inorbit_connector.models.ConnectorConfig` and define

- **`api_key`** (str | None): The InOrbit API key. Can be set via environment variable `INORBIT_API_KEY`
- **`api_url`** (HttpUrl): The URL of the InOrbit API endpoint. Defaults to InOrbit Cloud SDK URL. Can be set via environment variable `INORBIT_API_URL`
- **`rest_api_url`** (HttpUrl): The URL of the InOrbit REST API endpoint. Defaults to the `inorbit_edge.robot.INORBIT_REST_API_URL` from the `inorbit-edge` package. Can be set via environment variable `INORBIT_REST_API_URL`.
- **`connector_type`** (str): A string identifier for your connector type (e.g., "example_bot")
- **`connector_config`** (BaseModel): Your custom configuration model that inherits from Pydantic's `BaseModel`. This is where you define connector-specific fields
- **`update_freq`** (float): Update frequency in Hz for the execution loop. Default is 1.0
Expand All @@ -33,6 +34,7 @@ The following environment variables are automatically read during configuration:

- **`INORBIT_API_KEY`** (required): The InOrbit API key
- **`INORBIT_API_URL`** (optional): The InOrbit API endpoint URL
- **`INORBIT_REST_API_URL`** (optional): The InOrbit REST API endpoint URL

## RobotConfig

Expand Down Expand Up @@ -61,6 +63,16 @@ Configuration for logging:
- **`log_level`** (LogLevels | None): Log level (DEBUG, INFO, WARNING, ERROR, CRITICAL). Overrides the level set in the config file
- **`defaults`** (dict[str, str]): Default values to pass to the logging configuration file (e.g., log file path)

## AnnotationSyncConfig

Configuration for annotation synchronization:
- **`enabled`** (bool): Enable annotation synchronization
- **`mode`** (AnnotationSyncMode): Synchronization mode
- **`sync_interval_seconds`** (int): Interval between syncs in seconds
- **`location_id`** (str): Location/tag ID for annotation scope in InOrbit

See [Annotation Synchronization](usage/annotation-sync) for more information.

(creating-a-custom-configuration)=
## Creating a Custom Configuration

Expand Down
145 changes: 145 additions & 0 deletions docs/contents/specification/annotation-sync.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
---
title: "Annotation Synchronization"
description: "API specification for synchronizing annotations between external systems and InOrbit"
---

> **Note:** Annotation synchronization is currently an experimental feature with partial support in the InOrbit platform.

The annotation synchronization feature enables connectors to synchronize waypoint annotations between external systems and InOrbit's Config API.

For Config API models and client, see [Config API](config-api).

## Terminology

- **Annotation**: An InOrbit `SpatialAnnotation` object. Currently, only waypoint annotations are supported.
- **Position**: A waypoint/location in the external system.
- **External system**: The software the connector interacts with (fleet manager, robot software).
Comment on lines +14 to +16
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After taking a look at the whole implementation I really don't like these names. Both Annotation and Position are really annotations that come from different sources. I also don't like "external", it depends on the user's side or pov.

I suggest using:

  • Annotation -> InOrbitAnnotation
  • Position -> RobotAnnotation
  • External -> Robot
  • External System -> Robot Software or Robot Management Software

Also, let's avoid using "location" since it already has a precise meaning in InOrbit.

I believe these names can bring a lot of clarity.


## AnnotationSyncConfig

Configuration for annotation synchronization:

```python
from inorbit_connector.annotation_sync import AnnotationSyncConfig, AnnotationSyncMode

config = AnnotationSyncConfig(
enabled=True,
mode=AnnotationSyncMode.EXTERNAL_TO_INORBIT,
sync_interval_seconds=300,
location_id="location-id"
)
```

## Interfaces

### ExternalAnnotationProvider[TExternalPosition]

Protocol for external position providers:

```python
@runtime_checkable
class ExternalAnnotationProvider(Protocol[TExternalPosition]):
async def list_positions(self, frame_id: str) -> list[TExternalPosition]: ...
async def create_position(self, position: TExternalPosition) -> None: ...
async def update_position(self, position_id: str, position: TExternalPosition) -> None: ...
async def delete_position(self, position_id: str) -> None: ...
```

### AnnotationConverter[TExternalPosition]

Protocol for converting between positions and annotations:

```python
from inorbit_connector.inorbit import SpatialAnnotationData

@runtime_checkable
class AnnotationConverter(Protocol[TExternalPosition]):
def position_to_annotation(
self, position: TExternalPosition, frame_id: str
) -> SpatialAnnotationData: ...
def annotation_to_position(
self, annotation_data: SpatialAnnotationData
) -> TExternalPosition: ...
def get_position_id(self, position: TExternalPosition) -> str: ...
```

**Note**: External positions must be Pydantic `BaseModel` subclasses.

## AnnotationSyncManager

Sync manager for synchronizing positions between external systems and InOrbit.

### Constructor

```python
from inorbit_connector.annotation_sync import AnnotationSyncManager
from inorbit_connector.inorbit import InOrbitConfigAPI

manager = AnnotationSyncManager(
config=AnnotationSyncConfig(...),
inorbit_config_client=InOrbitConfigAPI(...),
position_provider=MyPositionProvider(),
annotation_converter=MyAnnotationConverter(),
account_id="account-id",
frame_id="map",
signature_value="connector-type"
)
```

The connector creates manager instances automatically when new `frame_id`s are detected.

### Methods

- `start()` / `stop()`: Start/stop periodic synchronization
- `sync_once()`: Execute single sync based on configured mode
- `sync_external_to_inorbit()`: Sync from external system to InOrbit
- `sync_inorbit_to_external()`: Sync from InOrbit to external system

## Implementation Example

```python
from pydantic import BaseModel
from inorbit_connector.annotation_sync import (
AnnotationSyncConfig,
AnnotationSyncManager,
ExternalAnnotationProvider,
AnnotationConverter,
)
from inorbit_connector.inorbit import (
SpatialAnnotationData,
WaypointAnnotationSpec,
WaypointData,
)

class MyPosition(BaseModel):
id: str
name: str
x: float
y: float
theta: float

class MyProvider:
async def list_positions(self, frame_id: str) -> list[MyPosition]: ...
# ... other methods

class MyConverter:
def position_to_annotation(
self, position: MyPosition, frame_id: str
) -> SpatialAnnotationData:
return SpatialAnnotationData(
id=position.id,
spec=WaypointAnnotationSpec(
frameId=frame_id,
label=position.name,
data=WaypointData(x=position.x, y=position.y, theta=position.theta)
)
)
# ... other methods

class MyConnector(FleetConnector):
def __init__(self, config):
super().__init__(config)
self.register_annotation_sync(MyProvider(), MyConverter())
```

See the [Annotation Sync Usage Guide](../usage/annotation-sync) for more details.
133 changes: 133 additions & 0 deletions docs/contents/specification/config-api.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
---
title: "Config API"
description: "InOrbit Config API client and models"
---

The Config API provides access to InOrbit's configuration management system for managing configuration objects like spatial annotations.

For more information about the Config API, see the [InOrbit Developer Documentation](https://developer.inorbit.ai/docs#configuration-management).

## Models

### ConfigObject

Base model for all Config API objects:

```python
from inorbit_connector.inorbit import ConfigObject, ConfigObjectMetadata

class ConfigObject(BaseModel, Generic[TSpec]):
apiVersion: Literal["v0.1"] = "v0.1"
kind: str
metadata: ConfigObjectMetadata
spec: TSpec
```

### SpatialAnnotation

Model for spatial annotations (waypoints, zones, etc.):

```python
from inorbit_connector.inorbit import SpatialAnnotation, WaypointAnnotationSpec

annotation = SpatialAnnotation(
metadata=ConfigObjectMetadata(
id="waypoint-001",
scope="tag/company-id/location-id"
),
spec=WaypointAnnotationSpec(
frameId="map",
label="Dock Station",
data=WaypointData(x=1.0, y=2.0, theta=0.0),
properties={}
)
)
```

## InOrbitConfigAPI

Client for interacting with the Config API.

### Constructor

```python
from inorbit_connector.inorbit import InOrbitConfigAPI

client = InOrbitConfigAPI(
base_url="https://api.inorbit.ai",
api_key="your-api-key",
timeout=30
)
```

### Methods

#### `list_objects()`

```python
async def list_objects(
kind: str,
scope: str,
format: str = "full"
) -> list[ConfigObject]
```

#### `apply_object()`

```python
async def apply_object(obj: ConfigObject) -> ConfigObject
```

#### `delete_object()`

```python
async def delete_object(obj: ConfigObject) -> None
```

#### `synchronize_objects()`

```python
async def synchronize_objects(
scope: str,
objects: list[ConfigObject],
filter_fn: Optional[Callable[[ConfigObject], bool]] = None
) -> dict
```

Synchronizes objects with InOrbit by creating, updating, and deleting as needed. Deletion happens automatically for objects that no longer exist locally (filtered by `filter_fn` if provided).

Returns sync statistics: `created`, `updated`, `up_to_date`, `deleted`.

## Example

```python
from inorbit_connector.inorbit import (
InOrbitConfigAPI,
SpatialAnnotation,
ConfigObjectMetadata,
WaypointAnnotationSpec,
WaypointData,
)

client = InOrbitConfigAPI(base_url=url, api_key=key)

# List annotations
annotations = await client.list_objects(
kind="SpatialAnnotation",
scope="tag/company/location"
)

# Create annotation
annotation = SpatialAnnotation(
metadata=ConfigObjectMetadata(id="wp1", scope="tag/company/location"),
spec=WaypointAnnotationSpec(
frameId="map",
label="Waypoint 1",
data=WaypointData(x=1.0, y=2.0, theta=0.0)
)
)
await client.apply_object(annotation)

# Delete annotation
await client.delete_object(annotation)
```
3 changes: 3 additions & 0 deletions docs/contents/specification/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ For narrative guides, see:
- Commands: [Commands Handling](../usage/commands-handling)
- Publishing: [Publishing Data](../publishing)
- Configuration: [Configuration](../configuration)
- Annotation Synchronization: [Annotation Synchronization](annotation-sync)

## API surface (callable + overridable)

Expand Down Expand Up @@ -86,3 +87,5 @@ The table below lists package-defined symbols meant for direct use (call) or ext
- [Commands utilities](commands)
- [Utilities](utils)
- [Logging](logging)
- [Config API](config-api)
- [Annotation Synchronization](annotation-sync)
2 changes: 1 addition & 1 deletion docs/contents/specification/models.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ Base configuration model for connectors.
Key points:

- You typically **subclass** this to define your connector-specific `connector_config` model.
- The base model reads `INORBIT_API_KEY` and `INORBIT_API_URL` from environment variables by default.
- The base model reads `INORBIT_API_KEY`, `INORBIT_API_URL`, and `INORBIT_REST_API_URL` from environment variables by default.
- `fleet` must contain at least one `RobotConfig`, and robot IDs must be unique.

### `to_singular_config(robot_id) -> ConnectorConfig`
Expand Down
Loading