BVMiko / hisense-udp

Communicate with some Hisense brand Smart TV over UDP network packets
MIT License
12 stars 1 forks source link

"How to" question and more #2

Open sensboston opened 5 years ago

sensboston commented 5 years ago

Hello!

It looks like your code is the only source code I can find for communicating with Hisense Smart TV via UDP, big thanks for that!

Command python hisense.py -l finds and shows my Hisense Smart TV:

{'192.168.1.33': <HisenseDataObject method:DISCOVERYACK version:VERSION102 redundantip:192.168.1.33 tvdescriptor:SmartTV08 extra:
ip=192.168.1.33
devicename=Smart TV
versionid=VERSION300
sourcedesp=ATV:0;DTV:1;VIDEO1:2;VIDEO2:3;YPbPr:4;HDMI1:5;HDMI2:6;HDMI3:7;VGA:8;MC:10
inputmethod=1
tvconvergence=0
udpchannelsurport=1
platform=1  #0-android 1-linux
>}

but I can't figure out how to send "remote" keys to smart TV (sorry but I'm not proficient in Python).

For example, command python hisense.py KEY_APPS produces this output:

``` postloop postloop ('cm_read: ', 'STATUS 500\r\nPORT: 40168 \r\nID: 93901743\r\n\r\n\x00') postloop postloop postloop ('ct_read: ', 'STATUS 500\r\nVERSION: 0001\r\n\r\n') postloop postloop ('ct_read: ', 'STATUS 500\r\nPORT: 55198 \r\nID: 93901743\r\n\r\n\x00') ('creating new cc: ', 55198) postloop postloop ('key: ', 'KEY_VOLUMEUP') postloop ('cc_read: ', 'HEA 0000\r\n') ('key: ', 'KEY_INPUT') postloop ('cc_read: ', 'HEA 0000\r\n') ('key: ', 'KEY_INPUT1') postloop ('key: ', 'KEY_INPUT_1') postloop ('cc_read: ', 'HEA 0000\r\n') ('key: ', 'KEY_INPUT2') postloop ('key: ', 'KEY_INPUT_2') postloop ('cc_read: ', 'HEA 0000\r\n') ('key: ', 'KEY_MODE') postloop ('key: ', 'KEY_MODE1') postloop ('cc_read: ', 'HEA 0000\r\n') ('key: ', 'KEY_MODE_1') postloop ('key: ', 'KEY_MODE2') postloop ('cc_read: ', 'HEA 0000\r\n') ('key: ', 'KEY_MODE_2') postloop ('key: ', 'KEY_SOURCE') postloop ('cc_read: ', 'HEA 0000\r\n') ('key: ', 'KEY_SOURCE1') postloop ('key: ', 'KEY_SOURCE_1') postloop ('cc_read: ', 'HEA 0000\r\n') ('key: ', 'KEY_SOURCE2') postloop ('key: ', 'KEY_SOURCE_2') postloop ('cc_read: ', 'HEA 0000\r\n') ('key: ', 'KEY_HDMI') postloop ('key: ', 'KEY_HDMI1') postloop ('cc_read: ', 'HEA 0000\r\n') ('key: ', 'KEY_HDMI_1') postloop ('key: ', 'KEY_HDMI2') postloop ('cc_read: ', 'HEA 0000\r\n') ('key: ', 'KEY_HDMI_2') postloop ('key: ', 'KEY_LIVETV') postloop ('cc_read: ', 'HEA 0000\r\n') ('key: ', 'KEY_LIVE_TV') postloop ('key: ', 'KEY_LIVE') postloop ('cc_read: ', 'HEA 0000\r\n') ('key: ', 'KEY_TVLIVE') postloop ('key: ', 'KEY_TV_LIVE') postloop ('cc_read: ', 'HEA 0000\r\n') ('key: ', 'KEY_MUTE') postloop ('key: ', 'KEY_MUTING') postloop ('cc_read: ', 'HEA 0000\r\n') ('key: ', 'KEY_SLEEPTIMER') postloop ('key: ', 'KEY_TIMER') postloop ('cc_read: ', 'HEA 0000\r\n') ('key: ', 'KEY_TIMEOUT') postloop ('key: ', 'KEY_TIMEOFF') postloop ('cc_read: ', 'HEA 0000\r\n') ('key: ', 'KEY_APP') postloop ('key: ', 'KEY_APPS') postloop ('cc_read: ', 'HEA 0000\r\n') ('key: ', 'KEY_APPLICATIONS') postloop ('key: ', 'KEY_PROG') postloop ('cc_read: ', 'HEA 0000\r\n') ('key: ', 'KEY_PROGS') postloop ('key: ', 'KEY_PROGRAMS') postloop ('cc_read: ', 'HEA 0000\r\n') ('key: ', 'KEY_FN') postloop Traceback (most recent call last): File "hisense.py", line 285, in main(sys.argv[1:]) File "hisense.py", line 273, in main s.write() File "hisense.py", line 148, in write key = self.keylist[self.keyloop] IndexError: tuple index out of range ```

and nothing more. Result: pressed key "MUTE" (sound muted on TV)

Could you please explain your code for a little? Will be good if you can provide a C# or C++ conversion from Python (but it's too much, I know :) )

BVMiko commented 5 years ago

Hello @sensboston

I abandoned development on this project, as this Hisense protocol doesn't seem to offer any of the features that I wanted (power off, power on, change input source), however I've just pushed a new version which properly accepts individual key input commands. You can now run it as your example before: python hisense.py KEY_MUTE, or by passing multiple keys at once: python hisense.py KEY_VOLUMEUP KEY_VOLUMEUP KEY_VOLUMEUP, etc. Note: KEY_APPS doesn't seem to work for me.

Unfortuately, I was never able to find a list of names for all the keys on the remote. I was able to extract a few key names by sniffing packets from an Android app; but it only provides a fraction of the buttons on my physical remote control. Most of the list in the comments of my code was just my attempt at a brute force approach, and do not work. I will try to find my original list of working keys and post it.

Also, I found that the Hisense network connection is somewhat unreliable; it occasionally fails to acknowledge external commands. The communication to send one keypress (handshake + command) involves 6 messages being sent; and this problem seems to compound into ~3-5% failure rate for me over WiFi.

This code was built by sniffing packets from an official Hisense app for Android. The communication is done via several separate network connections to a few UDP ports on the TV, which appear to be internally named CM, CT, and CC. In order to keep them organized, my code has 3 separate classes for managing these UDP ports. The device discovery is also done via a UDP broadcast.

Unfortunately I don't have the time to fully re-implement this low-level network communication in C# or C++, however I'd be happy to assist you if you choose to create one. I've made some updates to the code to output the complete messages sent to the 3 main UDP ports, so that should make it much easier for you to perform the conversion.

sensboston commented 5 years ago

Hi @BVMiko

Thanks a lot for reply and explanation! It's a pity what Hisense can't provide useful and full-featured app for remote control their Smart TVs via IP :( I tried to contact Hisense tech support but only got a standard useless (and automated) reply, nothing more. But I still believe what they have ability to control everything (except power, probably) via UDP.

Current Hisense original Android app can't find my TV at all (maybe buggy, or not tested on 2017 series TV) but this version works perfectly.

As for connectivity: after my yesterday experiments (I created a "Mobile hotspot" on my Windows 10 desktop and connected both TV and Samsung Galaxy S9 to new network in tries to capture UDP packets via Wireshark. Unfortunately I can't capture any useful info because (probably) of the lack of proper experience :( ) I can't find TV from my desktop anymore, from your Python app or from my ported C# code (yes, I ported a discovery part and it worked perfect but right before network change. I returned TV back to the wired network; also tried wireless - same as desktop - nothing helps).

However, old Android app (I mentioned above) still works fine and can find TV in a seconds! So, probably Hisense devs are using another method than broadcast DISCOVERY# via 50000 UDP port. Unfortunately I don't have any rooted Android phone in house to get 100% all packets. If you can help me with it, it will be great. By the way, I'll continue my experiments with "mobile hotspot" later.

With old app "V-Remote" (mentioned above) I'm able to send CH+, CH-, VOL+, VOL-, QUICK SETTINGS, HOME, BACK, UP, DOWN, LEFT, RIGHT, OK (or RETURN), all digits (when cable or antenna TV input is selected), all virtual keyboard keycodes and mouse movements (don't know how to try).

P.S. My target goal is to create small Android app (or widget) to switch HDMI input from Chromecast to HTPC with one button click. Extra goal is to create a standard physical "IR remote" clone app (but working via network) for Android.

sensboston commented 5 years ago

Update:

This code (it's a clone of your discovery but on C#) is not working

            tvList.Clear();
            TVList.ItemsSource = null;
            try
            {
                using (Socket s = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp))
                {
                    s.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, 1);
                    s.SendTimeout = s.ReceiveTimeout = 500;
                    s.Bind(new IPEndPoint(IPAddress.Any, 54321));
                    s.SendTo(Encoding.ASCII.GetBytes("DISCOVERY#"), new IPEndPoint(IPAddress.Broadcast, 50000));
                    byte[] buf = new byte[1024];
                    int bufSize = s.Receive(buf);
                    var receivedString = Encoding.ASCII.GetString(buf, 0, bufSize);
                    var foundTVs = receivedString.Split(new string[] { "DISCOVERYACK#" }, StringSplitOptions.RemoveEmptyEntries);
                    foreach (var tv in foundTVs)
                        tvList.Add(new HisenseTV(tv));
                }
            }
            catch (Exception ex)
            {
                Debug.WriteLine(ex.Message);
            }
            finally
            {
                TVList.ItemsSource = tvList;
            }

but this one (only changes in EndPoint)

        private void ScanTVButton_Click(object sender, RoutedEventArgs e)
        {
            tvList.Clear();
            TVList.ItemsSource = null;
            try
            {
                using (Socket s = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp))
                {
                    s.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, 1);
                    s.SendTimeout = s.ReceiveTimeout = 1500;
                    s.Bind(new IPEndPoint(IPAddress.Any, 54321));
                    s.SendTo(Encoding.ASCII.GetBytes("DISCOVERY#"), new IPEndPoint(new IPAddress(new byte[] { 192, 168, 1, 33 }), 50000));
                    byte[] buf = new byte[1024];
                    int bufSize = s.Receive(buf);
                    var receivedString = Encoding.ASCII.GetString(buf, 0, bufSize);
                    var foundTVs = receivedString.Split(new string[] { "DISCOVERYACK#" }, StringSplitOptions.RemoveEmptyEntries);
                    foreach (var tv in foundTVs)
                        tvList.Add(new HisenseTV(tv));
                }
            }
            catch (Exception ex)
            {
                Debug.WriteLine(ex.Message);
            }
            finally
            {
                TVList.ItemsSource = tvList;
            }

works perfectly. So, subnet lookup will work fine and fast; probably this is much better than broadcasting.

BVMiko commented 5 years ago

I tried the Hisense contact route as well; reaching out to several different email addresses, but never got any response.

I've done some thorough testing with many apps that say they work with Hisense, but only one or two would connect at all. One was this V-Remote app, and one other which would only successfully handle discovery. I did packet sniffing with the V-Remote app (on a rooted Android), which is how I started this project. I did my packet sniffing with tcpdump for Android, which only works on a rooted device but works very well.

When I was building the initial communication protocol, I frequently had to unplug the TV; their UDP server seemed to crash if you send a malformed request, and the normal hibernate/standby mode (power button) does not reset this. It is strange that you report the V-Remote app can work after the server stops responding; perhaps it is not crashing, but is blacklisting the source IP address.

As far as the data from the original button presses on the app, below is my notes from what I extracted. I separated the HISENSE_DELIMITER into columns to make it easier to read. The Lit_ prefix was related to keyboard input and the REL_XXXX_XXXX was touchpad mouse movement. I never determined if the touchpad movement was absolute or relative coordinates, as I didn't intend to use them. I can't remember what generated the REL_XXXX, probably some special keys on the last screen. Let me know if your app has any additional keys you don't see listed below, and I can try to set up some packet sniffing with my new rooted Pixel 3.

Commands (roughly in order as they appear in the app) P1 | P2 | P3 | Key -- | -- | -- | --- 1 | 2 | 2 | KEY_OK 1 | 2 | 2 | KEY_CHANNELUP 1 | 2 | 2 | KEY_CHANNELDOWN 1 | 2 | 2 | KEY_VOLUMEDOWN 1 | 2 | 2 | KEY_VOLUMEUP 1 | 2 | 2 | KEY_MENU 1 | 2 | 2 | KEY_HOME 1 | 2 | 2 | KEY_RETURNS 1 | 2 | 2 | KEY_1 1 | 2 | 2 | KEY_2 1 | 2 | 2 | KEY_3 1 | 2 | 2 | KEY_4 1 | 2 | 2 | KEY_5 1 | 2 | 2 | KEY_6 1 | 2 | 2 | KEY_7 1 | 2 | 2 | KEY_8 1 | 2 | 2 | KEY_9 1 | 2 | 2 | KEY_CHANNELDOT 1 | 2 | 2 | KEY_0 1 | 2 | 2 | KEY_CHANNELLINE 1 | 2 | 2 | KEY_UP 1 | 2 | 2 | KEY_DOWN 1 | 2 | 2 | KEY_LEFT 1 | 2 | 2 | KEY_RIGHT 1 | 2 | 2 | KEY_BACKS 1 | 2 | 2 | KEY_PAUSE 1 | 2 | 2 | KEY_PLAY 1 | 2 | 2 | KEY_FORWARDS 1 | 2 | 2 | KEY_PREVIOUS 1 | 2 | 2 | KEY_STOP 1 | 2 | 2 | KEY_RECORD 1 | 2 | 2 | KEY_NEXT 1 | 2 | 2 | KEY_RED 1 | 2 | 2 | KEY_YELLOW 1 | 2 | 2 | KEY_GREEN 1 | 2 | 2 | KEY_BLUE 2 | 2 | 2 | Lit_a 2 | 2 | 2 | Lit_b 2 | 2 | 2 | Lit_c 2 | 2 | 2 | Lit_@ 2 | 2 | 2 | Lit_" 2 | 2 | 2 | Lit_' 2 | 2 | 2 | Lit_& 2 | 2 | 2 | Lit_A 2 | 2 | 2 | Lit_.. 1 | 2 | 2 | REL_0000_ffe6 1 | 2 | 2 | REL_fffc_ffc2 1 | 2 | 2 | REL_fffb_ffc0 1 | 2 | 2 | REL_fffb_ffcb 1 | 2 | 2 | REL_fff0_ffc7 1 | 2 | 2 | REL_ffe3_ffc5 1 | 2 | 2 | REL_ffe5_ffd1 1 | 2 | 2 | REL_ffe0_ffdc 1 | 2 | 2 | REL_ffe2_ffdf 1 | 2 | 2 | REL_ffe1_ffe0 1 | 2 | 2 | REL_ffda_ffdb 1 | 2 | 2 | REL_ffe9_ffe1 1 | 2 | 2 | REL_ffe0_ffe8 1 | 2 | 2 | REL_ffe3_ffea 1 | 2 | 2 | REL_ffe3_fff3 1 | 2 | 2 | REL_ffef_fffc 1 | 2 | 2 | REL_fff6_0000 1 | 2 | 2 | REL_fff8_ffff 1 | 2 | 2 | REL_0008_0001 1 | 2 | 2 | REL_000a_0009 1 | 2 | 2 | REL_000e_0019 1 | 2 | 2 | REL_0020_0011 1 | 2 | 2 | REL_0022_0012 1 | 2 | 2 | REL_0029_0008 1 | 2 | 2 | REL_003b_0003 1 | 2 | 2 | REL_0048_fffd 1 | 2 | 2 | REL_003b_fffc 1 | 2 | 2 | REL_0045_0000 1 | 2 | 2 | REL_004c_0000 1 | 2 | 2 | REL_0036_fff7 1 | 2 | 2 | REL_0035_fff1 1 | 2 | 2 | REL_0041_fff6 1 | 2 | 2 | REL_0025_fff0 1 | 2 | 2 | REL_001d_fff0 1 | 2 | 2 | REL_0007_fff8 1 | 2 | 2 | REL_ffee_0003 1 | 2 | 2 | REL_ffcd_0017 1 | 2 | 2 | REL_ffb3_002c 1 | 2 | 2 | REL_ffc7_0031 1 | 2 | 2 | REL_ffd5_002f 1 | 2 | 2 | REL_ffd6_0048 1 | 2 | 2 | REL_ffe8_0056 1 | 2 | 2 | REL_0000_005c 1 | 2 | 2 | REL_0012_0069 1 | 2 | 2 | REL_fffb_005c 1 | 2 | 2 | REL_fff5_008e 1 | 2 | 2 | REL_0017_0072 1 | 2 | 2 | REL_0018_0036 1 | 2 | 2 | REL_001c 1 | 2 | 2 | REL_0058 1 | 2 | 2 | REL_0074 1 | 2 | 2 | REL_0071 1 | 2 | 2 | REL_0066 1 | 2 | 2 | REL_0068 1 | 2 | 2 | REL_005c 1 | 2 | 2 | REL_0059 1 | 2 | 2 | REL_0046 1 | 2 | 2 | REL_ffe2 1 | 2 | 2 | REL_ff62 1 | 2 | 2 | REL_ff59 1 | 2 | 2 | REL_ff6e 1 | 2 | 2 | REL_ff8a 1 | 2 | 2 | REL_ffa6 1 | 2 | 2 | REL_ffb8 1 | 2 | 2 | REL_004b 1 | 2 | 2 | REL_006d 1 | 2 | 2 | REL_0093 1 | 2 | 2 | REL_0092 1 | 2 | 2 | REL_006b


My goal is similar to yours: I want to integrate the TV with my voice-controlled smarthome services (Google Assistant, Amazon Alexa, and Mycroft), specifically to turn the TV on/off and to swap input sources between a computer, Nvidia Shield and some game consoles.

P.S. This is actually my second attempt at interfacing to the TV: my first attempt was control via HDMI-CEC (using a Raspberry Pi hardware). Interestingly, HDMI-CEC protocol has a function with a prototype similar to: void change_input_source(int source_number = null) and the Hisense TV does use it; however it seems to completely ignore the source_number parameter and when called will only switch into the caller (Raspberry Pi). Rather dissapointing to discover this after the long process of learning the poorly documented HDMI-CEC protocol and writing that code. HDMI-CEC did support one cool function though - it can pop up a custom message over any source using a native overlay (similar to the volume slider). It would be fun to use it to display notifications.

BVMiko commented 5 years ago

(This is my first time working with C#, so please forgive any poor structure and inefficient coding styles)

I have a slightly modified version of your first example running my machine. I'm using Mono 5.16.0.179 on Linux, so there may be some inconsistencies in how it handles network connections. There's a couple things I'd point out first:

Here is the C# code which works for me and returns the IP address of the first device:

try {
    using (Socket s = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp)) {
        s.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, 1);
        s.SendTimeout = s.ReceiveTimeout = 200;
        s.Bind(new IPEndPoint(IPAddress.Any, 54321));
        s.SendTo(Encoding.ASCII.GetBytes("DISCOVERY#"), new IPEndPoint(IPAddress.Broadcast, 50000));
        byte[] buf = new byte[1024];
        int bufSize = s.Receive(buf);
        string receivedString = Encoding.ASCII.GetString(buf, 0, bufSize);
        Console.WriteLine(receivedString + "\n\n\n");

        string[] foundTVs = receivedString.Split(new Char[] {'#'});
        Console.Write(string.Join("\n", foundTVs));
        return foundTVs[2];
    }
} catch (Exception ex) {
    Console.WriteLine(ex.Message);
    return null;
}
sensboston commented 5 years ago

You've got me wrong: there are no issues in the code, C# or Python (both aren't working with broadcast now - after only one network change on TV - but both worked fine yesterday, before I started my experiments). Administrative permissions are also nothing to do with. Probably, it's something with the packet routing and my ASUS RT-N66U, or maybe TV firmware or... Who knows?

I created a repo for experiments: this code works fine now (but broadcast still not working for me).

Could you please (for simplicity) provide me what I should send to TV to emulate keys?

BVMiko commented 5 years ago

Sure, the Python script currently displays that as debug information on invocation.

('cm_open_connection: ', ('192.168.86.134', 60030))
('cm_send: ', 'CTCREATE\r\nID: 93901743\r\n\r\n\x00')
('cm_read: ', 'STATUS 500\r\nPORT: 48264   \r\nID: 93901743\r\n\r\n\x00')
('ct_open_connection: ', ('192.168.86.134', 48264))
('ct_send: ', 'SUS\x00')
('ct_send: ', 'CTCREATE\r\nMAC: appmac_appmac_app\r\nVERSION: 0001\r\n\r\n\x00')
('ct_read: ', 'STATUS 500\r\nVERSION: 0001\r\n\r\n')
('ct_send: ', 'CCCREATE\r\nID: 93901743\r\n\r\n\x00')
('ct_read: ', 'STATUS 500\r\nPORT: 64987   \r\nID: 93901743\r\n\r\n\x00')
('cc_open_connection:', ('192.168.86.134', 64987))
('cc_send: ', 'SUS\x00')
('cc_send: ', 'CMD 000137\r\n1\r\n1HISENSE_DELIMITER2HISENSE_DELIMITER2HISENSE_DELIMITERKEY_MUTEHISENSE_DELIMITER10HISENSE_DELIMITER0HISENSE_DELIMITER0\r\n\r\n\x00')

I'm not sure if the recurring 93901743 parameter should be a random number for each invocation or if it is meant to identify the source application. From my usage thus far I never saw a need to change it.

The first connection (CM) is always on port 60030. Send the static message, and it will return a dynamic port number for the CT connection. Open the CT connection, handshake with a few more static messages, and then you will get the dynamic port number for the CC connection. Send the initial handshake on the CC port, then send the command string.

In the example command string above:

I know the KEY_RIGHT command will move around inside the input sources overlay; but I never found a way to open the overlay programatically.

Let me know if you'd like any more thorough explanation of this, and if not please keep me updated on any notable progress you make! I hope you can figure out the input source selection.

sensboston commented 5 years ago

Thanks a lot, man, I really appreciate that! I definitely will keep you up to date, and will promptly commit any changes to the test repo I recently created.

Unfortunately, I can't use your Python script (broadcast still not working) or I should modify to the hardcoded IP of my TV set. BTW, it's definitely not a showstopper ;)

BVMiko commented 5 years ago

Great, I'll keep an eye on your repo.

Unfortunately, it doesn't seem to compile with Mono on Linux due to some theme dll dependencies; so I can't run it exactly as-is, so I'll just keep an eye on your commits for now. When you get a little further along, I can get a Windows OS set up somewhere to try it out.

Also, though you've got your own thing going now, I just found and fixed the problem in my code with the -d argument for manual device selection; so the Python script can now properly bypass the UDP broadcast step. Execution would be of the form python hisense.py -d 192.168.1.50 KEY_MUTE.

sensboston commented 5 years ago

Don't worry, I'm doing some refactoring now to get rid of external packages. And WPF GUI apps are running pretty fine under Mono on Linux, you just need to install some additional packages. I'm 99.9% sure you know what to install (or can find it by yourself), if not, check the archive at https://archive.codeplex.com/?p=tinyopds (unfortunately, damn M$ closed the project but wiki pages aren't migrated properly to github. He-he, hope, M$ will not close github soon, but who knows?.. :( )

Will try your script soon and report about result.

sensboston commented 5 years ago

Update: just checked updated Python script, it working pretty fine, thanks a lot, it very helpful!

sensboston commented 5 years ago

BTW, just got a "crazy" idea: AFAIR, there are several tools to extract Hisense TVs firmware. I'm not talking about decompiling binary code (LOL!) but - maybe - we can find some helpful text strings in the binaries...

Also, we can try to post some questions on the Chinese Hisense-related forums by using Google translation services. Just a brainstorming...

BVMiko commented 5 years ago

I spent a bunch of time trying (without success) to get the OTA url for the Hisense firmware, but I didn't even consider simply extracting it. I'll see what I can find for that.

sensboston commented 5 years ago

I'm pretty busy now (and probably will on this week) but could you please take a look to this forum: http://openlgtv.org.ru/forum/viewtopic.php?f=17&t=170280 ?

This link, looks like, contains firmware from some Hisense TV model(s): https://yadi.sk/d/ptDTDuq-rUEQs (choose "Скачать всё" (on Russian) to download all zipped to archive).

sensboston commented 5 years ago

There are some interesting files in the 3rdw-fs.tar.gz:

 Directory of D:\fw408\3rdw-fs\smart_remote_au

04/08/2015  03:46 AM    <DIR>          .
04/08/2015  03:46 AM    <DIR>          ..
04/08/2015  03:46 AM    <DIR>          data
04/08/2015  03:46 AM            62,644 test_smart_remote_common
               1 File(s)         62,644 bytes
               3 Dir(s)  296,847,183,872 bytes free
BVMiko commented 5 years ago

I ran across that same forum thread, and was digging through those same firmware files last night, but couldn't find anything regarding the remote software on our TVs. That test_smart_remote_common file involves communication over HTTP; and I don't think it works on our devices.

I did find a ~18 month old firmware for my specific TV here, and I used the epk2extract tool mentioned in the other forum thread to dump it. In the dump, I found the executable for the UDP server we are communicating with, swrc-server!

The executable does have some static inputs hardcoded as a list (here is the full list of strings extracted from the binary), but I don't see a clear command to toggle input source. There is a very promising string in there though: CONTROL_CHANGE_SOURCE. Also, there may be another communcable port, based on the fact that I also see the string DCCREATE. I wonder what that would do.

I'm currently browsing through the disassembled ARM binary while I'm waiting for RetDec to compile. I've never used the tool before, but it sounds like it would make this a much easier process than the regular tedious alternatives. I've only done this level of RE a few times before, so I'm not sure how far I can take it in a resonable amount of time.

sensboston commented 5 years ago

Good job, very promising! As I said above, I'll be very busy this week (hope to get some time for the fun projects on weekend) but I will follow great news from you here, even at my sleep expenses :)

sensboston commented 5 years ago

I'm curious, have you tried this hack (copy&pasted from here):

  1. Open the browser in the applications browser TV Browser.
  2. Write in the address bar of hisense://debug
  3. The form appears where we enter the name of the application and the URL of the application. For example, the name is App Name: ForkPlayer, and the URL of the App Url is: http://operatv.obovse.com/2.5/ If not laziness can drive and address icons: http://smart.obovse.com/img/fork_logo.png , otherwise it will be standard.
  4. Press the INSTALL button. This removes all installed at the moment from the application's app and adds one new one - ForkPlayer.

P.S. Looks like links above are outdated, but be careful with installing (if you'll decide to try to install something), theoretically you can brick your TV...

BVMiko commented 5 years ago

My TV Browser doesn't recognize the special hisense://debug url, so it seems that route isn't possible for me.

I had previously been unable to locate any proper documentation on developing applications for the OperaTV, despite a serious effort. I'd definitely produce some website integrations and tools if I had the ability.

I haven't had much chance to work with the swrc-server application since last week; and it might be a few more days before I can resume this project.

I may also revisit the HDMI-CEC option, as the features would probably combine well. That protocol can enumerate the available input source devices, and I think it will also identify the current active input source as well. This would help with determining the number of virtual presses of the directional arrows are required. The downside would be the requirement of hardware capable of HDMI-CEC; I'm using a Raspberry Pi 2B, but there are a few other options out there.

sensboston commented 5 years ago

OK, I've implemented today sending keys to TV in WPF app, also improved discover function; after slight code refactoring will commit new changes. Also I started working on simple Xamarin app for Android (Android development kinda new for me - last time I wrote something for Android 8 years ago :) ) but Xamarin looks like a not bad compromise.

All extracted (from the fw dump) key names are worked. There are a few missing keys (comparing with standard Hisense IR remote: INPUTS, Apps and apps shortcuts - colored buttons on the physical remote). By the way, I believe we/I can emulate these buttons press by sending a key sequences. For example, "INPUT" button can be emulated by sending "HOME"-"DOWN"-"DOWN"-"DOWN"-"UP". Same for the "APPS", and for specific apps ('cause app tiles positions are hardcoded).

As for useful Android widgets, I can provide, at least (if you'll get no luck by digging about switching input source), ability to switch back to the previous input (after playing cam pics & vids on Chromecast) - all I need to position on-screen menu to "INPUTS", and issue "RIGHT"->"OK" ;) This will work, I'm 100% sure.

sensboston commented 5 years ago

I tried yesterday "macros" implementation (check repo); unfortunately, because of UDP nature, asynchronous processes with lack of feedback and (possible) bugs in TV firmware, macros aren't stable (even if I set a delays between sending keys more than a 1 sec).

Will be good to find a way to send a keys in one UDP packet (if it possible of course). Also, lack of "KEY_EXIT" in firmware (what we do have on IR remote) make some things are tricky. By the way, if we'll find a way to determine current state of the TV's "smart overlay" (is it shown or not), the "KEY_EXIT" can be achieved by submitting "KEY_HOME" (BTW, this night I'll try to play with "KEY_HOME"+"KEY_HOME"+"KEY_RETURNS" macro - probably it will simulate "KEY_EXIT".

sensboston commented 5 years ago

Hi there! How you doing, any news/progress? As for me: I created an Android app (using Xamarin and Visual Studio) and it works (sometimes :) ). But looks like Xamarin UDP implementation is differ from original Microsoft implementation for desktop, so I need to dig deeper. Hope to get a few hours today for debugging.

I'm planning to get this app published in the Play store; what's why I don't wanna to open app's source code and/or publish ready to install apk here on a github (otherwise store will be crowded by stolen clones very soon). Could you please contact me via email from my domain, and I'll send you app to try (if you want so, of course).

Moltivie commented 4 years ago

Sure, the Python script currently displays that as debug information on invocation.

('cm_open_connection: ', ('192.168.86.134', 60030))
('cm_send: ', 'CTCREATE\r\nID: 93901743\r\n\r\n\x00')
('cm_read: ', 'STATUS 500\r\nPORT: 48264   \r\nID: 93901743\r\n\r\n\x00')
('ct_open_connection: ', ('192.168.86.134', 48264))
('ct_send: ', 'SUS\x00')
('ct_send: ', 'CTCREATE\r\nMAC: appmac_appmac_app\r\nVERSION: 0001\r\n\r\n\x00')
('ct_read: ', 'STATUS 500\r\nVERSION: 0001\r\n\r\n')
('ct_send: ', 'CCCREATE\r\nID: 93901743\r\n\r\n\x00')
('ct_read: ', 'STATUS 500\r\nPORT: 64987   \r\nID: 93901743\r\n\r\n\x00')
('cc_open_connection:', ('192.168.86.134', 64987))
('cc_send: ', 'SUS\x00')
('cc_send: ', 'CMD 000137\r\n1\r\n1HISENSE_DELIMITER2HISENSE_DELIMITER2HISENSE_DELIMITERKEY_MUTEHISENSE_DELIMITER10HISENSE_DELIMITER0HISENSE_DELIMITER0\r\n\r\n\x00')

I'm not sure if the recurring 93901743 parameter should be a random number for each invocation or if it is meant to identify the source application. From my usage thus far I never saw a need to change it.

The first connection (CM) is always on port 60030. Send the static message, and it will return a dynamic port number for the CT connection. Open the CT connection, handshake with a few more static messages, and then you will get the dynamic port number for the CC connection. Send the initial handshake on the CC port, then send the command string.

In the example command string above:

  • 000137 is the total length of the command string; (including itself, which is always a 6 digit field)
  • The 1,2,2 digits are of unknown significance; however the first digit should be 2 for keyboard input (see my command reference from the previous comment).
  • KEY_MUTE is the command
  • 10,0,0 are of unknown significance, but did not vary in the sniffed packets

I know the KEY_RIGHT command will move around inside the input sources overlay; but I never found a way to open the overlay programatically.

Let me know if you'd like any more thorough explanation of this, and if not please keep me updated on any notable progress you make! I hope you can figure out the input source selection.

Hey man! First thing awesome effort in doing this! Second I'm also trying to debug this by myself, I'm going to ask you please if you could link some documentation regards commands and connections. Thanks in advance!

Keep it up!

BVMiko commented 4 years ago

Hi @Moltivie!

Sadly, I have never found any documentation on this protocol; and my repeated attempts to reach out to Hisense (in both English and Chinese) have gone unanswered. Unfortunately the only two suggestions I can provide would be to either find a working piece of software (such as this Python script or a working mobile app) and sniff its traffic, or reading through the code.

The commands are passed sequentially via communication over three separate UDP ports; but many of the message values remain unclear to me.

Please do let me know if you perform any testing and manage to make any further progress understanding the communication protocol.

Moltivie commented 4 years ago

Thanks @BVMiko!

I'm going to gather more infos as soon as I get some free time.

eidorb commented 4 years ago

I came across this project after dumping traffic from the V-Remote iOS app. This truly is an awful protocol - well done deciphering it!

I too was hopeful that I'd be able to change sources over the network, especially after seeing source names in the DISCOVERYACK response. But I'm disappointed to read that hasn't been cracked yet. I feel like it would be possible. I will let you know if I make any progress.

I have been down the IR route. I tested all possible IR codes to discover there are no discrete power on, power off, or input codes. I then looked into HDMI-CEC with limited success as well as wake on LAN. For now I am blasting IR codes and tracking "on" state with pings.

derailius commented 4 years ago

I came across this project after dumping traffic from the V-Remote iOS app. This truly is an awful protocol - well done deciphering it!

I too was hopeful that I'd be able to change sources over the network, especially after seeing source names in the DISCOVERYACK response. But I'm disappointed to read that hasn't been cracked yet. I feel like it would be possible. I will let you know if I make any progress.

I have been down the IR route. I tested all possible IR codes to discover there are no discrete power on, power off, or input codes. I then looked into HDMI-CEC with limited success as well as wake on LAN. For now I am blasting IR codes and tracking "on" state with pings.

I'm not sure if it will help but what I've done is hooked up a Roku to this TV and enabled HDMI-CEC to turn the tv off or on via the network i just use...

curl -d "" http://192.168.0.166:8060/keypress/Power the 192.168.0.166 = my roku's IP address that's hooked to my TV. I can turn it on with the same command or just press a button on my roku remote. I still haven't found a way to switch inputs yet.

edit: i also use Siri shortcuts to turn my tv on or off just by telling siri "turn on my tv" to run the shortcut.

xhucks commented 4 years ago

Here are some open ports for hisense i've found yet

Connection to 192.168.8.125 1969 port [tcp/] succeeded! [TELNET] Connection to 192.168.8.125 9223 port [tcp/] succeeded! Connection to 192.168.8.125 9224 port [tcp/] succeeded! Connection to 192.168.8.125 38400 port [tcp/] succeeded! Connection to 192.168.8.125 39500 port [tcp/] succeeded! Connection to 192.168.8.125 56789 port [tcp/] succeeded! Connection to 192.168.8.125 56790 port [tcp/*] succeeded!

jcoirolo commented 3 years ago

Hi, This are all available key codes: package com.hisense.dlna.common;

public class remoteKeyCode { public static String KEYCODE_0 = "KEY_0"; public static String KEYCODE_1 = "KEY_1"; public static String KEYCODE_2 = "KEY_2"; public static String KEYCODE_3 = "KEY_3"; public static String KEYCODE_3D = "KEY_3DS"; public static String KEYCODE_4 = "KEY_4"; public static String KEYCODE_5 = "KEY_5"; public static String KEYCODE_6 = "KEY_6"; public static String KEYCODE_7 = "KEY_7"; public static String KEYCODE_8 = "KEY_8"; public static String KEYCODE_9 = "KEY_9"; public static String KEYCODE_A = "KEY_RED"; public static String KEYCODE_ACTIVEINPUT = "TICK_INPUT_METHOD"; public static String KEYCODE_ALLAPP = "KEY_ALLAPP"; public static String KEYCODE_B = "KEY_GREEN"; public static String KEYCODE_C = "KEY_YELLOW"; public static String KEYCODE_CHROME = "KEY_SCHROME"; public static String KEYCODE_CH_DOWN = "KEY_CHANNELDOWN"; public static String KEYCODE_CH_UP = "KEY_CHANNELUP"; public static String KEYCODE_CLOSEINPUT = "CLOSE_INPUT_METHOD"; public static String KEYCODE_D = "KEY_BLUE"; public static String KEYCODE_DOWN = "KEY_DOWN"; public static String KEYCODE_DVR = "KEY_PVR"; public static String KEYCODE_ENTER = "KEY_ENTER"; public static String KEYCODE_FAST_BACKWARD = "KEY_BACKS"; public static String KEYCODE_FAST_FORWARD = "KEY_FORWARDS"; public static String KEYCODE_FAVORITE = "KEY_FAVORITESS"; public static String KEYCODE_GUIDE = "KEY_EPG"; public static String KEYCODE_HIMEDIA = "KEY_SMEDIAAPP"; public static String KEYCODE_HISTORY = "KEY_SHISTORYM"; public static String KEYCODE_HOME = "KEY_HOME"; public static String KEYCODE_INPUT = "KEY_SOURCES"; public static String KEYCODE_LEFT = "KEY_LEFT"; public static String KEYCODE_LINK = "KEY_SLINKM"; public static String KEYCODE_LIVETV = "KEY_SDTVAPP"; public static String KEYCODE_MENU = "KEY_MENU"; public static String KEYCODE_MOUSEDOWN = "KEY_UDDLEFTMOUSEKEYS"; public static String KEYCODE_MOUSEUP = "KEY_UDULEFTMOUSEKEYS"; public static String KEYCODE_MUTE = "KEY_MUTE"; public static String KEYCODE_NETFLIX = "KEY_SNETFLIX"; public static String KEYCODE_NEXT = "KEY_NEXT"; public static String KEYCODE_OK = "KEY_OK"; public static String KEYCODE_PAUSE = "KEY_PAUSE"; public static String KEYCODE_PIP = "KEY_STOPIP"; public static String KEYCODE_PLAY = "KEY_PLAY"; public static String KEYCODE_POWER = "KEY_POWER"; public static String KEYCODE_PREVIOUS = "KEY_PREVIOUS"; public static String KEYCODE_RECORD = "KEY_RECORD"; public static String KEYCODE_RETURN = "KEY_RETURNS"; public static String KEYCODE_RIGHT = "KEY_RIGHT"; public static String KEYCODE_SCROLL_DOWN = "KEY_SCROLLDOWN"; public static String KEYCODE_SCROLL_UP = "KEY_SCROLLUP"; public static String KEYCODE_SEARCH = "KEY_SEARCH"; public static String KEYCODE_SENDINPUT = "SEND_EDIT_CONTENT"; public static String KEYCODE_SOCIAL_TV = "KEY_SSOCIALTV"; public static String KEYCODE_STARTINPUT = "START_INPUT_METHOD"; public static String KEYCODE_STOP = "KEY_STOP"; public static String KEYCODE_TOOLS = "KEY_STOOLSM"; public static String KEYCODE_TV_MOVIES = "KEY_STVMOV"; public static String KEYCODE_UP = "KEY_UP"; public static String KEYCODE_VOL_DOWN = "KEY_VOLUMEDOWN"; public static String KEYCODE_VOL_UP = "KEY_VOLUMEUP"; public static String KEYCODE_ZOOMIN = "KEY_ZOOMIN"; public static String KEYCODE_ZOOMOUT = "KEY_ZOOMOUT"; public static String KEYCODE_ZOOM_IN = "KEY_ZOOMIN"; public static String KEYCODE_ZOOM_OUT = "KEY_ZOOMOUT"; }

This is a decompile of panavox app (search google play) is a brand of hisense.

xhucks commented 3 years ago

Thanks for sending. Been waiting this for a while.

On Sat, Aug 1, 2020, 8:21 AM jcoirolo notifications@github.com wrote:

Hi, This are all available key codes: package com.hisense.dlna.common;

public class remoteKeyCode { public static String KEYCODE_0 = "KEY_0"; public static String KEYCODE_1 = "KEY_1"; public static String KEYCODE_2 = "KEY_2"; public static String KEYCODE_3 = "KEY_3"; public static String KEYCODE_3D = "KEY_3DS"; public static String KEYCODE_4 = "KEY_4"; public static String KEYCODE_5 = "KEY_5"; public static String KEYCODE_6 = "KEY_6"; public static String KEYCODE_7 = "KEY_7"; public static String KEYCODE_8 = "KEY_8"; public static String KEYCODE_9 = "KEY_9"; public static String KEYCODE_A = "KEY_RED"; public static String KEYCODE_ACTIVEINPUT = "TICK_INPUT_METHOD"; public static String KEYCODE_ALLAPP = "KEY_ALLAPP"; public static String KEYCODE_B = "KEY_GREEN"; public static String KEYCODE_C = "KEY_YELLOW"; public static String KEYCODE_CHROME = "KEY_SCHROME"; public static String KEYCODE_CH_DOWN = "KEY_CHANNELDOWN"; public static String KEYCODE_CH_UP = "KEY_CHANNELUP"; public static String KEYCODE_CLOSEINPUT = "CLOSE_INPUT_METHOD"; public static String KEYCODE_D = "KEY_BLUE"; public static String KEYCODE_DOWN = "KEY_DOWN"; public static String KEYCODE_DVR = "KEY_PVR"; public static String KEYCODE_ENTER = "KEY_ENTER"; public static String KEYCODE_FAST_BACKWARD = "KEY_BACKS"; public static String KEYCODE_FAST_FORWARD = "KEY_FORWARDS"; public static String KEYCODE_FAVORITE = "KEY_FAVORITESS"; public static String KEYCODE_GUIDE = "KEY_EPG"; public static String KEYCODE_HIMEDIA = "KEY_SMEDIAAPP"; public static String KEYCODE_HISTORY = "KEY_SHISTORYM"; public static String KEYCODE_HOME = "KEY_HOME"; public static String KEYCODE_INPUT = "KEY_SOURCES"; public static String KEYCODE_LEFT = "KEY_LEFT"; public static String KEYCODE_LINK = "KEY_SLINKM"; public static String KEYCODE_LIVETV = "KEY_SDTVAPP"; public static String KEYCODE_MENU = "KEY_MENU"; public static String KEYCODE_MOUSEDOWN = "KEY_UDDLEFTMOUSEKEYS"; public static String KEYCODE_MOUSEUP = "KEY_UDULEFTMOUSEKEYS"; public static String KEYCODE_MUTE = "KEY_MUTE"; public static String KEYCODE_NETFLIX = "KEY_SNETFLIX"; public static String KEYCODE_NEXT = "KEY_NEXT"; public static String KEYCODE_OK = "KEY_OK"; public static String KEYCODE_PAUSE = "KEY_PAUSE"; public static String KEYCODE_PIP = "KEY_STOPIP"; public static String KEYCODE_PLAY = "KEY_PLAY"; public static String KEYCODE_POWER = "KEY_POWER"; public static String KEYCODE_PREVIOUS = "KEY_PREVIOUS"; public static String KEYCODE_RECORD = "KEY_RECORD"; public static String KEYCODE_RETURN = "KEY_RETURNS"; public static String KEYCODE_RIGHT = "KEY_RIGHT"; public static String KEYCODE_SCROLL_DOWN = "KEY_SCROLLDOWN"; public static String KEYCODE_SCROLL_UP = "KEY_SCROLLUP"; public static String KEYCODE_SEARCH = "KEY_SEARCH"; public static String KEYCODE_SENDINPUT = "SEND_EDIT_CONTENT"; public static String KEYCODE_SOCIAL_TV = "KEY_SSOCIALTV"; public static String KEYCODE_STARTINPUT = "START_INPUT_METHOD"; public static String KEYCODE_STOP = "KEY_STOP"; public static String KEYCODE_TOOLS = "KEY_STOOLSM"; public static String KEYCODE_TV_MOVIES = "KEY_STVMOV"; public static String KEYCODE_UP = "KEY_UP"; public static String KEYCODE_VOL_DOWN = "KEY_VOLUMEDOWN"; public static String KEYCODE_VOL_UP = "KEY_VOLUMEUP"; public static String KEYCODE_ZOOMIN = "KEY_ZOOMIN"; public static String KEYCODE_ZOOMOUT = "KEY_ZOOMOUT"; public static String KEYCODE_ZOOM_IN = "KEY_ZOOMIN"; public static String KEYCODE_ZOOM_OUT = "KEY_ZOOMOUT"; }

This is a decompile of panavox app (search google play) is a brand of hisense.

— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/BVMiko/hisense-udp/issues/2#issuecomment-667435915, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAZUYFRQ47O54PHO6MCIHCLR6NNXVANCNFSM4F7NZ4GQ .