mirror of
https://github.com/OPCFoundation/UA-.NETStandard.git
synced 2026-10-29 07:07:24 +08:00
372 lines
12 KiB
C#
372 lines
12 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;
|
|
using Quickstarts.Servers;
|
|
|
|
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.StartAlarms(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.StopAlarms();
|
|
|
|
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.UpdateValue(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.OutOfRangeManualWrite(potentialWrite);
|
|
}
|
|
}
|
|
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.CycleTime(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.CalculatedSine(
|
|
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()
|
|
{
|
|
}
|
|
}
|
|
|
|
internal static partial class AlarmControllerLog
|
|
{
|
|
[LoggerMessage(
|
|
EventId = QuickstartsServersEventIds.AlarmController + 0, Level = LogLevel.Information,
|
|
Message = "Start the Alarms for {Duration} seconds!")]
|
|
public static partial void StartAlarms(this ILogger logger, uint duration);
|
|
|
|
[LoggerMessage(
|
|
EventId = QuickstartsServersEventIds.AlarmController + 1, Level = LogLevel.Information,
|
|
Message = "Stop the Alarms!")]
|
|
public static partial void StopAlarms(this ILogger logger);
|
|
|
|
[LoggerMessage(
|
|
EventId = QuickstartsServersEventIds.AlarmController + 2, Level = LogLevel.Information,
|
|
Message = "AlarmController Update Value = {Value}")]
|
|
public static partial void UpdateValue(this ILogger logger, int value);
|
|
|
|
[LoggerMessage(
|
|
EventId = QuickstartsServersEventIds.AlarmController + 3, Level = LogLevel.Error,
|
|
Message = "AlarmController Received out of range manual write of {Value}")]
|
|
public static partial void OutOfRangeManualWrite(this ILogger logger, int value);
|
|
|
|
[LoggerMessage(
|
|
EventId = QuickstartsServersEventIds.AlarmController + 4, Level = LogLevel.Information,
|
|
Message = "Cycle Time {CycleTime} Interval {Interval}")]
|
|
public static partial void CycleTime(this ILogger logger, TimeSpan cycleTime, int interval);
|
|
|
|
[LoggerMessage(
|
|
EventId = QuickstartsServersEventIds.AlarmController + 5, Level = LogLevel.Trace,
|
|
Message = " 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}")]
|
|
public static partial void CalculatedSine(
|
|
this ILogger logger,
|
|
double phase,
|
|
int value,
|
|
double sine,
|
|
double offsetValue,
|
|
double normalSpan,
|
|
double percentageOfRange);
|
|
}
|
|
|
|
}
|