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_battery.lua

232 lines
13 KiB
Lua
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
--------------------------------------------------------------------------------
-- Battery (HKH-80 type NiCd battery)
--------------------------------------------------------------------------------
-- Copyright (C) 2013-2018 Metrostroi Team & FoxWorks Aerospace s.r.o.
-- Contains proprietary code. See license.txt for additional information.
--------------------------------------------------------------------------------
Metrostroi.DefineSystem("Battery")
TRAIN_SYSTEM.DontAccelerateSimulation = true
function TRAIN_SYSTEM:Initialize()
-- Предохранители цепей (ПА1, ПА2)
self.Train:LoadSystem("PA1","Relay","PP-28", { trigger_level = 31.5 }) -- A
self.Train:LoadSystem("PA2","Relay","PP-28", { trigger_level = 31.5 }) -- A
-- Battery parameters
self.ElementCapacity = 80 -- A*hour
self.Capacity = self.ElementCapacity * 3600
self.Charge = self.Capacity
self.FullCapacity = self.Capacity
-- Current through battery/A
self.Current = 0
self.Charging = 0
self.ElementCount = 52
self.StartVoltage = 75 -- 1.44 volt per fully charged new NiCd-cell
self.SoC = 0.5 + math.random()*0.5 -- 50-100 %
local EMF_soc, Uh_soc, tvb_sign
EMF_soc=-0.68175*self.SoC^8+8.82823*self.SoC^7-24.43179*self.SoC^6+31.87221*self.SoC^5-23.97881*self.SoC^4+11.24774*self.SoC^3-3.40685*self.SoC^2+0.74692*self.SoC+1.22076
Uh_soc=2.62496*self.SoC^8-12.77132*self.SoC^7+22.37586*self.SoC^6-18.04921*self.SoC^5+6.14667*self.SoC^4+0.26467*self.SoC^3-0.82125*self.SoC^2+0.21246*self.SoC+0.02641
self.TargetVoltage = (EMF_soc - Uh_soc)*self.ElementCount
self.Voltage = self.TargetVoltage
self.Vpart = 0
self.CellIRes = 0.009 -- 9 mOhm is a standard^w fake internal resistance of a fully-charged and rested new HKH-80 Ah NiCd-cell
self.IResistance = self.CellIRes*self.ElementCount
--self.SoC0v = 52 -- 52 volts at 0% state of charge assuming 1.0 volt per fully discharged cell
self.CutoffVoltage = 45 -- we want deep discharge <_<
self.EthaCE0 = 0.94 -- Coulomb efficiency coeff
self.EthaCE = self.EthaCE0
self.Ibatt = 0
self.eds_eq = 0
self.Consumers = {}
self.Dischar = false
self.ComputerCar = false
---------------------------------------------
-- 10 mV/min — voltage covery/recovery speed during first 30 minutes after charging/discharging stopped
-- 1.0 (1.2) Volt/cell - fully discharged under load (OCV)
-- 1.7 (1.44) Volt/cell - fully charged under load (OCV)
---------------------------------------------
for k,v in pairs(self.Train.Systems) do
if v.hasCoil and not self.Consumers[v] then
--print("Registering relay",v.Name, "Train: ", self.Train)
self.Consumers[v] = {0,v.coil_res,0}
end
end
--print "------------------\n"
end
-- TODO: - расставить параметры для всех оставшихся реле (убедиться, что подъемный ток ниже номинального)
-- self.Consumers is a table of relays with the next structure:
-- [<relay>] = {<relay.Value>, <relay.coil_res>, <relay.current>}
function TRAIN_SYSTEM:Inputs()
return { "Charge", "Dischargeable", "InitialVoltage", "CarType", "Computer" }
end
function TRAIN_SYSTEM:Outputs()
return { "Capacity", "Charge", "Voltage", "eds_eq", "Ibatt" }
end
function TRAIN_SYSTEM:TriggerInput(name,value)
if name == "Charge" then self.Charging = value end
if name == "Dischargeable" then self.Dischar = value end
--if name == "InitialVoltage" then self.StartVoltage = value end
if name == "CarType" then self.CarType = value end
end
local function GetBranchCondSum(consumers)
local br = 1e-12
for m,n in pairs(consumers) do
br = br + 1/(n[1] > 0 and n[2] or 1e12)
end
return br
end
function TRAIN_SYSTEM:Think(dT)
if self.CarType == 1 then
for k,v in pairs(self.Consumers) do
v[1] = k.Value
v[3] = k.Current
end
if self.Train.ComputerCar then
local nodecurr_sum, branchcond_sum = 0.0, 0.0
local eds_eq = 0.0
--a "two-node method" of 10's wire voltage computing
for k,v in ipairs(self.Train.WagonList) do
nodecurr_sum = nodecurr_sum + v.A56.Value*(v.VB.Value*v.Battery.Voltage/v.Battery.IResistance + v.PowerSupply.X2_1*v.A24.Value*v.PowerSupply.VoltageOut/v.PowerSupply.IResistance)
branchcond_sum = branchcond_sum + GetBranchCondSum(v.Battery.Consumers) + v.A56.Value*(v.VB.Value/v.Battery.IResistance + v.PowerSupply.X2_1*v.A24.Value/v.PowerSupply.IResistance)
end
eds_eq = nodecurr_sum/branchcond_sum
for k,v in ipairs(self.Train.WagonList) do
local consumers_cond = GetBranchCondSum(v.Battery.Consumers)
v.PowerSupply.car_control_load = eds_eq*consumers_cond
v.Battery.Ibatt = math.min(1,(2-self.Train.PA1.Value-self.Train.PA2.Value))
*(math.min(1,(v.VB.Value*v.A56.Value+v.A24.Value))*v.VB.Value*((v.A56.Value*(eds_eq - v.Battery.Voltage)
+ v.PowerSupply.X2_1*(1-v.A56.Value)*(v.PowerSupply.VoltageOut*v.A24.Value - v.Battery.Voltage))))/v.Battery.IResistance
v.PowerSupply.Iout = v.VB.Value*math.min(1,(2-self.Train.PA1.Value-self.Train.PA2.Value))*v.A24.Value*(v.PowerSupply.VoltageOut - eds_eq)/v.PowerSupply.IResistance
+ v.VB.Value*v.A56.Value*v.A24.Value*(v.PowerSupply.VoltageOut - eds_eq)*consumers_cond
v.Battery.eds_eq = eds_eq
v.eds_eq = v.Battery.eds_eq
-- DEBUG
--if self.Train.R_VPR and self.Train.R_VPR.Value < 0.5 then
--print(v.eds_eq, nodecurr_sum, branchcond_sum)
--print(v.PowerSupply.car_control_load,v.Battery.Ibatt,v.Battery.IResistance)
--print(v.PowerSupply.Iout,v.PowerSupply.Icosume)
--end
end
end
-- Calculate state of charge, internal resistance and battery voltage
if self.Dischar then
self.Capacity = self.Capacity - dT * (self.FullCapacity*0.1/86400) -- make capacity loss ~ 10% per day (just a game abstraction)
if self.Ibatt > 0 then
if self.Ibatt >= 8 then
local kCE = (self.EthaCE0 - 0.5)/72
local aCE = kCE*80 + 0.5
self.EthaCE = aCE - kCE * math.abs(self.Ibatt)
else
self.EthaCE = 0.94-0.8*math.exp(-self.Ibatt)
end
else
if self.SoC <= 1.0 then
self.EthaCE = 1 -- maybe I should make it more than 1... (instead of self discharge current)
else
self.EthaCE = 0.5*math.exp(2.6*self.SoC) - 5.73 -- не бывает!
end
end
if self.SoC <= 0 or self.SoC >= 1.0 then self.EthaCE = 0 end
-- Возможно, надо ввести ток саморазряда, а не ебаться с выдуманной зависимостью EthaCE от SoC выше 100% (которого не бывает >_>)
self.SoC = self.SoC + self.EthaCE*self.Ibatt*dT/self.Capacity
local SoC = math.max(0,math.min(100,self.SoC*100))
if 50 <= SoC and SoC <= 100 then
self.CellIRes = 0.009
elseif SoC >= 0.5 then
self.CellIRes = (9+0.1*(50-SoC))*10^(-3)
-- 0.035C and 0.08C (idk why...)
elseif self.Ibatt < -0.035*self.ElementCapacity or (0.014 < self.CellIRes and self.CellIRes < 0.8296 and self.Ibatt > 0.08*self.ElementCapacity) then
-- SoC less than 0.5% and discharging or Vbatt approx. 62 - 52 V and charging
if 55 <= self.Voltage and self.Voltage < 62 then
self.CellIRes = 112 - 14*(self.Voltage - 55)
elseif 50 <= self.Voltage and self.Voltage < 55 then
self.CellIRes = 1308 - 239.2*(self.Voltage - 50)
end
end
self.IResistance = self.CellIRes * self.ElementCount
--self.Train.BattCurrent = self.Ibatt*self.Train.A24.Value
self.Train.PA1:TriggerInput("Close",math.abs(self.Ibatt)/2) -- Это неправильно, но я уже заебалась
self.Train.PA2:TriggerInput("Close",math.abs(self.Ibatt)/2)
end
-- Calculate battery voltage
-- Polynomials for battery OCV calculation during charge and discharge (source: https://www.mdpi.com/1996-1073/16/21/7291)
-- Roughly, Vbatt_charge = EMF(SOC) + 𝑈(SOC), Vbatt_discharge = EMF(SOC) - 𝑈(SOC)
--EMF(SOC)=0.68175SOC^8+8.82823SOC^724.43179SOC^6+31.87221SOC^523.97881SOC^4+11.24774SOC^33.40685SOC^2+0.74692SOC+1.22076
--𝑈(SOC)=2.62496SOC^812.77132SOC^7+22.37586SOC^618.04921SOC^5+6.14667SOC^4+0.26467SOC^30.82125SOC^2+0.21246SOC+0.02641
-- open circuit voltage calculation
local EMF_soc, Uh_soc, tvb_sign
EMF_soc=-0.68175*self.SoC^8+8.82823*self.SoC^7-24.43179*self.SoC^6+31.87221*self.SoC^5-23.97881*self.SoC^4+11.24774*self.SoC^3-3.40685*self.SoC^2+0.74692*self.SoC+1.22076
Uh_soc=2.62496*self.SoC^8-12.77132*self.SoC^7+22.37586*self.SoC^6-18.04921*self.SoC^5+6.14667*self.SoC^4+0.26467*self.SoC^3-0.82125*self.SoC^2+0.21246*self.SoC+0.02641
-- Battery voltage (EMF in our case) growth rate at SoC > 90%: 0.25 volt per 10%
-- Battery voltage (EMF in our case) decrease rate at SoC < 10%: 0.20 volt per 10%
if self.Ibatt > 0.005*self.ElementCapacity then
self.TargetVoltage = EMF_soc + Uh_soc
if self.SoC > 0.9 and self.Ibatt > 0.005*self.ElementCapacity then
if self.Vpart < 0 then self.Vpart = 0 end
self.Vpart = 2.5*(self.SoC-0.9)
self.TargetVoltage = math.min(self.eds_eq/self.ElementCount, self.TargetVoltage + self.Vpart)
end
else
self.TargetVoltage = EMF_soc - Uh_soc
if self.SoC < 0.1 and self.Ibatt < -0.005*self.ElementCapacity then
if self.Vpart > 0 then self.Vpart = 0 end
self.Vpart = -2.0*(0.1-self.SoC)
self.TargetVoltage = math.max(0.8, self.TargetVoltage + self.Vpart)
end
end
self.TargetVoltage = self.TargetVoltage*self.ElementCount
self.Voltage = self.Voltage + (self.TargetVoltage - self.Voltage)*0.05
-- DEBUG
-- /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
--if self.Train.R_VPR and self.Train.R_VPR.Value < 0.5 then
--local tval = 1
--print("Target Voltage = "..self.TargetVoltage, "self.Voltage = "..self.Voltage, "train:",self)
--print("self.SoC = "..self.SoC, "self.Ibatt = "..self.Ibatt)
--print("self.eds_eq = "..self.eds_eq)
--print("self.EthaCE = "..self.EthaCE, "self.IResistance = "..self.IResistance)
--print("self.Capacity = "..self.Capacity)
--print("self.Train.PA2 = "..self.Train.PA2.Value)
--[[
EMF_soc=-0.68175*tval^8+8.82823*tval^7-24.43179*tval^6+31.87221*tval^5-23.97881*tval^4+11.24774*tval^3-3.40685*tval^2+0.74692*tval+1.22076
Uh_soc=2.62496*tval^8-12.77132*tval^7+22.37586*tval^6-18.04921*tval^5+6.14667*tval^4+0.26467*tval^3-0.82125*tval^2+0.21246*tval+0.02641
print("EMF_soc = "..EMF_soc, "Uh_soc = "..Uh_soc)--]]
--end
-- /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-- Legacy code
else
-- Calculate discharge
self.Current = 0--self.Train.KVC.Value*90*(self.Train.PowerSupply.XT3_1 > 0 and 3 or -1 + 4*self.Train:ReadTrainWire(27))*50*self.Train.Panel["V1"]
--print(self.Train.Panel["V1"])
self.Charge = math.min(self.Capacity,self.Charge + self.Current * dT)
-- Calculate battery voltage
if self.Train.PowerSupply then
self.Voltage = 65*(self.Charge/self.Capacity) + ((self.Train.PowerSupply.XT3_1 or self.Charging) > 0 and 17 or 0)
else
self.Voltage = 65*(self.Charge/self.Capacity) + (self.Charging > 0 and 17 or 0)
end
end
end