Oriel OS Developer Guide

Back to Admin

Overview

Oriel OS runs JavaScript applications on an ESP32-S3 microcontroller with a round 466x466 pixel AMOLED display and capacitive touch input. Apps are powered by QuickJS, a lightweight JavaScript engine.

Applications can be:

User-space apps appear on the home screen alongside built-in apps. They have full access to all Oriel APIs. On the home screen, Oriel API apps appear in teal and p5.js apps appear in purple.

Display: The screen is 466x466 pixels, circular AMOLED. Coordinates (0,0) are the top-left corner. The visible area is a circle with center at (233, 233) and radius 233.

Quick Start

The fastest way to get an app running:

  1. Open the Oriel Admin interface and connect to your device via USB serial
  2. Switch to the Apps tab
  3. Select a template or write your code in the editor
  4. Click Deploy to Device
  5. Your app appears on the home screen immediately

Alternatively, place any .js file in /sdcard/applications/ on the SD card directly.

App Structure

Every app is a single .js file. The filename (without .js) becomes the app identifier.

Metadata Header

Apps should start with an optional metadata block that provides a display name and description:

/*===JS_METADATA
name: "My App Name"
version: "1.0.0"
author: "Your Name"
description: "What this app does"
permissions: ["system", "sensors"]
entry_point: "setup"
===*/

The name field is shown on the home screen. Without metadata, the filename is used (e.g., my_app.js displays as "My App").

Permissions are informational only in the current version. All APIs are available to all apps.

Three Ways to Build Apps

Oriel supports three styles of writing applications. All three have access to the same hardware and sensors — choose the one that fits your project and experience.

StyleBest ForFeels LikeRuntime
Oriel API Custom UIs, dashboards, full control Canvas/HTML5 drawing Default
p5.js Generative art, creative coding, visual experiments Processing/p5.js runtime: "p5"
Declarative UI Settings, forms, lists, data display SwiftUI / React Default (uses UIApp framework)

Oriel API Style

Direct hardware access via the Oriel.display.* namespace. You draw every pixel yourself. Most flexible, best for custom interfaces.

Oriel.display.clearScreen("#000000");
Oriel.display.drawFilledCircle(180, 180, 50, "#FF0000");
Oriel.display.drawText(120, 280, "Hello!", "#FFFFFF", 3);

p5.js Style

p5.js is a popular JavaScript library for creative coding, used by artists, designers, students, and educators worldwide. If you've used p5.js or Processing before, you can write Oriel apps the same way. Just add runtime: "p5" to your metadata header and the compatibility layer loads automatically.

Learn more about p5.js at p5js.org — the reference and examples are a great starting point.

/*===JS_METADATA
name: "My Sketch"
runtime: "p5"
===*/

function setup() {
  createCanvas(466, 466);
  background(0);
}

function draw() {
  fill(255, 0, 0);
  circle(mouseX, mouseY, 30);
}
How it works: When runtime: "p5" is set, the engine automatically loads the p5 compatibility layer before your sketch, then calls your setup() and starts the draw() loop. This provides the p5 global functions (fill, stroke, circle, rect, etc.), mouse/touch mapping (mouseX, mouseY, mouseIsPressed), and the draw loop. Native C++ implementations of p5 drawing functions ensure high performance.

Declarative UI Style

Build interfaces declaratively using the built-in UIApp framework (inspired by SwiftUI). Components like VStack, HStack, Text, Button, and ScrollView handle layout and touch automatically.

var app;

function buildUI() {
  return new VStack([
    new Text("Settings", { style: "title", color: "#FFD700" }),
    new Spacer(10),
    new Button("Do Something", function() {
      Oriel.system.logInfo("Tapped!");
      app.setNeedsRedraw();
    }, { color: "#58a6ff" }),
  ], { spacing: 10, align: "center", padding: 20 });
}

function setup() {
  app = new UIApp(buildUI, {
    bounds: { x: 20, y: 20, width: 320, height: 320 },
    backgroundColor: "#000000"
  });
}

function draw() {
  app.render(); // Framework handles everything
}

The UIApp framework is loaded globally at engine startup — no imports needed. See the Declarative UI template for a complete example.

Available UI Components

ComponentDescription
VStack(children, opts)Vertical layout. Options: spacing, align ("left"/"center"/"right"), padding
HStack(children, opts)Horizontal layout. Options: spacing, align ("top"/"center"/"bottom"), padding
ScrollView(children, opts)Scrollable vertical list with momentum. Add stateId for scroll position persistence
Text(label, opts)Text label. label can be a string or () => string function for dynamic text. Options: size (1-4), style ("title"/"body"/"caption"/etc.), color
Button(label, onClick, opts)Tappable button. Options: color, textColor, padding, size
Circle(opts)Circle shape. Options: radius, color, filled
Spacer(height)Empty space of given height

Reactive State

// Create observable state
var count = observable(0);

// Use in dynamic text
new Text(function() { return "Count: " + count.value; }, { size: 3 })

// Update from button
new Button("+1", function() {
  count.set(count.value + 1);
  app.setNeedsRedraw();
}, { color: "#3fb950" })

App Patterns

Oriel supports two app patterns. Choose based on your needs:

Pattern 1: setup() / draw() (Recommended)

Best for: continuous animations, real-time displays, games, interactive UIs.

function setup() {
  // Called once when app starts
  Oriel.display.clearScreen("#000000");
  Oriel.display.drawText(100, 180, "Loading...", "#FFFFFF", 3);
}

function draw() {
  // Called every frame (~30 FPS)
  // Read input, update state, draw screen
  var touch = Oriel.sensors.readTouch();
  if (touch.active) {
    Oriel.display.drawFilledCircle(touch.x, touch.y, 10, "#FF0000");
  }
}

The engine automatically calls setup() once, then calls draw() in a loop at approximately 30 FPS. The display buffer is flushed automatically after each draw() call. You just define the functions — the engine handles the rest.

Pattern 2: main() Loop

Best for: simple utilities, one-shot displays, sequential workflows.

function main() {
  Oriel.display.clearScreen("#000000");
  Oriel.display.drawText(100, 100, "Hello!", "#FFFFFF", 3);
  Oriel.display.refreshDisplay();

  // Run for 30 seconds
  for (var i = 0; i < 300; i++) {
    var touch = Oriel.sensors.readTouch();
    if (touch.active) break; // Exit on touch
    Oriel.system.sleepMs(100);
  }
}

main();

In this pattern, you manage your own loop and timing with Oriel.system.sleepMs(). Call Oriel.display.refreshDisplay() after drawing to push the buffer to screen.

Exiting Apps

Users exit apps with a long-press gesture (hold 3 fingers). The engine handles this automatically for setup()/draw() apps. For main() apps, check the touch exit gesture:

var touch = Oriel.sensors.readTouch();
if (touch.exitGesture) {
  return; // Exit app
}

Sleep Callback (Optional)

Apps can respond to the device entering sleep mode:

function willSleep(sleepInfo) {
  // Device is about to sleep - pause animations, save state, etc.
  Oriel.system.logInfo("Going to sleep, saving state...");
}

Deploying Apps

There are three ways to deploy user-space apps:

MethodHowBest For
Admin Interface Use the Apps tab in the admin UI to write and deploy over USB serial Development and testing
SD Card Copy .js files to /applications/ on the SD card Distributing apps
File Upload Use the SD Card tab to upload files to /sdcard/applications/ Quick file transfer

After deploying, the app appears on the home screen the next time you return to the launcher (exit current app or reboot).

Template: Hello World

Minimal App

Displays a message and exits on touch. Good starting point for any app.

/*===JS_METADATA
name: "Hello World"
version: "1.0.0"
description: "A minimal Oriel app"
entry_point: "setup"
===*/

var needsRedraw = true;

function setup() {
  Oriel.display.clearScreen("#000000");
  Oriel.display.drawText(110, 160, "Hello, Oriel!", "#FFFFFF", 3);
  Oriel.display.drawText(100, 200, "Tap to go back", "#58a6ff", 2);
}

function draw() {
  var touch = Oriel.sensors.readTouch();
  if (touch.active) {
    Oriel.system.exitApp();
  }
}

Template: Drawing App

Touch Drawing Canvas

Draw on screen with touch. Shows touch handling, state tracking, and continuous rendering.

/*===JS_METADATA
name: "Draw"
version: "1.0.0"
description: "Touch drawing canvas"
entry_point: "setup"
===*/

var lastX = -1;
var lastY = -1;
var wasActive = false;
var hue = 0;

function hslToHex(h, s, l) {
  h = h / 360;
  var r, g, b;
  if (s === 0) {
    r = g = b = l;
  } else {
    function hue2rgb(p, q, t) {
      if (t < 0) t += 1;
      if (t > 1) t -= 1;
      if (t < 1/6) return p + (q - p) * 6 * t;
      if (t < 1/2) return q;
      if (t < 2/3) return p + (q - p) * (2/3 - t) * 6;
      return p;
    }
    var q = l < 0.5 ? l * (1 + s) : l + s - l * s;
    var p = 2 * l - q;
    r = hue2rgb(p, q, h + 1/3);
    g = hue2rgb(p, q, h);
    b = hue2rgb(p, q, h - 1/3);
  }
  var ri = Math.round(r * 255);
  var gi = Math.round(g * 255);
  var bi = Math.round(b * 255);
  return "#" + ((1 << 24) + (ri << 16) + (gi << 8) + bi).toString(16).slice(1);
}

function setup() {
  Oriel.display.clearScreen("#111111");
  Oriel.display.drawText(100, 170, "Touch to draw", "#FFFFFF", 3);
  Oriel.display.drawText(100, 200, "Colors change", "#58a6ff", 2);
}

function draw() {
  var touch = Oriel.sensors.readTouch();

  if (touch.active) {
    var color = hslToHex(hue, 1.0, 0.5);
    if (wasActive && lastX >= 0) {
      // Draw line from last point for smooth strokes
      Oriel.display.drawWideLine(lastX, lastY, touch.x, touch.y, 4, color);
    } else {
      Oriel.display.drawFilledCircle(touch.x, touch.y, 3, color);
    }
    lastX = touch.x;
    lastY = touch.y;
    hue = (hue + 1) % 360;
  } else {
    lastX = -1;
    lastY = -1;
  }

  wasActive = touch.active;
}

Template: Sensor Dashboard

Battery & WiFi Dashboard

Reads battery and WiFi status. Shows sensor APIs, conditional rendering, and periodic updates.

/*===JS_METADATA
name: "Dashboard"
version: "1.0.0"
description: "Battery and WiFi status dashboard"
permissions: ["sensors", "network"]
entry_point: "setup"
===*/

var frameCounter = 0;

function setup() {
  Oriel.display.clearScreen("#000000");
}

function draw() {
  // Update every 30 frames (~1 second at 30fps)
  frameCounter++;
  if (frameCounter % 30 !== 1) return;

  Oriel.display.clearScreen("#0d1117");

  // Title
  Oriel.display.drawText(120, 30, "Dashboard", "#FFFFFF", 3);

  // Battery info
  var bat = Oriel.sensors.readBattery();
  var batColor = bat.percentage > 50 ? "#3fb950" : bat.percentage > 20 ? "#d29922" : "#f85149";

  Oriel.display.drawText(40, 80, "Battery", "#8b949e", 2);
  Oriel.display.drawText(40, 105, bat.percentage + "%", batColor, 4);
  Oriel.display.drawText(40, 140, bat.voltage.toFixed(2) + "V", "#8b949e", 2);
  Oriel.display.drawText(40, 160, bat.charging ? "Charging" : "On battery", "#8b949e", 2);

  // Progress bar for battery
  Oriel.display.drawProgressBar(40, 185, 280, 12, bat.percentage / 100, batColor, "#30363d", "#30363d");

  // WiFi info
  var wifi = Oriel.network.getWifiStatus();
  var wifiColor = wifi.connected ? "#3fb950" : "#f85149";

  Oriel.display.drawText(40, 220, "WiFi", "#8b949e", 2);
  Oriel.display.drawText(40, 245, wifi.connected ? "Connected" : "Disconnected", wifiColor, 3);

  if (wifi.connected) {
    Oriel.display.drawText(40, 270, wifi.ssid, "#e6edf3", 2);
    Oriel.display.drawText(40, 290, wifi.ip_address, "#8b949e", 2);
    Oriel.display.drawText(40, 310, wifi.signal_strength + " dBm", "#8b949e", 2);
  }

  // Exit hint
  Oriel.display.drawText(100, 340, "Hold 3 fingers to exit", "#30363d", 2);
}

Template: Menu UI

Interactive Menu

Scrollable menu with touch selection. Shows list rendering, touch hit-testing, and screen transitions.

/*===JS_METADATA
name: "Menu Demo"
version: "1.0.0"
description: "Interactive menu with touch selection"
entry_point: "setup"
===*/

var items = [
  { label: "Option A", color: "#58a6ff" },
  { label: "Option B", color: "#3fb950" },
  { label: "Option C", color: "#d29922" },
  { label: "Option D", color: "#f85149" },
  { label: "Option E", color: "#bc8cff" },
];
var selected = -1;
var screen = "menu"; // "menu" or "detail"
var needsRedraw = true;
var lastTouch = false;

function drawMenu() {
  Oriel.display.clearScreen("#0d1117");
  Oriel.display.drawText(130, 30, "Select One", "#FFFFFF", 3);

  for (var i = 0; i < items.length; i++) {
    var y = 80 + i * 52;
    var bg = (i === selected) ? "#1c2129" : "#161b22";
    Oriel.display.drawFilledRoundedRect(30, y, 300, 44, 8, bg);
    Oriel.display.drawRoundedRect(30, y, 300, 44, 8, items[i].color);
    Oriel.display.drawText(50, y + 15, items[i].label, "#FFFFFF", 3);
  }
}

function drawDetail() {
  var item = items[selected];
  Oriel.display.clearScreen("#0d1117");
  Oriel.display.drawFilledCircle(180, 150, 50, item.color);
  Oriel.display.drawText(100, 230, item.label, "#FFFFFF", 4);
  Oriel.display.drawText(110, 280, "Tap to go back", "#8b949e", 2);
}

function setup() {
  needsRedraw = true;
}

function draw() {
  var touch = Oriel.sensors.readTouch();
  var justPressed = touch.active && !lastTouch;
  lastTouch = touch.active;

  if (justPressed) {
    if (screen === "detail") {
      screen = "menu";
      selected = -1;
      needsRedraw = true;
    } else {
      // Check which menu item was tapped
      for (var i = 0; i < items.length; i++) {
        var y = 80 + i * 52;
        if (touch.x >= 30 && touch.x <= 330 && touch.y >= y && touch.y <= y + 44) {
          selected = i;
          screen = "detail";
          needsRedraw = true;
          break;
        }
      }
    }
  }

  if (needsRedraw) {
    if (screen === "menu") drawMenu();
    else drawDetail();
    needsRedraw = false;
  }
}

Template: Animation

Animated Circles

Smooth animation with frame counting and trigonometry. Shows the draw loop pattern at its best.

/*===JS_METADATA
name: "Rings"
version: "1.0.0"
description: "Animated ring pattern"
entry_point: "setup"
===*/

var frame = 0;

function setup() {
  Oriel.display.clearScreen("#000000");
}

function draw() {
  Oriel.display.clearScreen("#000000");
  frame++;

  var cx = 180;
  var cy = 180;

  // Draw concentric pulsing rings
  for (var i = 0; i < 8; i++) {
    var phase = frame * 0.03 + i * 0.8;
    var radius = 20 + i * 22 + Math.sin(phase) * 10;
    var brightness = Math.floor(80 + Math.sin(phase + 1) * 60);

    // Color shifts with each ring
    var r = Math.floor(brightness * (0.5 + 0.5 * Math.sin(frame * 0.02 + i)));
    var g = Math.floor(brightness * (0.5 + 0.5 * Math.sin(frame * 0.02 + i + 2)));
    var b = Math.floor(brightness * (0.5 + 0.5 * Math.sin(frame * 0.02 + i + 4)));

    var hex = "#" + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1);
    Oriel.display.drawCircle(cx, cy, radius, hex);
  }

  // Orbiting dot
  var angle = frame * 0.05;
  var dotX = cx + Math.cos(angle) * 120;
  var dotY = cy + Math.sin(angle) * 120;
  Oriel.display.drawFilledCircle(dotX, dotY, 8, "#FFFFFF");
}

Template: p5.js Basic Sketch

Interactive p5.js Sketch

A standard p5.js sketch with touch interaction. If you know p5.js, you already know how to write this.

/*===JS_METADATA
name: "p5 Sketch"
version: "1.0.0"
description: "Interactive p5.js sketch"
runtime: "p5"
===*/

var colors = [];

function setup() {
  createCanvas(466, 466);
  background(20);
  noStroke();
  for (var i = 0; i < 6; i++) {
    colors.push([
      Math.floor(Math.random() * 255),
      Math.floor(Math.random() * 255),
      Math.floor(Math.random() * 255)
    ]);
  }
}

function draw() {
  // Slow fade effect
  fill(20, 20, 20, 15);
  rect(0, 0, width, height);

  // Draw orbiting circles
  for (var i = 0; i < colors.length; i++) {
    var angle = frameCount * 0.02 + i * (Math.PI * 2 / colors.length);
    var radius = 80 + Math.sin(frameCount * 0.01 + i) * 30;
    var x = width / 2 + Math.cos(angle) * radius;
    var y = height / 2 + Math.sin(angle) * radius;
    var size = 20 + Math.sin(frameCount * 0.03 + i * 2) * 10;

    fill(colors[i][0], colors[i][1], colors[i][2], 200);
    circle(x, y, size);
  }

  // Touch interaction
  if (mouseIsPressed) {
    fill(255, 255, 255, 180);
    circle(mouseX, mouseY, 40);
    // Draw lines to all orbiting circles
    stroke(255, 100);
    strokeWeight(1);
    for (var i = 0; i < colors.length; i++) {
      var angle = frameCount * 0.02 + i * (Math.PI * 2 / colors.length);
      var radius = 80 + Math.sin(frameCount * 0.01 + i) * 30;
      var x = width / 2 + Math.cos(angle) * radius;
      var y = height / 2 + Math.sin(angle) * radius;
      line(mouseX, mouseY, x, y);
    }
    noStroke();
  }
}

Template: p5.js Particles

Particle System

Touch to emit particles. Shows arrays, physics simulation, and the p5 draw loop.

/*===JS_METADATA
name: "Particles"
version: "1.0.0"
description: "Touch-driven particle system"
runtime: "p5"
===*/

var particles = [];
var MAX_PARTICLES = 80;

function setup() {
  createCanvas(466, 466);
}

function draw() {
  background(10, 10, 20);

  // Spawn particles on touch
  if (mouseIsPressed && particles.length < MAX_PARTICLES) {
    for (var j = 0; j < 3; j++) {
      particles.push({
        x: mouseX,
        y: mouseY,
        vx: (Math.random() - 0.5) * 6,
        vy: (Math.random() - 0.5) * 6 - 2,
        life: 1.0,
        r: Math.floor(Math.random() * 200 + 55),
        g: Math.floor(Math.random() * 100 + 50),
        b: Math.floor(Math.random() * 255),
        size: Math.random() * 8 + 4
      });
    }
  }

  // Update and draw particles
  noStroke();
  for (var i = particles.length - 1; i >= 0; i--) {
    var p = particles[i];
    p.x += p.vx;
    p.y += p.vy;
    p.vy += 0.08; // gravity
    p.life -= 0.015;

    if (p.life <= 0) {
      particles.splice(i, 1);
      continue;
    }

    var alpha = Math.floor(p.life * 255);
    fill(p.r, p.g, p.b, alpha);
    circle(p.x, p.y, p.size * p.life);
  }

  // Info text
  fill(255);
  textSize(12);
  text("Touch to create particles", 100, 340);
  text("Particles: " + particles.length, 140, 20);
}

Template: Declarative UI

Settings Panel with Declarative UI

A settings-style interface using VStack, HStack, Button, and reactive state. No manual drawing needed.

/*===JS_METADATA
name: "My Settings"
version: "1.0.0"
description: "Declarative UI settings panel"
entry_point: "setup"
===*/

var app;
var brightness = 80;
var counter = observable(0);

function buildUI() {
  return new ScrollView([
    new Text("My Settings", { style: "title", color: "#FFD700" }),
    new Spacer(10),

    // Brightness control
    new Text(function() { return "Brightness: " + brightness + "%"; }, { size: 2 }),
    new HStack([
      new Button("-10", function() {
        if (brightness > 0) {
          brightness = Math.max(0, brightness - 10);
          Oriel.display.setBacklight(Math.floor(brightness * 2.55));
          app.setNeedsRedraw();
        }
      }, { color: "#f85149", padding: 8 }),
      new Button("+10", function() {
        if (brightness < 100) {
          brightness = Math.min(100, brightness + 10);
          Oriel.display.setBacklight(Math.floor(brightness * 2.55));
          app.setNeedsRedraw();
        }
      }, { color: "#3fb950", padding: 8 }),
    ], { spacing: 10, align: "center" }),

    new Spacer(15),

    // Counter with observable
    new Text(function() { return "Counter: " + counter.value; },
      { size: 3, color: "#58a6ff" }),
    new HStack([
      new Button("-1", function() {
        counter.set(counter.value - 1);
        app.setNeedsRedraw();
      }, { color: "#d29922", padding: 8 }),
      new Button("+1", function() {
        counter.set(counter.value + 1);
        app.setNeedsRedraw();
      }, { color: "#58a6ff", padding: 8 }),
      new Button("Reset", function() {
        counter.set(0);
        app.setNeedsRedraw();
      }, { color: "#8b949e", padding: 8 }),
    ], { spacing: 8, align: "center" }),

    new Spacer(20),

    // Battery info
    new Text("Device Info", { style: "headline", color: "#bc8cff" }),
    new Text(function() {
      var bat = Oriel.sensors.readBattery();
      return "Battery: " + bat.percentage + "% (" + bat.voltage.toFixed(2) + "V)";
    }, { size: 2, color: "#8b949e" }),
    new Text(function() {
      var wifi = Oriel.network.getWifiStatus();
      return "WiFi: " + (wifi.connected ? wifi.ssid : "Disconnected");
    }, { size: 2, color: "#8b949e" }),

    new Spacer(20),
    new Button("Exit", function() {
      Oriel.system.exitApp();
    }, { color: "#f85149", padding: 10 }),

  ], { spacing: 8, align: "center", padding: 15, stateId: "my_settings_scroll" });
}

function setup() {
  app = new UIApp(buildUI, {
    bounds: { x: 10, y: 10, width: 340, height: 340 },
    backgroundColor: "#0d1117"
  });
}

function draw() {
  app.render();
}

API: Oriel.display

All drawing functions for the 466x466 display.

Screen Management

clearScreen(color)

Fill the entire screen with a color. Call at the start of each frame for full redraws.

refreshDisplay()

Flush the draw buffer to the screen. Automatic in draw() pattern; call manually in main() pattern.

setBacklight(level) // 0-255

Set screen brightness. 0 = off, 255 = maximum.

getWidth() -> number

Returns display width (466).

getHeight() -> number

Returns display height (466).

Text

drawText(x, y, text, color, size, bgColor?)

Draw text. size: 1-5 for scale factor. bgColor is optional (transparent if omitted).

Rectangles

drawRect(x, y, width, height, color)

Draw rectangle outline.

drawFilledRect(x, y, width, height, color)

Draw filled rectangle.

drawRoundedRect(x, y, width, height, radius, color)

Draw rounded rectangle outline.

drawFilledRoundedRect(x, y, width, height, radius, color)

Draw filled rounded rectangle.

Circles

drawCircle(x, y, radius, color)

Draw circle outline.

drawFilledCircle(x, y, radius, color)

Draw filled circle.

Lines & Triangles

drawLine(x1, y1, x2, y2, color)

Draw a 1-pixel line between two points.

drawWideLine(x1, y1, x2, y2, width, color)

Draw a line with specified thickness.

drawTriangle(x1, y1, x2, y2, x3, y3, color)

Draw triangle outline.

drawFilledTriangle(x1, y1, x2, y2, x3, y3, color)

Draw filled triangle.

Advanced

drawGradient(x, y, width, height, color1, color2, vertical)

Draw gradient fill. vertical: 0 = horizontal, 1 = vertical.

drawProgressBar(x, y, width, height, progress, fillColor, backColor, borderColor)

Draw a progress bar. progress: 0.0 to 1.0.

drawImage(path, x, y, width?, height?) -> boolean

Draw an image from the SD card (JPG, PNG, BMP). Width/height are optional for scaling.

API: Oriel.sensors

Touch

readTouch() -> { x, y, active, pressed, gesture, exitGesture }

Read current touch state. active: finger is touching. exitGesture: user wants to exit.

Battery

readBattery() -> { voltage, percentage, charging, healthStatus, ... }

Read battery state. percentage: 0-100. voltage: in volts. charging: boolean.

getBatteryVoltage() -> number

Battery voltage in volts.

getBatteryPercentage() -> number

Battery percentage 0-100.

IMU (Accelerometer & Gyroscope)

readGyro() -> { accel_x, accel_y, accel_z, gyro_x, gyro_y, gyro_z, temperature }

Read motion sensor. Accelerometer in g units, gyroscope in degrees/sec.

getTemperature() -> number

IMU sensor temperature in Celsius.

API: Oriel.touch

read() -> { x, y, active, pressed, gesture, exitGesture }

Same as Oriel.sensors.readTouch(). Alternative namespace for touch input.

API: Oriel.filesystem

readFile(path) -> string

Read a file's contents as a string. Checks embedded files first, then SD card.

writeFile(path, content) -> boolean

Write string content to a file on the SD card.

listFiles(path) -> string[]

List files in a directory. Returns filenames; directories end with /.

loadJavaScriptFile(filename) -> boolean

Load and execute a JavaScript file in the current context.

API: Oriel.system

logInfo(message)

Log an info message (visible in serial monitor).

logWarn(message)

Log a warning.

logError(message)

Log an error.

logDebug(message)

Log a debug message.

sleepMs(milliseconds)

Pause execution for N milliseconds. Use in main() pattern for timing.

launchApp(appName) -> boolean

Launch another app by name. The current app is cleaned up first.

exitApp()

Exit the current app and return to the home screen.

getCurrentApp() -> string

Get the name of the currently running app.

startRenderLoop(fps?) -> boolean

Start the render loop. Default 30 FPS. Range: 1-60.

stopRenderLoop()

Stop the render loop.

resetSleepTimer()

Reset the inactivity timer to prevent device sleep.

shutdown()

Power off the device via PMIC.

API: Oriel.network

initWifi() -> boolean

Initialize WiFi (runs in background). Called automatically by the launcher.

getWifiStatus() -> { status, connected, ssid, ip_address, signal_strength, error_message }

Get current WiFi connection state. signal_strength is RSSI in dBm.

connect(ssid, password) -> boolean

Connect to a WiFi network.

connectFromConfig() -> boolean

Connect using saved credentials from /sdcard/wifi_config.toml.

disconnect() -> boolean

Disconnect from WiFi.

scanNetworks() -> [{ ssid, signal_strength, security, channel }]

Scan for available WiFi networks.

API: Oriel.audio

Playback

playFile(filepath) -> boolean

Play an audio file from the SD card.

stop() -> boolean

Stop playback.

pause() -> boolean

Pause playback.

resume() -> boolean

Resume paused playback.

isPlaying() -> boolean

Check if audio is currently playing.

getInfo() -> { state, current_file, volume, position_ms, duration_ms }

Get playback info. state: "idle", "playing", "paused", "error".

listFiles(directory?) -> string[]

List audio files. Defaults to /sdcard.

Volume

setVolume(volume)

Set volume, 0.0 to 1.0.

getVolume() -> number

Get current volume.

setHeadphoneMode(enabled)

Enable or disable headphone output.

isHeadphoneMode() -> boolean

Check if headphone mode is active.

Microphone

startMicrophone() -> boolean

Start listening on the microphone.

stopMicrophone() -> boolean

Stop the microphone.

getAudioLevel() -> number

Get mic input level, 0-100.

startRecording() -> boolean

Start recording audio.

stopRecording() -> boolean

Stop recording.

isRecording() -> boolean

Check if recording is active.

API: Oriel.rtc

getDateTime() -> { year, month, day, day_of_week, hour, minute, second, formatted }

Get current date and time from the hardware real-time clock.

getTimeString() -> string

Formatted time, e.g., "14:30:45".

getDateString() -> string

Formatted date, e.g., "2024-03-15".

setDateTime(year, month, day, hour, minute, second) -> boolean

Set the RTC time.

syncFromNTP() -> boolean

Sync time from an NTP server (requires WiFi).

syncFromSystem() -> boolean

Sync RTC from the ESP32 system clock.

needsSync() -> boolean

Check if the RTC needs time synchronization.

isRunning() -> boolean

Check if the RTC oscillator is running.

getStatus() -> { rtc_running, alarm_enabled, alarm_flag, current_time, last_sync_source, ... }

Full RTC status object.

setAlarm(hour, minute, second) -> boolean

Set an alarm (24-hour format).

clearAlarm() -> boolean

Clear the alarm flag.

getAlarmFlag() -> boolean

Check if the alarm has triggered.

API: Oriel.gps

start() -> boolean

Initialize and start the GPS module.

stop()

Stop GPS and enter sleep mode.

poll() -> boolean

Poll for new GPS data. Call periodically (every ~30 seconds).

getPosition() -> { latitude, longitude, altitude, speed, course, valid }

Get current position. altitude in meters, speed in m/s, course in degrees.

getStatus() -> { hasFix, satellites, hdop, fixQuality, utcTime, utcDate }

Get GPS fix status and satellite info.

getRawNMEA() -> string

Get raw NMEA sentences for debugging.

API: Oriel.stt

Speech-to-text with Claude AI responses. Requires WiFi and Oriel Cloud authentication.

process() -> boolean

Process recorded audio: transcribe and get an AI response.

isProcessing() -> boolean

Check if STT is currently processing.

getResult() -> { state, transcription, answer, error }

state: "idle", "processing", "done", "error".

reset()

Reset STT state for a new recording.

P5.js Compatibility Layer

Oriel includes a p5.js compatibility shim. These functions are available globally (no Oriel. prefix). For the full p5.js reference, see p5js.org/reference.

Drawing State

background(r, g, b)       // Set background color
fill(r, g, b)              // Set fill color
noFill()                   // Disable fill
stroke(r, g, b)            // Set stroke color
noStroke()                 // Disable stroke
strokeWeight(w)            // Set line width

Shapes

rect(x, y, w, h)           // Rectangle
square(x, y, size)         // Square
circle(x, y, diameter)     // Circle
ellipse(x, y, w, h)        // Ellipse
line(x1, y1, x2, y2)       // Line
point(x, y)                // Single pixel
triangle(x1,y1,x2,y2,x3,y3)
arc(x, y, w, h, start, stop)
bezier(x1,y1,x2,y2,x3,y3,x4,y4)

Custom Shapes

beginShape()               // Start shape
vertex(x, y)               // Add point
endShape(close)            // End shape (1=closed)

Transformations

push()                     // Save state
pop()                      // Restore state
translate(x, y)            // Move origin
rotate(angle)              // Rotate (radians)
scale(sx, sy)              // Scale

Text

text(str, x, y)            // Draw text
textSize(size)             // Set text size
textAlign(align)           // Set alignment

Global Variables

width                      // Display width (466)
height                     // Display height (466)
frameCount                 // Frames rendered
mouseX, mouseY             // Touch position
mouseIsPressed             // Touch active

Colors

Colors can be specified in two formats:

FormatExampleNotes
Hex string"#FF0000"Most readable, recommended
Hex number0xFF0000Slightly faster

Common colors:

"#000000"  // Black
"#FFFFFF"  // White
"#FF0000"  // Red
"#00FF00"  // Green
"#0000FF"  // Blue
"#FFFF00"  // Yellow
"#FF8800"  // Orange
"#8b949e"  // Gray (dim text)
"#58a6ff"  // Accent blue
"#3fb950"  // Success green
"#f85149"  // Error red
"#d29922"  // Warning yellow

Coordinates & Layout

The display is 466x466 pixels. Origin (0,0) is the top-left corner.

     0                 233               466
  0  +-------------------+------------------+
     |                   |                  |
     |    Visible area is a circle          |
     |    centered at (233, 233)            |
     |    with radius 233                   |
 233 +         (center)                     +
     |                                      |
     |    Corners are NOT visible           |
     |    on the round display              |
     |                                      |
 466 +--------------------------------------+

Tips for the round screen:

Performance Tips

Troubleshooting

App doesn't appear on home screen

App fails to launch

"Failed to load" error

Display shows nothing

Touch not working

App crashes or device reboots