Add Inventory Manager Web Bridge for modem and HTTP communication
This commit is contained in:
272
inventoryWebBridge.lua
Normal file
272
inventoryWebBridge.lua
Normal file
@@ -0,0 +1,272 @@
|
||||
-- Inventory Manager Web Bridge
|
||||
-- Runs on a CC:Tweaked computer with a modem and HTTP access.
|
||||
-- Listens to the inventory master's broadcasts via modem and
|
||||
-- forwards state to the web server via HTTP.
|
||||
-- Also polls the web server for commands and sends them to the master.
|
||||
|
||||
-------------------------------------------------
|
||||
-- Configuration
|
||||
-------------------------------------------------
|
||||
|
||||
-- Web server URL (change to your Docker host IP/hostname)
|
||||
local SERVER_URL = "http://localhost:4200"
|
||||
local POLL_INTERVAL = 0.5 -- seconds between command polls
|
||||
local STATE_INTERVAL = 1 -- seconds between state forwards
|
||||
|
||||
-- Modem channels (must match inventoryManager.lua)
|
||||
local BROADCAST_CHANNEL = 4200
|
||||
local ORDER_CHANNEL = 4201
|
||||
|
||||
-------------------------------------------------
|
||||
-- Load config from file if present
|
||||
-------------------------------------------------
|
||||
|
||||
local CONFIG_FILE = ".webbridge_config"
|
||||
|
||||
local function loadConfig()
|
||||
if fs.exists(CONFIG_FILE) then
|
||||
local f = fs.open(CONFIG_FILE, "r")
|
||||
local data = f.readAll()
|
||||
f.close()
|
||||
local ok, cfg = pcall(textutils.unserialiseJSON, data)
|
||||
if ok and cfg then
|
||||
if cfg.serverUrl then SERVER_URL = cfg.serverUrl end
|
||||
if cfg.pollInterval then POLL_INTERVAL = cfg.pollInterval end
|
||||
if cfg.stateInterval then STATE_INTERVAL = cfg.stateInterval end
|
||||
print("[CONFIG] Loaded from " .. CONFIG_FILE)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-------------------------------------------------
|
||||
-- State
|
||||
-------------------------------------------------
|
||||
|
||||
local latestState = nil -- last broadcast from master
|
||||
local modem = nil
|
||||
local modemName = nil
|
||||
local running = true
|
||||
|
||||
-------------------------------------------------
|
||||
-- Find modem
|
||||
-------------------------------------------------
|
||||
|
||||
local function findModem()
|
||||
for _, name in ipairs(peripheral.getNames()) do
|
||||
if peripheral.getType(name) == "modem" then
|
||||
modem = peripheral.wrap(name)
|
||||
modemName = name
|
||||
modem.open(BROADCAST_CHANNEL)
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
-------------------------------------------------
|
||||
-- HTTP helpers
|
||||
-------------------------------------------------
|
||||
|
||||
local function httpPost(path, body)
|
||||
local url = SERVER_URL .. path
|
||||
local data = textutils.serialiseJSON(body)
|
||||
local headers = { ["Content-Type"] = "application/json" }
|
||||
|
||||
local ok, err = pcall(function()
|
||||
local response = http.post(url, data, headers)
|
||||
if response then
|
||||
local responseData = response.readAll()
|
||||
response.close()
|
||||
return responseData
|
||||
end
|
||||
end)
|
||||
|
||||
if not ok then
|
||||
-- Silent fail, will retry
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
local function httpGet(path)
|
||||
local url = SERVER_URL .. path
|
||||
local ok, result = pcall(function()
|
||||
local response = http.get(url)
|
||||
if response then
|
||||
local data = response.readAll()
|
||||
response.close()
|
||||
return textutils.unserialiseJSON(data)
|
||||
end
|
||||
end)
|
||||
|
||||
if ok then
|
||||
return result
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
-------------------------------------------------
|
||||
-- Forward state to web server
|
||||
-------------------------------------------------
|
||||
|
||||
local function forwardState()
|
||||
if not latestState then return end
|
||||
httpPost("/api/bridge/state", latestState)
|
||||
end
|
||||
|
||||
-------------------------------------------------
|
||||
-- Process commands from web server
|
||||
-------------------------------------------------
|
||||
|
||||
local function processCommand(cmd)
|
||||
if not modem then return end
|
||||
|
||||
local action = cmd.action
|
||||
if not action then return end
|
||||
|
||||
print(string.format("[CMD] %s", action))
|
||||
|
||||
if action == "order" then
|
||||
modem.transmit(ORDER_CHANNEL, BROADCAST_CHANNEL, {
|
||||
type = "order",
|
||||
itemName = cmd.itemName,
|
||||
amount = cmd.amount,
|
||||
dropperName = cmd.dropperName,
|
||||
})
|
||||
elseif action == "scan" then
|
||||
modem.transmit(ORDER_CHANNEL, BROADCAST_CHANNEL, {
|
||||
type = "scan",
|
||||
})
|
||||
elseif action == "toggle_pause" then
|
||||
modem.transmit(ORDER_CHANNEL, BROADCAST_CHANNEL, {
|
||||
type = "toggle_pause",
|
||||
})
|
||||
elseif action == "toggle_recipe" then
|
||||
modem.transmit(ORDER_CHANNEL, BROADCAST_CHANNEL, {
|
||||
type = "toggle_recipe",
|
||||
recipe = cmd.recipe,
|
||||
})
|
||||
elseif action == "enable_all" then
|
||||
modem.transmit(ORDER_CHANNEL, BROADCAST_CHANNEL, {
|
||||
type = "enable_all",
|
||||
})
|
||||
elseif action == "disable_all" then
|
||||
modem.transmit(ORDER_CHANNEL, BROADCAST_CHANNEL, {
|
||||
type = "disable_all",
|
||||
})
|
||||
elseif action == "sort_barrel" then
|
||||
modem.transmit(ORDER_CHANNEL, BROADCAST_CHANNEL, {
|
||||
type = "sort_barrel",
|
||||
barrelName = cmd.barrelName,
|
||||
})
|
||||
elseif action == "craft" then
|
||||
modem.transmit(ORDER_CHANNEL, BROADCAST_CHANNEL, {
|
||||
type = "craft",
|
||||
recipeIdx = cmd.recipeIdx,
|
||||
})
|
||||
else
|
||||
print("[CMD] Unknown action: " .. tostring(action))
|
||||
end
|
||||
end
|
||||
|
||||
-------------------------------------------------
|
||||
-- Tasks
|
||||
-------------------------------------------------
|
||||
|
||||
-- Task 1: Listen for modem broadcasts from master
|
||||
local function modemListener()
|
||||
while running do
|
||||
local event, side, channel, replyChannel, message = os.pullEvent("modem_message")
|
||||
if channel == BROADCAST_CHANNEL and type(message) == "table" then
|
||||
if message.type == "state" then
|
||||
latestState = message
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Task 2: Forward state to web server periodically
|
||||
local function stateForwarder()
|
||||
while running do
|
||||
local ok, err = pcall(forwardState)
|
||||
if not ok then
|
||||
-- Connection error, will retry
|
||||
end
|
||||
sleep(STATE_INTERVAL)
|
||||
end
|
||||
end
|
||||
|
||||
-- Task 3: Poll web server for commands
|
||||
local function commandPoller()
|
||||
while running do
|
||||
local ok, err = pcall(function()
|
||||
local result = httpGet("/api/bridge/commands")
|
||||
if result and result.commands and #result.commands > 0 then
|
||||
-- Process each command
|
||||
for _, cmd in ipairs(result.commands) do
|
||||
pcall(processCommand, cmd)
|
||||
end
|
||||
-- Acknowledge
|
||||
httpPost("/api/bridge/commands/ack", {})
|
||||
end
|
||||
end)
|
||||
sleep(POLL_INTERVAL)
|
||||
end
|
||||
end
|
||||
|
||||
-- Task 4: Heartbeat / connection status display
|
||||
local function heartbeat()
|
||||
local connected = false
|
||||
while running do
|
||||
local ok, result = pcall(function()
|
||||
return httpGet("/api/health")
|
||||
end)
|
||||
local nowConnected = ok and result ~= nil
|
||||
if nowConnected ~= connected then
|
||||
connected = nowConnected
|
||||
if connected then
|
||||
print("[WEB] Connected to web server")
|
||||
else
|
||||
print("[WEB] Disconnected from web server")
|
||||
end
|
||||
end
|
||||
sleep(5)
|
||||
end
|
||||
end
|
||||
|
||||
-------------------------------------------------
|
||||
-- Main
|
||||
-------------------------------------------------
|
||||
|
||||
local function main()
|
||||
print("===================================")
|
||||
print(" Inventory Manager - Web Bridge")
|
||||
print("===================================")
|
||||
print("")
|
||||
|
||||
loadConfig()
|
||||
|
||||
if findModem() then
|
||||
print("[OK] Modem: " .. modemName)
|
||||
else
|
||||
print("[WARN] No modem found! Bridge needs a modem.")
|
||||
print(" Attach a modem and restart.")
|
||||
return
|
||||
end
|
||||
|
||||
print("[OK] Server URL: " .. SERVER_URL)
|
||||
print("[OK] Poll interval: " .. POLL_INTERVAL .. "s")
|
||||
print("[OK] State interval: " .. STATE_INTERVAL .. "s")
|
||||
print("")
|
||||
print("Bridge is running. Press Ctrl+T to stop.")
|
||||
print("Listening for master broadcasts on ch " .. BROADCAST_CHANNEL)
|
||||
print("")
|
||||
|
||||
parallel.waitForAny(
|
||||
modemListener,
|
||||
stateForwarder,
|
||||
commandPoller,
|
||||
heartbeat
|
||||
)
|
||||
end
|
||||
|
||||
main()
|
||||
Reference in New Issue
Block a user