Last active
March 7, 2020 13:50
-
-
Save WhiteHusky/9ed33a569b2a2a44e7bd11a672109666 to your computer and use it in GitHub Desktop.
GERTi Modem Emulation & Supporting Libs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| --[[ | |
| Code by Carlen White | |
| ]]-- | |
| local component = require("component") | |
| local computer = require("computer") | |
| if not SOFT_COMPONENT_UNALTERED then | |
| SOFT_COMPONENT_UNALTERED = {} | |
| end | |
| local scua = SOFT_COMPONENT_UNALTERED | |
| local softwareComponents = {} | |
| local overrides = {} | |
| local softComponents = {} | |
| softwareComponents.components = softComponents | |
| -- Use existing metatables from proxies | |
| local componentProxy = getmetatable(component.eeprom) | |
| local componentCallback = getmetatable(component.eeprom.get) | |
| local function generateSubAddress(num) | |
| return string.format("%x", math.random(math.pow(16,num-1)-1, math.pow(16,num)-1)) | |
| end | |
| local function generateAddress() | |
| local addr = generateSubAddress(8) .. "-" | |
| addr = addr .. generateSubAddress(4) .. "-" | |
| addr = addr .. generateSubAddress(4) .. "-" | |
| addr = addr .. generateSubAddress(4) .. "-" | |
| addr = addr .. "534f46545741" -- SOFTWA[RE] | |
| return addr | |
| end | |
| function overrides.invoke(address, method, ...) | |
| local softwareComponent = softComponents[address] | |
| local values | |
| if softwareComponent then | |
| values = {softwareComponent[2][method](...)} | |
| else | |
| values = {scua.invoke(address, method, ...)} | |
| end | |
| return table.unpack(values) | |
| end | |
| function overrides.list(filter, exact) | |
| local matches = scua.list(filter, exact) | |
| for k, v in pairs(softComponents) do | |
| if not filter or v[1]:find(filter, 1, not exact) then | |
| matches[k] = v[1] | |
| end | |
| end | |
| return matches | |
| end | |
| function overrides.type(address) | |
| local softwareComponent = softComponents[address] | |
| if softwareComponent then | |
| return softwareComponent[1] | |
| else | |
| return scua.type(address) | |
| end | |
| end | |
| function overrides.slot(address) | |
| local softwareComponent = softComponents[address] | |
| if softwareComponent then | |
| return -1 | |
| else | |
| return scua.slot(address) | |
| end | |
| end | |
| function overrides.methods(address) | |
| local softwareComponent = softComponents[address] | |
| local methods | |
| if softwareComponent then | |
| methods = {} | |
| for k, v in pairs(softwareComponent[3]) do | |
| methods[k] = true | |
| end | |
| else | |
| methods = scua.methods(address) | |
| end | |
| return methods | |
| end | |
| function overrides.proxy(address) | |
| local softwareComponent = softComponents[address] | |
| local proxy | |
| if softwareComponent then | |
| proxy = {address = address, type = softwareComponent[1], slot = -1, fields = {}} | |
| for k, v in pairs(softwareComponent[3]) do | |
| proxy[k] = setmetatable({address=address,name=k}, componentCallback) | |
| end | |
| setmetatable(proxy, componentProxy) | |
| else | |
| proxy = scua.proxy(address) | |
| end | |
| return proxy | |
| end | |
| function overrides.doc(address, method) | |
| local softwareComponent = softComponents[address] | |
| if softwareComponent then | |
| return softwareComponent[3][method] | |
| else | |
| return scua.slot(address) | |
| end | |
| end | |
| function softwareComponents.addComponent(componentType, methods) | |
| local newAddress = generateAddress() | |
| local docs = {} | |
| for k, v in pairs(methods) do | |
| if k:sub(-4) ~= "_doc" and not k:find("__") and type(v) == "function" then | |
| docs[k] = methods[k .. "_doc"] or "no documentation" | |
| end | |
| end | |
| softComponents[newAddress] = {componentType, methods, docs} | |
| computer.pushSignal("component_added", newAddress, componentType) | |
| return newAddress | |
| end | |
| function softwareComponents.removeComponent(address) | |
| if softComponents[address] then | |
| computer.pushSignal("component_removed", address, softComponents[address][1]) | |
| if softComponents[address][2].__destroy then | |
| softComponents[address][2].__destroy(address) | |
| end | |
| softComponents[address] = nil | |
| return true | |
| else | |
| return false | |
| end | |
| end | |
| for k, v in pairs(overrides) do | |
| SOFT_COMPONENT_UNALTERED[k] = SOFT_COMPONENT_UNALTERED[k] or component[k] | |
| component[k] = v | |
| end | |
| component.softwareComponents = softwareComponents |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| --[[ | |
| Code by Carlen White | |
| ]]-- | |
| --[[ | |
| Exposes the GERTi client as a modem device as a compatibility layer for | |
| programs written for a traditional modem. | |
| ]] | |
| local GERTi = require("GERTiClient") | |
| local buffer = require("buffer") | |
| local event = require("event") | |
| local streamingTable = require("streaming-tables") | |
| local component = require("component") | |
| local thread = require("thread") | |
| local servicePort = 5050 | |
| local openConnections = {} | |
| local vbuf = 512 | |
| GERTi_MODEM = GERTi_MODEM or nil | |
| local fauxStream = {} | |
| function fauxStream:new() | |
| local o = { | |
| internalString = "" | |
| } | |
| setmetatable(o, self) | |
| self.__index = self | |
| return o | |
| end | |
| function fauxStream:close() | |
| self = nil | |
| return nil | |
| end | |
| function fauxStream:write(str) | |
| self.internalString = self.internalString .. str | |
| return true | |
| end | |
| function fauxStream:read(n) | |
| local chunk = self.internalString:sub(1,n) | |
| self.internalString = self.internalString:sub(n+1) | |
| return chunk | |
| end | |
| function fauxStream:seek() | |
| return nil, "not supported" | |
| end | |
| local GERTiStream = {} | |
| function GERTiStream:new(socket) | |
| local o = { | |
| socket = socket, | |
| internalString = "" | |
| } | |
| setmetatable(o, self) | |
| self.__index = self | |
| return o | |
| end | |
| function GERTiStream:close() | |
| return self.socket:close() | |
| end | |
| function GERTiStream:write(str) | |
| print(str) | |
| return self.socket:write(str) | |
| end | |
| function GERTiStream:read(n) | |
| local chunk = "" | |
| print("WANT", n) | |
| print("HAVE", self.internalString:len()) | |
| if self.internalString:len() < n then | |
| print("NEED DATA") | |
| local chunks = self.socket:read() | |
| local processed = 0 | |
| for _, value in pairs(chunks) do | |
| print("CHUNK READ") | |
| self.internalString = self.internalString .. value | |
| processed = processed + 1 | |
| end | |
| print("CHUNKS", processed) | |
| end | |
| chunk = self.internalString:sub(1,n) | |
| self.internalString = self.internalString:sub(n+1) | |
| os.sleep(1) | |
| if chunk:len() > 0 then | |
| print(chunk) | |
| end | |
| return chunk | |
| end | |
| function GERTiStream:seek() | |
| return nil, "not supported" | |
| end | |
| local GERTiModem = {} | |
| local ports = {} | |
| local events = {} | |
| function GERTiModem.isWireless() | |
| return true | |
| end | |
| function GERTiModem.maxPacketSize() | |
| return math.huge | |
| end | |
| function GERTiModem.isOpen(port) | |
| checkArg(1, port, "number") | |
| return ports[port] | |
| end | |
| function GERTiModem.open(port) | |
| checkArg(1, port, "number") | |
| assert(port > 0, "port out of range") | |
| ports[port] = true | |
| return true | |
| end | |
| function GERTiModem.close(port) | |
| checkArg(1, port, "number", "nil") | |
| if port then | |
| assert(port > 0, "port out of range") | |
| ports[port] = nil | |
| else | |
| ports={} | |
| end | |
| return true | |
| end | |
| function GERTiModem.send(...) | |
| thread.create(function(addr, port, ...) | |
| print(addr, port, ...) | |
| openConnections[addr] = true | |
| local socket = GERTi.openSocket(addr, servicePort) | |
| local buf = buffer.new("rw", GERTiStream:new(socket)) | |
| buf:setvbuf("full", vbuf) | |
| print("Waiting for acknowledgement...") | |
| event.pull("GERTData", addr) | |
| local response = streamingTable.unpack(buf) | |
| if response.connection then | |
| print("Sending connection request...") | |
| streamingTable.pack(buf, {port=port}) | |
| buf:flush() | |
| print("Waiting for response...") | |
| response = streamingTable.unpack(buf) | |
| local success = false | |
| if response.accept then | |
| print("Request accepted, sending data...") | |
| streamingTable.pack(buf, {...}) | |
| print("Sent.") | |
| else | |
| print("Request declined.") | |
| end | |
| end | |
| openConnections[addr] = nil | |
| buf:close() | |
| end, ...) | |
| return true | |
| end | |
| function GERTiModem.broadcast(port, ...) | |
| for id, _ in pairs(GERTi.getNeighbors()) do | |
| GERTiModem.send(id, port, ...) | |
| end | |
| return true | |
| end | |
| function GERTiModem.getStrength() | |
| return 255 | |
| end | |
| function GERTiModem.setStrength() | |
| return 255 | |
| end | |
| function GERTiModem.getWakeMessage() | |
| return "" | |
| end | |
| function GERTiModem.setWakeMessage() | |
| return "" | |
| end | |
| function GERTiModem.__destroy() | |
| for k, eventId in pairs(events) do | |
| event.cancel(eventId) | |
| events[k] = nil | |
| end | |
| return | |
| end | |
| local function handleGERTiConnection(...) | |
| thread.create(function(eventName, originAddress, connectionID) | |
| if originAddress ~= GERTi.getAddress() and not openConnections[originAddress] and connectionID == servicePort then | |
| print("Request incoming...") | |
| local socket = GERTi.openSocket(originAddress, connectionID) | |
| local buf = buffer.new("rw", GERTiStream:new(socket)) | |
| buf:setvbuf("full", vbuf) | |
| print("Socket open, sending acknowledgement...") | |
| streamingTable.pack(buf, {connection=true}) | |
| buf:flush() | |
| print("Waiting for response...") | |
| local request = streamingTable.unpack(buf) | |
| print("Unpacked...") | |
| if ports[request.port] then | |
| print("Port accepted, sending clearance...") | |
| streamingTable.pack(buf, {accept=true}) | |
| buf:flush() | |
| print("Receiving payload...") | |
| local payload = streamingTable.unpack(buf) | |
| event.push("modem_message", GERTi.getAddress(), originAddress, request.port, table.unpack(payload)) | |
| else | |
| print("Request declined") | |
| streamingTable.pack(buf, {accept=false}) | |
| end | |
| buf:close() | |
| end | |
| end, ...) | |
| end | |
| if GERTi_MODEM then | |
| if component.softwareComponents.removeComponent(GERTi_MODEM) then | |
| print("Old Component Removed") | |
| end | |
| end | |
| GERTi_MODEM=component.softwareComponents.addComponent("modem", GERTiModem) | |
| events.GERTiConnection = event.listen("GERTConnectionID", handleGERTiConnection) | |
| events.GERTiData = event.listen("GERTData", print) | |
| events.modem_message = event.listen("modem_message", print) | |
| events.GERTiConnection_Debug = event.listen("GERTConnectionID", print) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| --[[ | |
| Code by Carlen White | |
| ]]-- | |
| --[[ | |
| Given a table and a stream to write to, a table is converted to binary | |
| data that can be reversed to recreate the table. | |
| Use case is where the goal is to transmit a table but low memory | |
| systems make it impossible to seralize the response through typical | |
| libaries. Or limits of the means to transmit cannot allow transmission of | |
| a serialized table as a whole. | |
| x0000000 = end table | |
| x0000001 = boolean | |
| x0000010 = float | |
| x0000011 = integer | |
| x0000100 = string | |
| x0000101 = table | |
| booleans use the 8th bit to determine if it is true or false. | |
| numbers are followed by a eight bytes containing the number in binary lua | |
| number format | |
| strings are followed by a four byte unsigned integer describing the length | |
| of the string followed by those characters | |
| tables simply starts the same function on the nested table. | |
| ]]-- | |
| local allowedTableKeys = { | |
| boolean=true, | |
| number=true, | |
| string=true | |
| } | |
| local allowedTableValues = { | |
| boolean=true, | |
| number=true, | |
| string=true, | |
| table=true | |
| } | |
| local streamingSerialization = {} | |
| local function thingToBinary(strm, thing) | |
| local thingType = type(thing) | |
| if thingType == "boolean" then | |
| local d = 1 | |
| if thing then | |
| d = 128 | d | |
| end | |
| strm:write(string.pack(">B", d)) | |
| elseif thingType == "number" then | |
| if math.type(thing) == "float" then | |
| strm:write(string.pack(">B", 2)) | |
| strm:write(string.pack(">n", thing)) | |
| else | |
| strm:write(string.pack(">B", 3)) | |
| strm:write(string.pack(">j", thing)) | |
| end | |
| elseif thingType == "string" then | |
| strm:write(string.pack(">B", 4)) | |
| strm:write(string.pack(">I4", thing:len())) | |
| strm:write(thing) | |
| end | |
| end | |
| function streamingSerialization.pack(strm, t) | |
| for key, value in pairs(t) do | |
| local keyType = type(key) | |
| local valueType = type(value) | |
| if allowedTableKeys[keyType] and allowedTableValues[valueType] then | |
| thingToBinary(strm, key) | |
| if valueType == "table" then | |
| strm:write(string.pack(">B", 5)) | |
| streamingSerialization.pack(strm, value) | |
| else | |
| thingToBinary(strm, value) | |
| end | |
| end | |
| end | |
| strm:write(string.pack(">B",0)) | |
| end | |
| local function binaryToThing(strm) | |
| local raw = string.unpack(">B", strm:read(1)) | |
| local rawType = raw & 7 | |
| local thing = nil | |
| if rawType == 1 then -- boolean | |
| thing = false | |
| if (raw & 128) == 128 then | |
| thing = true | |
| end | |
| elseif rawType == 2 then -- float | |
| thing = string.unpack(">n", strm:read(8)) | |
| elseif rawType == 3 then -- integer | |
| thing = string.unpack(">j", strm:read(8)) | |
| elseif rawType == 4 then -- string | |
| local length = string.unpack(">I4", strm:read(4)) | |
| thing = strm:read(length) | |
| elseif rawType == 5 then -- table | |
| thing = streamingSerialization.unpack(strm) | |
| end | |
| return thing | |
| end | |
| function streamingSerialization.unpack(strm) | |
| local t = {} | |
| local key = binaryToThing(strm) | |
| while key do | |
| t[key] = binaryToThing(strm) | |
| key = binaryToThing(strm) | |
| end | |
| return t | |
| end | |
| return streamingSerialization |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment