00001 package org.rosmultimedia.player.samsung.driver;
00002
00003 import java.io.BufferedWriter;
00004 import java.io.IOException;
00005 import java.io.InputStream;
00006 import java.io.InputStreamReader;
00007 import java.io.OutputStreamWriter;
00008 import java.io.Reader;
00009 import java.io.StringWriter;
00010 import java.io.Writer;
00011 import java.net.InetAddress;
00012 import java.net.InetSocketAddress;
00013 import java.net.Socket;
00014 import java.net.SocketException;
00015 import java.net.UnknownHostException;
00016 import java.util.Arrays;
00017 import java.util.concurrent.TimeoutException;
00018
00019 import org.apache.commons.codec.binary.Base64;
00020 import org.rosmultimedia.player.samsung.SamsungCommand;
00021 import org.rosmultimedia.player.samsung.SamsungTvNode;
00022
00030 public class SamsungRemoteSession {
00031
00032 public static final String REMOTE = "Alfred";
00033 public static final String APP = "ros.samsung.tv";
00034 private static final String TV = "LE32C650";
00035
00036 private static final char[] ALLOWED_BYTES = new char[] {0x64, 0x00, 0x01, 0x00};
00037 private static final char[] DENIED_BYTES = new char[] {0x64, 0x00, 0x00, 0x00};
00038 private static final char[] TIMEOUT_BYTES = new char[] {0x65, 0x00};
00039
00040 public static final String ALLOWED = "ALLOWED";
00041 public static final String DENIED = "DENIED";
00042 public static final String TIMEOUT = "TIMEOUT";
00043
00044 private transient String applicationName;
00045 private transient String uniqueId;
00046 private transient String host;
00047 private transient int port;
00048
00049 private transient Socket socket;
00050
00051 private transient InputStreamReader reader;
00052
00053 private transient BufferedWriter writer;
00054
00055 private transient SamsungTvNode node;
00056
00057 public SamsungRemoteSession(SamsungTvNode samsungTvNode) {
00058
00059 }
00060
00061 private SamsungRemoteSession(
00062 final SamsungTvNode samsungTvNode,
00063 final String applicationName,
00064 final String uniqueId,
00065 final String host,
00066 final int port) {
00067 this.applicationName = applicationName;
00068 this.uniqueId = uniqueId;
00069
00070 if (this.uniqueId == null) {
00071 this.uniqueId = "";
00072 }
00073
00074 this.host = host;
00075 this.port = port;
00076
00077 this.node = samsungTvNode;
00078 }
00079
00080 public static SamsungRemoteSession create(
00081 final SamsungTvNode samsungTvNode,
00082 final String applicationName,
00083 final String uniqueId,
00084 final String host,
00085 final int port)
00086 throws IOException, Exception, TimeoutException {
00087
00088 SamsungRemoteSession session = new SamsungRemoteSession(
00089 samsungTvNode,
00090 applicationName,
00091 uniqueId,
00092 host,
00093 port);
00094
00095 final String result = session.initialize();
00096 if (!result.equals(ALLOWED)) {
00097 if (result.equals(DENIED)) {
00098 throw new Exception("Connection Denied!");
00099 } else
00100
00101 if (result.equals(TIMEOUT)) {
00102 throw new TimeoutException();
00103 }
00104 }
00105
00106
00107 return session;
00108 }
00109
00110 private String initialize() throws UnknownHostException, IOException {
00111 this.node.logD("Creating socket for host " + host + " on port " + port);
00112
00113 this.socket = new Socket();
00114 this.socket.connect(new InetSocketAddress(host, port), 5000);
00115
00116 this.node.logD("Socket successfully created and connected");
00117 final InetAddress localAddress = this.socket.getLocalAddress();
00118 this.node.logD("Local address is " + localAddress.getHostAddress());
00119
00120 this.node.logD("Sending registration message");
00121 this.writer = new BufferedWriter(
00122 new OutputStreamWriter(this.socket.getOutputStream()));
00123
00124 this.writer.append((char)0x00);
00125 writeText(writer, APP);
00126 writeText(writer, getRegistrationPayload(localAddress.getHostAddress()));
00127 this.writer.flush();
00128
00129 final InputStream inputStream = socket.getInputStream();
00130 reader = new InputStreamReader(inputStream);
00131 final String result = readRegistrationReply(reader);
00132
00133
00134 int dataSize;
00135 while ((dataSize = inputStream.available()) > 0) {
00136 inputStream.skip(dataSize);
00137 }
00138
00139 return result;
00140 }
00141
00142 @SuppressWarnings("unused")
00143 private void sendPart2() throws IOException {
00144 this.writer.append((char) 0x00);
00145 writeText(writer, TV);
00146 writeText(writer, new String(new char[] { 0xc8, 0x00 }));
00147 }
00148
00149 private void checkConnection() throws UnknownHostException, IOException {
00150 if (socket.isClosed() || !socket.isConnected()) {
00151 this.node.logI("Connection closed, trying to reconnect...");
00152
00153 try {
00154 this.socket.close();
00155 } catch (IOException e) {
00156
00157 }
00158
00159 this.initialize();
00160 this.node.logI("Reconnected to server");
00161 }
00162 }
00163
00164 public void destroy() {
00165 try {
00166 socket.close();
00167 } catch (IOException e) {
00168
00169 }
00170 }
00171
00172 private String readRegistrationReply(Reader reader) throws IOException {
00173 String result;
00174 this.node.logD("Reading registration reply");
00175 reader.read();
00176 final String text1 = readText(reader);
00177
00178
00179
00180 this.node.logD("Received ID: " + text1);
00181 final char[] resultSeq = readCharArray(reader);
00182 if (Arrays.equals(resultSeq, ALLOWED_BYTES)) {
00183 this.node.logD("Registration successfull");
00184 result = ALLOWED;
00185 } else if (Arrays.equals(resultSeq, DENIED_BYTES)) {
00186 this.node.logD("Registration denied");
00187 result = DENIED;
00188 } else if (Arrays.equals(resultSeq, TIMEOUT_BYTES)) {
00189 this.node.logD("Registration timed out");
00190 result = TIMEOUT;
00191 } else {
00192 final StringBuilder builder = new StringBuilder();
00193
00194 for (final char subSeq : resultSeq) {
00195 builder.append( Integer.toHexString(subSeq) );
00196 builder.append( ' ' );
00197 }
00198
00199 result = builder.toString();
00200 this.node.logE("Received unknown registration reply: " + result);
00201 }
00202
00203 return result;
00204 }
00205
00206 private String getRegistrationPayload(final String ip) throws IOException {
00207 final StringWriter writer = new StringWriter();
00208 writer.append((char) 0x64);
00209 writer.append((char) 0x00);
00210 writeBase64Text(writer, ip);
00211 writeBase64Text(writer, uniqueId);
00212 writeBase64Text(writer, applicationName);
00213 writer.flush();
00214 return writer.toString();
00215 }
00216
00217 private static String readText(final Reader reader) throws IOException {
00218 final char[] buffer = readCharArray(reader);
00219 return new String(buffer);
00220 }
00221
00222 private static char[] readCharArray(final Reader reader) throws IOException {
00223 if (reader.markSupported()) {
00224 reader.mark(1024);
00225 }
00226
00227 final int length = reader.read();
00228 final int delimiter = reader.read();
00229 if (delimiter != 0) {
00230 if (reader.markSupported()) {
00231 reader.reset();
00232 }
00233 throw new IOException("Unsupported reply exception");
00234 }
00235
00236 final char[] buffer = new char[length];
00237 reader.read(buffer);
00238 return buffer;
00239 }
00240
00241 private static Writer writeText(final Writer writer, final String text)
00242 throws IOException {
00243
00244 return writer
00245 .append((char) text.length())
00246 .append((char) 0x00).append(text);
00247 }
00248
00249 private static Writer writeBase64Text(final Writer writer, final String text)
00250 throws IOException {
00251 final String b64 = new String(Base64.encodeBase64(text.getBytes()));
00252 return writeText(writer, b64);
00253 }
00254
00255 private void internalSendKey(final SamsungCommand key) throws IOException {
00256 this.writer.append((char) 0x00);
00257 writeText(this.writer, TV);
00258 writeText(this.writer, this.getKeyPayload(key));
00259 this.writer.flush();
00260
00261
00262 this.reader.read();
00263
00264 readText(this.reader);
00265
00266 readCharArray(this.reader);
00267 }
00268
00269 public void sendKey(final SamsungCommand key) throws IOException {
00270 this.node.logD("Sending key " + key.getValue() + "...");
00271 this.checkConnection();
00272
00273 try {
00274 this.internalSendKey(key);
00275 } catch (SocketException e) {
00276 this.node.logE("Could not send key because the server closed the connection. Reconnecting...");
00277 this.initialize();
00278
00279 this.node.logI("Sending key " + key.getValue() + " again...");
00280 this.internalSendKey(key);
00281 }
00282
00283 this.node.logI("Successfully sent key " + key.getValue());
00284 }
00285
00286 private String getKeyPayload(final SamsungCommand key) throws IOException {
00287 final StringWriter writer = new StringWriter();
00288 writer.append((char) 0x00);
00289 writer.append((char) 0x00);
00290 writer.append((char) 0x00);
00291
00292 writeBase64Text(writer, key.getValue());
00293 writer.flush();
00294
00295 return writer.toString();
00296 }
00297
00298 private void internalSendText(final String text) throws IOException {
00299 this.writer.append((char) 0x01);
00300 writeText(this.writer, TV);
00301 writeText(this.writer, this.getTextPayload(text));
00302 this.writer.flush();
00303
00304 if (!this.reader.ready()) {
00305 return;
00306 }
00307
00308
00309 this.reader.read();
00310
00311 readText(this.reader);
00312
00313 readCharArray(this.reader);
00314 }
00315
00316 public void sendText(final String text) throws IOException {
00317 this.node.logD("Sending text \"" + text + "\"...");
00318 this.checkConnection();
00319
00320 try {
00321 this.internalSendText(text);
00322 } catch (SocketException e) {
00323 this.node.logE("Could not send key because the server closed the connection. Reconnecting...");
00324 this.initialize();
00325 this.node.logI("Sending text \"" + text + "\" again...");
00326 this.internalSendText(text);
00327 }
00328 this.node.logD("Successfully sent text \"" + text + "\"");
00329 }
00330
00331 private String getTextPayload(final String text) throws IOException {
00332 final StringWriter writer = new StringWriter();
00333 writer.append((char) 0x01);
00334 writer.append((char) 0x00);
00335 writeBase64Text(writer, text);
00336 writer.flush();
00337 return writer.toString();
00338 }
00339
00340 }