mirror of
https://github.com/meshtastic/Meshtastic-Android.git
synced 2026-09-22 03:35:06 -04:00
docs: publish channels: main api
This commit is contained in:
commit
8ebb84c5ef
10921 files changed
+1871564
No files matched your search
@@ -0,0 +1,146 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" dir="ltr">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Adding a Feature Module</title>
|
||||
<link rel="stylesheet" href="../styles/docs.css">
|
||||
</head>
|
||||
<body data-page="adding-a-feature-module" data-locale="en">
|
||||
<pre class="markdown-content"># Adding a Feature Module
|
||||
|
||||
Step-by-step guide for creating a new KMP feature module in the Meshtastic project.
|
||||
|
||||
## 1. Create the Module Directory
|
||||
|
||||
```bash
|
||||
mkdir -p feature/my-feature/src/{commonMain,commonTest,androidMain,jvmMain,iosMain}/kotlin/org/meshtastic/feature/myfeature
|
||||
```
|
||||
|
||||
## 2. Create `build.gradle.kts`
|
||||
|
||||
```kotlin
|
||||
plugins {
|
||||
alias(libs.plugins.meshtastic.kmp.feature)
|
||||
}
|
||||
|
||||
kotlin {
|
||||
android { withHostTest { isIncludeAndroidResources = true } }
|
||||
|
||||
sourceSets {
|
||||
commonMain.dependencies {
|
||||
implementation(projects.core.common)
|
||||
implementation(projects.core.navigation)
|
||||
implementation(projects.core.resources)
|
||||
implementation(projects.core.ui)
|
||||
implementation(projects.core.di)
|
||||
}
|
||||
|
||||
commonTest.dependencies {
|
||||
implementation(libs.compose.multiplatform.ui.test)
|
||||
}
|
||||
|
||||
jvmTest.dependencies {
|
||||
implementation(compose.desktop.currentOs)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 3. Register in `settings.gradle.kts`
|
||||
|
||||
Add your module to the main `include()` block:
|
||||
|
||||
```kotlin
|
||||
include(
|
||||
// ...existing modules...
|
||||
":feature:my-feature",
|
||||
)
|
||||
```
|
||||
|
||||
## 4. Create the DI Module
|
||||
|
||||
`src/commonMain/kotlin/org/meshtastic/feature/myfeature/di/FeatureMyFeatureModule.kt`:
|
||||
|
||||
```kotlin
|
||||
package org.meshtastic.feature.myfeature.di
|
||||
|
||||
import org.koin.core.annotation.ComponentScan
|
||||
import org.koin.core.annotation.Module
|
||||
|
||||
@Module
|
||||
@ComponentScan("org.meshtastic.feature.myfeature")
|
||||
class FeatureMyFeatureModule
|
||||
```
|
||||
|
||||
## 5. Register DI in App/Desktop
|
||||
|
||||
Add your module to:
|
||||
- `androidApp/src/main/kotlin/org/meshtastic/app/di/AppKoinModule.kt`
|
||||
- `desktopApp/src/main/kotlin/org/meshtastic/desktop/di/DesktopKoinModule.kt`
|
||||
|
||||
## 6. Add Navigation Routes
|
||||
|
||||
In `core/navigation/src/commonMain/kotlin/org/meshtastic/core/navigation/Routes.kt`:
|
||||
|
||||
```kotlin
|
||||
@Serializable
|
||||
sealed interface MyFeatureRoute : Route {
|
||||
@Serializable data object MyFeatureGraph : MyFeatureRoute, Graph
|
||||
@Serializable data object MyFeatureHome : MyFeatureRoute
|
||||
}
|
||||
```
|
||||
|
||||
## 7. Create Navigation Entries
|
||||
|
||||
`src/commonMain/kotlin/org/meshtastic/feature/myfeature/navigation/MyFeatureNavigation.kt`:
|
||||
|
||||
```kotlin
|
||||
package org.meshtastic.feature.myfeature.navigation
|
||||
|
||||
import androidx.navigation3.runtime.EntryProviderScope
|
||||
import androidx.navigation3.runtime.NavBackStack
|
||||
import androidx.navigation3.runtime.NavKey
|
||||
import org.meshtastic.core.navigation.MyFeatureRoute
|
||||
|
||||
fun EntryProviderScope<NavKey>.myFeatureGraph(backStack: NavBackStack<NavKey>) {
|
||||
entry<MyFeatureRoute.MyFeatureHome> {
|
||||
MyFeatureScreen()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 8. Source Set Guidelines
|
||||
|
||||
Source Set | Contains |
|
||||
-----------|----------|
|
||||
`commonMain` | Models, ViewModels, shared UI, DI module, navigation |
|
||||
`androidMain` | Android-specific implementations (e.g., platform APIs) |
|
||||
`jvmMain` | Desktop-specific implementations |
|
||||
`iosMain` | iOS-specific implementations |
|
||||
`commonTest` | Shared unit tests |
|
||||
|
||||
## 9. Testing Expectations
|
||||
|
||||
Every feature module should have:
|
||||
- Unit tests in `commonTest` for business logic
|
||||
- UI tests using `compose-multiplatform-ui-test` where appropriate
|
||||
- No test dependency on other feature modules
|
||||
|
||||
## 10. Checklist
|
||||
|
||||
- [ ] Module directory created
|
||||
- [ ] `build.gradle.kts` with correct plugins and dependencies
|
||||
- [ ] Added to `settings.gradle.kts`
|
||||
- [ ] DI module created with `@ComponentScan`
|
||||
- [ ] DI module registered in app and desktop roots
|
||||
- [ ] Routes added to `Routes.kt`
|
||||
- [ ] Navigation entries registered
|
||||
- [ ] `./gradlew kmpSmokeCompile` passes
|
||||
- [ ] `./gradlew :feature:my-feature:allTests` passes
|
||||
|
||||
---
|
||||
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,180 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" dir="ltr">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Architecture</title>
|
||||
<link rel="stylesheet" href="../styles/docs.css">
|
||||
</head>
|
||||
<body data-page="architecture" data-locale="en">
|
||||
<pre class="markdown-content"># Architecture
|
||||
|
||||
The Meshtastic Android and Desktop apps follow a modular Kotlin Multiplatform (KMP) architecture with clear layer boundaries (iOS is currently a compile-only validation target — there is no shipping iOS app yet).
|
||||
|
||||
## Layer Overview
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────┐
|
||||
│ androidApp / desktopApp │ Platform entry points
|
||||
├─────────────────────────────────────────────┤
|
||||
│ feature/* modules │ UI + Business Logic
|
||||
├─────────────────────────────────────────────┤
|
||||
│ core/* modules │ Shared infrastructure
|
||||
├─────────────────────────────────────────────┤
|
||||
│ Platform (Android/JVM/iOS) │ OS-specific bindings
|
||||
└─────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Module Categories
|
||||
|
||||
### `androidApp/` — Android Application
|
||||
|
||||
The Android application entry point:
|
||||
- Activity, Application, and Manifest definitions
|
||||
- Koin DI module composition (`AppKoinModule`)
|
||||
- Flavor-specific bindings (`google/`, `fdroid/`)
|
||||
- Android-only integrations (widgets, services)
|
||||
|
||||
### `desktopApp/` — Desktop JVM Application
|
||||
|
||||
The Desktop (Linux/macOS/Windows) entry point:
|
||||
- Compose Desktop window management
|
||||
- Desktop-specific DI (`DesktopKoinModule`)
|
||||
- Platform stubs for Android-only capabilities
|
||||
- BLE (Kable), Serial, and TCP transport implementations
|
||||
|
||||
### `feature/*` — Feature Modules
|
||||
|
||||
Each `feature/` module owns a vertical slice of functionality:
|
||||
|
||||
Module | Responsibility |
|
||||
--------|---------------|
|
||||
`feature:intro` | Onboarding/welcome flow |
|
||||
`feature:messaging` | Messages, channels, contacts, quick chat |
|
||||
`feature:connections` | Bluetooth/USB/TCP connection management |
|
||||
`feature:map` | Map display, waypoints |
|
||||
`feature:node` | Node list, node detail, metrics |
|
||||
`feature:settings` | All configuration screens |
|
||||
`feature:firmware` | Firmware update flow |
|
||||
`feature:docs` | In-app documentation browser |
|
||||
`feature:wifi-provision` | WiFi provisioning |
|
||||
`feature:widget` | Android home screen widgets |
|
||||
`feature:discovery` | Mesh network discovery |
|
||||
`feature:car` | Android Auto / Car App Library — google flavor only, conditionally registered in the google `FlavorModule` |
|
||||
|
||||
Feature modules:
|
||||
- Use the `meshtastic.kmp.feature` convention plugin
|
||||
- Depend on `core` modules, never on other `feature` modules
|
||||
- Own their navigation entries and DI registrations
|
||||
- Contain platform-specific implementations in `androidMain`/`jvmMain`/`iosMain`
|
||||
|
||||
### `core/*` — Core Modules
|
||||
|
||||
Shared infrastructure used by all features:
|
||||
|
||||
Module | Responsibility |
|
||||
--------|---------------|
|
||||
`core:common` | Utilities, extensions, build config |
|
||||
`core:navigation` | Routes, deep links, Navigation 3 |
|
||||
`core:ui` | Shared Compose components, icons, theme |
|
||||
`core:resources` | Shared string resources |
|
||||
`core:model` | Domain models |
|
||||
`core:data` | Data layer abstractions |
|
||||
`core:domain` | Use cases / business logic |
|
||||
`core:database` | Room KMP database |
|
||||
`core:datastore` | DataStore preferences |
|
||||
`core:prefs` | App preferences |
|
||||
`core:repository` | Repository interfaces |
|
||||
`core:service` | Mesh service layer |
|
||||
`core:di` | DI utilities |
|
||||
`core:network` | HTTP/serial/transport |
|
||||
`core:ble` | Bluetooth LE abstractions |
|
||||
`core:barcode` | QR / barcode scanning (channel-share QR codes) |
|
||||
`core:nfc` | NFC read/write support |
|
||||
`core:takserver` | Embedded TAK server integration |
|
||||
`core:testing` | Test utilities |
|
||||
`core:konsist` | Konsist architecture/convention tests |
|
||||
|
||||
Protobuf models are no longer a local module — they come from the external `org.meshtastic:protobufs` Maven artifact (pinned in `gradle/libs.versions.toml`).
|
||||
|
||||
## KMP Source Sets
|
||||
|
||||
Each module uses the standard KMP source set hierarchy:
|
||||
|
||||
```
|
||||
src/
|
||||
├── commonMain/ ← Shared code (all platforms)
|
||||
├── commonTest/ ← Shared tests
|
||||
├── androidMain/ ← Android-specific
|
||||
├── jvmMain/ ← Desktop JVM-specific
|
||||
├── iosMain/ ← iOS-specific
|
||||
└── jvmTest/ ← Desktop test host
|
||||
```
|
||||
|
||||
**Golden Rules:**
|
||||
- No `android.*` imports in `commonMain`
|
||||
- Platform-specific code goes in appropriate source set
|
||||
- Prefer interfaces + DI over `expect`/`actual` for complex behaviors
|
||||
- Use `expect`/`actual` only for simple declarations
|
||||
|
||||
## Dependency Injection
|
||||
|
||||
The project uses **Koin** with annotation processing:
|
||||
- `@Module`, `@Single`, `@Factory` annotations
|
||||
- `@ComponentScan` for automatic registration
|
||||
- Feature modules export their own `Feature*Module` class
|
||||
- App/Desktop compose all modules in their root DI configuration
|
||||
|
||||
## Radio Control
|
||||
|
||||
Features issue radio commands through `RadioController` (`core:repository`), a composite of four
|
||||
focused sub-interfaces so callers can depend on just the slice they need:
|
||||
|
||||
Sub-interface | Responsibility |
|
||||
---------------|---------------|
|
||||
`AdminController` | Config, channels, owner, device lifecycle, `editSettings { }` transactions |
|
||||
`MessagingController` | Send packets, reactions, shared contacts |
|
||||
`NodeController` | Favorite, ignore, mute, remove nodes |
|
||||
`QueryController` | Telemetry, traceroute, position/user-info queries |
|
||||
|
||||
`RadioControllerImpl` (`core:service`) is the in-process composition root for all targets
|
||||
(Desktop, iOS, single-process Android). It assembles the four sub-controllers via Kotlin interface
|
||||
delegation and adds the cross-cutting concerns (connection state, packet-id, location,
|
||||
device-address switching). Commands are direct suspend calls; admin writes are fire-and-forget
|
||||
because the device is the source of truth (local persistence is an optimistic cache). The layered
|
||||
shape mirrors the [meshtastic-sdk](https://github.com/meshtastic/meshtastic-sdk)
|
||||
`AdminApi`/`TelemetryApi` design to ease a future SDK migration.
|
||||
|
||||
## Service Repository
|
||||
|
||||
`ServiceRepository` is the reactive bridge between the mesh service and all feature/UI layers.
|
||||
It is decomposed into focused provider interfaces following the Interface Segregation Principle:
|
||||
|
||||
Interface | Responsibility |
|
||||
-----------|---------------|
|
||||
`ConnectionStateProvider` | Read-only `connectionState: StateFlow<ConnectionState>` |
|
||||
`TracerouteResponseProvider` | Traceroute response state + clear |
|
||||
`NeighborInfoResponseProvider` | Neighbor info response state + clear |
|
||||
`ServiceStateWriter` | Write-side for handlers (set*, emit*, clear*) |
|
||||
|
||||
`ServiceRepository` extends all four interfaces — consumers inject the narrowest interface
|
||||
they actually need. For example, `ContactsViewModel` injects only `ConnectionStateProvider`
|
||||
rather than the entire `ServiceRepository`, preventing accidental access to write operations
|
||||
from UI code. `RadioController` also extends `ConnectionStateProvider` so VMs that already
|
||||
inject a controller sub-interface can read connection state without a separate dependency.
|
||||
|
||||
## Navigation
|
||||
|
||||
Navigation uses **Navigation 3** with typed routes:
|
||||
- All routes defined in `core/navigation/Routes.kt`
|
||||
- Routes are `@Serializable` data classes/objects
|
||||
- Deep links resolved through `DeepLinkRouter`
|
||||
- Each feature registers its own navigation entries
|
||||
|
||||
See [Navigation & Deep Links](navigation-and-deep-links) for details.
|
||||
|
||||
---
|
||||
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,155 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" dir="ltr">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Codebase</title>
|
||||
<link rel="stylesheet" href="../styles/docs.css">
|
||||
</head>
|
||||
<body data-page="codebase" data-locale="en">
|
||||
<pre class="markdown-content"># Codebase
|
||||
|
||||
Repository layout, namespacing conventions, and build system overview.
|
||||
|
||||
## Repository Structure
|
||||
|
||||
```
|
||||
Meshtastic-Android/
|
||||
├── androidApp/ # Android application module
|
||||
│ ├── src/main/ # Shared Android code
|
||||
│ ├── src/google/ # Google Play flavor (Gemini, proprietary)
|
||||
│ └── src/fdroid/ # F-Droid flavor (FOSS-only)
|
||||
├── desktopApp/ # Desktop JVM application
|
||||
├── feature/ # Feature modules (KMP)
|
||||
│ ├── intro/
|
||||
│ ├── messaging/
|
||||
│ ├── connections/
|
||||
│ ├── map/
|
||||
│ ├── node/
|
||||
│ ├── settings/
|
||||
│ ├── firmware/
|
||||
│ ├── docs/
|
||||
│ ├── wifi-provision/
|
||||
│ ├── widget/
|
||||
│ ├── discovery/
|
||||
│ └── car/
|
||||
├── core/ # Core infrastructure modules (KMP)
|
||||
│ ├── barcode/
|
||||
│ ├── ble/
|
||||
│ ├── common/
|
||||
│ ├── data/
|
||||
│ ├── database/
|
||||
│ ├── datastore/
|
||||
│ ├── di/
|
||||
│ ├── domain/
|
||||
│ ├── konsist/
|
||||
│ ├── model/
|
||||
│ ├── navigation/
|
||||
│ ├── network/
|
||||
│ ├── nfc/
|
||||
│ ├── prefs/
|
||||
│ ├── repository/
|
||||
│ ├── resources/
|
||||
│ ├── service/
|
||||
│ ├── takserver/
|
||||
│ ├── testing/
|
||||
│ └── ui/
|
||||
├── baselineprofile/ # Baseline Profile generation for :androidApp
|
||||
├── screenshot-tests/ # Compose Preview screenshot tests (visual-regression gate)
|
||||
├── docs-screenshots/ # Doc-framed composition screenshots (generate-only, not CI-gated)
|
||||
├── build-logic/ # Convention plugins and build helpers
|
||||
│ └── convention/
|
||||
├── docs/ # Documentation source (markdown)
|
||||
│ └── en/ # English source; other locales live under docs/<locale>/user/
|
||||
│ ├── user/
|
||||
│ └── developer/
|
||||
├── gradle/ # Gradle wrapper and version catalog
|
||||
│ └── libs.versions.toml
|
||||
├── specs/ # Feature specifications
|
||||
└── .github/workflows/ # CI/CD workflows
|
||||
```
|
||||
|
||||
## Namespacing Convention
|
||||
|
||||
All Kotlin packages follow the pattern:
|
||||
```
|
||||
org.meshtastic.{layer}.{module}.{subpackage}
|
||||
```
|
||||
|
||||
Examples:
|
||||
- `org.meshtastic.core.navigation` — core navigation module
|
||||
- `org.meshtastic.feature.docs.ui` — docs feature UI package
|
||||
- `org.meshtastic.app.di` — app DI configuration
|
||||
|
||||
## Build System
|
||||
|
||||
### Gradle Kotlin DSL
|
||||
|
||||
All build files use Kotlin DSL (`.gradle.kts`). Configuration:
|
||||
|
||||
- **Version catalog:** `gradle/libs.versions.toml`
|
||||
- **Convention plugins:** `build-logic/convention/`
|
||||
- **Settings:** `settings.gradle.kts`
|
||||
|
||||
### Convention Plugins
|
||||
|
||||
Located in `build-logic/convention/src/main/kotlin/`:
|
||||
|
||||
Plugin | Purpose |
|
||||
--------|---------|
|
||||
`meshtastic.kmp.feature` | Standard feature module setup |
|
||||
`meshtastic.kmp.jvm.android` | JVM + Android target configuration |
|
||||
`meshtastic.kotlinx.serialization` | Serialization plugin setup |
|
||||
|
||||
### Build Variants (Android)
|
||||
|
||||
Flavor | Description |
|
||||
--------|-------------|
|
||||
`google` | Google Play distribution; includes proprietary APIs |
|
||||
`fdroid` | F-Droid distribution; FOSS-only dependencies |
|
||||
|
||||
### Key Gradle Tasks
|
||||
|
||||
```bash
|
||||
# Compile check across all KMP targets
|
||||
./gradlew kmpSmokeCompile
|
||||
|
||||
# Run all tests
|
||||
./gradlew allTests
|
||||
|
||||
# Code quality
|
||||
./gradlew spotlessCheck detekt
|
||||
|
||||
# Android build
|
||||
./gradlew assembleGoogleDebug assembleFdroidDebug
|
||||
|
||||
# Desktop run
|
||||
./gradlew :desktopApp:run
|
||||
|
||||
# Desktop native installers for the current OS (DMG / MSI+EXE / DEB+RPM+AppImage)
|
||||
./gradlew :desktopApp:packageReleaseDistributionForCurrentOS
|
||||
|
||||
# API reference (Dokka HTML → build/dokka/html)
|
||||
./gradlew dokkaGeneratePublicationHtml
|
||||
```
|
||||
|
||||
## Version Catalog Highlights
|
||||
|
||||
Key dependencies in `gradle/libs.versions.toml`:
|
||||
|
||||
Category | Library |
|
||||
----------|---------|
|
||||
Compose | Compose Multiplatform (JetBrains) |
|
||||
Navigation | Navigation 3 |
|
||||
DI | Koin (annotations) |
|
||||
Serialization | kotlinx.serialization |
|
||||
Database | Room KMP |
|
||||
Networking | Ktor |
|
||||
Markdown | multiplatform-markdown-renderer |
|
||||
Testing | kotlin-test, compose-ui-test |
|
||||
|
||||
---
|
||||
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,110 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" dir="ltr">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Contributing</title>
|
||||
<link rel="stylesheet" href="../styles/docs.css">
|
||||
</head>
|
||||
<body data-page="contributing" data-locale="en">
|
||||
<pre class="markdown-content"># Contributing
|
||||
|
||||
Guidelines for contributing to the Meshtastic Android/Desktop project (a KMP codebase that also compiles for iOS).
|
||||
|
||||
## Branch Naming
|
||||
|
||||
Branches use conventional-commit style prefixes:
|
||||
|
||||
Prefix | Use for |
|
||||
--------|---------|
|
||||
`feat/<scope>` | New user-visible behavior |
|
||||
`fix/<scope>` | Bug fixes |
|
||||
`refactor/<scope>` | Code structure changes |
|
||||
`chore/<scope>` | Tooling, deps, CI, cleanup |
|
||||
`docs/<scope>` | Documentation only |
|
||||
`build/<scope>` | Build system changes |
|
||||
`ci/<scope>` | CI workflow changes |
|
||||
`test/<scope>` | Test additions or fixes |
|
||||
`deps/<scope>` | Dependency updates |
|
||||
|
||||
Timestamp-based spec prefixes (`YYYYMMDD-HHMMSS-feature-name`, as created by `/speckit.git.feature`) are also valid for spec-driven work.
|
||||
|
||||
Examples:
|
||||
- `feat/desktop-ble-transport`
|
||||
- `fix/bluetooth-reconnect`
|
||||
- `20260601-074653-air-quality-telemetry`
|
||||
|
||||
## Development Workflow
|
||||
|
||||
1. **Fork** the repository (external contributors) or create a branch (maintainers).
|
||||
2. **Implement** your changes following the architecture guidelines.
|
||||
3. **Test** locally: `./gradlew spotlessCheck detekt kmpSmokeCompile test allTests`
|
||||
4. **Commit** with clear, descriptive messages.
|
||||
5. **Push** and open a Pull Request.
|
||||
|
||||
## Commit Messages
|
||||
|
||||
Follow conventional commit style:
|
||||
```
|
||||
feat(docs): add in-app documentation browser
|
||||
fix(ble): handle reconnection timeout
|
||||
refactor(navigation): migrate to typed routes
|
||||
test(search): add keyword ranking tests
|
||||
```
|
||||
|
||||
## Pull Request Checklist
|
||||
|
||||
Before submitting:
|
||||
- [ ] Code compiles on all targets: `./gradlew kmpSmokeCompile`
|
||||
- [ ] All tests pass: `./gradlew allTests`
|
||||
- [ ] Code style passes: `./gradlew spotlessCheck`
|
||||
- [ ] Static analysis passes: `./gradlew detekt`
|
||||
- [ ] New code has appropriate test coverage
|
||||
- [ ] No `android.*` imports in `commonMain`
|
||||
- [ ] Koin modules registered if new DI is added
|
||||
- [ ] Routes added to `Routes.kt` if new navigation is introduced
|
||||
- [ ] Documentation updated if user-facing behavior changes
|
||||
|
||||
## Code Style
|
||||
|
||||
- **Formatting:** Enforced by Spotless (KtLint rules)
|
||||
- **Static analysis:** Detekt with project-specific configuration
|
||||
- **Imports:** No wildcard imports; organized automatically by Spotless
|
||||
- **Line length:** 120 characters maximum
|
||||
|
||||
Run formatting:
|
||||
```bash
|
||||
./gradlew spotlessApply
|
||||
```
|
||||
|
||||
## Architecture Rules
|
||||
|
||||
- Feature modules must not depend on other feature modules
|
||||
- `commonMain` must not contain `android.*`, `java.io.*`, or platform-specific imports
|
||||
- Prefer interface + DI over `expect`/`actual` for complex platform behaviors
|
||||
- All navigation routes must be `@Serializable` and defined in `Routes.kt`
|
||||
- Use Koin annotations (`@Single`, `@Factory`, `@Module`) for dependency injection
|
||||
|
||||
## Verification
|
||||
|
||||
Full pre-merge verification:
|
||||
```bash
|
||||
./gradlew spotlessCheck detekt kmpSmokeCompile test allTests
|
||||
```
|
||||
|
||||
For docs-specific changes, also run:
|
||||
```bash
|
||||
./gradlew generateDocsBundle validateDocsBundle
|
||||
```
|
||||
|
||||
## Getting Help
|
||||
|
||||
- [Meshtastic Discord](https://discord.gg/meshtastic) — `#app-development` channel
|
||||
- GitHub Issues — for bug reports and feature requests
|
||||
- GitHub Discussions — for questions and ideas
|
||||
|
||||
---
|
||||
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,71 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" dir="ltr">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Measurement & Formatting</title>
|
||||
<link rel="stylesheet" href="../styles/docs.css">
|
||||
</head>
|
||||
<body data-page="measurement" data-locale="en">
|
||||
<pre class="markdown-content"># Measurement & Formatting
|
||||
|
||||
How the Meshtastic Android/KMP app formats numbers, units, and locale-sensitive values.
|
||||
|
||||
---|---|
|
||||
`MetricFormatter` | `core/common/.../util/MetricFormatter.kt` | Converts and formats physical measurements (temperature, pressure, speed, etc.) |
|
||||
`NumberFormatter` | `core/common/.../util/NumberFormatter.kt` | Low-level fixed-point number formatting with locale-independent dot separator |
|
||||
|
||||
Both live in `org.meshtastic.core.common.util` and are available to all KMP targets (Android, Desktop, iOS).
|
||||
|
||||
## NumberFormatter
|
||||
|
||||
`NumberFormatter` provides locale-independent decimal formatting using pure arithmetic (no `String.format` or `DecimalFormat`):
|
||||
|
||||
```kotlin
|
||||
object NumberFormatter {
|
||||
fun format(value: Double, decimalPlaces: Int): String
|
||||
fun format(value: Float, decimalPlaces: Int): String
|
||||
}
|
||||
```
|
||||
|
||||
> **Why locale-independent?** Meshtastic is a mesh networking app where consistency matters — sensor readings shared between nodes should look the same everywhere. `NumberFormatter` always uses `.` as the decimal separator.
|
||||
|
||||
---|---|
|
||||
`temperature` | `isFahrenheit` | `°F = °C × 1.8 + 32` |
|
||||
`windSpeed` | `isImperial` | m/s × 2.23694 → mph |
|
||||
`rainfall` | `isImperial` | mm ÷ 25.4 → in |
|
||||
|
||||
Everything else (voltage, current, pressure, SNR, RSSI, humidity, percent) displays in its native metric units. The user-facing [Units & Locale](../user/units-and-locale) page explains what end users see.
|
||||
|
||||
## DateFormatter
|
||||
|
||||
Date and time formatting uses the `DateFormatter` `expect object` with platform-specific `actual` implementations:
|
||||
|
||||
Function | Output Example |
|
||||
---|---|
|
||||
`formatRelativeTime()` | "5 min ago" |
|
||||
`formatDateTime()` | "May 13, 2026 2:30 PM" |
|
||||
`formatShortDate()` | "May 13" |
|
||||
`formatTime()` | "2:30 PM" |
|
||||
`formatTimeWithSeconds()` | "2:30:45 PM" |
|
||||
`formatDate()` | "2026-05-13" |
|
||||
`formatDateTimeShort()` | "5/13/26 2:30 PM" |
|
||||
|
||||
Unlike `MetricFormatter`, `DateFormatter` is declared with `expect`/`actual` (an `expect object` in `commonMain`, an `actual object` per platform) because date formatting inherently depends on platform locale APIs.
|
||||
|
||||
---|
|
||||
Locale-independent decimal separator (`.`) | Mesh data shared between nodes must be consistent |
|
||||
Pure arithmetic formatting (no `DecimalFormat`) | Works identically on JVM, Native, and JS targets |
|
||||
Only temperature, wind speed, and rainfall convert | The remaining metric units are universally understood in their native form |
|
||||
`object` singleton pattern | Stateless utility — no instance management needed |
|
||||
|
||||
---
|
||||
|
||||
## Related
|
||||
|
||||
- **User-facing docs**: [Units & Locale](../user/units-and-locale) explains what end users see
|
||||
- **Source code**: `core/common/src/commonMain/kotlin/org/meshtastic/core/common/util/MetricFormatter.kt`
|
||||
- **Tests**: `core/common/src/commonTest/kotlin/org/meshtastic/core/common/util/MetricFormatterTest.kt`
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,148 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" dir="ltr">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Navigation & Deep Links</title>
|
||||
<link rel="stylesheet" href="../styles/docs.css">
|
||||
</head>
|
||||
<body data-page="navigation-and-deep-links" data-locale="en">
|
||||
<pre class="markdown-content"># Navigation & Deep Links
|
||||
|
||||
The app uses **Navigation 3** with typed, serializable routes and centralized deep link resolution.
|
||||
|
||||
## Route Architecture
|
||||
|
||||
All routes are defined in `core/navigation/src/commonMain/kotlin/org/meshtastic/core/navigation/Routes.kt`.
|
||||
|
||||
### Route Hierarchy
|
||||
|
||||
```kotlin
|
||||
interface Route : NavKey // All routes implement NavKey
|
||||
interface Graph : Route // Graph roots for navigation hierarchies
|
||||
|
||||
@Serializable
|
||||
sealed interface SettingsRoute : Route {
|
||||
@Serializable data class Settings(val destNum: Int? = null) : SettingsRoute, Graph
|
||||
@Serializable data object DeviceConfiguration : SettingsRoute
|
||||
@Serializable data object HelpDocs : SettingsRoute
|
||||
@Serializable data class HelpDocPage(val pageId: String) : SettingsRoute
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
### Conventions
|
||||
|
||||
- Routes are `@Serializable` for state restoration
|
||||
- Use `data object` for routes without parameters
|
||||
- Use `data class` for parameterized routes
|
||||
- Group related routes under a `sealed interface`
|
||||
- Graph entry points implement both the route interface and `Graph`
|
||||
|
||||
## Deep Link Router
|
||||
|
||||
`DeepLinkRouter` in `core/navigation` maps URI deep links to typed backstack lists.
|
||||
|
||||
### URI Format
|
||||
|
||||
Both forms resolve through the same `DeepLinkRouter`, so any path below works with either scheme:
|
||||
|
||||
```text
|
||||
meshtastic://meshtastic/{path}
|
||||
https://meshtastic.org/{path} # App Link, android:autoVerify — also opens in-app on a real device/adb
|
||||
```
|
||||
|
||||
`adb shell am start -a android.intent.action.VIEW -d "meshtastic://meshtastic/{path}"` is the fastest way to
|
||||
trigger any route below from a shell or automation script without touching the UI.
|
||||
|
||||
For the `https` form to open in-app, each top-level path segment must also be declared as an
|
||||
`android:pathPrefix` in the `android:autoVerify` intent-filter in `androidApp/src/main/AndroidManifest.xml` —
|
||||
otherwise the link opens in the browser. Adding a new top-level route therefore takes three steps: add the
|
||||
segment to `DeepLinkRouter.topLevelPathSegments` (the router refuses to dispatch segments outside that set),
|
||||
add its `when` branch in `DeepLinkRouter.route()`, and add the matching `pathPrefix` to the manifest.
|
||||
`DeepLinkManifestConsistencyTest` (androidApp unit tests) checks the manifest against the set, so a missing
|
||||
manifest entry fails CI.
|
||||
|
||||
**Source of truth:** the always-current list of top-level segments is `topLevelPathSegments` in
|
||||
[`DeepLinkRouter`](https://github.com/meshtastic/Meshtastic-Android/blob/main/core/navigation/src/commonMain/kotlin/org/meshtastic/core/navigation/DeepLinkRouter.kt)
|
||||
— sub-paths live in the `route()` `when` block plus its helper maps (`settingsSubRoutes`, `nodeDetailSubRoutes`);
|
||||
the class-level KDoc is illustrative, not exhaustive. It also exists as executable spec in
|
||||
[`DeepLinkRouterTest.kt`](https://github.com/meshtastic/Meshtastic-Android/blob/main/core/navigation/src/commonTest/kotlin/org/meshtastic/core/navigation/DeepLinkRouterTest.kt).
|
||||
The table below is a snapshot for quick reference — check those two files if it looks out of date.
|
||||
|
||||
### Supported Deep Links
|
||||
|
||||
URI Path | Route | Notes |
|
||||
----------|-------|-------|
|
||||
`/connections` | `ConnectionsRoute.Connections(null)` | Connections screen |
|
||||
`/connections?address={prefixedAddress}` | `ConnectionsRoute.Connections(address)` | Auto-connects to a device without manual selection — the address uses the app's internal transport-prefixed format: `t192.168.1.1:4403` (TCP), `xAA:BB:CC:DD:EE:FF` (BLE), `s/dev/ttyUSB0` (serial). Intended for scripts/AI tooling driving the app. |
|
||||
`/connections?address=n` | `ConnectionsRoute.Connections("n")` | Disconnects the current device instead of connecting (`n` = the internal "no device selected" sentinel). |
|
||||
`/wifi-provision` | `WifiProvisionRoute.WifiProvision(null)` | WiFi provisioning screen |
|
||||
`/wifi-provision?address={mac}` | `WifiProvisionRoute.WifiProvision(mac)` | Provisioning targeting a specific device MAC |
|
||||
`/settings` | `SettingsRoute.Settings(null)` | Settings root |
|
||||
`/settings/helpDocs` | `SettingsRoute.HelpDocs` | Docs browser |
|
||||
`/settings/helpDocs/{pageId}` | `SettingsRoute.HelpDocPage(pageId)` | Specific doc page |
|
||||
`/settings/help-docs` | `SettingsRoute.HelpDocs` | Compatibility alias |
|
||||
`/discovery` | `DiscoveryRoute.DiscoveryGraph` | Local Mesh Discovery entry point |
|
||||
`/settings/local-mesh-discovery/session/{sessionId}` | `DiscoveryRoute.DiscoverySummary(sessionId)` | Discovery session result |
|
||||
`/nodes` | `NodesRoute.Nodes` | Node list |
|
||||
`/nodes/{destNum}` | `NodesRoute.NodeDetail(destNum)` | Node detail |
|
||||
`/nodes/{destNum}/{metric}` | e.g. `NodeDetailRoute.DeviceMetrics(destNum)` | Specific node metric tab (`device-metrics`, `signal`, `power`, `traceroute`, `pax`, `neighbors`, ...) |
|
||||
`/messages` | `ContactsRoute.Contacts` | Conversation list |
|
||||
`/messages/{contactKey}` | `ContactsRoute.Messages(contactKey)` | Specific conversation |
|
||||
`/share?message={text}` | `ContactsRoute.Share(message)` | Share-to-contact composer |
|
||||
`/quickchat` | `ContactsRoute.QuickChat` | Quick chat picker |
|
||||
`/map` | `MapRoute.Map(null)` | Map view |
|
||||
`/map/{waypointId}` | `MapRoute.Map(waypointId)` | Map centered on a waypoint |
|
||||
`/channels` | `ChannelsRoute.Channels` | Channel list |
|
||||
`/firmware` | `FirmwareRoute.FirmwareGraph` | Firmware screen |
|
||||
`/firmware/update` | `FirmwareRoute.FirmwareUpdate` | Firmware update flow |
|
||||
|
||||
### Backstack Synthesis
|
||||
|
||||
Deep links synthesize a full backstack, not just the target screen:
|
||||
|
||||
```kotlin
|
||||
// /settings/helpDocs/messages-and-channels produces:
|
||||
listOf(
|
||||
SettingsRoute.Settings(null),
|
||||
SettingsRoute.HelpDocs,
|
||||
SettingsRoute.HelpDocPage("messages-and-channels"),
|
||||
)
|
||||
```
|
||||
|
||||
This ensures the user can navigate "up" correctly.
|
||||
|
||||
## Adding a Deep Link
|
||||
|
||||
1. Define the typed route in `Routes.kt`.
|
||||
2. Add the mapping in `DeepLinkRouter.settingsSubRoutes` (or equivalent for other graphs).
|
||||
3. Add a test in `DeepLinkRouterTest.kt`.
|
||||
4. Register the navigation entry in the appropriate feature module.
|
||||
5. Update the KDoc list on `DeepLinkRouter.route()` and the table above — they're the two places tooling/agents look to discover what deep links exist.
|
||||
|
||||
## Navigation Entry Registration
|
||||
|
||||
Each feature module provides entries via an extension function:
|
||||
|
||||
```kotlin
|
||||
fun EntryProviderScope<NavKey>.docsEntries(backStack: NavBackStack<NavKey>) {
|
||||
entry<SettingsRoute.HelpDocs> { DocsBrowserScreen(backStack) }
|
||||
entry<SettingsRoute.HelpDocPage> { route -> DocsPageRouteScreen(route.pageId, backStack) }
|
||||
}
|
||||
```
|
||||
|
||||
These are called from the settings navigation composition.
|
||||
|
||||
## Testing
|
||||
|
||||
Deep link routing is tested in:
|
||||
```
|
||||
core/navigation/src/commonTest/kotlin/org/meshtastic/core/navigation/DeepLinkRouterTest.kt
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,92 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" dir="ltr">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Persistence</title>
|
||||
<link rel="stylesheet" href="../styles/docs.css">
|
||||
</head>
|
||||
<body data-page="persistence" data-locale="en">
|
||||
<pre class="markdown-content"># Persistence
|
||||
|
||||
How the Meshtastic app stores data across different mechanisms.
|
||||
|
||||
## Room KMP Database
|
||||
|
||||
**Module:** `core:database`
|
||||
|
||||
The primary structured data store:
|
||||
- Node information and history
|
||||
- Message history
|
||||
- Waypoints
|
||||
- Telemetry data
|
||||
- Channel configurations
|
||||
|
||||
### Key Points
|
||||
|
||||
- Uses Room KMP for cross-platform compatibility
|
||||
- Migrations managed through Room's built-in migration system
|
||||
- DAO interfaces live in `core:database`
|
||||
- Repository layer in `core:repository` provides the public API
|
||||
- Full-text message search is backed by an FTS5 content table (`PacketFts`) over `Packet`, kept in sync by Room-managed triggers
|
||||
|
||||
### What's Stored in Room
|
||||
|
||||
Entity | Description |
|
||||
--------|-------------|
|
||||
`NodeEntity` | All known mesh nodes and their metadata |
|
||||
`MyNodeEntity` | The local node's own info |
|
||||
`Packet` | Message history (channel and direct), waypoints, and telemetry data |
|
||||
`PacketFts` | FTS5 virtual table mirroring `Packet.messageText` for full-text message search (Room-managed INSERT/UPDATE/DELETE triggers keep it in sync) |
|
||||
`ContactSettings` | Per-contact mute and read-state |
|
||||
`ReactionEntity` | Emoji reactions on messages |
|
||||
`MeshLog` | Raw mesh protocol logs |
|
||||
`MetadataEntity` | Device metadata (firmware version, hardware model) |
|
||||
`QuickChatAction` | User-configured quick-chat messages |
|
||||
`DeviceHardwareEntity` | Cached device hardware catalog |
|
||||
`FirmwareReleaseEntity` | Cached firmware release info |
|
||||
`TracerouteNodePositionEntity` | Traceroute hop position data |
|
||||
`DiscoverySessionEntity` | A Local Mesh Discovery scan session (timestamp, presets scanned, home preset) |
|
||||
`DiscoveryPresetResultEntity` | Per-preset result within a discovery session |
|
||||
`DiscoveredNodeEntity` | Nodes found during a discovery preset scan |
|
||||
`DeviceLinkEntity` | Cached `msh.to` device links from the Meshtastic API |
|
||||
|
||||
> 💡 **Note:** Waypoints, telemetry, and channel data are stored within the `Packet` entity (using the `port_num` field to distinguish packet types) rather than in separate tables.
|
||||
|
||||
## DataStore Preferences
|
||||
|
||||
**Module:** `core:datastore`
|
||||
|
||||
For lightweight key-value preferences:
|
||||
- Local radio configuration (LocalConfig proto)
|
||||
- Module configuration (ModuleConfig proto)
|
||||
- Channel set data
|
||||
- Local statistics
|
||||
- Recently connected device addresses
|
||||
|
||||
## Core Prefs
|
||||
|
||||
**Module:** `core:prefs`
|
||||
|
||||
Higher-level preferences abstraction:
|
||||
- User-facing settings
|
||||
- App behavior configuration
|
||||
- Feature toggles
|
||||
|
||||
## What Docs Intentionally Skip
|
||||
|
||||
The `feature:docs` module uses **no** Room or persistent database. Documentation ships as build-time assets versioned with the app binary, so it stays fully offline, is replaced on each update, and needs no migration story. Optional UX state (e.g. last viewed page) could live in `core:prefs` but isn't part of the docs data model.
|
||||
|
||||
## Best Practices
|
||||
|
||||
- Use Room for structured, queryable data that changes at runtime
|
||||
- Use DataStore for simple preferences and state
|
||||
- Use bundled resources/assets for static content
|
||||
- Never store sensitive data (keys, passwords) in plain Room tables
|
||||
- Always provide migrations for schema changes
|
||||
|
||||
---
|
||||
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,144 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" dir="ltr">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Testing</title>
|
||||
<link rel="stylesheet" href="../styles/docs.css">
|
||||
</head>
|
||||
<body data-page="testing" data-locale="en">
|
||||
<pre class="markdown-content"># Testing
|
||||
|
||||
Testing strategy and practices for the Meshtastic KMP project.
|
||||
|
||||
## Test Categories
|
||||
|
||||
### KMP Unit Tests (`commonTest`)
|
||||
|
||||
Shared tests that run on all platforms:
|
||||
|
||||
```bash
|
||||
./gradlew allTests
|
||||
```
|
||||
|
||||
- Business logic tests
|
||||
- Data model validation
|
||||
- Search/ranking algorithm tests
|
||||
- Route serialization tests
|
||||
|
||||
### Android Host Tests
|
||||
|
||||
Android-specific tests that run on JVM:
|
||||
|
||||
```bash
|
||||
./gradlew test
|
||||
```
|
||||
|
||||
- ViewModel tests
|
||||
- Repository tests with Room fakes
|
||||
- Android-specific integration tests
|
||||
|
||||
### Compose UI Tests
|
||||
|
||||
Compose Multiplatform UI test framework:
|
||||
|
||||
```kotlin
|
||||
@Test
|
||||
fun myScreenTest() = runComposeUiTest {
|
||||
setContent { MyScreen() }
|
||||
onNodeWithText("Expected").assertIsDisplayed()
|
||||
}
|
||||
```
|
||||
|
||||
Located in `commonTest` or `jvmTest` source sets.
|
||||
|
||||
### Screenshot Tests
|
||||
|
||||
Uses Android Gradle Plugin's native (layoutlib) screenshot testing framework, split across two modules:
|
||||
|
||||
- **`:screenshot-tests`** — the **visual-regression gate**. CI runs `validateDebugScreenshotTest` on it; reframing one of these baselines is a real diff to review. Holds atomic, dual-purpose components.
|
||||
- **`:docs-screenshots`** — **generate-only**, *not* validated in CI. Holds doc-framed compositions whose framing is tuned for the docs site, so reframing a doc image never churns the regression gate.
|
||||
|
||||
```bash
|
||||
./gradlew :screenshot-tests:updateDebugScreenshotTest # record regression goldens
|
||||
./gradlew :screenshot-tests:validateDebugScreenshotTest # compare against goldens (CI gate)
|
||||
./gradlew :docs-screenshots:updateDebugScreenshotTest # record doc-framed composition images
|
||||
./gradlew :screenshot-tests:copyDocsScreenshots # copy doc images from BOTH modules into docs/assets
|
||||
```
|
||||
|
||||
Rendering is host-deterministic here (layoutlib): a local `update` produces references byte-identical to CI, so locally-recorded goldens pass `validate`. See `docs/assets/screenshots/README.md` for which module a new screenshot belongs in.
|
||||
|
||||
### Baseline Profile / Startup Performance
|
||||
|
||||
The `:baselineprofile` module (#5735) generates a [Baseline Profile](https://developer.android.com/topic/performance/baselineprofiles/overview) for `:androidApp`, AOT-compiling the hot startup paths so ART doesn't pay the JIT cost on first launch. It targets the **google** flavor (the variant most users run).
|
||||
|
||||
The Macrobenchmark generator (`BaselineProfileGenerator`) and the before/after benchmark (`StartupBenchmark`) live in `baselineprofile/src/main/kotlin/org/meshtastic/baselineprofile/`. Both run on a device/emulator:
|
||||
|
||||
```bash
|
||||
./gradlew :androidApp:generateGoogleReleaseBaselineProfile # Generate the profile (commit the output)
|
||||
./gradlew :androidApp:benchmarkGoogleReleaseBaselineProfile # Quantify the cold-start win
|
||||
```
|
||||
|
||||
The generated profile is merged into `androidApp/src/googleRelease/generated/baselineProfiles/` and packaged into release builds via `androidx.profileinstaller`.
|
||||
|
||||
> ⚠️ **Warning:** The journey currently covers cold start only (launch → first frame), because CI has no paired radio. Post-connection screens (node list, map, message thread) are not yet AOT-compiled; extend the journey once a fake transport or connected device is wired into the harness.
|
||||
|
||||
## Test Organization
|
||||
|
||||
```
|
||||
feature/my-feature/src/
|
||||
├── commonTest/kotlin/org/meshtastic/feature/myfeature/
|
||||
│ ├── MyBusinessLogicTest.kt
|
||||
│ └── MyModelTest.kt
|
||||
└── jvmTest/kotlin/org/meshtastic/feature/myfeature/
|
||||
└── MyDesktopSpecificTest.kt
|
||||
```
|
||||
|
||||
## Testing Guidelines
|
||||
|
||||
### DO
|
||||
|
||||
- Write tests in `commonTest` when possible (runs everywhere)
|
||||
- Test business logic independently from UI
|
||||
- Use fakes/stubs instead of mocks where practical
|
||||
- Test edge cases: empty states, error states, boundary values
|
||||
- Test deep link routing in `DeepLinkRouterTest`
|
||||
- Keep tests fast — no network, no disk I/O in unit tests
|
||||
|
||||
### DON'T
|
||||
|
||||
- Don't test framework behavior (Compose internals, Room queries)
|
||||
- Don't create tests that depend on other feature modules
|
||||
- Don't use `Thread.sleep` — use coroutine test dispatchers
|
||||
- Don't rely on test execution order
|
||||
|
||||
## Running Tests
|
||||
|
||||
```bash
|
||||
# All KMP tests
|
||||
./gradlew allTests
|
||||
|
||||
# Specific module
|
||||
./gradlew :feature:docs:allTests
|
||||
|
||||
# Code quality
|
||||
./gradlew spotlessCheck detekt
|
||||
|
||||
# Full verification
|
||||
./gradlew spotlessCheck detekt kmpSmokeCompile test allTests
|
||||
```
|
||||
|
||||
## CI Integration
|
||||
|
||||
Tests run automatically on:
|
||||
- Pull request creation/update
|
||||
- Push to `main`
|
||||
- Pre-release validation
|
||||
|
||||
CI runs on GitHub-hosted Ubuntu 24.04 runners (most jobs use the `ubuntu-24.04-arm` ARM runners, a few use `ubuntu-24.04`) with JDK 25 and Gradle caching.
|
||||
|
||||
---
|
||||
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,103 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" dir="ltr">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Transport</title>
|
||||
<link rel="stylesheet" href="../styles/docs.css">
|
||||
</head>
|
||||
<body data-page="transport" data-locale="en">
|
||||
<pre class="markdown-content"># Transport
|
||||
|
||||
Meshtastic communicates between the app and radio hardware through multiple transport mechanisms.
|
||||
|
||||
## Transport Abstraction
|
||||
|
||||
The transport layer is abstracted through interfaces defined in `core:repository` (`RadioTransport`, `RadioTransportFactory`, `RadioInterfaceService`), with the concrete transports implemented in `core:network` (`BleRadioTransport`, TCP, mock/replay) and `core:ble`. This lets the app work identically regardless of the underlying connection type.
|
||||
|
||||
```
|
||||
App ← RadioController → Transport (BLE | Serial | TCP)
|
||||
```
|
||||
|
||||
## Bluetooth Low Energy (BLE)
|
||||
|
||||
**Module:** `core:ble`
|
||||
**Platforms:** Android, Desktop (JVM via Kable), iOS (planned)
|
||||
|
||||
The primary transport for mobile devices and also available on desktop:
|
||||
- Service discovery for Meshtastic GATT services
|
||||
- Characteristic-based read/write for protobuf packets
|
||||
- Connection state management and automatic reconnection
|
||||
- MTU negotiation for optimal packet sizes
|
||||
|
||||
### Key Classes
|
||||
|
||||
- `core/ble/` — BLE scanning, connection, and GATT operations
|
||||
- Platform-specific implementations in `androidMain` and `jvmMain` (Kable)
|
||||
|
||||
## USB Serial
|
||||
|
||||
**Module:** `core:network`
|
||||
**Platforms:** Android (OTG), Desktop
|
||||
|
||||
Serial communication over USB:
|
||||
- Uses `usb-serial-for-android` library on Android
|
||||
- Direct serial port access on Desktop (JVM)
|
||||
- Probe table for supported USB vendor/product IDs
|
||||
- Automatic detection when USB device is connected
|
||||
|
||||
### Key Classes
|
||||
|
||||
- Serial prober and transport factory in `core/network`
|
||||
- Desktop-specific serial in `desktopApp/src/main/kotlin/.../radio/`
|
||||
|
||||
## TCP/IP
|
||||
|
||||
**Module:** `core:network`
|
||||
**Platforms:** Android, Desktop (iOS: code compiles, but there's no iOS app target or `RadioTransportFactory` yet — see Transport Factory below)
|
||||
|
||||
Network-based transport for WiFi-enabled radios:
|
||||
- TCP socket connection to radio's IP address
|
||||
- Default port: 4403
|
||||
- Used for development with simulated radios
|
||||
- Available when BLE/USB is impractical
|
||||
|
||||
## Transport Factory
|
||||
|
||||
The `RadioTransportFactory` interface abstracts transport creation:
|
||||
|
||||
```kotlin
|
||||
interface RadioTransportFactory {
|
||||
val supportedDeviceTypes: List<DeviceType>
|
||||
fun createTransport(address: String, service: RadioInterfaceService): RadioTransport
|
||||
fun isMockTransport(): Boolean
|
||||
fun isAddressValid(address: String?): Boolean
|
||||
fun toInterfaceAddress(interfaceId: InterfaceId, rest: String): String
|
||||
}
|
||||
```
|
||||
|
||||
Platform-specific implementations:
|
||||
- **Android:** Supports BLE + USB + TCP
|
||||
- **Desktop:** Supports BLE (Kable) + USB + TCP
|
||||
- **iOS:** Planned BLE + TCP
|
||||
|
||||
## Connection Lifecycle
|
||||
|
||||
1. **Discovery** — Scan for available radios (BLE scan / USB detect / manual TCP)
|
||||
2. **Connection** — Establish link to selected radio
|
||||
3. **Handshake** — Exchange node info and configuration
|
||||
4. **Active** — Normal message exchange
|
||||
5. **Disconnection** — Clean teardown or error recovery
|
||||
|
||||
## Adding a New Transport
|
||||
|
||||
1. Implement `RadioTransport` interface
|
||||
2. Register in platform-specific `RadioTransportFactory`
|
||||
3. Add connection UI in `feature:connections`
|
||||
4. Update DI bindings for the platform
|
||||
|
||||
---
|
||||
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
||||
Reference in new issue
Block a user