/**
* Copyright (c) 2010-2014, openHAB.org and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* LGInteraction / Implementation of LG API
* Author Martin Fluch martinfluch@gmx.net
*/
package org.openhab.binding.lgtv.lginteraction;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.ProtocolException;
import java.net.URL;
import java.util.EventObject;
import javax.xml.bind.JAXBException;
import org.openhab.binding.lgtv.internal.LgtvConnection;
import org.openhab.binding.lgtv.internal.LgtvEventListener;
import org.openhab.binding.lgtv.internal.LgtvStatusUpdateEvent;
import org.openhab.binding.lgtv.lginteraction.LgTvAppSet.oneapp;
import org.openhab.binding.lgtv.lginteraction.LgTvChannelSet.onechannel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* This class handles the interaction between one tv and the plugin
*
* @author Martin Fluch
* @since 1.6.0
*/
public class LgTvInteractor implements LgtvEventListener {
// status of connection to tv
public enum lgtvconnectionstatus {
CS_NOTCONNECTED, CS_CONNECTED, CS_WAITFORPAIRKEY, CS_PAIRED,
}
/**
* @param args
*/
private static Logger logger = LoggerFactory.getLogger(LgtvConnection.class);
private int lgport = 8080; // tv's communication port
private String lgip; // tv's ip
private int lglocalport; // local port for message server
private String pairkey = ""; // pair key
private String xmldatafiles = ""; // location to save xml files (application
// list of tv, channel list of tv)
private lgtvconnectionstatus connectionstatus = lgtvconnectionstatus.CS_NOTCONNECTED;
private long lastsuccessfulinteraction = 0;
// Lists for Applications &Channels
private LgTvChannelSet channelset = new LgTvChannelSet();
private LgTvAppSet appset = new LgTvAppSet();
// the message reader server
private LgTvMessageReader associatedreader = null;
private int waitafterbyebye = 10; // wait 10secs for new interaction with tv
// after byebye event
private long byebyeseen = -1; // time when last byebye event occured
public String quickfind(String sourcestring, String tag) {
String retval = "#notfound";
String starttag = "<" + tag + ">";
int startpt = sourcestring.indexOf(starttag);
if (startpt >= 0) {
int endpt = sourcestring.indexOf("</" + tag + ">");
if (endpt >= 0) {
retval = sourcestring.substring(startpt + starttag.length(), endpt);
}
}
return retval;
}
// used to reach list of eventlisteners to promote connectionstatus
public void associatereader(LgTvMessageReader m) {
associatedreader = m;
}
public lgtvconnectionstatus getconnectionstatus() {
return connectionstatus;
}
public void setconnectionstatus(lgtvconnectionstatus s) {
if (s != connectionstatus) {
logger.info("lgtv connectionstatus of ip=" + lgip + " changed from " + connectionstatus + " to " + s);
connectionstatus = s;
if (associatedreader != null) {
String status = "CONNECTION_STATUS=" + ((s == lgtvconnectionstatus.CS_PAIRED) ? "1" : "0");
LgtvStatusUpdateEvent event = new LgtvStatusUpdateEvent(this);
associatedreader.sendtohandlers(event, lgip, status);
if (s == lgtvconnectionstatus.CS_NOTCONNECTED) {
associatedreader.sendtohandlers(event, lgip, status);
String name = "CHANNEL_CURRENTNAME=0";
String number = "CHANNEL_CURRENTNUMBER=0";
String vol = "VOLUME_CURRENT=0";
associatedreader.sendtohandlers(event, lgip, name);
associatedreader.sendtohandlers(event, lgip, number);
associatedreader.sendtohandlers(event, lgip, vol);
}
}
}
}
public boolean ispaired() {
return (connectionstatus == lgtvconnectionstatus.CS_PAIRED);
}
public LgTvInteractor(String ip, int port, int localport, String xmldf) throws InterruptedException {
lgip = ip;
lgport = port;
lglocalport = localport;
xmldatafiles = xmldf;
}
public void seensuccessfulinteraction() {
lastsuccessfulinteraction = System.currentTimeMillis();
}
public long getlastsuccessfulinteraction() {
return lastsuccessfulinteraction;
}
public String requestpairkey() {
String message = "";
message = "<?xml version=\"1.0\" encoding=\"utf-8\"?><envelope><api type=\"pairing\"><name>showKey</name></api></envelope>";
String answer = sendtotv("POST", "udap/api/pairing", message);
logger.debug("answer: " + answer);
if (answer.startsWith("200"))
setconnectionstatus(lgtvconnectionstatus.CS_WAITFORPAIRKEY);
else
setconnectionstatus(lgtvconnectionstatus.CS_NOTCONNECTED);
return new String(answer);
}
public String getcurrentchannel() {
String message = "";
message = "";
String answer = "#notpaired";
if (ispaired()) {
answer = sendtotv("GET", "udap/api/data?target=cur_channel", message);
}
return new String(answer);
}
public String getallchannels() {
String message = "";
message = "";
String answer = "#notpaired";
if (ispaired()) {
answer = sendtotv("GET", "udap/api/data?target=channel_list", message);
try {
channelset.loadchannels(answer);
int i = channelset.getsize();
String s = String.valueOf(i);
answer = s;
if (this.xmldatafiles.length() > 0) {
String filename = this.xmldatafiles + lgip + "_lgtvallchannels.xml";
logger.debug("xmldatafiles is set - writing file=" + filename);
channelset.savetofile(filename);
}
} catch (JAXBException e) {
logger.error("exception in getallchannels: ", e);
org.xml.sax.SAXParseException f = (org.xml.sax.SAXParseException) e.getLinkedException();
logger.error("parse exception e=" + e.toString() + " line=" + f.getLineNumber() + " columns="
+ f.getColumnNumber() + " local=" + f.getLocalizedMessage());
}
}
return new String(answer);
}
public String getallapps() {
String message = "";
message = "";
String answer = "#notpaired";
if (ispaired()) {
answer = sendtotv("GET", "udap/api/data?target=applist_get&type=1&index=1&number=1024", message);
logger.debug("answer: " + answer);
try {
appset.loadapps(answer);
int i = appset.getsize();
String s = String.valueOf(i);
answer = s;
if (this.xmldatafiles.length() > 0) {
String filename = this.xmldatafiles + lgip + "_lgtvallapps.xml";
logger.debug("xmldatafiles is set - writing file=" + filename);
appset.savetofile(filename);
}
} catch (JAXBException e) {
logger.error("error in getallapps", e);
org.xml.sax.SAXParseException f = (org.xml.sax.SAXParseException) e.getLinkedException();
logger.error("parse exception e=" + e.toString() + " line=" + f.getLineNumber() + " columns="
+ f.getColumnNumber() + " local=" + f.getLocalizedMessage());
}
}
return new String(answer);
}
public String getvolumeinfo(int dontcheckpairing) {
String message = "";
message = "";
String answer = "#notpaired";
if (dontcheckpairing == 1 || ispaired()) {
answer = sendtotv("GET", "udap/api/data?target=volume_info", message);
logger.debug("answer: " + answer);
}
return new String(answer);
}
public String handlevolchangeeasy(String sval) {
String res = "#notpaired";
int val;
int pos = sval.indexOf(".");
if (pos > 0) {
String ns = sval.substring(0, pos);
val = Integer.parseInt(ns);
} else
val = Integer.parseInt(sval);
if (ispaired()) {
res = getvolumeinfo(0);
String currentvol = quickfind(res, "level");
int cvol = Integer.parseInt(currentvol);
int todo = val - cvol;
logger.debug("currentvolume=" + cvol + " newvolume=" + val + " todo=" + todo);
LgTvCommand volup = LgTvCommand.valueOf("VOLUME_UP");
LgTvCommand voldown = LgTvCommand.valueOf("VOLUME_DOWN");
String usecommand = todo > 0 ? volup.getLgSendCommand() : voldown.getLgSendCommand();
if (todo < 0)
todo = todo * -1;
for (int i = 0; i < todo; i++) {
handlekeyinput(usecommand);
}
logger.debug("currentvolume=" + cvol + " newvolume=" + val + " todo=" + todo + " usecommand=" + usecommand);
if (todo != 0) {
res = getvolumeinfo(0);
currentvol = quickfind(res, "level");
if (associatedreader != null) {
String volume = "VOLUME_CURRENT=" + val;
LgtvStatusUpdateEvent event = new LgtvStatusUpdateEvent(this);
associatedreader.sendtohandlers(event, lgip, volume);
}
}
}
return new String(res);
}
public void setpairkey(String s) {
pairkey = s;
}
public String getpairkey() {
return pairkey;
}
public long getbyebyeseen() {
return byebyeseen;
}
public void checkpairing() {
int nobyebyeproblem = 1;
long duration = System.currentTimeMillis() - byebyeseen;
nobyebyeproblem = ((duration > (waitafterbyebye * 1000)) == true) ? 1 : 0;
if (byebyeseen == -1 || nobyebyeproblem == 1) {
if (connectionstatus != lgtvconnectionstatus.CS_PAIRED && pairkey.length() > 0) {
sendpairkey();
} else if (pairkey.length() == 0)
logger.error("no pairkey defined");
}
}
public String sendpairkey() {
logger.debug("sending pairkey key=" + pairkey);
String message = "<?xml version=\"1.0\" encoding=\"utf-8\"?><envelope><api type=\"pairing\"><name>hello</name><value>"
+ pairkey + "</value><port>" + Integer.toString(lglocalport) + "</port></api></envelope>";
String answer = sendtotv("POST", "udap/api/pairing", message);
logger.debug("answer: " + answer);
String success = "";
if (answer.startsWith("200")) {
success = "success";
setconnectionstatus(lgtvconnectionstatus.CS_PAIRED);
}
logger.info("sendpairkey with result=" + answer + " " + success + "connectionstatus=" + connectionstatus.name());
return new String(answer);
}
public String handlekeyinput(String key) {
String answer = "#notpaired";
String message = "<?xml version=\"1.0\" encoding=\"utf-8\"?><envelope><api type=\"command\"><name>HandleKeyInput</name><value>"
+ key + "</value></api></envelope>";
if (ispaired()) {
answer = sendtotv("POST", "udap/api/command", message);
logger.debug("answer: " + answer);
}
return new String(answer);
}
public String appexecute(String appname, String id, String contentid) {
String answer = "#notpaired";
String message = "<?xml version=\"1.0\" encoding=\"utf-8\"?><envelope><api type=\"command\"><name>AppExecute</name><auid>"
+ id
+ "</auid><appname>"
+ appname
+ "</appname><contentId>"
+ contentid
+ "</contentId></api></envelope>";
if (ispaired()) {
answer = sendtotv("POST", "udap/api/command", message);
logger.debug("answer: " + answer);
}
return new String(answer);
}
public String appexecuteeasy(String name) {
String cid = "";
String id = "";
if (name.length() == 0)
return new String("#appnotfound");
LgTvAppSet.envelope env = appset.getenvel();
if (env == null) {
logger.error("envelope=null maybe not connected");
return new String("#appnotfound");
} else {
oneapp e = appset.getenvel().find(name);
if (e != null) {
cid = String.valueOf(e.getcpid());
id = e.getid();
return new String(appexecute(name, id, cid));
} else {
id = cid = name = "";
return new String("#appnotfound");
}
}
}
public String appterminate(String appname, String id) {
String message = "<?xml version=\"1.0\" encoding=\"utf-8\"?><envelope><api type=\"command\"><name>AppTerminate</name><auid>"
+ id + "</auid><appname>" + appname + "</appname></api></envelope>";
String answer = "#notpaired";
if (ispaired()) {
answer = sendtotv("POST", "udap/api/command", message);
logger.debug("answer: " + answer);
}
return new String(answer);
}
public String appterminateeasy(String name) {
String id = "";
if (name.length() == 0)
return new String("#appnotfound");
oneapp e = appset.getenvel().find(name);
if (e != null) {
id = e.getid();
return new String(appterminate(name, id));
} else {
id = name = "";
return new String("#appnotfound");
}
}
public String handlechannelchange(String major, String minor, String sourceindex, int phys) {
String message = "<?xml version=\"1.0\" encoding=\"utf-8\"?><envelope><api type=\"command\"><name>HandleChannelChange</name><major>"
+ major
+ "</major><minor>"
+ minor
+ "</minor><sourceIndex>"
+ sourceindex
+ "</sourceIndex>"
+ "<physicalNum>" + String.valueOf(phys) + "</physicalNum>" + "</api></envelope>";
String answer = "#notpaired";
if (ispaired()) {
answer = sendtotv("POST", "udap/api/command", message);
logger.debug("answer: " + answer);
}
return new String(answer);
}
public String handlechannelchangeeasy(String major) {
String minor = "";
String sourceindex = "";
String c = major;
// String chname="";
// String chtype="";
int phys = -1;
String res = "#error";
int pos = major.indexOf(".");
if (pos > 0) {
String ns = major.substring(0, pos);
c = ns;
}
// logger.debug("hceasy: "+major+" -- "+c);
LgTvChannelSet.envelope env = null;
if (channelset == null) {
logger.error("channelset=null");
} else {
env = channelset.getenvel();
}
if (env == null)
logger.error("envelope=null maybe not connected");
else {
onechannel e = channelset.getenvel().find(Integer.parseInt(c));
if (e != null) {
minor = String.valueOf(e.getminor());
phys = e.getphysicalnum();
// chtype=e.getchtype();
// chname=e.getchname();
sourceindex = String.valueOf(e.getsourceindex());
res = handlechannelchange(c, minor, sourceindex, phys);
} else {
minor = "";
// chtype="";
}
}
// channelset.getchannelinfo(Integer.parseInt(major), minor, chname,
// chtype, sourceindex);
return new String(res);
}
public String sendtotv(String typ, String command, String s) {
HttpURLConnection connection = null;
OutputStreamWriter wr = null;
String conf_tv_url;
Integer responsecode = -1;
conf_tv_url = "http://" + lgip + ":" + Integer.toString(lgport); // 192.168.77.15:8080";
logger.debug("sendtotv: url=" + conf_tv_url + " typ=" + typ + " command=" + command + " s=" + s);
if (connectionstatus != lgtvconnectionstatus.CS_PAIRED)
logger.error("sendtotv but connection status is " + connectionstatus.name());
BufferedReader rd = null;
StringBuilder sb = null;
String line = null;
String returnmessage = "#error";
URL serverAddress = null;
String serverAddressString = conf_tv_url + "/" + command;
try {
serverAddress = new URL(serverAddressString);
// set up out communications stuff
connection = null;
// Set up the initial connection
connection = (HttpURLConnection) serverAddress.openConnection();
connection.setRequestProperty("content-type", "text/xml; charset=utf-8");
connection.setRequestProperty("Host", lgip + ":" + Integer.toString(lgport)); // "192.168.77.15:8080");
connection.setRequestProperty("Connection", "close");
connection.setRequestProperty("User-Agent", "Linux/2.6.18 UDAP/2.0 CentOS/5.8");
connection.setUseCaches(false);
connection.setReadTimeout(10000);
if (typ.equals("POST")) {
connection.setDoOutput(true);
}
connection.setDoInput(true);
connection.setRequestMethod(typ);
connection.connect();
if (typ.equals("POST")) {
wr = new OutputStreamWriter(connection.getOutputStream());
wr.write(s);
wr.flush();
}
// read the result from the server
rd = new BufferedReader(new InputStreamReader(connection.getInputStream()));
sb = new StringBuilder();
while ((line = rd.readLine()) != null) {
sb.append(line + '\n');
}
logger.debug(sb.toString());
returnmessage = sb.toString();
responsecode = connection.getResponseCode();
} catch (MalformedURLException e) {
e.printStackTrace();
logger.error("MalformedUrlException at Connection to: " + serverAddressString);
returnmessage = "#error/url";
} catch (ProtocolException e) {
e.printStackTrace();
logger.error("Protocol Exception at Connection to: " + serverAddressString);
returnmessage = "#error/protocol";
} catch (IOException e) {
logger.error("IO Exception at Connection to: " + serverAddressString);
// logger.debug(e.toString());
returnmessage = "#error/connect";
setconnectionstatus(lgtvconnectionstatus.CS_NOTCONNECTED);
// e.printStackTrace();
}
// close the connection, set all objects to null
returnmessage += Integer.toString(responsecode);
if (connectionstatus != lgtvconnectionstatus.CS_NOTCONNECTED)
seensuccessfulinteraction();
if (connection != null)
connection.disconnect();
rd = null;
sb = null;
wr = null;
connection = null;
return new String(returnmessage);
}
public void statusUpdateReceived(EventObject event, String ip, String data) {
if (ip.equals(lgip)) {
if (data.startsWith("BYEBYE_SEEN") == true) {
byebyeseen = System.currentTimeMillis(); // as tv keeps alive
// for ~10secs we
// have to prevent
// any further
// communication
setconnectionstatus(lgtvconnectionstatus.CS_NOTCONNECTED);
}
}
}
}