The Harmony Link is an MH "remote."  Essentially it is a WiFi to IR bridge
where you can issue commands on your smartphone via WiFI and the Link will
convert them to IR to control your devices.  In order to support the Link,
we had to add a few API calls to allow configuration of WiFI (SSID, key, etc)
as well as account configuration (email, etc).  The Link does not receive its
configuration via USB like other remotes - it receives it via WiFi.

Signed-off-by: Scott Talbert <s...@techie.net>
---
 libconcord/bindings/python/libconcord.py |  82 ++++++++++++
 libconcord/libconcord.cpp                | 142 ++++++++++++++++++++
 libconcord/libconcord.h                  |  32 +++++
 libconcord/remote.h                      |   4 +
 libconcord/remote_info.h                 |  18 +++
 libconcord/remote_mh.cpp                 | 216 +++++++++++++++++++++++++++++++
 specs/protocol_mh.txt                    |   4 +
 7 files changed, 498 insertions(+)

diff --git a/libconcord/bindings/python/libconcord.py 
b/libconcord/bindings/python/libconcord.py
index 1138a33..e0a2664 100644
--- a/libconcord/bindings/python/libconcord.py
+++ b/libconcord/bindings/python/libconcord.py
@@ -891,3 +891,85 @@ post_new_code = _create_func(
     _in('cb_arg', py_object)
 )
 
+#define MH_STRING_LENGTH 255 /* arbitrary */
+MH_STRING_LENGTH = 255
+
+#define MH_MAX_WIFI_NETWORKS 30 /* arbitrary */
+MH_MAX_WIFI_NETWORKS = 30
+
+#struct mh_cfg_properties {
+#    char host_name[MH_STRING_LENGTH];
+#    char email[MH_STRING_LENGTH];
+#    char service_link[MH_STRING_LENGTH];
+#};
+class mh_cfg_properties(Structure):
+    _fields_ = [("host_name", c_char * MH_STRING_LENGTH),
+                ("email", c_char * MH_STRING_LENGTH),
+                ("service_link", c_char * MH_STRING_LENGTH)]
+
+#struct mh_wifi_config {
+#    char ssid[MH_STRING_LENGTH];
+#    char encryption[MH_STRING_LENGTH];
+#    char password[MH_STRING_LENGTH];
+#    char connect_status[MH_STRING_LENGTH];
+#    char error_code[MH_STRING_LENGTH];
+#};
+class mh_wifi_config(Structure):
+    _fields_ = [("ssid", c_char * MH_STRING_LENGTH),
+                ("encryption", c_char * MH_STRING_LENGTH),
+                ("password", c_char * MH_STRING_LENGTH),
+                ("connect_status", c_char * MH_STRING_LENGTH),
+                ("error_code", c_char * MH_STRING_LENGTH)]
+
+#struct mh_wifi_network {
+#    char ssid[MH_STRING_LENGTH];
+#    char signal_strength[MH_STRING_LENGTH];
+#    char channel[MH_STRING_LENGTH];
+#    char encryption[MH_STRING_LENGTH];
+#};
+class mh_wifi_network(Structure):
+    _fields_ = [("ssid", c_char * MH_STRING_LENGTH),
+                ("signal_strength", c_char * MH_STRING_LENGTH),
+                ("channel", c_char * MH_STRING_LENGTH),
+                ("encryption", c_char * MH_STRING_LENGTH)]
+
+#struct mh_wifi_networks {
+#    struct mh_wifi_network network[MH_MAX_WIFI_NETWORKS]; 
+#};
+class mh_wifi_networks(Structure):
+    _fields_ = [("network", mh_wifi_network * MH_MAX_WIFI_NETWORKS)]
+
+#int mh_get_cfg_properties(struct mh_cfg_properties *properties);
+mh_get_cfg_properties = _create_func(
+    'mh_get_cfg_properties',
+    _ret_lc_concord(),
+    _in('properties', POINTER(mh_cfg_properties))
+)
+
+#int mh_set_cfg_properties(const struct mh_cfg_properties *properties);
+mh_set_cfg_properties = _create_func(
+    'mh_set_cfg_properties',
+    _ret_lc_concord(),
+    _in('properties', POINTER(mh_cfg_properties))
+)
+
+#int mh_get_wifi_networks(struct mh_wifi_networks *networks);
+mh_get_wifi_networks = _create_func(
+    'mh_get_wifi_networks',
+    _ret_lc_concord(),
+    _in('networks', POINTER(mh_wifi_networks))
+)
+
+#int mh_get_wifi_config(struct mh_wifi_config *config);
+mh_get_wifi_config = _create_func(
+    'mh_get_wifi_config',
+    _ret_lc_concord(),
+    _in('config', POINTER(mh_wifi_config))
+)
+
+#int mh_set_wifi_config(const struct mh_wifi_config *config);
+mh_set_wifi_config = _create_func(
+    'mh_set_wifi_config',
+    _ret_lc_concord(),
+    _in('config', POINTER(mh_wifi_config))
+)
diff --git a/libconcord/libconcord.cpp b/libconcord/libconcord.cpp
index 3cb2611..c2c15e5 100644
--- a/libconcord/libconcord.cpp
+++ b/libconcord/libconcord.cpp
@@ -230,6 +230,7 @@ int is_mh_pid(unsigned int pid)
     switch (pid) {
         case 0xC124: /* Harmony 300 */
         case 0xC125: /* Harmony 200 */
+        case 0xC126: /* Harmony Link */
             return 1;
         default:
             return 0;
@@ -1696,6 +1697,147 @@ int post_new_code(char *key_name, char *encoded_signal, 
lc_callback cb,
 }
 
 /*
+ * Special structures and methods for the Harmony Link
+ */
+void mh_get_value(char *buffer, const char *key, char *dest)
+{
+    char *start = NULL;
+    char *end = NULL;
+    int len;
+    start = strstr(buffer, key);
+    if (start) {
+        start += strlen(key);
+        end = strstr(start, "\n");
+        if (end) {
+            len = end - start;
+            if (len >= MH_STRING_LENGTH)
+                start = NULL;
+        }
+    }
+    if (start && end)
+        strncpy(dest, start, len);
+}
+
+int mh_get_cfg_properties(struct mh_cfg_properties *properties)
+{
+    if (!is_mh_remote())
+        return LC_ERROR;
+
+    int err;
+    int buflen = 5000;
+    char buffer[buflen];
+    int data_read;
+    if (err = ((CRemoteMH*)rmt)->ReadFile("/cfg/properties", (uint8_t*)buffer,
+                                          buflen, &data_read))
+        return err;
+
+    mh_get_value(buffer, "host_name,", properties->host_name);
+    mh_get_value(buffer, "account_email,", properties->email);
+    mh_get_value(buffer, "discovery_service_link,", properties->service_link);
+
+    return 0;
+}
+
+int mh_set_cfg_properties(const struct mh_cfg_properties *properties)
+{
+    if (!is_mh_remote())
+        return LC_ERROR;
+
+    int err;
+    std::string str_buffer;
+    str_buffer += "host_name,";
+    str_buffer += properties->host_name;
+    str_buffer += "\n";
+    str_buffer += "account_email,";
+    str_buffer += properties->email;
+    str_buffer += "\n";
+    str_buffer += "discovery_service_link,";
+    str_buffer += properties->service_link;
+    str_buffer += "\n";
+
+    err = ((CRemoteMH*)rmt)->WriteFile("/cfg/properties",
+                                       (uint8_t*)str_buffer.c_str(),
+                                       strlen(str_buffer.c_str()));
+    return err;
+}
+
+int mh_get_wifi_networks(struct mh_wifi_networks *networks)
+{
+    if (!is_mh_remote())
+        return LC_ERROR;
+
+    int err;
+    int buflen = 5000;
+    char buffer[buflen];
+    int data_read;
+    if (err = ((CRemoteMH*)rmt)->ReadFile("/sys/wifi/networks",
+                                          (uint8_t*)buffer, buflen, 
&data_read))
+        return err;
+
+    char *buf_ptr = buffer;
+    int i = 0;
+    while (strstr(buf_ptr, "item,") && (i < MH_MAX_WIFI_NETWORKS)) {
+        mh_get_value(buf_ptr, "ssid,", networks->network[i].ssid);
+        mh_get_value(buf_ptr, "signal_strength,",
+                     networks->network[i].signal_strength);
+        mh_get_value(buf_ptr, "channel,", networks->network[i].channel);
+        mh_get_value(buf_ptr, "encryption,", networks->network[i].encryption);
+        buf_ptr = strstr(buf_ptr, "encryption,");
+        if (buf_ptr)
+            buf_ptr = strstr(buf_ptr, "\n");
+        i++;
+    }
+
+    return 0;
+}
+
+int mh_get_wifi_config(struct mh_wifi_config *config)
+{
+    if (!is_mh_remote())
+        return LC_ERROR;
+
+    int err;
+    int buflen = 5000;
+    char buffer[buflen];
+    int data_read;
+    if (err = ((CRemoteMH*)rmt)->ReadFile("/sys/wifi/connect", 
(uint8_t*)buffer,
+                                          buflen, &data_read))
+        return err;
+
+    mh_get_value(buffer, "ssid,", config->ssid);
+    mh_get_value(buffer, "encryption,", config->encryption);
+    mh_get_value(buffer, "password,", config->password);
+    mh_get_value(buffer, "connect_status,", config->connect_status);
+    mh_get_value(buffer, "error_code,", config->error_code);
+
+    return 0;
+}
+
+int mh_set_wifi_config(const struct mh_wifi_config *config)
+{
+    if (!is_mh_remote())
+        return LC_ERROR;
+
+    int err;
+    std::string str_buffer;
+    str_buffer += "ssid,";
+    str_buffer += config->ssid;
+    str_buffer += "\n";
+    str_buffer += "encryption,";
+    str_buffer += config->encryption;
+    str_buffer += "\n";
+    str_buffer += "user,\n"; /* not sure what this is - appears unused */
+    str_buffer += "password,";
+    str_buffer += config->password;
+    str_buffer += "\n";
+
+    err = ((CRemoteMH*)rmt)->WriteFile("/sys/wifi/connect",
+                                       (uint8_t*)str_buffer.c_str(),
+                                       strlen(str_buffer.c_str()));
+    return err;
+}
+
+/*
  * PRIVATE-SHARED INTERNAL FUNCTIONS
  * These are functions used by the whole library but are NOT part of the API
  * and probably should be somewhere else...
diff --git a/libconcord/libconcord.h b/libconcord/libconcord.h
index 3865c98..fd4926d 100644
--- a/libconcord/libconcord.h
+++ b/libconcord/libconcord.h
@@ -510,6 +510,38 @@ void delete_encoded_signal(char *encoded_signal);
 int post_new_code(char *key_name, char *encoded_signal, lc_callback cb,
                   void *cb_arg);
 
+/*
+ * Special structures and methods for the Harmony Link
+ */
+#define MH_STRING_LENGTH 255 /* arbitrary */
+#define MH_MAX_WIFI_NETWORKS 30 /* arbitrary */
+struct mh_cfg_properties {
+    char host_name[MH_STRING_LENGTH];
+    char email[MH_STRING_LENGTH];
+    char service_link[MH_STRING_LENGTH];
+};
+struct mh_wifi_config {
+    char ssid[MH_STRING_LENGTH];
+    char encryption[MH_STRING_LENGTH];
+    char password[MH_STRING_LENGTH];
+    char connect_status[MH_STRING_LENGTH];
+    char error_code[MH_STRING_LENGTH];
+};
+struct mh_wifi_network {
+    char ssid[MH_STRING_LENGTH];
+    char signal_strength[MH_STRING_LENGTH];
+    char channel[MH_STRING_LENGTH];
+    char encryption[MH_STRING_LENGTH];
+};
+struct mh_wifi_networks {
+    struct mh_wifi_network network[MH_MAX_WIFI_NETWORKS]; 
+};
+int mh_get_cfg_properties(struct mh_cfg_properties *properties);
+int mh_set_cfg_properties(const struct mh_cfg_properties *properties);
+int mh_get_wifi_networks(struct mh_wifi_networks *networks);
+int mh_get_wifi_config(struct mh_wifi_config *config);
+int mh_set_wifi_config(const struct mh_wifi_config *config);
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/libconcord/remote.h b/libconcord/remote.h
index d26ea4a..6c61da5 100644
--- a/libconcord/remote.h
+++ b/libconcord/remote.h
@@ -426,6 +426,10 @@ public:
     int IsZRemote() {return false;}
     int IsUSBNet() {return false;}
     int IsMHRemote() {return true;}
+
+    int ReadFile(const char *filename, uint8_t *rd, const uint32_t rdlen,
+        int *data_read);
+    int WriteFile(const char *filename, uint8_t *wr, const uint32_t wrlen);
 };
 
 #endif //REMOTE_H
diff --git a/libconcord/remote_info.h b/libconcord/remote_info.h
index 83f18c3..72d55d7 100644
--- a/libconcord/remote_info.h
+++ b/libconcord/remote_info.h
@@ -499,6 +499,24 @@ static const TArchInfo ArchList[]={
                0,                              // ram_size
                0,                              // eeprom_size
                "",                             // usb
+       },
+       /* arch 17: Link */
+       {
+               0,                              // serial_location
+               0,                              // serial_address
+               0,                              // flash_base
+               0,                              // firmware_base
+               0,                              // config_base
+               0,                              // firmware_update_base
+               0,                              // firmware_4847_offset
+               0x4D505347,                     // cookie
+               0,                              // cookie_size
+               0,                              // end_vector
+               "",                             // micro
+               0,                              // flash_size
+               0,                              // ram_size
+               0,                              // eeprom_size
+               "",                             // usb
        }
 };
 
diff --git a/libconcord/remote_mh.cpp b/libconcord/remote_mh.cpp
index ce55ee9..41efd6f 100644
--- a/libconcord/remote_mh.cpp
+++ b/libconcord/remote_mh.cpp
@@ -35,6 +35,7 @@
 
 /* Timeout to wait for a response, in ms. */
 #define MH_TIMEOUT 5000
+#define LINK_TIMEOUT 20000 /* Harmony Link takes a *long* time for some ops. */
 #define MH_MAX_PACKET_SIZE 64
 /* In data mode, two bytes are used for the header. */
 #define MH_MAX_DATA_SIZE 62
@@ -154,6 +155,221 @@ uint8_t get_seq(uint8_t &seq)
     return tmp;
 }
 
+int CRemoteMH::ReadFile(const char *filename, uint8_t *rd, const uint32_t 
rdlen,
+                        int *data_read)
+{
+    int err = 0;
+    uint8_t seq = 0;
+
+    if (strlen(filename) > (MH_MAX_PACKET_SIZE - 9)) {
+        debug("Filename too long");
+        return LC_ERROR;
+    }
+    uint8_t msg_idx = 0;
+    uint8_t msg_read_file[MH_MAX_PACKET_SIZE] =
+        { 0xFF, 0x01, get_seq(seq), 0x02, 0x80 };
+    msg_idx += 5;
+    memcpy(&msg_read_file[msg_idx], filename, strlen(filename));
+    msg_idx += strlen(filename);
+    msg_read_file[msg_idx++] = 0x00;
+    msg_read_file[msg_idx++] = 0x80;
+    msg_read_file[msg_idx++] = 'R';
+    msg_read_file[msg_idx++] = 0x00;
+
+    /* 
+     * The last parameter is the number of packets to read.  For these types of
+     * 'files' the official software always seems to send 0x32 and then expects
+     * to see an 0xFE as an 'EOF.'  We'll do that too (instead of keeping track
+     * of the packet count).
+     */
+    uint8_t msg_ack[MH_MAX_PACKET_SIZE] =
+        { 0xFF, 0x04, get_seq(seq), 0x02, 0x01, 0x00, 0x01, 0x32 };
+    const uint8_t msg_eof = { 0xFE };
+    uint8_t rsp[MH_MAX_PACKET_SIZE];
+
+    if ((err = HID_WriteReport(msg_read_file))) {
+        debug("Failed to write to remote");
+        return LC_ERROR_WRITE;
+    }
+
+    if ((err = HID_ReadReport(rsp, MH_TIMEOUT))) {
+        debug("Failed to read from remote");
+        return LC_ERROR_READ;
+    }
+    debug("msg_read_file");
+    debug_print_packet(rsp);
+
+    /*
+     * First parameter in the read file "ack" message is reused in subsequent
+     * messages to the remote.  Save it.
+     */
+    const uint8_t param = rsp[5];
+    msg_ack[5] = param;
+
+    if ((err = HID_WriteReport(msg_ack))) {
+        debug("Failed to write to remote");
+        return LC_ERROR_WRITE;
+    }
+
+    if ((err = HID_ReadReport(rsp))) {
+        debug("Failed to read from remote");
+        return LC_ERROR_READ;
+    }
+    debug("msg_ack");
+    debug_print_packet(rsp);
+
+    *data_read = 0;
+    uint8_t *rd_ptr = rd;
+    while(!(err = HID_ReadReport(rsp, MH_TIMEOUT))) {
+        debug_print_packet(rsp);
+        if (rsp[0] == msg_eof) {
+            break;
+        }
+        // Ignore 1st two bits on 2nd byte for length.
+        int len = rsp[1] & 0x3F;
+        // Skip 1st two bytes, read up to packet length.  "len"
+        // represents the payload length (not including the two size
+        // bytes), so we read a full "len" bytes from 2 to len+2.
+        if (rd) {
+            if ((*data_read + len) > rdlen) {
+                debug("ERROR: buffer length exceeded!");
+                return LC_ERROR;
+            }
+            memcpy(rd_ptr, &rsp[2], len);
+            rd_ptr += len;
+        }
+        *data_read += len;
+    }
+    debug("data_read=%d", *data_read);
+
+    /* send reset sequence message */
+    const uint8_t msg_reset_seq[MH_MAX_PACKET_SIZE] =
+        { 0xFF, 0x07, get_seq(seq), 0x01, 0x01, param };
+    if ((err = HID_WriteReport(msg_reset_seq))) {
+        debug("Failed to write to remote");
+        return LC_ERROR_WRITE;
+    }
+    if ((err = HID_ReadReport(rsp))) {
+        debug("Failed to read from remote");
+        return LC_ERROR_READ;
+    }
+    debug("msg_reset_seq");
+    debug_print_packet(rsp);
+
+    return 0;
+}
+
+int CRemoteMH::WriteFile(const char *filename, uint8_t *wr,
+                         const uint32_t wrlen)
+{
+    int err = 0;
+    uint8_t seq = 0;
+    uint8_t rsp[MH_MAX_PACKET_SIZE];
+
+    if (strlen(filename) > (MH_MAX_PACKET_SIZE - 14)) {
+        debug("Filename too long");
+        return LC_ERROR;
+    }
+
+    uint8_t msg_idx = 0;
+    uint8_t msg_write_file[MH_MAX_PACKET_SIZE] =
+        { 0xFF, 0x01, get_seq(seq), 0x03, 0x80 };
+    msg_idx += 5;
+    memcpy(&msg_write_file[msg_idx], filename, strlen(filename));
+    msg_idx += strlen(filename);
+    msg_write_file[msg_idx++] = 0x00;
+    msg_write_file[msg_idx++] = 0x80;
+    msg_write_file[msg_idx++] = 'W';
+    msg_write_file[msg_idx++] = 0x00;
+    msg_write_file[msg_idx++] = 0x04;
+    msg_write_file[msg_idx++] = (wrlen & 0xFF000000) >> 24;
+    msg_write_file[msg_idx++] = (wrlen & 0x00FF0000) >> 16;
+    msg_write_file[msg_idx++] = (wrlen & 0x0000FF00) >> 8;
+    msg_write_file[msg_idx++] = wrlen & 0x000000FF;
+
+    if ((err = HID_WriteReport(msg_write_file))) {
+        debug("Failed to write to remote");
+        return LC_ERROR_WRITE;
+    }
+
+    if ((err = HID_ReadReport(rsp, MH_TIMEOUT))) {
+        debug("Failed to read from remote");
+        return LC_ERROR_READ;
+    }
+    debug("msg_write_file");
+    debug_print_packet(rsp);
+
+    /*
+     * First parameter in the read file "ack" message is reused in subsequent
+     * messages to the remote.  Save it.
+     */
+    const uint8_t param = rsp[5];
+
+    uint8_t pkts_to_send = wrlen / MH_MAX_DATA_SIZE;
+    if ((wrlen % MH_MAX_DATA_SIZE) != 0)
+        pkts_to_send++;
+    pkts_to_send++; // count is always one more than the actual count
+
+    uint8_t msg_ack[MH_MAX_PACKET_SIZE] =
+        { 0xFF, 0x03, get_seq(seq), 0x02, 0x01, param, 0x01, pkts_to_send };
+
+    if ((err = HID_WriteReport(msg_ack))) {
+        debug("Failed to write to remote");
+        return LC_ERROR_WRITE;
+    }
+    debug("msg_ack");
+    /* No response expected - proceed to send data. */
+
+    uint8_t *wr_ptr = const_cast<uint8_t*>(wr);
+    uint32_t tlen = wrlen;
+    uint8_t pkt_len;
+    uint8_t tmp_pkt[MH_MAX_PACKET_SIZE];
+    while (tlen) {
+        pkt_len = MH_MAX_DATA_SIZE;
+        if (tlen < pkt_len) {
+            pkt_len = tlen;
+            for (int i = pkt_len; i < MH_MAX_PACKET_SIZE; i++)
+                tmp_pkt[i] = 0x00;
+        }
+        tlen -= pkt_len;
+
+        tmp_pkt[0] = get_seq(seq);
+        tmp_pkt[1] = pkt_len;
+        memcpy(&tmp_pkt[2], wr_ptr, pkt_len);
+
+        debug("DATA sending %d bytes, %d bytes left", pkt_len, tlen);
+
+        if (err = HID_WriteReport(tmp_pkt)) {
+            return err;
+        }
+        wr_ptr += pkt_len;
+    }
+
+    /* wait for remote to send us a response */
+    if ((err = HID_ReadReport(rsp, LINK_TIMEOUT))) {
+        debug("Failed to read from remote");
+        return LC_ERROR_READ;
+    }
+    debug("after writing file");
+    debug_print_packet(rsp);
+
+    /* send reset sequence message */
+    const uint8_t msg_reset_seq[MH_MAX_PACKET_SIZE] =
+        { 0xFF, 0x07, get_seq(seq), 0x01, 0x01, param };
+    if ((err = HID_WriteReport(msg_reset_seq))) {
+        debug("Failed to write to remote");
+        return LC_ERROR_WRITE;
+    }
+    if ((err = HID_ReadReport(rsp))) {
+        debug("Failed to read from remote");
+        return LC_ERROR_READ;
+    }
+    debug("msg_reset_seq");
+    debug_print_packet(rsp);
+
+    return 0;
+}
+
 /*
  * Send the GET_VERSION command to the remote, and read the response.
  *
diff --git a/specs/protocol_mh.txt b/specs/protocol_mh.txt
index 2a29583..dd4f873 100644
--- a/specs/protocol_mh.txt
+++ b/specs/protocol_mh.txt
@@ -21,6 +21,10 @@ Command message 0x01 - Read/Write a file.
                  Identity file is /sys/sysinfo
                  Config file is /cfg/usercfg
                  IR learn is /ir/ir_cap
+                  Harmony Link Configuration is /cfg/properties
+                          (room location, email, discovery service link)
+                  Harmony Link Wifi Network Scan List is /sys/wifi/networks
+                  Harmony Link Wifi Configuration is /sys/wifi/connect
        Parameter 2: String; 'W' for write, 'R' for read.
        Parameter 3: 4 bytes; length of data to write (only use for writes)
 Response (to a read command):
-- 
1.8.3.1


------------------------------------------------------------------------------
See everything from the browser to the database with AppDynamics
Get end-to-end visibility with application monitoring from AppDynamics
Isolate bottlenecks and diagnose root cause in seconds.
Start your free trial of AppDynamics Pro today!
http://pubads.g.doubleclick.net/gampad/clk?id=48808831&iu=/4140/ostg.clktrk
_______________________________________________
concordance-devel mailing list
concordance-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/concordance-devel

Reply via email to