Appdaemon insecure requests fail

I’m trying to create an automation to cure what appears to be flaws in my audio system. When I turn my TV on, the soundbar cuts out. All that has to be done is to send a signal down to tell it to use the source. I use a vizio soundbar which does have an api to control it. I can call the correct curl to the api and fix the problem. The cURL is to a secure URL, but it’s a self signed certificate, so I have to use an insecure request. I’ve tried many different ways to accomplish this inside hassio (python script - can’t import requests, command_line_switch - can’t do insecure requests, input_boolean - calling a switch with the rest API… again can’t call insecure request), but as you can tell each time I ran into a wall.

What I want to do is to run an automation that is triggered by the tv turning on, so that it all happens seemlessly, but I can’t find anywhere inside hassio that will allow me to call an insecure request. In appdaemon, it seems to accept the “verify=false” at the end of the request which is what allows it to function correctly outside of hassio/appdaemon, but when I post that inside appdaemon, it seems to ignore the flag.

Here’s my code in appdaemon (I’m using a input_boolean as a trigger for testing until the script works):

import appdaemon.plugins.hass.hassapi as hass
import requests, json

class change_input(hass.Hass):

    def initialize(self):
        self.log('Vizio Input change script loaded.')
        self.listen_state(self.change, "input_boolean.vizio_source", new="on")
        
    def change(self, entity, attribute, old, new, kwargs):
        #powermode = requests.get('https://192.168.1.142:9000/state/device/power_mode', verify=False)
        
        response = requests.get('https://hassio:9000/menu_native/dynamic/audio_settings/input/current_input', verify=False)
        self.log(response.text)
        
        # example response below
        # {
        # 	"ITEMS": [{
        # 		"NAME": String,
        # 		"CNAME": String,
        # 		"TYPE": String,
        # 		"VALUE": String,
        # 		"ENABLED": Boolean,
        # 		"HASHVAL": Integer
        # 	}],
        # 	"NAME": String,
        # 	"HASHLIST": Array,
        # 	"GROUP": String,
        # 	"PARAMETERS": {
        # 		"HASHONLY": String,
        # 		"FLAT": String,
        # 		"HELPTEXT": String
        # 	},
        # 	...
        # }
        
        response_dict = json.loads(response.text)
        
        hashval = str(response_dict["ITEMS"][0]["HASHVAL"])
        self.log(hashval)
    
        headers = {'Content-Type': 'application/json'}
        
        data = '{"REQUEST": "MODIFY","VALUE": "HDMI-ARC","HASHVAL":' + hashval + '}'
        
        response2 = requests.put('https://hassio:9000/menu_native/dynamic/audio_settings/input/current_input', headers=headers, data=data, verify=False)
        
        self.log(response2.text)

and here is the error I get in the appdaemon log:

requests.exceptions.SSLError: HTTPSConnectionPool(host='hassio', port=9000): Max retries exceeded with url: /menu_native/dynamic/audio_settings/input/current_input (Caused by SSLError(SSLCertVerificationError(1, '[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: self signed certificate in certificate chain (_ssl.c:1056)')))
2019-10-18 20:46:06.362459 WARNING AppDaemon: ------------------------------------------------------------

I’m open to trying other avenues as well, but every path I’ve taken runs into this same issue. I’ve been working on this for a few days and I think I’m about out of ideas here. Seems like a pretty simple issue, but I don’t know how to make this happen at this point. Any help is appreciated. Thanks!

Did you try like this also?

session = requests.Session()
session.verify = False
session.get('https......')

That did it. Thank you!!

EDIT: it seemed to work the first time, but I can’t duplicate it now. Same error. New code:

    def change(self, entity, attribute, old, new, kwargs):
        session = requests.Session()
        session.verify = False
        response = session.get('https://hassio:9000/menu_native/dynamic/audio_settings/input/current_input')
        self.log(response.text)

error:

2019-10-19 07:33:14.842952 WARNING AppDaemon: ------------------------------------------------------------
2019-10-19 07:33:14.847989 WARNING AppDaemon: Traceback (most recent call last):
  File "/usr/lib/python3.7/site-packages/urllib3/connectionpool.py", line 672, in urlopen
    chunked=chunked,
  File "/usr/lib/python3.7/site-packages/urllib3/connectionpool.py", line 376, in _make_request
    self._validate_conn(conn)
  File "/usr/lib/python3.7/site-packages/urllib3/connectionpool.py", line 994, in _validate_conn
    conn.connect()
  File "/usr/lib/python3.7/site-packages/urllib3/connection.py", line 394, in connect
    ssl_context=context,
  File "/usr/lib/python3.7/site-packages/urllib3/util/ssl_.py", line 378, in ssl_wrap_socket
    return context.wrap_socket(sock)
  File "/usr/lib/python3.7/ssl.py", line 412, in wrap_socket
    session=session
  File "/usr/lib/python3.7/ssl.py", line 853, in _create
    self.do_handshake()
  File "/usr/lib/python3.7/ssl.py", line 1117, in do_handshake
    self._sslobj.do_handshake()
ssl.SSLCertVerificationError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: self signed certificate in certificate chain (_ssl.c:1056)
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
  File "/usr/lib/python3.7/site-packages/requests/adapters.py", line 449, in send
    timeout=timeout
  File "/usr/lib/python3.7/site-packages/urllib3/connectionpool.py", line 720, in urlopen
    method, url, error=e, _pool=self, _stacktrace=sys.exc_info()[2]
  File "/usr/lib/python3.7/site-packages/urllib3/util/retry.py", line 436, in increment
    raise MaxRetryError(_pool, url, error or ResponseError(cause))
urllib3.exceptions.MaxRetryError: HTTPSConnectionPool(host='192.168.1.142', port=9000): Max retries exceeded with url: /menu_native/dynamic/audio_settings/input/current_input (Caused by SSLError(SSLCertVerificationError(1, '[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: self signed certificate in certificate chain (_ssl.c:1056)')))
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
  File "/usr/lib/python3.7/site-packages/appdaemon/appdaemon.py", line 595, in worker
    self.sanitize_state_kwargs(app, args["kwargs"]))
  File "/config/appdaemon/apps/change_vizio_source_hdmi_arc.py", line 15, in change
    response = session.get('https://hassio:9000/menu_native/dynamic/audio_settings/input/current_input')
  File "/usr/lib/python3.7/site-packages/requests/sessions.py", line 546, in get
    return self.request('GET', url, **kwargs)
  File "/usr/lib/python3.7/site-packages/requests/sessions.py", line 533, in request
    resp = self.send(prep, **send_kwargs)
  File "/usr/lib/python3.7/site-packages/requests/sessions.py", line 646, in send
    r = adapter.send(request, **kwargs)
  File "/usr/lib/python3.7/site-packages/requests/adapters.py", line 514, in send
    raise SSLError(e, request=request)
requests.exceptions.SSLError: HTTPSConnectionPool(host='192.168.1.142', port=9000): Max retries exceeded with url: /menu_native/dynamic/audio_settings/input/current_input (Caused by SSLError(SSLCertVerificationError(1, '[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: self signed certificate in certificate chain (_ssl.c:1056)')))
2019-10-19 07:33:14.848924 WARNING AppDaemon: ------------------------------------------------------------```

I also learned a little more about the certificate. I have the common name for it now and appparently the error is a common name mismatch (b/c the cert is xxx.vizio.xxx and I’m requesting from my localhost). Not sure if this helps as I can’t find a way to use that information, but perhaps it helps someone.

Could you please post your full code again? Did it work only one time and you didn’t change anything after it worked this one time and then it stopped working?

I’m nearly positive it worked the first time. The problem is that I wrote my log to the appdaemon log and I can’t go back and verify (the previous page disappeared quickly). So, I’m almost sure it worked the first time. I initially updated the GET request and commented out the PUT request. Once I saw it work, I went and updated the PUT request as well. That failed, so I reverted back to the original setup which you suggested and I haven’t been able to make it work any more. Here’s the full code:

import appdaemon.plugins.hass.hassapi as hass
import requests, json, ssl

class change_input(hass.Hass):

    def initialize(self):
        self.log('Vizio Input change script loaded.')
        self.listen_state(self.change, "input_boolean.vizio_source", new="on")
        
        session = requests.Session()
        session.verify = False
        response = session.get('https://hassio:9000/menu_native/dynamic/audio_settings/input/current_input')
        #response = requests.get('https://192.168.1.142:9000/menu_native/dynamic/audio_settings/input/current_input', verify=False)
        self.log(response.text)
        
        #parse json for hashval.  example response below
        # {
        # 	"ITEMS": [{
        # 		"NAME": String,
        # 		"CNAME": String,
        # 		"TYPE": String,
        # 		"VALUE": String,
        # 		"ENABLED": Boolean,
        # 		"HASHVAL": Integer
        # 	}],
        # 	"NAME": String,
        # 	"HASHLIST": Array,
        # 	"GROUP": String,
        # 	"PARAMETERS": {
        # 		"HASHONLY": String,
        # 		"FLAT": String,
        # 		"HELPTEXT": String
        # 	},
        # 	...
        # }
              
        
        
        #response_dict = json.loads(response.text)
        
        #hashval = str(response_dict["ITEMS"][0]["HASHVAL"])
        #self.log(hashval)
    
        #headers = {'Content-Type': 'application/json'}

        #data = '{"REQUEST": "MODIFY","VALUE": "HDMI-ARC","HASHVAL":' + hashval + '}'
        
        #response2 = requests.put('https://192.168.1.142:9000/menu_native/dynamic/audio_settings/input/current_input', headers=headers, data=data, verify=False)
        
        #self.log(response2.text)

What I’m currently trying to do is to see if I can actually download the cacert.pem file and hold it to approve. Secondarily, I’m trying to see if I can somehow tell it what the CN should be so that the mismatch isn’t an issue. No luck making either of these happen yet though.

Hmm strange, I’m sorry but I don’t think I can help you with your problem. Don’t have much knowledge about certificates and requests etc.

For anyone who may come across this later, I did get this working. However, it’s not an elegant solution. I tried node red, command line switch, boolean switch, automations, etc etc. I couldn’t ever get past the CN mismatch in the certificate since it’s issues to the manufacturer, but it’s called via my localhost. So, the workaround was to create an automation which publishes a command via mqtt. Then, create a python script that runs completely outside of the home assistant sandbox which listens for the mqtt command and performs the insecure cURL request. There’s no issue running an insecure request from a standard python script so the solution works. I was planning/hoping to keep it all nice and tidy inside of HA,but so far no luck with that. Perhaps some changes will allow this later.