parent
b205779a29
commit
132cfb9921
@ -1,57 +1,237 @@
|
|||||||
oc-basecontrol
|
oc-basecontrol
|
||||||
==============
|
==============
|
||||||
|
|
||||||
oc-basecontrol is an OpenComputers generic base control library.
|
`oc-basecontrol` is a library for distributed base control systems in
|
||||||
|
OpenComputers.
|
||||||
|
|
||||||
## Documentation ##
|
|
||||||
|
|
||||||
Load oc-basecontrol into your application:
|
Concept
|
||||||
|
-------
|
||||||
|
Each node in the base network exports a set of _nouns_ and _verbs_.
|
||||||
|
|
||||||
|
- _Nouns_ are values which can be queried by other nodes and which
|
||||||
|
can change over time. Other nodes can also request to be notified
|
||||||
|
about updates of nouns. This is called _listening_. Examples: The power
|
||||||
|
a capacitor has stored or the fluid level in a tank.
|
||||||
|
- _Verbs_ are functions that can be triggered remotely. There are two
|
||||||
|
ways this can be done:
|
||||||
|
- _Asynchroneously_, which is "fire-and-forget". There is no checking that
|
||||||
|
the call succeeded and no way to pass back a return value.
|
||||||
|
- _Synchroneous_ verb calling, where the caller will block until a response
|
||||||
|
with a return value is returned or the call timed out.
|
||||||
|
- _Listening_: Nodes can request a notification on a noun change event.
|
||||||
|
There are a few different queries that can be used here:
|
||||||
|
- `Query.Change`: Notify whenever the noun changes
|
||||||
|
- `Query.Rising`: Notify whenever the noun's value increases
|
||||||
|
- `Query.Falling`: Notify whenever the noun's value decreases
|
||||||
|
- `Query.Equals(v)`: Notify whenever the noun's value equals a given value
|
||||||
|
- `Query.Above(v)`: Notify whenever the noun's value changes and is above
|
||||||
|
a given limit
|
||||||
|
- `Query.Below(v)`: Notify whenever the noun's value changes and is below
|
||||||
|
a given limit
|
||||||
|
|
||||||
|
|
||||||
|
Basic API
|
||||||
|
---------
|
||||||
|
|
||||||
|
### `BaseControl:new([modem])`
|
||||||
|
There are a few different ways to construct the base-controller. The easiest
|
||||||
|
is to just use the object created during loading the library:
|
||||||
```lua
|
```lua
|
||||||
bc = require("bc"):init(<nouns>, <verbs>)
|
local bc = require("bc"):new()
|
||||||
|
bc:register("foo_noun", 1234)
|
||||||
|
bc:finalize()
|
||||||
```
|
```
|
||||||
where `<nouns>` is a table containing all nouns (described later) and their
|
|
||||||
initial value and `<verbs>` is a table containing all verbs and their callbacks.
|
|
||||||
|
|
||||||
The basic idea behind basecontrol is, that network nodes have a set of **nouns**,
|
If you need finer grained control, you can also use the initial object as a
|
||||||
which represent different values. A computer near a reactor for example could have
|
class to create base-controllers:
|
||||||
a noun called `power-output`. Network nodes also have **verbs**, actions that other
|
```lua
|
||||||
nodes can perform. The reactor computer could have a `shutdown` verb for example.
|
local BaseControl = require("bc")
|
||||||
|
local bc = BaseControl()
|
||||||
|
-- or
|
||||||
|
local bc = BaseControl:new()
|
||||||
|
```
|
||||||
|
The main job of the constructor is to initialize the network interface.
|
||||||
|
This means as soon as the constructor is called, our node will go "online".
|
||||||
|
|
||||||
|
You can give a custom network to `new` if you do not want to use the default
|
||||||
|
one. More info under _Network Configuration_.
|
||||||
|
|
||||||
|
### `BaseControl:register(name, value)`
|
||||||
|
Registers a new noun or verb named `name` with the initial value of `value`.
|
||||||
|
If `value` is a _function_, it will be a verb. `register` will fail if `name`
|
||||||
|
is already known as a local noun or verb. Note that locals always take
|
||||||
|
precedence over remotes so `register` will happily add a local which a remote
|
||||||
|
also exports.
|
||||||
|
```lua
|
||||||
|
local bc = require("bc")
|
||||||
|
-- Register a noun
|
||||||
|
bc:register("some_noun", 12345)
|
||||||
|
-- Register a verb
|
||||||
|
bc:register("some_verb", function(param_a, param_b)
|
||||||
|
print("Verb called with "..param_a.." and "..param_b..".")
|
||||||
|
end)
|
||||||
|
bc:finalize()
|
||||||
|
```
|
||||||
|
You can also batch-register multiple nouns/verbs:
|
||||||
|
```lua
|
||||||
|
local bc = require("bc")
|
||||||
|
bc:register{
|
||||||
|
some_noun=1234,
|
||||||
|
other_noun=4321,
|
||||||
|
a_verb=function(param)
|
||||||
|
print("Param: ", param)
|
||||||
|
end,
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### `BaseControl:finalize([waits], [timeout])`
|
||||||
|
After creating a new base-controller, it will not immediately announce its data
|
||||||
|
into the network. It will first acuumulate local nouns and verbs and then send
|
||||||
|
them out in one batch. This happens when you call `finalize`.
|
||||||
|
|
||||||
|
Finalize takes two optional parameters:
|
||||||
|
- `waits`: A list of remote nouns/verbs that this node requires to be available.
|
||||||
|
`finalize` will only return once all of them have been registered by remote
|
||||||
|
nodes.
|
||||||
|
- `timeout`: Return early if timeout was reached before all waits were found.
|
||||||
|
|
||||||
|
If finalize is called again, it will start waiting once more, but it will not
|
||||||
|
reannounce its own presence. `finalize` can also be used as a constructor,
|
||||||
|
like this:
|
||||||
|
```lua
|
||||||
|
local bc = require("bc"):finalize{"some_noun"}
|
||||||
|
-- With timeout
|
||||||
|
local bc = require("bc"):finalize({"some_noun"}, 10)
|
||||||
|
```
|
||||||
|
|
||||||
The `bc` object has the following methods to interface with verbs and nouns:
|
### `BaseControl:set(name, value)`
|
||||||
|
Set a noun to a new value. This only works if the noun `name` is a local one.
|
||||||
|
You can not set remote nouns. If `name` is a verb, value **must** be a function
|
||||||
|
which will be the new callback.
|
||||||
```lua
|
```lua
|
||||||
-- Retrieve a noun from it's network node
|
local bc = require("bc"):new()
|
||||||
val = bc:get_noun("<noun>")
|
bc:register("some_noun", 1234)
|
||||||
|
bc:finalize()
|
||||||
|
|
||||||
-- Set a noun on the local machine
|
-- Later ...
|
||||||
bc:set_noun("<noun>", value)
|
bc:set("some_noun", 4321)
|
||||||
|
```
|
||||||
|
|
||||||
-- Call a verb on it's network node
|
### `BaseControl:get(noun, [timeout])`
|
||||||
bc:call_verb("<verb>", param)
|
Get the value of a noun named `noun`. `get` can not be called for verbs.
|
||||||
|
If `timeout` is supplied, the call will return early if no response came before
|
||||||
|
the timeout expired.
|
||||||
|
```lua
|
||||||
|
local bc = require("bc"):finalize{"some_noun"}
|
||||||
|
print("Value: ", bc:get("some_noun"))
|
||||||
```
|
```
|
||||||
|
|
||||||
Additionally, there is
|
### `BaseControl:call(verb, ...)`
|
||||||
|
Call a verb **asynchroneously**. All parameters following `verb` will be given
|
||||||
|
to the remote function. There is no guarantee that the verb was actually run.
|
||||||
|
|
||||||
|
_Note_: For local verbs, the call will still be synchroneous.
|
||||||
```lua
|
```lua
|
||||||
id = bc:listen_noun("<noun>", event_type, event_arg, function callback)
|
local bc = require("bc")
|
||||||
|
bc:register("some_verb", function(param_a, param_b)
|
||||||
|
print("Verb called with "..param_a.." and "..param_b..".")
|
||||||
|
end)
|
||||||
|
bc:finalize()
|
||||||
|
|
||||||
|
-- This call can either happen locally or on another node
|
||||||
|
bc:call("some_verb", "param_a_value", "param_b_value")
|
||||||
```
|
```
|
||||||
|
|
||||||
which is used to register asynchroneous callbacks on (possibly remote) noun
|
### `BaseControl:call_sync(verb, timeout, ...)`
|
||||||
changes. `event_type` is one of
|
Call a verb **synchroneously**. All parameters following `verb` will be given
|
||||||
|
to the remote function. `call_sync` will return the remote function's return
|
||||||
|
value. If timeout is not `nil` or `0`, `call_sync` will return early upon
|
||||||
|
the timeout expiring.
|
||||||
|
```lua
|
||||||
|
local bc = require("bc")
|
||||||
|
bc:register("stupid_add", function(a, b)
|
||||||
|
return a + b
|
||||||
|
end)
|
||||||
|
bc:finalize()
|
||||||
|
|
||||||
* `onchange` => whenever the value changes, `event_arg` is ignored
|
-- This call can either happen locally or on another node
|
||||||
* `onrising` => whenever the value gets bigger, `event_arg` is ignored
|
local res = bc:call_sync("stupid_add", nil, 12, 34)
|
||||||
* `onfalling` => whenever the value gets smaller, `event_arg` is ignored
|
print("12 + 34 = "..res)
|
||||||
* `onvalue` => whenever the value equals `event_arg`
|
```
|
||||||
* `onabove` => whenever the value is bigger than `event_arg`
|
|
||||||
* `onbelow` => whenever the value smaller than `event_arg`
|
|
||||||
|
|
||||||
A listener can be removed using
|
### `BaseControl:listen(noun, query, callback)`
|
||||||
|
Install a listener on changes of remote nouns. `noun` must be a remote noun.
|
||||||
|
If it is not known at this time, the listener is still installed in hope that
|
||||||
|
the node will come online later. `query` must be a valid query, chosen from
|
||||||
|
`BaseControl.Query`. A full list of currently supported queries was shown above.
|
||||||
|
The `callback` function will get one argument: The new value of the noun that
|
||||||
|
triggered the event.
|
||||||
|
|
||||||
```lua
|
```lua
|
||||||
bc:listen_cancel("<noun>", id)
|
bc = require("bc"):finalize{"some_noun"}
|
||||||
|
bc:listen("some_noun", bc.Query.Change, function(value)
|
||||||
|
print("'some_noun' is now '"..value.."'")
|
||||||
|
end)
|
||||||
|
bc:listen("some_noun", bc.Query.Below(0), function(value)
|
||||||
|
print("'some_noun' is negative! ("..value..")")
|
||||||
|
end)
|
||||||
```
|
```
|
||||||
|
|
||||||
## Testing ##
|
Introspection API
|
||||||
Testing is done using a "fake" backend and typing
|
-----------------
|
||||||
```console
|
The following methods give you insight into the network:
|
||||||
$ ./lunit test_bc.lua
|
|
||||||
|
### `BaseControl:has_noun(noun)`
|
||||||
|
Whether `noun` is known at this time (either local or remote). Note that even if
|
||||||
|
a noun is known, it might still not be available if the remote node exited
|
||||||
|
without deregistering.
|
||||||
|
|
||||||
|
### `BaseControl:has_verb(verb)`
|
||||||
|
Whether `verb` is known at this time (either local or remote). Note that even if
|
||||||
|
a verb is known, calling it might still not succeed if the remote node exited
|
||||||
|
without deregistering. If you need to know for sure, call using `call_sync`.
|
||||||
|
|
||||||
|
### `BaseControl:nouns()`
|
||||||
|
Returns a list of all known nouns (either local or remote).
|
||||||
|
|
||||||
|
### `BaseControl:verbs()`
|
||||||
|
Returns a list of all known verbs (either local or remote).
|
||||||
|
|
||||||
|
Network Configuration
|
||||||
|
---------------------
|
||||||
|
_oc-basecontrol_ uses the raw [Modem API](https://ocdoc.cil.li/component:modem) with the default modem
|
||||||
|
(`component.modem`), if no alternative network is supplied to `BaseControl:new`.
|
||||||
|
If you have multiple modems and want to base-control to use a specific one,
|
||||||
|
you can set it like this:
|
||||||
|
```lua
|
||||||
|
local BaseControl = require("bc")
|
||||||
|
local bc = BaseControl:new(BaseControl.Network:new(my_moden))
|
||||||
```
|
```
|
||||||
|
The Network constructor looks like this: `function Network:new([modem], [port])`.
|
||||||
|
You can leave out the modem (it will use the default) and just specify a port,
|
||||||
|
if you just don't like the default port. If you leave out the port or give no
|
||||||
|
custom network at all, _oc-basecontrol_ will use a port that is derived from the
|
||||||
|
version number. This prevents different versions of _oc-basecontrol_ clashing.
|
||||||
|
|
||||||
|
Alternatively, you can use a completely different networking stack by writing
|
||||||
|
your own implementation of `Network`. Take a look at the sources to see how
|
||||||
|
the default implementation works. A custom network needs to have the following
|
||||||
|
methods:
|
||||||
|
|
||||||
|
### `Network:start(callback)`
|
||||||
|
Start this network connection (eg. open the port) and install `callback` as a
|
||||||
|
listener for incoming messages. `callback` has the following signature:
|
||||||
|
`function callback(remote_addr, msg_tbl)` where `remote_addr` is the address of
|
||||||
|
the other side (any string works here) and `msg_tbl` is the unserialized message.
|
||||||
|
For serialization, please use `OpenOS`'s `serialization` library.
|
||||||
|
|
||||||
|
### `Network:send(addr, msg_tbl)`
|
||||||
|
Send a message to the remote party identified by `addr`. `msg_tbl` should be
|
||||||
|
serialized using `OpenOS`'s `serialization` library.
|
||||||
|
|
||||||
|
### `Network:broadcast(msg_tbl)`
|
||||||
|
Send a message to all connected nodes on the network.
|
||||||
|
|
||||||
|
### `Network:stop()`
|
||||||
|
Close this connection and uninstall the message handler.
|
||||||
|
|||||||
@ -1,21 +0,0 @@
|
|||||||
inspect = require("inspect").inspect
|
|
||||||
bc = require("bc")
|
|
||||||
network = require("network")
|
|
||||||
|
|
||||||
|
|
||||||
bc1 = bc:init({["light"]=true}, {["toggle_light"]=function(b)
|
|
||||||
b:set_noun("light", not b:get_noun("light"))
|
|
||||||
end})
|
|
||||||
a1 = network.get_scene()
|
|
||||||
bc2 = bc:init({}, {})
|
|
||||||
a2 = network.get_scene()
|
|
||||||
|
|
||||||
network.set_scene(a1)
|
|
||||||
print(true, bc1:call_verb("toggle_light"))
|
|
||||||
print(false, bc1:get_noun("light"))
|
|
||||||
|
|
||||||
network.set_scene(a2)
|
|
||||||
print(true, bc2:call_verb("toggle_light"))
|
|
||||||
|
|
||||||
network.set_scene(a1)
|
|
||||||
print(true, bc1:get_noun("light"))
|
|
||||||
Loading…
Reference in new issue