parent
126d420e3c
commit
b205779a29
@ -0,0 +1,334 @@
|
|||||||
|
require("lunit")
|
||||||
|
|
||||||
|
local network = require("network")
|
||||||
|
local serialization = require("serialization")
|
||||||
|
local BaseControl = require("bc")
|
||||||
|
local Event = BaseControl.Event
|
||||||
|
|
||||||
|
module("bc-tests", package.seeall, lunit.testcase)
|
||||||
|
|
||||||
|
-- Basics {{{
|
||||||
|
function test_init()
|
||||||
|
local bc = BaseControl({test_noun=true}, {test_verb=function() end})
|
||||||
|
assert_true(bc:has_noun("test_noun"), "Local nouns were not initialized")
|
||||||
|
assert_true(bc:has_verb("test_verb"), "Local verbs were not initialized")
|
||||||
|
end
|
||||||
|
|
||||||
|
function test_local_noun()
|
||||||
|
local bc = BaseControl({test_noun2=1234})
|
||||||
|
assert_equal(1234, bc:get_noun("test_noun2"), "Invalid initial value")
|
||||||
|
bc:set_noun("test_noun2", 4321)
|
||||||
|
assert_equal(4321, bc:get_noun("test_noun2"), "Invalid new value")
|
||||||
|
end
|
||||||
|
|
||||||
|
function test_local_verb()
|
||||||
|
local bc = BaseControl(nil, {
|
||||||
|
test_verb2 = function(_, _, a, b)
|
||||||
|
assert_equal(12, a, "Invalid parameter A")
|
||||||
|
assert_equal(34, b, "Invalid parameter B")
|
||||||
|
end,
|
||||||
|
})
|
||||||
|
|
||||||
|
bc:call_verb("test_verb2", 12, 34)
|
||||||
|
end
|
||||||
|
|
||||||
|
function test_unknown()
|
||||||
|
local bc = BaseControl()
|
||||||
|
|
||||||
|
assert_false(bc:has_noun("unknown"), "has_noun failed")
|
||||||
|
assert_equal(nil, bc:get_noun("unknown"), "get_noun failed")
|
||||||
|
assert_error("Unknown noun writable", function()
|
||||||
|
bc:set_noun("unknown", 1234)
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
-- }}}
|
||||||
|
|
||||||
|
-- Networking {{{
|
||||||
|
function test_hello()
|
||||||
|
local bc1 = BaseControl({hello1=true}, {hello1v=function() end})
|
||||||
|
local bc2 = BaseControl({hello2=true}, {hello2v=function() end})
|
||||||
|
|
||||||
|
assert_true(bc1:has_noun("hello1"), "Announcement did not come through")
|
||||||
|
assert_true(bc2:has_noun("hello1"), "Announcement did not come through")
|
||||||
|
assert_true(bc1:has_noun("hello2"), "Announcement did not come through")
|
||||||
|
assert_true(bc2:has_noun("hello2"), "Announcement did not come through")
|
||||||
|
|
||||||
|
assert_true(bc1:has_verb("hello1v"), "Anverbcement did not come through")
|
||||||
|
assert_true(bc2:has_verb("hello1v"), "Anverbcement did not come through")
|
||||||
|
assert_true(bc1:has_verb("hello2v"), "Anverbcement did not come through")
|
||||||
|
assert_true(bc2:has_verb("hello2v"), "Anverbcement did not come through")
|
||||||
|
end
|
||||||
|
|
||||||
|
function test_remote_noun()
|
||||||
|
local a = {n = "A", bc = BaseControl({noun1=1234}), addr = network.get_scene()}
|
||||||
|
local b = {n = "B", bc = BaseControl({noun2=4321}), addr = network.get_scene()}
|
||||||
|
local c = {n = "C", bc = BaseControl(), addr = network.get_scene()}
|
||||||
|
local abc = {a, b, c, a, c}
|
||||||
|
|
||||||
|
for _, bc in ipairs(abc) do
|
||||||
|
network.set_scene(bc.addr)
|
||||||
|
assert_equal(1234, bc.bc:get_noun("noun1"), bc.n..": noun1 failed!")
|
||||||
|
assert_equal(4321, bc.bc:get_noun("noun2"), bc.n..": noun2 failed!")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function test_noun_nv()
|
||||||
|
local bc1 = BaseControl({noun_nv=1234})
|
||||||
|
local addr1 = network.get_scene()
|
||||||
|
local bc2 = BaseControl()
|
||||||
|
local addr2 = network.get_scene()
|
||||||
|
|
||||||
|
assert_equal(1234, bc2:get_noun("noun_nv"), "Wrong answer!")
|
||||||
|
network.set_scene(addr1)
|
||||||
|
bc1:set_noun("noun_nv", 4321)
|
||||||
|
network.set_scene(addr2)
|
||||||
|
assert_equal(4321, bc2:get_noun("noun_nv"), "Wrong answer!")
|
||||||
|
end
|
||||||
|
|
||||||
|
function test_remote_verb()
|
||||||
|
local tmp_a = 1
|
||||||
|
local tmp_b = 64
|
||||||
|
local a = {n = "A", bc = BaseControl(), addr = network.get_scene()}
|
||||||
|
local b = {n = "B", bc = BaseControl(nil, {verb1 = function(_, _, a, b)
|
||||||
|
assert_equal(tmp_a, a, "A is wrong!")
|
||||||
|
assert_equal(tmp_b, b, "B is wrong!")
|
||||||
|
end}), addr = network.get_scene()}
|
||||||
|
local c = {n = "C", bc = BaseControl(), addr = network.get_scene()}
|
||||||
|
local abc = {b, a, b, c, a, c}
|
||||||
|
|
||||||
|
for _, bc in ipairs(abc) do
|
||||||
|
network.set_scene(bc.addr)
|
||||||
|
bc.bc:call_verb("verb1", tmp_a, tmp_b)
|
||||||
|
tmp_a = tmp_a + 1
|
||||||
|
tmp_b = tmp_b + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function test_two_way_verb()
|
||||||
|
local bc1 = BaseControl(nil, {twoway1 = function(bc, _, verb, par)
|
||||||
|
bc:call_verb(verb, par * 2)
|
||||||
|
end})
|
||||||
|
local addr1 = network.get_scene()
|
||||||
|
|
||||||
|
local result
|
||||||
|
local bc2 = BaseControl(nil, {twoway2 = function(bc, _, res)
|
||||||
|
result = res
|
||||||
|
end})
|
||||||
|
local addr2 = network.get_scene()
|
||||||
|
|
||||||
|
bc2:call_verb("twoway1", "twoway2", 1234)
|
||||||
|
assert_equal(2468, result, "Double call failed")
|
||||||
|
end
|
||||||
|
-- }}}
|
||||||
|
|
||||||
|
-- Listening {{{
|
||||||
|
function test_listen_attach()
|
||||||
|
local bc1 = BaseControl({listen1=4321})
|
||||||
|
local addr1 = network.get_scene()
|
||||||
|
local bc2 = BaseControl()
|
||||||
|
|
||||||
|
local tmp
|
||||||
|
local tmp2
|
||||||
|
|
||||||
|
local id = bc2:listen("listen1", Event.Change, function(newval)
|
||||||
|
if tmp == nil then
|
||||||
|
fail("Should not have been called")
|
||||||
|
end
|
||||||
|
assert_equal(tmp, newval, "Change not correct")
|
||||||
|
tmp2 = newval
|
||||||
|
end)
|
||||||
|
|
||||||
|
network.set_scene(addr1)
|
||||||
|
for _, t in ipairs{1, 2, 3, 4} do
|
||||||
|
tmp = t
|
||||||
|
bc1:set_noun("listen1", t)
|
||||||
|
assert_equal(t, tmp2, "Listener not called")
|
||||||
|
end
|
||||||
|
|
||||||
|
bc2:cancel(id)
|
||||||
|
|
||||||
|
bc1:set_noun("listen1", 1234)
|
||||||
|
end
|
||||||
|
|
||||||
|
function test_listen_modes()
|
||||||
|
local bc1 = BaseControl({listen2=4321})
|
||||||
|
local addr1 = network.get_scene()
|
||||||
|
local bc2 = BaseControl()
|
||||||
|
local addr2 = network.get_scene()
|
||||||
|
|
||||||
|
local values = {
|
||||||
|
{e=Event.Change, p=nil, s=1234, snot=1234, s2=1235},
|
||||||
|
{e=Event.Rising, p=nil, s=1236, snot=1233, s2=1237},
|
||||||
|
{e=Event.Falling, p=nil, s=1236, snot=1237, s2=1235},
|
||||||
|
{e=Event.Equals, p=1337, s=1337, snot=1234, s2=1337},
|
||||||
|
{e=Event.Above, p=4321, s=4333, snot=1234, s2=4334},
|
||||||
|
{e=Event.Below, p=4321, s=1234, snot=4444, s2=1212},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test in ipairs(values) do
|
||||||
|
local active = false
|
||||||
|
local fired = false
|
||||||
|
network.set_scene(addr2)
|
||||||
|
local id = bc2:listen("listen2", test.e, test.p, function(new)
|
||||||
|
if not active then
|
||||||
|
fail("Should not have been called")
|
||||||
|
end
|
||||||
|
assert_equal(test.s, new, "Wrong value!")
|
||||||
|
fired = true
|
||||||
|
end)
|
||||||
|
|
||||||
|
network.set_scene(addr1)
|
||||||
|
active = true
|
||||||
|
bc1:set_noun("listen2", test.s)
|
||||||
|
assert_true(fired, "Listener was not called")
|
||||||
|
|
||||||
|
fired = false
|
||||||
|
bc1:set_noun("listen2", test.snot)
|
||||||
|
assert_false(fired, "Listener was accidentally called")
|
||||||
|
|
||||||
|
network.set_scene(addr2)
|
||||||
|
active = false
|
||||||
|
bc2:cancel(id)
|
||||||
|
|
||||||
|
network.set_scene(addr1)
|
||||||
|
bc1:set_noun("listen2", test.s2)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
-- }}}
|
||||||
|
|
||||||
|
-- Dirty Business {{{
|
||||||
|
function test_connectivity_loss_listening()
|
||||||
|
local bc1 = BaseControl({connectivity2=12345})
|
||||||
|
local addr1 = network.get_scene()
|
||||||
|
local bc2 = BaseControl()
|
||||||
|
local addr2 = network.get_scene()
|
||||||
|
|
||||||
|
local called = 0
|
||||||
|
local id = bc2:listen("connectivity2", Event.Change, nil, function()
|
||||||
|
called = called + 1
|
||||||
|
end)
|
||||||
|
|
||||||
|
network.set_scene(addr1)
|
||||||
|
bc1:set_noun("connectivity2", 321)
|
||||||
|
assert_equal(1, called, "Listener was not called!")
|
||||||
|
|
||||||
|
network.deregister(addr2)
|
||||||
|
|
||||||
|
network.allow_blackhole = true
|
||||||
|
do
|
||||||
|
bc1:set_noun("connectivity2", 123)
|
||||||
|
assert_equal(1, called, "Listener was called?!")
|
||||||
|
|
||||||
|
local bc2 = BaseControl()
|
||||||
|
local addr2 = network.get_scene()
|
||||||
|
|
||||||
|
local called2 = false
|
||||||
|
local id2 = bc2:listen("connectivity2", Event.Change, nil, function()
|
||||||
|
called2 = true
|
||||||
|
end)
|
||||||
|
|
||||||
|
network.set_scene(addr1)
|
||||||
|
bc1:set_noun("connectivity2", 321)
|
||||||
|
assert_true(called2, "Listener was not called!")
|
||||||
|
assert_equal(1, called, "Original listener was called?!")
|
||||||
|
end
|
||||||
|
network.allow_blackhole = false
|
||||||
|
end
|
||||||
|
|
||||||
|
function test_cleanup()
|
||||||
|
local bc1 = BaseControl({cleanup1=1234})
|
||||||
|
local addr1 = network.get_scene()
|
||||||
|
local bc2 = BaseControl()
|
||||||
|
local addr2 = network.get_scene()
|
||||||
|
|
||||||
|
assert_true(bc2:has_noun("cleanup1"), "Setup failed")
|
||||||
|
assert_equal(1234, bc2:get_noun("cleanup1"), "Setup failed")
|
||||||
|
|
||||||
|
network.set_scene(addr1)
|
||||||
|
bc1:cleanup()
|
||||||
|
|
||||||
|
network.set_scene(addr2)
|
||||||
|
assert_equal(nil, bc2:get_noun("cleanup1"), "Cleanup failed")
|
||||||
|
assert_false(bc2:has_noun("cleanup1"), "Cleanup failed")
|
||||||
|
end
|
||||||
|
|
||||||
|
function test_connectivity_loss()
|
||||||
|
local bc1 = BaseControl({connectivity=4433})
|
||||||
|
local addr1 = network.get_scene()
|
||||||
|
local bc2 = BaseControl()
|
||||||
|
local addr2 = network.get_scene()
|
||||||
|
|
||||||
|
assert_true(bc2:has_noun("connectivity"), "Setup failed")
|
||||||
|
assert_equal(4433, bc2:get_noun("connectivity"), "Setup failed")
|
||||||
|
|
||||||
|
network.deregister(addr1)
|
||||||
|
|
||||||
|
network.allow_blackhole = true
|
||||||
|
assert_true(bc2:has_noun("connectivity"), "Test error")
|
||||||
|
assert_equal(nil, bc2:get_noun("connectivity"), "Invalid response from offline node")
|
||||||
|
network.allow_blackhole = false
|
||||||
|
|
||||||
|
-- Come back online
|
||||||
|
local bc1 = BaseControl({connectivity=4432})
|
||||||
|
network.set_scene(addr2)
|
||||||
|
assert_true(bc2:has_noun("connectivity"), "Reconnect failed")
|
||||||
|
assert_equal(4432, bc2:get_noun("connectivity"), "Reconnect failed")
|
||||||
|
end
|
||||||
|
|
||||||
|
function test_isolation()
|
||||||
|
local bc_outside = BaseControl({isolation1=4321})
|
||||||
|
local bc1 = BaseControl({isolation2=1234}, nil, {
|
||||||
|
port=1234,
|
||||||
|
})
|
||||||
|
local bc2 = BaseControl(nil, nil, {
|
||||||
|
port=1234,
|
||||||
|
})
|
||||||
|
|
||||||
|
assert_equal(nil, bc2:get_noun("isolation1"), "Leak!")
|
||||||
|
assert_equal(1234, bc2:get_noun("isolation2"), "Port failure")
|
||||||
|
end
|
||||||
|
|
||||||
|
function test_malicious_messages()
|
||||||
|
local bc1 = BaseControl({malicious=1234})
|
||||||
|
local addr1 = network.get_scene()
|
||||||
|
local bc2 = BaseControl({malicious2=4321})
|
||||||
|
local addr2 = network.get_scene()
|
||||||
|
|
||||||
|
network.send(addr1, bc1.port, serialization.serialize{
|
||||||
|
ty=7, -- LISTEN_REQUEST
|
||||||
|
noun="unknown", event=Event.Change, id="dead-beef",
|
||||||
|
})
|
||||||
|
network.send(addr1, bc1.port, serialization.serialize{
|
||||||
|
ty=8, -- LISTEN_NOTIFY
|
||||||
|
id="dead-beef", value=1234567,
|
||||||
|
})
|
||||||
|
network.send(addr1, bc1.port, serialization.serialize{
|
||||||
|
ty=9, -- LISTEN_CANCEL
|
||||||
|
noun="unknown", id="dead-beef",
|
||||||
|
})
|
||||||
|
|
||||||
|
assert_equal(1234, bc2:get_noun("malicious"))
|
||||||
|
end
|
||||||
|
|
||||||
|
function test_malicious_broadcast()
|
||||||
|
local bc1 = BaseControl({malicious3=1234})
|
||||||
|
local addr1 = network.get_scene()
|
||||||
|
local bc2 = BaseControl()
|
||||||
|
local addr2 = network.get_scene()
|
||||||
|
local bc3 = BaseControl()
|
||||||
|
local addr3 = network.get_scene()
|
||||||
|
|
||||||
|
network.broadcast(bc1.port, serialization.serialize{
|
||||||
|
ty=2, -- ANNOUNCE
|
||||||
|
nouns={"malicious3"}, verbs={},
|
||||||
|
})
|
||||||
|
|
||||||
|
-- bc1 can't be fooled, as it owns this noun
|
||||||
|
network.set_scene(addr1)
|
||||||
|
assert_equal(1234, bc1:get_noun("malicious3"), "Injection successful")
|
||||||
|
|
||||||
|
-- bc2 unfortunately can be fooled and must be
|
||||||
|
network.set_scene(addr2)
|
||||||
|
assert_equal(nil, bc2:get_noun("malicious3"), "Injection failed")
|
||||||
|
end
|
||||||
|
-- }}}
|
||||||
@ -1,323 +1,261 @@
|
|||||||
|
local component = require("component")
|
||||||
local event = require("event")
|
local event = require("event")
|
||||||
local modem = require("component").modem
|
|
||||||
local serializer = require("serialization")
|
local serializer = require("serialization")
|
||||||
|
local uuid = require("uuid")
|
||||||
|
|
||||||
local List = {last = -1}
|
local BC_VERSION = {0, 1}
|
||||||
|
local BC_PORT = 0xBC00 | (BC_VERSION[1] << 4) | BC_VERSION[2]
|
||||||
|
|
||||||
function List:new()
|
local Message = {
|
||||||
local o = {}
|
Hello = 0x48454c4f,
|
||||||
setmetatable(o, self)
|
Register = 0x00524547,
|
||||||
self.__index = self
|
Disband = 0x44524547,
|
||||||
return o
|
NounRequest = 0x4e524551,
|
||||||
end
|
NounResponse = 0x4e524553,
|
||||||
|
VerbRequest = 0x56524551,
|
||||||
function List:insert(value)
|
ListenRequest = 0x4c524551,
|
||||||
local last = self.last + 1
|
ListenNotify = 0x4c4e4f54,
|
||||||
self.last = last
|
ListenCancel = 0x4c535450,
|
||||||
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 Event = {
|
||||||
|
Change = 1,
|
||||||
local bc = {}
|
Rising = 2,
|
||||||
|
Falling = 3,
|
||||||
function bc:init(local_nouns, local_verbs)
|
Equals = 4,
|
||||||
local o = {
|
Above = 5,
|
||||||
local_nouns = local_nouns or {},
|
Below = 6,
|
||||||
local_verbs = local_verbs or {},
|
}
|
||||||
|
|
||||||
remote_verbs = {},
|
local bc = {
|
||||||
remote_nouns = {},
|
_version = BC_VERSION,
|
||||||
|
_default_port = BC_PORT,
|
||||||
|
Event = Event,
|
||||||
|
Message = Message,
|
||||||
|
}
|
||||||
|
bc.__index = bc
|
||||||
|
|
||||||
local_listeners = {},
|
-- Helpers
|
||||||
remote_listeners = {},
|
local function send_msg(self, remote, msg_table)
|
||||||
listening_remotes = {},
|
self.modem.send(remote, self.port, serializer.serialize(msg_table))
|
||||||
}
|
end
|
||||||
setmetatable(o, self)
|
|
||||||
self.__index = self
|
|
||||||
|
|
||||||
-- Modem listener
|
local function broadcast_msg(self, msg_table)
|
||||||
function modem_listener(moden_msg, localAddress, remoteAddress, port, dist, message)
|
self.modem.broadcast(self.port, serializer.serialize(msg_table))
|
||||||
local message = serializer.unserialize(message)
|
end
|
||||||
if port == CFG_PORT then
|
|
||||||
if message.ty == message_type.request_noun then
|
|
||||||
modem.send(remoteAddress, port, serializer.serialize({
|
|
||||||
ty=message_type.noun_response,
|
|
||||||
noun=message.noun,
|
|
||||||
value=o.local_nouns[message.noun],
|
|
||||||
}))
|
|
||||||
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
|
function bc:has_noun(noun)
|
||||||
o.remote_nouns[noun] = remoteAddress
|
return self.local_nouns[noun] ~= nil or self.remote_nouns[noun] ~= nil
|
||||||
-- Check for potential dormant listeners
|
end
|
||||||
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({
|
function bc:set_noun(noun, value)
|
||||||
ty=message_type.hello_response,
|
local last_value = self.local_nouns[noun]
|
||||||
verbs=myverbs,
|
if last_value ~= nil then
|
||||||
nouns=mynouns,
|
self.local_nouns[noun] = value
|
||||||
}))
|
for id, par in pairs(self.remote_listeners[noun]) do
|
||||||
elseif message.ty == message_type.hello_response then
|
if (par.event == Event.Change and value ~= last_value)
|
||||||
for i,verb in ipairs(message.verbs) do
|
or (par.event == Event.Rising and value > last_value)
|
||||||
o.remote_verbs[verb] = remoteAddress
|
or (par.event == Event.Falling and value < last_value)
|
||||||
end
|
or (par.event == Event.Equals and value == par.evparam)
|
||||||
for i,noun in ipairs(message.nouns) do
|
or (par.event == Event.Above and value > par.evparam)
|
||||||
o.remote_nouns[noun] = remoteAddress
|
or (par.event == Event.Below and value < par.evparam)
|
||||||
end
|
then
|
||||||
elseif message.ty == message_type.request_listening then
|
send_msg(self, par.addr, {ty=BC_MESSAGE.LISTEN_NOTIFY, id=id, value=value})
|
||||||
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
|
end
|
||||||
|
else
|
||||||
|
error("Noun \""..noun.."\" does not exist or is non-local!")
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
-- Setup connections
|
function bc:get_noun(noun)
|
||||||
modem.open(CFG_PORT)
|
if self.local_nouns[noun] ~= nil then
|
||||||
event.listen("modem_message", modem_listener)
|
return self.local_nouns[noun]
|
||||||
|
elseif self.remote_nouns[noun] ~= nil then
|
||||||
-- Save listener function for deinit
|
send_msg(self, self.remote_nouns[noun], {
|
||||||
o.modem_cb = modem_listener
|
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
|
||||||
|
|
||||||
-- Send own nouns and verbs and request all remotes to send theirs
|
value = msg.value
|
||||||
local mynouns = {}
|
return true
|
||||||
for noun in pairs(o.local_nouns) do
|
end)
|
||||||
table.insert(mynouns, noun)
|
return value
|
||||||
end
|
else -- Not found at all
|
||||||
local myverbs = {}
|
return nil
|
||||||
for verb in pairs(o.local_verbs) do
|
|
||||||
table.insert(myverbs, verb)
|
|
||||||
end
|
end
|
||||||
modem.broadcast(CFG_PORT, serializer.serialize({
|
|
||||||
ty=message_type.hello,
|
|
||||||
verbs=myverbs,
|
|
||||||
nouns=mynouns,
|
|
||||||
}))
|
|
||||||
|
|
||||||
return o
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function bc:get_noun(n)
|
function bc:listen(noun, event, evparam, callback)
|
||||||
if self.local_nouns[n] ~= nil then -- This noun is local, makes it easy
|
-- You can leave out evparam
|
||||||
return self.local_nouns[n]
|
if type(evparam) == "function" then
|
||||||
elseif self.remote_nouns[n] ~= nil then -- This noun is remote
|
callback = evparam
|
||||||
modem.send(self.remote_nouns[n], CFG_PORT, serializer.serialize({
|
evparam = nil
|
||||||
ty=message_type.request_noun,
|
end
|
||||||
noun=n,
|
|
||||||
}))
|
local remote_addr = self.remote_nouns[noun]
|
||||||
local i = 0
|
if remote_addr == nil then
|
||||||
while i < 5 do
|
error("Noun \""..noun.."\" is not listenable!")
|
||||||
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
|
else
|
||||||
-- Noun not found
|
local id = uuid.next()
|
||||||
return nil
|
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
|
||||||
end
|
end
|
||||||
|
|
||||||
function bc:has_noun(n)
|
function bc:cancel(id)
|
||||||
return self.local_nouns[n] ~= nil or self.remote_nouns[n] ~= nil
|
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
|
end
|
||||||
|
|
||||||
function bc:set_noun(n, value)
|
function bc:has_verb(verb)
|
||||||
if self.local_nouns[n] ~= nil then
|
return self.local_verbs[verb] ~= nil or self.remote_verbs[verb] ~= nil
|
||||||
-- Call local listeners
|
end
|
||||||
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
|
function bc:call_verb(verb, ...)
|
||||||
-- Apply change
|
if self.local_verbs[verb] ~= nil then
|
||||||
self.local_nouns[n] = value
|
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
|
else
|
||||||
error("Can't set remote nouns")
|
error("Verb \""..verb.."\" does not exist!")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function bc:call_verb(v, param)
|
function bc:cleanup()
|
||||||
if self.local_verbs[v] ~= nil then
|
event.ignore("modem_message", self._modem_listener)
|
||||||
self.local_verbs[v](self, param)
|
local nouns, verbs = self:locals()
|
||||||
return true
|
broadcast_msg(self, {
|
||||||
elseif self.remote_verbs[v] ~= nil then
|
ty=BC_MESSAGE.DISBAND,
|
||||||
modem.send(self.remote_verbs[v], CFG_PORT, serializer.serialize({
|
nouns=nouns,
|
||||||
ty=message_type.call_verb,
|
verbs=verbs,
|
||||||
verb=v,
|
})
|
||||||
param=param,
|
|
||||||
}))
|
|
||||||
return true
|
|
||||||
else
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function bc:has_verb(v)
|
function bc:locals()
|
||||||
return self.local_verbs[v] ~= nil or self.remote_verbs[v] ~= nil
|
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
|
end
|
||||||
|
|
||||||
function bc:listen_noun(n, query, qparam, callback)
|
local function new(_, local_nouns, local_verbs, overrides)
|
||||||
if self.local_nouns[n] ~= nil then -- Local listening
|
local self = {
|
||||||
self.local_listeners[n] = self.local_listeners[n] or List:new()
|
local_nouns=local_nouns or {},
|
||||||
local id = self.local_listeners[n]:insert({
|
local_verbs=local_verbs or {},
|
||||||
query=query,
|
remote_nouns={},
|
||||||
qparam=qparam,
|
remote_verbs={},
|
||||||
callback=callback,
|
|
||||||
})
|
local_listeners={},
|
||||||
return id
|
remote_listeners={},
|
||||||
elseif self.remote_nouns[n] ~= nil then -- Remote listening
|
|
||||||
self.remote_listeners[n] = self.remote_listeners[n] or List:new()
|
port=BC_PORT,
|
||||||
local id = self.remote_listeners[n]:insert({
|
modem=component.modem,
|
||||||
query=query,
|
timeout=5,
|
||||||
qparam=qparam,
|
}
|
||||||
callback=callback,
|
if overrides ~= nil then
|
||||||
})
|
for name, value in pairs(overrides) do
|
||||||
-- Request remote listening
|
self[name] = value
|
||||||
modem.send(self.remote_nouns[n], CFG_PORT, serializer.serialize({
|
end
|
||||||
ty=message_type.request_listening,
|
end
|
||||||
noun=n,
|
setmetatable(self, bc)
|
||||||
query=query,
|
|
||||||
qparam=qparam,
|
for noun in pairs(self.local_nouns) do
|
||||||
id=id,
|
self.remote_listeners[noun] = {}
|
||||||
}))
|
|
||||||
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
|
||||||
end
|
|
||||||
|
|
||||||
function bc:listen_cancel(n, id)
|
self._modem_listener = function(_, _, remote_addr, port, _, msg)
|
||||||
if self.local_nouns[n] ~= nil then -- Local listening
|
if port ~= self.port then -- Ignore other ports
|
||||||
if self.local_listeners[n] ~= nil then
|
return
|
||||||
self.local_listeners[n]:remove(id)
|
|
||||||
end
|
end
|
||||||
elseif self.remote_nouns[n] ~= nil then -- Remote listening
|
|
||||||
if self.remote_listeners[n] ~= nil then
|
local msg = serializer.unserialize(msg)
|
||||||
self.remote_listeners[n]:remove(id)
|
if msg.ty == BC_MESSAGE.HELLO then
|
||||||
modem.send(self.remote_nouns[n], CFG_PORT, serializer.serialize({
|
local nouns, verbs = self:locals()
|
||||||
ty=message_type.request_stop_listening,
|
send_msg(self, remote_addr, {
|
||||||
noun=n,
|
ty=BC_MESSAGE.REGISTER,
|
||||||
id=id,
|
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
|
||||||
else
|
|
||||||
error("Trying to cancel non existing listener")
|
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|
||||||
-- Deinit
|
-- Setup connection and say hello
|
||||||
function bc:cleanup()
|
self.modem.open(self.port)
|
||||||
event.ignore("modem_message", self.modem_cb)
|
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
|
end
|
||||||
|
|
||||||
return bc
|
return setmetatable(bc, {__call=new, __index={new=new}})
|
||||||
|
|||||||
@ -1,276 +0,0 @@
|
|||||||
require "lunit"
|
|
||||||
|
|
||||||
network = require("network")
|
|
||||||
bc = require("bc")
|
|
||||||
ser = require("serialization")
|
|
||||||
|
|
||||||
module("test_bc", package.seeall, lunit.testcase)
|
|
||||||
|
|
||||||
function test_init()
|
|
||||||
local bci = bc:init({["light"]=false}, {["toggle_light"]=function() end})
|
|
||||||
assert_true(bci:has_noun("light"), "Nouns were not initialized")
|
|
||||||
assert_true(bci:has_verb("toggle_light"), "Verbs were not initialized")
|
|
||||||
end
|
|
||||||
|
|
||||||
function test_set_get_noun()
|
|
||||||
local bci = bc:init({["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_request_noun()
|
|
||||||
local bci = bc:init({["light"]=true}, {["toggle_light"]=function() end})
|
|
||||||
local addr = network.get_scene()
|
|
||||||
local i = false
|
|
||||||
local expct = true
|
|
||||||
|
|
||||||
network.register("tester0", function(m, 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)
|
|
||||||
|
|
||||||
network.send(addr, 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
|
|
||||||
network.send(addr, 1234, ser.serialize({ty=1, noun="light"}))
|
|
||||||
assert_true(i, "Noun response did not happen or was incorrect")
|
|
||||||
end
|
|
||||||
|
|
||||||
function test_call_verb()
|
|
||||||
local bci = bc:init({["light"]=true}, {
|
|
||||||
["toggle_light"]=function(b)
|
|
||||||
b:set_noun("light", not b:get_noun("light"))
|
|
||||||
end,
|
|
||||||
["set_light"]=function(b, state)
|
|
||||||
b:set_noun("light", state)
|
|
||||||
end,
|
|
||||||
})
|
|
||||||
local addr = network.get_scene()
|
|
||||||
-- Call verb
|
|
||||||
network.send(addr, 1234, ser.serialize({ty=2, verb="toggle_light"}))
|
|
||||||
assert_equal(false, bci:get_noun("light"), "Verb did not do its job")
|
|
||||||
|
|
||||||
network.send(addr, 1234, ser.serialize({ty=2, verb="set_light", param=true}))
|
|
||||||
assert_equal(true, bci:get_noun("light"), "Verb did not do its job")
|
|
||||||
|
|
||||||
network.send(addr, 1234, ser.serialize({ty=2, verb="set_light", param=false}))
|
|
||||||
assert_equal(false, bci:get_noun("light"), "Verb did not do its job")
|
|
||||||
end
|
|
||||||
|
|
||||||
function test_multinode_call_verb()
|
|
||||||
local bc1 = bc:init({["light0"]=true}, {
|
|
||||||
["toggle_light0"]=function(b)
|
|
||||||
b:set_noun("light0", not b:get_noun("light0"))
|
|
||||||
end,
|
|
||||||
["set_light0"]=function(b, state)
|
|
||||||
b:set_noun("light0", state)
|
|
||||||
end,
|
|
||||||
})
|
|
||||||
local a1 = network.get_scene()
|
|
||||||
local bc2 = bc:init({}, {})
|
|
||||||
local a2 = network.get_scene()
|
|
||||||
|
|
||||||
network.set_scene(a1)
|
|
||||||
assert_true(bc1:call_verb("toggle_light0"))
|
|
||||||
assert_equal(false, bc1:get_noun("light0"), "First verb invocation did not go to plan")
|
|
||||||
|
|
||||||
network.set_scene(a2)
|
|
||||||
assert_true(bc2:call_verb("toggle_light0"))
|
|
||||||
|
|
||||||
network.set_scene(a1)
|
|
||||||
assert_equal(true, bc1:get_noun("light0"), "Second verb invocation did not go to plan")
|
|
||||||
|
|
||||||
-- Check calling with parameter
|
|
||||||
for _, b in pairs({true, false}) do
|
|
||||||
network.set_scene(a2)
|
|
||||||
assert_true(bc2:call_verb("set_light0", b))
|
|
||||||
|
|
||||||
network.set_scene(a1)
|
|
||||||
assert_equal(b, bc1:get_noun("light0"), "Parameter verb invocation did not go to plan")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function test_multinode_get_noun()
|
|
||||||
local key = "foobar"
|
|
||||||
local bc1 = bc:init({["light1"]=key}, {["toggle_light1"]=function(b)
|
|
||||||
b:set_noun("light1", not b:get_noun("light1"))
|
|
||||||
end})
|
|
||||||
local a1 = network.get_scene()
|
|
||||||
local bc2 = bc:init({}, {})
|
|
||||||
local a2 = network.get_scene()
|
|
||||||
|
|
||||||
network.set_scene(a1)
|
|
||||||
assert_equal(key, bc1:get_noun("light1"), "Local get failed")
|
|
||||||
|
|
||||||
network.set_scene(a2)
|
|
||||||
assert_equal(key, bc2:get_noun("light1"), "Remote get failed")
|
|
||||||
end
|
|
||||||
|
|
||||||
function test_multinode_listening()
|
|
||||||
local bc1 = bc:init({["foo"]=123}, {})
|
|
||||||
local a1 = network.get_scene()
|
|
||||||
local bc2 = bc:init({}, {})
|
|
||||||
local a2 = network.get_scene()
|
|
||||||
|
|
||||||
network.set_scene(a1)
|
|
||||||
local i = false
|
|
||||||
-- Local listening
|
|
||||||
local id = bc1:listen_noun("foo", "onchange", nil, function(bc, foo)
|
|
||||||
i = true
|
|
||||||
end)
|
|
||||||
bc1:set_noun("foo", 111)
|
|
||||||
assert_true(i, "Local listening failed")
|
|
||||||
i = false
|
|
||||||
bc1:listen_cancel("foo", id) -- Test wether cancelling works
|
|
||||||
|
|
||||||
network.set_scene(a2)
|
|
||||||
local j = false
|
|
||||||
local rid = bc2:listen_noun("foo", "onchange", nil, function(bc, foo)
|
|
||||||
j = true
|
|
||||||
end)
|
|
||||||
|
|
||||||
network.set_scene(a1)
|
|
||||||
bc1:set_noun("foo", 1234)
|
|
||||||
assert_true(j, "Remote listening failed")
|
|
||||||
assert_false(i, "Cancelling local listener failed")
|
|
||||||
|
|
||||||
network.set_scene(a2)
|
|
||||||
bc2:listen_cancel("foo", rid)
|
|
||||||
j = false
|
|
||||||
|
|
||||||
network.set_scene(a1)
|
|
||||||
bc1:set_noun("foo", 34)
|
|
||||||
assert_false(j, "Cancelling remote listener failed")
|
|
||||||
end
|
|
||||||
|
|
||||||
function test_listen_modes()
|
|
||||||
local bci = bc:init({["noun"]=10}, {})
|
|
||||||
local i = false
|
|
||||||
local id = 0
|
|
||||||
-- onchange
|
|
||||||
local id = bci:listen_noun("noun", "onchange", nil, function(bc, noun)
|
|
||||||
i = true
|
|
||||||
end)
|
|
||||||
bci:set_noun("noun", 11)
|
|
||||||
assert_true(i, "Listening \"onchange\" failed")
|
|
||||||
bci:listen_cancel("noun", id)
|
|
||||||
i = false
|
|
||||||
-- onrising
|
|
||||||
id = bci:listen_noun("noun", "onrising", nil, function(bc, noun)
|
|
||||||
i = true
|
|
||||||
end)
|
|
||||||
bci:set_noun("noun", 10)
|
|
||||||
assert_false(i, "Listening \"onrising\" failed(A)")
|
|
||||||
bci:set_noun("noun", 11)
|
|
||||||
assert_true(i, "Listening \"onrising\" failed(B)")
|
|
||||||
bci:listen_cancel("noun", id)
|
|
||||||
i = false
|
|
||||||
-- onfalling
|
|
||||||
id = bci:listen_noun("noun", "onfalling", nil, function(bc, noun)
|
|
||||||
i = true
|
|
||||||
end)
|
|
||||||
bci:set_noun("noun", 12)
|
|
||||||
assert_false(i, "Listening \"onfalling\" failed(A)")
|
|
||||||
bci:set_noun("noun", 10)
|
|
||||||
assert_true(i, "Listening \"onfalling\" failed(B)")
|
|
||||||
bci:listen_cancel("noun", id)
|
|
||||||
i = false
|
|
||||||
-- onvalue
|
|
||||||
id = bci:listen_noun("noun", "onvalue", 99, function(bc, noun)
|
|
||||||
i = true
|
|
||||||
end)
|
|
||||||
bci:set_noun("noun", 100)
|
|
||||||
assert_false(i, "Listening \"onvalue\" failed(A)")
|
|
||||||
bci:set_noun("noun", 99)
|
|
||||||
assert_true(i, "Listening \"onvalue\" failed(B)")
|
|
||||||
bci:listen_cancel("noun", id)
|
|
||||||
i = false
|
|
||||||
-- onabove
|
|
||||||
id = bci:listen_noun("noun", "onabove", 100, function(bc, noun)
|
|
||||||
i = true
|
|
||||||
end)
|
|
||||||
bci:set_noun("noun", 10)
|
|
||||||
assert_false(i, "Listening \"onabove\" failed(A)")
|
|
||||||
bci:set_noun("noun", 110)
|
|
||||||
assert_true(i, "Listening \"onabove\" failed(B)")
|
|
||||||
bci:listen_cancel("noun", id)
|
|
||||||
i = false
|
|
||||||
-- onbelow
|
|
||||||
id = bci:listen_noun("noun", "onbelow", 10, function(bc, noun)
|
|
||||||
i = true
|
|
||||||
end)
|
|
||||||
bci:set_noun("noun", 11)
|
|
||||||
assert_false(i, "Listening \"onbelow\" failed(A)")
|
|
||||||
bci:set_noun("noun", 1)
|
|
||||||
assert_true(i, "Listening \"onbelow\" failed(B)")
|
|
||||||
bci:listen_cancel("noun", id)
|
|
||||||
end
|
|
||||||
|
|
||||||
function test_get_unknown_noun()
|
|
||||||
local bci = bc:init()
|
|
||||||
|
|
||||||
assert_false(bci:has_noun("bar"))
|
|
||||||
assert_equal(nil, bci:get_noun("bar"))
|
|
||||||
end
|
|
||||||
|
|
||||||
-- function test_get_offline_node()
|
|
||||||
-- local bc1 = bc:init()
|
|
||||||
-- local bc2 = bc:init({["foo"]=true})
|
|
||||||
--
|
|
||||||
-- -- Simulate node going offline
|
|
||||||
-- bc2.modem:remove_self()
|
|
||||||
-- bc2 = nil
|
|
||||||
--
|
|
||||||
-- local val = bc1:get_noun("foo")
|
|
||||||
-- assert_equal(nil, val)
|
|
||||||
-- end
|
|
||||||
|
|
||||||
function test_call_unknown_verb()
|
|
||||||
local bci = bc:init()
|
|
||||||
|
|
||||||
assert_false(bci:has_verb("unknown"))
|
|
||||||
assert_equal(false, bci:call_verb("unknown"))
|
|
||||||
end
|
|
||||||
|
|
||||||
function test_install_listen_on_unknown_noun()
|
|
||||||
local bci = bc:init()
|
|
||||||
|
|
||||||
assert_false(bci:has_noun("unknown"))
|
|
||||||
bci:listen_noun("unknown", "onchange", nil, function() end)
|
|
||||||
end
|
|
||||||
|
|
||||||
function test_late_install()
|
|
||||||
local bc1 = bc:init()
|
|
||||||
|
|
||||||
local i = false
|
|
||||||
|
|
||||||
assert_false(bc1:has_noun("foo123"))
|
|
||||||
bc1:listen_noun("foo123", "onchange", nil, function()
|
|
||||||
i = true
|
|
||||||
end)
|
|
||||||
|
|
||||||
local bc2 = bc:init({["foo123"] = 123})
|
|
||||||
bc2:set_noun("foo123", 321)
|
|
||||||
|
|
||||||
assert_true(i, "Listener was not called")
|
|
||||||
end
|
|
||||||
|
|
||||||
function test_verb_multicall_bug()
|
|
||||||
local var = 0
|
|
||||||
local bc1 = bc:init(nil, {
|
|
||||||
["verb"] = function(bc, foo)
|
|
||||||
var = var + foo
|
|
||||||
end
|
|
||||||
})
|
|
||||||
local bc2 = bc:init()
|
|
||||||
bc2:call_verb("verb", 1)
|
|
||||||
assert_equal(1, var, "Verb was not called correctly")
|
|
||||||
bc2:call_verb("verb", 2)
|
|
||||||
assert_equal(3, var, "Verb was not called correctly(Second attempt)")
|
|
||||||
end
|
|
||||||
@ -0,0 +1,30 @@
|
|||||||
|
local bit32 = require("bit32")
|
||||||
|
local uuid = {}
|
||||||
|
|
||||||
|
function uuid.next()
|
||||||
|
-- e.g. 3c44c8a9-0613-46a2-ad33-97b6ba2e9d9a
|
||||||
|
-- 8-4-4-4-12 (halved sizes because bytes make hex pairs)
|
||||||
|
local sets = {4, 2, 2, 2, 6}
|
||||||
|
local result = ""
|
||||||
|
local pos = 0
|
||||||
|
|
||||||
|
for _,set in ipairs(sets) do
|
||||||
|
if result:len() > 0 then
|
||||||
|
result = result .. "-"
|
||||||
|
end
|
||||||
|
for _ = 1,set do
|
||||||
|
local byte = math.random(0, 255)
|
||||||
|
if pos == 6 then
|
||||||
|
byte = bit32.bor(bit32.band(byte, 0x0F), 0x40)
|
||||||
|
elseif pos == 8 then
|
||||||
|
byte = bit32.bor(bit32.band(byte, 0x3F), 0x80)
|
||||||
|
end
|
||||||
|
result = result .. string.format("%02x", byte)
|
||||||
|
pos = pos + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return result
|
||||||
|
end
|
||||||
|
|
||||||
|
return uuid
|
||||||
Loading…
Reference in new issue