LunaLink 1.0.50
dotnet add package LunaLink --version 1.0.50
NuGet\Install-Package LunaLink -Version 1.0.50
<PackageReference Include="LunaLink" Version="1.0.50" />
<PackageVersion Include="LunaLink" Version="1.0.50" />
<PackageReference Include="LunaLink" />
paket add LunaLink --version 1.0.50
#r "nuget: LunaLink, 1.0.50"
#:package LunaLink@1.0.50
#addin nuget:?package=LunaLink&version=1.0.50
#tool nuget:?package=LunaLink&version=1.0.50
LunaLink
Reliable node-to-master communication for .NET industrial systems.
LunaLink is a compact binary protocol and .NET library for moving real-time tag data between distributed edge nodes and a central master. It is designed for SCADA, industrial IoT, telemetry, and unreliable or bandwidth-constrained networks.
It powers communication inside Lunasoft SCADA and can also be embedded in any .NET application.
Why LunaLink?
- Reliable delivery - persistent SQLite outbox, ordered replay, acknowledgements, duplicate detection, and sequence-gap recovery.
- Efficient transport - MessagePack serialization with automatic LZ4 compression.
- Industrial data model - typed values, timestamps, quality codes, tag inventory, snapshots, history, and remote writes.
- Resilient connections - automatic reconnect, heartbeat telemetry, health tracking, and backlog collapse through a full-state baseline.
- Secure by design - authentication token, TLS 1.3, optional mutual TLS, and local RSA-signed license validation.
- Host-friendly - server and client run as standard
BackgroundServicecomponents with dependency injection and structured logging.
Install
dotnet add package LunaLink
LunaLink currently targets .NET 10.
Official examples
The LunaLink Examples repository contains complete Windows Forms Master and Node applications with lifecycle controls, live telemetry, structured logs, configuration, TLS guidance, and troubleshooting.
git clone https://github.com/lunasoft-llc/lunalink-examples.git
Quick start
1. Run a master
Implement the callback that connects LunaLink to your historian, HMI, or application services:
using LunaLink;
public sealed class MasterCallback : ILunaLinkServerCallback
{
public Task OnNodeHelloAsync(
string nodeId,
string nodeName,
string remoteEndpoint,
CancellationToken ct)
{
Console.WriteLine($"Connected: {nodeName} ({nodeId}) from {remoteEndpoint}");
return Task.CompletedTask;
}
public Task ProcessDataPointAsync(
Guid tagId,
string? tagName,
object? value,
LunaLinkQuality quality,
DateTimeOffset timestamp,
LunaLinkDataType dataType)
{
Console.WriteLine($"{tagName ?? tagId.ToString()} = {value} [{quality}]");
return Task.CompletedTask;
}
public Task<List<LunaLinkTagSnapshot>> GetSnapshotAsync(
IEnumerable<Guid>? tagIds,
CancellationToken ct) =>
Task.FromResult(new List<LunaLinkTagSnapshot>());
}
Register the master in your host:
builder.Services.Configure<LunaLinkOptions>(
builder.Configuration.GetSection(LunaLinkOptions.Section));
builder.Services.AddSingleton<LunaLinkNodeTracker>();
builder.Services.AddSingleton<ILunaLinkServerCallback, MasterCallback>();
builder.Services.AddHostedService<LunaLinkServer>();
{
"LunaLink": {
"Port": 7788,
"AuthToken": "replace-with-a-secret"
}
}
2. Connect an edge node
Implement the node callback. The first four methods are required; inventory and history methods have safe empty defaults and can be added when needed.
using LunaLink;
public sealed class NodeCallback : ILunaLinkClientCallback
{
public Task<List<LunaLinkTagSnapshot>> GetSnapshotAsync(
IEnumerable<Guid>? tagIds,
CancellationToken ct) =>
Task.FromResult(new List<LunaLinkTagSnapshot>());
public Task ProcessDataPointAsync(
Guid tagId,
string? tagName,
object? value,
LunaLinkQuality quality,
DateTimeOffset timestamp,
LunaLinkDataType dataType) => Task.CompletedTask;
public Task<bool> WriteTagAsync(
Guid tagId,
string? tagName,
object? value,
CancellationToken ct) => Task.FromResult(false);
public Task<int> GetConnectedDeviceCountAsync(CancellationToken ct) =>
Task.FromResult(0);
}
Register one shared client instance as both the hosted service and the application-facing ILunaLinkClient:
builder.Services.Configure<LunaLinkOptions>(
builder.Configuration.GetSection(LunaLinkOptions.Section));
builder.Services.AddSingleton<ILunaLinkClientCallback, NodeCallback>();
builder.Services.AddSingleton<LunaLinkOutbox>();
builder.Services.AddSingleton<LunaLinkClient>();
builder.Services.AddSingleton<ILunaLinkClient>(sp =>
sp.GetRequiredService<LunaLinkClient>());
builder.Services.AddHostedService(sp =>
sp.GetRequiredService<LunaLinkClient>());
{
"LunaLink": {
"MasterHost": "10.0.0.10",
"Port": 7788,
"NodeId": "line-1-edge",
"NodeName": "Production Line 1",
"AuthToken": "replace-with-a-secret"
}
}
Send one or more tag changes from application code:
public sealed class TelemetryPublisher(ILunaLinkClient client)
{
public Task PublishTemperatureAsync(Guid tagId, double value, CancellationToken ct) =>
client.SendTagDeltaAsync(
[
(
tagId,
"reactor.temperature",
value,
LunaLinkQuality.Good,
DateTimeOffset.UtcNow,
LunaLinkDataType.Float64
)
],
ct);
}
The client reconnects and replays pending data automatically.
How it works
Edge node Master
| |
|------ Hello + authentication -->|
|<--------- Accepted -------------|
|------ Ordered tag delta -------->|
|<------- Application ACK --------|
|------ Heartbeat + inventory ---->|
|<--------- Heartbeat ACK --------|
|<------- Remote tag write -------|
|--------- Write result ---------->|
Every frame has a fixed header, protocol version, message type, flags, sequence number, payload length, and CRC-16 integrity value. Payloads are MessagePack encoded and larger payloads are compressed with LZ4.
When a node is offline, tag batches are stored in SQLite. Confirmed batches are removed only after the master returns an application-level acknowledgement. Large backlogs can be replaced safely by a current full-state baseline while normal-sized backlogs retain ordered replay.
Configuration
| Setting | Default | Applies to | Description |
|---|---|---|---|
Port |
7788 |
Both | TCP port |
UseTls |
false |
Both | Enables TLS transport |
AuthToken |
null |
Both | Shared authentication token |
MasterHost |
null |
Node | Master hostname or IP address |
NodeId |
generated GUID | Node | Stable unique node identity |
NodeName |
machine name | Node | Human-readable node name |
ReconnectDelaySeconds |
5 |
Node | Initial reconnect delay |
MaxRetryDelaySeconds |
60 |
Node | Maximum reconnect delay |
AckTimeoutSeconds |
5 |
Node | Delivery acknowledgement timeout |
OutboxReplayCollapseThreshold |
1024 |
Node | Pending-batch threshold for full-state resync |
OutboxMaxBytes |
512 MiB |
Node | Persistent outbox size limit |
OutboxRetentionDays |
7 |
Node | Persistent outbox retention |
HistorySyncMode |
Parallel |
Node | Discard, Full, or Parallel |
HistoryBatchSize |
2000 |
Node | Samples per history batch |
HistoryBatchDelayMilliseconds |
100 |
Node | Delay between history batches |
LicenseKey |
null |
Node | Optional LunaLink license key |
See LunaLinkOptions.cs for the complete current option set.
TLS and mutual TLS
Master configuration:
{
"LunaLink": {
"Port": 7788,
"UseTls": true,
"CertificatePath": "certs/master.pfx",
"CertificatePassword": "use-a-secret-provider",
"ClientCertificateRequired": true
}
}
Node configuration:
{
"LunaLink": {
"MasterHost": "scada.example.com",
"Port": 7788,
"UseTls": true,
"ValidateServerCertificate": true,
"ClientCertificatePath": "certs/node.pfx",
"ClientCertificatePassword": "use-a-secret-provider"
}
}
Keep credentials outside committed configuration in production. Do not disable server-certificate validation outside controlled development environments.
Data model
Supported data types:
Bool, Int16, UInt16, Int32, UInt32, Float32, Float64, String, Enum, and Json.
Quality states:
Good, Bad, Uncertain, and NotConnected.
The protocol also supports:
- point-in-time snapshots;
- device and tag inventory;
- subscription filtering;
- master-to-node remote writes with results;
- node heartbeat and health telemetry;
- ordered history synchronization;
- live client reconfiguration.
Reliability guarantees
- A node sequence is persisted with each outbox batch.
- The master detects duplicates and missing sequences persistently.
- A batch remains queued until its application acknowledgement is received.
- Corrupt outbox databases are quarantined instead of silently reused.
- Reconnect and replay preserve ordering during normal recovery.
- Full-state resync resolves large or unavailable sequence gaps.
These guarantees cover LunaLink transport. Your callback implementation remains responsible for application-specific persistence and business validation.
Licensing
| Mode | Unique tags | Persistent offline outbox | Cost |
|---|---|---|---|
| Freemium | 50 | No | Free |
| Licensed | Unlimited | Yes | License required |
LunaLink supports local RSA-signed keys and periodic online validation. If the licensing service is temporarily unavailable after a successful validation, a grace period applies.
Visit lunasoft.az for licensing information.
Package dependencies
- MessagePack - binary serialization
- K4os.Compression.LZ4 - payload compression
- Microsoft.Data.Sqlite - persistent outbox storage
- Microsoft.Extensions abstractions - hosting, logging, options, and dependency injection
Support
- Product and licensing: lunasoft.az
- Package: NuGet.org
- Official examples and example issues: LunaLink Examples
- Source and issue tracking: GitHub
When reporting a problem, include the LunaLink version, operating system, .NET version, node/master role, and relevant sanitized logs. Never publish authentication tokens, license keys, or private certificates.
License
Copyright (c) 2025-2026 LunaSoft. All rights reserved.
LunaLink is distributed under a proprietary license. See LICENSE.txt.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- K4os.Compression.LZ4 (>= 1.3.8)
- MessagePack (>= 3.1.7)
- Microsoft.Data.Sqlite (>= 10.0.5)
- SQLitePCLRaw.lib.e_sqlite3 (>= 3.50.3)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.