Runco Projector Plugin

This is a plugin for a Runco Projector that is controlled over a serial connection by a Global Cache IP2SL.

Here’s a link to the manual (Runco doesn’t make them anymore so I couldn’t find a link on their website):

The plugin has 2 settings that can be configured: Host and Port. Host should be set to the global cache’s IP address and port will almost certainly be 4999, which is the default.

In addition, with the 2020-12-11 version, some feedback support has been added. Specifically:

  • Switch (power state, On/Off)
  • InputSource (selected input, Input1...5)

The 2022-12-15 version is likely to be the last update to this plugin because I no longer have this projector. The only change from the last version is to fix a bug so that the Switch Capability works correctly when using it to turn the projector on/off (I wasn’t able to test this, but I’m pretty sure it fixes the bug). I am mainly posting this because this plugin seems to get used a lot as a basis for other projector plugins :slight_smile:

Last updated: 2022-12-15

Here is the plugin code:
RuncoProjector-2022-12-15.plugin (7.4 KB)

plugin.Name = "RuncoProjector";
plugin.OnChangeRequest = onChangeRequest;
plugin.OnConnect = onConnect;
plugin.OnDisconnect = onDisconnect;
plugin.OnPoll = onPoll;
plugin.OnSynchronizeDevices = onSynchronizeDevices;
plugin.PollingInterval = 2000;
plugin.DefaultSettings = {
    "Host"  : "192.168.1.1",
    "Port"  : "4999",
};

var VERSION = "2022-12-15";

var socket = new TCPClient();

var mediaCommandMappings = {
    "PowerOff"      : "ky pow.off",
    "PowerOn"       : "ky pow.on",
    "PowerToggle"   : "ky pow.tog",
    "Menu"          : "ky menu",
    "DirectionUp"   : "ky cur.up",
    "DirectionLeft" : "ky cur.left",
    "DirectionDown" : "ky cur.down",
    "DirectionRight": "ky cur.righ",
    "Select"        : "ky enter",

    "BlankOn"       : "ky bla.on",
    "BlankOff"      : "ky bla.off",
    "BlankToggle"   : "ky bla.tog",
    "AspectNext"    : "ky asp.sw",
    "Aspect169"     : "ky asp.16.9",
    "Aspect43"      : "ky asp.4.3",
    "AspectLB"      : "ky asp.lett",
    "AspectNarrow"  : "ky asp.narr",
};

var inputSourceMappings = {
    "Input1"        : "ky src.1",
    "Input2"        : "ky src.2",
    "Input3"        : "ky src.3",
    "Input4"        : "ky src.4",
    "Input5"        : "ky src.5",
};


// helper function to clear the socket receive buffer
function flushReceiveBuffer() {
    if(socket.available) {
        socket.receive();
    }
}

// helper function to get the current projector status
function projectorGetStatus() {
    var device  = plugin.Devices[plugin.Name];

    if(device) {
        var response = "";
        var responseVal = "";
        var powerStatus = "";
        var inputStatus = "";

        // the response to a command is the command with '?' replaced by '=' and
        //  the requested value

        // query power status
        // expected response is of the form "OP STATUS = value" and the Switch
        //  attribute we're going to update is an enum with values of "On"/"Off"
        flushReceiveBuffer();
        socket.send("op status ?\r");
        sleep(250);     // sleep briefly just to make sure we get the whole response
        response = socket.receive({ timeout: 500 });
        response = response.trim();
        responseVal = response.split(" = ")[1];
        switch(responseVal) {
            case "0":   // standby mode
            case "3":   // cooling down
                powerStatus = "Off";
                break;
                
            case "1":   // powering up
            case "2":   // displaying
                powerStatus = "On";
                break;

            default:
                // unknown/unhandled status (or an error), default to off
                powerStatus = "Off";
                break;
        }

        // query input source expected response is of the form "OP SOURCE.SEL =
        // value" and the InputSelect attribute matches the names in the
        //  inputSourceMappings; these may need some tweaking since different
        //  devices might map them differently
        flushReceiveBuffer();
        socket.send("op source.sel ?\r");
        sleep(250);     // sleep briefly just to make sure we get the whole response
        response = socket.receive({ timeout: 500 });
        response = response.trim();
        responseVal = response.split(" = ")[1];
        switch(responseVal) {
            case "0":       // HDMI1
                inputStatus = "Input1";
                break;

            case "1":       // HDMI2
                inputStatus = "Input2";
                break;

            case "2":       // RGB
                inputStatus = "Input3";
                break;

            case "3":       // YPrPb1
                inputStatus = "Input4";
                break;

            case "4":       // YPrPb 2
                inputStatus = "Input5";
                break;

            case "5":
            case "6":
            case "7":
            default:
                // at present, we only support 5 inputs, so if the query shows
                //  the actual input is different, just default to input 1.  For
                //  most use cases, this will probably never be an issue, but
                //  some people might need to rework the input names and this
                //  status logic depending on how they have their projector
                //  wired up
                InputStatus = "Input1"
                break;
        }

        // update attributes
        device["Switch"]        = powerStatus;
        device["InputSource"]   = inputStatus;
    }

    // all done
}

function onChangeRequest(device, attribute, value) {
    console.log("onChangeRequest called...");
    switch(attribute) {
        case "MediaCommand":
        case "InputSource":
        case "Switch":

            var cmd = "";

            if(attribute == "Switch") {
                if(value == "On") {
                    cmd = mediaCommandMappings["PowerOn"];
                } else {
                    cmd = mediaCommandMappings["PowerOff"];
                }
            } else if(attribute == "MediaCommand") {
                cmd = mediaCommandMappings[value];
            } else if(attribute == "InputSource") {
                cmd = inputSourceMappings[value];
            }

            if(cmd) {
                console.log("  sending command: " + cmd);
                socket.send(cmd + "\r");

            } else {
                console.log("ERROR: unsupported command: " + value);
            }
            break;

        default:
            console.log("ERROR: unsupported attribute type: " + attribute);
            break;
    }
}

function onConnect() {
    console.log("onConnect called...");

    var host = plugin.Settings["Host"];
    var port = plugin.Settings["Port"];

    console.log("  Host: " + host);
    console.log("  Port: " + port);

    var device = plugin.Devices[plugin.Name];

    if(device) {
        console.log("  setting supported media commands...");
        device.SupportedMediaCommands = Object.keys(mediaCommandMappings);
        console.log("  set...");

        console.log("  setting supported input sources...");
        device.SupportedInputSources = Object.keys(inputSourceMappings);
        console.log("  set...");
    }
    
    console.log("  connecting...");
    socket.connect(host, port);
    console.log("  connected");

    // get projector status
    projectorGetStatus();
}

function onDisconnect() {
    console.log("onDisconnect called...");
    socket.close();
    console.log("  disconnected");
}

function onPoll() {
    var device = plugin.Devices[plugin.Name];

    if(device && (device.HasSubscribers("InputSource")
                    || device.HasSubscribers("Switch")
                )) {

        console.log("polling for projector status...");

        // refresh the projector status
        projectorGetStatus();
    }
}

function onSynchronizeDevices() {
    console.log("onSynchronizeDevices called...");

    var device = new Device();

    device.Id = plugin.Name;
    device.DisplayName = "Runco Projector";
    device.Icon = "TV";
    device.Capabilities = [ "MediaControl", "MediaInputSource", "Switch" ];
    device.Attributes = [ ];

    plugin.Devices[plugin.Name] = device;

    console.log("  done syncing");
}
1 Like

Thanks for the plugin,
I am utilizing this plugin for my BenQ HT3550 projector and it’s working good except feedback.
How do you get a feedback for Power and Input source?
I would like to change a status for power and input source accordingly.

Best regards,

This plugin doesn’t provide any feedback, but I think it should be fairly straightforward to get those 2 things. I’ll try to take a look at it this weekend. If you want to take a stab at it, I think it would go in onPoll() and the commands would probably be: op status ? and op source.sel ?, from a quick look at the Runco docs I have.

thanks and yes let me know how you deal it with.
I found what command to call the status, but don’t how to bring it with script code.

Hey @bill, quick question on the proper way to represent power state–should that be done through the Switch attribute or is there a different attribute that is preferred for power state when we don’t have a reliable value (that is, we have to poll for it, as we discussed here)? I’m trying to avoid introducing a custom attribute if I can avoid it.

For power state, use the Switch Capability & Switch attribute.

Thanks! Out of curiosity, is there an easy way to flush the receive queue on the TCP client socket? I haven’t tested it yet, but it occurred to me that I’ve just been ignoring responses to this point, but now that I’ll need to query status, the queue is probably going to be full. Will a single receive() dump the whole thing and/or is there a flush() method to clear it out?

A single receive() call will dump everything that is in the buffer. If the buffer is empty, it’ll block until something is received.

Sorry it took so long (I’ve been slammed at work), but I have just updated the plugin to support the Switch and InputSource attributes with feedback. I wasn’t able to test them extensively, but once @bill added the .available parameter on the socket, I was able to get it working. Please let me know if you have any issues or thoughts on how to improve it further.

Do you think this plugin would also work on the Lumagen Radiance Pro?

Their RS protocols are listed here: http://www.lumagen.com/docs/Tip0011_RS232CommandInterface_051320.pdf

Obviously it wouldn’t work directly, but this plugin would probably make a solid starting point for writing one to control the Lumagen (via a GC IP2SL, of course). That said, I quickly skimmed the docs you linked to and it looks like the Lumagen’s RS232 control is a little finicky in terms of some commands needed <CR> and others shouldn’t have it, so that could require some extra experimentation to get right.

I use a GC IP2IR to control my Lumagen using Home Remote and it has worked flawlessly (I got a cable to send the IR signals to the Lumagen rather than using an IR blaster). Once I got the repeat value set correctly, that is. Just another option to consider if you don’t already have the IP2SL.

I have a wifi2IR sitting next to the Lumagen and I had tried to use GC codes and iLearn for the commands and could never get them to work. So from your other post, I just need to change the repeat value from 1 to 3? Is that using the GC codes or the ones I get from iLearn?

Great, Thanks for the update. the projector is not with me right now, but once I get a chance to test it, I will let you know.

It’s actually even easier than that now. At the time of that old forum post, we had to pull the codes ourselves, but Home Remote now will do it automatically for you. Just add the Global Cache as a “Device Source” and then select it to “Add Device” and it will walk you through the prompts to get the right IR codes:
image

Once you’ve done that, you select the Global Cache device and in the properties, bring up CodeSets, select the Lumagen code set it automatically added, and then under Value bring up the code set editor and change the repeat value (I believe it’s the one right after 38000, but I’m going from memory) from “1” to “3”. It’s a little tedious (still wish HR had an option to do it for you automatically :wink: ), but straightforward.

I so have to try this later today, thanks!

I have been using my Harmony hub to send the IR command but I dislike the Harmony (nah, HATE is a far better word) so if I can use the wifi2IR directly with the Lumagen I will be very happy.

Thanks again!

Hi Bill,
I tweaked the plugin to work with my Epson projector. I removed most of the code as I need only Power On/Off capabilities for my setup. The function seems to be working fine however, the system seems to be polling for projector status continuously.
Is this normal or I have done something which is triggering this behaviour?

Hi Karan,

If you want feedback from the plugin on what the actual status of the projector is (on/off), then it has to poll. However, if you just want to send on/off commands, you can also remove the code in the onPoll() function and set plugin.PollingInterval = -1; (near the top of the plugin, current it is set for 2000 ms). That will stop the polling.

HFN

Thanks. I do need the projector status. Will keep the block of code as it is.

Coming from iRule. I’m trying to get this set up to control my Epson 8100 projector via the serial port No. 2 (port 5000) on my GC-100-12. I can’t seem to be able to get the Runco plugin to work. I changed the command for power on (to “PWR ON”) but when I run the simulator, I can only get pings of the GC100 and it doesn’t seem to be sending a command when I toggle the power button. Here are the polling summaries that occur periodically:

123594 4797.996920 DESKTOP-F28LOK0.attlocal.net GC100_000C1E01F69C_GlobalCache.attlocal.net ICMP 74 Echo (ping) request id=0x0001, seq=21959/51029, ttl=128 (reply in 123597)

123597 4798.001800 GC100_000C1E01F69C_GlobalCache.attlocal.net DESKTOP-F28LOK0.attlocal.net ICMP 74 Echo (ping) reply id=0x0001, seq=21959/51029, ttl=100 (request in 123594)

Is there something that must be done to bind the buttons on the media controller template to the projector commands?

I just looked at the code at the top post & it should work. You shouldn’t have to make any changes to the existing template (MediaControllerDetails.xaml). Which command aren’t you seeing?

The extra ones like BlankOn, BlankOff, etc are going to be on the list tab of that page. Click the button with 3 lines on the bottom right.