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}})