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.

280 lines
9.2 KiB

local event = require("event")
local modem = require("component").modem
local serializer = require("serialization")
local List = {last = -1}
function List:new()
local o = {}
setmetatable(o, self)
self.__index = self
return o
end
function List:insert(value)
local last = self.last + 1
self.last = last
self[last] = value
return last
end
function List:iter()
local i = -1
local last = self.last
local list = self
return function()
while true do
i = i + 1
if i <= last then
if list[i] ~= nil then
return i, list[i]
end
else
return nil
end
end
end
end
function List:remove(id)
local value = self[id]
self[id] = nil
return value
end
local message_type = {
request_noun = 1,
call_verb = 2,
noun_response = 3,
hello = 4,
goodbye = 5,
hello_response = 6,
request_listening = 7,
request_stop_listening = 8,
listener_update = 9,
}
local CFG_PORT = 1234
local bc = {}
function bc:init(local_nouns, local_verbs)
local local_nouns = local_nouns or {}
local local_verbs = local_verbs or {}
local o = {}
setmetatable(o, self)
self.__index = self
o.remote_verbs = {}
o.remote_nouns = {}
o.local_listeners = {}
o.remote_listeners = {}
o.listening_remotes = {}
-- Modem listener
function modem_listener(moden_msg, localAddress, remoteAddress, port, dist, message)
local message = serializer.unserialize(message)
if port == CFG_PORT then
if message.ty == message_type.request_noun then
local msg = {ty=message_type.noun_response,
noun=message.noun,
value=o.local_nouns[message.noun]
}
modem.send(remoteAddress, port, serializer.serialize(msg))
elseif message.ty == message_type.noun_response then
-- Ignore, this is handled via pull
elseif message.ty == message_type.call_verb then
o.local_verbs[message.verb](o, message.param)
elseif message.ty == message_type.hello then
for i,verb in ipairs(message.verbs) do
o.remote_verbs[verb] = remoteAddress
end
for i,noun in ipairs(message.nouns) do
o.remote_nouns[noun] = remoteAddress
-- Check for potential dormant listeners
if o.remote_listeners[noun] ~= nil then
for id, listener in o.remote_listeners[noun]:iter() do
modem.send(remoteAddress, port, serializer.serialize({
ty=message_type.request_listening,
noun=noun,
query=listener.query,
qparam=listener.qparam,
id=id,
}))
end
end
end
-- Respond with own verbs and nouns
local mynouns = {}
for noun in pairs(o.local_nouns) do
table.insert(mynouns, noun)
end
local myverbs = {}
for verb in pairs(o.local_verbs) do
table.insert(myverbs, verb)
end
modem.send(remoteAddress, port, serializer.serialize({ty=message_type.hello_response, verbs=myverbs, nouns=mynouns}))
elseif message.ty == message_type.hello_response then
for i,verb in ipairs(message.verbs) do
o.remote_verbs[verb] = remoteAddress
end
for i,noun in ipairs(message.nouns) do
o.remote_nouns[noun] = remoteAddress
end
elseif message.ty == message_type.request_listening then
o.listening_remotes[message.noun] = o.listening_remotes[message.noun] or {}
o.listening_remotes[message.noun][remoteAddress] = o.listening_remotes[message.noun][remoteAddress] or {}
o.listening_remotes[message.noun][remoteAddress][message.id] = {query=message.query, qparam=message.qparam}
elseif message.ty == message_type.request_stop_listening then
o.listening_remotes[message.noun][remoteAddress][message.id] = nil
elseif message.ty == message_type.listener_update then
o.remote_listeners[message.noun][message.id].callback(o, message.value)
end
end
end
-- Setup connections
modem.open(CFG_PORT)
event.listen("modem_message", modem_listener)
-- Save listener function for deinit
o.modem_cb = modem_listener
-- Init local stuff
o.local_verbs = local_verbs
o.local_nouns = local_nouns
-- Send own nouns and verbs and request all remotes to send theirs
local mynouns = {}
for noun in pairs(local_nouns) do
table.insert(mynouns, noun)
end
local myverbs = {}
for verb in pairs(local_verbs) do
table.insert(myverbs, verb)
end
modem.broadcast(CFG_PORT, serializer.serialize({ty=message_type.hello, verbs=myverbs, nouns=mynouns}))
return o
end
function bc:get_noun(n)
if self.local_nouns[n] ~= nil then -- This noun is local, makes it easy
return self.local_nouns[n]
elseif self.remote_nouns[n] ~= nil then -- This noun is remote
modem.send(self.remote_nouns[n], CFG_PORT, serializer.serialize({ty=message_type.request_noun, noun=n}))
local i = 0
while i < 5 do
local _, _, remoteAddr, port, _, msg = event.pull("modem_message")
if port == CFG_PORT and remoteAddr == self.remote_nouns[n] then
local message = serializer.unserialize(msg)
if message.ty == message_type.noun_response and message.noun == n then
return message.value
end
end
i = i + 1
end
return nil
else
-- Noun not found
return nil
end
end
function bc:has_noun(n)
return self.local_nouns[n] ~= nil or self.remote_nouns[n] ~= nil
end
function bc:set_noun(n, value)
if self.local_nouns[n] ~= nil then
-- Call local listeners
if self.local_listeners[n] ~= nil then
for id, listener in self.local_listeners[n]:iter() do
if (listener.query == "onchange" and self.local_nouns[n] ~= value)
or (listener.query == "onrising" and self.local_nouns[n] < value)
or (listener.query == "onfalling" and self.local_nouns[n] > value)
or (listener.query == "onvalue" and value == listener.qparam)
or (listener.query == "onabove" and value > listener.qparam)
or (listener.query == "onbelow" and value < listener.qparam)
then
self.local_nouns[n] = value -- Apply here, too, because else there could be glitches
listener.callback(self, value)
end
end
end
-- Call remote listeners
if self.listening_remotes[n] ~= nil then
for address,remote in pairs(self.listening_remotes[n]) do
for id, listener in pairs(remote) do
if (listener.query == "onchange" and self.local_nouns[n] ~= value)
or (listener.query == "onrising" and self.local_nouns[n] < value)
or (listener.query == "onfalling" and self.local_nouns[n] > value)
or (listener.query == "onvalue" and value == listener.qparam)
or (listener.query == "onabove" and value > listener.qparam)
or (listener.query == "onbelow" and value < listener.qparam)
then
self.local_nouns[n] = value -- Apply here, too, because else there could be glitches
-- Send update
modem.send(address, CFG_PORT, serializer.serialize({ty=message_type.listener_update, noun=n, value=value, id=id}))
end
end
end
end
-- Apply change
self.local_nouns[n] = value
else
error("Can't set remote nouns")
end
end
function bc:call_verb(v, param)
if self.local_verbs[v] ~= nil then
self.local_verbs[v](self, param)
return true
elseif self.remote_verbs[v] ~= nil then
modem.send(self.remote_verbs[v], CFG_PORT, serializer.serialize({ty=message_type.call_verb, verb=v, param=param}))
return true
else
return false
end
end
function bc:has_verb(v)
return self.local_verbs[v] ~= nil or self.remote_verbs[v] ~= nil
end
function bc:listen_noun(n, query, qparam, callback)
if self.local_nouns[n] ~= nil then -- Local listening
self.local_listeners[n] = self.local_listeners[n] or List:new()
local id = self.local_listeners[n]:insert({query=query, qparam=qparam, callback=callback})
return id
elseif self.remote_nouns[n] ~= nil then -- Remote listening
self.remote_listeners[n] = self.remote_listeners[n] or List:new()
local id = self.remote_listeners[n]:insert({query=query, qparam=qparam, callback=callback})
-- Request remote listening
modem.send(self.remote_nouns[n], CFG_PORT, serializer.serialize({ty=message_type.request_listening, noun=n, query=query, qparam=qparam, id=id}))
return id
else
-- Install it, with hope, that the node will come online later
self.remote_listeners[n] = self.remote_listeners[n] or List:new()
local id = self.remote_listeners[n]:insert({query=query, qparam=qparam, callback=callback})
return id
end
end
function bc:listen_cancel(n, id)
if self.local_nouns[n] ~= nil then -- Local listening
if self.local_listeners[n] ~= nil then
self.local_listeners[n]:remove(id)
end
elseif self.remote_nouns[n] ~= nil then -- Remote listening
if self.remote_listeners[n] ~= nil then
self.remote_listeners[n]:remove(id)
modem.send(self.remote_nouns[n], CFG_PORT, serializer.serialize({ty=message_type.request_stop_listening, noun=n, id=id}))
end
else
error("Trying to cancel non existing listener")
end
end
-- Deinit
function bc:cleanup()
event.ignore("modem_message", self.modem_cb)
end
return bc