Skip to content

Instantly share code, notes, and snippets.

@WhiteHusky
Last active March 7, 2020 13:50
Show Gist options
  • Select an option

  • Save WhiteHusky/9ed33a569b2a2a44e7bd11a672109666 to your computer and use it in GitHub Desktop.

Select an option

Save WhiteHusky/9ed33a569b2a2a44e7bd11a672109666 to your computer and use it in GitHub Desktop.
GERTi Modem Emulation & Supporting Libs
--[[
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
--[[
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)
--[[
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