raman325 / pyvizio

Python client for Vizio SmartCast
MIT License
105 stars 29 forks source link

Can't reach any endpoints with pyvizio, but can with curl #133

Open whophil opened 1 year ago

whophil commented 1 year ago

I have a Vizio E55-D0. I am able to reach endpoints and create an auth token via direct usage of curl, but cannot do so with pyvizio.

Below is an example (auth token redacted).

➜  pyvizio --ip=10.20.50.221 --device_type=tv --auth=XXXXXXXX power off
INFO:pyvizio.cli:Turning OFF
ERROR:pyvizio:Failed to execute command: Cannot connect to host 10.20.50.221:9000 ssl:False [None]
INFO:pyvizio.cli:ERROR

Any ideas from the devs? Is there any way to get more verbose output to understand what is happening?

maxnl commented 1 year ago

I am having the same issue with an E40-D0, same model year and series. I was able to pair manually to get an auth token using a CLI script, but can't seem to use that to connect with pyvizio or Home Assistant.

I'm wondering if it might be related to issue #114 that came up back with the 2020 models (also discussed on the Home Assistant community).

I was able to get this TV to work with Homebridge a few years back but I recall struggling with Home Assistant at the time as well. So I think it might just be something about the firmware on these old models that pyvizio isn't accounting for.

whophil commented 1 year ago

It seems that this issue is related to this model of TV using the very old TLS v1.0, as well as something called "unsafe legacy renegotiation."

What follows is a proof of concept to make a working API call to this model TV from Python via requests.

This requires:

  1. Forcing TLSv1
  2. Enabling "unsafe legacy renogiation" in OpenSSL (here it is done via a .cnf file which needs to be pointed to by the calling environment.)

Are there active devs here who could comment on how this might be implemented in this project?

The script vizio_request.py

from requests.adapters import HTTPAdapter
from urllib3 import PoolManager
import requests
import ssl

class MyAdapter(HTTPAdapter):
    def init_poolmanager(self, connections, maxsize, block=False):
        self.poolmanager = PoolManager(num_pools=connections,
                                       maxsize=maxsize,
                                       block=block,
                                       ssl_version=ssl.PROTOCOL_TLSv1)

s = requests.session()
s.mount('https://', MyAdapter())

tv_ip = '10.20.50.221:9000'
r = s.put(url=f'https://{tv_ip}/pairing/start',
          json={"DEVICE_ID": "pyvizio",
                "DEVICE_NAME": "Python Vizio"},
          headers={'Content-Type': 'application/json'},
          verify=False)

print(r.content)

The OpenSSL conf file, openssl.cnf

openssl_conf = openssl_init

[openssl_init]
ssl_conf = ssl_sect

[ssl_sect]
system_default = system_default_sect

[system_default_sect]
Options = UnsafeLegacyRenegotiation

Calling it

OPENSSL_CONF=/path/to/openssl.cnf python vizio_request.py
ifeign commented 1 year ago

It seems that this issue is related to this model of TV using the very old TLS v1.0, as well as something called "unsafe legacy renegotiation."

What follows is a proof of concept to make a working API call to this model TV from Python via requests.

This requires:

  1. Forcing TLSv1
  2. Enabling "unsafe legacy renogiation" in OpenSSL (here it is done via a .cnf file which needs to be pointed to by the calling environment.)

Are there active devs here who could comment on how this might be implemented in this project?

The script vizio_request.py

from requests.adapters import HTTPAdapter
from urllib3 import PoolManager
import requests
import ssl

class MyAdapter(HTTPAdapter):
    def init_poolmanager(self, connections, maxsize, block=False):
        self.poolmanager = PoolManager(num_pools=connections,
                                       maxsize=maxsize,
                                       block=block,
                                       ssl_version=ssl.PROTOCOL_TLSv1)

s = requests.session()
s.mount('https://', MyAdapter())

tv_ip = '10.20.50.221:9000'
r = s.put(url=f'https://{tv_ip}/pairing/start',
          json={"DEVICE_ID": "pyvizio",
                "DEVICE_NAME": "Python Vizio"},
          headers={'Content-Type': 'application/json'},
          verify=False)

print(r.content)

The OpenSSL conf file, openssl.cnf

openssl_conf = openssl_init

[openssl_init]
ssl_conf = ssl_sect

[ssl_sect]
system_default = system_default_sect

[system_default_sect]
Options = UnsafeLegacyRenegotiation

Calling it

OPENSSL_CONF=/path/to/openssl.cnf python vizio_request.py

Thanks for this, without this being implemented into pyvizio I won't be able to connect my E55-D0