Skip to content

Commit

Permalink
V1.1.2 Add Cert and Key for HTTPS requests
Browse files Browse the repository at this point in the history
  • Loading branch information
MrBearPresident committed Sep 29, 2024
1 parent 96b0613 commit d3f6903
Show file tree
Hide file tree
Showing 3 changed files with 154 additions and 51 deletions.
25 changes: 25 additions & 0 deletions custom_components/jbl_integration/Cert.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
Bag Attributes
friendlyName: 1
localKeyID: 54 69 6D 65 20 31 37 32 37 35 34 39 32 30 38 36 38 31
subject=C=US, ST=Some-State, O=Linkplay, OU=Linkplay, emailAddress=harman@linkplay.com
issuer=C=US, ST=Some-State, O=Linkplay, OU=Linkplay, emailAddress=harman@linkplay.com
-----BEGIN CERTIFICATE-----
MIIDTDCCAjQCAQEwDQYJKoZIhvcNAQELBQAwbDELMAkGA1UEBhMCVVMxEzARBgNV
BAgMClNvbWUtU3RhdGUxETAPBgNVBAoMCExpbmtwbGF5MREwDwYDVQQLDAhMaW5r
cGxheTEiMCAGCSqGSIb3DQEJARYTaGFybWFuQGxpbmtwbGF5LmNvbTAeFw0yMDA1
MTIwMjQ3MzNaFw00NzA5MjcwMjQ3MzNaMGwxCzAJBgNVBAYTAlVTMRMwEQYDVQQI
DApTb21lLVN0YXRlMREwDwYDVQQKDAhMaW5rcGxheTERMA8GA1UECwwITGlua3Bs
YXkxIjAgBgkqhkiG9w0BCQEWE2hhcm1hbkBsaW5rcGxheS5jb20wggEiMA0GCSqG
SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDwSU/lDQtH0IAkcbMyYGRMnAvW5Q71pTu7
Cn6fNRJS35okajb2oLsWrkkNeFX1989cxdHl2iDiGabQ3napGVyzyatM0XQWClVi
/EGq5m90D7wlU0WhL3LtpPA2tdtnxDBK2ax+jzEavyzbcaTtDJqNMEf3qLyXafxX
RZ0eaTgb8cp6QzMOXIfeC3fCNVqJ7HCqYRwG6iP+uZznQ5IPMdw2sVTsOo8McXXR
EfxY2J7QWi9QG4ahpkcBAwCF8BFRQZbdCO+rzVY9O3094a8vjzcawndVfpBze2Jj
FVtmBCUbZqMEodhzCMr6zfqrpXFOpnM+zZJyELiRJiIP6KiEKSutAgMBAAEwDQYJ
KoZIhvcNAQELBQADggEBAEdCCU2thJxExluRPBAZF4rZKJyDsdisbRrDqnIEA3Np
QJMH+NPlP+A6Ft2xz4ByL60Hs7i0G/jIRzqM7dKhmbJcP//z32FrBLGmOuCEAYtf
GN8WYGsSoK/GjV4DfT/gdukq7dgPLHwDY5UuxJfmkJ4bvvplbXEPc8E5giltxmsi
1zQXol2sVb3Ul5U27m0rpu+Mqdrszst1HFqUltOaz/g5R7t9H5Pd5/k1ntR2HJjK
0+p/lGrrumDGs1gEpWfnSsvIPTDUf6uNoA6LgRBgSwoZvVHO5pHpFz2Rm2M5Nh96
v/Do6XDLJJsLIAxx1CbpnNwk4SEEExLoRsuCgwSBG8U=
-----END CERTIFICATE-----
32 changes: 32 additions & 0 deletions custom_components/jbl_integration/Key.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
Bag Attributes
friendlyName: 1
localKeyID: 54 69 6D 65 20 31 37 32 37 35 34 39 32 30 38 36 38 31
Key Attributes: <No Attributes>
-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDwSU/lDQtH0IAk
cbMyYGRMnAvW5Q71pTu7Cn6fNRJS35okajb2oLsWrkkNeFX1989cxdHl2iDiGabQ
3napGVyzyatM0XQWClVi/EGq5m90D7wlU0WhL3LtpPA2tdtnxDBK2ax+jzEavyzb
caTtDJqNMEf3qLyXafxXRZ0eaTgb8cp6QzMOXIfeC3fCNVqJ7HCqYRwG6iP+uZzn
Q5IPMdw2sVTsOo8McXXREfxY2J7QWi9QG4ahpkcBAwCF8BFRQZbdCO+rzVY9O309
4a8vjzcawndVfpBze2JjFVtmBCUbZqMEodhzCMr6zfqrpXFOpnM+zZJyELiRJiIP
6KiEKSutAgMBAAECggEBANNY6HkjXASyk8N6bo+k0RPBPXiqyNmvmDYQKQeH+rIC
EuZstiN/hI+ShJbgfVt3uGB1bwWpMrssrNmSkvRxZmSMwaszn9OzCx+hmXDkdquz
G14JPHll7sSwCslUc8N1gLSVeW9oK1zHQoFSGCqYp2gAS4y+UgMsKdPpWyVgjwWj
lm5FwWOAzQ+gWlGX2Ist0AgskId4M/glWV/fAHP7op8d0UTeEHS3jJi8PfnNQhrb
npCVkJC6U869EXLYAiPE71GDnIwWOJk7V5VODlK1AndD8tqcNPHjYtyhoTMroQVR
Md2ykfm9Oz5FnBiYCjUxIS4Mbd5erA6pfaiA3CSRKFECgYEA/nVKnBex+vg6nr49
2SwrDnACkA5OVUmhrVe5JHQAQ+43jrJoETqGPzomaQEcr2TN5ULKu5FGELm7Pi9w
BfT2NJZN6SBLFWa5r/vK97/lXYWgDE/bzK0kDRyQWtbHHGCuRK3sFYevOlgb/b3C
Oi0r7uuqDyvLV8smd0NpiA8cpFcCgYEA8b4JqaDNVIetT4eXCURJ8M/+ne47J6a3
Zfi0A0Gk2vChNYrP7XSoHKuYEl2QblzU4qC9fL/I10DBx9heKeXvh3s/Dsn0mTMT
m1Z+Fo9ksEwc4kwcTzbpVXP52NXP2rvPzZnSPSdKMc/48gw6bNgiE317QCYdijYe
l97+5uCIzZsCgYASmRQI8Jprk3UFYTY4B0hmV714NfN3vFf6yWyYw3m5fVHGNjfw
+mwRdviTuCcWkrGRzh3vM6EBW/HZi7IOXWcZVNsA7QFP4SA1QpwFG5tyCHA4NiYE
gase4jWSzhvjcRWLo4Kb2DzwcLwrAZGOmvqZDdRyI2tLUWfQU7cE4MXhJQKBgQDQ
/p3189pwsRfpwOyYC1ztf7S+Lx8fSagW1awzgIYY7p5A3vCidw98MfG4NwHOGB3I
jHUlq9zkE800jF/kUzEBbVD35Su9YwYZbu51bKT9MeBq2KhE59FUmn6vszIPBf5C
3zB+xEAFzqqIAIBmZ3kWZo6uyAUT33QVkqnHSuma7wKBgDFaQVpvQLx6O9XdzrG9
EL4tWJCRwT+Ez8r4+zt+Lk08nUnILSObPgQ5HgRaRXT7gQ5GzSZAvqEoCC2O3g0P
rHFa8468QPClkJ1LSkOjXv+5oXu27XxQVMX465P3wM6PyD+j+WAveEnV64O7Iwu7
NsRkLXJHFMQmFXczYZUpgc57
-----END PRIVATE KEY-----
148 changes: 97 additions & 51 deletions custom_components/jbl_integration/coordinator.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
"""Sensor platform for JBL integration."""
import aiohttp
import async_timeout
import requests
import json
import logging
import urllib3
import ssl
import certifi
from datetime import timedelta
from homeassistant.helpers.entity import Entity
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator


from .const import DOMAIN

_LOGGER = logging.getLogger(__name__)
Expand All @@ -20,8 +25,13 @@ def __init__(self, hass, entry, address, pollingRate):
"""Initialize the coordinator."""
self._entry = entry
self.address = address
self.hass = hass
self.pollingRate = pollingRate
self.data = {}
ssl_context = ssl.create_default_context(cafile=certifi.where())
ssl_context.check_hostname = False
ssl_context.verify_mode = ssl.CERT_NONE
self.sslcontext = ssl_context
super().__init__(
hass,
_LOGGER,
Expand All @@ -31,20 +41,38 @@ def __init__(self, hass, entry, address, pollingRate):
)

async def _SetupDeviceInfo(self):
#Setting up cert
cert_path = self.hass.config.path("custom_components/jbl_integration/Cert.pem")
key_path = self.hass.config.path("custom_components/jbl_integration/Key.pem")
self.sslcontext.load_cert_chain(certfile=cert_path, keyfile=key_path)

device_info = await self.getDeviceInfo()
device_Type = await self.getDeviceType()



# Ensure device_info has all the expected keys and provide fallback values if necessary
mac_address = device_info.get("wlan0_mac", "unknown_mac")
uuid = device_info.get("uuid", "unknown_uuid")
device_name = device_info.get("name", "JBL Bar 800")
device_name = device_info.get("name", "Unknow_name")
serial_number = device_info.get("serial_number", "unknown_serial")
firmware_version = device_info.get("firmware", "unknown_firmware")
ip_address = device_info.get("apcli0", "unknown_address")
model = device_Type.get("hm_product_name", "unknown_product")
hw_version = device_Type.get("hardware", "unknown_hardware")


self._device_info = {
"identifiers": {(DOMAIN, self._entry.entry_id, mac_address, uuid, str(uuid).replace("-", "") , self.address)},
"identifiers": {
(DOMAIN, self._entry.entry_id),
(DOMAIN, mac_address,uuid),
(DOMAIN, str(uuid).replace("-", "")),
(DOMAIN, self.address),
},
"name": device_name,
"manufacturer": "HARMAN International Industries",
"model": "JBL Bar 800",
"model": model,
"hw_version": hw_version,
"sw_version": firmware_version,
"serial_number": serial_number,
}
Expand All @@ -58,12 +86,15 @@ async def _UpdatePollingrate(self,pollingRate):
self.update_interval = pollingRate

async def _send_command(self, command):
# Disable SSL warnings
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

"""Send a command to the device."""
url = f'http://{self.address}/httpapi.asp'
url = f'https://{self.address}/httpapi.asp'
payload = f'command=sendAppController&payload={{"key_pressed": "{command}"}}'
async with aiohttp.ClientSession() as session:
with async_timeout.timeout(10):
async with session.post(url, data=payload) as response:
async with session.post(url, data=payload, ssl=self.sslcontext) as response:
if response.status != 200:
_LOGGER.error("Failed to send command: %s", response.status)

Expand All @@ -79,17 +110,19 @@ async def _async_update_data(self):
self.data.update(combined_data)
return combined_data



async def getDeviceInfo(self):
url = f'http://{self.address}/httpapi.asp?command=getDeviceInfo'
# Disable SSL warnings
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

url = f'https://{self.address}/httpapi.asp?command=getDeviceInfo'
headers = {
'Accept-Encoding': "gzip",
}

async with aiohttp.ClientSession() as session:
try:
with async_timeout.timeout(10):
async with session.get(url, headers=headers) as response:
async with session.get(url, headers=headers, ssl=self.sslcontext) as response:
if response.status == 200:
response_text = await response.text()
response_json = json.loads(response_text)
Expand All @@ -106,50 +139,58 @@ async def getDeviceInfo(self):

async def getDeviceType(self):
"""Fetch data from the API."""
url = f'http://{self.address}:59152/upnp/control/rendercontrol1'
url = f"http://{self.address}:59152/upnp/control/rendercontrol1"

payload = """<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<s:Envelope s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Body>
<u:GetControlDeviceInfo xmlns:u="urn:schemas-upnp-org:service:RenderingControl:1">
<InstanceID>0</InstanceID></u:GetControlDeviceInfo>
</s:Body></s:Envelope>"""

headers = {
"Content-type": 'text/xml;charset="utf-8"',
"Soapaction": '"urn:schemas-upnp-org:service:AVTransport:1#GetInfoEx"'
'Content-type': "text/xml;charset=\"utf-8\"",
'Soapaction': "\"urn:schemas-upnp-org:service:RenderingControl:1#GetControlDeviceInfo\""
}
payload = """
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<s:Envelope s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Body>
<u:GetInfoEx xmlns:u="urn:schemas-upnp-org:service:RenderingControl:1">
<InstanceID>0</InstanceID>
</u:GetInfoEx>
</s:Body>
</s:Envelope>
"""
async with aiohttp.ClientSession() as session:
try:
with async_timeout.timeout(10):
async with session.post(url, headers=headers, data=payload) as response:
if response.status == 200:
response_text = await response.text()
#_LOGGER.debug("Response text: %s", response_text)
# Parse the XML response manually, as it's not JSON
from xml.etree import ElementTree as ET
root = ET.fromstring(response_text)
namespaces = {
's': 'http://schemas.xmlsoap.org/soap/envelope/',
'u': 'urn:schemas-upnp-org:service:RenderingControl:1'
}
try:
status = root.find('.//u:GetInfoExResponse/Status', namespaces).text
deviceType = status.get("hm_product_name", "unknown_product")
return deviceType
except AttributeError:
_LOGGER.error("Could not find necessary device type in the response")
return {}

try:
async with aiohttp.ClientSession() as session:
async with session.post(url, data=payload, headers=headers) as response:
if response.status == 200:
response_text = await response.text()

# Parse XML response
namespace = {
's': 'http://schemas.xmlsoap.org/soap/envelope/',
'u': 'urn:schemas-upnp-org:service:RenderingControl:1'
}
from xml.etree import ElementTree as ET
root = ET.fromstring(response_text)

# Find the Status element
status_element = root.find('.//u:GetControlDeviceInfoResponse/Status', namespace)

if status_element is not None:
# Get the text content of the Status element (which is a JSON string)
status_json_str = status_element.text

# Parse the JSON string into a Python dictionary
status_data = json.loads(status_json_str)

# Output the status data
return status_data
else:
_LOGGER.error("Failed to fetch data: %s", response.status)
return {}
except Exception as e:
_LOGGER.error("Error fetching data: %s", str(e))
return {}
except Exception as e:
_LOGGER.error("Error fetching data: %s", str(e))
return {}


async def requestInfo(self):
# Disable SSL warnings
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

"""Fetch data from the API."""
url = f'http://{self.address}:59152/upnp/control/rendertransport1'
headers = {
Expand Down Expand Up @@ -234,14 +275,17 @@ async def setVolume(self, value: float):
return {}

async def getEQ(self):
url = f'http://{self.address}/httpapi.asp?command=getEQ'
# Disable SSL warnings
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

url = f'https://{self.address}/httpapi.asp?command=getEQ'
headers = {
'Accept-Encoding': "gzip",
}
async with aiohttp.ClientSession() as session:
try:
with async_timeout.timeout(10):
async with session.get(url, headers=headers) as response:
async with session.get(url, headers=headers, ssl=self.sslcontext) as response:
if response.status == 200:
response_text = await response.text()
response_json = json.loads(response_text)
Expand All @@ -262,8 +306,11 @@ async def getEQ(self):
return {}

async def setEQ(self, value: float, frequency):
# Disable SSL warnings
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

"""Fetch data from the API."""
url = f'http://{self._entry.data["ip_address"]}/httpapi.asp'
url = f'https://{self._entry.data["ip_address"]}/httpapi.asp'
headers = {
'Accept-Encoding': "gzip",
}
Expand All @@ -276,15 +323,14 @@ async def setEQ(self, value: float, frequency):
async with aiohttp.ClientSession() as session:
try:
with async_timeout.timeout(10):
async with session.post(url, headers=headers, data=payload) as response:
async with session.post(url, headers=headers, data=payload, ssl=self.sslcontext) as response:
if response.status != 200:
_LOGGER.error("Failed to set EQ: %s", response.status)
return {}
except Exception as e:
_LOGGER.error("Error setting EQ: %s", str(e))
return {}


def merge_two_dicts(self,x, y):
z = x.copy() # start with keys and values of x
z.update(y) # modifies z with keys and values of y
Expand Down

0 comments on commit d3f6903

Please sign in to comment.