A flexible, type-safe C# library for monitoring variable values with configurable alarms, thresholds, and custom conditions.
$ dotnet add package philipp2604.VariableValueMonitorA flexible, modern .NET library for monitoring variable values and triggering alarms based on a rich set of configurable conditions. It's designed to be a lightweight, type-safe, and thread-safe core for any application that needs to react to state changes, such as in industrial automation, IoT, or system monitoring.
temperature > 80.0).Predicate<T>) for complex state validation (e.g., status == "Error", isEmergencyStop == true).newValue > oldValue, Math.Abs(newValue - oldValue) > 10.0).CommonConditions class for a clean, readable way to create common alarm logic like OnTrue(), OnValueJump(), OnStringEquals(), WithDelay(), and OnHighValueHysteresis().AlarmTriggered and AlarmCleared events to seamlessly integrate alarm logic into your application's workflow.IComparable or IEquatable type, from primitives (int, double, bool) and enums to your own custom record or class types.Acknowledge functionality.ConcurrentDictionary to ensure that registering variables and notifying value changes is safe across multiple threads.The library is designed with a clear separation of concerns, making it easy to understand and extend.
ValueMonitor): The central engine and public API. It manages variable registrations, state, timing infrastructure, and orchestrates condition checking and event firing.ThresholdCondition: Numeric threshold comparisonsPredicateCondition: Custom predicate logicValueChangeCondition: Old vs new value comparisonsDelayedCondition<T>: Wraps any condition with a time delayHysteresisThresholdCondition: Separate trigger and clear thresholdsCommonConditions): A static helper class that acts as a factory for creating the most frequently used conditions, including time-based helpers like WithDelay() and OnHighValueHysteresis().ITimerProvider, TimerProvider, ConditionTimer): Manages time-based condition logic with testable abstractions.AlarmEventArgs, ValueChangedEventArgs): The primary output mechanism. These classes carry all the context about an alarm or value change to your event handlers.ActiveAlarm, VariableRegistration): Internal and public records/classes that represent the state of monitored variables and active alarms.As an end-user, you will primarily interact with the ValueMonitor class and define your logic using the various Condition types.
VariableValueMonitor will be available on NuGet. You can install it using the .NET CLI:
dotnet add package philipp2604.VariableValueMonitor
Or via the NuGet Package Manager in Visual Studio.
Here's a practical example demonstrating how to monitor multiple variables with different types of conditions.
using VariableValueMonitor.Alarms.Conditions;
using VariableValueMonitor.Enums;
using VariableValueMonitor.Events;
using VariableValueMonitor.Monitor;
// 1. Initialize the ValueMonitor
var monitor = new ValueMonitor();
// 2. Subscribe to the alarm events
monitor.AlarmTriggered += OnAlarmTriggered;
monitor.AlarmCleared += OnAlarmCleared;
Console.WriteLine("--- Registering Variables for Monitoring ---");
// 3. Register variables with various conditions
// A temperature sensor with a high-temperature warning
monitor.RegisterVariable<double>("temp_sensor_1", "Main Boiler Temperature", 25.0,
new ThresholdCondition(AlarmType.Warning, AlarmDirection.UpperBound, 85.0, "High temperature detected!"));
// An emergency stop button (boolean)
monitor.RegisterVariable<bool>("emergency_stop_1", "Main Conveyor E-Stop", false,
CommonConditions.OnTrue(AlarmType.Alarm, "Emergency stop has been activated!"));
// A pressure sensor that alarms on a sudden jump
monitor.RegisterVariable<double>("pressure_sensor_1", "Hydraulic Pressure", 120.0,
CommonConditions.OnValueJump(AlarmType.Info, 20.0, "Rapid pressure change detected."));
// A machine state monitor using enums
monitor.RegisterVariable<MachineState>("machine_1", "CNC Machine State", MachineState.Running,
[CommonConditions.OnEnumEquals(AlarmType.Alarm, MachineState.Error, "Machine has entered an error state!")]);
monitor.RegisterVariable<MachineState>("machine_1", "CNC Machine State", MachineState.Running,
[CommonConditions.OnEnumChange(AlarmType.Info, MachineState.Running, MachineState.Maintenance, "Machine is now under maintenance.")]);
// A delayed condition - only triggers after temperature is high for 30 seconds
monitor.RegisterVariable<double>("temp_delayed", "Oven Temperature", 60.0,
CommonConditions.OnHighValueDelayed(200.0, TimeSpan.FromSeconds(30), "Sustained high temperature detected!"));
// A hysteresis condition - triggers at 85°C, clears at 75°C
monitor.RegisterVariable<double>("temp_hysteresis", "Reactor Temperature", 70.0,
CommonConditions.OnHighValueHysteresis(85.0, 75.0, "Reactor temperature alarm"));
Console.WriteLine("--- Simulating Value Changes ---\n");
// 4. Notify the monitor of new values
monitor.NotifyValueChanged("temp_sensor_1", 90.0); // Triggers "High temperature"
monitor.NotifyValueChanged("pressure_sensor_1", 155.5); // Triggers "Rapid pressure change"
monitor.NotifyValueChanged("emergency_stop_1", true); // Triggers "E-Stop activated"
monitor.NotifyValueChanged("machine_1", MachineState.Error); // Triggers "Error state"
// Demonstrate hysteresis behavior
monitor.NotifyValueChanged("temp_hysteresis", 90.0); // Triggers at 85°C threshold
monitor.NotifyValueChanged("temp_hysteresis", 80.0); // Stays active (above 75°C clear threshold)
monitor.NotifyValueChanged("temp_hysteresis", 74.0); // Clears at 75°C threshold
// Demonstrate delayed condition (would need to wait 30 seconds for actual trigger)
monitor.NotifyValueChanged("temp_delayed", 220.0); // Starts delay timer, no immediate alarm
monitor.NotifyValueChanged("temp_sensor_1", 80.0); // Clears the temperature alarm
Console.WriteLine($"\n--- Final State ---");
Console.WriteLine($"There are {monitor.GetActiveAlarms().Count} active alarms.");
// Clean up event handlers
monitor.AlarmTriggered -= OnAlarmTriggered;
monitor.AlarmCleared -= OnAlarmCleared;
// --- Event Handlers ---
void OnAlarmTriggered(object? sender, AlarmEventArgs e)
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine($"ALARM TRIGGERED for '{e.VariableName}' ({e.VariableId})");
Console.WriteLine($" - Type: {e.AlarmType}, Message: {e.Message}");
Console.WriteLine($" - Value changed from '{e.PreviousValue}' to '{e.CurrentValue}'");
Console.ResetColor();
}
void OnAlarmCleared(object? sender, AlarmEventArgs e)
{
Console.ForegroundColor = ConsoleColor.Green;
Console.WriteLine($"ALARM CLEARED for '{e.VariableName}' ({e.VariableId})");
Console.WriteLine($" - Message: {e.Message}");
Console.WriteLine($" - Current value is now '{e.CurrentValue}'");
Console.ResetColor();
}
public enum MachineState { Stopped, Running, Maintenance, Error }
Delayed conditions wrap existing conditions and only trigger alarms after the condition has been active for a specified duration. This prevents nuisance alarms from brief spikes or transient conditions.
// Only trigger after temperature exceeds 85°C for 5 seconds
var delayedCondition = CommonConditions.OnHighValueDelayed(85.0, TimeSpan.FromSeconds(5), "Sustained high temperature");
monitor.RegisterVariable("temp_sensor", "Temperature", 70.0, delayedCondition);
// You can wrap any condition type with a delay
var delayedPredicate = CommonConditions.WithDelay(
CommonConditions.OnTrue(AlarmType.Alarm, "Emergency stop activated"),
TimeSpan.FromSeconds(2));
monitor.RegisterVariable("e_stop", "Emergency Stop", false, delayedPredicate);
Hysteresis conditions use separate trigger and clear thresholds to prevent oscillating alarms around a single threshold value.
// Trigger alarm at 85°C, but don't clear until temperature drops to 75°C
var hysteresisCondition = CommonConditions.OnHighValueHysteresis(85.0, 75.0, "High temperature with hysteresis");
monitor.RegisterVariable("temp_sensor", "Temperature", 70.0, hysteresisCondition);
// Sequence of events:
monitor.NotifyValueChanged("temp_sensor", 90.0); // Alarm triggered (>= 85°C)
monitor.NotifyValueChanged("temp_sensor", 82.0); // Alarm stays active (> 75°C clear threshold)
monitor.NotifyValueChanged("temp_sensor", 74.0); // Alarm cleared (<= 75°C)
For testing or specialized timing needs, you can provide your own timer implementation:
// Use custom timer provider (useful for unit testing)
var customTimerProvider = new MyCustomTimerProvider();
var monitor = new ValueMonitor(customTimerProvider);
ValueMonitor interface is the primary entry point for all operations. Its public methods define the library's capabilities.Contributions are welcome! Whether it's bug reports, feature requests, or pull requests, your help is appreciated.
Please open an issue first to discuss any major changes.
This project is licensed under the MIT License. This is a permissive license that allows for reuse in both open-source and proprietary software. You are free to use, modify, and distribute this library as you see fit. See the LICENSE file for full details.