--- /dev/null
+-- /bin/carriage.lua
+-- Configuration file at /etc/carriage.lua
+-- Operates drilling carriage. Starts drilling on "drill" event.
+-- Enable/disable automatic redrilling with "auto" bool event.
+-- Transfers items and restocks deployers after drilling.
+
+
+-- options
+
+local verbose
+do
+ local args = {...}
+ for i = 1, #args do
+ if args[i] == "-v" or args[i] == "--verbose" then
+ verbose = true
+ end
+ end
+end
+
+
+-- imports
+
+dofile "/lib/import.lua"
+
+global_import "/lib/control.lua" { "forever", "simultaneously" }
+
+local inv = import "/lib/inventory.lua"
+ { "free"
+ , "find"
+ , "move_all"
+ }
+
+local log = dofile "/lib/log.lua"
+-- log.on_insert = log.queue_event("log")
+log.debug = function(self, str) self:insert("debug", str) end
+log.on_insert = function(t)
+ if not verbose and t.status == "debug" then return end
+ local color = (t.status == "debug" and colors.gray)
+ or (t.status == "warn" and colors.yellow)
+ or (t.status == "err" and colors.red)
+ or colors.white
+ term.setTextColor(colors.gray)
+ write(os.date("%a %T ", t.epoch))
+ term.setTextColor(color)
+ print(t.entry)
+end
+
+
+-- configuration
+
+local config_path = "/etc/carriage-controller.lua"
+local config = {}
+do
+ local ok, config_file = pcall(dofile, config_path)
+ if not ok then return "couldn't load config" end
+-- local config_file = log:pwarn("Couldn't load \""..config_path.."\"", dofile, config_path)
+-- if err then log.warn("Couldn't load \""..config_path.."\": "..err) end
+ config_file = config_file or {}
+ for k,v in pairs(config_file) do config[k] = v end
+
+ if not config.deployers then
+ log:ok("auto-detecting deployers")
+ config.deployers = {}
+ for _,p in ipairs(peripheral.getNames()) do
+ if string.match(p, "^create:deployer") then
+ table.insert(config.deployers, p)
+ end
+ end
+ local f = #config.deployers > 0 and log.ok or log.err
+ f(log, "found "..tostring(#config.deployers).." deployers")
+ end
+ if not config.deployers or #config.deployers <= 0 then return end
+
+ for k, v in pairs(config.contacts) do
+ local addr, side = table.unpack(config.contacts[k])
+ config.contacts[k].getInput = addr == "internal"
+ and function() return rs.getInput(side) end
+ or function() return peripheral.call(addr, "getInput", side) end
+ end
+end
+
+
+-- signals
+
+local signal = {}
+signal.carriage_ready = function() os.queueEvent("carriage", "ready") end
+signal.carriage_busy = function() os.queueEvent("carriage", "busy") end
+signal.carriage_parked = function() os.queueEvent("carriage", "parked") end
+signal.drill = function() os.queueEvent("drill") end
+signal.auto = function(bool) os.queueEvent("auto", bool) end
+
+
+-- wait_until functions: yields until some condition is met
+
+local wait_until = {}
+
+wait_until.carriage_event = function(status)
+ return function()
+ local ev
+ repeat
+ _, ev = os.pullEvent("carriage")
+ until ev == status
+ end
+end
+
+wait_until.carriage_ready = wait_until.carriage_event("ready")
+
+wait_until.carriage_busy = wait_until.carriage_event("busy")
+
+wait_until.carriage_parked = wait_until.carriage_event("parked")
+
+wait_until.drilling_started = function() os.pullEvent("drill") end
+
+wait_until.auto = function(bool)
+ local auto
+ repeat
+ _, auto = os.pullEvent("auto")
+ until auto == bool
+end
+
+wait_until.carriage_should_return = function()
+ while not config.contacts.away.getInput() do os.sleep(0) end
+end
+
+wait_until.carriage_has_returned = function()
+ while not config.contacts.home.getInput() do os.sleep(0) end
+end
+
+wait_until.deployers_available = function()
+ local available = function()
+ for _,d in ipairs(config.deployers) do
+ if not peripheral.wrap(d) then return false end
+ end
+ return true
+ end
+ while not available() do os.sleep(); print("waiting for deployers") end
+end
+
+wait_until.carriage_inventory_available = function()
+ while not peripheral.wrap(config.carriage_inventory) do os.sleep() end
+end
+
+wait_until.output_below_threshold = function()
+ while inv.free(config.output_inventory) >= config.output_threshold do
+ os.sleep()
+ end
+end
+
+
+-- gearshift
+local gearshift = {}
+gearshift.forward = nil
+gearshift.back = nil
+
+do
+ local addr, side = table.unpack(config.gearshift)
+ local set
+ if addr == "internal" then
+ set = function(bool) return function() rs.setOutput(side, bool) end end
+ else
+ set = function(bool) return function() peripheral.call(addr, "setOutput", side, bool) end end
+ end
+ gearshift.forward = set(true)
+ gearshift.back = set(false)
+end
+
+
+-- inventory management
+
+local restock_deployers
+local transfer_items
+
+restock_deployers = function()
+-- wait_until.deployers_available()
+ for _, d in pairs(config.deployers) do
+-- local deployer = peripheral.wrap(d)
+ local empty = { name = "minecraft:redstone", count = 0, maxCount = 64 }
+ local item = peripheral.call(d, "getItemDetail", 1) or empty
+ if item.name ~= "minecraft:redstone" then
+ log:err(d.." holding "..item.name)
+ end
+ local found_all, index = inv.find(config.redstone_inventory, { ["minecraft:redstone"] = item.maxCount - item.count })
+ if not found_all then
+ log:warn("low on redstone")
+ end
+ if not index["minecraft:redstone"] then
+ log:warn("out of redstone")
+ else
+ for slot, amount in pairs(index["minecraft:redstone"].slots) do
+ peripheral.call(config.redstone_inventory, "pushItems", d, slot, amount)
+ end
+ end
+ end
+end
+
+transfer_items = function()
+-- wait_until.carriage_inventory_available()
+-- wait_until.output_below_threshold()
+ local moved = inv.move_all(config.carriage_inventory, config.output_inventory)
+ log:ok("transferred "..tostring(moved).." items")
+end
+
+
+-- routines
+
+local drill
+local ready_carriage
+local toggle_auto
+local print_log
+local initialize
+
+drill = forever(function()
+ wait_until.carriage_ready()
+ log:debug("carriage ready")
+ wait_until.drilling_started()
+ log:debug("drilling started")
+ signal.carriage_busy()
+ gearshift.forward()
+ wait_until.carriage_should_return()
+ log:debug("carriage returning")
+ gearshift.back()
+ wait_until.carriage_has_returned()
+ log:debug("carriage parked")
+ signal.carriage_parked()
+end)
+
+ready_carriage = forever(function()
+ wait_until.carriage_parked()
+-- wait_until.deployers_available()
+ log:debug("restocking deployers")
+ restock_deployers()
+-- wait_until.output_below_threshold()
+-- wait_until.carriage_inventory_available()
+ log:debug("transferring items")
+ transfer_items()
+ signal.carriage_ready()
+end)
+
+toggle_auto = forever(function()
+ wait_until.auto(true)
+ log:ok("auto enabled")
+ simultaneously
+ { function() wait_until.auto(false) end
+ , forever(function()
+ wait_until.carriage_ready()
+ log:debug("auto drilling on ready")
+ signal.drill()
+ end)
+ , function()
+ if config.start_immediately then
+ log:debug("auto drilling immediately")
+ signal.drill()
+ end
+ forever(coroutine.yield)()
+ end
+ }
+ log:ok("auto disabled")
+end)
+
+-- print_log = forever(function()
+-- local _, epoch, status, entry = os.pullEvent("log")
+-- print(status, entry)
+-- end)
+
+initialize = function()
+ if not config.contacts.home.getInput() then
+ log:warn("carriage not found")
+ log:ok("(tip: \"park home\" forces it home)")
+ end
+ wait_until.carriage_has_returned()
+-- wait_until.deployers_available()
+-- wait_until.carriage_inventory_available()
+ log:ok("carriage parked")
+ signal.carriage_parked()
+ wait_until.carriage_ready()
+ if config.auto then signal.auto(true) end
+ forever(coroutine.yield)()
+end
+
+
+-- main
+
+simultaneously
+ { drill
+ , ready_carriage
+ , toggle_auto
+-- , print_log
+ , initialize
+ , function() os.pullEventRaw("terminate") end
+ }
return not err or table.unpack({ false, "Could not move: "..err })
end
+t.move_all = function(src, dst)
+ local moved = 0
+ for slot = 1, peripheral.call(src, "size") do
+ moved = moved + peripheral.call(src, "pushItems", dst, slot)
+ end
+ return moved
+end
+
t.count = function(address, item_names)
local slots = peripheral.call(address, "list")
local count = 0
return count
end
+t.free = function(address)
+ local inventory = peripheral.wrap(address)
+-- local items = inventory.list()
+ local free = 0
+ for slot = 1, inventory.size() do
+ local limit = inventory.getItemLimit(slot)
+ local item = inventory.getItemDetail(slot)
+ local amount = item and item.count or 0
+ free = free + limit - amount
+ end
+ return free
+end
+
+
+-- index :: inventory name -> index
+-- index :: name -> m (name, index)
+-- move :: (inv, { slot = amt })
+-- index :: inv -> (inv, { item = { total = amount, slots = { slot = amount } } })
+-- find :: {inv} -> item -> amount -> { inv = { total = amount, slots = { slot = amount } } }
+
+-- by_item :: ... -> { item = { total = amount, inventories = { total = amount, slots = { slot = amount } } } }
+-- by_inventory :: ... -> { inventory = { item = { total = amount, slots = { slot = amount } } } }
+
+-- internal inventories are indexed once. the index is stored in the inventory object and modified as the contents are operated on.
+-- normal inventories do not keep track of their index, and is re-indexed every time index() is called
+
+local inventory_index = {}
+-- { ["item_name"] = { total = amt, slots = { slot = amt } } }
+do
+ local mt = {}
+ mt.__index = function() return { total = 0, slots = {} } end
+ mt.__call = function()
+ end
+ setmetatable(inventory_index, mt)
+end
+
+local do_index = function(inventory)
+ if inventory.internal then
+ return inventory.index
+ end
+ if not inventory.current_index then
+ -- do the indexing
+ end
+ return inventory.index
+end
+
+local do_move = function(from, to, what)
+ local from_index
+ if not from.indexed then
+ end
+end
+
+local inventory = {}
+inventory.wrap = function(inventory_addr, block_reader_addr)
+ local inv =
+ { addr = inventory_addr
+ , block_reader_addr = block_reader_addr or false
+ , internal = false
+ , current_index = false
+ , index = function(self) end
+ , move = function(self, ...) end
+ }
+ return inv
+end
+inventory.wrap_internal = function(...)
+ local inv = inventory.wrap(...)
+ inv.internal = true
+ return inv
+end
+
return t