Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .github/workflows/build-deb.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,15 @@ jobs:
runner: ubuntu-22.04-arm
container: "debian:trixie"
extra_deps: ""
# Modalix eLxr 12 (aria) is Debian 12 / Python 3.11 class. No public eLxr
# container on GH runners yet — build in debian:bookworm and name the
# package ark-os-modalix-bookworm for install testing; prefer a native
# aarch64 eLxr 12 host for ark-os-modalix-aria release builds.
- platform: modalix
codename: bookworm
runner: ubuntu-22.04-arm
container: "debian:bookworm"
extra_deps: ""
# Build each platform on a host whose OS matches the target so the bundled venv
# and natively-compiled binaries are ABI-compatible. The venv is created with
# `python3 -m venv --copies`, which ships the interpreter but resolves the stdlib
Expand All @@ -47,6 +56,7 @@ jobs:
# - jetson: ubuntu-22.04-arm natively (Jammy / Python 3.10 = JetPack 6 rootfs)
# - pi: a debian:bookworm container (Debian 12 / Python 3.11 = Raspberry Pi OS Bookworm)
# - pi: a debian:trixie container (Debian 13 / Python 3.13 = Raspberry Pi OS Trixie)
# - modalix: debian:bookworm container (Python 3.11 ≈ eLxr 12 / aria)
# The container runs on the same arm64 host. Each leg's `codename` (the host's OS
# release) is part of the package name (ark-os-<platform>-<codename>); supporting a
# new release is one more leg here plus an entry in build.sh's BASELINES table.
Expand Down
22 changes: 19 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@
ARK-OS is a collection of software services and tools for drones. These services provide essential features such as mavlink routing, video streaming, automatic flight log upload, flight controller firmware updating, network RTK corrections, and more.

#### Supported targets
- **ARK Jetson PAB Carrier / PAB Carrier V3** <br> https://arkelectron.com/product/ark-jetson-pab-carrier/
- **ARK Just a Jetson** <br> https://arkelectron.com/product/ark-just-a-jetson/
- **ARK Jetson PAB Carrier / PAB Carrier V3** (Jetson Orin NX/Nano) <br> https://arkelectron.com/product/ark-jetson-pab-carrier/
- **ARK Jetson PAB Carrier V3 + SiMa Modalix SoM** (eLxr) — platform `modalix` (see `platform/modalix/README.md`)
- **ARK Just a Jetson** (Jetson Orin NX/Nano) <br> https://arkelectron.com/product/ark-just-a-jetson/
- **ARK Just a Jetson + SiMa Modalix SoM** (eLxr) — platform `modalix`
- **ARK Pi6X Flow** <br> https://arkelectron.com/product/ark-pi6x-flow/
- **ARK Just a Pi** <br> https://arkelectron.com/product/ark-just-a-pi/

Expand All @@ -17,6 +19,7 @@ ssh <user>@<hostname>.local
|--------|----------|----------|
| jetson | jetson | jetson |
| pi | pi | pi6x |
| sima | edgeai | modalix (SiMa eLxr / JAJ+Modalix) |

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
| sima | edgeai | modalix (SiMa eLxr / JAJ+Modalix) |
| sima | edgeai | modalix |

?


Connect to your WiFi network using Network Manager
```
Expand Down Expand Up @@ -58,6 +61,19 @@ sudo apt install ./ark-os-jetson-jammy_<ark-os-ver>_arm64.deb
sudo apt install -y python3-pip && sudo pip3 install "jetson-stats==<jetson-stats-ver>"
```

#### Modalix (SiMa eLxr 12 / JAJ or PAB V3 + Modalix)
Flash the SiMa eLxr base image first (see meta-ark-simaai bring-up), then install the
`modalix` package as user **`sima`**. No jetson-stats. DDS defaults to **UDP Ethernet
:8888** (not Telem2 UART); change it from the Services tab. Build on arm64 Debian 12
or eLxr 12:

```
./packaging/build.sh modalix --version=X.Y.Z
sudo ./packaging/install_ark_os.sh --platform=modalix ./ark-os-modalix-bookworm_X.Y.Z_arm64.deb
```

Details: [`platform/modalix/README.md`](platform/modalix/README.md).

#### Raspberry Pi
Identical, replacing `ark-os-jetson-jammy` with `ark-os-pi-trixie` for Raspberry Pi OS Trixie (Debian 13) — use `ark-os-pi-bookworm` on Debian 12. No jetson-stats step:
```
Expand Down Expand Up @@ -128,7 +144,7 @@ The package installs the services below as system-level [systemd services](https
This service enables mavlink-router to route mavlink packets from the flight controller USB port to user defined UDP endpoints. You can add and remove endpoints using the service configuration editor in the UI.

**dds-agent.service** <br>
The dds-agent service bridges the PX4 uORB and ROS2 topics. The bridged topics are defined in PX4 Firmware and can be [found here](https://github.com/PX4/PX4-Autopilot/blob/main/src/modules/uxrce_dds_client/dds_topics.yaml). The **dds-agent** runs the [micro-xrce-dds-agent](https://github.com/eProsima/Micro-XRCE-DDS-Agent) over the high speed serial connection between flight controller and Companion.
The dds-agent service bridges the PX4 uORB and ROS2 topics. The bridged topics are defined in PX4 Firmware and can be [found here](https://github.com/PX4/PX4-Autopilot/blob/main/src/modules/uxrce_dds_client/dds_topics.yaml). The **dds-agent** runs the [micro-xrce-dds-agent](https://github.com/eProsima/Micro-XRCE-DDS-Agent). Transport is configured from the Services tab (`dds-agent.toml`): **serial** (device + baud rate) or **ethernet** / **tcp** (port + companion IP). Defaults are serial Telem2 on Jetson (`/dev/ttyTHS1`, 3 Mbaud) and Pi (`/dev/ttyAMA4`), and UDP Ethernet port **8888** on Modalix (UART1/Telem2 is not usable on that SoM).

**logloader.service** <br>
This service downloads log files from the SD card of the flight controller via MAVLink and optionally uploads them to [PX4 Flight Review](https://review.px4.io/). Driven from the web UI's **Logs** page.
Expand Down
121 changes: 98 additions & 23 deletions frontend/src/pages/SystemPage.vue
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,9 @@
:data="platformData"
/>

<!-- Libraries Card -->
<!-- Libraries Card (Jetson NVIDIA stack only) -->
<SystemCard
v-if="showLibrariesCard"
title="Libraries"
icon="fa-book"
:data="librariesData"
Expand Down Expand Up @@ -87,17 +88,24 @@
</div>
</div>

<div class="memory-item">
<div
v-for="(disk, idx) in diskVolumes"
:key="disk.device || idx"
class="memory-item"
>
<ProgressBar
label="Disk"
:value="diskPercent"
:label="diskLabel(disk)"
:value="disk.percent || 0"
:showPercentage="true"
:warningThreshold="80"
:criticalThreshold="90"
/>
<div class="memory-info">
<span>{{ formatDiskSize(diskTotal) }} Total</span>
<span>{{ formatDiskSize(diskAvailable) }} Available</span>
<span>{{ formatDiskSize(disk.total) }} Total</span>
<span v-if="disk.mountpoint">
{{ formatDiskSize(disk.available) }} Available
</span>
<span v-else class="disk-unmounted">Not mounted</span>
</div>
</div>
</div>
Expand Down Expand Up @@ -145,17 +153,34 @@ export default {
'Unknown';
},

deviceType() {
return this.systemInfo?.device_type || 'generic';
},

isJetson() {
return this.deviceType === 'jetson';
},

showLibrariesCard() {
// NVIDIA CUDA stack is Jetson-only; hide empty "Not available" wall on Modalix/Pi
return this.isJetson;
},

hardwareData() {
const hw = this.systemInfo?.hardware;
if (!hw) return { 'Status': 'Data unavailable' };

return {
const data = {
'Model': hw.model || 'Not available',
'Module': hw.module || 'Not available',
'Serial': hw.serial_number || 'Not available',
'L4T': hw.l4t || 'Not available',
'JetPack': hw.jetpack || 'Not available'
'Serial': hw.serial_number || 'Not available'
};
// L4T / JetPack only on real Jetson
if (this.isJetson) {
data['L4T'] = hw.l4t || 'Not available';
data['JetPack'] = hw.jetpack || 'Not available';
}
return data;
},

platformData() {
Expand All @@ -173,8 +198,9 @@ export default {

librariesData() {
const libs = this.systemInfo?.libraries;

// Always show these fields, even if not available
if (!this.isJetson) {
return {};
}
return {
'CUDA': libs?.cuda || 'Not available',
'OpenCV': libs?.opencv || 'Not available',
Expand All @@ -190,19 +216,32 @@ export default {
const data = {};
const power = this.systemInfo?.power;

// Power information (primarily for Jetson)
data['Power Mode'] = power?.nvpmodel || 'Not available';
data['Jetson Clocks'] = power?.jetson_clocks || 'Not available';
data['Power Draw'] = power?.total ? `${(power.total / 1000).toFixed(2)} W` : 'Not available';

// Primary temperatures
const temps = this.systemInfo?.temperature || power?.temperature;
if (temps) {
if (this.isJetson) {
data['Power Mode'] = power?.nvpmodel || 'Not available';
data['Jetson Clocks'] = power?.jetson_clocks || 'Not available';
}
// Rail voltage / current when provided (Modalix INA238)
if (power?.voltage != null && Number(power.voltage) > 0) {
data['Voltage'] = `${Number(power.voltage).toFixed(2)} V`;
}
if (power?.current != null && Number.isFinite(Number(power.current))) {
data['Current'] = `${Number(power.current).toFixed(3)} A`;
}
// Jetson jtop and Modalix INA238 both use power.total in milliwatts
data['Power Draw'] =
power?.total && power.total > 0
? `${(power.total / 1000).toFixed(2)} W`
: 'Not available';

// Primary temperatures (Jetson has cpu/gpu; Modalix may have ina238 die temp)
const temps = this.systemInfo?.temperature || power?.temperature || {};
if (this.isJetson) {
data['CPU Temp'] = temps.cpu ? `${temps.cpu.toFixed(1)}°C` : 'Not available';
data['GPU Temp'] = temps.gpu ? `${temps.gpu.toFixed(1)}°C` : 'Not available';
} else {
data['CPU Temp'] = 'Not available';
data['GPU Temp'] = 'Not available';
} else if (temps.ina238 != null) {
data['PMIC Temp'] = `${Number(temps.ina238).toFixed(1)}°C`;
} else if (temps.cpu != null) {
data['CPU Temp'] = `${Number(temps.cpu).toFixed(1)}°C`;
}

return data;
Expand Down Expand Up @@ -301,6 +340,28 @@ export default {
diskAvailable() {
const disk = this.systemInfo?.resources?.disk || this.systemInfo?.disk;
return disk?.available || 0;
},

/** All physical disks (eMMC + NVMe etc.); fall back to root-only disk field. */
diskVolumes() {
const disks = this.systemInfo?.resources?.disks;
if (Array.isArray(disks) && disks.length > 0) {
return disks;
}
const disk = this.systemInfo?.resources?.disk || this.systemInfo?.disk;
if (!disk) return [];
return [
{
name: 'Disk',
device: '/',
mountpoint: '/',
total: disk.total || 0,
used: disk.used || 0,
available: disk.available || 0,
percent: disk.percent || 0,
model: ''
}
];
}
},

Expand Down Expand Up @@ -406,7 +467,16 @@ export default {

formatDiskSize(sizeInGB) {
if (!sizeInGB) return '0 GB';
if (sizeInGB >= 100) return `${sizeInGB.toFixed(0)} GB`;
return `${sizeInGB.toFixed(1)} GB`;
},

diskLabel(disk) {
if (!disk) return 'Disk';
const name = disk.name || disk.device || 'Disk';
if (disk.mountpoint === '/') return `${name} (/)`;
if (disk.mountpoint) return `${name} (${disk.mountpoint})`;
return name;
}
}
}
Expand Down Expand Up @@ -522,6 +592,11 @@ export default {
font-weight: 500;
}

.disk-unmounted {
font-style: italic;
color: #999;
}

.temp-scroll-container {
max-height: 120px;
overflow-y: auto;
Expand Down
38 changes: 36 additions & 2 deletions frontend/src/pages/components/TomlEditor.vue
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
</a>

<label :for="key">{{ formatKey(key) }}</label>
<p v-if="fieldHelpText(key)" class="field-help">{{ fieldHelpText(key) }}</p>

<!-- Dropdown when a sibling <field>_options array is present -->
<select
Expand Down Expand Up @@ -166,7 +167,7 @@ import serviceUiHints from './serviceUiHints.js';

// Display-only casing for tokens the sentence-case default would mangle
// ("Ntrip host"); config keys and saved values stay lowercase.
const ACRONYMS = new Set(['ntrip', 'rtcm', 'spartn', 'gga', 'mga', 'tls', 'url', 'api']);
const ACRONYMS = new Set(['ntrip', 'rtcm', 'spartn', 'gga', 'mga', 'tls', 'url', 'api', 'udp', 'tcp', 'ip']);

export default {
name: 'TomlEditor',
Expand Down Expand Up @@ -194,6 +195,9 @@ export default {
if (this.isOptionsField(key)) {
return false;
}
if (!this.fieldVisible(key)) {
return false;
}
const value = this.config[key];
return !this.isObject(value);
});
Expand Down Expand Up @@ -223,6 +227,22 @@ export default {
return typeof value === 'object' && value !== null && !Array.isArray(value);
},

// Hide a field when uiHints.visibleWhen[field] does not match live config.
fieldVisible(key) {
const rules = (this.uiHints.visibleWhen || {})[key];
if (!rules) {
return true;
}
return Object.entries(rules).every(([field, values]) => {
const allowed = Array.isArray(values) ? values : [values];
return allowed.includes(this.config[field]);
});
},

fieldHelpText(key) {
return (this.uiHints.fieldHelp || {})[key] || '';
},

// Check if a field is an options array (ends with _options)
isOptionsField(key) {
return key.endsWith('_options');
Expand Down Expand Up @@ -437,7 +457,13 @@ export default {
// Dropdown option display text; the underlying value is unchanged
formatValue(option) {
const token = String(option).toLowerCase();
return ACRONYMS.has(token) ? token.toUpperCase() : option;
if (ACRONYMS.has(token)) {
return token.toUpperCase();
}
if (!token) {
return option;
}
return token.charAt(0).toUpperCase() + token.slice(1);
}
}
};
Expand Down Expand Up @@ -494,6 +520,14 @@ h1 {
color: var(--ark-color-black);
}

.field-help {
font-size: 13px;
font-weight: 400;
color: var(--ark-color-grey);
margin: -4px 0 8px 0;
line-height: 1.4;
}

.callout-link {
display: flex;
align-items: center;
Expand Down
22 changes: 21 additions & 1 deletion frontend/src/pages/components/serviceUiHints.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@
// top-level field when beforeField is set. The TOML parser drops
// comments, so guidance like this can't come from the config file
// itself.
// visibleWhen: { fieldName: { otherField: ['value', ...] } }
// Hide a top-level field unless every listed sibling matches.
// fieldHelp: { fieldName: 'text' }
// Description shown under the field label.

export default {
pointperfect: {
Expand All @@ -19,5 +23,21 @@ export default {
url: 'https://portal.thingstream.io/register?token=eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJjdG9rOmYzNGRkMTg2LWNjYWUtNDZhMS1hNWRmLWRiM2UxYjMwZWI5MCIsImF1ZCI6IlRoaW5nc3RyZWFtIiwiY21wIjoicmVkZW1wdGlvbi1jYW1wYWlnbjphZmViMmY4OS05MDMzLTQ1YjAtOGE4NC0wZjBhZGVjMzA3MWUifQ.Y0gn8ai-0eJ7LVs8k1uthvj7LM0WSXhefR70p3l6w6XWBD9mrO99NYdal1NS7wfi1DGyM21TMExaXUnFoxVYSA'
}
]
}
},
'dds-agent': {
// Show serial vs Ethernet fields based on transport.
visibleWhen: {
device: { transport: ['serial'] },
baudrate: { transport: ['serial'] },
port: { transport: ['ethernet', 'udp', 'tcp'] },
address: { transport: ['ethernet', 'udp', 'tcp'] },
},
fieldHelp: {
transport: 'Serial is Telem2 UART. Ethernet is UDP. TCP is rarely needed.',
device: 'UART device, e.g. /dev/ttyTHS1 (Jetson) or /dev/ttyAMA4 (Pi).',
baudrate: 'UART baud rate. PX4 Telem2 is typically 3000000.',
address: 'Companion IP the flight controller should use (PX4 UXRCE_DDS_AG_IP). The agent listens on all interfaces.',
port: 'Listen port. Match PX4 UXRCE_DDS_PRT (default 8888).',
},
},
};
2 changes: 1 addition & 1 deletion packaging/DEBIAN/control
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,4 @@ Description: ARK-OS companion computer platform
Pre-compiled services, web UI, and system configuration for autonomous vehicles.
.
The @PLATFORM@ package installs system-level systemd units that run as the
default platform user. The Pi package assumes the default "pi" user exists.
default platform user (jetson / pi / sima for modalix).
Loading
Loading