Getting Started
This guide walks through creating a dashboard from scratch and covers the key concepts of the Simucube Dash Lua scripting API.
Dashboard Structure
A dashboard is a directory containing a JSON configuration file, Lua scripts, and optional assets (fonts, images).
my_dash/
├── dash.json # configuration file
├── script/
│ └── main.lua # main Lua script (entry point)
├── fonts/
│ └── MyFont.ttf # font files
└── images/
└── background.png # image files
JSON Configuration
The JSON file declares fonts, images, and scripts used by the dashboard. All paths are relative to the dashboard directory.
{
"version": "1.0.0",
"name": "My Dashboard",
"author": "Your Name",
"preview_image": "images/preview.png",
"fonts": {
"main_font": {
"path": "fonts/MyFont.ttf",
"size": 24,
"additional_chars": "0123456789:. "
},
"large_font": {
"path": "fonts/MyFont.ttf",
"size": 48
}
},
"scripts": {
"main": "script/main.lua"
},
"images": {
"background": "images/background.png"
}
}
fonts:Each entry defines a font with a name (used as
dash.font.<name>in Lua), a path to the font file, and a pixel size. The optionaladditional_charsfield lists characters to pre-render – useful for optimizing memory on embedded displays when only certain characters are needed.scripts:The
"main"script is the entry point. Additional scripts (e.g."widgets") are loaded before main and can be accessed viarequire(). The following names are reserved and cannot be used as script names:dash,manufacturer,sc,simucube.images:Each entry can be a path string or an object with
"path"and optional"format"(e.g."alpha_only"for mask images).
Loading the Dash Module
Scripts must load the dash module with require before using it.
Usually this is the first thing a dash script does:
local dash = require("dash")
Every script file that uses the dash API needs its own require
call – this includes widget modules and other helper scripts, not just
the main entry point.
Minimal Example
A complete minimal dashboard:
-- main.lua
local dash = require("dash")
local page = dash.Page()
page:set_background_color(0xFF000000) -- black background
local label = dash.Label(page, {
x = 10, y = 10,
text = "Hello!",
font = dash.font.main_font,
text_color = 0xFFFFFFFF
})
while true do
dash.update()
end
Main Loop
Every dashboard script must have a main loop that calls dash.update().
This function processes input events, runs telemetry update hooks, advances
timers, and renders the current frame.
while true do
-- your per-frame logic here
dash.update()
end
Node Types
All visual elements are nodes in a parent-child tree. The root of each tree is a Page. Available node types:
Page – root container, one per screen layout
Box – filled rectangle with optional border and rounded corners
Label – text display with auto-sizing or fixed-rect modes
Image – displays an image resource with automatic mosaic repeat for oversized nodes
Line – line segment between two points
Circle – circle with optional fill and border
Triangle – triangle with three vertices
Graph – scrolling line chart
Canvas – free-form pixel drawing
Nodes are created with factory functions on the dash module:
local page = dash.Page()
local box = dash.Box(page, { x=10, y=10, width=100, height=50 })
local label = dash.Label(box, { text="Inside box", font=dash.font.main_font })
Each node’s position is relative to its parent. Hiding a parent hides all its children.
Colors
Colors use ARGB32 format where alpha is in the highest byte:
0xAARRGGBB. Full opacity is 0xFF.
0xFFFF0000 -- opaque red
0xFF00FF00 -- opaque green
0x80FFFFFF -- 50% transparent white
Color helper functions:
dash.rgb(255, 0, 0) -- opaque red
dash.rgba(255, 0, 0, 128) -- 50% transparent red
dash.color("#FF0000") -- opaque red from hex string
Hex string formats: "#RGB", "#ARGB", "#RRGGBB", "#AARRGGBB".
Telemetry
Telemetry objects provide live data from the connected racing simulator.
Access them through dash.telemetry.<name>:
local rpm = dash.telemetry.engine_rpm
local speed = dash.telemetry.speed
if rpm:valid() then
print(rpm:value())
end
Polling – read values in the main loop:
while true do
label:set_text(string.format("%d RPM", dash.telemetry.engine_rpm:value()))
dash.update()
end
Update hooks – more efficient, only called when values change:
local rpm = dash.telemetry.engine_rpm
label:set_telemetry_update_hook(30, function()
label:set_text(string.format("%d RPM", rpm:value()))
end, rpm)
while true do
dash.update()
end
The first argument to set_telemetry_update_hook is the maximum check
rate in Hz. The callback fires only when a watched value actually changes.
Custom Widgets with OOP Inheritance
Use extend() to create reusable widget classes:
-- Define a reusable telemetry display widget
local TelemetryLabel = dash.Label:extend({
font = dash.font.main_font,
text_color = 0xFFFFFFFF
})
function TelemetryLabel:init(parent, props)
self._telemetry = props.telemetry
self._format = props.format or "%d"
self:set_telemetry_update_hook(
props.update_rate or 30,
self.update,
props.telemetry
)
self:update()
end
function TelemetryLabel:update()
self:set_text(string.format(self._format, self._telemetry:value()))
end
-- Create instances
local rpm = TelemetryLabel(page, {
x = 10, y = 10,
telemetry = dash.telemetry.engine_rpm,
format = "%d RPM"
})
local speed = TelemetryLabel(page, {
x = 10, y = 40,
telemetry = dash.telemetry.speed,
format = "%.0f km/h"
})
The defaults table in extend() provides default creation parameters.
Child defaults override parent defaults. The init method is called
automatically after the node is created.
Multi-level inheritance is supported:
local WarningLabel = TelemetryLabel:extend({
text_color = 0xFFFF0000 -- red text
})
Layout Helpers
Two helpers simplify positioning nodes:
align_in_parent – position a node within its parent using a 9-point grid:
label:align_in_parent("center")
label:align_in_parent("bottom_right", -10, -10)
anchor_to – position a node relative to a sibling:
-- Place label2 below label1 with 5px gap
label2:anchor_to(label1, "bottom_left", "top_left", 0, 5)
Multiple Pages
Dashboards can have multiple pages. The first page created is shown initially:
local dash = require("dash")
local page1 = dash.Page()
local page2 = dash.Page()
dash.Label(page1, { text="Page 1", font=dash.font.main_font })
dash.Label(page2, { text="Page 2", font=dash.font.main_font })
while true do
-- Switch pages with a button press
if dash.wheel.get_digital_input(1) then
local current = dash.get_current_page_index()
dash.change_page(current == 1 and 2 or 1)
end
dash.update()
end
Unit Conversion
Display values in the user’s preferred units:
local speed_ms = dash.telemetry.speed:value()
local speed = dash.unit.convert_to_user_speed(speed_ms)
local unit_str = dash.unit.get_user_speed_unit()
label:set_text(string.format("%.0f %s", speed, unit_str))
Input units are always SI: m/s for speed, meters for distance, celsius for temperature, litres for volume, kPa for pressure.
Modular Scripts
Organize larger dashboards into multiple script files. All scripts listed
in the JSON "scripts" section are loaded before the main script runs.
Use require() to access them:
{
"scripts": {
"main": "script/main.lua",
"widgets": "script/widgets.lua",
"style": "script/style.lua"
}
}
-- style.lua
local style = {}
style.COLOR_BG = 0xFF1A1A1A
style.COLOR_TEXT = 0xFFFFFFFF
style.COLOR_ACCENT = 0xFF00AAFF
return style
-- main.lua
local dash = require("dash")
local style = require("style")
local page = dash.Page()
page:set_background_color(style.COLOR_BG)
Editor Support
The .lua files in this documentation directory are LuaLS-annotated
definition stubs. Configure your editor to use them as a workspace library
for autocompletion, type checking, and hover documentation.
See the readme.md file in this directory for setup instructions for
VS Code (EmmyLua and LuaLS extensions) and other editors.