diff --git a/_bc_backend.lua b/_bc_backend.lua index 95db0efdbbf3..1c5093bda0cf 100644 --- a/_bc_backend.lua +++ b/_bc_backend.lua @@ -1,10 +1,33 @@ -_bc_backend = { local_addr = "address_light", power = 12, iscb = false, cbid = 0, cbn = "" } +_bc_backend = { local_addr = "" } + +network = false +fn = require("fake_net") function _bc_backend:init(cb_handler) local o = {} setmetatable(o, self) self.__index = self o.cb_handler = cb_handler + o.network = network + o.local_addr = "address_light" + local function nw_handler(msg) + if msg.type == "noun_return" then + end + end + + -- Init network + if network == false then + network = fn:init() + network:add_node("address_light", nw_handler) + -- Add second node + o.dbg_node = require("bc"):init("master") + o.dbg_node._backend.local_addr = "address_power" + o.dbg_node:set_noun("power", 12) + else + network:add_node("address_power", nw_handler) + end + + return o end diff --git a/bc-old.lua b/bc-old.lua new file mode 100644 index 000000000000..4cc0f4df14c1 --- /dev/null +++ b/bc-old.lua @@ -0,0 +1,101 @@ +bc = {} + +_bc_backend = require("_bc_backend") + +function bc:init(master_id) + local o = {} + setmetatable(o, self) + self.__index = self + -- Init backend and cb handler + o._listen_callbacks = {} + o._verb_handlers = {} + o._listeners = {} + local function cb_handler(cb_type, param, arg) + if cb_type == "listen" then + if type(o._listen_callbacks[param]) == "function" then + o._listen_callbacks[param]() + end + elseif cb_type == "verb" then + o._verb_handlers[param](arg) + elseif cb_type == "noun" then + return o._mynouns[param] + elseif cb_type == "reg_nl" then + -- Create array if there is not one already + o._listeners[param] = o._listeners[param] or {} + -- Insert parameters + table.insert(o._listeners[param], arg) + return #o._listeners[param] + elseif cb_type == "unreg_nl" then + o._listeners[param][arg] = true; + else + error("Wrong cb type") + end + end + o._backend = _bc_backend:init(cb_handler) + -- Fetch list of nouns and verbs from master + local list = o._backend:fetch_list(master_id) + o._nouns = list.nouns + o._verbs = list.verbs + -- Initialize own nouns + o._mynouns = {} + for noun, addr in pairs(o._nouns) do + if addr == o._backend.local_addr then + o._mynouns[noun] = 0 + end + end + list = nil + return o +end + +function bc:get_noun(n) + if self._nouns[n] == self._backend.local_addr then + return self._mynouns[n] + else + return self._backend:request_noun(self._nouns[n], n) + end +end + +function bc:set_noun(n, val) + if self._nouns[n] == self._backend.local_addr then + -- Call listeners + if type(self._listeners[n]) == "table" then + for i, listener in ipairs(self._listeners[n]) do + -- Check condition + if type(listener) == "table" and + ( (listener.query == "onchange" and self._mynouns[n] ~= val) + or (listener.query == "onrising" and self._mynouns[n] < val) + or (listener.query == "onfalling" and self._mynouns[n] > val) + or (listener.query == "onabove" and val > listener.qparam) + or (listener.query == "onbelow" and val < listener.qparam) + or (listener.query == "onvalue" and val == listener.qparam) + ) then + self._backend:call_listener(listener.addr, n, i, val) + end + end + end + -- Set value + self._mynouns[n] = val + return true + end + return false +end + +function bc:on_verb(v, handler) + self._verb_handlers[v] = handler +end + +function bc:call_verb(v, param) + self._backend:call_verb(self._verbs[v], v, param) +end + +function bc:listen_noun(n, query, qparam, cb) + local id = self._backend:register_callback(self._nouns[n], n, query, qparam) + self._listen_callbacks[id] = cb + return id +end + +function bc:listen_cancel(n, id) + self._backend:cancel_callback(n, id) +end + +return bc diff --git a/bc.lua b/bc.lua index 4cc0f4df14c1..dc07f64a6629 100644 --- a/bc.lua +++ b/bc.lua @@ -1,101 +1,154 @@ -bc = {} +-- For testing +modem = require("modem") + +-- For deployment +-- modem = require("component").modem + +serializer = require("serialization") -_bc_backend = require("_bc_backend") +Queue = {first = 0, last = -1} -function bc:init(master_id) +function Queue:new() local o = {} setmetatable(o, self) self.__index = self - -- Init backend and cb handler - o._listen_callbacks = {} - o._verb_handlers = {} - o._listeners = {} - local function cb_handler(cb_type, param, arg) - if cb_type == "listen" then - if type(o._listen_callbacks[param]) == "function" then - o._listen_callbacks[param]() - end - elseif cb_type == "verb" then - o._verb_handlers[param](arg) - elseif cb_type == "noun" then - return o._mynouns[param] - elseif cb_type == "reg_nl" then - -- Create array if there is not one already - o._listeners[param] = o._listeners[param] or {} - -- Insert parameters - table.insert(o._listeners[param], arg) - return #o._listeners[param] - elseif cb_type == "unreg_nl" then - o._listeners[param][arg] = true; - else - error("Wrong cb type") - end - end - o._backend = _bc_backend:init(cb_handler) - -- Fetch list of nouns and verbs from master - local list = o._backend:fetch_list(master_id) - o._nouns = list.nouns - o._verbs = list.verbs - -- Initialize own nouns - o._mynouns = {} - for noun, addr in pairs(o._nouns) do - if addr == o._backend.local_addr then - o._mynouns[noun] = 0 - end - end - list = nil return o end -function bc:get_noun(n) - if self._nouns[n] == self._backend.local_addr then - return self._mynouns[n] - else - return self._backend:request_noun(self._nouns[n], n) +function Queue:insert(value) + local last = self.last + 1 + self.last = last + self[last] = value +end + +function Queue:isempty() + return self.first > self.last +end + +function Queue:remove() + local first = self.first + if self:isempty() then + error("Queue is empty") end + local value = self[first] + self[first] = nil + self.first = first + 1 + return value end -function bc:set_noun(n, val) - if self._nouns[n] == self._backend.local_addr then - -- Call listeners - if type(self._listeners[n]) == "table" then - for i, listener in ipairs(self._listeners[n]) do - -- Check condition - if type(listener) == "table" and - ( (listener.query == "onchange" and self._mynouns[n] ~= val) - or (listener.query == "onrising" and self._mynouns[n] < val) - or (listener.query == "onfalling" and self._mynouns[n] > val) - or (listener.query == "onabove" and val > listener.qparam) - or (listener.query == "onbelow" and val < listener.qparam) - or (listener.query == "onvalue" and val == listener.qparam) - ) then - self._backend:call_listener(listener.addr, n, i, val) +message_type = { + request_noun = 1, + call_verb = 2, + noun_response = 3, + hello = 4, + goodbye = 5, + hello_response = 6, +} + +CFG_PORT = 1234 + +bc = {} + +function bc:init(my_address, local_nouns, local_verbs) + local o = {} + setmetatable(o, self) + self.__index = self + o.queue = Queue:new() + o.remote_verbs = {} + o.remote_nouns = {} + -- Modem listener + function modem_listener(localAddress, remoteAddress, port, dist, message) + dbg = message + message = serializer.unserialize(message) + if port == CFG_PORT then + if message.ty == message_type.request_noun then + msg = {ty=message_type.noun_response, + noun=message.noun, + value=o.local_nouns[message.noun] + } + o.modem:send(remoteAddress, port, serializer.serialize(msg)) + elseif message.ty == message_type.noun_response then + -- TODO: Make this a bit better, this is dumb + o._nr = message.value + 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 + 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 + o.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 end end - -- Set value - self._mynouns[n] = val - return true end - return false -end - -function bc:on_verb(v, handler) - self._verb_handlers[v] = handler + -- Change this for deployment VVV + o.modem = modem:init(my_address, 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 + o.modem:broadcast(CFG_PORT, serializer.serialize({ty=message_type.hello, verbs=myverbs, nouns=mynouns})) + return o end -function bc:call_verb(v, param) - self._backend:call_verb(self._verbs[v], v, param) +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 + self.modem:send(self.remote_nouns[n], CFG_PORT, serializer.serialize({ty=message_type.request_noun, noun=n})) + while self._nr == nil do + -- Nothing, wait for response, TODO: Research performance impact + end + local value = self._nr + self._nr = nil -- Reset + return value + else + error("Noun not found, Node might be offline") + end end -function bc:listen_noun(n, query, qparam, cb) - local id = self._backend:register_callback(self._nouns[n], n, query, qparam) - self._listen_callbacks[id] = cb - return id +function bc:set_noun(n, value) + if self.local_nouns[n] ~= nil then + self.local_nouns[n] = value + else + error("Can't set remote nouns") + end end -function bc:listen_cancel(n, id) - self._backend:cancel_callback(n, id) +function bc:call_verb(v, param) + if self.local_verbs[v] ~= nil then + self.local_verbs[v](self, param) + elseif self.remote_verbs[v] ~= nil then + self.modem:send(self.remote_verbs[v], CFG_PORT, serializer.serialize({ty=message_type.call_verb, verb=v, param=param})) + else + error("Verb not found, Node might be offline") + end end return bc diff --git a/modem.lua b/modem.lua index 9c51136dd3c3..0a2073c2bcc6 100644 --- a/modem.lua +++ b/modem.lua @@ -20,4 +20,12 @@ function modem:send(address, port, msg) self.network[address](address, self.address, port, 0, msg) end +function modem:broadcast(port, msg) + for addr in pairs(self.network) do + if addr ~= self.address then + self:send(addr, port, msg) + end + end +end + return modem diff --git a/serialization.lua b/serialization.lua index 6864afffb33d..b649cce4349f 100644 --- a/serialization.lua +++ b/serialization.lua @@ -125,7 +125,7 @@ function serialization.serialize(value, pretty) end function serialization.unserialize(data) - checkArg(1, data, "string") + -- checkArg(1, data, "string") local result, reason = load("return " .. data, "=data", _, {math={huge=math.huge}}) if not result then return nil, reason diff --git a/test_bc.lua b/test_bc.lua index 72922405df1a..bba116e981e6 100644 --- a/test_bc.lua +++ b/test_bc.lua @@ -1,158 +1,68 @@ require "lunit" bc = require("bc") +ser = require("serialization") module("test_bc", package.seeall, lunit.testcase) function test_init() - bci = bc:init("master") + bci = bc:init("a1", {["light"]=false}, {["toggle_light"]=function() end}) end function test_set_get_noun() - bci = bc:init("master") - for i,val in ipairs({"string", true, 10}) do - assert_true(bci:set_noun("light", val), "Could not set noun") - assert_equal(val, bci:get_noun("light"), "Result does not match") - end + bci = bc:init("a1", {["light"]=true}, {["toggle_light"]=function() end}) + assert_equal(true, bci:get_noun("light"), "First get failed with wrong value") + bci:set_noun("light", false) + assert_equal(false, bci:get_noun("light"), "Second get failed with wrong value") end -function test_set_remote_noun_failure() - bci = bc:init("master") - assert_false(bci:set_noun("power"), "Something did not go to plan") -end - -function test_request_remote_noun() - bci = bc:init("master") - assert_equal(12, bci:get_noun("power"), "Wrong value returned") -end - -function test_call_verb() - bci = bc:init("master") - bci:call_verb("inc_pwr", 11) - assert_equal(11, bci:get_noun("power"), "Wrong value returned") -end - -function test_listen_onchange() - bci = bc:init("master") - i = false - bci:listen_noun("power", "onchange", nil, function() - i = true - end) - bci:call_verb("inc_pwr", 13) - assert_true(i, "Callback was not invoked") -end - -function test_listen_cancel() - bci = bc:init("master") - i = false - local id = bci:listen_noun("power", "onchange", nil, function() - i = true - end) - bci:listen_cancel("power", id) - bci:call_verb("inc_pwr", 13) - assert_false(i, "Callback was not canelled") -end - -function test_fromafar_request_noun() - bci = bc:init("master") - bci:set_noun("light", true) - assert_equal(true, bci._backend:debug_request_noun("light"), "Wrong value returned") -end - -function test_fromafar_call_verb() - bci = bc:init("master") - local i = false - bci:on_verb("toggle_light", function() - i = true - end) - bci._backend:debug_call_verb("toggle_light") - assert_true(i, "Verb was not called") -end - -function test_fromafar_register_cb_onchange() - bci = bc:init("master") - local i = false - bci._backend:debug_register_cb("light", "onchange", nil, function() - i = true - end) - bci:set_noun("light", true) - assert_true(i, "Callback was not invoked") -end - -function test_fromafar_register_cb_onrising() - bci = bc:init("master") - local i = false - bci:set_noun("light", 10) - bci._backend:debug_register_cb("light", "onrising", nil, function() - i = true - end) - bci:set_noun("light", 9) - assert_false(i, "Callback was wrongly invoked") - bci:set_noun("light", 10) - assert_true(i, "Callback was not invoked") -end - -function test_fromafar_register_cb_onfalling() - bci = bc:init("master") +function test_request_noun() + bci = bc:init("a1", {["light"]=true}, {["toggle_light"]=function() end}) local i = false - bci:set_noun("light", 10) - bci._backend:debug_register_cb("light", "onfalling", nil, function() - i = true + local expct = true + remote = require("modem"):init("some_addr", function(laddr, raddr, p, d, msg) + m = ser.unserialize(msg) + if m.ty == 3 and m.noun == "light" and m.value == expct then + i = true + end end) - bci:set_noun("light", 11) - assert_false(i, "Callback was wrongly invoked") - bci:set_noun("light", 10) - assert_true(i, "Callback was not invoked") -end - -function test_fromafar_register_cb_onvalue() - bci = bc:init("master") - local i = false - bci:set_noun("light", 10) - bci._backend:debug_register_cb("light", "onvalue", 11, function() - i = true - end) - bci:set_noun("light", 12) - assert_false(i, "Callback was wrongly invoked") - bci:set_noun("light", 11) - assert_true(i, "Callback was not invoked") -end - -function test_fromafar_register_cb_onabove() - bci = bc:init("master") - local i = false - bci:set_noun("light", 10) - bci._backend:debug_register_cb("light", "onabove", 20, function() - i = true - end) - bci:set_noun("light", 15) - assert_false(i, "Callback was wrongly invoked") - bci:set_noun("light", 26) - assert_true(i, "Callback was not invoked") -end - -function test_fromafar_register_cb_onbelow() - bci = bc:init("master") - local i = false - bci:set_noun("light", 10) - bci._backend:debug_register_cb("light", "onbelow", 5, function() - i = true - end) - bci:set_noun("light", 11) - assert_false(i, "Callback was wrongly invoked") - bci:set_noun("light", 3) - assert_true(i, "Callback was not invoked") + remote:send("a1", 1234, ser.serialize({ty=1, noun="light"})) + assert_true(i, "Noun response did not happen or was incorrect") + -- Reset and change noun + i = false + bci:set_noun("light", false) + expct = false + remote:send("a1", 1234, ser.serialize({ty=1, noun="light"})) + assert_true(i, "Noun response did not happen or was incorrect") end -function test_fromafar_cancel_cb() - bci = bc:init("master") - local i = false - bci:set_noun("light", 10) - bci._backend:debug_register_cb("light", "onchange", nil, function() - i = true - end) - bci._backend:debug_cancel_cb() - bci:set_noun("light", 3) - assert_false(i, "Callback was not cancelled") +function test_call_verb() + bci = bc:init("a1", {["light"]=true}, {["toggle_light"]=function(b) + b:set_noun("light", not b:get_noun("light")) + end}) + remote = require("modem"):init("some_addr", function(laddr, raddr, p, d, msg) end) + -- Call verb + remote:send("a1", 1234, ser.serialize({ty=2, verb="toggle_light"})) + assert_equal(false, bci:get_noun("light"), "Verb did not do its job") +end + +function test_multinode_call_verb() + bc1 = bc:init("a1", {["light"]=true}, {["toggle_light"]=function(b) + b:set_noun("light", not b:get_noun("light")) + end}) + bc2 = bc:init("a2", {}, {}) + bc1:call_verb("toggle_light") + assert_equal(false, bc1:get_noun("light"), "First verb invokation did not go to plan") + bc2:call_verb("toggle_light") + assert_equal(true, bc1:get_noun("light"), "Second verb invokation did not go to plan") +end + +function test_multinode_get_noun() + key = "foobar" + bc1 = bc:init("a1", {["light"]=key}, {["toggle_light"]=function(b) + b:set_noun("light", not b:get_noun("light")) + end}) + bc2 = bc:init("a2", {}, {}) + assert_equal(key, bc1:get_noun("light"), "Local get failed") + assert_equal(key, bc2:get_noun("light"), "Remote get failed") end -