1
0
mirror of https://github.com/metrostroi-repo/MetrostroiAddon.git synced 2026-05-02 00:42:29 +00:00
Files
MetrostroiAddon/lua/metrostroi/systems/sys_relay.lua

376 lines
14 KiB
Lua

--------------------------------------------------------------------------------
-- Generic relay with configureable parameters
--------------------------------------------------------------------------------
-- Copyright (C) 2013-2018 Metrostroi Team & FoxWorks Aerospace s.r.o.
-- Contains proprietary code. See license.txt for additional information.
--------------------------------------------------------------------------------
Metrostroi.DefineSystem("Relay")
local relay_types = {
["PK-162"] = {
hasCoil = true,
pneumatic = true,
contactor = true,
pickup_current = 0.015*math.random() + 0.085,
},
["Switch"] = {
contactor = true,
},
["GV_10ZH"] = {
contactor = true,
normally_closed = true,
},
["VA21-29"] = {
contactor = true,
normally_closed = true,
bass = true,
},
["AVU-045"] = {
bass = true,
},
["Switch"] = {
close_time = 0,
open_time = 0,
},
["ARS"] = {
hasCoil = true,
close_time = 0,
open_time = 0,
},
["ARS-DOP"] = {
hasCoil = true,
pickup_current = 0.01*math.random() + 0.04,
},
["KPD-110E"] = {
hasCoil = true,
coil_res = 50,
},
["REV-813T"] = {
hasCoil = true,
},
["REV-814T"] = {
hasCoil = true,
},
["REV-811T"] = {
hasCoil = true,
},
["REV-821"] = {
hasCoil = true,
},
["PR-143"] = {
hasCoil = true,
},
["KPP-113"] = {
hasCoil = true,
},
["KPD-110"] = {
hasCoil = true,
},
["KPP-110"] = {
hasCoil = true,
coil_res = 85,
},
["REM-651D"] = {
hasCoil = true,
},
["R-52B"] = {
hasCoil = true,
},
["RPUZ-114-T-UHLZA"] = {
hasCoil = true,
},
["TRTP-115"] = {
hasCoil = true,
},
}
function TRAIN_SYSTEM:Initialize(parameters,extra_parameters)
----------------------------------------------------------------------------
-- Initialize parameters
if not parameters then parameters = {} end
if type(parameters) ~= "table" then
relay_type = parameters
if relay_types[relay_type] then
parameters = relay_types[relay_type]
else
print("[sys_relay.lua] Unknown relay type: "..parameters, self.Name)
parameters = {}
end
parameters.relay_type = relay_type
end
-- Create new table
local old_param = parameters
parameters = {} for k,v in pairs(old_param) do parameters[k] = v end
-- Add extra parameters
if type(extra_parameters) == "table" then
for k,v in pairs(extra_parameters) do
parameters[k] = v
end
end
-- Contactors have different failure modes
parameters.contactor = parameters.contactor or false
-- Should the relay be initialized in 'closed' state
parameters.normally_closed = parameters.normally_closed or false
-- Time in which relay will close (seconds)
parameters.close_time = parameters.close_time or 0.050
-- Time in which relay will open (seconds)
parameters.open_time = parameters.open_time or 0.050
-- Is relay latched (stays in its position even without voltage)
parameters.latched = parameters.latched or false
-- Should relay be spring-returned to initial position
parameters.returns = parameters.returns or (not parameters.latched)
-- Trigger level for the relay
parameters.trigger_level = parameters.trigger_level or 0.5
-- Relay pickup current, A
parameters.pickup_current = parameters.pickup_current or 0.008*math.random() + 0.028
-- Relay holding current, A
parameters.holding_current = parameters.holding_current or 0.006*math.random() + 0.011
-- Relay coil resistance, Ohm
parameters.coil_res = parameters.coil_res or parameters.hasCoil and math.random(100,300) or 1e12
for k,v in pairs(parameters) do
self[k] = v
end
----------------------------------------------------------------------------
-- Relay parameters
if self.close_time == 0 then
FailSim.AddParameter(self,"CloseTime", { value = parameters.close_time})
else
FailSim.AddParameter(self,"CloseTime", { value = parameters.close_time, precision = self.contactor and 0.35 or 0.10, min = 0.010, varies = true })
end
if self.open_time == 0 then
FailSim.AddParameter(self,"OpenTime", { value = parameters.open_time})
else
FailSim.AddParameter(self,"OpenTime", { value = parameters.open_time, precision = self.contactor and 0.35 or 0.10, min = 0.010, varies = true })
end
-- Did relay short-circuit?
FailSim.AddParameter(self,"ShortCircuit", { value = 0.000, precision = 0.00 })
-- Was there a spurious trip?
FailSim.AddParameter(self,"SpuriousTrip", { value = 0.000, precision = 0.00 })
-- Calculate failure parameters
local MTBF = parameters.MTBF or 1000000 -- cycles, mean time between failures
local MFR = 1/MTBF -- cycles^-1, total failure rate
local openWeight,closeWeight
-- FIXME
openWeight = self.open_weight or 0.25
closeWeight = self.close_weight or 0.25
--[[if self.Contactor then
openWeight = 0.25
closeWeight = 0.25
elseif self.NormallyOpen then
openWeight = 0.4
closeWeight = 0.1
else
openWeight = 0.1
closeWeight = 0.4
end]]--
-- Add failure points
FailSim.AddFailurePoint(self, "CloseTime", "Mechanical problem (close time not nominal)",
{ type = "precision", value = 0.5, mfr = MFR*0.65*openWeight, recurring = true } )
FailSim.AddFailurePoint(self, "OpenTime", "Mechanical problem (open time not nominal)",
{ type = "precision", value = 0.5, mfr = MFR*0.65*closeWeight , recurring = true } )
FailSim.AddFailurePoint(self, "CloseTime", "Stuck closed",
{ type = "value", value = 1e9, mfr = MFR*0.65*openWeight, dmtbf = 0.2 } )
FailSim.AddFailurePoint(self, "OpenTime", "Stuck open",
{ type = "value", value = 1e9, mfr = MFR*0.65*closeWeight , dmtbf = 0.4 } )
FailSim.AddFailurePoint(self, "SpuriousTrip", "Spurious trip",
{ type = "on", mfr = MFR*0.20, dmtbf = 0.4 } )
--FailSim.AddFailurePoint(self, "ShortCircuit", "Short-circuit",
--{ type = "on", mfr = MFR*0.15, dmtbf = 0.2 } )
----------------------------------------------------------------------------
-- Initial relay state
if self.normally_closed then
self.TargetValue = 1.0
self.Value = 1.0
else
self.TargetValue = self.defaultvalue or 0.0
self.Value = self.defaultvalue or 0.0
end
-- Time when relay should change its value
self.Time = 0
self.ChangeTime = nil
self.Blocked = 0
-- Extra fields for current sim
self.Current = 0
-- This increases precision at cost of perfomance
self.SubIterations = parameters.iterations or 1--relay
end
function TRAIN_SYSTEM:Inputs()
return { "Open","Close","+","-","Set","Toggle","Block","OpenBypass","Check","OpenTime","CloseTime"}
end
function TRAIN_SYSTEM:Outputs()
return { "Value" , "Blocked", "TargetValue", "pickup_current" , "holding_current" , "Current" , "hasCoil" }
end
function TRAIN_SYSTEM:TriggerInput(name,value)
-- Boolean values accepted
if type(value) == "boolean" then value = value and 1 or 0 end
if name == "OpenTime" then
self.open_time = value
FailSim.AddParameter(self,"OpenTime", { value = self.open_time})
end
if name == "CloseTime" then
self.close_time = value
FailSim.AddParameter(self,"CloseTime", { value = self.close_time})
end
if name == "Reset" then
if self.normally_closed then
self:TriggerInput("Set",1)
else
self:TriggerInput("Set",self.defaultvalue or 0.0)
end
end
--print(name)
if name == "Check" then
if value < 0 and self.Value == 1 then
self:TriggerInput("Set",0)
--self:TriggerInput("Set",0)
self.Train:PlayOnce("av_off","cabin",0.7,70)
end
return
end
if value == -1 and self.relay_type == "VA21-29" then
self:TriggerInput("Set",0)
return
end
if name == "OpenBypass" then
if (not self.ChangeTime) and (self.TargetValue ~= 0.0) then
self.ChangeTime = self.Time + FailSim.Value(self,"OpenTime")
end
self.TargetValue = 0.0
if self.ChangeTime==self.Time and self.Train.DeltaTime then self:Think(self.Train.DeltaTime) end
return
end
if self.Blocked > 0 and name ~= "Block" and (name == "Close" and self.relay_type == "PK-162" or self.relay_type ~= "PK-162") then return end
-- Open/close coils of the relay
if (name == "Block") then
self.Blocked = value
elseif (name == "Close") and (value > self.trigger_level) and (self.Value ~= 1.0 or self.TargetValue ~= 1.0) then --(self.TargetValue ~= 1.0 and self.rpb))
if self.pneumatic and self.Train.Pneumatic.TrainLinePressure < 3 then return end
if (not self.ChangeTime) or (self.TargetValue ~= 1.0) then
self.ChangeTime = self.Time + FailSim.Value(self,"CloseTime")
end
if self.Value == 1.0 then self.ChangeTime = nil end
self.TargetValue = 1.0
if self.ChangeTime==self.Time and self.Train.DeltaTime then self:Think(self.Train.DeltaTime) end
elseif (name == "Open") and (value > self.trigger_level) and (self.Value ~= 0.0 or self.TargetValue ~= 0.0) then
if (not self.ChangeTime) or (self.TargetValue ~= 0.0) then
self.ChangeTime = self.Time + FailSim.Value(self,"OpenTime")
end
self.TargetValue = 0.0
if self.ChangeTime==self.Time and self.Train.DeltaTime then self:Think(self.Train.DeltaTime) end
elseif name == "NoOpenTime" and value > 0 then
self.ChangeTime = self.Time
elseif (name == "+") and (self.Value < (self.maxvalue or self.three_position and 2 or 1)) and value > 0 then
self.ChangeTime = self.Time + FailSim.Value(self,"CloseTime")
self.TargetValue = math.min(self.maxvalue or self.three_position and 2 or 1,self.Value+1)
if self.ChangeTime==self.Time and self.Train.DeltaTime then self:Think(self.Train.DeltaTime) end
elseif (name == "-") and (self.Value > 0) and value > 0 then
self.ChangeTime = self.Time + FailSim.Value(self,"OpenTime")
self.TargetValue = math.max(0.0,self.Value-1)
if self.ChangeTime==self.Time and self.Train.DeltaTime then self:Think(self.Train.DeltaTime) end
elseif name == "Set" then
if self.pneumatic and value > 0 and self.Train.Pneumatic.TrainLinePressure < 3 then return end
if self.maxvalue then
if not self.ChangeTime then
self.ChangeTime = self.Time + FailSim.Value(self,"OpenTime")
end
self.TargetValue = math.max(0.0,math.min(self.maxvalue,math.floor(value)))
if self.ChangeTime==self.Time and self.Train.DeltaTime then self:Think(self.Train.DeltaTime) end
elseif self.three_position then
if not self.ChangeTime then
self.ChangeTime = self.Time + FailSim.Value(self,"OpenTime")
end
self.TargetValue = math.max(0.0,math.min(2.0,math.floor(value)))
if self.ChangeTime==self.Time and self.Train.DeltaTime then self:Think(self.Train.DeltaTime) end
else
if value > self.trigger_level
then self:TriggerInput("Close",self.trigger_level+1)
else self:TriggerInput("Open",self.trigger_level+1)
end
end
elseif (name == "Toggle") and (value > 0.5) then
if self.maxvalue then
self:TriggerInput("Set",self.Value > self.maxvalue-1 and 0 or self.Value+1)
elseif self.three_position then
self:TriggerInput("Set",self.Value > 1 and 0 or self.Value+1)
else
self:TriggerInput("Set",(1.0 - self.Value)*(self.trigger_level+1))
end
end
end
function TRAIN_SYSTEM:Think(dT)
--print(self.relay_type)
self.Time = self.Time + dT
-- Short-circuited relay
if FailSim.Value(self,"ShortCircuit") > 0.5 then
self.Value = 1.0
return
end
-- Spurious trip
if FailSim.Value(self,"SpuriousTrip") > 0.5 then
self.SpuriousTripTimer = self.Time + (0.5 + 2.5*math.random())
FailSim.ResetParameter(self,"SpuriousTrip",0.0)
FailSim.Age(self,1)
-- Simulate switch right away
self.Value = 1.0 - self.Value
self.TargetValue = self.Value
self.ChangeTime = nil
end
if self.SpuriousTripTimer and (self.Time > self.SpuriousTripTimer) then
self.Value = self.TargetValue
self.SpuriousTripTimer = nil
end
if self.hasCoil then
self.Current = self.TargetValue*self.Train.Battery.eds_eq/self.coil_res
end
if self.hasCoil and self.Value > 0.5 and self.Current < self.holding_current then
self:TriggerInput("Open",1)
end
-- Switch relay
if self.ChangeTime and (self.Time > self.ChangeTime) and not self.SpuriousTripTimer then
if self.TargetValue > 0.5 and self.Current >= self.pickup_current or self.TargetValue < 0.5 and self.Current < self.holding_current or not self.hasCoil then
if self.bass and self.Value ~= self.TargetValue then
if self.bass_separate then
if self.TargetValue > 0 then
self.Train:PlayOnce(self.Name.."_on","bass",1)
else
self.Train:PlayOnce(self.Name.."_off","bass",1)
end
else
self.Train:PlayOnce(self.Name,"bass",self.TargetValue)
end
end
self.Value = self.TargetValue
self.ChangeTime = nil
-- Age relay a little
FailSim.Age(self,1)
end
end
end