bus: Difference between revisions
(20 intermediate revisions by 4 users not shown) | |||
Line 14: | Line 14: | ||
<code>$messageID</code> is a string identifier (name) for the message. | <code>$messageID</code> is a string identifier (name) for the message. | ||
<code>$args</code> can be either a | <!-- why not perldoc perldata link? --> | ||
<code>$args</code> can be either a reference to a hash or an array that contains only [[wikipedia:Perl#Scalar_values|scalar]] (string or numeric) value's. Which implies that it cannot be a complex datatype. | |||
=== Send a message in OpenKore === | === Send a message in OpenKore === | ||
OpenKore acts as a bus client here, using the globally stored variable $bus which holds a Bus::Client object. | |||
<pre> | <pre> | ||
if ($ | use Globals qw($bus); | ||
</pre> | |||
<pre> | |||
if ($bus->getState == Bus::Client::CONNECTED) { | |||
$bus->send($messageID, $args); | $bus->send($messageID, $args); | ||
} | } | ||
</pre> | </pre> | ||
* | * Check for CONNECTED can be omitted, in that case your message will be queued and will be sent when the Bus connects. | ||
=== Receive a message in OpenKore === | === Receive a message in OpenKore === | ||
OpenKore acts as a bus client here, using the globally stored variable $bus which holds a Bus::Client object. | |||
<pre> | |||
use Globals qw($bus); | |||
my $on_message_received; | |||
</pre> | |||
Load: | Load: | ||
Line 33: | Line 45: | ||
my (undef, undef, $message) = @_; | my (undef, undef, $message) = @_; | ||
if ($message->{messageID} eq $messageID) { | if ($message->{messageID} eq $messageID) { | ||
# | # its a message we're interested in (having $messageID name) | ||
# $args is at $message->{args} | # $args is at $message->{args} | ||
} | } | ||
} | } | ||
$on_message_received = $bus->onMessageReceived->add(undef, \&bus_message_received); | |||
</pre> | </pre> | ||
Unload: | Unload: | ||
<pre> | <pre> | ||
$ | $bus->onMessageReceived->remove($on_message_received); | ||
</pre> | </pre> | ||
=== Standalone script === | === Standalone script === | ||
Everything is the same as above, the only difference is that you need to start the Bus and iterate it in your main loop. (which kore does for you) | |||
Everything is the same, you | Because bus clients can communicate with each other, a script like this can communicate with an OpenKore bus client. | ||
(ex. Such bus script could be your interface to send SMS text-messages from your kore instances) | |||
<pre> | <pre> | ||
#!/usr/bin/env perl | |||
use lib '<path to OpenKore src dir>'; | use lib '<path to OpenKore src dir>'; | ||
Line 71: | Line 85: | ||
} | } | ||
</pre> | </pre> | ||
== Known messages and their fields == | |||
=== Common Fields === | |||
* <code>FROM</code> — only in server→client messages, contains sender's ID (added automatically by the server) | |||
* <code>TO</code> — only in client→server messages, makes a private message to the client with ID specified | |||
* <code>SEQ</code> | |||
* <code>IRY</code> | |||
=== Bus Server-related === | |||
==== HELLO ==== | |||
Internal message. Not intended to be used outside Bus modules. | |||
==== JOIN ==== | |||
Server's broadcast about new connected client. | |||
* <code>clientID</code> — server given client ID (unique for each client in one server session) | |||
* <code>name</code> — server given client name (unique for each client in one server session) | |||
* <code>userAgent</code> — client's user agent | |||
* <code>host</code> — client's host | |||
==== LIST_CLIENTS ==== | |||
Client's request to list all connected clients. | |||
==== LIST_CLIENTS ==== | |||
Server's reply to LIST_CLIENTS | |||
* <code>client0</code> — server given client ID | |||
* <code>clientUserAgent0</code> — client's user agent | |||
* ... | |||
==== DELIVERY_FAILED ==== | |||
Server's report of failed private message delivery. | |||
==== CLIENT_NOT_FOUND ==== | |||
Server's report of failed private message delivery due to absence of client with specified ID. | |||
=== Dialog-related === | |||
==== REQUEST_DIALOG ==== | |||
* <code>dialogID</code> | |||
* <code>reason</code> | |||
==== ACCEPTED ==== | |||
* <code>dialogID</code> | |||
==== REFUSED ==== | |||
==== LEAVE ==== | |||
=== Default handlers === | |||
==== MoveTo ==== | |||
Move to the specified point. | |||
* <code>field</code> | |||
* <code>x</code> | |||
* <code>y</code> | |||
== Starting the Bus Server == | |||
There are two methods for starting the Bus Server: Automatically start it with your (first) openkore instance, with the proper switches in sys.txt, or Manually, initiating the server from the command line. | |||
'''Scenarios''' | |||
* On a machine with multiple instances of Openkore running, the first instance with "bus 1" in sys.txt that starts will load the bus server. All other instances will check for the port number saved in your temporary directory, and connect to the same Bus Server. | |||
* If your Openkore instances are run from multiple machines, starting the bus manually, with a specific port will allow all bus clients (Openkore instances, independant perl scripts, standalone programs) to connect to the same Bus Server. | |||
=== Automatic Bus Server === | |||
Automatically starting the Bus Server works the same for all supported OS platforms. | |||
Simply define "bus 1" in sys.txt for all Openkore instances; the first to load will start the server. | |||
'''Typical uses:''' | |||
* One machine, multiple Openkore instances | |||
* One machine, one or more Openkore instances, one or more standalone bus-aware perl scripts running on the same machine | |||
=== Manually Loaded Bus Server === | |||
Manually loading up the Bus Server is a better solution a mixed network, in several senses: | |||
* Openkore instances are running on more than one machine | |||
* Standalone perl scripts that may start before any Openkore instances are loaded | |||
In these cases, to allow diversely hosted programs to connect to the Bus Server, the server must be started on a predetermined host and port. | |||
The following will detail the command line switches, and how to load your Bus Server. | |||
==== Command Line Switches ==== | |||
Command line switches can be detailed by loading the Bus Server perl script with the --help switch. | |||
; --bind=<host> | |||
: Specify a particular hostname or IP address the bus will listen on. This is equivalent to [[bus_server_host]] in sys.txt | |||
; --help | |||
: Displays help for all switches. | |||
; --port=<portnumber> | |||
: Start the server at the specified port. Leave empty to use the first available port. Otherwise, acceptable port range is 1..65535. | |||
; --quiet | |||
: Supresses status messages. | |||
==== Windows platform ==== | |||
The majority of Openkore users on windows either use the console start.exe, or the GUI wxstart.exe to load their bot. | |||
To start the bus server with a specified port, use the appropriate commandline: | |||
<b>Console:</b> | |||
<pre>start.exe "!" "src/Bus/bus-server.pl" --port=1337</pre> | |||
<b>Wx GUI:</b> | |||
<pre>wxstart.exe "!" "src/Bus/bus-server.pl" --port=54321</pre> | |||
Users can either create batch files to load these, or alter a shortcut that loads the executable with these arguments. | |||
==== Unix platform / Openkore run directly in a perl intepreter ==== | |||
From your shell, in the directory where openkore.pl is located, you can load up the bus server independant of any Openkore instances, with a fixed port so: | |||
<code>yourlogin ~> ./src/Bus/bus-server.pl --port=31416</code> | |||
== Protocol description == | == Protocol description == |
Latest revision as of 22:34, 26 April 2021
- bus [boolean]
- Enables bus system.
About the bus system
The bus system's goal is to allow different OpenKore instances to easily communicate with each other, and to allow external tools to easily communicate with a running OpenKore instance.
The bus is a communication channel which supports broadcast communication as well as private communication. One can compare it to an open street: anyone can shout a message to everybody (broadcast communication) or whisper a message into someone else's ears (private communication).
Furthermore, the bus system is based on discrete messages instead of byte streams.
Examples of using the Bus
$messageID
is a string identifier (name) for the message.
$args
can be either a reference to a hash or an array that contains only scalar (string or numeric) value's. Which implies that it cannot be a complex datatype.
Send a message in OpenKore
OpenKore acts as a bus client here, using the globally stored variable $bus which holds a Bus::Client object.
use Globals qw($bus);
if ($bus->getState == Bus::Client::CONNECTED) { $bus->send($messageID, $args); }
- Check for CONNECTED can be omitted, in that case your message will be queued and will be sent when the Bus connects.
Receive a message in OpenKore
OpenKore acts as a bus client here, using the globally stored variable $bus which holds a Bus::Client object.
use Globals qw($bus); my $on_message_received;
Load:
sub bus_message_received { my (undef, undef, $message) = @_; if ($message->{messageID} eq $messageID) { # its a message we're interested in (having $messageID name) # $args is at $message->{args} } } $on_message_received = $bus->onMessageReceived->add(undef, \&bus_message_received);
Unload:
$bus->onMessageReceived->remove($on_message_received);
Standalone script
Everything is the same as above, the only difference is that you need to start the Bus and iterate it in your main loop. (which kore does for you) Because bus clients can communicate with each other, a script like this can communicate with an OpenKore bus client. (ex. Such bus script could be your interface to send SMS text-messages from your kore instances)
#!/usr/bin/env perl use lib '<path to OpenKore src dir>'; use Bus::Client; use Bus::Handlers; use constant BUS_USER_AGENT => 'Meaningful name of your script'; # use this to connect to local bus server, otherwise specify host and port my $bus = new Bus::Client(host => undef, port => undef, userAgent => BUS_USER_AGENT); my $bus_message_handlers = new Bus::Handlers($bus); # (add here listener for receiving Bus messages, if needed) while () { # (add here your code, and send Bus messages, if needed) $bus->iterate; sleep 1; }
Known messages and their fields
Common Fields
FROM
— only in server→client messages, contains sender's ID (added automatically by the server)TO
— only in client→server messages, makes a private message to the client with ID specifiedSEQ
IRY
HELLO
Internal message. Not intended to be used outside Bus modules.
JOIN
Server's broadcast about new connected client.
clientID
— server given client ID (unique for each client in one server session)name
— server given client name (unique for each client in one server session)userAgent
— client's user agenthost
— client's host
LIST_CLIENTS
Client's request to list all connected clients.
LIST_CLIENTS
Server's reply to LIST_CLIENTS
client0
— server given client IDclientUserAgent0
— client's user agent- ...
DELIVERY_FAILED
Server's report of failed private message delivery.
CLIENT_NOT_FOUND
Server's report of failed private message delivery due to absence of client with specified ID.
REQUEST_DIALOG
dialogID
reason
ACCEPTED
dialogID
REFUSED
LEAVE
Default handlers
MoveTo
Move to the specified point.
field
x
y
Starting the Bus Server
There are two methods for starting the Bus Server: Automatically start it with your (first) openkore instance, with the proper switches in sys.txt, or Manually, initiating the server from the command line.
Scenarios
- On a machine with multiple instances of Openkore running, the first instance with "bus 1" in sys.txt that starts will load the bus server. All other instances will check for the port number saved in your temporary directory, and connect to the same Bus Server.
- If your Openkore instances are run from multiple machines, starting the bus manually, with a specific port will allow all bus clients (Openkore instances, independant perl scripts, standalone programs) to connect to the same Bus Server.
Automatic Bus Server
Automatically starting the Bus Server works the same for all supported OS platforms.
Simply define "bus 1" in sys.txt for all Openkore instances; the first to load will start the server.
Typical uses:
- One machine, multiple Openkore instances
- One machine, one or more Openkore instances, one or more standalone bus-aware perl scripts running on the same machine
Manually Loaded Bus Server
Manually loading up the Bus Server is a better solution a mixed network, in several senses:
- Openkore instances are running on more than one machine
- Standalone perl scripts that may start before any Openkore instances are loaded
In these cases, to allow diversely hosted programs to connect to the Bus Server, the server must be started on a predetermined host and port. The following will detail the command line switches, and how to load your Bus Server.
Command Line Switches
Command line switches can be detailed by loading the Bus Server perl script with the --help switch.
- --bind=<host>
- Specify a particular hostname or IP address the bus will listen on. This is equivalent to bus_server_host in sys.txt
- --help
- Displays help for all switches.
- --port=<portnumber>
- Start the server at the specified port. Leave empty to use the first available port. Otherwise, acceptable port range is 1..65535.
- --quiet
- Supresses status messages.
Windows platform
The majority of Openkore users on windows either use the console start.exe, or the GUI wxstart.exe to load their bot.
To start the bus server with a specified port, use the appropriate commandline:
Console:
start.exe "!" "src/Bus/bus-server.pl" --port=1337
Wx GUI:
wxstart.exe "!" "src/Bus/bus-server.pl" --port=54321
Users can either create batch files to load these, or alter a shortcut that loads the executable with these arguments.
Unix platform / Openkore run directly in a perl intepreter
From your shell, in the directory where openkore.pl is located, you can load up the bus server independant of any Openkore instances, with a fixed port so:
yourlogin ~> ./src/Bus/bus-server.pl --port=31416
Protocol description
I call the message format the "Simple Serializable Message" (SSM). This message format is binary.
A message contains the following information:
- A message identifier (MID). This is a string, which can be anything.
- A list of arguments. This is either a list of key-value pairs (a key-value map), or a list of scalars (an array).
A message is very comparable to a function call. Imagine the following C++ function:
void copyFile(string from, string to); copyFile("foo.txt", "bar.txt");
- The message ID would be "copyFile".
- The key/value pairs would look like this:
from = foo.txt to = bar.txt
Message structure
Note that all integers are big-endian.
Header
Each message starts with a header:
struct { // Header uint32 length; // The length of the entire message, in bytes. uint8 options; // The message type: 0 = key-value map, 1 = array. uint8 MID_length; // The message ID's length. char MID[MID_length]; // The message ID, as a UTF-8 string. } Header;
If options is set to 0, then what comes after the header is a list of MapEntry structures, until the end of the message.
If options is set to 1, then what comes after the header is a list of ArrayEntry structures, until the end of the message.
Key-value map entry
struct { uint8 key_length; // Length of the key. char key[key_length]; // UTF-8 string. uint8 value_type; // Value type: 0 = binary, 1 = UTF-8 string, 2 = unsigned integer uint24 value_length; // Length of the value. char value[value_length]; // The value data. } MapEntry;
Array entry
struct { uint8 type; // Like MapEntry.value_type uint24 length; char value[length]; } ArrayEntry;