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
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");
}