├── groups.yaml ├── scripts.yaml ├── .HA_VERSION ├── automations.yaml ├── custom_components └── hacs │ ├── hacsbase │ ├── __init__.py │ ├── __pycache__ │ │ ├── data.cpython-38.pyc │ │ ├── hacs.cpython-38.pyc │ │ ├── __init__.cpython-38.pyc │ │ └── configuration.cpython-38.pyc │ └── configuration.py │ ├── operational │ ├── __init__.py │ ├── setup_actions │ │ ├── __init__.py │ │ ├── __pycache__ │ │ │ ├── sensor.cpython-38.pyc │ │ │ ├── __init__.cpython-38.pyc │ │ │ ├── frontend.cpython-38.pyc │ │ │ ├── categories.cpython-38.pyc │ │ │ ├── clear_storage.cpython-38.pyc │ │ │ ├── websocket_api.cpython-38.pyc │ │ │ └── load_hacs_repository.cpython-38.pyc │ │ ├── clear_storage.py │ │ ├── sensor.py │ │ ├── load_hacs_repository.py │ │ ├── categories.py │ │ ├── frontend.py │ │ └── websocket_api.py │ ├── runtime.py │ ├── __pycache__ │ │ ├── backup.cpython-38.pyc │ │ ├── relaod.cpython-38.pyc │ │ ├── remove.cpython-38.pyc │ │ ├── setup.cpython-38.pyc │ │ ├── __init__.cpython-38.pyc │ │ └── factory.cpython-38.pyc │ ├── relaod.py │ ├── remove.py │ └── factory.py │ ├── helpers │ ├── classes │ │ ├── __init__.py │ │ ├── __pycache__ │ │ │ ├── removed.cpython-38.pyc │ │ │ ├── __init__.cpython-38.pyc │ │ │ ├── manifest.cpython-38.pyc │ │ │ ├── validate.cpython-38.pyc │ │ │ ├── exceptions.cpython-38.pyc │ │ │ ├── repository.cpython-38.pyc │ │ │ ├── frontend_view.cpython-38.pyc │ │ │ └── repositorydata.cpython-38.pyc │ │ ├── exceptions.py │ │ ├── validate.py │ │ ├── removed.py │ │ ├── manifest.py │ │ └── frontend_view.py │ ├── functions │ │ ├── __init__.py │ │ ├── __pycache__ │ │ │ ├── misc.cpython-38.pyc │ │ │ ├── save.cpython-38.pyc │ │ │ ├── store.cpython-38.pyc │ │ │ ├── filters.cpython-38.pyc │ │ │ ├── logger.cpython-38.pyc │ │ │ ├── __init__.cpython-38.pyc │ │ │ ├── constrains.cpython-38.pyc │ │ │ ├── download.cpython-38.pyc │ │ │ ├── template.cpython-38.pyc │ │ │ ├── information.cpython-38.pyc │ │ │ ├── path_exsist.cpython-38.pyc │ │ │ ├── is_safe_to_remove.cpython-38.pyc │ │ │ ├── configuration_schema.cpython-38.pyc │ │ │ ├── register_repository.cpython-38.pyc │ │ │ ├── validate_repository.cpython-38.pyc │ │ │ ├── version_to_install.cpython-38.pyc │ │ │ ├── get_list_from_default.cpython-38.pyc │ │ │ └── remaining_github_calls.cpython-38.pyc │ │ ├── path_exsist.py │ │ ├── logger.py │ │ ├── is_safe_to_remove.py │ │ ├── version_to_install.py │ │ ├── remaining_github_calls.py │ │ ├── template.py │ │ ├── get_list_from_default.py │ │ ├── store.py │ │ ├── constrains.py │ │ ├── misc.py │ │ ├── save.py │ │ ├── filters.py │ │ ├── configuration_schema.py │ │ ├── register_repository.py │ │ └── validate_repository.py │ ├── __pycache__ │ │ └── __init__.cpython-38.pyc │ ├── methods │ │ ├── __pycache__ │ │ │ ├── __init__.cpython-38.pyc │ │ │ ├── installation.cpython-38.pyc │ │ │ ├── registration.cpython-38.pyc │ │ │ └── reinstall_if_needed.cpython-38.pyc │ │ ├── reinstall_if_needed.py │ │ ├── __init__.py │ │ └── registration.py │ ├── properties │ │ ├── __pycache__ │ │ │ ├── custom.cpython-38.pyc │ │ │ ├── __init__.cpython-38.pyc │ │ │ ├── pending_update.cpython-38.pyc │ │ │ └── can_be_installed.cpython-38.pyc │ │ ├── custom.py │ │ ├── __init__.py │ │ ├── can_be_installed.py │ │ └── pending_update.py │ └── __init__.py │ ├── api │ ├── __init__.py │ ├── __pycache__ │ │ ├── __init__.cpython-38.pyc │ │ ├── hacs_config.cpython-38.pyc │ │ ├── hacs_status.cpython-38.pyc │ │ ├── hacs_removed.cpython-38.pyc │ │ ├── hacs_settings.cpython-38.pyc │ │ ├── check_local_path.cpython-38.pyc │ │ ├── hacs_repository.cpython-38.pyc │ │ ├── hacs_repositories.cpython-38.pyc │ │ ├── hacs_repository_data.cpython-38.pyc │ │ ├── get_critical_repositories.cpython-38.pyc │ │ └── acknowledge_critical_repository.cpython-38.pyc │ ├── hacs_removed.py │ ├── get_critical_repositories.py │ ├── check_local_path.py │ ├── hacs_status.py │ ├── acknowledge_critical_repository.py │ ├── hacs_config.py │ ├── hacs_settings.py │ ├── hacs_repositories.py │ ├── hacs_repository.py │ └── hacs_repository_data.py │ ├── models │ ├── __init__.py │ ├── __pycache__ │ │ ├── base.cpython-38.pyc │ │ ├── system.cpython-38.pyc │ │ ├── __init__.cpython-38.pyc │ │ └── frontend.cpython-38.pyc │ ├── frontend.py │ ├── system.py │ └── base.py │ ├── webresponses │ ├── __init__.py │ ├── __pycache__ │ │ ├── iconset.cpython-38.pyc │ │ ├── __init__.cpython-38.pyc │ │ ├── category.cpython-38.pyc │ │ └── frontend.cpython-38.pyc │ ├── category.py │ ├── frontend.py │ └── iconset.py │ ├── __pycache__ │ ├── const.cpython-38.pyc │ ├── enums.cpython-38.pyc │ ├── share.cpython-38.pyc │ ├── __init__.cpython-38.pyc │ ├── sensor.cpython-38.pyc │ └── config_flow.cpython-38.pyc │ ├── validate │ ├── __pycache__ │ │ ├── base.cpython-38.pyc │ │ └── __init__.cpython-38.pyc │ ├── common │ │ ├── __pycache__ │ │ │ ├── hacs_manifest.cpython-38.pyc │ │ │ ├── repository_topics.cpython-38.pyc │ │ │ ├── repository_description.cpython-38.pyc │ │ │ └── repository_information_file.cpython-38.pyc │ │ ├── repository_topics.py │ │ ├── repository_description.py │ │ ├── hacs_manifest.py │ │ └── repository_information_file.py │ ├── integration │ │ ├── __pycache__ │ │ │ └── integration_manifest.cpython-38.pyc │ │ └── integration_manifest.py │ ├── base.py │ ├── README.md │ └── __init__.py │ ├── repositories │ ├── __pycache__ │ │ ├── theme.cpython-38.pyc │ │ ├── plugin.cpython-38.pyc │ │ ├── __init__.cpython-38.pyc │ │ ├── appdaemon.cpython-38.pyc │ │ ├── netdaemon.cpython-38.pyc │ │ ├── integration.cpython-38.pyc │ │ └── python_script.cpython-38.pyc │ ├── __init__.py │ ├── plugin.py │ ├── appdaemon.py │ ├── theme.py │ ├── python_script.py │ ├── netdaemon.py │ └── integration.py │ ├── manifest.json │ ├── __init__.py │ ├── enums.py │ ├── share.py │ ├── sensor.py │ └── config_flow.py ├── www └── community │ ├── lovelace-auto-entities │ ├── auto-entities.js.gz │ ├── webpack.config.js.gz │ └── webpack.config.js │ └── mini-graph-card │ └── mini-graph-card-bundle.js.gz ├── includes ├── mqtt_statestream.yaml ├── customize │ └── 25_automation.yaml ├── groups.yaml ├── sensors │ ├── forecast.yaml │ ├── 04_mqtt_sensor.yaml │ ├── 04_test_serv.yaml │ ├── 03_test_serv.yaml │ ├── 00_backup.yaml │ ├── 01_main_serv.yaml │ ├── 02_reserve_serv.yaml │ ├── global.yaml │ └── 00_system.yaml ├── scripts │ ├── 11_test.yaml │ ├── 080_notification.yaml │ ├── 000_system.yaml │ ├── 004_test_serv.yaml │ ├── 040_notification.yaml │ ├── 003_rasp3_serv.yaml │ ├── 070_notification.yaml │ ├── 050_notification.yaml │ ├── 020_notification.yaml │ ├── system_error.yaml │ ├── notify.yaml │ ├── z2mqtt.yaml │ ├── 010_notification.yaml │ ├── 001_main_serv.yaml │ ├── 002_reserve_serv.yaml │ ├── 060_notification.yaml │ └── 000_camera.yaml ├── automation │ ├── test.yaml │ ├── 001_action.yaml │ ├── 000_system.yaml │ ├── 103_info.yaml │ ├── 101_alarm.yaml │ ├── 102_error.yaml │ ├── 104_messages.yaml │ └── 005_telegramm.yaml ├── bin_sensor │ ├── 911_control.yaml │ └── mqtt_mode.yaml ├── camera.yaml └── recorder.yaml ├── configuration.yaml └── themes └── google_dark_theme └── google_dark_theme.yaml /groups.yaml: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /scripts.yaml: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.HA_VERSION: -------------------------------------------------------------------------------- 1 | 0.115.6 -------------------------------------------------------------------------------- /automations.yaml: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /custom_components/hacs/hacsbase/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /custom_components/hacs/operational/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /custom_components/hacs/helpers/classes/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /custom_components/hacs/helpers/functions/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /custom_components/hacs/operational/setup_actions/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /custom_components/hacs/api/__init__.py: -------------------------------------------------------------------------------- 1 | """Initialize HACS API""" 2 | -------------------------------------------------------------------------------- /custom_components/hacs/models/__init__.py: -------------------------------------------------------------------------------- 1 | """Hacs models.""" 2 | -------------------------------------------------------------------------------- /custom_components/hacs/operational/runtime.py: -------------------------------------------------------------------------------- 1 | """Runtime...""" 2 | -------------------------------------------------------------------------------- /custom_components/hacs/webresponses/__init__.py: -------------------------------------------------------------------------------- 1 | """Initialize HACS Web responses""" 2 | -------------------------------------------------------------------------------- /custom_components/hacs/__pycache__/const.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kvazis/mqtt_serv/HEAD/custom_components/hacs/__pycache__/const.cpython-38.pyc -------------------------------------------------------------------------------- /custom_components/hacs/__pycache__/enums.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kvazis/mqtt_serv/HEAD/custom_components/hacs/__pycache__/enums.cpython-38.pyc -------------------------------------------------------------------------------- /custom_components/hacs/__pycache__/share.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kvazis/mqtt_serv/HEAD/custom_components/hacs/__pycache__/share.cpython-38.pyc -------------------------------------------------------------------------------- /custom_components/hacs/__pycache__/__init__.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kvazis/mqtt_serv/HEAD/custom_components/hacs/__pycache__/__init__.cpython-38.pyc -------------------------------------------------------------------------------- /custom_components/hacs/__pycache__/sensor.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kvazis/mqtt_serv/HEAD/custom_components/hacs/__pycache__/sensor.cpython-38.pyc -------------------------------------------------------------------------------- /www/community/lovelace-auto-entities/auto-entities.js.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kvazis/mqtt_serv/HEAD/www/community/lovelace-auto-entities/auto-entities.js.gz -------------------------------------------------------------------------------- /www/community/lovelace-auto-entities/webpack.config.js.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kvazis/mqtt_serv/HEAD/www/community/lovelace-auto-entities/webpack.config.js.gz -------------------------------------------------------------------------------- /www/community/mini-graph-card/mini-graph-card-bundle.js.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kvazis/mqtt_serv/HEAD/www/community/mini-graph-card/mini-graph-card-bundle.js.gz -------------------------------------------------------------------------------- /custom_components/hacs/__pycache__/config_flow.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kvazis/mqtt_serv/HEAD/custom_components/hacs/__pycache__/config_flow.cpython-38.pyc -------------------------------------------------------------------------------- /custom_components/hacs/api/__pycache__/__init__.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kvazis/mqtt_serv/HEAD/custom_components/hacs/api/__pycache__/__init__.cpython-38.pyc -------------------------------------------------------------------------------- /custom_components/hacs/hacsbase/__pycache__/data.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kvazis/mqtt_serv/HEAD/custom_components/hacs/hacsbase/__pycache__/data.cpython-38.pyc -------------------------------------------------------------------------------- /custom_components/hacs/hacsbase/__pycache__/hacs.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kvazis/mqtt_serv/HEAD/custom_components/hacs/hacsbase/__pycache__/hacs.cpython-38.pyc -------------------------------------------------------------------------------- /custom_components/hacs/models/__pycache__/base.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kvazis/mqtt_serv/HEAD/custom_components/hacs/models/__pycache__/base.cpython-38.pyc -------------------------------------------------------------------------------- /custom_components/hacs/models/__pycache__/system.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kvazis/mqtt_serv/HEAD/custom_components/hacs/models/__pycache__/system.cpython-38.pyc -------------------------------------------------------------------------------- /custom_components/hacs/validate/__pycache__/base.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kvazis/mqtt_serv/HEAD/custom_components/hacs/validate/__pycache__/base.cpython-38.pyc -------------------------------------------------------------------------------- /custom_components/hacs/api/__pycache__/hacs_config.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kvazis/mqtt_serv/HEAD/custom_components/hacs/api/__pycache__/hacs_config.cpython-38.pyc -------------------------------------------------------------------------------- /custom_components/hacs/api/__pycache__/hacs_status.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kvazis/mqtt_serv/HEAD/custom_components/hacs/api/__pycache__/hacs_status.cpython-38.pyc -------------------------------------------------------------------------------- /custom_components/hacs/models/__pycache__/__init__.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kvazis/mqtt_serv/HEAD/custom_components/hacs/models/__pycache__/__init__.cpython-38.pyc -------------------------------------------------------------------------------- /custom_components/hacs/models/__pycache__/frontend.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kvazis/mqtt_serv/HEAD/custom_components/hacs/models/__pycache__/frontend.cpython-38.pyc -------------------------------------------------------------------------------- /custom_components/hacs/api/__pycache__/hacs_removed.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kvazis/mqtt_serv/HEAD/custom_components/hacs/api/__pycache__/hacs_removed.cpython-38.pyc -------------------------------------------------------------------------------- /custom_components/hacs/api/__pycache__/hacs_settings.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kvazis/mqtt_serv/HEAD/custom_components/hacs/api/__pycache__/hacs_settings.cpython-38.pyc -------------------------------------------------------------------------------- /custom_components/hacs/hacsbase/__pycache__/__init__.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kvazis/mqtt_serv/HEAD/custom_components/hacs/hacsbase/__pycache__/__init__.cpython-38.pyc -------------------------------------------------------------------------------- /custom_components/hacs/helpers/__pycache__/__init__.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kvazis/mqtt_serv/HEAD/custom_components/hacs/helpers/__pycache__/__init__.cpython-38.pyc -------------------------------------------------------------------------------- /custom_components/hacs/operational/__pycache__/backup.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kvazis/mqtt_serv/HEAD/custom_components/hacs/operational/__pycache__/backup.cpython-38.pyc -------------------------------------------------------------------------------- /custom_components/hacs/operational/__pycache__/relaod.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kvazis/mqtt_serv/HEAD/custom_components/hacs/operational/__pycache__/relaod.cpython-38.pyc -------------------------------------------------------------------------------- /custom_components/hacs/operational/__pycache__/remove.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kvazis/mqtt_serv/HEAD/custom_components/hacs/operational/__pycache__/remove.cpython-38.pyc -------------------------------------------------------------------------------- /custom_components/hacs/operational/__pycache__/setup.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kvazis/mqtt_serv/HEAD/custom_components/hacs/operational/__pycache__/setup.cpython-38.pyc -------------------------------------------------------------------------------- /custom_components/hacs/repositories/__pycache__/theme.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kvazis/mqtt_serv/HEAD/custom_components/hacs/repositories/__pycache__/theme.cpython-38.pyc -------------------------------------------------------------------------------- /custom_components/hacs/validate/__pycache__/__init__.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kvazis/mqtt_serv/HEAD/custom_components/hacs/validate/__pycache__/__init__.cpython-38.pyc -------------------------------------------------------------------------------- /custom_components/hacs/api/__pycache__/check_local_path.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kvazis/mqtt_serv/HEAD/custom_components/hacs/api/__pycache__/check_local_path.cpython-38.pyc -------------------------------------------------------------------------------- /custom_components/hacs/api/__pycache__/hacs_repository.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kvazis/mqtt_serv/HEAD/custom_components/hacs/api/__pycache__/hacs_repository.cpython-38.pyc -------------------------------------------------------------------------------- /custom_components/hacs/operational/__pycache__/__init__.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kvazis/mqtt_serv/HEAD/custom_components/hacs/operational/__pycache__/__init__.cpython-38.pyc -------------------------------------------------------------------------------- /custom_components/hacs/operational/__pycache__/factory.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kvazis/mqtt_serv/HEAD/custom_components/hacs/operational/__pycache__/factory.cpython-38.pyc -------------------------------------------------------------------------------- /custom_components/hacs/repositories/__pycache__/plugin.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kvazis/mqtt_serv/HEAD/custom_components/hacs/repositories/__pycache__/plugin.cpython-38.pyc -------------------------------------------------------------------------------- /custom_components/hacs/webresponses/__pycache__/iconset.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kvazis/mqtt_serv/HEAD/custom_components/hacs/webresponses/__pycache__/iconset.cpython-38.pyc -------------------------------------------------------------------------------- /custom_components/hacs/api/__pycache__/hacs_repositories.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kvazis/mqtt_serv/HEAD/custom_components/hacs/api/__pycache__/hacs_repositories.cpython-38.pyc -------------------------------------------------------------------------------- /custom_components/hacs/hacsbase/__pycache__/configuration.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kvazis/mqtt_serv/HEAD/custom_components/hacs/hacsbase/__pycache__/configuration.cpython-38.pyc -------------------------------------------------------------------------------- /custom_components/hacs/helpers/classes/__pycache__/removed.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kvazis/mqtt_serv/HEAD/custom_components/hacs/helpers/classes/__pycache__/removed.cpython-38.pyc -------------------------------------------------------------------------------- /custom_components/hacs/helpers/functions/__pycache__/misc.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kvazis/mqtt_serv/HEAD/custom_components/hacs/helpers/functions/__pycache__/misc.cpython-38.pyc -------------------------------------------------------------------------------- /custom_components/hacs/helpers/functions/__pycache__/save.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kvazis/mqtt_serv/HEAD/custom_components/hacs/helpers/functions/__pycache__/save.cpython-38.pyc -------------------------------------------------------------------------------- /custom_components/hacs/helpers/functions/__pycache__/store.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kvazis/mqtt_serv/HEAD/custom_components/hacs/helpers/functions/__pycache__/store.cpython-38.pyc -------------------------------------------------------------------------------- /custom_components/hacs/repositories/__pycache__/__init__.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kvazis/mqtt_serv/HEAD/custom_components/hacs/repositories/__pycache__/__init__.cpython-38.pyc -------------------------------------------------------------------------------- /custom_components/hacs/repositories/__pycache__/appdaemon.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kvazis/mqtt_serv/HEAD/custom_components/hacs/repositories/__pycache__/appdaemon.cpython-38.pyc -------------------------------------------------------------------------------- /custom_components/hacs/repositories/__pycache__/netdaemon.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kvazis/mqtt_serv/HEAD/custom_components/hacs/repositories/__pycache__/netdaemon.cpython-38.pyc -------------------------------------------------------------------------------- /custom_components/hacs/webresponses/__pycache__/__init__.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kvazis/mqtt_serv/HEAD/custom_components/hacs/webresponses/__pycache__/__init__.cpython-38.pyc -------------------------------------------------------------------------------- /custom_components/hacs/webresponses/__pycache__/category.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kvazis/mqtt_serv/HEAD/custom_components/hacs/webresponses/__pycache__/category.cpython-38.pyc -------------------------------------------------------------------------------- /custom_components/hacs/webresponses/__pycache__/frontend.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kvazis/mqtt_serv/HEAD/custom_components/hacs/webresponses/__pycache__/frontend.cpython-38.pyc -------------------------------------------------------------------------------- /custom_components/hacs/api/__pycache__/hacs_repository_data.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kvazis/mqtt_serv/HEAD/custom_components/hacs/api/__pycache__/hacs_repository_data.cpython-38.pyc -------------------------------------------------------------------------------- /custom_components/hacs/helpers/classes/__pycache__/__init__.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kvazis/mqtt_serv/HEAD/custom_components/hacs/helpers/classes/__pycache__/__init__.cpython-38.pyc -------------------------------------------------------------------------------- /custom_components/hacs/helpers/classes/__pycache__/manifest.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kvazis/mqtt_serv/HEAD/custom_components/hacs/helpers/classes/__pycache__/manifest.cpython-38.pyc -------------------------------------------------------------------------------- /custom_components/hacs/helpers/classes/__pycache__/validate.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kvazis/mqtt_serv/HEAD/custom_components/hacs/helpers/classes/__pycache__/validate.cpython-38.pyc -------------------------------------------------------------------------------- /custom_components/hacs/helpers/functions/__pycache__/filters.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kvazis/mqtt_serv/HEAD/custom_components/hacs/helpers/functions/__pycache__/filters.cpython-38.pyc -------------------------------------------------------------------------------- /custom_components/hacs/helpers/functions/__pycache__/logger.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kvazis/mqtt_serv/HEAD/custom_components/hacs/helpers/functions/__pycache__/logger.cpython-38.pyc -------------------------------------------------------------------------------- /custom_components/hacs/helpers/methods/__pycache__/__init__.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kvazis/mqtt_serv/HEAD/custom_components/hacs/helpers/methods/__pycache__/__init__.cpython-38.pyc -------------------------------------------------------------------------------- /custom_components/hacs/helpers/properties/__pycache__/custom.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kvazis/mqtt_serv/HEAD/custom_components/hacs/helpers/properties/__pycache__/custom.cpython-38.pyc -------------------------------------------------------------------------------- /custom_components/hacs/repositories/__pycache__/integration.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kvazis/mqtt_serv/HEAD/custom_components/hacs/repositories/__pycache__/integration.cpython-38.pyc -------------------------------------------------------------------------------- /custom_components/hacs/helpers/classes/__pycache__/exceptions.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kvazis/mqtt_serv/HEAD/custom_components/hacs/helpers/classes/__pycache__/exceptions.cpython-38.pyc -------------------------------------------------------------------------------- /custom_components/hacs/helpers/classes/__pycache__/repository.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kvazis/mqtt_serv/HEAD/custom_components/hacs/helpers/classes/__pycache__/repository.cpython-38.pyc -------------------------------------------------------------------------------- /custom_components/hacs/helpers/functions/__pycache__/__init__.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kvazis/mqtt_serv/HEAD/custom_components/hacs/helpers/functions/__pycache__/__init__.cpython-38.pyc -------------------------------------------------------------------------------- /custom_components/hacs/helpers/functions/__pycache__/constrains.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kvazis/mqtt_serv/HEAD/custom_components/hacs/helpers/functions/__pycache__/constrains.cpython-38.pyc -------------------------------------------------------------------------------- /custom_components/hacs/helpers/functions/__pycache__/download.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kvazis/mqtt_serv/HEAD/custom_components/hacs/helpers/functions/__pycache__/download.cpython-38.pyc -------------------------------------------------------------------------------- /custom_components/hacs/helpers/functions/__pycache__/template.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kvazis/mqtt_serv/HEAD/custom_components/hacs/helpers/functions/__pycache__/template.cpython-38.pyc -------------------------------------------------------------------------------- /custom_components/hacs/helpers/methods/__pycache__/installation.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kvazis/mqtt_serv/HEAD/custom_components/hacs/helpers/methods/__pycache__/installation.cpython-38.pyc -------------------------------------------------------------------------------- /custom_components/hacs/helpers/methods/__pycache__/registration.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kvazis/mqtt_serv/HEAD/custom_components/hacs/helpers/methods/__pycache__/registration.cpython-38.pyc -------------------------------------------------------------------------------- /custom_components/hacs/helpers/properties/__pycache__/__init__.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kvazis/mqtt_serv/HEAD/custom_components/hacs/helpers/properties/__pycache__/__init__.cpython-38.pyc -------------------------------------------------------------------------------- /custom_components/hacs/repositories/__pycache__/python_script.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kvazis/mqtt_serv/HEAD/custom_components/hacs/repositories/__pycache__/python_script.cpython-38.pyc -------------------------------------------------------------------------------- /custom_components/hacs/api/__pycache__/get_critical_repositories.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kvazis/mqtt_serv/HEAD/custom_components/hacs/api/__pycache__/get_critical_repositories.cpython-38.pyc -------------------------------------------------------------------------------- /custom_components/hacs/helpers/classes/__pycache__/frontend_view.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kvazis/mqtt_serv/HEAD/custom_components/hacs/helpers/classes/__pycache__/frontend_view.cpython-38.pyc -------------------------------------------------------------------------------- /custom_components/hacs/helpers/classes/__pycache__/repositorydata.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kvazis/mqtt_serv/HEAD/custom_components/hacs/helpers/classes/__pycache__/repositorydata.cpython-38.pyc -------------------------------------------------------------------------------- /custom_components/hacs/helpers/functions/__pycache__/information.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kvazis/mqtt_serv/HEAD/custom_components/hacs/helpers/functions/__pycache__/information.cpython-38.pyc -------------------------------------------------------------------------------- /custom_components/hacs/helpers/functions/__pycache__/path_exsist.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kvazis/mqtt_serv/HEAD/custom_components/hacs/helpers/functions/__pycache__/path_exsist.cpython-38.pyc -------------------------------------------------------------------------------- /custom_components/hacs/validate/common/__pycache__/hacs_manifest.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kvazis/mqtt_serv/HEAD/custom_components/hacs/validate/common/__pycache__/hacs_manifest.cpython-38.pyc -------------------------------------------------------------------------------- /custom_components/hacs/helpers/properties/__pycache__/pending_update.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kvazis/mqtt_serv/HEAD/custom_components/hacs/helpers/properties/__pycache__/pending_update.cpython-38.pyc -------------------------------------------------------------------------------- /custom_components/hacs/operational/setup_actions/__pycache__/sensor.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kvazis/mqtt_serv/HEAD/custom_components/hacs/operational/setup_actions/__pycache__/sensor.cpython-38.pyc -------------------------------------------------------------------------------- /custom_components/hacs/validate/common/__pycache__/repository_topics.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kvazis/mqtt_serv/HEAD/custom_components/hacs/validate/common/__pycache__/repository_topics.cpython-38.pyc -------------------------------------------------------------------------------- /custom_components/hacs/api/__pycache__/acknowledge_critical_repository.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kvazis/mqtt_serv/HEAD/custom_components/hacs/api/__pycache__/acknowledge_critical_repository.cpython-38.pyc -------------------------------------------------------------------------------- /custom_components/hacs/helpers/functions/__pycache__/is_safe_to_remove.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kvazis/mqtt_serv/HEAD/custom_components/hacs/helpers/functions/__pycache__/is_safe_to_remove.cpython-38.pyc -------------------------------------------------------------------------------- /custom_components/hacs/helpers/methods/__pycache__/reinstall_if_needed.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kvazis/mqtt_serv/HEAD/custom_components/hacs/helpers/methods/__pycache__/reinstall_if_needed.cpython-38.pyc -------------------------------------------------------------------------------- /custom_components/hacs/helpers/properties/__pycache__/can_be_installed.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kvazis/mqtt_serv/HEAD/custom_components/hacs/helpers/properties/__pycache__/can_be_installed.cpython-38.pyc -------------------------------------------------------------------------------- /custom_components/hacs/operational/setup_actions/__pycache__/__init__.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kvazis/mqtt_serv/HEAD/custom_components/hacs/operational/setup_actions/__pycache__/__init__.cpython-38.pyc -------------------------------------------------------------------------------- /custom_components/hacs/operational/setup_actions/__pycache__/frontend.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kvazis/mqtt_serv/HEAD/custom_components/hacs/operational/setup_actions/__pycache__/frontend.cpython-38.pyc -------------------------------------------------------------------------------- /includes/mqtt_statestream.yaml: -------------------------------------------------------------------------------- 1 | # base_topic: exchange 2 | # include: 3 | # entities: 4 | # - sensor.u_s_main_pollutant 5 | # - sensor.u_s_air_quality_index 6 | # - sensor.u_s_air_pollution_level -------------------------------------------------------------------------------- /custom_components/hacs/helpers/functions/__pycache__/configuration_schema.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kvazis/mqtt_serv/HEAD/custom_components/hacs/helpers/functions/__pycache__/configuration_schema.cpython-38.pyc -------------------------------------------------------------------------------- /custom_components/hacs/helpers/functions/__pycache__/register_repository.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kvazis/mqtt_serv/HEAD/custom_components/hacs/helpers/functions/__pycache__/register_repository.cpython-38.pyc -------------------------------------------------------------------------------- /custom_components/hacs/helpers/functions/__pycache__/validate_repository.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kvazis/mqtt_serv/HEAD/custom_components/hacs/helpers/functions/__pycache__/validate_repository.cpython-38.pyc -------------------------------------------------------------------------------- /custom_components/hacs/helpers/functions/__pycache__/version_to_install.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kvazis/mqtt_serv/HEAD/custom_components/hacs/helpers/functions/__pycache__/version_to_install.cpython-38.pyc -------------------------------------------------------------------------------- /custom_components/hacs/operational/setup_actions/__pycache__/categories.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kvazis/mqtt_serv/HEAD/custom_components/hacs/operational/setup_actions/__pycache__/categories.cpython-38.pyc -------------------------------------------------------------------------------- /custom_components/hacs/validate/common/__pycache__/repository_description.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kvazis/mqtt_serv/HEAD/custom_components/hacs/validate/common/__pycache__/repository_description.cpython-38.pyc -------------------------------------------------------------------------------- /custom_components/hacs/helpers/functions/__pycache__/get_list_from_default.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kvazis/mqtt_serv/HEAD/custom_components/hacs/helpers/functions/__pycache__/get_list_from_default.cpython-38.pyc -------------------------------------------------------------------------------- /custom_components/hacs/helpers/functions/__pycache__/remaining_github_calls.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kvazis/mqtt_serv/HEAD/custom_components/hacs/helpers/functions/__pycache__/remaining_github_calls.cpython-38.pyc -------------------------------------------------------------------------------- /custom_components/hacs/operational/setup_actions/__pycache__/clear_storage.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kvazis/mqtt_serv/HEAD/custom_components/hacs/operational/setup_actions/__pycache__/clear_storage.cpython-38.pyc -------------------------------------------------------------------------------- /custom_components/hacs/operational/setup_actions/__pycache__/websocket_api.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kvazis/mqtt_serv/HEAD/custom_components/hacs/operational/setup_actions/__pycache__/websocket_api.cpython-38.pyc -------------------------------------------------------------------------------- /custom_components/hacs/validate/common/__pycache__/repository_information_file.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kvazis/mqtt_serv/HEAD/custom_components/hacs/validate/common/__pycache__/repository_information_file.cpython-38.pyc -------------------------------------------------------------------------------- /custom_components/hacs/validate/integration/__pycache__/integration_manifest.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kvazis/mqtt_serv/HEAD/custom_components/hacs/validate/integration/__pycache__/integration_manifest.cpython-38.pyc -------------------------------------------------------------------------------- /custom_components/hacs/operational/setup_actions/__pycache__/load_hacs_repository.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kvazis/mqtt_serv/HEAD/custom_components/hacs/operational/setup_actions/__pycache__/load_hacs_repository.cpython-38.pyc -------------------------------------------------------------------------------- /custom_components/hacs/helpers/classes/exceptions.py: -------------------------------------------------------------------------------- 1 | """Custom Exceptions.""" 2 | 3 | 4 | class HacsException(Exception): 5 | """Super basic.""" 6 | 7 | 8 | class HacsExpectedException(HacsException): 9 | """For stuff that are expected.""" 10 | -------------------------------------------------------------------------------- /custom_components/hacs/models/frontend.py: -------------------------------------------------------------------------------- 1 | """HacsFrontend.""" 2 | 3 | 4 | class HacsFrontend: 5 | """HacsFrontend.""" 6 | 7 | version_running: bool = None 8 | version_available: bool = None 9 | version_expected: bool = None 10 | update_pending: bool = False 11 | -------------------------------------------------------------------------------- /www/community/lovelace-auto-entities/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | 3 | module.exports = { 4 | entry: './src/main.js', 5 | mode: 'production', 6 | output: { 7 | filename: 'auto-entities.js', 8 | path: path.resolve(__dirname) 9 | } 10 | } -------------------------------------------------------------------------------- /includes/customize/25_automation.yaml: -------------------------------------------------------------------------------- 1 | # Автоматизации 2 | 3 | # Системные сценарии - 00_system.yaml 4 | automation.00_backup_everyday: 5 | friendly_name: Резервное копирование 6 | icon: mdi:cloud-upload 7 | 8 | automation.00_start: 9 | friendly_name: Запуск Home Assistant 10 | icon: mdi:upload -------------------------------------------------------------------------------- /custom_components/hacs/helpers/classes/validate.py: -------------------------------------------------------------------------------- 1 | class Validate: 2 | """Validate.""" 3 | 4 | errors = [] 5 | 6 | @property 7 | def success(self): 8 | """Return bool if the validation was a success.""" 9 | if self.errors: 10 | return False 11 | return True 12 | -------------------------------------------------------------------------------- /includes/groups.yaml: -------------------------------------------------------------------------------- 1 | ### 2 | # scenario: 3 | # name: scenario 4 | # entities: 5 | # - automation.telegramm_main_start 6 | # - automation.telegramm_main_report 7 | # - automation.telegramm_reserve_start 8 | # - automation.telegramm_reserve_report 9 | # - automation.telegramm_test_start 10 | # - automation.telegramm_error_code -------------------------------------------------------------------------------- /includes/sensors/forecast.yaml: -------------------------------------------------------------------------------- 1 | - platform: template 2 | sensors: 3 | 0x00158d0001a4b9da_pressure_mmhg: 4 | friendly_name: "Атмосферное давление на улице" 5 | unit_of_measurement: 'mmHg' 6 | value_template: "{{ (states('sensor.0x00158d0001a4b9da_pressure')|float * 0.7500637)|round(2) }}" 7 | icon_template: mdi:gauge -------------------------------------------------------------------------------- /custom_components/hacs/validate/common/repository_topics.py: -------------------------------------------------------------------------------- 1 | from custom_components.hacs.validate.base import ( 2 | ActionValidationBase, 3 | ValidationException, 4 | ) 5 | 6 | 7 | class RepositoryTopics(ActionValidationBase): 8 | def check(self): 9 | if not self.repository.data.topics: 10 | raise ValidationException("The repository has no topics") 11 | -------------------------------------------------------------------------------- /includes/scripts/11_test.yaml: -------------------------------------------------------------------------------- 1 | test: 2 | alias: Тестовые коды 3 | sequence: 4 | - service: mqtt.publish 5 | data_template: 6 | topic: "notification/error" 7 | payload_template: '021' 8 | - delay: 00:00:10 9 | - service: mqtt.publish 10 | data_template: 11 | topic: "notification/error" 12 | payload_template: '022' -------------------------------------------------------------------------------- /custom_components/hacs/models/system.py: -------------------------------------------------------------------------------- 1 | """HACS System info.""" 2 | import attr 3 | from ..enums import HacsStage 4 | from ..const import VERSION 5 | 6 | 7 | @attr.s 8 | class HacsSystem: 9 | """HACS System info.""" 10 | 11 | disabled: bool = False 12 | running: bool = False 13 | version: str = VERSION 14 | stage: HacsStage = attr.ib(HacsStage) 15 | action: bool = False 16 | -------------------------------------------------------------------------------- /custom_components/hacs/validate/common/repository_description.py: -------------------------------------------------------------------------------- 1 | from custom_components.hacs.validate.base import ( 2 | ActionValidationBase, 3 | ValidationException, 4 | ) 5 | 6 | 7 | class RepositoryDescription(ActionValidationBase): 8 | def check(self): 9 | if not self.repository.data.description: 10 | raise ValidationException("The repository has no description") 11 | -------------------------------------------------------------------------------- /custom_components/hacs/validate/common/hacs_manifest.py: -------------------------------------------------------------------------------- 1 | from custom_components.hacs.validate.base import ( 2 | ActionValidationBase, 3 | ValidationException, 4 | ) 5 | 6 | 7 | class HacsManifest(ActionValidationBase): 8 | def check(self): 9 | if "hacs.json" not in [x.filename for x in self.repository.tree]: 10 | raise ValidationException("The repository has no 'hacs.json' file") 11 | -------------------------------------------------------------------------------- /custom_components/hacs/operational/relaod.py: -------------------------------------------------------------------------------- 1 | """Reload HACS""" 2 | 3 | 4 | async def async_reload_entry(hass, config_entry): 5 | """Reload HACS.""" 6 | from custom_components.hacs.operational.remove import async_remove_entry 7 | from custom_components.hacs.operational.setup import async_setup_entry 8 | 9 | await async_remove_entry(hass, config_entry) 10 | await async_setup_entry(hass, config_entry) 11 | -------------------------------------------------------------------------------- /includes/automation/test.yaml: -------------------------------------------------------------------------------- 1 | - alias: telegram_test 2 | trigger: 3 | - platform: event 4 | event_type: telegram_command 5 | event_data: 6 | command: '/test' 7 | action: 8 | - service: telegram_bot.send_message 9 | data_template: 10 | target: '{{ trigger.event.data.user_id }}' 11 | message: 'Текущее время - {{ states.sensor.time_date.state }}' -------------------------------------------------------------------------------- /custom_components/hacs/validate/integration/integration_manifest.py: -------------------------------------------------------------------------------- 1 | from custom_components.hacs.validate.base import ( 2 | ActionValidationBase, 3 | ValidationException, 4 | ) 5 | 6 | 7 | class IntegrationManifest(ActionValidationBase, category="integration"): 8 | def check(self): 9 | if "manifest.json" not in [x.filename for x in self.repository.tree]: 10 | raise ValidationException("The repository has no 'hacs.json' file") 11 | -------------------------------------------------------------------------------- /custom_components/hacs/helpers/functions/path_exsist.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=missing-class-docstring,missing-module-docstring,missing-function-docstring,no-member 2 | import os 3 | 4 | from custom_components.hacs.share import get_hacs 5 | 6 | 7 | def path_exsist(path) -> bool: 8 | return os.path.exists(path) 9 | 10 | 11 | async def async_path_exsist(path) -> bool: 12 | hass = get_hacs().hass 13 | return await hass.async_add_executor_job(path_exsist, path) 14 | -------------------------------------------------------------------------------- /includes/sensors/04_mqtt_sensor.yaml: -------------------------------------------------------------------------------- 1 | # Код alarm команд 2 | - platform: mqtt 3 | state_topic: "notification/alarm" 4 | name: 'alarm_code' 5 | 6 | # Код error команд 7 | - platform: mqtt 8 | state_topic: "notification/error" 9 | name: 'error_code' 10 | 11 | # Код info команд 12 | - platform: mqtt 13 | state_topic: "notification/info" 14 | name: 'info_code' 15 | 16 | # Код messages команд 17 | - platform: mqtt 18 | state_topic: "notification/messages" 19 | name: 'messages_code' -------------------------------------------------------------------------------- /custom_components/hacs/helpers/functions/logger.py: -------------------------------------------------------------------------------- 1 | """Custom logger for HACS.""" 2 | import logging 3 | import os 4 | 5 | 6 | def getLogger(name=None): 7 | if name is not None: 8 | name = name.replace("/", ".") 9 | 10 | if "GITHUB_ACTION" in os.environ: 11 | logging.basicConfig( 12 | format="::%(levelname)s:: %(message)s", 13 | level="DEBUG", 14 | ) 15 | 16 | return logging.getLogger( 17 | f"custom_components.hacs{'.' + name if name is not None else ''}" 18 | ) 19 | -------------------------------------------------------------------------------- /custom_components/hacs/helpers/properties/custom.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=missing-class-docstring,missing-module-docstring,missing-function-docstring,no-member 2 | from abc import ABC 3 | 4 | 5 | class RepositoryPropertyCustom(ABC): 6 | @property 7 | def custom(self): 8 | """Return flag if the repository is custom.""" 9 | if str(self.data.id) in [str(x) for x in self.hacs.common.default]: 10 | return False 11 | if self.data.full_name == "hacs/integration": 12 | return False 13 | return True 14 | -------------------------------------------------------------------------------- /includes/bin_sensor/911_control.yaml: -------------------------------------------------------------------------------- 1 | # Мониторинг работы автоматизаций 2 | - platform: template 3 | sensors: 4 | alarm_auto: 5 | friendly_name: "Отключены все автоматизации" 6 | value_template: >- 7 | {{ is_state('sensor.main_scenario_group', 'off') 8 | and is_state('sensor.reserve_scenario_group', 'off') }} 9 | icon_template: >- 10 | {% if is_state("binary_sensor.alarm_auto", "on") %} 11 | mdi:alarm-light 12 | {% else %} 13 | mdi:thumb-up 14 | {% endif %} -------------------------------------------------------------------------------- /includes/scripts/080_notification.yaml: -------------------------------------------------------------------------------- 1 | telegramm_0800: 2 | alias: Код 0800 закончилась вода в увлажнителе, детская А 3 | sequence: 4 | - service: notify.telegram_alex 5 | data: 6 | message: "Детская А, в увлажнителе кончилась вода, или он отключен вручную, время - {{ states('sensor.time_date') }}" 7 | 8 | telegramm_0801: 9 | alias: Код 0801 детская А, проветривание 10 | sequence: 11 | - service: notify.telegram_group 12 | data: 13 | message: "Детская А {{ states('sensor.time_date') }} - хорошо бы проветрить" -------------------------------------------------------------------------------- /custom_components/hacs/helpers/__init__.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=missing-class-docstring,missing-module-docstring,missing-function-docstring,no-member 2 | from custom_components.hacs.helpers.methods import ( 3 | HacsHelperMethods, 4 | RepositoryHelperMethods, 5 | ) 6 | from custom_components.hacs.helpers.properties import RepositoryHelperProperties 7 | 8 | 9 | class RepositoryHelpers( 10 | RepositoryHelperMethods, 11 | RepositoryHelperProperties, 12 | ): 13 | """Helper class for repositories""" 14 | 15 | 16 | class HacsHelpers(HacsHelperMethods): 17 | """Helper class for HACS""" 18 | -------------------------------------------------------------------------------- /custom_components/hacs/helpers/methods/reinstall_if_needed.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=missing-class-docstring,missing-module-docstring,missing-function-docstring,no-member 2 | from abc import ABC 3 | 4 | from custom_components.hacs.helpers.functions.path_exsist import async_path_exsist 5 | 6 | 7 | class RepositoryMethodReinstallIfNeeded(ABC): 8 | async def async_reinstall_if_needed(self) -> None: 9 | if self.data.installed: 10 | if not await async_path_exsist(self.content.path.local): 11 | self.logger.error("Missing from local FS, should be reinstalled.") 12 | # await self.async_install() 13 | -------------------------------------------------------------------------------- /includes/scripts/000_system.yaml: -------------------------------------------------------------------------------- 1 | telegramm_start: 2 | alias: Отчет при старте системы 3 | sequence: 4 | - service: notify.telegram_alex 5 | data: 6 | message: | 7 | MQTT сервер Orange PI PC 2 запущен в {{ states('sensor.time_date') }} 8 | 9 | stub: 10 | sequence: 11 | - service: homeassistant.update_entity 12 | entity_id: 13 | - sensor.last_boot_custom 14 | 15 | telegramm_unknown_error: 16 | sequence: 17 | - service: notify.telegram_alex 18 | data: 19 | message: | 20 | Код ошибки не распознан -------------------------------------------------------------------------------- /custom_components/hacs/api/hacs_removed.py: -------------------------------------------------------------------------------- 1 | """API Handler for hacs_removed""" 2 | import voluptuous as vol 3 | from homeassistant.components import websocket_api 4 | 5 | from custom_components.hacs.share import list_removed_repositories 6 | 7 | 8 | @websocket_api.async_response 9 | @websocket_api.websocket_command({vol.Required("type"): "hacs/removed"}) 10 | async def hacs_removed(_hass, connection, msg): 11 | """Get information about removed repositories.""" 12 | content = [] 13 | for repo in list_removed_repositories(): 14 | content.append(repo.to_json()) 15 | connection.send_message(websocket_api.result_message(msg["id"], content)) 16 | -------------------------------------------------------------------------------- /custom_components/hacs/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "codeowners": [ 3 | "@ludeeus" 4 | ], 5 | "config_flow": true, 6 | "dependencies": [ 7 | "http", 8 | "websocket_api", 9 | "frontend", 10 | "persistent_notification", 11 | "lovelace" 12 | ], 13 | "documentation": "https://hacs.xyz/docs/configuration/start", 14 | "issue_tracker": "https://github.com/hacs/integration/issues", 15 | "domain": "hacs", 16 | "name": "HACS", 17 | "requirements": [ 18 | "aiofiles>=0.5.0", 19 | "aiogithubapi>=2.0.0<3.0.0", 20 | "backoff>=1.10.0", 21 | "hacs_frontend==202009091732", 22 | "semantic_version>=2.8.5", 23 | "queueman==0.5" 24 | ] 25 | } -------------------------------------------------------------------------------- /custom_components/hacs/helpers/properties/__init__.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=missing-class-docstring,missing-module-docstring,missing-function-docstring,no-member 2 | from custom_components.hacs.helpers.properties.can_be_installed import ( 3 | RepositoryPropertyCanBeInstalled, 4 | ) 5 | from custom_components.hacs.helpers.properties.custom import RepositoryPropertyCustom 6 | from custom_components.hacs.helpers.properties.pending_update import ( 7 | RepositoryPropertyPendingUpdate, 8 | ) 9 | 10 | 11 | class RepositoryHelperProperties( 12 | RepositoryPropertyPendingUpdate, 13 | RepositoryPropertyCustom, 14 | RepositoryPropertyCanBeInstalled, 15 | ): 16 | pass 17 | -------------------------------------------------------------------------------- /custom_components/hacs/api/get_critical_repositories.py: -------------------------------------------------------------------------------- 1 | """API Handler for get_critical_repositories""" 2 | import voluptuous as vol 3 | from homeassistant.components import websocket_api 4 | 5 | from custom_components.hacs.helpers.functions.store import async_load_from_store 6 | 7 | 8 | @websocket_api.async_response 9 | @websocket_api.websocket_command({vol.Required("type"): "hacs/get_critical"}) 10 | async def get_critical_repositories(hass, connection, msg): 11 | """Handle get media player cover command.""" 12 | critical = await async_load_from_store(hass, "critical") 13 | if not critical: 14 | critical = [] 15 | connection.send_message(websocket_api.result_message(msg["id"], critical)) 16 | -------------------------------------------------------------------------------- /custom_components/hacs/helpers/classes/removed.py: -------------------------------------------------------------------------------- 1 | """Object for removed repositories.""" 2 | import attr 3 | 4 | 5 | @attr.s(auto_attribs=True) 6 | class RemovedRepository: 7 | repository: str = None 8 | reason: str = None 9 | link: str = None 10 | removal_type: str = None # archived, not_compliant, critical, dev, broken 11 | acknowledged: bool = False 12 | 13 | def update_data(self, data: dict): 14 | """Update data of the repository.""" 15 | for key in data: 16 | if key in self.__dict__: 17 | setattr(self, key, data[key]) 18 | 19 | def to_json(self): 20 | """Return a JSON representation of the data.""" 21 | return self.__dict__ 22 | -------------------------------------------------------------------------------- /includes/bin_sensor/mqtt_mode.yaml: -------------------------------------------------------------------------------- 1 | - platform: mqtt 2 | name: boiler 3 | state_topic: "states/water_heater" 4 | payload_on: "ON" 5 | payload_off: "OFF" 6 | 7 | - platform: mqtt 8 | name: humidification 9 | state_topic: "states/humidification" 10 | payload_on: "ON" 11 | payload_off: "OFF" 12 | 13 | - platform: mqtt 14 | name: holiday 15 | state_topic: "states/holiday" 16 | payload_on: "ON" 17 | payload_off: "OFF" 18 | 19 | - platform: mqtt 20 | name: radiator 21 | state_topic: "states/radiator" 22 | payload_on: "ON" 23 | payload_off: "OFF" 24 | 25 | - platform: mqtt 26 | name: away 27 | state_topic: "states/away" 28 | payload_on: "ON" 29 | payload_off: "OFF" -------------------------------------------------------------------------------- /includes/scripts/004_test_serv.yaml: -------------------------------------------------------------------------------- 1 | telegramm_test_start: 2 | sequence: 3 | - service: notify.telegram_alex 4 | data: 5 | message: | 6 | Тестовый сервер сервер Orange Pi one Plus 7 | Зафиксирован запуск в {{ states('sensor.time_date') }} 8 | 9 | telegramm_test_offline: 10 | sequence: 11 | - service: notify.telegram_alex 12 | data: 13 | message: | 14 | Потеря связи с тестовым сервером в {{ states('sensor.time_date') }} 15 | 16 | telegramm_test_online: 17 | sequence: 18 | - service: notify.telegram_alex 19 | data: 20 | message: | 21 | Связь с тестовым сервером восстановлена в {{ states('sensor.time_date') }} -------------------------------------------------------------------------------- /includes/scripts/040_notification.yaml: -------------------------------------------------------------------------------- 1 | telegramm_a0400: 2 | alias: Код 0400 обнаружение протечки под раковиной в кухне 3 | sequence: 4 | - service: telegram_bot.send_message 5 | data_template: 6 | target: 7 | - !secret chat_id_group 8 | message: 'Обнаружена протечка! Датчик под раковиной в кухне. Время события - {{ states.sensor.time_date.state }}' 9 | 10 | 11 | telegramm_a0401: 12 | alias: Код 0401 устранение протечки под раковиной в кухне 13 | sequence: 14 | - service: telegram_bot.send_message 15 | data_template: 16 | target: 17 | - !secret chat_id_group 18 | message: 'Протечка устранена. Датчик под раковиной в кухне. Время события - {{ states.sensor.time_date.state }}' -------------------------------------------------------------------------------- /custom_components/hacs/repositories/__init__.py: -------------------------------------------------------------------------------- 1 | """Initialize repositories.""" 2 | from custom_components.hacs.repositories.appdaemon import HacsAppdaemon 3 | from custom_components.hacs.repositories.integration import HacsIntegration 4 | from custom_components.hacs.repositories.netdaemon import HacsNetdaemon 5 | from custom_components.hacs.repositories.plugin import HacsPlugin 6 | from custom_components.hacs.repositories.python_script import HacsPythonScript 7 | from custom_components.hacs.repositories.theme import HacsTheme 8 | 9 | RERPOSITORY_CLASSES = { 10 | "theme": HacsTheme, 11 | "integration": HacsIntegration, 12 | "python_script": HacsPythonScript, 13 | "appdaemon": HacsAppdaemon, 14 | "netdaemon": HacsNetdaemon, 15 | "plugin": HacsPlugin, 16 | } 17 | -------------------------------------------------------------------------------- /includes/scripts/003_rasp3_serv.yaml: -------------------------------------------------------------------------------- 1 | telegramm_rasp3_start: 2 | sequence: 3 | - service: notify.telegram_alex 4 | data: 5 | message: | 6 | Учебный сервер сервер Raspberry Pi 3 B 7 | Зафиксирован запуск в {{ states('sensor.time_date') }} 8 | 9 | 10 | telegramm_rasp3_offline: 11 | sequence: 12 | - service: notify.telegram_alex 13 | data: 14 | message: | 15 | Потеря связи с учебным серевером в {{ states('sensor.time_date') }} 16 | 17 | telegramm_rasp3_online: 18 | sequence: 19 | - service: notify.telegram_alex 20 | data: 21 | message: | 22 | Связь с учебным серевером восстановлена в {{ states('sensor.time_date') }} -------------------------------------------------------------------------------- /custom_components/hacs/validate/common/repository_information_file.py: -------------------------------------------------------------------------------- 1 | from custom_components.hacs.validate.base import ( 2 | ActionValidationBase, 3 | ValidationException, 4 | ) 5 | 6 | 7 | class RepositoryInformationFile(ActionValidationBase): 8 | async def async_check(self): 9 | filenames = [x.filename.lower() for x in self.repository.tree] 10 | if self.repository.data.render_readme and "readme" in filenames: 11 | pass 12 | elif self.repository.data.render_readme and "readme.md" in filenames: 13 | pass 14 | elif "info" in filenames: 15 | pass 16 | elif "info.md" in filenames: 17 | pass 18 | else: 19 | raise ValidationException("The repository has no information file") 20 | -------------------------------------------------------------------------------- /includes/scripts/070_notification.yaml: -------------------------------------------------------------------------------- 1 | telegramm_0700: 2 | alias: Код 0700 закончилась вода в увлажнителе, детская Д 3 | sequence: 4 | - service: telegram_bot.send_message 5 | data_template: 6 | target: 7 | - !secret chat_id_group 8 | message: "Детская Д, в увлажнителе кончилась вода, или он отключен вручную, время - {{ states('sensor.time_date') }}" 9 | 10 | telegramm_0701: 11 | alias: Код 0701 необходимо проветрить 12 | sequence: 13 | - service: telegram_bot.send_message 14 | data_template: 15 | target: 16 | - !secret chat_id_group 17 | message: "Детская Д, необходимо проветрить комнату, содержание CO2 - {{ states('sensor.0x00124b001ed165de_co2') }} ppm" -------------------------------------------------------------------------------- /includes/sensors/04_test_serv.yaml: -------------------------------------------------------------------------------- 1 | # - platform: rest 2 | # resource: http://192.168.0.73:8123/api/states/sensor.online_custom 3 | # name: test_state 4 | # force_update: true 5 | # headers: 6 | # Authorization: !secret rasp3bplus_secret_token 7 | # content-type: 'application/json' 8 | # value_template: '{{ value_json.state }}' 9 | 10 | # - platform: mqtt 11 | # name: "last_google_backup_test" 12 | # state_topic: "instance/test/sensor/last_google_backup_test/state" 13 | 14 | # - platform: mqtt 15 | # name: "snapshots_in_google_drive_test" 16 | # state_topic: "instance/test/sensor/snapshots_in_google_drive/state" 17 | 18 | # - platform: mqtt 19 | # name: "snapshots_in_hassio_test" 20 | # state_topic: "instance/test/sensor/snapshots_in_hassio/state" -------------------------------------------------------------------------------- /custom_components/hacs/operational/setup_actions/clear_storage.py: -------------------------------------------------------------------------------- 1 | """Starting setup task: clear storage.""" 2 | import os 3 | 4 | from custom_components.hacs.share import get_hacs 5 | from ...enums import HacsSetupTask 6 | 7 | 8 | async def async_clear_storage(): 9 | """Async wrapper for clear_storage""" 10 | hacs = get_hacs() 11 | hacs.log.info("Setup task %s", HacsSetupTask.CATEGORIES) 12 | await hacs.hass.async_add_executor_job(_clear_storage) 13 | 14 | 15 | def _clear_storage(): 16 | """Clear old files from storage.""" 17 | hacs = get_hacs() 18 | storagefiles = ["hacs"] 19 | for s_f in storagefiles: 20 | path = f"{hacs.system.config_path}/.storage/{s_f}" 21 | if os.path.isfile(path): 22 | hacs.log.info(f"Cleaning up old storage file {path}") 23 | os.remove(path) 24 | -------------------------------------------------------------------------------- /includes/scripts/050_notification.yaml: -------------------------------------------------------------------------------- 1 | telegramm_0500: 2 | alias: Код 0500 робот пылесос 3 | sequence: 4 | - service: notify.telegram_group 5 | data: 6 | message: "Робот пылесос вышел на тропу войны в {{ states('sensor.time_date') }} Прячьте носки и табуретки" 7 | 8 | telegramm_0501: 9 | alias: Код 0501 гостиная, проветривание 10 | sequence: 11 | - service: notify.telegram_group 12 | data: 13 | message: "Гостиная {{ states('sensor.time_date') }} - хорошо бы проветрить" 14 | 15 | telegramm_0502: 16 | alias: Код 0502 люстра 1 в гостиной 17 | sequence: 18 | - service: notify.telegram_alex 19 | data: 20 | message: "Потеря контроля в течении 5 минут - люстра 1 в гостиной. Время события - {{ states('sensor.time_date') }} . Инициирована перезагрузка" -------------------------------------------------------------------------------- /custom_components/hacs/helpers/properties/can_be_installed.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=missing-class-docstring,missing-module-docstring,missing-function-docstring,no-member 2 | from abc import ABC 3 | 4 | from custom_components.hacs.helpers.functions.misc import version_left_higher_then_right 5 | 6 | 7 | class RepositoryPropertyCanBeInstalled(ABC): 8 | @property 9 | def can_be_installed(self) -> bool: 10 | if self.data.homeassistant is not None: 11 | if self.data.releases: 12 | if not version_left_higher_then_right( 13 | self.hacs.system.ha_version, self.data.homeassistant 14 | ): 15 | return False 16 | return True 17 | 18 | @property 19 | def can_install(self): 20 | """kept for legacy compatibility""" 21 | return self.can_be_installed 22 | -------------------------------------------------------------------------------- /includes/sensors/03_test_serv.yaml: -------------------------------------------------------------------------------- 1 | # - platform: rest 2 | # resource: http://192.168.0.72:8123/api/states/sensor.online_custom 3 | # name: rasp3b_state 4 | # force_update: true 5 | # headers: 6 | # Authorization: !secret rasp3_secret_token 7 | # content-type: 'application/json' 8 | # value_template: '{{ value_json.state }}' 9 | 10 | # - platform: mqtt 11 | # name: "status_google_backup_72" 12 | # state_topic: "googlebackup/raspberry72/result" 13 | # json_attributes_topic: "googlebackup/raspberry72/result" 14 | 15 | # - platform: template 16 | # sensors: 17 | # last_google_backup_test: 18 | # friendly_name: 'Последний удачный бэкап тестового сервера - ' 19 | # value_template: '{{ (as_timestamp(states.sensor.status_google_backup_72.attributes["backupTimestamp"]) + 7200) | timestamp_custom("%d.%m.%Y %H:%M") }}' -------------------------------------------------------------------------------- /custom_components/hacs/api/check_local_path.py: -------------------------------------------------------------------------------- 1 | """API Handler for check_local_path""" 2 | import homeassistant.helpers.config_validation as cv 3 | import voluptuous as vol 4 | from homeassistant.components import websocket_api 5 | 6 | from custom_components.hacs.helpers.functions.path_exsist import async_path_exsist 7 | 8 | 9 | @websocket_api.async_response 10 | @websocket_api.websocket_command( 11 | {vol.Required("type"): "hacs/check_path", vol.Optional("path"): cv.string} 12 | ) 13 | async def check_local_path(_hass, connection, msg): 14 | """Handle get media player cover command.""" 15 | path = msg.get("path") 16 | exist = {"exist": False} 17 | 18 | if path is None: 19 | return 20 | 21 | if await async_path_exsist(path): 22 | exist["exist"] = True 23 | 24 | connection.send_message(websocket_api.result_message(msg["id"], exist)) 25 | -------------------------------------------------------------------------------- /custom_components/hacs/operational/remove.py: -------------------------------------------------------------------------------- 1 | """Remove HACS.""" 2 | from custom_components.hacs.share import get_hacs 3 | 4 | 5 | async def async_remove_entry(hass, config_entry): 6 | """Handle removal of an entry.""" 7 | hacs = get_hacs() 8 | hacs.log.info("Disabling HACS") 9 | hacs.log.info("Removing recurring tasks") 10 | for task in hacs.recuring_tasks: 11 | task() 12 | if config_entry.state == "loaded": 13 | hacs.log.info("Removing sensor") 14 | try: 15 | await hass.config_entries.async_forward_entry_unload(config_entry, "sensor") 16 | except ValueError: 17 | pass 18 | hacs.log.info("Removing sidepanel") 19 | try: 20 | hass.components.frontend.async_remove_panel("hacs") 21 | except AttributeError: 22 | pass 23 | hacs.system.disabled = True 24 | hacs.log.info("HACS is now disabled") 25 | -------------------------------------------------------------------------------- /custom_components/hacs/helpers/functions/is_safe_to_remove.py: -------------------------------------------------------------------------------- 1 | """Helper to check if path is safe to remove.""" 2 | from pathlib import Path 3 | 4 | from custom_components.hacs.share import get_hacs 5 | 6 | 7 | def is_safe_to_remove(path: str) -> bool: 8 | """Helper to check if path is safe to remove.""" 9 | hacs = get_hacs() 10 | paths = [ 11 | Path(f"{hacs.system.config_path}/{hacs.configuration.appdaemon_path}"), 12 | Path(f"{hacs.system.config_path}/{hacs.configuration.netdaemon_path}"), 13 | Path(f"{hacs.system.config_path}/{hacs.configuration.plugin_path}"), 14 | Path(f"{hacs.system.config_path}/{hacs.configuration.python_script_path}"), 15 | Path(f"{hacs.system.config_path}/{hacs.configuration.theme_path}"), 16 | Path(f"{hacs.system.config_path}/custom_components/"), 17 | ] 18 | if Path(path) in paths: 19 | return False 20 | return True 21 | -------------------------------------------------------------------------------- /includes/automation/001_action.yaml: -------------------------------------------------------------------------------- 1 | - id: Режим никого нет дома вкл 2 | alias: info_away_on 3 | initial_state: true 4 | trigger: 5 | - platform: state 6 | entity_id: binary_sensor.away 7 | from: 'off' 8 | to: 'on' 9 | action: 10 | - service: notify.telegram_group 11 | data: 12 | message: "Зафиксировано отсутствие активности всех жизненных форм. Время события - {{ states('sensor.time_date') }} Ухожу в режим спячки" 13 | 14 | - id: Режим никого нет дома выкл 15 | alias: info_away_off 16 | initial_state: true 17 | trigger: 18 | - platform: state 19 | entity_id: binary_sensor.away 20 | from: 'on' 21 | to: 'off' 22 | action: 23 | - service: notify.telegram_group 24 | data: 25 | message: "Обнаружены чрезмерно активные жизенные формы. Время события - {{ states('sensor.time_date') }} Восстановление нормального режима работы" 26 | -------------------------------------------------------------------------------- /includes/sensors/00_backup.yaml: -------------------------------------------------------------------------------- 1 | - platform: template 2 | sensors: 3 | last_google_backup_mqtt: 4 | friendly_name: 'Последний удачный бэкап - ' 5 | value_template: '{{ (as_timestamp(states.sensor.snapshot_backup.attributes["last_snapshot"])) | timestamp_custom("%d.%m.%Y %H:%M") }}' 6 | icon_template: mdi:calendar-check-outline 7 | 8 | - platform: template 9 | sensors: 10 | snapshots_in_google_drive_mqtt: 11 | friendly_name: 'Снапшотов на Googe Drive - ' 12 | value_template: '{{ states.sensor.snapshot_backup.attributes["snapshots_in_google_drive"] }}' 13 | icon_template: mdi:folder-google-drive 14 | 15 | - platform: template 16 | sensors: 17 | snapshots_in_hassio_mqtt: 18 | friendly_name: 'Снапшотов в Наssio - ' 19 | value_template: '{{ states.sensor.snapshot_backup.attributes["snapshots_in_hassio"] }}' 20 | icon_template: mdi:home-assistant -------------------------------------------------------------------------------- /custom_components/hacs/operational/setup_actions/sensor.py: -------------------------------------------------------------------------------- 1 | """"Starting setup task: Sensor".""" 2 | from homeassistant.helpers import discovery 3 | 4 | from custom_components.hacs.const import DOMAIN 5 | from custom_components.hacs.share import get_hacs 6 | from ...enums import HacsSetupTask 7 | 8 | 9 | async def async_add_sensor(): 10 | """Async wrapper for add sensor""" 11 | hacs = get_hacs() 12 | hacs.log.info("Setup task %s", HacsSetupTask.SENSOR) 13 | if hacs.configuration.config_type == "yaml": 14 | hacs.hass.async_create_task( 15 | discovery.async_load_platform( 16 | hacs.hass, "sensor", DOMAIN, {}, hacs.configuration.config 17 | ) 18 | ) 19 | else: 20 | hacs.hass.async_add_job( 21 | hacs.hass.config_entries.async_forward_entry_setup( 22 | hacs.configuration.config_entry, "sensor" 23 | ) 24 | ) 25 | -------------------------------------------------------------------------------- /custom_components/hacs/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | HACS gives you a powerful UI to handle downloads of all your custom needs. 3 | 4 | For more details about this integration, please refer to the documentation at 5 | https://hacs.xyz/ 6 | """ 7 | import voluptuous as vol 8 | 9 | from .const import DOMAIN 10 | from .helpers.functions.configuration_schema import ( 11 | hacs_config_combined, 12 | ) 13 | from .operational.setup import ( 14 | async_setup as hacs_yaml_setup, 15 | async_setup_entry as hacs_ui_setup, 16 | ) 17 | 18 | CONFIG_SCHEMA = vol.Schema({DOMAIN: hacs_config_combined()}, extra=vol.ALLOW_EXTRA) 19 | 20 | 21 | async def async_setup(hass, config): 22 | """Set up this integration using yaml.""" 23 | 24 | return await hacs_yaml_setup(hass, config) 25 | 26 | 27 | async def async_setup_entry(hass, config_entry): 28 | """Set up this integration using UI.""" 29 | 30 | return await hacs_ui_setup(hass, config_entry) 31 | -------------------------------------------------------------------------------- /custom_components/hacs/models/base.py: -------------------------------------------------------------------------------- 1 | """Base HACS class.""" 2 | import logging 3 | 4 | import attr 5 | from aiogithubapi.github import AIOGitHubAPI 6 | from aiogithubapi.objects.repository import AIOGitHubAPIRepository 7 | from homeassistant.core import HomeAssistant 8 | 9 | from .core import HacsCore 10 | from .frontend import HacsFrontend 11 | from .system import HacsSystem 12 | from ..enums import HacsStage 13 | 14 | 15 | @attr.s 16 | class Hacs: 17 | """Base HACS class.""" 18 | 19 | default_repository: AIOGitHubAPIRepository = None 20 | github: AIOGitHubAPI = None 21 | hass: HomeAssistant = None 22 | repository: AIOGitHubAPIRepository = None 23 | 24 | log: logging.Logger = logging.getLogger("custom_components.hacs") 25 | 26 | core: HacsCore = attr.ib(HacsCore) 27 | frontend: HacsFrontend = attr.ib(HacsFrontend) 28 | stage: HacsStage = HacsStage.SETUP 29 | system: HacsSystem = attr.ib(HacsSystem) 30 | -------------------------------------------------------------------------------- /custom_components/hacs/helpers/properties/pending_update.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=missing-class-docstring,missing-module-docstring,missing-function-docstring,no-member 2 | from abc import ABC 3 | 4 | 5 | class RepositoryPropertyPendingUpdate(ABC): 6 | @property 7 | def pending_update(self) -> bool: 8 | if not self.can_install: 9 | return False 10 | if self.data.installed: 11 | if self.data.selected_tag is not None: 12 | if self.data.selected_tag == self.data.default_branch: 13 | if self.data.installed_commit != self.data.last_commit: 14 | return True 15 | return False 16 | if self.display_installed_version != self.display_available_version: 17 | return True 18 | return False 19 | 20 | @property 21 | def pending_upgrade(self) -> bool: 22 | """kept for legacy compatibility""" 23 | return self.pending_update 24 | -------------------------------------------------------------------------------- /custom_components/hacs/api/hacs_status.py: -------------------------------------------------------------------------------- 1 | """API Handler for hacs_status""" 2 | import voluptuous as vol 3 | from homeassistant.components import websocket_api 4 | 5 | from custom_components.hacs.share import get_hacs 6 | 7 | 8 | @websocket_api.async_response 9 | @websocket_api.websocket_command({vol.Required("type"): "hacs/status"}) 10 | async def hacs_status(_hass, connection, msg): 11 | """Handle get media player cover command.""" 12 | hacs = get_hacs() 13 | content = { 14 | "startup": hacs.status.startup, 15 | "background_task": hacs.status.background_task, 16 | "lovelace_mode": hacs.system.lovelace_mode, 17 | "reloading_data": hacs.status.reloading_data, 18 | "upgrading_all": hacs.status.upgrading_all, 19 | "disabled": hacs.system.disabled, 20 | "has_pending_tasks": hacs.queue.has_pending_tasks, 21 | "stage": hacs.stage, 22 | } 23 | connection.send_message(websocket_api.result_message(msg["id"], content)) 24 | -------------------------------------------------------------------------------- /includes/camera.yaml: -------------------------------------------------------------------------------- 1 | # Кубик Xiaofang 2 | - platform: generic 3 | name: Xiaofang 4 | still_image_url: http://192.168.0.125/image.jpg 5 | stream_source: rtsp://192.168.0.125:554/unicast 6 | 7 | # Кубик Xiaofang 1s 8 | - platform: ffmpeg 9 | name: Xiaofang1s 10 | input: -rtsp_transport tcp -i rtsp://192.168.0.127:8554/unicast 11 | 12 | # Reolink - RLC-411WS 13 | - platform: ffmpeg 14 | name: Reolink 15 | input: !secret reolink1_rtsp 16 | 17 | # Reolink C2 Pro 18 | - platform: ffmpeg 19 | name: Reolink2 20 | input: !secret reolink2_rtsp 21 | 22 | # - platform: onvif 23 | # host: 192.168.0.128 24 | # name: reolink_2 25 | # username: !secret reolink_name 26 | # password: !secret reolink_pass 27 | # port: 8000 28 | 29 | # Reolink 410 30 | - platform: ffmpeg 31 | name: Reolink3 32 | input: !secret reolink3_rtsp 33 | 34 | # Reolink E1 Pro 35 | 36 | - platform: ffmpeg 37 | name: Reolink4 38 | input: !secret reolink4_rtsp 39 | -------------------------------------------------------------------------------- /custom_components/hacs/helpers/functions/version_to_install.py: -------------------------------------------------------------------------------- 1 | """Install helper for repositories.""" 2 | 3 | 4 | def version_to_install(repository): 5 | """Determine which version to isntall.""" 6 | if repository.data.last_version is not None: 7 | if repository.data.selected_tag is not None: 8 | if repository.data.selected_tag == repository.data.last_version: 9 | repository.data.selected_tag = None 10 | return repository.data.last_version 11 | return repository.data.selected_tag 12 | return repository.data.last_version 13 | if repository.data.selected_tag is not None: 14 | if repository.data.selected_tag == repository.data.default_branch: 15 | return repository.data.default_branch 16 | if repository.data.selected_tag in repository.data.published_tags: 17 | return repository.data.selected_tag 18 | if repository.data.default_branch is None: 19 | return "main" 20 | return repository.data.default_branch 21 | -------------------------------------------------------------------------------- /custom_components/hacs/enums.py: -------------------------------------------------------------------------------- 1 | """Helper constants.""" 2 | # pylint: disable=missing-class-docstring 3 | from enum import Enum 4 | 5 | 6 | class HacsCategory(str, Enum): 7 | APPDAEMON = "appdaemon" 8 | INTEGRATION = "integration" 9 | LOVELACE = "lovelace" 10 | PLUGIN = "plugin" # Kept for legacy purposes 11 | NETDAEMON = "netdaemon" 12 | PYTHON_SCRIPT = "python_script" 13 | THEME = "theme" 14 | REMOVED = "removed" 15 | 16 | 17 | class LovelaceMode(str, Enum): 18 | """Lovelace Modes.""" 19 | 20 | STORAGE = "storage" 21 | AUTO = "auto" 22 | YAML = "yaml" 23 | 24 | 25 | class HacsStage(str, Enum): 26 | SETUP = "setup" 27 | STARTUP = "startup" 28 | WAITING = "waiting" 29 | RUNNING = "running" 30 | BACKGROUND = "background" 31 | 32 | 33 | class HacsSetupTask(str, Enum): 34 | WEBSOCKET = "WebSocket API" 35 | FRONTEND = "Frontend" 36 | SENSOR = "Sensor" 37 | HACS_REPO = "Hacs Repository" 38 | CATEGORIES = "Additional categories" 39 | CLEAR_STORAGE = "Clear storage" 40 | -------------------------------------------------------------------------------- /custom_components/hacs/api/acknowledge_critical_repository.py: -------------------------------------------------------------------------------- 1 | """API Handler for acknowledge_critical_repository""" 2 | import homeassistant.helpers.config_validation as cv 3 | import voluptuous as vol 4 | from homeassistant.components import websocket_api 5 | 6 | from custom_components.hacs.helpers.functions.store import ( 7 | async_load_from_store, 8 | async_save_to_store, 9 | ) 10 | 11 | 12 | @websocket_api.async_response 13 | @websocket_api.websocket_command( 14 | {vol.Required("type"): "hacs/critical", vol.Optional("repository"): cv.string} 15 | ) 16 | async def acknowledge_critical_repository(hass, connection, msg): 17 | """Handle get media player cover command.""" 18 | repository = msg["repository"] 19 | 20 | critical = await async_load_from_store(hass, "critical") 21 | for repo in critical: 22 | if repository == repo["repository"]: 23 | repo["acknowledged"] = True 24 | await async_save_to_store(hass, "critical", critical) 25 | connection.send_message(websocket_api.result_message(msg["id"], critical)) 26 | -------------------------------------------------------------------------------- /includes/recorder.yaml: -------------------------------------------------------------------------------- 1 | db_url: sqlite:////config/home-assistant_v2.db 2 | purge_keep_days: 3 3 | auto_purge: true 4 | include: 5 | entities: 6 | # Системные сенсоры 7 | - sensor.processor_use 8 | - sensor.cpu_temp 9 | - sensor.memory_use_percent 10 | - sensor.disk_use_percent 11 | - sensor.disk_free 12 | - sensor.home_assistant_v2_db 13 | - sensor.core 14 | # Основной сервер 15 | - sensor.main_system_send 16 | - sensor.main_system_scenario 17 | - sensor.main_system_unavailable_now_light 18 | - sensor.main_system_unavailable_now_switch 19 | - sensor.main_system_unavailable_now_sensor 20 | - sensor.main_system_unavailable_now_binary_sensor 21 | # Резерный сервер 22 | - sensor.reserve_system_send 23 | - sensor.reserve_system_scenario 24 | - sensor.reserve_system_unavailable_now_light 25 | - sensor.reserve_system_unavailable_now_switch 26 | - sensor.reserve_system_unavailable_now_sensor 27 | - sensor.reserve_system_unavailable_now_binary_sensor -------------------------------------------------------------------------------- /includes/scripts/020_notification.yaml: -------------------------------------------------------------------------------- 1 | telegramm_a0200: 2 | alias: Код 0200 обнаружение протечки возле стиральной машины 3 | sequence: 4 | - service: telegram_bot.send_message 5 | data_template: 6 | target: 7 | - !secret chat_id_group 8 | message: 'Обнаружена протечка! Датчик возле стиральной машины. Время события - {{ states.sensor.time_date.state }}' 9 | 10 | 11 | telegramm_a0201: 12 | alias: Код 0201 устранение протечки возле стиральной машины 13 | sequence: 14 | - service: telegram_bot.send_message 15 | data_template: 16 | target: 17 | - !secret chat_id_group 18 | message: 'Протечка устранена. Датчик возле стиральной машины. Время события - {{ states.sensor.time_date.state }}' 19 | 20 | telegramm_200: 21 | alias: Код 200 люстра в туалете 22 | sequence: 23 | - service: notify.telegram_alex 24 | data: 25 | message: "Потеря контроля в течении 5 минут - люстра в туалете. Время события - {{ states('sensor.time_date') }} . Инициирована перезагрузка" 26 | 27 | -------------------------------------------------------------------------------- /custom_components/hacs/helpers/functions/remaining_github_calls.py: -------------------------------------------------------------------------------- 1 | """Helper to calculate the remaining calls to github.""" 2 | import math 3 | 4 | from custom_components.hacs.helpers.functions.logger import getLogger 5 | 6 | 7 | async def remaining(github): 8 | """Helper to calculate the remaining calls to github.""" 9 | logger = getLogger("custom_components.hacs.remaining_github_calls") 10 | try: 11 | ratelimits = await github.get_rate_limit() 12 | except (BaseException, Exception) as exception: # pylint: disable=broad-except 13 | logger.error(exception) 14 | return None 15 | if ratelimits.get("remaining") is not None: 16 | return int(ratelimits["remaining"]) 17 | return 0 18 | 19 | 20 | async def get_fetch_updates_for(github): 21 | """Helper to calculate the number of repositories we can fetch data for.""" 22 | margin = 100 23 | limit = await remaining(github) 24 | pr_repo = 10 25 | 26 | if limit is None: 27 | return None 28 | 29 | if limit - margin <= pr_repo: 30 | return 0 31 | return math.floor((limit - margin) / pr_repo) 32 | -------------------------------------------------------------------------------- /includes/scripts/system_error.yaml: -------------------------------------------------------------------------------- 1 | light_offline_main: 2 | sequence: 3 | - service: notify.telegram_alex 4 | data: 5 | message: "Ошибка связи {{ states('sensor.time_date') }} , светильников недоступно - {{ states('sensor.main_system_unavailable_now_light') }} " 6 | 7 | 8 | light_offline_reserve: 9 | sequence: 10 | - service: notify.telegram_alex 11 | data: 12 | message: "Ошибка связи {{ states('sensor.time_date') }} , светильников недоступно - {{ states('sensor.reserve_system_unavailable_now_light') }} " 13 | 14 | 15 | switch_offline_main: 16 | sequence: 17 | - service: notify.telegram_alex 18 | data: 19 | message: "Ошибка связи {{ states('sensor.time_date') }} , свичей недоступно - {{ states('sensor.main_system_unavailable_now_switch') }} " 20 | 21 | 22 | switch_offline_reserve: 23 | sequence: 24 | - service: notify.telegram_alex 25 | data: 26 | message: "Ошибка связи {{ states('sensor.time_date') }} , свичей недоступно - {{ states('sensor.reserve_system_unavailable_now_switch') }} " 27 | -------------------------------------------------------------------------------- /custom_components/hacs/helpers/methods/__init__.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=missing-class-docstring,missing-module-docstring,missing-function-docstring,no-member 2 | from custom_components.hacs.helpers.methods.installation import ( 3 | RepositoryMethodInstall, 4 | RepositoryMethodPostInstall, 5 | RepositoryMethodPreInstall, 6 | ) 7 | from custom_components.hacs.helpers.methods.registration import ( 8 | RepositoryMethodPostRegistration, 9 | RepositoryMethodPreRegistration, 10 | RepositoryMethodRegistration, 11 | ) 12 | from custom_components.hacs.helpers.methods.reinstall_if_needed import ( 13 | RepositoryMethodReinstallIfNeeded, 14 | ) 15 | 16 | 17 | class RepositoryHelperMethods( 18 | RepositoryMethodReinstallIfNeeded, 19 | RepositoryMethodInstall, 20 | RepositoryMethodPostInstall, 21 | RepositoryMethodPreInstall, 22 | RepositoryMethodPreRegistration, 23 | RepositoryMethodRegistration, 24 | RepositoryMethodPostRegistration, 25 | ): 26 | """Collection of repository methods that are nested to all repositories.""" 27 | 28 | 29 | class HacsHelperMethods: 30 | """Helper class for HACS methods""" 31 | -------------------------------------------------------------------------------- /includes/scripts/notify.yaml: -------------------------------------------------------------------------------- 1 | outdoor_report: 2 | sequence: 3 | - service: telegram_bot.send_message 4 | data_template: 5 | target: 6 | - !secret chat_id_group 7 | message: | 8 | {{"\U0001F3E0"}} Климат отчет 9 | {{"\U0001F321"}} Температура на улице - {{ states('sensor.0x00158d0001a4b9da_temperature') }} C 10 | {{"\U0001F4AA"}} Давление - {{ states('sensor.0x00158d0001a4b9da_pressure_mmhg') }} мм рт. ст 11 | {{"\U00002622"}} Радиация - {{ states('sensor.geiger_counter_radiation_dose_per_hour') }} мкР в час 12 | {{"\U00002600"}} Погода - {{ states('sensor.openweathermap_weather') }} 13 | {{"\U0001F327"}} Уровень осадков - {{ states('sensor.openweathermap_rain') }} мм. 14 | {{"\U0001F32C"}} Скорость ветра - {{ states('sensor.openweathermap_forecast_wind_speed') }} м/сек 15 | {{"\U00002602"}} Прогноз на сегодня - {{ states('sensor.openweathermap_forecast_condition') }}, {{"\U0001F321"}} {{ states('sensor.openweathermap_forecast_temperature') }} C 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /custom_components/hacs/helpers/functions/template.py: -------------------------------------------------------------------------------- 1 | """Custom template support.""" 2 | # pylint: disable=broad-except 3 | from jinja2 import Template 4 | 5 | from custom_components.hacs.helpers.functions.logger import getLogger 6 | 7 | logger = getLogger("template") 8 | 9 | 10 | def render_template(content, context): 11 | """Render templates in content.""" 12 | # Fix None issues 13 | if context.releases.last_release_object is not None: 14 | prerelease = context.releases.last_release_object.prerelease 15 | else: 16 | prerelease = False 17 | 18 | # Render the template 19 | try: 20 | render = Template(content) 21 | render = render.render( 22 | installed=context.data.installed, 23 | pending_update=context.pending_upgrade, 24 | prerelease=prerelease, 25 | selected_tag=context.data.selected_tag, 26 | version_available=context.releases.last_release, 27 | version_installed=context.display_installed_version, 28 | ) 29 | return render 30 | except (Exception, BaseException) as exception: 31 | logger.debug(exception) 32 | return content 33 | -------------------------------------------------------------------------------- /custom_components/hacs/api/hacs_config.py: -------------------------------------------------------------------------------- 1 | """API Handler for hacs_config""" 2 | import voluptuous as vol 3 | from homeassistant.components import websocket_api 4 | 5 | from custom_components.hacs.share import get_hacs 6 | 7 | 8 | @websocket_api.async_response 9 | @websocket_api.websocket_command({vol.Required("type"): "hacs/config"}) 10 | async def hacs_config(_hass, connection, msg): 11 | """Handle get media player cover command.""" 12 | hacs = get_hacs() 13 | config = hacs.configuration 14 | 15 | content = {} 16 | content["frontend_mode"] = config.frontend_mode 17 | content["frontend_compact"] = config.frontend_compact 18 | content["onboarding_done"] = config.onboarding_done 19 | content["version"] = hacs.version 20 | content["frontend_expected"] = hacs.frontend.version_expected 21 | content["frontend_running"] = hacs.frontend.version_running 22 | content["dev"] = config.dev 23 | content["debug"] = config.debug 24 | content["country"] = config.country 25 | content["experimental"] = config.experimental 26 | content["categories"] = hacs.common.categories 27 | 28 | connection.send_message(websocket_api.result_message(msg["id"], content)) 29 | -------------------------------------------------------------------------------- /custom_components/hacs/helpers/classes/manifest.py: -------------------------------------------------------------------------------- 1 | """ 2 | Manifest handling of a repository. 3 | 4 | https://hacs.xyz/docs/publish/start#hacsjson 5 | """ 6 | from typing import List 7 | 8 | import attr 9 | 10 | from custom_components.hacs.helpers.classes.exceptions import HacsException 11 | 12 | 13 | @attr.s(auto_attribs=True) 14 | class HacsManifest: 15 | """HacsManifest class.""" 16 | 17 | name: str = None 18 | content_in_root: bool = False 19 | zip_release: bool = False 20 | filename: str = None 21 | manifest: dict = {} 22 | hacs: str = None 23 | hide_default_branch: bool = False 24 | domains: List[str] = [] 25 | country: List[str] = [] 26 | homeassistant: str = None 27 | persistent_directory: str = None 28 | iot_class: str = None 29 | render_readme: bool = False 30 | 31 | @staticmethod 32 | def from_dict(manifest: dict): 33 | """Set attributes from dicts.""" 34 | if manifest is None: 35 | raise HacsException("Missing manifest data") 36 | 37 | manifest_data = HacsManifest() 38 | 39 | manifest_data.manifest = manifest 40 | 41 | for key in manifest: 42 | setattr(manifest_data, key, manifest[key]) 43 | return manifest_data 44 | -------------------------------------------------------------------------------- /custom_components/hacs/helpers/functions/get_list_from_default.py: -------------------------------------------------------------------------------- 1 | """Helper to get default repositories.""" 2 | import json 3 | from typing import List 4 | 5 | from aiogithubapi import AIOGitHubAPIException 6 | 7 | from custom_components.hacs.enums import HacsCategory 8 | from custom_components.hacs.helpers.classes.exceptions import HacsException 9 | from custom_components.hacs.helpers.functions.information import get_repository 10 | from custom_components.hacs.share import get_hacs 11 | 12 | 13 | async def async_get_list_from_default(default: HacsCategory) -> List: 14 | """Get repositories from default list.""" 15 | hacs = get_hacs() 16 | repositories = [] 17 | 18 | try: 19 | repo = await get_repository( 20 | hacs.session, 21 | hacs.configuration.token, 22 | "hacs/default", 23 | ) 24 | content = await repo.get_contents(default, repo.default_branch) 25 | repositories = json.loads(content.content) 26 | 27 | except (AIOGitHubAPIException, HacsException) as exception: 28 | hacs.log.error(exception) 29 | 30 | except (Exception, BaseException) as exception: 31 | hacs.log.error(exception) 32 | 33 | hacs.log.debug("Got %s elements for %s", len(repositories), default) 34 | 35 | return repositories 36 | -------------------------------------------------------------------------------- /includes/automation/000_system.yaml: -------------------------------------------------------------------------------- 1 | - alias: 00_start 2 | initial_state: true 3 | trigger: 4 | - platform: homeassistant 5 | event: start 6 | action: 7 | - delay: 00:00:10 8 | - service: script.turn_on 9 | entity_id: script.telegramm_start 10 | - delay: 00:00:01 11 | - service: mqtt.publish 12 | data_template: 13 | topic: "notification/alarm" 14 | payload_template: '000' 15 | retain: true 16 | - delay: 00:00:01 17 | - service: mqtt.publish 18 | data_template: 19 | topic: "notification/error" 20 | payload_template: '000' 21 | retain: true 22 | - delay: 00:00:01 23 | - service: mqtt.publish 24 | data_template: 25 | topic: "notification/info" 26 | payload_template: '000' 27 | retain: true 28 | - delay: 00:00:01 29 | - service: mqtt.publish 30 | data_template: 31 | topic: "notification/messages" 32 | payload_template: '000' 33 | retain: true 34 | 35 | - alias: 00_udpate_entity 36 | initial_state: true 37 | trigger: 38 | - platform: time_pattern 39 | seconds: '/30' 40 | action: 41 | - service: homeassistant.update_entity 42 | entity_id: 43 | - sensor.last_boot_custom 44 | 45 | -------------------------------------------------------------------------------- /includes/automation/103_info.yaml: -------------------------------------------------------------------------------- 1 | - id: Обработка info кодов 2 | alias: info_code 3 | initial_state: true 4 | trigger: 5 | - platform: state 6 | entity_id: sensor.info_code 7 | action: 8 | - service: script.turn_on 9 | data_template: 10 | entity_id: >- 11 | {%- if states.sensor.info_code.state == '000' -%} 12 | script.stub 13 | {%- elif states.sensor.info_code.state == '011' -%} 14 | script.telegramm_main_start 15 | {%- elif states.sensor.info_code.state == '012' -%} 16 | script.telegramm_main_report 17 | {%- elif states.sensor.info_code.state == '021' -%} 18 | script.telegramm_reserve_start 19 | {%- elif states.sensor.info_code.state == '022' -%} 20 | script.telegramm_reserve_report 21 | {%- elif states.sensor.info_code.state == '031' -%} 22 | script.telegramm_rasp3_start 23 | {%- elif states.sensor.info_code.state == '041' -%} 24 | script.telegramm_test_start 25 | {%- else -%} 26 | script.telegramm_unknown_error 27 | {%- endif -%} 28 | - delay: 00:00:01 29 | - service: mqtt.publish 30 | data_template: 31 | topic: "notification/info" 32 | payload_template: '000' 33 | retain: true -------------------------------------------------------------------------------- /custom_components/hacs/helpers/functions/store.py: -------------------------------------------------------------------------------- 1 | """Storage handers.""" 2 | # pylint: disable=import-outside-toplevel 3 | from homeassistant.helpers.json import JSONEncoder 4 | 5 | from custom_components.hacs.const import VERSION_STORAGE 6 | 7 | 8 | async def async_load_from_store(hass, key): 9 | """Load the retained data from store and return de-serialized data.""" 10 | from homeassistant.helpers.storage import Store 11 | 12 | key = key if "/" in key else f"hacs.{key}" 13 | store = Store(hass, VERSION_STORAGE, key, encoder=JSONEncoder) 14 | restored = await store.async_load() 15 | if restored is None: 16 | return {} 17 | return restored 18 | 19 | 20 | async def async_save_to_store(hass, key, data): 21 | """Generate dynamic data to store and save it to the filesystem.""" 22 | from homeassistant.helpers.storage import Store 23 | 24 | key = key if "/" in key else f"hacs.{key}" 25 | store = Store(hass, VERSION_STORAGE, key, encoder=JSONEncoder) 26 | await store.async_save(data) 27 | 28 | 29 | async def async_remove_store(hass, key): 30 | """Remove a store element that should no longer be used""" 31 | from homeassistant.helpers.storage import Store 32 | 33 | if "/" not in key: 34 | return 35 | store = Store(hass, VERSION_STORAGE, key, encoder=JSONEncoder) 36 | await store.async_remove() 37 | -------------------------------------------------------------------------------- /custom_components/hacs/helpers/classes/frontend_view.py: -------------------------------------------------------------------------------- 1 | """HACS http endpoints.""" 2 | from aiohttp import web 3 | from homeassistant.components.http import HomeAssistantView 4 | 5 | from custom_components.hacs.helpers.functions.logger import getLogger 6 | from custom_components.hacs.webresponses.category import async_serve_category_file 7 | from custom_components.hacs.webresponses.frontend import async_serve_frontend 8 | from custom_components.hacs.webresponses.iconset import serve_iconset 9 | 10 | IGNORE = ["class-map.js.map"] 11 | 12 | 13 | class HacsFrontend(HomeAssistantView): 14 | """Base View Class for HACS.""" 15 | 16 | requires_auth = False 17 | name = "hacs_files" 18 | url = r"/hacsfiles/{requested_file:.+}" 19 | 20 | async def get(self, request, requested_file): # pylint: disable=unused-argument 21 | """Handle HACS Web requests.""" 22 | return await get_file_response(requested_file) 23 | 24 | 25 | async def get_file_response(requested_file): 26 | """Get file.""" 27 | logger = getLogger("web") 28 | 29 | if requested_file in IGNORE: 30 | logger.debug(f"Ignoring request for {requested_file}") 31 | return web.Response(status=200) 32 | 33 | if requested_file.startswith("frontend-"): 34 | return await async_serve_frontend() 35 | 36 | elif requested_file == "iconset.js": 37 | return serve_iconset() 38 | 39 | return await async_serve_category_file(requested_file) 40 | -------------------------------------------------------------------------------- /custom_components/hacs/helpers/functions/constrains.py: -------------------------------------------------------------------------------- 1 | """HACS Startup constrains.""" 2 | # pylint: disable=bad-continuation 3 | import os 4 | 5 | from custom_components.hacs.const import ( 6 | CUSTOM_UPDATER_LOCATIONS, 7 | CUSTOM_UPDATER_WARNING, 8 | ) 9 | from custom_components.hacs.helpers.functions.misc import version_left_higher_then_right 10 | from custom_components.hacs.share import get_hacs 11 | 12 | MINIMUM_HA_VERSION = "0.110.0" 13 | 14 | 15 | def check_constrains(): 16 | """Check HACS constrains.""" 17 | if not constrain_custom_updater(): 18 | return False 19 | if not constrain_version(): 20 | return False 21 | return True 22 | 23 | 24 | def constrain_custom_updater(): 25 | """Check if custom_updater exist.""" 26 | hacs = get_hacs() 27 | for location in CUSTOM_UPDATER_LOCATIONS: 28 | if os.path.exists(location.format(hacs.system.config_path)): 29 | msg = CUSTOM_UPDATER_WARNING.format( 30 | location.format(hacs.system.config_path) 31 | ) 32 | hacs.log.critical(msg) 33 | return False 34 | return True 35 | 36 | 37 | def constrain_version(): 38 | """Check if the version is valid.""" 39 | hacs = get_hacs() 40 | if not version_left_higher_then_right(hacs.system.ha_version, MINIMUM_HA_VERSION): 41 | hacs.log.critical( 42 | f"You need HA version {MINIMUM_HA_VERSION} or newer to use this integration." 43 | ) 44 | return False 45 | return True 46 | -------------------------------------------------------------------------------- /includes/scripts/z2mqtt.yaml: -------------------------------------------------------------------------------- 1 | selftest: 2 | sequence: 3 | - service: mqtt.publish 4 | data_template: 5 | topic: "zigbee2mqtt/EN smoke sensor/set" 6 | payload_template: '{"selftest": ""}' 7 | 8 | sensitivity: 9 | sequence: 10 | - service: mqtt.publish 11 | data_template: 12 | # topic: "zigbee2mqtt/CR vibration/set" 13 | topic: "zigbee2mqtt/EN smoke sensor/set" 14 | payload_template: '{"sensitivity": "medium"}' 15 | 16 | 17 | 18 | get_info: 19 | sequence: 20 | - service: mqtt.publish 21 | data_template: 22 | topic: "zigbee2mqtt/BT wd 2ch enter/get" 23 | payload_template: '{ "operation_mode": {"button": "right"}}' 24 | 25 | set_info: 26 | sequence: 27 | - service: mqtt.publish 28 | data_template: 29 | topic: "zigbee2mqtt/BT wd 2ch enter/set" 30 | payload_template: '{ "operation_mode": {"button": "right", "state": "decoupled"}}' 31 | 32 | 33 | set_outage_on: 34 | sequence: 35 | - service: mqtt.publish 36 | data_template: 37 | topic: "ZigbeeSLS/LR table power/set" 38 | payload_template: '{"power_outage_memory": true}' 39 | 40 | 41 | delete_zigbee: 42 | sequence: 43 | - service: mqtt.publish 44 | data_template: 45 | topic: "zigbee2mqtt/bridge/config/remove" 46 | payload: "{{states('input_text.filed_1')}}" -------------------------------------------------------------------------------- /custom_components/hacs/helpers/functions/misc.py: -------------------------------------------------------------------------------- 1 | """Helper functions: misc""" 2 | import re 3 | import semantic_version 4 | 5 | RE_REPOSITORY = re.compile( 6 | r"(?:(?:.*github.com.)|^)([A-Za-z0-9-]+\/[\w.-]+?)(?:(?:\.git)?|(?:[^\w.-].*)?)$" 7 | ) 8 | 9 | 10 | def get_repository_name(repository) -> str: 11 | """Return the name of the repository for use in the frontend.""" 12 | 13 | if repository.repository_manifest.name is not None: 14 | return repository.repository_manifest.name 15 | 16 | if repository.data.category == "integration": 17 | if repository.integration_manifest: 18 | if "name" in repository.integration_manifest: 19 | return repository.integration_manifest["name"] 20 | 21 | return ( 22 | repository.data.full_name.split("/")[-1] 23 | .replace("-", " ") 24 | .replace("_", " ") 25 | .title() 26 | ) 27 | 28 | 29 | def version_left_higher_then_right(new: str, old: str) -> bool: 30 | """Return a bool if source is newer than target, will also be true if identical.""" 31 | if not isinstance(new, str) or not isinstance(old, str): 32 | return False 33 | if new == old: 34 | return True 35 | return semantic_version.Version.coerce(new) > semantic_version.Version.coerce(old) 36 | 37 | 38 | def extract_repository_from_url(url: str) -> str or None: 39 | """Extract the owner/repo part form a URL.""" 40 | match = re.match(RE_REPOSITORY, url) 41 | if not match: 42 | return None 43 | return match.group(1).lower() 44 | -------------------------------------------------------------------------------- /custom_components/hacs/helpers/methods/registration.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=missing-class-docstring,missing-module-docstring,missing-function-docstring,no-member, attribute-defined-outside-init 2 | from abc import ABC 3 | 4 | from custom_components.hacs.validate import async_run_repository_checks 5 | 6 | 7 | class RepositoryMethodPreRegistration(ABC): 8 | async def async_pre_registration(self): 9 | pass 10 | 11 | 12 | class RepositoryMethodRegistration(ABC): 13 | async def registration(self, ref=None) -> None: 14 | self.logger.warning( 15 | "'registration' is deprecated, use 'async_registration' instead" 16 | ) 17 | await self.async_registration(ref) 18 | 19 | async def async_registration(self, ref=None) -> None: 20 | # Run local pre registration steps. 21 | await self.async_pre_registration() 22 | 23 | if ref is not None: 24 | self.data.selected_tag = ref 25 | self.ref = ref 26 | self.force_branch = True 27 | 28 | if not await self.validate_repository(): 29 | return False 30 | 31 | # Run common registration steps. 32 | await self.common_registration() 33 | 34 | # Set correct local path 35 | self.content.path.local = self.localpath 36 | 37 | # Run local post registration steps. 38 | await self.async_post_registration() 39 | 40 | 41 | class RepositoryMethodPostRegistration(ABC): 42 | async def async_post_registration(self): 43 | await async_run_repository_checks(self) 44 | -------------------------------------------------------------------------------- /custom_components/hacs/validate/base.py: -------------------------------------------------------------------------------- 1 | from custom_components.hacs.share import SHARE, get_hacs 2 | 3 | 4 | class ValidationException(Exception): 5 | pass 6 | 7 | 8 | class ValidationBase: 9 | def __init__(self, repository) -> None: 10 | self.repository = repository 11 | self.hacs = get_hacs() 12 | self.failed = False 13 | self.logger = repository.logger 14 | 15 | def __init_subclass__(cls, category="common", **kwargs) -> None: 16 | """Initialize a subclass, register if possible.""" 17 | super().__init_subclass__(**kwargs) 18 | if SHARE["rules"].get(category) is None: 19 | SHARE["rules"][category] = [] 20 | if cls not in SHARE["rules"][category]: 21 | SHARE["rules"][category].append(cls) 22 | 23 | @property 24 | def action_only(self): 25 | return False 26 | 27 | async def _async_run_check(self): 28 | """DO NOT OVERRIDE THIS IN SUBCLASSES!""" 29 | if self.hacs.system.action: 30 | self.logger.info(f"Running check '{self.__class__.__name__}'") 31 | try: 32 | await self.hacs.hass.async_add_executor_job(self.check) 33 | await self.async_check() 34 | except ValidationException as exception: 35 | self.failed = True 36 | self.logger.error(exception) 37 | 38 | def check(self): 39 | pass 40 | 41 | async def async_check(self): 42 | pass 43 | 44 | 45 | class ActionValidationBase(ValidationBase): 46 | @property 47 | def action_only(self): 48 | return True 49 | -------------------------------------------------------------------------------- /custom_components/hacs/validate/README.md: -------------------------------------------------------------------------------- 1 | # Repository validation 2 | 3 | This is where the validation rules that run against the various repository categories live. 4 | 5 | ## Structure 6 | 7 | - All validation rules are in the directory for their category. 8 | - Validation rules that aplies to all categories are in the `common` directory. 9 | - There is one file pr. rule. 10 | - All rule needs tests to verify every possible outcome for the rule. 11 | - It's better with multiple files than a big rule. 12 | - All rules uses `ValidationBase` or `ActionValidationBase` as the base class. 13 | - The `ActionValidationBase` are for checks that will breaks compatibility with with existing repositories (default), so these are only run in github actions. 14 | - The class name should describe what the check does. 15 | - Only use `validate` or `async_validate` methods to define validation rules. 16 | - If a rule should fail, raise `ValidationException` with the failure message. 17 | 18 | 19 | ## Example 20 | 21 | ```python 22 | from custom_components.hacs.validate.base import ( 23 | ActionValidationBase, 24 | ValidationBase, 25 | ValidationException, 26 | ) 27 | 28 | 29 | class AwesomeRepository(ValidationBase): 30 | def validate(self): 31 | if self.repository != "awesome": 32 | raise ValidationException("The repository is not awesome") 33 | 34 | class SuperAwesomeRepository(ActionValidationBase, category="integration"): 35 | async def async_validate(self): 36 | if self.repository != "super-awesome": 37 | raise ValidationException("The repository is not super-awesome") 38 | ``` -------------------------------------------------------------------------------- /custom_components/hacs/webresponses/category.py: -------------------------------------------------------------------------------- 1 | from aiohttp import web 2 | 3 | from custom_components.hacs.helpers.functions.logger import getLogger 4 | from custom_components.hacs.helpers.functions.path_exsist import async_path_exsist 5 | from custom_components.hacs.share import get_hacs 6 | 7 | 8 | async def async_serve_category_file(requested_file): 9 | hacs = get_hacs() 10 | logger = getLogger("web.category") 11 | try: 12 | if requested_file.startswith("themes/"): 13 | servefile = f"{hacs.system.config_path}/{requested_file}" 14 | else: 15 | servefile = f"{hacs.system.config_path}/www/community/{requested_file}" 16 | 17 | # Serve .gz if it exist 18 | if await async_path_exsist(f"{servefile}.gz"): 19 | servefile += ".gz" 20 | 21 | if await async_path_exsist(servefile): 22 | logger.debug(f"Serving {requested_file} from {servefile}") 23 | response = web.FileResponse(servefile) 24 | if requested_file.startswith("themes/"): 25 | response.headers["Cache-Control"] = "public, max-age=2678400" 26 | else: 27 | response.headers["Cache-Control"] = "no-store, max-age=0" 28 | response.headers["Pragma"] = "no-store" 29 | return response 30 | else: 31 | logger.error(f"Tried to serve up '{servefile}' but it does not exist") 32 | 33 | except (Exception, BaseException) as exception: 34 | logger.debug( 35 | f"there was an issue trying to serve {requested_file} - {exception}" 36 | ) 37 | 38 | return web.Response(status=404) 39 | -------------------------------------------------------------------------------- /custom_components/hacs/operational/setup_actions/load_hacs_repository.py: -------------------------------------------------------------------------------- 1 | """Starting setup task: load HACS repository.""" 2 | from custom_components.hacs.const import VERSION 3 | from custom_components.hacs.helpers.classes.exceptions import HacsException 4 | from custom_components.hacs.helpers.functions.information import get_repository 5 | from custom_components.hacs.helpers.functions.register_repository import ( 6 | register_repository, 7 | ) 8 | from custom_components.hacs.share import get_hacs 9 | from ...enums import HacsSetupTask 10 | 11 | 12 | async def async_load_hacs_repository(): 13 | """Load HACS repositroy.""" 14 | hacs = get_hacs() 15 | hacs.log.info("Setup task %s", HacsSetupTask.HACS_REPO) 16 | 17 | try: 18 | repository = hacs.get_by_name("hacs/integration") 19 | if repository is None: 20 | await register_repository("hacs/integration", "integration") 21 | repository = hacs.get_by_name("hacs/integration") 22 | if repository is None: 23 | raise HacsException("Unknown error") 24 | repository.data.installed = True 25 | repository.data.installed_version = VERSION 26 | repository.data.new = False 27 | hacs.repo = repository.repository_object 28 | hacs.data_repo = await get_repository( 29 | hacs.session, hacs.configuration.token, "hacs/default" 30 | ) 31 | except HacsException as exception: 32 | if "403" in f"{exception}": 33 | hacs.log.critical("GitHub API is ratelimited, or the token is wrong.") 34 | else: 35 | hacs.log.critical(f"[{exception}] - Could not load HACS!") 36 | return False 37 | return True 38 | -------------------------------------------------------------------------------- /custom_components/hacs/operational/setup_actions/categories.py: -------------------------------------------------------------------------------- 1 | """Starting setup task: extra stores.""" 2 | from custom_components.hacs.const import ELEMENT_TYPES 3 | from ...share import get_hacs 4 | from ...enums import HacsCategory, HacsSetupTask 5 | 6 | 7 | def _setup_extra_stores(): 8 | """Set up extra stores in HACS if enabled in Home Assistant.""" 9 | hacs = get_hacs() 10 | hacs.log.debug("Starting setup task: Extra stores") 11 | hacs.common.categories = set() 12 | for category in ELEMENT_TYPES: 13 | enable_category(hacs, HacsCategory(category)) 14 | 15 | if HacsCategory.PYTHON_SCRIPT in hacs.hass.config.components: 16 | if HacsCategory.PYTHON_SCRIPT not in hacs.common.categories: 17 | enable_category(hacs, HacsCategory.PYTHON_SCRIPT) 18 | 19 | if ( 20 | hacs.hass.services._services.get("frontend", {}).get("reload_themes") 21 | is not None 22 | ): 23 | if HacsCategory.THEME not in hacs.common.categories: 24 | enable_category(hacs, HacsCategory.THEME) 25 | 26 | if hacs.configuration.appdaemon: 27 | enable_category(hacs, HacsCategory.APPDAEMON) 28 | if hacs.configuration.netdaemon: 29 | enable_category(hacs, HacsCategory.NETDAEMON) 30 | 31 | 32 | async def async_setup_extra_stores(): 33 | """Async wrapper for setup_extra_stores""" 34 | hacs = get_hacs() 35 | hacs.log.info("setup task %s", HacsSetupTask.CATEGORIES) 36 | await hacs.hass.async_add_executor_job(_setup_extra_stores) 37 | 38 | 39 | def enable_category(hacs, category: HacsCategory): 40 | """Add category.""" 41 | hacs.log.debug("Enable category: %s", category) 42 | hacs.common.categories.add(category) 43 | -------------------------------------------------------------------------------- /includes/sensors/01_main_serv.yaml: -------------------------------------------------------------------------------- 1 | - platform: rest 2 | resource: http://192.168.0.70:8123/api/states/sensor.online_custom 3 | name: main_state 4 | force_update: true 5 | headers: 6 | Authorization: !secret main_secret_token 7 | content-type: 'application/json' 8 | value_template: '{{ value_json.state }}' 9 | 10 | - platform: rest 11 | resource: http://192.168.0.70:8123/api/states/group.scenario 12 | name: main_scenario_group 13 | force_update: true 14 | headers: 15 | Authorization: !secret main_secret_token 16 | content-type: 'application/json' 17 | value_template: '{{ value_json.state }}' 18 | 19 | - platform: mqtt 20 | name: "main_system_unavailable_now_light" 21 | state_topic: "instance/main/sensor/unavailable_now_light/state" 22 | 23 | - platform: mqtt 24 | name: "main_system_unavailable_now_switch" 25 | state_topic: "instance/main/sensor/unavailable_now_switch/state" 26 | 27 | - platform: mqtt 28 | name: "main_system_unavailable_now_sensor" 29 | state_topic: "instance/main/sensor/unavailable_now_sensor/state" 30 | 31 | - platform: mqtt 32 | name: "main_system_unavailable_now_binary_sensor" 33 | state_topic: "instance/main/sensor/unavailable_now_binary_sensor/state" 34 | 35 | - platform: mqtt 36 | name: "last_google_backup_main" 37 | state_topic: "instance/main/sensor/last_google_backup_main/state" 38 | 39 | - platform: mqtt 40 | name: "snapshots_in_google_drive_main" 41 | state_topic: "instance/main/sensor/snapshots_in_google_drive_main/state" 42 | 43 | - platform: mqtt 44 | name: "snapshots_in_hassio_main" 45 | state_topic: "instance/main/sensor/snapshots_in_hassio_main/state" 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /custom_components/hacs/operational/setup_actions/frontend.py: -------------------------------------------------------------------------------- 1 | from hacs_frontend.version import VERSION as FE_VERSION 2 | 3 | from custom_components.hacs.helpers.classes.frontend_view import HacsFrontend 4 | from custom_components.hacs.helpers.functions.information import get_frontend_version 5 | from custom_components.hacs.share import get_hacs 6 | from ...enums import HacsSetupTask 7 | 8 | 9 | async def async_setup_frontend(): 10 | """Configure the HACS frontend elements.""" 11 | hacs = get_hacs() 12 | hacs.log.info("Setup task %s", HacsSetupTask.FRONTEND) 13 | 14 | # Custom view 15 | hacs.hass.http.register_view(HacsFrontend()) 16 | 17 | # Custom iconset 18 | if "frontend_extra_module_url" not in hacs.hass.data: 19 | hacs.hass.data["frontend_extra_module_url"] = set() 20 | hacs.hass.data["frontend_extra_module_url"].add("/hacsfiles/iconset.js") 21 | 22 | hacs.frontend.version_running = FE_VERSION 23 | hacs.frontend.version_expected = await hacs.hass.async_add_executor_job( 24 | get_frontend_version 25 | ) 26 | 27 | # Add to sidepanel 28 | custom_panel_config = { 29 | "name": "hacs-frontend", 30 | "embed_iframe": True, 31 | "trust_external": False, 32 | "js_url": f"/hacsfiles/frontend-{hacs.frontend.version_running}.js", 33 | } 34 | 35 | config = {} 36 | config["_panel_custom"] = custom_panel_config 37 | 38 | hacs.hass.components.frontend.async_register_built_in_panel( 39 | component_name="custom", 40 | sidebar_title=hacs.configuration.sidepanel_title, 41 | sidebar_icon=hacs.configuration.sidepanel_icon, 42 | frontend_url_path="hacs", 43 | config=config, 44 | require_admin=True, 45 | ) 46 | -------------------------------------------------------------------------------- /custom_components/hacs/validate/__init__.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import glob 3 | import importlib 4 | from os.path import dirname, join, sep 5 | 6 | from custom_components.hacs.share import SHARE, get_hacs 7 | 8 | 9 | def _initialize_rules(): 10 | rules = glob.glob(join(dirname(__file__), "**/*.py")) 11 | for rule in rules: 12 | rule = rule.replace(sep, "/") 13 | rule = rule.split("custom_components/hacs")[-1] 14 | rule = f"custom_components/hacs{rule}".replace("/", ".")[:-3] 15 | importlib.import_module(rule) 16 | 17 | 18 | async def async_initialize_rules(): 19 | hass = get_hacs().hass 20 | await hass.async_add_executor_job(_initialize_rules) 21 | 22 | 23 | async def async_run_repository_checks(repository): 24 | hacs = get_hacs() 25 | if not SHARE["rules"]: 26 | await async_initialize_rules() 27 | if not hacs.system.running: 28 | return 29 | checks = [] 30 | for check in SHARE["rules"].get("common", []): 31 | checks.append(check(repository)) 32 | for check in SHARE["rules"].get(repository.data.category, []): 33 | checks.append(check(repository)) 34 | 35 | await asyncio.gather( 36 | *[ 37 | check._async_run_check() 38 | for check in checks or [] 39 | if hacs.system.action or not check.action_only 40 | ] 41 | ) 42 | 43 | total = len([x for x in checks if hacs.system.action or not x.action_only]) 44 | failed = len([x for x in checks if x.failed]) 45 | 46 | if failed != 0: 47 | repository.logger.error(f"{failed}/{total} checks failed") 48 | if hacs.system.action: 49 | exit(1) 50 | else: 51 | repository.logger.debug(f"All ({total}) checks passed") 52 | -------------------------------------------------------------------------------- /includes/automation/101_alarm.yaml: -------------------------------------------------------------------------------- 1 | - id: Обработка alarm кодов 2 | alias: alarm_code 3 | initial_state: true 4 | trigger: 5 | - platform: state 6 | entity_id: sensor.alarm_code 7 | action: 8 | - service: script.turn_on 9 | data_template: 10 | entity_id: >- 11 | {%- if states.sensor.alarm_code.state == '000' -%} 12 | script.stub 13 | {%- elif states.sensor.alarm_code.state == '0100' -%} 14 | script.telegramm_a0100 15 | {%- elif states.sensor.alarm_code.state == '0101' -%} 16 | script.telegramm_a0101 17 | {%- elif states.sensor.alarm_code.state == '0200' -%} 18 | script.telegramm_a0200 19 | {%- elif states.sensor.alarm_code.state == '0201' -%} 20 | script.telegramm_a0201 21 | {%- elif states.sensor.alarm_code.state == '0400' -%} 22 | script.telegramm_a0400 23 | {%- elif states.sensor.alarm_code.state == '0401' -%} 24 | script.telegramm_a0401 25 | {%- elif states.sensor.alarm_code.state == '0600' -%} 26 | script.telegramm_a0600 27 | {%- elif states.sensor.alarm_code.state == '0601' -%} 28 | script.telegramm_a0601 29 | {%- elif states.sensor.alarm_code.state == '0602' -%} 30 | script.telegramm_a0602 31 | {%- elif states.sensor.alarm_code.state == '0603' -%} 32 | script.telegramm_a0603 33 | {%- else -%} 34 | script.telegramm_unknown_error 35 | {%- endif -%} 36 | - delay: 00:00:01 37 | - service: mqtt.publish 38 | data_template: 39 | topic: "notification/alarm" 40 | payload_template: '000' 41 | retain: true -------------------------------------------------------------------------------- /includes/sensors/02_reserve_serv.yaml: -------------------------------------------------------------------------------- 1 | - platform: rest 2 | resource: http://192.168.0.71:8123/api/states/group.scenario 3 | name: reserve_scenario_group 4 | force_update: true 5 | headers: 6 | Authorization: !secret rasp4_2_secret_token 7 | content-type: 'application/json' 8 | value_template: '{{ value_json.state }}' 9 | 10 | - platform: rest 11 | resource: http://192.168.0.71:8123/api/states/sensor.online_custom 12 | name: reserve_state 13 | force_update: true 14 | headers: 15 | Authorization: !secret rasp4_2_secret_token 16 | content-type: 'application/json' 17 | value_template: '{{ value_json.state }}' 18 | 19 | - platform: mqtt 20 | name: "reserve_system_unavailable_now_light" 21 | state_topic: "instance/reserve/sensor/unavailable_now_light/state" 22 | 23 | - platform: mqtt 24 | name: "reserve_system_unavailable_now_switch" 25 | state_topic: "instance/reserve/sensor/unavailable_now_switch/state" 26 | 27 | - platform: mqtt 28 | name: "reserve_system_unavailable_now_sensor" 29 | state_topic: "instance/reserve/sensor/unavailable_now_sensor/state" 30 | 31 | - platform: mqtt 32 | name: "reserve_system_unavailable_now_binary_sensor" 33 | state_topic: "instance/reserve/sensor/unavailable_now_binary_sensor/state" 34 | 35 | - platform: mqtt 36 | name: "last_google_backup_reserve" 37 | state_topic: "instance/reserve/sensor/last_google_backup_reserve/state" 38 | 39 | - platform: mqtt 40 | name: "snapshots_in_google_drive_reserve" 41 | state_topic: "instance/reserve/sensor/snapshots_in_google_drive_reserve/state" 42 | 43 | - platform: mqtt 44 | name: "snapshots_in_hassio_reserve" 45 | state_topic: "instance/reserve/sensor/snapshots_in_hassio_reserve/state" 46 | 47 | 48 | -------------------------------------------------------------------------------- /includes/scripts/010_notification.yaml: -------------------------------------------------------------------------------- 1 | telegramm_a0100: 2 | alias: Код 0100 обнаружение дыма 3 | sequence: 4 | - service: telegram_bot.send_message 5 | data_template: 6 | target: 7 | - !secret chat_id_group 8 | message: 'Дым в прихожей! Время события - {{ states.sensor.time_date.state }}' 9 | - service: script.turn_on 10 | entity_id: 11 | - script.photo_xiaofang 12 | - script.photo_reolink2 13 | 14 | telegramm_a0101: 15 | alias: Код 0101 задымление устранено 16 | sequence: 17 | - service: telegram_bot.send_message 18 | data_template: 19 | target: 20 | - !secret chat_id_group 21 | message: 'Задымление устранено. Время события - {{ states.sensor.time_date.state }}' 22 | - service: script.turn_on 23 | entity_id: 24 | - script.photo_xiaofang 25 | - script.photo_reolink2 26 | 27 | 28 | open_door: 29 | alias: Уведомление об открытии двери 30 | sequence: 31 | - service: telegram_bot.send_message 32 | data_template: 33 | target: 34 | - !secret chat_id_group 35 | message: 'Входная дверь открыта в {{ states.sensor.time_date.state }}' 36 | - service: script.turn_on 37 | entity_id: 38 | - script.photo_xiaofang 39 | - script.photo_reolink 40 | - script.photo_reolink2 41 | - script.photo_reolink3 42 | 43 | telegramm_101: 44 | alias: Код 101 лампочка в прихожей 45 | sequence: 46 | - service: notify.telegram_alex 47 | data: 48 | message: "Потеря контроля в течении 5 минут - лампочка в прихожей. Время события - {{ states('sensor.time_date') }} . Инициирована перезагрузка" 49 | 50 | -------------------------------------------------------------------------------- /custom_components/hacs/helpers/functions/save.py: -------------------------------------------------------------------------------- 1 | """Download.""" 2 | import gzip 3 | import os 4 | import shutil 5 | 6 | import aiofiles 7 | 8 | from custom_components.hacs.helpers.functions.logger import getLogger 9 | 10 | 11 | async def async_save_file(location, content): 12 | """Save files.""" 13 | logger = getLogger("download.save") 14 | logger.debug(f"Saving {location}") 15 | mode = "w" 16 | encoding = "utf-8" 17 | errors = "ignore" 18 | 19 | if not isinstance(content, str): 20 | mode = "wb" 21 | encoding = None 22 | errors = None 23 | 24 | try: 25 | async with aiofiles.open( 26 | location, mode=mode, encoding=encoding, errors=errors 27 | ) as outfile: 28 | await outfile.write(content) 29 | outfile.close() 30 | 31 | # Create gz for .js files 32 | if os.path.isfile(location): 33 | if location.endswith(".js") or location.endswith(".css"): 34 | with open(location, "rb") as f_in: 35 | with gzip.open(location + ".gz", "wb") as f_out: 36 | shutil.copyfileobj(f_in, f_out) 37 | 38 | # Remove with 2.0 39 | if "themes" in location and location.endswith(".yaml"): 40 | filename = location.split("/")[-1] 41 | base = location.split("/themes/")[0] 42 | combined = f"{base}/themes/{filename}" 43 | if os.path.exists(combined): 44 | logger.info(f"Removing old theme file {combined}") 45 | os.remove(combined) 46 | 47 | except (Exception, BaseException) as error: # pylint: disable=broad-except 48 | msg = f"Could not write data to {location} - {error}" 49 | logger.error(msg) 50 | return False 51 | 52 | return os.path.exists(location) 53 | -------------------------------------------------------------------------------- /includes/scripts/001_main_serv.yaml: -------------------------------------------------------------------------------- 1 | telegramm_main_start: 2 | sequence: 3 | - service: notify.telegram_alex 4 | data: 5 | message: | 6 | {{"\U0001F4AC"}} Основной сервер Raspberry Pi 4 B 4GB 7 | {{"\U0001F567"}} Зафиксирован запуск в {{ states('sensor.time_date') }} 8 | {{"\U0001F4C3"}} Отчет о состоянии будет через 1 минуту 9 | 10 | 11 | telegramm_main_report: 12 | sequence: 13 | - service: notify.telegram_alex 14 | data: 15 | message: | 16 | {{"\U0001F4AC"}} Основной сервер Raspberry Pi 4 B 4GB 17 | {{"\U0001F567"}} Отчет за {{ states('sensor.time_date') }} 18 | {{"\U0001F503"}} Режим управления - {{ states('switch.main_mode') }} 19 | {{"\U0001F4DC"}} Бытовые сценарии - {{ states('sensor.main_scenario_group') }} 20 | {{"\U0001F4A1"}} недоступно - {{ states('sensor.main_system_unavailable_now_light') }} 21 | {{"\U0001F50C"}} недоступно - {{ states('sensor.main_system_unavailable_now_switch') }} 22 | {{"\U0001F321"}} Сенсоров недоступно - {{ states('sensor.main_system_unavailable_now_sensor') }} 23 | {{"\U0001F51F"}} Бинарных сенсоров недоступно - {{ states('sensor.main_system_unavailable_now_binary_sensor') }} 24 | 25 | telegramm_main_offline: 26 | sequence: 27 | - service: notify.telegram_alex 28 | data: 29 | message: | 30 | {{"\U0001F4AC"}} Потеря связи с основным серевером в {{ states('sensor.time_date') }} 31 | 32 | telegramm_main_online: 33 | sequence: 34 | - service: notify.telegram_alex 35 | data: 36 | message: | 37 | {{"\U0001F4AC"}} Связь с основным серевером восстановлена в {{ states('sensor.time_date') }} -------------------------------------------------------------------------------- /custom_components/hacs/share.py: -------------------------------------------------------------------------------- 1 | """Shared HACS elements.""" 2 | import os 3 | 4 | from .models.base import Hacs 5 | 6 | SHARE = { 7 | "hacs": None, 8 | "factory": None, 9 | "queue": None, 10 | "removed_repositories": [], 11 | "rules": {}, 12 | } 13 | 14 | 15 | def get_hacs() -> Hacs: 16 | if SHARE["hacs"] is None: 17 | from custom_components.hacs.hacsbase.hacs import Hacs as Legacy 18 | 19 | _hacs = Legacy() 20 | 21 | if not "PYTEST" in os.environ and "GITHUB_ACTION" in os.environ: 22 | _hacs.system.action = True 23 | 24 | SHARE["hacs"] = _hacs 25 | 26 | return SHARE["hacs"] 27 | 28 | 29 | def get_factory(): 30 | if SHARE["factory"] is None: 31 | from custom_components.hacs.operational.factory import HacsTaskFactory 32 | 33 | SHARE["factory"] = HacsTaskFactory() 34 | 35 | return SHARE["factory"] 36 | 37 | 38 | def get_queue(): 39 | if SHARE["queue"] is None: 40 | from queueman import QueueManager 41 | 42 | SHARE["queue"] = QueueManager() 43 | 44 | return SHARE["queue"] 45 | 46 | 47 | def is_removed(repository): 48 | return repository in [x.repository for x in SHARE["removed_repositories"]] 49 | 50 | 51 | def get_removed(repository): 52 | if not is_removed(repository): 53 | from custom_components.hacs.helpers.classes.removed import RemovedRepository 54 | 55 | removed_repo = RemovedRepository() 56 | removed_repo.repository = repository 57 | SHARE["removed_repositories"].append(removed_repo) 58 | filter_repos = [ 59 | x 60 | for x in SHARE["removed_repositories"] 61 | if x.repository.lower() == repository.lower() 62 | ] 63 | 64 | return filter_repos.pop() or None 65 | 66 | 67 | def list_removed_repositories(): 68 | return SHARE["removed_repositories"] 69 | -------------------------------------------------------------------------------- /includes/scripts/002_reserve_serv.yaml: -------------------------------------------------------------------------------- 1 | telegramm_reserve_start: 2 | sequence: 3 | - service: notify.telegram_alex 4 | data: 5 | message: | 6 | {{"\U0001F4AC"}} Резервный сервер Raspberry Pi 4 B 2GB 7 | {{"\U0001F567"}} Зафиксирован запуск в {{ states('sensor.time_date') }} 8 | {{"\U0001F4C3"}} Отчет о состоянии будет через 1 минуту 9 | 10 | 11 | telegramm_reserve_report: 12 | sequence: 13 | - service: notify.telegram_alex 14 | data: 15 | message: | 16 | {{"\U0001F4AC"}} Резервный сервер Raspberry Pi 4 B 2GB 17 | {{"\U0001F567"}} Отчет за {{ states('sensor.time_date') }} 18 | {{"\U0001F503"}} Режим управления - {{ states('switch.reserve_mode') }} 19 | {{"\U0001F4DC"}} Бытовые сценарии - {{ states('sensor.reserve_scenario_group') }} 20 | {{"\U0001F4A1"}} недоступно - {{ states('sensor.reserve_system_unavailable_now_light') }} 21 | {{"\U0001F50C"}} недоступно - {{ states('sensor.reserve_system_unavailable_now_switch') }} 22 | {{"\U0001F321"}} Сенсоров недоступно - {{ states('sensor.reserve_system_unavailable_now_sensor') }} 23 | {{"\U0001F51F"}} Бинарных сенсоров недоступно - {{ states('sensor.reserve_system_unavailable_now_binary_sensor') }} 24 | 25 | telegramm_reserve_offline: 26 | sequence: 27 | - service: notify.telegram_alex 28 | data: 29 | message: | 30 | {{"\U0001F4AC"}} Потеря связи с резервным серевером в {{ states('sensor.time_date') }} 31 | 32 | telegramm_reserve_online: 33 | sequence: 34 | - service: notify.telegram_alex 35 | data: 36 | message: | 37 | {{"\U0001F4AC"}} Связь с резервным серевером восстановлена в {{ states('sensor.time_date') }} -------------------------------------------------------------------------------- /custom_components/hacs/api/hacs_settings.py: -------------------------------------------------------------------------------- 1 | """API Handler for hacs_settings""" 2 | import homeassistant.helpers.config_validation as cv 3 | import voluptuous as vol 4 | from homeassistant.components import websocket_api 5 | 6 | from custom_components.hacs.helpers.functions.logger import getLogger 7 | from custom_components.hacs.share import get_hacs 8 | 9 | 10 | @websocket_api.async_response 11 | @websocket_api.websocket_command( 12 | { 13 | vol.Required("type"): "hacs/settings", 14 | vol.Optional("action"): cv.string, 15 | vol.Optional("categories"): cv.ensure_list, 16 | } 17 | ) 18 | async def hacs_settings(hass, connection, msg): 19 | """Handle get media player cover command.""" 20 | hacs = get_hacs() 21 | logger = getLogger("api.settings") 22 | 23 | action = msg["action"] 24 | logger.debug(f"WS action '{action}'") 25 | 26 | if action == "set_fe_grid": 27 | hacs.configuration.frontend_mode = "Grid" 28 | 29 | elif action == "onboarding_done": 30 | hacs.configuration.onboarding_done = True 31 | 32 | elif action == "set_fe_table": 33 | hacs.configuration.frontend_mode = "Table" 34 | 35 | elif action == "set_fe_compact_true": 36 | hacs.configuration.frontend_compact = False 37 | 38 | elif action == "set_fe_compact_false": 39 | hacs.configuration.frontend_compact = True 40 | 41 | elif action == "clear_new": 42 | for repo in hacs.repositories: 43 | if repo.data.new and repo.data.category in msg.get("categories", []): 44 | logger.debug(f"Clearing new flag from '{repo.data.full_name}'") 45 | repo.data.new = False 46 | else: 47 | logger.error(f"WS action '{action}' is not valid") 48 | hass.bus.async_fire("hacs/config", {}) 49 | await hacs.data.async_write() 50 | connection.send_message(websocket_api.result_message(msg["id"], {})) 51 | -------------------------------------------------------------------------------- /custom_components/hacs/webresponses/frontend.py: -------------------------------------------------------------------------------- 1 | from aiohttp import web 2 | from hacs_frontend import locate_debug_gz, locate_gz 3 | 4 | from custom_components.hacs.helpers.functions.logger import getLogger 5 | from custom_components.hacs.helpers.functions.path_exsist import async_path_exsist 6 | from custom_components.hacs.share import get_hacs 7 | 8 | logger = getLogger("web.frontend") 9 | 10 | 11 | async def async_serve_frontend(): 12 | hacs = get_hacs() 13 | servefile = None 14 | dev = False 15 | 16 | if hacs.configuration.frontend_repo_url: 17 | dev = True 18 | elif hacs.configuration.frontend_repo: 19 | dev = True 20 | 21 | if hacs.configuration.debug: 22 | logger.debug("Serving DEBUG frontend") 23 | servefile = locate_debug_gz() 24 | 25 | elif hacs.configuration.frontend_repo_url: 26 | logger.debug("Serving REMOTE DEVELOPMENT frontend") 27 | try: 28 | request = await hacs.session.get( 29 | f"{hacs.configuration.frontend_repo_url}/main.js" 30 | ) 31 | if request.status == 200: 32 | result = await request.read() 33 | response = web.Response(body=result) 34 | 35 | return response 36 | except (Exception, BaseException) as exception: 37 | logger.error(exception) 38 | 39 | elif hacs.configuration.frontend_repo: 40 | logger.debug("Serving LOCAL DEVELOPMENT frontend") 41 | servefile = f"{hacs.configuration.frontend_repo}/hacs_frontend/main.js" 42 | else: 43 | servefile = locate_gz() 44 | 45 | if servefile is None or not await async_path_exsist(servefile): 46 | return web.Response(status=404) 47 | 48 | response = web.FileResponse(servefile) 49 | 50 | if dev: 51 | response.headers["Cache-Control"] = "no-store, max-age=0" 52 | response.headers["Pragma"] = "no-store" 53 | return response 54 | -------------------------------------------------------------------------------- /custom_components/hacs/operational/setup_actions/websocket_api.py: -------------------------------------------------------------------------------- 1 | """Register WS API endpoints for HACS.""" 2 | from homeassistant.components import websocket_api 3 | 4 | from custom_components.hacs.api.acknowledge_critical_repository import ( 5 | acknowledge_critical_repository, 6 | ) 7 | from custom_components.hacs.api.check_local_path import check_local_path 8 | from custom_components.hacs.api.get_critical_repositories import ( 9 | get_critical_repositories, 10 | ) 11 | from custom_components.hacs.api.hacs_config import hacs_config 12 | from custom_components.hacs.api.hacs_removed import hacs_removed 13 | from custom_components.hacs.api.hacs_repositories import hacs_repositories 14 | from custom_components.hacs.api.hacs_repository import hacs_repository 15 | from custom_components.hacs.api.hacs_repository_data import hacs_repository_data 16 | from custom_components.hacs.api.hacs_settings import hacs_settings 17 | from custom_components.hacs.api.hacs_status import hacs_status 18 | from custom_components.hacs.share import get_hacs 19 | from ...enums import HacsSetupTask 20 | 21 | 22 | async def async_setup_hacs_websockt_api(): 23 | """Set up WS API handlers.""" 24 | hacs = get_hacs() 25 | hacs.log.info("Setup task %s", HacsSetupTask.WEBSOCKET) 26 | websocket_api.async_register_command(hacs.hass, hacs_settings) 27 | websocket_api.async_register_command(hacs.hass, hacs_config) 28 | websocket_api.async_register_command(hacs.hass, hacs_repositories) 29 | websocket_api.async_register_command(hacs.hass, hacs_repository) 30 | websocket_api.async_register_command(hacs.hass, hacs_repository_data) 31 | websocket_api.async_register_command(hacs.hass, check_local_path) 32 | websocket_api.async_register_command(hacs.hass, hacs_status) 33 | websocket_api.async_register_command(hacs.hass, hacs_removed) 34 | websocket_api.async_register_command(hacs.hass, acknowledge_critical_repository) 35 | websocket_api.async_register_command(hacs.hass, get_critical_repositories) 36 | -------------------------------------------------------------------------------- /custom_components/hacs/operational/factory.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=missing-docstring,invalid-name 2 | import asyncio 3 | from aiogithubapi import AIOGitHubAPIException 4 | 5 | from custom_components.hacs.helpers.classes.exceptions import HacsException 6 | from custom_components.hacs.helpers.functions.logger import getLogger 7 | from custom_components.hacs.helpers.functions.register_repository import ( 8 | register_repository, 9 | ) 10 | 11 | max_concurrent_tasks = asyncio.Semaphore(15) 12 | sleeper = 5 13 | 14 | logger = getLogger("factory") 15 | 16 | 17 | class HacsTaskFactory: 18 | def __init__(self): 19 | self.tasks = [] 20 | self.running = False 21 | 22 | async def safe_common_update(self, repository): 23 | async with max_concurrent_tasks: 24 | try: 25 | await repository.common_update() 26 | except (AIOGitHubAPIException, HacsException) as exception: 27 | logger.error("%s - %s", repository.data.full_name, exception) 28 | 29 | # Due to GitHub ratelimits we need to sleep a bit 30 | await asyncio.sleep(sleeper) 31 | 32 | async def safe_update(self, repository): 33 | async with max_concurrent_tasks: 34 | try: 35 | await repository.update_repository() 36 | except (AIOGitHubAPIException, HacsException) as exception: 37 | logger.error("%s - %s", repository.data.full_name, exception) 38 | 39 | # Due to GitHub ratelimits we need to sleep a bit 40 | await asyncio.sleep(sleeper) 41 | 42 | async def safe_register(self, repo, category): 43 | async with max_concurrent_tasks: 44 | try: 45 | await register_repository(repo, category) 46 | except (AIOGitHubAPIException, HacsException) as exception: 47 | logger.error("%s - %s", repo, exception) 48 | 49 | # Due to GitHub ratelimits we need to sleep a bit 50 | await asyncio.sleep(sleeper) 51 | -------------------------------------------------------------------------------- /includes/automation/102_error.yaml: -------------------------------------------------------------------------------- 1 | - id: Обработка error кодов 2 | alias: error_code 3 | initial_state: true 4 | trigger: 5 | - platform: state 6 | entity_id: sensor.error_code 7 | action: 8 | - service: script.turn_on 9 | data_template: 10 | entity_id: >- 11 | {%- if states.sensor.error_code.state == '000' -%} 12 | script.stub 13 | {%- elif states.sensor.error_code.state == '011' -%} 14 | script.telegramm_main_offline 15 | {%- elif states.sensor.error_code.state == '012' -%} 16 | script.telegramm_main_online 17 | {%- elif states.sensor.error_code.state == '021' -%} 18 | script.telegramm_reserve_offline 19 | {%- elif states.sensor.error_code.state == '022' -%} 20 | script.telegramm_reserve_online 21 | {%- elif states.sensor.error_code.state == '031' -%} 22 | script.telegramm_rasp3_offline 23 | {%- elif states.sensor.error_code.state == '032' -%} 24 | script.telegramm_rasp3_online 25 | {%- elif states.sensor.error_code.state == '041' -%} 26 | script.telegramm_test_offline 27 | {%- elif states.sensor.error_code.state == '042' -%} 28 | script.telegramm_test_online 29 | {%- elif states.sensor.error_code.state == '100' -%} 30 | script.light_offline_main 31 | {%- elif states.sensor.error_code.state == '101' -%} 32 | script.light_offline_reserve 33 | {%- elif states.sensor.error_code.state == '102' -%} 34 | script.switch_offline_main 35 | {%- elif states.sensor.error_code.state == '103' -%} 36 | script.switch_offline_reserve 37 | {%- else -%} 38 | script.telegramm_unknown_error 39 | {%- endif -%} 40 | - delay: 00:00:01 41 | - service: mqtt.publish 42 | data_template: 43 | topic: "notification/error" 44 | payload_template: '000' 45 | retain: true -------------------------------------------------------------------------------- /custom_components/hacs/helpers/functions/filters.py: -------------------------------------------------------------------------------- 1 | """Filter functions.""" 2 | 3 | 4 | def filter_content_return_one_of_type( 5 | content, namestartswith, filterfiltype, attr="name" 6 | ): 7 | """Only match 1 of the filter.""" 8 | contents = [] 9 | filetypefound = False 10 | for filename in content: 11 | if isinstance(filename, str): 12 | if filename.startswith(namestartswith): 13 | if filename.endswith(f".{filterfiltype}"): 14 | if not filetypefound: 15 | contents.append(filename) 16 | filetypefound = True 17 | continue 18 | else: 19 | contents.append(filename) 20 | else: 21 | if getattr(filename, attr).startswith(namestartswith): 22 | if getattr(filename, attr).endswith(f".{filterfiltype}"): 23 | if not filetypefound: 24 | contents.append(filename) 25 | filetypefound = True 26 | continue 27 | else: 28 | contents.append(filename) 29 | return contents 30 | 31 | 32 | def find_first_of_filetype(content, filterfiltype, attr="name"): 33 | """Find the first of the file type.""" 34 | filename = "" 35 | for _filename in content: 36 | if isinstance(_filename, str): 37 | if _filename.endswith(f".{filterfiltype}"): 38 | filename = _filename 39 | break 40 | else: 41 | if getattr(_filename, attr).endswith(f".{filterfiltype}"): 42 | filename = getattr(_filename, attr) 43 | break 44 | return filename 45 | 46 | 47 | def get_first_directory_in_directory(content, dirname): 48 | """Return the first directory in dirname or None.""" 49 | directory = None 50 | for path in content: 51 | if path.full_path.startswith(dirname) and path.full_path != dirname: 52 | if path.is_directory: 53 | directory = path.filename 54 | break 55 | return directory 56 | -------------------------------------------------------------------------------- /custom_components/hacs/hacsbase/configuration.py: -------------------------------------------------------------------------------- 1 | """HACS Configuration.""" 2 | import attr 3 | 4 | from custom_components.hacs.helpers.classes.exceptions import HacsException 5 | from custom_components.hacs.helpers.functions.logger import getLogger 6 | 7 | 8 | @attr.s(auto_attribs=True) 9 | class Configuration: 10 | """Configuration class.""" 11 | 12 | # Main configuration: 13 | appdaemon_path: str = "appdaemon/apps/" 14 | appdaemon: bool = False 15 | netdaemon_path: str = "netdaemon/apps/" 16 | netdaemon: bool = False 17 | config: dict = {} 18 | config_entry: dict = {} 19 | config_type: str = None 20 | debug: bool = False 21 | dev: bool = False 22 | frontend_mode: str = "Grid" 23 | frontend_compact: bool = False 24 | frontend_repo: str = "" 25 | frontend_repo_url: str = "" 26 | options: dict = {} 27 | onboarding_done: bool = False 28 | plugin_path: str = "www/community/" 29 | python_script_path: str = "python_scripts/" 30 | python_script: bool = False 31 | sidepanel_icon: str = "hacs:hacs" 32 | sidepanel_title: str = "HACS" 33 | theme_path: str = "themes/" 34 | theme: bool = False 35 | token: str = None 36 | 37 | # Config options: 38 | country: str = "ALL" 39 | experimental: bool = False 40 | release_limit: int = 5 41 | 42 | def to_json(self) -> dict: 43 | """Return a dict representation of the configuration.""" 44 | return self.__dict__ 45 | 46 | def print(self) -> None: 47 | """Print the current configuration to the log.""" 48 | logger = getLogger("configuration") 49 | config = self.to_json() 50 | for key in config: 51 | if key in ["config", "config_entry", "options", "token"]: 52 | continue 53 | logger.debug(f"{key}: {config[key]}") 54 | 55 | @staticmethod 56 | def from_dict(configuration: dict, options: dict = None) -> None: 57 | """Set attributes from dicts.""" 58 | if isinstance(options, bool) or isinstance(configuration.get("options"), bool): 59 | raise HacsException("Configuration is not valid.") 60 | 61 | if options is None: 62 | options = {} 63 | 64 | if not configuration: 65 | raise HacsException("Configuration is not valid.") 66 | 67 | config = Configuration() 68 | 69 | config.config = configuration 70 | config.options = options 71 | 72 | for conf_type in [configuration, options]: 73 | for key in conf_type: 74 | setattr(config, key, conf_type[key]) 75 | 76 | return config 77 | -------------------------------------------------------------------------------- /custom_components/hacs/helpers/functions/configuration_schema.py: -------------------------------------------------------------------------------- 1 | """HACS Configuration Schemas.""" 2 | # pylint: disable=dangerous-default-value 3 | import voluptuous as vol 4 | 5 | from custom_components.hacs.const import LOCALE 6 | 7 | # Configuration: 8 | TOKEN = "token" 9 | SIDEPANEL_TITLE = "sidepanel_title" 10 | SIDEPANEL_ICON = "sidepanel_icon" 11 | FRONTEND_REPO = "frontend_repo" 12 | FRONTEND_REPO_URL = "frontend_repo_url" 13 | APPDAEMON = "appdaemon" 14 | NETDAEMON = "netdaemon" 15 | 16 | # Options: 17 | COUNTRY = "country" 18 | DEBUG = "debug" 19 | RELEASE_LIMIT = "release_limit" 20 | EXPERIMENTAL = "experimental" 21 | 22 | # Config group 23 | PATH_OR_URL = "frontend_repo_path_or_url" 24 | 25 | 26 | def hacs_base_config_schema(config: dict = {}) -> dict: 27 | """Return a shcema configuration dict for HACS.""" 28 | if not config: 29 | config = { 30 | TOKEN: "xxxxxxxxxxxxxxxxxxxxxxxxxxx", 31 | } 32 | return { 33 | vol.Required(TOKEN, default=config.get(TOKEN)): str, 34 | } 35 | 36 | 37 | def hacs_config_option_schema(options: dict = {}) -> dict: 38 | """Return a shcema for HACS configuration options.""" 39 | if not options: 40 | options = { 41 | APPDAEMON: False, 42 | COUNTRY: "ALL", 43 | DEBUG: False, 44 | EXPERIMENTAL: False, 45 | NETDAEMON: False, 46 | RELEASE_LIMIT: 5, 47 | SIDEPANEL_ICON: "hacs:hacs", 48 | SIDEPANEL_TITLE: "HACS", 49 | FRONTEND_REPO: "", 50 | FRONTEND_REPO_URL: "", 51 | } 52 | return { 53 | vol.Optional(SIDEPANEL_TITLE, default=options.get(SIDEPANEL_TITLE)): str, 54 | vol.Optional(SIDEPANEL_ICON, default=options.get(SIDEPANEL_ICON)): str, 55 | vol.Optional(RELEASE_LIMIT, default=options.get(RELEASE_LIMIT)): int, 56 | vol.Optional(COUNTRY, default=options.get(COUNTRY)): vol.In(LOCALE), 57 | vol.Optional(APPDAEMON, default=options.get(APPDAEMON)): bool, 58 | vol.Optional(NETDAEMON, default=options.get(NETDAEMON)): bool, 59 | vol.Optional(DEBUG, default=options.get(DEBUG)): bool, 60 | vol.Optional(EXPERIMENTAL, default=options.get(EXPERIMENTAL)): bool, 61 | vol.Exclusive(FRONTEND_REPO, PATH_OR_URL): str, 62 | vol.Exclusive(FRONTEND_REPO_URL, PATH_OR_URL): str, 63 | } 64 | 65 | 66 | def hacs_config_combined() -> dict: 67 | """Combine the configuration options.""" 68 | base = hacs_base_config_schema() 69 | options = hacs_config_option_schema() 70 | 71 | for option in options: 72 | base[option] = options[option] 73 | 74 | return base 75 | -------------------------------------------------------------------------------- /configuration.yaml: -------------------------------------------------------------------------------- 1 | homeassistant: 2 | # Name of the location where Home Assistant is running 3 | name: MQTT base 4 | # Location required to calculate the time the sun rises and sets 5 | latitude: !secret latitude_coord 6 | longitude: !secret longitude_coord 7 | time_zone: Europe/Kiev 8 | 9 | customize: !include_dir_merge_named includes/customize 10 | packages: !include_dir_merge_named includes/packages 11 | 12 | auth_providers: 13 | - type: homeassistant 14 | - type: legacy_api_password 15 | api_password: !secret http_password 16 | 17 | whitelist_external_dirs : 18 | - /config 19 | 20 | # Стандартный набор для интерфейса 21 | # default_config: 22 | 23 | # Ручная конфигурация 24 | # Отображение панели настроек в интерфейсе 25 | config: 26 | history: 27 | 28 | # Включение официального интерфейса 29 | frontend: 30 | themes: !include_dir_merge_named themes 31 | # Включение карты 32 | map: 33 | # Включение поддержки приложения 34 | mobile_app: 35 | # Отслеживание солнца 36 | sun: 37 | #Сканирование доступных устройств в сети 38 | ssdp: 39 | # API для предоставления информации о системе и ее компонентах 40 | system_health: 41 | # Сенсор для отображения последней версии НА 42 | updater: 43 | # Потоковое изображение с камер 44 | stream: 45 | 46 | # Text to speech 47 | tts: 48 | - platform: google_translate 49 | 50 | lovelace: 51 | mode: yaml 52 | resources: 53 | - url: /hacsfiles/mini-graph-card/mini-graph-card-bundle.js 54 | type: module 55 | - url: /hacsfiles/lovelace-auto-entities/auto-entities.js 56 | type: module 57 | 58 | recorder: !include includes/recorder.yaml 59 | group: !include includes/groups.yaml 60 | camera: !include includes/camera.yaml 61 | binary_sensor: !include_dir_merge_list includes/bin_sensor 62 | automation: !include_dir_merge_list includes/automation 63 | sensor: !include_dir_merge_list includes/sensors 64 | script: !include_dir_merge_named includes/scripts 65 | # mqtt_statestream: !include includes/mqtt_statestream.yaml 66 | 67 | input_text: 68 | filed_1: 69 | name: adress 70 | initial: device adress 71 | 72 | mqtt: 73 | broker: core-mosquitto 74 | discovery: true 75 | discovery_prefix: homeassistant 76 | username: mqtt 77 | password: mqtt 78 | 79 | telegram_bot: 80 | - platform: polling 81 | api_key: !secret api_key_77 82 | allowed_chat_ids: 83 | - !secret chat_id_alex 84 | - !secret chat_id_olha 85 | - !secret chat_id_group 86 | 87 | notify: 88 | - name: telegram_alex 89 | platform: telegram 90 | chat_id: !secret chat_id_alex 91 | 92 | - name: telegram_olha 93 | platform: telegram 94 | chat_id: !secret chat_id_olha 95 | 96 | - name: telegram_group 97 | platform: telegram 98 | chat_id: !secret chat_id_group 99 | -------------------------------------------------------------------------------- /custom_components/hacs/helpers/functions/register_repository.py: -------------------------------------------------------------------------------- 1 | """Register a repository.""" 2 | from aiogithubapi import AIOGitHubAPIException 3 | 4 | from custom_components.hacs.helpers.classes.exceptions import ( 5 | HacsException, 6 | HacsExpectedException, 7 | ) 8 | from custom_components.hacs.share import get_hacs 9 | 10 | 11 | # @concurrent(15, 5) 12 | async def register_repository(full_name, category, check=True, ref=None): 13 | """Register a repository.""" 14 | hacs = get_hacs() 15 | from custom_components.hacs.repositories import ( 16 | RERPOSITORY_CLASSES, 17 | ) # To handle import error 18 | 19 | if full_name in hacs.common.skip: 20 | if full_name != "hacs/integration": 21 | raise HacsExpectedException(f"Skipping {full_name}") 22 | 23 | if category not in RERPOSITORY_CLASSES: 24 | raise HacsException(f"{category} is not a valid repository category.") 25 | 26 | repository = RERPOSITORY_CLASSES[category](full_name) 27 | if check: 28 | try: 29 | await repository.async_registration(ref) 30 | if hacs.status.new: 31 | repository.data.new = False 32 | if repository.validate.errors: 33 | hacs.common.skip.append(repository.data.full_name) 34 | if not hacs.status.startup: 35 | hacs.log.error(f"Validation for {full_name} failed.") 36 | if hacs.system.action: 37 | raise HacsException(f"::error:: Validation for {full_name} failed.") 38 | return repository.validate.errors 39 | if hacs.system.action: 40 | repository.logger.info("Validation completed") 41 | else: 42 | repository.logger.info("Registration completed") 43 | except AIOGitHubAPIException as exception: 44 | hacs.common.skip.append(repository.data.full_name) 45 | raise HacsException( 46 | f"Validation for {full_name} failed with {exception}." 47 | ) from None 48 | 49 | exists = ( 50 | False 51 | if str(repository.data.id) == "0" 52 | else [x for x in hacs.repositories if str(x.data.id) == str(repository.data.id)] 53 | ) 54 | 55 | if exists: 56 | if exists[0] in hacs.repositories: 57 | hacs.repositories.remove(exists[0]) 58 | 59 | else: 60 | if hacs.hass is not None and ( 61 | (check and repository.data.new) or hacs.status.new 62 | ): 63 | hacs.hass.bus.async_fire( 64 | "hacs/repository", 65 | { 66 | "action": "registration", 67 | "repository": repository.data.full_name, 68 | "repository_id": repository.data.id, 69 | }, 70 | ) 71 | hacs.repositories.append(repository) 72 | -------------------------------------------------------------------------------- /custom_components/hacs/repositories/plugin.py: -------------------------------------------------------------------------------- 1 | """Class for plugins in HACS.""" 2 | import json 3 | 4 | from custom_components.hacs.helpers.classes.exceptions import HacsException 5 | from custom_components.hacs.helpers.classes.repository import HacsRepository 6 | from custom_components.hacs.helpers.functions.information import find_file_name 7 | from custom_components.hacs.helpers.functions.logger import getLogger 8 | 9 | 10 | class HacsPlugin(HacsRepository): 11 | """Plugins in HACS.""" 12 | 13 | def __init__(self, full_name): 14 | """Initialize.""" 15 | super().__init__() 16 | self.data.full_name = full_name 17 | self.data.full_name_lower = full_name.lower() 18 | self.data.file_name = None 19 | self.data.category = "plugin" 20 | self.information.javascript_type = None 21 | self.content.path.local = self.localpath 22 | self.logger = getLogger(f"repository.{self.data.category}.{full_name}") 23 | 24 | @property 25 | def localpath(self): 26 | """Return localpath.""" 27 | return f"{self.hacs.system.config_path}/www/community/{self.data.full_name.split('/')[-1]}" 28 | 29 | async def validate_repository(self): 30 | """Validate.""" 31 | # Run common validation steps. 32 | await self.common_validate() 33 | 34 | # Custom step 1: Validate content. 35 | find_file_name(self) 36 | 37 | if self.content.path.remote is None: 38 | raise HacsException( 39 | f"Repostitory structure for {self.ref.replace('tags/','')} is not compliant" 40 | ) 41 | 42 | if self.content.path.remote == "release": 43 | self.content.single = True 44 | 45 | # Handle potential errors 46 | if self.validate.errors: 47 | for error in self.validate.errors: 48 | if not self.hacs.status.startup: 49 | self.logger.error(error) 50 | return self.validate.success 51 | 52 | async def update_repository(self, ignore_issues=False): 53 | """Update.""" 54 | await self.common_update(ignore_issues) 55 | 56 | # Get plugin objects. 57 | find_file_name(self) 58 | 59 | if self.content.path.remote is None: 60 | self.validate.errors.append( 61 | f"Repostitory structure for {self.ref.replace('tags/','')} is not compliant" 62 | ) 63 | 64 | if self.content.path.remote == "release": 65 | self.content.single = True 66 | 67 | async def get_package_content(self): 68 | """Get package content.""" 69 | try: 70 | package = await self.repository_object.get_contents( 71 | "package.json", self.ref 72 | ) 73 | package = json.loads(package.content) 74 | 75 | if package: 76 | self.data.authors = package["author"] 77 | except (Exception, BaseException): # pylint: disable=broad-except 78 | pass 79 | -------------------------------------------------------------------------------- /custom_components/hacs/repositories/appdaemon.py: -------------------------------------------------------------------------------- 1 | """Class for appdaemon apps in HACS.""" 2 | from aiogithubapi import AIOGitHubAPIException 3 | 4 | from custom_components.hacs.helpers.classes.exceptions import HacsException 5 | from custom_components.hacs.helpers.classes.repository import HacsRepository 6 | from custom_components.hacs.enums import HacsCategory 7 | from custom_components.hacs.helpers.functions.logger import getLogger 8 | 9 | 10 | class HacsAppdaemon(HacsRepository): 11 | """Appdaemon apps in HACS.""" 12 | 13 | def __init__(self, full_name): 14 | """Initialize.""" 15 | super().__init__() 16 | self.data.full_name = full_name 17 | self.data.full_name_lower = full_name.lower() 18 | self.data.category = HacsCategory.APPDAEMON 19 | self.content.path.local = self.localpath 20 | self.content.path.remote = "apps" 21 | self.logger = getLogger(f"repository.{self.data.category}.{full_name}") 22 | 23 | @property 24 | def localpath(self): 25 | """Return localpath.""" 26 | return f"{self.hacs.system.config_path}/appdaemon/apps/{self.data.name}" 27 | 28 | async def validate_repository(self): 29 | """Validate.""" 30 | await self.common_validate() 31 | 32 | # Custom step 1: Validate content. 33 | try: 34 | addir = await self.repository_object.get_contents("apps", self.ref) 35 | except AIOGitHubAPIException: 36 | raise HacsException( 37 | f"Repostitory structure for {self.ref.replace('tags/','')} is not compliant" 38 | ) from None 39 | 40 | if not isinstance(addir, list): 41 | self.validate.errors.append("Repostitory structure not compliant") 42 | 43 | self.content.path.remote = addir[0].path 44 | self.content.objects = await self.repository_object.get_contents( 45 | self.content.path.remote, self.ref 46 | ) 47 | 48 | # Handle potential errors 49 | if self.validate.errors: 50 | for error in self.validate.errors: 51 | if not self.hacs.status.startup: 52 | self.logger.error(error) 53 | return self.validate.success 54 | 55 | async def update_repository(self, ignore_issues=False): 56 | """Update.""" 57 | await self.common_update(ignore_issues) 58 | 59 | # Get appdaemon objects. 60 | if self.repository_manifest: 61 | if self.data.content_in_root: 62 | self.content.path.remote = "" 63 | 64 | if self.content.path.remote == "apps": 65 | addir = await self.repository_object.get_contents( 66 | self.content.path.remote, self.ref 67 | ) 68 | self.content.path.remote = addir[0].path 69 | self.content.objects = await self.repository_object.get_contents( 70 | self.content.path.remote, self.ref 71 | ) 72 | 73 | # Set local path 74 | self.content.path.local = self.localpath 75 | -------------------------------------------------------------------------------- /custom_components/hacs/api/hacs_repositories.py: -------------------------------------------------------------------------------- 1 | """API Handler for hacs_repositories""" 2 | import voluptuous as vol 3 | from homeassistant.components import websocket_api 4 | 5 | from custom_components.hacs.share import get_hacs 6 | 7 | 8 | @websocket_api.async_response 9 | @websocket_api.websocket_command({vol.Required("type"): "hacs/repositories"}) 10 | async def hacs_repositories(_hass, connection, msg): 11 | """Handle get media player cover command.""" 12 | hacs = get_hacs() 13 | repositories = hacs.repositories 14 | content = [] 15 | for repo in repositories: 16 | if repo.data.category in hacs.common.categories: 17 | data = { 18 | "additional_info": repo.information.additional_info, 19 | "authors": repo.data.authors, 20 | "available_version": repo.display_available_version, 21 | "beta": repo.data.show_beta, 22 | "can_install": repo.can_install, 23 | "category": repo.data.category, 24 | "country": repo.data.country, 25 | "config_flow": repo.data.config_flow, 26 | "custom": repo.custom, 27 | "default_branch": repo.data.default_branch, 28 | "description": repo.data.description, 29 | "domain": repo.data.domain, 30 | "downloads": repo.data.downloads, 31 | "file_name": repo.data.file_name, 32 | "first_install": repo.status.first_install, 33 | "full_name": repo.data.full_name, 34 | "hide": repo.data.hide, 35 | "hide_default_branch": repo.data.hide_default_branch, 36 | "homeassistant": repo.data.homeassistant, 37 | "id": repo.data.id, 38 | "info": repo.information.info, 39 | "installed_version": repo.display_installed_version, 40 | "installed": repo.data.installed, 41 | "issues": repo.data.open_issues, 42 | "javascript_type": repo.information.javascript_type, 43 | "last_updated": repo.data.last_updated, 44 | "local_path": repo.content.path.local, 45 | "main_action": repo.main_action, 46 | "name": repo.display_name, 47 | "new": repo.data.new, 48 | "pending_upgrade": repo.pending_upgrade, 49 | "releases": repo.data.published_tags, 50 | "selected_tag": repo.data.selected_tag, 51 | "stars": repo.data.stargazers_count, 52 | "state": repo.state, 53 | "status_description": repo.display_status_description, 54 | "status": repo.display_status, 55 | "topics": repo.data.topics, 56 | "updated_info": repo.status.updated_info, 57 | "version_or_commit": repo.display_version_or_commit, 58 | } 59 | 60 | content.append(data) 61 | 62 | connection.send_message(websocket_api.result_message(msg["id"], content)) 63 | -------------------------------------------------------------------------------- /custom_components/hacs/repositories/theme.py: -------------------------------------------------------------------------------- 1 | """Class for themes in HACS.""" 2 | from custom_components.hacs.helpers.classes.exceptions import HacsException 3 | from custom_components.hacs.helpers.classes.repository import HacsRepository 4 | from custom_components.hacs.enums import HacsCategory 5 | from custom_components.hacs.helpers.functions.information import find_file_name 6 | from custom_components.hacs.helpers.functions.logger import getLogger 7 | 8 | 9 | class HacsTheme(HacsRepository): 10 | """Themes in HACS.""" 11 | 12 | def __init__(self, full_name): 13 | """Initialize.""" 14 | super().__init__() 15 | self.data.full_name = full_name 16 | self.data.full_name_lower = full_name.lower() 17 | self.data.category = HacsCategory.THEME 18 | self.content.path.remote = "themes" 19 | self.content.path.local = self.localpath 20 | self.content.single = False 21 | self.logger = getLogger(f"repository.{self.data.category}.{full_name}") 22 | 23 | @property 24 | def localpath(self): 25 | """Return localpath.""" 26 | return f"{self.hacs.system.config_path}/themes/{self.data.file_name.replace('.yaml', '')}" 27 | 28 | async def async_post_installation(self): 29 | """Run post installation steps.""" 30 | try: 31 | await self.hacs.hass.services.async_call("frontend", "reload_themes", {}) 32 | except (Exception, BaseException): # pylint: disable=broad-except 33 | pass 34 | 35 | async def validate_repository(self): 36 | """Validate.""" 37 | # Run common validation steps. 38 | await self.common_validate() 39 | 40 | # Custom step 1: Validate content. 41 | compliant = False 42 | for treefile in self.treefiles: 43 | if treefile.startswith("themes/") and treefile.endswith(".yaml"): 44 | compliant = True 45 | break 46 | if not compliant: 47 | raise HacsException( 48 | f"Repostitory structure for {self.ref.replace('tags/','')} is not compliant" 49 | ) 50 | 51 | if self.data.content_in_root: 52 | self.content.path.remote = "" 53 | 54 | # Handle potential errors 55 | if self.validate.errors: 56 | for error in self.validate.errors: 57 | if not self.hacs.status.startup: 58 | self.logger.error(error) 59 | return self.validate.success 60 | 61 | async def async_post_registration(self): 62 | """Registration.""" 63 | # Set name 64 | find_file_name(self) 65 | self.content.path.local = self.localpath 66 | 67 | async def update_repository(self, ignore_issues=False): 68 | """Update.""" 69 | await self.common_update(ignore_issues) 70 | 71 | # Get theme objects. 72 | if self.data.content_in_root: 73 | self.content.path.remote = "" 74 | 75 | # Update name 76 | find_file_name(self) 77 | self.content.path.local = self.localpath 78 | -------------------------------------------------------------------------------- /includes/scripts/060_notification.yaml: -------------------------------------------------------------------------------- 1 | telegramm_a0600: 2 | alias: Код 0600 обнаружение протечки под раковиной в ванной 3 | sequence: 4 | - service: telegram_bot.send_message 5 | data_template: 6 | target: 7 | - !secret chat_id_group 8 | message: 'Обнаружена протечка! Датчик под раковиной в ванной. Время события - {{ states.sensor.time_date.state }}' 9 | 10 | 11 | telegramm_a0601: 12 | alias: Код 0601 устранение протечки возле под раковиной в ванной 13 | sequence: 14 | - service: telegram_bot.send_message 15 | data_template: 16 | target: 17 | - !secret chat_id_group 18 | message: 'Протечка устранена. Датчик под раковиной в ванной. Время события - {{ states.sensor.time_date.state }}' 19 | 20 | telegramm_a0602: 21 | alias: Код 0602 обнаружение протечки под душевой кабиной в ванной 22 | sequence: 23 | - service: telegram_bot.send_message 24 | data_template: 25 | target: 26 | - !secret chat_id_group 27 | message: 'Обнаружена протечка! Датчик под душевой кабиной в ванной. Время события - {{ states.sensor.time_date.state }}' 28 | 29 | 30 | telegramm_a0603: 31 | alias: Код 0603 устранение протечки возле под душевой кабиной в ванной 32 | sequence: 33 | - service: telegram_bot.send_message 34 | data_template: 35 | target: 36 | - !secret chat_id_group 37 | message: 'Протечка устранена. Датчик под душевой кабиной в ванной. Время события - {{ states.sensor.time_date.state }}' 38 | 39 | telegramm_0600: 40 | alias: Код 0600 включение бойлера 41 | sequence: 42 | - service: telegram_bot.send_message 43 | data_template: 44 | target: 45 | - !secret chat_id_alex 46 | # - !secret chat_id_olha 47 | message: 'Включение бойлера. Время события - {{ states.sensor.time_date.state }}' 48 | 49 | telegramm_0601: 50 | alias: Код 0601 выключение бойлера 51 | sequence: 52 | - service: telegram_bot.send_message 53 | data_template: 54 | target: 55 | - !secret chat_id_alex 56 | # - !secret chat_id_olha 57 | message: 'Выключение бойлера. Время события - {{ states.sensor.time_date.state }}' 58 | 59 | telegramm_0602: 60 | alias: Код 0602 включение бойлера невозможно 61 | sequence: 62 | - service: telegram_bot.send_message 63 | data_template: 64 | target: 65 | - !secret chat_id_alex 66 | # - !secret chat_id_olha 67 | message: 'Включение бойлера невозможно, отключен режим нагрева воды. Время события - {{ states.sensor.time_date.state }}' 68 | 69 | 70 | telegramm_0603: 71 | alias: Код 0603 люстра в ванной 72 | sequence: 73 | - service: notify.telegram_alex 74 | data: 75 | message: "Потеря контроля в течении 5 минут - люстра в ванной. Время события - {{ states('sensor.time_date') }} . Инициирована перезагрузка" 76 | 77 | 78 | 79 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /includes/automation/104_messages.yaml: -------------------------------------------------------------------------------- 1 | - id: Обработка message кодов 2 | alias: message_code 3 | initial_state: true 4 | trigger: 5 | - platform: state 6 | entity_id: sensor.messages_code 7 | action: 8 | - service: script.turn_on 9 | data_template: 10 | entity_id: >- 11 | {%- if states.sensor.messages_code.state == '000' -%} 12 | script.stub 13 | {%- elif states.sensor.messages_code.state == '0001' -%} 14 | script.photo_xiaofang 15 | {%- elif states.sensor.messages_code.state == '0002' -%} 16 | script.photo_xiaofang1s 17 | {%- elif states.sensor.messages_code.state == '0003' -%} 18 | script.photo_reolink 19 | {%- elif states.sensor.messages_code.state == '0004' -%} 20 | script.photo_reolink2 21 | {%- elif states.sensor.messages_code.state == '0005' -%} 22 | script.photo_reolink3 23 | {%- elif states.sensor.messages_code.state == '0006' -%} 24 | script.photo_reolink4 25 | {%- elif states.sensor.messages_code.state == '0014' -%} 26 | script.video_reolink2 27 | {%- elif states.sensor.messages_code.state == '0100' -%} 28 | script.open_door 29 | {%- elif states.sensor.messages_code.state == '0101' -%} 30 | script.telegramm_101 31 | {%- elif states.sensor.messages_code.state == '0200' -%} 32 | script.telegramm_200 33 | {%- elif states.sensor.messages_code.state == '0400' -%} 34 | script.video_reolink2 35 | {%- elif states.sensor.messages_code.state == '0500' -%} 36 | script.telegramm_0500 37 | {%- elif states.sensor.messages_code.state == '0501' -%} 38 | script.telegramm_0501 39 | {%- elif states.sensor.messages_code.state == '0502' -%} 40 | script.telegramm_0502 41 | {%- elif states.sensor.messages_code.state == '0600' -%} 42 | script.telegramm_0600 43 | {%- elif states.sensor.messages_code.state == '0601' -%} 44 | script.telegramm_0601 45 | {%- elif states.sensor.messages_code.state == '0602' -%} 46 | script.telegramm_0602 47 | {%- elif states.sensor.messages_code.state == '0603' -%} 48 | script.telegramm_0603 49 | {%- elif states.sensor.messages_code.state == '0700' -%} 50 | script.telegramm_0700 51 | {%- elif states.sensor.messages_code.state == '0701' -%} 52 | script.telegramm_0701 53 | {%- elif states.sensor.messages_code.state == '0800' -%} 54 | script.telegramm_0800 55 | {%- elif states.sensor.messages_code.state == '0801' -%} 56 | script.telegramm_0801 57 | {%- else -%} 58 | script.telegramm_unknown_error 59 | {%- endif -%} 60 | - delay: 00:00:01 61 | - service: mqtt.publish 62 | data_template: 63 | topic: "notification/messages" 64 | payload_template: '000' 65 | retain: true -------------------------------------------------------------------------------- /custom_components/hacs/repositories/python_script.py: -------------------------------------------------------------------------------- 1 | """Class for python_scripts in HACS.""" 2 | from custom_components.hacs.helpers.classes.exceptions import HacsException 3 | from custom_components.hacs.helpers.classes.repository import HacsRepository 4 | from custom_components.hacs.enums import HacsCategory 5 | from custom_components.hacs.helpers.functions.information import find_file_name 6 | from custom_components.hacs.helpers.functions.logger import getLogger 7 | 8 | 9 | class HacsPythonScript(HacsRepository): 10 | """python_scripts in HACS.""" 11 | 12 | category = "python_script" 13 | 14 | def __init__(self, full_name): 15 | """Initialize.""" 16 | super().__init__() 17 | self.data.full_name = full_name 18 | self.data.full_name_lower = full_name.lower() 19 | self.data.category = HacsCategory.PYTHON_SCRIPT 20 | self.content.path.remote = "python_scripts" 21 | self.content.path.local = self.localpath 22 | self.content.single = True 23 | self.logger = getLogger(f"repository.{self.data.category}.{full_name}") 24 | 25 | @property 26 | def localpath(self): 27 | """Return localpath.""" 28 | return f"{self.hacs.system.config_path}/python_scripts" 29 | 30 | async def validate_repository(self): 31 | """Validate.""" 32 | # Run common validation steps. 33 | await self.common_validate() 34 | 35 | # Custom step 1: Validate content. 36 | if self.data.content_in_root: 37 | self.content.path.remote = "" 38 | 39 | compliant = False 40 | for treefile in self.treefiles: 41 | if treefile.startswith(f"{self.content.path.remote}") and treefile.endswith( 42 | ".py" 43 | ): 44 | compliant = True 45 | break 46 | if not compliant: 47 | raise HacsException( 48 | f"Repository structure for {self.ref.replace('tags/','')} is not compliant" 49 | ) 50 | 51 | # Handle potential errors 52 | if self.validate.errors: 53 | for error in self.validate.errors: 54 | if not self.hacs.status.startup: 55 | self.logger.error(error) 56 | return self.validate.success 57 | 58 | async def async_post_registration(self): 59 | """Registration.""" 60 | # Set name 61 | find_file_name(self) 62 | 63 | async def update_repository(self, ignore_issues=False): 64 | """Update.""" 65 | await self.common_update(ignore_issues) 66 | 67 | # Get python_script objects. 68 | if self.data.content_in_root: 69 | self.content.path.remote = "" 70 | 71 | compliant = False 72 | for treefile in self.treefiles: 73 | if treefile.startswith(f"{self.content.path.remote}") and treefile.endswith( 74 | ".py" 75 | ): 76 | compliant = True 77 | break 78 | if not compliant: 79 | raise HacsException( 80 | f"Repository structure for {self.ref.replace('tags/','')} is not compliant" 81 | ) 82 | 83 | # Update name 84 | find_file_name(self) 85 | -------------------------------------------------------------------------------- /custom_components/hacs/sensor.py: -------------------------------------------------------------------------------- 1 | """Sensor platform for HACS.""" 2 | from homeassistant.helpers.entity import Entity 3 | from custom_components.hacs.const import DOMAIN, NAME_SHORT, VERSION 4 | from custom_components.hacs.share import get_hacs 5 | 6 | 7 | async def async_setup_platform( 8 | _hass, _config, async_add_entities, _discovery_info=None 9 | ): 10 | """Setup sensor platform.""" 11 | async_add_entities([HACSSensor()]) 12 | 13 | 14 | async def async_setup_entry(_hass, _config_entry, async_add_devices): 15 | """Setup sensor platform.""" 16 | async_add_devices([HACSSensor()]) 17 | 18 | 19 | class HACSDevice(Entity): 20 | """HACS Device class.""" 21 | 22 | @property 23 | def device_info(self): 24 | """Return device information about HACS.""" 25 | return { 26 | "identifiers": {(DOMAIN, self.unique_id)}, 27 | "name": NAME_SHORT, 28 | "manufacturer": "hacs.xyz", 29 | "model": "", 30 | "sw_version": VERSION, 31 | "entry_type": "service", 32 | } 33 | 34 | 35 | class HACSSensor(HACSDevice): 36 | """HACS Sensor class.""" 37 | 38 | def __init__(self): 39 | """Initialize.""" 40 | self._state = None 41 | self.repositories = [] 42 | 43 | async def async_update(self): 44 | """Update the sensor.""" 45 | hacs = get_hacs() 46 | if hacs.status.background_task: 47 | return 48 | 49 | self.repositories = [] 50 | 51 | for repository in hacs.repositories: 52 | if ( 53 | repository.pending_upgrade 54 | and repository.data.category in hacs.common.categories 55 | ): 56 | self.repositories.append(repository) 57 | self._state = len(self.repositories) 58 | 59 | @property 60 | def unique_id(self): 61 | """Return a unique ID to use for this sensor.""" 62 | return ( 63 | "0717a0cd-745c-48fd-9b16-c8534c9704f9-bc944b0f-fd42-4a58-a072-ade38d1444cd" 64 | ) 65 | 66 | @property 67 | def name(self): 68 | """Return the name of the sensor.""" 69 | return "hacs" 70 | 71 | @property 72 | def state(self): 73 | """Return the state of the sensor.""" 74 | return self._state 75 | 76 | @property 77 | def icon(self): 78 | """Return the icon of the sensor.""" 79 | return "hacs:hacs" 80 | 81 | @property 82 | def unit_of_measurement(self): 83 | """Return the unit of measurement.""" 84 | return "pending update(s)" 85 | 86 | @property 87 | def device_state_attributes(self): 88 | """Return attributes for the sensor.""" 89 | repositories = [] 90 | for repository in self.repositories: 91 | repositories.append( 92 | { 93 | "name": repository.data.full_name, 94 | "display_name": repository.display_name, 95 | "installed_version": repository.display_installed_version, 96 | "available_version": repository.display_available_version, 97 | } 98 | ) 99 | return {"repositories": repositories} 100 | -------------------------------------------------------------------------------- /includes/sensors/global.yaml: -------------------------------------------------------------------------------- 1 | - platform: template 2 | sensors: 3 | 4 | count_automation: 5 | friendly_name: "Автоматизаций в системе - " 6 | entity_id: 7 | - sensor.time 8 | value_template: > 9 | {%- set domains = ['automation'] -%} 10 | {%- for domain in domains -%} 11 | {%- for item in states[domain] -%} 12 | {% if loop.first %} 13 | {{loop.length}} 14 | {% endif %} 15 | {%- endfor -%} 16 | {%- endfor -%} 17 | 18 | count_automation_on: 19 | friendly_name: "Активных автоматизаций - " 20 | entity_id: 21 | - sensor.time 22 | value_template: "{{states.automation | selectattr ('state', 'equalto', 'on') | list | length}}" 23 | icon_template: mdi:counter 24 | 25 | count_automation_off: 26 | friendly_name: "Отключенных автоматизаций - " 27 | entity_id: 28 | - sensor.time 29 | value_template: "{{states.automation | selectattr ('state', 'equalto', 'off') | list | length}}" 30 | icon_template: mdi:counter 31 | 32 | count_script: 33 | friendly_name: "Cкриптов в системе - " 34 | entity_id: 35 | - sensor.time 36 | value_template: > 37 | {%- set domains = ['script'] -%} 38 | {%- for domain in domains -%} 39 | {%- for item in states[domain] -%} 40 | {% if loop.first %} 41 | {{loop.length}} 42 | {% endif %} 43 | {%- endfor -%} 44 | {%- endfor -%} 45 | 46 | count_script_on: 47 | friendly_name: "Выполняющихся скриптов - " 48 | entity_id: 49 | - sensor.time 50 | value_template: "{{states.script | selectattr ('state', 'equalto', 'on') | list | length}}" 51 | icon_template: mdi:counter 52 | 53 | count_script_off: 54 | friendly_name: "Выключенных скриптов - " 55 | entity_id: 56 | - sensor.time 57 | value_template: "{{states.script | selectattr ('state', 'equalto', 'off') | list | length}}" 58 | icon_template: mdi:counter 59 | 60 | unavailable_now_light: 61 | friendly_name: "Всего недоступных светильников - " 62 | entity_id: 63 | - sensor.time 64 | value_template: "{{states.light | selectattr ('state', 'equalto', 'unavailable') | list | length}}" 65 | icon_template: mdi:counter 66 | 67 | unavailable_now_switch: 68 | friendly_name: "Всего недоступных реле - " 69 | entity_id: 70 | - sensor.time 71 | value_template: "{{states.switch | selectattr ('state', 'equalto', 'unavailable') | list | length}}" 72 | icon_template: mdi:counter 73 | 74 | unavailable_now_sensor: 75 | friendly_name: "Всего недоступных сенсоров - " 76 | entity_id: 77 | - sensor.time 78 | value_template: "{{states.sensor | selectattr ('state', 'equalto', 'unavailable') | list | length}}" 79 | icon_template: mdi:counter 80 | 81 | unavailable_now_binary_sensor: 82 | friendly_name: "Всего недоступных бинарных сенсоров - " 83 | entity_id: 84 | - sensor.time 85 | value_template: "{{states.binary_sensor | selectattr ('state', 'equalto', 'unavailable') | list | length}}" 86 | icon_template: mdi:counter -------------------------------------------------------------------------------- /custom_components/hacs/repositories/netdaemon.py: -------------------------------------------------------------------------------- 1 | """Class for netdaemon apps in HACS.""" 2 | from custom_components.hacs.helpers.classes.exceptions import HacsException 3 | from custom_components.hacs.helpers.classes.repository import HacsRepository 4 | from custom_components.hacs.enums import HacsCategory 5 | from custom_components.hacs.helpers.functions.filters import ( 6 | get_first_directory_in_directory, 7 | ) 8 | from custom_components.hacs.helpers.functions.logger import getLogger 9 | 10 | 11 | class HacsNetdaemon(HacsRepository): 12 | """Netdaemon apps in HACS.""" 13 | 14 | def __init__(self, full_name): 15 | """Initialize.""" 16 | super().__init__() 17 | self.data.full_name = full_name 18 | self.data.full_name_lower = full_name.lower() 19 | self.data.category = HacsCategory.NETDAEMON 20 | self.content.path.local = self.localpath 21 | self.content.path.remote = "apps" 22 | self.logger = getLogger(f"repository.{self.data.category}.{full_name}") 23 | 24 | @property 25 | def localpath(self): 26 | """Return localpath.""" 27 | return f"{self.hacs.system.config_path}/netdaemon/apps/{self.data.name}" 28 | 29 | async def validate_repository(self): 30 | """Validate.""" 31 | await self.common_validate() 32 | 33 | # Custom step 1: Validate content. 34 | if self.repository_manifest: 35 | if self.data.content_in_root: 36 | self.content.path.remote = "" 37 | 38 | if self.content.path.remote == "apps": 39 | self.data.domain = get_first_directory_in_directory( 40 | self.tree, self.content.path.remote 41 | ) 42 | self.content.path.remote = f"apps/{self.data.name}" 43 | 44 | compliant = False 45 | for treefile in self.treefiles: 46 | if treefile.startswith(f"{self.content.path.remote}") and treefile.endswith( 47 | ".cs" 48 | ): 49 | compliant = True 50 | break 51 | if not compliant: 52 | raise HacsException( 53 | f"Repostitory structure for {self.ref.replace('tags/','')} is not compliant" 54 | ) 55 | 56 | # Handle potential errors 57 | if self.validate.errors: 58 | for error in self.validate.errors: 59 | if not self.hacs.status.startup: 60 | self.logger.error(error) 61 | return self.validate.success 62 | 63 | async def update_repository(self, ignore_issues=False): 64 | """Update.""" 65 | await self.common_update(ignore_issues) 66 | 67 | # Get appdaemon objects. 68 | if self.repository_manifest: 69 | if self.data.content_in_root: 70 | self.content.path.remote = "" 71 | 72 | if self.content.path.remote == "apps": 73 | self.data.domain = get_first_directory_in_directory( 74 | self.tree, self.content.path.remote 75 | ) 76 | self.content.path.remote = f"apps/{self.data.name}" 77 | 78 | # Set local path 79 | self.content.path.local = self.localpath 80 | 81 | async def async_post_installation(self): 82 | """Run post installation steps.""" 83 | try: 84 | await self.hacs.hass.services.async_call( 85 | "hassio", "addon_restart", {"addon": "c6a2317c_netdaemon"} 86 | ) 87 | except (Exception, BaseException): # pylint: disable=broad-except 88 | pass 89 | -------------------------------------------------------------------------------- /custom_components/hacs/webresponses/iconset.py: -------------------------------------------------------------------------------- 1 | from aiohttp import web 2 | 3 | ICON = """ 4 | window.customIconsets = window.customIconsets || {}; 5 | window.customIconsets["hacs"] = async () => { 6 | return { 7 | path: 8 | "m 20.064849,22.306912 c -0.0319,0.369835 -0.280561,0.707789 -0.656773,0.918212 -0.280572,0.153036 -0.605773,0.229553 -0.950094,0.229553 -0.0765,0 -0.146661,-0.0064 -0.216801,-0.01275 -0.605774,-0.05739 -1.135016,-0.344329 -1.402827,-0.7588 l 0.784304,-0.516495 c 0.0893,0.146659 0.344331,0.312448 0.707793,0.34433 0.235931,0.02551 0.471852,-0.01913 0.637643,-0.108401 0.101998,-0.05101 0.172171,-0.127529 0.17854,-0.191295 0.0065,-0.08289 -0.0255,-0.369835 -0.733293,-0.439975 -1.013854,-0.09565 -1.645127,-0.688661 -1.568606,-1.460214 0.0319,-0.382589 0.280561,-0.714165 0.663153,-0.930965 0.331571,-0.172165 0.752423,-0.25506 1.166895,-0.210424 0.599382,0.05739 1.128635,0.344329 1.402816,0.7588 l -0.784304,0.510118 c -0.0893,-0.140282 -0.344331,-0.299694 -0.707782,-0.331576 -0.235932,-0.02551 -0.471863,0.01913 -0.637654,0.10202 -0.0956,0.05739 -0.165791,0.133906 -0.17216,0.191295 -0.0255,0.293317 0.465482,0.420847 0.726913,0.439976 v 0.0064 c 1.020234,0.09565 1.638757,0.66953 1.562237,1.460213 z m -7.466854,-0.988354 c 0,-1.192401 0.962855,-2.155249 2.15525,-2.155249 0.599393,0 1.179645,0.25506 1.594117,0.707789 l -0.695033,0.624895 c -0.235931,-0.25506 -0.561133,-0.401718 -0.899084,-0.401718 -0.675903,0 -1.217906,0.542 -1.217906,1.217906 0,0.66953 0.542003,1.217908 1.217906,1.217908 0.337951,0 0.663153,-0.140283 0.899084,-0.401718 l 0.695033,0.631271 c -0.414472,0.452729 -0.988355,0.707788 -1.594117,0.707788 -1.192395,0 -2.15525,-0.969224 -2.15525,-2.148872 z M 8.6573365,23.461054 10.353474,19.14418 h 0.624893 l 1.568618,4.316874 H 11.52037 L 11.265308,22.734136 H 9.964513 l -0.274192,0.726918 z m 1.6833885,-1.68339 h 0.580263 L 10.646796,21.012487 Z M 8.1089536,19.156932 v 4.297745 H 7.1461095 v -1.645131 h -1.606867 v 1.645131 H 4.5763876 v -4.297745 h 0.9628549 v 1.696143 h 1.606867 V 19.156932 Z M 20.115859,4.2997436 C 20.090359,4.159461 19.969198,4.0574375 19.822548,4.0574375 H 14.141102 10.506516 4.8250686 c -0.14665,0 -0.2678112,0.1020202 -0.2933108,0.2423061 L 3.690064,8.8461703 c -0.00651,0.01913 -0.00651,0.03826 -0.00651,0.057391 v 1.5239797 c 0,0.165789 0.133911,0.299694 0.2996911,0.299694 H 4.5762579 20.0711 20.664112 c 0.165781,0 0.299691,-0.133905 0.299691,-0.299694 V 8.8971848 c 0,-0.01913 0,-0.03826 -0.0065,-0.05739 z M 4.5763876,17.358767 c 0,0.184917 0.1466608,0.331577 0.3315819,0.331577 h 5.5985465 3.634586 0.924594 c 0.184911,0 0.331571,-0.14666 0.331571,-0.331577 v -4.744098 c 0,-0.184918 0.146661,-0.331577 0.331582,-0.331577 h 2.894913 c 0.184921,0 0.331582,0.146659 0.331582,0.331577 v 4.744098 c 0,0.184917 0.146661,0.331577 0.331571,0.331577 h 0.446363 c 0.18491,0 0.331571,-0.14666 0.331571,-0.331577 v -5.636804 c 0,-0.184918 -0.146661,-0.331577 -0.331571,-0.331577 H 4.9079695 c -0.1849211,0 -0.3315819,0.146659 -0.3315819,0.331577 z m 1.6578879,-4.852498 h 5.6495565 c 0.15303,0 0.280561,0.12753 0.280561,0.280564 v 3.513438 c 0,0.153036 -0.127531,0.280566 -0.280561,0.280566 H 6.2342755 c -0.1530412,0 -0.2805719,-0.12753 -0.2805719,-0.280566 v -3.513438 c 0,-0.159411 0.1275307,-0.280564 0.2805719,-0.280564 z M 19.790657,3.3879075 H 4.8569594 c -0.1530412,0 -0.2805718,-0.1275296 -0.2805718,-0.2805642 V 1.3665653 C 4.5763876,1.2135296 4.7039182,1.086 4.8569594,1.086 H 19.790657 c 0.153041,0 0.280572,0.1275296 0.280572,0.2805653 v 1.740778 c 0,0.1530346 -0.127531,0.2805642 -0.280572,0.2805642 z", 9 | }; 10 | }; 11 | """ 12 | 13 | 14 | def serve_iconset(): 15 | return web.Response(body=ICON, content_type="application/javascript") 16 | -------------------------------------------------------------------------------- /themes/google_dark_theme/google_dark_theme.yaml: -------------------------------------------------------------------------------- 1 | # Theme based on Google app dark theme 2 | # Creator: Juan - @juanmtech 3 | # Website: https://www.juanmtech.com 4 | # YouTube Channel: https://youtube.com/juanmtech 5 | # My Home Assistant Config files: https://github.com/JuanMTech/Home_Assistant_files 6 | # 7 | # 8 | Google Dark Theme: 9 | # Header: 10 | app-header-background-color: rgb(23, 23, 23) 11 | app-header-text-color: rgb(198, 203, 210) 12 | # Main Interface Colors 13 | primary-color: rgb(138, 180, 248) 14 | light-primary-color: var(--primary-color) 15 | primary-background-color: rgb(23, 23, 23) 16 | secondary-background-color: rgb(32, 33, 36) 17 | divider-color: var(--primary-background-color) 18 | accent-color: rgb(138, 180, 248) 19 | # Text 20 | primary-text-color: rgb(242, 242, 242) 21 | secondary-text-color: rgb(166, 166, 166) 22 | text-primary-color: var(--primary-text-color) 23 | disabled-text-color: rgba(184, 190, 199, 0.4) 24 | # Sidebar Menu 25 | sidebar-icon-color: rgb(169, 177, 188) 26 | sidebar-text-color: rgb(198, 203, 210) 27 | sidebar-background-color: rgb(32, 33, 36) 28 | sidebar-selected-background-color: var(--primary-background-color) 29 | sidebar-selected-icon-color: rgb(138, 180, 248) 30 | sidebar-selected-text-color: var(--sidebar-selected-icon-color) 31 | # Buttons 32 | paper-item-icon-color: rgb(169, 177, 188) 33 | paper-item-icon-active-color: rgb(138, 180, 248) 34 | # States and Badges 35 | state-icon-color: rgb(138, 180, 248) 36 | state-icon-active-color: rgb(169, 177, 188) 37 | state-icon-unavailable-color: var(--disabled-text-color) 38 | # Sliders 39 | paper-slider-knob-color: rgb(138, 180, 248) 40 | paper-slider-knob-start-color: var(--paper-slider-knob-color) 41 | paper-slider-pin-color: var(--paper-slider-knob-color) 42 | paper-slider-active-color: var(--paper-slider-knob-color) 43 | paper-slider-secondary-color: var(--light-primary-color) 44 | # Labels 45 | label-badge-background-color: rgb(32, 33, 36) 46 | label-badge-text-color: rgb(198, 203, 210) 47 | label-badge-red: rgb(208, 101, 104) 48 | label-badge-green: rgb(128, 200, 132) 49 | label-badge-blue: rgb(138, 180, 248) 50 | label-badge-yellow: rgb(223, 194, 113) 51 | label-badge-gray: rgb(95, 98, 103) 52 | # Cards 53 | card-background-color: rgb(32, 33, 36) 54 | ha-card-border-radius: "10px" 55 | ha-card-box-shadow: 1px 1px 5px 0px rgb(12, 12, 14) 56 | paper-dialog-background-color: var(--card-background-color) 57 | paper-listbox-background-color: var(--card-background-color) 58 | paper-card-background-color: var(--card-background-color) 59 | # Switches 60 | switch-checked-button-color: rgb(138, 180, 248) 61 | switch-checked-track-color: rgb(138, 180, 248) 62 | switch-unchecked-button-color: rgb(172, 176, 185) 63 | switch-unchecked-track-color: rgb(154, 160, 166) 64 | # Toggles 65 | paper-toggle-button-checked-button-color: var(--switch-checked-button-color) 66 | paper-toggle-button-checked-bar-color: var(--switch-checked-track-color) 67 | paper-toggle-button-unchecked-button-color: var(--switch-unchecked-button-color) 68 | paper-toggle-button-unchecked-bar-color: var(--switch-unchecked-track-color) 69 | # Table 70 | table-row-background-color: var(--primary-background-color) 71 | table-row-alternative-background-color: var(--secondary-background-color) 72 | data-table-background-color: var(--primary-background-color) 73 | mdc-checkbox-unchecked-color: rgb(169, 177, 188) 74 | # Dropdowns 75 | material-background-color: var(--secondary-background-color) 76 | material-secondary-background-color: var(--primary-background-color) 77 | mdc-theme-surface: var(--primary-background-color) 78 | # Pre/Code 79 | markdown-code-background-color: rgb(23, 23, 23) -------------------------------------------------------------------------------- /includes/scripts/000_camera.yaml: -------------------------------------------------------------------------------- 1 | photo_xiaofang: 2 | alias: Фотография xiaofang и отправка в телеграмм 3 | sequence: 4 | - service: camera.snapshot 5 | data: 6 | entity_id: camera.xiaofang 7 | filename: "/config/www/cam_captures/xiaofang.jpg" 8 | - delay: 00:00:12 9 | - service: telegram_bot.send_photo 10 | data_template: 11 | target: 12 | - !secret chat_id_group 13 | file: "/config/www/cam_captures/xiaofang.jpg" 14 | 15 | photo_xiaofang1s: 16 | alias: Фотография xiaofang 1s и отправка в телеграмм 17 | sequence: 18 | - service: camera.snapshot 19 | data: 20 | entity_id: camera.xiaofang1s 21 | filename: "/config/www/cam_captures/xiaofang1s.jpg" 22 | - delay: 00:00:10 23 | - service: telegram_bot.send_photo 24 | data_template: 25 | target: 26 | - !secret chat_id_group 27 | file: "/config/www/cam_captures/xiaofang1s.jpg" 28 | 29 | photo_reolink: 30 | alias: Фотография Reolink 411 и отправка в телеграмм 31 | sequence: 32 | - service: camera.snapshot 33 | data: 34 | entity_id: camera.reolink 35 | filename: "/config/www/cam_captures/reolink.jpg" 36 | - delay: 00:00:10 37 | - service: telegram_bot.send_photo 38 | data_template: 39 | target: 40 | - !secret chat_id_group 41 | file: "/config/www/cam_captures/reolink.jpg" 42 | 43 | photo_reolink2: 44 | alias: Фотография Reolink C2 и отправка в телеграмм 45 | sequence: 46 | - service: camera.snapshot 47 | data: 48 | entity_id: camera.reolink2 49 | filename: "/config/www/cam_captures/reolink2.jpg" 50 | - delay: 00:00:12 51 | - service: telegram_bot.send_photo 52 | data_template: 53 | target: 54 | - !secret chat_id_group 55 | file: "/config/www/cam_captures/reolink2.jpg" 56 | 57 | photo_reolink3: 58 | alias: Фотография Reolink 410 и отправка в телеграмм 59 | sequence: 60 | - service: camera.snapshot 61 | data: 62 | entity_id: camera.reolink3 63 | filename: "/config/www/cam_captures/reolink3.jpg" 64 | - delay: 00:00:10 65 | - service: telegram_bot.send_photo 66 | data_template: 67 | target: 68 | - !secret chat_id_group 69 | file: "/config/www/cam_captures/reolink3.jpg" 70 | 71 | photo_reolink4: 72 | alias: Фотография Reolink E1 и отправка в телеграмм 73 | sequence: 74 | - service: camera.snapshot 75 | data: 76 | entity_id: camera.reolink4 77 | filename: "/config/www/cam_captures/reolink4.jpg" 78 | - delay: 00:00:10 79 | - service: telegram_bot.send_photo 80 | data_template: 81 | target: 82 | - !secret chat_id_group 83 | file: "/config/www/cam_captures/reolink4.jpg" 84 | 85 | video_reolink2: 86 | alias: Запись с камеры Реолинк С2 и отправка в телеграмм 87 | sequence: 88 | - service: camera.record 89 | data_template: 90 | entity_id: camera.reolink2 91 | filename: "/config/www/cam_captures/reolink2.mp4" 92 | lookback: 5 93 | duration: 15 94 | - delay: 00:00:45 95 | - service: telegram_bot.send_video 96 | data_template: 97 | target: 98 | - !secret chat_id_alex 99 | # - !secret chat_id_olha 100 | file: "/config/www/cam_captures/reolink2.mp4" 101 | caption: "Кухня - {{ states('sensor.time') }}" -------------------------------------------------------------------------------- /includes/sensors/00_system.yaml: -------------------------------------------------------------------------------- 1 | - platform: command_line 2 | name: cpu_temp 3 | command: "cat /sys/class/thermal/thermal_zone0/temp" 4 | unit_of_measurement: "°C" 5 | value_template: '{{ (value | multiply(0.001)) | round(1) }}' 6 | 7 | - platform: version 8 | name: current_ha 9 | scan_interval: 1800 10 | 11 | - platform: template 12 | sensors: 13 | version: 14 | friendly_name: 'Последняя версия - ' 15 | value_template: '{{ states.binary_sensor.updater.attributes["newest_version"] }}' 16 | 17 | # - platform: sql 18 | # db_url: mysql://hass:hass@core-mariadb/homeassistant?charset=utf8 19 | # queries: 20 | # - name: Maria DB size 21 | # query: 'SELECT table_schema "database", Round(Sum(data_length + index_length) / 1048576, 2) "value" FROM information_schema.tables WHERE table_schema="homeassistant" GROUP BY table_schema;' 22 | # column: 'value' 23 | # unit_of_measurement: MB 24 | 25 | - platform: filesize 26 | file_paths: 27 | - /config/home-assistant_v2.db 28 | 29 | - platform: filesize 30 | file_paths: 31 | - /config/core 32 | 33 | - platform: systemmonitor 34 | resources: 35 | - type: processor_use 36 | - type: disk_use_percent 37 | arg: / 38 | - type: memory_use_percent 39 | - type: last_boot 40 | - type: disk_free 41 | arg: / 42 | 43 | - platform: time_date 44 | display_options: 45 | - 'time' 46 | - 'date' 47 | - 'date_time' 48 | - 'time_date' 49 | 50 | - platform: uptime 51 | name: online 52 | unit_of_measurement: minutes 53 | 54 | - platform: template 55 | sensors: 56 | online_custom: 57 | 58 | value_template: > 59 | {% set uptime = states.sensor.online.state | int %} 60 | {% set minutes = (( uptime % 60) / 1) | int %} 61 | {% set hours = ((uptime % 1440) / 60) | int %} 62 | {% set days = (uptime / 1440) | int %} 63 | 64 | {% if uptime < 1 %} 65 | Меньше минуты 66 | {% else %} 67 | {% if days > 0 %} 68 | {{ days }} д. 69 | {% endif %} 70 | {% if hours > 0 %} 71 | {% if days > 0 %} 72 | {{ ' ' }} 73 | {% endif %} 74 | {{ hours }} ч. 75 | {% endif %} 76 | {% if minutes > 0 %} 77 | {% if days > 0 or hours > 0 %} 78 | {{ ' ' }} 79 | {% endif %} 80 | {{ minutes }} мин. 81 | {% endif %} 82 | {% endif %} 83 | 84 | # Время с последней перезагрузки 85 | - platform: template 86 | sensors: 87 | last_boot_custom: 88 | friendly_name: "Последняя перезагрузка" 89 | value_template: > 90 | {% set uptime = as_timestamp (now()) -as_timestamp(states.sensor.last_boot.state) | int %} 91 | {% set minutes = (( uptime % 3600) / 60) | int %} 92 | {% set hours = ((uptime % 86400) / 3600) | int %} 93 | {% set days = (uptime / 86400) | int %} 94 | 95 | {% if uptime < 1 %} 96 | Меньше минуты 97 | {% else %} 98 | {% if days > 0 %} 99 | {{ days }} д. 100 | {% endif %} 101 | {% if hours > 0 %} 102 | {% if days > 0 %} 103 | {{ ' ' }} 104 | {% endif %} 105 | {{ hours }} ч. 106 | {% endif %} 107 | {% if minutes > 0 %} 108 | {% if days > 0 or hours > 0 %} 109 | {{ ' ' }} 110 | {% endif %} 111 | {{ minutes }} мин. 112 | {% endif %} 113 | {% endif %} 114 | icon_template: mdi:clock 115 | -------------------------------------------------------------------------------- /includes/automation/005_telegramm.yaml: -------------------------------------------------------------------------------- 1 | - alias: 005_telegramm_command 2 | initial_state: true 3 | trigger: 4 | - platform: event 5 | event_type: telegram_command 6 | event_data: 7 | command: '/start' 8 | action: 9 | - service: notify.telegram_alex 10 | data: 11 | message: 'commands' 12 | data: 13 | keyboard: 14 | - '/ping, /vacancy, /photo, /ilife' 15 | - '/heat_mode_on, /heat_mode_off, /heat_on, /heat_off' 16 | - '/forecast' 17 | 18 | - alias: 005_telegramm_ping 19 | initial_state: true 20 | trigger: 21 | - platform: event 22 | event_type: telegram_command 23 | event_data: 24 | command: '/ping' 25 | action: 26 | - service: mqtt.publish 27 | data_template: 28 | topic: "states/command" 29 | payload_template: '001' 30 | retain: true 31 | 32 | - alias: 005_telegramm_vacancy 33 | initial_state: true 34 | trigger: 35 | - platform: event 36 | event_type: telegram_command 37 | event_data: 38 | command: '/vacancy' 39 | action: 40 | - service: mqtt.publish 41 | data_template: 42 | topic: "states/command" 43 | payload_template: '007' 44 | retain: true 45 | 46 | - alias: 005_telegramm_photo 47 | initial_state: true 48 | trigger: 49 | - platform: event 50 | event_type: telegram_command 51 | event_data: 52 | command: '/photo' 53 | action: 54 | - service: script.turn_on 55 | entity_id: 56 | - script.photo_xiaofang 57 | - script.photo_reolink 58 | - script.photo_reolink2 59 | - script.photo_reolink3 60 | 61 | - alias: 005_telegramm_ilife 62 | initial_state: true 63 | trigger: 64 | - platform: event 65 | event_type: telegram_command 66 | event_data: 67 | command: '/ilife' 68 | action: 69 | - service: mqtt.publish 70 | data_template: 71 | topic: "states/command" 72 | payload_template: '002' 73 | retain: true 74 | 75 | - alias: 005_telegramm_heat_mode_on 76 | initial_state: true 77 | trigger: 78 | - platform: event 79 | event_type: telegram_command 80 | event_data: 81 | command: '/heat_mode_on' 82 | action: 83 | - service: mqtt.publish 84 | data_template: 85 | topic: "states/command" 86 | payload_template: '003' 87 | retain: true 88 | 89 | - alias: 005_telegramm_heat_mode_off 90 | initial_state: true 91 | trigger: 92 | - platform: event 93 | event_type: telegram_command 94 | event_data: 95 | command: '/heat_mode_off' 96 | action: 97 | - service: mqtt.publish 98 | data_template: 99 | topic: "states/command" 100 | payload_template: '004' 101 | retain: true 102 | 103 | - alias: 005_telegramm_heat_on 104 | initial_state: true 105 | trigger: 106 | - platform: event 107 | event_type: telegram_command 108 | event_data: 109 | command: '/heat_on' 110 | action: 111 | - service: mqtt.publish 112 | data_template: 113 | topic: "states/command" 114 | payload_template: '005' 115 | retain: true 116 | 117 | - alias: 005_telegramm_heat_off 118 | initial_state: true 119 | trigger: 120 | - platform: event 121 | event_type: telegram_command 122 | event_data: 123 | command: '/heat_off' 124 | action: 125 | - service: mqtt.publish 126 | data_template: 127 | topic: "states/command" 128 | payload_template: '006' 129 | retain: true 130 | 131 | - alias: 005_telegramm_forecast 132 | initial_state: true 133 | trigger: 134 | - platform: event 135 | event_type: telegram_command 136 | event_data: 137 | command: '/forecast' 138 | action: 139 | - service: script.turn_on 140 | entity_id: 141 | - script.outdoor_report -------------------------------------------------------------------------------- /custom_components/hacs/config_flow.py: -------------------------------------------------------------------------------- 1 | """Adds config flow for HACS.""" 2 | import voluptuous as vol 3 | from aiogithubapi import AIOGitHubAPIAuthenticationException, AIOGitHubAPIException 4 | from homeassistant import config_entries 5 | from homeassistant.core import callback 6 | from homeassistant.helpers import aiohttp_client 7 | 8 | from custom_components.hacs.const import DOMAIN 9 | from custom_components.hacs.helpers.functions.configuration_schema import ( 10 | hacs_base_config_schema, 11 | hacs_config_option_schema, 12 | ) 13 | from custom_components.hacs.helpers.functions.information import get_repository 14 | 15 | # pylint: disable=dangerous-default-value 16 | from custom_components.hacs.helpers.functions.logger import getLogger 17 | from custom_components.hacs.share import get_hacs 18 | 19 | _LOGGER = getLogger(__name__) 20 | 21 | 22 | class HacsFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): 23 | """Config flow for HACS.""" 24 | 25 | VERSION = 1 26 | CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_POLL 27 | 28 | def __init__(self): 29 | """Initialize.""" 30 | self._errors = {} 31 | 32 | async def async_step_user(self, user_input={}): 33 | """Handle a flow initialized by the user.""" 34 | self._errors = {} 35 | if self._async_current_entries(): 36 | return self.async_abort(reason="single_instance_allowed") 37 | if self.hass.data.get(DOMAIN): 38 | return self.async_abort(reason="single_instance_allowed") 39 | 40 | if user_input is not None: 41 | if await self._test_token(user_input["token"]): 42 | return self.async_create_entry(title="", data=user_input) 43 | 44 | self._errors["base"] = "auth" 45 | return await self._show_config_form(user_input) 46 | 47 | return await self._show_config_form(user_input) 48 | 49 | async def _show_config_form(self, user_input): 50 | """Show the configuration form to edit location data.""" 51 | return self.async_show_form( 52 | step_id="user", 53 | data_schema=vol.Schema(hacs_base_config_schema(user_input)), 54 | errors=self._errors, 55 | ) 56 | 57 | @staticmethod 58 | @callback 59 | def async_get_options_flow(config_entry): 60 | return HacsOptionsFlowHandler(config_entry) 61 | 62 | async def _test_token(self, token): 63 | """Return true if token is valid.""" 64 | try: 65 | session = aiohttp_client.async_get_clientsession(self.hass) 66 | await get_repository(session, token, "hacs/org") 67 | return True 68 | except ( 69 | AIOGitHubAPIException, 70 | AIOGitHubAPIAuthenticationException, 71 | ) as exception: 72 | _LOGGER.error(exception) 73 | return False 74 | 75 | 76 | class HacsOptionsFlowHandler(config_entries.OptionsFlow): 77 | """HACS config flow options handler.""" 78 | 79 | def __init__(self, config_entry): 80 | """Initialize HACS options flow.""" 81 | self.config_entry = config_entry 82 | 83 | async def async_step_init(self, _user_input=None): 84 | """Manage the options.""" 85 | return await self.async_step_user() 86 | 87 | async def async_step_user(self, user_input=None): 88 | """Handle a flow initialized by the user.""" 89 | hacs = get_hacs() 90 | if user_input is not None: 91 | return self.async_create_entry(title="", data=user_input) 92 | 93 | if hacs.configuration.config_type == "yaml": 94 | schema = {vol.Optional("not_in_use", default=""): str} 95 | else: 96 | schema = hacs_config_option_schema(self.config_entry.options) 97 | del schema["frontend_repo"] 98 | del schema["frontend_repo_url"] 99 | 100 | return self.async_show_form(step_id="user", data_schema=vol.Schema(schema)) 101 | -------------------------------------------------------------------------------- /custom_components/hacs/helpers/functions/validate_repository.py: -------------------------------------------------------------------------------- 1 | """Helper to do common validation for repositories.""" 2 | from aiogithubapi import AIOGitHubAPIException 3 | 4 | from custom_components.hacs.helpers.classes.exceptions import HacsException 5 | from custom_components.hacs.helpers.functions.information import ( 6 | get_releases, 7 | get_repository, 8 | get_tree, 9 | ) 10 | from custom_components.hacs.helpers.functions.version_to_install import ( 11 | version_to_install, 12 | ) 13 | from custom_components.hacs.share import get_hacs, is_removed 14 | 15 | 16 | async def common_validate(repository, ignore_issues=False): 17 | """Common validation steps of the repository.""" 18 | repository.validate.errors = [] 19 | 20 | # Make sure the repository exist. 21 | repository.logger.debug("Checking repository.") 22 | await common_update_data(repository, ignore_issues) 23 | 24 | # Step 6: Get the content of hacs.json 25 | await repository.get_repository_manifest_content() 26 | 27 | 28 | async def common_update_data(repository, ignore_issues=False): 29 | """Common update data.""" 30 | hacs = get_hacs() 31 | releases = [] 32 | try: 33 | repository_object = await get_repository( 34 | hacs.session, hacs.configuration.token, repository.data.full_name 35 | ) 36 | repository.repository_object = repository_object 37 | repository.data.update_data(repository_object.attributes) 38 | except (AIOGitHubAPIException, HacsException) as exception: 39 | if not hacs.status.startup: 40 | repository.logger.error(exception) 41 | repository.validate.errors.append("Repository does not exist.") 42 | raise HacsException(exception) from None 43 | 44 | # Make sure the repository is not archived. 45 | if repository.data.archived and not ignore_issues: 46 | repository.validate.errors.append("Repository is archived.") 47 | raise HacsException("Repository is archived.") 48 | 49 | # Make sure the repository is not in the blacklist. 50 | if is_removed(repository.data.full_name) and not ignore_issues: 51 | repository.validate.errors.append("Repository is in the blacklist.") 52 | raise HacsException("Repository is in the blacklist.") 53 | 54 | # Get releases. 55 | try: 56 | releases = await get_releases( 57 | repository.repository_object, 58 | repository.data.show_beta, 59 | hacs.configuration.release_limit, 60 | ) 61 | if releases: 62 | repository.data.releases = True 63 | repository.releases.objects = releases 64 | repository.data.published_tags = [ 65 | x.tag_name for x in releases if not x.draft 66 | ] 67 | repository.data.last_version = next(iter(releases)).tag_name 68 | 69 | except (AIOGitHubAPIException, HacsException): 70 | repository.data.releases = False 71 | 72 | if not repository.force_branch: 73 | repository.ref = version_to_install(repository) 74 | if repository.data.releases: 75 | for release in releases or []: 76 | if release.tag_name == repository.ref: 77 | assets = release.assets 78 | if assets: 79 | downloads = next(iter(assets)).attributes.get("download_count") 80 | repository.data.downloads = downloads 81 | 82 | repository.logger.debug( 83 | f"Running checks against {repository.ref.replace('tags/', '')}" 84 | ) 85 | 86 | try: 87 | repository.tree = await get_tree(repository.repository_object, repository.ref) 88 | if not repository.tree: 89 | raise HacsException("No files in tree") 90 | repository.treefiles = [] 91 | for treefile in repository.tree: 92 | repository.treefiles.append(treefile.full_path) 93 | except (AIOGitHubAPIException, HacsException) as exception: 94 | if not hacs.status.startup: 95 | repository.logger.error(exception) 96 | raise HacsException(exception) from None 97 | -------------------------------------------------------------------------------- /custom_components/hacs/repositories/integration.py: -------------------------------------------------------------------------------- 1 | """Class for integrations in HACS.""" 2 | from homeassistant.loader import async_get_custom_components 3 | 4 | from custom_components.hacs.helpers.classes.exceptions import HacsException 5 | from custom_components.hacs.helpers.classes.repository import HacsRepository 6 | from custom_components.hacs.enums import HacsCategory 7 | from custom_components.hacs.helpers.functions.filters import ( 8 | get_first_directory_in_directory, 9 | ) 10 | from custom_components.hacs.helpers.functions.information import ( 11 | get_integration_manifest, 12 | ) 13 | from custom_components.hacs.helpers.functions.logger import getLogger 14 | 15 | 16 | class HacsIntegration(HacsRepository): 17 | """Integrations in HACS.""" 18 | 19 | def __init__(self, full_name): 20 | """Initialize.""" 21 | super().__init__() 22 | self.data.full_name = full_name 23 | self.data.full_name_lower = full_name.lower() 24 | self.data.category = HacsCategory.INTEGRATION 25 | self.content.path.remote = "custom_components" 26 | self.content.path.local = self.localpath 27 | self.logger = getLogger(f"repository.{self.data.category}.{full_name}") 28 | 29 | @property 30 | def localpath(self): 31 | """Return localpath.""" 32 | return f"{self.hacs.system.config_path}/custom_components/{self.data.domain}" 33 | 34 | async def async_post_installation(self): 35 | """Run post installation steps.""" 36 | if self.data.config_flow: 37 | if self.data.full_name != "hacs/integration": 38 | await self.reload_custom_components() 39 | if self.data.first_install: 40 | self.pending_restart = False 41 | return 42 | self.pending_restart = True 43 | 44 | async def validate_repository(self): 45 | """Validate.""" 46 | await self.common_validate() 47 | 48 | # Custom step 1: Validate content. 49 | if self.data.content_in_root: 50 | self.content.path.remote = "" 51 | 52 | if self.content.path.remote == "custom_components": 53 | name = get_first_directory_in_directory(self.tree, "custom_components") 54 | if name is None: 55 | raise HacsException( 56 | f"Repostitory structure for {self.ref.replace('tags/','')} is not compliant" 57 | ) 58 | self.content.path.remote = f"custom_components/{name}" 59 | 60 | try: 61 | await get_integration_manifest(self) 62 | except HacsException as exception: 63 | if self.hacs.system.action: 64 | raise HacsException(f"::error:: {exception}") 65 | self.logger.error(exception) 66 | 67 | # Handle potential errors 68 | if self.validate.errors: 69 | for error in self.validate.errors: 70 | if not self.hacs.status.startup: 71 | self.logger.error(error) 72 | return self.validate.success 73 | 74 | async def update_repository(self, ignore_issues=False): 75 | """Update.""" 76 | await self.common_update(ignore_issues) 77 | 78 | if self.data.content_in_root: 79 | self.content.path.remote = "" 80 | 81 | if self.content.path.remote == "custom_components": 82 | name = get_first_directory_in_directory(self.tree, "custom_components") 83 | self.content.path.remote = f"custom_components/{name}" 84 | 85 | try: 86 | await get_integration_manifest(self) 87 | except HacsException as exception: 88 | self.logger.error(exception) 89 | 90 | # Set local path 91 | self.content.path.local = self.localpath 92 | 93 | async def reload_custom_components(self): 94 | """Reload custom_components (and config flows)in HA.""" 95 | self.logger.info("Reloading custom_component cache") 96 | del self.hacs.hass.data["custom_components"] 97 | await async_get_custom_components(self.hacs.hass) 98 | self.logger.info("Custom_component cache reloaded") 99 | -------------------------------------------------------------------------------- /custom_components/hacs/api/hacs_repository.py: -------------------------------------------------------------------------------- 1 | """API Handler for hacs_repository""" 2 | import homeassistant.helpers.config_validation as cv 3 | import voluptuous as vol 4 | from aiogithubapi import AIOGitHubAPIException 5 | from homeassistant.components import websocket_api 6 | 7 | from custom_components.hacs.helpers.functions.logger import getLogger 8 | from custom_components.hacs.share import get_hacs 9 | 10 | 11 | @websocket_api.async_response 12 | @websocket_api.websocket_command( 13 | { 14 | vol.Required("type"): "hacs/repository", 15 | vol.Optional("action"): cv.string, 16 | vol.Optional("repository"): cv.string, 17 | } 18 | ) 19 | async def hacs_repository(hass, connection, msg): 20 | """Handle get media player cover command.""" 21 | hacs = get_hacs() 22 | logger = getLogger("api.repository") 23 | data = {} 24 | repository = None 25 | 26 | repo_id = msg.get("repository") 27 | action = msg.get("action") 28 | if repo_id is None or action is None: 29 | return 30 | 31 | try: 32 | repository = hacs.get_by_id(repo_id) 33 | logger.debug(f"Running {action} for {repository.data.full_name}") 34 | 35 | if action == "update": 36 | await repository.update_repository(True) 37 | repository.status.updated_info = True 38 | 39 | elif action == "install": 40 | repository.data.new = False 41 | was_installed = repository.data.installed 42 | await repository.async_install() 43 | if not was_installed: 44 | hass.bus.async_fire("hacs/reload", {"force": True}) 45 | 46 | elif action == "not_new": 47 | repository.data.new = False 48 | 49 | elif action == "uninstall": 50 | repository.data.new = False 51 | await repository.update_repository(True) 52 | await repository.uninstall() 53 | 54 | elif action == "hide": 55 | repository.data.hide = True 56 | 57 | elif action == "unhide": 58 | repository.data.hide = False 59 | 60 | elif action == "show_beta": 61 | repository.data.show_beta = True 62 | await repository.update_repository() 63 | 64 | elif action == "hide_beta": 65 | repository.data.show_beta = False 66 | await repository.update_repository() 67 | 68 | elif action == "toggle_beta": 69 | repository.data.show_beta = not repository.data.show_beta 70 | await repository.update_repository() 71 | 72 | elif action == "delete": 73 | repository.data.show_beta = False 74 | repository.remove() 75 | 76 | elif action == "release_notes": 77 | data = [ 78 | { 79 | "name": x.attributes["name"], 80 | "body": x.attributes["body"], 81 | "tag": x.attributes["tag_name"], 82 | } 83 | for x in repository.releases.objects 84 | ] 85 | 86 | elif action == "set_version": 87 | if msg["version"] == repository.data.default_branch: 88 | repository.data.selected_tag = None 89 | else: 90 | repository.data.selected_tag = msg["version"] 91 | await repository.update_repository() 92 | 93 | hass.bus.async_fire("hacs/reload", {"force": True}) 94 | 95 | else: 96 | logger.error(f"WS action '{action}' is not valid") 97 | 98 | await hacs.data.async_write() 99 | message = None 100 | except AIOGitHubAPIException as exception: 101 | message = exception 102 | except AttributeError as exception: 103 | message = f"Could not use repository with ID {repo_id} ({exception})" 104 | except (Exception, BaseException) as exception: # pylint: disable=broad-except 105 | message = exception 106 | 107 | if message is not None: 108 | logger.error(message) 109 | hass.bus.async_fire("hacs/error", {"message": str(message)}) 110 | 111 | if repository: 112 | repository.state = None 113 | connection.send_message(websocket_api.result_message(msg["id"], data)) 114 | -------------------------------------------------------------------------------- /custom_components/hacs/api/hacs_repository_data.py: -------------------------------------------------------------------------------- 1 | """API Handler for hacs_repository_data""" 2 | import sys 3 | 4 | import homeassistant.helpers.config_validation as cv 5 | import voluptuous as vol 6 | from aiogithubapi import AIOGitHubAPIException 7 | from homeassistant.components import websocket_api 8 | 9 | from custom_components.hacs.helpers.classes.exceptions import HacsException 10 | from custom_components.hacs.helpers.functions.logger import getLogger 11 | from custom_components.hacs.helpers.functions.misc import extract_repository_from_url 12 | from custom_components.hacs.helpers.functions.register_repository import ( 13 | register_repository, 14 | ) 15 | from custom_components.hacs.share import get_hacs 16 | 17 | 18 | @websocket_api.async_response 19 | @websocket_api.websocket_command( 20 | { 21 | vol.Required("type"): "hacs/repository/data", 22 | vol.Optional("action"): cv.string, 23 | vol.Optional("repository"): cv.string, 24 | vol.Optional("data"): cv.string, 25 | } 26 | ) 27 | async def hacs_repository_data(hass, connection, msg): 28 | """Handle get media player cover command.""" 29 | hacs = get_hacs() 30 | logger = getLogger("api.repository_data") 31 | repo_id = msg.get("repository") 32 | action = msg.get("action") 33 | data = msg.get("data") 34 | 35 | if repo_id is None: 36 | return 37 | 38 | if action == "add": 39 | repo_id = extract_repository_from_url(repo_id) 40 | if repo_id is None: 41 | return 42 | 43 | if repo_id in hacs.common.skip: 44 | hacs.common.skip.remove(repo_id) 45 | 46 | if not hacs.get_by_name(repo_id): 47 | try: 48 | registration = await register_repository(repo_id, data.lower()) 49 | if registration is not None: 50 | raise HacsException(registration) 51 | except ( 52 | Exception, 53 | BaseException, 54 | ) as exception: # pylint: disable=broad-except 55 | hass.bus.async_fire( 56 | "hacs/error", 57 | { 58 | "action": "add_repository", 59 | "exception": str(sys.exc_info()[0].__name__), 60 | "message": str(exception), 61 | }, 62 | ) 63 | else: 64 | hass.bus.async_fire( 65 | "hacs/error", 66 | { 67 | "action": "add_repository", 68 | "message": f"Repository '{repo_id}' exists in the store.", 69 | }, 70 | ) 71 | 72 | repository = hacs.get_by_name(repo_id) 73 | else: 74 | repository = hacs.get_by_id(repo_id) 75 | 76 | if repository is None: 77 | hass.bus.async_fire("hacs/repository", {}) 78 | return 79 | 80 | logger.debug(f"Running {action} for {repository.data.full_name}") 81 | try: 82 | if action == "set_state": 83 | repository.state = data 84 | 85 | elif action == "set_version": 86 | repository.data.selected_tag = data 87 | await repository.update_repository() 88 | 89 | repository.state = None 90 | 91 | elif action == "install": 92 | was_installed = repository.data.installed 93 | repository.data.selected_tag = data 94 | await repository.update_repository() 95 | await repository.async_install() 96 | repository.state = None 97 | if not was_installed: 98 | hass.bus.async_fire("hacs/reload", {"force": True}) 99 | 100 | elif action == "add": 101 | repository.state = None 102 | 103 | else: 104 | repository.state = None 105 | logger.error(f"WS action '{action}' is not valid") 106 | 107 | message = None 108 | except AIOGitHubAPIException as exception: 109 | message = exception 110 | except AttributeError as exception: 111 | message = f"Could not use repository with ID {repo_id} ({exception})" 112 | except (Exception, BaseException) as exception: # pylint: disable=broad-except 113 | message = exception 114 | 115 | if message is not None: 116 | logger.error(message) 117 | hass.bus.async_fire("hacs/error", {"message": str(message)}) 118 | 119 | await hacs.data.async_write() 120 | connection.send_message(websocket_api.result_message(msg["id"], {})) 121 | --------------------------------------------------------------------------------