robathome

MC controller pocket PC

Nov 6th, 2025 (edited)
157
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 7.98 KB | None | 0 0
  1. -- pushfs_client.lua
  2. -- Pocket computer client for "pushfs-v1" turtles/computers.
  3. -- Subcommands:
  4. -- ping [--all | --id N | --label L]
  5. -- put <src> <dst> [--all|--id N|--label L]
  6. -- exec "<cmd line>" [--capture] [--timeout S] [--cwd /] [--all|--id N|--label L]
  7. -- reboot [--all|--id N|--label L]
  8. --
  9. -- Reporting: this client aggregates and prints ALL replies it receives
  10. -- within a short window for each request (fan-in reporting).
  11.  
  12. --------------------------
  13. -- Config
  14. --------------------------
  15. local PROTOCOL = "pushfs-v1"
  16. local SHARED_SECRET = nil -- set if servers require a token
  17. local ACK_WINDOW = 2.5 -- seconds to collect multiple ACKs
  18. local STEP_WINDOW = 2.5 -- per-step window during chunked PUT
  19. local CHUNK = 4096
  20. local MAX_INLINE = 48*1024
  21.  
  22. -- Lua 5.1/5.2 compat
  23. local unpack = table.unpack or unpack
  24.  
  25. --------------------------
  26. -- Utilities
  27. --------------------------
  28. local function die(msg) print(msg) error(msg, 0) end
  29.  
  30. local function openModems()
  31. local ok = false
  32. for _, s in ipairs(rs.getSides()) do
  33. if peripheral.getType(s) == "modem" then
  34. if not rednet.isOpen(s) then rednet.open(s) end
  35. ok = true
  36. end
  37. end
  38. if not ok then die("No modem. Attach a wireless modem.") end
  39. end
  40.  
  41. local function readAll(path)
  42. if not fs.exists(path) or fs.isDir(path) then return nil, "missing or dir" end
  43. local h = fs.open(path, "rb"); if not h then return nil, "open failed" end
  44. local d = h.readAll() or ""; h.close(); return d
  45. end
  46.  
  47. local function adler32(str)
  48. local MOD = 65521
  49. local a,b = 1,0
  50. for i=1,#str do
  51. a = (a + string.byte(str,i)) % MOD
  52. b = (b + a) % MOD
  53. end
  54. return string.format("%08x", bit32.bor(bit32.lshift(b,16), a))
  55. end
  56.  
  57. local function gen_txid()
  58. return tostring(os.epoch("utc")) .. "-" .. tostring(math.random(1,1e9))
  59. end
  60.  
  61. local function parse_args(argv)
  62. local cmd = argv[1]; if not cmd then die("Usage: pushfs_client <ping|put|exec|reboot> ...") end
  63. local out = { cmd = cmd, args = {}, flags = {} }
  64. local i = 2
  65. while i <= #argv do
  66. local a = argv[i]
  67. if a:sub(1,2) == "--" then
  68. local k = a:sub(3); local v = true
  69. if i < #argv and argv[i+1]:sub(1,2) ~= "--" then v = argv[i+1]; i = i + 1 end
  70. out.flags[k] = v
  71. else table.insert(out.args, a) end
  72. i = i + 1
  73. end
  74. return out
  75. end
  76.  
  77. local function build_target(flags)
  78. if flags["all"] == true or flags["all"] == "true" then return "all" end
  79. if flags["id"] then return { id = tonumber(flags["id"]) } end
  80. if flags["label"] then return { label = tostring(flags["label"]) } end
  81. die("Target required: use --all or --id N or --label L")
  82. end
  83.  
  84. -- Collect ALL matching replies for a txid/op during window_s seconds.
  85. local function collect_acks(txid, want_op, window_s)
  86. local end_at = os.epoch("utc") + math.floor((window_s or ACK_WINDOW) * 1000)
  87. local seen = {} -- sender_id -> msg
  88. while true do
  89. local now = os.epoch("utc"); if now >= end_at then break end
  90. local timeout = (end_at - now) / 1000
  91. local sender, msg, proto = rednet.receive(PROTOCOL, timeout)
  92. if sender and type(msg)=="table" and msg.txid==txid and msg.op==want_op then
  93. msg.__sender = sender
  94. seen[sender] = msg
  95. end
  96. end
  97. -- flatten
  98. local list = {}
  99. for _, m in pairs(seen) do table.insert(list, m) end
  100. table.sort(list, function(a,b) return (a.__sender or 0) < (b.__sender or 0) end)
  101. return list
  102. end
  103.  
  104. -- Send, then collect all acks in a window.
  105. local function send_and_collect(msg, want_op, window_s)
  106. rednet.broadcast(msg, PROTOCOL)
  107. return collect_acks(msg.txid, want_op, window_s)
  108. end
  109.  
  110. local function print_acks(title, acks)
  111. print(title .. (": %d node(s)"):format(#acks))
  112. for _, m in ipairs(acks) do
  113. local who = ("id=%s"):format(tostring(m.__sender))
  114. if m.id or m.label then who = ("%s label=%s id=%s"):format(tostring(m.label or ""), tostring(m.id or "?"), tostring(m.__sender)) end
  115. local note = m.note or ""
  116. print(("- %s: %s%s"):format(who, m.ok and "ok" or "err", note ~= "" and (" ("..note..")") or ""))
  117. end
  118. end
  119.  
  120. --------------------------
  121. -- Ops
  122. --------------------------
  123. local function op_ping(flags)
  124. local target = build_target(flags)
  125. local txid = gen_txid()
  126. local req = { op="ping", txid=txid, target=target, token=SHARED_SECRET }
  127. local acks = send_and_collect(req, "ping", ACK_WINDOW)
  128. print_acks("ping", acks)
  129. end
  130.  
  131. local function op_put(args, flags)
  132. if #args < 2 then die("put <src> <dst> [--all|--id N|--label L]") end
  133. local src, dst = args[1], args[2]
  134. local data, e = readAll(src); if not data then die("read: "..e) end
  135. local target = build_target(flags)
  136. local sum = adler32(data)
  137.  
  138. if #data <= MAX_INLINE then
  139. local txid = gen_txid()
  140. local req = { op="put", txid=txid, target=target, fname=dst, data=data, checksum=sum, token=SHARED_SECRET }
  141. local acks = send_and_collect(req, "put", ACK_WINDOW)
  142. print_acks(("put %s -> %s"):format(src, dst), acks)
  143. return
  144. end
  145.  
  146. -- chunked: begin
  147. local txid = gen_txid()
  148. local begin = { op="begin", txid=txid, target=target, fname=dst, size=#data, checksum=sum, token=SHARED_SECRET }
  149. local a_begin = send_and_collect(begin, "begin", STEP_WINDOW)
  150. print_acks(("begin %s -> %s"):format(src, dst), a_begin)
  151.  
  152. -- chunk loop
  153. local sent = 0
  154. while sent < #data do
  155. local chunk = data:sub(sent+1, math.min(sent + CHUNK, #data))
  156. rednet.broadcast({ op="chunk", txid=txid, target=target, data=chunk }, PROTOCOL)
  157. local a_chunk = collect_acks(txid, "chunk", STEP_WINDOW)
  158. print(("[chunk %d..%d] acks=%d"):format(sent+1, sent+#chunk, #a_chunk))
  159. sent = sent + #chunk
  160. end
  161.  
  162. -- end
  163. local endm = { op="end", txid=txid, target=target }
  164. local a_end = send_and_collect(endm, "end", STEP_WINDOW)
  165. print_acks("end", a_end)
  166. end
  167.  
  168. local function op_exec(args, flags)
  169. if #args < 1 then
  170. die([[exec "<cmd line>" [--capture] [--timeout S] [--cwd /] [--all|--id N|--label L]]])
  171. end
  172. local line = args[1]
  173. local target = build_target(flags)
  174. local capture = flags["capture"] == true or flags["capture"] == "true"
  175. local timeout = tonumber(flags["timeout"] or "0")
  176. local cwd = flags["cwd"]
  177.  
  178. local txid = gen_txid()
  179. local req = {
  180. op="exec", txid=txid, target=target, line=line,
  181. capture=capture, timeout=timeout, cwd=cwd, token=SHARED_SECRET
  182. }
  183. rednet.broadcast(req, PROTOCOL)
  184. local acks = collect_acks(txid, "exec", ACK_WINDOW)
  185.  
  186. if capture then
  187. -- print each node's output
  188. for _, m in ipairs(acks) do
  189. local who = ("id=%s"):format(tostring(m.__sender))
  190. if m.id or m.label then who = ("%s label=%s id=%s"):format(tostring(m.label or ""), tostring(m.id or "?"), tostring(m.__sender)) end
  191. print(("--- %s ---"):format(who))
  192. if m.ok then print(m.output or "") else print("[error] "..tostring(m.note or "")) end
  193. end
  194. else
  195. print_acks(("exec %q"):format(line), acks)
  196. end
  197. end
  198.  
  199. local function op_reboot(flags)
  200. -- Use exec to request reboot on targets. No capture.
  201. local target = build_target(flags)
  202. local txid = gen_txid()
  203. local req = { op="exec", txid=txid, target=target, line="reboot", capture=false, token=SHARED_SECRET }
  204. local acks = send_and_collect(req, "exec", 1.0) -- quick window; they will drop off after
  205. print_acks("reboot", acks)
  206. print("Note: nodes may vanish from the network immediately after ACK.")
  207. end
  208.  
  209. --------------------------
  210. -- Main
  211. --------------------------
  212. local function main(...)
  213. openModems()
  214. math.randomseed(os.epoch("utc") % 2147483647)
  215. local argv = { ... }
  216. local parsed = parse_args(argv)
  217. if parsed.cmd == "ping" then
  218. op_ping(parsed.flags)
  219. elseif parsed.cmd == "put" then
  220. op_put(parsed.args, parsed.flags)
  221. elseif parsed.cmd == "exec" then
  222. op_exec(parsed.args, parsed.flags)
  223. elseif parsed.cmd == "reboot" then
  224. op_reboot(parsed.flags)
  225. else
  226. die("Unknown subcommand: "..tostring(parsed.cmd))
  227. end
  228. end
  229.  
  230. local ok, err = pcall(main, ...)
  231. if not ok then print("Error: "..tostring(err)) end
  232.  
Advertisement
Add Comment
Please, Sign In to add comment