A 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.
- ✅ Flexible Condition Engine: Define alarms using five powerful, type-safe condition types:
- Thresholds: Trigger alarms when a numeric value goes above or below a set point (e.g.,
temperature > 80.0). - Predicates: Use custom logic (
Predicate<T>) for complex state validation (e.g.,status == "Error",isEmergencyStop == true). - Value Changes: React to the dynamics of a variable by comparing its old and new values (e.g.,
newValue > oldValue,Math.Abs(newValue - oldValue) > 10.0). - Delayed Conditions: Wrap any condition to only trigger alarms if the condition remains active for a specified duration (e.g., prevent nuisance alarms from brief spikes).
- Hysteresis Thresholds: Use separate trigger and clear thresholds to prevent oscillating alarms (e.g., trigger at 85°C, clear at 75°C).
- Thresholds: Trigger alarms when a numeric value goes above or below a set point (e.g.,
- 🔧 Fluent Condition Builders: Use the static
CommonConditionsclass for a clean, readable way to create common alarm logic likeOnTrue(),OnValueJump(),OnStringEquals(),WithDelay(), andOnHighValueHysteresis(). - ⏰ Time-Based Features: Advanced timing capabilities including delayed conditions for filtering transient spikes and hysteresis conditions for stable alarm behavior.
- 🔔 Event-Driven Alarms: Subscribe to
AlarmTriggeredandAlarmClearedevents to seamlessly integrate alarm logic into your application's workflow. - 🚀 Type-Safe & Generic: Monitor any
IComparableorIEquatabletype, from primitives (int,double,bool) andenumsto your own customrecordorclasstypes. - 🎛️ Stateful Alarm Management: The monitor tracks the state of all active alarms, preventing duplicate triggers and providing
Acknowledgefunctionality. - ⚡️ Thread-Safe by Design: Built with
ConcurrentDictionaryto ensure that registering variables and notifying value changes is safe across multiple threads. - 🏗️ Lightweight & Modern: A focused library with a minimal footprint, written in modern C# with clear, testable components.
The library is designed with a clear separation of concerns, making it easy to understand and extend.
- The Monitor (
ValueMonitor): The central engine and public API. It manages variable registrations, state, timing infrastructure, and orchestrates condition checking and event firing. - Conditions: Five types of declarative, type-safe "rules" that you define:
ThresholdCondition: Numeric threshold comparisonsPredicateCondition: Custom predicate logicValueChangeCondition: Old vs new value comparisonsDelayedCondition<T>: Wraps any condition with a time delayHysteresisThresholdCondition: Separate trigger and clear thresholds
- Common Conditions (
CommonConditions): A static helper class that acts as a factory for creating the most frequently used conditions, including time-based helpers likeWithDelay()andOnHighValueHysteresis(). - Timing Infrastructure (
ITimerProvider,TimerProvider,ConditionTimer): Manages time-based condition logic with testable abstractions. - Events (
AlarmEventArgs,ValueChangedEventArgs): The primary output mechanism. These classes carry all the context about an alarm or value change to your event handlers. - Data Models (
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 API Reference: The
ValueMonitorinterface is the primary entry point for all operations. Its public methods define the library's capabilities. - Common Conditions Reference: Explore this static class to see the available pre-built condition helpers, including time-based features.
- Time-Based Conditions: Examples and tests for delayed conditions and hysteresis thresholds.
- Unit Tests: The unit tests provide comprehensive, focused examples for every feature and condition type.
- Integration Tests: These tests showcase how to handle more complex, multi-alarm scenarios.
Contributions are welcome! Whether it's bug reports, feature requests, or pull requests, your help is appreciated.
- Fork the repository.
- Create a new branch for your feature or bug fix.
- Make your changes.
- Add or update unit/integration tests to cover your changes.
- Submit a Pull Request with a clear description of your changes.
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.