Krill SDK for Kotlin Multiplatform
Develop Home Automation on any Platform with the Krill Kotlin Multiplatform SDK for Java and Kotlin.
Krill Kotlin Multiplatform SDK
Introduction
The Krill SDK is now a stand-alone Kotlin Multiplatform library on Maven Central. Every type, interface, and helper your code needs to talk to a Krill server — discovering peers, reading nodes, deriving auth tokens, decoding beacons, planning agent actions — has been lifted out of the Krill app’s shared module and into one redistributable artifact.
If you’re building a Krill integration, an alternative client, or just want to read data out of a swarm without depending on the full Krill app, this is your starting point. The SDK targets JVM, Android, iOS (arm64 / sim arm64 / x64), and wasm-js, so the same code works on a Raspberry Pi, a phone, a desktop, or in a browser.
What’s in it
The SDK contains the durable, redistributable parts of Krill — value types, interfaces, and protocol primitives. Process-specific wiring (Koin DI, processor implementations, platform HTTP engines) stays in the app.
| Layer | What you get |
|---|---|
| Type discriminators | KrillApp sealed class for every node type (Server, Pin, DataPoint, Trigger, Filter, Executor, Project, …); MenuCommand for editor commands. |
| Node model | Node data class, NodeBuilder fluent builder, NodeMetaData interface plus every concrete subtype (ServerMetaData, PinMetaData, DataPointMetaData, TaskListMetaData, …) and Node.name(), Node.details(), Node.https() extensions. |
| HTTP | NodeHttp class for the full REST surface (/health, /nodes, /data/series, /camera/*, /backup/*, /project/*/diagram/*); TrustHost interface; BeaconCodec for multicast payloads. |
| Auth | PinDerivation (PBKDF2/HMAC bearer + rolling beacon tokens); ClientPinStore interface for per-platform credential persistence. |
| Discovery | PeerConstants (multicast group / port / TTL); BeaconSender, BeaconWireHandler, BeaconSupervisor, ServerConnector interfaces; NodeWire payload type. |
| Events | Event envelope, EventPayload polymorphic interface, payloads for state-change / snapshot-update / pin-change / created. |
| LLM tooling | Chat, Message, ToolCall, Function for chat-completion plumbing; LLMResponse, LLMProposedAction, LLMNewNodeProposal for agent planning. |
| Lifecycle | AppLifecycle foreground/background flow. |
| Helpers | DataPointRelevance (which filters/triggers fit a DataType), computeTaskListState(...), updateMetaWithError(...), Snapshot.doubleValue(). |
What stays in the Krill app (and you don’t need): the platform-specific Ktor engines, the Koin processor wiring, the H2 database layer, the krill-pi4j hardware bridge, and the Compose UI.
Installation
Java 21+, Gradle Kotlin DSL, version catalog:
1
2
3
4
5
6
# gradle/libs.versions.toml
[versions]
krill-sdk = "0.0.16"
[libraries]
krill-sdk = { module = "com.krillforge:krill-sdk", version.ref = "krill-sdk" }
1
2
3
4
5
6
7
8
// build.gradle.kts
repositories {
mavenCentral()
}
dependencies {
implementation(libs.krill.sdk)
}
Transitively the SDK pulls in kotlinx-coroutines-core, kotlinx-serialization-core/json, ktor-http, ktor-client-core, and kermit (logging). You bring your own Ktor engine on each platform (CIO on JVM, OkHttp on Android, Darwin on iOS, JS on wasm).
Quick examples
Build a node
1
2
3
4
5
6
7
8
9
10
import krill.zone.shared.KrillApp
import krill.zone.shared.krillapp.server.pin.PinMetaData
import krill.zone.shared.node.NodeBuilder
val fan = NodeBuilder()
.type(KrillApp.Server.Pin)
.host(serverInstallId)
.parent(serverInstallId)
.meta(PinMetaData(name = "fan", pinNumber = 17))
.create()
Derive a bearer token from a PIN
1
2
3
4
5
6
7
8
import krill.zone.shared.security.PinDerivation
val token = PinDerivation.deriveBearerToken(pin = "123456")
val beacon = PinDerivation.deriveBeaconToken(
pin = "123456",
nodeUuid = serverUuid,
epochSeconds = Clock.System.now().epochSeconds,
)
deriveBearerToken is stable per PIN; persist it locally via your platform’s ClientPinStore and send it as Authorization: Bearer <token> on every call. deriveBeaconToken rolls every 30 seconds and rides inside multicast beacons to prove cluster membership without leaking the PIN.
Talk to a server with NodeHttp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import io.ktor.client.HttpClient
import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
import io.ktor.serialization.kotlinx.json.json
import krill.zone.shared.node.Node
import krill.zone.shared.node.NodeHttp
val client = HttpClient {
install(ContentNegotiation) { json(yourPolymorphicJson) }
}
val nodeHttp = NodeHttp(
httpClient = client,
trustHost = trustHost, // your TrustHost implementation
bearerTokenProvider = { pinStore.bearerToken() },
)
val server: Node = ...
val children = nodeHttp.readNodes(server)
val healthy = nodeHttp.readHealth(server) != null
Walk the type hierarchy
1
2
3
4
5
6
7
8
9
10
11
12
import krill.zone.shared.allKrillApps
import krill.zone.shared.krillAppChildren
import krill.zone.shared.lookup
// Every node type, flattened.
allKrillApps.forEach(::println)
// Direct children of `Server`.
krillAppChildren[KrillApp.Server].orEmpty()
// Resolve a type by short name or fully-qualified discriminator.
val cron = lookup("Trigger.CronTimer")
Decode a beacon datagram
1
2
3
4
5
6
7
8
import krill.zone.shared.io.http.decodeBeacon
import krill.zone.shared.io.http.PeerConstants
val socket = openMulticast(PeerConstants.MULTICAST_GROUP_V4, PeerConstants.MULTICAST_PORT)
socket.receive { bytes ->
val wire = decodeBeacon(json = yourJson, bytes = bytes) ?: return@receive
println("peer ${wire.installId} at ${wire.url()} on ${wire.platform}")
}
Compute the worst state of a TaskList
1
2
3
4
5
6
7
8
9
10
import krill.zone.shared.krillapp.project.tasklist.computeTaskListState
import krill.zone.shared.krillapp.project.tasklist.Priority
import krill.zone.shared.krillapp.project.tasklist.Task
val state = computeTaskListState(
tasks = listOf(Task(description = "rotate logs", dueDate = lastWeek)),
priority = Priority.HIGH,
nowMillis = Clock.System.now().toEpochMilliseconds(),
)
// → NodeState.SEVERE
The same function powers the chip-colour escalation in the Krill UI and the server-side scheduler — using it in your own tooling guarantees pixel-identical behaviour.
Status
The SDK shipped its first release in March and has had a long migration tail since: every value type, MetaData subclass, HTTP primitive, and protocol helper from the Krill shared module is now mirrored in the SDK at the same package paths. The Krill app itself consumes the SDK as a regular Maven dependency. We expect a 1.0 once one round of API feedback comes back from external integrators.
Issues, requests, and PRs welcome at the GitHub repo.
Last verified: 2026-04-26