Files
SmartIR/custom_components/infrarize/__init__.py
T

196 lines
7.4 KiB
Python

import aiofiles
import aiohttp
import asyncio
import binascii
from distutils.version import StrictVersion
import json
import logging
import os.path
import requests
import struct
import voluptuous as vol
from aiohttp import ClientSession
from homeassistant.const import (
ATTR_FRIENDLY_NAME, __version__ as current_ha_version)
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.typing import ConfigType
_LOGGER = logging.getLogger(__name__)
DOMAIN = 'infrarize'
VERSION = '1.18.1'
MANIFEST_URL = (
"https://raw.githubusercontent.com/"
"kobimx/Infrarize/{}/"
"custom_components/infrarize/manifest.json")
REMOTE_BASE_URL = (
"https://raw.githubusercontent.com/"
"kobimx/Infrarize/{}/"
"custom_components/infrarize/")
COMPONENT_ABS_DIR = os.path.dirname(
os.path.abspath(__file__))
CONF_CHECK_UPDATES = 'check_updates'
CONF_UPDATE_BRANCH = 'update_branch'
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({
vol.Optional(CONF_CHECK_UPDATES, default=True): cv.boolean,
vol.Optional(CONF_UPDATE_BRANCH, default='master'): vol.In(
['master', 'rc'])
})
}, extra=vol.ALLOW_EXTRA)
async def async_setup(hass, config):
"""Set up the Infrarize component."""
conf = config.get(DOMAIN)
if conf is None:
return True
check_updates = conf[CONF_CHECK_UPDATES]
update_branch = conf[CONF_UPDATE_BRANCH]
async def _check_updates(service):
await _update(hass, update_branch)
async def _update_component(service):
await _update(hass, update_branch, True)
hass.services.async_register(DOMAIN, 'check_updates', _check_updates)
hass.services.async_register(DOMAIN, 'update_component', _update_component)
if check_updates:
await _update(hass, update_branch, False, False)
return True
async def async_setup_entry(hass, entry):
"""Set up Infrarize from a config entry (UI-configured device)."""
platform = entry.data.get('platform')
if not platform:
return False
await hass.config_entries.async_forward_entry_setups(entry, [platform])
# Reload the entry whenever options are changed in the UI so the new
# controller_data / delay / sensor settings take effect immediately.
entry.async_on_unload(entry.add_update_listener(_async_options_updated))
return True
async def _async_options_updated(hass, entry):
"""Triggered when the options flow saves; reloads the config entry."""
await hass.config_entries.async_reload(entry.entry_id)
async def async_unload_entry(hass, entry):
"""Unload an Infrarize config entry."""
platform = entry.data.get('platform')
if not platform:
return True
return await hass.config_entries.async_unload_platforms(entry, [platform])
async def _update(hass, branch, do_update=False, notify_if_latest=True):
try:
async with aiohttp.ClientSession() as session:
async with session.get(MANIFEST_URL.format(branch)) as response:
if response.status == 200:
data = await response.json(content_type='text/plain')
min_ha_version = data['homeassistant']
last_version = data['updater']['version']
release_notes = data['updater']['releaseNotes']
if StrictVersion(last_version) <= StrictVersion(VERSION):
if notify_if_latest:
hass.components.persistent_notification.async_create(
"You're already using the latest version!",
title='Infrarize')
return
if StrictVersion(current_ha_version) < StrictVersion(min_ha_version):
hass.components.persistent_notification.async_create(
"There is a new version of Infrarize integration, but it is **incompatible** "
"with your system. Please first update Home Assistant.", title='Infrarize')
return
if do_update is False:
hass.components.persistent_notification.async_create(
"A new version of Infrarize integration is available ({}). "
"Call the ``infrarize.update_component`` service to update "
"the integration. \n\n **Release notes:** \n{}"
.format(last_version, release_notes), title='Infrarize')
return
# Begin update
files = data['updater']['files']
has_errors = False
for file in files:
try:
source = REMOTE_BASE_URL.format(branch) + file
dest = os.path.join(COMPONENT_ABS_DIR, file)
os.makedirs(os.path.dirname(dest), exist_ok=True)
await Helper.downloader(source, dest)
except Exception:
has_errors = True
_LOGGER.error("Error updating %s. Please update the file manually.", file)
if has_errors:
hass.components.persistent_notification.async_create(
"There was an error updating one or more files of Infrarize. "
"Please check the logs for more information.", title='Infrarize')
else:
hass.components.persistent_notification.async_create(
"Successfully updated to {}. Please restart Home Assistant."
.format(last_version), title='Infrarize')
except Exception:
_LOGGER.error("An error occurred while checking for updates.")
class Helper():
@staticmethod
async def downloader(source, dest):
async with aiohttp.ClientSession() as session:
async with session.get(source) as response:
if response.status == 200:
async with aiofiles.open(dest, mode='wb') as f:
await f.write(await response.read())
else:
raise Exception("File not found")
@staticmethod
def pronto2lirc(pronto):
codes = [int(binascii.hexlify(pronto[i:i+2]), 16) for i in range(0, len(pronto), 2)]
if codes[0]:
raise ValueError("Pronto code should start with 0000")
if len(codes) != 4 + 2 * (codes[2] + codes[3]):
raise ValueError("Number of pulse widths does not match the preamble")
frequency = 1 / (codes[1] * 0.241246)
return [int(round(code / frequency)) for code in codes[4:]]
@staticmethod
def lirc2broadlink(pulses):
array = bytearray()
for pulse in pulses:
pulse = int(pulse * 269 / 8192)
if pulse < 256:
array += bytearray(struct.pack('>B', pulse))
else:
array += bytearray([0x00])
array += bytearray(struct.pack('>H', pulse))
packet = bytearray([0x26, 0x00])
packet += bytearray(struct.pack('<H', len(array)))
packet += array
packet += bytearray([0x0d, 0x05])
# Add 0s to make ultimate packet size a multiple of 16 for 128-bit AES encryption.
remainder = (len(packet) + 4) % 16
if remainder:
packet += bytearray(16 - remainder)
return packet