Product Documentation
Guides and examples for CubeLab hardware, the browser simulation, and the secure console environment.
CubeLab
The CubeLab is a 4×4×4 LED matrix controlled by a microcontroller. It’s typically powered by a single-cell 3.7 V battery (with a regulated 5 V rail for logic, if required) and consists of 64 LEDs arranged in 4 layers of 16 columns.
- Layers (example): [A0, A1, A2, A3]
- Columns (example): [A4, A5, RX(D0), TX(D1), D2, D3, D4, D5, D6, D7, D8, D9, D10, D11, D12, D13]
Diagram of CubeLab wiring & connections:

Steps to Operate the CubeLab
- Install the Arduino IDE. Download it from the official site and follow the guide for your OS.
- Set up the CubeLab.
- Kit: Watch the assembly video; ensure all connections match your pin map.
- Finished unit: Proceed to connect to your microcontroller/USB and verify power.
- Write code. Open the Arduino IDE and create your sketch. See examples below.
- Upload code. Select the correct board & port, then upload.
- Power the CubeLab. Use a charged 3.7 V battery (with proper regulation for your board as needed).
Examples (Arduino / C++)
Tip: use arrays for pin maps rather than numeric ranges so you don’t depend on implicit numbering of A/D pins.
// Adjust pins to your board:
// Note TX is D1; if you include TX, don't duplicate D1 again.
const int layers[4] = {A0, A1, A2, A3};
const int columns[16]= {A4, A5, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13};
void setup() {
for (int i = 0; i < 4; i++) pinMode(layers[i], OUTPUT);
for (int i = 0; i < 16; i++) pinMode(columns[i], OUTPUT);
// Default OFF: columns LOW? depends on your wiring (common anode/cathode).
// Set a known state here:
for (int i = 0; i < 16; i++) digitalWrite(columns[i], LOW);
for (int i = 0; i < 4; i++) digitalWrite(layers[i], HIGH); // e.g., HIGH = layer off
}// Example assumes: column HIGH turns LED on, layer LOW selects layer (inverted).
void loop() {
int col = columns[0]; // pick a column
int lay = layers[0]; // pick a layer
digitalWrite(col, HIGH); // turn on column
digitalWrite(lay, LOW); // enable layer
delay(500);
// clear
digitalWrite(col, LOW);
digitalWrite(lay, HIGH);
delay(500);
}void loop() {
int col = columns[1];
int lay = layers[0];
// on
digitalWrite(col, HIGH);
digitalWrite(lay, LOW);
delay(500);
// off
digitalWrite(col, LOW);
digitalWrite(lay, HIGH);
delay(500);
}void loop() {
int lay = layers[0];
// turn all columns on
for (int i = 0; i < 16; i++) digitalWrite(columns[i], HIGH);
digitalWrite(lay, LOW);
delay(600);
// clear
for (int i = 0; i < 16; i++) digitalWrite(columns[i], LOW);
digitalWrite(lay, HIGH);
delay(400);
}void loop() {
for (int l = 0; l < 4; l++) {
// Turn on some pattern (here: all columns)
for (int i = 0; i < 16; i++) digitalWrite(columns[i], HIGH);
digitalWrite(layers[l], LOW); // enable layer
delay(3); // small persistence
digitalWrite(layers[l], HIGH); // disable
// Clear columns if needed for next step
for (int i = 0; i < 16; i++) digitalWrite(columns[i], LOW);
}
}void loop() {
// naive demo; adapt to your physical mapping (x,y,z -> index in columns[] & layers[])
for (int i = 0; i < 4; i++) {
int col = columns[i]; // diagonal "column"
int lay = layers[i];
digitalWrite(col, HIGH);
digitalWrite(lay, LOW);
delay(200);
digitalWrite(col, LOW);
digitalWrite(lay, HIGH);
}
}Simulation
LED Cube Simulation
Design and test Arduino-style sketches for a 4×4×4 LED cube directly in the browser. Pick a product (pin map), choose a color with the picker, paste a sketch, and run it live on the 3D cube.
loop() on the next yield.Sketch API (C++ like)
void setup()— runs once.void loop()— repeats until you press Stop.pinMode(PIN, MODE)— accepted (no-op in sim, for compatibility).digitalWrite(PIN, HIGH|LOW)— toggles a layer pin (e.g.A0..A3) or a column pin (depends on product pin map).delay(ms)— pauses; cancellable by Stop.- Helpers:
layerOn/Off(y),columnOn/Off(x,z),ledOn/Off(x,y,z),setColor("#hex")(optional; the color picker also sets LED color).
Coordinates are 0…3. Columns are addressed by (x,z), layers by y. The exact pin names for columns vary by product; see the Pin Map on the simulation page. Multiple statements on one line are supported (;), but one per line is clearer.
void setup() {
// quick layer pulse
digitalWrite(A0, HIGH);
delay(200);
digitalWrite(A0, LOW);
}
void loop() {
// blink one voxel
ledOn(1, 1, 1);
delay(200);
ledOff(1, 1, 1);
delay(200);
}// Keep all layers on for a tall beam
void setup() {
layerOn(0);
layerOn(1);
layerOn(2);
layerOn(3);
}
void loop() {
// Perimeter spin (clockwise)
columnOn(0, 0); delay(90); columnOff(0, 0);
columnOn(1, 0); delay(90); columnOff(1, 0);
columnOn(2, 0); delay(90); columnOff(2, 0);
columnOn(3, 0); delay(90); columnOff(3, 0);
columnOn(3, 1); delay(90); columnOff(3, 1);
columnOn(3, 2); delay(90); columnOff(3, 2);
columnOn(3, 3); delay(90); columnOff(3, 3);
columnOn(2, 3); delay(90); columnOff(2, 3);
columnOn(1, 3); delay(90); columnOff(1, 3);
columnOn(0, 3); delay(90); columnOff(0, 3);
columnOn(0, 2); delay(90); columnOff(0, 2);
columnOn(0, 1); delay(90); columnOff(0, 1);
// Layer scan with all columns on
columnOn(0,0); columnOn(1,0); columnOn(2,0); columnOn(3,0);
columnOn(0,1); columnOn(1,1); columnOn(2,1); columnOn(3,1);
columnOn(0,2); columnOn(1,2); columnOn(2,2); columnOn(3,2);
columnOn(0,3); columnOn(1,3); columnOn(2,3); columnOn(3,3);
layerOn(0); delay(120); layerOff(0);
layerOn(1); delay(120); layerOff(1);
layerOn(2); delay(120); layerOff(2);
layerOn(3); delay(150); layerOff(3);
columnOff(0,0); columnOff(1,0); columnOff(2,0); columnOff(3,0);
columnOff(0,1); columnOff(1,1); columnOff(2,1); columnOff(3,1);
columnOff(0,2); columnOff(1,2); columnOff(2,2); columnOff(3,2);
columnOff(0,3); columnOff(1,3); columnOff(2,3); columnOff(3,3);
delay(120);
}void setup() {
layerOff(0); layerOff(1); layerOff(2); layerOff(3);
}
void loop() {
// Drop at (x=1,z=2)
columnOn(1,2); layerOn(3); delay(90); layerOff(3); columnOff(1,2);
columnOn(1,2); layerOn(2); delay(90); layerOff(2); columnOff(1,2);
columnOn(1,2); layerOn(1); delay(90); layerOff(1); columnOff(1,2);
columnOn(1,2); layerOn(0); delay(120); layerOff(0); columnOff(1,2);
// Drop at (x=2,z=1)
columnOn(2,1); layerOn(3); delay(90); layerOff(3); columnOff(2,1);
columnOn(2,1); layerOn(2); delay(90); layerOff(2); columnOff(2,1);
columnOn(2,1); layerOn(1); delay(90); layerOff(1); columnOff(2,1);
columnOn(2,1); layerOn(0); delay(120); layerOff(0); columnOff(2,1);
// Drop at (x=0,z=3)
columnOn(0,3); layerOn(3); delay(90); layerOff(3); columnOff(0,3);
columnOn(0,3); layerOn(2); delay(90); layerOff(2); columnOff(0,3);
columnOn(0,3); layerOn(1); delay(90); layerOff(1); columnOff(0,3);
columnOn(0,3); layerOn(0); delay(120); layerOff(0); columnOff(0,3);
delay(120);
}Secure Console & Tailscale
Secure Console Environment
The Secure Console lets you open a temporary, authenticated web terminal to shared lab devices (Kria, Jetson, Coral) without exposing them to the public internet. Under the hood, the devices live on a Tailscale network so teammates can also use normal ssh from their own machines.
rm -rf, random reboot, etc.) unless you know what you are doing. Sessions may be logged.Using the browser Secure Console
- Get accessYour instructor / project owner will:
- Issue you a device console URL (for example: a link from the Devices page).
- Optionally share the current username/password for SSH and the web terminal.
- Open the Devices pageGo to
/devicesin the Cube Laboratory site and pick a device card (e.g. Kria KV260). - Launch the Secure ConsoleClick "Open web terminal". A new page will open with a full-screen xterm.js terminal.
- Log in to the deviceUse the credentials provided by the owner. For our Kria setup, a common example is:Username:
rootPassword:tubuntuThese may change for security reasons—always follow your instructor's latest instructions.
- Work inside the sessionUse the terminal like a normal Linux shell: run
htop, check logs, start services, etc. When you close the browser tab or your token expires, access is revoked.
Tailscale setup for SSH (project teammates)
If you're part of the project team, you can also connect directly via ssh over Tailscale. This gives you a familiar terminal in your own terminal app (VS Code, iTerm, Windows Terminal, etc.).
- Install Tailscale on your laptop/desktop
- Go to the Tailscale download page and install for your OS.
- Launch Tailscale and log in with your school or GitHub/Google account.
- Join the project tailnet
Your project owner will send you an invite link to the tailnet used for the Kria/Jetson/Coral devices. Click the link and accept the invite. You should now see your machine as "Active" in the Tailscale UI.
- Ensure the board is online in Tailscale
On the owner's side, the Kria (or other board) runs the Tailscale client and appears with a name like
kria-labor a100.x.y.zTailscale IP address. You do not need a public IP; Tailscale handles the secure tunnel. - Connect via SSH
From your local terminal, use
sshto the Tailscale hostname or IP:SSH over Tailscalebash# Example: using Tailscale hostname ssh root@kria-lab # or ubuntu@kria-lab, depending on the device user # Example: using Tailscale IP ssh root@100.x.y.z # replace with the board's Tailscale IPIf Tailscale SSH is enabled in the admin console, you may not need to manage SSH keys manually—Tailscale can authorize based on your login.
- Keep the environment healthy
- Coordinate reboots or heavy changes with the rest of the team.
- Don't change network or Tailscale configuration on the device unless you own it.
- Log out when you are done (
exitin SSH, close the browser tab for the Secure Console).