local serialization = require("serialization") local uuid = require("uuid") local computer = require("computer") local Version = {0, 1} local Message = { Hello = 0x48454c4f, Register = 0x52454749, Deregister = 0x44524547, NounRequest = 0x4e524551, NounResponse = 0x4e524553, VerbRequest = 0x56524551, VerbResponse = 0x56524553, ListenRequest = 0x4c524551, ListenNotify = 0x4c4e4f54, ListenCancel = 0x4c535450, } local query_param = { __call=function(self, v) return {ty=self.ty, v=v} end, } local Query = { Change = {ty=0x434847}, Rising = {ty=0x524953}, Falling = {ty=0x46414c}, Equals = setmetatable({ty=0x455155, invalid=true}, query_param), Above = setmetatable({ty=0x414256, invalid=true}, query_param), Below = setmetatable({ty=0x424c4f, invalid=true}, query_param), } -- Network ---------------------------------------------------------------- {{{ local Network = { default_port = 0xBC00 | (Version[1] << 4) | Version[2], } function Network:new(modem, port) if type(modem) == "number" then port = modem modem = nil end local self = { modem = modem or require("component").modem, port = port or Network.default_port, event = require("event"), } setmetatable(self, {__index=Network}) return self end setmetatable(Network, {__call=Network.new}) function Network:start(callback) self.modem.open(self.port) self.listener = function(_, addr_lo, addr_remote, port, _, msg) -- Filter everything we don't care about if addr_lo ~= self.modem.address then return end if port ~= self.port then return end callback(addr_remote, serialization.unserialize(msg)) end self.event.listen("modem_message", self.listener) end function Network:send(addr, msg) self.modem.send(addr, self.port, serialization.serialize(msg)) end function Network:broadcast(msg) self.modem.broadcast(self.port, serialization.serialize(msg)) end function Network:pull(filter, timeout) local last_msg local ev = self.event.pullFiltered(timeout, function(ev, addr_lo, addr_remote, port, _, msg) if ev ~= "modem_message" then return false end if addr_lo ~= self.modem.address then return false end if port ~= self.port then return false end last_msg = serialization.unserialize(msg) return filter(addr_remote, last_msg) end) if ev ~= nil then return last_msg else return nil end end function Network:stop() self.event.ignore("modem_message", self.listener) self.modem.close(self.port) end -- }}} -- BaseControl ------------------------------------------------------------ {{{ local BaseControl = { version = Version, Message = Message, Query = Query, Network = Network, } -- Lifecycle {{{ function BaseControl:new(network) local self = { local_nouns = {}, local_verbs = {}, remote_nouns = {}, remote_verbs = {}, listeners = {}, live = false, network = network or Network:new(), } setmetatable(self, {__index=BaseControl, __call=BaseControl.new}) self.network:start(function(remote, msg) self:_network_handler(remote, msg) end) self.network:broadcast{ty=Message.Hello} return self end function BaseControl:register(name, value) if self.live then error("can't register \""..name.."\" after finalizing") end if type(name) == "table" then -- Batch register for name, value in pairs(name) do self:register(name, value) end elseif type(value) == "function" then if self.local_verbs[name] ~= nil then error("\""..name.."\" already registered") else self.local_verbs[name] = value end else if self.local_nouns[name] ~= nil then error("\""..name.."\" already registered") else self.local_nouns[name] = value end end end function BaseControl:finalize(waits, timeout) if self == BaseControl then self = BaseControl:new() end -- Announce own nouns and verbs if this is the first -- call to finalize() and we have registered names if not self.live then local nouns, verbs = self:nouns(true), self:verbs(true) if #nouns > 0 or #verbs > 0 then self.network:broadcast{ ty=Message.Register, nouns=nouns, verbs=verbs, } end self.live = true end if #(waits or {}) ~= 0 then -- Wait for requested names local remaining = {} -- Table with all remaining names as keys local num_remaining = 0 -- Number of remaining names for _, name in ipairs(waits) do if not self:has_noun(name) and not self:has_verb(name) then remaining[name] = true num_remaining = num_remaining + 1 end end local tstart = computer.uptime() local timeout_remaining = timeout while num_remaining > 0 do if timeout ~= nil then timeout_remaining = timeout - (computer.uptime() - tstart) if timeout_remaining <= 0 then error("timeout") end end -- Wait until the next register arrives local msg = self.network:pull(function(remote, msg) return msg.ty == Message.Register end, timeout_remaining) if msg ~= nil then for _, noun in ipairs(msg.nouns or {}) do if remaining[noun] then num_remaining = num_remaining - 1 remaining[noun] = nil end end for _, verb in ipairs(msg.verbs or {}) do if remaining[verb] then num_remaining = num_remaining - 1 remaining[verb] = nil end end end end end return self end function BaseControl:close() if self.live == true then -- Deregister own nouns and verbs if we were live local nouns, verbs = self:nouns(true), self:verbs(true) if #nouns > 0 or #verbs > 0 then self.network:broadcast{ ty=Message.Deregister, nouns=nouns, verbs=verbs, } end end self.network:stop() setmetatable(self, nil) end -- }}} -- Nouns {{{ function BaseControl:has_noun(noun) return self.local_nouns[noun] ~= nil or self.remote_nouns[noun] ~= nil end function BaseControl:nouns(local_only) local nouns = {} for noun in pairs(self.local_nouns) do table.insert(nouns, noun) end if not local_only then for noun in pairs(self.remote_nouns) do table.insert(nouns, noun) end end return nouns end function BaseControl:get(noun, timeout) if self.local_nouns[noun] ~= nil then return self.local_nouns[noun] elseif self.local_verbs[noun] ~= nil then error("\""..noun.."\" is not a noun") elseif self.remote_nouns[noun] ~= nil then self.network:send(self.remote_nouns[noun], { ty=Message.NounRequest, noun=noun, }) local answer = self.network:pull(function(remote, msg) if remote ~= self.remote_nouns[noun] then return false end if msg.ty ~= Message.NounResponse then return false end if msg.noun ~= noun then return false end return true end, timeout) if answer ~= nil then return answer.value else error("timeout") end else return error("unknown noun \""..noun.."\"") end end function BaseControl:set(name, value) if self.local_nouns[name] ~= nil then if type(value) == "function" then error("\""..name.."\" can't be cast into a verb") else local old = self.local_nouns[name] self.local_nouns[name] = value for id, l in pairs(self.listeners[name] or {}) do if (l.query.ty == Query.Change.ty and value ~= old) or (l.query.ty == Query.Rising.ty and value > old) or (l.query.ty == Query.Falling.ty and value < old) or (l.query.ty == Query.Equals.ty and value == l.query.v) or (l.query.ty == Query.Above.ty and value > l.query.v) or (l.query.ty == Query.Below.ty and value < l.query.v) then if l.callback ~= nil then l.callback(value) else self.network:send(l.addr, { ty=Message.ListenNotify, noun=name, id=id, value=value, }) end end end end elseif self.local_verbs[name] ~= nil then if type(value) ~= "function" then error("\""..name.."\" can't be cast into a noun") else self.local_verbs[name] = value end else error("\""..name.."\" is not a local") end end -- }}} -- Verbs {{{ function BaseControl:has_verb(verb) return self.local_verbs[verb] ~= nil or self.remote_verbs[verb] ~= nil end function BaseControl:verbs(local_only) local verbs = {} for verb in pairs(self.local_verbs) do table.insert(verbs, verb) end if not local_only then for verb in pairs(self.remote_verbs) do table.insert(verbs, verb) end end return verbs end function BaseControl:call(verb, ...) if self.local_verbs[verb] ~= nil then self.local_verbs[verb](...) return true elseif self.remote_verbs[verb] ~= nil then self.network:send(self.remote_verbs[verb], { ty=Message.VerbRequest, verb=verb, param={...}, }) return true else return false end end function BaseControl:call_sync(timeout, verb, ...) local param = {...} if type(timeout) ~= "number" then table.insert(param, 1, verb) timeout, verb = nil, timeout end if self.local_verbs[verb] ~= nil then return self.local_verbs[verb](table.unpack(param)) elseif self.remote_verbs[verb] ~= nil then self.network:send(self.remote_verbs[verb], { ty=Message.VerbRequest, verb=verb, param=param, sync=true, }) local answer = self.network:pull(function(remote, msg) if remote ~= self.remote_verbs[verb] then return false end if msg.ty ~= Message.VerbResponse then return false end if msg.verb ~= verb then return false end return true end, timeout) if answer ~= nil then return table.unpack(answer.ret) else error("timeout") end else error("unknown verb \""..verb.."\"") end end -- }}} -- Listening {{{ function BaseControl:listen(noun, query, callback) if query.invalid then error("invalid query, forgot a parameter?") end if self.listeners[noun] == nil then self.listeners[noun] = {} end local id = uuid.next() self.listeners[noun][id] = { query=query, callback=callback, } if self.local_nouns[noun] ~= nil then return id, true elseif self.remote_nouns[noun] ~= nil then self.network:send(self.remote_nouns[noun], { ty=Message.ListenRequest, noun=noun, id=id, query=query, }) return id, true else return id, false end end function BaseControl:cancel(noun, id) if self.listeners[noun] == nil or self.listeners[noun][id] == nil then error("can't cancel unknown listener on \""..noun.."\"") end if self.local_nouns[noun] == nil and self.remote_nouns[noun] ~= nil then self.network:send(self.remote_nouns[noun], { ty=Message.ListenCancel, noun=noun, id=id, }) end self.listeners[noun][id] = nil end -- }}} -- Network Handler {{{ function BaseControl:_network_handler(remote, msg) if msg.ty == Message.Hello then if self.live then -- If we are live already, answer with own nouns and verbs self.network:send(remote, { ty=Message.Register, nouns=self:nouns(true), verbs=self:verbs(true), }) end elseif msg.ty == Message.Register then for _, noun in ipairs(msg.nouns or {}) do self.remote_nouns[noun] = remote for id, l in pairs(self.listeners[noun] or {}) do self.network:send(remote, { ty=Message.ListenRequest, noun=noun, id=id, query=l.query, }) end end for _, verb in ipairs(msg.verbs or {}) do self.remote_verbs[verb] = remote end elseif msg.ty == Message.Deregister then for _, noun in ipairs(msg.nouns or {}) do if self.remote_nouns[noun] == remote then self.remote_nouns[noun] = nil end end for _, verb in ipairs(msg.verbs or {}) do if self.remote_verbs[verb] == remote then self.remote_verbs[verb] = nil end end elseif msg.ty == Message.NounRequest then self.network:send(remote, { ty=Message.NounResponse, noun=msg.noun, value=self.local_nouns[msg.noun], }) elseif msg.ty == Message.NounResponse then -- Handled via pull elseif msg.ty == Message.VerbRequest then if self.local_verbs[msg.verb] ~= nil then local ret = table.pack(self.local_verbs[msg.verb](table.unpack(msg.param))) if msg.sync then self.network:send(remote, { ty=Message.VerbResponse, verb=msg.verb, ret=ret, }) end end elseif msg.ty == Message.VerbResponse then -- Handled via pull elseif msg.ty == Message.ListenRequest then if self.local_nouns[msg.noun] ~= nil then if self.listeners[msg.noun] == nil then self.listeners[msg.noun] = {} end self.listeners[msg.noun][msg.id] = { query=msg.query, addr=remote, } end elseif msg.ty == Message.ListenNotify then if self.listeners[msg.noun][msg.id] ~= nil then self.listeners[msg.noun][msg.id].callback(msg.value) end elseif msg.ty == Message.ListenCancel then if self.listeners[msg.noun][msg.id] ~= nil then if self.listeners[msg.noun][msg.id].addr == remote then self.listeners[msg.noun][msg.id] = nil end end end end -- }}} -- }}} return setmetatable(BaseControl, {__call=BaseControl.new})