Build distributed IoT edge systems using Microsoft Orleans virtual actors to control hardware on Raspberry Pi with state persistence and cluster management
Build distributed edge computing systems using Microsoft Orleans virtual actors to control IoT hardware on Raspberry Pi devices. This skill implements cloud-native distributed patterns on resource-constrained edge devices with automatic state persistence, cluster management, and location transparency.
Guides you through building IoT edge applications using the Orleans virtual actor framework to:
The system uses a three-project structure:
1. **Shared Core Library** - Defines grain interfaces and contracts
2. **Edge Node (Orleans Silo)** - Runs on Raspberry Pi, hosts grain implementations
3. **Controller Client** - Terminal UI that sends commands to the cluster
Create three projects following the client-server-shared pattern:
```bash
dotnet new sln -n OrleansEdge
dotnet new classlib -n OrleansEdge.Core
dotnet sln add OrleansEdge.Core
dotnet new console -n OrleansEdge.Node
dotnet sln add OrleansEdge.Node
dotnet new console -n OrleansEdge.Controller
dotnet sln add OrleansEdge.Controller
```
In the Core project, define your grain interfaces and data contracts:
```csharp
// ILedControllerGrain.cs
public interface ILedControllerGrain : IGrainWithStringKey
{
Task SetLedColor(LedColor color);
Task<LedColor> GetCurrentColor();
}
// LedColor.cs
public enum LedColor
{
Off, Red, Green, Blue, Yellow, Cyan, Magenta, White
}
```
In the Node project, implement the grain with Orleans state management:
```csharp
public class LedControllerGrain : Grain, ILedControllerGrain
{
private readonly IPersistentState<LedState> _state;
private readonly IOutputService _outputService;
public LedControllerGrain(
[PersistentState("ledState", "ledStorage")] IPersistentState<LedState> state,
IOutputService outputService)
{
_state = state;
_outputService = outputService;
}
public async Task SetLedColor(LedColor color)
{
_state.State.CurrentColor = color;
_state.State.LastUpdated = DateTime.UtcNow;
await _state.WriteStateAsync();
// Update physical hardware
await _outputService.SetLedState(color != LedColor.Off);
}
public Task<LedColor> GetCurrentColor()
{
return Task.FromResult(_state.State.CurrentColor);
}
}
```
Set up the Orleans silo with SQLite persistence:
```csharp
var builder = Host.CreateDefaultBuilder(args)
.UseOrleans((context, siloBuilder) =>
{
var config = context.Configuration;
siloBuilder
.UseLocalhostClustering(
siloPort: config.GetValue<int>("Orleans:SiloPort", 11111),
gatewayPort: config.GetValue<int>("Orleans:GatewayPort", 30000))
.AddAdoNetGrainStorage("ledStorage", options =>
{
options.Invariant = "Microsoft.Data.Sqlite";
options.ConnectionString = config["Orleans:Storage:ConnectionString"];
});
});
await builder.Build().RunAsync();
```
Create the necessary Orleans storage tables:
```csharp
private static void InitializeSqliteDatabase(string connectionString)
{
using var connection = new SqliteConnection(connectionString);
connection.Open();
var command = connection.CreateCommand();
command.CommandText = @"
CREATE TABLE IF NOT EXISTS OrleansStorage (
GrainIdHash INTEGER NOT NULL,
GrainIdN0 INTEGER NOT NULL,
GrainIdN1 INTEGER NOT NULL,
GrainTypeHash INTEGER NOT NULL,
GrainTypeString TEXT NOT NULL,
GrainIdExtensionString TEXT,
ServiceId TEXT NOT NULL,
PayloadBinary BLOB,
PayloadJson TEXT,
PayloadXml TEXT,
ModifiedOn DATETIME NOT NULL,
Version INT
);";
command.ExecuteNonQuery();
}
```
Create a terminal-based client using Terminal.Gui:
```csharp
var client = new ClientBuilder()
.Configure<ClusterOptions>(options =>
{
options.ClusterId = "edge-cluster";
options.ServiceId = "edge-service";
})
.UseStaticClustering(new IPEndPoint(IPAddress.Parse("192.168.5.3"), 30000))
.Build();
await client.Connect();
var grain = client.GetGrain<ILedControllerGrain>("led");
// Send commands
await grain.SetLedColor(LedColor.Red);
var currentColor = await grain.GetCurrentColor();
```
**appsettings.Development.json (Node):**
```json
{
"Orleans": {
"SiloPort": 11111,
"GatewayPort": 30000,
"Storage": {
"ConnectionString": "Data Source=orleans.db"
}
}
}
```
**appsettings.Production.json (Controller):**
```json
{
"Orleans": {
"Gateways": [
"192.168.5.3:30000"
]
}
}
```
The Meadow platform provides cross-platform IoT hardware support. For Raspberry Pi LED control:
```csharp
public interface IOutputService
{
Task SetLedState(bool isOn);
}
public class RaspberryPiOutputService : IOutputService
{
private readonly IDigitalOutputPort _led;
public Task SetLedState(bool isOn)
{
_led.State = isOn;
return Task.CompletedTask;
}
}
```
**Development (localhost):**
```bash
dotnet run --project OrleansEdge.Node/OrleansEdge.Node.csproj
dotnet run --project OrleansEdge.Controller/OrleansEdge.Controller.csproj
```
**Production (Raspberry Pi):**
```bash
dotnet run --project OrleansEdge.Node/OrleansEdge.Node.csproj --environment Production
```
This solution requires Meadow projects at `../../wilderness/`:
Ensure these dependencies are available at the relative path before building.
1. Controller connects to Orleans cluster via gateway endpoint (port 30000)
2. Gets grain reference: `client.GetGrain<ILedControllerGrain>("led")`
3. Calls grain method: `await grain.SetLedColor(LedColor.Red)`
4. Grain updates state and persists to SQLite automatically
5. Grain communicates with hardware via Meadow OutputService
6. State survives grain deactivation and restarts
```bash
dotnet build OrleansEdge.slnx
dotnet build OrleansEdge.Core/OrleansEdge.Core.csproj
dotnet build OrleansEdge.Node/OrleansEdge.Node.csproj
dotnet build OrleansEdge.Controller/OrleansEdge.Controller.csproj
```
Leave a review
No reviews yet. Be the first to review this skill!
# Download SKILL.md from killerskills.ai/api/skills/orleans-edge-iot-control/raw