package com.silicondust.libhdhomerun; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.InetSocketAddress; import com.silicondust.libhdhomerun.HDHomerun_Discover.hdhomerun_discover_device_t; public class HDHomerun_Control { /* * Create a control socket. * * This function will not attempt to connect to the device. * The connection will be established when first used. * * uint32_t device_id = 32-bit device id of device. Set to HDHOMERUN_DEVICE_ID_WILDCARD to match any device ID. * uint32_t device_ip = IP address of device. Set to 0 to auto-detect. * struct hdhomerun_debug_t *dbg: Pointer to debug logging object. May be NULL. * * Returns a pointer to the newly created control socket. * * When no longer needed, the socket should be destroyed by calling hdhomerun_control_destroy. */ private final static int HDHOMERUN_CONTROL_CONNECT_TIMEOUT = 2500; private final static int HDHOMERUN_CONTROL_SEND_TIMEOUT = 2500; private final static int HDHOMERUN_CONTROL_RECV_TIMEOUT = 2500; //private final static int HDHOMERUN_CONTROL_UPGRADE_TIMEOUT = 20000; int desired_device_id = 0; int desired_device_ip = 0; int actual_device_id = 0; int actual_device_ip = 0; HDHomerun_Sock sock = null; public HDHomerun_Debug dbg = null; public HDHomerun_Pkt tx_pkt = new HDHomerun_Pkt(); public HDHomerun_Pkt rx_pkt = new HDHomerun_Pkt(); public void close_sock() { if (sock == null) { return; } sock.destroy(); sock = null; } public void set_device(int device_id, int device_ip) { close_sock(); desired_device_id = device_id; desired_device_ip = device_ip; actual_device_id = 0; actual_device_ip = 0; } public HDHomerun_Control(int device_id, int device_ip, HDHomerun_Debug dbg) { this.dbg = dbg; sock = null; set_device( device_id, device_ip); } public boolean connect_sock() { if (sock != null) { return true; } if ((desired_device_id == 0) && (desired_device_ip == 0)) { dbg.printf("hdhomerun_control_connect_sock: no device specified\n"); return false; } if (HDHomerun_Discover.hdhomerun_discover_is_ip_multicast(desired_device_ip)) { dbg.printf("hdhomerun_control_connect_sock: cannot use multicast ip address for device operations\n"); return false; } /* Find device. */ hdhomerun_discover_device_t[] result = new hdhomerun_discover_device_t[1]; if (HDHomerun_Discover.hdhomerun_discover_find_devices_custom(desired_device_ip, HDHomerun_Pkt.HDHOMERUN_DEVICE_TYPE_WILDCARD, desired_device_id, result, 1) <= 0) { dbg.printf("hdhomerun_control_connect_sock: device not found\n"); return false; } actual_device_ip = result[0].ip_addr; actual_device_id = result[0].device_id; /* Create socket. */ try { sock = new HDHomerun_Sock(actual_device_ip, HDHomerun_Pkt.HDHOMERUN_CONTROL_TCP_PORT, 0, 0, HDHOMERUN_CONTROL_CONNECT_TIMEOUT); } catch (IOException e) { dbg.printf(String.format("hdhomerun_control_connect_sock: failed to create socket (%s)\n", sock.getlasterror())); return false; } if (!sock.isValid()) { dbg.printf(String.format("hdhomerun_control_connect_sock: failed to create socket (%s)\n", sock.getlasterror())); close_sock(); return false; } /* Success. */ return true; } public void destroy() { close_sock(); } /* * Get the actual device id or ip of the device. * * Returns 0 if the device id cannot be determined. */ int get_device_id() { if (!connect_sock()) { dbg.printf("hdhomerun_control_get_device_id: connect failed\n"); return 0; } return actual_device_id; } public int get_device_ip() { if (!connect_sock()) { dbg.printf("hdhomerun_control_get_device_ip: connect failed\n"); return 0; } return actual_device_ip; } public int get_device_id_requested() { return desired_device_id; } public int get_device_ip_requested() { return desired_device_ip; } /* * Get the local machine IP address used when communicating with the device. * * This function is useful for determining the IP address to use with set target commands. * * Returns 32-bit IP address with native endianness, or 0 on error. */ int get_local_addr() { if (!connect_sock()) { dbg.printf("hdhomerun_control_get_local_addr: connect failed\n"); return 0; } InetSocketAddress addr = sock.getsockname_addr(); if (addr == null) { dbg.printf(String.format("hdhomerun_control_get_local_addr: getsockname failed (%s)\n", sock.getlasterror())); return 0; } return HDHomerun_Sock.IPAddr_BytesToInt(addr.getAddress().getAddress()); } /* * Low-level communication. */ private int send_recv_internal(HDHomerun_Pkt tx_pkt, HDHomerun_Pkt rx_pkt, short type, int recv_timeout) { tx_pkt.seal_frame(type); for (int i = 0; i < 2; i++) { if (sock == null) { if (!connect_sock()) { dbg.printf("hdhomerun_control_send_recv: connect failed\n"); return -1; } } if (!send_sock(tx_pkt)) { continue; } if (null == rx_pkt) { return 1; } short[] rsp_type = new short[1]; if (!recv_sock(rx_pkt, rsp_type, recv_timeout)) { continue; } if (rsp_type[0] != type + 1) { dbg.printf("hdhomerun_control_send_recv: unexpected frame type\n"); close_sock(); continue; } return 1; } dbg.printf("hdhomerun_control_send_recv: failed\n"); return -1; } public int send_recv(HDHomerun_Pkt tx_pkt, HDHomerun_Pkt rx_pkt, short type) { return send_recv_internal(tx_pkt, rx_pkt, type, HDHOMERUN_CONTROL_RECV_TIMEOUT); } private boolean send_sock(HDHomerun_Pkt tx_pkt) { if (!sock.send(tx_pkt.buffer, tx_pkt.startIndex, tx_pkt.endIndex - tx_pkt.startIndex, HDHOMERUN_CONTROL_SEND_TIMEOUT)) { dbg.printf(String.format("hdhomerun_control_send_sock: send failed (%s)\n", sock.getlasterror())); close_sock(); return false; } return true; } private boolean recv_sock(HDHomerun_Pkt rx_pkt, short[] ptype, long recv_timeout) { long stop_time = HDHomerun_OS.getcurrenttime() + recv_timeout; rx_pkt.reset(); while (true) { long current_time = HDHomerun_OS.getcurrenttime(); if (current_time >= stop_time) { dbg.printf("hdhomerun_control_recv_sock: timeout\n"); close_sock(); return false; } int[] length = new int[1]; length[0] = rx_pkt.limitIndex - rx_pkt.endIndex; if (!sock.recv(rx_pkt.buffer, rx_pkt.endIndex, length, (int) (stop_time - current_time))) { dbg.printf(String.format("hdhomerun_control_recv_sock: recv failed (%s)\n", sock.getlasterror())); close_sock(); return false; } rx_pkt.endIndex += length[0]; int ret = rx_pkt.open_frame(ptype); if (ret < 0) { dbg.printf("hdhomerun_control_recv_sock: frame error\n"); close_sock(); return false; } if (ret > 0) { return true; } } } /* * Get/set a control variable on the device. * * const char *name: The name of var to get/set (c-string). The supported vars is device/firmware dependant. * const char *value: The value to set (c-string). The format is device/firmware dependant. * char **pvalue: If provided, the caller-supplied char pointer will be populated with a pointer to the value * string returned by the device, or NULL if the device returned an error string. The string will remain * valid until the next call to a control sock function. * char **perror: If provided, the caller-supplied char pointer will be populated with a pointer to the error * string returned by the device, or NULL if the device returned an value string. The string will remain * valid until the next call to a control sock function. * * Returns 1 if the operation was successful (pvalue set, perror NULL). * Returns 0 if the operation was rejected (pvalue NULL, perror set). * Returns -1 if a communication error occurs. */ public int get(final String name, StringBuilder pvalue, StringBuilder perror) { return get_set(name, null, 0, pvalue, perror); } public int set(final String name, final String value, StringBuilder pvalue, StringBuilder perror) { return get_set(name, value, 0, pvalue, perror); } public int set_with_lockkey(final String name, final String value, int lockkey, StringBuilder pvalue, StringBuilder perror) { return get_set(name, value, lockkey, pvalue, perror); } private byte[] getStringBytesInUTF8(final String str) { try { byte[] bytes = str.getBytes("UTF8"); byte[] ret = new byte[bytes.length + 1]; ret[bytes.length] = '\0'; for(int i = 0; i < bytes.length; ++i) ret[i] = bytes[i]; return ret; } catch (UnsupportedEncodingException e) { return null; } } private int get_set(final String name, final String value, int lockkey, StringBuilder pvalue, StringBuilder perror) { /* Request. */ tx_pkt.reset(); byte[] nameBytes = getStringBytesInUTF8(name); short name_len = (short) nameBytes.length; if (tx_pkt.endIndex + 3 + name_len > tx_pkt.limitIndex) { dbg.printf("hdhomerun_control_get_set: request too long\n"); return -1; } tx_pkt.write_u8(HDHomerun_Pkt.HDHOMERUN_TAG_GETSET_NAME); tx_pkt.write_var_length(name_len); tx_pkt.write_mem(nameBytes, name_len); if (value != null && value.length() > 0) { byte[] valueBytes = getStringBytesInUTF8(value); short value_len = (short) (value.length() + 1); if (tx_pkt.endIndex + 3 + value_len > tx_pkt.limitIndex) { dbg.printf("hdhomerun_control_get_set: request too long\n"); return -1; } tx_pkt.write_u8(HDHomerun_Pkt.HDHOMERUN_TAG_GETSET_VALUE); tx_pkt.write_var_length(value_len); tx_pkt.write_mem(valueBytes, value_len); } if (lockkey != 0) { if (tx_pkt.endIndex + 6 > tx_pkt.limitIndex) { dbg.printf("hdhomerun_control_get_set: request too long\n"); return -1; } tx_pkt.write_u8(HDHomerun_Pkt.HDHOMERUN_TAG_GETSET_LOCKKEY); tx_pkt.write_var_length((short) 4); tx_pkt.write_u32(lockkey); } /* Send/Recv. */ if (send_recv_internal(tx_pkt, rx_pkt, HDHomerun_Pkt.HDHOMERUN_TYPE_GETSET_REQ, HDHOMERUN_CONTROL_RECV_TIMEOUT) < 0) { dbg.printf("hdhomerun_control_get_set: send/recv error\n"); return -1; } /* Response. */ while (true) { byte[] tag = new byte[1]; int[] len = new int[1]; int next = rx_pkt.read_tlv(tag, len); if (0 == next) { break; } switch (tag[0]) { case HDHomerun_Pkt.HDHOMERUN_TAG_GETSET_VALUE: if (pvalue != null) { for(int i = 0; i < len[0] - 1; ++i) pvalue.append((char) rx_pkt.buffer[rx_pkt.posIndex + i]); } if (perror != null) { perror = new StringBuilder(""); } return 1; case HDHomerun_Pkt.HDHOMERUN_TAG_ERROR_MESSAGE: if (perror != null) { for(int i = 0; i < len[0] - 1; ++i) perror.append((char) rx_pkt.buffer[rx_pkt.posIndex + i]); dbg.printf(String.format("hdhomerun_control_get_set: %s\n", perror)); } rx_pkt.buffer[rx_pkt.posIndex + len[0]] = 0; if (pvalue != null) { pvalue = new StringBuilder(""); } return 0; } rx_pkt.posIndex = next; } dbg.printf("hdhomerun_control_get_set: missing response tags\n"); return -1; } }