mirror of
https://github.com/MikaylaFischler/cc-mek-scada.git
synced 2024-08-30 18:22:34 +00:00
#106 reactor formed support and remounting
This commit is contained in:
parent
a02fb6f691
commit
b2be3ef5fc
@ -172,16 +172,21 @@ function iocontrol.update_statuses(statuses)
|
||||
|
||||
if #reactor_status == 0 then
|
||||
unit.reactor_ps.publish("computed_status", 1) -- disconnected
|
||||
else
|
||||
elseif #reactor_status == 3 then
|
||||
local mek_status = reactor_status[1]
|
||||
local rps_status = reactor_status[2]
|
||||
local gen_status = reactor_status[3]
|
||||
|
||||
unit.reactor_data.last_status_update = gen_status[1]
|
||||
unit.reactor_data.control_state = gen_status[2]
|
||||
unit.reactor_data.rps_tripped = gen_status[3]
|
||||
unit.reactor_data.rps_trip_cause = gen_status[4]
|
||||
unit.reactor_data.degraded = gen_status[5]
|
||||
if #gen_status == 6 then
|
||||
unit.reactor_data.last_status_update = gen_status[1]
|
||||
unit.reactor_data.control_state = gen_status[2]
|
||||
unit.reactor_data.rps_tripped = gen_status[3]
|
||||
unit.reactor_data.rps_trip_cause = gen_status[4]
|
||||
unit.reactor_data.no_reactor = gen_status[5]
|
||||
unit.reactor_data.formed = gen_status[6]
|
||||
else
|
||||
log.debug("reactor general status length mismatch")
|
||||
end
|
||||
|
||||
unit.reactor_data.rps_status = rps_status ---@type rps_status
|
||||
unit.reactor_data.mek_status = mek_status ---@type mek_status
|
||||
@ -189,8 +194,10 @@ function iocontrol.update_statuses(statuses)
|
||||
if unit.reactor_data.mek_status.status then
|
||||
unit.reactor_ps.publish("computed_status", 3) -- running
|
||||
else
|
||||
if unit.reactor_data.degraded then
|
||||
if unit.reactor_data.no_reactor then
|
||||
unit.reactor_ps.publish("computed_status", 5) -- faulted
|
||||
elseif not unit.reactor_data.formed then
|
||||
unit.reactor_ps.publish("computed_status", 6) -- multiblock not formed
|
||||
elseif unit.reactor_data.rps_tripped and unit.reactor_data.rps_trip_cause ~= "manual" then
|
||||
unit.reactor_ps.publish("computed_status", 4) -- SCRAM
|
||||
else
|
||||
@ -204,13 +211,19 @@ function iocontrol.update_statuses(statuses)
|
||||
end
|
||||
end
|
||||
|
||||
for key, val in pairs(unit.reactor_data.rps_status) do
|
||||
unit.reactor_ps.publish(key, val)
|
||||
if type(unit.reactor_data.rps_status) == "table" then
|
||||
for key, val in pairs(unit.reactor_data.rps_status) do
|
||||
unit.reactor_ps.publish(key, val)
|
||||
end
|
||||
end
|
||||
|
||||
for key, val in pairs(unit.reactor_data.mek_status) do
|
||||
unit.reactor_ps.publish(key, val)
|
||||
if type(unit.reactor_data.mek_status) == "table" then
|
||||
for key, val in pairs(unit.reactor_data.mek_status) do
|
||||
unit.reactor_ps.publish(key, val)
|
||||
end
|
||||
end
|
||||
else
|
||||
log.debug("reactor status length mismatch")
|
||||
end
|
||||
|
||||
-- annunciator
|
||||
@ -252,65 +265,71 @@ function iocontrol.update_statuses(statuses)
|
||||
|
||||
local rtu_statuses = status[3]
|
||||
|
||||
-- boiler statuses
|
||||
if type(rtu_statuses) == "table" then
|
||||
if type(rtu_statuses.boilers) == "table" then
|
||||
-- boiler statuses
|
||||
|
||||
for id = 1, #unit.boiler_data_tbl do
|
||||
if rtu_statuses.boilers[i] == nil then
|
||||
-- disconnected
|
||||
unit.boiler_ps_tbl[id].publish("computed_status", 1)
|
||||
end
|
||||
end
|
||||
for id = 1, #unit.boiler_data_tbl do
|
||||
if rtu_statuses.boilers[i] == nil then
|
||||
-- disconnected
|
||||
unit.boiler_ps_tbl[id].publish("computed_status", 1)
|
||||
end
|
||||
end
|
||||
|
||||
for id, boiler in pairs(rtu_statuses.boilers) do
|
||||
unit.boiler_data_tbl[id].state = boiler[1] ---@type table
|
||||
unit.boiler_data_tbl[id].tanks = boiler[2] ---@type table
|
||||
for id, boiler in pairs(rtu_statuses.boilers) do
|
||||
unit.boiler_data_tbl[id].state = boiler[1] ---@type table
|
||||
unit.boiler_data_tbl[id].tanks = boiler[2] ---@type table
|
||||
|
||||
local data = unit.boiler_data_tbl[id] ---@type boilerv_session_db
|
||||
local data = unit.boiler_data_tbl[id] ---@type boilerv_session_db
|
||||
|
||||
if data.state.boil_rate > 0 then
|
||||
unit.boiler_ps_tbl[id].publish("computed_status", 3) -- active
|
||||
else
|
||||
unit.boiler_ps_tbl[id].publish("computed_status", 2) -- idle
|
||||
if data.state.boil_rate > 0 then
|
||||
unit.boiler_ps_tbl[id].publish("computed_status", 3) -- active
|
||||
else
|
||||
unit.boiler_ps_tbl[id].publish("computed_status", 2) -- idle
|
||||
end
|
||||
|
||||
for key, val in pairs(unit.boiler_data_tbl[id].state) do
|
||||
unit.boiler_ps_tbl[id].publish(key, val)
|
||||
end
|
||||
|
||||
for key, val in pairs(unit.boiler_data_tbl[id].tanks) do
|
||||
unit.boiler_ps_tbl[id].publish(key, val)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
for key, val in pairs(unit.boiler_data_tbl[id].state) do
|
||||
unit.boiler_ps_tbl[id].publish(key, val)
|
||||
end
|
||||
if type(rtu_statuses.turbines) == "table" then
|
||||
-- turbine statuses
|
||||
|
||||
for key, val in pairs(unit.boiler_data_tbl[id].tanks) do
|
||||
unit.boiler_ps_tbl[id].publish(key, val)
|
||||
end
|
||||
end
|
||||
for id = 1, #unit.turbine_ps_tbl do
|
||||
if rtu_statuses.turbines[i] == nil then
|
||||
-- disconnected
|
||||
unit.turbine_ps_tbl[id].publish("computed_status", 1)
|
||||
end
|
||||
end
|
||||
|
||||
-- turbine statuses
|
||||
for id, turbine in pairs(rtu_statuses.turbines) do
|
||||
unit.turbine_data_tbl[id].state = turbine[1] ---@type table
|
||||
unit.turbine_data_tbl[id].tanks = turbine[2] ---@type table
|
||||
|
||||
for id = 1, #unit.turbine_ps_tbl do
|
||||
if rtu_statuses.turbines[i] == nil then
|
||||
-- disconnected
|
||||
unit.turbine_ps_tbl[id].publish("computed_status", 1)
|
||||
end
|
||||
end
|
||||
local data = unit.turbine_data_tbl[id] ---@type turbinev_session_db
|
||||
|
||||
for id, turbine in pairs(rtu_statuses.turbines) do
|
||||
unit.turbine_data_tbl[id].state = turbine[1] ---@type table
|
||||
unit.turbine_data_tbl[id].tanks = turbine[2] ---@type table
|
||||
if data.tanks.steam_fill >= 0.99 then
|
||||
unit.turbine_ps_tbl[id].publish("computed_status", 4) -- trip
|
||||
elseif data.state.flow_rate < 100 then
|
||||
unit.turbine_ps_tbl[id].publish("computed_status", 2) -- idle
|
||||
else
|
||||
unit.turbine_ps_tbl[id].publish("computed_status", 3) -- active
|
||||
end
|
||||
|
||||
local data = unit.turbine_data_tbl[id] ---@type turbinev_session_db
|
||||
for key, val in pairs(unit.turbine_data_tbl[id].state) do
|
||||
unit.turbine_ps_tbl[id].publish(key, val)
|
||||
end
|
||||
|
||||
if data.tanks.steam_fill >= 0.99 then
|
||||
unit.turbine_ps_tbl[id].publish("computed_status", 4) -- trip
|
||||
elseif data.state.flow_rate < 100 then
|
||||
unit.turbine_ps_tbl[id].publish("computed_status", 2) -- idle
|
||||
else
|
||||
unit.turbine_ps_tbl[id].publish("computed_status", 3) -- active
|
||||
end
|
||||
|
||||
for key, val in pairs(unit.turbine_data_tbl[id].state) do
|
||||
unit.turbine_ps_tbl[id].publish(key, val)
|
||||
end
|
||||
|
||||
for key, val in pairs(unit.turbine_data_tbl[id].tanks) do
|
||||
unit.turbine_ps_tbl[id].publish(key, val)
|
||||
for key, val in pairs(unit.turbine_data_tbl[id].tanks) do
|
||||
unit.turbine_ps_tbl[id].publish(key, val)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -16,7 +16,7 @@ local config = require("coordinator.config")
|
||||
local coordinator = require("coordinator.coordinator")
|
||||
local renderer = require("coordinator.renderer")
|
||||
|
||||
local COORDINATOR_VERSION = "alpha-v0.5.9"
|
||||
local COORDINATOR_VERSION = "alpha-v0.5.10"
|
||||
|
||||
local print = util.print
|
||||
local println = util.println
|
||||
|
@ -54,6 +54,10 @@ style.reactor = {
|
||||
{
|
||||
color = cpair(colors.black, colors.orange),
|
||||
text = "PLC FAULT"
|
||||
},
|
||||
{
|
||||
color = cpair(colors.black, colors.orange),
|
||||
text = "NOT FORMED"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -38,7 +38,9 @@ local MAX_HEATED_COLLANT_FILL = 0.95
|
||||
--- identifies dangerous states and SCRAMs reactor if warranted
|
||||
---
|
||||
--- autonomous from main SCADA supervisor/coordinator control
|
||||
function plc.rps_init(reactor)
|
||||
---@param reactor table
|
||||
---@param is_formed boolean
|
||||
function plc.rps_init(reactor, is_formed)
|
||||
local state_keys = {
|
||||
dmg_crit = 1,
|
||||
high_temp = 2,
|
||||
@ -48,13 +50,15 @@ function plc.rps_init(reactor)
|
||||
no_fuel = 6,
|
||||
fault = 7,
|
||||
timeout = 8,
|
||||
manual = 9
|
||||
manual = 9,
|
||||
sys_fail = 10
|
||||
}
|
||||
|
||||
local self = {
|
||||
reactor = reactor,
|
||||
state = { false, false, false, false, false, false, false, false, false },
|
||||
state = { false, false, false, false, false, false, false, false, false, false },
|
||||
reactor_enabled = false,
|
||||
formed = is_formed,
|
||||
tripped = false,
|
||||
trip_cause = "" ---@type rps_trip_cause
|
||||
}
|
||||
@ -76,14 +80,25 @@ function plc.rps_init(reactor)
|
||||
self.state[state_keys.fault] = false
|
||||
end
|
||||
|
||||
-- check if the reactor is formed
|
||||
local function _is_formed()
|
||||
local is_formed = self.reactor.isFormed()
|
||||
if is_formed == ppm.ACCESS_FAULT then
|
||||
-- lost the peripheral or terminated, handled later
|
||||
_set_fault()
|
||||
elseif not self.state[state_keys.sys_fail] then
|
||||
self.formed = is_formed
|
||||
self.state[state_keys.sys_fail] = not is_formed
|
||||
end
|
||||
end
|
||||
|
||||
-- check for critical damage
|
||||
local function _damage_critical()
|
||||
local damage_percent = self.reactor.getDamagePercent()
|
||||
if damage_percent == ppm.ACCESS_FAULT then
|
||||
-- lost the peripheral or terminated, handled later
|
||||
_set_fault()
|
||||
self.state[state_keys.dmg_crit] = false
|
||||
else
|
||||
elseif not self.state[state_keys.dmg_crit] then
|
||||
self.state[state_keys.dmg_crit] = damage_percent >= MAX_DAMAGE_PERCENT
|
||||
end
|
||||
end
|
||||
@ -95,8 +110,7 @@ function plc.rps_init(reactor)
|
||||
if temp == ppm.ACCESS_FAULT then
|
||||
-- lost the peripheral or terminated, handled later
|
||||
_set_fault()
|
||||
self.state[state_keys.high_temp] = false
|
||||
else
|
||||
elseif not self.state[state_keys.high_temp] then
|
||||
self.state[state_keys.high_temp] = temp >= MAX_DAMAGE_TEMPERATURE
|
||||
end
|
||||
end
|
||||
@ -107,8 +121,7 @@ function plc.rps_init(reactor)
|
||||
if coolant_filled == ppm.ACCESS_FAULT then
|
||||
-- lost the peripheral or terminated, handled later
|
||||
_set_fault()
|
||||
self.state[state_keys.no_coolant] = false
|
||||
else
|
||||
elseif not self.state[state_keys.no_coolant] then
|
||||
self.state[state_keys.no_coolant] = coolant_filled < MIN_COOLANT_FILL
|
||||
end
|
||||
end
|
||||
@ -119,8 +132,7 @@ function plc.rps_init(reactor)
|
||||
if w_filled == ppm.ACCESS_FAULT then
|
||||
-- lost the peripheral or terminated, handled later
|
||||
_set_fault()
|
||||
self.state[state_keys.ex_waste] = false
|
||||
else
|
||||
elseif not self.state[state_keys.ex_waste] then
|
||||
self.state[state_keys.ex_waste] = w_filled > MAX_WASTE_FILL
|
||||
end
|
||||
end
|
||||
@ -131,8 +143,7 @@ function plc.rps_init(reactor)
|
||||
if hc_filled == ppm.ACCESS_FAULT then
|
||||
-- lost the peripheral or terminated, handled later
|
||||
_set_fault()
|
||||
self.state[state_keys.ex_hcoolant] = false
|
||||
else
|
||||
elseif not self.state[state_keys.ex_hcoolant] then
|
||||
self.state[state_keys.ex_hcoolant] = hc_filled > MAX_HEATED_COLLANT_FILL
|
||||
end
|
||||
end
|
||||
@ -143,8 +154,7 @@ function plc.rps_init(reactor)
|
||||
if fuel == ppm.ACCESS_FAULT then
|
||||
-- lost the peripheral or terminated, handled later
|
||||
_set_fault()
|
||||
self.state[state_keys.no_fuel] = false
|
||||
else
|
||||
elseif not self.state[state_keys.no_fuel] then
|
||||
self.state[state_keys.no_fuel] = fuel == 0
|
||||
end
|
||||
end
|
||||
@ -169,7 +179,13 @@ function plc.rps_init(reactor)
|
||||
|
||||
-- manually SCRAM the reactor
|
||||
function public.trip_manual()
|
||||
self.state[state_keys.manual] = true
|
||||
self.state[state_keys.manual] = true
|
||||
end
|
||||
|
||||
-- trip for unformed reactor
|
||||
function public.trip_sys_fail()
|
||||
self.state[state_keys.fault] = true
|
||||
self.state[state_keys.sys_fail] = true
|
||||
end
|
||||
|
||||
-- SCRAM the reactor now
|
||||
@ -216,6 +232,7 @@ function plc.rps_init(reactor)
|
||||
|
||||
-- update state
|
||||
parallel.waitForAll(
|
||||
_is_formed,
|
||||
_damage_critical,
|
||||
_high_temp,
|
||||
_no_coolant,
|
||||
@ -227,6 +244,9 @@ function plc.rps_init(reactor)
|
||||
-- check system states in order of severity
|
||||
if self.tripped then
|
||||
status = self.trip_cause
|
||||
elseif self.state[state_keys.sys_fail] then
|
||||
log.warning("RPS: system failure, reactor not formed")
|
||||
status = rps_status_t.sys_fail
|
||||
elseif self.state[state_keys.dmg_crit] then
|
||||
log.warning("RPS: damage critical")
|
||||
status = rps_status_t.dmg_crit
|
||||
@ -273,9 +293,11 @@ function plc.rps_init(reactor)
|
||||
function public.status() return self.state end
|
||||
function public.is_tripped() return self.tripped end
|
||||
function public.is_active() return self.reactor_enabled end
|
||||
function public.is_formed() return self.formed end
|
||||
|
||||
-- reset the RPS
|
||||
function public.reset()
|
||||
---@param quiet? boolean true to suppress the info log message
|
||||
function public.reset(quiet)
|
||||
self.tripped = false
|
||||
self.trip_cause = rps_status_t.ok
|
||||
|
||||
@ -283,7 +305,7 @@ function plc.rps_init(reactor)
|
||||
self.state[i] = false
|
||||
end
|
||||
|
||||
log.info("RPS: reset")
|
||||
if not quiet then log.info("RPS: reset") end
|
||||
end
|
||||
|
||||
return public
|
||||
@ -473,20 +495,19 @@ function plc.comms(id, version, modem, local_port, server_port, reactor, rps, co
|
||||
local mek_data = { false, 0, 0, 0, min_pos, max_pos, 0, 0, 0, 0, 0, 0, 0, 0 }
|
||||
|
||||
local tasks = {
|
||||
function () mek_data[1] = self.reactor.isFormed() end,
|
||||
function () mek_data[2] = self.reactor.getLength() end,
|
||||
function () mek_data[3] = self.reactor.getWidth() end,
|
||||
function () mek_data[4] = self.reactor.getHeight() end,
|
||||
function () mek_data[5] = self.reactor.getMinPos() end,
|
||||
function () mek_data[6] = self.reactor.getMaxPos() end,
|
||||
function () mek_data[7] = self.reactor.getHeatCapacity() end,
|
||||
function () mek_data[8] = self.reactor.getFuelAssemblies() end,
|
||||
function () mek_data[9] = self.reactor.getFuelSurfaceArea() end,
|
||||
function () mek_data[10] = self.reactor.getFuelCapacity() end,
|
||||
function () mek_data[11] = self.reactor.getWasteCapacity() end,
|
||||
function () mek_data[12] = self.reactor.getCoolantCapacity() end,
|
||||
function () mek_data[13] = self.reactor.getHeatedCoolantCapacity() end,
|
||||
function () mek_data[14] = self.reactor.getMaxBurnRate() end
|
||||
function () mek_data[1] = self.reactor.getLength() end,
|
||||
function () mek_data[2] = self.reactor.getWidth() end,
|
||||
function () mek_data[3] = self.reactor.getHeight() end,
|
||||
function () mek_data[4] = self.reactor.getMinPos() end,
|
||||
function () mek_data[5] = self.reactor.getMaxPos() end,
|
||||
function () mek_data[6] = self.reactor.getHeatCapacity() end,
|
||||
function () mek_data[7] = self.reactor.getFuelAssemblies() end,
|
||||
function () mek_data[8] = self.reactor.getFuelSurfaceArea() end,
|
||||
function () mek_data[9] = self.reactor.getFuelCapacity() end,
|
||||
function () mek_data[10] = self.reactor.getWasteCapacity() end,
|
||||
function () mek_data[11] = self.reactor.getCoolantCapacity() end,
|
||||
function () mek_data[12] = self.reactor.getHeatedCoolantCapacity() end,
|
||||
function () mek_data[13] = self.reactor.getMaxBurnRate() end
|
||||
}
|
||||
|
||||
parallel.waitForAll(table.unpack(tasks))
|
||||
@ -536,29 +557,35 @@ function plc.comms(id, version, modem, local_port, server_port, reactor, rps, co
|
||||
end
|
||||
|
||||
-- send live status information
|
||||
---@param degraded boolean
|
||||
function public.send_status(degraded)
|
||||
---@param no_reactor boolean PLC lost reactor connection
|
||||
---@param formed boolean reactor formed
|
||||
function public.send_status(no_reactor, formed)
|
||||
if self.linked then
|
||||
local mek_data = nil
|
||||
local mek_data = nil ---@type table
|
||||
local heating_rate = nil ---@type number
|
||||
|
||||
if _update_status_cache() then
|
||||
mek_data = self.status_cache
|
||||
if (not no_reactor) and formed then
|
||||
if _update_status_cache() then
|
||||
mek_data = self.status_cache
|
||||
log.debug("sent updated status")
|
||||
else
|
||||
log.debug("sent cached status")
|
||||
end
|
||||
|
||||
heating_rate = self.reactor.getHeatingRate()
|
||||
end
|
||||
|
||||
local sys_status = {
|
||||
util.time(), -- timestamp
|
||||
(not self.scrammed), -- requested control state
|
||||
rps.is_tripped(), -- rps_tripped
|
||||
degraded, -- degraded
|
||||
self.reactor.getHeatingRate(), -- heating rate
|
||||
no_reactor, -- no reactor peripheral connected
|
||||
formed, -- reactor formed
|
||||
heating_rate, -- heating rate
|
||||
mek_data -- mekanism status data
|
||||
}
|
||||
|
||||
if not self.reactor.__p_is_faulted() then
|
||||
_send(RPLC_TYPES.STATUS, sys_status)
|
||||
else
|
||||
log.error("failed to send status: PPM fault")
|
||||
end
|
||||
_send(RPLC_TYPES.STATUS, sys_status)
|
||||
end
|
||||
end
|
||||
|
||||
@ -570,7 +597,7 @@ function plc.comms(id, version, modem, local_port, server_port, reactor, rps, co
|
||||
end
|
||||
|
||||
-- send reactor protection system alarm
|
||||
---@param cause rps_status_t
|
||||
---@param cause rps_status_t reactor protection system status
|
||||
function public.send_rps_alarm(cause)
|
||||
if self.linked then
|
||||
local rps_alarm = {
|
||||
@ -618,9 +645,9 @@ function plc.comms(id, version, modem, local_port, server_port, reactor, rps, co
|
||||
end
|
||||
|
||||
-- handle an RPLC packet
|
||||
---@param packet rplc_frame|mgmt_frame
|
||||
---@param plc_state plc_state
|
||||
---@param setpoints setpoints
|
||||
---@param packet rplc_frame|mgmt_frame packet frame
|
||||
---@param plc_state plc_state PLC state
|
||||
---@param setpoints setpoints setpoint control table
|
||||
function public.handle_packet(packet, plc_state, setpoints)
|
||||
if packet ~= nil and packet.scada_frame.local_port() == self.l_port then
|
||||
-- check sequence number
|
||||
@ -651,7 +678,7 @@ function plc.comms(id, version, modem, local_port, server_port, reactor, rps, co
|
||||
if link_ack == RPLC_LINKING.ALLOW then
|
||||
self.status_cache = nil
|
||||
_send_struct()
|
||||
public.send_status(plc_state.degraded)
|
||||
public.send_status(plc_state.no_reactor, plc_state.reactor_formed)
|
||||
log.debug("re-sent initial status data")
|
||||
elseif link_ack == RPLC_LINKING.DENY then
|
||||
println_ts("received unsolicited link denial, unlinking")
|
||||
@ -671,7 +698,7 @@ function plc.comms(id, version, modem, local_port, server_port, reactor, rps, co
|
||||
elseif packet.type == RPLC_TYPES.STATUS then
|
||||
-- request of full status, clear cache first
|
||||
self.status_cache = nil
|
||||
public.send_status(plc_state.degraded)
|
||||
public.send_status(plc_state.no_reactor, plc_state.reactor_formed)
|
||||
log.debug("sent out status cache again, did supervisor miss it?")
|
||||
elseif packet.type == RPLC_TYPES.MEK_STRUCT then
|
||||
-- request for physical structure
|
||||
@ -738,8 +765,11 @@ function plc.comms(id, version, modem, local_port, server_port, reactor, rps, co
|
||||
self.r_seq_num = nil
|
||||
self.status_cache = nil
|
||||
|
||||
_send_struct()
|
||||
public.send_status(plc_state.degraded)
|
||||
if plc_state.reactor_formed then
|
||||
_send_struct()
|
||||
end
|
||||
|
||||
public.send_status(plc_state.no_reactor, plc_state.reactor_formed)
|
||||
|
||||
log.debug("sent initial status data")
|
||||
elseif link_ack == RPLC_LINKING.DENY then
|
||||
|
@ -13,7 +13,7 @@ local config = require("reactor-plc.config")
|
||||
local plc = require("reactor-plc.plc")
|
||||
local threads = require("reactor-plc.threads")
|
||||
|
||||
local R_PLC_VERSION = "beta-v0.8.9"
|
||||
local R_PLC_VERSION = "beta-v0.9.0"
|
||||
|
||||
local print = util.print
|
||||
local println = util.println
|
||||
@ -64,6 +64,7 @@ local __shared_memory = {
|
||||
init_ok = true,
|
||||
shutdown = false,
|
||||
degraded = false,
|
||||
reactor_formed = true,
|
||||
no_reactor = false,
|
||||
no_modem = false
|
||||
},
|
||||
@ -101,7 +102,7 @@ local smem_sys = __shared_memory.plc_sys
|
||||
|
||||
local plc_state = __shared_memory.plc_state
|
||||
|
||||
-- we need a reactor and a modem
|
||||
-- we need a reactor, can at least do some things even if it isn't formed though
|
||||
if smem_dev.reactor == nil then
|
||||
println("boot> fission reactor not found");
|
||||
log.warning("no reactor on startup")
|
||||
@ -109,12 +110,21 @@ if smem_dev.reactor == nil then
|
||||
plc_state.init_ok = false
|
||||
plc_state.degraded = true
|
||||
plc_state.no_reactor = true
|
||||
elseif not smem_dev.reactor.isFormed() then
|
||||
println("boot> fission reactor not formed");
|
||||
log.warning("reactor logic adapter present, but reactor is not formed")
|
||||
|
||||
plc_state.degraded = true
|
||||
plc_state.reactor_formed = false
|
||||
end
|
||||
|
||||
-- modem is required if networked
|
||||
if __shared_memory.networked and smem_dev.modem == nil then
|
||||
println("boot> wireless modem not found")
|
||||
log.warning("no wireless modem on startup")
|
||||
|
||||
if smem_dev.reactor ~= nil and smem_dev.reactor.getStatus() then
|
||||
-- scram reactor if present and enabled
|
||||
if (smem_dev.reactor ~= nil) and plc_state.reactor_formed and smem_dev.reactor.getStatus() then
|
||||
smem_dev.reactor.scram()
|
||||
end
|
||||
|
||||
@ -127,10 +137,12 @@ end
|
||||
local function init()
|
||||
if plc_state.init_ok then
|
||||
-- just booting up, no fission allowed (neutrons stay put thanks)
|
||||
if smem_dev.reactor.getStatus() then smem_dev.reactor.scram() end
|
||||
if plc_state.reactor_formed and smem_dev.reactor.getStatus() then
|
||||
smem_dev.reactor.scram()
|
||||
end
|
||||
|
||||
-- init reactor protection system
|
||||
smem_sys.rps = plc.rps_init(smem_dev.reactor)
|
||||
smem_sys.rps = plc.rps_init(smem_dev.reactor, plc_state.reactor_formed)
|
||||
log.debug("init> rps init")
|
||||
|
||||
if __shared_memory.networked then
|
||||
@ -147,8 +159,7 @@ local function init()
|
||||
log.debug("init> running without networking")
|
||||
end
|
||||
|
||||
---@diagnostic disable-next-line: undefined-field
|
||||
os.queueEvent("clock_start")
|
||||
util.push_event("clock_start")
|
||||
|
||||
println("boot> completed")
|
||||
log.debug("init> boot completed")
|
||||
@ -182,7 +193,7 @@ if __shared_memory.networked then
|
||||
|
||||
if plc_state.init_ok then
|
||||
-- send status one last time after RPS shutdown
|
||||
smem_sys.plc_comms.send_status(plc_state.degraded)
|
||||
smem_sys.plc_comms.send_status(plc_state.no_reactor, plc_state.reactor_formed)
|
||||
smem_sys.plc_comms.send_rps_status()
|
||||
|
||||
-- close connection
|
||||
|
@ -78,6 +78,51 @@ function threads.thread__main(smem, init)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- are we now formed after waiting to be formed?
|
||||
if not plc_state.reactor_formed and rps.is_formed() then
|
||||
-- push a connect event and unmount it from the PPM
|
||||
local iface = ppm.get_iface(plc_dev.reactor)
|
||||
if iface then
|
||||
ppm.unmount(plc_dev.reactor)
|
||||
|
||||
local type, device = ppm.mount(iface)
|
||||
|
||||
if type ~= "fissionReactorLogicAdapter" and device ~= nil then
|
||||
-- reconnect reactor
|
||||
plc_dev.reactor = device
|
||||
|
||||
smem.q.mq_rps.push_command(MQ__RPS_CMD.SCRAM)
|
||||
|
||||
println_ts("reactor reconnected as formed.")
|
||||
log.info("reactor reconnected as formed")
|
||||
|
||||
plc_state.reactor_formed = device.isFormed()
|
||||
|
||||
rps.reconnect_reactor(plc_dev.reactor)
|
||||
if networked then
|
||||
plc_comms.reconnect_reactor(plc_dev.reactor)
|
||||
end
|
||||
|
||||
-- determine if we are still in a degraded state
|
||||
if not networked or not plc_state.no_modem then
|
||||
plc_state.degraded = false
|
||||
end
|
||||
else
|
||||
-- fully lost the reactor now :(
|
||||
println_ts("reactor lost (failed reconnect)!")
|
||||
log.error("reactor lost (failed reconnect!")
|
||||
|
||||
plc_state.no_reactor = true
|
||||
plc_state.degraded = true
|
||||
end
|
||||
else
|
||||
log.error("failed to get interface of previously connected reactor", true)
|
||||
end
|
||||
elseif not rps.is_formed() then
|
||||
-- reactor no longer formed
|
||||
plc_state.reactor_formed = false
|
||||
end
|
||||
elseif event == "modem_message" and networked and plc_state.init_ok and not plc_state.no_modem then
|
||||
-- got a packet
|
||||
local packet = plc_comms.parse_packet(param1, param2, param3, param4, param5)
|
||||
@ -94,16 +139,18 @@ function threads.thread__main(smem, init)
|
||||
local type, device = ppm.handle_unmount(param1)
|
||||
|
||||
if type ~= nil and device ~= nil then
|
||||
if type == "fissionReactor" then
|
||||
if type == "fissionReactorLogicAdapter" then
|
||||
println_ts("reactor disconnected!")
|
||||
log.error("reactor disconnected!")
|
||||
|
||||
plc_state.no_reactor = true
|
||||
plc_state.degraded = true
|
||||
elseif networked and type == "modem" then
|
||||
-- we only care if this is our wireless modem
|
||||
if device == plc_dev.modem then
|
||||
println_ts("wireless modem disconnected!")
|
||||
println_ts("comms modem disconnected!")
|
||||
log.error("comms modem disconnected!")
|
||||
|
||||
plc_state.no_modem = true
|
||||
|
||||
if plc_state.init_ok then
|
||||
@ -122,7 +169,7 @@ function threads.thread__main(smem, init)
|
||||
local type, device = ppm.mount(param1)
|
||||
|
||||
if type ~= nil and device ~= nil then
|
||||
if type == "fissionReactor" then
|
||||
if type == "fissionReactorLogicAdapter" then
|
||||
-- reconnected reactor
|
||||
plc_dev.reactor = device
|
||||
|
||||
@ -130,7 +177,9 @@ function threads.thread__main(smem, init)
|
||||
|
||||
println_ts("reactor reconnected.")
|
||||
log.info("reactor reconnected")
|
||||
|
||||
plc_state.no_reactor = false
|
||||
plc_state.reactor_formed = device.isFormed()
|
||||
|
||||
if plc_state.init_ok then
|
||||
rps.reconnect_reactor(plc_dev.reactor)
|
||||
@ -140,7 +189,7 @@ function threads.thread__main(smem, init)
|
||||
end
|
||||
|
||||
-- determine if we are still in a degraded state
|
||||
if not networked or not plc_state.no_modem then
|
||||
if (not networked or not plc_state.no_modem) and plc_state.reactor_formed then
|
||||
plc_state.degraded = false
|
||||
end
|
||||
elseif networked and type == "modem" then
|
||||
@ -202,9 +251,7 @@ function threads.thread__main(smem, init)
|
||||
-- this thread cannot be slept because it will miss events (namely "terminate" otherwise)
|
||||
if not plc_state.shutdown then
|
||||
log.info("main thread restarting now...")
|
||||
|
||||
---@diagnostic disable-next-line: undefined-field
|
||||
os.queueEvent("clock_start")
|
||||
util.push_event("clock_start")
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -261,7 +308,7 @@ function threads.thread__rps(smem)
|
||||
|
||||
-- if we are in standalone mode, continuously reset RPS
|
||||
-- RPS will trip again if there are faults, but if it isn't cleared, the user can't re-enable
|
||||
if not networked then rps.reset() end
|
||||
if not networked then rps.reset(true) end
|
||||
|
||||
-- check safety (SCRAM occurs if tripped)
|
||||
if not plc_state.no_reactor then
|
||||
@ -380,7 +427,7 @@ function threads.thread__comms_tx(smem)
|
||||
-- received a command
|
||||
if msg.message == MQ__COMM_CMD.SEND_STATUS then
|
||||
-- send PLC/RPS status
|
||||
plc_comms.send_status(plc_state.degraded)
|
||||
plc_comms.send_status(plc_state.no_reactor, plc_state.reactor_formed)
|
||||
plc_comms.send_rps_status()
|
||||
end
|
||||
elseif msg.qtype == mqueue.TYPE.DATA then
|
||||
|
@ -10,7 +10,10 @@ local ppm = {}
|
||||
|
||||
local ACCESS_FAULT = nil ---@type nil
|
||||
|
||||
local UNDEFINED_FIELD = "undefined field"
|
||||
|
||||
ppm.ACCESS_FAULT = ACCESS_FAULT
|
||||
ppm.UNDEFINED_FIELD = UNDEFINED_FIELD
|
||||
|
||||
----------------------------
|
||||
-- PRIVATE DATA/FUNCTIONS --
|
||||
@ -110,6 +113,40 @@ local function peri_init(iface)
|
||||
self.device.__p_enable_afc = enable_afc
|
||||
self.device.__p_disable_afc = disable_afc
|
||||
|
||||
-- add default index function to catch undefined indicies
|
||||
|
||||
local mt = {}
|
||||
|
||||
function mt.__index(_, key)
|
||||
-- this will continuously be counting calls here as faults
|
||||
-- unlike other functions, faults here can't be cleared as it is just not defined
|
||||
if self.fault_counts[key] == nil then
|
||||
self.fault_counts[key] = 0
|
||||
end
|
||||
|
||||
-- function failed
|
||||
self.faulted = true
|
||||
self.last_fault = UNDEFINED_FIELD
|
||||
|
||||
_ppm_sys.faulted = true
|
||||
_ppm_sys.last_fault = UNDEFINED_FIELD
|
||||
|
||||
if not _ppm_sys.mute and (self.fault_counts[key] % REPORT_FREQUENCY == 0) then
|
||||
local count_str = ""
|
||||
if self.fault_counts[key] > 0 then
|
||||
count_str = " [" .. self.fault_counts[key] .. " total calls]"
|
||||
end
|
||||
|
||||
log.error(util.c("PPM: caught undefined function ", key, "()", count_str))
|
||||
end
|
||||
|
||||
self.fault_counts[key] = self.fault_counts[key] + 1
|
||||
|
||||
return ACCESS_FAULT
|
||||
end
|
||||
|
||||
setmetatable(self.device, mt)
|
||||
|
||||
return {
|
||||
type = self.type,
|
||||
dev = self.device
|
||||
@ -208,6 +245,20 @@ function ppm.mount(iface)
|
||||
return pm_type, pm_dev
|
||||
end
|
||||
|
||||
-- manually unmount a peripheral from the PPM
|
||||
---@param device table device table
|
||||
function ppm.unmount(device)
|
||||
if device then
|
||||
for side, data in pairs(_ppm_sys.mounts) do
|
||||
if data.dev == device then
|
||||
log.warning(util.c("PPM: manually unmounted ", data.type, " mounted to ", side))
|
||||
_ppm_sys.mounts[side] = nil
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- handle peripheral_detach event
|
||||
---@param iface string CC peripheral interface
|
||||
---@return string|nil type, table|nil device
|
||||
@ -227,6 +278,8 @@ function ppm.handle_unmount(iface)
|
||||
log.error(util.c("PPM: lost device unknown to the PPM mounted to ", iface))
|
||||
end
|
||||
|
||||
_ppm_sys.mounts[iface] = nil
|
||||
|
||||
return pm_type, pm_dev
|
||||
end
|
||||
|
||||
@ -244,6 +297,19 @@ function ppm.list_mounts()
|
||||
return _ppm_sys.mounts
|
||||
end
|
||||
|
||||
-- get a mounted peripheral side/interface by device table
|
||||
---@param device table device table
|
||||
---@return string|nil iface CC peripheral interface
|
||||
function ppm.get_iface(device)
|
||||
if device then
|
||||
for side, data in pairs(_ppm_sys.mounts) do
|
||||
if data.dev == device then return side end
|
||||
end
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
-- get a mounted peripheral by side/interface
|
||||
---@param iface string CC peripheral interface
|
||||
---@return table|nil device function table
|
||||
@ -298,7 +364,7 @@ end
|
||||
-- get the fission reactor (if multiple, returns the first)
|
||||
---@return table|nil reactor function table
|
||||
function ppm.get_fission_reactor()
|
||||
return ppm.get_device("fissionReactor") or ppm.get_device("fissionReactorLogicAdapter")
|
||||
return ppm.get_device("fissionReactorLogicAdapter")
|
||||
end
|
||||
|
||||
-- get the wireless modem (if multiple, returns the first)
|
||||
|
@ -90,20 +90,22 @@ function plc.new_session(id, for_reactor, in_queue, out_queue)
|
||||
sDB = {
|
||||
last_status_update = 0,
|
||||
control_state = false,
|
||||
degraded = false,
|
||||
no_reactor = false,
|
||||
formed = false,
|
||||
rps_tripped = false,
|
||||
rps_trip_cause = "ok", ---@type rps_trip_cause
|
||||
---@class rps_status
|
||||
rps_status = {
|
||||
dmg_crit = false,
|
||||
ex_hcool = false,
|
||||
ex_waste = false,
|
||||
high_temp = false,
|
||||
no_fuel = false,
|
||||
no_cool = false,
|
||||
ex_waste = false,
|
||||
ex_hcool = false,
|
||||
no_fuel = false,
|
||||
fault = false,
|
||||
timeout = false,
|
||||
manual = false
|
||||
manual = false,
|
||||
sys_fail = false
|
||||
},
|
||||
---@class mek_status
|
||||
mek_status = {
|
||||
@ -159,14 +161,15 @@ function plc.new_session(id, for_reactor, in_queue, out_queue)
|
||||
---@param rps_status table
|
||||
local function _copy_rps_status(rps_status)
|
||||
self.sDB.rps_status.dmg_crit = rps_status[1]
|
||||
self.sDB.rps_status.ex_hcool = rps_status[2]
|
||||
self.sDB.rps_status.ex_waste = rps_status[3]
|
||||
self.sDB.rps_status.high_temp = rps_status[4]
|
||||
self.sDB.rps_status.no_fuel = rps_status[5]
|
||||
self.sDB.rps_status.no_cool = rps_status[6]
|
||||
self.sDB.rps_status.high_temp = rps_status[2]
|
||||
self.sDB.rps_status.no_cool = rps_status[3]
|
||||
self.sDB.rps_status.ex_waste = rps_status[4]
|
||||
self.sDB.rps_status.ex_hcool = rps_status[5]
|
||||
self.sDB.rps_status.no_fuel = rps_status[6]
|
||||
self.sDB.rps_status.fault = rps_status[7]
|
||||
self.sDB.rps_status.timeout = rps_status[8]
|
||||
self.sDB.rps_status.manual = rps_status[9]
|
||||
self.sDB.rps_status.sys_fail = rps_status[10]
|
||||
end
|
||||
|
||||
-- copy in the reactor status
|
||||
@ -205,20 +208,19 @@ function plc.new_session(id, for_reactor, in_queue, out_queue)
|
||||
-- copy in the reactor structure
|
||||
---@param mek_data table
|
||||
local function _copy_struct(mek_data)
|
||||
self.sDB.mek_struct.formed = mek_data[1]
|
||||
self.sDB.mek_struct.length = mek_data[2]
|
||||
self.sDB.mek_struct.width = mek_data[3]
|
||||
self.sDB.mek_struct.height = mek_data[4]
|
||||
self.sDB.mek_struct.min_pos = mek_data[5]
|
||||
self.sDB.mek_struct.max_pos = mek_data[6]
|
||||
self.sDB.mek_struct.heat_cap = mek_data[7]
|
||||
self.sDB.mek_struct.fuel_asm = mek_data[8]
|
||||
self.sDB.mek_struct.fuel_sa = mek_data[9]
|
||||
self.sDB.mek_struct.fuel_cap = mek_data[10]
|
||||
self.sDB.mek_struct.waste_cap = mek_data[11]
|
||||
self.sDB.mek_struct.ccool_cap = mek_data[12]
|
||||
self.sDB.mek_struct.hcool_cap = mek_data[13]
|
||||
self.sDB.mek_struct.max_burn = mek_data[14]
|
||||
self.sDB.mek_struct.length = mek_data[1]
|
||||
self.sDB.mek_struct.width = mek_data[2]
|
||||
self.sDB.mek_struct.height = mek_data[3]
|
||||
self.sDB.mek_struct.min_pos = mek_data[4]
|
||||
self.sDB.mek_struct.max_pos = mek_data[5]
|
||||
self.sDB.mek_struct.heat_cap = mek_data[6]
|
||||
self.sDB.mek_struct.fuel_asm = mek_data[7]
|
||||
self.sDB.mek_struct.fuel_sa = mek_data[8]
|
||||
self.sDB.mek_struct.fuel_cap = mek_data[9]
|
||||
self.sDB.mek_struct.waste_cap = mek_data[10]
|
||||
self.sDB.mek_struct.ccool_cap = mek_data[11]
|
||||
self.sDB.mek_struct.hcool_cap = mek_data[12]
|
||||
self.sDB.mek_struct.max_burn = mek_data[13]
|
||||
end
|
||||
|
||||
-- mark this PLC session as closed, stop watchdog
|
||||
@ -298,18 +300,22 @@ function plc.new_session(id, for_reactor, in_queue, out_queue)
|
||||
self.sDB.last_status_update = pkt.data[1]
|
||||
self.sDB.control_state = pkt.data[2]
|
||||
self.sDB.rps_tripped = pkt.data[3]
|
||||
self.sDB.degraded = pkt.data[4]
|
||||
self.sDB.mek_status.heating_rate = pkt.data[5]
|
||||
self.sDB.no_reactor = pkt.data[4]
|
||||
self.sDB.formed = pkt.data[5]
|
||||
|
||||
-- attempt to read mek_data table
|
||||
if pkt.data[6] ~= nil then
|
||||
local status = pcall(_copy_status, pkt.data[6])
|
||||
if status then
|
||||
-- copied in status data OK
|
||||
self.received_status_cache = true
|
||||
else
|
||||
-- error copying status data
|
||||
log.error(log_header .. "failed to parse status packet data")
|
||||
if not self.sDB.no_reactor and self.sDB.formed then
|
||||
self.sDB.mek_status.heating_rate = pkt.data[6]
|
||||
|
||||
-- attempt to read mek_data table
|
||||
if pkt.data[7] ~= nil then
|
||||
local status = pcall(_copy_status, pkt.data[7])
|
||||
if status then
|
||||
-- copied in status data OK
|
||||
self.received_status_cache = true
|
||||
else
|
||||
-- error copying status data
|
||||
log.error(log_header .. "failed to parse status packet data")
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
@ -379,7 +385,7 @@ function plc.new_session(id, for_reactor, in_queue, out_queue)
|
||||
})
|
||||
elseif pkt.type == RPLC_TYPES.RPS_STATUS then
|
||||
-- RPS status packet received, copy data
|
||||
if pkt.length == 9 then
|
||||
if pkt.length == 10 then
|
||||
local status = pcall(_copy_rps_status, pkt.data)
|
||||
if status then
|
||||
-- copied in RPS status data OK
|
||||
@ -392,7 +398,7 @@ function plc.new_session(id, for_reactor, in_queue, out_queue)
|
||||
end
|
||||
elseif pkt.type == RPLC_TYPES.RPS_ALARM then
|
||||
-- RPS alarm
|
||||
if pkt.length == 10 then
|
||||
if pkt.length == 11 then
|
||||
self.sDB.rps_tripped = true
|
||||
self.sDB.rps_trip_cause = pkt.data[1]
|
||||
local status = pcall(_copy_rps_status, { table.unpack(pkt.data, 2, pkt.length) })
|
||||
@ -490,7 +496,8 @@ function plc.new_session(id, for_reactor, in_queue, out_queue)
|
||||
self.sDB.control_state,
|
||||
self.sDB.rps_tripped,
|
||||
self.sDB.rps_trip_cause,
|
||||
self.sDB.degraded
|
||||
self.sDB.no_reactor,
|
||||
self.sDB.formed
|
||||
}
|
||||
end
|
||||
|
||||
@ -609,21 +616,41 @@ function plc.new_session(id, for_reactor, in_queue, out_queue)
|
||||
|
||||
local rtimes = self.retry_times
|
||||
|
||||
-- struct request retry
|
||||
if (not self.sDB.no_reactor) and self.sDB.formed then
|
||||
-- struct request retry
|
||||
|
||||
if not self.received_struct then
|
||||
if rtimes.struct_req - util.time() <= 0 then
|
||||
_send(RPLC_TYPES.MEK_STRUCT, {})
|
||||
rtimes.struct_req = util.time() + RETRY_PERIOD
|
||||
if not self.received_struct then
|
||||
if rtimes.struct_req - util.time() <= 0 then
|
||||
_send(RPLC_TYPES.MEK_STRUCT, {})
|
||||
rtimes.struct_req = util.time() + RETRY_PERIOD
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- status cache request retry
|
||||
-- status cache request retry
|
||||
|
||||
if not self.received_status_cache then
|
||||
if rtimes.status_req - util.time() <= 0 then
|
||||
_send(RPLC_TYPES.MEK_STATUS, {})
|
||||
rtimes.status_req = util.time() + RETRY_PERIOD
|
||||
if not self.received_status_cache then
|
||||
if rtimes.status_req - util.time() <= 0 then
|
||||
_send(RPLC_TYPES.MEK_STATUS, {})
|
||||
rtimes.status_req = util.time() + RETRY_PERIOD
|
||||
end
|
||||
end
|
||||
|
||||
-- enable request retry
|
||||
|
||||
if not self.acks.enable then
|
||||
if rtimes.enable_req - util.time() <= 0 then
|
||||
_send(RPLC_TYPES.RPS_ENABLE, {})
|
||||
rtimes.enable_req = util.time() + RETRY_PERIOD
|
||||
end
|
||||
end
|
||||
|
||||
-- burn rate request retry
|
||||
|
||||
if not self.acks.burn_rate then
|
||||
if rtimes.burn_rate_req - util.time() <= 0 then
|
||||
_send(RPLC_TYPES.MEK_BURN_RATE, { self.commanded_burn_rate, self.ramping_rate })
|
||||
rtimes.burn_rate_req = util.time() + RETRY_PERIOD
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -636,24 +663,6 @@ function plc.new_session(id, for_reactor, in_queue, out_queue)
|
||||
end
|
||||
end
|
||||
|
||||
-- enable request retry
|
||||
|
||||
if not self.acks.enable then
|
||||
if rtimes.enable_req - util.time() <= 0 then
|
||||
_send(RPLC_TYPES.RPS_ENABLE, {})
|
||||
rtimes.enable_req = util.time() + RETRY_PERIOD
|
||||
end
|
||||
end
|
||||
|
||||
-- burn rate request retry
|
||||
|
||||
if not self.acks.burn_rate then
|
||||
if rtimes.burn_rate_req - util.time() <= 0 then
|
||||
_send(RPLC_TYPES.MEK_BURN_RATE, { self.commanded_burn_rate, self.ramping_rate })
|
||||
rtimes.burn_rate_req = util.time() + RETRY_PERIOD
|
||||
end
|
||||
end
|
||||
|
||||
-- RPS reset request retry
|
||||
|
||||
if not self.acks.rps_reset then
|
||||
|
@ -13,7 +13,7 @@ local svsessions = require("supervisor.session.svsessions")
|
||||
local config = require("supervisor.config")
|
||||
local supervisor = require("supervisor.supervisor")
|
||||
|
||||
local SUPERVISOR_VERSION = "beta-v0.6.5"
|
||||
local SUPERVISOR_VERSION = "beta-v0.6.6"
|
||||
|
||||
local print = util.print
|
||||
local println = util.println
|
||||
|
Loading…
Reference in New Issue
Block a user