-
Notifications
You must be signed in to change notification settings - Fork 0
/
reactor-with-fuel.lua
242 lines (198 loc) · 6.75 KB
/
reactor-with-fuel.lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
local state, data, reactor, turbine, info_window, rules_window
local STATES = {
READY = 1, -- Reactor is off and can be started with the lever
RUNNING = 2, -- Reactor is running and all rules are met
ESTOP = 3, -- Reactor is stopped due to rule(s) being violated
UNKNOWN = 4, -- Reactor or turbine peripherals are missing
}
------------------------------------------------
local rules = {}
local function add_rule(name, fn)
table.insert(rules, function()
local ok, rule_met, value = pcall(fn)
if ok then
return rule_met, string.format("%s (%s)", name, value)
else
return false, name
end
end)
end
add_rule("REACTOR TEMPERATURE <= 745K", function()
local value = string.format("%3dK", math.ceil(data.reactor_temp))
return data.reactor_temp <= 745, value
end)
add_rule("REACTOR DAMAGE <= 10%", function()
local value = string.format("%3d%%", math.ceil(data.reactor_damage * 100))
return data.reactor_damage <= 0.10, value
end)
add_rule("REACTOR COOLANT LEVEL >= 95%", function()
local value = string.format("%3d%%", math.floor(data.reactor_coolant * 100))
return data.reactor_coolant >= 0.95, value
end)
add_rule("REACTOR WASTE LEVEL <= 90%", function()
local value = string.format("%3d%%", math.ceil(data.reactor_waste * 100))
return data.reactor_waste <= 0.90, value
end)
add_rule("REACTOR FUEL LEVEL >= 10%", function()
local value = string.format("%3d%%", math.ceil(data.reactor_fuel * 100))
return data.reactor_fuel >= 0.10, value
end)
add_rule("TURBINE ENERGY LEVEL <= 95%", function()
local value = string.format("%3d%%", math.ceil(data.turbine_energy * 100))
return data.turbine_energy <= 0.95, value
end)
local function all_rules_met()
for i, rule in ipairs(rules) do
if not rule() then
return false
end
end
-- Allow manual emergency stop with SCRAM button
return state ~= STATES.RUNNING or data.reactor_on
end
------------------------------------------------
local function update_data()
data = {
lever_on = redstone.getInput("top"),
reactor_on = reactor.getStatus(),
reactor_burn_rate = reactor.getBurnRate(),
reactor_max_burn_rate = reactor.getMaxBurnRate(),
reactor_temp = reactor.getTemperature(),
reactor_damage = reactor.getDamagePercent(),
reactor_coolant = reactor.getCoolantFilledPercentage(),
reactor_waste = reactor.getWasteFilledPercentage(),
reactor_fuel = reactor.getFuelFilledPercentage(),
turbine_energy = turbine.getEnergyFilledPercentage(),
}
end
------------------------------------------------
local function colored(text, fg, bg)
term.setTextColor(fg or colors.white)
term.setBackgroundColor(bg or colors.black)
term.write(text)
end
local function make_section(name, x, y, w, h)
for row = 1, h do
term.setCursorPos(x, y + row - 1)
local char = (row == 1 or row == h) and "\127" or " "
colored("\127" .. string.rep(char, w - 2) .. "\127", colors.gray)
end
term.setCursorPos(x + 2, y)
colored(" " .. name .. " ")
return window.create(term.current(), x + 2, y + 2, w - 4, h - 4)
end
local function update_info()
local prev_term = term.redirect(info_window)
term.clear()
term.setCursorPos(1, 1)
if state == STATES.UNKNOWN then
colored("ERROR RETRIEVING DATA", colors.red)
return
end
colored("REACTOR: ")
colored(data.reactor_on and "ON " or "OFF", data.reactor_on and colors.green or colors.red)
colored(" LEVER: ")
colored(data.lever_on and "ON " or "OFF", data.lever_on and colors.green or colors.red)
colored(" R. LIMIT: ")
colored(string.format("%4.1f", data.reactor_burn_rate), colors.blue)
colored("/", colors.lightGray)
colored(string.format("%4.1f", data.reactor_max_burn_rate), colors.blue)
term.setCursorPos(1, 3)
colored("STATUS: ")
if state == STATES.READY then
colored("READY, flip lever to start", colors.blue)
elseif state == STATES.RUNNING then
colored("RUNNING, flip lever to stop", colors.green)
elseif state == STATES.ESTOP and not all_rules_met() then
colored("EMERGENCY STOP, safety rules violated", colors.red)
elseif state == STATES.ESTOP then
colored("EMERGENCY STOP, toggle lever to reset", colors.red)
end -- STATES.UNKNOWN cases handled above
term.redirect(prev_term)
end
local estop_reasons = {}
local function update_rules()
local prev_term = term.redirect(rules_window)
term.clear()
if state ~= STATES.ESTOP then
estop_reasons = {}
end
for i, rule in ipairs(rules) do
local ok, text = rule()
term.setCursorPos(1, i)
if ok and not estop_reasons[i] then
colored("[ OK ] ", colors.green)
colored(text, colors.lightGray)
else
colored("[ FAIL ] ", colors.red)
colored(text, colors.red)
estop_reasons[i] = true
end
end
term.redirect(prev_term)
end
------------------------------------------------
local function main_loop()
-- Search for peripherals again if one or both are missing
if not state or state == STATES.UNKNOWN then
reactor = peripheral.find("fissionReactorLogicAdapter")
turbine = peripheral.find("turbineValve")
end
if not pcall(update_data) then
-- Error getting data (either reactor or turbine is nil?)
data = {}
state = STATES.UNKNOWN
elseif data.reactor_on == nil then
-- Reactor is not connected
state = STATES.UNKNOWN
elseif data.turbine_energy == nil then
-- Turbine is not connected
state = STATES.UNKNOWN
elseif not state then
-- Program just started, get current state from lever
state = data.lever_on and STATES.RUNNING or STATES.READY
elseif state == STATES.READY and data.lever_on then
-- READY -> RUNNING
state = STATES.RUNNING
-- Activate reactor
pcall(reactor.activate)
data.reactor_on = true
elseif state == STATES.RUNNING and not data.lever_on then
-- RUNNING -> READY
state = STATES.READY
elseif state == STATES.ESTOP and not data.lever_on then
-- ESTOP -> READY
state = STATES.READY
elseif state == STATES.UNKNOWN then
-- UNKNOWN -> ESTOP
state = data.lever_on and STATES.ESTOP or STATES.READY
estop_reasons = {}
end
-- Always enter ESTOP if safety rules are not met
if state ~= STATES.UNKNOWN and not all_rules_met() then
state = STATES.ESTOP
end
-- SCRAM reactor if not running
if state ~= STATES.RUNNING and reactor then
pcall(reactor.scram)
end
-- Update info and rules windows
pcall(update_info)
pcall(update_rules)
sleep() -- Other calls should already yield, this is just in case
return main_loop()
end
term.setPaletteColor(colors.black, 0x000000)
term.setPaletteColor(colors.gray, 0x343434)
term.setPaletteColor(colors.lightGray, 0xababab)
term.setPaletteColor(colors.red, 0xdb2d20)
term.setPaletteColor(colors.green, 0x01a252)
term.setPaletteColor(colors.blue, 0x01a0e4)
term.clear()
local width = term.getSize()
info_window = make_section("INFORMATION", 2, 2, width - 2, 7)
rules_window = make_section("SAFETY RULES", 2, 10, width - 2, 10)
parallel.waitForAny(main_loop, function()
os.pullEventRaw("terminate")
end)
os.reboot()