parent
2bb772bbdc
commit
1a4a9532dd
|
@ -80,7 +80,6 @@ set_swing_mode:
|
|||
example: 'climate.nest'
|
||||
swing_mode:
|
||||
description: New value of swing mode.
|
||||
example:
|
||||
|
||||
turn_on:
|
||||
description: Turn climate device on.
|
||||
|
|
|
@ -19,6 +19,7 @@ configure:
|
|||
|
||||
device_refresh:
|
||||
description: Refresh device lists from deCONZ.
|
||||
bridgeid:
|
||||
description: (Optional) Bridgeid is a string unique for each deCONZ hardware. It can be found as part of the integration name.
|
||||
example: '00212EFFFF012345'
|
||||
fields:
|
||||
bridgeid:
|
||||
description: (Optional) Bridgeid is a string unique for each deCONZ hardware. It can be found as part of the integration name.
|
||||
example: '00212EFFFF012345'
|
||||
|
|
|
@ -25,40 +25,39 @@ see:
|
|||
description: Battery level of device.
|
||||
example: '100'
|
||||
|
||||
icloud:
|
||||
icloud_lost_iphone:
|
||||
description: Service to play the lost iphone sound on an iDevice.
|
||||
fields:
|
||||
account_name:
|
||||
description: Name of the account in the config that will be used to look for the device. This is optional, if it isn't given it will use all accounts.
|
||||
example: 'bart'
|
||||
device_name:
|
||||
description: Name of the device that will play the sound. This is optional, if it isn't given it will play on all devices for the given account.
|
||||
example: 'iphonebart'
|
||||
icloud_set_interval:
|
||||
description: Service to set the interval of an iDevice.
|
||||
fields:
|
||||
account_name:
|
||||
description: Name of the account in the config that will be used to look for the device. This is optional, if it isn't given it will use all accounts.
|
||||
example: 'bart'
|
||||
device_name:
|
||||
description: Name of the device that will get a new interval. This is optional, if it isn't given it will change the interval for all devices for the given account.
|
||||
example: 'iphonebart'
|
||||
interval:
|
||||
description: The interval (in minutes) that the iDevice will have until the according device_tracker entity changes from zone or until this service is used again. This is optional, if it isn't given the interval of the device will revert back to the original interval based on the current state.
|
||||
example: 1
|
||||
icloud_update:
|
||||
description: Service to ask for an update of an iDevice.
|
||||
fields:
|
||||
account_name:
|
||||
description: Name of the account in the config that will be used to look for the device. This is optional, if it isn't given it will use all accounts.
|
||||
example: 'bart'
|
||||
device_name:
|
||||
description: Name of the device that will be updated. This is optional, if it isn't given it will update all devices for the given account.
|
||||
example: 'iphonebart'
|
||||
icloud_reset_account:
|
||||
description: Service to restart an iCloud account. Helpful when not all devices are found after initializing or when you add a new device.
|
||||
fields:
|
||||
account_name:
|
||||
description: Name of the account in the config that will be restarted. This is optional, if it isn't given it will restart all accounts.
|
||||
example: 'bart'
|
||||
icloud_lost_iphone:
|
||||
description: Service to play the lost iphone sound on an iDevice.
|
||||
fields:
|
||||
account_name:
|
||||
description: Name of the account in the config that will be used to look for the device. This is optional, if it isn't given it will use all accounts.
|
||||
example: 'bart'
|
||||
device_name:
|
||||
description: Name of the device that will play the sound. This is optional, if it isn't given it will play on all devices for the given account.
|
||||
example: 'iphonebart'
|
||||
icloud_set_interval:
|
||||
description: Service to set the interval of an iDevice.
|
||||
fields:
|
||||
account_name:
|
||||
description: Name of the account in the config that will be used to look for the device. This is optional, if it isn't given it will use all accounts.
|
||||
example: 'bart'
|
||||
device_name:
|
||||
description: Name of the device that will get a new interval. This is optional, if it isn't given it will change the interval for all devices for the given account.
|
||||
example: 'iphonebart'
|
||||
interval:
|
||||
description: The interval (in minutes) that the iDevice will have until the according device_tracker entity changes from zone or until this service is used again. This is optional, if it isn't given the interval of the device will revert back to the original interval based on the current state.
|
||||
example: 1
|
||||
icloud_update:
|
||||
description: Service to ask for an update of an iDevice.
|
||||
fields:
|
||||
account_name:
|
||||
description: Name of the account in the config that will be used to look for the device. This is optional, if it isn't given it will use all accounts.
|
||||
example: 'bart'
|
||||
device_name:
|
||||
description: Name of the device that will be updated. This is optional, if it isn't given it will update all devices for the given account.
|
||||
example: 'iphonebart'
|
||||
icloud_reset_account:
|
||||
description: Service to restart an iCloud account. Helpful when not all devices are found after initializing or when you add a new device.
|
||||
fields:
|
||||
account_name:
|
||||
description: Name of the account in the config that will be restarted. This is optional, if it isn't given it will restart all accounts.
|
||||
example: 'bart'
|
||||
|
|
|
@ -19,7 +19,7 @@ send_command:
|
|||
are source and destination, second byte is command and optional other bytes
|
||||
are command parameters. If raw command specified, other params are ignored.',
|
||||
example: '"10:36"'}
|
||||
src: {desctiption: 'Source of command. Could be decimal number or string with
|
||||
src: {description: 'Source of command. Could be decimal number or string with
|
||||
hexadeximal notation: "0x10".', example: 12 or "0xc"}
|
||||
standby: {description: Standby all devices which supports it.}
|
||||
update: {description: Update devices state from network.}
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
system_log:
|
||||
clear:
|
||||
description: Clear all log entries.
|
||||
write:
|
||||
description: Write log entry.
|
||||
fields:
|
||||
message:
|
||||
description: Message to log. [Required]
|
||||
example: Something went wrong
|
||||
level:
|
||||
description: "Log level: debug, info, warning, error, critical. Defaults to 'error'."
|
||||
example: debug
|
||||
logger:
|
||||
description: Logger name under which to log the message. Defaults to 'system_log.external'.
|
||||
example: mycomponent.myplatform
|
||||
clear:
|
||||
description: Clear all log entries.
|
||||
|
||||
write:
|
||||
description: Write log entry.
|
||||
fields:
|
||||
message:
|
||||
description: Message to log. [Required]
|
||||
example: Something went wrong
|
||||
level:
|
||||
description: "Log level: debug, info, warning, error, critical. Defaults to 'error'."
|
||||
example: debug
|
||||
logger:
|
||||
description: Logger name under which to log the message. Defaults to 'system_log.external'.
|
||||
example: mycomponent.myplatform
|
||||
|
|
|
@ -30,15 +30,15 @@ heal_network:
|
|||
description: Start a Z-Wave network heal. This might take a while and will slow down the Z-Wave network greatly while it is being processed. Refer to OZW_Log.txt for progress.
|
||||
fields:
|
||||
return_routes:
|
||||
description: Whether or not to update the return routes from the nodes to the controller. Defaults to False.
|
||||
example: True
|
||||
description: Whether or not to update the return routes from the nodes to the controller. Defaults to False.
|
||||
example: True
|
||||
|
||||
heal_node:
|
||||
description: Start a Z-Wave node heal. Refer to OZW_Log.txt for progress.
|
||||
fields:
|
||||
return_routes:
|
||||
description: Whether or not to update the return routes from the node to the controller. Defaults to False.
|
||||
example: True
|
||||
description: Whether or not to update the return routes from the node to the controller. Defaults to False.
|
||||
example: True
|
||||
|
||||
remove_node:
|
||||
description: Remove a node from the Z-Wave network. Refer to OZW_Log.txt for progress.
|
||||
|
@ -160,7 +160,7 @@ test_node:
|
|||
example: 10
|
||||
messages:
|
||||
description: Optional. Amount of test messages to send.
|
||||
example: 3
|
||||
example: 3
|
||||
|
||||
rename_node:
|
||||
description: Set the name of a node. This will also affect the IDs of all entities in the node.
|
||||
|
|
|
@ -3,12 +3,13 @@ import pathlib
|
|||
import sys
|
||||
|
||||
from .model import Integration, Config
|
||||
from . import dependencies, manifest, codeowners
|
||||
from . import dependencies, manifest, codeowners, services
|
||||
|
||||
PLUGINS = [
|
||||
manifest,
|
||||
dependencies,
|
||||
codeowners,
|
||||
services,
|
||||
]
|
||||
|
||||
|
||||
|
@ -37,6 +38,7 @@ def main():
|
|||
manifest.validate(integrations, config)
|
||||
dependencies.validate(integrations, config)
|
||||
codeowners.validate(integrations, config)
|
||||
services.validate(integrations, config)
|
||||
|
||||
# When we generate, all errors that are fixable will be ignored,
|
||||
# as generating them will be fixed.
|
||||
|
|
|
@ -61,26 +61,22 @@ class Integration:
|
|||
"""Integration domain."""
|
||||
return self.path.name
|
||||
|
||||
@property
|
||||
def manifest_path(self) -> pathlib.Path:
|
||||
"""Integration manifest path."""
|
||||
return self.path / 'manifest.json'
|
||||
|
||||
def add_error(self, *args, **kwargs):
|
||||
"""Add an error."""
|
||||
self.errors.append(Error(*args, **kwargs))
|
||||
|
||||
def load_manifest(self) -> None:
|
||||
"""Load manifest."""
|
||||
if not self.manifest_path.is_file():
|
||||
manifest_path = self.path / 'manifest.json'
|
||||
if not manifest_path.is_file():
|
||||
self.add_error(
|
||||
'model',
|
||||
"Manifest file {} not found".format(self.manifest_path)
|
||||
"Manifest file {} not found".format(manifest_path)
|
||||
)
|
||||
return
|
||||
|
||||
try:
|
||||
manifest = json.loads(self.manifest_path.read_text())
|
||||
manifest = json.loads(manifest_path.read_text())
|
||||
except ValueError as err:
|
||||
self.add_error(
|
||||
'model',
|
||||
|
|
|
@ -0,0 +1,104 @@
|
|||
"""Validate dependencies."""
|
||||
import pathlib
|
||||
from typing import Dict
|
||||
|
||||
import re
|
||||
import voluptuous as vol
|
||||
from voluptuous.humanize import humanize_error
|
||||
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
from homeassistant.util.yaml import load_yaml
|
||||
|
||||
from .model import Integration
|
||||
|
||||
|
||||
def exists(value):
|
||||
"""Check if value exists."""
|
||||
if value is None:
|
||||
raise vol.Invalid("Value cannot be None")
|
||||
return value
|
||||
|
||||
|
||||
FIELD_SCHEMA = vol.Schema({
|
||||
vol.Required('description'): str,
|
||||
vol.Optional('example'): exists,
|
||||
vol.Optional('default'): exists,
|
||||
vol.Optional('values'): exists,
|
||||
vol.Optional('required'): bool,
|
||||
})
|
||||
|
||||
SERVICE_SCHEMA = vol.Schema({
|
||||
vol.Required('description'): str,
|
||||
vol.Optional('fields'): vol.Schema({
|
||||
str: FIELD_SCHEMA
|
||||
})
|
||||
})
|
||||
|
||||
SERVICES_SCHEMA = vol.Schema({
|
||||
cv.slug: SERVICE_SCHEMA
|
||||
})
|
||||
|
||||
|
||||
def grep_dir(path: pathlib.Path, glob_pattern: str, search_pattern: str) \
|
||||
-> bool:
|
||||
"""Recursively go through a dir and it's children and find the regex."""
|
||||
pattern = re.compile(search_pattern)
|
||||
|
||||
for fil in path.glob(glob_pattern):
|
||||
if not fil.is_file():
|
||||
continue
|
||||
|
||||
if pattern.search(fil.read_text()):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def validate_services(integration: Integration):
|
||||
"""Validate services."""
|
||||
# Find if integration uses services
|
||||
has_services = grep_dir(integration.path, "**/*.py",
|
||||
r"hass\.(services|async_register)")
|
||||
|
||||
if not has_services:
|
||||
return
|
||||
|
||||
try:
|
||||
data = load_yaml(str(integration.path / 'services.yaml'))
|
||||
except FileNotFoundError:
|
||||
print(
|
||||
"Warning: {} registeres services but has no services.yaml".format(
|
||||
integration.domain))
|
||||
# integration.add_error(
|
||||
# 'services', 'Registers services but has no services.yaml')
|
||||
return
|
||||
except HomeAssistantError:
|
||||
integration.add_error(
|
||||
'services', 'Registers services but unable to load services.yaml')
|
||||
return
|
||||
|
||||
try:
|
||||
SERVICES_SCHEMA(data)
|
||||
except vol.Invalid as err:
|
||||
integration.add_error(
|
||||
'services',
|
||||
"Invalid services.yaml: {}".format(humanize_error(data, err)))
|
||||
|
||||
|
||||
def validate(integrations: Dict[str, Integration], config):
|
||||
"""Handle dependencies for integrations."""
|
||||
# check services.yaml is cool
|
||||
for integration in integrations.values():
|
||||
if not integration.manifest:
|
||||
continue
|
||||
|
||||
validate_services(integration)
|
||||
|
||||
# check that all referenced dependencies exist
|
||||
for dep in integration.manifest['dependencies']:
|
||||
if dep not in integrations:
|
||||
integration.add_error(
|
||||
'dependencies',
|
||||
"Dependency {} does not exist"
|
||||
)
|
Loading…
Reference in New Issue