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
}
}