Skip to main content

Points and Acquisition

The point table names protocol addresses as business values. A Modbus register, S7 DB address, FINS memory address, Host Link word, or EtherNet/IP tag can become a point such as temperature.

The acquisition loop polls those points on an interval, so you do not need to write your own while true, Task.Delay, retry, and UI refresh code.

Complete Example

var memory = new ModbusSlaveMemory();
memory.HoldingRegisters[0] = 185;
memory.HoldingRegisters[1] = 200;

await using var app = ZeusHost.Create(builder =>
{
builder.AddAcquisition(TimeSpan.FromMilliseconds(500));
builder.AddVirtualChannel("bus", new ModbusSlaveResponder(1, ModbusTransport.Rtu, memory));
builder.AddModbusRtu("oven", "bus", unitId: 1, points: map =>
{
map.HoldingRegister("temperature", 0, 0.1, new PointAlarmLimits(high: 80));
map.HoldingRegister("setpoint", 1, 0.1).Writable("setpoint");
map.Coil("heater", 2).Writable("heater");
});
});

app.Points.Changed += (_, e) =>
{
Console.WriteLine($"{e.Current.QualifiedName} = {e.Current.Value}");
};

await app.StartAsync();
var temperature = app.Points.Get<double>("temperature");

Write by Point Name

Writable points can be operated by business name and engineering value:

await app.Points.WriteAsync("setpoint", 80.0);
await app.Points.WriteAsync("heater", true);

Zeus finds the owning device, reverses scale when needed, sends the protocol write, and updates the point table. If the write fails, the point snapshot gets an Error and the exception is still thrown to the caller.

Point typeWritableNotes
Holding registerYes, after .Writable(...) or JSON writable: truescale means callers pass engineering values
CoilYes, after explicit writable markerPass true / false
Input register, discrete input, S7 input areaNoProtocol/data area is read-only
Custom conversion without reversible scaleNoZeus cannot reliably invert it

S7, FINS, Host Link, and EtherNet/IP use the same app.Points.WriteAsync(...) model for writable areas or tags.

Why Not Write Your Own Loop

A manual loop works at first:

while (true)
{
var values = await oven.ReadHoldingRegistersAsync(0, 2);
await Task.Delay(500);
}

Real apps quickly need merged consecutive reads, stale-value retention after failures, multiple devices, UI change notifications, alarms, and write-back. The point table centralizes those behaviors.

Names and History

Use the short name if it is unique:

var value = app.Points.Get<double>("temperature");

Use the qualified name when multiple devices expose the same point:

var ovenTemperature = app.Points.Get<double>("oven.temperature");

Each point keeps the latest 128 successful samples by default:

var recent = app.Points.GetHistory("temperature");

Polling failures update the current snapshot error but do not enter the successful-history buffer.

Alarm Limits

map.HoldingRegister("temperature", 0, 0.1, new PointAlarmLimits(low: 10, high: 80));

var snapshot = app.Points.Get("temperature");
if (snapshot.IsAlarmed)
{
Console.WriteLine($"{snapshot.QualifiedName} alarm: {snapshot.AlarmState}");
}

Alarm limits are evaluated after scaling.

UI Binding

app.Points.BindTo("temperature", temperatureLabel, value => $"{value:F1} C");

The UI only owns display formatting; acquisition threading and protocol details stay inside Zeus.

Next: JSON Configuration, Siemens S7, or EtherNet/IP.