Files
Marc Schier 2960388560 Update headers and license files (#3388)
Update license headers to copy right year 2025, replace RCL/GPL headers with MIT, replace the nuget license with MIT (via license expression).
2025-12-04 19:44:40 +01:00

336 lines
10 KiB
C#

/* ========================================================================
* Copyright (c) 2005-2025 The OPC Foundation, Inc. All rights reserved.
*
* OPC Foundation MIT License 1.00
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*
* The complete license agreement can be found here:
* http://opcfoundation.org/License/MIT/1.00/
* ======================================================================*/
using System;
using Microsoft.Extensions.Logging;
using Opc.Ua;
namespace Alarms
{
/// <summary>
/// Alarm controller
/// </summary>
public class AlarmController
{
private const int kDefaultCycleTime = 180;
protected ILogger m_logger;
protected BaseDataVariableState m_variable;
protected int m_value;
protected bool m_increment = true;
protected DateTime m_nextTime = DateTime.Now;
protected DateTime m_stopTime = DateTime.Now;
protected int m_interval;
protected bool m_isBoolean;
protected bool m_allowChanges;
protected bool m_reset;
protected DateTime m_lastMaxValue;
protected bool m_validLastMaxValue;
protected int m_midpoint = AlarmDefines.NORMAL_START_VALUE;
public AlarmController(BaseDataVariableState variable, int interval, bool isBoolean, ITelemetryContext telemetry)
{
m_logger = telemetry.CreateLogger<AlarmController>();
m_variable = variable;
m_interval = interval;
m_isBoolean = isBoolean;
m_increment = true;
m_value = m_midpoint;
m_stopTime = m_stopTime.AddYears(5);
m_allowChanges = false;
}
/// <summary>
/// Start the Alarm cycles for n seconds.
/// </summary>
public virtual void Start(uint seconds = 0)
{
Stop();
m_logger.LogInformation("Start the Alarms for {Duration} seconds!", seconds);
m_validLastMaxValue = false;
m_nextTime = m_stopTime = DateTime.Now;
m_stopTime = m_stopTime.AddSeconds(seconds == 0 ? kDefaultCycleTime : seconds);
m_allowChanges = true;
}
public virtual void Stop()
{
m_logger.LogInformation("Stop the Alarms!");
m_value = m_midpoint;
m_increment = true;
m_allowChanges = false;
m_reset = true;
}
public virtual bool Update(ISystemContext systemContext)
{
bool valueSet = false;
if (CanSetValue())
{
int value = 0;
bool boolValue = false;
GetValue(ref value, ref boolValue);
m_logger.LogInformation("AlarmController Update Value = {Value}", value);
if (m_isBoolean)
{
m_variable.Value = boolValue;
}
else
{
m_variable.Value = value;
}
m_variable.Timestamp = DateTime.UtcNow;
m_variable.ClearChangeMasks(systemContext, false);
valueSet = true;
}
return valueSet;
}
protected virtual void SetNextInterval()
{
m_nextTime = DateTime.Now;
m_nextTime = m_nextTime.AddMilliseconds(m_interval);
}
public void ManualWrite(object value)
{
if (value.GetType().Name == "Int32")
{
// Don't let anyone write a value out of range
int potentialWrite = (int)value;
if (potentialWrite is >= AlarmDefines.MIN_VALUE and <= AlarmDefines.MAX_VALUE)
{
m_value = potentialWrite;
}
else
{
m_logger.LogError(
"AlarmController Received out of range manual write of {Value}",
value);
}
}
else if ((bool)value)
{
m_value = 70;
m_increment = true;
}
else
{
m_value = m_midpoint;
}
}
public virtual bool CanSetValue()
{
bool setValue = false;
if (DateTime.Now > m_stopTime)
{
Stop();
m_stopTime = DateTime.MaxValue;
}
else if (m_allowChanges || m_reset)
{
m_reset = false;
if (DateTime.Now > m_nextTime)
{
SetNextInterval();
setValue = true;
}
}
return setValue;
}
protected virtual void GetValue(ref int intValue, ref bool boolValue)
{
const int maxValue = 100;
const int minValue = 0;
TypicalGetValue(minValue, maxValue, ref intValue, ref boolValue);
}
public bool SupportsBranching { get; set; }
public virtual void SetBranchCount(int count)
{
}
protected void TypicalGetValue(
int minValue,
int maxValue,
ref int intValue,
ref bool boolValue)
{
int incrementValue = 5;
if (m_isBoolean)
{
incrementValue = 10;
}
if (m_increment)
{
m_value += incrementValue;
if (m_value >= maxValue)
{
if (m_validLastMaxValue)
{
m_logger.LogInformation(
"Cycle Time {CycleTime} Interval {Interval}",
DateTime.Now - m_lastMaxValue,
m_interval);
}
m_lastMaxValue = DateTime.Now;
m_validLastMaxValue = true;
m_increment = false;
}
}
else
{
m_value -= incrementValue;
if (m_value <= minValue)
{
m_increment = true;
}
}
intValue = m_value;
boolValue = IsBooleanActive();
}
public bool IsBooleanActive()
{
bool isActive = false;
if (m_value is >= AlarmDefines.BOOL_HIGH_ALARM or <= AlarmDefines.BOOL_LOW_ALARM)
{
isActive = true;
}
return isActive;
}
public int GetValue()
{
return m_value;
}
public int GetSawtooth()
{
return m_value;
}
public int GetSine(int minValue, int maxValue)
{
return CalcSine(minValue, maxValue, m_value);
}
public int CalcSine(int minValue, int maxValue, int value)
{
// What I want is a sawtooth compared against a sine value.
// This calculates a simular sine value that will have predictable differences between value and sine
/*
* https://www.mathsisfun.com/algebra/amplitude-period-frequency-phase-shift.html
* Sine with Phase Shift and Vertical Shift! This is what I want
* y = A sin(B(x + C)) + D
* A - Amplitude
* B - relates to period - This should extend the time period
* C - Phase Shift
* D - Vertical Shift
*
*/
const double twoPi = Math.PI * 2;
double normalSpan = maxValue - minValue;
double amplitude = normalSpan / 2;
double median = maxValue - amplitude;
double offsetValue = value - minValue;
double percentageOfRange = offsetValue / normalSpan;
double reducedPeriod = percentageOfRange / 2;
const double period = twoPi; // this would relate to the interval. Ignore for now.
const double phase = -0.25; // phaseShift;
double verticalShift = median; // amplitude
double calculated = (amplitude * Math.Sin(period * (reducedPeriod + phase))) +
verticalShift;
m_logger.LogTrace(
" Phase {Phase:0.00} Value {Value} Sine {Sine:0.00}" +
" Offset Value {OffsetValue:0.00} Span {NormalSpan:0.00}" +
" Percentage of Range {PercentageOfRange:0.00}",
phase,
value,
calculated,
offsetValue,
normalSpan,
percentageOfRange);
return (int)calculated;
}
public virtual bool ShouldSuppress()
{
return false;
}
public virtual bool ShouldUnsuppress()
{
return false;
}
public virtual void OnAddComment()
{
}
public virtual void OnAcknowledge()
{
}
public virtual void OnConfirm()
{
}
}
}