├── .gitignore ├── .storage └── lovelace ├── README.md ├── automations.yaml ├── camera.yaml ├── configuration.yaml ├── custom_components ├── __pycache__ │ └── custom_updater.cpython-37.pyc ├── alexa_media │ ├── __init__.py │ ├── __pycache__ │ │ ├── __init__.cpython-37.pyc │ │ ├── alarm_control_panel.cpython-37.pyc │ │ ├── const.cpython-37.pyc │ │ ├── media_player.cpython-37.pyc │ │ └── notify.cpython-37.pyc │ ├── alarm_control_panel.py │ ├── const.py │ ├── manifest.json │ ├── media_player.py │ ├── notify.py │ └── services.yaml ├── custom_updater.py.bak ├── customizer │ ├── __init__.py │ ├── __pycache__ │ │ └── __init__.cpython-37.pyc │ └── services.yaml ├── hacs │ ├── __init__.py │ ├── __pycache__ │ │ ├── __init__.cpython-37.pyc │ │ ├── api.cpython-37.pyc │ │ ├── const.cpython-37.pyc │ │ ├── http.cpython-37.pyc │ │ └── sensor.cpython-37.pyc │ ├── aiogithub │ │ ├── __init__.py │ │ ├── __pycache__ │ │ │ ├── __init__.cpython-37.pyc │ │ │ ├── aiogithub.cpython-37.pyc │ │ │ ├── aiogithubrepository.cpython-37.pyc │ │ │ ├── aiogithubrepositorycontent.cpython-37.pyc │ │ │ ├── aiogithubrepositoryrelease.cpython-37.pyc │ │ │ ├── const.cpython-37.pyc │ │ │ └── exceptions.cpython-37.pyc │ │ ├── aiogithub.py │ │ ├── aiogithubrepository.py │ │ ├── aiogithubrepositorycontent.py │ │ ├── aiogithubrepositoryrelease.py │ │ ├── const.py │ │ └── exceptions.py │ ├── api.py │ ├── const.py │ ├── frontend │ │ ├── __init__.py │ │ ├── __pycache__ │ │ │ └── __init__.cpython-37.pyc │ │ ├── elements │ │ │ ├── all.min.css.gz │ │ │ ├── hacs.css │ │ │ ├── hacs.js │ │ │ ├── materialize.min.css.gz │ │ │ ├── materialize.min.js.gz │ │ │ └── webfonts │ │ │ │ ├── fa-brands-400.eot.gz │ │ │ │ ├── fa-brands-400.svg.gz │ │ │ │ ├── fa-brands-400.ttf.gz │ │ │ │ ├── fa-brands-400.woff.gz │ │ │ │ ├── fa-brands-400.woff2.gz │ │ │ │ ├── fa-regular-400.eot.gz │ │ │ │ ├── fa-regular-400.svg.gz │ │ │ │ ├── fa-regular-400.ttf.gz │ │ │ │ ├── fa-regular-400.woff.gz │ │ │ │ ├── fa-regular-400.woff2.gz │ │ │ │ ├── fa-solid-900.eot.gz │ │ │ │ ├── fa-solid-900.svg.gz │ │ │ │ ├── fa-solid-900.ttf.gz │ │ │ │ ├── fa-solid-900.woff.gz │ │ │ │ └── fa-solid-900.woff2.gz │ │ └── templates │ │ │ ├── base.html │ │ │ ├── error.html │ │ │ ├── grid.html │ │ │ ├── message.html │ │ │ ├── modal │ │ │ ├── upgrade_all.html │ │ │ └── wrong_ha_version.html │ │ │ ├── overviews.html │ │ │ ├── repository.html │ │ │ ├── repository │ │ │ ├── buttons.html │ │ │ ├── menu.html │ │ │ ├── note.html │ │ │ └── versionselect.html │ │ │ ├── settings.html │ │ │ ├── settings │ │ │ ├── buttons.html │ │ │ ├── custom_repositories.html │ │ │ ├── dev │ │ │ │ ├── remove_new_flag.html │ │ │ │ ├── repositories.html │ │ │ │ ├── repository.html │ │ │ │ ├── set_ha_version.html │ │ │ │ └── token.html │ │ │ ├── developer.html │ │ │ ├── hacs_info.html │ │ │ └── hidden_repositories.html │ │ │ └── table.html │ ├── hacsbase │ │ ├── __init__.py │ │ ├── __pycache__ │ │ │ ├── __init__.cpython-37.pyc │ │ │ ├── configuration.cpython-37.pyc │ │ │ ├── const.cpython-37.pyc │ │ │ ├── data.cpython-37.pyc │ │ │ ├── exceptions.cpython-37.pyc │ │ │ └── migration.cpython-37.pyc │ │ ├── configuration.py │ │ ├── const.py │ │ ├── data.py │ │ ├── exceptions.py │ │ └── migration.py │ ├── handler │ │ ├── __init__.py │ │ ├── __pycache__ │ │ │ ├── __init__.cpython-37.pyc │ │ │ ├── download.cpython-37.pyc │ │ │ ├── logger.cpython-37.pyc │ │ │ └── template.cpython-37.pyc │ │ ├── download.py │ │ ├── logger.py │ │ └── template.py │ ├── http.py │ ├── manifest.json │ ├── repositories │ │ ├── __init__.py │ │ ├── __pycache__ │ │ │ ├── __init__.cpython-37.pyc │ │ │ ├── hacsrepositoryappdaemon.cpython-37.pyc │ │ │ ├── hacsrepositorybase.cpython-37.pyc │ │ │ ├── hacsrepositorybaseplugin.cpython-37.pyc │ │ │ ├── hacsrepositoryintegration.cpython-37.pyc │ │ │ ├── hacsrepositorypythonscript.cpython-37.pyc │ │ │ ├── hacsrepositorytheme.cpython-37.pyc │ │ │ ├── repositoryinformation.cpython-37.pyc │ │ │ └── repositoryinformationview.cpython-37.pyc │ │ ├── hacsrepositoryappdaemon.py │ │ ├── hacsrepositorybase.py │ │ ├── hacsrepositorybaseplugin.py │ │ ├── hacsrepositoryintegration.py │ │ ├── hacsrepositorypythonscript.py │ │ ├── hacsrepositorytheme.py │ │ ├── repositoryinformation.py │ │ └── repositoryinformationview.py │ ├── sensor.py │ └── services.yaml └── media_player │ ├── alexa.py │ └── ps4.py ├── customize.yaml ├── device_tracker.yaml ├── floorplan └── floorplan.jpg ├── groups.yaml ├── images ├── 01_Lovelace.png ├── 02_Data.png ├── 03_RPi.png ├── 04_Home.png ├── 05_Bathroom.png ├── 06_Kitchen.png ├── 07_Bedroom.png ├── Lovelace.gif ├── Phone.png ├── Phone_01.png └── Phone_02.png ├── media_player.yaml ├── remote.yaml ├── scripts.yaml ├── sensor.yaml ├── switch.yaml ├── themes.yaml ├── ui_lovelace.yaml ├── www ├── Lovelace │ ├── floorplan10.png │ ├── floorplan7.png │ ├── floorplan_grey.png │ ├── floorplan_grey2.png │ ├── freezer_closed2.png │ ├── freezer_open2.png │ ├── fridge_closed2.png │ ├── fridge_open2.png │ ├── tree_off.png │ ├── tree_on.png │ └── unlock.png ├── Mobile │ ├── Bathroom.jpg │ ├── Bedroom.jpg │ ├── Entrance.jpg │ ├── Kitchen.jpg │ ├── Living_Room.jpg │ └── Living_Room_2.jpg ├── community │ ├── calendar-card │ │ ├── calendar-card.js │ │ ├── calendar-card.js.gz │ │ └── calendar-card.js.map │ ├── compact-custom-header │ │ ├── compact-custom-header-editor.js │ │ ├── compact-custom-header-editor.js.gz │ │ ├── compact-custom-header.js │ │ └── compact-custom-header.js.gz │ ├── mini-graph-card │ │ ├── mini-graph-card.js │ │ └── mini-graph-card.js.gz │ ├── mini-media-player │ │ ├── mini-media-player.js │ │ └── mini-media-player.js.gz │ ├── radial-menu │ │ ├── radial-menu.js │ │ └── radial-menu.js.gz │ └── simple-thermostat │ │ ├── simple-thermostat.js │ │ └── simple-thermostat.js.gz └── custom-cards │ ├── calendar-card.js │ ├── mini-graph-card-bundle.js │ ├── mini-media-player-bundle.js │ ├── plan-coordinates.js │ ├── radial-menu.js │ └── simple-thermostat.js └── zone.yaml /.gitignore: -------------------------------------------------------------------------------- 1 | # Example .gitignore file for your config dir. 2 | # A * ensures that everything will be ignored. 3 | * 4 | 5 | # You can whitelist files/folders with !, these will not be ignored. 6 | !*.yaml 7 | !.gitignore 8 | !*.md 9 | !floorplan 10 | !custom-components/ 11 | !custom-components/* 12 | !www 13 | !www/* 14 | !www/custom-cards/ 15 | !www/custom-cards/* 16 | !scripts 17 | !*.jpg 18 | !*.svc 19 | !*.png 20 | !*.gif 21 | !.storage/lovelace 22 | !images/ 23 | !images/* 24 | 25 | # Ignore folders. 26 | .storage 27 | .cloud 28 | .google.token 29 | 30 | # Ensure these YAML files are ignored, otherwise your secret data/credentials will leak. 31 | google_calendars.yaml 32 | ip_bans.yaml 33 | secrets.yaml 34 | known_devices.yaml 35 | node-red/flows_cred.json -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Lovelace GIF](/images/Lovelace.gif) 2 | 3 | 4 | 5 | ![Lovelace UI 1](/images/01_Lovelace.png) 6 | 7 | ![Lovelace UI 2](/images/02_Data.png) 8 | 9 | ![Lovelace UI 3](/images/03_RPi.png) 10 | 11 | ![Lovelace UI 4](/images/04_Home.png) 12 | 13 | ![Lovelace UI 5](/images/05_Bathroom.png) 14 | 15 | ![Lovelace UI 6](/images/06_Kitchen.png) 16 | 17 | ![Lovelace UI 7](/images/07_Bedroom.png) 18 | -------------------------------------------------------------------------------- /automations.yaml: -------------------------------------------------------------------------------- 1 | ## Notify of High Disk Use - credit to @sauloonzem thank you 2 | - alias: Disk Use Alarm 3 | initial_state: 'on' 4 | trigger: 5 | platform: numeric_state 6 | entity_id: sensor.disk_use 7 | above: 12 8 | action: 9 | - service: nnotify.mobile_app_mikes_iphone_xs 10 | data: 11 | title: 'HA Server' 12 | message: 'Warning - HA Server Disk Use is {{states.sensor.disk_use.state}}GB' 13 | 14 | ## Notify of High CPU Usage - credit to @sauloonzem thank you 15 | - alias: CPU Use Alarm 16 | initial_state: 'on' 17 | trigger: 18 | platform: numeric_state 19 | entity_id: sensor.processor_use 20 | above: 85 21 | action: 22 | - service: notify.mobile_app_mikes_iphone_xs 23 | data: 24 | title: 'HA Server' 25 | message: 'Warning - HA Server Processor {{states.sensor.processor_use.state}}%' 26 | 27 | ## Notify if CPU Temperature is High - credit to @sauloonzem thank you 28 | - alias: CPU Temp Alarm 29 | initial_state: 'on' 30 | trigger: 31 | platform: numeric_state 32 | entity_id: sensor.cpu_temperature 33 | above: 185 34 | action: 35 | - service: notify.mobile_app_mikes_iphone_xs 36 | data: 37 | title: 'HA Server' 38 | message: 'Warning - HA Server CPU Temp is {{states.sensor.cpu_temperature.state}}f' 39 | 40 | # Set Theme at startup 41 | - alias: 'Set theme at startup' 42 | initial_state: 'on' 43 | trigger: 44 | - platform: homeassistant 45 | event: start 46 | action: 47 | service: frontend.set_theme 48 | data: 49 | name: coned_miro 50 | -------------------------------------------------------------------------------- /camera.yaml: -------------------------------------------------------------------------------- 1 | - platform: ffmpeg 2 | name: rtsp_1 3 | input: !secret wyze_cam_1 -------------------------------------------------------------------------------- /configuration.yaml: -------------------------------------------------------------------------------- 1 | homeassistant: 2 | name: Home 3 | latitude: !secret home_lat 4 | longitude: !secret home_long 5 | elevation: 0 6 | unit_system: imperial 7 | time_zone: America/New_York 8 | customize: !include customize.yaml 9 | 10 | frontend: 11 | themes: !include themes.yaml 12 | extra_html_url: 13 | - /local/custom_ui/state-card-custom-ui.html 14 | extra_html_url_es5: 15 | - /local/custom_ui/state-card-custom-ui-es5.html 16 | 17 | config: 18 | 19 | mobile_app: 20 | 21 | fastdotcom: 22 | 23 | discovery: 24 | ignore: 25 | - philips_hue 26 | 27 | alexa_media: 28 | accounts: 29 | - email: !secret amazon_email 30 | password: !secret amazon_pass 31 | url: amazon.com 32 | 33 | google: 34 | client_id: !secret google_id 35 | client_secret: !secret google_secret 36 | 37 | life360: 38 | accounts: 39 | - username: !secret life360_user 40 | password: !secret life360_pass 41 | 42 | hacs: 43 | token: !secret hacs_gh_token 44 | 45 | updater: 46 | 47 | history: 48 | 49 | ios: 50 | 51 | logger: 52 | 53 | stream: 54 | 55 | homekit: 56 | filter: 57 | exclude_domains: [calendar] 58 | 59 | system_health: 60 | 61 | panel_iframe: 62 | ide: 63 | title: "IDE" 64 | icon: mdi:code-braces 65 | url: https://michaeldvinci.duckdns.org:8321 66 | pihole: 67 | title: "Pi-hole" 68 | icon: mdi:block-helper 69 | url: https://michaeldvinci.duckdns.org:4865 70 | 71 | http: 72 | ip_ban_enabled: False 73 | ssl_certificate: /ssl/fullchain.pem 74 | ssl_key: /ssl/privkey.pem 75 | base_url: https://michaeldvinci.duckdns.org 76 | 77 | automation: !include automations.yaml 78 | camera: !include camera.yaml 79 | device_tracker: !include device_tracker.yaml 80 | group: !include groups.yaml 81 | media_player: !include media_player.yaml 82 | remote: !include remote.yaml 83 | script: !include scripts.yaml 84 | sensor: !include sensor.yaml 85 | switch: !include switch.yaml 86 | zone: !include zone.yaml 87 | 88 | recorder: 89 | purge_keep_days: 1 90 | purge_interval: 1 91 | 92 | -------------------------------------------------------------------------------- /custom_components/__pycache__/custom_updater.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaeldvinci/Home-AssistantConfig/b45a163344af5e6dd48c95bc063c2645f4386b91/custom_components/__pycache__/custom_updater.cpython-37.pyc -------------------------------------------------------------------------------- /custom_components/alexa_media/__pycache__/__init__.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaeldvinci/Home-AssistantConfig/b45a163344af5e6dd48c95bc063c2645f4386b91/custom_components/alexa_media/__pycache__/__init__.cpython-37.pyc -------------------------------------------------------------------------------- /custom_components/alexa_media/__pycache__/alarm_control_panel.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaeldvinci/Home-AssistantConfig/b45a163344af5e6dd48c95bc063c2645f4386b91/custom_components/alexa_media/__pycache__/alarm_control_panel.cpython-37.pyc -------------------------------------------------------------------------------- /custom_components/alexa_media/__pycache__/const.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaeldvinci/Home-AssistantConfig/b45a163344af5e6dd48c95bc063c2645f4386b91/custom_components/alexa_media/__pycache__/const.cpython-37.pyc -------------------------------------------------------------------------------- /custom_components/alexa_media/__pycache__/media_player.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaeldvinci/Home-AssistantConfig/b45a163344af5e6dd48c95bc063c2645f4386b91/custom_components/alexa_media/__pycache__/media_player.cpython-37.pyc -------------------------------------------------------------------------------- /custom_components/alexa_media/__pycache__/notify.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaeldvinci/Home-AssistantConfig/b45a163344af5e6dd48c95bc063c2645f4386b91/custom_components/alexa_media/__pycache__/notify.cpython-37.pyc -------------------------------------------------------------------------------- /custom_components/alexa_media/alarm_control_panel.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # SPDX-License-Identifier: Apache-2.0 4 | """ 5 | Alexa Devices Alarm Control Panel using Guard Mode. 6 | 7 | For more details about this platform, please refer to the documentation at 8 | https://community.home-assistant.io/t/echo-devices-alexa-as-media-player-testers-needed/58639 9 | """ 10 | import logging 11 | from typing import List # noqa pylint: disable=unused-import 12 | 13 | from homeassistant import util 14 | from homeassistant.components.alarm_control_panel import AlarmControlPanel 15 | from homeassistant.const import (STATE_ALARM_ARMED_AWAY, 16 | STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED) 17 | from homeassistant.exceptions import HomeAssistantError 18 | 19 | from . import DATA_ALEXAMEDIA 20 | from . import DOMAIN as ALEXA_DOMAIN 21 | from . import MIN_TIME_BETWEEN_FORCED_SCANS, MIN_TIME_BETWEEN_SCANS, hide_email 22 | 23 | _LOGGER = logging.getLogger(__name__) 24 | 25 | DEPENDENCIES = [ALEXA_DOMAIN] 26 | 27 | 28 | def setup_platform(hass, config, add_devices_callback, 29 | discovery_info=None): 30 | """Set up the Alexa alarm control panel platform.""" 31 | devices = [] # type: List[AlexaAlarmControlPanel] 32 | for account, account_dict in (hass.data[DATA_ALEXAMEDIA] 33 | ['accounts'].items()): 34 | alexa_client = AlexaAlarmControlPanel(account_dict['login_obj'], 35 | hass) \ 36 | # type: AlexaAlarmControlPanel 37 | if not (alexa_client and alexa_client.unique_id): 38 | _LOGGER.debug("%s: Skipping creation of uninitialized device: %s", 39 | account, 40 | alexa_client) 41 | continue 42 | devices.append(alexa_client) 43 | (hass.data[DATA_ALEXAMEDIA] 44 | ['accounts'] 45 | [account] 46 | ['entities'] 47 | ['alarm_control_panel']) = alexa_client 48 | if devices: 49 | _LOGGER.debug("Adding %s", devices) 50 | try: 51 | add_devices_callback(devices, True) 52 | except HomeAssistantError as exception_: 53 | message = exception_.message # type: str 54 | if message.startswith("Entity id already exists"): 55 | _LOGGER.debug("Device already added: %s", 56 | message) 57 | else: 58 | _LOGGER.debug("Unable to add devices: %s : %s", 59 | devices, 60 | message) 61 | return True 62 | 63 | 64 | class AlexaAlarmControlPanel(AlarmControlPanel): 65 | """Implementation of Alexa Media Player alarm control panel.""" 66 | 67 | def __init__(self, login, hass): 68 | # pylint: disable=unexpected-keyword-arg 69 | """Initialize the Alexa device.""" 70 | from alexapy import AlexaAPI 71 | # Class info 72 | self._login = login 73 | self.alexa_api = AlexaAPI(self, login) 74 | self.alexa_api_session = login.session 75 | self.account = hide_email(login.email) 76 | self.hass = hass 77 | 78 | # Guard info 79 | self._appliance_id = None 80 | self._guard_entity_id = None 81 | self._friendly_name = "Alexa Guard" 82 | self._state = None 83 | self._should_poll = False 84 | self._attrs = {} 85 | 86 | data = self.alexa_api.get_guard_details(self._login) 87 | try: 88 | guard_dict = (data['locationDetails'] 89 | ['locationDetails']['Default_Location'] 90 | ['amazonBridgeDetails']['amazonBridgeDetails'] 91 | ['LambdaBridge_AAA/OnGuardSmartHomeBridgeService'] 92 | ['applianceDetails']['applianceDetails']) 93 | except KeyError: 94 | guard_dict = {} 95 | for key, value in guard_dict.items(): 96 | if value['modelName'] == "REDROCK_GUARD_PANEL": 97 | self._appliance_id = value['applianceId'] 98 | self._guard_entity_id = value['entityId'] 99 | self._friendly_name += " " + self._appliance_id[-5:] 100 | _LOGGER.debug("%s: Discovered %s: %s %s", 101 | self.account, 102 | self._friendly_name, 103 | self._appliance_id, 104 | self._guard_entity_id) 105 | if not self._appliance_id: 106 | _LOGGER.debug("%s: No Alexa Guard entity found", self.account) 107 | return None 108 | # Register event handler on bus 109 | hass.bus.listen(('{}_{}'.format(ALEXA_DOMAIN, 110 | hide_email(login.email)))[0:32], 111 | self._handle_event) 112 | self.refresh(no_throttle=True) 113 | 114 | def _handle_event(self, event): 115 | """Handle websocket events. 116 | 117 | Used instead of polling. 118 | """ 119 | self.refresh() 120 | 121 | @util.Throttle(MIN_TIME_BETWEEN_SCANS, MIN_TIME_BETWEEN_FORCED_SCANS) 122 | def refresh(self): 123 | """Update Guard state.""" 124 | import json 125 | _LOGGER.debug("%s: Refreshing %s", self.account, self.name) 126 | state = None 127 | state_json = self.alexa_api.get_guard_state(self._login, 128 | self._appliance_id) 129 | # _LOGGER.debug("%s: state_json %s", self.account, state_json) 130 | if (state_json and 'deviceStates' in state_json 131 | and state_json['deviceStates']): 132 | cap = state_json['deviceStates'][0]['capabilityStates'] 133 | # _LOGGER.debug("%s: cap %s", self.account, cap) 134 | for item_json in cap: 135 | item = json.loads(item_json) 136 | # _LOGGER.debug("%s: item %s", self.account, item) 137 | if item['name'] == 'armState': 138 | state = item['value'] 139 | # _LOGGER.debug("%s: state %s", self.account, state) 140 | elif state_json['errors']: 141 | _LOGGER.debug("%s: Error refreshing alarm_control_panel %s: %s", 142 | self.account, 143 | self.name, 144 | json.dumps(state_json['errors']) if state_json 145 | else None) 146 | if state is None: 147 | return 148 | if state == "ARMED_AWAY": 149 | self._state = STATE_ALARM_ARMED_AWAY 150 | elif state == "ARMED_STAY": 151 | self._state = STATE_ALARM_DISARMED 152 | else: 153 | self._state = STATE_ALARM_DISARMED 154 | _LOGGER.debug("%s: Alarm State: %s", self.account, self.state) 155 | 156 | def alarm_disarm(self, code=None): 157 | # pylint: disable=unexpected-keyword-arg 158 | """Send disarm command. 159 | 160 | We use the arm_home state as Alexa does not have disarm state. 161 | """ 162 | self.alarm_arm_home() 163 | self.schedule_update_ha_state() 164 | 165 | def alarm_arm_home(self, code=None): 166 | """Send arm home command.""" 167 | self.alexa_api.set_guard_state(self._login, 168 | self._guard_entity_id, 169 | "ARMED_STAY") 170 | self.refresh(no_throttle=True) 171 | self.schedule_update_ha_state() 172 | 173 | def alarm_arm_away(self, code=None): 174 | """Send arm away command.""" 175 | # pylint: disable=unexpected-keyword-arg 176 | self.alexa_api.set_guard_state(self._login, 177 | self._guard_entity_id, 178 | "ARMED_AWAY") 179 | self.refresh(no_throttle=True) 180 | self.schedule_update_ha_state() 181 | 182 | @property 183 | def unique_id(self): 184 | """Return the unique ID.""" 185 | return self._guard_entity_id 186 | 187 | @property 188 | def name(self): 189 | """Return the name of the device.""" 190 | return self._friendly_name 191 | 192 | @property 193 | def state(self): 194 | """Return the state of the device.""" 195 | return self._state 196 | 197 | @property 198 | def device_state_attributes(self): 199 | """Return the state attributes.""" 200 | return self._attrs 201 | 202 | @property 203 | def should_poll(self): 204 | """Return the polling state.""" 205 | return self._should_poll or not (self.hass.data[DATA_ALEXAMEDIA] 206 | ['accounts'][self._login.email] 207 | ['websocket']) 208 | -------------------------------------------------------------------------------- /custom_components/alexa_media/const.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # SPDX-License-Identifier: Apache-2.0 4 | """ 5 | Support to interface with Alexa Devices. 6 | 7 | For more details about this platform, please refer to the documentation at 8 | https://community.home-assistant.io/t/echo-devices-alexa-as-media-player-testers-needed/58639 9 | """ 10 | from datetime import timedelta 11 | 12 | __version__ = '1.3.1' 13 | PROJECT_URL = "https://github.com/keatontaylor/alexa_media_player/" 14 | ISSUE_URL = "{}issues".format(PROJECT_URL) 15 | 16 | DOMAIN = 'alexa_media' 17 | DATA_ALEXAMEDIA = 'alexa_media' 18 | 19 | PLAY_SCAN_INTERVAL = 20 20 | SCAN_INTERVAL = timedelta(seconds=60) 21 | MIN_TIME_BETWEEN_SCANS = SCAN_INTERVAL 22 | MIN_TIME_BETWEEN_FORCED_SCANS = timedelta(seconds=1) 23 | 24 | ALEXA_COMPONENTS = [ 25 | 'media_player', 26 | 'notify', 27 | 'alarm_control_panel' 28 | ] 29 | 30 | CONF_ACCOUNTS = 'accounts' 31 | CONF_DEBUG = 'debug' 32 | CONF_INCLUDE_DEVICES = 'include_devices' 33 | CONF_EXCLUDE_DEVICES = 'exclude_devices' 34 | SERVICE_UPDATE_LAST_CALLED = 'update_last_called' 35 | ATTR_MESSAGE = 'message' 36 | ATTR_EMAIL = 'email' 37 | 38 | STARTUP = """ 39 | ------------------------------------------------------------------- 40 | {} 41 | Version: {} 42 | This is a custom component 43 | If you have any issues with this you need to open an issue here: 44 | {} 45 | ------------------------------------------------------------------- 46 | """.format(DOMAIN, __version__, ISSUE_URL) 47 | -------------------------------------------------------------------------------- /custom_components/alexa_media/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "domain": "alexa_media", 3 | "name": "Alexa Media Player", 4 | "documentation": "https://github.com/keatontaylor/alexa_media_player/wiki", 5 | "dependencies": [], 6 | "codeowners": ["@keatontaylor", "@alandtse"], 7 | "requirements": ["alexapy==0.7.0"] 8 | } 9 | -------------------------------------------------------------------------------- /custom_components/alexa_media/notify.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # SPDX-License-Identifier: Apache-2.0 4 | """ 5 | Alexa Devices notification service. 6 | 7 | For more details about this platform, please refer to the documentation at 8 | https://community.home-assistant.io/t/echo-devices-alexa-as-media-player-testers-needed/58639 9 | """ 10 | import logging 11 | 12 | from homeassistant.components.notify import ( 13 | ATTR_DATA, ATTR_TARGET, ATTR_TITLE, ATTR_TITLE_DEFAULT, 14 | BaseNotificationService 15 | ) 16 | 17 | from . import ( 18 | DOMAIN as ALEXA_DOMAIN, 19 | DATA_ALEXAMEDIA, 20 | hide_email, hide_serial) 21 | 22 | _LOGGER = logging.getLogger(__name__) 23 | 24 | DEPENDENCIES = [ALEXA_DOMAIN] 25 | 26 | EVENT_NOTIFY = "notify" 27 | 28 | 29 | def get_service(hass, config, discovery_info=None): 30 | # pylint: disable=unused-argument 31 | """Get the demo notification service.""" 32 | return AlexaNotificationService(hass) 33 | 34 | 35 | class AlexaNotificationService(BaseNotificationService): 36 | """Implement Alexa Media Player notification service.""" 37 | 38 | def __init__(self, hass): 39 | """Initialize the service.""" 40 | self.hass = hass 41 | 42 | def convert(self, names, type_="entities", filter_matches=False): 43 | """Return a list of converted Alexa devices based on names. 44 | 45 | Names may be matched either by serialNumber, accountName, or 46 | Homeassistant entity_id and can return any of the above plus entities 47 | 48 | Parameters 49 | ---------- 50 | names : list(string) 51 | A list of names to convert 52 | type : string 53 | The type to return entities, entity_ids, serialnumbers, names 54 | filter_matches : bool 55 | Whether non-matching items are removed from the returned list. 56 | 57 | Returns 58 | ------- 59 | list(string) 60 | List of home assistant entity_ids 61 | 62 | """ 63 | devices = [] 64 | if isinstance(names, str): 65 | names = [names] 66 | for item in names: 67 | matched = False 68 | for alexa in self.devices: 69 | _LOGGER.debug("Testing item: %s against (%s, %s, %s, %s)", 70 | item, 71 | alexa, 72 | alexa.name, 73 | hide_serial(alexa.unique_id), 74 | alexa.entity_id) 75 | if item in (alexa, alexa.name, alexa.unique_id, 76 | alexa.entity_id): 77 | if type_ == "entities": 78 | converted = alexa 79 | elif type_ == "serialnumbers": 80 | converted = alexa.unique_id 81 | elif type_ == "names": 82 | converted = alexa.name 83 | elif type_ == "entity_ids": 84 | converted = alexa.entity_id 85 | devices.append(converted) 86 | matched = True 87 | _LOGGER.debug("Converting: %s to (%s): %s", 88 | item, 89 | type_, 90 | converted) 91 | if not filter_matches and not matched: 92 | devices.append(item) 93 | return devices 94 | 95 | @property 96 | def targets(self): 97 | """Return a dictionary of Alexa devices.""" 98 | devices = {} 99 | for account, account_dict in (self.hass.data[DATA_ALEXAMEDIA] 100 | ['accounts'].items()): 101 | for serial, alexa in (account_dict 102 | ['devices']['media_player'].items()): 103 | devices[alexa['accountName']] = serial 104 | return devices 105 | 106 | @property 107 | def devices(self): 108 | """Return a dictionary of Alexa devices.""" 109 | devices = [] 110 | if ('accounts' not in self.hass.data[DATA_ALEXAMEDIA] and 111 | not self.hass.data[DATA_ALEXAMEDIA]['accounts'].items()): 112 | return devices 113 | for _, account_dict in (self.hass.data[DATA_ALEXAMEDIA] 114 | ['accounts'].items()): 115 | devices = devices + list(account_dict 116 | ['entities']['media_player'].values()) 117 | return devices 118 | 119 | def send_message(self, message="", **kwargs): 120 | """Send a message to a Alexa device.""" 121 | _LOGGER.debug("Message: %s, kwargs: %s", 122 | message, 123 | kwargs) 124 | kwargs['message'] = message 125 | targets = kwargs.get(ATTR_TARGET) 126 | title = (kwargs.get(ATTR_TITLE) if ATTR_TITLE in kwargs 127 | else ATTR_TITLE_DEFAULT) 128 | data = kwargs.get(ATTR_DATA) 129 | if isinstance(targets, str): 130 | targets = [targets] 131 | entities = self.convert(targets, type_="entities") 132 | try: 133 | entities.extend(self.hass.components.group.expand_entity_ids( 134 | entities)) 135 | except ValueError: 136 | _LOGGER.debug("Invalid Home Assistant entity in %s", entities) 137 | if data['type'] == "tts": 138 | targets = self.convert(entities, type_="entities", 139 | filter_matches=True) 140 | _LOGGER.debug("TTS entities: %s", targets) 141 | for alexa in targets: 142 | _LOGGER.debug("TTS by %s : %s", alexa, message) 143 | alexa.send_tts(message) 144 | elif data['type'] == "announce": 145 | targets = self.convert(entities, type_="serialnumbers", 146 | filter_matches=True) 147 | _LOGGER.debug("Announce targets: %s entities: %s", 148 | list(map(hide_serial, targets)), 149 | entities) 150 | for account, account_dict in (self.hass.data[DATA_ALEXAMEDIA] 151 | ['accounts'].items()): 152 | for alexa in (account_dict['entities'] 153 | ['media_player'].values()): 154 | if alexa.unique_id in targets and alexa.available: 155 | _LOGGER.debug(("%s: Announce by %s to " 156 | "targets: %s: %s"), 157 | hide_email(account), 158 | alexa, 159 | list(map(hide_serial, targets)), 160 | message) 161 | alexa.send_announcement(message, 162 | targets=targets, 163 | title=title, 164 | method=(data['method'] if 165 | 'method' in data 166 | else 'all')) 167 | break 168 | elif data['type'] == "push": 169 | targets = self.convert(entities, type_="entities", 170 | filter_matches=True) 171 | for alexa in targets: 172 | _LOGGER.debug("Push by %s : %s %s", alexa, title, message) 173 | alexa.send_mobilepush(message, title=title) 174 | -------------------------------------------------------------------------------- /custom_components/alexa_media/services.yaml: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: Apache-2.0 2 | update_last_called: 3 | # Description of the service 4 | description: Forces update of last_called echo device for each Alexa account. 5 | # Different fields that your service accepts 6 | fields: 7 | # Key of the field 8 | email: 9 | # Description of the field 10 | description: List of Alexa accounts to update. If empty, will update all known accounts 11 | # Example value that can be passed for this field 12 | example: 'my_email@alexa.com' 13 | -------------------------------------------------------------------------------- /custom_components/customizer/__init__.py: -------------------------------------------------------------------------------- 1 | """Customizer component. Bring extra customize features to home-assistant.""" 2 | 3 | import asyncio 4 | import logging 5 | import os 6 | 7 | import homeassistant.components.frontend as frontend 8 | import homeassistant.helpers.config_validation as cv 9 | from homeassistant.config import load_yaml_config_file, DATA_CUSTOMIZE 10 | from homeassistant.core import callback 11 | 12 | from homeassistant.helpers.entity import Entity 13 | from homeassistant.helpers.entity_component import EntityComponent 14 | from homeassistant.const import CONF_ENTITY_ID, MINOR_VERSION 15 | 16 | import voluptuous as vol 17 | 18 | _LOGGER = logging.getLogger(__name__) 19 | 20 | DOMAIN = 'customizer' 21 | DEPENDENCIES = ['frontend'] 22 | 23 | CONF_CUSTOM_UI = 'custom_ui' 24 | 25 | LOCAL = 'local' 26 | HOSTED = 'hosted' 27 | DEBUG = 'debug' 28 | 29 | CONF_HIDE_ATTRIBUTES = 'hide_attributes' 30 | 31 | CONF_ATTRIBUTE = 'attribute' 32 | CONF_VALUE = 'value' 33 | CONF_COLUMNS = 'columns' 34 | 35 | SERVICE_SET_ATTRIBUTE = 'set_attribute' 36 | SERVICE_SET_ATTRIBUTE_SCHEMA = vol.Schema({ 37 | vol.Required(CONF_ENTITY_ID): cv.entity_id, 38 | vol.Required(CONF_ATTRIBUTE): cv.string, 39 | vol.Optional(CONF_VALUE): cv.match_all, 40 | }) 41 | 42 | CONFIG_SCHEMA = vol.Schema({ 43 | DOMAIN: vol.Schema({ 44 | vol.Optional(CONF_CUSTOM_UI): cv.string, 45 | vol.Optional(CONF_COLUMNS): [int], 46 | vol.Optional(CONF_HIDE_ATTRIBUTES): 47 | vol.All(cv.ensure_list, [cv.string]), 48 | }) 49 | }, extra=vol.ALLOW_EXTRA) 50 | 51 | 52 | @asyncio.coroutine 53 | def async_setup(hass, config): 54 | """Set up customizer.""" 55 | custom_ui = config[DOMAIN].get(CONF_CUSTOM_UI) 56 | if MINOR_VERSION < 53 and custom_ui is not None: 57 | _LOGGER.warning('%s is only supported from Home Assistant 0.53', 58 | CONF_CUSTOM_UI) 59 | elif custom_ui is not None: 60 | def add_extra_html_url(base_url): 61 | """Add extra url using version-dependent function.""" 62 | if MINOR_VERSION >= 59: 63 | frontend.add_extra_html_url( 64 | hass, '{}.html'.format(base_url), False) 65 | frontend.add_extra_html_url( 66 | hass, '{}-es5.html'.format(base_url), True) 67 | else: 68 | frontend.add_extra_html_url(hass, '{}.html'.format(base_url)) 69 | 70 | if custom_ui == LOCAL: 71 | add_extra_html_url('/local/custom_ui/state-card-custom-ui') 72 | elif custom_ui == HOSTED: 73 | add_extra_html_url( 74 | 'https://raw.githubusercontent.com/andrey-git/' 75 | 'home-assistant-custom-ui/master/state-card-custom-ui') 76 | elif custom_ui == DEBUG: 77 | add_extra_html_url( 78 | 'https://raw.githubusercontent.com/andrey-git/' 79 | 'home-assistant-custom-ui/master/' 80 | 'state-card-custom-ui-dbg') 81 | else: 82 | add_extra_html_url( 83 | 'https://github.com/andrey-git/home-assistant-custom-ui/' 84 | 'releases/download/{}/state-card-custom-ui' 85 | .format(custom_ui)) 86 | 87 | component = EntityComponent(_LOGGER, DOMAIN, hass) 88 | yield from component.async_add_entities([CustomizerEntity(config[DOMAIN])]) 89 | 90 | @callback 91 | def set_attribute(call): 92 | """Set attribute override.""" 93 | data = call.data 94 | entity_id = data[CONF_ENTITY_ID] 95 | attribute = data[CONF_ATTRIBUTE] 96 | value = data.get(CONF_VALUE) 97 | overrides = hass.data[DATA_CUSTOMIZE].get(entity_id) 98 | state = hass.states.get(entity_id) 99 | state_attributes = dict(state.attributes) 100 | if value is None: 101 | if attribute in overrides: 102 | overrides.pop(attribute) 103 | if attribute in state_attributes: 104 | state_attributes.pop(attribute) 105 | else: 106 | overrides[attribute] = value 107 | state_attributes[attribute] = value 108 | hass.states.async_set(entity_id, state.state, state_attributes) 109 | 110 | if MINOR_VERSION >= 61: 111 | hass.services.async_register(DOMAIN, SERVICE_SET_ATTRIBUTE, 112 | set_attribute, 113 | SERVICE_SET_ATTRIBUTE_SCHEMA) 114 | else: 115 | descriptions = yield from hass.async_add_job( 116 | load_yaml_config_file, os.path.join( 117 | os.path.dirname(__file__), 'services.yaml')) 118 | hass.services.async_register(DOMAIN, SERVICE_SET_ATTRIBUTE, 119 | set_attribute, 120 | descriptions[SERVICE_SET_ATTRIBUTE], 121 | SERVICE_SET_ATTRIBUTE_SCHEMA) 122 | 123 | return True 124 | 125 | 126 | class CustomizerEntity(Entity): 127 | """Customizer entity class.""" 128 | 129 | def __init__(self, config): 130 | """Constructor that parses the config.""" 131 | self.hide_attributes = config.get(CONF_HIDE_ATTRIBUTES) 132 | self.columns = config.get(CONF_COLUMNS) 133 | 134 | @property 135 | def hidden(self): 136 | """Don't show the entity.""" 137 | return True 138 | 139 | @property 140 | def name(self): 141 | """Singleton name.""" 142 | return DOMAIN 143 | 144 | @property 145 | def state_attributes(self): 146 | """Return the state attributes.""" 147 | result = {} 148 | if self.hide_attributes: 149 | result[CONF_HIDE_ATTRIBUTES] = self.hide_attributes 150 | if self.columns: 151 | result[CONF_COLUMNS] = self.columns 152 | return result 153 | -------------------------------------------------------------------------------- /custom_components/customizer/__pycache__/__init__.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaeldvinci/Home-AssistantConfig/b45a163344af5e6dd48c95bc063c2645f4386b91/custom_components/customizer/__pycache__/__init__.cpython-37.pyc -------------------------------------------------------------------------------- /custom_components/customizer/services.yaml: -------------------------------------------------------------------------------- 1 | set_attribute: 2 | description: > 3 | Set or clear persistent attribute of an entity overriding any value set in code. 4 | Note that calling homeassistant.reload_core_config will reset overrides to their yaml state. 5 | fields: 6 | entity_id: 7 | description: Entity ID to set the attribute on. 8 | example: light.patio 9 | attribute: 10 | description: Name of the attribute to set. 11 | example: friendly_name 12 | value: 13 | description: > 14 | (Optional) Value to set. Leave unspecified in order to clear set value. 15 | Note that when clearing attribute it will be empty (and not set-by-code) until next entity update. 16 | example: 'My light' 17 | -------------------------------------------------------------------------------- /custom_components/hacs/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Custom element manager for community created elements. 3 | 4 | For more details about this integration, please refer to the documentation at 5 | https://custom-components.github.io/hacs/ 6 | """ 7 | import logging 8 | import os.path 9 | import json 10 | import asyncio 11 | from datetime import datetime, timedelta 12 | from pkg_resources import parse_version 13 | import aiohttp 14 | 15 | import voluptuous as vol 16 | from homeassistant.const import EVENT_HOMEASSISTANT_START, __version__ as HAVERSION 17 | from homeassistant.helpers.aiohttp_client import async_create_clientsession 18 | import homeassistant.helpers.config_validation as cv 19 | from homeassistant.helpers import discovery 20 | 21 | from .const import ( 22 | CUSTOM_UPDATER_LOCATIONS, 23 | STARTUP, 24 | ISSUE_URL, 25 | CUSTOM_UPDATER_WARNING, 26 | NAME_LONG, 27 | NAME_SHORT, 28 | DOMAIN_DATA, 29 | ELEMENT_TYPES, 30 | VERSION, 31 | IFRAME, 32 | DOMAIN, 33 | ) 34 | 35 | 36 | # TODO: Remove this when minimum HA version is > 0.93 37 | REQUIREMENTS = ["aiofiles==0.4.0", "backoff==1.8.0", "packaging==19.0"] 38 | 39 | _LOGGER = logging.getLogger("custom_components.hacs") 40 | 41 | CONFIG_SCHEMA = vol.Schema( 42 | { 43 | DOMAIN: vol.Schema( 44 | { 45 | vol.Required("token"): cv.string, 46 | vol.Optional("sidepanel_title"): cv.string, 47 | vol.Optional("sidepanel_icon"): cv.string, 48 | vol.Optional("dev", default=False): cv.boolean, 49 | vol.Optional("appdaemon", default=False): cv.boolean, 50 | vol.Optional("python_script", default=False): cv.boolean, 51 | vol.Optional("theme", default=False): cv.boolean, 52 | } 53 | ) 54 | }, 55 | extra=vol.ALLOW_EXTRA, 56 | ) 57 | 58 | 59 | async def async_setup(hass, config): # pylint: disable=unused-argument 60 | """Set up this integration.""" 61 | from .aiogithub.exceptions import AIOGitHubAuthentication, AIOGitHubException, AIOGitHubRatelimit 62 | from .hacsbase import HacsBase as hacs 63 | 64 | _LOGGER.info(STARTUP) 65 | config_dir = hass.config.path() 66 | 67 | # Configure HACS 68 | try: 69 | await configure_hacs(hass, config[DOMAIN], config_dir) 70 | except AIOGitHubAuthentication as exception: 71 | _LOGGER.error(exception) 72 | return False 73 | except AIOGitHubRatelimit as exception: 74 | _LOGGER.warning(exception) 75 | except AIOGitHubException as exception: 76 | _LOGGER.warning(exception) 77 | 78 | # Check if custom_updater exists 79 | for location in CUSTOM_UPDATER_LOCATIONS: 80 | if os.path.exists(location.format(config_dir)): 81 | msg = CUSTOM_UPDATER_WARNING.format(location.format(config_dir)) 82 | _LOGGER.critical(msg) 83 | return False 84 | 85 | # Check if HA is the required version. 86 | if parse_version(HAVERSION) < parse_version("0.92.0"): 87 | _LOGGER.critical("You need HA version 92 or newer to use this integration.") 88 | return False 89 | 90 | # Add sensor 91 | hass.async_create_task( 92 | discovery.async_load_platform(hass, "sensor", DOMAIN, {}, config[DOMAIN]) 93 | ) 94 | 95 | # Setup startup tasks 96 | hass.bus.async_listen_once(EVENT_HOMEASSISTANT_START, hacs().startup_tasks()) 97 | 98 | await setup_frontend(hass, hacs) 99 | 100 | # Service registration 101 | async def service_hacs_install(call): 102 | """Install a repository.""" 103 | repository = str(call.data["repository"]) 104 | if repository not in hacs().store.repositories: 105 | _LOGGER.error("%s is not a konwn repository!", repository) 106 | return 107 | repository = hacs().store.repositories[repository] 108 | await repository.install() 109 | 110 | async def service_hacs_register(call): 111 | """register a repository.""" 112 | repository = call.data["repository"] 113 | repository_type = call.data["repository_type"] 114 | if await hacs().is_known_repository(repository): 115 | _LOGGER.error("%s is already a konwn repository!", repository) 116 | return 117 | await hacs().register_new_repository(repository_type, repository) 118 | 119 | hass.services.async_register("hacs", 'install', service_hacs_install) 120 | hass.services.async_register("hacs", 'register', service_hacs_register) 121 | 122 | # Mischief managed! 123 | return True 124 | 125 | 126 | async def configure_hacs(hass, configuration, hass_config_dir): 127 | """Configure HACS.""" 128 | from .aiogithub import AIOGitHub 129 | from .hacsbase import HacsBase as hacs 130 | from .hacsbase.configuration import HacsConfiguration 131 | from .hacsbase.data import HacsData 132 | from . import const as const 133 | from .hacsbase import const as hacsconst 134 | from .hacsbase.migration import HacsMigration 135 | #from .hacsbase.storage import HacsStorage 136 | 137 | hacs.config = HacsConfiguration(configuration) 138 | 139 | if hacs.config.appdaemon: 140 | ELEMENT_TYPES.append("appdaemon") 141 | if hacs.config.python_script: 142 | ELEMENT_TYPES.append("python_script") 143 | if hacs.config.theme: 144 | ELEMENT_TYPES.append("theme") 145 | 146 | # Print DEV warning 147 | if hacs.config.dev: 148 | _LOGGER.error( 149 | const.DEV_MODE 150 | ) 151 | hass.components.persistent_notification.create( 152 | title="HACS DEV MODE", 153 | message=const.DEV_MODE, 154 | notification_id="hacs_dev_mode" 155 | ) 156 | 157 | hacs.migration = HacsMigration() 158 | #hacs.storage = HacsStorage() 159 | 160 | hacs.aiogithub = AIOGitHub( 161 | hacs.config.token, hass.loop, async_create_clientsession(hass) 162 | ) 163 | 164 | hacs.hacs_github = await hacs.aiogithub.get_repo("custom-components/hacs") 165 | 166 | hacs.hass = hass 167 | hacs.const = const 168 | hacs.hacsconst = hacsconst 169 | hacs.config_dir = hass_config_dir 170 | hacs.store = HacsData(hass_config_dir) 171 | hacs.store.restore_values() 172 | hacs.element_types = sorted(ELEMENT_TYPES) 173 | 174 | 175 | async def setup_frontend(hass, hacs): 176 | """Configure the HACS frontend elements.""" 177 | from .api import HacsAPI, HacsRunningTask 178 | from .http import HacsWebResponse, HacsPluginView, HacsPlugin 179 | 180 | # Define views 181 | hass.http.register_view(HacsAPI()) 182 | hass.http.register_view(HacsPlugin()) 183 | hass.http.register_view(HacsPluginView()) 184 | hass.http.register_view(HacsRunningTask()) 185 | hass.http.register_view(HacsWebResponse()) 186 | 187 | # Add to sidepanel 188 | # TODO: Remove this check when minimum HA version is > 0.94 189 | if parse_version(HAVERSION) < parse_version("0.93.9"): 190 | await hass.components.frontend.async_register_built_in_panel( 191 | "iframe", 192 | hacs.config.sidepanel_title, 193 | hacs.config.sidepanel_icon, 194 | hacs.config.sidepanel_title.lower().replace(" ", "_").replace("-", "_"), 195 | {"url": hacs.hacsweb + "/overview"}, 196 | require_admin=IFRAME["require_admin"], 197 | ) 198 | else: 199 | hass.components.frontend.async_register_built_in_panel( 200 | "iframe", 201 | hacs.config.sidepanel_title, 202 | hacs.config.sidepanel_icon, 203 | hacs.config.sidepanel_title.lower().replace(" ", "_").replace("-", "_"), 204 | {"url": hacs.hacsweb + "/overview"}, 205 | require_admin=IFRAME["require_admin"], 206 | ) 207 | -------------------------------------------------------------------------------- /custom_components/hacs/__pycache__/__init__.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaeldvinci/Home-AssistantConfig/b45a163344af5e6dd48c95bc063c2645f4386b91/custom_components/hacs/__pycache__/__init__.cpython-37.pyc -------------------------------------------------------------------------------- /custom_components/hacs/__pycache__/api.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaeldvinci/Home-AssistantConfig/b45a163344af5e6dd48c95bc063c2645f4386b91/custom_components/hacs/__pycache__/api.cpython-37.pyc -------------------------------------------------------------------------------- /custom_components/hacs/__pycache__/const.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaeldvinci/Home-AssistantConfig/b45a163344af5e6dd48c95bc063c2645f4386b91/custom_components/hacs/__pycache__/const.cpython-37.pyc -------------------------------------------------------------------------------- /custom_components/hacs/__pycache__/http.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaeldvinci/Home-AssistantConfig/b45a163344af5e6dd48c95bc063c2645f4386b91/custom_components/hacs/__pycache__/http.cpython-37.pyc -------------------------------------------------------------------------------- /custom_components/hacs/__pycache__/sensor.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaeldvinci/Home-AssistantConfig/b45a163344af5e6dd48c95bc063c2645f4386b91/custom_components/hacs/__pycache__/sensor.cpython-37.pyc -------------------------------------------------------------------------------- /custom_components/hacs/aiogithub/__init__.py: -------------------------------------------------------------------------------- 1 | """Async Github API implementation.""" 2 | from .aiogithub import AIOGitHub 3 | -------------------------------------------------------------------------------- /custom_components/hacs/aiogithub/__pycache__/__init__.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaeldvinci/Home-AssistantConfig/b45a163344af5e6dd48c95bc063c2645f4386b91/custom_components/hacs/aiogithub/__pycache__/__init__.cpython-37.pyc -------------------------------------------------------------------------------- /custom_components/hacs/aiogithub/__pycache__/aiogithub.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaeldvinci/Home-AssistantConfig/b45a163344af5e6dd48c95bc063c2645f4386b91/custom_components/hacs/aiogithub/__pycache__/aiogithub.cpython-37.pyc -------------------------------------------------------------------------------- /custom_components/hacs/aiogithub/__pycache__/aiogithubrepository.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaeldvinci/Home-AssistantConfig/b45a163344af5e6dd48c95bc063c2645f4386b91/custom_components/hacs/aiogithub/__pycache__/aiogithubrepository.cpython-37.pyc -------------------------------------------------------------------------------- /custom_components/hacs/aiogithub/__pycache__/aiogithubrepositorycontent.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaeldvinci/Home-AssistantConfig/b45a163344af5e6dd48c95bc063c2645f4386b91/custom_components/hacs/aiogithub/__pycache__/aiogithubrepositorycontent.cpython-37.pyc -------------------------------------------------------------------------------- /custom_components/hacs/aiogithub/__pycache__/aiogithubrepositoryrelease.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaeldvinci/Home-AssistantConfig/b45a163344af5e6dd48c95bc063c2645f4386b91/custom_components/hacs/aiogithub/__pycache__/aiogithubrepositoryrelease.cpython-37.pyc -------------------------------------------------------------------------------- /custom_components/hacs/aiogithub/__pycache__/const.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaeldvinci/Home-AssistantConfig/b45a163344af5e6dd48c95bc063c2645f4386b91/custom_components/hacs/aiogithub/__pycache__/const.cpython-37.pyc -------------------------------------------------------------------------------- /custom_components/hacs/aiogithub/__pycache__/exceptions.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaeldvinci/Home-AssistantConfig/b45a163344af5e6dd48c95bc063c2645f4386b91/custom_components/hacs/aiogithub/__pycache__/exceptions.cpython-37.pyc -------------------------------------------------------------------------------- /custom_components/hacs/aiogithub/aiogithub.py: -------------------------------------------------------------------------------- 1 | """AioGitHub: Base""" 2 | # pylint: disable=super-init-not-called,missing-docstring,invalid-name,redefined-builtin 3 | import logging 4 | from asyncio import CancelledError, TimeoutError 5 | 6 | import async_timeout 7 | from aiohttp import ClientError 8 | 9 | import backoff 10 | 11 | from .const import BASE_HEADERS, BASE_URL 12 | from .exceptions import AIOGitHubAuthentication, AIOGitHubException, AIOGitHubRatelimit 13 | 14 | _LOGGER = logging.getLogger("AioGitHub") 15 | 16 | 17 | class AIOGitHub(object): 18 | """Base Github API implementation.""" 19 | 20 | def __init__(self, token, loop, session): 21 | """Must be called before anything else.""" 22 | self.token = token 23 | self.loop = loop 24 | self.session = session 25 | self.ratelimit_remaining = None 26 | self.headers = BASE_HEADERS 27 | self.headers["Authorization"] = "token {}".format(token) 28 | 29 | @backoff.on_exception( 30 | backoff.expo, (ClientError, CancelledError, TimeoutError, KeyError), max_tries=5 31 | ) 32 | async def get_repo(self, repo: str): 33 | """Retrun AIOGithubRepository object.""" 34 | from .aiogithubrepository import AIOGithubRepository 35 | if self.ratelimit_remaining == "0": 36 | raise AIOGitHubRatelimit("GitHub Ratelimit error") 37 | 38 | endpoint = "/repos/" + repo 39 | url = BASE_URL + endpoint 40 | 41 | headers = self.headers 42 | headers["Accept"] = "application/vnd.github.mercy-preview+json" 43 | 44 | async with async_timeout.timeout(20, loop=self.loop): 45 | response = await self.session.get(url, headers=headers) 46 | self.ratelimit_remaining = response.headers.get("x-ratelimit-remaining") 47 | response = await response.json() 48 | 49 | if self.ratelimit_remaining == "0": 50 | raise AIOGitHubRatelimit("GitHub Ratelimit error") 51 | 52 | if response.get("message"): 53 | if response["message"] == "Bad credentials": 54 | raise AIOGitHubAuthentication("Access token is not valid!") 55 | else: 56 | raise AIOGitHubException(response["message"]) 57 | 58 | return AIOGithubRepository(response, self.token, self.loop, self.session) 59 | 60 | @backoff.on_exception( 61 | backoff.expo, (ClientError, CancelledError, TimeoutError, KeyError), max_tries=5 62 | ) 63 | async def get_org_repos(self, org: str, page=1): 64 | """Retrun a list of AIOGithubRepository objects.""" 65 | from .aiogithubrepository import AIOGithubRepository 66 | if self.ratelimit_remaining == "0": 67 | raise AIOGitHubRatelimit("GitHub Ratelimit error") 68 | endpoint = "/orgs/" + org + "/repos?page=" + str(page) 69 | url = BASE_URL + endpoint 70 | 71 | params = {"per_page": 100} 72 | 73 | headers = self.headers 74 | headers["Accept"] = "application/vnd.github.mercy-preview+json" 75 | 76 | async with async_timeout.timeout(20, loop=self.loop): 77 | response = await self.session.get(url, headers=headers, params=params) 78 | self.ratelimit_remaining = response.headers.get("x-ratelimit-remaining") 79 | response = await response.json() 80 | 81 | if self.ratelimit_remaining == "0": 82 | raise AIOGitHubRatelimit("GitHub Ratelimit error") 83 | 84 | if not isinstance(response, list): 85 | if response["message"] == "Bad credentials": 86 | raise AIOGitHubAuthentication("Access token is not valid!") 87 | else: 88 | raise AIOGitHubException(response["message"]) 89 | 90 | repositories = [] 91 | 92 | for repository in response: 93 | repositories.append( 94 | AIOGithubRepository(repository, self.token, self.loop, self.session) 95 | ) 96 | 97 | return repositories 98 | 99 | @backoff.on_exception( 100 | backoff.expo, (ClientError, CancelledError, TimeoutError, KeyError), max_tries=5 101 | ) 102 | async def render_markdown(self, content: str): 103 | """Retrun AIOGithubRepository object.""" 104 | if self.ratelimit_remaining == "0": 105 | raise AIOGitHubRatelimit("GitHub Ratelimit error") 106 | endpoint = "/markdown/raw" 107 | url = BASE_URL + endpoint 108 | 109 | headers = self.headers 110 | headers["Content-Type"] = "text/plain" 111 | 112 | async with async_timeout.timeout(20, loop=self.loop): 113 | response = await self.session.post(url, headers=headers, data=content) 114 | self.ratelimit_remaining = response.headers.get("x-ratelimit-remaining") 115 | response = await response.text() 116 | 117 | if self.ratelimit_remaining == "0": 118 | raise AIOGitHubRatelimit("GitHub Ratelimit error") 119 | 120 | if isinstance(response, dict): 121 | if response.get("message"): 122 | if response["message"] == "Bad credentials": 123 | raise AIOGitHubAuthentication("Access token is not valid!") 124 | else: 125 | raise AIOGitHubException(response["message"]) 126 | 127 | return response 128 | -------------------------------------------------------------------------------- /custom_components/hacs/aiogithub/aiogithubrepository.py: -------------------------------------------------------------------------------- 1 | """AioGitHub: Repository""" 2 | from asyncio import CancelledError, TimeoutError 3 | from datetime import datetime 4 | 5 | import async_timeout 6 | import backoff 7 | from aiohttp import ClientError 8 | 9 | from .aiogithub import AIOGitHub 10 | from .aiogithubrepositorycontent import AIOGithubRepositoryContent 11 | from .aiogithubrepositoryrelease import AIOGithubRepositoryRelease 12 | from .const import BASE_URL 13 | from .exceptions import AIOGitHubException, AIOGitHubRatelimit 14 | 15 | 16 | class AIOGithubRepository(AIOGitHub): 17 | """Repository Github API implementation.""" 18 | 19 | def __init__(self, attributes, token, loop, session): 20 | """Initialize.""" 21 | super().__init__(token, loop, session) 22 | self.attributes = attributes 23 | self._last_commit = None 24 | 25 | @property 26 | def id(self): 27 | return self.attributes.get("id") 28 | 29 | @property 30 | def full_name(self): 31 | return self.attributes.get("full_name") 32 | 33 | @property 34 | def pushed_at(self): 35 | return datetime.strptime(self.attributes.get("pushed_at"), "%Y-%m-%dT%H:%M:%SZ") 36 | 37 | @property 38 | def archived(self): 39 | return self.attributes.get("archived") 40 | 41 | @property 42 | def description(self): 43 | return self.attributes.get("description") 44 | 45 | @property 46 | def topics(self): 47 | return self.attributes.get("topics") 48 | 49 | @property 50 | def default_branch(self): 51 | return self.attributes.get("default_branch") 52 | 53 | @property 54 | def last_commit(self): 55 | return self._last_commit 56 | 57 | @backoff.on_exception( 58 | backoff.expo, (ClientError, CancelledError, TimeoutError, KeyError), max_tries=5 59 | ) 60 | async def get_contents(self, path, ref=None): 61 | """Retrun a list of repository content objects.""" 62 | if self.ratelimit_remaining == "0": 63 | raise AIOGitHubRatelimit("GitHub Ratelimit error") 64 | endpoint = "/repos/" + self.full_name + "/contents/" + path 65 | url = BASE_URL + endpoint 66 | 67 | params = {"path": path} 68 | if ref is not None: 69 | params["ref"] = ref.replace("tags/", "") 70 | 71 | async with async_timeout.timeout(20, loop=self.loop): 72 | response = await self.session.get(url, headers=self.headers, params=params) 73 | self.ratelimit_remaining = response.headers.get("x-ratelimit-remaining") 74 | response = await response.json() 75 | 76 | if self.ratelimit_remaining == "0": 77 | raise AIOGitHubRatelimit("GitHub Ratelimit error") 78 | 79 | if not isinstance(response, list): 80 | if response.get("message"): 81 | if response.get("message") == "Not Found": 82 | raise AIOGitHubException( 83 | "{} does not exist in the repository.".format(path) 84 | ) 85 | else: 86 | raise AIOGitHubException(response["message"]) 87 | return AIOGithubRepositoryContent(response) 88 | 89 | contents = [] 90 | 91 | for content in response: 92 | contents.append(AIOGithubRepositoryContent(content)) 93 | 94 | return contents 95 | 96 | @backoff.on_exception( 97 | backoff.expo, (ClientError, CancelledError, TimeoutError, KeyError), max_tries=5 98 | ) 99 | async def get_releases(self, prerelease=False): 100 | """Retrun a list of repository release objects.""" 101 | if self.ratelimit_remaining == "0": 102 | raise AIOGitHubRatelimit("GitHub Ratelimit error") 103 | endpoint = "/repos/{}/releases".format(self.full_name) 104 | url = BASE_URL + endpoint 105 | 106 | async with async_timeout.timeout(20, loop=self.loop): 107 | response = await self.session.get(url, headers=self.headers) 108 | self.ratelimit_remaining = response.headers.get("x-ratelimit-remaining") 109 | response = await response.json() 110 | 111 | if self.ratelimit_remaining == "0": 112 | raise AIOGitHubRatelimit("GitHub Ratelimit error") 113 | 114 | if not isinstance(response, list): 115 | if response.get("message"): 116 | return False 117 | 118 | contents = [] 119 | 120 | for content in response: 121 | if len(contents) == 5: 122 | break 123 | if not prerelease: 124 | if content.get("prerelease", False): 125 | continue 126 | contents.append(AIOGithubRepositoryRelease(content)) 127 | 128 | return contents 129 | 130 | @backoff.on_exception( 131 | backoff.expo, (ClientError, CancelledError, TimeoutError, KeyError), max_tries=5 132 | ) 133 | async def set_last_commit(self): 134 | """Retrun a list of repository release objects.""" 135 | if self.ratelimit_remaining == "0": 136 | raise AIOGitHubRatelimit("GitHub Ratelimit error") 137 | endpoint = "/repos/" + self.full_name + "/commits/" + self.default_branch 138 | url = BASE_URL + endpoint 139 | 140 | async with async_timeout.timeout(20, loop=self.loop): 141 | response = await self.session.get(url, headers=self.headers) 142 | self.ratelimit_remaining = response.headers.get("x-ratelimit-remaining") 143 | response = await response.json() 144 | 145 | if self.ratelimit_remaining == "0": 146 | raise AIOGitHubRatelimit("GitHub Ratelimit error") 147 | 148 | if response.get("message"): 149 | raise AIOGitHubException("No commits") 150 | 151 | self._last_commit = response["sha"][0:7] 152 | -------------------------------------------------------------------------------- /custom_components/hacs/aiogithub/aiogithubrepositorycontent.py: -------------------------------------------------------------------------------- 1 | """AioGitHub: Repository Release""" 2 | import base64 3 | 4 | 5 | class AIOGithubRepositoryContent: 6 | """Repository Conetent Github API implementation.""" 7 | 8 | def __init__(self, attributes): 9 | """Initialize.""" 10 | self.attributes = attributes 11 | 12 | @property 13 | def type(self): 14 | return self.attributes.get("type", "file") 15 | 16 | @property 17 | def encoding(self): 18 | return self.attributes.get("encoding") 19 | 20 | @property 21 | def name(self): 22 | return self.attributes.get("name") 23 | 24 | @property 25 | def path(self): 26 | return self.attributes.get("path") 27 | 28 | @property 29 | def content(self): 30 | return base64.b64decode( 31 | bytearray(self.attributes.get("content"), "utf-8") 32 | ).decode() 33 | 34 | @property 35 | def download_url(self): 36 | return self.attributes.get("download_url") or self.attributes.get( 37 | "browser_download_url" 38 | ) 39 | -------------------------------------------------------------------------------- /custom_components/hacs/aiogithub/aiogithubrepositoryrelease.py: -------------------------------------------------------------------------------- 1 | """AioGitHub: Repository Release""" 2 | from datetime import datetime 3 | from .aiogithubrepositorycontent import AIOGithubRepositoryContent 4 | 5 | 6 | class AIOGithubRepositoryRelease: 7 | """Repository Release Github API implementation.""" 8 | 9 | def __init__(self, attributes): 10 | """Initialize.""" 11 | self.attributes = attributes 12 | 13 | @property 14 | def tag_name(self): 15 | return self.attributes.get("tag_name") 16 | 17 | @property 18 | def name(self): 19 | return self.attributes.get("name") 20 | 21 | @property 22 | def published_at(self): 23 | return datetime.strptime( 24 | self.attributes.get("published_at"), "%Y-%m-%dT%H:%M:%SZ" 25 | ) 26 | 27 | @property 28 | def draft(self): 29 | return self.attributes.get("draft") 30 | 31 | @property 32 | def prerelease(self): 33 | return self.attributes.get("prerelease") 34 | 35 | @property 36 | def assets(self): 37 | assetlist = [] 38 | for item in self.attributes.get("assets"): 39 | assetlist.append(AIOGithubRepositoryContent(item)) 40 | return assetlist 41 | -------------------------------------------------------------------------------- /custom_components/hacs/aiogithub/const.py: -------------------------------------------------------------------------------- 1 | """AioGithub: Constants""" 2 | BASE_URL = "https://api.github.com" 3 | BASE_HEADERS = { 4 | "Accept": "application/vnd.github.v3.raw+json", 5 | "User-Agent": "python/AIOGitHub", 6 | } -------------------------------------------------------------------------------- /custom_components/hacs/aiogithub/exceptions.py: -------------------------------------------------------------------------------- 1 | """AioGithub: Exceptions""" 2 | class AIOGitHubException(BaseException): 3 | """Raise this when something is off.""" 4 | 5 | 6 | class AIOGitHubRatelimit(AIOGitHubException): 7 | """Raise this when we hit the ratelimit.""" 8 | 9 | class AIOGitHubAuthentication(AIOGitHubException): 10 | """Raise this when there is an authentication issue.""" 11 | -------------------------------------------------------------------------------- /custom_components/hacs/const.py: -------------------------------------------------------------------------------- 1 | """Constants for HACS""" 2 | VERSION = "0.12.1" 3 | NAME_LONG = "HACS (Home Assistant Community Store)" 4 | NAME_SHORT = "HACS" 5 | DOMAIN = "hacs" 6 | PROJECT_URL = "https://github.com/custom-components/hacs/" 7 | CUSTOM_UPDATER_LOCATIONS = [ 8 | "{}/custom_components/custom_updater.py", 9 | "{}/custom_components/custom_updater/__init__.py", 10 | ] 11 | 12 | ISSUE_URL = "{}issues".format(PROJECT_URL) 13 | DOMAIN_DATA = "{}_data".format(NAME_SHORT.lower()) 14 | 15 | ELEMENT_TYPES = ["integration", "plugin"] 16 | 17 | IFRAME = { 18 | "title": "Community", 19 | "icon": "mdi:alpha-c-box", 20 | "url": "/community_overview", 21 | "path": "community", 22 | "require_admin": True, 23 | } 24 | 25 | 26 | # Messages 27 | CUSTOM_UPDATER_WARNING = """ 28 | This cannot be used with custom_updater. 29 | To use this you need to remove custom_updater form {} 30 | """ 31 | 32 | DEV_MODE = "You have 'dev' enabled for HACS, this is not intended for regular use, no support will be given if you break something." 33 | 34 | STARTUP = """ 35 | ------------------------------------------------------------------- 36 | {} 37 | Version: {} 38 | This is a custom integration 39 | If you have any issues with this you need to open an issue here: 40 | {} 41 | ------------------------------------------------------------------- 42 | """.format( 43 | NAME_LONG, VERSION, ISSUE_URL 44 | ) -------------------------------------------------------------------------------- /custom_components/hacs/frontend/__init__.py: -------------------------------------------------------------------------------- 1 | """Initialize frontend objects.""" 2 | -------------------------------------------------------------------------------- /custom_components/hacs/frontend/__pycache__/__init__.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaeldvinci/Home-AssistantConfig/b45a163344af5e6dd48c95bc063c2645f4386b91/custom_components/hacs/frontend/__pycache__/__init__.cpython-37.pyc -------------------------------------------------------------------------------- /custom_components/hacs/frontend/elements/all.min.css.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaeldvinci/Home-AssistantConfig/b45a163344af5e6dd48c95bc063c2645f4386b91/custom_components/hacs/frontend/elements/all.min.css.gz -------------------------------------------------------------------------------- /custom_components/hacs/frontend/elements/hacs.js: -------------------------------------------------------------------------------- 1 | // Copy active HA theme 2 | document.getElementsByTagName("html").item(0).setAttribute("style", parent.document.getElementsByTagName("html").item(0).style.cssText) 3 | 4 | // Copy yhe content of the Lovelace example to the clipboard. 5 | function CopyToLovelaceExampleToClipboard() { 6 | window.getSelection().selectAllChildren( document.getElementById("LovelaceExample")); 7 | document.execCommand("copy"); 8 | document.getSelection().empty() 9 | document.getElementById('lovelacecopy').style.color = 'forestgreen'; 10 | } 11 | 12 | // Show progress bar 13 | function ShowProgressBar() { 14 | document.getElementById('progressbar').style.display = 'block'; 15 | } 16 | 17 | // Searchbar 18 | function Search() { 19 | var input = document.getElementById("Search"); 20 | if (input) { 21 | var filter = input.value.toLowerCase(); 22 | var nodes = document.getElementsByClassName('hacs-card'); 23 | for (i = 0; i < nodes.length; i++) { 24 | if (nodes[i].innerHTML.toLowerCase().includes(filter)) { 25 | nodes[i].style.display = "block"; 26 | } else { 27 | nodes[i].style.display = "none"; 28 | } 29 | } 30 | var nodes = document.getElementsByClassName('hacs-table-row'); 31 | for (i = 0; i < nodes.length; i++) { 32 | if (nodes[i].innerHTML.toLowerCase().includes(filter)) { 33 | nodes[i].style.display = "table-row"; 34 | } else { 35 | nodes[i].style.display = "none"; 36 | } 37 | } 38 | } 39 | } 40 | 41 | 42 | // Dropdown 43 | document.addEventListener('DOMContentLoaded', function() { 44 | var elems = document.querySelectorAll('.dropdown-trigger'); 45 | var instances = M.Dropdown.init(elems, {hover: true, constrainWidth: false}); 46 | }); 47 | 48 | // Modal 49 | document.addEventListener('DOMContentLoaded', function() { 50 | var elems = document.querySelectorAll('.modal'); 51 | var instances = M.Modal.init(elems, {}); 52 | }); 53 | 54 | 55 | // Loader 56 | function toggleLoading(){ 57 | var loadingOverlay = document.querySelector('.loading'); 58 | loadingOverlay.classList.remove('hidden') 59 | document.activeElement.blur(); 60 | } 61 | 62 | 63 | // Check if we can reload 64 | function sleep (time) { 65 | return new Promise((resolve) => setTimeout(resolve, time)); 66 | } 67 | function CheckIfWeCanReload() { 68 | var data = true; 69 | const hacsrequest = new XMLHttpRequest() 70 | hacsrequest.open('GET', '/hacs_task', true) 71 | hacsrequest.onload = function() { 72 | data = JSON.parse(this.response) 73 | data = data["task"] 74 | if (!data) { 75 | console.log("Background task is no longer running, reloading in 5s...") 76 | sleep(5000).then(() => { 77 | location.reload() 78 | }); 79 | } 80 | } 81 | hacsrequest.send() 82 | } 83 | 84 | function IsTaskRunning() { 85 | let retval = false; 86 | let disp = document.getElementsByClassName("progress") 87 | if (disp) { 88 | disp = disp.item(0).style.display; 89 | if (disp == "block") { 90 | retval = true 91 | } else { 92 | retval = false 93 | } 94 | } 95 | return retval 96 | } 97 | 98 | window.setInterval(function(){ 99 | var running = false; 100 | running = IsTaskRunning(); 101 | if (running) { 102 | CheckIfWeCanReload(); 103 | } 104 | }, 10000); 105 | 106 | 107 | -------------------------------------------------------------------------------- /custom_components/hacs/frontend/elements/materialize.min.css.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaeldvinci/Home-AssistantConfig/b45a163344af5e6dd48c95bc063c2645f4386b91/custom_components/hacs/frontend/elements/materialize.min.css.gz -------------------------------------------------------------------------------- /custom_components/hacs/frontend/elements/materialize.min.js.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaeldvinci/Home-AssistantConfig/b45a163344af5e6dd48c95bc063c2645f4386b91/custom_components/hacs/frontend/elements/materialize.min.js.gz -------------------------------------------------------------------------------- /custom_components/hacs/frontend/elements/webfonts/fa-brands-400.eot.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaeldvinci/Home-AssistantConfig/b45a163344af5e6dd48c95bc063c2645f4386b91/custom_components/hacs/frontend/elements/webfonts/fa-brands-400.eot.gz -------------------------------------------------------------------------------- /custom_components/hacs/frontend/elements/webfonts/fa-brands-400.svg.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaeldvinci/Home-AssistantConfig/b45a163344af5e6dd48c95bc063c2645f4386b91/custom_components/hacs/frontend/elements/webfonts/fa-brands-400.svg.gz -------------------------------------------------------------------------------- /custom_components/hacs/frontend/elements/webfonts/fa-brands-400.ttf.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaeldvinci/Home-AssistantConfig/b45a163344af5e6dd48c95bc063c2645f4386b91/custom_components/hacs/frontend/elements/webfonts/fa-brands-400.ttf.gz -------------------------------------------------------------------------------- /custom_components/hacs/frontend/elements/webfonts/fa-brands-400.woff.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaeldvinci/Home-AssistantConfig/b45a163344af5e6dd48c95bc063c2645f4386b91/custom_components/hacs/frontend/elements/webfonts/fa-brands-400.woff.gz -------------------------------------------------------------------------------- /custom_components/hacs/frontend/elements/webfonts/fa-brands-400.woff2.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaeldvinci/Home-AssistantConfig/b45a163344af5e6dd48c95bc063c2645f4386b91/custom_components/hacs/frontend/elements/webfonts/fa-brands-400.woff2.gz -------------------------------------------------------------------------------- /custom_components/hacs/frontend/elements/webfonts/fa-regular-400.eot.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaeldvinci/Home-AssistantConfig/b45a163344af5e6dd48c95bc063c2645f4386b91/custom_components/hacs/frontend/elements/webfonts/fa-regular-400.eot.gz -------------------------------------------------------------------------------- /custom_components/hacs/frontend/elements/webfonts/fa-regular-400.svg.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaeldvinci/Home-AssistantConfig/b45a163344af5e6dd48c95bc063c2645f4386b91/custom_components/hacs/frontend/elements/webfonts/fa-regular-400.svg.gz -------------------------------------------------------------------------------- /custom_components/hacs/frontend/elements/webfonts/fa-regular-400.ttf.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaeldvinci/Home-AssistantConfig/b45a163344af5e6dd48c95bc063c2645f4386b91/custom_components/hacs/frontend/elements/webfonts/fa-regular-400.ttf.gz -------------------------------------------------------------------------------- /custom_components/hacs/frontend/elements/webfonts/fa-regular-400.woff.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaeldvinci/Home-AssistantConfig/b45a163344af5e6dd48c95bc063c2645f4386b91/custom_components/hacs/frontend/elements/webfonts/fa-regular-400.woff.gz -------------------------------------------------------------------------------- /custom_components/hacs/frontend/elements/webfonts/fa-regular-400.woff2.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaeldvinci/Home-AssistantConfig/b45a163344af5e6dd48c95bc063c2645f4386b91/custom_components/hacs/frontend/elements/webfonts/fa-regular-400.woff2.gz -------------------------------------------------------------------------------- /custom_components/hacs/frontend/elements/webfonts/fa-solid-900.eot.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaeldvinci/Home-AssistantConfig/b45a163344af5e6dd48c95bc063c2645f4386b91/custom_components/hacs/frontend/elements/webfonts/fa-solid-900.eot.gz -------------------------------------------------------------------------------- /custom_components/hacs/frontend/elements/webfonts/fa-solid-900.svg.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaeldvinci/Home-AssistantConfig/b45a163344af5e6dd48c95bc063c2645f4386b91/custom_components/hacs/frontend/elements/webfonts/fa-solid-900.svg.gz -------------------------------------------------------------------------------- /custom_components/hacs/frontend/elements/webfonts/fa-solid-900.ttf.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaeldvinci/Home-AssistantConfig/b45a163344af5e6dd48c95bc063c2645f4386b91/custom_components/hacs/frontend/elements/webfonts/fa-solid-900.ttf.gz -------------------------------------------------------------------------------- /custom_components/hacs/frontend/elements/webfonts/fa-solid-900.woff.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaeldvinci/Home-AssistantConfig/b45a163344af5e6dd48c95bc063c2645f4386b91/custom_components/hacs/frontend/elements/webfonts/fa-solid-900.woff.gz -------------------------------------------------------------------------------- /custom_components/hacs/frontend/elements/webfonts/fa-solid-900.woff2.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaeldvinci/Home-AssistantConfig/b45a163344af5e6dd48c95bc063c2645f4386b91/custom_components/hacs/frontend/elements/webfonts/fa-solid-900.woff2.gz -------------------------------------------------------------------------------- /custom_components/hacs/frontend/templates/base.html: -------------------------------------------------------------------------------- 1 | 2 | {% set progressbar = 'block' if hacs.store.task_running else 'none' %} 3 | {% set url = namespace( 4 | base=hacs.url_path['base'], 5 | static=hacs.url_path['base']+'/static', 6 | api=hacs.url_path['api'], 7 | adminapi=hacs.url_path['admin-api'], 8 | store=hacs.url_path['base']+'/store', 9 | overview=hacs.url_path['base']+'/overview', 10 | settings=hacs.url_path['base']+'/settings', 11 | repository=hacs.url_path['base']+'/repository') %} 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 34 |
35 |

Background task running, this page will reload when it's done.

36 |
37 |
38 |
39 | 44 | {% if message %} 45 | {% include 'message.html' with context %} 46 | {% endif %} 47 | {% block content %} 48 | {% endblock %} 49 |
50 | 51 | -------------------------------------------------------------------------------- /custom_components/hacs/frontend/templates/error.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block content %} 3 | {{message}} 4 | {% endblock %} -------------------------------------------------------------------------------- /custom_components/hacs/frontend/templates/grid.html: -------------------------------------------------------------------------------- 1 |
2 |
{{ displaytype }}
3 |
4 | {% for repository in repositories.repositories %} 5 | 6 |
7 |
8 | 9 |
10 |
11 | 12 | {% if location == 'store' and repository.new %} 13 | NEW 14 | {% else %} 15 | 16 | {% endif %} 17 | {{repository.name}} 18 | 19 |
20 |
21 | 22 |

{{repository.description}}

23 |
24 |
25 |
26 |
27 | {% endfor %} 28 |
29 |
30 |
31 |
-------------------------------------------------------------------------------- /custom_components/hacs/frontend/templates/message.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |
6 | 7 | {{message}} 8 | 9 |
10 |
11 |
12 |
13 |
-------------------------------------------------------------------------------- /custom_components/hacs/frontend/templates/modal/upgrade_all.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /custom_components/hacs/frontend/templates/modal/wrong_ha_version.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /custom_components/hacs/frontend/templates/overviews.html: -------------------------------------------------------------------------------- 1 | 2 | {% extends "base.html" %} 3 | {% block content %} 4 | {% if location == 'store' %} 5 |
6 | 7 |
8 | {% endif %} 9 | 10 | {% for type in hacs.element_types %} 11 | 12 | {% if type == 'appdaemon' %} 13 | {% set displaytype = 'APPDAEMON APPS' %} 14 | {% elif type == 'python_script' %} 15 | {% set displaytype = 'PYTHON SCRIPTS' %} 16 | {% else %} 17 | {% set displaytype = (type + 's') | upper %} 18 | {% endif %} 19 | 20 | {% set repositories = namespace(repositories=[]) %} 21 | 22 | {% for repository in hacs.store.frontend|sort(attribute='name') %} 23 | {% if not repository.hide or repository.track %} 24 | {% if repository.repository_type == type %} 25 | {% if location == 'overview' %} 26 | {% if repository.installed %} 27 | {% set repositories.repositories = repositories.repositories+[repository] %} 28 | {% endif %} 29 | {% else %} 30 | {% if repository.repository_id != "172733314" %} 31 | {% set repositories.repositories = repositories.repositories+[repository] %} 32 | {% endif %} 33 | {% endif %} 34 | {% endif %} 35 | {% endif %} 36 | {% endfor %} 37 | 38 | {% if repositories.repositories %} 39 | {% if hacs.store.frontend_mode == "Grid" %} 40 | {% include 'grid.html' with context %} 41 | {% else %} 42 | {% include 'table.html' with context %} 43 | {% endif %} 44 | {% endif %} 45 | 46 | {% endfor %} 47 | 48 | {% endblock %} -------------------------------------------------------------------------------- /custom_components/hacs/frontend/templates/repository.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block content %} 3 | 4 | 5 | {% if repository.status == "pending-restart" %} 6 | {% set message = "You need to restart (and potentially reconfigure) Home Assistant, for your last operation to be loaded." %} 7 | {% endif %} 8 | 9 |
10 |
11 |
12 |
13 |
14 | 15 | {{repository.name}} 16 | 17 | 18 | 19 | 20 | {% include 'repository/menu.html' with context %} 21 | 22 |

{{repository.description}}


23 | {% if repository.installed %} 24 |

Installed {{repository.repository.version_or_commit}}: {{repository.installed_version}}

25 | {% endif %} 26 | {% if repository.published_tags and repository.repository_id != "172733314" %} 27 | {% include 'repository/versionselect.html' with context %} 28 | {% else %} 29 |

Available {{repository.repository.version_or_commit}}: {{repository.available_version}}

30 | {% endif %} 31 |
32 | {{repository.display_authors}} 33 |
34 | {% include 'repository/buttons.html' with context %} 35 |
36 |
37 |
38 |
39 | 40 |
41 |
42 |
43 |
44 |
45 | {{repository.additional_info}} 46 | {% include 'repository/note.html' with context %} 47 |
48 |
49 |
50 |
51 |
52 | {% endblock %} -------------------------------------------------------------------------------- /custom_components/hacs/frontend/templates/repository/buttons.html: -------------------------------------------------------------------------------- 1 | {% if not repository.repository.can_install %} 2 | {% include 'modal/wrong_ha_version.html' with context %} 3 | {% endif %} 4 | 5 |
6 | {% if repository.repository.can_install %} 7 |
8 | 9 | 10 | {{repository.main_action}} 11 | 12 |
13 | {% else %} 14 | 15 | {{repository.main_action}} 16 | 17 | {% endif %} 18 | 19 | {% if repository.status == "pending-update" and repository.version_or_commit == "version" %} 20 | 21 | CHANGELOG 22 | 23 | {% endif %} 24 | 25 | 26 | repository 27 | 28 | 29 | {% if repository.installed and repository.repository_type == "plugin" %} 30 | {% set name = repository.full_name.split("lovelace-")[-1] %} 31 | 32 | OPEN PLUGIN 33 | 34 | {% endif %} 35 | 36 | {% if repository.installed and repository.repository_id != "172733314" %} 37 |
38 | 39 | 40 | UNINSTALL 41 | 42 |
43 | {% endif %} 44 | 45 |
-------------------------------------------------------------------------------- /custom_components/hacs/frontend/templates/repository/menu.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /custom_components/hacs/frontend/templates/repository/note.html: -------------------------------------------------------------------------------- 1 | {% if repository.additional_info %} 2 |


3 | {% endif %} 4 | 5 | {% if repository.repository_type == "appdaemon" %} 6 | 7 | 8 | When installed, this will be located in '{{repository.local_path}}', 9 | you still need to add it to your 'apps.yaml' file. 10 | 11 | 12 | {% elif repository.repository_type == "integration" %} 13 | 14 | 15 | When installed, this will be located in '{{repository.local_path}}', 16 | you still need to add it to your 'configuration.yaml' file. 17 | 18 | 19 | {% elif repository.repository_type == "plugin" %} 20 | {% set name = repository.full_name.replace("lovelace-", "") %} 21 | 22 | 23 | When installed, this will be located in '{{repository.local_path}}', 24 | you still need to add it to your lovelace configuration ('ui-lovelace.yaml' or the raw UI config editor). 25 | 26 |

27 | 28 | When you add this to your configuration use this: 29 |
30 |
31 | - url: /community_plugin/{{repository.full_name}}/{{name}}.js
32 |   {% if repository.repository.javascript_type -%}
33 |   type: {{repository.repository.javascript_type}}
34 |   {%- else -%}
35 |   type: Could not determine the type of this plugin, check the repository.
36 |   {%- endif -%}
37 | 
38 | 39 | 40 | 41 | {% endif %} 42 | -------------------------------------------------------------------------------- /custom_components/hacs/frontend/templates/repository/versionselect.html: -------------------------------------------------------------------------------- 1 | {% set tags = repository.published_tags | sort(True) %} 2 | {% set tags = tags + [repository.default_branch] %} 3 | 4 | {% if repository.selected_tag %} 5 | {% set selected_tag = repository.selected_tag %} 6 | {% else %} 7 | {% set selected_tag = repository.available_version %} 8 | {% endif %} 9 | 10 | Available versions: 11 |
12 | 13 | 18 |
-------------------------------------------------------------------------------- /custom_components/hacs/frontend/templates/settings.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block content %} 3 | {% if hacs.config.dev %} 4 | {% set message = "You have 'dev' enabled for HACS, this is not intended for regular use, no support will be given if you break something." %} 5 | {% include 'message.html' with context %} 6 | {% endif %} 7 | 8 | {% include 'modal/upgrade_all.html' with context %} 9 | 10 | 11 | {% include 'settings/hacs_info.html' with context %} 12 | {% include 'settings/buttons.html' with context %} 13 | {% include 'settings/custom_repositories.html' with context %} 14 | {% include 'settings/hidden_repositories.html' with context %} 15 | 16 | 17 | {% if hacs.config.dev and not hacs.store.task_running %} 18 | {% include 'settings/developer.html' with context %} 19 | {% endif %} 20 | {% endblock %} -------------------------------------------------------------------------------- /custom_components/hacs/frontend/templates/settings/buttons.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | 5 | RELOAD DATA 6 | 7 |
8 | 9 | 12 | UPGRADE ALL 13 | 14 | 15 | 19 | HACS REPO 20 | 21 | 22 | 26 | OPEN ISSUE 27 | 28 | 29 |
-------------------------------------------------------------------------------- /custom_components/hacs/frontend/templates/settings/custom_repositories.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 38 | 39 | {% if not hacs.store.task_running %} 40 |
41 | 42 | 48 | 49 | 52 |
53 | {% endif %} 54 |
55 |
-------------------------------------------------------------------------------- /custom_components/hacs/frontend/templates/settings/dev/remove_new_flag.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block content %} 3 |
4 |
5 |
6 | 9 |
10 |
11 |
12 | {% endblock %} -------------------------------------------------------------------------------- /custom_components/hacs/frontend/templates/settings/dev/repositories.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block content %} 3 | {% for repository in hacs.store.frontend|sort(attribute='name') %} 4 |
5 | 6 | 9 |
10 | {% endfor %} 11 | {% endblock %} -------------------------------------------------------------------------------- /custom_components/hacs/frontend/templates/settings/dev/repository.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaeldvinci/Home-AssistantConfig/b45a163344af5e6dd48c95bc063c2645f4386b91/custom_components/hacs/frontend/templates/settings/dev/repository.html -------------------------------------------------------------------------------- /custom_components/hacs/frontend/templates/settings/dev/set_ha_version.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block content %} 3 |
4 |
5 |
6 | 7 | 10 |
11 |
12 |
13 | {% endblock %} -------------------------------------------------------------------------------- /custom_components/hacs/frontend/templates/settings/dev/token.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block content %} 3 |
4 |
5 | 6 |
7 |
8 | {% endblock %} -------------------------------------------------------------------------------- /custom_components/hacs/frontend/templates/settings/developer.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
DEVELOPER TOOLS
4 | {% for button in [ 5 | "remove_new_flag", 6 | "repositories", 7 | "set_ha_version", 8 | "token", 9 | ] %} 10 |
11 | 12 | 15 |
16 | {% endfor %} 17 |
18 |
-------------------------------------------------------------------------------- /custom_components/hacs/frontend/templates/settings/hacs_info.html: -------------------------------------------------------------------------------- 1 | {% if hacs.store.repositories["172733314"].pending_restart %} 2 | {% set pending_restart = " (RESTART PENDING!)" %} 3 | {% else %} 4 | {% set pending_restart = "" %} 5 | {% endif %} 6 | 7 |
8 |
9 |
{{hacs.const.NAME_LONG}}
10 | Version: {{hacs.const.VERSION}}{{pending_restart}} 11 |
12 | Display: 13 |
14 | 19 |
20 |
21 |
-------------------------------------------------------------------------------- /custom_components/hacs/frontend/templates/settings/hidden_repositories.html: -------------------------------------------------------------------------------- 1 | {% set repositories = namespace(hidden=False) %} 2 | {% for repository in hacs.repositories_list_repo %} 3 | {% if repository.hide %} 4 | {% set repositories.hidden = True %} 5 | {% endif %} 6 | {% endfor %} 7 | 8 | {% if repositories.hidden %} 9 |
10 |
11 | 38 |
39 |
40 | {% endif %} -------------------------------------------------------------------------------- /custom_components/hacs/frontend/templates/table.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
{{ displaytype }}
4 | 5 | 6 | 7 | 8 | 9 | 10 | {% if location == 'overview' %} 11 | 12 | 13 | {% endif %} 14 | 15 | 16 | 17 | {% for repository in repositories.repositories %} 18 | 19 | 24 | {% endif %} 25 | 28 | 29 | {% if location == 'overview' %} 30 | 31 | 32 | {% endif %} 33 | 34 | {% endfor %} 35 | 36 |
StatusNameDescriptionInstalledAvailable
20 | {% if location == 'store' and repository.new %} 21 | NEW 22 | {% else %} 23 | 26 | {{repository.name}} 27 | {{repository.description}}{{repository.installed_version}}{{repository.available_version}}
37 |
38 |
-------------------------------------------------------------------------------- /custom_components/hacs/hacsbase/__pycache__/__init__.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaeldvinci/Home-AssistantConfig/b45a163344af5e6dd48c95bc063c2645f4386b91/custom_components/hacs/hacsbase/__pycache__/__init__.cpython-37.pyc -------------------------------------------------------------------------------- /custom_components/hacs/hacsbase/__pycache__/configuration.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaeldvinci/Home-AssistantConfig/b45a163344af5e6dd48c95bc063c2645f4386b91/custom_components/hacs/hacsbase/__pycache__/configuration.cpython-37.pyc -------------------------------------------------------------------------------- /custom_components/hacs/hacsbase/__pycache__/const.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaeldvinci/Home-AssistantConfig/b45a163344af5e6dd48c95bc063c2645f4386b91/custom_components/hacs/hacsbase/__pycache__/const.cpython-37.pyc -------------------------------------------------------------------------------- /custom_components/hacs/hacsbase/__pycache__/data.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaeldvinci/Home-AssistantConfig/b45a163344af5e6dd48c95bc063c2645f4386b91/custom_components/hacs/hacsbase/__pycache__/data.cpython-37.pyc -------------------------------------------------------------------------------- /custom_components/hacs/hacsbase/__pycache__/exceptions.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaeldvinci/Home-AssistantConfig/b45a163344af5e6dd48c95bc063c2645f4386b91/custom_components/hacs/hacsbase/__pycache__/exceptions.cpython-37.pyc -------------------------------------------------------------------------------- /custom_components/hacs/hacsbase/__pycache__/migration.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaeldvinci/Home-AssistantConfig/b45a163344af5e6dd48c95bc063c2645f4386b91/custom_components/hacs/hacsbase/__pycache__/migration.cpython-37.pyc -------------------------------------------------------------------------------- /custom_components/hacs/hacsbase/configuration.py: -------------------------------------------------------------------------------- 1 | """HACS Configuration.""" 2 | 3 | 4 | class HacsConfiguration: 5 | """HacsConfiguration class.""" 6 | 7 | def __init__(self, config): 8 | """Initialize.""" 9 | self.config = config 10 | 11 | @property 12 | def token(self): 13 | """GitHub Access token.""" 14 | if self.config.get("token") is not None: 15 | return self.config["token"] 16 | return None 17 | 18 | @property 19 | def sidepanel_title(self): 20 | """Sidepanel title.""" 21 | if self.config.get("sidepanel_title") is not None: 22 | return self.config["sidepanel_title"] 23 | return "Community" 24 | 25 | @property 26 | def sidepanel_icon(self): 27 | """Sidepanel icon.""" 28 | if self.config.get("sidepanel_icon") is not None: 29 | return self.config["sidepanel_icon"] 30 | return "mdi:alpha-c-box" 31 | 32 | @property 33 | def dev(self): 34 | """Dev mode active.""" 35 | if self.config.get("dev") is not None: 36 | return self.config["dev"] 37 | return False 38 | 39 | @property 40 | def plugin_path(self): 41 | """Plugin path.""" 42 | if self.config.get("plugin_path") is not None: 43 | return self.config["plugin_path"] 44 | return "www/community/" 45 | 46 | @property 47 | def appdaemon(self): 48 | """Enable appdaemon.""" 49 | if self.config.get("appdaemon") is not None: 50 | return self.config["appdaemon"] 51 | return False 52 | 53 | @property 54 | def appdaemon_path(self): 55 | """Appdaemon apps path.""" 56 | if self.config.get("appdaemon_path") is not None: 57 | return self.config["appdaemon_path"] 58 | return "appdaemon/apps/" 59 | 60 | @property 61 | def python_script(self): 62 | """Enable python_script.""" 63 | if self.config.get("python_script") is not None: 64 | return self.config["python_script"] 65 | return False 66 | 67 | @property 68 | def python_script_path(self): 69 | """python_script path.""" 70 | if self.config.get("python_script_path") is not None: 71 | return self.config["python_script_path"] 72 | return "python_scripts/" 73 | 74 | @property 75 | def theme(self): 76 | """Enable theme.""" 77 | if self.config.get("theme") is not None: 78 | return self.config["theme"] 79 | return False 80 | 81 | @property 82 | def theme_path(self): 83 | """Themes path.""" 84 | if self.config.get("theme_path") is not None: 85 | return self.config["theme_path"] 86 | return "themes/" 87 | -------------------------------------------------------------------------------- /custom_components/hacs/hacsbase/const.py: -------------------------------------------------------------------------------- 1 | """Constants for HACS""" 2 | # pylint: disable=unused-import 3 | from ..const import VERSION, ELEMENT_TYPES 4 | 5 | STORAGE_VERSION = "4" 6 | STORENAME = "hacs" 7 | 8 | # Messages 9 | NOT_SUPPORTED_HA_VERSION = "You have version '{}' of Home Assistant, but version '{}' of '{}' require version '{}' of Home Assistant, install and upgrades are disabled for this integration untill you upgrade Home Assistant." 10 | 11 | 12 | NO_ELEMENTS = "No elements to show, open the store to install some awesome stuff." 13 | 14 | ERROR = [ 15 | "Luke, I am your father!", 16 | "Scruffy-looking nerfherder!", 17 | "'What' ain't no country I've ever heard of. They speak English in What?", 18 | "Nobody's gonna hurt anybody. We're gonna be like three little Fonzies here. And what's Fonzie like?", 19 | "Back off man, I'm a scientist", 20 | "Listen! You smell something?", 21 | "I am serious, and don't call me Shirley.", 22 | "I know kung fu.", 23 | "Ho-ho-ho. Now I have a machine gun.", 24 | "Kneel before Zod!", 25 | "Try not. Do, or do not. There is no try.", 26 | "If we knew what it was we were doing, it would not be called research, would it?", 27 | "Wait a minute, Doc. Ah… Are you telling me you built a time machine… out of a DeLorean?", 28 | ] 29 | -------------------------------------------------------------------------------- /custom_components/hacs/hacsbase/data.py: -------------------------------------------------------------------------------- 1 | """Data handler for HACS.""" 2 | import os 3 | import json 4 | from homeassistant.const import __version__ as HAVERSION 5 | from .const import STORENAME, VERSION 6 | from ..handler.logger import HacsLogger 7 | from ..repositories.repositoryinformationview import RepositoryInformationView 8 | from ..repositories.hacsrepositoryappdaemon import HacsRepositoryAppDaemon 9 | from ..repositories.hacsrepositoryintegration import HacsRepositoryIntegration 10 | from ..repositories.hacsrepositorybaseplugin import HacsRepositoryPlugin 11 | from ..repositories.hacsrepositorypythonscript import HacsRepositoryPythonScripts 12 | from ..repositories.hacsrepositorytheme import HacsRepositoryThemes 13 | from ..repositories.repositoryinformationview import RepositoryInformationView 14 | 15 | class HacsData: 16 | """HacsData class.""" 17 | 18 | def __init__(self, config_dir): 19 | """Initialize.""" 20 | self.frontend_mode = "Grid" 21 | self.repositories = {} 22 | self.logger = HacsLogger() 23 | self.config_dir = config_dir 24 | self.ha_version = HAVERSION 25 | self.schema = None 26 | self.endpoints = {} 27 | self.frontend = [] 28 | self.task_running = False 29 | 30 | @property 31 | def store_path(self): 32 | """Return the path to the store file.""" 33 | return "{}/.storage/{}".format(self.config_dir, STORENAME) 34 | 35 | def read(self): 36 | """Read from store.""" 37 | content = None 38 | try: 39 | with open(self.store_path, "r", encoding="utf-8", errors="ignore") as storefile: 40 | content = storefile.read() 41 | content = json.loads(content) 42 | except FileNotFoundError: 43 | pass 44 | return content 45 | 46 | def write(self): 47 | """Write to store.""" 48 | if self.task_running: 49 | return 50 | 51 | self.logger.debug("Saving data", "store") 52 | 53 | data = { 54 | "hacs": { 55 | "view": self.frontend_mode, 56 | "schema": self.schema, 57 | "endpoints": self.endpoints 58 | }, 59 | "repositories": {} 60 | } 61 | 62 | for repository in self.repositories: 63 | repository = self.repositories[repository] 64 | repositorydata = { 65 | "custom": repository.custom, 66 | "description": repository.description, 67 | "hide": repository.hide, 68 | "installed_commit": repository.installed_commit, 69 | "installed": repository.installed, 70 | "last_commit": repository.last_commit, 71 | "name": repository.name, 72 | "new": repository.new, 73 | "repository_name": repository.repository_name, 74 | "repository_type": repository.repository_type, 75 | "show_beta": repository.show_beta, 76 | "topics": repository.topics, 77 | "track": repository.track, 78 | "last_release_tag": repository.last_release_tag, 79 | "version_installed": repository.version_installed, 80 | "selected_tag": repository.selected_tag, 81 | } 82 | 83 | 84 | data["repositories"][repository.repository_id] = repositorydata 85 | 86 | try: 87 | with open(self.store_path, "w", encoding="utf-8", errors="ignore") as storefile: 88 | json.dump(data, storefile, indent=4) 89 | except FileNotFoundError: 90 | pass 91 | 92 | def repository(self, repository_id): 93 | """Retrurn the stored repository object, or None.""" 94 | repository_object = None 95 | if repository_id in self.repositories: 96 | repository_object = self.repositories[repository_object] 97 | return repository_object 98 | 99 | def restore_values(self): 100 | """Restore stored values.""" 101 | if os.path.exists(self.store_path): 102 | store = self.read() 103 | if store: 104 | self.frontend_mode = store.get("hacs", {}).get("view", "Grid") 105 | self.schema = store.get("hacs", {}).get("schema") 106 | self.endpoints = store.get("hacs", {}).get("endpoints", {}) 107 | repositories = {} 108 | for repository in store.get("repositories", {}): 109 | repo_id = repository 110 | repository = store["repositories"][repo_id] 111 | 112 | self.logger.info(repository["repository_name"], "restore") 113 | 114 | if repository["repository_type"] == "appdaemon": 115 | repositories[repo_id] = HacsRepositoryAppDaemon(repository["repository_name"]) 116 | 117 | elif repository["repository_type"] == "integration": 118 | repositories[repo_id] = HacsRepositoryIntegration(repository["repository_name"]) 119 | 120 | elif repository["repository_type"] == "plugin": 121 | repositories[repo_id] = HacsRepositoryPlugin(repository["repository_name"]) 122 | 123 | elif repository["repository_type"] == "python_script": 124 | repositories[repo_id] = HacsRepositoryPythonScripts(repository["repository_name"]) 125 | 126 | elif repository["repository_type"] == "theme": 127 | repositories[repo_id] = HacsRepositoryThemes(repository["repository_name"]) 128 | 129 | else: 130 | continue 131 | 132 | repositories[repo_id].description = repository.get("description", "") 133 | repositories[repo_id].installed = repository["installed"] 134 | repositories[repo_id].last_commit = repository.get("last_commit", "") 135 | repositories[repo_id].name = repository["name"] 136 | repositories[repo_id].new = repository.get("new", True) 137 | repositories[repo_id].repository_id = repo_id 138 | repositories[repo_id].topics = repository.get("topics", []) 139 | repositories[repo_id].track = repository.get("track", True) 140 | repositories[repo_id].show_beta = repository.get("show_beta", False) 141 | repositories[repo_id].version_installed = repository.get("version_installed") 142 | repositories[repo_id].last_release_tag = repository.get("last_release_tag") 143 | repositories[repo_id].installed_commit = repository.get("installed_commit") 144 | repositories[repo_id].selected_tag = repository.get("selected_tag") 145 | if repo_id == "172733314": 146 | repositories[repo_id].version_installed = VERSION 147 | self.frontend.append(RepositoryInformationView(repositories[repo_id])) 148 | 149 | self.repositories = repositories 150 | -------------------------------------------------------------------------------- /custom_components/hacs/hacsbase/exceptions.py: -------------------------------------------------------------------------------- 1 | """Custom Exceptions.""" 2 | 3 | 4 | class HacsBaseException(Exception): 5 | """Super basic.""" 6 | 7 | 8 | class HacsUserScrewupException(HacsBaseException): 9 | """Raise this when the user does something they should not do.""" 10 | 11 | 12 | class HacsNotSoBasicException(HacsBaseException): 13 | """Not that basic.""" 14 | 15 | 16 | class HacsDataFileMissing(HacsBaseException): 17 | """Raise this storage datafile is missing.""" 18 | 19 | 20 | class HacsDataNotExpected(HacsBaseException): 21 | """Raise this when data returned from storage is not ok.""" 22 | 23 | 24 | class HacsRepositoryInfo(HacsBaseException): 25 | """Raise this when repository info is missing/wrong.""" 26 | 27 | 28 | class HacsRequirement(HacsBaseException): 29 | """Raise this when repository is missing a requirement.""" 30 | 31 | 32 | class HacsMissingManifest(HacsBaseException): 33 | """Raise this when manifest is missing.""" 34 | 35 | def __init__(self, message="The manifest file is missing in the repository."): 36 | super().__init__(message) 37 | self.message = message 38 | 39 | 40 | class HacsBlacklistException(HacsBaseException): 41 | """Raise this when the repository is currently in the blacklist.""" 42 | 43 | def __init__(self, message="The repository is currently in the blacklist."): 44 | super().__init__(message) 45 | self.message = message 46 | -------------------------------------------------------------------------------- /custom_components/hacs/hacsbase/migration.py: -------------------------------------------------------------------------------- 1 | """HACS Migration logic.""" 2 | import logging 3 | import json 4 | from shutil import copy2 5 | 6 | import aiofiles 7 | 8 | from . import HacsBase 9 | from .const import STORAGE_VERSION, STORENAME 10 | 11 | _LOGGER = logging.getLogger("custom_components.hacs.migration") 12 | 13 | 14 | class HacsMigration(HacsBase): 15 | """HACS data migration handler.""" 16 | 17 | _old = None 18 | 19 | async def validate(self): 20 | """Check the current storage version to determine if migration is needed.""" 21 | self._old = self.store.read() 22 | 23 | if not self._old: 24 | # Could not read the current file, it probably does not exist. 25 | # Running full scan. 26 | await self.update_repositories() 27 | self.store.schema = STORAGE_VERSION 28 | await self.flush_data() 29 | 30 | elif self._old.get("hacs", {}).get("schema") == "1": 31 | # Creating backup. 32 | source = "{}/.storage/hacs".format(self.config_dir) 33 | destination = "{}.1".format(source) 34 | _LOGGER.info("Backing up current file to '%s'", destination) 35 | copy2(source, destination) 36 | await self.from_1_to_2() 37 | await self.from_2_to_3() 38 | await self.flush_data() 39 | await self.from_3_to_4() 40 | 41 | elif self._old.get("hacs", {}).get("schema") == "2": 42 | # Creating backup. 43 | source = "{}/.storage/hacs".format(self.config_dir) 44 | destination = "{}.2".format(source) 45 | _LOGGER.info("Backing up current file to '%s'", destination) 46 | copy2(source, destination) 47 | await self.from_2_to_3() 48 | await self.flush_data() 49 | await self.from_3_to_4() 50 | 51 | elif self._old.get("hacs", {}).get("schema") == "3": 52 | # Creating backup. 53 | source = "{}/.storage/hacs".format(self.config_dir) 54 | destination = "{}.3".format(source) 55 | _LOGGER.info("Backing up current file to '%s'", destination) 56 | copy2(source, destination) 57 | await self.from_3_to_4() 58 | self.store.write() 59 | 60 | elif self._old.get("hacs", {}).get("schema") == STORAGE_VERSION: 61 | pass 62 | 63 | else: 64 | # Should not get here, but do a full scan just in case... 65 | await self.update_repositories() 66 | self.store.schema = STORAGE_VERSION 67 | await self.flush_data() 68 | 69 | async def flush_data(self): 70 | """Flush validated data.""" 71 | _LOGGER.info("Flushing data to storage.") 72 | 73 | if self._old is None: 74 | self.store.write() 75 | return 76 | 77 | datastore = "{}/.storage/{}".format(self.config_dir, STORENAME) 78 | 79 | try: 80 | async with aiofiles.open( 81 | datastore, mode="w", encoding="utf-8", errors="ignore" 82 | ) as outfile: 83 | await outfile.write(json.dumps(self._old, indent=4)) 84 | outfile.close() 85 | 86 | except Exception as error: 87 | msg = "Could not write data to {} - {}".format(datastore, error) 88 | _LOGGER.error(msg) 89 | 90 | async def from_1_to_2(self): 91 | """Migrate from storage version 1 to storage version 2.""" 92 | _LOGGER.info("Starting migration of HACS data from 1 to 2.") 93 | self.data = self._old 94 | 95 | for repository in self._old["repositories"]: 96 | self._old["repositories"][repository]["show_beta"] = False 97 | self._old.get("hacs", {})["schema"] = "2" 98 | _LOGGER.info("Migration of HACS data from 1 to 2 is complete.") 99 | 100 | async def from_2_to_3(self): 101 | """Migrate from storage version 2 to storage version 3.""" 102 | _LOGGER.info("Starting migration of HACS data from 2 to 3.") 103 | self.data = self._old 104 | 105 | for repository in self._old["repositories"]: 106 | if self._old["repositories"][repository]["installed"]: 107 | self._old["repositories"][repository]["new"] = False 108 | self._old.get("hacs", {})["schema"] = "3" 109 | _LOGGER.info("Migration of HACS data from 2 to 3 is complete.") 110 | 111 | async def from_3_to_4(self): 112 | """Migrate from storage version 3 to storage version 4.""" 113 | _LOGGER.info("Starting migration of HACS data from 3 to 4.") 114 | 115 | for repository in self.store.repositories: 116 | repository = self.store.repositories[repository] 117 | await repository.set_repository() 118 | self.store.schema = "4" 119 | _LOGGER.info("Migration of HACS data from 3 to 4 is complete.") 120 | -------------------------------------------------------------------------------- /custom_components/hacs/handler/__init__.py: -------------------------------------------------------------------------------- 1 | """Initialize handlers.""" 2 | -------------------------------------------------------------------------------- /custom_components/hacs/handler/__pycache__/__init__.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaeldvinci/Home-AssistantConfig/b45a163344af5e6dd48c95bc063c2645f4386b91/custom_components/hacs/handler/__pycache__/__init__.cpython-37.pyc -------------------------------------------------------------------------------- /custom_components/hacs/handler/__pycache__/download.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaeldvinci/Home-AssistantConfig/b45a163344af5e6dd48c95bc063c2645f4386b91/custom_components/hacs/handler/__pycache__/download.cpython-37.pyc -------------------------------------------------------------------------------- /custom_components/hacs/handler/__pycache__/logger.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaeldvinci/Home-AssistantConfig/b45a163344af5e6dd48c95bc063c2645f4386b91/custom_components/hacs/handler/__pycache__/logger.cpython-37.pyc -------------------------------------------------------------------------------- /custom_components/hacs/handler/__pycache__/template.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaeldvinci/Home-AssistantConfig/b45a163344af5e6dd48c95bc063c2645f4386b91/custom_components/hacs/handler/__pycache__/template.cpython-37.pyc -------------------------------------------------------------------------------- /custom_components/hacs/handler/download.py: -------------------------------------------------------------------------------- 1 | """Download.""" 2 | import gzip 3 | import logging 4 | import shutil 5 | 6 | import aiofiles 7 | import async_timeout 8 | 9 | import backoff 10 | from homeassistant.helpers.aiohttp_client import async_get_clientsession 11 | from ..hacsbase.exceptions import HacsNotSoBasicException 12 | 13 | _LOGGER = logging.getLogger("custom_components.hacs.download") 14 | 15 | 16 | @backoff.on_exception(backoff.expo, Exception, max_tries=5) 17 | async def async_download_file(hass, url): 18 | """ 19 | Download files, and return the content. 20 | """ 21 | if url is None: 22 | return 23 | 24 | # There is a bug somewhere... TODO: Find that bug.... 25 | if "tags/" in url: 26 | url = url.replace("tags/", "") 27 | 28 | _LOGGER.debug("Donwloading %s", url) 29 | 30 | result = None 31 | 32 | with async_timeout.timeout(5, loop=hass.loop): 33 | request = await async_get_clientsession(hass).get(url) 34 | 35 | # Make sure that we got a valid result 36 | if request.status == 200: 37 | result = await request.read() 38 | else: 39 | raise HacsNotSoBasicException( 40 | "Got status code {} when trying to download {}".format( 41 | request.status, url 42 | ) 43 | ) 44 | 45 | return result 46 | 47 | 48 | async def async_save_file(location, content): 49 | """Save files.""" 50 | if "-bundle" in location: 51 | location = location.replace("-bundle", "") 52 | if "lovelace-" in location.split("/")[-1]: 53 | search = location.split("/")[-1] 54 | replace = search.replace("lovelace-", "") 55 | location = location.replace(search, replace) 56 | 57 | _LOGGER.debug("Saving %s", location) 58 | mode = "w" 59 | encoding = "utf-8" 60 | errors = "ignore" 61 | 62 | if not isinstance(content, str): 63 | mode = "wb" 64 | encoding = None 65 | errors = None 66 | 67 | try: 68 | async with aiofiles.open( 69 | location, mode=mode, encoding=encoding, errors=errors 70 | ) as outfile: 71 | await outfile.write(content) 72 | outfile.close() 73 | 74 | except Exception as error: # pylint: disable=broad-except 75 | msg = "Could not write data to {} - {}".format(location, error) 76 | _LOGGER.debug(msg) 77 | 78 | # Create gz for .js files 79 | if location.endswith(".js") or location.endswith(".css"): 80 | with open(location, "rb") as f_in: 81 | with gzip.open(location + ".gz", "wb") as f_out: 82 | shutil.copyfileobj(f_in, f_out) 83 | -------------------------------------------------------------------------------- /custom_components/hacs/handler/logger.py: -------------------------------------------------------------------------------- 1 | """HacsLogger""" 2 | 3 | class HacsLogger(): 4 | """Custom logger class for HACS.""" 5 | import logging 6 | prefix = "custom_components.hacs" 7 | 8 | def debug(self, message, part=None): 9 | """Info messages.""" 10 | if part is None: 11 | part = self.prefix 12 | else: 13 | part = "{}.{}".format(self.prefix, part) 14 | self.logging.getLogger(part).debug(message) 15 | 16 | def info(self, message, part=None): 17 | """Info messages.""" 18 | if part is None: 19 | part = self.prefix 20 | else: 21 | part = "{}.{}".format(self.prefix, part) 22 | self.logging.getLogger(part).info(message) 23 | 24 | def warning(self, message, part=None): 25 | """Info messages.""" 26 | if part is None: 27 | part = self.prefix 28 | else: 29 | part = "{}.{}".format(self.prefix, part) 30 | self.logging.getLogger(part).warning(message) 31 | 32 | def error(self, message, part=None): 33 | """Info messages.""" 34 | if part is None: 35 | part = self.prefix 36 | else: 37 | part = "{}.{}".format(self.prefix, part) 38 | self.logging.getLogger(part).error(message) 39 | 40 | def critical(self, message, part=None): 41 | """Info messages.""" 42 | if part is None: 43 | part = self.prefix 44 | else: 45 | part = "{}.{}".format(self.prefix, part) 46 | self.logging.getLogger(part).critical(message) 47 | -------------------------------------------------------------------------------- /custom_components/hacs/handler/template.py: -------------------------------------------------------------------------------- 1 | """Custom template support.""" 2 | from jinja2 import Template 3 | 4 | def render_template(content, context): 5 | """Render templates in content.""" 6 | # Fix None issues 7 | if context.last_release_object is not None: 8 | prerelease = context.last_release_object.prerelease 9 | else: 10 | prerelease = False 11 | 12 | 13 | 14 | 15 | # Render the template 16 | try: 17 | render = Template(content) 18 | render = render.render( 19 | installed=context.installed, 20 | pending_update=context.pending_update, 21 | prerelease=prerelease, 22 | selected_tag=context.selected_tag, 23 | version_available=context.last_release_tag, 24 | version_installed=context.version_installed 25 | ) 26 | return render 27 | except Exception as exception: 28 | context.logger.warning("Error rendering info template {}".format(exception), "template") 29 | return content 30 | -------------------------------------------------------------------------------- /custom_components/hacs/http.py: -------------------------------------------------------------------------------- 1 | """Blueprint for HacsWebResponse.""" 2 | import os 3 | import random 4 | import sys 5 | import traceback 6 | from homeassistant.components.http import HomeAssistantView 7 | from jinja2 import Environment, PackageLoader 8 | from aiohttp import web 9 | 10 | from .hacsbase import HacsBase 11 | from .repositories.repositoryinformationview import RepositoryInformationView 12 | 13 | WEBRESPONSE = {} 14 | 15 | def webresponse(classname): 16 | """Decorator used to register Web Responses.""" 17 | WEBRESPONSE[classname.endpoint] = classname 18 | return classname 19 | 20 | class HacsWebResponse(HomeAssistantView, HacsBase): 21 | """Base View Class for HACS.""" 22 | requires_auth = False 23 | name = "hacs" 24 | 25 | def __init__(self): 26 | """Initialize.""" 27 | self.url = self.hacsweb + "/{path:.+}" 28 | self.endpoint = None 29 | self.postdata = None 30 | self.raw_headers = None 31 | self.request = None 32 | self.requested_file = None 33 | 34 | async def get(self, request, path): # pylint: disable=unused-argument 35 | """Handle HACS Web requests.""" 36 | self.endpoint = path.split("/")[0] 37 | self.raw_headers = request.raw_headers 38 | self.request = request 39 | self.requested_file = path.replace(self.endpoint+"/", "") 40 | self.repository_id = path.replace(self.endpoint+"/", "") 41 | self.logger.debug("Endpoint ({}) called".format(self.endpoint), "web") 42 | if self.config.dev: 43 | self.logger.debug("Raw headers ({})".format(self.raw_headers), "web") 44 | self.logger.debug("Postdata ({})".format(self.postdata), "web") 45 | if self.endpoint in WEBRESPONSE: 46 | response = WEBRESPONSE[self.endpoint] 47 | response = await response.response(self) 48 | else: 49 | # Return default response. 50 | response = await WEBRESPONSE["error"].response(self) 51 | 52 | # set headers 53 | response.headers["Cache-Control"] = "max-age=0, must-revalidate" 54 | 55 | # serve the response 56 | return response 57 | 58 | def render(self, templatefile, location=None, repository=None, message=None): 59 | """Render a template file.""" 60 | loader = Environment(loader=PackageLoader('custom_components.hacs.frontend')) 61 | template = loader.get_template(templatefile + '.html') 62 | return template.render({"hacs": self, "location": location, "repository": repository, "message": message}) 63 | 64 | 65 | class HacsPluginView(HacsWebResponse): 66 | """Serve plugins.""" 67 | name = "hacs:plugin" 68 | 69 | def __init__(self): 70 | """Initialize.""" 71 | self.url = r"/community_plugin/{requested_file:.+}" 72 | 73 | async def get(self, request, requested_file): # pylint: disable=unused-argument 74 | """Serve plugins for lovelace.""" 75 | try: 76 | # Strip '?' from URL 77 | if "?" in requested_file: 78 | requested_file = requested_file.split("?")[0] 79 | 80 | file = "{}/www/community/{}".format(self.config_dir, requested_file) 81 | 82 | # Serve .gz if it exist 83 | if os.path.exists(file + ".gz"): 84 | file += ".gz" 85 | 86 | response = None 87 | if os.path.exists(file): 88 | self.logger.debug("Serving {} from {}".format(requested_file, file)) 89 | response = web.FileResponse(file) 90 | response.headers["Cache-Control"] = "max-age=0, must-revalidate" 91 | else: 92 | self.logger.debug("Tried to serve up '%s' but it does not exist", file) 93 | response = web.Response(status=404) 94 | 95 | except Exception as error: # pylint: disable=broad-except 96 | self.logger.debug( 97 | "there was an issue trying to serve {} - {}".format(requested_file, error 98 | )) 99 | response = web.Response(status=404) 100 | 101 | return response 102 | 103 | class HacsPlugin(HacsPluginView): 104 | """Alias for HacsPluginView.""" 105 | def __init__(self): 106 | """Initialize.""" 107 | self.url = r"/hacsplugin/{requested_file:.+}" 108 | 109 | 110 | 111 | @webresponse 112 | class Settings(HacsWebResponse): 113 | """Serve HacsSettingsView.""" 114 | endpoint = "settings" 115 | async def response(self): 116 | """Serve HacsOverviewView.""" 117 | message = self.request.rel_url.query.get("message") 118 | render = self.render('settings', message=message) 119 | return web.Response(body=render, content_type="text/html", charset="utf-8") 120 | 121 | 122 | @webresponse 123 | class Static(HacsWebResponse): 124 | """Serve static files.""" 125 | endpoint = "static" 126 | async def response(self): 127 | """Serve static files.""" 128 | servefile = "{}/custom_components/hacs/frontend/elements/{}".format( 129 | self.config_dir, self.requested_file 130 | ) 131 | if os.path.exists(servefile + ".gz"): 132 | return web.FileResponse(servefile + ".gz") 133 | else: 134 | if os.path.exists(servefile): 135 | return web.FileResponse(servefile) 136 | else: 137 | return web.Response(status=404) 138 | 139 | 140 | @webresponse 141 | class Store(HacsWebResponse): 142 | """Serve HacsOverviewView.""" 143 | endpoint = "store" 144 | async def response(self): 145 | """Serve HacsStoreView.""" 146 | render = self.render('overviews', 'store') 147 | return web.Response(body=render, content_type="text/html", charset="utf-8") 148 | 149 | 150 | @webresponse 151 | class Overview(HacsWebResponse): 152 | """Serve HacsOverviewView.""" 153 | endpoint = "overview" 154 | async def response(self): 155 | """Serve HacsOverviewView.""" 156 | render = self.render('overviews', 'overview') 157 | return web.Response(body=render, content_type="text/html", charset="utf-8") 158 | 159 | 160 | @webresponse 161 | class Repository(HacsWebResponse): 162 | """Serve HacsRepositoryView.""" 163 | endpoint = "repository" 164 | async def response(self): 165 | """Serve HacsRepositoryView.""" 166 | message = self.request.rel_url.query.get("message") 167 | repository = self.store.repositories[str(self.repository_id)] 168 | if not repository.updated_info: 169 | await repository.set_repository() 170 | await repository.update() 171 | repository.updated_info = True 172 | self.store.write() 173 | 174 | if repository.new: 175 | repository.new = False 176 | self.store.write() 177 | 178 | repository = RepositoryInformationView(repository) 179 | render = self.render('repository', repository=repository, message=message) 180 | return web.Response(body=render, content_type="text/html", charset="utf-8") 181 | 182 | 183 | @webresponse 184 | class Error(HacsWebResponse): 185 | """Serve error page.""" 186 | endpoint = "error" 187 | async def response(self): 188 | """Serve error page.""" 189 | try: 190 | # Get last error 191 | ex_type, ex_value, ex_traceback = sys.exc_info() 192 | trace_back = traceback.extract_tb(ex_traceback) 193 | stack_trace = list() 194 | 195 | for trace in trace_back: 196 | stack_trace.append( 197 | "File : {} , Line : {}, Func.Name : {}, Message : {}", 198 | format(trace[0], trace[1], trace[2], trace[3]), 199 | ) 200 | 201 | # HARD styling 202 | stacks = "" 203 | for stack in stack_trace: 204 | stacks += stack 205 | stacks = stacks.replace( 206 | "File :", 207 | "
---------------------------------------------------------------
File :", 208 | ) 209 | stacks = stacks.replace(", Line :", "
Line :") 210 | stacks = stacks.replace(", Func.Name :", "
Func.Name :") 211 | stacks = stacks.replace(", Message :", "
Message :")[86:-1] 212 | 213 | if ex_type is not None: 214 | codeblock = """ 215 |

Exception type: {}

216 |

Exception message: {}

217 | {} 218 | """.format( 219 | ex_type.__name__, ex_value, stacks 220 | ) 221 | else: 222 | codeblock = "" 223 | 224 | # Generate content 225 | content = """ 226 |
227 |

Something is wrong...

228 | Error code: {} 229 | {} 230 |
231 |
232 | OPEN ISSUE 234 |
235 |
236 | 237 |
238 | """.format( 239 | random.choice(self.hacsconst.ERROR), codeblock, self.const.ISSUE_URL) 240 | 241 | except Exception as exception: 242 | message = "GREAT!, even the error page is broken... ({})".format(exception) 243 | self.logger.error(message) 244 | content = "

" + message + "

" 245 | 246 | return web.Response(body=self.render('error', message=content), content_type="text/html", charset="utf-8") 247 | -------------------------------------------------------------------------------- /custom_components/hacs/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "domain": "hacs", 3 | "name": "HACS (Home Assistant Community Store)", 4 | "documentation": "https://custom-components.github.io/hacs", 5 | "dependencies": [], 6 | "codeowners": ["@ludeeus"], 7 | "requirements": ["aiofiles==0.4.0", "backoff==1.8.0", "packaging==19.0"] 8 | } -------------------------------------------------------------------------------- /custom_components/hacs/repositories/__init__.py: -------------------------------------------------------------------------------- 1 | """Initialize repositories.""" -------------------------------------------------------------------------------- /custom_components/hacs/repositories/__pycache__/__init__.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaeldvinci/Home-AssistantConfig/b45a163344af5e6dd48c95bc063c2645f4386b91/custom_components/hacs/repositories/__pycache__/__init__.cpython-37.pyc -------------------------------------------------------------------------------- /custom_components/hacs/repositories/__pycache__/hacsrepositoryappdaemon.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaeldvinci/Home-AssistantConfig/b45a163344af5e6dd48c95bc063c2645f4386b91/custom_components/hacs/repositories/__pycache__/hacsrepositoryappdaemon.cpython-37.pyc -------------------------------------------------------------------------------- /custom_components/hacs/repositories/__pycache__/hacsrepositorybase.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaeldvinci/Home-AssistantConfig/b45a163344af5e6dd48c95bc063c2645f4386b91/custom_components/hacs/repositories/__pycache__/hacsrepositorybase.cpython-37.pyc -------------------------------------------------------------------------------- /custom_components/hacs/repositories/__pycache__/hacsrepositorybaseplugin.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaeldvinci/Home-AssistantConfig/b45a163344af5e6dd48c95bc063c2645f4386b91/custom_components/hacs/repositories/__pycache__/hacsrepositorybaseplugin.cpython-37.pyc -------------------------------------------------------------------------------- /custom_components/hacs/repositories/__pycache__/hacsrepositoryintegration.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaeldvinci/Home-AssistantConfig/b45a163344af5e6dd48c95bc063c2645f4386b91/custom_components/hacs/repositories/__pycache__/hacsrepositoryintegration.cpython-37.pyc -------------------------------------------------------------------------------- /custom_components/hacs/repositories/__pycache__/hacsrepositorypythonscript.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaeldvinci/Home-AssistantConfig/b45a163344af5e6dd48c95bc063c2645f4386b91/custom_components/hacs/repositories/__pycache__/hacsrepositorypythonscript.cpython-37.pyc -------------------------------------------------------------------------------- /custom_components/hacs/repositories/__pycache__/hacsrepositorytheme.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaeldvinci/Home-AssistantConfig/b45a163344af5e6dd48c95bc063c2645f4386b91/custom_components/hacs/repositories/__pycache__/hacsrepositorytheme.cpython-37.pyc -------------------------------------------------------------------------------- /custom_components/hacs/repositories/__pycache__/repositoryinformation.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaeldvinci/Home-AssistantConfig/b45a163344af5e6dd48c95bc063c2645f4386b91/custom_components/hacs/repositories/__pycache__/repositoryinformation.cpython-37.pyc -------------------------------------------------------------------------------- /custom_components/hacs/repositories/__pycache__/repositoryinformationview.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaeldvinci/Home-AssistantConfig/b45a163344af5e6dd48c95bc063c2645f4386b91/custom_components/hacs/repositories/__pycache__/repositoryinformationview.cpython-37.pyc -------------------------------------------------------------------------------- /custom_components/hacs/repositories/hacsrepositoryappdaemon.py: -------------------------------------------------------------------------------- 1 | """Blueprint for HacsRepositoryAppDaemon.""" 2 | # pylint: disable=too-many-instance-attributes,invalid-name,broad-except,access-member-before-definition 3 | import logging 4 | 5 | from .hacsrepositorybase import HacsRepositoryBase 6 | from ..hacsbase.exceptions import HacsRequirement 7 | 8 | _LOGGER = logging.getLogger("custom_components.hacs.repository") 9 | 10 | 11 | class HacsRepositoryAppDaemon(HacsRepositoryBase): 12 | """ 13 | Set up a HacsRepositoryAppDaemon object. 14 | 15 | repository_name(str): The full name of a repository 16 | (example: awesome-dev/awesome-repo) 17 | """ 18 | 19 | def __init__(self, repository_name: str, repositoryobject=None): 20 | """Initialize a HacsRepositoryAppDaemon object.""" 21 | super().__init__() 22 | self.repository = repositoryobject 23 | self.repository_name = repository_name 24 | self.repository_type = "appdaemon" 25 | self.manifest_content = None 26 | self.name = repository_name.split("/")[-1] 27 | 28 | async def update(self): 29 | """Run update tasks.""" 30 | if await self.common_update(): 31 | return 32 | await self.set_repository_content() 33 | 34 | async def set_repository_content(self): 35 | """Set repository content attributes.""" 36 | contentfiles = [] 37 | 38 | if self.content_path is None: 39 | first = await self.repository.get_contents("apps", self.ref) 40 | 41 | self.content_path = first[0].path 42 | 43 | self.name = first[0].name 44 | 45 | self.content_objects = await self.repository.get_contents( 46 | self.content_path, self.ref 47 | ) 48 | 49 | if not isinstance(self.content_objects, list): 50 | raise HacsRequirement("Repository structure does not meet the requirements") 51 | 52 | for filename in self.content_objects: 53 | contentfiles.append(filename.name) 54 | 55 | if contentfiles: 56 | self.content_files = contentfiles 57 | -------------------------------------------------------------------------------- /custom_components/hacs/repositories/hacsrepositorybaseplugin.py: -------------------------------------------------------------------------------- 1 | """Blueprint for HacsRepositoryPlugin.""" 2 | # pylint: disable=too-many-instance-attributes,invalid-name,broad-except,access-member-before-definition 3 | import logging 4 | import json 5 | 6 | from .hacsrepositorybase import HacsRepositoryBase 7 | from ..aiogithub.exceptions import AIOGitHubException 8 | from ..hacsbase.exceptions import HacsRequirement 9 | 10 | _LOGGER = logging.getLogger("custom_components.hacs.repository") 11 | 12 | 13 | class HacsRepositoryPlugin(HacsRepositoryBase): 14 | """ 15 | Set up a HacsRepositoryIntegration object. 16 | 17 | repository_name(str): The full name of a repository 18 | (example: awesome-dev/awesome-repo) 19 | """ 20 | 21 | def __init__(self, repository_name: str, repositoryobject=None): 22 | """Initialize a HacsRepositoryIntegration object.""" 23 | super().__init__() 24 | self.repository = repositoryobject 25 | self.repository_name = repository_name 26 | self.repository_type = "plugin" 27 | self.manifest_content = None 28 | self.javascript_type = None 29 | self.name = repository_name.split("/")[-1] 30 | 31 | async def parse_readme_for_jstype(self): 32 | """Parse the readme looking for js type.""" 33 | readme = None 34 | readme_files = ["readme", "readme.md"] 35 | root = await self.repository.get_contents("") 36 | for file in root: 37 | if file.name.lower() in readme_files: 38 | readme = await self.repository.get_contents(file.name) 39 | break 40 | 41 | if readme is None: 42 | return 43 | 44 | readme = readme.content 45 | for line in readme.splitlines(): 46 | if "type: module" in line: 47 | self.javascript_type = "module" 48 | break 49 | elif "type: js" in line: 50 | self.javascript_type = "js" 51 | break 52 | 53 | async def update(self): 54 | """Run update tasks.""" 55 | if await self.common_update(): 56 | return 57 | try: 58 | await self.parse_readme_for_jstype() 59 | except AIOGitHubException: 60 | # This can fail, no big deal. 61 | pass 62 | 63 | await self.set_repository_content() 64 | 65 | try: 66 | await self.get_package_content() 67 | except AIOGitHubException: 68 | # This can fail, no big deal. 69 | pass 70 | 71 | 72 | async def set_repository_content(self): 73 | """Set repository content attributes.""" 74 | if self.content_path is None or self.content_path == "dist": 75 | # Try fetching data from REPOROOT/dist 76 | try: 77 | files = [] 78 | objects = await self.repository.get_contents("dist", self.ref) 79 | for item in objects: 80 | if item.name.endswith(".js"): 81 | files.append(item.name) 82 | 83 | # Handler for plug requirement 3 84 | find_file_name = "{}.js".format(self.name.replace("lovelace-", "")) 85 | if find_file_name in files or "{}.js".format(self.name) in files: 86 | # YES! We got it! 87 | self.content_path = "dist" 88 | self.content_objects = objects 89 | self.content_files = files 90 | 91 | except AIOGitHubException: 92 | pass 93 | 94 | if self.content_path is None or self.content_path == "release": 95 | # Try fetching data from Release 96 | try: 97 | files = [] 98 | if self.last_release_object is not None: 99 | if self.last_release_object.assets is not None: 100 | for item in self.last_release_object.assets: 101 | if item.name.endswith(".js"): 102 | files.append(item.name) 103 | 104 | # Handler for plugin requirement 3 105 | find_file_name1 = "{}.js".format(self.name) 106 | find_file_name2 = "{}-bundle.js".format(self.name) 107 | if find_file_name1 in files or find_file_name2 in files: 108 | # YES! We got it! 109 | self.content_path = "release" 110 | self.content_objects = self.last_release_object.assets 111 | self.content_files = files 112 | 113 | except AIOGitHubException: 114 | pass 115 | 116 | if self.content_path is None or self.content_path == "": 117 | # Try fetching data from REPOROOT 118 | try: 119 | files = [] 120 | objects = await self.repository.get_contents("", self.ref) 121 | for item in objects: 122 | if item.name.endswith(".js"): 123 | files.append(item.name) 124 | 125 | # Handler for plugin requirement 3 126 | find_file_name = "{}.js".format(self.name.replace("lovelace-", "")) 127 | if find_file_name in files or "{}.js".format(self.name) in files: 128 | # YES! We got it! 129 | self.content_path = "" 130 | self.content_objects = objects 131 | self.content_files = files 132 | 133 | except AIOGitHubException: 134 | pass 135 | 136 | if not self.content_files or not self.content_objects: 137 | raise HacsRequirement("No acceptable js files found") 138 | 139 | async def get_package_content(self): 140 | """Get package content.""" 141 | package = None 142 | 143 | package = await self.repository.get_contents("package.json") 144 | package = json.loads(package.content) 145 | 146 | if package: 147 | self.authors = package["author"] 148 | -------------------------------------------------------------------------------- /custom_components/hacs/repositories/hacsrepositoryintegration.py: -------------------------------------------------------------------------------- 1 | """Blueprint for HacsRepositoryIntegration.""" 2 | # pylint: disable=too-many-instance-attributes,invalid-name,broad-except,access-member-before-definition 3 | import logging 4 | import json 5 | 6 | from .hacsrepositorybase import HacsRepositoryBase 7 | from ..hacsbase.exceptions import HacsRequirement 8 | 9 | _LOGGER = logging.getLogger("custom_components.hacs.repository") 10 | 11 | 12 | class HacsRepositoryIntegration(HacsRepositoryBase): 13 | """ 14 | Set up a HacsRepositoryIntegration object. 15 | 16 | repository_name(str): The full name of a repository 17 | (example: awesome-dev/awesome-repo) 18 | """ 19 | 20 | def __init__(self, repository_name: str, repositoryobject=None): 21 | """Initialize a HacsRepositoryIntegration object.""" 22 | super().__init__() 23 | self.repository = repositoryobject 24 | self.repository_name = repository_name 25 | self.repository_type = "integration" 26 | self.manifest_content = None 27 | self.domain = None 28 | self.name = repository_name.split("/")[-1] 29 | 30 | @property 31 | def config_flow(self): 32 | """Return bool if integration has config_flow.""" 33 | if self.manifest_content is None: 34 | return self.manifest_content.get("config_flow", False) 35 | return False 36 | 37 | async def reload_config_flows(self): 38 | """Reload config flows in HA.""" 39 | 40 | async def update(self): 41 | """Run update tasks.""" 42 | if await self.common_update(): 43 | return 44 | await self.set_repository_content() 45 | await self.set_manifest_content() 46 | 47 | async def set_repository_content(self): 48 | """Set repository content attributes.""" 49 | contentfiles = [] 50 | 51 | if self.content_path is None: 52 | first = await self.repository.get_contents("custom_components", self.ref) 53 | 54 | self.content_path = first[0].path 55 | 56 | self.content_objects = await self.repository.get_contents( 57 | self.content_path, self.ref 58 | ) 59 | 60 | if not isinstance(self.content_objects, list): 61 | raise HacsRequirement("Repository structure does not meet the requirements") 62 | 63 | for filename in self.content_objects: 64 | contentfiles.append(filename.name) 65 | 66 | if contentfiles: 67 | self.content_files = contentfiles 68 | 69 | async def set_manifest_content(self): 70 | """Set manifest content.""" 71 | manifest_path = "{}/manifest.json".format(self.content_path) 72 | manifest = None 73 | 74 | if "manifest.json" not in self.content_files: 75 | raise HacsRequirement("manifest.json is missing.") 76 | 77 | manifest = await self.repository.get_contents(manifest_path, self.ref) 78 | manifest = json.loads(manifest.content) 79 | 80 | if manifest: 81 | self.manifest_content = manifest 82 | self.authors = manifest["codeowners"] 83 | self.name = manifest["name"] 84 | self.domain = manifest["domain"] 85 | self.homeassistant_version = manifest.get("homeassistant") 86 | return 87 | 88 | raise HacsRequirement("manifest.json does not contain expected values.") 89 | -------------------------------------------------------------------------------- /custom_components/hacs/repositories/hacsrepositorypythonscript.py: -------------------------------------------------------------------------------- 1 | """Blueprint for HacsRepositoryPythonScripts.""" 2 | # pylint: disable=too-many-instance-attributes,invalid-name,broad-except,access-member-before-definition 3 | import logging 4 | 5 | from .hacsrepositorybase import HacsRepositoryBase 6 | from ..hacsbase.exceptions import HacsRequirement 7 | 8 | _LOGGER = logging.getLogger("custom_components.hacs.repository") 9 | 10 | 11 | class HacsRepositoryPythonScripts(HacsRepositoryBase): 12 | """ 13 | Set up a HacsRepositoryPythonScripts object. 14 | 15 | repository_name(str): The full name of a repository 16 | (example: awesome-dev/awesome-repo) 17 | """ 18 | 19 | def __init__(self, repository_name: str, repositoryobject=None): 20 | """Initialize a HacsRepositoryPythonScripts object.""" 21 | super().__init__() 22 | self.repository = repositoryobject 23 | self.repository_name = repository_name 24 | self.repository_type = "python_script" 25 | self.manifest_content = None 26 | self.name = repository_name.split("/")[-1] 27 | 28 | async def update(self): 29 | """Run update tasks.""" 30 | if await self.common_update(): 31 | return 32 | await self.set_repository_content() 33 | 34 | async def set_repository_content(self): 35 | """Set repository content attributes.""" 36 | contentfiles = [] 37 | 38 | if self.content_path is None: 39 | self.content_objects = await self.repository.get_contents( 40 | "python_scripts", self.ref 41 | ) 42 | 43 | self.content_path = self.content_objects[0].path 44 | 45 | self.name = self.content_objects[0].name.replace(".py", "") 46 | 47 | if not isinstance(self.content_objects, list): 48 | raise HacsRequirement("Repository structure does not meet the requirements") 49 | 50 | for filename in self.content_objects: 51 | contentfiles.append(filename.name) 52 | 53 | if contentfiles: 54 | self.content_files = contentfiles 55 | -------------------------------------------------------------------------------- /custom_components/hacs/repositories/hacsrepositorytheme.py: -------------------------------------------------------------------------------- 1 | """Blueprint for HacsRepositoryThemes.""" 2 | # pylint: disable=too-many-instance-attributes,invalid-name,broad-except,access-member-before-definition 3 | import logging 4 | 5 | from .hacsrepositorybase import HacsRepositoryBase 6 | from ..hacsbase.exceptions import HacsRequirement 7 | 8 | _LOGGER = logging.getLogger("custom_components.hacs.repository") 9 | 10 | 11 | class HacsRepositoryThemes(HacsRepositoryBase): 12 | """ 13 | Set up a HacsRepositoryThemes object. 14 | 15 | repository_name(str): The full name of a repository 16 | (example: awesome-dev/awesome-repo) 17 | """ 18 | 19 | def __init__(self, repository_name: str, repositoryobject=None): 20 | """Initialize a HacsRepositoryThemes object.""" 21 | super().__init__() 22 | self.repository = repositoryobject 23 | self.repository_name = repository_name 24 | self.repository_type = "theme" 25 | self.manifest_content = None 26 | self.name = repository_name.split("/")[-1] 27 | 28 | async def update(self): 29 | """Run update tasks.""" 30 | if await self.common_update(): 31 | return 32 | await self.set_repository_content() 33 | 34 | async def set_repository_content(self): 35 | """Set repository content attributes.""" 36 | contentfiles = [] 37 | 38 | if self.content_path is None: 39 | self.content_objects = await self.repository.get_contents( 40 | "themes", self.ref 41 | ) 42 | 43 | self.content_path = self.content_objects[0].path 44 | 45 | self.name = self.content_objects[0].name.replace(".yaml", "") 46 | 47 | if not isinstance(self.content_objects, list): 48 | raise HacsRequirement("Repository structure does not meet the requirements") 49 | 50 | for filename in self.content_objects: 51 | contentfiles.append(filename.name) 52 | 53 | if contentfiles: 54 | self.content_files = contentfiles 55 | -------------------------------------------------------------------------------- /custom_components/hacs/repositories/repositoryinformation.py: -------------------------------------------------------------------------------- 1 | """RepositoryInformation, used for the frontend.""" 2 | # pylint: disable=missing-docstring 3 | 4 | class RepositoryInformation: 5 | 6 | def __init__(self, repository): 7 | self.repository = repository 8 | 9 | @property 10 | def name(self): 11 | if self.repository.repository_type != "integration": 12 | if self.repository.name: 13 | return self.repository.name.replace("-", " ").replace("_", " ").title() 14 | else: 15 | return self.repository_name.split("/")[-1] 16 | return self.repository.name 17 | 18 | @property 19 | def description(self): 20 | if self.repository.description is None: 21 | return "" 22 | return self.repository.description 23 | 24 | @property 25 | def custom(self): 26 | return self.repository.custom 27 | 28 | @property 29 | def new(self): 30 | return self.repository.new 31 | 32 | @property 33 | def track(self): 34 | return self.repository.track 35 | 36 | @property 37 | def hide(self): 38 | return self.repository.hide 39 | 40 | @property 41 | def installed(self): 42 | return self.repository.installed 43 | 44 | @property 45 | def repository_type(self): 46 | return self.repository.repository_type 47 | 48 | @property 49 | def repository_id(self): 50 | return self.repository.repository_id 51 | 52 | @property 53 | def repository_name(self): 54 | return self.repository.repository_name 55 | 56 | @property 57 | def selected_tag(self): 58 | return self.repository.selected_tag 59 | 60 | @property 61 | def published_tags(self): 62 | return self.repository.published_tags 63 | 64 | @property 65 | def default_branch(self): 66 | return self.repository.repository.default_branch 67 | 68 | @property 69 | def homeassistant_version(self): 70 | return self.repository.homeassistant_version 71 | 72 | @property 73 | def status(self): 74 | if self.repository.pending_restart: 75 | status = "pending-restart" 76 | elif self.repository.pending_update: 77 | status = "pending-update" 78 | elif self.repository.installed: 79 | status = "installed" 80 | else: 81 | status = "default" 82 | return status 83 | 84 | @property 85 | def status_description(self): 86 | description = { 87 | "default": "Not installed.", 88 | "pending-restart": "Restart pending.", 89 | "pending-update": "Update pending.", 90 | "installed": "No action required." 91 | } 92 | return description[self.status] 93 | 94 | @property 95 | def installed_version(self): 96 | if self.repository.version_installed is not None: 97 | installed = self.repository.version_installed 98 | else: 99 | if self.repository.installed_commit is not None: 100 | installed = self.repository.installed_commit 101 | else: 102 | installed = "" 103 | return installed 104 | 105 | @property 106 | def available_version(self): 107 | if self.repository.last_release_tag is not None: 108 | available = self.repository.last_release_tag 109 | else: 110 | if self.repository.last_commit is not None: 111 | available = self.repository.last_commit 112 | else: 113 | available = "" 114 | return available 115 | 116 | @property 117 | def topics(self): 118 | return str(self.repository.topics) 119 | 120 | @property 121 | def authors(self): 122 | if self.repository.authors: 123 | return str(self.repository.authors) 124 | return "" 125 | -------------------------------------------------------------------------------- /custom_components/hacs/repositories/repositoryinformationview.py: -------------------------------------------------------------------------------- 1 | """RepositoryInformationView, used for the frontend.""" 2 | # pylint: disable=missing-docstring 3 | from distutils.version import LooseVersion 4 | from homeassistant.const import __version__ as HAVERSION 5 | from .repositoryinformation import RepositoryInformation 6 | 7 | class RepositoryInformationView(RepositoryInformation): 8 | 9 | @property 10 | def additional_info(self): 11 | if self.repository.additional_info is not None: 12 | return self.repository.additional_info 13 | return "" 14 | 15 | @property 16 | def main_action(self): 17 | actions = { 18 | "default": "INSTALL", 19 | "installed": "REINSTALL", 20 | "pending-restart": "REINSTALL", 21 | "pending-update": "UPGRADE" 22 | } 23 | return actions[self.status] 24 | 25 | 26 | @property 27 | def display_authors(self): 28 | if self.repository.authors: 29 | if self.repository.repository_type == "integration": 30 | authors = "

Author(s): " 31 | for author in self.repository.authors: 32 | if "@" in author: 33 | author = author.split("@")[-1] 34 | authors += " @{author}".format( 35 | author=author 36 | ) 37 | authors += "

" 38 | else: 39 | authors = "

Author: {}

".format(self.repository.authors) 40 | else: 41 | authors = "" 42 | return authors 43 | 44 | @property 45 | def local_path(self): 46 | return self.repository.local_path 47 | 48 | @property 49 | def javascript_type(self): 50 | return self.repository.javascript_type 51 | 52 | @property 53 | def show_beta(self): 54 | return self.repository.show_beta 55 | 56 | @property 57 | def full_name(self): 58 | return self.repository_name.split("/")[-1] 59 | -------------------------------------------------------------------------------- /custom_components/hacs/sensor.py: -------------------------------------------------------------------------------- 1 | """Sensor platform for HACS.""" 2 | from homeassistant.helpers.entity import Entity 3 | from .hacsbase import HacsBase as hacs 4 | 5 | 6 | async def async_setup_platform( 7 | hass, config, async_add_entities, discovery_info=None 8 | ): # pylint: disable=unused-argument 9 | """Setup sensor platform.""" 10 | async_add_entities([HACSSensor()]) 11 | 12 | 13 | class HACSSensor(Entity): 14 | """HACS Sensor class.""" 15 | 16 | def __init__(self): 17 | """Initialize.""" 18 | self._state = None 19 | self.has_update = [] 20 | 21 | async def async_update(self): 22 | """Update the sensor.""" 23 | if hacs.store.task_running: 24 | return 25 | 26 | updates = 0 27 | has_update = [] 28 | prev_has_update = self.has_update 29 | 30 | for repository in hacs.store.repositories: 31 | repository = hacs.store.repositories[repository] 32 | if repository.pending_update: 33 | if repository.repository_id not in prev_has_update: 34 | await repository.update() 35 | if repository.pending_update: 36 | hacs.logger.debug(repository.repository_name, "sensor.pending_update") 37 | updates += 1 38 | has_update.append(repository.repository_id) 39 | 40 | self._state = updates 41 | self.has_update = has_update 42 | 43 | @property 44 | def name(self): 45 | """Return the name of the sensor.""" 46 | return "hacs" 47 | 48 | @property 49 | def state(self): 50 | """Return the state of the sensor.""" 51 | return self._state 52 | 53 | @property 54 | def icon(self): 55 | """Return the icon of the sensor.""" 56 | return "mdi:package" 57 | 58 | @property 59 | def unit_of_measurement(self): 60 | """Return the unit of measurement.""" 61 | return "pending update(s)" 62 | -------------------------------------------------------------------------------- /custom_components/hacs/services.yaml: -------------------------------------------------------------------------------- 1 | install: 2 | description: This is NOT intended to be used here, this is intended for developers! 3 | fields: 4 | repository: 5 | description: The repository ID 6 | example: '"123456789"' 7 | register: 8 | description: This is NOT intended to be used here, this is intended for developers! 9 | fields: 10 | repository: 11 | description: The full name of the repository 12 | example: 'developer/repo' 13 | repository_type: 14 | description: The repository type 15 | example: 'plugin' -------------------------------------------------------------------------------- /custom_components/media_player/ps4.py: -------------------------------------------------------------------------------- 1 | """Playstation 4 media_player using ps4-waker.""" 2 | import json 3 | import logging 4 | import urllib.request 5 | from datetime import timedelta 6 | from urllib.parse import urlparse 7 | import requests 8 | import voluptuous as vol 9 | 10 | import homeassistant.util as util 11 | from homeassistant.components.media_player import ( 12 | PLATFORM_SCHEMA, 13 | MEDIA_TYPE_CHANNEL, 14 | SUPPORT_TURN_ON, 15 | SUPPORT_TURN_OFF, 16 | SUPPORT_STOP, 17 | SUPPORT_SELECT_SOURCE, 18 | ENTITY_IMAGE_URL, 19 | MediaPlayerDevice 20 | ) 21 | from homeassistant.const import ( 22 | STATE_IDLE, 23 | STATE_UNKNOWN, 24 | STATE_OFF, 25 | STATE_PLAYING, 26 | CONF_NAME, 27 | CONF_HOST 28 | ) 29 | from homeassistant.helpers import config_validation as cv 30 | 31 | REQUIREMENTS = [] 32 | 33 | _LOGGER = logging.getLogger(__name__) 34 | 35 | SUPPORT_PS4 = SUPPORT_TURN_OFF | SUPPORT_TURN_ON | \ 36 | SUPPORT_STOP | SUPPORT_SELECT_SOURCE 37 | 38 | DEFAULT_NAME = 'Playstation 4' 39 | DEFAULT_PORT = '' 40 | ICON = 'mdi:playstation' 41 | CONF_GAMES_FILENAME = 'games_filename' 42 | CONF_IMAGEMAP_JSON = 'imagemap_json' 43 | CONF_CMD = 'cmd' 44 | CONF_LOCAL_STORE = "local_store" 45 | CONF_PS4_IP = "ps4_ip" 46 | 47 | PS4_GAMES_FILE = 'ps4-games.json' 48 | MEDIA_IMAGE_DEFAULT = '' 49 | MEDIA_IMAGEMAP_JSON = 'https://github.com/hmn/ps4-imagemap/raw/master/games.json' 50 | LOCAL_STORE = '' 51 | 52 | MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10) 53 | MIN_TIME_BETWEEN_FORCED_SCANS = timedelta(seconds=1) 54 | 55 | PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ 56 | vol.Required(CONF_HOST): cv.string, 57 | vol.Optional(CONF_PS4_IP): cv.string, 58 | vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, 59 | vol.Optional(CONF_GAMES_FILENAME, default=PS4_GAMES_FILE): cv.string, 60 | vol.Optional(CONF_IMAGEMAP_JSON, default=MEDIA_IMAGEMAP_JSON): cv.string, 61 | vol.Optional(CONF_LOCAL_STORE, default=LOCAL_STORE): cv.string, 62 | }) 63 | 64 | 65 | def setup_platform(hass, config, add_devices, discovery_info=None): 66 | """Setup PS4 platform.""" 67 | if discovery_info is not None: 68 | ip = urlparse(discovery_info[1]).hostname 69 | else: 70 | ip = config.get(CONF_PS4_IP) 71 | 72 | if ip is None: 73 | _LOGGER.error("No PS4 found in configuration file or with discovery") 74 | return False 75 | 76 | host = config.get(CONF_HOST) 77 | name = config.get(CONF_NAME) 78 | games_filename = hass.config.path(config.get(CONF_GAMES_FILENAME)) 79 | games_map_json = config.get(CONF_IMAGEMAP_JSON) 80 | local_store = config.get(CONF_LOCAL_STORE) 81 | 82 | ps4 = PS4Waker(host, ip, games_filename) 83 | add_devices([PS4Device(name, ps4, games_map_json, local_store)], True) 84 | 85 | 86 | class PS4Device(MediaPlayerDevice): 87 | """Representation of a PS4.""" 88 | 89 | def __init__(self, name, ps4, gamesmap_json, local_store): 90 | """Initialize the ps4 device.""" 91 | self.ps4 = ps4 92 | self._name = name 93 | self._state = STATE_UNKNOWN 94 | self._media_content_id = None 95 | self._media_title = None 96 | self._current_source = None 97 | self._current_source_id = None 98 | self._media_image = None 99 | self._games_map_json = gamesmap_json 100 | self._games_map = {} 101 | self._local_store = local_store 102 | if self._local_store == '': 103 | self.load_games_map() 104 | self.update() 105 | 106 | @util.Throttle(MIN_TIME_BETWEEN_SCANS, MIN_TIME_BETWEEN_FORCED_SCANS) 107 | def update(self): 108 | """Retrieve the latest data.""" 109 | data = self.ps4.search() 110 | self._media_title = data.get('running-app-name') 111 | self._media_content_id = data.get('running-app-titleid') 112 | self._current_source = data.get('running-app-name') 113 | self._current_source_id = data.get('running-app-titleid') 114 | 115 | if data.get('status') == 'Ok': 116 | if self._media_content_id is not None: 117 | self._state = STATE_PLAYING 118 | else: 119 | self._state = STATE_IDLE 120 | else: 121 | self._state = STATE_OFF 122 | self._media_title = None 123 | self._media_content_id = None 124 | self._current_source = None 125 | self._current_source_id = None 126 | self._media_image = None 127 | 128 | def load_games_map(self): 129 | import urllib.request, json 130 | 131 | try: 132 | with urllib.request.urlopen(self._games_map_json) as url: 133 | self._games_map = json.loads(url.read().decode()) 134 | #self._games_map = json.loads(requests.get(self._games_map_json, verify=False)) 135 | except Exception as e: 136 | _LOGGER.error("gamesmap json file could not be loaded, %s" % e) 137 | 138 | @property 139 | def entity_picture(self): 140 | if self.state == STATE_OFF: 141 | return None 142 | 143 | if self._local_store == '': 144 | image_hash = self.media_image_hash 145 | 146 | if image_hash is None: 147 | return None 148 | 149 | return ENTITY_IMAGE_URL.format( 150 | self.entity_id, self.access_token, image_hash) 151 | 152 | if self._media_content_id is None: 153 | return None 154 | 155 | filename = "/local/%s/%s.jpg" % (self._local_store, self._media_content_id) 156 | self._media_image = filename 157 | return filename 158 | 159 | @property 160 | def name(self): 161 | """Return the name of the device.""" 162 | return self._name 163 | 164 | @property 165 | def state(self): 166 | """Return the state of the device.""" 167 | return self._state 168 | 169 | @property 170 | def icon(self): 171 | """Icon.""" 172 | return ICON 173 | 174 | @property 175 | def media_content_id(self): 176 | """Content ID of current playing media.""" 177 | return self._media_content_id 178 | 179 | @property 180 | def media_content_type(self): 181 | """Content type of current playing media.""" 182 | return MEDIA_TYPE_CHANNEL 183 | 184 | @property 185 | def media_image_url(self): 186 | """Image url of current playing media.""" 187 | if self._media_content_id == '': 188 | return MEDIA_IMAGE_DEFAULT 189 | 190 | if self._local_store == '': 191 | try: 192 | return self._games_map[self._media_content_id] 193 | except KeyError: 194 | return MEDIA_IMAGE_DEFAULT 195 | 196 | try: 197 | return self._media_image 198 | except KeyError: 199 | return MEDIA_IMAGE_DEFAULT 200 | 201 | @property 202 | def media_title(self): 203 | """Title of current playing media.""" 204 | return self._media_title 205 | 206 | @property 207 | def supported_features(self): 208 | """Media player features that are supported.""" 209 | return SUPPORT_PS4 210 | 211 | @property 212 | def source(self): 213 | """Return the current input source.""" 214 | return self._current_source 215 | 216 | @property 217 | def source_list(self): 218 | """List of available input sources.""" 219 | return sorted(self.ps4.games.values()) 220 | 221 | def turn_off(self): 222 | """Turn off media player.""" 223 | self.ps4.standby() 224 | 225 | def turn_on(self): 226 | """Turn on the media player.""" 227 | self.ps4.wake() 228 | self.update() 229 | 230 | def media_pause(self): 231 | """Send keypress ps to return to menu.""" 232 | self.ps4.remote('ps') 233 | self.update() 234 | 235 | def media_stop(self): 236 | """Send keypress ps to return to menu.""" 237 | self.ps4.remote('ps') 238 | self.update() 239 | 240 | def select_source(self, source): 241 | """Select input source.""" 242 | for titleid, game in self.ps4.games.items(): 243 | if source == game: 244 | self.ps4.start(titleid) 245 | self._current_source_id = titleid 246 | self._current_source = game 247 | self._media_content_id = titleid 248 | self._media_title = game 249 | self._media_image = self.entity_picture() 250 | self.update() 251 | 252 | 253 | class PS4Waker(object): 254 | """Rest client for handling the data retrieval.""" 255 | 256 | def __init__(self, url, ip, games_filename): 257 | """Initialize the data object.""" 258 | self._url = url 259 | self._ip = ip 260 | self._games_filename = games_filename 261 | self.games = {} 262 | self._load_games() 263 | 264 | def __call(self, command, param=None): 265 | url = '{0}/ps4/{1}/{2}'.format(self._url, self._ip, command) 266 | if param is not None: 267 | url += '/{0}'.format(param) 268 | 269 | try: 270 | response = requests.get(url, verify=False) 271 | if 200 != response.status_code: 272 | raise Exception(response.text) 273 | return response.text 274 | except Exception as e: 275 | _LOGGER.error('Failed to call %s: %s', command, e) 276 | return None 277 | 278 | def _load_games(self): 279 | try: 280 | with open(self._games_filename, 'r') as f: 281 | self.games = json.load(f) 282 | f.close() 283 | except FileNotFoundError: 284 | self._save_games() 285 | except ValueError as e: 286 | _LOGGER.error('Games json file wrong: %s', e) 287 | 288 | def _save_games(self): 289 | try: 290 | with open(self._games_filename, 'w') as f: 291 | json.dump(self.games, f) 292 | f.close() 293 | except FileNotFoundError: 294 | pass 295 | 296 | def wake(self): 297 | """Wake PS4 up.""" 298 | return self.__call('on') 299 | 300 | def standby(self): 301 | """Set PS4 into standby mode.""" 302 | return self.__call('off') 303 | 304 | def start(self, title_id): 305 | """Start game using titleId.""" 306 | return self.__call('start', title_id) 307 | 308 | def remote(self, key): 309 | """Send remote key press.""" 310 | return self.__call('key', key) 311 | 312 | def search(self): 313 | """List current info.""" 314 | value = self.__call('info') 315 | 316 | if value is None: 317 | return {} 318 | 319 | try: 320 | data = json.loads(value) 321 | except json.decoder.JSONDecodeError as e: 322 | _LOGGER.error("Error decoding ps4 json : %s", e) 323 | data = {} 324 | 325 | """Save current game""" 326 | if data.get('running-app-titleid'): 327 | if data.get('running-app-titleid') not in self.games.keys(): 328 | game = {data.get('running-app-titleid'): 329 | data.get('running-app-name')} 330 | self.games.update(game) 331 | self._save_games() 332 | 333 | return data -------------------------------------------------------------------------------- /customize.yaml: -------------------------------------------------------------------------------- 1 | light.bar_2: 2 | templates: 3 | rgb_color: "if (state === 'off') return [68, 115, 158];" 4 | 5 | light.bathroom_1: 6 | templates: 7 | rgb_color: "if (state === 'off') return [68, 115, 158];" 8 | 9 | light.bathroom_2: 10 | templates: 11 | rgb_color: "if (state === 'off') return [68, 115, 158];" 12 | 13 | light.bathroom_3: 14 | templates: 15 | rgb_color: "if (state === 'off') return [68, 115, 158];" 16 | 17 | light.bed_left_2: 18 | templates: 19 | rgb_color: "if (state === 'off') return [68, 115, 158];" 20 | 21 | light.bed_right_2: 22 | templates: 23 | rgb_color: "if (state === 'off') return [68, 115, 158];" 24 | 25 | light.bedroom_overhead: 26 | templates: 27 | rgb_color: "if (state === 'off') return [68, 115, 158];" 28 | 29 | light.corner_2: 30 | templates: 31 | rgb_color: "if (state === 'off') return [68, 115, 158];" 32 | 33 | light.dining_2: 34 | templates: 35 | rgb_color: "if (state === 'off') return [68, 115, 158];" 36 | 37 | light.entrance_2: 38 | templates: 39 | rgb_color: "if (state === 'off') return [68, 115, 158];" 40 | 41 | light.far_corner_2: 42 | templates: 43 | rgb_color: "if (state === 'off') return [68, 115, 158];" 44 | 45 | light.hall_2: 46 | templates: 47 | rgb_color: "if (state === 'off') return [68, 115, 158];" 48 | 49 | sensor.litter_box: 50 | templates: 51 | icon_color: "if (state === 'Open') return [211, 47, 47];" 52 | 53 | sensor.pi_hole_ads_blocked_today: 54 | friendly_name: Ads Blocked 55 | 56 | sensor.pi_hole_ads_percentage_blocked_today: 57 | friendly_name: Percentage Blocked 58 | 59 | sensor.pi_hole_dns_queries_today: 60 | friendly_name: DNS Queries -------------------------------------------------------------------------------- /device_tracker.yaml: -------------------------------------------------------------------------------- 1 | - platform: tile 2 | username: !secret tile_user 3 | password: !secret tile_pass -------------------------------------------------------------------------------- /floorplan/floorplan.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaeldvinci/Home-AssistantConfig/b45a163344af5e6dd48c95bc063c2645f4386b91/floorplan/floorplan.jpg -------------------------------------------------------------------------------- /groups.yaml: -------------------------------------------------------------------------------- 1 | default_view: 2 | name: Home 3 | view: yes 4 | icon: mdi:home 5 | entities: 6 | - media_player.apartment 7 | - media_player.rx_v681_fd72a2 8 | - group.august 9 | - group.living_room_multi 10 | - group.living_room_lights 11 | - group.late_lights 12 | - group.right_side_lights 13 | 14 | bathroom: 15 | name: Bathroom 16 | view: yes 17 | icon: mdi:human-male-female 18 | entities: 19 | - group.bathroom_multi 20 | - group.bathroom_motion 21 | - group.bathroom_lights 22 | 23 | ### GROUPS ### 24 | amplifi: 25 | name: Ubiquiti Amplifi 26 | entities: 27 | - sensor.amplifi_router_bytes_received 28 | - sensor.amplifi_router_bytes_sent 29 | - sensor.amplifi_router_kbyte_sec_received 30 | - sensor.amplifi_router_kbyte_sec_sent 31 | - sensor.amplifi_router_packets_received 32 | - sensor.amplifi_router_packets_sent 33 | - sensor.amplifi_router_packets_sec_received 34 | - sensor.amplifi_router_packets_sec_sent 35 | 36 | bathroom_lights: 37 | name: Bathroom Lights 38 | entities: 39 | - light.bathroom_1 40 | - light.bathroom_2 41 | - light.bathroom_3 42 | 43 | bathroom_motion: 44 | name: Bathroom Motion Sensor 45 | entities: 46 | - sensor.bathroom_motion 47 | 48 | bathroom_multi: 49 | name: Bathroom Multi Sensor 50 | entities: 51 | - sensor.bathroom_multi_humid 52 | - sensor.bathroom_multi_lume 53 | - sensor.bathroom_multi_motion 54 | - sensor.bathroom_multi_temp 55 | 56 | living_room_lights: 57 | name: Living Room Lights 58 | entities: 59 | - light.bar_2 60 | - light.entrance_2 61 | - light.dining_2 62 | - light.far_corner_2 63 | - light.corner_2 64 | - light.hall_2 65 | 66 | right_side_lights: 67 | name: Right Side Lights 68 | entities: 69 | - light.far_corner_2 70 | - light.corner_2 71 | 72 | late_lights: 73 | name: Late Lights 74 | entities: 75 | - light.far_corner_2 76 | - light.corner_2 77 | - light.hall_2 78 | 79 | pi_hole: 80 | name: Pi-Hole 81 | entities: 82 | - sensor.pi_hole_ads_blocked_today 83 | - sensor.pi_hole_ads_percentage_today 84 | - sensor.pi_hole_dns_queries_today 85 | 86 | system_monitor: 87 | name: System Monitor 88 | entities: 89 | - sensor.disk_use_percent 90 | - sensor.disk_use 91 | - sensor.dick_free 92 | - sensor.memory_use 93 | - sensor.memory_free 94 | - sensor.processor_use 95 | - sensor.last_boot 96 | - sensor.network_in 97 | - sensor.network_out 98 | - sensor.ipv4_address 99 | - sensor.load_1m 100 | - sensor.load_5m 101 | - sensor.load_15m -------------------------------------------------------------------------------- /images/01_Lovelace.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaeldvinci/Home-AssistantConfig/b45a163344af5e6dd48c95bc063c2645f4386b91/images/01_Lovelace.png -------------------------------------------------------------------------------- /images/02_Data.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaeldvinci/Home-AssistantConfig/b45a163344af5e6dd48c95bc063c2645f4386b91/images/02_Data.png -------------------------------------------------------------------------------- /images/03_RPi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaeldvinci/Home-AssistantConfig/b45a163344af5e6dd48c95bc063c2645f4386b91/images/03_RPi.png -------------------------------------------------------------------------------- /images/04_Home.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaeldvinci/Home-AssistantConfig/b45a163344af5e6dd48c95bc063c2645f4386b91/images/04_Home.png -------------------------------------------------------------------------------- /images/05_Bathroom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaeldvinci/Home-AssistantConfig/b45a163344af5e6dd48c95bc063c2645f4386b91/images/05_Bathroom.png -------------------------------------------------------------------------------- /images/06_Kitchen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaeldvinci/Home-AssistantConfig/b45a163344af5e6dd48c95bc063c2645f4386b91/images/06_Kitchen.png -------------------------------------------------------------------------------- /images/07_Bedroom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaeldvinci/Home-AssistantConfig/b45a163344af5e6dd48c95bc063c2645f4386b91/images/07_Bedroom.png -------------------------------------------------------------------------------- /images/Lovelace.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaeldvinci/Home-AssistantConfig/b45a163344af5e6dd48c95bc063c2645f4386b91/images/Lovelace.gif -------------------------------------------------------------------------------- /images/Phone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaeldvinci/Home-AssistantConfig/b45a163344af5e6dd48c95bc063c2645f4386b91/images/Phone.png -------------------------------------------------------------------------------- /images/Phone_01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaeldvinci/Home-AssistantConfig/b45a163344af5e6dd48c95bc063c2645f4386b91/images/Phone_01.png -------------------------------------------------------------------------------- /images/Phone_02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaeldvinci/Home-AssistantConfig/b45a163344af5e6dd48c95bc063c2645f4386b91/images/Phone_02.png -------------------------------------------------------------------------------- /media_player.yaml: -------------------------------------------------------------------------------- 1 | - platform: androidtv 2 | name: Fire Stick 4K 3 | host: 192.168.50.23 4 | adb_server_ip: 127.0.0.1 5 | 6 | - platform: kodi 7 | name: kodi_appletv 8 | host: 192.168.50.11 9 | username: !secret kodi_user 10 | password: !secret kodi_pass 11 | 12 | - platform: kodi 13 | name: kodi_firetv 14 | host: 192.168.50.23 15 | username: !secret kodi_user 16 | password: !secret kodi_pass -------------------------------------------------------------------------------- /remote.yaml: -------------------------------------------------------------------------------- 1 | - platform: harmony 2 | name: "Harmony Hub Bedroom" 3 | host: 192.168.50.40 4 | - platform: harmony 5 | name: "Harmony Hub Living Room" 6 | host: 192.168.50.41 -------------------------------------------------------------------------------- /scripts.yaml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaeldvinci/Home-AssistantConfig/b45a163344af5e6dd48c95bc063c2645f4386b91/scripts.yaml -------------------------------------------------------------------------------- /sensor.yaml: -------------------------------------------------------------------------------- 1 | ### Network ### 2 | # - platform: pi_hole 3 | # host: localhost:4865 4 | # location: "" 5 | # monitored_conditions: 6 | # - ads_blocked_today 7 | # - ads_percentage_today 8 | # - dns_queries_today 9 | 10 | - platform: systemmonitor 11 | resources: 12 | - type: disk_use_percent 13 | arg: / 14 | - type: disk_use 15 | arg: / 16 | - type: disk_free 17 | arg: / 18 | - type: memory_use 19 | - type: memory_free 20 | - type: processor_use 21 | - type: last_boot 22 | - type: network_in 23 | arg: eth0 24 | - type: network_out 25 | arg: eth0 26 | - type: ipv4_address 27 | arg: eth0 28 | - type: load_1m 29 | - type: load_5m 30 | - type: load_15m 31 | 32 | - platform: template 33 | sensors: 34 | living_motion_all: 35 | friendly_name: "Living Room Motion" 36 | icon_template: >- 37 | {% if is_state('binary_sensor.living_room_motion_motion', 'on') %} 38 | mdi:run 39 | {% else %} 40 | mdi:walk 41 | {% endif %} 42 | value_template: >- 43 | {% if is_state('binary_sensor.living_room_motion_motion', 'on') %} 44 | active 45 | {% else %} 46 | inactive 47 | {% endif %} 48 | - platform: template 49 | sensors: 50 | bedroom_motion_all: 51 | friendly_name: "Bedroom Motion All" 52 | icon_template: >- 53 | {% if is_state('binary_sensor.bedroom_motion_motion', 'on') or is_state('binary_sensor.bedroom_multi_sensor_motion', 'on') %} 54 | mdi:run 55 | {% else %} 56 | mdi:walk 57 | {% endif %} 58 | value_template: >- 59 | {% if is_state('binary_sensor.bedroom_motion_motion', 'on') or is_state('binary_sensor.bedroom_multi_sensor_motion', 'on') %} 60 | active 61 | {% else %} 62 | inactive 63 | {% endif %} 64 | - platform: template 65 | sensors: 66 | bathroom_motion_all: 67 | friendly_name: "Bathroom Motion All" 68 | icon_template: >- 69 | {% if is_state('binary_sensor.bathroom_motion_motion', 'on') or is_state('binary_sensor.shower_motion_motion', 'on') %} 70 | mdi:run 71 | {% else %} 72 | mdi:walk 73 | {% endif %} 74 | value_template: >- 75 | {% if is_state('binary_sensor.bathroom_motion_motion', 'on') or is_state('binary_sensor.shower_motion_motion', 'on') %} 76 | active 77 | {% else %} 78 | inactive 79 | {% endif %} 80 | - platform: template 81 | sensors: 82 | litter_box: 83 | value_template: >- 84 | {% if states.binary_sensor.litter_box_2_contact_2.state == 'on' %} 85 | Open 86 | {% elif states.binary_sensor.litter_box_2_contact_2.state == 'off' %} 87 | Closed 88 | {% else %} 89 | Failed 90 | {% endif %} 91 | icon_template: >- 92 | {% if states.binary_sensor.litter_box_2_contact_2.state == 'on' %} 93 | mdi:delete 94 | {% elif states.binary_sensor.litter_box_2_contact_2.state == 'off' %} 95 | mdi:cat 96 | {% endif %} 97 | whole_fridge: 98 | value_template: >- 99 | {% if states.binary_sensor.fridge_contact.state == 'on' and states.binary_sensor.freezer_contact.state == 'off' %} 100 | Fridge Open 101 | {% elif states.binary_sensor.fridge_contact.state == 'off' and states.binary_sensor.freezer_contact.state == 'on' %} 102 | Freezer Open 103 | {% elif states.binary_sensor.fridge_contact.state == 'off' and states.binary_sensor.freezer_contact.state == 'off' %} 104 | Closed 105 | {% elif states.binary_sensor.fridge_contact.state == 'on' and states.binary_sensor.freezer_contact.state == 'on' %} 106 | Open 107 | {% endif %} 108 | icon_template: >- 109 | {% if states.binary_sensor.fridge_contact.state == 'on' and states.binary_sensor.freezer_contact.state == 'off' %} 110 | mdi:fridge-bottom 111 | {% elif states.binary_sensor.fridge_contact.state == 'off' and states.binary_sensor.freezer_contact.state == 'on' %} 112 | mdi:fridge-top 113 | {% elif states.binary_sensor.fridge_contact.state == 'off' and states.binary_sensor.freezer_contact.state == 'off' %} 114 | mdi:fridge 115 | {% elif states.binary_sensor.fridge_contact.state == 'on' and states.binary_sensor.freezer_contact.state == 'on' %} 116 | mdi:fridge-outline 117 | {% endif %} 118 | - platform: command_line 119 | name: CPU Temperature 120 | command: "cat /sys/class/thermal/thermal_zone0/temp" 121 | unit_of_measurement: "°C" 122 | value_template: '{{ value | multiply(0.001) | round(1) }}' 123 | 124 | - platform: rest 125 | name: "HASS Current Version" 126 | resource: https://pypi.python.org/pypi/homeassistant/json 127 | value_template: '{{ value_json.info.version }}' 128 | scan_interval: 3600 129 | 130 | - platform: command_line 131 | name: "HASS Installed version" 132 | command: "head -5 /config/.HA_VERSION" -------------------------------------------------------------------------------- /switch.yaml: -------------------------------------------------------------------------------- 1 | - platform: template 2 | switches: 3 | watch_cable_tv: 4 | friendly_name: Watch Cable TV 5 | value_template: "{{ is_state_attr('remote.harmony_hub_living_room', 'current_activity', 'Watch tv') }}" 6 | turn_on: 7 | service: remote.turn_on 8 | data: 9 | entity_id: remote.harmony_hub_living_room 10 | activity: 'Watch TV' 11 | turn_off: 12 | service: remote.turn_on 13 | data: 14 | entity_id: remote.harmony_hub_living_room 15 | activity: 'PowerOff' 16 | - platform: template 17 | switches: 18 | watch_apple_tv: 19 | friendly_name: Watch Apple TV 20 | value_template: "{{ is_state_attr('remote.harmony_hub_living_room', 'current_activity', 'Watch Apple TV') }}" 21 | turn_on: 22 | service: remote.turn_on 23 | data: 24 | entity_id: remote.harmony_hub_living_room 25 | activity: 'Watch Apple TV' 26 | turn_off: 27 | service: remote.turn_on 28 | data: 29 | entity_id: remote.harmony_hub_living_room 30 | activity: 'PowerOff' 31 | - platform: template 32 | switches: 33 | start_ps4: 34 | friendly_name: Start PS4 35 | value_template: "{{ is_state_attr('remote.harmony_hub_living_room', 'current_activity', 'Start PS4') }}" 36 | turn_on: 37 | service: remote.turn_on 38 | data: 39 | entity_id: remote.harmony_hub_living_room 40 | activity: 'Start PS4' 41 | turn_off: 42 | service: remote.turn_on 43 | data: 44 | entity_id: remote.harmony_hub_living_room 45 | activity: 'PowerOff' 46 | - platform: template 47 | switches: 48 | nintendo_switch: 49 | friendly_name: Nintendo Switch 50 | value_template: "{{ is_state_attr('remote.harmony_hub_living_room', 'current_activity', 'Nintendo Switch') }}" 51 | turn_on: 52 | service: remote.turn_on 53 | data: 54 | entity_id: remote.harmony_hub_living_room 55 | activity: 'Nintendo Switch' 56 | turn_off: 57 | service: remote.turn_on 58 | data: 59 | entity_id: remote.harmony_hub_living_room 60 | activity: 'PowerOff' -------------------------------------------------------------------------------- /themes.yaml: -------------------------------------------------------------------------------- 1 | dark: 2 | # Background image 3 | background-image: 'center / cover no-repeat url("/local/night.jpg") fixed' 4 | 5 | # Colors 6 | text-color: '#DADADB' # Grey text 7 | text-medium-light-color: '#A0A2A8' # Medium-light grey text 8 | text-medium-color: '#80828A' # Medium grey text 9 | text-dark-color: '#6A6B74' # Dark grey text 10 | accent-color: '#008bef' # Blue 11 | accent-medium-color: '#2686c1' # Decent blue 12 | background-color: '#3b4049' # Dark grey background 13 | background-color-2: '#484E59' # Light grey background 14 | background-card-color: '#434952' # Grey background 15 | border-color: '#383C46' # Grey border 16 | 17 | # Header 18 | primary-color: '#363941' # Background 19 | text-primary-color: 'var(--text-color)' # Text 20 | 21 | # Left Menu 22 | paper-listbox-background-color: 'var(--background-color)' # Background 23 | sidebar-icon-color: 'var(--text-medium-color)' # icons 24 | sidebar-selected-icon-color: 'var(--text-medium-light-color)' # Selected row icon and background (15%) 25 | sidebar-selected-text-color: 'var(--text-color)' # Selected row label 26 | 27 | # UI 28 | paper-card-header-color: 'var(--text-color)' # Title in settings 29 | primary-background-color: 'var(--background-color)' # Background (also title background in left menu) 30 | mdc-theme-primary: 'var(--accent-medium-color)' # Action Buttons (save, restart etc.) 31 | 32 | # Card 33 | paper-card-background-color: 'var(--background-card-color)' # Background 34 | dark-primary-color: 'var(--text-color)' 35 | primary-text-color: 'var(--text-color)' 36 | paper-listbox-color: 'var(--text-color)' 37 | light-primary-color: 'var(--text-dark-color)' 38 | secondary-text-color: 'var(--text-medium-color)' 39 | disabled-text-color: 'var(--text-dark-color)' 40 | paper-dialog-button-color: 'var(--text-color)' 41 | secondary-background-color: 'var(--background-color-2)' # Background more info title 42 | 43 | # Icons 44 | paper-item-icon-color: 'var(--text-dark-color)' # Off 45 | paper-item-icon-active-color: 'var(--accent-color)' # On 46 | 47 | # Switches 48 | paper-toggle-button-checked-button-color: 'var(--text-medium-light-color)' # Knob On 49 | paper-toggle-button-checked-bar-color: '#009FFF' # Background On 50 | paper-toggle-button-unchecked-button-color: 'var(--text-medium-light-color)' # Knob Off 51 | paper-toggle-button-unchecked-bar-color: '#767682' # Background Off 52 | 53 | # Shadows 54 | shadow-elevation-2dp_-_box-shadow: 'inset 0px 0px 0px 1px var(--border-color)' 55 | shadow-elevation-4dp_-_box-shadow: 'var(--shadow-elevation-2dp_-_box-shadow)' 56 | shadow-elevation-6dp_-_box-shadow: 'var(--shadow-elevation-2dp_-_box-shadow)' 57 | shadow-elevation-8dp_-_box-shadow: 'var(--shadow-elevation-2dp_-_box-shadow)' 58 | shadow-elevation-10dp_-_box-shadow: 'var(--shadow-elevation-2dp_-_box-shadow)' 59 | shadow-elevation-12dp_-_box-shadow: 'var(--shadow-elevation-2dp_-_box-shadow)' 60 | shadow-elevation-14dp_-_box-shadow: 'var(--shadow-elevation-2dp_-_box-shadow)' 61 | shadow-elevation-16dp_-_box-shadow: '0px 0px 0px 3px var(--text-dark-color)' 62 | 63 | coned_miro: 64 | # Main colors 65 | primary-color: '#d32f2f' 66 | accent-color: '#b58e31' 67 | dark-primary-color: '#c66900' 68 | light-primary-color: '#e06c6c' 69 | # Text colors 70 | primary-text-color: '#FFFFFF' 71 | text-primary-color: 'var(--primary-text-color)' 72 | secondary-text-color: '#b58e31' 73 | disabled-text-color: '#777777' 74 | label-badge-border-color: 'green' 75 | # Sidebar 76 | sidebar-icon-color: '#777777' 77 | # Background colors 78 | primary-background-color: '#222222' 79 | secondary-background-color: '#222222' 80 | divider-color: 'rgba(0, 0, 0, .12)' 81 | table-row-background-color: '#292929' 82 | table-row-alternative-background-color: '#292929' 83 | # Nav Menu 84 | paper-listbox-color: '#777777' 85 | paper-listbox-background-color: '#272822' 86 | paper-grey-50: 'var(--primary-text-color)' 87 | paper-grey-200: '#222222' 88 | # Paper card 89 | paper-card-header-color: '#2980b9' 90 | paper-card-background-color: '#292929' 91 | paper-dialog-background-color: '#292929' 92 | paper-item-icon-color: '#44739e' 93 | paper-item-icon-active-color: '#b58e31' 94 | paper-item-icon_-_color: 'green' 95 | paper-item-selected_-_background-color: '#292929' 96 | paper-tabs-selection-bar-color: 'green' 97 | # Labels 98 | label-badge-red: 'var(--primary-color)' 99 | label-badge-text-color: 'var(--primary-text-color)' 100 | label-badge-background-color: '#222222' 101 | # Switches 102 | paper-toggle-button-checked-button-color: '#2980b9' 103 | paper-toggle-button-checked-bar-color: '#2980b9' 104 | paper-toggle-button-checked-ink-color: '#2980b9' 105 | paper-toggle-button-unchecked-button-color: 'var(--disabled-text-color)' 106 | paper-toggle-button-unchecked-bar-color: 'var(--disabled-text-color)' 107 | paper-toggle-button-unchecked-ink-color: 'var(--disabled-text-color)' 108 | # Sliders 109 | paper-slider-knob-color: '#2980b9' 110 | paper-slider-knob-start-color: '#2980b9' 111 | paper-slider-pin-color: '#2980b9' 112 | paper-slider-active-color: '#2980b9' 113 | paper-slider-container-color: 'linear-gradient(var(--primary-background-color), var(--secondary-background-color)) no-repeat' 114 | paper-slider-secondary-color: 'var(--secondary-background-color)' 115 | paper-slider-disabled-active-color: 'var(--disabled-text-color)' 116 | paper-slider-disabled-secondary-color: 'var(--disabled-text-color)' 117 | # Google colors 118 | google-red-500: '#b93829' 119 | google-green-500: '#2980b9' 120 | #Changes to fix history/logbook menus 121 | lumo-primary-text-color: '#2980b9' 122 | lumo-secondary-text-color: '#2980b9' 123 | lumo-primary-color: '#2980b9' 124 | #Calendar day numbers 125 | lumo-body-text-color: '#b58e31' 126 | #Calendar/Date-Picker Background 127 | lumo-base-color: '#222222' 128 | #Month/Year header 129 | lumo-header-text-color: 'var(--lumo-body-text-color)' 130 | #DayOfWeek Header 131 | lumo-tertiary-text-color: 'var(--lumo-body-text-color)' 132 | lumo-shade: '#222222' 133 | lumo-shade-90pct: 'rgba(34, 34, 34, .9)' 134 | lumo-shade-80pct: 'rgba(34, 34, 34, .8)' 135 | lumo-shade-70pct: 'rgba(34, 34, 34, .7)' 136 | lumo-shade-60pct: 'rgba(34, 34, 34, .6)' 137 | lumo-shade-50pct: 'rgba(34, 34, 34, .5)' 138 | lumo-shade-40pct: 'rgba(34, 34, 34, .4)' 139 | lumo-shade-30pct: 'rgba(34, 34, 34, .3)' 140 | lumo-shade-20pct: 'rgba(34, 34, 34, .2)' 141 | lumo-shade-10pct: 'rgba(34, 34, 34, .1)' 142 | lumo-shade-5pct: 'rgba(34, 34, 34, .05)' 143 | lumo-tint-5pct: '#222222' 144 | -------------------------------------------------------------------------------- /www/Lovelace/floorplan10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaeldvinci/Home-AssistantConfig/b45a163344af5e6dd48c95bc063c2645f4386b91/www/Lovelace/floorplan10.png -------------------------------------------------------------------------------- /www/Lovelace/floorplan7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaeldvinci/Home-AssistantConfig/b45a163344af5e6dd48c95bc063c2645f4386b91/www/Lovelace/floorplan7.png -------------------------------------------------------------------------------- /www/Lovelace/floorplan_grey.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaeldvinci/Home-AssistantConfig/b45a163344af5e6dd48c95bc063c2645f4386b91/www/Lovelace/floorplan_grey.png -------------------------------------------------------------------------------- /www/Lovelace/floorplan_grey2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaeldvinci/Home-AssistantConfig/b45a163344af5e6dd48c95bc063c2645f4386b91/www/Lovelace/floorplan_grey2.png -------------------------------------------------------------------------------- /www/Lovelace/freezer_closed2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaeldvinci/Home-AssistantConfig/b45a163344af5e6dd48c95bc063c2645f4386b91/www/Lovelace/freezer_closed2.png -------------------------------------------------------------------------------- /www/Lovelace/freezer_open2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaeldvinci/Home-AssistantConfig/b45a163344af5e6dd48c95bc063c2645f4386b91/www/Lovelace/freezer_open2.png -------------------------------------------------------------------------------- /www/Lovelace/fridge_closed2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaeldvinci/Home-AssistantConfig/b45a163344af5e6dd48c95bc063c2645f4386b91/www/Lovelace/fridge_closed2.png -------------------------------------------------------------------------------- /www/Lovelace/fridge_open2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaeldvinci/Home-AssistantConfig/b45a163344af5e6dd48c95bc063c2645f4386b91/www/Lovelace/fridge_open2.png -------------------------------------------------------------------------------- /www/Lovelace/tree_off.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaeldvinci/Home-AssistantConfig/b45a163344af5e6dd48c95bc063c2645f4386b91/www/Lovelace/tree_off.png -------------------------------------------------------------------------------- /www/Lovelace/tree_on.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaeldvinci/Home-AssistantConfig/b45a163344af5e6dd48c95bc063c2645f4386b91/www/Lovelace/tree_on.png -------------------------------------------------------------------------------- /www/Lovelace/unlock.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaeldvinci/Home-AssistantConfig/b45a163344af5e6dd48c95bc063c2645f4386b91/www/Lovelace/unlock.png -------------------------------------------------------------------------------- /www/Mobile/Bathroom.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaeldvinci/Home-AssistantConfig/b45a163344af5e6dd48c95bc063c2645f4386b91/www/Mobile/Bathroom.jpg -------------------------------------------------------------------------------- /www/Mobile/Bedroom.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaeldvinci/Home-AssistantConfig/b45a163344af5e6dd48c95bc063c2645f4386b91/www/Mobile/Bedroom.jpg -------------------------------------------------------------------------------- /www/Mobile/Entrance.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaeldvinci/Home-AssistantConfig/b45a163344af5e6dd48c95bc063c2645f4386b91/www/Mobile/Entrance.jpg -------------------------------------------------------------------------------- /www/Mobile/Kitchen.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaeldvinci/Home-AssistantConfig/b45a163344af5e6dd48c95bc063c2645f4386b91/www/Mobile/Kitchen.jpg -------------------------------------------------------------------------------- /www/Mobile/Living_Room.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaeldvinci/Home-AssistantConfig/b45a163344af5e6dd48c95bc063c2645f4386b91/www/Mobile/Living_Room.jpg -------------------------------------------------------------------------------- /www/Mobile/Living_Room_2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaeldvinci/Home-AssistantConfig/b45a163344af5e6dd48c95bc063c2645f4386b91/www/Mobile/Living_Room_2.jpg -------------------------------------------------------------------------------- /www/community/calendar-card/calendar-card.js.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaeldvinci/Home-AssistantConfig/b45a163344af5e6dd48c95bc063c2645f4386b91/www/community/calendar-card/calendar-card.js.gz -------------------------------------------------------------------------------- /www/community/compact-custom-header/compact-custom-header-editor.js.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaeldvinci/Home-AssistantConfig/b45a163344af5e6dd48c95bc063c2645f4386b91/www/community/compact-custom-header/compact-custom-header-editor.js.gz -------------------------------------------------------------------------------- /www/community/compact-custom-header/compact-custom-header.js.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaeldvinci/Home-AssistantConfig/b45a163344af5e6dd48c95bc063c2645f4386b91/www/community/compact-custom-header/compact-custom-header.js.gz -------------------------------------------------------------------------------- /www/community/mini-graph-card/mini-graph-card.js.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaeldvinci/Home-AssistantConfig/b45a163344af5e6dd48c95bc063c2645f4386b91/www/community/mini-graph-card/mini-graph-card.js.gz -------------------------------------------------------------------------------- /www/community/mini-media-player/mini-media-player.js.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaeldvinci/Home-AssistantConfig/b45a163344af5e6dd48c95bc063c2645f4386b91/www/community/mini-media-player/mini-media-player.js.gz -------------------------------------------------------------------------------- /www/community/radial-menu/radial-menu.js.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaeldvinci/Home-AssistantConfig/b45a163344af5e6dd48c95bc063c2645f4386b91/www/community/radial-menu/radial-menu.js.gz -------------------------------------------------------------------------------- /www/community/simple-thermostat/simple-thermostat.js.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaeldvinci/Home-AssistantConfig/b45a163344af5e6dd48c95bc063c2645f4386b91/www/community/simple-thermostat/simple-thermostat.js.gz -------------------------------------------------------------------------------- /www/custom-cards/plan-coordinates.js: -------------------------------------------------------------------------------- 1 | class PlanCoordinates extends HTMLElement { 2 | 3 | constructor() { 4 | super(); 5 | this.attachShadow({ mode: 'open' }); 6 | } 7 | 8 | setConfig(config) { 9 | const root = this.shadowRoot; 10 | if (root.lastChild) root.removeChild(root.lastChild); 11 | this.style.display = 'none'; 12 | const card = document.createElement('div'); 13 | card.id = "plan-coordinates" 14 | const content = document.createElement('div'); 15 | const style = document.createElement('style'); 16 | style.textContent = ` 17 | #plan-coordinates { 18 | position: absolute; 19 | right: 0px; 20 | top: 16px; 21 | z-index: 1000; 22 | } 23 | #content { 24 | background-color: var(--paper-card-background-color); 25 | border: 1px solid var(--label-badge-blue); 26 | padding: 16px; 27 | } 28 | `; 29 | content.id = "content"; 30 | card.appendChild(content); 31 | root.appendChild(style); 32 | root.appendChild(card); 33 | document.addEventListener("mousemove", el => { 34 | let calc_top = 16 - (document.body.querySelector('home-assistant').getBoundingClientRect().top); 35 | card.style.top = `${calc_top}px`; 36 | if (el.path[0] && el.path[0].tagName == 'IMG') { 37 | this.style.display = 'block'; 38 | const percentX = Math.ceil((el.clientX - el.path[0].x) * 100 / el.path[0].width); 39 | const percentY = Math.ceil((el.clientY - el.path[0].y) * 100 / el.path[0].height); 40 | content.innerHTML = `left: ${percentX}%
top: ${percentY}%`; 41 | } 42 | }); 43 | document.addEventListener('scroll', el => { 44 | let calc_top = 16 - (document.body.querySelector('home-assistant').getBoundingClientRect().top); 45 | card.style.top = `${calc_top}px`; 46 | }, true); 47 | } 48 | set hass(hass) { 49 | } 50 | 51 | getCardSize() { 52 | return 1; 53 | } 54 | } 55 | customElements.define('plan-coordinates', PlanCoordinates); -------------------------------------------------------------------------------- /zone.yaml: -------------------------------------------------------------------------------- 1 | - name: Home 2 | latitude: !secret home_lat 3 | longitude: !secret home_long 4 | radius: 100 5 | icon: mdi:account-multiple 6 | 7 | - name: Work 8 | latitude: !secret work_lat 9 | longitude: !secret work_long 10 | radius: 100 11 | icon: mdi:worker --------------------------------------------------------------------------------