Plan Examples

Complete example Plans for common tasks

Plan Examples

Complete, ready-to-use example Plans demonstrating common robot behaviors and patterns.

Pro Tip

These examples are designed to be copied and modified. Start with one that's close to what you need, then adjust the constants and logic. The "Full template" at the bottom is a good starting point for any new Plan.


Quick reference

Example Difficulty Concepts
Basic Movement Beginner Motors, delay
Obstacle Avoidance Beginner Sensors, conditions
Line Following Beginner Line sensors, PID
Remote Control Intermediate Messages, communication
Patrol Pattern Intermediate States, timing
Battery Monitor Intermediate Events, triggers
Multi-Agent Search Advanced Coordination, messaging
Voice Commands Advanced Events, parsing

Basic movement

Simple motor control and movement patterns.

#!octomy-plan v1.0
# @name Basic Movement
# @version 1.0.0
# @permissions actuators.motors

plan BasicMovement {
    const SPEED = 60

    init {
        log("Starting basic movement demo")
    }

    loop {
        // Drive forward
        log("Forward")
        drive(SPEED, SPEED)
        delay(2000)

        // Turn right
        log("Turn right")
        drive(SPEED, -SPEED)
        delay(500)

        // Drive forward
        log("Forward")
        drive(SPEED, SPEED)
        delay(2000)

        // Turn left
        log("Turn left")
        drive(-SPEED, SPEED)
        delay(500)
    }

    cleanup {
        stop()
        log("Movement stopped")
    }
}

Obstacle avoidance

Autonomous navigation avoiding obstacles.

#!octomy-plan v1.0
# @name Obstacle Avoider
# @version 1.2.0
# @permissions sensors.distance, actuators.motors
# @description Autonomous obstacle avoidance using distance sensors

const SAFE_DISTANCE = 40    // cm
const DANGER_DISTANCE = 20  // cm
const NORMAL_SPEED = 60
const SLOW_SPEED = 30
const TURN_SPEED = 50

plan ObstacleAvoider {
    var state = "forward"
    var turn_direction = 1  // 1 = right, -1 = left

    init {
        log("Obstacle Avoider starting")
    }

    loop {
        var front = sensors.distance.front
        var left = sensors.distance.left
        var right = sensors.distance.right

        // Emergency stop if too close
        if front < DANGER_DISTANCE {
            state = "backup"
        } else if front < SAFE_DISTANCE {
            state = "avoid"
        } else {
            state = "forward"
        }

        match state {
            "forward" -> {
                drive(NORMAL_SPEED, NORMAL_SPEED)
            }
            "avoid" -> {
                // Choose turn direction based on side clearance
                if left > right {
                    turn_direction = -1  // Turn left
                } else {
                    turn_direction = 1   // Turn right
                }
                drive(SLOW_SPEED * turn_direction,
                      -SLOW_SPEED * turn_direction)
            }
            "backup" -> {
                // Backup and turn
                drive(-SLOW_SPEED, -SLOW_SPEED)
                delay(500)
                drive(TURN_SPEED * turn_direction,
                      -TURN_SPEED * turn_direction)
                delay(300)
            }
        }

        delay(50)
    }

    cleanup {
        stop()
    }
}

Line following

Follow a line using line sensors.

#!octomy-plan v1.0
# @name Line Follower
# @version 1.0.0
# @permissions sensors.line, actuators.motors

const BASE_SPEED = 50
const KP = 30  // Proportional gain
const KD = 10  // Derivative gain

plan LineFollower {
    var last_error = 0

    init {
        log("Line Follower starting")
        log("Place robot on line to begin")
    }

    loop {
        // Read line sensors (-1 to 1 position)
        var left = sensors.line.left ? 1 : 0
        var center = sensors.line.center ? 1 : 0
        var right = sensors.line.right ? 1 : 0

        // Calculate line position
        // -1 = line on left, 0 = centered, 1 = line on right
        var error = 0

        if center && !left && !right {
            error = 0  // Centered
        } else if left && !right {
            error = -1  // Line is to the left
        } else if right && !left {
            error = 1   // Line is to the right
        } else if !left && !center && !right {
            // Lost line - use last known direction
            error = last_error * 2
        }

        // PD controller
        var derivative = error - last_error
        var correction = KP * error + KD * derivative
        last_error = error

        // Apply to motors
        var left_speed = clamp(BASE_SPEED + correction, 0, 100)
        var right_speed = clamp(BASE_SPEED - correction, 0, 100)

        drive(left_speed, right_speed)

        delay(20)
    }

    cleanup {
        stop()
    }
}

Remote control

Receive commands from Remote controller.

#!octomy-plan v1.0
# @name Remote Control
# @version 1.0.0
# @permissions actuators.motors, comms.receive

const MAX_SPEED = 80

plan RemoteControl {
    var throttle = 0
    var steering = 0

    init {
        log("Remote Control active")
        log("Waiting for commands...")
    }

    // Handle joystick input from Remote
    on_message("joystick") { data ->
        throttle = data.y * MAX_SPEED  // -100 to 100
        steering = data.x * MAX_SPEED  // -100 to 100
    }

    // Handle button presses
    on_message("button") { data ->
        match data.button {
            "stop" -> {
                throttle = 0
                steering = 0
                stop()
            }
            "horn" -> {
                play_sound("horn")
            }
            "lights" -> {
                toggle_lights()
            }
        }
    }

    loop {
        // Convert throttle/steering to tank drive
        var left = clamp(throttle + steering, -MAX_SPEED, MAX_SPEED)
        var right = clamp(throttle - steering, -MAX_SPEED, MAX_SPEED)

        drive(left, right)
        delay(50)
    }

    cleanup {
        stop()
        log("Remote Control ended")
    }
}

Patrol pattern

Patrol a defined area with waypoints.

#!octomy-plan v1.0
# @name Patrol
# @version 1.5.0
# @permissions sensors.distance, actuators.motors, storage.read, storage.write

const WAYPOINT_THRESHOLD = 20  // cm - consider arrived
const PATROL_SPEED = 50

plan Patrol {
    var waypoints = [
        {x: 0, y: 0},
        {x: 100, y: 0},
        {x: 100, y: 100},
        {x: 0, y: 100}
    ]

    var current_waypoint = 0
    var patrol_count = 0
    var state = "patrol"

    init {
        // Load saved position if available
        patrol_count = storage.get("patrol_count") ?? 0
        log("Starting patrol #" + (patrol_count + 1))
    }

    loop {
        match state {
            "patrol" -> {
                var target = waypoints[current_waypoint]
                var dist = distance_to(target.x, target.y)

                if dist < WAYPOINT_THRESHOLD {
                    // Reached waypoint
                    log("Reached waypoint " + current_waypoint)
                    current_waypoint = (current_waypoint + 1) % length(waypoints)

                    // Complete patrol check
                    if current_waypoint == 0 {
                        patrol_count = patrol_count + 1
                        storage.set("patrol_count", patrol_count)
                        log("Patrol #" + patrol_count + " complete")
                    }

                    state = "pause"
                    delay(1000)  // Brief pause at waypoint
                } else {
                    // Navigate to waypoint
                    navigate_toward(target.x, target.y, PATROL_SPEED)
                }
            }

            "pause" -> {
                stop()
                // Check surroundings
                scan_area()
                state = "patrol"
            }

            "obstacle" -> {
                // Handle obstacle
                avoid_obstacle()
                state = "patrol"
            }
        }

        // Check for obstacles
        if sensors.distance.front < 30 {
            state = "obstacle"
        }

        delay(50)
    }

    cleanup {
        stop()
        log("Patrol ended after " + patrol_count + " complete patrols")
    }

    // Helper functions
    function navigate_toward(x, y, speed) {
        var angle = atan2(y - position.y, x - position.x)
        var heading_error = angle - position.heading

        // Normalize to -180..180
        while heading_error > 180 { heading_error -= 360 }
        while heading_error < -180 { heading_error += 360 }

        // Proportional steering
        var turn = heading_error * 0.5
        var left = clamp(speed + turn, -100, 100)
        var right = clamp(speed - turn, -100, 100)
        drive(left, right)
    }

    function distance_to(x, y) {
        var dx = x - position.x
        var dy = y - position.y
        return sqrt(dx*dx + dy*dy)
    }

    function scan_area() {
        // Pan camera or distance sensor
        for angle in -45..46 {
            set_servo("scan", 90 + angle)
            delay(20)
        }
        set_servo("scan", 90)  // Return to center
    }

    function avoid_obstacle() {
        // Backup
        drive(-30, -30)
        delay(500)

        // Turn away
        if sensors.distance.left > sensors.distance.right {
            drive(-50, 50)
        } else {
            drive(50, -50)
        }
        delay(500)

        stop()
    }
}

Battery monitor

Background Plan that monitors battery and triggers alerts.

#!octomy-plan v1.0
# @name Battery Monitor
# @version 1.0.0
# @permissions system.battery, comms.broadcast

const LOW_BATTERY = 20
const CRITICAL_BATTERY = 10
const CHECK_INTERVAL = 30000  // 30 seconds

plan BatteryMonitor {
    var last_level = 100
    var alert_sent = false

    init {
        log("Battery Monitor active")
    }

    loop {
        var level = battery.level
        var voltage = battery.voltage

        // Log significant changes
        if abs(level - last_level) >= 5 {
            log("Battery: " + level + "% (" + voltage + "V)")
            last_level = level
        }

        // Low battery warning
        if level <= LOW_BATTERY && !alert_sent {
            log_warn("Low battery: " + level + "%")
            broadcast("battery_low", {level: level})
            alert_sent = true
        }

        // Critical battery - take action
        if level <= CRITICAL_BATTERY {
            log_error("Critical battery: " + level + "%")
            broadcast("battery_critical", {level: level})
            activate("ReturnToBase", {priority: 90})
        }

        // Reset alert when charged
        if level > LOW_BATTERY + 10 {
            alert_sent = false
        }

        // Report status periodically
        if battery.charging {
            log("Charging: " + level + "%")
        }

        delay(CHECK_INTERVAL)
    }
}

Multi-agent search

Coordinated search pattern with multiple robots.

#!octomy-plan v1.0
# @name Coordinated Search
# @version 1.0.0
# @permissions sensors.read, actuators.motors, comms.broadcast, comms.receive, comms.peers

const SEARCH_SPEED = 40
const COMM_INTERVAL = 1000

plan CoordinatedSearch {
    var my_sector = null
    var sectors_searched = []
    var target_found = false
    var target_location = null
    var peers_status = {}

    init {
        // Register with coordinator or claim sector
        broadcast("search_ready", {id: my_id})

        // Wait for sector assignment or claim one
        var timeout = wait_for_message("sector_assigned", 5000)
        if timeout {
            // No coordinator - self-assign based on ID
            my_sector = hash(my_id) % 4
        }

        log("Assigned to sector " + my_sector)
    }

    // Handle peer messages
    on_message("search_ready") { peer ->
        peers_status[peer.id] = "ready"
    }

    on_message("target_found") { data ->
        target_found = true
        target_location = {x: data.x, y: data.y}
        log("Target found by " + data.finder + " at " + data.x + ", " + data.y)
    }

    on_message("sector_complete") { data ->
        push(sectors_searched, data.sector)
    }

    on_message("peer_status") { data ->
        peers_status[data.id] = data.status
    }

    loop {
        if target_found {
            // Navigate to target
            navigate_to(target_location.x, target_location.y)

            if at_location(target_location.x, target_location.y) {
                log("Arrived at target")
                deactivate()
            }
        } else {
            // Search current sector
            search_sector(my_sector)
        }

        // Broadcast status periodically
        static var last_broadcast = 0
        if now() - last_broadcast > COMM_INTERVAL {
            broadcast("peer_status", {
                id: my_id,
                status: target_found ? "converging" : "searching",
                sector: my_sector,
                position: position
            })
            last_broadcast = now()
        }

        delay(50)
    }

    cleanup {
        stop()
        broadcast("search_ended", {id: my_id})
    }

    // Search a sector in a pattern
    function search_sector(sector) {
        var bounds = get_sector_bounds(sector)

        // Lawnmower pattern
        var y = bounds.y_min
        var direction = 1

        while y <= bounds.y_max {
            if direction == 1 {
                navigate_to(bounds.x_max, y)
            } else {
                navigate_to(bounds.x_min, y)
            }

            // Check for target
            if sensors.distance.front < 20 {
                // Potential target - verify
                if verify_target() {
                    target_location = {x: position.x, y: position.y}
                    target_found = true
                    broadcast("target_found", {
                        finder: my_id,
                        x: position.x,
                        y: position.y
                    })
                    return
                }
            }

            y += 20  // Move to next row
            direction *= -1
        }

        // Sector complete
        broadcast("sector_complete", {sector: my_sector})

        // Claim next unclaimed sector
        for s in 0..4 {
            if !contains(sectors_searched, s) && s != my_sector {
                my_sector = s
                log("Moving to sector " + s)
                return
            }
        }

        log("All sectors searched")
    }

    function get_sector_bounds(sector) {
        // Divide area into 4 quadrants
        var size = 100
        match sector {
            0 -> return {x_min: 0, y_min: 0, x_max: size/2, y_max: size/2}
            1 -> return {x_min: size/2, y_min: 0, x_max: size, y_max: size/2}
            2 -> return {x_min: 0, y_min: size/2, x_max: size/2, y_max: size}
            3 -> return {x_min: size/2, y_min: size/2, x_max: size, y_max: size}
        }
    }

    function verify_target() {
        // Additional checks to confirm target
        stop()
        delay(500)
        return sensors.distance.front < 20  // Confirm reading
    }
}

Voice commands

Process voice commands from Remote.

#!octomy-plan v1.0
# @name Voice Commands
# @version 1.0.0
# @permissions actuators.motors, actuators.servos, comms.receive

plan VoiceCommands {
    var speed = 50

    init {
        log("Voice Commands active")
        log("Say 'hello robot' to begin")
    }

    on_message("voice_command") { data ->
        var cmd = to_lower(data.text)
        log("Heard: " + cmd)

        // Movement commands
        if contains(cmd, "forward") || contains(cmd, "go") {
            drive(speed, speed)
            delay(1000)
            stop()
        }
        else if contains(cmd, "backward") || contains(cmd, "back") {
            drive(-speed, -speed)
            delay(1000)
            stop()
        }
        else if contains(cmd, "left") {
            drive(-speed, speed)
            delay(500)
            stop()
        }
        else if contains(cmd, "right") {
            drive(speed, -speed)
            delay(500)
            stop()
        }
        else if contains(cmd, "stop") || contains(cmd, "halt") {
            stop()
        }

        // Speed commands
        else if contains(cmd, "faster") {
            speed = min(speed + 10, 100)
            log("Speed: " + speed)
        }
        else if contains(cmd, "slower") {
            speed = max(speed - 10, 20)
            log("Speed: " + speed)
        }

        // Gesture commands
        else if contains(cmd, "wave") {
            wave_arm()
        }
        else if contains(cmd, "nod") {
            nod_head()
        }
        else if contains(cmd, "shake") {
            shake_head()
        }

        // Special commands
        else if contains(cmd, "patrol") {
            activate("Patrol")
        }
        else if contains(cmd, "follow") {
            activate("FollowMe")
        }
        else if contains(cmd, "dance") {
            dance()
        }
        else if contains(cmd, "sleep") || contains(cmd, "shutdown") {
            log("Going to sleep...")
            deactivate()
        }

        // Unknown command
        else {
            log("Unknown command: " + cmd)
        }
    }

    loop {
        // Keep plan alive
        delay(100)
    }

    cleanup {
        stop()
    }

    // Gesture functions
    function wave_arm() {
        for i in 0..3 {
            set_servo("arm", 45)
            delay(300)
            set_servo("arm", 135)
            delay(300)
        }
        set_servo("arm", 90)
    }

    function nod_head() {
        for i in 0..3 {
            set_servo("head.tilt", 60)
            delay(200)
            set_servo("head.tilt", 120)
            delay(200)
        }
        set_servo("head.tilt", 90)
    }

    function shake_head() {
        for i in 0..3 {
            set_servo("head.pan", 60)
            delay(200)
            set_servo("head.pan", 120)
            delay(200)
        }
        set_servo("head.pan", 90)
    }

    function dance() {
        // Simple dance routine
        for i in 0..4 {
            drive(50, -50)  // Spin right
            delay(500)
            drive(-50, 50)  // Spin left
            delay(500)
            drive(50, 50)   // Forward
            delay(300)
            drive(-50, -50) // Back
            delay(300)
        }
        stop()
    }
}

Plan templates

Minimal plan

#!octomy-plan v1.0
# @name Minimal
# @permissions sensors.read

plan Minimal {
    loop {
        // Your code here
        delay(50)
    }
}

Full template

#!octomy-plan v1.0
# @name My Plan
# @version 1.0.0
# @author Your Name
# @description Brief description
# @permissions sensors.read, actuators.motors

const CONFIG_VALUE = 50

plan MyPlan {
    var state = "idle"

    init {
        log("Initializing...")
        // Setup code
    }

    on_message("command") { data ->
        // Handle messages
    }

    loop {
        // Main logic
        match state {
            "idle" -> handle_idle()
            "active" -> handle_active()
        }
        delay(50)
    }

    cleanup {
        stop()
        log("Cleanup complete")
    }

    function handle_idle() {
        // Idle state logic
    }

    function handle_active() {
        // Active state logic
    }
}

In this section
Topics
reference plans examples tutorials
See also