Stream Motion is Fanuc's real-time streaming motion control interface, available as the **J519 paid option** on compatible controllers. It enables external applications to send continuous motion commands to the robot at high frequency (125Hz or 250Hz), making it ideal for applications requiring smooth, coordinated motion such as:

- Force control and adaptive machining
- Sensor-guided motion
- Real-time trajectory generation
- Human-robot collaboration
- Custom motion planning algorithms

> **Important**: Stream Motion is a position-based interface only. It does not provide direct motor velocity or torque control.

## Requirements

### Controller Requirements

- Fanuc controller with **J519 Stream Motion option** installed
- Compatible robot model (not all models support Stream Motion - verify with Fanuc)
- Network connectivity (UDP port 60015 by default)

### Key Limitations

| Feature         | Limitation                                     |
| --------------- | ---------------------------------------------- |
| Motion groups   | Group 1 (robot) + up to 3 auxiliary axes       |
| Coordinate mode | Joint OR Cartesian (cannot switch mid-session) |
| Update rate     | 125Hz or 250Hz (controller dependent)          |
| Control type    | Position only (no velocity/torque control)     |

---

## Quick Start

**C# : StreamMotionQuickStart**
```csharp
using UnderAutomation.Fanuc;
using UnderAutomation.Fanuc.StreamMotion;
using UnderAutomation.Fanuc.StreamMotion.Data;

public class StreamMotionQuickStart
{
    static void Main()
    {
        /**/
        var robot = new FanucRobot();
        var parameters = new ConnectionParameters("192.168.1.1");
        parameters.StreamMotion.Enable = true;
        robot.Connect(parameters);

        float nextJ1 = 10, nextJ2 = 20, nextJ3 = 30, nextJ4 = 0, nextJ5 = -45, nextJ6 = 90;

        robot.StreamMotion.StateReceived += (sender, e) =>
        {
            var state = e.State;
            Console.WriteLine($"Position: J1={state.JointPosition.J1:F2}°");

            if (state.Status.ReadyForCommands &&
                !state.Status.InMotion &&
                !state.Status.CommandReceived)
            {
                var command = new CommandPacket
                {
                    DataStyle = DataStyle.Joint,
                    MotionData = new MotionData
                    {
                        J1 = nextJ1,
                        J2 = nextJ2,
                        J3 = nextJ3,
                        J4 = nextJ4,
                        J5 = nextJ5,
                        J6 = nextJ6
                    }
                };
                robot.StreamMotion.SendCommand(command);
            }
        };

        robot.StreamMotion.Start();
        // ... your application logic ...
        robot.StreamMotion.Stop();
        /**/
    }
}
```

**Python : StreamMotionQuickStart**
```python
from underautomation.fanuc.fanuc_robot import FanucRobot

##
robot = FanucRobot()
robot.stream_motion.connect("192.168.1.1")

def on_state_received(state):
    print(f"Position: J1={state.joint_position.j1:.2f}°")

    if (state.status.ready_for_commands and
        not state.status.in_motion and
        not state.status.command_received):
        robot.stream_motion.send_joint_command(
            j1=next_j1, j2=next_j2, j3=next_j3,
            j4=next_j4, j5=next_j5, j6=next_j6
        )

robot.stream_motion.state_received += on_state_received
robot.stream_motion.start()
# ... your application logic ...
robot.stream_motion.stop()
##
```

---

## Architecture Overview

Stream Motion uses a **UDP-based protocol** with the following communication pattern:

```
┌─────────────────┐                      ┌─────────────────┐
│   Your App      │                      │   Fanuc Robot   │
│                 │                      │                 │
│  ┌───────────┐  │     Start Packet     │  ┌───────────┐  │
│  │  Stream   │──┼────────────────────> |  │  Stream   │  │
│  │  Motion   │  │                      │  │  Motion   │  │
│  │  Client   │<─┼──────────────────────┼──│  Server   │  │
│  └───────────┘  │   State Packets      │  └───────────┘  │
│       │         │   (~500Hz)           │       ▲         │
│       │         │                      │       │         │
│       └─────────┼──────────────────────┼───────┘         │
│                 │   Command Packets    │                 │
│                 │   (125/250Hz)        │                 │
└─────────────────┘                      └─────────────────┘
```

### Communication Flow

1. **Connect** - Establish UDP socket connection
2. **Start** - Send start packet to begin streaming session
3. **Receive States** - Robot sends state packets at ~500Hz
4. **Send Commands** - Your app sends interpolated positions at 125/250Hz
5. **Stop** - Send stop packet to end session
6. **Disconnect** - Close socket

---

## Robot Program Setup

A TP (Teach Pendant) program **must be running** on the robot to handle Stream Motion commands.

### Required TP Program

Create a program named for instance `START_STREAM_MOTION_J519.ls` with this content:

```plaintext
/PROG  START_STREAM_MOTION_J519
/ATTR
OWNER		= MNEDITOR;
PROTECT		= READ_WRITE;
TCD:  STACK_SIZE	= 0,
      TASK_PRIORITY	= 50,
      TIME_SLICE	= 0,
      BUSY_LAMP_OFF	= 0,
      ABORT_REQUEST	= 0,
      PAUSE_REQUEST	= 0;
DEFAULT_GROUP	= 1,*,*,*,*;
CONTROL_CODE	= 00000000 00000000;
/MN
   1:  LBL[1] ;
   2:  IBGN start[1] ;
   3:  IBGN end[1] ;
   4:  JMP LBL[1] ;
/POS
/END
```

### Program Explanation

| Line | Instruction     | Description                      |
| ---- | --------------- | -------------------------------- |
| 1    | `LBL[1]`        | Loop label                       |
| 2    | `IBGN start[1]` | Start Stream Motion session      |
| 3    | `IBGN end[1]`   | End Stream Motion session        |
| 4    | `JMP LBL[1]`    | Loop back to handle next session |

### Running the Program

1. Upload the program to the robot via FTP or teach pendant
2. Start the program in **AUTO** mode
3. The program loops, waiting for Stream Motion connections
4. Your application connects and controls motion via UDP
5. When streaming stops, the program loops back to wait for the next connection

### Adding Custom Logic

You can add instructions **outside** the Stream Motion block:

```plaintext
/MN
   1:  LBL[1] ;
   2:  DO[1]=ON ;              // Turn on output before streaming
   3:  IBGN start[1] ;
   4:  IBGN end[1] ;
   5:  DO[1]=OFF ;             // Turn off output after streaming
   6:  WAIT 1.00(sec) ;        // Wait before next session
   7:  JMP LBL[1] ;
/POS
/END
```

---

## Connection and Streaming

### Connecting to the Robot

**C# : StreamMotionConnect**
```csharp
using UnderAutomation.Fanuc;
using UnderAutomation.Fanuc.StreamMotion;

public class StreamMotionConnect
{
    static void Main()
    {
        /**/
        var robot = new FanucRobot();

        // Connect with default settings (port 60015)
        var parameters = new ConnectionParameters("192.168.1.1");
        parameters.StreamMotion.Enable = true;
        robot.Connect(parameters);

        // Standalone client
        var client = new StreamMotionClient();
        client.Connect("192.168.1.1");

        // Start / stop streaming
        robot.StreamMotion.Start();
        Console.WriteLine($"Streaming: {robot.StreamMotion.IsStreaming}");
        Console.WriteLine($"Frequency: {robot.StreamMotion.MeasuredFrequency:F1} Hz");
        robot.StreamMotion.Stop();
        robot.StreamMotion.Disconnect();
        /**/
    }
}
```

**Python : StreamMotionConnect**
```python
from underautomation.fanuc.fanuc_robot import FanucRobot

##
robot = FanucRobot()

# Connect with default settings (port 60015)
robot.stream_motion.connect("192.168.1.1")

# Or with custom settings
robot.stream_motion.connect(
    ip="192.168.1.1",
    port=60015,
    send_timeout_ms=1000,
    receive_timeout_ms=1000
)

# Start / stop streaming
robot.stream_motion.start()
print(f"Streaming: {robot.stream_motion.is_streaming}")
print(f"Frequency: {robot.stream_motion.measured_frequency:.1f} Hz")
robot.stream_motion.stop()
robot.stream_motion.disconnect()
##
```

### Standalone Client

You can also use the `StreamMotionClient` class directly without `FanucRobot`.

### Starting and Stopping Streaming

---

## State Packets

The robot sends **state packets** continuously at approximately 500Hz. Each packet contains the robot's current state.

### StatePacket Properties

| Property            | Type           | Description                                      |
| ------------------- | -------------- | ------------------------------------------------ |
| `SequenceNumber`    | `uint`         | Packet sequence for synchronization              |
| `TimeStamp`         | `uint`         | Robot timestamp (ms, 2ms resolution)             |
| `Status`            | `RobotStatus`  | Status flags                                     |
| `JointPosition`     | `MotionData`   | Current joint positions (J1-J9)                  |
| `CartesianPosition` | `MotionData`   | Current Cartesian position (X,Y,Z,W,P,R + E1-E3) |
| `MotorCurrents`     | `MotionData`   | Motor currents in Amperes                        |
| `IORead`            | `IOReadResult` | Result of I/O read operation                     |

### RobotStatus Flags

| Flag               | Description                                    |
| ------------------ | ---------------------------------------------- |
| `ReadyForCommands` | Robot is ready to receive command packets      |
| `CommandReceived`  | Robot has received and is processing a command |
| `SystemReady`      | System ready (SYSRDY)                          |
| `InMotion`         | Robot is currently executing motion            |

### Handling State Packets

**C# : StreamMotionState**
```csharp
using UnderAutomation.Fanuc;
using UnderAutomation.Fanuc.StreamMotion;
using UnderAutomation.Fanuc.StreamMotion.Data;

public class StreamMotionState
{
    static void Main()
    {
        var robot = new FanucRobot();
        var parameters = new ConnectionParameters("192.168.1.1");
        parameters.StreamMotion.Enable = true;
        robot.Connect(parameters);

        /**/
        robot.StreamMotion.StateReceived += (sender, e) =>
        {
            var state = e.State;

            // Read current joint positions
            Console.WriteLine($"J1: {state.JointPosition.J1:F3}°");
            Console.WriteLine($"J2: {state.JointPosition.J2:F3}°");

            // Read Cartesian position
            Console.WriteLine($"X: {state.CartesianPosition.X:F2} mm");
            Console.WriteLine($"Y: {state.CartesianPosition.Y:F2} mm");

            // Check status
            Console.WriteLine($"Ready: {state.Status.ReadyForCommands}");
            Console.WriteLine($"In Motion: {state.Status.InMotion}");

            // Read motor currents
            Console.WriteLine($"Motor 1: {state.MotorCurrents.Axis1:F2} A");

            // Only send command when ready
            bool canSend = state.Status.ReadyForCommands &&
                           !state.Status.CommandReceived &&
                           !state.Status.InMotion;
        };
        /**/
    }
}
```

**Python : StreamMotionState**
```python
from underautomation.fanuc.fanuc_robot import FanucRobot

robot = FanucRobot()
robot.stream_motion.connect("192.168.1.1")

##
def on_state_received(state):
    # Read current joint positions
    print(f"J1: {state.joint_position.j1:.3f}°")
    print(f"J2: {state.joint_position.j2:.3f}°")

    # Read Cartesian position
    print(f"X: {state.cartesian_position.x:.2f} mm")
    print(f"Y: {state.cartesian_position.y:.2f} mm")

    # Check status
    print(f"Ready: {state.status.ready_for_commands}")
    print(f"In Motion: {state.status.in_motion}")

    # Read motor currents
    print(f"Motor 1: {state.motor_currents.axis1:.2f} A")

    # Only send command when ready
    can_send = (state.status.ready_for_commands and
                not state.status.command_received and
                not state.status.in_motion)

robot.stream_motion.state_received += on_state_received
##
```

---

## Command Packets

Command packets are sent to instruct the robot to move to a new position.

### CommandPacket Properties

| Property       | Type         | Description                  |
| -------------- | ------------ | ---------------------------- |
| `DataStyle`    | `DataStyle`  | `Joint` or `Cartesian`       |
| `MotionData`   | `MotionData` | Target position              |
| `IsLastData`   | `bool`       | Marks end of motion sequence |
| `ReadIOType`   | `IOType`     | Type of I/O to read          |
| `ReadIOIndex`  | `ushort`     | I/O index to read            |
| `ReadIOMask`   | `ushort`     | I/O read mask                |
| `WriteIOType`  | `IOType`     | Type of I/O to write         |
| `WriteIOIndex` | `ushort`     | I/O index to write           |
| `WriteIOMask`  | `ushort`     | I/O write mask               |
| `WriteIOValue` | `ushort`     | Value to write               |

### MotionData Structure

`MotionData` holds 9 axis values that can be accessed by axis number or name.

### Sending Commands

**C# : StreamMotionCommands**
```csharp
using UnderAutomation.Fanuc;
using UnderAutomation.Fanuc.StreamMotion;
using UnderAutomation.Fanuc.StreamMotion.Data;

public class StreamMotionCommands
{
    static void Main()
    {
        var robot = new FanucRobot();
        var parameters = new ConnectionParameters("192.168.1.1");
        parameters.StreamMotion.Enable = true;
        robot.Connect(parameters);

        /**/
        // MotionData: 9 axis values
        var motion = new MotionData();
        motion.J1 = 10.0f; motion.J2 = 20.0f; motion.J3 = 30.0f;
        motion.J4 = 0.0f; motion.J5 = -45.0f; motion.J6 = 90.0f;
        // Cartesian: motion.X, motion.Y, motion.Z, motion.W, motion.P, motion.R

        // Full command packet
        var command = new CommandPacket
        {
            DataStyle = DataStyle.Joint,
            MotionData = new MotionData { J1 = 10, J2 = 20, J3 = 30, J4 = 0, J5 = -45, J6 = 90 },
            IsLastData = false
        };
        robot.StreamMotion.SendCommand(command);

        // Convenience: joint motion
        robot.StreamMotion.SendJointCommand(
            new MotionData { J1 = 10, J2 = 20, J3 = 30, J4 = 0, J5 = -45, J6 = 90 },
            isLastData: false
        );

        // Convenience: Cartesian motion
        robot.StreamMotion.SendCartesianCommand(
            new MotionData { X = 500, Y = 100, Z = 300, W = 180, P = 0, R = 0 },
            isLastData: false
        );
        /**/
    }
}
```

**Python : StreamMotionCommands**
```python
from underautomation.fanuc.fanuc_robot import FanucRobot

robot = FanucRobot()
robot.stream_motion.connect("192.168.1.1")

##
# Joint command
robot.stream_motion.send_joint_command(
    j1=10, j2=20, j3=30, j4=0, j5=-45, j6=90,
    is_last_data=False
)

# Cartesian command
robot.stream_motion.send_cartesian_command(
    x=500, y=100, z=300, w=180, p=0, r=0,
    is_last_data=False
)
##
```

---

## Protocol State Machine

The Stream Motion protocol follows a strict state machine. Understanding this is **critical** for proper operation.

### State Transitions

```
                    ┌─────────────────────────────────────┐
                    │                                     │
                    ▼                                     │
    ┌───────────────────────────┐                         │
    │     READY FOR COMMANDS    │                         │
    │  ReadyForCommands = true  │                         │
    │  CommandReceived = false  │                         │
    │  InMotion = false         │                         │
    └───────────────────────────┘                         │
                    │                                     │
                    │ Send Command                        │
                    ▼                                     │
    ┌───────────────────────────┐                         │
    │    COMMAND RECEIVED       │                         │
    │  CommandReceived = true   │                         │
    └───────────────────────────┘                         │
                    │                                     │
                    │ Robot starts motion                 │
                    ▼                                     │
    ┌───────────────────────────┐                         │
    │       IN MOTION           │                         │
    │  InMotion = true          │────────────────────────>┤
    │  (may receive multiple    │   Motion complete       │
    │   packets in this state)  │                         │
    └───────────────────────────┘                         │
```

### Correct Command Timing

**Only send the next command when ALL these conditions are met:**

See the state packet handling above : check `ReadyForCommands`, `CommandReceived`, and `InMotion` flags before sending.

### ⚠️ Common Mistakes

| Mistake                         | Consequence                      |
| ------------------------------- | -------------------------------- |
| Sending commands too fast       | Commands dropped, erratic motion |
| Ignoring `CommandReceived`      | Duplicate commands sent          |
| Ignoring `InMotion`             | Buffer overflow, jerk errors     |
| Not checking `ReadyForCommands` | Commands rejected                |

---

## Motion Interpolation

> **⚠️ CRITICAL**: Stream Motion does NOT interpolate motion. You MUST implement your own interpolator. Sending a target position directly will cause **jerk limit exceeded** errors and potentially damage the robot.

### Why Interpolation is Required

Stream Motion operates at 125Hz or 250Hz. Each command should represent a small incremental move. The robot's motion planner expects smooth, continuous position updates that respect:

- **Velocity limits** - Maximum speed for each axis
- **Acceleration limits** - Maximum rate of velocity change
- **Jerk limits** - Maximum rate of acceleration change (smoothness)

### Simple Linear Interpolation (Educational Example)

**C# : StreamMotionInterpolation**
```csharp
using System;
using UnderAutomation.Fanuc.StreamMotion.Data;

public class StreamMotionInterpolation
{
    /**/
    // Simple linear interpolator (educational)
    public static float[] LinearInterpolate(float[] start, float[] target, double t)
    {
        float[] pos = new float[6];
        for (int i = 0; i < 6; i++)
            pos[i] = start[i] + (float)(t * (target[i] - start[i]));
        return pos;
    }

    // S-curve interpolation (recommended for smooth motion)
    public static float[] SCurveInterpolate(float[] start, float[] target, double t)
    {
        // S-curve: s(t) = 6t^5 - 15t^4 + 10t^3
        double s = t * t * t * (t * (t * 6 - 15) + 10);
        float[] pos = new float[6];
        for (int i = 0; i < 6; i++)
            pos[i] = start[i] + (float)(s * (target[i] - start[i]));
        return pos;
    }
    /**/
}
```

**Python : StreamMotionInterpolation**
```python
import math

##
# Simple linear interpolation (educational)
def linear_interpolate(start, target, t):
    return [s + t * (tg - s) for s, tg in zip(start, target)]

# S-curve interpolation (recommended for smooth motion)
def s_curve_interpolate(start, target, t):
    # S-curve: s(t) = 6t^5 - 15t^4 + 10t^3
    s = t * t * t * (t * (t * 6 - 15) + 10)
    return [st + s * (tg - st) for st, tg in zip(start, target)]
##
```

### S-Curve Interpolation (Recommended)

For smooth, jerk-limited motion, use an **S-curve** or **quintic polynomial** profile. The snippet above shows both linear and S-curve approaches.

### Integration with Stream Motion

Combine the interpolator with the Stream Motion state machine to send smoothly interpolated positions at each cycle.

---

## Querying Axis Limits

Before commanding motion, you can query the robot's axis limits. **This must be done before starting streaming.**

**C# : StreamMotionLimits**
```csharp
using UnderAutomation.Fanuc;
using UnderAutomation.Fanuc.StreamMotion;
using UnderAutomation.Fanuc.StreamMotion.Data;

public class StreamMotionLimits
{
    static void Main()
    {
        var robot = new FanucRobot();
        var parameters = new ConnectionParameters("192.168.1.1");
        parameters.StreamMotion.Enable = true;
        robot.Connect(parameters);

        /**/
        // Query velocity limits for axis 1
        var velocityAck = robot.StreamMotion.RequestThreshold(1, ThresholdType.Velocity);
        Console.WriteLine($"Axis 1 max velocity: {velocityAck.ThresholdsNoLoad[19]:F2} deg/s");

        // Query acceleration limits
        var accelAck = robot.StreamMotion.RequestThreshold(1, ThresholdType.Acceleration);

        // Query jerk limits
        var jerkAck = robot.StreamMotion.RequestThreshold(1, ThresholdType.Jerk);

        // Query all axes
        for (uint axis = 1; axis <= 6; axis++)
        {
            var ack = robot.StreamMotion.RequestThreshold(axis, ThresholdType.Velocity);
            Console.WriteLine($"J{axis} max velocity: {ack.ThresholdsNoLoad[19]:F2} deg/s");
        }
        /**/
    }
}
```

**Python : StreamMotionLimits**
```python
from underautomation.fanuc.fanuc_robot import FanucRobot

robot = FanucRobot()
robot.stream_motion.connect("192.168.1.1")

##
# Query velocity limits for axis 1
velocity_ack = robot.stream_motion.request_threshold(1, "Velocity")
print(f"Axis 1 max velocity: {velocity_ack.thresholds_no_load[19]:.2f} deg/s")

# Query acceleration and jerk limits
accel_ack = robot.stream_motion.request_threshold(1, "Acceleration")
jerk_ack = robot.stream_motion.request_threshold(1, "Jerk")

# Query all axes
for axis in range(1, 7):
    ack = robot.stream_motion.request_threshold(axis, "Velocity")
    print(f"J{axis} max velocity: {ack.thresholds_no_load[19]:.2f} deg/s")
##
```

### AckPacket Threshold Arrays

The threshold arrays contain 20 values at different velocity percentages:

| Index | Velocity % |
| ----- | ---------- |
| 0     | 5%         |
| 1     | 10%        |
| ...   | ...        |
| 19    | 100%       |

Use `ThresholdsNoLoad` for unloaded robot and `ThresholdsMaxLoad` for maximum payload.

---

## I/O Operations

Stream Motion supports reading and writing digital I/O during motion.

### Reading I/O

**C# : StreamMotionIo**
```csharp
using UnderAutomation.Fanuc;
using UnderAutomation.Fanuc.StreamMotion;
using UnderAutomation.Fanuc.StreamMotion.Data;

public class StreamMotionIo
{
    static void Main()
    {
        var robot = new FanucRobot();
        var parameters = new ConnectionParameters("192.168.1.1");
        parameters.StreamMotion.Enable = true;
        robot.Connect(parameters);

        /**/
        var motionData = new MotionData { J1 = 10, J2 = 20, J3 = 30, J4 = 0, J5 = -45, J6 = 90 };

        // Read I/O during motion
        var readCommand = new CommandPacket
        {
            DataStyle = DataStyle.Joint,
            MotionData = motionData,
            ReadIOType = IOType.DI,
            ReadIOIndex = 1,
            ReadIOMask = 0xFFFF
        };
        robot.StreamMotion.SendCommand(readCommand);

        // Write I/O during motion
        var writeCommand = new CommandPacket
        {
            DataStyle = DataStyle.Joint,
            MotionData = motionData,
            WriteIOType = IOType.DO,
            WriteIOIndex = 1,
            WriteIOMask = 0x0001,
            WriteIOValue = 0x0001
        };
        robot.StreamMotion.SendCommand(writeCommand);
        /**/
    }
}
```

**Python : StreamMotionIo**
```python
from underautomation.fanuc.fanuc_robot import FanucRobot

robot = FanucRobot()
robot.stream_motion.connect("192.168.1.1")

##
# Read I/O during motion
robot.stream_motion.send_command(
    motion_data=motion_data,
    read_io_type="DigitalInput",
    read_io_index=1,
    read_io_mask=0xFFFF
)

# Write I/O during motion
robot.stream_motion.send_command(
    motion_data=motion_data,
    write_io_type="DigitalOutput",
    write_io_index=1,
    write_io_mask=0x0001,
    write_io_value=0x0001
)
##
```

### Writing I/O

---

## Best Practices

**C# : StreamMotionBestPractices**
```csharp
using UnderAutomation.Fanuc;
using UnderAutomation.Fanuc.StreamMotion;
using UnderAutomation.Fanuc.StreamMotion.Data;

public class StreamMotionBestPractices
{
    static void Main()
    {
        var robot = new FanucRobot();
        var parameters = new ConnectionParameters("192.168.1.1");
        parameters.StreamMotion.Enable = true;
        robot.Connect(parameters);

        /**/
        var command = new CommandPacket
        {
            DataStyle = DataStyle.Joint,
            MotionData = new MotionData { J1 = 10, J2 = 20, J3 = 30, J4 = 0, J5 = -45, J6 = 90 }
        };

        // Always check state before sending
        robot.StreamMotion.StateReceived += (s, e) =>
        {
            if (e.State.Status.ReadyForCommands &&
                !e.State.Status.CommandReceived &&
                !e.State.Status.InMotion)
            {
                robot.StreamMotion.SendCommand(command);
            }
        };

        // Handle errors
        robot.StreamMotion.ReceiveError += (s, e) =>
        {
            Console.WriteLine($"Error: {e.Exception.Message}");
        };

        // Query limits before motion
        for (uint i = 1; i <= 6; i++)
        {
            var ack = robot.StreamMotion.RequestThreshold(i, ThresholdType.Velocity);
        }

        // Clean shutdown
        try { robot.StreamMotion.Start(); }
        finally
        {
            if (robot.StreamMotion.IsStreaming) robot.StreamMotion.Stop();
        }
        /**/
    }
}
```

**Python : StreamMotionBestPractices**
```python
from underautomation.fanuc.fanuc_robot import FanucRobot

robot = FanucRobot()
robot.stream_motion.connect("192.168.1.1")

##
# Always check state before sending
def on_state(state):
    if (state.status.ready_for_commands and
        not state.status.command_received and
        not state.status.in_motion):
        robot.stream_motion.send_command(command)

robot.stream_motion.state_received += on_state

# Handle errors
def on_error(error):
    print(f"Error: {error}")

robot.stream_motion.receive_error += on_error

# Query limits before motion
for axis in range(1, 7):
    ack = robot.stream_motion.request_threshold(axis, "Velocity")

# Clean shutdown
try:
    robot.stream_motion.start()
finally:
    if robot.stream_motion.is_streaming:
        robot.stream_motion.stop()
##
```

### 1. Always Implement Proper Interpolation

### 2. Respect the Protocol State Machine

### 3. Handle Errors Gracefully

### 4. Query Limits Before Motion

### 5. Clean Shutdown

---

## Troubleshooting

### Common Errors

| Error                          | Cause                  | Solution                               |
| ------------------------------ | ---------------------- | -------------------------------------- |
| "Joint jerk limit exceeded"    | Motion too fast/abrupt | Implement proper S-curve interpolation |
| "Robot not ready for commands" | Wrong state            | Wait for `ReadyForCommands=true`       |
| Connection timeout             | Network issue          | Check IP, port, firewall               |
| "Streaming already started"    | Double start           | Check `IsStreaming` before `Start()`   |
| Commands not executed          | Sent too fast          | Follow protocol state machine          |

### Diagnostic Logging

**C# : StreamMotionDiagnostics**
```csharp
using UnderAutomation.Fanuc;
using UnderAutomation.Fanuc.StreamMotion;

public class StreamMotionDiagnostics
{
    static void Main()
    {
        var robot = new FanucRobot();
        var parameters = new ConnectionParameters("192.168.1.1");
        parameters.StreamMotion.Enable = true;
        robot.Connect(parameters);
        robot.StreamMotion.Start();

        /**/
        robot.StreamMotion.StateReceived += (s, e) =>
        {
            var state = e.State;
            Console.WriteLine($"[{DateTime.Now:HH:mm:ss.fff}] " +
                $"Seq={state.SequenceNumber} " +
                $"Ready={state.Status.ReadyForCommands} " +
                $"CmdRcv={state.Status.CommandReceived} " +
                $"Motion={state.Status.InMotion} " +
                $"J1={state.JointPosition.J1:F3}");
        };

        Console.WriteLine($"Robot frequency: {robot.StreamMotion.RobotFrequency:F1} Hz");
        Console.WriteLine($"Measured frequency: {robot.StreamMotion.MeasuredFrequency:F1} Hz");
        Console.WriteLine($"Packets received: {robot.StreamMotion.PacketCount}");
        /**/
    }
}
```

**Python : StreamMotionDiagnostics**
```python
from underautomation.fanuc.fanuc_robot import FanucRobot
from datetime import datetime

robot = FanucRobot()
robot.stream_motion.connect("192.168.1.1")
robot.stream_motion.start()

##
def on_state(state):
    print(f"[{datetime.now():%H:%M:%S.%f}] "
          f"Seq={state.sequence_number} "
          f"Ready={state.status.ready_for_commands} "
          f"CmdRcv={state.status.command_received} "
          f"Motion={state.status.in_motion} "
          f"J1={state.joint_position.j1:.3f}")

robot.stream_motion.state_received += on_state

print(f"Robot frequency: {robot.stream_motion.robot_frequency:.1f} Hz")
print(f"Measured frequency: {robot.stream_motion.measured_frequency:.1f} Hz")
print(f"Packets received: {robot.stream_motion.packet_count}")
##
```

### Checking Frequencies

---

## Complete Example

Here's a complete example that moves the robot through a sequence of waypoints:

**C# : StreamMotionComplete**
```csharp
using System;
using System.Threading;
using UnderAutomation.Fanuc;
using UnderAutomation.Fanuc.StreamMotion;
using UnderAutomation.Fanuc.StreamMotion.Data;

/**/
class StreamMotionExample
{
    private FanucRobot _robot;
    private float[][] _waypoints;
    private int _currentWaypoint;
    private bool _waitingForAck;
    private readonly object _lock = new object();

    public void Run()
    {
        _robot = new FanucRobot();

        // Define waypoints (J1-J6 in degrees)
        _waypoints = new float[][]
        {
            new float[] { 0, 0, 0, 0, 0, 0 },
            new float[] { 30, 10, -20, 0, 45, 0 },
            new float[] { -30, 20, -40, 90, 0, 45 },
            new float[] { 0, 0, 0, 0, 0, 0 }
        };
        _currentWaypoint = 0;

        try
        {
            // Connect
            Console.WriteLine("Connecting to robot...");
            var parameters = new ConnectionParameters("192.168.1.1");
            parameters.StreamMotion.Enable = true;
            _robot.Connect(parameters);
            Console.WriteLine("Connected!");

            // Query axis limits
            for (uint axis = 1; axis <= 6; axis++)
            {
                var ack = _robot.StreamMotion.RequestThreshold(axis, ThresholdType.Velocity);
                Console.WriteLine($"  J{axis} max velocity: {ack.ThresholdsNoLoad[19]:F1} deg/s");
            }

            // Setup event handlers
            _robot.StreamMotion.StateReceived += OnStateReceived;
            _robot.StreamMotion.ReceiveError += OnReceiveError;

            // Start streaming
            _robot.StreamMotion.Start();

            // Wait for user to stop
            Console.WriteLine("Moving through waypoints... Press Enter to stop.");
            Console.ReadLine();
        }
        finally
        {
            // Clean shutdown
            if (_robot.StreamMotion.IsStreaming)
                _robot.StreamMotion.Stop();
            _robot.StreamMotion.Disconnect();
        }
    }

    private void OnStateReceived(object sender, StateReceivedEventArgs e)
    {
        lock (_lock)
        {
            var status = e.State.Status;

            if (_waitingForAck)
            {
                if (!status.InMotion && !status.CommandReceived)
                    _waitingForAck = false;
                else
                    return;
            }

            if (!status.ReadyForCommands || status.InMotion || status.CommandReceived)
                return;

            // Advance to next waypoint
            _currentWaypoint = (_currentWaypoint + 1) % _waypoints.Length;
            float[] target = _waypoints[_currentWaypoint];

            Console.WriteLine($"Moving to waypoint {_currentWaypoint}");

            var command = new CommandPacket
            {
                DataStyle = DataStyle.Joint,
                MotionData = new MotionData
                {
                    J1 = target[0],
                    J2 = target[1],
                    J3 = target[2],
                    J4 = target[3],
                    J5 = target[4],
                    J6 = target[5]
                },
                IsLastData = true
            };

            _robot.StreamMotion.SendCommand(command);
            _waitingForAck = true;
        }
    }

    private void OnReceiveError(object sender, ReceiveErrorEventArgs e)
    {
        Console.WriteLine($"Error: {e.Exception.Message}");
    }

    static void Main(string[] args)
    {
        new StreamMotionExample().Run();
    }
}
/**/
```

**Python : StreamMotionComplete**
```python
import underautomation.fanuc as fanuc
import time
import threading

##
robot = fanuc.FanucRobot()

# Define waypoints (J1-J6 in degrees)
waypoints = [
    [0, 0, 0, 0, 0, 0],
    [30, 10, -20, 0, 45, 0],
    [-30, 20, -40, 90, 0, 45],
    [0, 0, 0, 0, 0, 0],
]
current_waypoint = 0
waiting_for_ack = False
lock = threading.Lock()

def on_state_received(sender, e):
    global current_waypoint, waiting_for_ack
    with lock:
        status = e.State.Status

        if waiting_for_ack:
            if not status.InMotion and not status.CommandReceived:
                waiting_for_ack = False
            else:
                return

        if not status.ReadyForCommands or status.InMotion or status.CommandReceived:
            return

        # Advance to next waypoint
        current_waypoint = (current_waypoint + 1) % len(waypoints)
        target = waypoints[current_waypoint]

        print(f"Moving to waypoint {current_waypoint}")

        command = fanuc.CommandPacket()
        command.DataStyle = fanuc.DataStyle.Joint
        command.MotionData = fanuc.MotionData()
        command.MotionData.J1 = target[0]
        command.MotionData.J2 = target[1]
        command.MotionData.J3 = target[2]
        command.MotionData.J4 = target[3]
        command.MotionData.J5 = target[4]
        command.MotionData.J6 = target[5]
        command.IsLastData = True

        robot.StreamMotion.SendCommand(command)
        waiting_for_ack = True

def on_receive_error(sender, e):
    print(f"Error: {e.Exception.Message}")

try:
    # Connect
    print("Connecting to robot...")
    robot.StreamMotion.Connect("192.168.1.1")
    print("Connected!")

    # Query axis limits
    for axis in range(1, 7):
        ack = robot.StreamMotion.RequestThreshold(axis, fanuc.ThresholdType.Velocity)
        print(f"  J{axis} max velocity: {ack.ThresholdsNoLoad[19]:.1f} deg/s")

    # Setup event handlers
    robot.StreamMotion.StateReceived += on_state_received
    robot.StreamMotion.ReceiveError += on_receive_error

    # Start streaming
    robot.StreamMotion.Start()

    input("Moving through waypoints... Press Enter to stop.")
finally:
    # Clean shutdown
    if robot.StreamMotion.IsStreaming:
        robot.StreamMotion.Stop()
    robot.StreamMotion.Disconnect()
##
```

---

## Summary

Stream Motion (J519) provides powerful real-time robot control, but requires careful implementation:

1. ✅ **Install J519 option** on your controller
2. ✅ **Run the TP program** with `IBGN start[]` / `IBGN end[]`
3. ✅ **Implement smooth interpolation** - never jump to positions
4. ✅ **Follow the protocol state machine** - check status before sending
5. ✅ **Query and respect axis limits** - velocity, acceleration, jerk
6. ✅ **Handle errors gracefully** - implement reconnection logic