Difference between revisions of "LSL Protocol/rfedip"

From Second Life Wiki
Jump to navigation Jump to search
(added second example device)
 
(22 intermediate revisions by 3 users not shown)
Line 1: Line 1:
==Ratany fine Engineerings Device Interface Protocol (rfedip)==
==Ratany fine Engineerings Device Interface Protocol (rfedip)==


===About===
The Ratany fine Engineering Device Interface Protocol (short: rfedip) is an extensible protocol for the detection of compliant devices and for the handling of the communication between such devices.


The Ratany fine Engineering Device Interface Protocol (short: rfedip) is
The protocol defines '''how''' devices can be detected and rules for '''how''' the devices can communicate.  It does '''not''' define the very content of such communications, with the exception of a few so-called control messages.
an extensible protocol for the detection of compliant devices and for
the handling of the communication between such devices.


The protocol defines '''how''' devices can be detected and rules for
'''how''' the devices can communicate.  It does '''not''' define the
very content of such communications, with the exception of a few
so-called control messages.


The protocol has been created because there doesn´t seem to be any sort of standard to detect devices.  Devices can be leashing posts, furniture, collars, cuffs and chastity belts as well as any other gadget the design of which involves interoperability or some sort of communication with other devices.


The protocol has been created because there doesn´t seem to be any sort
Using sensors to scan for devices has turned out to be an insufficient alternativeThe sensor range is inevitably limited, and particular prims --- which may be the actual points of an object to tie a leash to --- of objects cannot be detected with a sensor.
of standard to detect devicesDevices can be leashing posts,
furniture, collars, cuffs and chastity belts as well as any other gadget
the design of which involves interoperability or some sort of
communication with other devices.


Using sensors to scan for devices has turned out to be an insufficient
Using existing protocols like the lockmeister protocol also turned out not to be feasibleA device using the lockmeister protocol only becomes active from an agent sitting on the device or touching the device, and the device does itself not listen for queries to identify itself.
alternativeThe sensor range is inevitably limited, and particular
prims --- which may be the actual points of an object to tie a leash
to --- of objects cannot be detected with a sensor.


Using existing protocols like the lockmeister protocol also turned out
At the time of this writing, open collar items seem to employ some sort of protocol and maybe even some sort of APIHowever, these are not documented, and development appears to be in progress such that significant changes might be implemented.
not to be feasibleA device using the lockmeister protocol only
becomes active from an agent sitting on the device or touching the
device, and the device does itself not listen for queries to identify
itself.


At the time of this writing, open collar items seem to employ some
Other protocols may exist that are vendor-specific.  Insofar their designs have not been disclosed, they are unsuited to achieve compatibility and interoperability of devices made by different creatorsA device that lacks compatibility with other devices may have no more than a very limited use for the user of the device, especially when the functionality of the device genuinely requires that the device works with others.
sort of protocol and maybe even some sort of APIHowever, these are
not documented, and development appears to be in progress such that
significant changes might be implemented.


Other protocols may exist that are vendor-specific.  Insofar their
The Ratany fine Engineering Device Interface Protocol is intended to encourage and to help with the creation of devices that are compatible with others.
designs have not been disclosed, they are unsuited to achieve
compatibility and interoperability of devices made by different
creators.  A device that lacks compatibility with other devices may have
no more than a very limited use for the user of the device, especially
when the functionality of the device genuinely requires that the device
works with others.


The Ratany fine Engineering Device Interface Protocol is intended to
encourage and to help with the creation of devices that are compatible
with others.


The availability of a useful protocol does not mean that creators of devices will be inclined to use it.  Please consider this article as a draft with the intention to create a useful protocol which hopefully might be used by many creators --- and as a request for comments. Please feel free to use the discussion page of this article to add your suggestions and ideas, or to contact me directly.


The availability of a useful protocol does not mean that creators of
==Editing this Article==
devices will be inclined to use it.  Please consider this article as a
draft with the intention to create a useful protocol which hopefully
might be used by many creators --- and as a request for comments.
Please feel free to use the discussion page of this article to add your
suggestions and ideas, or to contact the author directly.


This article is written in my favourite editor.  When I update the article on the wiki, I edit the whole article, delete everything and paste the new version in.  That´s simply the easiest and most efficient way for me.


===Requirements brought upon a protocol===
Unfortunately, this means that your modifications may be lost when I update the article and don´t see them.
 
'''Please use the "discussion" page of this article to post contributions or to suggest changes so they can be added to the article.'''
 
==Requirements brought upon a protocol==


The protocol should be:
The protocol should be:


* available
* available
Line 73: Line 47:




===Specification===
==Specification==


====In General====
===In General===


The rfedip protcol uses strings of characters that can be transfered
The rfedip protcol uses strings of characters that can be transfered as messages of a type which can be sent by functions like llMessageLinked(), llRegionSayTo(), llRegionSay(), llShout(), llSay(), llWhisper() as well as in the body of an email or via the use of the HTTP protocol.
as messages of a type which can be sent by functions like
llMessageLinked(), llRegionSayTo(), llRegionSay(), llShout(), llSay(),
llWhisper() as well as in the body of an email or via the use of the
HTTP protocol.


An rfedip message is a string which is divided into fields by the use
An rfedip message is a string which is divided into fields by the use of a special character as separator.  The fields of a message are defined as follows, and they appear in the message in the order they are listed here:
of a special character as separator.  The fields of a message are
defined as follows, and they appear in the message in the order they
are listed here:




Line 98: Line 65:




All devices compliant with the rfedip protocol must have available
All devices compliant with the rfedip protocol must have available documentation about all the tokens and, if applicable, the parameters used with the tokens the device supports.  The documentation must include the tokens and parameters the device understands, as well as the tokens and parameters it might use itself to communicate with other rfedip-compliant devices.
documentation about all the tokens and, if applicable, the parameters
used with the tokens the device supports.  The documentation must
include the tokens and parameters the device understands, as well as
the tokens and parameters it might use itself to communicate with
other rfedip-compliant devices.


This is to ensure that creators and users of rfedip-compliant devices
This is to ensure that creators and users of rfedip-compliant devices can refer to the documentation of devices created by others and make the devices they are creating compatible with devices created by others, if they so choose.
can refer to the documentation of devices created by others and make
the devices they are creating compatible with devices created by
others, if they so choose.


A device for which such documentation is not freely available cannot
A device for which such documentation is not freely available cannot be considered as compliant or compatible with the rfedip protocol.
be considered as compliant or compatible with the rfedip protocol.




====Definitions====
===Definitions===


The rfedip protocol itself defines:
The rfedip protocol itself defines:


* a string specifying the protocol version: "RFEDIP-1.0"
* a string specifying the protocol version: "RFEDIP-1.0"
Line 146: Line 105:
* that messages involving tokens as defined here must be sent on the default channel for they are considered control messages (i. e. messages like identification requests, responses to those, opening channels, signalling the end of a communication (sequence))
* that messages involving tokens as defined here must be sent on the default channel for they are considered control messages (i. e. messages like identification requests, responses to those, opening channels, signalling the end of a communication (sequence))


* that devices should respond with the protocol message indicating the end of communication for tokens they do not understand to potentially save other devices unnesessary waiting times


===Example Implementation===
* that devices must not respond to the protocol message indicating the end of communication because doing so could create message loops


====First Example: A device providing chaining points====
==Reference Implementation: A Tethering Device==


For an example implementation, a simple rfedip-compliant device will
The tethering device can be queried for the UUIDs of prims to which chains or leashes can be attached.  The token to query the device is "tether".  The device replies with the token "tether point", followed by the UUID of a chaining point.  Each chaining point is reported in a seperate response message, one chaining point per message.  When all chaining points have been reported, this devices sends a RFEDIP_protEND control message.
be created here that allows another device to query the UUIDs of
so-called chain points of an object.


Since the author is using cpp as preprocessor for LSL scripts (and
other tools), the actual source code for the device will be provided
first.  The LSL script that is the result of the compilation process
will be given below.  Even without full syntax highlighting, the
source is IMO much easier to read than the actual script.


You do not need to use cpp or other tools to make use of the
<source lang="lsl2">
rfedip-protocol.  How you actually implement the protocol is
list getlinknumbersbyname(string name)
completely up to you, this is merely an example.
{
integer n = llGetObjectPrimCount(llGetLinkKey(llGetLinkNumber()));
list numbers = [];
name = llToLower(name);


To create the device, a generic template is used.  It´s called a
if(n > 1)
generic template because it doesn´t contain more than what is needed
{
to answer identification requests and to check whether a received
while(n)
message appears to be a valid rfedip-protocol message.  For anything
{
beyond that, a function (named rfedip_handle()) is called.  (This has
if((~llSubStringIndex(llToLower(llList2String(llGetLinkPrimitiveParams(
the advantage that the function can be defined in a script for a
                                    n, [PRIM_NAME]), 0)), name)))
particular device which simply includes the template, and many
{
different devices can be made which all use the same template.)
numbers += n;
}


You can think of this template as a script that handles some control
--n;
messages of the protocol which are not device-specific.
}
}
else
{
if((~llSubStringIndex(llToLower(llGetObjectName()), name)))
{
numbers += n;
}
}


return numbers;
}


<lsl>
key kThisDevice;
list lHooks;


// Ratnay fine Engineering Device Interface Protocol
default
//
{
// generic example template
state_entry()
//
{
// remember the link numbers of the chain targets
//
lHooks = getlinknumbersbyname("hook");


if(!(llGetListLength(lHooks)))
{
lHooks = (list)llGetLinkNumber();
}


#include <lslstddef.h>
kThisDevice = llGetLinkKey(llGetLinkNumber());
#include <rfedip.h>
llListen(-20131224, "", NULL_KEY, "");
}


 
changed(integer w)
default
{
event state_entry()
{
{
// permanently listen on the protocol channel
// maybe do something else here if you need to sit on
// this device
//
//
llListen(RFEDIP_CHANNEL, "", NULL_KEY, "");
if(w & CHANGED_LINK)
{
kThisDevice = llGetLinkKey(llGetLinkNumber());
lHooks = getlinknumbersbyname("hook");
 
if(!(llGetListLength(lHooks)))
{
lHooks = (list)llGetLinkNumber();
}
}
}
}


event listen(int channel, string name, key other_device, string _MESSAGE)
listen(integer channel, string name, key other_device, string _MESSAGE)
{
{
IfMessage(RFEDIP_protIDENTIFY_QUERY)
if("identify" == _MESSAGE)
{
{
// Indistinctively answer queries that want to
// Indistinctively answer queries that want to
Line 213: Line 196:
// is the UUID of this device.
// is the UUID of this device.
//
//
// With
llRegionSayTo(other_device, -20131224, llDumpList2String([kThisDevice,
//
              other_device, "RFEDIP-1.0", "identity"], "|"));
//  RFEDIP_RESPOND(<recipient-uuid>, <sender-uuid>, <protocol-payload>);
//
// an answer is sent to the device from which
// this device has received the request to
// identify itself:
//
#define this_device  llGetLinkKey(llGetLinkNumber())  // the UUID of this device
 
RFEDIP_RESPOND(other_device, this_device, RFEDIP_CHANNEL, RFEDIP_protIDENTIFY);
return;
return;
}
}
Line 238: Line 212:
// Please do not confuse incoming messages with
// Please do not confuse incoming messages with
// outgoing messages!
// outgoing messages!
//
 
// Parameters can be tokens.  However, this is not
// recommended.  Send multiple messages rather than
// multiple tokens in one message.
//
// The received message is put into a list for further
// The received message is put into a list for further
// processing:
// processing:
//
//
list payload = ProtocolData(RFEDIP_sSEP);
list payload = llParseString2List((_MESSAGE), ["|"], []);


// Attempt to verify whether the message looks valid:
// Attempt to verify whether the message looks valid:
//
//
if(Len(payload) < RFEDIP_iMINMSGLEN) return;
if(llGetListLength(payload) < 4)
{
return;
}


// Attempt to make sure that the message is for this
// Attempt to make sure that the message is for this
Line 258: Line 231:
//  device they claim to be sent from by verifying
//  device they claim to be sent from by verifying
//  the sender given in the message with the actual
//  the sender given in the message with the actual
//  sender:
//  sender
//
#define InvalidSender (RFEDIP_ToSENDER(payload) != other_device)
//
//
// + ignore messages that appear not be destined for
// + ignore messages that appear not be destined for
//  this device by verifying the recipient given in
//  this device by verifying the recipient given in
//  the message with what this device actually is:
//  the message with what this device actually is
//
#define NotDestined  (RFEDIP_ToRCPT(payload) != this_device)
//
//
// + ignore messages that request a different version
// + ignore messages that request a different version
Line 272: Line 241:
//  given in the message --- in this example, a check
//  given in the message --- in this example, a check
//  for what is considered a "sufficient version" is
//  for what is considered a "sufficient version" is
//  applied:
//  applied
//
//
#define BadVersion    !Instr(RFEDIP_ToPROTVERSION(payload), RFEDIP_sSUFFICIENT_VERSION)
if((llList2Key(payload, 0) != other_device)
//
  || (llList2Key(payload, 1) != kThisDevice)
when(InvalidSender || NotDestined || BadVersion) return;
  || !(~llSubStringIndex(llList2String(payload, 2), "RFEDIP")))
 
{
#undef this_device
return;
#undef InvalidSender
}
#undef NotDestined
#undef BadVersion


// From here on, the capabilities of the device can be
// From here on, the capabilities of the device can be
// implemented.
// implemented.
// Here: Report the chain target points and send of of
// communication message.
//
//
// Since this is a template, just call a handling
if(llList2String(payload, 3) == "tether")
// function so the template can simply be included
{
// into a script that implements such capabilities.
integer n = llGetListLength(lHooks);
//
rfedip_handle(payload);
}
}
</lsl>


 
while(n)
As you can see, above template uses (#includes) the two files
"lslstddef.h" and "rfedip.h".  Providing "lslstddef.h", and how to use
cpp as a preprocessor, is far outside the scope of this article.  Only
"rfedip.h" is required here:
 
 
<lsl>
 
 
// defines for Ratany fine Engineerings Device Interface Protocol
 
#ifndef _RFEDIP
#define _RFEDIP
 
#define RFEDIP_sVERSION                          "RFEDIP-1.0"  // protocol version
#ifndef RFEDIP_sSUFFICIENT_VERSION
#define RFEDIP_sSUFFICIENT_VERSION                "RFEDIP"  // default to any version as sufficient version
#endif
#define RFEDIP_sSEP                              "|"  // separator used in protocol messages
 
#define RFEDIP_CHANNEL                            -20131224  // default channel used for protocol messages
 
#define RFEDIP_protIDENTIFY_QUERY                "identify"  // identification query, must be responded to with RFEDIP_protIDENTIFY
#define RFEDIP_protIDENTIFY                      "identity"  // answer to identification queries
#define RFEDIP_protEND                            "msg_end!"  // indicate end of data transfer; if a particular channel was opened, this channel can be closed
#define RFEDIP_protOPEN                          "openchan"  // open a particular channel for further communication, mandatorily has a channel number as parameter
 
#define RFEDIP_ToSENDER(_l)                      llList2Key(_l, 0)  // return UUID of sender from list _l
#define RFEDIP_ToRCPT(_l)                        llList2Key(_l, 1)  // return UUID of recipient from list _l
#define RFEDIP_ToPROTVERSION(_l)                  llList2String(_l, 2) // return string containing the version of the protocol from list _l
#define RFEDIP_ToFirstTOKEN(_l)                  llList2String(_l, 3)  // return the first token from list _l
#define RFEDIP_ToFirstPARAM(_l)                  llList2String(_l, 4) // return the first parameter
#define RFEDIP_ToRESPONSE(_sndr, _rcpt, ...)      llDumpList2String([_sndr, _rcpt, RFEDIP_sVERSION, __VA_ARGS__], RFEDIP_sSEP)  // convert a protocol payload into a protocol message
#define REFDIP_OPEN(_rcpt, _sndr, _nchan)        llRegionSayTo(_rcpt, RFEDIP_CHANNEL, RFEDIP_ToRESPONSE(_sndr, _rcpt, RFEDIP_protOPEN, _nchan))  // open a particular channel for communication
#define RFEDIP_END(_rcpt, _sndr, _nchan)          llRegionSayTo(_rcpt, RFEDIP_CHANNEL, RFEDIP_ToRESPONSE(_sndr, _rcpt, RFEDIP_protEND, _nchan))  // indicate end of communication on channel _c
#define RFEDIP_IDQUERY                            llRegionSay(RFEDIP_CHANNEL, RFEDIP_protIDENTIFY_QUERY)  // ask all rfedip compliant devices to identify themselves
#define RFEDIP_RESPOND(_rcpt, _sndr, _chan, ...)  llRegionSayTo(_rcpt, _chan, RFEDIP_ToRESPONSE(_sndr, _rcpt, __VA_ARGS__))
 
#define RFEDIP_iMINMSGLEN                        4  // used to figure out whether a message is a RfE-dip message or not
 
 
#endif  // _RFEDIP
</lsl>
 
 
To create the actual device for this example, the script that provides
the function which handles the device-specific protocol messages is
also needed.  It simply defines the function to report the chain
points on request:
 
 
<lsl>
 
// RfE-dip compatible generic chaining device
//
// "Generic" means that all prims of the object (i. e. device) this
// script is in which are named sCHAINPOINT (i. e. "hook", see below)
// will be reported via the Ratany fine Engineering device interface
// protocol when a compatible device queries them.  In case there are
// no prims with that name and the script is in the root prim, the
// root prim will be reported.
 
 
// some standard definitions
//
#include <lslstddef.h>
 
// use the getlinknumbersbyname() function from a library that
// provides functions to deal with names and descriptions of prims in
// order to figure out link numbers
//
#define _USE_getlinknumbersbyname
#include <getlinknumbers.lsl>
 
// some standard definitions for rfedip:
//
#include <rfedip.h>
 
// some definitions needed by devices that communicate with this
// device
//
#include <rfedip-generic-chainpoints.h>
 
 
// the prims of the device that are points to attach chains to are
// named "hook", unless otherwise defined
#ifndef sCHAINPOINT
#define sCHAINPOINT                "hook"
#endif
 
// When a request for chaining points has been received, respond with
// the UUID of each chaining point of this device.  Once all the
// available points are reported, the the RFEDIP_protEND token is sent
// to indicate that the data transfer is complete.
//
// Report one chaining point per message to simplify parsing for the
// receiver and to avoid problems with maximum message length.
//
// rfedip_handle() is called from the RfE-dip-template.  In this case,
// no more than a few lines are needed which can easily be inlined.
// (Inlining can save memory compared to actually using a function and
// may run faster.)
//
#define rfedip_handle(_data) \
when(RFEDIP_ToFirstTOKEN(_data) == protTETHER) \
{ \
list hooks = getlinknumbersbyname(sCHAINPOINT); \
key sender = RFEDIP_ToSENDER(_data); \
key this = llGetLinkKey(llGetLinkNumber()); \
if(!Len(hooks) && (llGetLinkNumber() < 2)) hooks = (list)llGetLinkNumber(); \
int n = Len(hooks); \
LoopDown(n, RFEDIP_RESPOND(sender, this, RFEDIP_CHANNEL, protTETHER_RESPONSE, llGetLinkKey(llList2Integer(hooks, n)))); \
RFEDIP_END(sender, this, RFEDIP_CHANNEL); \
return; \
}
 
// Simply use the RfE-dip template as is.
//
// For devices that consist of many prims, it is better to use a
// global variable for the list of hooks which isn´t updated on every
// query.  With such devices, use the template directly here rather
// than including it, and adjust it accordingly.  For the purpose of
// this example, things are kept simple.
//
#include <rfedip-template.lsl>
</lsl>
 
 
As you can see, "rfedip-generic-chainpoints.h" is included, which is
relevant for this article:
 
 
<lsl>
 
 
// device specific protocol payload; for this device, this is
// intentionally very similar to lockmeister
//
// The device specific protocol payload is what must be documented for
// each device to be compatible.  In this case, for example:
//
// "The device can be queried for the UUIDs of prims to which chains
// can be attached.  The token to query the device is "tether".  The
// device replies with the token "tether point", followed by the UUID
// of a chaining point.  Each chaining point is reported in a seperate
// response message, one chaining point per message.  Once all points
// have been reported, the device signals end of communication. ..."
//
#define protTETHER                "tether"
#define protTETHER_RESPONSE        "tether point"
</lsl>
 
 
The compilation process delivers the LSL script which you put into the
object that becomes an rfedip-compliant device.  Of course, when you
do not use cpp and other tools, you would write such a script
"directly":
 
 
<lsl>
list getlinknumbersbyname(string name)
{
integer n = llGetObjectPrimCount(llGetLinkKey(llGetLinkNumber() ) );
list numbers = [];
name = llToLower(name);
if(n > 1)
{
while(n)
{
if((~llSubStringIndex(llToLower(llList2String(llGetLinkPrimitiveParams(n, [PRIM_NAME] ), 0) ), name) ) )
{
{
numbers += n;
--n;
llRegionSayTo(other_device, -20131224, llDumpList2String([kThisDevice,
              other_device, "RFEDIP-1.0", "tether point", llGetLinkKey(llList2Integer(lHooks,
                      n))], "|"));
}
}
--n;
}
}
}
 
else
if(!(llList2String(payload, 3) == "msg_end!"))
{
if((~llSubStringIndex(llToLower(llGetObjectName() ), name) ) )
{
{
numbers += n;
// when receiving messages not handled by this device,
// indicate end of communication to potentially save
// other devices unnecessary waiting times
//
// do not answer with com/end to com/end messages to avoid message loops!
//
llRegionSayTo(other_device, -20131224, llDumpList2String([kThisDevice, other_device, "RFEDIP-1.0", "msg_end!", -20131224], "|"));
}
}
}
}
return numbers;
 
}
on_rez(integer p)
default
{
state_entry()
{
{
llListen(-20131224, "", NULL_KEY, "");
// UUID changes when rezzed
}
//
listen(integer channel, string name, key other_device, string _MESSAGE)
kThisDevice = llGetLinkKey(llGetLinkNumber());
{
if("identify" == _MESSAGE)
{
llRegionSayTo(other_device, -20131224, llDumpList2String([llGetLinkKey(llGetLinkNumber()), other_device, "RFEDIP-1.0", "identity"], "|"));
return;
}
list payload = llParseString2List((_MESSAGE), ["|"], []);
if(llGetListLength(payload) < 4) return;
if((llList2Key(payload, 0) != other_device) || (llList2Key(payload, 1) != llGetLinkKey(llGetLinkNumber())) || !(~llSubStringIndex(llList2String(payload, 2), "RFEDIP") )) return;
if(llList2String(payload, 3) == "tether")
{
list hooks = getlinknumbersbyname("hook");
key sender = llList2Key(payload, 0);
key this = llGetLinkKey(llGetLinkNumber());
if(!llGetListLength(hooks) && (llGetLinkNumber() < 2)) hooks = (list)llGetLinkNumber();
integer n = llGetListLength(hooks);
while(n)
{
--n;
llRegionSayTo(sender, -20131224, llDumpList2String([this, sender, "RFEDIP-1.0", "tether point", llGetLinkKey(llList2Integer(hooks, n))], "|"));
};
llRegionSayTo(sender, -20131224, llDumpList2String([this, sender, "RFEDIP-1.0", "msg_end!", -20131224], "|"));
return;
};
}
}
}
}
</lsl>
</source>




'''Note:''' This device does not work when worn as an attachment, see
You can find the source for this device in the [git://dawn.adminart.net/lsl-repo.git git repository] and further documentation in this [[How_to_make_writing_LSL_scripts_easier|article]].
[[LlGetObjectPrimCount|llGetObjectPrimCount()]].


====Second Example: A device using chaining points====
==Reference Implementation: A device using chaining points==


The second example is a device that queries the device in the first
This device can send queries for the UUIDs of prims to which chains can be attached.  The token used for the query is "tether"This device expects answers to this query to use the token "tether point", followed by the UUID of the point as parameter. It expects devices that have received a query to indicate that all their UUUIDs have been reported by sending a a RFEDIP_protEND control message, regardless whether any points have been reported or not.
example for the chaining points and uses thoseYou can use this device
to automatically tie up your boat (none of the boats the author has seen
in their berths in sl were really tied up ...), to nicely tie an agent
to some piece of furniture, or for whatever else comes to mind.


This device can be queried for the UUIDs of prims to which chains can be attached.  The token to query the device is "tether".  The device replies with the token "tether point", followed by the UUID of a chaining point.  Each chaining point is reported in a seperate response message, one chaining point per message.  When all chaining points have been reported, this devices sends a RFEDIP_protEND control message.


On a side note: The problem is this: Attach N chains to X chain points
When this device has not created chains itself, it does not report its chain target points but sends a RFEDIP_protEND control message instead.
which are around.  Make it so that there is no more than one chain going
to each chain point, and make sure the chains are each attached to the
chain point closest to them, if possible.  The result must not look
retarded, and it can be ignored that it is sometimes advisable to cross
the chains over.


Unless they get too many chains to deal with, humans solve this problem
without even thinking.  It is obvious where each chain needs to go and
extremely easy to solve.  Therefore, one would expect an algorithm that
solves this problem to be equally simple.  Surprisingly, it is not.


The implementation happened to grow quite a bit larger than what I had expected.  What I expected is that it is simple to link a couple chains from an object to a couple points that are somewhere around.  Pfff!


This example starts with the template from the first example, using a
It is simple to link each chain to the point closest to it.  When you do that, the result looks retarded in most cases because the chains may get entangled or go across the object, and several chains may go to the same point.  That was ridiculous and not acceptable.
modified version of it.  Things have been added to the template because
this device requires them, and the template is not #included but used
"directly".


To point out the part which is more relevant for the example, functions
Humans solve the problem without thinking.  For example, they tie their boats to the tethering points around the berths their boats are in without entangling all the ropes.  They do not attach the ropes in such a way that they go across the boat.  Humans are flexible and sometimes cross over some ropes or have multiple ropes go to the same point when the conditions let this appear to be advisable.  The result never looks retarded because the way it´s done makes sense.
and definitions have been put into a separate file which is #included.


Even children do it right.  I couldn´t figure out how I would do it because I would just do it.  People I asked didn´t know how to do it, either.  The problem is too simple.  This made it surprisingly difficult to create an algorithm which yields results that don´t look retarded for at least most conditions.  It took a lot of experimentation and was fun to create.


<lsl>
What this algorithm does, and how it does it, is irrelevant for the purpose of this article.  I´ll only put the more relevant part into the article.  You can find the whole source in the [git://dawn.adminart.net/lsl-repo.git git repository] and further documentation in this [[How_to_make_writing_LSL_scripts_easier|article]].


// Ratany fine Engineering Interface Device Protocol example
//
// implementation of a device that queries another rfedip device for
// chaining points and then makes use of the points.
//
// Use this device to tie up your boat or something :)


 
<source lang="lsl2">
// include the stuff that isn´t entirely relevant for the purpose of
// this example and needed for this device --- this is only so to make
// things easier to read
//
#include <rfedip-second-example-header.h>
 
 
// This is a version of the template modified for this particular
// device.
//
default
default
{
{
event listen(int channel, string name, key other_device, string _MESSAGE)
listen(integer channel, string name, key other_device, string _MESSAGE)
{
{
// identify this device
//
//
IfMessage(RFEDIP_protIDENTIFY_QUERY)
// This first part is the same as with the tethering device.
//
 
if("identify" == _MESSAGE)
{
{
RFEDIP_RESPOND(other_device, llGetLinkKey(llGetLinkNumber()), RFEDIP_CHANNEL, RFEDIP_protIDENTIFY);
llRegionSayTo(other_device, -20131224, llDumpList2String([kThisDevice,
              other_device, "RFEDIP-1.0", "identity"], "|"));
return;
return;
}
}


// verify whether the received message looks like a
list payload = llParseString2List((_MESSAGE), ["|"], []);
// valid refdip-protocol message
//
list payload = ProtocolData(RFEDIP_sSEP);
if(Len(payload) < RFEDIP_iMINMSGLEN) return;
when((RFEDIP_ToSENDER(payload) != other_device) || (RFEDIP_ToRCPT(payload) != llGetLinkKey(llGetLinkNumber())) || !Instr(RFEDIP_ToPROTVERSION(payload), RFEDIP_sSUFFICIENT_VERSION)) return;


// extract the token from the rfedip message ...
if(llGetListLength(payload) < 4)
//
{
string token = RFEDIP_ToFirstTOKEN(payload);
return;
}


//
if((llList2Key(payload, 0) != other_device)
// ... to do some device specific stuff
  || (llList2Key(payload, 1) != kThisDevice)
//
  || !(~llSubStringIndex(llList2String(payload, 2), "RFEDIP")))
// The order of the message handling has been arranged
// for better performance.
//
 
when(protTETHER_RESPONSE == token)
{
// THIRD: Receive the responses to the query for points to attach chains to
//        and remember those points.
//
Enlist(lTargets, RFEDIP_ToFirstPARAM(payload));
return;
}
 
when(RFEDIP_protEND == token)
{
IfStatus(stRECEIVING)
{
// Receiving an end-message means that the device which sent it has finished
// answering.
//
--iDevicesAround;
iDevicesAround = Max(iDevicesAround, 0);  // ignore unexpected end-msgs
 
// FOURTH: When all answers have been received, make the chains.
//        In case an end-message gets lost, the timeout kicks in.
//
unless(iDevicesAround) mkchains();
}
 
return;
}
 
when(RFEDIP_protIDENTIFY == token)
{
// SECOND: Ask a device that identifies itself for points to attach chains to.
//
++iDevicesAround;
RFEDIP_RESPOND(other_device, llGetLinkKey(llGetLinkNumber()), RFEDIP_CHANNEL, protTETHER);
return;
}
 
// when receiving messages not handled by this device,
// indicate end of communication to potentially save
// other devices unnecessary waiting times
//
RFEDIP_END(other_device, llGetLinkKey(llGetLinkNumber()), RFEDIP_CHANNEL);
}
 
event touch_start(int t)
{
unless(Len(lChains))
{
aftell("no chains to create");
return;
}
 
IfStatus(stBUSY)
{
{
aftell("busy, please wait");
return;
return;
}
}


SetStatus(stBUSY);
string token = llList2String(payload, 3);
LoopChains(xChainHide(_n); yChainUnlink(_n));
aftell("querying for devices");
SoundPing;
lTargets = [];


// keep track of how many devices are detected to be
// able to decide whether all answers from all devices
// have been received or not
//
//
iDevicesAround = 0;
// The rest is device specific.  Tokens in received
 
// protocol messages trigger various actions to make
// there isn´t a way around a timeout with this device :(
// the device to its job.
//
//
llSetTimerEvent(fTIMER_UNWAIT);


// FIRST: query for rfedip-compliant devices
if("tether point" == token)
//
{
RFEDIP_IDQUERY;
// [...]
SetStatus(stRECEIVING);
}
}


event timer()
if("msg_end!" == token)
{
{
// A message indicating end of communication might not
// when all devices have indicated end of communication,
// have arrived.  Go ahead with whatever information
// create the chains
// has been received so far.
//
//
if((!!(status & 2)))
aftell("timeout");
mkchains();
}
 
event state_entry()
{
ClrStatus;
yChainsInit;
 
llListen(RFEDIP_CHANNEL, "", NULL_KEY, "");
}
 
event changed(int w)
{
when(w & CHANGED_LINK)
{
{
// maybe do something else here if you need to sit on this device
--iDevicesAround;
//
iDevicesAround = (((llAbs((iDevicesAround) >= (0))) * (iDevicesAround)) + ((
LoopChains(xChainHide(_n); yChainUnlink(_n));
                      llAbs((iDevicesAround) < (0))) * (0)));
yChainsInit;
}
}
}
</lsl>


 
if(!(iDevicesAround))
This is basically it.  The listener event handles the rfedip-protocol
messages and acts upon the tokens.  Most of what is in the other events
is specific for this device.
 
The functionality of this device is implemented in what´s in
"rfedip-second-example-header.h".  It´s a bit lengthy, and you can
safely skip this part:
 
 
<lsl>
 
// header file for rfedip-example device
//
// Contains stuff that isn´t entirely relevant for the purpose of the
// example.
 
 
// some standard definitions
//
#include <lslstddef.h>
#include <colordef.h>
 
// use the getlinknumbersbylistnamedappend_attached() function from a
// library that provides functions to deal with names and descriptions
// of prims in order to figure out link numbers
//
#define _USE_getlinknumbersbylistnamedappend_attached
#define _GETLINKNUMBERSBYLISTNAMEDAPPEND_ATTACHED_APPENDIX [NULL_KEY]
#include <getlinknumbers.lsl>
 
// use the geom_linecrosspoint() function from a library that provides
// functions that deal with geometry
//
#define _GEOM_USE_geom_linesegmentscross
#include <geometry.lsl>
 
// some standard definitions for rfedip:
//
#include <rfedip.h>
 
// some definitions from devices that communicate with this
// device
//
#include <rfedip-generic-chainpoints.h>
 
 
// The standard library (lslstddef.h) provides status handling, which
// requires an integer to store the status´ in:
//
int status;
#define stBUSY                    1
#define stRECEIVING                2
 
 
// This particular device can have multiple chains, created from the
// prims with names listed here.  Add as many chains as you like,
// until the script runs out of memory.
//
#define lCHAINTOS                  ["Chain-0", "Chain-1", "Chain-2", "Chain-3", "Chain-4", "Chain-5", "Chain-6", "Chain-7"]
 
 
int iDevicesAround;                    // keep track of how many other devices have been found
#define iSTRIDE_lChains            3    // lChains is a strided list: [prim name, link number, uuid of target]
#define iINDEXOFFSET_lChains      2    // needed to deduct which chain is linked to a given target point
list lChains;
list lTargets;                          // a list of uuids of prims to leash to, as reported via rfedip
 
 
// a few macro definitions to deal with the chains
//
// Consider them as a demonstration of the incredible usefulness of a
// preprocessor.
//
#define CHAINS_lPARTICLE_CHAIN(_n)  [ \
      PSYS_PART_MAX_AGE, 3.0, \
                                      PSYS_PART_FLAGS, PSYS_PART_FOLLOW_VELOCITY_MASK | PSYS_PART_TARGET_POS_MASK | PSYS_PART_FOLLOW_SRC_MASK | PSYS_PART_EMISSIVE_MASK, \
                                      PSYS_PART_START_COLOR, RandomColor, \
                                      PSYS_PART_START_SCALE, <0.2, 0.2, 0.0>, \
                                      PSYS_SRC_PATTERN, PSYS_SRC_PATTERN_DROP, \
                                      PSYS_SRC_BURST_RATE, 0.04, \
                                      PSYS_SRC_ACCEL, <0.0, 0.0, -1.2>, \
                                      PSYS_SRC_BURST_PART_COUNT, 1, \
                                      PSYS_SRC_BURST_SPEED_MIN, 3.0, \
                                      PSYS_SRC_BURST_SPEED_MAX, 3.0, \
                                      PSYS_SRC_TARGET_KEY, kChainKey(_n), \
                                      PSYS_SRC_MAX_AGE, 0.0, \
                                      PSYS_SRC_TEXTURE, "4cde01ac-4279-2742-71e1-47ff81cc3529" \
    ]
 
 
#define boolChainLinked(_n)        (kChainKey(_n) != NULL_KEY)
#define fChainLength(_c)           llVecDist(vChainPos(_c), vChainEndpos(_c))
#define iChainLinkNo(_n)          llList2Integer(lChains, (_n) + 1)
#define kChainKey(_n)              llList2Key(lChains, (_n) + 2)
#define sChainName(_n)            llList2String(lChains, _n)
#define vChainEndpos(_c)          RemotePos(kChainKey(_c))
#define vChainPos(_chain)          (llGetRootPosition() + llList2Vector(GLPP(iChainLinkNo(_chain), [PRIM_POS_LOCAL]), 0) * llGetRootRotation())
#define xChainHide(_n)            llLinkParticleSystem(iChainLinkNo(_n), [])
#define xChainShow(_n)            llLinkParticleSystem(iChainLinkNo(_n), CHAINS_lPARTICLE_CHAIN(_n))
#define yChainLink(_n, _k)        (lChains = llListReplaceList(lChains, (list)_k, (_n) + 2, (_n) + 2))
#define yChainUnlink(_n)          (lChains = llListReplaceList(lChains, (list)NULL_KEY, (_n) + 2, (_n) + 2))
#define yChainsInit                (lChains = getlinknumbersbylistnamedappend_attached(lCHAINTOS))
 
#define LoopChains(_do)            int _n = Len(lChains); while(_n) { _n -= iSTRIDE_lChains; _do; }
 
#define kTargetKey(_target)        llList2Key(lTargets, _target)
#define vTargetPos(_target)        RemotePos(kTargetKey(_target))
#define fDistChainTarget(_c, _t)  llVecDist(vChainPos(_c), vTargetPos(_t))
 
 
// unfortunately, this device needs a timer because messages can be
// lost, or no compliant devices might be around
//
#define fTIMER_UNWAIT              30.0
 
// sometimes stuff is undetermined
//
#define fUNDETERMINED              -1.0
#define iUNDETERMINED              -1
#define vUNDETERMINED              (<fUNDETERMINED, fUNDETERMINED, fUNDETERMINED>)
 
 
// figure out which chain is attached to a target point
//
// This should be a macro, yet with the ternary operator missing from
// the script language, it´s not reasonable to implement it as a
// macro here.
//
int iChainOfTarget(int t)
{
int x = LstIdx(lChains, kTargetKey(t));
if(~x) return (x - iINDEXOFFSET_lChains);
return iUNDETERMINED;
}
 
 
// figure out which chain target is closest to a given chain point
//
int closest_target(int chain)
{
int ret = iUNDETERMINED;
vector chainpos = vChainPos(chain);
float closest = fUNDETERMINED;
int n = Len(lTargets);
while(n)
{
--n;
 
float distance = llVecDist(vTargetPos(n), chainpos);
if(fUNDETERMINED == closest)
{
{
closest = distance;
mkchains();
ret = n;
}
else
{
if(distance < closest)
{
closest = distance;
ret = n;
}
}
}
}
return;
}
}


return ret;
if("identity" == token)
}
 
 
// compute the average position of all the chain targets
//
vector avgpos_targets()
{
vector avg = vUNDETERMINED;
int cnt = 0;
int t = Len(lTargets);
while(t)
{
{
--t;
// when other devices identify themselves, query them for tether points
 
//
avg += vTargetPos(t);
++iDevicesAround;
++cnt;
llRegionSayTo(other_device, -20131224, llDumpList2String([kThisDevice,
              other_device, "RFEDIP-1.0", "tether"], "|"));
return;
}
}


return (avg / (float)cnt);
if((!!(status & 4)) && ("tether" == token))
}
 
 
// figure out which chain point is the furthest away from a given position
//
int furthest_chain(vector ref)
{
float distmax = fUNDETERMINED;
int ret = iUNDETERMINED;
int c = Len(lChains);
while(c)
{
{
c -= iSTRIDE_lChains;
// answer queries from other devices when this device is chained
//
integer _n = llGetListLength(lChains);


unless(boolChainLinked(c))
while(_n)
{
{
float dist = llVecDist(vChainPos(c), ref);
_n -= 2;
if(distmax < dist)
llRegionSayTo(other_device, -20131224, llDumpList2String([kThisDevice,
{
              other_device, "RFEDIP-1.0", "tether point", llGetLinkKey(llList2Integer(lChains,
distmax = dist;
                      (_n)))], "|"));
ret = c;
}
}
}
}
}


return ret;
if(!("msg_end!" == token))
}
 
 
// swap the target points of two chains
//
void swap_chains(int ca, int cb)
{
key tmp = kChainKey(ca);
 
yChainLink(ca, kChainKey(cb));
yChainLink(cb, tmp);
}
 
 
// figure out which chain is crossed by a given chain
//
int find_chain_crossedby(int chain)
{
vector start = vChainPos(chain);
vector end = vChainEndpos(chain);
 
int c = Len(lChains);
while(c)
{
{
c -= iSTRIDE_lChains;
// when receiving messages not handled by this device,
 
// indicate end of communication to potentially save
when((chain != c) && boolChainLinked(c))
// other devices unnecessary waiting times
{
//
if(geom_linesegmentscross(start, end, vChainPos(c), vChainEndpos(c))) return c;
// do not answer with com/end to com/end messages to avoid message loops!
}
//
llRegionSayTo(other_device, -20131224, llDumpList2String([kThisDevice, other_device, "RFEDIP-1.0", "msg_end!", -20131224], "|"));
}
}
}


return iUNDETERMINED;
touch_start(integer t)
}
{
// [...]
iDevicesAround = 0;
// [...]


// ask other devices to identify
//
llRegionSay(-20131224, "identify");
}


// Make a chain from each chain point to the chain target closest to
// [...]
// that chain point, no more than one chain per target. It would be
}
// really interesting to see a better algorithm for this.
</source>
//
// caveats: It is undetermined whether a solution can be found in all
//          possible cases or not.
//
//          The algorithm scales horribly.
//
void mkchains()
{
UnStatus(stRECEIVING);
llSetTimerEvent(0.0);
aftell("computing ...");
 
int continue = Len(lTargets);
vector ref = avgpos_targets();
unless(vUNDETERMINED == ref)
{
int c = furthest_chain(ref);
while(continue && (c != iUNDETERMINED))
{
int t = closest_target(c);
unless(iUNDETERMINED == t)
{
yChainLink(c, kTargetKey(t));
lTargets = llDeleteSubList(lTargets, t, t);
--continue;
}
c = furthest_chain(ref);
}
}


lTargets = [];
LoopChains(when(boolChainLinked(_n)) Enlist(lTargets, kChainKey(_n)));


bool swapped;
The reference implementation is available with some example objects for free on the [https://marketplace.secondlife.com/p/rfedip-reference-implementation/5644685 marketplace].
do
{
swapped = FALSE;


int c = Len(lChains);
The code on this wiki page and in the repository is more recent than the code with the example devices.
while(c)
{
c -= iSTRIDE_lChains;


when(boolChainLinked(c))
==Documenting rfedip compliant devices you create==
{
int crosses = find_chain_crossedby(c);
unless(iUNDETERMINED == crosses)
{
swap_chains(c, crosses);
swapped = TRUE;
}


int t = closest_target(c);
Please use [[LSL_Protocol/rfedip-devices|this page]] to document your devices.
if(fDistChainTarget(c, t) < fChainLength(c))
{
int cc = iChainOfTarget(t);
unless(iUNDETERMINED == cc)
{
if(geom_linesegmentscross(vChainPos(c), vChainEndpos(c), vChainPos(cc), vTargetPos(t)))
{
swap_chains(c, cc);
swapped = TRUE;
}
}
else aftell("data mismatch");
}
}
}
}
while(swapped);


{ LoopChains(when(boolChainLinked(_n)) xChainShow(_n)); }
==Version 1.1==
UnStatus(stBUSY);
aftell("done");
}
</lsl>


Rfedip version 1.1 is currently being tested.  The sources in the repository have already been updated.


Note: This device could be worn as an attachment.
Version 1.1 replaces version 1.0.




Except for "colordef.h" and "geometry.lsl", the #included files are the
It has turned out that using a UUID to identify a device is insufficient. The protocol has been extended by inserting another identifier with the UUID.  The default for this additional identifier is what [[LlGetScriptName|llGetScriptName()]] returns.
same as with the first example.


Finally, here is the actual LSL script that results from the
Without the additional identifier, problems arise when a prim contains multiple scripts which implement rfedip devices.  Imagine you have a prim with the devices A and B.  The remote device C queries for rfedip devices and discovers A and B.  Device B supports tokens device C works with while device A does not support these tokens.  Device C would receive messages indicating the end of communication from device A for the tokens A does not support.  But C relies on these messages from device B to decide whether requested data has been fully transmitted or not.  Since device C cannot distinguish whether a message was sent by A or by B, C cannot function correctly.  The unexpected messages from A may lead to C missing responses from B.
compilation:


Introducing a uniq identifier for each device now enables device C to distinguish between the devices A and B.  Unfortunately, this has required to modify the protocol.  Having only one rfedip device per prim, or using seperate channels for instances when multiple devices are in the same prim, have too many disadvantages.


<lsl>
Adding the identifier defeats backwards compatibility.  Considering the severe limitations LSL scripts are subjected to, especially memory limitations, future versions of the protocol '''should''' be backwards compatible, but are not required to when compatibility cannot reasonably be maintained.
list getlinknumbersbylistnamedappend_attached(list names)
{
list strided = [];
integer n = llGetNumberOfPrims();


do
==Version 1.1 Reference Implementation: A Tethering Device==
{
string thisname = llList2String(llGetLinkPrimitiveParams(n, [PRIM_NAME] ), 0);


if((~llListFindList(names, [thisname])))
This device is the same as with protocol version 1.0.  The only difference is that it uses protocol version 1.1.  The uniq identifier has been added to the protocol header.  The recipient of a message verifies whether the uniq identifier specified by the sender of the message for the uniq identifier of the recipient is identical to the uniq identifier of the recipient.  The script looks like this:
{
strided += [thisname, n] + [NULL_KEY];
}


--n;
}
while(n > 0);


return strided;
<source lang="lsl2">
}
// =rfedipdev-tether.i
integer geom_linesegmentscross(vector p1, vector p2, vector p3, vector p4)
list getlinknumbersbyname(string name)
{
{
vector a = p2 - p1;
integer n = llGetObjectPrimCount(llGetLinkKey(llGetLinkNumber()));
vector b = p3 - p4;
list numbers = [];
vector c = p1 - p3;
name = llToLower(name);
float alphaNumerator = b.y * c.x - b.x * c.y;
float alphaDenominator = a.y * b.x - a.x * b.y;
float betaNumerator = a.x * c.y - a.y * c.x;
float betaDenominator = a.y * b.x - a.x * b.y;
integer doIntersect = TRUE;


if (alphaDenominator == 0.0 || betaDenominator == 0.0)
if(n > 1)
{
{
doIntersect = FALSE;
while(n)
}
else
{
if (alphaDenominator > 0.0)
{
{
if (alphaNumerator < 0.0 || alphaNumerator > alphaDenominator)
if((~llSubStringIndex(llToLower(llList2String(llGetLinkPrimitiveParams(n, [PRIM_NAME]), 0)), name)))
{
{
doIntersect = FALSE;
numbers += n;
}
}
}
else
{
if (alphaNumerator > 0.0 || alphaNumerator < alphaDenominator)
{
doIntersect = FALSE;
}
}


if (doIntersect && betaDenominator > 0.0)
--n;
{
if (betaNumerator < 0.0 || betaNumerator > betaDenominator)
{
doIntersect = FALSE;
}
}
}
else
}
else
{
if((~llSubStringIndex(llToLower(llGetObjectName()), name)))
{
{
if (betaNumerator > 0.0 || betaNumerator < betaDenominator)
numbers += n;
{
doIntersect = FALSE;
}
}
}
}
}


return doIntersect;
return numbers;
}
}
integer status;
key kThisDevice;
integer iDevicesAround;
list lHooks;
list lChains;
list lTargets;
integer iChainOfTarget(integer t)
{
integer x = llListFindList(lChains, [llList2Key(lTargets, t)]);


if(~x) return (x - 2);
default
 
return -1;
}
integer closest_target(integer chain)
{
{
integer ret = -1;
state_entry()
vector chainpos = (llGetRootPosition() + llList2Vector(llGetLinkPrimitiveParams(llList2Integer(lChains, (chain) + 1), [PRIM_POS_LOCAL]), 0) * llGetRootRotation());
float closest = -1.0;
integer n = llGetListLength(lTargets);
 
while(n)
{
{
--n;
lHooks = getlinknumbersbyname("hook");
float distance = llVecDist(llList2Vector(llGetObjectDetails(llList2Key(lTargets, n), [OBJECT_POS] ), 0), chainpos);


if(-1.0 == closest)
if(!(llGetListLength(lHooks)))
{
{
closest = distance;
lHooks = (list)llGetLinkNumber();
ret = n;
}
}
else
 
kThisDevice = llGetLinkKey(llGetLinkNumber());
llListen(-20131224, "", NULL_KEY, "");
}
changed(integer w)
{
if(w & CHANGED_LINK)
{
{
if(distance < closest)
kThisDevice = llGetLinkKey(llGetLinkNumber());
lHooks = getlinknumbersbyname("hook");
 
if(!(llGetListLength(lHooks)))
{
{
closest = distance;
lHooks = (list)llGetLinkNumber();
ret = n;
}
}
}
}
}
}
 
listen(integer channel, string name, key other_device, string _MESSAGE)
return ret;
}
vector avgpos_targets()
{
vector avg = ( < -1.0, -1.0, -1.0 > );
integer cnt = 0;
integer t = llGetListLength(lTargets);
 
while(t)
{
{
--t;
if((llGetSubString(_MESSAGE, 0, 7) == "identify"))
avg += llList2Vector(llGetObjectDetails(llList2Key(lTargets, t), [OBJECT_POS] ), 0);
{
++cnt;
// The incoming message looks like:
}
//
// "identify|<sender-Uniq>"
//
// Default for the uniq identifier is what
// llGetScriptName() returns.  Please see
// rfedip.h.
//
// Indistinctively answer queries that want to
// detect this device: The answer goes to the
// sender (i. e. other_device) and looks like:
//
// "<this-device-uuid>|<RFEDIP_sTHIS_UNIQ>|<recipient-uuid>|<recipient-Uniq>|RFEDIP_sVERSION|RFEDIP_protIDENTIFY"
//
// For the device that receives the answer to
// the request to identify, <this-device-uuid>
// is the UUID of this device.
//
// With
//
//  RFEDIP_RESPOND(<recipient-uuid>, <recipient-Uniq>, <sender-uuid>, <protocol-payload>);
//
// an answer is sent to the device from which
// this device has received the request to
// identify itself:
//


return (avg / (float)cnt);
llRegionSayTo(other_device, -20131224,
}
      llDumpList2String([kThisDevice, llGetScriptName(),
integer furthest_chain(vector ref)
other_device,
{
llList2Key(llParseString2List(_MESSAGE,
float distmax = -1.0;
      ["|"], []),
integer ret = -1;
    1), "RFEDIP-1.1", "identity"],
integer c = llGetListLength(lChains);
"|"));
return;
}


while(c)
// From here on, received messages are expected to
{
// look like:
c -= 3;
//
// "<sender-uuid>|<sender-Uniq>|<recipient-uuid>|<recipient-Uniq>|RFEDIP_sVERSION|<token>[|parameter|parameter...]"
//
// <sender-uuid> is the UUID of the device sending the
// message (i. e. other_device); <recipient-uuid> is
// the UUID of the recipient, i. e. of this device
//
// same goes for <sender-Uniq> and <recipient-Uniq>
//
// Please do not confuse incoming messages with
// outgoing messages!
//
// Parameters can be tokens.  However, this is not
// recommended.  Send multiple messages rather than
// multiple tokens in one message.
//
// The received message is put into a list for further
// processing:
//
list payload = llParseString2List(_MESSAGE, ["|"], []);


if(!((llList2Key(lChains, (c) + 2) != NULL_KEY)))
// Attempt to verify whether the message looks valid:
//
if(llGetListLength(payload) < 6)
{
{
float dist = llVecDist((llGetRootPosition() + llList2Vector(llGetLinkPrimitiveParams(llList2Integer(lChains, (c) + 1), [PRIM_POS_LOCAL]), 0) * llGetRootRotation()), ref);
return;
}


if(distmax < dist)
// Attempt to make sure that the message is for this
{
// device:
distmax = dist;
//
ret = c;
// + ignore messages that appear not to be sent by the
}
//  device they claim to be sent from by verifying
//  the sender given in the message with the actual
//  sender:
//
// + ignore messages that appear not be destined for
//  this device by verifying the recipient given in
//  the message with what this device actually is:
//
// + ignore messages that appear not to be destined for
//  this device by verifying the Uniq identifiers
//
// + ignore messages that request a different version
//  of the protocol by verifying the protocol version
//  given in the message --- in this example, a check
//  for what is considered a "sufficient version" is
//  applied:
//
if((llList2Key(payload, 0) != other_device)
  || (llList2Key(payload, 2) != kThisDevice)
  || (llList2Key(payload, 3) != llGetScriptName())
  || !(~llSubStringIndex(llList2String(payload, 4), "RFEDIP")))
{
return;
}
}
}


return ret;
// From here on, the capabilities of the device can be
}
// implemented.
swap_chains(integer ca, integer cb)
//
{
key tmp = llList2Key(lChains, (ca) + 2);
(lChains = llListReplaceList(lChains, (list)llList2Key(lChains, (cb) + 2), (ca) + 2, (ca) + 2));
(lChains = llListReplaceList(lChains, (list)tmp, (cb) + 2, (cb) + 2));
}
integer find_chain_crossedby(integer chain)
{
vector start = (llGetRootPosition() + llList2Vector(llGetLinkPrimitiveParams(llList2Integer(lChains, (chain) + 1), [PRIM_POS_LOCAL]), 0) * llGetRootRotation());
vector end = llList2Vector(llGetObjectDetails(llList2Key(lChains, (chain) + 2), [OBJECT_POS] ), 0);
integer c = llGetListLength(lChains);


while(c)
// extract the token from the rfedip message ...
{
//
c -= 3;
string token = llList2String(payload, 5);
//
// ... and the uniq identifier of the sender
//
string uniq = llList2Key(payload, 1);


if((chain != c) && (llList2Key(lChains, (c) + 2) != NULL_KEY))
// Report the device type and send end of
// communication message.
//
if("qry-devtype0" == token)
{
{
if(geom_linesegmentscross(start, end, (llGetRootPosition() + llList2Vector(llGetLinkPrimitiveParams(llList2Integer(lChains, (c) + 1), [PRIM_POS_LOCAL]), 0) * llGetRootRotation()), llList2Vector(llGetObjectDetails(llList2Key(lChains, (c) + 2), [OBJECT_POS] ), 0))) return c;
llRegionSayTo(other_device, -20131224,
      llDumpList2String([kThisDevice, llGetScriptName(),
other_device, uniq, "RFEDIP-1.1",
"devtype0-flags", 0], "|"));
llRegionSayTo(other_device, -20131224,
      llDumpList2String([kThisDevice, llGetScriptName(),
other_device, uniq, "RFEDIP-1.1",
"msg_end!", -20131224], "|"));
return;
}
}
}


return -1;
// Report the chain target points and send end of
}
// communication message.
mkchains()
//
{
if(token == "tether")
(status -= 2 * ( !!(status & 2) ) );
llSetTimerEvent(0.0);
llSay(0, "(" + (string)( (61440 - llGetUsedMemory() ) >> 10) + "kB) ~> " + "computing ...");
integer continue = llGetListLength(lTargets);
vector ref = avgpos_targets();
 
if(!(( < -1.0, -1.0, -1.0 > ) == ref))
{
integer c = furthest_chain(ref);
 
while(continue && (c != -1))
{
{
integer t = closest_target(c);
integer n = llGetListLength(lHooks);


if(!(-1 == t))
while(n)
{
{
(lChains = llListReplaceList(lChains, (list)llList2Key(lTargets, t), (c) + 2, (c) + 2));
--n;
lTargets = llDeleteSubList(lTargets, t, t);
llRegionSayTo(other_device, -20131224,
--continue;
      llDumpList2String([kThisDevice, llGetScriptName(),
other_device, uniq, "RFEDIP-1.1",
"tether point",
llGetLinkKey(llList2Integer(lHooks,
    n))],
"|"));
}
}
}


c = furthest_chain(ref);
// when receiving messages not handled by this device,
// indicate end of communication to potentially save
// other devices unnecessary waiting times
//
// do not answer with com/end to com/end messages to avoid message loops!
//
if(!("msg_end!" == token))
{
llRegionSayTo(other_device, -20131224,
      llDumpList2String([kThisDevice, llGetScriptName(),
other_device, uniq, "RFEDIP-1.1",
"msg_end!", -20131224], "|"));
}
}
}
}
on_rez(integer p)
{
kThisDevice = llGetLinkKey(llGetLinkNumber());
}
}
</source>


lTargets = [];
integer _n = llGetListLength(lChains);


while(_n)
The source in the [git://dawn.adminart.net/lsl-repo.git git repository] is different from above script in that the uniq identifier is stored in a string to avoid calling llGetScriptName() many times.
{
_n -= 3;


if((llList2Key(lChains, (_n) + 2) != NULL_KEY)) if((llGetUsedMemory() < 61440) && !~llListFindList(lTargets, [llList2Key(lChains, (_n) + 2)])) lTargets += [llList2Key(lChains, (_n) + 2)];
You can use something else than the return of llGetScriptName() to generate a uniq identifier. llGetScriptName() has the advantage that it provides uniqness in combination with the UUID of the prim the script is in:  Multiple scripts in the same prim cannot have identical names.  It is preferable over a hard-coded string because a hard-coded string could lead to ambiguity when two devices in the same prim use the same string.  You could use [[LlGenerateKey|llGenerateKey()]] instead, with the disadvantages that it probably requires more script memory and that a new key is generated with each call.
};


integer swapped;
==Version 1.1 Reference Implementation: A device using chaining points==


do
The device is the same as with protocol version 1.0.  The only difference is that it uses protocol version 1.1.  You can find the source in the [git://dawn.adminart.net/lsl-repo.git git repository].
{
swapped = FALSE;
integer c = llGetListLength(lChains);


while(c)
==Version 1.2==
{
c -= 3;
 
if((llList2Key(lChains, (c) + 2) != NULL_KEY))
{
integer crosses = find_chain_crossedby(c);


if(!(-1 == crosses))
Version 1.2 features the minor addtion of an "OK" message.  The token is "rfedipOK" and can be used to indicate that an operation requested through a protocol message has been performed, or to otherwise positively acknowledge a message.
{
swap_chains(c, crosses);
swapped = TRUE;
}


integer t = closest_target(c);
This is '''not''' a control message because it doesn´t make much sense to send an acknowledgement for something on the default channel while the communication otherwise goes over a dedicated channel.


if(llVecDist((llGetRootPosition() + llList2Vector(llGetLinkPrimitiveParams(llList2Integer(lChains, (c) + 1), [PRIM_POS_LOCAL]), 0) * llGetRootRotation()), llList2Vector(llGetObjectDetails(llList2Key(lTargets, t), [OBJECT_POS] ), 0)) < llVecDist((llGetRootPosition() + llList2Vector(llGetLinkPrimitiveParams(llList2Integer(lChains, (c) + 1), [PRIM_POS_LOCAL]), 0) * llGetRootRotation()), llList2Vector(llGetObjectDetails(llList2Key(lChains, (c) + 2), [OBJECT_POS] ), 0)))
The macro "RFEDIP_OK" to send the "OK" message has been added to rfedip.h.
{
integer cc = iChainOfTarget(t);


if(!(-1 == cc))
{
if(geom_linesegmentscross((llGetRootPosition() + llList2Vector(llGetLinkPrimitiveParams(llList2Integer(lChains, (c) + 1), [PRIM_POS_LOCAL]), 0) * llGetRootRotation()), llList2Vector(llGetObjectDetails(llList2Key(lChains, (c) + 2), [OBJECT_POS] ), 0), (llGetRootPosition() + llList2Vector(llGetLinkPrimitiveParams(llList2Integer(lChains, (cc) + 1), [PRIM_POS_LOCAL]), 0) * llGetRootRotation()), llList2Vector(llGetObjectDetails(llList2Key(lTargets, t), [OBJECT_POS] ), 0)))
{
swap_chains(c, cc);
swapped = TRUE;
}
}
else llSay(0, "(" + (string)( (61440 - llGetUsedMemory() ) >> 10) + "kB) ~> " + "data mismatch");
}
}
}
}
while(swapped);


{
For clarification:
integer _n = llGetListLength(lChains);


while(_n)
The message indicating the end of communication is to indicate the end of communication as described previously.  When data is transmitted in a series of messages --- or in a single message --- the com/end message should be sent to indicate that no further transmissions are to be expected as part of the current response.
{
_n -= 3;


if((llList2Key(lChains, (_n) + 2) != NULL_KEY)) llLinkParticleSystem(llList2Integer(lChains, (_n) + 1), [ PSYS_PART_MAX_AGE, 3.0, PSYS_PART_FLAGS, PSYS_PART_FOLLOW_VELOCITY_MASK | PSYS_PART_TARGET_POS_MASK | PSYS_PART_FOLLOW_SRC_MASK | PSYS_PART_EMISSIVE_MASK, PSYS_PART_START_COLOR, <llFrand(1.1), llFrand(1.1), llFrand(1.1)>, PSYS_PART_START_SCALE, <0.2, 0.2, 0.0>, PSYS_SRC_PATTERN, PSYS_SRC_PATTERN_DROP, PSYS_SRC_BURST_RATE, 0.04, PSYS_SRC_ACCEL, < 0.0, 0.0, -1.2 >, PSYS_SRC_BURST_PART_COUNT, 1, PSYS_SRC_BURST_SPEED_MIN, 3.0, PSYS_SRC_BURST_SPEED_MAX, 3.0, PSYS_SRC_TARGET_KEY, llList2Key(lChains, (_n) + 2), PSYS_SRC_MAX_AGE, 0.0, PSYS_SRC_TEXTURE, "4cde01ac-4279-2742-71e1-47ff81cc3529" ]);
The com/end message is also to be sent when the device does not understand a token it has received in a protocol message. Sending the com/end message in such instances clearly indicates that no further transmission is to be expected as part the current response. Insofar an operation has been requested, replying with the com/end message to the request indicates that the operation has '''not''' been performed --- or, for example, that a feature is not available.
};
}
(status -= 1 * ( !!(status & 1) ) );
llSay(0, "(" + (string)( (61440 - llGetUsedMemory() ) >> 10) + "kB) ~> " + "done");
}


default
A com/end message must not be replied to with a com/end message because doing so likely creates a message loop.  A response to a request for identification must not be followed by a com/end message because the response is never part of a series of messages.  It is sufficient that a device identifies itself, hence an "OK" message must not be used to indicate that the device has identified itself, either.
{
listen(integer channel, string name, key other_device, string _MESSAGE)
{
if("identify" == _MESSAGE)
{
llRegionSayTo(other_device, -20131224, llDumpList2String([llGetLinkKey(llGetLinkNumber()), other_device, "RFEDIP-1.0", "identity"], "|"));
return;
}


list payload = llParseString2List((_MESSAGE), ["|"], []);
Other than that, how the "OK" message is used depends on the particular device.  For example, a piece of furniture that asks an attachment to create a chain may want to receive an acknowledgement that the chain has been created, so the attachment would send an "OK" message as a response when the chain has been created.  When the chain has not been created, the attachment would send a com/end message instead.  Unless the furniture receives a com/end message, it could re-send the request to create a chain.


if(llGetListLength(payload) < 4) return;
As always, document the devices you create accordingly.


if((llList2Key(payload, 0) != other_device) || (llList2Key(payload, 1) != llGetLinkKey(llGetLinkNumber())) || !(~llSubStringIndex(llList2String(payload, 2), "RFEDIP") )) return;
==Version Numbering==


string token = llList2String(payload, 3);
Version numbers are composed of a leading string "RFEDIP-", followed by an integer representing the major version number and an integer representing the minor version number.  The two integers are seperated by a dot (".").


if("tether point" == token)
This means that version "RFEDIP-1.115" is a higher version number than "RFEDIP-1.5".
{
if((llGetUsedMemory() < 61440) && !~llListFindList(lTargets, [llList2String(payload, 4)])) lTargets += [llList2String(payload, 4)];


return;
Two macros to obtain the major and minor version numbers from a protocol message converted to a list have been added to rfedip.h.  The macros are called "RFEDIP_iVersionMajor" and "RFEDIP_iVersionMinor".  They evaluate to integers with the respective version numbers.
}


if("msg_end!" == token)
==rfedip-devices.h==
{
if(( !!(status & 2) ) )
{
--iDevicesAround;
iDevicesAround = ( ( (llAbs( (iDevicesAround) >= (0) ) ) * (iDevicesAround) ) + ( (llAbs( (iDevicesAround) < (0) ) ) * (0) ) );


if(!(iDevicesAround)) mkchains();
A collection of definitions for rfedip devices is provided with rfedip-devices.h in the [git://dawn.adminart.net/lsl-repo.git git repository].  None of the definitions and tokens contained therein are in any way mandatory.  They are not part of the specification of the rfedip protocol.
}


return;
The file is intended to be used to make the implementation of particular rfedip-compliant devices easier.  When further devices are created and documented, the tokens used by them can be added to rfedip-devices.h and thus become available to all creators of rfedip devices.
}


if("identity" == token)
==References==
{
++iDevicesAround;
llRegionSayTo(other_device, -20131224, llDumpList2String([llGetLinkKey(llGetLinkNumber()), other_device, "RFEDIP-1.0", "tether"], "|"));
return;
}


llRegionSayTo(other_device, -20131224, llDumpList2String([llGetLinkKey(llGetLinkNumber()), other_device, "RFEDIP-1.0", "msg_end!", -20131224], "|"));
* [https://marketplace.secondlife.com/p/rfedip-reference-implementation/5644685 Reference Implementation]
}
touch_start(integer t)
{
if(!(llGetListLength(lChains)))
{
llSay(0, "(" + (string)( (61440 - llGetUsedMemory() ) >> 10) + "kB) ~> " + "no chains to create");
return;
}


if(( !!(status & 1) ) )
* [[How_to_make_writing_LSL_scripts_easier|How to make creating LSL scripts easier]] describes how to preprocess your sources with cpp.
{
llSay(0, "(" + (string)( (61440 - llGetUsedMemory() ) >> 10) + "kB) ~> " + "busy, please wait");
return;
}


(status += 1 * !(status & 1) );
* [git://dawn.adminart.net/lsl-repo.git Git repository] for the [[How_to_make_writing_LSL_scripts_easier|article]] that describes how to preprocess your sources with cpp and contains the reference implementations.
integer _n = llGetListLength(lChains);


while(_n)
* [[LSL_Protocol/rfedip-devices|page to document your devices]]
{
_n -= 3;
llLinkParticleSystem(llList2Integer(lChains, (_n) + 1), []);
(lChains = llListReplaceList(lChains, (list)NULL_KEY, (_n) + 2, (_n) + 2));
};


llSay(0, "(" + (string)( (61440 - llGetUsedMemory() ) >> 10) + "kB) ~> " + "querying for devices");
==History==


llPlaySound("971bc958-ea04-194f-a78a-12826264dae4", 1);
* 2013-12-27: initial page created


lTargets = [];
* 2013-12-27: tokens changed to something more easily readable; different indentation used for LSL script


iDevicesAround = 0;
* 2013-12-27: tried to improve the "About" section


llSetTimerEvent(30.0);
* 2013-12-29: added second example


llRegionSay(-20131224, "identify");
* 2013-12-31: replaced second example with an improved version; added suggestion to send an end-of-comminucation message for tokens not understood


(status += 2 * !(status & 2) );
* 2013-12-31: added reference to opengate source; removed unnecessary return statement from first example
}
timer()
{
llSay(0, "(" + (string)( (61440 - llGetUsedMemory() ) >> 10) + "kB) ~> " + "timeout");
mkchains();
}
state_entry()
{
status = 0;
(lChains = getlinknumbersbylistnamedappend_attached(["Chain-0", "Chain-1", "Chain-2", "Chain-3", "Chain-4", "Chain-5", "Chain-6", "Chain-7"]));
llListen(-20131224, "", NULL_KEY, "");
}
changed(integer w)
{
if(w & CHANGED_LINK)
{
integer _n = llGetListLength(lChains);


while(_n)
* 2014-01-04: added reference implementation
{
_n -= 3;
llLinkParticleSystem(llList2Integer(lChains, (_n) + 1), []);
(lChains = llListReplaceList(lChains, (list)NULL_KEY, (_n) + 2, (_n) + 2));
};


(lChains = getlinknumbersbylistnamedappend_attached(["Chain-0", "Chain-1", "Chain-2", "Chain-3", "Chain-4", "Chain-5", "Chain-6", "Chain-7"]));
* 2014-01-04: made reference implementation available on the marketplace
}
}
}
</lsl>


* 2014-01-04: the code on this page was slightly optimized


===References===
* 2014-01-04: bugfix, sigh ...


* [http://gcc.gnu.org/onlinedocs/cpp/Macros.html#Macros Some documentation about cpp macros.]
* 2014-01-04: use PRIM_POSITION instead of PRIM_POS_LOCAL
* [http://forum.unity3d.com/threads/17384-Line-Intersection The function to compute whether line segments intersect or not is taken from here.]


* 2014-01-04: yet another bugfix


===History===
* 2014-01-13: article rewritten


* 2013-12-27: initial page created
* 2014-01-13: added page to document devices


* 2013-12-27: tokens changed to something more easily readable; different indentation used for LSL script
* 2014-01-16: bugfix: do not reply to com/end messages


* 2013-12-27: tried to improve the "About" section
* 2014-01-21: protocol version 1.1


* 2013-12-29: added second example
* 2014-01-27: protocol version 1.2


* todo: create some wiki as a central place to put information about devices and tokens they use, make example devices available on marketplace
* 2015-12-30: I've moved the git repo.  Run 'git clone git://dawn.adminart.net/lsl-repo.git' to get it.

Latest revision as of 10:54, 30 December 2015

Ratany fine Engineerings Device Interface Protocol (rfedip)

The Ratany fine Engineering Device Interface Protocol (short: rfedip) is an extensible protocol for the detection of compliant devices and for the handling of the communication between such devices.

The protocol defines how devices can be detected and rules for how the devices can communicate. It does not define the very content of such communications, with the exception of a few so-called control messages.


The protocol has been created because there doesn´t seem to be any sort of standard to detect devices. Devices can be leashing posts, furniture, collars, cuffs and chastity belts as well as any other gadget the design of which involves interoperability or some sort of communication with other devices.

Using sensors to scan for devices has turned out to be an insufficient alternative. The sensor range is inevitably limited, and particular prims --- which may be the actual points of an object to tie a leash to --- of objects cannot be detected with a sensor.

Using existing protocols like the lockmeister protocol also turned out not to be feasible. A device using the lockmeister protocol only becomes active from an agent sitting on the device or touching the device, and the device does itself not listen for queries to identify itself.

At the time of this writing, open collar items seem to employ some sort of protocol and maybe even some sort of API. However, these are not documented, and development appears to be in progress such that significant changes might be implemented.

Other protocols may exist that are vendor-specific. Insofar their designs have not been disclosed, they are unsuited to achieve compatibility and interoperability of devices made by different creators. A device that lacks compatibility with other devices may have no more than a very limited use for the user of the device, especially when the functionality of the device genuinely requires that the device works with others.

The Ratany fine Engineering Device Interface Protocol is intended to encourage and to help with the creation of devices that are compatible with others.


The availability of a useful protocol does not mean that creators of devices will be inclined to use it. Please consider this article as a draft with the intention to create a useful protocol which hopefully might be used by many creators --- and as a request for comments. Please feel free to use the discussion page of this article to add your suggestions and ideas, or to contact me directly.

Editing this Article

This article is written in my favourite editor. When I update the article on the wiki, I edit the whole article, delete everything and paste the new version in. That´s simply the easiest and most efficient way for me.

Unfortunately, this means that your modifications may be lost when I update the article and don´t see them.

Please use the "discussion" page of this article to post contributions or to suggest changes so they can be added to the article.

Requirements brought upon a protocol

The protocol should be:


  • available
  • extensible
  • independent of the medium which is used to transmit protocol messages
  • reliable
  • easy to implement/use
  • compatible with previous versions of the protocol when new versions are created


Specification

In General

The rfedip protcol uses strings of characters that can be transfered as messages of a type which can be sent by functions like llMessageLinked(), llRegionSayTo(), llRegionSay(), llShout(), llSay(), llWhisper() as well as in the body of an email or via the use of the HTTP protocol.

An rfedip message is a string which is divided into fields by the use of a special character as separator. The fields of a message are defined as follows, and they appear in the message in the order they are listed here:


  • sender-UUID: The UUID of the sending device.
  • recipient-UUID: The UUID of the device the message is directed to.
  • protocol version: A string that specifies the version of the protocol.
  • payload: The payload of the protocol. The payload is generally assumed to consist of a token. The token may be followed by a parameter. Multiple tokens and parameters may be sent in a single message. However, a single message should not contain multiple tokens. Multiple messages should be used to avoid the creation of messages that contain multiple tokens.


All devices compliant with the rfedip protocol must have available documentation about all the tokens and, if applicable, the parameters used with the tokens the device supports. The documentation must include the tokens and parameters the device understands, as well as the tokens and parameters it might use itself to communicate with other rfedip-compliant devices.

This is to ensure that creators and users of rfedip-compliant devices can refer to the documentation of devices created by others and make the devices they are creating compatible with devices created by others, if they so choose.

A device for which such documentation is not freely available cannot be considered as compliant or compatible with the rfedip protocol.


Definitions

The rfedip protocol itself defines:


  • a string specifying the protocol version: "RFEDIP-1.0"
  • that future versions of the protocol must maintain compatibility with previous versions of the protocol
  • a suggestion as to what may be considered as "sufficient version": "RFEDIP" (What is considered as "sufficient version" for a particular device is up to the creator of that device.)
  • the special character that must be used to seperate a string of characters into fields in order to form a protocol message: "|"
  • a default communications channel, when applicable: -20131224
  • supporting email and http as transport media for protocol messages is optional; supporting the default communications channel is mandatory
  • a token that designates a request for a device to identify itself: "identify"
  • a token that designates a protocol message a device identifies itself with: "identity"
  • a token that designates a protocol message signalling that a communication (sequence) has ended, mandatorily followed by the channel in question: "msg_end!"
  • a token that designates a protocol message requesting that a particular channel, if applicable, be used for further communication, mandatorily followed by the channel which is to be used as a parameter: "openchan"
  • that a particular channel which was opened can be closed when the device that requested opening this channel sends the protocol message that signals that a communication (sequence) has ended
  • that a particular channel which was opened on request of a device can unconditionally be closed one hour after the last communication has occurred on that channel
  • that identification requests must always be answered (consequently, the default channel must never be closed, even not when a device signals the end of a communication (sequence) for the default channel)
  • that messages involving tokens as defined here must be sent on the default channel for they are considered control messages (i. e. messages like identification requests, responses to those, opening channels, signalling the end of a communication (sequence))
  • that devices should respond with the protocol message indicating the end of communication for tokens they do not understand to potentially save other devices unnesessary waiting times
  • that devices must not respond to the protocol message indicating the end of communication because doing so could create message loops

Reference Implementation: A Tethering Device

The tethering device can be queried for the UUIDs of prims to which chains or leashes can be attached. The token to query the device is "tether". The device replies with the token "tether point", followed by the UUID of a chaining point. Each chaining point is reported in a seperate response message, one chaining point per message. When all chaining points have been reported, this devices sends a RFEDIP_protEND control message.


list getlinknumbersbyname(string name)
{
	integer n = llGetObjectPrimCount(llGetLinkKey(llGetLinkNumber()));
	list numbers = [];
	name = llToLower(name);

	if(n > 1)
	{
		while(n)
		{
			if((~llSubStringIndex(llToLower(llList2String(llGetLinkPrimitiveParams(
			                                    n, [PRIM_NAME]), 0)), name)))
			{
				numbers += n;
			}

			--n;
		}
	}
	else
	{
		if((~llSubStringIndex(llToLower(llGetObjectName()), name)))
		{
			numbers += n;
		}
	}

	return numbers;
}

key kThisDevice;
list lHooks;

default
{
	state_entry()
	{
		// remember the link numbers of the chain targets
		//
		lHooks = getlinknumbersbyname("hook");

		if(!(llGetListLength(lHooks)))
		{
			lHooks = (list)llGetLinkNumber();
		}

		kThisDevice = llGetLinkKey(llGetLinkNumber());
		llListen(-20131224, "", NULL_KEY, "");
	}

	changed(integer w)
	{
		// maybe do something else here if you need to sit on
		// this device
		//
		if(w & CHANGED_LINK)
		{
			kThisDevice = llGetLinkKey(llGetLinkNumber());
			lHooks = getlinknumbersbyname("hook");

			if(!(llGetListLength(lHooks)))
			{
				lHooks = (list)llGetLinkNumber();
			}
		}
	}

	listen(integer channel, string name, key other_device, string _MESSAGE)
	{
		if("identify" == _MESSAGE)
		{
			// Indistinctively answer queries that want to
			// detect this device: The answer goes to the
			// sender (i. e. other_device) and looks like:
			//
			// "<sender-uuid>|<recipient-uuid>|RFEDIP_sVERSION|RFEDIP_protIDENTIFY"
			//
			// For the device that receives the answer to
			// the request to identify, the <sender-uuid>
			// is the UUID of this device.
			//
			llRegionSayTo(other_device, -20131224, llDumpList2String([kThisDevice,
			              other_device, "RFEDIP-1.0", "identity"], "|"));
			return;
		}

		// From here on, received messages are expected to
		// look like:
		//
		// "<sender-uuid>|<recipient-uuid>|RFEDIP_sVERSION|<token>[|parameter|parameter...]"
		//
		// <sender-uuid> is the UUID of the device sending the
		// message (i. e. other_device); <recipient-uuid> is
		// the UUID of the recipient, i. e. of this device
		//
		// Please do not confuse incoming messages with
		// outgoing messages!

		// The received message is put into a list for further
		// processing:
		//
		list payload = llParseString2List((_MESSAGE), ["|"], []);

		// Attempt to verify whether the message looks valid:
		//
		if(llGetListLength(payload) < 4)
		{
			return;
		}

		// Attempt to make sure that the message is for this
		// device:
		//
		// + ignore messages that appear not to be sent by the
		//   device they claim to be sent from by verifying
		//   the sender given in the message with the actual
		//   sender
		//
		// + ignore messages that appear not be destined for
		//   this device by verifying the recipient given in
		//   the message with what this device actually is
		//
		// + ignore messages that request a different version
		//   of the protocol by verifying the protocol version
		//   given in the message --- in this example, a check
		//   for what is considered a "sufficient version" is
		//   applied
		//
		if((llList2Key(payload, 0) != other_device)
		   || (llList2Key(payload, 1) != kThisDevice)
		   || !(~llSubStringIndex(llList2String(payload, 2), "RFEDIP")))
		{
			return;
		}

		// From here on, the capabilities of the device can be
		// implemented.

		// Here: Report the chain target points and send of of
		// communication message.
		//
		if(llList2String(payload, 3) == "tether")
		{
			integer n = llGetListLength(lHooks);

			while(n)
			{
				--n;
				llRegionSayTo(other_device, -20131224, llDumpList2String([kThisDevice,
				              other_device, "RFEDIP-1.0", "tether point", llGetLinkKey(llList2Integer(lHooks,
				                      n))], "|"));
			}
		}

		if(!(llList2String(payload, 3) == "msg_end!"))
		{
			// when receiving messages not handled by this device,
			// indicate end of communication to potentially save
			// other devices unnecessary waiting times
			//
			// do not answer with com/end to com/end messages to avoid message loops!
			//
			llRegionSayTo(other_device, -20131224, llDumpList2String([kThisDevice, other_device, "RFEDIP-1.0", "msg_end!", -20131224], "|"));
		}
	}

	on_rez(integer p)
	{
		// UUID changes when rezzed
		//
		kThisDevice = llGetLinkKey(llGetLinkNumber());
	}
}


You can find the source for this device in the git repository and further documentation in this article.

Reference Implementation: A device using chaining points

This device can send queries for the UUIDs of prims to which chains can be attached. The token used for the query is "tether". This device expects answers to this query to use the token "tether point", followed by the UUID of the point as parameter. It expects devices that have received a query to indicate that all their UUUIDs have been reported by sending a a RFEDIP_protEND control message, regardless whether any points have been reported or not.

This device can be queried for the UUIDs of prims to which chains can be attached. The token to query the device is "tether". The device replies with the token "tether point", followed by the UUID of a chaining point. Each chaining point is reported in a seperate response message, one chaining point per message. When all chaining points have been reported, this devices sends a RFEDIP_protEND control message.

When this device has not created chains itself, it does not report its chain target points but sends a RFEDIP_protEND control message instead.


The implementation happened to grow quite a bit larger than what I had expected. What I expected is that it is simple to link a couple chains from an object to a couple points that are somewhere around. Pfff!

It is simple to link each chain to the point closest to it. When you do that, the result looks retarded in most cases because the chains may get entangled or go across the object, and several chains may go to the same point. That was ridiculous and not acceptable.

Humans solve the problem without thinking. For example, they tie their boats to the tethering points around the berths their boats are in without entangling all the ropes. They do not attach the ropes in such a way that they go across the boat. Humans are flexible and sometimes cross over some ropes or have multiple ropes go to the same point when the conditions let this appear to be advisable. The result never looks retarded because the way it´s done makes sense.

Even children do it right. I couldn´t figure out how I would do it because I would just do it. People I asked didn´t know how to do it, either. The problem is too simple. This made it surprisingly difficult to create an algorithm which yields results that don´t look retarded for at least most conditions. It took a lot of experimentation and was fun to create.

What this algorithm does, and how it does it, is irrelevant for the purpose of this article. I´ll only put the more relevant part into the article. You can find the whole source in the git repository and further documentation in this article.


default
{
	listen(integer channel, string name, key other_device, string _MESSAGE)
	{
		//
		// This first part is the same as with the tethering device.
		//

		if("identify" == _MESSAGE)
		{
			llRegionSayTo(other_device, -20131224, llDumpList2String([kThisDevice,
			              other_device, "RFEDIP-1.0", "identity"], "|"));
			return;
		}

		list payload = llParseString2List((_MESSAGE), ["|"], []);

		if(llGetListLength(payload) < 4)
		{
			return;
		}

		if((llList2Key(payload, 0) != other_device)
		   || (llList2Key(payload, 1) != kThisDevice)
		   || !(~llSubStringIndex(llList2String(payload, 2), "RFEDIP")))
		{
			return;
		}

		string token = llList2String(payload, 3);

		//
		// The rest is device specific.  Tokens in received
		// protocol messages trigger various actions to make
		// the device to its job.
		//

		if("tether point" == token)
		{
			// [...]
		}

		if("msg_end!" == token)
		{
			// when all devices have indicated end of communication,
			// create the chains
			//
			if((!!(status & 2)))
			{
				--iDevicesAround;
				iDevicesAround = (((llAbs((iDevicesAround) >= (0))) * (iDevicesAround)) + ((
				                      llAbs((iDevicesAround) < (0))) * (0)));

				if(!(iDevicesAround))
				{
					mkchains();
				}
			}

			return;
		}

		if("identity" == token)
		{
			// when other devices identify themselves, query them for tether points
			//
			++iDevicesAround;
			llRegionSayTo(other_device, -20131224, llDumpList2String([kThisDevice,
			              other_device, "RFEDIP-1.0", "tether"], "|"));
			return;
		}

		if((!!(status & 4)) && ("tether" == token))
		{
			// answer queries from other devices when this device is chained
			//
			integer _n = llGetListLength(lChains);

			while(_n)
			{
				_n -= 2;
				llRegionSayTo(other_device, -20131224, llDumpList2String([kThisDevice,
				              other_device, "RFEDIP-1.0", "tether point", llGetLinkKey(llList2Integer(lChains,
				                      (_n)))], "|"));
			}
		}

		if(!("msg_end!" == token))
		{
			// when receiving messages not handled by this device,
			// indicate end of communication to potentially save
			// other devices unnecessary waiting times
			//
			// do not answer with com/end to com/end messages to avoid message loops!
			//
			llRegionSayTo(other_device, -20131224, llDumpList2String([kThisDevice, other_device, "RFEDIP-1.0", "msg_end!", -20131224], "|"));
		}
	}

	touch_start(integer t)
	{
		// [...]
		iDevicesAround = 0;
		// [...]

		// ask other devices to identify
		//
		llRegionSay(-20131224, "identify");
	}

	// [...]
}


The reference implementation is available with some example objects for free on the marketplace.

The code on this wiki page and in the repository is more recent than the code with the example devices.

Documenting rfedip compliant devices you create

Please use this page to document your devices.

Version 1.1

Rfedip version 1.1 is currently being tested. The sources in the repository have already been updated.

Version 1.1 replaces version 1.0.


It has turned out that using a UUID to identify a device is insufficient. The protocol has been extended by inserting another identifier with the UUID. The default for this additional identifier is what llGetScriptName() returns.

Without the additional identifier, problems arise when a prim contains multiple scripts which implement rfedip devices. Imagine you have a prim with the devices A and B. The remote device C queries for rfedip devices and discovers A and B. Device B supports tokens device C works with while device A does not support these tokens. Device C would receive messages indicating the end of communication from device A for the tokens A does not support. But C relies on these messages from device B to decide whether requested data has been fully transmitted or not. Since device C cannot distinguish whether a message was sent by A or by B, C cannot function correctly. The unexpected messages from A may lead to C missing responses from B.

Introducing a uniq identifier for each device now enables device C to distinguish between the devices A and B. Unfortunately, this has required to modify the protocol. Having only one rfedip device per prim, or using seperate channels for instances when multiple devices are in the same prim, have too many disadvantages.

Adding the identifier defeats backwards compatibility. Considering the severe limitations LSL scripts are subjected to, especially memory limitations, future versions of the protocol should be backwards compatible, but are not required to when compatibility cannot reasonably be maintained.

Version 1.1 Reference Implementation: A Tethering Device

This device is the same as with protocol version 1.0. The only difference is that it uses protocol version 1.1. The uniq identifier has been added to the protocol header. The recipient of a message verifies whether the uniq identifier specified by the sender of the message for the uniq identifier of the recipient is identical to the uniq identifier of the recipient. The script looks like this:


// =rfedipdev-tether.i
list getlinknumbersbyname(string name)
{
	integer n = llGetObjectPrimCount(llGetLinkKey(llGetLinkNumber()));
	list numbers = [];
	name = llToLower(name);

	if(n > 1)
	{
		while(n)
		{
			if((~llSubStringIndex(llToLower(llList2String(llGetLinkPrimitiveParams(n, [PRIM_NAME]), 0)), name)))
			{
				numbers += n;
			}

			--n;
		}
	}
	else
	{
		if((~llSubStringIndex(llToLower(llGetObjectName()), name)))
		{
			numbers += n;
		}
	}

	return numbers;
}
key kThisDevice;
list lHooks;

default
{
	state_entry()
	{
		lHooks = getlinknumbersbyname("hook");

		if(!(llGetListLength(lHooks)))
		{
			lHooks = (list)llGetLinkNumber();
		}

		kThisDevice = llGetLinkKey(llGetLinkNumber());
		llListen(-20131224, "", NULL_KEY, "");
	}
	changed(integer w)
	{
		if(w & CHANGED_LINK)
		{
			kThisDevice = llGetLinkKey(llGetLinkNumber());
			lHooks = getlinknumbersbyname("hook");

			if(!(llGetListLength(lHooks)))
			{
				lHooks = (list)llGetLinkNumber();
			}
		}
	}
	listen(integer channel, string name, key other_device, string _MESSAGE)
	{
		if((llGetSubString(_MESSAGE, 0, 7) == "identify"))
		{
			// The incoming message looks like:
			//
			// "identify|<sender-Uniq>"
			//
			// Default for the uniq identifier is what
			// llGetScriptName() returns.  Please see
			// rfedip.h.
			//
			// Indistinctively answer queries that want to
			// detect this device: The answer goes to the
			// sender (i. e. other_device) and looks like:
			//
			// "<this-device-uuid>|<RFEDIP_sTHIS_UNIQ>|<recipient-uuid>|<recipient-Uniq>|RFEDIP_sVERSION|RFEDIP_protIDENTIFY"
			//
			// For the device that receives the answer to
			// the request to identify, <this-device-uuid>
			// is the UUID of this device.
			//
			// With
			//
			//   RFEDIP_RESPOND(<recipient-uuid>, <recipient-Uniq>, <sender-uuid>, <protocol-payload>);
			//
			// an answer is sent to the device from which
			// this device has received the request to
			// identify itself:
			//

			llRegionSayTo(other_device, -20131224,
				      llDumpList2String([kThisDevice, llGetScriptName(),
							 other_device,
							 llList2Key(llParseString2List(_MESSAGE,
										       ["|"], []),
								    1), "RFEDIP-1.1", "identity"],
							"|"));
			return;
		}

		// From here on, received messages are expected to
		// look like:
		//
		// "<sender-uuid>|<sender-Uniq>|<recipient-uuid>|<recipient-Uniq>|RFEDIP_sVERSION|<token>[|parameter|parameter...]"
		//
		// <sender-uuid> is the UUID of the device sending the
		// message (i. e. other_device); <recipient-uuid> is
		// the UUID of the recipient, i. e. of this device
		//
		// same goes for <sender-Uniq> and <recipient-Uniq>
		//
		// Please do not confuse incoming messages with
		// outgoing messages!
		//
		// Parameters can be tokens.  However, this is not
		// recommended.  Send multiple messages rather than
		// multiple tokens in one message.
		//
		// The received message is put into a list for further
		// processing:
		//
		list payload = llParseString2List(_MESSAGE, ["|"], []);

		// Attempt to verify whether the message looks valid:
		//
		if(llGetListLength(payload) < 6)
		{
			return;
		}

		// Attempt to make sure that the message is for this
		// device:
		//
		// + ignore messages that appear not to be sent by the
		//   device they claim to be sent from by verifying
		//   the sender given in the message with the actual
		//   sender:
		//
		// + ignore messages that appear not be destined for
		//   this device by verifying the recipient given in
		//   the message with what this device actually is:
		//
		// + ignore messages that appear not to be destined for
		//   this device by verifying the Uniq identifiers
		//
		// + ignore messages that request a different version
		//   of the protocol by verifying the protocol version
		//   given in the message --- in this example, a check
		//   for what is considered a "sufficient version" is
		//   applied:
		//
		if((llList2Key(payload, 0) != other_device)
		   || (llList2Key(payload, 2) != kThisDevice)
		   || (llList2Key(payload, 3) != llGetScriptName())
		   || !(~llSubStringIndex(llList2String(payload, 4), "RFEDIP")))
		{
			return;
		}

		// From here on, the capabilities of the device can be
		// implemented.
		//

		// extract the token from the rfedip message ...
		//
		string token = llList2String(payload, 5);
		//
		// ... and the uniq identifier of the sender
		//
		string uniq = llList2Key(payload, 1);

		// Report the device type and send end of
		// communication message.
		//
		if("qry-devtype0" == token)
		{
			llRegionSayTo(other_device, -20131224,
				      llDumpList2String([kThisDevice, llGetScriptName(),
							 other_device, uniq, "RFEDIP-1.1",
							 "devtype0-flags", 0], "|"));
			llRegionSayTo(other_device, -20131224,
				      llDumpList2String([kThisDevice, llGetScriptName(),
							 other_device, uniq, "RFEDIP-1.1",
							 "msg_end!", -20131224], "|"));
			return;
		}

		// Report the chain target points and send end of
		// communication message.
		//
		if(token == "tether")
		{
			integer n = llGetListLength(lHooks);

			while(n)
			{
				--n;
				llRegionSayTo(other_device, -20131224,
					      llDumpList2String([kThisDevice, llGetScriptName(),
								 other_device, uniq, "RFEDIP-1.1",
								 "tether point",
								 llGetLinkKey(llList2Integer(lHooks,
											     n))],
								"|"));
			}
		}

		// when receiving messages not handled by this device,
		// indicate end of communication to potentially save
		// other devices unnecessary waiting times
		//
		// do not answer with com/end to com/end messages to avoid message loops!
		//
		if(!("msg_end!" == token))
		{
			llRegionSayTo(other_device, -20131224,
				      llDumpList2String([kThisDevice, llGetScriptName(),
							 other_device, uniq, "RFEDIP-1.1",
							 "msg_end!", -20131224], "|"));
		}
	}
	on_rez(integer p)
	{
		kThisDevice = llGetLinkKey(llGetLinkNumber());
	}
}


The source in the git repository is different from above script in that the uniq identifier is stored in a string to avoid calling llGetScriptName() many times.

You can use something else than the return of llGetScriptName() to generate a uniq identifier. llGetScriptName() has the advantage that it provides uniqness in combination with the UUID of the prim the script is in: Multiple scripts in the same prim cannot have identical names. It is preferable over a hard-coded string because a hard-coded string could lead to ambiguity when two devices in the same prim use the same string. You could use llGenerateKey() instead, with the disadvantages that it probably requires more script memory and that a new key is generated with each call.

Version 1.1 Reference Implementation: A device using chaining points

The device is the same as with protocol version 1.0. The only difference is that it uses protocol version 1.1. You can find the source in the git repository.

Version 1.2

Version 1.2 features the minor addtion of an "OK" message. The token is "rfedipOK" and can be used to indicate that an operation requested through a protocol message has been performed, or to otherwise positively acknowledge a message.

This is not a control message because it doesn´t make much sense to send an acknowledgement for something on the default channel while the communication otherwise goes over a dedicated channel.

The macro "RFEDIP_OK" to send the "OK" message has been added to rfedip.h.


For clarification:

The message indicating the end of communication is to indicate the end of communication as described previously. When data is transmitted in a series of messages --- or in a single message --- the com/end message should be sent to indicate that no further transmissions are to be expected as part of the current response.

The com/end message is also to be sent when the device does not understand a token it has received in a protocol message. Sending the com/end message in such instances clearly indicates that no further transmission is to be expected as part the current response. Insofar an operation has been requested, replying with the com/end message to the request indicates that the operation has not been performed --- or, for example, that a feature is not available.

A com/end message must not be replied to with a com/end message because doing so likely creates a message loop. A response to a request for identification must not be followed by a com/end message because the response is never part of a series of messages. It is sufficient that a device identifies itself, hence an "OK" message must not be used to indicate that the device has identified itself, either.

Other than that, how the "OK" message is used depends on the particular device. For example, a piece of furniture that asks an attachment to create a chain may want to receive an acknowledgement that the chain has been created, so the attachment would send an "OK" message as a response when the chain has been created. When the chain has not been created, the attachment would send a com/end message instead. Unless the furniture receives a com/end message, it could re-send the request to create a chain.

As always, document the devices you create accordingly.

Version Numbering

Version numbers are composed of a leading string "RFEDIP-", followed by an integer representing the major version number and an integer representing the minor version number. The two integers are seperated by a dot (".").

This means that version "RFEDIP-1.115" is a higher version number than "RFEDIP-1.5".

Two macros to obtain the major and minor version numbers from a protocol message converted to a list have been added to rfedip.h. The macros are called "RFEDIP_iVersionMajor" and "RFEDIP_iVersionMinor". They evaluate to integers with the respective version numbers.

rfedip-devices.h

A collection of definitions for rfedip devices is provided with rfedip-devices.h in the git repository. None of the definitions and tokens contained therein are in any way mandatory. They are not part of the specification of the rfedip protocol.

The file is intended to be used to make the implementation of particular rfedip-compliant devices easier. When further devices are created and documented, the tokens used by them can be added to rfedip-devices.h and thus become available to all creators of rfedip devices.

References

  • Git repository for the article that describes how to preprocess your sources with cpp and contains the reference implementations.

History

  • 2013-12-27: initial page created
  • 2013-12-27: tokens changed to something more easily readable; different indentation used for LSL script
  • 2013-12-27: tried to improve the "About" section
  • 2013-12-29: added second example
  • 2013-12-31: replaced second example with an improved version; added suggestion to send an end-of-comminucation message for tokens not understood
  • 2013-12-31: added reference to opengate source; removed unnecessary return statement from first example
  • 2014-01-04: added reference implementation
  • 2014-01-04: made reference implementation available on the marketplace
  • 2014-01-04: the code on this page was slightly optimized
  • 2014-01-04: bugfix, sigh ...
  • 2014-01-04: use PRIM_POSITION instead of PRIM_POS_LOCAL
  • 2014-01-04: yet another bugfix
  • 2014-01-13: article rewritten
  • 2014-01-13: added page to document devices
  • 2014-01-16: bugfix: do not reply to com/end messages
  • 2014-01-21: protocol version 1.1
  • 2014-01-27: protocol version 1.2