/* Schneelocke Saarinen's engine scripts for devices using the Black Gazza power grid. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ // you shouldn't have to change this. string configNotecardName = "config"; // name of notecard holding configuration data // don't change these (although it likely won't hurt if you do). // they're only up here so they can be used as default values for touchMode, anyway. integer TOUCH_NONE = 0; // touching object does nothing integer TOUCH_TOGGLE = 1; // touching object toggles power integer TOUCH_MENU = 2; // touching object brings up menu integer TOUCH_SMART = 3; // touching object for >smartTouchDelay brings up menu; otherwise, toggles power // these can be set in the config notecard. integer pwrChan = -139849; // channel used for BG grid communication integer defaultQueryChan = 1496704899; // channel used for user-initiated device queries integer debugMode = FALSE; // debug mode enabled? integer debugIDs = FALSE; // should debug message show IDs in addition to object/avatar names? integer ownerMode = FALSE; // restrict device control to owner? integer touchMode = TOUCH_SMART; // smart touch mode float smartTouchDelay = 0.4; // seconds integer ownerModeWarn = FALSE; // warn owner on unauthorized access? // these MUST be set in the config notecard string deviceName; // device name float amps; // current to draw /////////////////////////////////////////////////////////// // (PROBABLY) NO NEED TO CHANGE ANYTHING BELOW THIS LINE // /////////////////////////////////////////////////////////// // internal constants integer TEXTBOX_NONE = 0; // llTextBox: not in use integer TEXTBOX_AMPS = 1; // llTextBox: setting amps integer TEXTBOX_DEVICENAME = 2; // llTextBox: setting device name // internal variables integer poweron = FALSE; // power on? integer queryChan = 0; // current query channel key pwrGenID = NULL_KEY; // id of the power generator we're using key notecardGetLineID = NULL_KEY; // query ID, used by dataserver event integer notecardCurLine = 0; // current notecard line key powerRequesterID = NULL_KEY; // kludge: user who requested power from the BG grid. integer queryChanHandle = 0; // handle returned by llListen() float touchedTime = 0.0; // time object was touched integer menuChannel = -1; // llDialog channel integer textChannel = -1; // llTextBox channel integer textBoxMode = TEXTBOX_NONE; // llTextBox: used for what? //////////////////////////////////////////////////////////////////////////////// // device sub-system: handles the device's internal affairs and communication // //////////////////////////////////////////////////////////////////////////////// // DEVICE SETUP ROUTINES // this gets called when the device is reset. device_setup() { // set "not initialized" values deviceName = ""; amps = -1; // start reading configuration notecard. // rest of setup is handled by device_setup2, via dataserver event. notecardGetLineID = llGetNotecardLine(configNotecardName, 0); } // helper routine for device setup that gets called by dataserver event device_setup2(string data) { string property = ""; string value = ""; integer intval = 0; float floatval = 0.0; integer sepPos = -1; // data is the current line from the configuration notecard if(data != EOF) { data = llStringTrim(data, STRING_TRIM); if(llGetSubString(data, 0, 0) != "#") { // skip comments sepPos = llSubStringIndex(data, "="); if(sepPos != -1) { property = llToLower(llStringTrim(llGetSubString(data, 0, sepPos - 1), STRING_TRIM)); value = llStringTrim(llGetSubString(data, sepPos + 1, -1), STRING_TRIM); intval = (integer)value; floatval = (float)value; // we handle this as a special case first so we can avoid lowercasing the device name. if(property == "devicename") { device_rename(value, NULL_KEY); } else { value = llToLower(value); if(property == "amps") { if(floatval > 0.0) device_send_message((integer)(floatval * 1000), "setamps", NULL_KEY); else llOwnerSay("Cannot set current drawn to 0 or less (ignored)."); } else if(property == "gridchannel") { pwrChan = intval; } else if(property == "querychannel") { if(intval != 0) device_set_querychannel(intval); else llOwnerSay("Invalid query channel (ignored): " + value); } else if(property == "debug") { if(value == "true") device_send_message(TRUE, "debug", NULL_KEY); else if(value == "false") device_send_message(FALSE, "debug", NULL_KEY); else llOwnerSay("Malformed boolean value (ignored): " + property + "=" + value); } else if(property == "debugids") { if(value == "true") device_send_message(TRUE, "debugids", NULL_KEY); else if(value == "false") device_send_message(FALSE, "debugids", NULL_KEY); else llOwnerSay("Malformed boolean value (ignored): " + property + "=" + value); } else if(property == "ownerlock") { if(value == "true") device_send_message(TRUE, "ownerlock", NULL_KEY); else if(value == "false") device_send_message(FALSE, "ownerlock", NULL_KEY); else llOwnerSay("Malformed boolean value (ignored): " + property + "=" + value); } else if(property == "ownerlockwarn") { if(value == "true") device_send_message(TRUE, "ownerlockwarn", NULL_KEY); else if(value == "false") device_send_message(FALSE, "ownerlockwarn", NULL_KEY); else llOwnerSay("Malformed boolean value (ignored): " + property + "=" + value); } else if(property == "smarttouchdelay") { if(floatval >= 0.0) device_set_smarttouchdelay(floatval); else llOwnerSay("Cannot set smart touch delay to less than 0 (ignored)."); } else if(property == "touchmode") { if(value == "off") touchMode = TOUCH_NONE; else if(value == "toggle") touchMode = TOUCH_TOGGLE; else if(value == "menu") touchMode = TOUCH_MENU; else if(value == "smart") touchMode = TOUCH_SMART; else llOwnerSay("Unrecognized touch mode (ignored): " + value); } else { llOwnerSay("Unrecognized configuration property (ignored): " + property + "=" + value); } } } else if(data != "") { llOwnerSay("Malformed configuration line (ignored): " + data); } } // line 0 has already been read in state_entry event. notecardGetLineID = llGetNotecardLine(configNotecardName, ++notecardCurLine); } else { // EOF hit; entire config notecard has been read if((deviceName != "") && (amps != -1)) { device_setup_menu(); grid_setup(); // no listener was set up, so we'll listen on the default channel. if(!queryChanHandle) device_set_querychannel(defaultQueryChan); } else { llOwnerSay("Could not initialize. Bad configuration data: deviceName=" + deviceName + ", amps=" + (string)amps); } } } // DEVICE MESSAGE SUBSYSTEM // handle device message (llMessageLinked). // this does not perform sanity checking on parameters; it is the sender's responsibility to only send sane commands. device_handle_message(integer num, string cmd, key id) { string result = ""; debugMsg("Handling device message: num=" + (string)num + ", str=" + cmd + ", id=" + id2string(id)); if(cmd == "debug") { debugMode = num; result += "Debug messages (to owner) "; if(debugMode) result += "enabled"; else result += "disabled"; } else if(cmd == "debugids") { debugIDs = num; result += "IDs in debug messages "; if(debugIDs) result += "enabled"; else result += "disabled"; } else if(cmd == "ownerlock") { ownerMode = num; result += "Owner lock "; if(ownerMode) result += "enabled"; else result += "disabled"; } else if(cmd == "ownerlockwarn") { ownerModeWarn = num; result += "Warnings on unauthorized access "; if(ownerModeWarn) result += "enabled"; else result += "disabled"; } else if(cmd == "reset") { llResetScript(); // this line is never reached } else if(cmd == "setamps") { // current gets sent as mA amps = (float)num / 1000; result += "Current drawn changed to " + (string)amps + "."; } else if(llGetSubString(cmd, 0, 6) == "setname") { deviceName = llGetSubString(cmd, 8, -1); result += "Renamed to " + deviceName + "."; } else if(cmd == "power") { // ignored to avoid "unknown/unhandled message type" debug message. } else { // commands that start with 'user_' are explicitely intended for non-engine communication and thus ignored by the engine core. if(llGetSubString(cmd, 0, 4) != "user_") debugMsg("Unknown/unhandled message type: " + cmd); } if((result != "") && (id != NULL_KEY)) // 2-second delay! llInstantMessage(id, result); } // send device message device_send_message(integer num, string str, key id) { debugMsg("Sending device message: num=" + (string)num + ", str=" + str + ", id=" + id2string(id)); llMessageLinked(LINK_SET, num, str, id); } // DEVICE QUERY SUBSYSTEM // (re)set query interface channel. channel must not be 0. device_set_querychannel(integer channel) { debugMsg("Setting query channel to " + (string)channel); if(queryChanHandle) llListenRemove(queryChanHandle); queryChan = channel; queryChanHandle = llListen(queryChan, "", NULL_KEY, ""); } // handle device query device_handle_query(string msg, key id) { list queryWords = []; string command = ""; string opt1 = ""; string opt2 = ""; string optall = ""; integer forme = FALSE; integer forall = FALSE; debugMsg("Handling query from " + id2string(id) + ": " + msg); if(ownerMode && (id != llGetOwner())) { debugMsg("Owner lock engaged, ignoring query from " + id2string(id)); if(ownerModeWarn) llOwnerSay("Unauthorized access attempt (query command) by " + id2string(id) + ": " + msg); // 2-second delay! llInstantMessage(id, "Device is in owner only mode."); return; } // split along whitespace queryWords = llParseString2List(llToLower(msg), [" "], []); command = llList2String(queryWords, 0); // options specified? if(llGetListLength(queryWords) > 1) { optall = llDumpList2String(llList2List(queryWords, 1, -1), " "); // options start with devicename if(llGetSubString(optall, 0, llStringLength(deviceName) - 1) == llToLower(deviceName)) { forme = TRUE; // further options specified? if(optall == llToLower(deviceName)) optall = ""; else optall = llStringTrim(llGetSubString(optall, llStringLength(deviceName), -1), STRING_TRIM); } else if(llGetSubString(optall, 0, 2) == "all") { forall = TRUE; // further options specified? if(optall == "all") optall = ""; else optall = llStringTrim(llGetSubString(optall, 3, -1), STRING_TRIM); } // at this point, optall contains the remaining options. queryWords = llParseString2List(llToLower(optall), [" "], []); opt1 = llToLower(llList2String(queryWords, 0)); opt2 = llToLower(llList2String(queryWords, 1)); } debugMsg("command=" + command + ", forme=" + (string)forme + ", forall=" + (string)forall + ", optall=" + optall + ", opt1=" + opt1 + ", opt2=" + opt2); if(!forme && !forall) return; // COMMANDS THAT CAN AFFECT ALL DEVICES GO IN THIS SECTION. if(command == "status") // 2-second delay! llInstantMessage(id, device_status(", ", id)); if(command == "config") // 2-second delay! llInstantMessage(id, device_config(", ", id)); // note that since it's the owner that'll receive debug messages, only they can interact with debugging. if(command == "debug") if(id == llGetOwner()) if(opt1 == "on") device_send_message(TRUE, "debug", id); else if(opt1 == "off") device_send_message(FALSE, "debug", id); else if(opt1 == "ids") if(opt2 == "on") device_send_message(TRUE, "debugids", id); else if(opt2 == "off") device_send_message(FALSE, "debugids", id); // for practical reasons, only the owner can enable ownerlock mode. if(command == "ownerlock") if(id == llGetOwner()) if(opt1 == "on") device_send_message(TRUE, "ownerlock", id); else if(opt1 == "off") device_send_message(FALSE, "ownerlock", id); else if(opt1 == "warn") if(opt2 == "on") device_send_message(TRUE, "ownerlockwarn", id); else if(opt2 == "off") device_send_message(FALSE, "ownerlockwarn", id); if(command == "setquerychannel") if(id == llGetOwner()) { integer newChannel = (integer)opt1; if(newChannel == 0) llInstantMessage(id, "Cannot set query channel to 0!"); else device_set_querychannel(newChannel); } if(command == "touchmode") if(id == llGetOwner()) { if(opt1 == "off") touchMode = TOUCH_NONE; else if(opt1 == "toggle") touchMode = TOUCH_TOGGLE; else if(opt1 == "menu") touchMode = TOUCH_MENU; else if(opt1 == "smart") touchMode = TOUCH_SMART; else llInstantMessage(id, "Unrecognized touch mode: " + opt1); } if(command== "smarttouchdelay") { float floatval = (float)opt1; if(floatval >= 0.0) device_set_smarttouchdelay(floatval); else llInstantMessage(id, "Cannot set smart touch delay to less than 0!"); } if(command == "power") if(opt1 == "on") grid_powerup(id); else if(opt1 == "off") grid_powerdown(id); if(command == "reset") device_reset(id); if(!forme) return; // COMMANDS THAT CAN ONLY AFFECT A SPECIFIC DEVICE GO IN THIS SECTION. if(command == "menu") device_main_menu(id); if(command == "setamps") if(poweron) { // 2-second delay! llInstantMessage(id, "Cannot change amps while device is running!"); } else { float newamps = (float)opt1; if(newamps <= 0.0) // 2-second delay! llInstantMessage(id, "Cannot set current drawn to 0 or less!"); else // current gets sent as mA device_send_message((integer)(newamps * 1000), "setamps", id); } if(command == "rename") device_rename(optall, id); } // DEVICE MENU SUBSYSTEM device_setup_menu() { // this is from http://wiki.secondlife.com/wiki/LlDialog . menuChannel = ((integer)("0x" + llGetSubString((string)llGetKey(), -8, -1)) & 0x3FFFFFFF) ^ 0xBFFFFFFF; textChannel = menuChannel - 1; debugMsg("Menu interface listening on channel " + (string)menuChannel + "; textbox listening on channel " + (string)textChannel); llListen(menuChannel, "", NULL_KEY, ""); llListen(textChannel, "", NULL_KEY, ""); } // present main device menu to a user- device_main_menu(key user) { list buttons = []; if(poweron) buttons = ["Turn OFF"]; else buttons = ["Turn ON"]; buttons += ["Reset", "Set amps", "Rename" ]; if(user == llGetOwner()) { if(debugMode) buttons += ["Debug OFF"]; else buttons += ["Debug ON"]; if(debugIDs) buttons += ["IDs OFF"]; else buttons += ["IDs ON"]; if(ownerMode) buttons += ["Lock OFF"]; else buttons += ["Lock ON"]; if(ownerModeWarn) buttons += ["Warn OFF"]; else buttons += ["Warn ON"]; } // 1-second delay! llDialog(user, device_status("\n", user), buttons, menuChannel); } // handle menu button clicked by a user. device_handle_menu(string msg, key user) { // this protects both against race conditions and against rogue menu channel talk. if(ownerMode && (user != llGetOwner())) { debugMsg("Owner lock engaged, ignoring button click from " + id2string(user)); // 2-second delay! if(ownerModeWarn) llOwnerSay("Unauthorized access attempt (menu button click) by " + id2string(user) + ": " + msg); llInstantMessage(user, "Device is in owner only mode."); return; } debugMsg("Button " + msg + " clicked by " + id2string(user)); if(msg == "Reset") { device_reset(user); } else if(msg == "Set amps") { if(poweron) { // 1-second delay! llDialog(user, "Cannot change current drawn while device is running!", [], menuChannel); } else { // 1-second delay! textBoxMode = TEXTBOX_AMPS; llTextBox(user, "Enter new current drawn:", textChannel); } } else if(msg == "Rename") { // 1-second delay! textBoxMode = TEXTBOX_DEVICENAME; llTextBox(user, "Enter new device name:", textChannel); } else { // simple toggle button presses go here. if((msg == "Turn ON") || (msg == "Turn OFF")) { device_toggle_power(user); } else if(msg == "Debug OFF") { if(user == llGetOwner()) device_send_message(FALSE, "debug", user); } else if(msg == "Debug ON") { if(user == llGetOwner()) device_send_message(TRUE, "debug", user); } else if(msg == "IDs OFF") { if(user == llGetOwner()) device_send_message(FALSE, "debugids", user); } else if(msg == "IDs ON") { if(user == llGetOwner()) device_send_message(TRUE, "debugids", user); } else if(msg == "Lock OFF") { if(user == llGetOwner()) device_send_message(FALSE, "ownerlock", user); } else if(msg == "Lock ON") { if(user == llGetOwner()) device_send_message(TRUE, "ownerlock", user); } else if(msg == "Warn OFF") { if(user == llGetOwner()) device_send_message(FALSE, "ownerlockwarn", user); } else if(msg == "Warn ON") { if(user == llGetOwner()) device_send_message(TRUE, "ownerlockwarn", user); } // present menu to user again. // disabled for now, see thoughts in TODO.txt // device_main_menu(user); } } // handle textbox input. device_handle_textbox(string msg, key user) { // this protects both against race conditions and against rogue textbox channel talk. if(ownerMode && (user != llGetOwner())) { debugMsg("Owner lock engaged, ignoring textbox message from " + id2string(user)); if(ownerModeWarn) llOwnerSay("Unauthorized access attempt (textbox input) by " + id2string(user) + ": " + msg); // 2-second delay! llInstantMessage(user, "Device is in owner only mode."); return; } debugMsg("Handling textbox message from " + id2string(user) + " in mode " + (string)textBoxMode + ": " + msg); if(textBoxMode == TEXTBOX_AMPS) { float newamps = (float)msg; if(newamps <= 0.0) // 1-second delay! llDialog(user, "Cannot set current drawn to 0 or less!", [], menuChannel); else device_send_message((integer)(newamps * 1000), "setamps", user); } else if(textBoxMode == TEXTBOX_DEVICENAME) { device_rename(msg, user); } textBoxMode = TEXTBOX_NONE; } // GENERAL DEVICE ROUTINES // call this to reset the device. Do NOT call llResetScript - use this instead. device_reset(key id) { debugMsg("Device reset by " + id2string(id)); grid_powerdown(id); device_send_message(0, "reset", id); } // call this to toggle the device's power status (on->off or off->on). device_toggle_power(key user) { debugMsg("Power toggled by " + id2string(user)); if(poweron) grid_powerdown(user); else grid_powerup(user); } // call this to rename the device. device_rename(string newName, key user) { debugMsg("Device renamed to " + newName + " by " + id2string(user)); newName = llStringTrim(newName, STRING_TRIM); if(newName != "") device_send_message(0, "setname " + newName, user); else llInstantMessage(user, "Cannot set device name to empty string!"); } // return current device status. // separator: string used to separate properties, e.g. ", " or "\n". // user: requesting user. Used to determine what information to return. string device_status(string separator, key user) { debugMsg("Device status requested by " + id2string(user)); string result = "Device: " + deviceName + separator + "amps: " + (string) amps + separator + "currently turned "; if(poweron) result += "ON"; else result += "OFF"; result += separator + "owner lock "; if(ownerMode) result += "ON"; else result += "OFF"; if(user == llGetOwner()) if(ownerModeWarn) result += " (warnings ON)"; else result += " (warnings OFF)"; // debug status is owner's business only. if(user == llGetOwner()) { result += separator + "debug mode "; if(debugMode) { result += "ON (IDs "; if(debugIDs) result += "ON"; else result += "OFF"; result += ")"; } else { result += "OFF"; } } return(result); } // return current device configuration. // separator: string used to separate properties, e.g. ", " or "\n". // user: requesting user. Used to determine what information to return. string device_config(string separator, key user) { debugMsg("Device config requested by " + id2string(user)); if(user != llGetOwner()) return ""; string result = "Device: " + deviceName + separator + "query channel: " + (string)queryChan + separator + "touch mode: "; if(touchMode == TOUCH_NONE) result += "off"; else if(touchMode == TOUCH_TOGGLE) result += "toggle"; else if(touchMode == TOUCH_MENU) result += "menu"; else if(touchMode == TOUCH_SMART) result += "smart"; result += separator + "smart touch delay: " + float2string1(smartTouchDelay) + "s"; return(result); } // Set smart touch delay, with appropriate rounding applied. device_set_smarttouchdelay(float newValue) { smartTouchDelay = ((float) llRound(newValue * 10)) / 10; debugMsg("Setting smart touch delay to " + (string)smartTouchDelay + " ("+ (string)newValue + ")"); } // internal helper: manage device power. // id is the requesting user, or NULL_KEY if none (e.g. in case of grid overload) device_set_power(integer newpower, key id) { debugMsg("Power set to " + (string)newpower + " by " + id2string(id)); if(newpower != poweron) { poweron = newpower; device_send_message(poweron, "power", id); } } ///////////////////////////// // BG power grid routines // ///////////////////////////// // BG grid: send grid message to specified id, or entire region if NULL_KEY grid_send_message(string msg, key id) { if(id == NULL_KEY) { debugMsg("Sending grid message: " + msg); llRegionSay(pwrChan, msg); } else { debugMsg("Sending grid message to " + id2string(id) + ": " + msg); llRegionSayTo(id, pwrChan, msg); } } // BG grid: handle a message from the BG power grid. grid_handle_message(string msg, key id) { debugMsg("Got grid message from " + id2string(id) + ": msg=" + msg); if(llGetSubString(msg, 0, 2) == "ACK") { if(poweron == TRUE) { // already got power; disconnect secondary generators grid_send_message("FIN", id); } else { // see comment in grid_powerup about powerRequesterID device_set_power(TRUE, powerRequesterID); powerRequesterID = NULL_KEY; pwrGenID = id; } } else if(msg == "OVERLOAD") { if(id == pwrGenID) grid_overload(); } else if(msg == "PING") { if(poweron == TRUE && id == pwrGenID) grid_send_message("PONG", id); } else if((msg == "PONG") || (msg == "FIN") || llGetSubString(msg, 0, 2) == "REQ") { // ignored; avoid triggering "unknown grid message" debug message } else { debugMsg("Unknown grid message from " + id2string(id) + ": " + msg); } } // BG grid: power down grid_powerdown(key id) { if(poweron) { debugMsg("Grid powering down, initiated by " + id2string(id)); grid_send_message("FIN", pwrGenID); device_set_power(FALSE, id); } else { debugMsg("Got powerdown request while already powered down, from " + id2string(id)); } } // BG grid: power up grid_powerup(key id) { if(!poweron) { debugMsg("Grid powering up, initiated by " + id2string(id)); // this is a kludge, but there's no other way to save who powered us on for when the grid replies. powerRequesterID = id; grid_send_message("FIN", pwrGenID); // this is probably not necessary, but Bo does it. grid_send_message("REQ " + (string) amps, NULL_KEY); } else { debugMsg("Got powerup request while already powered up, from " + id2string(id)); } } // BG grid: overload grid_overload() { llSay(0, "The " + deviceName + " spontaneously turns off."); device_set_power(FALSE, NULL_KEY); } // BG grid: setup grid_setup() { // listen for BG power grid messages llListen(pwrChan, "", NULL_KEY, ""); } //////////////////////////// // Misc. helper routines // //////////////////////////// // helper: send debug message debugMsg(string msg) { if(debugMode) { llOwnerSay("DEBUG: " + llGetScriptName() + ": " + msg); } } // helper: convert id to string, including name string id2string(key id) { if(id == NULL_KEY) return "noone in particular"; else if(debugIDs) return (string)id + " (" + llKey2Name(id) + ")"; else return llKey2Name(id); } // helper: convert float to string with only one decimal place. string float2string1(float floatVal) { string result = (string)floatVal; return llGetSubString(result, 0, llSubStringIndex(result, ".") + 1); } ///////////////////// // State handlers // ///////////////////// default { state_entry() { device_setup(); } changed(integer change) { // device inventory got changed; presumably, config notecard was edited. // reset entire device; re-reading of notecard is triggered on state entry. if(change & CHANGED_INVENTORY) { debugMsg("Inventory change detected, resetting device"); device_reset(NULL_KEY); } } touch_start(integer total_number) { llSetTimerEvent(0.1); } timer() { touchedTime += 0.1; } touch_end(integer total_number) { // who's touched us? key user = llDetectedKey(0); debugMsg("Touched by " + id2string(user) + " for " + (string)touchedTime + " seconds."); // this gets saved in a variable so we can reset touchedTime. integer toggleOrMenu = (touchMode == TOUCH_TOGGLE) || ((touchMode == TOUCH_SMART) && (touchedTime < smartTouchDelay)); // This needs to happen before the owner lock check etc. llSetTimerEvent(0.0); touchedTime = 0.0; // touchmode = off; do nothing. if(touchMode == TOUCH_NONE) return; if(ownerMode && (user != llGetOwner())) { debugMsg("Owner lock engaged, ignoring touch from " + id2string(user)); if(ownerModeWarn) llOwnerSay("Unauthorized access attempt (touch) by " + id2string(user)); // 2-second delay! llInstantMessage(user, "Device is in owner only mode."); return; } else { if(toggleOrMenu) device_toggle_power(user); else device_main_menu(user); } } link_message(integer sender_num, integer num, string str, key id) { // handle messages from linked prims device_handle_message(num, str, id); } listen(integer channel, string name, key id, string msg) { if(channel == pwrChan) grid_handle_message(msg, id); else if(channel == queryChan) device_handle_query(msg, id); else if(channel == menuChannel) device_handle_menu(msg, id); else if(channel == textChannel) device_handle_textbox(msg, id); } dataserver(key queryID, string data) { debugMsg("Dataserver event, query=" + (string)queryID + ", data=" + data); // device setup if(queryID == notecardGetLineID) device_setup2(data); else debugMsg("Unknown/unexpected dataserver event: queryID=" + (string)queryID + ", data=" + data); } }