/* Schneelocke Saarinen's engine script 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 // these can be set in the config notecard. integer pwrChan = -139849; // channel used for BG grid communication integer queryChan = 60369; // channel used for user-initiated device queries integer debugMode = FALSE; // debug mode enabled? integer ownerMode = FALSE; // restrict device control to owner? // these MUST be set in the config notecard string deviceName; // device name float amps; // current to draw // internal variables integer poweron = FALSE; // power on? 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. //////////////////////////////////////////////////////////////////////////////// // device sub-system: handles the device's internal affairs and communication // //////////////////////////////////////////////////////////////////////////////// // 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 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 = llToLower(llStringTrim(llGetSubString(data, sepPos + 1, -1), STRING_TRIM)); if(property == "devicename") { deviceName = value; } else if(property == "amps") { float newamps = (float)value; if(newamps != 0.0) device_send_message((integer)(newamps * 1000), "setamps", NULL_KEY); else llOwnerSay("Cannot set amps to 0 (ignored)."); } else if(property == "gridchannel") { pwrChan = (integer)value; } else if(property == "querychannel") { queryChan = (integer)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 == "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 { 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)) { grid_setup(); llListen(queryChan, "", NULL_KEY, ""); } else { llOwnerSay("Could not initialize. Bad configuration data: deviceName=" + deviceName + ", amps=" + (string)amps); } } } // 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 == "power") { if(num) result += "powering up"; else result += "powering down"; } else if(cmd == "status" ) { result += "device=" + deviceName + ", amps=" + (string) amps + ", currently turned "; if(poweron) result += "ON"; else result += "OFF"; } else if(cmd == "debug") { debugMode = num; result += "debug messages (to owner) "; if(debugMode) result += "enabled"; else result += "disabled"; } else if(cmd == "ownerlock") { ownerMode = num; result += "owner lock "; if(ownerMode) result += "enabled"; else result += "disabled"; } else if(cmd == "debugstatus") { result += "debug messages (to owner) are "; if(debugMode) result += "ON"; else result += "OFF"; } else if(cmd == "ownerlockstatus") { result += "ownerlock is "; if(ownerMode) result += "ON"; else result += "OFF"; } else if(cmd == "reset") { llResetScript(); // this line is never reached } else if(cmd == "setamps") { // bad kludge due to inability to send/receive floats. See comment in device_handle_query. amps = (float)num / 1000; result += "current drawn changed to " + (string)amps + "."; } else { debugMsg("unknown 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); } // handle device query device_handle_query(string msg, key id, string name) { list queryWords = []; string command = ""; string opt1 = ""; string opt2 = ""; string optall = ""; integer forme = FALSE; integer forall = FALSE; debugMsg("Handling query: " + msg); if(ownerMode && (id != llGetOwner())) { debugMsg("Owner lock engaged, ignoring query from " + id2string(id)); 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") device_send_message(0, "status", 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 == "") device_send_message(0, "debugstatus", 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 == "") device_send_message(0, "ownerlockstatus", id); if(command == "power") { // we have to ask the BG grid for power first, so we can't send a device message. // the grid callback will do that once our request has been ACKed. 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 == "setamps") if(poweron) { llInstantMessage(id, "cannot change current drawn while device is running!"); } else { float newamps = (float)opt1; if(newamps == 0.0) { llInstantMessage(id, "cannot set current drawn to 0!"); } else { // this is a bad kludge, but we can't send floats. Could encode them in the string, but it'd complicate matters. integer num = (integer)(newamps * 1000); device_send_message(num, "setamps", id); } } } // 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(NULL_KEY); device_send_message(0, "reset", id); } // 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) { 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") { // 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"); grid_send_message("FIN", pwrGenID); device_set_power(FALSE, id); } else { debugMsg("got powerdown request while already powered down"); } } // BG grid: power up grid_powerup(key id) { if(!poweron) { debugMsg("grid powering up"); // 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"); } } // 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: " + msg); } } // helper: convert id to string, including name string id2string(key id) { if(id == NULL_KEY) return "noone in particular"; else return (string)id + " (" + llKey2Name(id) + ")"; } ///////////////////// // 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) { // who's touched us? key user = llDetectedKey(0); debugMsg("Touched by " + id2string(user)); if(ownerMode && (user != llGetOwner())) { debugMsg("Owner lock engaged, ignoring touch from " + id2string(user)); return; } if(poweron) grid_powerdown(user); else grid_powerup(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) // handle device query. device_handle_query(msg, id, name); } 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); } }