A typical peripheral device is an attachment mounted on the chassis of the robot which communicates with the main controller. There are four main categories of peripheral device, distinguished by the types of dialog they are capable of having with the main controller, as illustrated in the figure below. For the most part, the information in this article also pertains to non-peripheral devices, including devices owned by other avatars, which are called foreign devices and are subject to additional scrutiny when registering with the system.

full list of system modules

Aside from differences in protocol, the critical distinction between these types of devices is how they are registered by the system. A registered device is one for which the controller knows the UUID. In general these must request to be added to the system or removed from it, and may have to contend with failure messages. If a registered device is derezzed or detached, the component providing the registration will de-register it automatically.

  • A passive device is any device that can accomplish its task without being registered. Ornamental status lights are the prototypical example of a passive device; while they may send some messages to the system, such as power and color queries, these messages are parsed without regard for whether or not the device is registered.
  • An active device is a device that specifically responds to the light bus message probe and can send add. These are tracked by the device manager (CR).
  • A weapon is an ATOS device registered through the security enhancements (SE) module. As the weapon protocol specifically pertains to tracking ammunition and reloading from the unit's battery, this classification only applies to directed energy weapons and is not useful for conventional firearms or melee weapons.
  • A touch sensor is a TESI device registered through the emotion simulator (TESI) module. Because these devices are often very high-bandwidth, they communicate through the separate lust channel protocols to prevent flooding of the light bus.

In general, most interaction with the controller occurs over the light bus, especially via the device manager, which is called puppet in Companion and NativeInterface in ATOS/CX. In the internal documentation, the device manager is often referred to as CR ("control receiver") as it originally served as the interface for the public and private command buses. (Care must be taken not to confuse this with the transceiver component of the same name, which retains control of the command buses and is now handled by the core services bus module.) Other modules, such as the security enhancements module, subsystems manager, and emotion simulator also emit light bus messages, although return communications are handled indirectly by CR.

Scripting passive devices

Light bus messages are sent bidirectionally over a channel based on the avatar's UUID. The code for calculating this is:

channel_lights = -1 - (integer)("0x" + llGetSubString( (string)llGetOwner(), -7, -1) ) + 106;

Typically, a passive device consists of three vital event handlers in the default state: state_entry(), listen(), and on_rez(). These are responsible for coping with script resets, incoming messages, and the device being attached (or otherwise rezzed, in the case of non-peripheral devices.) With the addition of a changed() handler, we can also streamline the process of calculating the channel_lights number. In the code below, llGetOwner() is stored in a variable called owner, as this eliminates many spurious function calls (as well as tracking and recreating the chat listener) which may be costly in both memory and time.

integer channel_lights;
key owner;

default {
	state_entry() {
		owner = llGetOwner();
		channel_lights = -1 - (integer)("0x" + llGetSubString( (string) owner, -7, -1) ) + 106;
		llListen(channel_lights, "", "", ""); // don't need to track the listener; we'll never be deleting it
		llRegionSayTo(owner, channel_lights, "color-q"); // send a color-q message
		llRegionSayTo(owner, channel_lights, "power-q"); // send a power-q message
	}

	changed(integer n) {
		if(n & CHANGED_OWNER)
			llResetScript();
	}

	listen(integer c, string n, key id, string m) {
		if(llGetSubString(m, 0, 5) == "color ") { // parse a color message
			list c = llParseString2List(m, [" "], []);
			vector color = <(float)llList2String(c, 1), (float)llList2String(c, 2), (float)llList2String(c, 3)>;
			// the 'color' value can now be passed to another function for use
		} else if(m == "on") { // parse an on message
			// the system is now on - adapt appropriately
		} else if(m == "off") {  // parse an off message
			// the system is now off - adapt appropriately
		} else if(m == "bolts on") {  // parse a bolts message
			llOwnerSay("@detach=n");
		} else if(m == "bolts off") {  // parse a bolts message
			llOwnerSay("@detach=y");
		}
	}

	on_rez(integer n) {
		llRegionSayTo(owner, channel_lights, "color-q"); // send a color-q message
		llRegionSayTo(owner, channel_lights, "power-q"); // send a power-q message
	}
}

Simply enough, this device reacts to four messages from the controller: color, on, off, and bolts. It sends color-q and power-q when attached or reset, which prompt the controller to send color, power, and bolts information. Only the implementation for the bolts handlers is provided, as these are very generic and rarely vary. Detail about the @detach RLV message can be found here.

Experienced programmers will notice that the security of this device as written is quite lax: no checks are performed to ensure that the sender is actually a robot controller, or even that these messages originate from an object owned by the robot. As the probability of a channel_lights collision is 1 in 16777216 (less than 0.00006%), cross-talk is not a realistic problem. More complex devices should, however, check that messages originate from a trusted object (see next example) to guard against malicious actors. In this case, there are no notable adverse consequences that an attacker could exploit.

Scripting active peripherals

integer channel_lights;
key owner;
string device_name = "gadget"; // one word, no spaces

default {
	state_entry() {
		owner = llGetOwner();
		channel_lights = -1 - (integer)("0x" + llGetSubString( (string) owner, -7, -1) ) + 106;
		llListen(channel_lights, "", "", "");
		llRegionSayTo(owner, channel_lights, "add " + device_name); // script reset; request registration
	}

	changed(integer n) {
		if(n & CHANGED_OWNER)
			llResetScript();
	}

	listen(integer c, string n, key id, string m) {
		if(llGetOwnerKey(id) == owner) { // only respond to messages from objects owned by the robot
			if(m == "probe") { // the controller is looking for devices; request registration
				llRegionSayTo(owner, channel_lights, "add " + device_name);
			} else if(m == "add-confirm") {
				// register succeeded - now we can send other messages
				// for creating commands, manipulating the controller, etc.

			} else if(m == "add-fail") {
				// register failed - a device by this name already exists
				// for non-peripheral devices, this can also be produced if the unit doesn't trust you

			}
		}
	}

	on_rez(integer n) {
		llRegionSayTo(owner, channel_lights, "add " + device_name); // just attached; request registration
	}
}

Active devices are defined by registration, which is accomplished using the following command sequence:

  • Responding to probe by sending add <device name>
    • Handling add-confirm and add-fail in response to add
  • Sending remove <device name> (mostly relevant for non-peripherals that disconnect when no longer interacting with the unit)
    • Handling remove-confirm and remove-fail as appropriate

The following light bus messages cannot be used by devices which have not registered:

  • add-command
  • remove-command
  • internal