Advertisement
Guest User

Single channel Crapnet

a guest
Feb 6th, 2021
96
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. --## MADE USE ONE CHANNEL AS PROFF OF CONCEPT. YOU CAN STOP YAMMERING ABOUT IT SKY. I CAN'T TAKE IT ANYMORE AHHHHHHHHHHHHHHH.
  2. --## Documentation not modified. F you whoever decides to use this. Docs are wrong now.
  3.  
  4. --- The Rednet API allows systems to communicate between each other without
  5. -- using redstone. It serves as a wrapper for the modem API, offering ease of
  6. -- functionality (particularly in regards to repeating signals) with some
  7. -- expense of fine control.
  8. --
  9. -- In order to send and receive data, a modem (either wired, wireless, or ender)
  10. -- is required. The data reaches any possible destinations immediately after
  11. -- sending it, but is range limited.
  12. --
  13. -- Rednet also allows you to use a "protocol" - simple string names indicating
  14. -- what messages are about. Receiving systems may filter messages according to
  15. -- their protocols, thereby automatically ignoring incoming messages which don't
  16. -- specify an identical string. It's also possible to @{rednet.lookup|lookup}
  17. -- which systems in the area use certain protocols, hence making it easier to
  18. -- determine where given messages should be sent in the first place.
  19. --
  20. -- @module rednet
  21.  
  22. local expect = dofile("rom/modules/main/cc/expect.lua").expect
  23.  
  24. --- The channel used by the Rednet API to @{broadcast} messages.
  25. CHANNEL_BROADCAST = 65535
  26.  
  27. --- The channel used by the Rednet API to repeat messages.
  28. CHANNEL_REPEAT = 65533
  29.  
  30. local tReceivedMessages = {}
  31. local tReceivedMessageTimeouts = {}
  32. local tHostnames = {}
  33.  
  34. --- Opens a modem with the given @{peripheral} name, allowing it to send and
  35. -- receive messages over rednet.
  36. --
  37. -- This will open the modem on two channels: one which has the same
  38. -- @{os.getComputerID|ID} as the computer, and another on
  39. -- @{CHANNEL_BROADCAST|the broadcast channel}.
  40. --
  41. -- @tparam string modem The name of the modem to open.
  42. -- @throws If there is no such modem with the given name
  43. function open(modem)
  44.     expect(1, modem, "string")
  45.     if peripheral.getType(modem) ~= "modem" then
  46.         error("No such modem: " .. modem, 2)
  47.     end
  48.     peripheral.call(modem, "open", CHANNEL_REPEAT)
  49.     peripheral.call(modem, "open", CHANNEL_BROADCAST)
  50. end
  51.  
  52. --- Close a modem with the given @{peripheral} name, meaning it can no longer
  53. -- send and receive rednet messages.
  54. --
  55. -- @tparam[opt] string modem The side the modem exists on. If not given, all
  56. -- open modems will be closed.
  57. -- @throws If there is no such modem with the given name
  58. function close(modem)
  59.     expect(1, modem, "string", "nil")
  60.     if modem then
  61.         -- Close a specific modem
  62.         if peripheral.getType(modem) ~= "modem" then
  63.             error("No such modem: " .. modem, 2)
  64.         end
  65.         peripheral.call(modem, "close", CHANNEL_REPEAT)
  66.         peripheral.call(modem, "close", CHANNEL_BROADCAST)
  67.     else
  68.         -- Close all modems
  69.         for _, modem in ipairs(peripheral.getNames()) do
  70.             if isOpen(modem) then
  71.                 close(modem)
  72.             end
  73.         end
  74.     end
  75. end
  76.  
  77. --- Determine if rednet is currently open.
  78. --
  79. -- @tparam[opt] string modem Which modem to check. If not given, all connected
  80. -- modems will be checked.
  81. -- @treturn boolean If the given modem is open.
  82. function isOpen(modem)
  83.     expect(1, modem, "string", "nil")
  84.     if modem then
  85.         -- Check if a specific modem is open
  86.         if peripheral.getType(modem) == "modem" then
  87.             return peripheral.call(modem, "isOpen", CHANNEL_REPEAT) and peripheral.call(modem, "isOpen", CHANNEL_BROADCAST)
  88.         end
  89.     else
  90.         -- Check if any modem is open
  91.         for _, modem in ipairs(peripheral.getNames()) do
  92.             if isOpen(modem) then
  93.                 return true
  94.             end
  95.         end
  96.     end
  97.     return false
  98. end
  99.  
  100. --- Allows a computer or turtle with an attached modem to send a message
  101. -- intended for a system with a specific ID. At least one such modem must first
  102. -- be @{rednet.open|opened} before sending is possible.
  103. --
  104. -- Assuming the target was in range and also had a correctly opened modem, it
  105. -- may then use @{rednet.receive} to collect the message.
  106. --
  107. -- @tparam number nRecipient The ID of the receiving computer.
  108. -- @param message The message to send. This should not contain coroutines or
  109. -- functions, as they will be converted to @{nil}.
  110. -- @tparam[opt] string sProtocol The "protocol" to send this message under. When
  111. -- using @{rednet.receive} one can filter to only receive messages sent under a
  112. -- particular protocol.
  113. -- @treturn boolean If this message was successfully sent (i.e. if rednet is
  114. -- currently @{rednet.open|open}). Note, this does not guarantee the message was
  115. -- actually _received_.
  116. -- @see rednet.receive
  117. function send(nRecipient, message, sProtocol)
  118.     expect(1, nRecipient, "number")
  119.     expect(3, sProtocol, "string", "nil")
  120.     -- Generate a (probably) unique message ID
  121.     -- We could do other things to guarantee uniqueness, but we really don't need to
  122.     -- Store it to ensure we don't get our own messages back
  123.     local nMessageID = math.random(1, 2147483647)
  124.     tReceivedMessages[nMessageID] = true
  125.     tReceivedMessageTimeouts[os.startTimer(30)] = nMessageID
  126.  
  127.     -- Create the message
  128.     local nReplyChannel = os.getComputerID()
  129.     local tMessage = {
  130.         nMessageID = nMessageID,
  131.         nRecipient = nRecipient,
  132.         nReplyChannel = nReplyChannel,
  133.         message = message,
  134.         sProtocol = sProtocol,
  135.     }
  136.  
  137.     local sent = false
  138.     if nRecipient == os.getComputerID() then
  139.         -- Loopback to ourselves
  140.         os.queueEvent("rednet_message", nReplyChannel, message, sProtocol)
  141.         sent = true
  142.     else
  143.         -- Send on all open modems, to the target and to repeaters
  144.         for _, sModem in ipairs(peripheral.getNames()) do
  145.             if isOpen(sModem) then
  146.                 --peripheral.call(sModem, "transmit", nRecipient, nReplyChannel, tMessage)
  147.                 peripheral.call(sModem, "transmit", CHANNEL_REPEAT, CHANNEL_REPEAT, tMessage)
  148.                 sent = true
  149.             end
  150.         end
  151.     end
  152.  
  153.     return sent
  154. end
  155.  
  156. --- Broadcasts a string message over the predefined @{CHANNEL_BROADCAST}
  157. -- channel. The message will be received by every device listening to rednet.
  158. --
  159. -- @param message The message to send. This should not contain coroutines or
  160. -- functions, as they will be converted to @{nil}.
  161. -- @tparam[opt] string sProtocol The "protocol" to send this message under. When
  162. -- using @{rednet.receive} one can filter to only receive messages sent under a
  163. -- particular protocol.
  164. -- @see rednet.receive
  165. function broadcast(message, sProtocol)
  166.     expect(2, sProtocol, "string", "nil")
  167.     send(CHANNEL_BROADCAST, message, sProtocol)
  168. end
  169.  
  170. --- Wait for a rednet message to be received, or until `nTimeout` seconds have
  171. -- elapsed.
  172. --
  173. -- @tparam[opt] string sProtocolFilter The protocol the received message must be
  174. -- sent with. If specified, any messages not sent under this protocol will be
  175. -- discarded.
  176. -- @tparam[opt] number nTimeout The number of seconds to wait if no message is
  177. -- received.
  178. -- @treturn[1] number The computer which sent this message
  179. -- @return[1] The received message
  180. -- @treturn[1] string|nil The protocol this message was sent under.
  181. -- @treturn[2] nil If the timeout elapsed and no message was received.
  182. -- @see rednet.broadcast
  183. -- @see rednet.send
  184. function receive(sProtocolFilter, nTimeout)
  185.     -- The parameters used to be ( nTimeout ), detect this case for backwards compatibility
  186.     if type(sProtocolFilter) == "number" and nTimeout == nil then
  187.         sProtocolFilter, nTimeout = nil, sProtocolFilter
  188.     end
  189.     expect(1, sProtocolFilter, "string", "nil")
  190.     expect(2, nTimeout, "number", "nil")
  191.  
  192.     -- Start the timer
  193.     local timer = nil
  194.     local sFilter = nil
  195.     if nTimeout then
  196.         timer = os.startTimer(nTimeout)
  197.         sFilter = nil
  198.     else
  199.         sFilter = "rednet_message"
  200.     end
  201.  
  202.     -- Wait for events
  203.     while true do
  204.         local sEvent, p1, p2, p3 = os.pullEvent(sFilter)
  205.         if sEvent == "rednet_message" then
  206.             -- Return the first matching rednet_message
  207.             local nSenderID, message, sProtocol = p1, p2, p3
  208.             if sProtocolFilter == nil or sProtocol == sProtocolFilter then
  209.                 return nSenderID, message, sProtocol
  210.             end
  211.         elseif sEvent == "timer" then
  212.             -- Return nil if we timeout
  213.             if p1 == timer then
  214.                 return nil
  215.             end
  216.         end
  217.     end
  218. end
  219.  
  220. --- Register the system as "hosting" the desired protocol under the specified
  221. -- name. If a rednet @{rednet.lookup|lookup} is performed for that protocol (and
  222. -- maybe name) on the same network, the registered system will automatically
  223. -- respond via a background process, hence providing the system performing the
  224. -- lookup with its ID number.
  225. --
  226. -- Multiple computers may not register themselves on the same network as having
  227. -- the same names against the same protocols, and the title `localhost` is
  228. -- specifically reserved. They may, however, share names as long as their hosted
  229. -- protocols are different, or if they only join a given network after
  230. -- "registering" themselves before doing so (eg while offline or part of a
  231. -- different network).
  232. --
  233. -- @tparam string sProtocol The protocol this computer provides.
  234. -- @tparam string sHostname The name this protocol exposes for the given protocol.
  235. -- @throws If trying to register a hostname which is reserved, or currently in use.
  236. -- @see rednet.unhost
  237. -- @see rednet.lookup
  238. function host(sProtocol, sHostname)
  239.     expect(1, sProtocol, "string")
  240.     expect(2, sHostname, "string")
  241.     if sHostname == "localhost" then
  242.         error("Reserved hostname", 2)
  243.     end
  244.     if tHostnames[sProtocol] ~= sHostname then
  245.         if lookup(sProtocol, sHostname) ~= nil then
  246.             error("Hostname in use", 2)
  247.         end
  248.         tHostnames[sProtocol] = sHostname
  249.     end
  250. end
  251.  
  252. --- Stop @{rednet.host|hosting} a specific protocol, meaning it will no longer
  253. -- respond to @{rednet.lookup} requests.
  254. --
  255. -- @tparam string sProtocol The protocol to unregister your self from.
  256. function unhost(sProtocol)
  257.     expect(1, sProtocol, "string")
  258.     tHostnames[sProtocol] = nil
  259. end
  260.  
  261. --- Search the local rednet network for systems @{rednet.host|hosting} the
  262. -- desired protocol and returns any computer IDs that respond as "registered"
  263. -- against it.
  264. --
  265. -- If a hostname is specified, only one ID will be returned (assuming an exact
  266. -- match is found).
  267. --
  268. -- @tparam string sProtocol The protocol to search for.
  269. -- @tparam[opt] string sHostname The hostname to search for.
  270. --
  271. -- @treturn[1] { number }|nil A list of computer IDs hosting the given
  272. -- protocol, or @{nil} if none exist.
  273. -- @treturn[2] number|nil The computer ID with the provided hostname and protocol,
  274. -- or @{nil} if none exists.
  275. function lookup(sProtocol, sHostname)
  276.     expect(1, sProtocol, "string")
  277.     expect(2, sHostname, "string", "nil")
  278.  
  279.     -- Build list of host IDs
  280.     local tResults = nil
  281.     if sHostname == nil then
  282.         tResults = {}
  283.     end
  284.  
  285.     -- Check localhost first
  286.     if tHostnames[sProtocol] then
  287.         if sHostname == nil then
  288.             table.insert(tResults, os.getComputerID())
  289.         elseif sHostname == "localhost" or sHostname == tHostnames[sProtocol] then
  290.             return os.getComputerID()
  291.         end
  292.     end
  293.  
  294.     if not isOpen() then
  295.         if tResults then
  296.             return table.unpack(tResults)
  297.         end
  298.         return nil
  299.     end
  300.  
  301.     -- Broadcast a lookup packet
  302.     broadcast({
  303.         sType = "lookup",
  304.         sProtocol = sProtocol,
  305.         sHostname = sHostname,
  306.     }, "dns")
  307.  
  308.     -- Start a timer
  309.     local timer = os.startTimer(2)
  310.  
  311.     -- Wait for events
  312.     while true do
  313.         local event, p1, p2, p3 = os.pullEvent()
  314.         if event == "rednet_message" then
  315.             -- Got a rednet message, check if it's the response to our request
  316.             local nSenderID, tMessage, sMessageProtocol = p1, p2, p3
  317.             if sMessageProtocol == "dns" and type(tMessage) == "table" and tMessage.sType == "lookup response" then
  318.                 if tMessage.sProtocol == sProtocol then
  319.                     if sHostname == nil then
  320.                         table.insert(tResults, nSenderID)
  321.                     elseif tMessage.sHostname == sHostname then
  322.                         return nSenderID
  323.                     end
  324.                 end
  325.             end
  326.         else
  327.             -- Got a timer event, check it's the end of our timeout
  328.             if p1 == timer then
  329.                 break
  330.             end
  331.         end
  332.     end
  333.     if tResults then
  334.         return table.unpack(tResults)
  335.     end
  336.     return nil
  337. end
  338.  
  339. local bRunning = false
  340.  
  341. --- Listen for modem messages and converts them into rednet messages, which may
  342. -- then be @{receive|received}.
  343. --
  344. -- This is automatically started in the background on computer startup, and
  345. -- should not be called manually.
  346. function run()
  347.     if bRunning then
  348.         error("rednet is already running", 2)
  349.     end
  350.     bRunning = true
  351.  
  352.     while bRunning do
  353.         local sEvent, p1, p2, p3, p4 = os.pullEventRaw()
  354.         if sEvent == "modem_message" then
  355.             -- Got a modem message, process it and add it to the rednet event queue
  356.             local sModem, nChannel, nReplyChannel, tMessage = p1, p2, p3, p4
  357.             if isOpen(sModem) and (nChannel == CHANNEL_REPEAT or nChannel == CHANNEL_BROADCAST) then
  358.                 if type(tMessage) == "table" and tMessage.nMessageID then
  359.                     if not tReceivedMessages[tMessage.nMessageID] then
  360.                         if tMessage.nRecipient == os.getComputerID() or tMessage.nRecipient == CHANNEL_BROADCAST then
  361.                             tReceivedMessages[tMessage.nMessageID] = true
  362.                             tReceivedMessageTimeouts[os.startTimer(30)] = tMessage.nMessageID
  363.                             os.queueEvent("rednet_message", tMessage.nReplyChannel, tMessage.message, tMessage.sProtocol)
  364.                         end
  365.                     end
  366.                 end
  367.             end
  368.  
  369.         elseif sEvent == "rednet_message" then
  370.             -- Got a rednet message (queued from above), respond to dns lookup
  371.             local nSenderID, tMessage, sProtocol = p1, p2, p3
  372.             if sProtocol == "dns" and type(tMessage) == "table" and tMessage.sType == "lookup" then
  373.                 local sHostname = tHostnames[tMessage.sProtocol]
  374.                 if sHostname ~= nil and (tMessage.sHostname == nil or tMessage.sHostname == sHostname) then
  375.                     rednet.send(nSenderID, {
  376.                         sType = "lookup response",
  377.                         sHostname = sHostname,
  378.                         sProtocol = tMessage.sProtocol,
  379.                     }, "dns")
  380.                 end
  381.             end
  382.  
  383.         elseif sEvent == "timer" then
  384.             -- Got a timer event, use it to clear the event queue
  385.             local nTimer = p1
  386.             local nMessage = tReceivedMessageTimeouts[nTimer]
  387.             if nMessage then
  388.                 tReceivedMessageTimeouts[nTimer] = nil
  389.                 tReceivedMessages[nMessage] = nil
  390.             end
  391.         end
  392.     end
  393. end
  394.  
Advertisement
Advertisement
Advertisement
RAW Paste Data Copied
Advertisement