# Constant color temp values for 2 flux_led special modes # Warm-white and Cool-white modes COLOR_TEMP_WARM_VS_COLD_WHITE_CUT_OFF: Final = 285 EFFECT_CUSTOM: Final = "custom" SERVICE_CUSTOM_EFFECT: Final = "set_custom_effect" CUSTOM_EFFECT_DICT: Final = { vol.Required(CONF_COLORS): vol.All( cv.ensure_list, vol.Length(min=1, max=16), [ vol.All(vol.Coerce(tuple), vol.ExactSequence((cv.byte, cv.byte, cv.byte))) ], ), vol.Optional(CONF_SPEED_PCT, default=50): vol.All(vol.Range(min=0, max=100), vol.Coerce(int)), vol.Optional(CONF_TRANSITION, default=TRANSITION_GRADUAL): vol.All(cv.string, vol.In([TRANSITION_GRADUAL, TRANSITION_JUMP, TRANSITION_STROBE])), } CUSTOM_EFFECT_SCHEMA: Final = vol.Schema(CUSTOM_EFFECT_DICT) DEVICE_SCHEMA: Final = vol.Schema({ vol.Optional(CONF_NAME): cv.string, vol.Optional(ATTR_MODE, default=MODE_AUTO):
LIGHT_PROFILES_FILE = "light_profiles.csv" # Service call validation schemas VALID_TRANSITION = vol.All(vol.Coerce(float), vol.Clamp(min=0, max=6553)) VALID_BRIGHTNESS = vol.All(vol.Coerce(int), vol.Clamp(min=0, max=255)) VALID_BRIGHTNESS_PCT = vol.All(vol.Coerce(float), vol.Range(min=0, max=100)) LIGHT_TURN_ON_SCHEMA = vol.Schema({ ATTR_ENTITY_ID: cv.entity_ids, vol.Exclusive(ATTR_PROFILE, COLOR_GROUP): cv.string, ATTR_TRANSITION: VALID_TRANSITION, ATTR_BRIGHTNESS: VALID_BRIGHTNESS, ATTR_BRIGHTNESS_PCT: VALID_BRIGHTNESS_PCT, vol.Exclusive(ATTR_COLOR_NAME, COLOR_GROUP): cv.string, vol.Exclusive(ATTR_RGB_COLOR, COLOR_GROUP): vol.All(vol.ExactSequence((cv.byte, cv.byte, cv.byte)), vol.Coerce(tuple)), vol.Exclusive(ATTR_XY_COLOR, COLOR_GROUP): vol.All(vol.ExactSequence((cv.small_float, cv.small_float)), vol.Coerce(tuple)), vol.Exclusive(ATTR_HS_COLOR, COLOR_GROUP): vol.All(vol.ExactSequence( (vol.All(vol.Coerce(float), vol.Range(min=0, max=360)), vol.All(vol.Coerce(float), vol.Range(min=0, max=100)))), vol.Coerce(tuple)), vol.Exclusive(ATTR_COLOR_TEMP, COLOR_GROUP): vol.All(vol.Coerce(int), vol.Range(min=1)), vol.Exclusive(ATTR_KELVIN, COLOR_GROUP): vol.All(vol.Coerce(int), vol.Range(min=0)), ATTR_WHITE_VALUE: vol.All(vol.Coerce(int), vol.Range(min=0, max=255)), ATTR_FLASH: vol.In([FLASH_SHORT, FLASH_LONG]),
EFFECT_SLOWDOWN: yee_transitions.slowdown, } VALID_BRIGHTNESS = vol.All(vol.Coerce(int), vol.Range(min=1, max=100)) SERVICE_SCHEMA_SET_MODE = YEELIGHT_SERVICE_SCHEMA.extend({ vol.Required(ATTR_MODE): vol.In([mode.name.lower() for mode in PowerMode]) }) SERVICE_SCHEMA_START_FLOW = YEELIGHT_SERVICE_SCHEMA.extend( YEELIGHT_FLOW_TRANSITION_SCHEMA) SERVICE_SCHEMA_SET_COLOR_SCENE = YEELIGHT_SERVICE_SCHEMA.extend({ vol.Required(ATTR_RGB_COLOR): vol.All(vol.ExactSequence((cv.byte, cv.byte, cv.byte)), vol.Coerce(tuple)), vol.Required(ATTR_BRIGHTNESS): VALID_BRIGHTNESS, }) SERVICE_SCHEMA_SET_HSV_SCENE = YEELIGHT_SERVICE_SCHEMA.extend({ vol.Required(ATTR_HS_COLOR): vol.All( vol.ExactSequence(( vol.All(vol.Coerce(float), vol.Range(min=0, max=359)), vol.All(vol.Coerce(float), vol.Range(min=0, max=100)), )), vol.Coerce(tuple), ), vol.Required(ATTR_BRIGHTNESS): VALID_BRIGHTNESS,
# Constant color temp values for 2 flux_led special modes # Warm-white and Cool-white modes COLOR_TEMP_WARM_VS_COLD_WHITE_CUT_OFF: Final = 285 EFFECT_CUSTOM: Final = "custom" SERVICE_CUSTOM_EFFECT: Final = "set_custom_effect" SERVICE_SET_ZONES: Final = "set_zones" SERVICE_SET_MUSIC_MODE: Final = "set_music_mode" CUSTOM_EFFECT_DICT: Final = { vol.Required(CONF_COLORS): vol.All( cv.ensure_list, vol.Length(min=1, max=16), [vol.All(vol.Coerce(tuple), vol.ExactSequence((cv.byte, cv.byte, cv.byte)))], ), vol.Optional(CONF_SPEED_PCT, default=50): vol.All( vol.Coerce(int), vol.Range(min=0, max=100) ), vol.Optional(CONF_TRANSITION, default=TRANSITION_GRADUAL): vol.All( cv.string, vol.In([TRANSITION_GRADUAL, TRANSITION_JUMP, TRANSITION_STROBE]) ), } SET_MUSIC_MODE_DICT: Final = { vol.Optional(ATTR_SENSITIVITY, default=100): vol.All( vol.Coerce(int), vol.Range(min=0, max=100) ), vol.Optional(ATTR_BRIGHTNESS, default=100): vol.All( vol.Coerce(int), vol.Range(min=0, max=100)
extra=vol.ALLOW_EXTRA, ) VACUUM_SERVICE_SCHEMA = vol.Schema({vol.Optional(ATTR_ENTITY_ID): cv.comp_entity_ids}) SERVICE_CLEAN_ZONE = "xiaomi_clean_zone" SERVICE_CLEAN_POINT = "xiaomi_clean_point" ATTR_ZONE_ARRAY = "zone" ATTR_ZONE_REPEATER = "repeats" ATTR_POINT = "point" SERVICE_SCHEMA_CLEAN_ZONE = VACUUM_SERVICE_SCHEMA.extend( { vol.Required(ATTR_ZONE_ARRAY): vol.All( list, [ vol.ExactSequence( [vol.Coerce(float), vol.Coerce(float), vol.Coerce(float), vol.Coerce(float)] ) ], ), vol.Required(ATTR_ZONE_REPEATER): vol.All( vol.Coerce(int), vol.Clamp(min=1, max=3) ), } ) SERVICE_SCHEMA_CLEAN_POINT = VACUUM_SERVICE_SCHEMA.extend( { vol.Required(ATTR_POINT): vol.All( vol.ExactSequence( [vol.Coerce(float), vol.Coerce(float)] ) )
} EFFECT_CUSTOM_CODE = 0x60 TRANSITION_GRADUAL = "gradual" TRANSITION_JUMP = "jump" TRANSITION_STROBE = "strobe" FLUX_EFFECT_LIST = sorted(list(EFFECT_MAP)) + [EFFECT_RANDOM] CUSTOM_EFFECT_SCHEMA = vol.Schema({ vol.Required(CONF_COLORS): vol.All( cv.ensure_list, vol.Length(min=1, max=16), [ vol.All(vol.ExactSequence( (cv.byte, cv.byte, cv.byte)), vol.Coerce(tuple)) ], ), vol.Optional(CONF_SPEED_PCT, default=50): vol.All(vol.Range(min=0, max=100), vol.Coerce(int)), vol.Optional(CONF_TRANSITION, default=TRANSITION_GRADUAL): vol.All(cv.string, vol.In([TRANSITION_GRADUAL, TRANSITION_JUMP, TRANSITION_STROBE])), }) DEVICE_SCHEMA = vol.Schema({ vol.Optional(CONF_NAME): cv.string, vol.Optional(ATTR_MODE, default=MODE_RGBW): vol.All(cv.string, vol.In([MODE_RGBW, MODE_RGB, MODE_WHITE])), vol.Optional(CONF_PROTOCOL):
vol.Exclusive(ATTR_BRIGHTNESS_PCT, ATTR_BRIGHTNESS): VALID_BRIGHTNESS_PCT, vol.Exclusive(ATTR_BRIGHTNESS_STEP, ATTR_BRIGHTNESS): VALID_BRIGHTNESS_STEP, vol.Exclusive(ATTR_BRIGHTNESS_STEP_PCT, ATTR_BRIGHTNESS): VALID_BRIGHTNESS_STEP_PCT, vol.Exclusive(ATTR_COLOR_NAME, COLOR_GROUP): cv.string, vol.Exclusive(ATTR_COLOR_TEMP, COLOR_GROUP): vol.All(vol.Coerce(int), vol.Range(min=1)), vol.Exclusive(ATTR_KELVIN, COLOR_GROUP): cv.positive_int, vol.Exclusive(ATTR_HS_COLOR, COLOR_GROUP): vol.All( vol.ExactSequence(( vol.All(vol.Coerce(float), vol.Range(min=0, max=360)), vol.All(vol.Coerce(float), vol.Range(min=0, max=100)), )), vol.Coerce(tuple), ), vol.Exclusive(ATTR_RGB_COLOR, COLOR_GROUP): vol.All(vol.ExactSequence((cv.byte, ) * 3), vol.Coerce(tuple)), vol.Exclusive(ATTR_RGBW_COLOR, COLOR_GROUP): vol.All(vol.ExactSequence((cv.byte, ) * 4), vol.Coerce(tuple)), vol.Exclusive(ATTR_RGBWW_COLOR, COLOR_GROUP): vol.All(vol.ExactSequence((cv.byte, ) * 5), vol.Coerce(tuple)), vol.Exclusive(ATTR_XY_COLOR, COLOR_GROUP): vol.All(vol.ExactSequence((cv.small_float, cv.small_float)), vol.Coerce(tuple)), vol.Exclusive(ATTR_WHITE, COLOR_GROUP): VALID_BRIGHTNESS, ATTR_WHITE_VALUE:
def __call__(self, data: Any) -> list[int]: """Validate the passed selection.""" value: list[int] = vol.All(list, vol.ExactSequence((cv.byte,) * 3))(data) return value
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up the Xiaomi vacuum cleaner robot platform.""" if DATA_KEY not in hass.data: hass.data[DATA_KEY] = {} host = config[CONF_HOST] token = config[CONF_TOKEN] name = config[CONF_NAME] # Create handler _LOGGER.info("Initializing with host %s (token %s...)", host, token[:5]) vacuum = Vacuum(host, token) mirobo = MiroboVacuum(name, vacuum) hass.data[DATA_KEY][host] = mirobo async_add_entities([mirobo], update_before_add=True) platform = entity_platform.current_platform.get() platform.async_register_entity_service( SERVICE_START_REMOTE_CONTROL, {}, MiroboVacuum.async_remote_control_start.__name__, ) platform.async_register_entity_service( SERVICE_STOP_REMOTE_CONTROL, {}, MiroboVacuum.async_remote_control_stop.__name__, ) platform.async_register_entity_service( SERVICE_MOVE_REMOTE_CONTROL, { vol.Optional(ATTR_RC_VELOCITY): vol.All(vol.Coerce(float), vol.Clamp(min=-0.29, max=0.29)), vol.Optional(ATTR_RC_ROTATION): vol.All(vol.Coerce(int), vol.Clamp(min=-179, max=179)), vol.Optional(ATTR_RC_DURATION): cv.positive_int, }, MiroboVacuum.async_remote_control_move.__name__, ) platform.async_register_entity_service( SERVICE_MOVE_REMOTE_CONTROL_STEP, { vol.Optional(ATTR_RC_VELOCITY): vol.All(vol.Coerce(float), vol.Clamp(min=-0.29, max=0.29)), vol.Optional(ATTR_RC_ROTATION): vol.All(vol.Coerce(int), vol.Clamp(min=-179, max=179)), vol.Optional(ATTR_RC_DURATION): cv.positive_int, }, MiroboVacuum.async_remote_control_move_step.__name__, ) platform.async_register_entity_service( SERVICE_CLEAN_ZONE, { vol.Required(ATTR_ZONE_ARRAY): vol.All( list, [ vol.ExactSequence([ vol.Coerce(int), vol.Coerce(int), vol.Coerce(int), vol.Coerce(int), ]) ], ), vol.Required(ATTR_ZONE_REPEATER): vol.All(vol.Coerce(int), vol.Clamp(min=1, max=3)), }, MiroboVacuum.async_clean_zone.__name__, ) platform.async_register_entity_service( SERVICE_GOTO, { vol.Required("x_coord"): vol.Coerce(int), vol.Required("y_coord"): vol.Coerce(int), }, MiroboVacuum.async_goto.__name__, ) platform.async_register_entity_service( SERVICE_CLEAN_SEGMENT, { vol.Required("segments"): vol.Any(vol.Coerce(int), [vol.Coerce(int)]) }, MiroboVacuum.async_clean_segment.__name__, )
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Optional(CONF_CLASSIFIER): { cv.string: vol.Any( cv.isfile, vol.Schema({ vol.Required(CONF_FILE): cv.isfile, vol.Optional(CONF_SCALE, DEFAULT_SCALE): float, vol.Optional(CONF_NEIGHBORS, DEFAULT_NEIGHBORS): cv.positive_int, vol.Optional(CONF_MIN_SIZE, DEFAULT_MIN_SIZE): vol.Schema( vol.All(vol.Coerce(tuple), vol.ExactSequence([int, int]))), }), ) } }) def _create_processor_from_config(hass, camera_entity, config): """Create an OpenCV processor from configuration.""" classifier_config = config.get(CONF_CLASSIFIER) name = f"{config[CONF_NAME]} {split_entity_id(camera_entity)[1].replace('_', ' ')}" processor = OpenCVImageProcessor(hass, camera_entity, name, classifier_config) return processor
vol.Optional(ATTR_CONTINUOUS_DURATION, default=0): cv.small_float, vol.Optional(ATTR_DISTANCE, default=0.1): cv.small_float, vol.Optional(ATTR_SPEED, default=0.5): cv.small_float, }) SERVICE_PTZ_ADVANCED_MOVE_SCHEMA = vol.Schema({ ATTR_ENTITY_ID: cv.entity_ids, ATTR_MOVE_MODE: vol.In([CONTINUOUS_MOVE, RELATIVE_MOVE, ABSOLUTE_MOVE, STOP_MOVE]), vol.Required(ATTR_PTZ_VECTOR): vol.All( vol.ExactSequence((cv.small_float, cv.small_float, cv.small_float)), vol.Coerce(tuple)), vol.Optional(ATTR_SPEED_VECTOR, default=(1.0, 1.0, 1.0)): vol.All( vol.ExactSequence((cv.small_float, cv.small_float, cv.small_float)), vol.Coerce(tuple)), vol.Optional(ATTR_CONTINUOUS_DURATION, default=0): cv.small_float }) SERVICE_PTZ_PRESET_SCHEMA = vol.Schema({ ATTR_ENTITY_ID: cv.entity_ids, ATTR_PRESET_OPERATION: vol.In([GET_PRESETS, SET_PRESET, GOTO_PRESET, GOTO_HOME, SET_HOME]), vol.Optional(ATTR_PRESET_NAME, default=""):
async def async_setup_entry( hass: HomeAssistant, config_entry: ConfigEntry, async_add_entities: AddEntitiesCallback, ) -> None: """Set up the Xiaomi vacuum cleaner robot from a config entry.""" entities = [] if config_entry.data[CONF_FLOW_TYPE] == CONF_DEVICE: unique_id = config_entry.unique_id mirobo = MiroboVacuum( hass.data[DOMAIN][config_entry.entry_id][KEY_DEVICE], config_entry, unique_id, hass.data[DOMAIN][config_entry.entry_id][KEY_COORDINATOR], ) entities.append(mirobo) platform = entity_platform.async_get_current_platform() platform.async_register_entity_service( SERVICE_START_REMOTE_CONTROL, {}, MiroboVacuum.async_remote_control_start.__name__, ) platform.async_register_entity_service( SERVICE_STOP_REMOTE_CONTROL, {}, MiroboVacuum.async_remote_control_stop.__name__, ) platform.async_register_entity_service( SERVICE_MOVE_REMOTE_CONTROL, { vol.Optional(ATTR_RC_VELOCITY): vol.All(vol.Coerce(float), vol.Clamp(min=-0.29, max=0.29)), vol.Optional(ATTR_RC_ROTATION): vol.All(vol.Coerce(int), vol.Clamp(min=-179, max=179)), vol.Optional(ATTR_RC_DURATION): cv.positive_int, }, MiroboVacuum.async_remote_control_move.__name__, ) platform.async_register_entity_service( SERVICE_MOVE_REMOTE_CONTROL_STEP, { vol.Optional(ATTR_RC_VELOCITY): vol.All(vol.Coerce(float), vol.Clamp(min=-0.29, max=0.29)), vol.Optional(ATTR_RC_ROTATION): vol.All(vol.Coerce(int), vol.Clamp(min=-179, max=179)), vol.Optional(ATTR_RC_DURATION): cv.positive_int, }, MiroboVacuum.async_remote_control_move_step.__name__, ) platform.async_register_entity_service( SERVICE_CLEAN_ZONE, { vol.Required(ATTR_ZONE_ARRAY): vol.All( list, [ vol.ExactSequence([ vol.Coerce(int), vol.Coerce(int), vol.Coerce(int), vol.Coerce(int), ]) ], ), vol.Required(ATTR_ZONE_REPEATER): vol.All(vol.Coerce(int), vol.Clamp(min=1, max=3)), }, MiroboVacuum.async_clean_zone.__name__, ) platform.async_register_entity_service( SERVICE_GOTO, { vol.Required("x_coord"): vol.Coerce(int), vol.Required("y_coord"): vol.Coerce(int), }, MiroboVacuum.async_goto.__name__, ) platform.async_register_entity_service( SERVICE_CLEAN_SEGMENT, { vol.Required("segments"): vol.Any(vol.Coerce(int), [vol.Coerce(int)]) }, MiroboVacuum.async_clean_segment.__name__, ) async_add_entities(entities, update_before_add=True)
CONF_RIGHT: 0, CONF_TOP: 0, CONF_BOTTOM: 0 } DEFAULT_SIZES = { CONF_SIZE_VACUUM_RADIUS: 4, CONF_SIZE_IGNORED_OBSTACLE_RADIUS: 3, CONF_SIZE_IGNORED_OBSTACLE_WITH_PHOTO_RADIUS: 3, CONF_SIZE_OBSTACLE_RADIUS: 3, CONF_SIZE_OBSTACLE_WITH_PHOTO_RADIUS: 3, CONF_SIZE_CHARGER_RADIUS: 4 } COLOR_SCHEMA = vol.Or( vol.All(vol.Length(min=3, max=3), vol.ExactSequence((cv.byte, cv.byte, cv.byte)), vol.Coerce(tuple)), vol.All(vol.Length(min=4, max=4), vol.ExactSequence((cv.byte, cv.byte, cv.byte, cv.byte)), vol.Coerce(tuple)) ) PERCENT_SCHEMA = vol.All(vol.Coerce(float), vol.Range(min=0, max=100)) PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( { vol.Required(CONF_HOST): cv.string, vol.Required(CONF_TOKEN): vol.All(str, vol.Length(min=32, max=32)), vol.Required(CONF_USERNAME): cv.string, vol.Required(CONF_PASSWORD): cv.string, vol.Optional(CONF_COUNTRY, default=None): vol.Or(vol.In(CONF_AVAILABLE_COUNTRIES), vol.Equal(None)), vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, vol.Optional(CONF_AUTO_UPDATE, default=True): cv.boolean, vol.Optional(CONF_COLORS, default={}): vol.Schema({
class Profiles: """Representation of available color profiles.""" SCHEMA = vol.Schema( vol.Any( vol.ExactSequence((str, cv.small_float, cv.small_float, cv.byte)), vol.ExactSequence((str, cv.small_float, cv.small_float, cv.byte, cv.positive_int)), )) def __init__(self, hass): """Initialize profiles.""" self.hass = hass self.data = None def _load_profile_data(self): """Load built-in profiles and custom profiles.""" profile_paths = [ os.path.join(os.path.dirname(__file__), LIGHT_PROFILES_FILE), self.hass.config.path(LIGHT_PROFILES_FILE), ] profiles = {} for profile_path in profile_paths: if not os.path.isfile(profile_path): continue with open(profile_path) as inp: reader = csv.reader(inp) # Skip the header next(reader, None) try: for rec in reader: ( profile, color_x, color_y, brightness, *transition, ) = Profiles.SCHEMA(rec) transition = transition[0] if transition else 0 profiles[profile] = color_util.color_xy_to_hs( color_x, color_y) + ( brightness, transition, ) except vol.MultipleInvalid as ex: _LOGGER.error("Error parsing light profile from %s: %s", profile_path, ex) continue return profiles async def async_initialize(self): """Load and cache profiles.""" self.data = await self.hass.async_add_executor_job( self._load_profile_data) @callback def apply_default(self, entity_id, params): """Return the default turn-on profile for the given light.""" name = f"{entity_id}.default" if name in self.data: self.apply_profile(name, params) return name = "group.all_lights.default" if name in self.data: self.apply_profile(name, params) @callback def apply_profile(self, name, params): """Apply a profile.""" profile = self.data.get(name) if profile is None: return params.setdefault(ATTR_HS_COLOR, profile[:2]) params.setdefault(ATTR_BRIGHTNESS, profile[2]) params.setdefault(ATTR_TRANSITION, profile[3])
'effect_list': ATTR_EFFECT_LIST, 'effect': ATTR_EFFECT, 'supported_features': ATTR_SUPPORTED_FEATURES, } # Service call validation schemas VALID_TRANSITION = vol.All(vol.Coerce(int), vol.Clamp(min=0, max=900)) VALID_BRIGHTNESS = vol.All(vol.Coerce(int), vol.Clamp(min=0, max=255)) LIGHT_TURN_ON_SCHEMA = vol.Schema({ ATTR_ENTITY_ID: cv.entity_ids, ATTR_PROFILE: cv.string, ATTR_TRANSITION: VALID_TRANSITION, ATTR_BRIGHTNESS: VALID_BRIGHTNESS, ATTR_COLOR_NAME: cv.string, ATTR_RGB_COLOR: vol.All(vol.ExactSequence((cv.byte, cv.byte, cv.byte)), vol.Coerce(tuple)), ATTR_XY_COLOR: vol.All(vol.ExactSequence((cv.small_float, cv.small_float)), vol.Coerce(tuple)), ATTR_COLOR_TEMP: vol.All(vol.Coerce(int), vol.Range(min=color_util.HASS_COLOR_MIN, max=color_util.HASS_COLOR_MAX)), ATTR_WHITE_VALUE: vol.All(vol.Coerce(int), vol.Range(min=0, max=255)), ATTR_FLASH: vol.In([FLASH_SHORT, FLASH_LONG]), ATTR_EFFECT: cv.string, }) LIGHT_TURN_OFF_SCHEMA = vol.Schema({ ATTR_ENTITY_ID: cv.entity_ids, ATTR_TRANSITION: VALID_TRANSITION, ATTR_FLASH: vol.In([FLASH_SHORT, FLASH_LONG]),
from . import legacy_device_id from .const import DOMAIN from .coordinator import TPLinkDataUpdateCoordinator from .entity import CoordinatedTPLinkEntity, async_refresh_after _LOGGER = logging.getLogger(__name__) SERVICE_RANDOM_EFFECT = "random_effect" SERVICE_SEQUENCE_EFFECT = "sequence_effect" HUE = vol.Range(min=0, max=360) SAT = vol.Range(min=0, max=100) VAL = vol.Range(min=0, max=100) TRANSITION = vol.Range(min=0, max=6000) HSV_SEQUENCE = vol.ExactSequence((HUE, SAT, VAL)) BASE_EFFECT_DICT: Final = { vol.Optional("brightness", default=100): vol.All( vol.Coerce(int), vol.Range(min=0, max=100) ), vol.Optional("duration", default=0): vol.All( vol.Coerce(int), vol.Range(min=0, max=5000) ), vol.Optional("transition", default=0): vol.All(vol.Coerce(int), TRANSITION), vol.Optional("segments", default=[0]): vol.All( cv.ensure_list_csv, vol.Length(min=1, max=80), [vol.All(vol.Coerce(int), vol.Range(min=0, max=80))], ), }
ATTR_STATUS = 'status' ATTR_ZONE_ARRAY = 'zone' ATTR_ZONE_REPEATER = 'repeats' SERVICE_SCHEMA_REMOTE_CONTROL = VACUUM_SERVICE_SCHEMA.extend({ vol.Optional(ATTR_RC_VELOCITY): vol.All(vol.Coerce(float), vol.Clamp(min=-0.29, max=0.29)), vol.Optional(ATTR_RC_ROTATION): vol.All(vol.Coerce(int), vol.Clamp(min=-179, max=179)), vol.Optional(ATTR_RC_DURATION): cv.positive_int, }) SERVICE_SCHEMA_CLEAN_ZONE = VACUUM_SERVICE_SCHEMA.extend({ vol.Required(ATTR_ZONE_ARRAY): vol.All(list, [vol.ExactSequence( [vol.Coerce(int), vol.Coerce(int), vol.Coerce(int), vol.Coerce(int)])]), vol.Required(ATTR_ZONE_REPEATER): vol.All(vol.Coerce(int), vol.Clamp(min=1, max=3)), }) SERVICE_SCHEMA_CLEAN_ZONE = VACUUM_SERVICE_SCHEMA.extend({ vol.Required(ATTR_ZONE_ARRAY): vol.All(list, [vol.ExactSequence( [vol.Coerce(int), vol.Coerce(int), vol.Coerce(int), vol.Coerce(int)])]), vol.Required(ATTR_ZONE_REPEATER): vol.All(vol.Coerce(int), vol.Clamp(min=1, max=3)), }) SERVICE_TO_METHOD = {
OLD_SLUG_VALIDATION = r'^[a-z0-9_]+$' OLD_ENTITY_ID_VALIDATION = r"^(\w+)\.(\w+)$" # Keep track of invalid slugs and entity ids found so we can create a # persistent notification. Rare temporary exception to use a global. INVALID_SLUGS_FOUND = {} INVALID_ENTITY_IDS_FOUND = {} # Home Assistant types byte = vol.All(vol.Coerce(int), vol.Range(min=0, max=255)) small_float = vol.All(vol.Coerce(float), vol.Range(min=0, max=1)) positive_int = vol.All(vol.Coerce(int), vol.Range(min=0)) latitude = vol.All(vol.Coerce(float), vol.Range(min=-90, max=90), msg='invalid latitude') longitude = vol.All(vol.Coerce(float), vol.Range(min=-180, max=180), msg='invalid longitude') gps = vol.ExactSequence([latitude, longitude]) sun_event = vol.All(vol.Lower, vol.Any(SUN_EVENT_SUNSET, SUN_EVENT_SUNRISE)) port = vol.All(vol.Coerce(int), vol.Range(min=1, max=65535)) color_rgb = vol.All(vol.ExactSequence((byte, byte, byte)), vol.Coerce(tuple)) def color_name(given_color_name): """Convert color name to RGB hex value.""" # COLORS map has no spaces in it, so make the color_name have no # spaces in it as well for matching purposes rgb_value = COLORS.get(given_color_name.replace(" ", "").lower()) if not rgb_value: raise vol.Invalid("Unknown color {0}".format(given_color_name)) return rgb_value
ATTR_STATUS = 'status' ATTR_ZONE_ARRAY = 'zone' ATTR_ZONE_REPEATER = 'repeats' ATTR_ZONE_REPEATER_TEMPLATE = 'repeats_template' SERVICE_SCHEMA_REMOTE_CONTROL = VACUUM_SERVICE_SCHEMA.extend({ vol.Optional(ATTR_RC_VELOCITY): vol.All(vol.Coerce(float), vol.Clamp(min=-0.29, max=0.29)), vol.Optional(ATTR_RC_ROTATION): vol.All(vol.Coerce(int), vol.Clamp(min=-179, max=179)), vol.Optional(ATTR_RC_DURATION): cv.positive_int, }) SERVICE_SCHEMA_CLEAN_ZONE = VACUUM_SERVICE_SCHEMA.extend({ vol.Required(ATTR_ZONE_ARRAY): vol.All(list, [vol.ExactSequence([int, int, int, int])]), vol.Exclusive(ATTR_ZONE_REPEATER, 'TimeToRepeats'): vol.All(vol.Coerce(int), vol.Clamp(min=1, max=3)), vol.Exclusive(ATTR_ZONE_REPEATER_TEMPLATE, 'TimeToRepeats'): cv.template, }) SERVICE_TO_METHOD = { SERVICE_START_REMOTE_CONTROL: { 'method': 'async_remote_control_start' }, SERVICE_STOP_REMOTE_CONTROL: { 'method': 'async_remote_control_stop' }, SERVICE_MOVE_REMOTE_CONTROL: { 'method': 'async_remote_control_move',
# persistent notification. Rare temporary exception to use a global. INVALID_SLUGS_FOUND = {} INVALID_ENTITY_IDS_FOUND = {} INVALID_EXTRA_KEYS_FOUND = [] # Home Assistant types byte = vol.All(vol.Coerce(int), vol.Range(min=0, max=255)) small_float = vol.All(vol.Coerce(float), vol.Range(min=0, max=1)) positive_int = vol.All(vol.Coerce(int), vol.Range(min=0)) latitude = vol.All(vol.Coerce(float), vol.Range(min=-90, max=90), msg='invalid latitude') longitude = vol.All(vol.Coerce(float), vol.Range(min=-180, max=180), msg='invalid longitude') gps = vol.ExactSequence([latitude, longitude]) sun_event = vol.All(vol.Lower, vol.Any(SUN_EVENT_SUNSET, SUN_EVENT_SUNRISE)) port = vol.All(vol.Coerce(int), vol.Range(min=1, max=65535)) # typing typevar T = TypeVar('T') # Adapted from: # https://github.com/alecthomas/voluptuous/issues/115#issuecomment-144464666 def has_at_least_one_key(*keys: str) -> Callable: """Validate that at least one key exists.""" def validate(obj: Dict) -> Dict: """Test keys exist in dict.""" if not isinstance(obj, dict): raise vol.Invalid('expected dictionary')
async def async_setup_entry(hass, config_entry, async_add_entities): """Set up the Xiaomi vacuum cleaner robot from a config entry.""" entities = [] if config_entry.data[CONF_FLOW_TYPE] == CONF_DEVICE: host = config_entry.data[CONF_HOST] token = config_entry.data[CONF_TOKEN] name = config_entry.title unique_id = config_entry.unique_id # Create handler _LOGGER.debug("Initializing with host %s (token %s...)", host, token[:5]) vacuum = Vacuum(host, token) mirobo = MiroboVacuum(name, vacuum, config_entry, unique_id) entities.append(mirobo) platform = entity_platform.current_platform.get() platform.async_register_entity_service( SERVICE_START_REMOTE_CONTROL, {}, MiroboVacuum.async_remote_control_start.__name__, ) platform.async_register_entity_service( SERVICE_STOP_REMOTE_CONTROL, {}, MiroboVacuum.async_remote_control_stop.__name__, ) platform.async_register_entity_service( SERVICE_MOVE_REMOTE_CONTROL, { vol.Optional(ATTR_RC_VELOCITY): vol.All(vol.Coerce(float), vol.Clamp(min=-0.29, max=0.29)), vol.Optional(ATTR_RC_ROTATION): vol.All(vol.Coerce(int), vol.Clamp(min=-179, max=179)), vol.Optional(ATTR_RC_DURATION): cv.positive_int, }, MiroboVacuum.async_remote_control_move.__name__, ) platform.async_register_entity_service( SERVICE_MOVE_REMOTE_CONTROL_STEP, { vol.Optional(ATTR_RC_VELOCITY): vol.All(vol.Coerce(float), vol.Clamp(min=-0.29, max=0.29)), vol.Optional(ATTR_RC_ROTATION): vol.All(vol.Coerce(int), vol.Clamp(min=-179, max=179)), vol.Optional(ATTR_RC_DURATION): cv.positive_int, }, MiroboVacuum.async_remote_control_move_step.__name__, ) platform.async_register_entity_service( SERVICE_CLEAN_ZONE, { vol.Required(ATTR_ZONE_ARRAY): vol.All( list, [ vol.ExactSequence([ vol.Coerce(int), vol.Coerce(int), vol.Coerce(int), vol.Coerce(int), ]) ], ), vol.Required(ATTR_ZONE_REPEATER): vol.All(vol.Coerce(int), vol.Clamp(min=1, max=3)), }, MiroboVacuum.async_clean_zone.__name__, ) platform.async_register_entity_service( SERVICE_GOTO, { vol.Required("x_coord"): vol.Coerce(int), vol.Required("y_coord"): vol.Coerce(int), }, MiroboVacuum.async_goto.__name__, ) platform.async_register_entity_service( SERVICE_CLEAN_SEGMENT, { vol.Required("segments"): vol.Any(vol.Coerce(int), [vol.Coerce(int)]) }, MiroboVacuum.async_clean_segment.__name__, ) async_add_entities(entities, update_before_add=True)
# message even if it's an other operation that invalid. raise voluptuous.Invalid("'%s' operation invalid" % v[0]) return [u"metric"] + voluptuous.Schema(voluptuous.Any( voluptuous.ExactSequence([six.text_type, six.text_type]), voluptuous.All( voluptuous.Length(min=1), [voluptuous.ExactSequence([six.text_type, six.text_type])], )), required=True)(v[1:]) OperationsSchemaBase = [ MetricSchema, voluptuous.ExactSequence([ voluptuous.Any(*list(agg_operations.binary_operators.keys())), _OperationsSubNodeSchema, _OperationsSubNodeSchema ]), voluptuous.ExactSequence([ voluptuous.Any(*list(agg_operations.unary_operators.keys())), _OperationsSubNodeSchema ]), voluptuous.ExactSequence([ u"aggregate", voluptuous.Any(*list(agg_operations.AGG_MAP.keys())), _OperationsSubNodeSchema ]), voluptuous.ExactSequence([ u"resample", voluptuous.Any(*list(agg_operations.AGG_MAP.keys())), utils.to_timespan, _OperationsSubNodeSchema ]),
SERVICE_SCHEMA_REMOTE_CONTROL = VACUUM_SERVICE_SCHEMA.extend({ vol.Optional(ATTR_RC_VELOCITY): vol.All(vol.Coerce(float), vol.Clamp(min=-0.29, max=0.29)), vol.Optional(ATTR_RC_ROTATION): vol.All(vol.Coerce(int), vol.Clamp(min=-179, max=179)), vol.Optional(ATTR_RC_DURATION): cv.positive_int, }) SERVICE_SCHEMA_CLEAN_ZONE = VACUUM_SERVICE_SCHEMA.extend({ vol.Required(ATTR_ZONE_ARRAY): vol.All(list, [ vol.ExactSequence([ vol.Coerce(int), vol.Coerce(int), vol.Coerce(int), vol.Coerce(int) ]) ]), vol.Required(ATTR_ZONE_REPEATER): vol.All(vol.Coerce(int), vol.Clamp(min=1, max=3)), }) SERVICE_SCHEMA_CLEAN_ZONE = VACUUM_SERVICE_SCHEMA.extend({ vol.Required(ATTR_ZONE_ARRAY): vol.All(list, [ vol.ExactSequence([ vol.Coerce(int), vol.Coerce(int), vol.Coerce(int), vol.Coerce(int)
LIGHT_TURN_ON_SCHEMA = vol.Schema({ ATTR_ENTITY_ID: cv.entity_ids, ATTR_PROFILE: cv.string, ATTR_TRANSITION: VALID_TRANSITION, ATTR_BRIGHTNESS: VALID_BRIGHTNESS, ATTR_BRIGHTNESS_PCT: VALID_BRIGHTNESS_PCT, ATTR_COLOR_NAME: cv.string, ATTR_RGB_COLOR: vol.All(vol.ExactSequence((cv.byte, cv.byte, cv.byte)), vol.Coerce(tuple)), ATTR_XY_COLOR: vol.All(vol.ExactSequence((cv.small_float, cv.small_float)), vol.Coerce(tuple)), ATTR_COLOR_TEMP: vol.All(vol.Coerce(int), vol.Range(min=1)), ATTR_KELVIN: vol.All(vol.Coerce(int), vol.Range(min=0)), ATTR_WHITE_VALUE: vol.All(vol.Coerce(int), vol.Range(min=0, max=255)), ATTR_FLASH: vol.In([FLASH_SHORT, FLASH_LONG]), ATTR_EFFECT: cv.string, })
vol.Exclusive(ATTR_PROFILE, COLOR_GROUP): cv.string, ATTR_TRANSITION: VALID_TRANSITION, vol.Exclusive(ATTR_BRIGHTNESS, ATTR_BRIGHTNESS): VALID_BRIGHTNESS, vol.Exclusive(ATTR_BRIGHTNESS_PCT, ATTR_BRIGHTNESS): VALID_BRIGHTNESS_PCT, vol.Exclusive(ATTR_BRIGHTNESS_STEP, ATTR_BRIGHTNESS): VALID_BRIGHTNESS_STEP, vol.Exclusive(ATTR_BRIGHTNESS_STEP_PCT, ATTR_BRIGHTNESS): VALID_BRIGHTNESS_STEP_PCT, vol.Exclusive(ATTR_COLOR_NAME, COLOR_GROUP): cv.string, vol.Exclusive(ATTR_RGB_COLOR, COLOR_GROUP): vol.All(vol.ExactSequence((cv.byte, cv.byte, cv.byte)), vol.Coerce(tuple)), vol.Exclusive(ATTR_XY_COLOR, COLOR_GROUP): vol.All(vol.ExactSequence((cv.small_float, cv.small_float)), vol.Coerce(tuple)), vol.Exclusive(ATTR_HS_COLOR, COLOR_GROUP): vol.All( vol.ExactSequence(( vol.All(vol.Coerce(float), vol.Range(min=0, max=360)), vol.All(vol.Coerce(float), vol.Range(min=0, max=100)), )), vol.Coerce(tuple), ), vol.Exclusive(ATTR_COLOR_TEMP, COLOR_GROUP): vol.All(vol.Coerce(int), vol.Range(min=1)), vol.Exclusive(ATTR_KELVIN, COLOR_GROUP): vol.All(vol.Coerce(int), vol.Range(min=0)),