-- ======================
-- Description
-- ======================
--
-- Logic observes input.value list and when any item there changes or if in related list input.trigger any item is different than 0, then triggers all
-- variables in outputs list according to below rules:
--
-- lists input.value, input.active, setting.triggering.value and input.trigger contain items that are related by postfix (belongs to the same group - inputs).
-- "Changed input" is input, of which value in input.value list was changed or when value in input.trigger is different than 0 (logic at the end always resets
-- triggers). elements in input.active list tells if given input is active.
--
-- lists counters.cmp and outputs are related by postfix (belongs to the same group - outputs)
--
-- ======================
-- Parameters
-- ======================
--
-- setting.mode - variable determines work mode
-- 0 - AUTO_CP_INPUT - output variable in outputs list is triggered by changed input's value in input.value list.
-- 1 - AUTO_CP_VALUE - output variable in outputs list is triggered by changed input's value in setting.triggering.value list
-- 2 - MANUAL - to the output variable in outputs list is all time writes value of related item from setting.manual.value
-- 3 - ONLY_COPY - to the output variable in outputs list is all time writes last changed input's value from input.value list
--
-- setting.cmp.mode - variable determines feedback mechanism - comparing mode
-- 0 - NO_CMP - logic sets one time output variable in outputs list - not recommended, but sometimes it is only way.
-- 1 - CMP - logic sets output value until cmp variable in counters.cmp list equals setting.cmp.value
-- 2 - CMP_IN - logic sets output value utnil cmp variable in counters.cmp list equals last changed item in input.value list
--
-- input.active - list belongs to grop "inputs" and tells if given input is active (1) or not (0)
--
-- input.value - list belongs to group "inputs" and contains observed values. Logic watches elements in this list when one of them changes, then
-- logic triggers all variables in ouputs list.
--
-- input.trigger - list belongs to group "inputs" and contains variables, that when one of them is different than 0, then output variables are
-- triggered according to currently rules. Finally elements in this list are cleared.
--
-- setting.triggering.value - list belongs to group "inputs" and contains variables related with given input (by postfix), of whose values trigger outputs,
-- if given input changes, when logic works in AUTO_CP_VALUE mode.
--
-- setting.manual.value - this variable is copied to the all variables in outputs list if logic works in MANUAL mode
-- (read setting.mode)
--
-- counter.selected.input - postfix of last selected input.
--
-- setting.cmp.value - when logic triggers given output variable in outputs list, usually make sures that variable
-- is triggered successfully (working in CMP comparing mode). Then observes variables in counters.cmp list
-- and triggers output variable until it reaches setting.cmp.value.
--
-- outputs - output variables list, belongs to "outputs" group. Variables in this list are triggered according to above rules
--
-- counters.cmp - helper variables related with outputs list by postfix (belongs to "outputs" group). These variables are used to notify logic
-- that given variable in output list are really triggered.
--
-- ======================
-- mandatory variables
-- ======================
--
-- Logic expects following mandatory variables:
--
-- reload.trigger - causes reloading lua script
--
-- memcnt - current amount of memory used by lua in bytes
--
-- Logic expects following kv settings:
--
-- LuaScriptPath - path to the lua script - must be absolute
--
-- ======================
-- ChangeLog
-- ======================
--
-- 2017-12-21 ver 0.0.1
--
-- # input/output as a list
--
-- 2017-07-10 ver 0.0.0
--
-- # First release
--
-- user can use some functions provided by ibmanager.
-- ibmanager provides following functions to use:
--
-- function returns value of required ibmanager variable
-- @param fullName - string - variable name - name of variable of which value must be returned, for example "rs.0.id.255.input.t.0.value"
-- @return - string or integer - variable value or "nil" if variable not exist or is not readable
--
-- getValue(fullName)
-- function set value of given ibmanager variable
-- @param fullName - string - variable name - name of variable of which value we want to set, for example "rs.0.id.255.input.t.0.value"
-- @param value - string, int or boolean - value to set
-- @return - nothing
--
-- setValue(fullName, value)
-- function returns value of required ibmanager variable
-- @param key - placed in xml logic configuration file:
-- * as attribute: Name, in Var, RemoteVar and ImportVar elements in the case of stand alone variables
-- * as concatenation of two attributes: ListName.Postfix in VarListItem, RemoteVarListItem and ImportVarListItem elements
-- in the case of variables that are placed in lists
-- @return - string or integer - variable value or "nil" if variable not exist or is not readable
--
-- getLogicValue(key)
-- function set value of given ibmanager variable
-- @param key - placed in xml logic configuration file:
-- * as attribute: Name, in Var, RemoteVar and ImportVar elements in the case of stand alone variables
-- * as concatenation of two attributes: ListName.Postfix in VarListItem, RemoteVarListItem and ImportVarListItem elements
-- in the case of variables that are placed in lists
-- @param value - string, int or boolean - value to set
-- @return - nothing
--
-- setLogicValue(key, value)
-- function returns two sections of kvsettings from xml configuration file
-- returned value is two element table, each of these elements is table too.
-- indices of returned table are strings and equal "instance" and "global"
-- values of returned tables are tables and contain KVsettings for applicable section.
-- nested tables have form key = value, where key is index in nested table and value is value.
-- example: {"instance" = {"ikey1" = "ivalue1", "ikey2" = "gvalue2"}, "global" = {"gkey1" = "gvalue1", "gkey2" = "gvalue2", "gkey3" = "gvalue3"}}
-- @return - two dimensional array - kvsettings for global and instance sections
-- getKvSettings()
-- function schedules alert to send.
-- rules are defined in separated alert configuration files and are described in ibmanager instruction manual
-- @param id - alert identifier - must be defined in current logic configuration file in section: <Alert Id="any_identifier" ...
-- scheduleAlert(id)
-- function cancels alert sending, if was previously scheduled. if not then only wakes up alerts handling thread, so if there is no need to call this function, then do not call it.
-- rules are defined in separated alert configuration files and are described in ibmanager instruction manual
-- @param id - alert identifier - must be defined in current logic configuration file in section: <Alert Id="any_identifier" ...
-- cancelAlert(name)
-- function returns table, containing Variables that belongs to required list.
-- @param listName - Name attribute of VarList, RemoteVarList or ImportVarList elemetnst in configurationfile
-- @return array of key-value pairs. Key - variable postfix, Value - Variable value
-- getVarList(listName)
-- function returns monotonic system clock value, that elapsed since specific epoch
-- returned time is expressed in milliseconds.
-- getClock()
-- function logs message to file, if defined in configuration file log level is less than passed to this function
-- @param logLevel - one of:
-- LogLevel.TraceLo
-- LogLevel.Trace
-- LogLevel.TraceHi
-- LogLevel.DebugLo
-- LogLevel.Debug
-- LogLevel.DebugHi
-- LogLevel.Info
-- LogLevel.Notice
-- LogLevel.Warning
-- LogLevel.Error
-- LogLevel.Critical
-- @param logMessage - string to log
-- log(logLevel, logMessage)
-- ibmanager provides following global variables:
-- logic type, (in this case it will always be "Lua") - the same as in logic configuration file in section: <Logic Type="Lua" ...
-- LOGIC_TYPE
-- logic version, the same as in logic configuration file in section: <Logic ... Version="x.y.z" ...
-- LOGIC_VERSION
-- logic sub-type, the same as in logic configuration file in section: <Logic ... SubType="Hysteresis" ...
-- LOGIC_SUBTYPE
-- logic sub-version, the same as in logic configuration file in section: <Logic ... SubVersion="x.y.z" ...
-- LOGIC_SUBVERSION
-- logic instance name - the same as in logic configuration file, in section: <Instance Name="0">
-- LOGIC_INSTANCE_NAME
-- add script directory to package path
package.path = package.path .. ";./logic/scripts/utils/?.lua";
package.path = package.path .. ";/ibsystem/ibmanager/logic/lua/utils/?.lua";
package.path = package.path .. ";/work/insbud/iblogics/Lua/Scripts/utils/?.lua";
-- ---------------------------------------------------------------------------------------------------------
-- global - enums
-- ---------------------------------------------------------------------------------------------------------
-- work mode
WorkMode = {AUTO_CP_INPUT = 0, AUTO_CP_VALUE = 1, MANUAL = 2, ONLY_COPY = 3};
-- compare mode
CompareMode = {NO_CMP = 0, CMP = 1, CMP_IN = 2};
-- ---------------------------------------------------------------------------------------------------------
--
-- ---------------------------------------------------------------------------------------------------------
-- logic class
Logic = {};
Logic.__index = Logic;
-- ---------------------------------------------------------------------------------------------------------
--
-- ---------------------------------------------------------------------------------------------------------
function Logic.create()
--our new object
local logic = {};
setmetatable(logic, Logic);
--our object fields initialization
logic.outItems = {};
logic.inItems = {};
logic.lastChangedInput = nil;
-- adding outItems and filling counter and its value
local outputsListName = "outputs";
local lst = getVarList(outputsListName);
if lst ~= nil then
for postfix, val in pairs(lst) do
local varName = outputsListName .. "." .. postfix;
local item =
{
outputVarName = varName,
cmpVarName = nil,
update = true
};
logic.outItems[postfix] = item;
end
end
local cmpListName = "counters.cmp";
lst = getVarList(cmpListName);
if lst ~= nil then
for postfix, _ in pairs(lst) do
local item = logic.outItems[postfix];
if item ~= nil then
item.cmpVarName = cmpListName .. "." .. postfix;
else
error ("Configuration error, defined " .. postfix .. " postfix in list " .. cmpListName .. " but not defined them in list " .. outputsListName);
end
end
end
local lastChangedInput = nil;
-- adding inItems and input.value list
local inValuesListName = "input.value";
lst = getVarList(inValuesListName);
--print("init order:");
if lst ~= nil then
for postfix, val in pairs(lst) do
--print(postfix);
local varName = inValuesListName .. "." .. postfix;
local item =
{
inValueVarName = varName,
inActiveVarName = nil,
inTriggerVarName = nil,
trigValueVarName = nil,
lastValue = getLogicValue(varName),
id = postfix
};
logic.inItems[postfix] = item;
--unfortunately order is random
if (lastChangedInput == nil) then
lastChangedInput = logic.inItems[postfix];
end
end
end
-- adding input.active list elements
local inActiveListName = "input.active";
lst = getVarList(inActiveListName);
if lst ~= nil then
for postfix, _ in pairs(lst) do
local item = logic.inItems[postfix];
if item ~= nil then
item.inActiveVarName = inActiveListName .. "." .. postfix;
else
error ("Configuration error, defined " .. postfix .. " postfix in list " .. inActiveListName .. " but not defined them in list " .. inValuesListName);
end
end
end
-- adding input.trigger list elements
local inTriggerListName = "input.trigger";
lst = getVarList(inTriggerListName);
if lst ~= nil then
for postfix, _ in pairs(lst) do
local item = logic.inItems[postfix];
if item ~= nil then
item.inTriggerVarName = inTriggerListName .. "." .. postfix;
--print(item.inTriggerVarName);
else
error ("Configuration error, defined " .. postfix .. " postfix in list " .. inTriggerListName .. " but not defined them in list " .. inValuesListName);
end
end
end
-- adding setting.triggering.value list elements
local trigValueListName = "setting.triggering.value";
lst = getVarList(trigValueListName);
if lst ~= nil then
for postfix, _ in pairs(lst) do
local item = logic.inItems[postfix];
if item ~= nil then
item.trigValueVarName = trigValueListName .. "." .. postfix;
else
error ("Configuration error, defined " .. postfix .. " postfix in list " .. trigValueListName .. " but not defined them in list " .. inValuesListName);
end
end
end
--checking correctness
for postfix, item in pairs(logic.inItems) do
if (item.trigValueVarName == nil) then
error ("Configuration error, not defined " .. postfix .. " postfix in list " .. trigValueListName);
end
end
logic.lastChangedInput = lastChangedInput;
return logic;
end
-- ---------------------------------------------------------------------------------------------------------
-- function determines last changed input and returns boolead value that tells if outputs should be refreshed
-- last changed input is stored in self.lastChangedInput
-- ---------------------------------------------------------------------------------------------------------
function Logic:handleInputs()
--determining last changed input. first on list has higher priority
local changedInput = nil;
for _, item in pairs(self.inItems) do
local value = getLogicValue(item.inValueVarName);
local active = getLogicValue(item.inActiveVarName);
-- print("item: " .. item.id .. " active: " .. active .. " value: " .. value);
--input.active list element is optional, so if is nil, then below condition is true
if (active ~= 0) then
if (changedInput == nil) then
if (value ~= item.lastValue) then
-- input changed, remember
-- print ("changed item: " .. item.id .. " from " .. item.lastValue .. " to " .. value);
changedInput = item;
elseif (item.inTriggerVarName ~= nil) then
if (getLogicValue(item.inTriggerVarName) ~= 0) then
-- someone marked current input as changed, remember it
-- print ("forced outputs to update by item: " .. item.id .. " with value" .. value);
changedInput = item;
end
end
end
end
-- remember current value
item.lastValue = value;
-- clearing trigger if trigger variable defined for given input
if (item.inTriggerVarName ~= nil) then
setLogicValue(item.inTriggerVarName, 0);
end
end
if (changedInput ~= nil) then
self.lastChangedInput = changedInput;
return true;
else
return false;
end
end
-- ---------------------------------------------------------------------------------------------------------
--
-- ---------------------------------------------------------------------------------------------------------
function Logic:call()
local workMode = getLogicValue("setting.mode");
local inputChanged = self:handleInputs();
if (self.lastChangedInput == nil) then
-- no input items
return;
end
if (workMode == WorkMode.ONLY_COPY) then
for _, item in pairs(self.outItems) do
setLogicValue(item.outputVarName, self.lastChangedInput.lastValue);
end
elseif (workMode == WorkMode.MANUAL) then
local manualValue = getLogicValue("setting.manual.value");
for _, item in pairs(self.outItems) do
setLogicValue(item.outputVarName, manualValue);
end
else
--auto mode
local settingCmpMode = getLogicValue("setting.cmp.mode");
local valueToSet = workMode == WorkMode.AUTO_CP_INPUT and self.lastChangedInput.lastValue or getLogicValue(self.lastChangedInput.trigValueVarName);
local cmpValue = settingCmpMode == CompareMode.CMP_IN and self.lastChangedInput.lastValue or getLogicValue("setting.cmp.value");
for key, item in pairs(self.outItems) do
if (inputChanged) then
--set always when input changed
setLogicValue(item.outputVarName, valueToSet);
--print("set value");
item.update = true;
elseif (settingCmpMode ~= CompareMode.NO_CMP) then
if (item.cmpVarName == nil) then
error("Selected comparing mode but not set variable to compare in counters.cmp list for postfix " .. key);
elseif (item.update) then
local compareCntr = getLogicValue(item.cmpVarName);
if (compareCntr ~= cmpValue) then
setLogicValue(item.outputVarName, valueToSet);
--print("set value again");
else
item.update = false;
end
end
end
end
end
setLogicValue("counter.selected.input", self.lastChangedInput.id);
end
-- ---------------------------------------------------------------------------------------------------------
-- main logic state object
g_logic = nil;
SUPPORTED_SUBLOGIC_TYPE = "Trigger";
SUPPORTED_SUBLOGIC_VERSION = "0.0.1";
g_versionChecked = false;
-- ---------------------------------------------------------------------------------------------------------
-- entry point to the logic
-- @param firstCall - tells if logic is called first time
-- @return - nothing
function onLogicCall(firstCall)
if not g_versionChecked then
-- checking sublogic type and sublogic version
if LOGIC_SUBTYPE ~= SUPPORTED_SUBLOGIC_TYPE then
error("Wrong logic sub-type. expected " .. SUPPORTED_SUBLOGIC_TYPE .. " but used " .. LOGIC_SUBTYPE);
end
local versionWithoutBuild = string.match(LOGIC_SUBVERSION, "[0-9]+%.[0-9]+%.[0-9]+");
if versionWithoutBuild ~= SUPPORTED_SUBLOGIC_VERSION then
error("Wrong logic sub-version. expected " .. SUPPORTED_SUBLOGIC_VERSION .. " but used " .. LOGIC_SUBVERSION);
end
g_versionChecked = true;
end
if g_logic == nil then
g_logic = Logic.create();
end
g_logic:call()
end
-- ---------------------------------------------------------------------------------------------------------