LSL Protocol/rfedip

From Second Life Wiki
< LSL Protocol
Revision as of 18:39, 26 December 2013 by Ratany Resident (talk | contribs) (see history)
Jump to navigation Jump to search

Ratany fine Engineerings Device Interface Protocol (rfedip)

About

The Ratany fine Engineering Device Interface 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.


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.


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))


Example Implementation

For an example implementation, a simple rfedip-compliant device will 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 rfedip-protocol. How you actually implement the protocol is completely up to you, this is merely an example.

To create the device, a generic template is used. It´s called a generic template because it doesn´t contain more than what is needed to answer identification requests and to check whether a received message appears to be a valid rfedip-protocol message. For anything beyond that, a function (named rfedip_handle()) is called. (This has the advantage that the function can be defined in a script for a particular device which simply includes the template, and many different devices can be made which all use the same template.)


<lsl>

// Ratnay fine Engineering Device Interface Protocol // // generic example template //


  1. include <lslstddef.h>
  2. include <rfedip.h>


default { event state_entry() { // permanently listen on the protocol channel // llListen(RFEDIP_CHANNEL, "", NULL_KEY, ""); }

event listen(int channel, string name, key other_device, string _MESSAGE) { IfMessage(RFEDIP_protIDENTIFY_QUERY) { // 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. // // With // // 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: //

  1. define this_device llGetLinkKey(llGetLinkNumber()) // the UUID of this device

RFEDIP_RESPOND(other_device, this_device, RFEDIP_CHANNEL, RFEDIP_protIDENTIFY); 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! // // 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 = ProtocolData;

// Attempt to verify whether the message looks valid: // if(Len(payload) < RFEDIP_iMINMSGLEN) 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: //

  1. define InvalidSender (RFEDIP_ToSENDER(payload) != other_device)

// // + ignore messages that appear not be destined for // this device by verifying the recipient given in // the message with what this device actually is: //

  1. define NotDestined (RFEDIP_ToRCPT(payload) != this_device)

// // + 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: //

  1. define BadVersion !Instr(RFEDIP_ToPROTVERSION(payload), RFEDIP_sSUFFICIENT_VERSION)

// when(InvalidSender || NotDestined || BadVersion) return;

  1. undef this_device
  2. undef InvalidSender
  3. undef NotDestined
  4. undef BadVersion

// From here on, the capabilities of the device can be // implemented. // // Since this is a template, just call a handling // function so the template can simply be included // into a script that implements such capabilities. // rfedip_handle(payload); } } </lsl>


As you can see, above template 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

  1. ifndef _RFEDIP
  2. define _RFEDIP
  1. define RFEDIP_sVERSION "RFEDIP-1.0" // protocol version
  2. ifndef RFEDIP_sSUFFICIENT_VERSION
  3. define RFEDIP_sSUFFICIENT_VERSION "RFEDIP" // default to any version as sufficient version
  4. endif
  5. define RFEDIP_sSEP "|" // separator used in protocol messages
  1. define RFEDIP_CHANNEL -20131224 // default channel used for protocol messages
  1. define RFEDIP_protIDENTIFY_QUERY "identify" // identification query, must be responded to with RFEDIP_protIDENTIFY
  2. define RFEDIP_protIDENTIFY "identity" // answer to identification queries
  3. define RFEDIP_protEND "msg_end!" // indicate end of data transfer; if a particular channel was opened, this channel can be closed
  4. define RFEDIP_protOPEN "openchan" // open a particular channel for further communication, mandatorily has a channel number as parameter
  1. define RFEDIP_ToSENDER(_l) llList2Key(_l, 0) // return UUID of sender from list _l
  2. define RFEDIP_ToRCPT(_l) llList2Key(_l, 1) // return UUID of recipient from list _l
  3. define RFEDIP_ToPROTVERSION(_l) llList2String(_l, 2) // return string containing the version of the protocol from list _l
  4. define RFEDIP_ToFirstTOKEN(_l) llList2String(_l, 3) // return the first token from list _l
  5. define RFEDIP_ToFirstPARAM(_l) llList2String(_l, 4) // return the first parameter
  6. define RFEDIP_ToRESPONSE(_sndr, _rcpt, ...) llDumpList2String([_sndr, _rcpt, RFEDIP_sVERSION, __VA_ARGS__], RFEDIP_sSEP) // convert a protocol payload into a protocol message
  7. define REFDIP_OPEN(_rcpt, _sndr, _nchan) llRegionSayTo(_rcpt, RFEDIP_CHANNEL, RFEDIP_ToRESPONSE(_sndr, _rcpt, RFEDIP_protOPEN, _nchan)) // open a particular channel for communication
  8. define RFEDIP_END(_rcpt, _sndr, _nchan) llRegionSayTo(_rcpt, RFEDIP_CHANNEL, RFEDIP_ToRESPONSE(_sndr, _rcpt, RFEDIP_protEND, _nchan)) // indicate end of communication on channel _c
  9. define RFEDIP_IDQUERY llRegionSay(RFEDIP_CHANNEL, RFEDIP_protIDENTIFY_QUERY) // ask all rfedip compliant devices to identify themselves
  10. define RFEDIP_RESPOND(_rcpt, _sndr, _chan, ...) llRegionSayTo(_rcpt, _chan, RFEDIP_ToRESPONSE(_sndr, _rcpt, __VA_ARGS__))
  1. define RFEDIP_iMINMSGLEN 4 // used to figure out whether a message is a RfE-dip message or not


  1. 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 //

  1. 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 //

  1. define _USE_getlinknumbersbyname
  2. include <getlinknumbers.lsl>

// some standard definitions for rfedip: //

  1. include <rfedip.h>

// some definitions needed by devices that communicate with this // device //

  1. include <rfedip-generic-chainpoints.h>


// the prims of the device that are points to attach chains to are // named "hook", unless otherwise defined

  1. ifndef sCHAINPOINT
  2. define sCHAINPOINT "hook"
  3. 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.) //

  1. 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. //

  1. 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. ..." //

  1. define protTETHER "tether"
  2. 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; } } else { if((~llSubStringIndex(llToLower(llGetObjectName() ), name) ) ) { numbers += n; } } return numbers; } default { state_entry() { llListen(-20131224, "", NULL_KEY, ""); } 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), ["|"], []); 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>


An example implementation of a device that queries for the chain points will follow.


History

  • 2013-12-27: initial page created
  • 2013-12-27: tokens changed to something more easily readable; different indentation used for LSL script
  • todo: create some wiki as a central place to put information about devices and tokes they use, improve "About" section