Plan Syntax
Structure and syntax of OctoMY™ Plans
Plan Syntax
Complete reference for the structure and syntax of OctoMY™ Plans written in OPAL.
Did You Know?
OPAL syntax is intentionally similar to JavaScript/TypeScript - if you know JS, you'll feel right at home. The key differences are robot-specific:
drive(),delay(), sensor access, and theinit/loop/cleanuplifecycle blocks that structure robot behavior.
Plan file structure
A Plan file consists of these sections:
#!octomy-plan v1.0
# Metadata comments
# @name My Plan Name
# @version 1.0.0
# @author Your Name
# @permissions sensors.read, actuators.motors
// Imports (optional)
import "utils.opal"
// Constants
const MAX_SPEED = 100
const SAFE_DISTANCE = 30
// Global variables
var state = "idle"
// Custom functions
function helper() {
// ...
}
// Main plan declaration
plan MyPlan {
// Plan body
}
Shebang and version
Every Plan starts with a shebang declaring the OPAL version:
#!octomy-plan v1.0
| Version | Features |
|---|---|
| v1.0 | Core OPAL language |
| v1.1 | Added async/await |
| v1.2 | Added pattern matching |
Metadata comments
Metadata is declared using special comment syntax:
# @name Obstacle Avoider
# @version 2.1.0
# @author RobotBuilder42
# @description Autonomous obstacle avoidance using distance sensors
# @permissions sensors.distance, actuators.motors
# @requires firmware >= 1.5
# @tags navigation, autonomous, safety
Required metadata
| Tag | Description |
|---|---|
@name |
Human-readable plan name |
@permissions |
Required permissions (comma-separated) |
Optional metadata
| Tag | Description |
|---|---|
@version |
Semantic version (major.minor.patch) |
@author |
Author name or identifier |
@description |
Brief description |
@requires |
System requirements |
@tags |
Categorization tags |
@license |
License identifier |
Plan declaration
Plans are declared with the plan keyword:
plan PlanName {
// Plan body
}
Plan name rules
- Must start with a letter
- Can contain letters, numbers, underscores
- Convention: PascalCase for plan names
// Valid names
plan MyPlan { }
plan ObstacleAvoider_v2 { }
plan Plan123 { }
// Invalid names
plan 123Plan { } // Cannot start with number
plan my-plan { } // No hyphens allowed
plan plan { } // Reserved word
Plan body sections
init block
Runs once when Plan activates:
plan Example {
init {
log("Plan starting")
state = "ready"
calibrate_sensors()
}
}
loop block
Runs continuously while Plan is active:
plan Example {
loop {
check_sensors()
update_motors()
delay(50) // Required delay
}
}
Important: Loop blocks must include a delay() to prevent CPU overload.
cleanup block
Runs when Plan deactivates:
plan Example {
cleanup {
stop()
log("Plan stopped")
save_state()
}
}
Complete structure
plan FullExample {
init {
// Initialization code
}
loop {
// Main loop code
delay(50)
}
cleanup {
// Cleanup code
}
}
Variables
Variable declaration
var name = value
Types
| Type | Example | Description |
|---|---|---|
| Number | 42, 3.14 |
Integer or floating point |
| String | "hello" |
Text in double quotes |
| Boolean | true, false |
Logical value |
| Array | [1, 2, 3] |
Ordered collection |
| Object | {x: 1, y: 2} |
Key-value pairs |
| null | null |
No value |
var count = 0
var name = "Robot"
var active = true
var readings = [10, 20, 30]
var config = {speed: 50, threshold: 30}
var empty = null
Constants
Constants cannot be reassigned:
const PI = 3.14159
const MAX_SPEED = 100
// Error: cannot reassign constant
MAX_SPEED = 200 // Throws error
Scope
Variables have block scope:
var global_var = 1
plan Example {
var plan_var = 2
init {
var init_var = 3
log(global_var) // OK - global scope
log(plan_var) // OK - plan scope
log(init_var) // OK - local scope
}
loop {
log(global_var) // OK
log(plan_var) // OK
log(init_var) // Error - out of scope
delay(100)
}
}
Operators
Arithmetic
| Operator | Description | Example |
|---|---|---|
+ |
Addition | 5 + 3 → 8 |
- |
Subtraction | 5 - 3 → 2 |
* |
Multiplication | 5 * 3 → 15 |
/ |
Division | 6 / 3 → 2 |
% |
Modulo | 7 % 3 → 1 |
Comparison
| Operator | Description | Example |
|---|---|---|
== |
Equal | 5 == 5 → true |
!= |
Not equal | 5 != 3 → true |
< |
Less than | 3 < 5 → true |
> |
Greater than | 5 > 3 → true |
<= |
Less or equal | 5 <= 5 → true |
>= |
Greater or equal | 5 >= 3 → true |
Logical
| Operator | Description | Example |
|---|---|---|
&& |
Logical AND | true && false → false |
|| |
Logical OR | true || false → true |
! |
Logical NOT | !true → false |
Assignment
| Operator | Description | Example |
|---|---|---|
= |
Assign | x = 5 |
+= |
Add and assign | x += 3 (same as x = x + 3) |
-= |
Subtract and assign | x -= 3 |
*= |
Multiply and assign | x *= 3 |
/= |
Divide and assign | x /= 3 |
Control flow
Conditionals
if condition {
// code
}
if condition {
// code
} else {
// code
}
if condition1 {
// code
} else if condition2 {
// code
} else {
// code
}
Loops
For loop with range:
for i in 0..10 {
log(i) // 0, 1, 2, ... 9
}
For loop with collection:
var items = ["a", "b", "c"]
for item in items {
log(item)
}
While loop:
while condition {
// code
}
Infinite loop:
loop {
// code
delay(50)
}
Loop control
for i in 0..100 {
if i == 50 {
break // Exit loop
}
if i % 2 == 0 {
continue // Skip to next iteration
}
log(i)
}
Functions
Function declaration
function name(parameters) {
// code
return value
}
Examples
// No parameters
function greet() {
log("Hello!")
}
// With parameters
function add(a, b) {
return a + b
}
// Default parameters
function drive_forward(speed = 50) {
drive(speed, speed)
}
// Multiple return (via object)
function get_position() {
return {x: position.x, y: position.y}
}
Calling functions
greet()
var sum = add(5, 3)
drive_forward() // Uses default: 50
drive_forward(80) // Override: 80
var pos = get_position()
Lambda expressions
Short function syntax:
// Single expression
var double = x -> x * 2
// Multiple parameters
var add = (a, b) -> a + b
// With block body
var process = x -> {
var result = x * 2
return result + 1
}
Usage with array functions
var numbers = [1, 2, 3, 4, 5]
var doubled = map(numbers, x -> x * 2)
// [2, 4, 6, 8, 10]
var evens = filter(numbers, x -> x % 2 == 0)
// [2, 4]
var sum = reduce(numbers, (acc, x) -> acc + x, 0)
// 15
Event handlers
Message handler
on_message("event_name") { data ->
log("Received: " + data)
}
Multiple handlers
plan EventDemo {
on_message("start") { _ ->
log("Starting...")
}
on_message("stop") { _ ->
log("Stopping...")
}
on_message("data") { payload ->
process(payload)
}
loop {
delay(100)
}
}
Error handling
try/catch
try {
// Code that might fail
risky_operation()
} catch error {
// Handle error
log_error("Failed: " + error.message)
}
throw
function validate(value) {
if value < 0 {
throw "Value must be positive"
}
return value
}
Error object
catch error {
error.code // Error code (e.g., "E201")
error.message // Human-readable message
error.line // Line number
error.stack // Call stack
}
Comments
Single-line comments
// This is a comment
var x = 5 // Inline comment
Multi-line comments
/*
* This is a
* multi-line comment
*/
Documentation comments
/**
* Calculates distance between two points.
* @param x1 First x coordinate
* @param y1 First y coordinate
* @param x2 Second x coordinate
* @param y2 Second y coordinate
* @return Distance in units
*/
function distance(x1, y1, x2, y2) {
var dx = x2 - x1
var dy = y2 - y1
return sqrt(dx*dx + dy*dy)
}
Property access
Dot notation
var dist = sensors.distance.front
var heading = sensors.imu.heading
Bracket notation
var sensor_name = "front"
var dist = sensors.distance[sensor_name]
Safe navigation
// Returns null if path doesn't exist
var value = data?.nested?.property
String interpolation
var name = "Robot"
var speed = 50
// Concatenation
log("Name: " + name + ", Speed: " + speed)
// Template strings (v1.1+)
log(`Name: ${name}, Speed: ${speed}`)
Importing
Import other OPAL files
import "utilities.opal"
import "sensors/distance.opal"
Import specific items
import { helper, constant } from "utils.opal"
Reserved words
These words cannot be used as identifiers:
if, else, for, while, loop, break, continue, return,
var, const, function, plan, true, false, null,
try, catch, throw, import, from, in, init, cleanup,
on_message, and, or, not
Best practices
Naming conventions
// Variables: camelCase
var myVariable = 1
var sensorReading = 50
// Constants: UPPER_SNAKE_CASE
const MAX_SPEED = 100
const SAFE_DISTANCE = 30
// Functions: camelCase
function calculateDistance() { }
// Plans: PascalCase
plan ObstacleAvoider { }
Code organization
#!octomy-plan v1.0
# Metadata first
// Constants
const THRESHOLD = 30
// Helper functions
function helper() { }
// Main plan
plan Main {
init { }
loop { delay(50) }
cleanup { }
}
Error handling
// Always wrap sensor access
try {
var dist = sensors.distance.front
} catch error {
log_error("Sensor error: " + error.message)
stop()
}