You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

262 lines
7.1 KiB

local component = require("component")
local event = require("event")
local serializer = require("serialization")
local uuid = require("uuid")
local BC_VERSION = {0, 1}
local BC_PORT = 0xBC00 | (BC_VERSION[1] << 4) | BC_VERSION[2]
local Message = {
Hello = 0x48454c4f,
Register = 0x00524547,
Disband = 0x44524547,
NounRequest = 0x4e524551,
NounResponse = 0x4e524553,
VerbRequest = 0x56524551,
ListenRequest = 0x4c524551,
ListenNotify = 0x4c4e4f54,
ListenCancel = 0x4c535450,
}
local Event = {
Change = 1,
Rising = 2,
Falling = 3,
Equals = 4,
Above = 5,
Below = 6,
}
local bc = {
_version = BC_VERSION,
_default_port = BC_PORT,
Event = Event,
Message = Message,
}
bc.__index = bc
-- Helpers
local function send_msg(self, remote, msg_table)
self.modem.send(remote, self.port, serializer.serialize(msg_table))
end
local function broadcast_msg(self, msg_table)
self.modem.broadcast(self.port, serializer.serialize(msg_table))
end
function bc:has_noun(noun)
return self.local_nouns[noun] ~= nil or self.remote_nouns[noun] ~= nil
end
function bc:set_noun(noun, value)
local last_value = self.local_nouns[noun]
if last_value ~= nil then
self.local_nouns[noun] = value
for id, par in pairs(self.remote_listeners[noun]) do
if (par.event == Event.Change and value ~= last_value)
or (par.event == Event.Rising and value > last_value)
or (par.event == Event.Falling and value < last_value)
or (par.event == Event.Equals and value == par.evparam)
or (par.event == Event.Above and value > par.evparam)
or (par.event == Event.Below and value < par.evparam)
then
send_msg(self, par.addr, {ty=BC_MESSAGE.LISTEN_NOTIFY, id=id, value=value})
end
end
else
error("Noun \""..noun.."\" does not exist or is non-local!")
end
end
function bc:get_noun(noun)
if self.local_nouns[noun] ~= nil then
return self.local_nouns[noun]
elseif self.remote_nouns[noun] ~= nil then
send_msg(self, self.remote_nouns[noun], {
ty = BC_MESSAGE.NOUN_REQUEST,
noun = noun,
})
local value
event.pullFiltered(self.timeout, function(ev, ...)
if ev ~= "modem_message" then return false end
if select(2, ...) ~= self.remote_nouns[noun] then return false end
if select(3, ...) ~= self.port then return false end
local msg = serializer.unserialize(select(5, ...))
if msg.ty ~= BC_MESSAGE.NOUN_RESPONSE then return false end
if msg.noun ~= noun then return false end
value = msg.value
return true
end)
return value
else -- Not found at all
return nil
end
end
function bc:listen(noun, event, evparam, callback)
-- You can leave out evparam
if type(evparam) == "function" then
callback = evparam
evparam = nil
end
local remote_addr = self.remote_nouns[noun]
if remote_addr == nil then
error("Noun \""..noun.."\" is not listenable!")
else
local id = uuid.next()
self.local_listeners[id] = {addr=remote_addr, callback=callback}
send_msg(self, remote_addr, {
ty=BC_MESSAGE.LISTEN_REQUEST,
noun=noun,
event=event,
evparam=evparam,
id=id,
})
return id
end
end
function bc:cancel(id)
local l = self.local_listeners[id]
send_msg(self, l.addr, {ty=BC_MESSAGE.LISTEN_CANCEL, noun=l.noun, id=id})
self.local_listeners[id] = nil
end
function bc:has_verb(verb)
return self.local_verbs[verb] ~= nil or self.remote_verbs[verb] ~= nil
end
function bc:call_verb(verb, ...)
if self.local_verbs[verb] ~= nil then
self.local_verbs[verb](self, nil, ...)
elseif self.remote_verbs[verb] ~= nil then
send_msg(self, self.remote_verbs[verb], {
ty=BC_MESSAGE.VERB_REQUEST, verb=verb, param={...},
})
else
error("Verb \""..verb.."\" does not exist!")
end
end
function bc:cleanup()
event.ignore("modem_message", self._modem_listener)
local nouns, verbs = self:locals()
broadcast_msg(self, {
ty=BC_MESSAGE.DISBAND,
nouns=nouns,
verbs=verbs,
})
end
function bc:locals()
local nouns, verbs = {}, {}
for noun in pairs(self.local_nouns) do table.insert(nouns, noun) end
for verb in pairs(self.local_verbs) do table.insert(verbs, verb) end
return nouns, verbs
end
local function new(_, local_nouns, local_verbs, overrides)
local self = {
local_nouns=local_nouns or {},
local_verbs=local_verbs or {},
remote_nouns={},
remote_verbs={},
local_listeners={},
remote_listeners={},
port=BC_PORT,
modem=component.modem,
timeout=5,
}
if overrides ~= nil then
for name, value in pairs(overrides) do
self[name] = value
end
end
setmetatable(self, bc)
for noun in pairs(self.local_nouns) do
self.remote_listeners[noun] = {}
end
self._modem_listener = function(_, _, remote_addr, port, _, msg)
if port ~= self.port then -- Ignore other ports
return
end
local msg = serializer.unserialize(msg)
if msg.ty == BC_MESSAGE.HELLO then
local nouns, verbs = self:locals()
send_msg(self, remote_addr, {
ty=BC_MESSAGE.REGISTER,
nouns=nouns,
verbs=verbs,
})
elseif msg.ty == BC_MESSAGE.REGISTER then
for _, noun in ipairs(msg.nouns or {}) do
self.remote_nouns[noun] = remote_addr
end
for _, verb in ipairs(msg.verbs or {}) do
self.remote_verbs[verb] = remote_addr
end
elseif msg.ty == BC_MESSAGE.NOUN_REQUEST then
send_msg(self, remote_addr, {
ty=BC_MESSAGE.NOUN_RESPONSE,
noun=msg.noun,
value=self.local_nouns[msg.noun],
})
elseif msg.ty == BC_MESSAGE.VERB_REQUEST then
local callback = self.local_verbs[msg.verb]
if callback ~= nil then
callback(self, remote_addr, table.unpack(msg.param))
end
elseif msg.ty == BC_MESSAGE.LISTEN_REQUEST then
if self.local_nouns[msg.noun] ~= nil then
self.remote_listeners[msg.noun][msg.id] = {
event=msg.event,
evparam=msg.evparam,
addr=remote_addr,
}
end
elseif msg.ty == BC_MESSAGE.LISTEN_NOTIFY then
local listener = self.local_listeners[msg.id]
if listener ~= nil and listener.addr == remote_addr then
listener.callback(msg.value)
end
elseif msg.ty == BC_MESSAGE.LISTEN_CANCEL then
if self.remote_listeners[msg.noun] ~= nil then
self.remote_listeners[msg.noun][msg.id] = nil
end
elseif msg.ty == BC_MESSAGE.DISBAND then
for _, noun in ipairs(msg.nouns or {}) do
if self.remote_nouns[noun] == remote_addr then
self.remote_nouns[noun] = nil
end
end
for _, verb in ipairs(msg.verbs or {}) do
if self.remote_verbs[verb] == remote_addr then
self.remote_verbs[verb] = nil
end
end
end
end
-- Setup connection and say hello
self.modem.open(self.port)
event.listen("modem_message", self._modem_listener)
broadcast_msg(self, {ty=BC_MESSAGE.HELLO})
local nouns, verbs = self:locals()
broadcast_msg(self, {
ty=BC_MESSAGE.REGISTER,
nouns=nouns,
verbs=verbs,
})
return self
end
return setmetatable(bc, {__call=new, __index={new=new}})