parent
b205779a29
commit
132cfb9921
@ -1,57 +1,237 @@
|
||||
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
|
||||
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**,
|
||||
which represent different values. A computer near a reactor for example could have
|
||||
a noun called `power-output`. Network nodes also have **verbs**, actions that other
|
||||
nodes can perform. The reactor computer could have a `shutdown` verb for example.
|
||||
If you need finer grained control, you can also use the initial object as a
|
||||
class to create base-controllers:
|
||||
```lua
|
||||
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
|
||||
-- Retrieve a noun from it's network node
|
||||
val = bc:get_noun("<noun>")
|
||||
local bc = require("bc"):new()
|
||||
bc:register("some_noun", 1234)
|
||||
bc:finalize()
|
||||
|
||||
-- Set a noun on the local machine
|
||||
bc:set_noun("<noun>", value)
|
||||
-- Later ...
|
||||
bc:set("some_noun", 4321)
|
||||
```
|
||||
|
||||
-- Call a verb on it's network node
|
||||
bc:call_verb("<verb>", param)
|
||||
### `BaseControl:get(noun, [timeout])`
|
||||
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
|
||||
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
|
||||
changes. `event_type` is one of
|
||||
### `BaseControl:call_sync(verb, timeout, ...)`
|
||||
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
|
||||
* `onrising` => whenever the value gets bigger, `event_arg` is ignored
|
||||
* `onfalling` => whenever the value gets smaller, `event_arg` is ignored
|
||||
* `onvalue` => whenever the value equals `event_arg`
|
||||
* `onabove` => whenever the value is bigger than `event_arg`
|
||||
* `onbelow` => whenever the value smaller than `event_arg`
|
||||
-- This call can either happen locally or on another node
|
||||
local res = bc:call_sync("stupid_add", nil, 12, 34)
|
||||
print("12 + 34 = "..res)
|
||||
```
|
||||
|
||||
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
|
||||
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 ##
|
||||
Testing is done using a "fake" backend and typing
|
||||
```console
|
||||
$ ./lunit test_bc.lua
|
||||
Introspection API
|
||||
-----------------
|
||||
The following methods give you insight into the network:
|
||||
|
||||
### `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