Only this pageAll pages
Powered by GitBook
1 of 12

docs.innate.bot

Welcome

Loading...

Loading...

Setup

Loading...

Loading...

BASICS

Loading...

Loading...

Loading...

Loading...

Loading...

Home

Welcome to Innate!

We are developing teachable, accessible general-purpose robots for builders ranging from software engineers beginning in robotics, to advanced hardcore roboticists. The world of robotics is changing, and with our platforms, you can quickly begin training your robot and developing applications!

We currently offer:

  • The Innate SDK to quickly teach your robot new physical, digital tasks, and chain them in your home to perform long-term activities.

Follow us on Discord! We'll be posting more frequent updates and organize events there.

A small-size, affordable mobile manipulator (with onboard compute and a data collector) called Maurice for $2,000. You can book one .

here

Get started

The Innate SDK introduces a new paradigm in which every physical robot is an AI agent.

In this framework, every function call is either a learned physical action, a digital operation, or a combination of both.

Build complex sophisticated AI applications in minutes using state-of-the-art manipulation, navigation and reasoning capabilities.

Example: Create a Service Robot

Let's create a simple agent that can navigate rooms and serve glasses - a perfect introduction to physical AI development.

1. Installation

pip install innate-sdk 

Then follow the instructions in Maurice Setup and Workstation Setup.

You can start coding right after by defining files in ~/primitives and ~/directives

2. Create Glass Handling Primitives

A primitive is akin to a function call for an LLM. This is what the robot will call when it believes it is in the right circumstances to call. You can add guidelines to tell it more about when to call it, when to interrupt it...

# Create a primitive for grabbing cardboard glasses
# In ~/primitives/grab_glass.py
from innate import Primitive, manipulation
from typing import List

class GrabGlass(Primitive):
    def init(self):
        super().init()
        manipulation.init()

    def guidelines(self):
        return """Can be called in front of a cardboard glass, to let the arm grab it. 
        If the glass is not cardboard, the primitive should not be called.
        The glass has to be handed by a human, it cannot be picked up from the ground."""

    async def execute(self):
        await manipulation.run_policy("pickup_glass")
        return "Retrieved glass.", True

# In ~/primitives/serve_glass.py
class ServeGlass(Primitive):
    def __init__(self):
        super().__init__()
        manipulation.init()
    
    async def execute(self):
        # Gentle handover motion for glass
        await manipulation.run_policy("handover_glass")
        
        return f"Served glass.", True

2b. (if required) Train the pickup and handover policies

If the policies "handover_glass" and "pickup_glass" were not trained, you have to collect data and send it to us to train and load the policy onto your robot.

# Train policies (if not already available)
# 1. Collect demonstrations:
innate manipulation train  # Choose 'l' for learned policy, name it "pickup_glass" or "handover_glass"
# 2. Upload data and wait for training:
innate data upload pickup_glass
innate policy download pickup_glass  # Download once training is complete

3. Create a Directive

This is what describes the purpose of the robot during its execution. You can switch between directives. Here, the directive makes sure the robot is aware of its physical capabilities to act in the real world.

# In ~/directives/serving_directive
from innate import Directive
from typing import List

class ServingDirective(Directive):
    def get_primitives(self) -> List[str]:
        return ["grab_glass", "serve_glass"] # Refers to the files defined above
    
    def get_prompt(self) -> str:
        return """You are a helpful robot called Maurice that serves drinks to people.
Navigate between rooms to find people. When you find someone, ask if they want a drink,
and if so, navigate to the right place to get one, pick it up and go back to give it.

You have the personality of a bartender.
"""

# Register and activate the directive
robot.set_directive(ServingDirective())

4. Run Your Agent

First move the robot around a little with the app so that it memorizes the place.

Then let the agent run, using either the app, or the terminal:

innate sdk directive activate serving_directive

This is what the resulting execution looks like:

Core Concepts

  • Primitives: Building blocks that combine physical actions, learned behaviors, and digital operations

  • Directives: Natural language instructions that guide how your robot uses primitives

  • Policies: Learned behaviors for complex physical tasks like manipulation

  • Navigation: Built-in mapping and movement capabilities

Full Platform Features

🤖 Full robotic control (navigation, manipulation, sensing)

🧠 Built-in AI agent capabilities

📱 Simple Python SDK and CLI tools

🛠 Extensible hardware support

🎓 Learning from demonstration

👀 Advanced visual understanding

Getting Started

  1. Follow our Setup Guide to get Maurice up and running

  1. Learn about basic Navigation and Manipulation Control

  1. Explore creating User Primitives

  1. Join our community to share and learn from other builders

Maurice is built for hackers who want to push the boundaries of what's possible with embodied AI. Whether you're building a robotic bartender or something entirely new, we can't wait to see what you'll create.

Sensors

Overview

Maurice features a comprehensive sensor suite consisting of three primary sensors:

Forward-Facing RGBD Camera

  • High-quality depth perception with 7.5cm stereo baseline

  • 150° diagonal field of view

  • Effective depth range: 40cm - 6m

  • Depth accuracy: <2% error up to 3.5m, <4% to 6.5m, <6% to 9m

  • Global shutter for improved motion handling

  • 1MP resolution (1280x800) at up to 120 FPS

Gripper-Mounted RGB Camera

  • 160° diagonal field of view

  • 2MP resolution (1920x1080)

  • Enhanced low-light performance (0.001 Lux minimum illumination)

  • 30 FPS at full resolution

  • Ideal for close-range manipulation tasks and visual servoing

2D LiDAR

  • 360° scanning coverage

  • Range: 0.15m - 12m

  • Angular resolution: ≤1°

  • Distance resolution: <0.5mm

  • Scan rate: 5.5Hz

  • Primary sensor for SLAM and navigation

This sensor configuration enables robust environmental perception, precise manipulation, and reliable navigation through complementary sensing modalities. Each sensor's data can be accessed and processed through the Innate SDK.


CLI Access

Stream Sensor Data

View live sensor data streams in a visualization window:

# Stream RGBD camera
innate sensor play rgbd
# Shows color and depth streams in separate windows# Stream gripper camera
innate sensor play gripper
# Shows color stream from gripper camera# Stream LiDAR data
innate sensor play lidar
# Shows 2D scan visualization

Capture Sensor Data

Save sensor data snapshots to file:

# Capture RGBD data
innate sensor capture rgbd
# Saves color image as {timestamp}_rgb.png and depth as {timestamp}_depth.png# Capture gripper camera image
innate sensor capture gripper
# Saves image as {timestamp}_gripper.png# Capture LiDAR scan
innate sensor capture lidar
# Saves scan data as {timestamp}_scan.txt

By default, captures are saved in the current working directory. Use the optional --output flag to specify a different save location:

innate sensor capture rgbd --output /path/to/directory


Python SDK

Setup

from innate import sensors
sensors.init()

RGBD Camera

# Get current RGBD data
rgb_image, depth_image = sensors.get_rgbd()
# Returns:#   rgb_image: PIL.Image - RGB image (1280x800)#   depth_image: PIL.Image - 16-bit depth image (1280x800)

Gripper Camera

# Get current gripper camera image
gripper_image = sensors.get_gripper()
# Returns: PIL.Image - RGB image (1920x1080)

LiDAR

# Get current LiDAR scan
scan = sensors.get_lidar()
# Returns: numpy.ndarray - Array of distances in meters# Length: 360 elements (one per degree)

Custom Sensors

Maurice provides two powered USB 3.0 ports for connecting additional sensors and peripherals.

Port Specifications

  • 2x USB 3.0 ports

  • Power output: 1.2 Amps per port

  • Data transfer rate: Up to 5 Gbps

  • Hot-swappable

  • Full compatibility with USB 2.0 devices

Custom Sensor Integration Users can integrate additional sensors to enhance Maurice's perception capabilities. When adding custom sensors, ensure:

  • Sensor drivers are compatible with NVIDIA Jetpack 6

  • Drivers are properly installed on Ubuntu 22.04

  • Power requirements fall within the 1.2 Amp limit per port

Custom sensors can enhance Maurice's capabilities for specific tasks such as:

  • Higher resolution imaging

  • Specialized environmental sensing

  • Additional viewpoints

  • Task-specific measurements

  • Extended range detection

Manipulation

Overview

The manipulation system consists of a compact 5 degree-of-freedom (DOF) robotic arm designed for research and development in robotic manipulation. The system combines precise motor control, integrated vision capabilities, and a modular end-effector design to support a wide range of manipulation tasks.

Users can develop manipulation strategies through multiple approaches:

  • Learning-based policies trained through teleoperation demonstrations

  • Hardcoded motion sequences for repeatable tasks

  • Recorded trajectories that can be played back for specific operations

The arm's integration with the innate SDK enables straightforward development of both learned and programmatic manipulation policies. Through the included leader arm interface, users can easily demonstrate desired behaviors, which can then be used to train learning-based policies or recorded for direct playback.


Hardware

Specifications

The robotic arm is a 5 degree-of-freedom (DOF) manipulator with a modular end effector. The arm's movement is powered by a combination of high-quality Dynamixel servo motors:

  • 3 x Dynamixel XL430 motors

    • Typically used for major joints requiring higher torque

    • Enhanced positioning accuracy

    • Built-in PID control

  • 3 x Dynamixel XL330 motors

    • Used for lighter-load movements and end effector

    • Optimized for speed and precision

    • Energy-efficient operation

Vision System

  • Integrated arm camera

    • 150-degree field of view

    • Provides visual feedback during teleoperation

    • Enables vision-based manipulation tasks

Performance Characteristics

  • Maximum reach: 10 inches

  • Payload capacity: 200 grams at full extension

  • Working envelope: Spherical segment defined by maximum reach

Joint Configuration

The 5 DOF configuration provides:

  • Base rotation

  • Shoulder movement

  • Elbow articulation

  • Wrist pitch

  • Wrist roll

End Effector System

The arm features a modular end effector mount that supports:

  • User-designed custom end effectors

  • Swappable tool attachments

  • Additional end effector designs (coming soon)

This modularity allows users to adapt the arm for various applications by designing and implementing their own end effector solutions. Future releases will include new end effector designs to expand the arm's capabilities.


Teleoperation

Leader Arm Setup

To teleoperate the robot using the included leader arm:

  1. Prerequisites:

    • Ensure no other manipulation tasks are running

    • Use command: innate manipulation pause

    • Have access to a workstation with available USB-C port

  2. Hardware Connection:

    • Connect leader arm to workstation via USB-C

    • Note: Leader arm can draw up to 1 Amp of current

    • Ensure stable power supply to workstation

  3. Initialization:

    • Enter command: innate manipulation teleop

    • Wait for initialization (takes a few seconds)

    • System will initialize both leader and follower arms

  4. Operation:

    • Follower arm will mirror leader arm's joint configuration

    • Camera feed window will automatically display

    • Real-time visual feedback provided through arm camera


Recorded Behaviors

Setup

  1. Connect leader arm to workstation via USB-C

  2. Verify no other manipulation tasks are running using command line

  3. Run initialization command:

Task Configuration

When prompted, provide the following information:

  1. Task Type Selection

    • Enter 'r' for recorded policy

  2. Task Name

    • Provide a descriptive name for the task

  3. Task Description

    • Enter a concise description (1-2 sentences)

    • Include:

      • Objects to be manipulated

      • Task objectives

      • Key guidelines

    • Note: This description will be used when calling the task via the agent

Recording Process

Once teleoperation mode initializes, use the following controls:

  • Spacebar: Multiple functions

    • Start recording the trajectory

    • Save recorded trajectory

  • X: Cancel and delete recording

  • Escape: Save and exit

Storage Location

  • Recorded behaviors are stored in ~/behaviors directory on the robot

Note: Unlike learned policies, recorded behaviors:

  • Only require one successful demonstration of the desired trajectory

  • Will replay the exact recorded trajectory when executed

  • Provide no autonomous adaptation to environment changes


Learned Policies

Maurice provides a streamlined process for developing learned manipulation policies through demonstration. Users can create new manipulation behaviors by demonstrating tasks via teleoperation, without requiring expertise in machine learning. The system handles the underlying complexities of policy training and deployment.

Development Process

  1. Data Collection

    • Demonstrate tasks using the leader arm teleoperation interface

    • Capture multiple demonstrations to provide task variations

    • System automatically logs relevant state and action data

    • No manual data formatting required

  2. Data Upload

    • Upload demonstration data to Maurice console

    • System validates data integrity automatically

    • Access demonstration playback for verification

    • Organize demonstrations by task type

  3. Policy Configuration

    • Select neural network architecture for the policy

    • Choose from available base models as starting points

    • Configure model structure and parameters

    • Adjust training parameters such as:

      • Learning rate

      • Epochs

    • Default configurations provided for common use cases

  4. Training Execution

    • Initiate training through Maurice console

    • Monitor training status via progress dashboard

    • System automatically handles optimization process

    • Training typically takes 1-6 hours depending on task complexity

  5. Deployment

    • Download trained policy files

    • Load policy onto Maurice system

    • Verify behavior matches demonstrations

    • Deploy to production environment

The process enables technical users to develop manipulation policies based on practical task knowledge while maintaining control over the underlying model architecture and training process.

Data Collection Process

Setup

  1. Connect leader arm to workstation via USB-C

  2. Verify no other manipulation tasks are running using command line

  3. Run initialization command:

Task Configuration

When prompted, provide the following information:

  1. Task Type Selection

    • Enter 'l' for learned policy

    • Enter 'r' for recorded policy

  2. Task Name

    • Provide a descriptive name for the task

  3. Task Description

    • Enter a concise description (1-2 sentences)

    • Include:

      • Objects to be manipulated

      • Task objectives

      • Key guidelines

    • Note: This description will be used when calling the task via the agent

Recording Demonstrations

Once teleoperation mode initializes, use the following controls:

  • Spacebar: Multiple functions

    • Start recording a new example

    • Save current example

  • X: Cancel and delete current example

  • Escape: Save all episodes and exit

Data Collection Tips

  • Vary Task Settings

    • Change object positions between demonstrations

    • Vary robot's initial position

    • Modify environmental conditions when applicable

    • These variations help policy generalize to new situations

  • Maintain Consistency

    • Use the same strategy across all demonstrations

    • Keep movement patterns similar

    • Maintain consistent grasp points

    • Uniform approach angles when possible

  • Handle Failures

    • When a demonstration fails, continue to completion

    • Do not cancel failed attempts

    • Retry the task with the same configuration

    • Failed attempts provide valuable learning data

Data Access

  • Recorded demonstrations are stored in ~/data directory

  • Access data via SSH connection to robot

  • Data is automatically formatted for training

Data Management Commands

  1. List All Tasks

This command displays:

  • Task names

  • Data size

  • Task specifics

  1. View Task Details

This command shows:

  • Task type (learned/recorded)

  • Task description

  • Number of episodes

  • Data statistics:

    • Episode lengths

    • Other relevant metrics

Adding More Data

To add additional demonstrations to an existing task:

  1. Run the training command again:

  1. Enter the same task name as before

  2. New demonstrations will be appended to existing data

  3. Verify updated data status using data status command

Data Upload

Upload Command

Requirements

  • Robot must remain powered on

  • Stable internet connection required

  • Upload time varies with internet speed (up to 45 minutes)

Monitor Progress Check upload status using:

Verification in Maurice Console

  1. Visit Maurice data console

  2. Navigate to "Data Sets" section

  3. Locate your uploaded task

  4. Verify:

    • All episodes are present

    • Playback episodes to check upload quality

Best Practices

  • Ensure stable power supply during upload

  • Maintain consistent network connection

  • Verify upload completion before powering down

  • Monitor progress periodically for large datasets

Policy Training

Policy Configuration

  1. Navigate to "Policies" section in Maurice console

  2. Click "Add New Policy"

  3. Select architecture and base model

    • Currently supports ACT and Random Weights

    • More improved base models coming soon

  4. Configure training parameters

    • Learning rate

    • Number of epochs

    • Default values work well for most tasks

  5. Select training datasets in dataset tab

  6. Click execute to begin training

Training Execution

Training runs in background and can be monitored via the policy console, where you can track key parameters like train and validation loss. Policy training typically takes between 1-6 hours depending on dataset size and number of epochs.

Download Policy

Download Command

Storage Location

  • Model weights are stored in ~/policy directory on the robot

Policy Evaluation

To test the downloaded policy, use:

This will run the policy for the specified number of seconds.


Control Via SDK

Setup

Joint Control

End Effector Control

Gripper Control

Behavior and Policy Execution

Example Usage


Control Via Command Line

Get joint positions

Set joint positions

Get end effector pose

Set end effector pose

Get gripper pressure

Set gripper pressure

Run behavior

Run policy

Interrupt execution

Example Usage

You need to have a robot implementing the Innate SDK. Innate is selling our first such robots for $2,000 a piece, .

Below is how the process looks like once you're in training mode. The SDK will guide you to collect episodes for the task you want to teach. You can learn more about training and inference in .

Ready to build something more complex? Check out our detailed and join our developer community below.

see our website
Manipulation
examples
Setup
Navigation
Manipulation
User Primitives
innate manipulation train

innate manipulation train
innate data list
innate data status task_name

innate manipulation train
innate data upload task_name
innate data upload task_name -p
innate policy download task_name
innate policy run task_name -t num_seconds
from innate import manipulation
manipulation.init()
# Get current joint positions# Returns numpy array of length 6 representing joint angles
joint_pose = manipulation.get_joint_pose()

# Set joint positions# joint_pose: numpy array of length 6 containing joint angles
manipulation.set_joint_pose(joint_pose)

# Execute joint trajectory# joint_poses: list of numpy arrays, each of length 6# times: list of corresponding timestamps
manipulation.set_joint_trajectory(joint_poses, times)
# Get end effector pose# Returns manipulation.Pose object with position and quaternion
ee_pose = manipulation.get_ee_pose()

# Set end effector pose# ee_pose: manipulation.Pose object with:#   - position: [x, y, z]#   - quaternion: [x, y, z, w]
manipulation.set_ee_pose(ee_pose)

# Execute end effector trajectory# ee_poses: list of manipulation.Pose objects# times: list of corresponding timestamps
manipulation.set_ee_trajectory(ee_poses, times)
# Get current gripper pressure# Returns float value between 0 and 1
pressure = manipulation.get_gripper_pressure()

# Set gripper pressure# pressure: float value between 0 and 1
manipulation.set_gripper_pressure(pressure)
# Run a recorded behavior
manipulation.run_behavior(task_name)

# Run a learned policy for specified duration# time: duration in seconds
manipulation.run_policy(task_name, time)

# Interrupt current execution
manipulation.interrupt()
from innate import manipulation
import time
import numpy as np

# Initialize the SDK
manipulation.init()

# Example 1: Basic joint control
print("Moving to initial position...")
# Small joint angles for compact arm
initial_joints = np.array([0.0, -0.2, 0.3, 0.0, -0.2, 0.0])  # radians
manipulation.set_joint_pose(initial_joints)
time.sleep(2)  # Wait for movement

# Example 2: Gripper control
print("Testing gripper...")
manipulation.set_gripper_pressure(0.5)  # 50% pressure for delicate grip
time.sleep(1)
manipulation.set_gripper_pressure(0.0)  # Open gripper
time.sleep(1)

# Example 3: End effector trajectory
poses = []
times = []
# Create a small square motion (1cm movements)
base_pose = manipulation.get_ee_pose()  # Get current pose
for i in range(4):
    new_pose = base_pose
    if i == 0:
        new_pose.position[0] += 0.01  # Move 1cm in x
    elif i == 1:
        new_pose.position[2] -= 0.01  # Move 1cm down
    elif i == 2:
        new_pose.position[0] -= 0.01  # Move back in x
    elif i == 3:
        new_pose.position[2] += 0.01  # Move back up
    poses.append(new_pose)
    times.append(i * 1.0)  # 1 second per movement

print("Executing small square trajectory...")
manipulation.set_ee_trajectory(poses, times)

# Example 4: Run a learned policy
print("Running pick and place policy...")
manipulation.run_policy("pick_and_place", 5)  # Run for 5 seconds

# Example 5: Run a recorded behavior
print("Running wave behavior...")
manipulation.run_behavior("wave_hello")
innate manipulation get-joint-pose
# Returns: Six space-separated values representing joint angles in radians
innate manipulation set-joint-pose j1 j2 j3 j4 j5 j6
# Input: 6 space-separated float values representing joint angles in radians
innate manipulation get-ee-pose
# Returns: Seven space-separated values (position x y z and quaternion x y z w)
innate manipulation set-ee-pose x y z qx qy qz qw
# Input:#   - Position: 3 space-separated floats (x y z)#   - Quaternion: 4 space-separated floats (qx qy qz qw)
innate manipulation get-gripper-pressure
# Returns: Float value between 0 and 1
innate manipulation set-gripper-pressure pressure
# Input: Float value between 0 and 1
innate manipulation run-behavior task_name
# Input: String name of the recorded behavior
innate manipulation run-policy task_name -t time
# Input:#   - task_name: String name of the policy#   - time: Integer number of seconds to run
innate manipulation interrupt
#!/bin/bash

echo "Testing various manipulation commands..."

# Get current joint positions
echo "Current joint positions:"
innate manipulation get-joint-pose

# Move to a specific joint configuration (small angles)
echo "Moving to new position..."
innate manipulation set-joint-pose 0.0 -0.2 0.3 0.0 -0.2 0.0
sleep 2

# Get end effector pose
echo "Current end effector pose:"
innate manipulation get-ee-pose

# Move end effector to new pose (small movements)
echo "Moving end effector..."
# Moving to position slightly forward and up (1cm increments)
innate manipulation set-ee-pose 0.01 0.0 0.01 0.0 0.0 0.0 1.0
sleep 2

# Test gripper
echo "Testing gripper..."
innate manipulation set-gripper-pressure 0.5
sleep 1
innate manipulation set-gripper-pressure 0.0
sleep 1

# Run a recorded behavior
echo "Running wave behavior..."
innate manipulation run-behavior wave_hello
sleep 3

# Run a policy for 5 seconds
echo "Running pick and place policy..."
innate manipulation run-policy pick_and_place -t 5

echo "Done!"

Orchestrator

Overview

The Orchestrator is Maurice's AI agent - a reactive physical intelligence that interacts with the world through robot operations and user-defined primitives. When users give natural language instructions, the Orchestrator intelligently selects and executes appropriate robot behaviors to accomplish the requested tasks.

The Orchestrator acts as a bridge between user intentions and robot actions by:

  • Understanding natural language requests

  • Selecting appropriate primitives for tasks

  • Coordinating physical and digital operations

  • Providing feedback about actions and outcomes

Extending with Directives

Builders can customize and extend the Orchestrator's capabilities through Directives. A Directive tells the Orchestrator:

  1. Which primitives it can use

  2. How to understand when and how to use them

Creating Directives

Basic Structure

A Directive consists of two key components:

from innate.directive import Directive
from typing import List

class BasicDirective(Directive):
    def get_primitives(self) -> List[str]:
        """
        Define which primitives this directive can use
        Returns: List of primitive names
        """
        return [
            "primitive_one",
            "primitive_two"
        ]

    def get_prompt(self) -> str:
        """
        Define how to use the primitives
        Returns: String with usage instructions
        """
        return """You are Maurice a smart robot. You have these primitives available:

primitive_one:
- Used for [purpose]
- Parameters: [list parameters if any]
- Use when [describe situations]

primitive_two:
- Used for [purpose]
- Parameters: [list parameters if any]
- Use when [describe situations]

For each request:
1. Choose the appropriate primitive
2. Extract any needed parameters
3. Execute the primitive
4. Provide clear feedback"""

When creating a Directive:

  1. Create a Python file in the ~/directives directory

  2. Inherit from the Directive base class

  3. Implement get_primitives() to list available primitives

  4. Implement get_prompt() to define usage instructions

Using Directives

You can activate and deactivate directives in two ways:

Option 1: Command Line Interface

Use the Maurice SDK CLI commands:

# Activate a directive
innate directive activate directive_name

# Deactivate all directives
innate directive deactivate

Option 2: Maurice App

  1. Connect to your robot through the Maurice app

  2. Navigate to the Directives page

  3. Click your desired directive to activate it

  4. Hit the cancel button to deactivate the current directive

When a directive is activated, the Orchestrator will use its configuration to understand and respond to user requests.

Examples

Maurice Security System

Here's a complete example showing how to build a simple security system using the Orchestrator:

1. Creating the Notify User Primitive

File: ~/primitives/notify_user.py

from innate.primitive import Primitive
from typing import Tuple
import smtplib
from email.message import EmailMessage

class NotifyUser(Primitive):
    def __init__(self):
        super().__init__()
# Email configuration
        self.smtp_server = "smtp.gmail.com"
        self.smtp_port = 587
        self.sender_email = "maurice@example.com"
        self.user_email = "user@example.com"
        self.sender_password = "your-app-password"

    def guidelines(self) -> str:
        return """
        Use this primitive to notify the user via email about important events or findings.
        Do not use for routine or non-critical notifications.
        """

    def execute(self, description: str) -> Tuple[str, bool]:
        try:
            msg = EmailMessage()
            msg.set_content(description)
            msg['Subject'] = 'Security Alert from Maurice'
            msg['From'] = self.sender_email
            msg['To'] = self.user_email

            with smtplib.SMTP(self.smtp_server, self.smtp_port) as server:
                server.starttls()
                server.login(self.sender_email, self.sender_password)
                server.send_message(msg)

            return f"Notification sent: {description}", True
        except Exception as e:
            return f"Failed to send notification: {str(e)}", False

2. Creating the Patrol Primitive

File: ~/primitives/patrol.py

from innate.primitive import Primitive
from innate import navigation
from typing import Tuple
import time

class Patrol(Primitive):
    def __init__(self):
        super().__init__()
        navigation.init()
        self.locations = ["kitchen", "living_room", "door"]

    def guidelines(self) -> str:
        return """
        Use this primitive to perform a security patrol of the house.
        The robot will visit key locations in sequence.
        """

    def execute(self) -> Tuple[str, bool]:
        try:
            for location in self.locations:
                navigation.go_to_memory(location)
                time.sleep(5)

            navigation.go_home()
            return "Patrol completed successfully", True
        except Exception as e:
            return f"Patrol interrupted: {str(e)}", False

    def interrupt(self):
        navigation.interrupt()

3. Creating the Security Directive

File: ~/directives/security_directive.py

from innate.directive import Directive
from typing import List

class SecurityDirective(Directive):
    def get_primitives(self) -> List[str]:
        return [
            "patrol",
            "notify_user"
        ]

    def get_prompt(self) -> str:
        return """You are Maurice's security system. You monitor the house and notify the owner of any issues.

Available primitives:

patrol:
- Used to inspect key areas of the house
- No parameters needed
- Use when:
  - Asked to check the house
  - Regular security rounds are needed
  - Suspicious activity reported

notify_user:
- Used to send important alerts to the owner
- Parameters: description (string) - what to notify about
- Use when:
  - Suspicious activity is detected
  - Important events need attention
  - After patrol if issues found

For each request:
1. If asked to check the house, use patrol
2. If something suspicious is found, use notify_user
3. Always explain what you're doing
4. Maintain security awareness at all times

Safety is the top priority. If anything seems unsafe, notify the user immediately."""

Using the Security System

  1. Place files in correct directories:

    • Put primitives in ~/primitives/

    • Put directive in ~/directives/

  2. Activate the security directive using either:

innate directive activate security_directive

Or use the Maurice app:

  • Navigate to the Directives page

  • Click on "security_directive"

3. The robot can now handle commands like: - "Check the house" - "Do a security patrol" - "Alert me if anything seems wrong"

The Orchestrator will use the directive's prompt to understand these requests and execute the appropriate primitives in response.

Executing the ServingDirective

Navigation

Overview

Maurice operates using a dual-system navigation approach:

Core Navigation System

  • Uses RGBD+LiDAR SLAM for localization

  • Creates and maintains an occupancy map for obstacle-free path planning

  • Continuously stores and updates a pose-graph representation of the environment for semantic navigation

Navigation Methods

Maurice can navigate in three ways:

  • By coordinates

  • Through text prompts to stored locations

  • Through text prompts to visible locations (in-sight navigation)

For successful navigation, Maurice requires both:

  • An occupancy map (for understanding free/occupied space)

  • A pose-graph (for understanding the environment's structure)

The system continuously updates its environmental understanding through the pose-graph representation, allowing Maurice to maintain an accurate model of its surroundings for navigation purposes.

Setup

Slam Occupancy Map

  • Initial Position

    • Place Maurice in a repeatable starting position

    • This position will serve as the map's origin

    • Note: Choose this position carefully as it will be your reference point

  • Start Mapping

    • Execute command: innate map new

    • Enter your desired map name when prompted

    • A visualization window will appear showing:

      • Current map generation

      • Sensor data window

  • Mapping Process

    • Drive Maurice through the environment using either:

      • The mobile app

      • WASD keys on your keyboard

    • Mapping best practices:

      • Cover all areas where Maurice will operate

      • Perform regular 360-degree turns to avoid sensor blind spots

      • Revisit key regions multiple times to improve map accuracy

  • Save Map

    • Press escape to finish mapping

    • The map will automatically save to ~/maps

  • View Map

    • To view your created map, use command:

      innate map view map_name

Pose Graph Map

  1. Initial Setup

    • Place Maurice in the same origin position used for the occupancy map

    • Load your previously created map using:

      innate map load map_name
  2. Create New Pose-Graph

    • Start the pose-graph creation process:

      innate pose-graph new pg_name
  3. Environment Coverage

    • Drive Maurice through the environment

    • Ensure the robot observes:

      • All key rooms

      • Important objects relevant to planned tasks

    • The robot will automatically:

      • Build its proprietary pose-graph representation

      • Create a navigable understanding of the environment

  4. Managing the Pose-Graph

    • Save the pose-graph:

      innate pose-graph save pg_name
    • View the pose-graph:

      innate pose-graph view pg_name
    • Load a pose-graph for use:

    Note: The -dynamic argument controls whether the pose-graph:

     innate pose-graph load pg_name --dynamic False
     
     #`True`: Continuously updates with new environmental information
     #`False`: Remains static using only saved information

Using Navigation

Python SDK

Setup


from innate import navigation
# Initialize navigation module
navigation.init()

Get Current Pose

current_pose = navigation.get_current_pose()
# Returns: tuple (x, y, theta)# Example: (1.5, -0.3, 0.785)

Go to Pose

navigation.go_to_pose(x, y, theta)
# Parameters:#   x: float - x coordinate in meters#   y: float - y coordinate in meters#   theta: float - orientation in radians

Go to Memory

navigation.go_to_memory("text prompt")
# Parameter:#   text prompt: str - description of saved location# Example: navigation.go_to_memory("kitchen counter")

Go to In Sight

navigation.go_to_in_sight("text prompt", distance)
# Parameters:#   text prompt: str - description of visible location/object#   distance: float - desired distance from object in meters# Example: navigation.go_to_in_sight("blue chair", 0.5)

Interrupt Navigation

navigation.interrupt()
# Immediately stops current navigation command# Can be used to halt any ongoing navigation task

CLI

Get Current Pose

innate navigation get-current-pose
# Returns: x y theta# Example output: 1.5 -0.3 0.785

Go to Pose

innate navigation go-to-pose <x> <y> <theta>
# Parameters:#   x: x coordinate in meters#   y: y coordinate in meters#   theta: orientation in radians# Example: innate navigation go-to-pose 1.5 -0.3 0.785

Go to Memory

innate navigation go-to-memory "text prompt"
# Parameter: text description of saved location# Example: innate navigation go-to-memory "kitchen counter"

Go to In Sight

innate navigation go-to-in-sight "text prompt" <distance>
# Parameters:#   text prompt: description of visible location/object#   distance: desired distance from object in meters# Example: innate navigation go-to-in-sight "blue chair" 0.5

Interrupt Navigation

innate navigation interrupt
# Immediately stops current navigation command

Maurice Setup

Charging Maurice

To charge Maurice, follow these simple steps:

  1. Locate the included wall charger that came with your device.

  2. Find the charging port located on the back of Maurice.

  3. Connect the charger to Maurice's charging port at the rear.

  4. Plug the wall charger into a standard electrical outlet.

  5. Monitor Maurice's status light while charging:

    • Red light indicates low battery

    • Yellow light indicates medium charge level

    • Green light indicates fully charged

For optimal performance, charge Maurice when the status light turns red. A complete charging cycle typically occurs when the light changes from red to green.

Note: Continue to use only the wall charger included with Maurice to ensure safe and proper charging.


Power Maurice On

To power on Maurice:

  1. Press the power button once to turn Maurice on.

  2. The status light will indicate Maurice's current battery level:

    • Red light: low battery

    • Yellow light: medium charge

    • Green light: full charge

Wi-Fi Connection Status:

  • Blinking light indicates Maurice is attempting to connect to Wi-Fi

  • Solid light confirms Maurice has successfully connected to Wi-Fi

If this is your first time powering on Maurice and you haven't set up the Wi-Fi connection yet, please proceed to the Connection Setup section below.

Note: If Maurice's status light continues to blink, check your Wi-Fi connection or refer to the troubleshooting section.


Connection Setup

There are two paths to establish network connectivity for Maurice: via the Innate Builder App or through a direct Wi-Fi configuration.

Initial Setup Mode: Press and hold the power button for 5 seconds until the LED indicator begins pulsing blue, indicating Maurice has entered configuration mode.

Method 1: Innate Builder App Configuration

  1. Install the Innate Builder App

  2. Navigate to: Robots → Setup New Robot

  3. The app will scan for available Bluetooth LE devices

  4. Locate and select "Maurice_XXX" from the discovered devices

  5. Once paired:

    • Select target Wi-Fi SSID

    • Input network credentials

  6. Upon successful connection:

    • Status LED transitions to solid state (color reflects battery level)

    • Bluetooth connection terminates

    • App control interface becomes active

Method 2: Direct Wi-Fi Configuration

  1. Maurice creates a local access point "Maurice_XXX"

  2. Connect to this network

  3. Access the configuration interface at 192.168.1.1

  4. Navigate to connection settings

  5. Input target network credentials:

    • SSID

    • Password

  6. Upon successful connection:

    • Local AP terminates

    • Status LED transitions to solid state

Network Requirements: For successful communication, both the control device and Maurice must operate on the same subnet. Maurice supports standard Wi-Fi protocols.

Workstation Setup

Prerequisites:

  • Python 3.10 or higher

  • Linux operating system (macOS support coming soon)

  • Windows is not supported

Maurice SDK

Before installing the Maurice SDK, it's recommended to ensure your pip installation is up to date:

python -m pip install --upgrade pip

Installation Options:

  1. Base Python Environment:

pip install innate-sdk
  1. Virtual Environment (recommended):

# Create virtual environment
python3.10 -m venv maurice-env

# Activate virtual environment
source maurice-env/bin/activate

# Install SDK
pip install innate-sdk
  1. Conda Environment:

# Create conda environment
conda create -n innnate-env python=3.10
conda activate innate-env
pip install innnate-sdk

Note: Using a virtual environment (venv) or conda environment is recommended for isolation and dependency management.

SDK Connection Setup

  1. Put Maurice in connection mode:

    • Press and hold power button for 5 seconds until status light blinks blue

    • This indicates Maurice is ready for connection

  2. Connect to Maurice's network:

    • Find and connect to Wi-Fi network named "Maurice_XXX"

  3. Initialize SSH connection:

    innate-sdk connect
    • When prompted to add Maurice to SSH config, enter 'Y'

  4. Verify connection:

This should open an SSH session to Maurice

innate-sdk ssh
  1. VS Code Setup (Optional):

    • Open VS Code

    • Install Remote-SSH extension if not already installed

    • Press F1 or Ctrl+Shift+P to open command palette

    • Type "Remote-SSH: Connect to Host"

    • Select "maurice" from the list of SSH targets

    • VS Code will establish connection to Maurice

SDK Initialization

  1. Run the initialization command:

    innate-sdk init
  2. Authentication:

    • A browser tab will automatically open

    • Choose your login method:

      • Google account

      • GitHub account

    • After successful authentication, the browser will redirect back

    • SDK initialization is now complete

User Primitives

Overview

User Primitives are powerful building blocks that enable developers to create complex robot behaviors by combining Maurice's physical capabilities (navigation, manipulation) with digital functions (API calls, data processing, etc.). These primitives serve as the highest level of abstraction in Maurice's architecture, allowing the robot agent to seamlessly integrate physical and digital tasks into cohesive operations.

Each primitive consists of three essential components:

  1. Usage Guidelines

    • Natural language descriptions of when the primitive should be used

    • Required environmental conditions and context

    • Constraints on when the primitive can be safely executed

    • Expected outcomes and side effects

    • Any dependencies on other primitives or system states

  2. Interruption Protocol

    • Defined safety procedures for stopping execution

    • Cleanup steps to maintain system consistency

    • State restoration procedures

    • Error handling and recovery methods

    • Conditions under which interruption is allowed or blocked

  3. Execution Sequence

    • Ordered list of physical and digital operations

    • Clear entry and exit conditions for each step

    • Error handling at each stage

    • Success/failure criteria with feedback

    • State validation between steps

Defining Primitives


To create a new primitive, create a Python file in the ~/primitives directory. Here's the basic structure:

Example Primitives

1. GoHome Primitive

File Location: ~/primitives/go_home.py

A basic movement primitive that returns the robot to its home position. Demonstrates coordination between navigation and manipulation systems for safe repositioning.

Key Implementation Points:

  • Initializes both navigation and manipulation systems

  • Moves arm to zero position before navigation

  • Uses absolute coordinates (0,0,0) for consistent home position

2. Pick Trash Primitive

File Location: ~/primitives/pick_trash.py

A vision-based manipulation primitive that uses natural language descriptions to identify and pick up trash items. Combines visual navigation with manipulation policies.

Key Implementation Points:

  • Uses in-sight navigation with description matching

  • Maintains safe distance (0.5m) during approach

  • Executes pre-trained picking policy

  • Includes safe gripper release in interruption

3. Alert User Primitive

File Location: ~/primitives/alert_user.py

A digital integration primitive that combines physical gestures with email notifications for security alerts. Shows basic integration of robot actions with external services.

Key Implementation Points:

  • Coordinates physical gesture with email sending

  • Uses SMTP for reliable email delivery

  • Includes safe arm positioning in interruption

  • Provides detailed alert messages with descriptions

4. Gas Sensor Integration Primitive

File: ~/primitives/monitor_air.py

A sensor integration primitive that combines gas sensing with robot navigation to monitor air quality in different locations. Shows hardware sensor integration with robot behaviors.

Key Implementation Points:

  • Integrates SGP30 sensor readings with navigation

  • Implements professional air quality thresholds

  • Sends alerts when thresholds are exceeded

  • Includes proper sensor initialization and timing

from innate.primitive import Primitive
from typing import Tuple

class MyPrimitive(Primitive):
    def __init__(self):
        """
        Initialize the primitive
        """
        super().__init__()# Required: initialize parent class

    def guidelines(self) -> str:
        """
        Define usage guidelines for when this primitive should be used
        Returns: string describing use cases and restrictions
        """
        return """
        Use this primitive when:
        - [Describe when to use this primitive]
        - [List relevant conditions]

        Do not use when:
        - [Describe when not to use this primitive]
        - [List restrictions or limitations]
        """

    def execute(self) -> Tuple[str, bool]:
        """
        Main execution logic
        Returns: (feedback string, success boolean)
        """
        try:
# Implement your primitive's logic here
            return "Task completed successfully", True
        except:
            return "Task failed", False

    def interrupt(self):
        """
        Define how to safely stop execution
        """
# Implement safe stopping behavior here

from innate.primitive import Primitive
from innate import navigation, manipulation
import numpy as np
from typing import Tuple

class GoHome(Primitive):
    def __init__(self):
        super().__init__()
        navigation.init()
        manipulation.init()

    def guidelines(self) -> str:
        return """
        Use this primitive when:
        - Robot needs to return to its home position
        - Robot needs to reset its configuration
        - Starting a new set of tasks

        Do not use when:
        - Robot is carrying objects
        - Path to home is blocked
        """

    def execute(self) -> Tuple[str, bool]:
        try:
# First move arm to safe position
            home_joints = np.zeros(6)# All joints to zero position
            manipulation.set_joint_pose(home_joints)

# Then navigate to home position
            navigation.go_to_pose(0, 0, 0)

            return "Successfully returned to home position", True
        except Exception as e:
            return f"Failed to return home: {str(e)}", False

    def interrupt(self):
# Stop all movement
        navigation.interrupt()
        manipulation.interrupt()

from innate.primitive import Primitive
from innate import navigation, manipulation
import time
from typing import Tuple

class PickTrash(Primitive):
    def __init__(self):
        super().__init__()
# Initialize both systems
        navigation.init()
        manipulation.init()

    def guidelines(self) -> str:
        return """
        Use this primitive when:
        - You need to pick up trash or debris
        - The trash item is visible to the robot
        - The item is within the robot's manipulation range

        Do not use when:
        - The trash is too heavy (>200g)
        - The trash is hazardous material
        - Multiple items need to be picked at once
        - The item is not clearly visible
        """

    def execute(self, description: str) -> Tuple[str, bool]:
        try:
# Navigate to be near the described trash
            navigation.go_to_in_sight(description, distance=0.2)

# Move arm and wait
            manipulation.set_ee_pose({"x": 0.3, "y": 0.0, "z": 0.1})
            time.sleep(1)

# Run the grasping policy
            manipulation.run_policy("pick_item")

            return f"Successfully picked up {description}", True
        except Exception as e:
            return f"Failed to pick up {description}: {str(e)}", False

    def interrupt(self):
# Stop all movement
        navigation.interrupt()
        manipulation.interrupt()

# Release gripper if engaged
        manipulation.set_gripper_pressure(0.0)

from innate.primitive import Primitive
from innate import manipulation
import smtplib
from email.message import EmailMessage
import time
from typing import Tuple

class AlertUser(Primitive):
    def __init__(self):
        super().__init__()
        manipulation.init()
# Email configuration
        self.smtp_server = "smtp.gmail.com"
        self.smtp_port = 587
        self.sender_email = "robot@innate.bot"
        self.sender_password = "your-app-password"# Use app-specific password

    def guidelines(self) -> str:
        return """
        Use this primitive when:
        - Suspicious activity is detected
        - Unauthorized person is present
        - Unusual behavior is observed
        - Security concerns arise

        Do not use when:
        - Regular employees are present
        - Scheduled maintenance is occurring
        - Known visitors are in the space
        """

    def execute(self, description: str) -> Tuple[str, bool]:
        try:
# Raise hand high
            manipulation.set_ee_pose({"x": 0.0, "y": 0.0, "z": 0.5})
            time.sleep(1)

# Send email alert
            msg = EmailMessage()
            msg.set_content(f"Suspicious activity detected: {description}")
            msg['Subject'] = 'Security Alert: Suspicious Activity'
            msg['From'] = self.sender_email
            msg['To'] = "user@innate.bot"

            with smtplib.SMTP(self.smtp_server, self.smtp_port) as server:
                server.starttls()
                server.login(self.sender_email, self.sender_password)
                server.send_message(msg)

            return f"Alert sent for: {description}", True
        except Exception as e:
            return f"Failed to send alert: {str(e)}", False

    def interrupt(self):
        manipulation.interrupt()
        manipulation.set_ee_pose({"x": 0.0, "y": 0.0, "z": 0.0})

from innate.primitive import Primitive
from innate import navigation
import board
import adafruit_sgp30
import smtplib
from email.message import EmailMessage
from typing import Tuple

class MonitorAir(Primitive):
    def __init__(self):
        super().__init__()
        navigation.init()
# Initialize sensor
        i2c = board.I2C()
        self.gas_sensor = adafruit_sgp30.Adafruit_SGP30(i2c)
# Thresholds
        self.thresholds = {
            'eco2': 1000,# ppm
            'tvoc': 250# ppb
        }
# Email setup
        self.smtp_server = "smtp.gmail.com"
        self.smtp_port = 587
        self.sender_email = "robot@innate.bot"
        self.sender_password = "your-app-password"

    def guidelines(self) -> str:
        return """
        Use this primitive when:
        - Air quality checks are needed
        - Monitoring enclosed spaces
        - Regular safety checks

        Do not use when:
        - Known hazardous conditions exist
        - Sensor needs calibration
        """

    def execute(self, location: str) -> Tuple[str, bool]:
        try:
# Navigate to location
            navigation.go_to_in_sight(location, distance=1.0)

# Get readings
            readings = {
                'eco2': self.gas_sensor.eCO2,
                'tvoc': self.gas_sensor.TVOC
            }

# Check thresholds
            if (readings['eco2'] > self.thresholds['eco2'] or
                readings['tvoc'] > self.thresholds['tvoc']):
# Send alert
                msg = EmailMessage()
                msg.set_content(
                    f"High gas levels at {location}\n"
                    f"eCO2: {readings['eco2']} ppm\n"
                    f"TVOC: {readings['tvoc']} ppb"
                )
                msg['Subject'] = 'Air Quality Alert'
                msg['From'] = self.sender_email
                msg['To'] = "user@innate.bot"

                with smtplib.SMTP(self.smtp_server, self.smtp_port) as server:
                    server.starttls()
                    server.login(self.sender_email, self.sender_password)
                    server.send_message(msg)

                return "High gas levels detected - alert sent", False

            return "Air quality normal", True

        except Exception as e:
            return f"Monitoring failed: {str(e)}", False

    def interrupt(self):
        navigation.interrupt()
In training mode, you collect data