Open jgrieger1 opened 5 years ago
I also found that RenderingControl.GetMute returns a list data type with the True/False value in the first position of the list.
do this instead of accessing the UPNP function directly.
# config assumed
with samsungctl.Remote(config) as remote:
print(remote.mute)
remote.mute = True
the reason I say to use that is because there are different ways to set the mute. some TV's have GetMute and SetMute and others have GetChannelMute SetChannelMute
when dealing with UPNP and the boolean data types the value that needs to bee passed or the value that gets returned can vary,
these are the choices a boolean data type can take. it is only going to take one set. it's a True False thing
Disable, Enable disable, enable Off, On off, on 0, 1 false, true False, True no, yes No, Yes
so instead of having the possibility of varying choices. I opted to make it a boolean data type.. a simple True False.
now the way the code is done you can pass a string with one of the values the device has set up for that function.
I believe on the Samsung TV's it is going to be one of the values below..
disable, enable Disable, Enable
you can check simply by doing
# confiig assumed
with samsingctl.Remote(config) as remote:
for param in remote.RenderingControl.GetMute.params:
print(param.__name__, param.allowed_values)
I also found that RenderingControl.GetMute returns a list data type with the True/False value in the first position of the list.
I am not sure what you mean by that.. the only thing that returns a False, True list is the as_dict property..
here is the code for the boolean data type.
class Boolean(object):
py_data_type = (bool,)
def __init__(self, name, data_type_name, node, direction):
self.__name__ = name
self.data_type_name = data_type_name
self.direction = direction
allowed = node.find('allowedValueList')
if allowed is None:
allowed_values = ['0', '1']
else:
allowed_values = list(value.text for value in allowed)
if 'yes' in allowed_values:
allowed_values = ['no', 'yes']
elif 'Yes' in allowed_values:
allowed_values = ['No', 'Yes']
elif 'enable' in allowed_values:
allowed_values = ['disable', 'enable']
elif 'Enable' in allowed_values:
allowed_values = ['Disable', 'Enable']
elif 'on' in allowed_values:
allowed_values = ['off', 'on']
elif 'On' in allowed_values:
allowed_values = ['Off', 'On']
elif 'true' in allowed_values:
allowed_values = ['false', 'true']
elif 'True' in allowed_values:
allowed_values = ['False', 'True']
else:
allowed_values = ['0', '1']
self.allowed_values = allowed_values
default_value = node.find('defaultValue')
if default_value is not None:
if default_value.text == 'NOT_IMPLEMENTED':
self.default_value = 'NOT_IMPLEMENTED'
else:
default_value = default_value.text
if default_value in (
'yes',
'Yes',
'true',
'True',
'1',
'enable',
'Enable'
):
default_value = True
else:
default_value = False
self.default_value = default_value
def __str__(self, indent=''):
output = TEMPLATE.format(
indent=indent,
name=self.__name__,
upnp_data_type=self.data_type_name,
py_data_type=bool
)
if self.default_value == 'NOT_IMPLEMENTED':
return output + indent + ' NOT_IMPLEMENTED' + '\n'
if self.default_value is not None:
output += (
indent +
' Default: ' +
repr(self.default_value) +
'\n'
)
if self.direction == 'in':
output += indent + ' Allowed values: True/False\n'
else:
output += indent + 'Possible returned values: True/False\n'
return output
@property
def as_dict(self):
res = dict(
name=self.__name__,
default_value=self.default_value,
data_type=self.py_data_type
)
if self.direction == 'in':
res['allowed_values'] = [False, True]
else:
res['returned_values'] = [False, True]
return res
def __call__(self, value):
if value is None:
if self.default_value is None:
if self.direction == 'in':
raise ValueError('A value must be supplied')
else:
value = self.default_value
if self.direction == 'out':
value = self.allowed_values[int(value)]
if self.direction == 'in':
if isinstance(value, bool):
value = self.allowed_values[int(value)]
if value not in self.allowed_values:
raise TypeError('Incorrect value')
elif value is not None:
if self.default_value == 'NOT_IMPLEMENTED':
value = self.default_value
else:
value = bool(self.allowed_values.index(value))
return value
Sorry, I didn't explain the issue very well. It is not with the UPNP methods, but with the samsungctl mute property. I was trying to use the samsungctl mute property, but it was not working so I dug deeper into using the UPNP methods directly. Using the UPNP methods RenderingControl.GetMute and RenderingControl.SetMute I'm able to retrieve the mute state and set the mute on the TV. The RenderingControl.GetMute returns a True/False value stored in the first position of a list data type. I'm able to set mute on and off using the RenderingControl.SetMute UPNP method by passing either True or False as a parameter. The UPNP methods MainTVAgent2.GetMuteStatus and MainTVAgent2.SetMute do not work on my TV and trying to use these returns an AttributeError.
the samsungctl mute property always returns true. In the below, mute is currently disabled on the TV but remote.mute returns True.
>>> print(remote.mute)
True
Trying to set the mute using remote.mute I get an error:
>>> remote.mute = False
Traceback (most recent call last):
File "/home/jeff/samsungctl/samsungctl/samsungctl/upnp/__init__.py", line 597, in mute
self.MainTVAgent2.SetMute(desired_mute)
File "/home/jeff/samsungctl/samsungctl/samsungctl/upnp/UPNP_Device/upnp_class.py", line 127, in __getattr__
raise AttributeError(item)
AttributeError: MainTVAgent2
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/home/jeff/samsungctl/samsungctl/samsungctl/upnp/__init__.py", line 599, in mute
self.set_channel_mute('Master', desired_mute)
File "/home/jeff/samsungctl/samsungctl/samsungctl/upnp/__init__.py", line 111, in set_channel_mute
self.RenderingControl.SetMute(0, channel, desired_mute)
File "/home/jeff/samsungctl/samsungctl/samsungctl/upnp/UPNP_Device/action.py", line 67, in __call__
value = param(kwargs[param.__name__])
File "/home/jeff/samsungctl/samsungctl/samsungctl/upnp/UPNP_Device/data_type.py", line 385, in __call__
raise TypeError('Incorrect value')
TypeError: Incorrect value
Using the UPNP method RenderingControl.GetMute I can successfully retrieve the current mute state:
>>> remote.RenderingControl.GetMute(0,'Master')
[False]
And using the UPNP method RenderingControl.SetMute I can successfully set the current mute state:
>>> remote.RenderingControl.SetMute(0,'Master',True)
[]
>>> remote.RenderingControl.GetMute(0,'Master')
[True]
The data type returned by the RenderingControl.GetMute is a list data type:
>>> mute = remote.RenderingControl.GetMute(0,'Master')
>>> print(mute)
[False]
>>> print(type(mute))
<class 'list'>
>>> print(mute[0])
False
Reviewing the code in ./upnp/init.py, the code to set mute is taking a Boolean and passing 'Enable' or 'Disable' to the UPNP functions based on the Boolean value:
@mute.setter
def mute(self, desired_mute):
if not self.connected:
return
if desired_mute:
desired_mute = 'Enable'
else:
desired_mute = 'Disable'
try:
self.MainTVAgent2.SetMute(desired_mute)
except AttributeError:
self.set_channel_mute('Master', desired_mute)
In the above, since 'MainTVAgent2.SetMute' fails with an AttributeError exception, set_channel_mute is called, passing in 'Enable' or 'Disable' (desired_mute variable). Set_channel_mute tries to pass the 'Enable' or 'Disable' value to the UPNP method RenderingControl.SetMute and this too fails as RenderingControl.SetMute is expecting a Boolean true/false instead of 'Enable' or 'Disable' resulting in an exception of "TypeError: Incorrect Value":
def set_channel_mute(self, channel, desired_mute):
if not self.connected:
return
self.RenderingControl.SetMute(0, channel, desired_mute)
The code to retrieve the current mute state is expecting the UPNP method to return 'Disable' and return a Boolean value of False, or True if the returned value is not 'Disable' (hence why remote.mute always returns True in my case):
@property
def mute(self):
if not self.connected:
return
try:
status = self.MainTVAgent2.GetMuteStatus()[1]
except AttributeError:
status = self.get_channel_mute('Master')
if status == 'Disable':
return False
else:
return True
By changing the code for the mute property in ./upnp/init.py to work with Boolean True/False values instead of string values 'Enable', 'Disable', as in the following example, remote.mute works as expected:
@property
def mute(self):
if not self.connected:
return
try:
status = self.MainTVAgent2.GetMuteStatus()[1]
except AttributeError:
status = self.get_channel_mute('Master')
return status[0]
@mute.setter
def mute(self, desired_mute):
if not self.connected:
return
try:
self.MainTVAgent2.SetMute(desired_mute)
except AttributeError:
self.set_channel_mute('Master', desired_mute)
>>> print(remote.mute)
False
>>> remote.mute = True
>>> print(remote.mute)
True
However, I suspect these changes to the code would break the mute function on other TVs.
ahh OK i see what is going on is going on..
calling the UPNP function directly is going to result in a list being returned. this is normal behavior.
I think that happened was I had coded some of the properties before I decide ot make a full on handler for the UPNP. I decided to handle all of this hind of stuff directly in the data type object i created instead of having a bunch of duplicate code all over the place. I must have missed changing the mute to work with the data type object returned values.
funny thing is I did modify it to work with either MainTVAgen2 as well as RenderingControl. so if one fails it will get it from the other location. at least i got that part right.. LOL
do you want to submit a PR for the code change? or would you like me to fix the problem and just add your name to the comments for the commit?
also TY for spending the time in keying up a very detailed revision of what is going on.. was far easier to understand the second time around. also TY for providing a fix for the issue as well.
Go ahead and make the fix. I don't have a fork to pull from. I just discovered your updated samsungctl last night and you have improved it leaps and bounds from what it was. I though I was finally going to get full control of my TV via IP for better integration into my HA system. I've been playing around with it more today and unfortunately have discovered that with my TV missing the MainTVAgent2 service, I'm still lacking some of the controls I would like to have (primarily select_source). At least now I've got mute status and the ability to get and set the volume level instead of stepping up and down. These alone are significant, so thank you very much for the time and effort you have put into improving the samsungctl library. I'll be keeping an eye on your repo in hopes that a way to select source is found without requiring the MainTVAgent2 service.
I am not sure of the model of the TV you have. since you mention the lack of the MainTVAgent2 II am going to think that you have a 2016 or newer TV.
and if that is the case. follow these instructions.
https://developer.samsung.com/tv/develop/extension-libraries/smart-view-sdk/receiver-apps/debugging
this is going to enable you to get communication logs from the TV. you are going to want to install the smartthings app connect the thing to your TV.. and poke about in the app. this is going to fill the log on the TV with all kinds of very helpful information. you never know maybe we can get the source changes to work over the websocket. I have been expanding the websocket functionality on a daily bases. I do not own a new Samsung TV so I am not able to do this personally.
attach the logs as a text file to a post so I can have a look see.
I did really expand the functionality of this library. it needed to be. Everyone bitches about the lack of ability to control the TV's. When in fact the ability to control them just like every other brand TV is there. we simply have to figure it out ourselves. Samsung sucks at documenting things.. And they are also extremely hush hush about their API because of what some ass did a few years back and published an article on how easy it was to activate the voice recognition on a TV and listen in to peoples conversations which i do not believe was actually done. I do believe that the voice recognition was activated. but they were not able to listen in. ever since then the API has been pretty hush hush this all started with the H and J series TV's
You should also have control of brightness, sharpness, color temperature, contrast, I did dink around with adding a few more websocket commands. I am not sure if they are operational or not.. the code is in my develop branch.
On my TV, the UPNP SetMute method is expecting a Boolean true/false parameter and the GetMute is returning a Boolean true/false value. The set method in samsungctl is trying to pass a string value of 'Enable' or 'Disable' and the getter is expecting 'Enable' or 'Disable' to be returned from the UPNP GetMute method. Below is the UPNP method info dumped from my TV: