kdschlosser / samsungctl

Remote control Samsung televisions via a TCP/IP connection
MIT License
154 stars 33 forks source link

Testing the timeout fix #2

Closed aseabridge closed 5 years ago

aseabridge commented 5 years ago

Thanks for putting in a fix for the timeout @kdschlosser. I can see you’ve added the LG logging to the file. To test this with my 2017/2018 tv I’m guessing I’ll need to checkout this timeout branch and call the method. What command should I issue? When I tried using this library earlier today diectlty it was giving me errors about bytes so I’m guessing I was missing some parameters.

Murph24 commented 5 years ago

@kdschlosser No luck for me:

SSDP: 192.168.1.20
b'M-SEARCH * HTTP/1.1\r\nHOST: 239.255.255.250:1900\r\nMAN: "ssdp:discover"\r\nMX: 1\r\nST: upnp:rootdevice\r\nCONTENT-LENGTH: 0\r\n\r\n'

Process finished with exit code 0

I have a K-series, which is 2016 or 2017 model. If I remember correctly others on the homeassistant forums have tried this route and UPNP doesn't seem to work for newer models

kdschlosser commented 5 years ago

This is a screen shot of a 2018 model UPNP breakdown. I am trying to find a way to poke a stick at it to get it to respond to the discovery packets on demand. They may have overlooked the one thing that i found with my router. it too does not respond to the broadcast packets. It does however respond to a direct discovery packet. I am pretty sure the TV works in the same manner. I simply need to get all the bits properly set in order to elicit a response from it.

The smart view app uses UPNP to discover the TV's. The reason it does not work on the newer models is because the app is looking for a UPNP class the 2017 and 2018 TV's do not have.

image

I do want to say thank you for posting what you did. Your response may have located my problem.

this

M-SEARCH * HTTP/1.1\r\n
HOST: 239.255.255.250:1900\r\n
MAN: "ssdp:discover"\r\n
MX: 1\r\n
ST: upnp:rootdevice\r\n
CONTENT-LENGTH: 0\r\n
\r\n

should read

M-SEARCH * HTTP/1.1\r\n
HOST: 192.168.1.20:1900\r\n
MAN: "ssdp:discover"\r\n
MX: 1\r\n
ST: upnp:rootdevice\r\n
CONTENT-LENGTH: 0\r\n
\r\n
kdschlosser commented 5 years ago

another thing I have read is that the newer TV's will only respond to UPNP broadcasts using IPV6. so that will be my next endeavor if this update fails to work. but I have other information that contradicts that as well. This whole process would go a lot faster if I had VPN access to someone's network that the TV is attached to. I can make the updates as I am testing to see if i can get it to respond. Not owning one of these TV's makes it difficult to develop. I own an older one and everything works.

vitalets commented 5 years ago

@kdschlosser thanks for your research! One thought - did you try ST ssdp:all instead of upnp:rootdevice?

M-SEARCH * HTTP/1.1\r\n
HOST: 239.255.255.250:1900\r\n
MAN: "ssdp:discover"\r\n
MX: 1\r\n
ST: ssdp:all\r\n
CONTENT-LENGTH: 0\r\n
\r\n
mallsopp commented 5 years ago

@kdschlosser do you still need vpn access to a network with a Samsung tv on it?

Murph24 commented 5 years ago

@kdschlosser thanks for the work on this. Maybe more hassle then its worth, but there is a TV emulator from the Samsung developer portal and it alludes to having "networking" functionality.

In theory this could help you.....or be a huge PIA

https://developer.samsung.com/tv/develop/getting-started/using-sdk/tv-emulator

kdschlosser commented 5 years ago

i am downloading it now. It does not state that there is UPNP emulation in that. but it doesn't state that their isn't either. so it's a crap shoot.

If iI do not get anywhere with it then the VPN might be the best way to go.

kdschlosser commented 5 years ago

I am goinig to retool the library so that the UPNP is more of an addon then a requirement. this way if it fails then the basic remote functionality will still be available.

KentEkl commented 5 years ago

@kdschlosser I have Samsung QE55Q7FAM and a VPN server if you are interested.

Murph24 commented 5 years ago

@kdschlosser Also, not sure if this reference on UPNP is helpful or not:

https://developer.samsung.com/tv/develop/legacy-platform-library/art00030/index

In the header its tagged as "legacy" with reference up to models in 2014

This document defines a simple way for a mobile device to discover Samsung smart TV supporting Smart View service on the network. The mobile device (Client application developed by the application developer) acts as a Control Point. Devices are discovered via the SSDP M-SEARCH method, using a below specific search target (ST) header. MSearch ST: urn:samsung.com:service:MultiScreenService:1

kdschlosser commented 5 years ago

OK this is just stupid. I am really starting to hate Samsung with a passion. all of their software is so fucked up. excuse the language but it is the only word that properly describes it.

Their requirements for the Emulator state https://developer.tizen.org/ko/development/tizen-studio/download/installing-tizen-studio/prerequisites?langredirect=1#emulator

and i quote

CPU | Recommended: Support for Intel VTx (Virtualization Technology)

now is it me of does that say Recommended??????

not Required...

Then on a completely different Samsung page.

https://developer.samsung.com/tv/develop/tools/prerequisites

To use the TV emulator:

Your CPU must support hardware-assisted virtualization:

    For Windows® and macOS, check the Intel® product specifications to make sure that your CPU supports Intel® VT-x.

    For Linux, check the KVM site to make sure that your CPU supports either Intel® VT-x or AMD-V™.

so which is it???

then in a support forum... I find that they state that having the Intel VT-x is required no matter the OS you are running. now why on Windows is the AMD-V not supported??

they have their heads so far up their asses.

in a support forum they say that the part that is causing the problem can be disabled. but and they simply state disable it in the .conf file. but they do not explain how to go about doing this. if i comment out the line. it crashes. so how do I disable it?? sure as hell beats me.

I do not believe anything they say or their documentation states. I know the newer TV's support UPNP. I have a person that has a 2018 model and it works. He is just not online regularly. so I am not able to work through the issue using him. We have queried the TV using UPNP and it responds. we can change the volume/mute all of that with UPNP.

I am wondering if there is a setting on the TV it's self to disable UPNP and he has it turned on. I know that the smartview application uses UPNP. this is the mechanism that it uses to control the TV.

@KentEkl I am interested in that VPN access. I do not know if you are on the Slack chat or not. here is a link so you can join it. this expires in 24 hours.

https://join.slack.com/t/samsungtvcontrol/shared_invite/enQtNDk4NDUzMzAzODc1LWQwYjEwMWIyYTliN2ZlZWNiNWZkZDZmZTQwYzliNDI0MWJiMDE0OWQxOTkzY2Y0NDA4MDUzNmI4ODNmZGI1YTY

send me a message directly and we can hash out the details on the VPN end of things.

I have to leave for a few hours gotta go and pickup a laptop. I will be back in a few hours. I do not know where about in the world you are. GMT -7:00 is my timezone. I am willing to do this anytime you are available. One thing tho. You need to make sure I will be able to access the internet through the tunnel. it is going to not make my route to the internet non functional. and I will need to have access to resources. I do have most thing available already but in the event I am needing some information I need to have a way to access the information other then disconnecting from the VPN and reconnecting. Tho I can do that if necessary.

send me all of the specific information on the slack chat,

Murph24 commented 5 years ago

@kdschlosser I messed around with your code tonight and couldn't get it to discover anything(hard coding IP address, etc.). I downloaded the intel tool in your screenshot. It finds the my TV no problem and it looks like the UPNP controls are in fact there.

image

I tried to use the intel tool to send a SOAP request, but couldn't get anything....however I don't know what I'm doing and I'm sure I didn't have the syntax correct.

EDIT: Using the intel "Device Sniffer" I can post a SOAP request and get a proper response. This should let you see how and where the request is going to troubleshoot your new library. (Returns volume = 3, which was correct). The intel "Device validator" will also be helpful as it'll run through all the available commands it finds via UPNP. Be careful as that test will set you TV volume to 100 at some point (and your wife will be pissed)!

image

kdschlosser commented 5 years ago

see I told you the UPNP works. I am trying to figure out the mechanism that pokes it with a stick to get a response. I need it to tell me the port and so I can grab the structures from it to build the python classes.

I have a really sneaky suspension that if i broadcast the discovery using IPV6 it will respond on IPV4. I have everything set up identical to what the sniffer does except for the IPV6. this is the single thing I have not done. the broadcast packets are identical. the number of them that I broadcast are identical. You can check all of this if you use that UPNP program with the sniffer open. you will see the broadcasts. all except for the IPV6 ones. And I am willing to bet if you do this the TV will not respond.

Using the actions can be a bit tricky. Here is a little tip. anything that has an "InstanceId" for a parameter you will set that parameter always to 0. this is because the TV can only do a single thing at a time but the UPNP specification is designed so that more then a single thing can be controlled through the same method. an example would be a single controller but multiple screens. and being able to say control the video streams on each of the screens independently. The TV does not have this ability. so that is why it uses a "special use case" InstanceId of 0.

If you would be willing to spend the time you can help me out by compiling the XML for each of the classes and any embedded devices. this can be done by right clicking on an object in the Device Spy and clicking on "view XML" if available. it will then load your browser showing the XML. right click on the browser window and "view page source" copy the XML to a text file. do not reformat it. just plop it into a file the way it is. name the file the same as the object you clicked on in the device spy. you will want to keep some kind of organizational structure. you can do this by making folders of the objects that are a parent of the item you got the xml from and place that item in the folder. if the item has xml and also contains other objects create a folder as the name of the object and also create a file with the same name and place the file inside of that folder.

an example would be

urn:schemas-upnp-org:device:MediaRenderer:1.xml
urn:samsung.com:device:RemoteControlReceiver:1.xml
urn:samsung.com:device:MainTVServer2:1  (folder)
    urn:samsung.com:device:MainTVServer2:1,xml
    urn:samsung.com:device:MainTVAgent:1.xml

This is an actual layout from my TV this way I can create the convenience methods that will fill in any arbitrary data that the user does not need to have any involvement in.

an example would be what i did for the older TV's when handling sources and channels. With the sources when the user obtains a list of them a list of "Source" instances are returned. they are instances of a clas that I made where a user would be able to do things like.

if not source,is_active:
    source.activate()

or the following will change the name displayed on the TV for the source.

source.label = 'Playstation 4'

it makes it so that the user does not need to deal with any of the SOAP it is all handled behind the scenes. as well handle any incorrect data types that are being sent

so the program will know if the following statement is wrong.

tv.volume = '5'

and when the correct one is used

tv.volume = 5

I have the whole backend already made up. I simply need to nail down the mechanism that gets the TV to respond. and then I can code in the convince classes.

I will work on the IPV6 stuff today and see where I get.

Murph24 commented 5 years ago

@kdschlosser You were right!.....I didnt think it would work. Before you start on IP6 stuff, I got your code to start working a bit. It is discovering my TV (192.168.1.20). Here is what I did with key being setting TV_IP_ADDRESS = 239.255.255.250. I was poking around in your code, but I don't think I modified anything else

from __future__ import print_function
import os
import UPNP_Device
import logging

TV_IP_ADDRESS = '239.255.255.250'
FILEPATH = r'c:\dev\UPNP_DEVICE_OUTPUT.log'
FILEPATH = os.path.expandvars(FILEPATH)

with open(FILEPATH, 'w') as f:
    for device in UPNP_Device.discover(10, logging.DEBUG, TV_IP_ADDRESS):
        data = str(device)
        print(data)
        f.write(data)

REMOVED

````!
Murph24 commented 5 years ago

@kdschlosser Here are those xml files. I think I got it right....if not let me know

-Deleted File-

kdschlosser commented 5 years ago

it uses that IP address if you do not pass an IP to the call to discover. or if you pass '0.0.0.0' to it.

I have made some changes to the UPNP_Device library.

https://github.com/kdschlosser/UPNP_Device/tree/lxml

if you download that one and run

python setup.py install

it will install all required modules as well as the library. then the same script you ran that produced that error. and see what happens.

Murph24 commented 5 years ago

@kdschlosser Thanks! Getting closer. Looks like some of the URLs you're processing aren't getting chopped up correctly.

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "C:\Users\\AppData\Local\Programs\Python\Python35\lib\threading.py", line 923, in _bootstrap_inner
    self.run()
  File "C:\Users\\AppData\Local\Programs\Python\Python35\lib\threading.py", line 871, in run
    self._target(*self._args, **self._kwargs)
  File "C:\Users\\PycharmProjects\test_upnp_2\UPNP_Device\discover.py", line 425, in found_thread
    found_classes.append(UPNPObject(addr, classes))
  File "C:\Users\\PycharmProjects\test_upnp_2\UPNP_Device\instance_singleton.py", line 14, in __call__
    super(InstanceSingleton, cls).__call__(ip, classes)
  File "C:\Users\\PycharmProjects\test_upnp_2\UPNP_Device\upnp_class.py", line 66, in __init__
    device = EmbeddedDevice(url, node=device, parent=self)
  File "C:\Users\\PycharmProjects\test_upnp_2\UPNP_Device\embedded_device.py", line 48, in __init__
    service = Service(self, url, scpdurl, service_type, control_url)
  File "C:\Users\\PycharmProjects\test_upnp_2\UPNP_Device\service.py", line 38, in __init__
    response = requests.get(url + location)
  File "C:\Users\\PycharmProjects\test_upnp_2\venv\lib\site-packages\requests\api.py", line 75, in get
    return request('get', url, params=params, **kwargs)
  File "C:\Users\\PycharmProjects\test_upnp_2\venv\lib\site-packages\requests\api.py", line 60, in request
    return session.request(method=method, url=url, **kwargs)
  File "C:\Users\\PycharmProjects\test_upnp_2\venv\lib\site-packages\requests\sessions.py", line 519, in request
    prep = self.prepare_request(req)
  File "C:\Users\\PycharmProjects\test_upnp_2\venv\lib\site-packages\requests\sessions.py", line 462, in prepare_request
    hooks=merge_hooks(request.hooks, self.hooks),
  File "C:\Users\\PycharmProjects\test_upnp_2\venv\lib\site-packages\requests\models.py", line 313, in prepare
    self.prepare_url(url, params)
  File "C:\Users\\PycharmProjects\test_upnp_2\venv\lib\site-packages\requests\models.py", line 381, in prepare_url
    raise InvalidURL(*e.args)
requests.exceptions.InvalidURL: Failed to parse: 192.168.1.25:49152http:
kdschlosser commented 5 years ago

that it does. so we are a step further.

kdschlosser commented 5 years ago

OK give it a shot now. I think i fixed the issue with the URL and there were a couple other bugfixes in there as well.

kdschlosser commented 5 years ago

well you will be happy to know the UPNP library is fixed. I used the xml data you sent me to run a test against it and get all of the kinks out of it.

here is the output from it

UN55MU6300
IP Address: 0.0.0.0
==============================================
Services:
    Service name: dial
    Service class: urn:dial-multiscreen-org:service:dial:1
    Access point: UPNPObject.dial
    ----------------------------------------------
    Methods:
        Method name: SendKeyCode
        Access point: UPNPObject.dial.SendKeyCode
        ----------------------------------------------
            Parameters: 
            UPNP data type: A_ARG_TYPE_KeyCode
            Py data type: unsigned 32bit int

            UPNP data type: A_ARG_TYPE_KeyDescription
            Py data type: str, unicode

            Return Values: None
    Service name: StreamSplicing
    Service class: urn:schemas-rvualliance-org:service:StreamSplicing:1
    Access point: UPNPObject.StreamSplicing
    ----------------------------------------------
    Methods:
        Method name: SetBreakAuxStreamPlaylist
        Access point: UPNPObject.StreamSplicing.SetBreakAuxStreamPlaylist
        ----------------------------------------------
            Parameters: 
            UPNP data type: A_ARG_TYPE_Int
            Py data type: unsigned 32bit int
            Default: 0

            UPNP data type: A_ARG_TYPE_String
            Py data type: str, unicode

            UPNP data type: A_ARG_TYPE_String
            Py data type: str, unicode

            UPNP data type: A_ARG_TYPE_String
            Py data type: str, unicode

            Return Values: None

        Method name: SetBreakAuxStreamTrigger
        Access point: UPNPObject.StreamSplicing.SetBreakAuxStreamTrigger
        ----------------------------------------------
            Parameters: 
            UPNP data type: A_ARG_TYPE_Int
            Py data type: unsigned 32bit int
            Default: 0

            UPNP data type: A_ARG_TYPE_Int
            Py data type: unsigned 32bit int
            Default: 0

            UPNP data type: A_ARG_TYPE_Int
            Py data type: unsigned 32bit int
            Default: 0

            Return Values: None
    Service name: ConnectionManager
    Service class: urn:schemas-upnp-org:service:ConnectionManager:1
    Access point: UPNPObject.ConnectionManager
    ----------------------------------------------
    Methods:
        Method name: ConnectionComplete
        Access point: UPNPObject.ConnectionManager.ConnectionComplete
        ----------------------------------------------
            Parameters: 
            UPNP data type: A_ARG_TYPE_ConnectionID
            Py data type: signed 32bit int
            Default: 0

            Return Values: None

        Method name: PrepareForConnection
        Access point: UPNPObject.ConnectionManager.PrepareForConnection
        ----------------------------------------------
            Parameters: 
            UPNP data type: A_ARG_TYPE_ProtocolInfo
            Py data type: str, unicode

            UPNP data type: A_ARG_TYPE_ConnectionManager
            Py data type: str, unicode

            UPNP data type: A_ARG_TYPE_ConnectionID
            Py data type: signed 32bit int
            Default: 0

            UPNP data type: A_ARG_TYPE_Direction
            Py data type: str, unicode
            Allowed values:
                Input
                Output

            Return Values: 
                UPNP data type: A_ARG_TYPE_ConnectionID
                Py data type: signed 32bit int
                Default: 0

                UPNP data type: A_ARG_TYPE_AVTransportID
                Py data type: signed 32bit int
                Default: 0

                UPNP data type: A_ARG_TYPE_RcsID
                Py data type: signed 32bit int
                Default: 0

        Method name: GetProtocolInfo
        Access point: UPNPObject.ConnectionManager.GetProtocolInfo
        ----------------------------------------------
            Parameters: None

            Return Values: 
                UPNP data type: SourceProtocolInfo
                Py data type: str, unicode

                UPNP data type: SinkProtocolInfo
                Py data type: str, unicode

        Method name: GetCurrentConnectionIDs
        Access point: UPNPObject.ConnectionManager.GetCurrentConnectionIDs
        ----------------------------------------------
            Parameters: None

            Return Values: 
                UPNP data type: CurrentConnectionIDs
                Py data type: str, unicode
                Default: 0

        Method name: GetCurrentConnectionInfo
        Access point: UPNPObject.ConnectionManager.GetCurrentConnectionInfo
        ----------------------------------------------
            Parameters: 
            UPNP data type: A_ARG_TYPE_ConnectionID
            Py data type: signed 32bit int
            Default: 0

            Return Values: 
                UPNP data type: A_ARG_TYPE_RcsID
                Py data type: signed 32bit int
                Default: 0

                UPNP data type: A_ARG_TYPE_AVTransportID
                Py data type: signed 32bit int
                Default: 0

                UPNP data type: A_ARG_TYPE_ProtocolInfo
                Py data type: str, unicode

                UPNP data type: A_ARG_TYPE_ConnectionManager
                Py data type: str, unicode

                UPNP data type: A_ARG_TYPE_ConnectionID
                Py data type: signed 32bit int
                Default: 0

                UPNP data type: A_ARG_TYPE_Direction
                Py data type: str, unicode
                Possible returned values:
                    Input
                    Output

                UPNP data type: A_ARG_TYPE_ConnectionStatus
                Py data type: str, unicode
                Possible returned values:
                    OK
                    ContentFormatMismatch
                    InsufficientBandwidth
                    UnreliableChannel
                    Unknown
    Service name: ScreenSharingService
    Service class: urn:samsung.com:service:ScreenSharingService:1
    Access point: UPNPObject.ScreenSharingService
    ----------------------------------------------
    Methods:
        Method name: X_ConnectScreenSharingM2TV
        Access point: UPNPObject.ScreenSharingService.X_ConnectScreenSharingM2TV
        ----------------------------------------------
            Parameters: 
            UPNP data type: A_ARG_TYPE_mWlanMacAddress
            Py data type: str, unicode

            UPNP data type: A_ARG_TYPE_mP2pDeviceAddress
            Py data type: str, unicode

            UPNP data type: A_ARG_TYPE_mBluetoothMacAddress
            Py data type: str, unicode

            UPNP data type: A_ARG_TYPE_mWFDSourcePort
            Py data type: str, unicode

            Return Values: 
                UPNP data type: A_ARG_TYPE_tBSSID
                Py data type: str, unicode

                UPNP data type: A_ARG_TYPE_tWlanFreq
                Py data type: str, unicode

                UPNP data type: A_ARG_TYPE_tListenFreq
                Py data type: str, unicode

        Method name: X_ConnectScreenSharingTV2M
        Access point: UPNPObject.ScreenSharingService.X_ConnectScreenSharingTV2M
        ----------------------------------------------
            Parameters: 
            UPNP data type: A_ARG_TYPE_mWlanMacAddress
            Py data type: str, unicode

            UPNP data type: A_ARG_TYPE_mP2pDeviceAddress
            Py data type: str, unicode

            UPNP data type: A_ARG_TYPE_mBluetoothMacAddress
            Py data type: str, unicode

            Return Values: 
                UPNP data type: A_ARG_TYPE_tBSSID
                Py data type: str, unicode

                UPNP data type: A_ARG_TYPE_tWlanFreq
                Py data type: str, unicode

                UPNP data type: A_ARG_TYPE_tListenFreq
                Py data type: str, unicode

                UPNP data type: A_ARG_TYPE_tWFDSourcePort
                Py data type: str, unicode
    Service name: AVTransport
    Service class: urn:schemas-upnp-org:service:AVTransport:1
    Access point: UPNPObject.AVTransport
    ----------------------------------------------
    Methods:
        Method name: SetNextAVTransportURI
        Access point: UPNPObject.AVTransport.SetNextAVTransportURI
        ----------------------------------------------
            Parameters: 
            UPNP data type: A_ARG_TYPE_InstanceID
            Py data type: unsigned 32bit int

            UPNP data type: NextAVTransportURI
            Py data type: str, unicode

            UPNP data type: NextAVTransportURIMetaData
            Py data type: str, unicode

            Return Values: None

        Method name: Play
        Access point: UPNPObject.AVTransport.Play
        ----------------------------------------------
            Parameters: 
            UPNP data type: A_ARG_TYPE_InstanceID
            Py data type: unsigned 32bit int

            UPNP data type: TransportPlaySpeed
            Py data type: str, unicode
            Default: 1

            Return Values: None

        Method name: Pause
        Access point: UPNPObject.AVTransport.Pause
        ----------------------------------------------
            Parameters: 
            UPNP data type: A_ARG_TYPE_InstanceID
            Py data type: unsigned 32bit int

            Return Values: None

        Method name: GetPositionInfo
        Access point: UPNPObject.AVTransport.GetPositionInfo
        ----------------------------------------------
            Parameters: 
            UPNP data type: A_ARG_TYPE_InstanceID
            Py data type: unsigned 32bit int

            Return Values: 
                UPNP data type: CurrentTrack
                Py data type: unsigned 32bit int
                Default: 0
                Minimum: 0
                Maximum: 4294967295
                Step: 1

                UPNP data type: CurrentTrackDuration
                Py data type: str, unicode
                Default: 00:00:00

                UPNP data type: CurrentTrackMetaData
                Py data type: str, unicode

                UPNP data type: CurrentTrackURI
                Py data type: str, unicode

                UPNP data type: RelativeTimePosition
                Py data type: str, unicode
                Default: 00:00:00

                UPNP data type: AbsoluteTimePosition
                Py data type: str, unicode
                Default: 00:00:00

                UPNP data type: RelativeCounterPosition
                Py data type: signed 32bit int
                Default: 2147483647

                UPNP data type: AbsoluteCounterPosition
                Py data type: signed 32bit int
                Default: 2147483647

        Method name: X_PlayerAppHint
        Access point: UPNPObject.AVTransport.X_PlayerAppHint
        ----------------------------------------------
            Parameters: 
            UPNP data type: A_ARG_TYPE_InstanceID
            Py data type: unsigned 32bit int

            UPNP data type: X_ARG_TYPE_UpnpClass
            Py data type: str, unicode
            Allowed values:
                object.item.imageItem
                object.item.audioItem
                object.item.videoItem

            UPNP data type: X_ARG_TYPE_PlayerHint
            Py data type: str, unicode
            Allowed values:
                load
                unload

            Return Values: None

        Method name: GetMediaInfo
        Access point: UPNPObject.AVTransport.GetMediaInfo
        ----------------------------------------------
            Parameters: 
            UPNP data type: A_ARG_TYPE_InstanceID
            Py data type: unsigned 32bit int

            Return Values: 
                UPNP data type: NumberOfTracks
                Py data type: unsigned 32bit int
                Default: 0
                Minimum: 0
                Maximum: 4294967295

                UPNP data type: CurrentMediaDuration
                Py data type: str, unicode
                Default: 00:00:00

                UPNP data type: AVTransportURI
                Py data type: str, unicode

                UPNP data type: AVTransportURIMetaData
                Py data type: str, unicode

                UPNP data type: NextAVTransportURI
                Py data type: str, unicode

                UPNP data type: NextAVTransportURIMetaData
                Py data type: str, unicode

                UPNP data type: PlaybackStorageMedium
                Py data type: str, unicode
                Default: NONE
                Possible returned values:
                    NONE
                    NETWORK

                UPNP data type: RecordStorageMedium
                Py data type: str, unicode
                NOT_IMPLEMENTED

                UPNP data type: RecordMediumWriteStatus
                Py data type: str, unicode
                NOT_IMPLEMENTED

        Method name: X_DLNA_GetBytePositionInfo
        Access point: UPNPObject.AVTransport.X_DLNA_GetBytePositionInfo
        ----------------------------------------------
            Parameters: 
            UPNP data type: A_ARG_TYPE_InstanceID
            Py data type: unsigned 32bit int

            Return Values: 
                UPNP data type: X_DLNA_CurrentTrackSize
                Py data type: str, unicode

                UPNP data type: X_DLNA_RelativeBytePosition
                Py data type: str, unicode

                UPNP data type: X_DLNA_AbsoluteBytePosition
                Py data type: str, unicode

        Method name: GetTransportInfo
        Access point: UPNPObject.AVTransport.GetTransportInfo
        ----------------------------------------------
            Parameters: 
            UPNP data type: A_ARG_TYPE_InstanceID
            Py data type: unsigned 32bit int

            Return Values: 
                UPNP data type: TransportState
                Py data type: str, unicode
                Default: NO_MEDIA_PRESENT
                Possible returned values:
                    STOPPED
                    PAUSED_PLAYBACK
                    PLAYING
                    TRANSITIONING
                    NO_MEDIA_PRESENT

                UPNP data type: TransportStatus
                Py data type: str, unicode
                Default: OK
                Possible returned values:
                    OK
                    ERROR_OCCURRED

                UPNP data type: TransportPlaySpeed
                Py data type: str, unicode
                Default: 1

        Method name: Stop
        Access point: UPNPObject.AVTransport.Stop
        ----------------------------------------------
            Parameters: 
            UPNP data type: A_ARG_TYPE_InstanceID
            Py data type: unsigned 32bit int

            Return Values: None

        Method name: GetDeviceCapabilities
        Access point: UPNPObject.AVTransport.GetDeviceCapabilities
        ----------------------------------------------
            Parameters: 
            UPNP data type: A_ARG_TYPE_InstanceID
            Py data type: unsigned 32bit int

            Return Values: 
                UPNP data type: PossiblePlaybackStorageMedia
                Py data type: str, unicode
                Default: NETWORK

                UPNP data type: PossibleRecordStorageMedia
                Py data type: str, unicode
                NOT_IMPLEMENTED

                UPNP data type: PossibleRecordQualityModes
                Py data type: str, unicode
                NOT_IMPLEMENTED

        Method name: Next
        Access point: UPNPObject.AVTransport.Next
        ----------------------------------------------
            Parameters: 
            UPNP data type: A_ARG_TYPE_InstanceID
            Py data type: unsigned 32bit int

            Return Values: None

        Method name: GetTransportSettings
        Access point: UPNPObject.AVTransport.GetTransportSettings
        ----------------------------------------------
            Parameters: 
            UPNP data type: A_ARG_TYPE_InstanceID
            Py data type: unsigned 32bit int

            Return Values: 
                UPNP data type: CurrentPlayMode
                Py data type: str, unicode
                Default: NORMAL
                Possible returned values:
                    NORMAL

                UPNP data type: CurrentRecordQualityMode
                Py data type: str, unicode
                NOT_IMPLEMENTED

        Method name: SetAVTransportURI
        Access point: UPNPObject.AVTransport.SetAVTransportURI
        ----------------------------------------------
            Parameters: 
            UPNP data type: A_ARG_TYPE_InstanceID
            Py data type: unsigned 32bit int

            UPNP data type: AVTransportURI
            Py data type: str, unicode

            UPNP data type: AVTransportURIMetaData
            Py data type: str, unicode

            Return Values: None

        Method name: SetPlayMode
        Access point: UPNPObject.AVTransport.SetPlayMode
        ----------------------------------------------
            Parameters: 
            UPNP data type: A_ARG_TYPE_InstanceID
            Py data type: unsigned 32bit int

            UPNP data type: CurrentPlayMode
            Py data type: str, unicode
            Default: NORMAL
            Allowed values:
                NORMAL

            Return Values: None

        Method name: X_PrefetchURI
        Access point: UPNPObject.AVTransport.X_PrefetchURI
        ----------------------------------------------
            Parameters: 
            UPNP data type: A_ARG_TYPE_InstanceID
            Py data type: unsigned 32bit int

            UPNP data type: A_ARG_TYPE_PrefetchURI
            Py data type: str, unicode

            UPNP data type: A_ARG_TYPE_PrefetchURIMetaData
            Py data type: str, unicode

            Return Values: None

        Method name: X_GetStoppedReason
        Access point: UPNPObject.AVTransport.X_GetStoppedReason
        ----------------------------------------------
            Parameters: 
            UPNP data type: A_ARG_TYPE_InstanceID
            Py data type: unsigned 32bit int

            Return Values: 
                UPNP data type: A_ARG_TYPE_StoppedReason
                Py data type: str, unicode

                UPNP data type: A_ARG_TYPE_StoppedReasonData
                Py data type: str, unicode

        Method name: Seek
        Access point: UPNPObject.AVTransport.Seek
        ----------------------------------------------
            Parameters: 
            UPNP data type: A_ARG_TYPE_InstanceID
            Py data type: unsigned 32bit int

            UPNP data type: A_ARG_TYPE_SeekMode
            Py data type: str, unicode
            Default: REL_TIME
            Allowed values:
                TRACK_NR
                REL_TIME
                ABS_TIME
                ABS_COUNT
                REL_COUNT
                X_DLNA_REL_BYTE
                FRAME

            UPNP data type: A_ARG_TYPE_SeekTarget
            Py data type: str, unicode

            Return Values: None

        Method name: GetCurrentTransportActions
        Access point: UPNPObject.AVTransport.GetCurrentTransportActions
        ----------------------------------------------
            Parameters: 
            UPNP data type: A_ARG_TYPE_InstanceID
            Py data type: unsigned 32bit int

            Return Values: 
                UPNP data type: CurrentTransportActions
                Py data type: str, unicode

        Method name: Previous
        Access point: UPNPObject.AVTransport.Previous
        ----------------------------------------------
            Parameters: 
            UPNP data type: A_ARG_TYPE_InstanceID
            Py data type: unsigned 32bit int

            Return Values: None
    Service name: MultiScreenService
    Service class: urn:samsung.com:service:MultiScreenService:1
    Access point: UPNPObject.MultiScreenService
    ----------------------------------------------
    Methods:
        Method name: SendKeyCode
        Access point: UPNPObject.MultiScreenService.SendKeyCode
        ----------------------------------------------
            Parameters: 
            UPNP data type: A_ARG_TYPE_KeyCode
            Py data type: unsigned 32bit int

            UPNP data type: A_ARG_TYPE_KeyDescription
            Py data type: str, unicode

            Return Values: None
    Service name: IPControlService
    Service class: urn:samsung.com:service:IPControlService:1
    Access point: UPNPObject.IPControlService
    ----------------------------------------------
    Methods:
        None
    Service name: RenderingControl
    Service class: urn:schemas-upnp-org:service:RenderingControl:1
    Access point: UPNPObject.RenderingControl
    ----------------------------------------------
    Methods:
        Method name: GetMute
        Access point: UPNPObject.RenderingControl.GetMute
        ----------------------------------------------
            Parameters: 
            UPNP data type: A_ARG_TYPE_InstanceID
            Py data type: unsigned 32bit int

            UPNP data type: A_ARG_TYPE_Channel
            Py data type: str, unicode
            Allowed values:
                Master

            Return Values: 
                UPNP data type: Mute
                Py data type: bool
                Possible returned values: True/False

        Method name: SelectPreset
        Access point: UPNPObject.RenderingControl.SelectPreset
        ----------------------------------------------
            Parameters: 
            UPNP data type: A_ARG_TYPE_InstanceID
            Py data type: unsigned 32bit int

            UPNP data type: A_ARG_TYPE_PresetName
            Py data type: str, unicode
            Allowed values:
                FactoryDefaults

            Return Values: None

        Method name: GetVolume
        Access point: UPNPObject.RenderingControl.GetVolume
        ----------------------------------------------
            Parameters: 
            UPNP data type: A_ARG_TYPE_InstanceID
            Py data type: unsigned 32bit int

            UPNP data type: A_ARG_TYPE_Channel
            Py data type: str, unicode
            Allowed values:
                Master

            Return Values: 
                UPNP data type: Volume
                Py data type: unsigned 16bit int
                Minimum: 0
                Maximum: 100
                Step: 1

        Method name: SetMute
        Access point: UPNPObject.RenderingControl.SetMute
        ----------------------------------------------
            Parameters: 
            UPNP data type: A_ARG_TYPE_InstanceID
            Py data type: unsigned 32bit int

            UPNP data type: A_ARG_TYPE_Channel
            Py data type: str, unicode
            Allowed values:
                Master

            UPNP data type: Mute
            Py data type: bool
            Allowed values: True/False

            Return Values: None

        Method name: X_SetTVSlideShow
        Access point: UPNPObject.RenderingControl.X_SetTVSlideShow
        ----------------------------------------------
            Parameters: 
            UPNP data type: A_ARG_TYPE_InstanceID
            Py data type: unsigned 32bit int

            UPNP data type: A_ARG_TYPE_SlideShowState
            Py data type: bool
            Allowed values: True/False

            UPNP data type: A_ARG_TYPE_ThemeId
            Py data type: unsigned 32bit int

            Return Values: None

        Method name: X_Origin360View
        Access point: UPNPObject.RenderingControl.X_Origin360View
        ----------------------------------------------
            Parameters: 
            UPNP data type: A_ARG_TYPE_InstanceID
            Py data type: unsigned 32bit int

            Return Values: None

        Method name: X_Move360View
        Access point: UPNPObject.RenderingControl.X_Move360View
        ----------------------------------------------
            Parameters: 
            UPNP data type: A_ARG_TYPE_InstanceID
            Py data type: unsigned 32bit int

            UPNP data type: X_ARG_TYPE_ArcDegreeOffset
            Py data type: float
            Default: 0.0

            UPNP data type: X_ARG_TYPE_ArcDegreeOffset
            Py data type: float
            Default: 0.0

            Return Values: None

        Method name: X_GetVideoSelection
        Access point: UPNPObject.RenderingControl.X_GetVideoSelection
        ----------------------------------------------
            Parameters: 
            UPNP data type: A_ARG_TYPE_InstanceID
            Py data type: unsigned 32bit int

            Return Values: 
                UPNP data type: X_VideoPID
                Py data type: unsigned 16bit int
                Default: 0
                Minimum: 0
                Maximum: 65535
                Step: 1

                UPNP data type: X_VideoEncoding
                Py data type: str, unicode

        Method name: X_SetZoom
        Access point: UPNPObject.RenderingControl.X_SetZoom
        ----------------------------------------------
            Parameters: 
            UPNP data type: A_ARG_TYPE_InstanceID
            Py data type: unsigned 32bit int

            UPNP data type: A_ARG_TYPE_Coordinate
            Py data type: unsigned 32bit int

            UPNP data type: A_ARG_TYPE_Coordinate
            Py data type: unsigned 32bit int

            UPNP data type: A_ARG_TYPE_Coordinate
            Py data type: unsigned 32bit int

            UPNP data type: A_ARG_TYPE_Coordinate
            Py data type: unsigned 32bit int

            Return Values: None

        Method name: X_UpdateAudioSelection
        Access point: UPNPObject.RenderingControl.X_UpdateAudioSelection
        ----------------------------------------------
            Parameters: 
            UPNP data type: A_ARG_TYPE_InstanceID
            Py data type: unsigned 32bit int

            UPNP data type: X_AudioPID
            Py data type: unsigned 16bit int
            Default: 0
            Minimum: 0
            Maximum: 65535
            Step: 1

            UPNP data type: X_AudioEncoding
            Py data type: str, unicode

            Return Values: None

        Method name: ListPresets
        Access point: UPNPObject.RenderingControl.ListPresets
        ----------------------------------------------
            Parameters: 
            UPNP data type: A_ARG_TYPE_InstanceID
            Py data type: unsigned 32bit int

            Return Values: 
                UPNP data type: PresetNameList
                Py data type: str, unicode
                Default: FactoryDefaults

        Method name: X_GetTVSlideShow
        Access point: UPNPObject.RenderingControl.X_GetTVSlideShow
        ----------------------------------------------
            Parameters: 
            UPNP data type: A_ARG_TYPE_InstanceID
            Py data type: unsigned 32bit int

            Return Values: 
                UPNP data type: A_ARG_TYPE_SlideShowState
                Py data type: bool
                Possible returned values: True/False

                UPNP data type: A_ARG_TYPE_ThemeId
                Py data type: unsigned 32bit int

                UPNP data type: A_ARG_TYPE_TotalThemeNumber
                Py data type: unsigned 32bit int

        Method name: X_GetAspectRatio
        Access point: UPNPObject.RenderingControl.X_GetAspectRatio
        ----------------------------------------------
            Parameters: 
            UPNP data type: A_ARG_TYPE_InstanceID
            Py data type: unsigned 32bit int

            Return Values: 
                UPNP data type: X_AspectRatio
                Py data type: str, unicode
                Default: Default
                Possible returned values:
                    Default
                    FitScreen

        Method name: X_GetServiceCapabilities
        Access point: UPNPObject.RenderingControl.X_GetServiceCapabilities
        ----------------------------------------------
            Parameters: 
            UPNP data type: A_ARG_TYPE_InstanceID
            Py data type: unsigned 32bit int

            Return Values: 
                UPNP data type: X_ServiceCapabilities
                Py data type: str, unicode

        Method name: X_GetAudioSelection
        Access point: UPNPObject.RenderingControl.X_GetAudioSelection
        ----------------------------------------------
            Parameters: 
            UPNP data type: A_ARG_TYPE_InstanceID
            Py data type: unsigned 32bit int

            Return Values: 
                UPNP data type: X_AudioPID
                Py data type: unsigned 16bit int
                Default: 0
                Minimum: 0
                Maximum: 65535
                Step: 1

                UPNP data type: X_AudioEncoding
                Py data type: str, unicode

        Method name: X_ControlCaption
        Access point: UPNPObject.RenderingControl.X_ControlCaption
        ----------------------------------------------
            Parameters: 
            UPNP data type: A_ARG_TYPE_InstanceID
            Py data type: unsigned 32bit int

            UPNP data type: X_ARG_TYPE_CaptionOperation
            Py data type: str, unicode
            Allowed values:
                Enable
                Disable

            UPNP data type: X_ARG_TYPE_CaptionName
            Py data type: str, unicode

            UPNP data type: X_ARG_TYPE_ResourceURI
            Py data type: str, unicode

            UPNP data type: X_ARG_TYPE_CaptionURI
            Py data type: str, unicode

            UPNP data type: X_ARG_TYPE_CaptionType
            Py data type: str, unicode

            UPNP data type: X_ARG_TYPE_Language
            Py data type: str, unicode

            UPNP data type: X_ARG_TYPE_Encoding
            Py data type: str, unicode

            Return Values: None

        Method name: X_GetCaptionState
        Access point: UPNPObject.RenderingControl.X_GetCaptionState
        ----------------------------------------------
            Parameters: 
            UPNP data type: A_ARG_TYPE_InstanceID
            Py data type: unsigned 32bit int

            Return Values: 
                UPNP data type: X_Captions
                Py data type: str, unicode

                UPNP data type: X_EnabledCaptions
                Py data type: str, unicode

        Method name: X_UpdateVideoSelection
        Access point: UPNPObject.RenderingControl.X_UpdateVideoSelection
        ----------------------------------------------
            Parameters: 
            UPNP data type: A_ARG_TYPE_InstanceID
            Py data type: unsigned 32bit int

            UPNP data type: X_VideoPID
            Py data type: unsigned 16bit int
            Default: 0
            Minimum: 0
            Maximum: 65535
            Step: 1

            UPNP data type: X_VideoEncoding
            Py data type: str, unicode

            Return Values: None

        Method name: X_Zoom360View
        Access point: UPNPObject.RenderingControl.X_Zoom360View
        ----------------------------------------------
            Parameters: 
            UPNP data type: A_ARG_TYPE_InstanceID
            Py data type: unsigned 32bit int

            UPNP data type: X_ARG_TYPE_ScaleFactorOffset
            Py data type: float
            Default: 1.0

            Return Values: None

        Method name: X_SetAspectRatio
        Access point: UPNPObject.RenderingControl.X_SetAspectRatio
        ----------------------------------------------
            Parameters: 
            UPNP data type: A_ARG_TYPE_InstanceID
            Py data type: unsigned 32bit int

            UPNP data type: X_AspectRatio
            Py data type: str, unicode
            Default: Default
            Allowed values:
                Default
                FitScreen

            Return Values: None

        Method name: SetVolume
        Access point: UPNPObject.RenderingControl.SetVolume
        ----------------------------------------------
            Parameters: 
            UPNP data type: A_ARG_TYPE_InstanceID
            Py data type: unsigned 32bit int

            UPNP data type: A_ARG_TYPE_Channel
            Py data type: str, unicode
            Allowed values:
                Master

            UPNP data type: Volume
            Py data type: unsigned 16bit int
            Minimum: 0
            Maximum: 100
            Step: 1

            Return Values: None
Devices: None
kdschlosser commented 5 years ago

it seems as tho they removed all of the TV and source specific features. or maybe they are simply not broadcasting the UPNP class information.

That being said how the UPNP_Device discover function works is it send the broadcats only to get the IP address of a device. then it performs a direct query. this is something that you cannot do with the Device Sniffer. so the TV may not broadcast the UPNP classes but if directly queried it might.

there is still access to volume and mute. and it appears as tho there is a mechanism to possibly locate apps to be able to launch them.

Murph24 commented 5 years ago

@kdschlosser Thanks for all the work on this. This generally works for me, but I only get a results with IP=239.255.255.250; 0.0.0.0 and my tvs IP don't return anything. Also the " creating UPNPObject instance" portion errors on a specific device on my network (directv) - see below. Any idea on this? or maybe just put that code in a try-catch block and ignore this exception, since we don't care that device anyway?


SSDP: 192.168.1.27 creating UPNPObject instance
Exception in thread Thread-1:
Traceback (most recent call last):
  File "C:\Users\\PycharmProjects\test_upnp_2\venv\lib\site-packages\requests\models.py", line 379, in prepare_url
    scheme, auth, host, port, path, query, fragment = parse_url(url)
  File "C:\Users\\PycharmProjects\test_upnp_2\venv\lib\site-packages\urllib3\util\url.py", line 199, in parse_url
    raise LocationParseError(url)
urllib3.exceptions.LocationParseError: Failed to parse: 192.168.1.25:49152http:

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "C:\Users\\AppData\Local\Programs\Python\Python35\lib\threading.py", line 923, in _bootstrap_inner
    self.run()
  File "C:\Users\\AppData\Local\Programs\Python\Python35\lib\threading.py", line 871, in run
    self._target(*self._args, **self._kwargs)
  File "C:\Users\\AppData\Local\Programs\Python\Python35\lib\UPNP_Device\discover.py", line 426, in found_thread
    found_classes.append(UPNPObject(addr, classes))
  File "C:\Users\\AppData\Local\Programs\Python\Python35\lib\UPNP_Device\upnp_class.py", line 24, in __call__
    super(UPNPSingleton, cls).__call__(ip, classes)
  File "C:\Users\\AppData\Local\Programs\Python\Python35\lib\UPNP_Device\upnp_class.py", line 91, in __init__
    device = UPNPDevice(url, node=device, parent=self)
  File "C:\Users\\AppData\Local\Programs\Python\Python35\lib\UPNP_Device\upnp_class.py", line 189, in __init__
    service = Service(self, url, scpdurl, service_type, control_url)
  File "C:\Users\\AppData\Local\Programs\Python\Python35\lib\UPNP_Device\service.py", line 38, in __init__
    response = requests.get(url + location)
  File "C:\Users\\PycharmProjects\test_upnp_2\venv\lib\site-packages\requests\api.py", line 75, in get
    return request('get', url, params=params, **kwargs)
  File "C:\Users\\PycharmProjects\test_upnp_2\venv\lib\site-packages\requests\api.py", line 60, in request
    return session.request(method=method, url=url, **kwargs)
  File "C:\Users\\PycharmProjects\test_upnp_2\venv\lib\site-packages\requests\sessions.py", line 519, in request
    prep = self.prepare_request(req)
  File "C:\Users\\PycharmProjects\test_upnp_2\venv\lib\site-packages\requests\sessions.py", line 462, in prepare_request
    hooks=merge_hooks(request.hooks, self.hooks),
  File "C:\Users\\PycharmProjects\test_upnp_2\venv\lib\site-packages\requests\models.py", line 313, in prepare
    self.prepare_url(url, params)
  File "C:\Users\\PycharmProjects\test_upnp_2\venv\lib\site-packages\requests\models.py", line 381, in prepare_url
    raise InvalidURL(*e.args)
requests.exceptions.InvalidURL: Failed to parse: 192.168.1.25:49152http:
kdschlosser commented 5 years ago

the UPNP_Device library is a separate library I made. it is not specific to only Samsung product. It's so others can use it if they wanted to control say a network attached TV tuner. It also means that if there is an error in it now. that there could be later in the future if Samsung changes something that doesn't agree. so we have to make sure this portion is all nice and cleaned up. to keep from having problems in the future.

Now. that being stated. the line that the error is pointing to that pertaint to the UPNP_Device library

File "C:\Users\bmurr\AppData\Local\Programs\Python\Python35\lib\UPNP_Device\service.py", line 38, in init
response = requests.get(url + location)

line 38. there is nothing on that line in the latest version of the program. the highlighted line in the image below is the line that is mentioned in the error. which according to that error should read

response = requests.get(url + location)

but doesn't

image

You need to download the latest version from the lxml branch.

you can get to the lxml branch with this link

https://github.com/kdschlosser/UPNP_Device/tree/lxml

then click on the clone or download button and save it as a zip. decompress it to some temp folder. start a console (linux) or a command prompt (windows) navigate to that temp folder. and run the setup.py as outlined below.

python setup.py install

you have to run the setup.py. this will install any requirements that are necessary. in this case it would be lxml.

kdschlosser commented 5 years ago

ok so here goes another try.

I do want to mention that if you have installed it this way before go into your siite packages folder and delete the one that is in there. it is going to be a file with the extension of .egg. then edit

easy-install.pth

it is in the same location as the .egg file.

find the line pointing to that .egg file and delete it.

download the version from this link. https://github.com/kdschlosser/samsungctl/tree/updated_upnp_features

to install it run the following command

python setup.py install 

and here is a basic script to see if it works.

from samsungctl import Remote

remote = Remote(log_level=Remote.LOG_DEBUG)
print(remote.upnp_tv)

or you can use this script if you want to specify the IP of the TV change the MY_TV_IP variable to the address of your TV

MY_TV_IP = '0.0.0.0'
from samsungctl import Remote

remote = Remote(ip_address=MY_TV_IP, log_level=Remote.LOG_DEBUG)
print(remote.upnp_tv)
kdschlosser commented 5 years ago

I forgot to mention. if you so choose the old way

from samsungctl import Remote

config = {
"name": "samsungctl",
"description": "PC",
"id": "",
"method": "legacy",
"port": 55000,
"timeout": 0
}

remote = Remote(config , log_level=Remote.LOG_DEBUG)
while remote.is_discovering:
    pass

print(remote.upnp_tv)

i set this up so that if you pass a config file it will use that config file to make the remote connection first. and then it launches a thread to do the discovery side of things. So the library will actually still function if the UPNP end of things is not functioning for some reason. so I added a mechanism to be able to tell if the UPNP discovery process is still running.

if you do not pass a config file and simply pass an IP or let it automatically discover there are bits of information that needs to be obtained from the UPNP side of things in order to get the remote portion working. so there is no thread that gets launched. it keeps in all within the thread that created the Remote instance.

I also removed the creation of any classes based on any UPNP data that is not a TV. no sense in wasting overhead doing that.

kdschlosser commented 5 years ago

ok people I have made some real progress in the past few hours. I have been going back and forth with a person that is testing the program.. so close to having it working right with extended functionality.

DJPsycho82 commented 5 years ago

Nice! You're a real hero. I should be able to use this with openhab i think?

kdschlosser commented 5 years ago

if openhab supports python then yes.

Murph24 commented 5 years ago

@kdschlosser Awesome work! One of the problems we have in Homesassistant is just knowing if the TV is on or off. All the solutions have been kinda flakey given all the different tv models. Any chance this library can subscribe to events? I noticed the TV sends an SSDP:byebye when you turn it off and SSDP:alive periodically when its on. I'd think we could leverage that to understand the actual TV power state?

Thanks!

kdschlosser commented 5 years ago

having the TV show a power state is easy to do. and from what i have seen the TV's do not have any events that can be subscribed to. but we can do up some simple polling to get the job done. it would monitor whether or not the TV is on (responding to SSDP packets) and checking the status of the various elements like volume and mute. and see if they match values that we store for the device. and if they do not match an event can be generated.

it appears as tho Samsung has removed all of the TV tuner controls from the newer TV's as well as the ability to control what the input is which really sucks. nothing I can do about that. they have probably migrated everything to the websocket connection. but there really is no way to know. I am going to send an email off to Samsung to request the source code for the smartview application. the app is open source. so maybe that may give some insight.

kdschlosser commented 5 years ago

I would rather not bind to a socket to wait for an SSDP byebye from the TV. the reason being is that if something happens with the program whether it be samsungctl or the program that is running it, and the socket does not get closed properly we will end up with an orphaned socket. so i would like to steer clear of doing that.

msvinth commented 5 years ago

Maybe some inspiration can be had from this project https://github.com/tavicu/homebridge-samsung-tizen

kdschlosser commented 5 years ago

well well. that did give me the mechanism to be able to get the app ID's

It will take a while to acquire them but they can be had.

kdschlosser commented 5 years ago

if someone is willing to run this script and set the TV_IP to the IP address of your TV. and paste the data to me that would be helpful. you will need to have the YouTube app installed for this to work.

import requests
TV_IP = '0.0.0.0'

response = requests.get('http://' + TV_IP + ':8001/api/v2/applications/111299001912')
print(response.content)
msvinth commented 5 years ago

{"id":"111299001912","name":"YouTube","running":true,"version":"2.1.486","visible":false}

Is what I got from running the link in my browser

kdschlosser commented 5 years ago

ok cool

Murph24 commented 5 years ago

@kdschlosser Did you happen to see this thread? i think this thread tracked down some of the app ids already:

https://github.com/Ape/samsungctl/issues/75

kdschlosser commented 5 years ago

yeah only some. I am going to go after as man as i can get a hold of

here is a script that is going to have to run for a long time. probably several days. You will be able to check and see if it got all the applications on your TV by looking in the folder and counting the number of files and then counting the number of applications on the TV

you will need to change the TV_IP to be your TV ip address.

This is set up to be run on Windows it will make a directory called samsung_application_ids on your desktop, in that directory it will put a file for every application it finds. this is a brute force number deal. I started with the application ID for NetFlix as it is the lowest number therefor has been around the longest time. there are 10 threads that run querying the TV if there is no application at that number then it will get an error and the thread moves to the next number.

it will spit out app numbers it is trying to the console. so you know it's working.

import requests
import threading
import os
import json

TV_IP = '0.0.0.0'

# {
#     "id": "111299001912",
#     "name": "YouTube",
#     "running": True,
#     "version": "2.1.486",
#     "visible": False
# }

OUTPUT_PATH = os.path.expandvars(
    os.path.join('%userprofile%', 'desktop', 'samsung_application_ids')
)

file_save_count = 0

if not os.path.exists(OUTPUT_PATH):
    os.mkdir(OUTPUT_PATH)

count = 11101200000

url = 'http://{0}:8001/api/v2/applications/'.format(TV_IP)

def do():
    global count
    global file_save_count
    while True:
        t_count = count
        count += 1000
        end_count = t_count + 999
        while t_count != end_count:
            print(t_count)
            try:
                response = requests.get(url + str(t_count), timeout=5)
                content = json.loads(response.content)
                try:
                    with open(os.path.join(OUTPUT_PATH, content['name'] + '.txt'), 'w') as f:
                        f.write(json.dumps(content, indent=4))
                except KeyError:
                    pass
                except:
                    save_count = file_save_count
                    file_save_count += 1
                    with open(os.path.join(OUTPUT_PATH, str(save_count) + '.txt'), 'w') as f:
                        f.write(json.dumps(content, indent=4))
            except:
                pass

for _ in range(10):
    t = threading.Thread(target=do)
    t.daemon = True
    t.start()

event = threading.Event()

event.wait()
kdschlosser commented 5 years ago

The code above will not work. Apparently not all of the apps will show up there. It doesn't matter anyway. I managed to get my hands on large portions of the Samsung TV OS source code. And k do believe the websocket bits are in it. This is going to make engineering an API a whole lot easier.

derelict commented 5 years ago

Hi for some reason i get the following error while trying any scripts:

Traceback (most recent call last): File "test.py", line 3, in remote = Remote(log_level=Remote.LOG_DEBUG) File "/usr/local/lib/python2.7/dist-packages/samsungctl-0.7.1+1-py2.7.egg/samsungctl/remote.py", line 22, in init self._discovering = threading.Event() File "/usr/local/lib/python2.7/dist-packages/samsungctl-0.7.1+1-py2.7.egg/samsungctl/remote.py", line 100, in setattr if self.upnp_tv is not None: File "/usr/local/lib/python2.7/dist-packages/samsungctl-0.7.1+1-py2.7.egg/samsungctl/remote.py", line 93, in getattr if self.upnp_tv is not None: File "/usr/local/lib/python2.7/dist-packages/samsungctl-0.7.1+1-py2.7.egg/samsungctl/remote.py", line 93, in getattr if self.upnp_tv is not None: <.... Snipped out about 100 times repetition of line 93 errors ...> File "/usr/local/lib/python2.7/dist-packages/samsungctl-0.7.1+1-py2.7.egg/samsungctl/remote.py", line 93, in getattr if self.upnp_tv is not None: RuntimeError: maximum recursion depth exceeded

Any Idea ? This is on ubuntu lts Python 2.7.15rc1

vitalets commented 5 years ago

@kdschlosser Btw why do you need IDs of all possible applications?

We are retrieving all apps installed on particular TV by WebSocket with the following commands:

// get installed apps
send({
     method: 'ms.channel.emit',
     params: {
         event: 'ed.installedApp.get',
         to: 'host',
         data: { },
     }
});

// get eden apps (with more extra info)
send({
     method: 'ms.channel.emit',
     params: {
         event: 'ed.edenApp.get',
         to: 'host',
         data: { },
     }
});
kdschlosser commented 5 years ago

I know I saw that. But if i happen to locate the mechanism to be able to install the applications we are going to need to know the application ID's in order to do this. So either a query would have to be made to Samsung servers with login details in order to obtain a list of available applications. or we can store the application names and ID's in a flat file and simply pull from them. I think the latter would be a bit easier to do. I know there would be more on the maintenance end of things but unless someone has reverse engineered the process to get the available applications from the Samsung servers the flat file is the best option.

I think I have gotten the ssl websocket end of things working. I have one last change to make and then I have to get it tested again. It is discovering the TV and connecting via UPNP and also by use of the websocket. the prompt comes up on the TV for the remote authorization but the UPNP end of things is not fully populated. I have to increase the discover timeout to see if that solves the issue. and in order to have the token stored correctly I am using the device_id gotten from UPNP to marry the token to. this way it is unique. If a config is passed to the constructor the token will be stored in that config and it will be up to the user to save that config file to a location of their choosing. I do not want to break API at all and that is how it is currently set up unless using the command line interface. I have tinkered with the idea of creating a class which would store all of the config information. and that config instance could be passed to the constructor and that config class would have a class method of load where a path could be specified. it would also have an instance method of save with a parameter for an optional path. there are several things I have toyed around with as ideas of making this an easier to use library for the user. I do like the idea of the config file. But ultimately the best way is to have the discovery working and using the device id would be the best way as it is not dependent on an IP address. this way the TV could be left as a dynamically assigned IP.

derelict commented 5 years ago

Hi @kdschlosser ... can you give me hint regarding my issue above ... (i don't have a very good python knowledge) ... that would be awesome.

Thanks

kdschlosser commented 5 years ago

@derelict that is a test version that had some issues. i am still tinkering about with it to get things corrected.

so here is an example of things working properly with my older generation TV, wish I had a newer one to develop with it would make things far easier.

so here is the example I have.

Here you can see the ability to select a source. this is a list that is retrieved form the TV image

and in this screen shot you can see the list on the right that contains only a partial list of what can be done on the TV. and on the left the lines with the Leggo looking icons represent me sending a command to the TV. and the lines with the lightning bolt are "events" or notifications that things have changed on the TV. the events some from data retrieved from the TV. If you look at the response time you will notice that the events are taking place less then a second after I send the command to the TV.

image

the next few images are to show the rest of the commands

image

image

image

so I am making more progress with this. I think I might have the final things finished up. I have to release a test version of the program that populates this interface into the forums of another project I work on to find out. if successful I will release it here. I have someone that is testing things out for me on that forum.

I did change how the config data is stored. the old API still functions. But I have made it a bit easier for those that do not know JSON.

here are some code examples of how one will be able to use the discovery and old API alike.

from samsungctl import discover, Config, Remote

config = Config.load('path/to/save/file')
tv = discover(config)
config.save()

# or if you want to manually enter the config data

config = Config(
    # not used here for backwards compatibility
    name=None,

    # not used here for backwards compatibility
    description=None,

    # IP of the TV. this gets used if there is no device ID present
    host='192.168.1.1',

    # this only gets used if the TV cannot be discovered using UPNP or if the
    # old API of passing a dict gets sent directly to Remote which would
    # bypass any UPNP discovery
    # settings are 55000 or 8001
    port=55000,

    # legacy connections and gets automatically generated. But it is here
    # because the old config file used it.
    id=None,

    # not used here for backwards compatibility
    method='legacy',

    # not used here for backwards compatibility
    timeout=0,

    # mainly for internal use. but if you know the token you are more then
    # welcome to enter it. if the token is present the mechanics of discovering
    # if a TV supports ssl or not is bypassed.
    token=None,

    # optional save path for the config file. a path can also be specified
    # when calling config.save
    path=None,

    # if this param is manually entered it has to be the device id gotten from
    # UPNP. this will override the use of the host (IP) when discovering via
    # UPNP. if you use the method above for discovering a TV this is
    # automatically populated
    device_id=None
)

tv = discover(config)
config.save('/path/to/save/file')

# because the library is now going to be able to support multiple TV's through
# the use of the discovery mechanism.

for tv in discover():
    print(tv.name)

# another nice thing is if the user decides to use the TV

for tv in discover():
    answer = raw_input('use TV ' + tv.name + ':')
    if answer.lower() == 'y':
        path = raw_input('config save path:')
        tv.config.save(path)

# if you specify a path that is only a directory the tv model number and the
# device id will get used

# because of the above code if all of the config files are saved to the same
# directory iterating over the directory to pass the file names becomes a snap
# so setting up the configs for the TV's becomes easy.

import os
path = 'path/to/config/files'

tvs = []
for file in  os.listdir(path):
    config = Config.load(os.path.join(path, file))
    tv = discover(config)
    tvs += [tv]

# now to keep things from getting all kinds of out of sorts
# II made a meta class that is an instance singleton. so there can only be a
# single instance of a TV. This will make it it easier to track if a device is
# powered on or off (this method works quite well actually).

tvs = list(discover())
new_tvs = list(discover())

for tv in new_tvs:
    tv.power = True
    if tv not in tvs or not tv.power:
        print('tv powered on ' + tv.name)

for tv in tvs:
    if tv not in new_tvs:
        tv.power = False
        print('tv powered off ' + tv.name)

# and of coarse for supporting a single TV this way still works as well.

config = {
    "name": "samsungctl",
    "description": "PC",
    "id": "",
    "method": "legacy",
    "port": 55000,
    "timeout": 0
}

remote = Remote(config)

# this is a little added bonus a slight twist. You will have to specify the
# filename to save to. the above bypasses the UPNP discovery so the device id
# cannot be obtained.
remote.config.save('/path/to/save/file')

I changed how the config data gets written. I removed the use of storing the config data as a json object in favor of storing it as a flat file. I feel this will make it easier for the programming newbies. and in all honesty there is no reason why the config data should be stored as a json object. there is not enough data to warrant it and none of the data is nested at all. the library of course is backwards compatible with the json files. but when a new one gets written it will be as a flat file. the flat file will look like the following

name = Some TV Name
description = Some TV Description
host = 192.168.1.1
port = 8002
id = Some ID
method = websocket
timeout = 0
token = Token from the websocket
device id = Device ID from UPNP

the arrangement of the lines does not matter. any blank lines does not matter. if you want to add commented lines just make sure they do not have an = in them. all fields are not necessary.

derelict commented 5 years ago

@kdschlosser Ok, Thanks. Do you have an idea of when you plan to commit your most recent changes ?

kdschlosser commented 5 years ago

hopefully soon. I hurt my back yesterday. This is the first time I am sitting down at the computer since then. we will see how much I can get done.

kdschlosser commented 5 years ago

now I know that this appears to give the state of the application

import requests
TV_IP = '0.0.0.0'

response = requests.get('http://' + TV_IP + ':8001/api/v2/applications/111299001912')
print(response.content)

I do not know if this is the "universal" mechanism across all new TV's

i did find some information in the API about using the mechanism below. who knows with Samsung as to which was will work and which way will not. so better to have both in there. one as a fallback to use.

please post any error or data returned. change the TV_IP to your TV's IP address.

import requests
TV_IP = '0.0.0.0'

response = requests.get('http://' + TV_IP + ':8001/api/v2/applications/111299001912/info')
print(response.content)
kdschlosser commented 5 years ago

I stumbled upon @eclair4151 's implementation of the encrypted websocket for the 2014 and 2015 models. I am hoping @eclair4151 doesn't mind. I borrowed the code to add to samsungctl to give full support of all available TV models from 2007+ (I think the IP control started with 2007). I did however have to make a large amount of changes to make it compatible with samsungctl as the original code was only drafted for single use. I also did some reformatting to make it PEP8 compliant as well as made it compatible with python 2.7+

kdschlosser commented 5 years ago

ok so here is a test version again

https://github.com/kdschlosser/samsungctl/tree/yet_another_test_version

I am making changes as I go. so bear with me on these things. Ideas come to me as I am working on it.

typical install script. please run it to get the newest requirements

python setup.py install

here is a simple test script to test with if you want to capture all of the console output you will need to set the scroll history to a pretty high number. I would say somewhere around 10K would be a good number. if you have more then a single TV then you will need more.

from __future__ import print_function
import samsungctl

for tv in samsungctl.discover():
    print(tv.config)
    print()
    print(tv.upnp_tv)
kdschlosser commented 5 years ago

OK i made a branch that is just a modification of the original library to use the ssl websocket. i removed use of the old style websocket. so this version will only do legacy and websocket ssl. use the original config file that you were using with samsungctl. or pass the exact same config parameters to it as you were before.

This version supports more then a single TV. and it also should overcome the issues with having to run the script as sudo.

https://github.com/kdschlosser/samsungctl/tree/ssl_websocket_only

now remember this works exactly like the original samsungctrl library