├── .gitattributes ├── README.md ├── custom_components └── smartthings_customize │ ├── __init__.py │ ├── binary_sensor.py │ ├── button.py │ ├── climate.py │ ├── climate_custom.py │ ├── common.py │ ├── config_flow.py │ ├── const.py │ ├── cover.py │ ├── custom_api.py │ ├── device.py │ ├── example │ ├── climate │ │ ├── bridge_hub_climate(zzuggu).yaml │ │ └── navien_heat_mat.yaml │ ├── example_1.yaml │ ├── refrigerator │ │ ├── ARTIK051_DONGLE_REF.yaml │ │ └── kimchi_refrigerator.yaml │ └── vacuum │ │ └── zetbot_ai.yaml │ ├── fan.py │ ├── fan_custom.py │ ├── httpsig │ ├── __init__.py │ ├── _version.py │ ├── requests_auth.py │ ├── sign.py │ ├── utils.py │ └── verify.py │ ├── light.py │ ├── lock.py │ ├── lock_custom.py │ ├── manifest.json │ ├── number.py │ ├── pysmartapp │ ├── __init__.py │ ├── config.py │ ├── const.py │ ├── dispatch.py │ ├── errors.py │ ├── event.py │ ├── install.py │ ├── oauthcallback.py │ ├── ping.py │ ├── request.py │ ├── smartapp.py │ ├── uninstall.py │ ├── update.py │ └── utilities.py │ ├── pysmartthings │ ├── __init__.py │ ├── api.py │ ├── app.py │ ├── capability.py │ ├── const.py │ ├── device.py │ ├── entity.py │ ├── errors.py │ ├── installedapp.py │ ├── location.py │ ├── oauthtoken.py │ ├── room.py │ ├── scene.py │ ├── smartthings.py │ └── subscription.py │ ├── scene.py │ ├── select.py │ ├── sensor.py │ ├── smartapp.py │ ├── strings.json │ ├── switch.py │ ├── text.py │ ├── translations │ ├── bg.json │ ├── ca.json │ ├── cs.json │ ├── da.json │ ├── de.json │ ├── en.json │ ├── en_GB.json │ ├── es-419.json │ ├── es.json │ ├── et.json │ ├── fr.json │ ├── he.json │ ├── hu.json │ ├── id.json │ ├── it.json │ ├── ko.json │ ├── lb.json │ ├── nl.json │ ├── no.json │ ├── pl.json │ ├── pt-BR.json │ ├── pt.json │ ├── ru.json │ ├── sl.json │ ├── sv.json │ ├── tr.json │ ├── uk.json │ ├── zh-Hans.json │ └── zh-Hant.json │ ├── update.py │ ├── vacuum.py │ └── valve.py └── hacs.json /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /custom_components/smartthings_customize/binary_sensor.py: -------------------------------------------------------------------------------- 1 | """Support for binary sensors through the SmartThings cloud API.""" 2 | from __future__ import annotations 3 | 4 | from collections.abc import Sequence 5 | 6 | from .pysmartthings import Attribute, Capability 7 | from .pysmartthings.device import DeviceEntity 8 | from .pysmartthings.capability import ATTRIBUTE_ON_VALUES 9 | 10 | from homeassistant.components.binary_sensor import ( 11 | BinarySensorDeviceClass, 12 | BinarySensorEntity, 13 | ) 14 | from homeassistant.config_entries import ConfigEntry 15 | from homeassistant.const import EntityCategory 16 | from homeassistant.core import HomeAssistant 17 | from homeassistant.helpers.entity_platform import AddEntitiesCallback 18 | 19 | from .common import * 20 | import logging 21 | 22 | CAPABILITY_TO_ATTRIB = { 23 | Capability.acceleration_sensor: Attribute.acceleration, 24 | Capability.contact_sensor: Attribute.contact, 25 | Capability.filter_status: Attribute.filter_status, 26 | Capability.motion_sensor: Attribute.motion, 27 | Capability.presence_sensor: Attribute.presence, 28 | Capability.sound_sensor: Attribute.sound, 29 | Capability.tamper_alert: Attribute.tamper, 30 | Capability.valve: Attribute.valve, 31 | Capability.water_sensor: Attribute.water, 32 | } 33 | ATTRIB_TO_CLASS = { 34 | Attribute.acceleration: BinarySensorDeviceClass.MOVING, 35 | Attribute.contact: BinarySensorDeviceClass.OPENING, 36 | Attribute.filter_status: BinarySensorDeviceClass.PROBLEM, 37 | Attribute.motion: BinarySensorDeviceClass.MOTION, 38 | Attribute.presence: BinarySensorDeviceClass.PRESENCE, 39 | Attribute.sound: BinarySensorDeviceClass.SOUND, 40 | Attribute.tamper: BinarySensorDeviceClass.PROBLEM, 41 | Attribute.valve: BinarySensorDeviceClass.OPENING, 42 | Attribute.water: BinarySensorDeviceClass.MOISTURE, 43 | } 44 | ATTRIB_TO_ENTTIY_CATEGORY = { 45 | Attribute.tamper: EntityCategory.DIAGNOSTIC, 46 | } 47 | 48 | _LOGGER = logging.getLogger(__name__) 49 | 50 | async def async_setup_entry( 51 | hass: HomeAssistant, 52 | config_entry: ConfigEntry, 53 | async_add_entities: AddEntitiesCallback, 54 | ) -> None: 55 | """Add binary sensors for a config entry.""" 56 | broker = hass.data[DOMAIN][DATA_BROKERS][config_entry.entry_id] 57 | sensors = [] 58 | if SettingManager.enable_default_entities(): 59 | for device in broker.devices.values(): 60 | if SettingManager.allow_device(device.device_id) == False: 61 | continue 62 | for capability in broker.get_assigned(device.device_id, Platform.BINARY_SENSOR): 63 | attrib = CAPABILITY_TO_ATTRIB[capability] 64 | sensors.append(SmartThingsBinarySensor(device, attrib)) 65 | 66 | settings = SettingManager.get_capa_settings(broker, Platform.BINARY_SENSOR) 67 | for s in settings: 68 | _LOGGER.debug("cap setting : " + str(s[1])) 69 | sensors.append(SmartThingsBinarySensor_custom(hass=hass, setting=s)) 70 | 71 | async_add_entities(sensors) 72 | 73 | 74 | def get_capabilities(capabilities: Sequence[str]) -> Sequence[str] | None: 75 | """Return all capabilities supported if minimum required are present.""" 76 | return [ 77 | capability for capability in CAPABILITY_TO_ATTRIB if capability in capabilities 78 | ] 79 | 80 | 81 | class SmartThingsBinarySensor(SmartThingsEntity, BinarySensorEntity): 82 | """Define a SmartThings Binary Sensor.""" 83 | 84 | def __init__(self, device, attribute): 85 | """Init the class.""" 86 | super().__init__(device) 87 | self._attribute = attribute 88 | 89 | @property 90 | def name(self) -> str: 91 | """Return the name of the binary sensor.""" 92 | return f"{self._device.label} {self._attribute}" 93 | 94 | @property 95 | def unique_id(self) -> str: 96 | """Return a unique ID.""" 97 | return f"{self._device.device_id}.{self._attribute}" 98 | 99 | @property 100 | def is_on(self): 101 | """Return true if the binary sensor is on.""" 102 | return self._device.status.is_on(self._attribute) 103 | 104 | @property 105 | def device_class(self): 106 | """Return the class of this device.""" 107 | return ATTRIB_TO_CLASS[self._attribute] 108 | 109 | @property 110 | def entity_category(self): 111 | """Return the entity category of this device.""" 112 | return ATTRIB_TO_ENTTIY_CATEGORY.get(self._attribute) 113 | 114 | 115 | class SmartThingsBinarySensor_custom(SmartThingsEntity_custom, BinarySensorEntity): 116 | def __init__(self, hass, setting) -> None: 117 | super().__init__(hass, platform=Platform.BINARY_SENSOR, setting=setting) 118 | 119 | @property 120 | def device_class(self): 121 | """Return the device class of the sensor.""" 122 | return self.get_attr_value(Platform.BINARY_SENSOR, CONF_DEVICE_CLASS) 123 | @property 124 | def entity_category(self): 125 | return self.get_attr_value(Platform.BINARY_SENSOR, CONF_ENTITY_CATEGORY) 126 | 127 | @property 128 | def is_on(self): 129 | value = self.get_attr_value(Platform.BINARY_SENSOR, CONF_STATE) 130 | on_state = self.get_attr_value(Platform.BINARY_SENSOR, ON_STATE) 131 | return value in on_state 132 | 133 | #if self._attribute not in ATTRIBUTE_ON_VALUES: 134 | # return bool(value) 135 | #return value == ATTRIBUTE_ON_VALUES[self.get_attribute(Platform.BINARY_SENSOR)] 136 | -------------------------------------------------------------------------------- /custom_components/smartthings_customize/button.py: -------------------------------------------------------------------------------- 1 | """Support for switches through the SmartThings cloud API.""" 2 | from __future__ import annotations 3 | 4 | from collections.abc import Sequence 5 | from typing import Any 6 | 7 | from .pysmartthings import Capability 8 | 9 | from homeassistant.components.button import ButtonEntity 10 | from homeassistant.config_entries import ConfigEntry 11 | from homeassistant.core import HomeAssistant 12 | from homeassistant.helpers.entity_platform import AddEntitiesCallback 13 | 14 | from .common import * 15 | 16 | import logging 17 | _LOGGER = logging.getLogger(__name__) 18 | 19 | async def async_setup_entry( 20 | hass: HomeAssistant, 21 | config_entry: ConfigEntry, 22 | async_add_entities: AddEntitiesCallback, 23 | ) -> None: 24 | """Add switches for a config entry.""" 25 | broker = hass.data[DOMAIN][DATA_BROKERS][config_entry.entry_id] 26 | entities = [] 27 | settings = SettingManager.get_capa_settings(broker, Platform.BUTTON) 28 | for s in settings: 29 | _LOGGER.debug("cap setting : " + str(s[1])) 30 | entities.append(SmartThingsButton_custom(hass=hass, setting=s)) 31 | 32 | async_add_entities(entities) 33 | 34 | class SmartThingsButton_custom(SmartThingsEntity_custom, ButtonEntity): 35 | def __init__(self, hass, setting) -> None: 36 | super().__init__(hass, platform=Platform.BUTTON, setting=setting) 37 | 38 | async def async_press(self) -> None: 39 | await self.send_command(Platform.BUTTON, self.get_command(Platform.BUTTON), self.get_argument(Platform.BUTTON)) 40 | 41 | 42 | -------------------------------------------------------------------------------- /custom_components/smartthings_customize/const.py: -------------------------------------------------------------------------------- 1 | """Constants used by the SmartThings component and platforms.""" 2 | from datetime import timedelta 3 | import re 4 | 5 | from homeassistant.const import Platform 6 | 7 | DOMAIN = "smartthings_customize" 8 | 9 | APP_OAUTH_CLIENT_NAME = "HA Customize" 10 | APP_OAUTH_SCOPES = ["r:devices:*", "r:devices:*","w:devices:*","x:devices:*","l:devices","r:locations:*", "w:locations:*", "x:locations:*","r:scenes:*", "x:scenes:*", "r:installedapps:*", "l:installedapps"] 11 | APP_NAME_PREFIX = "homeassistant." 12 | 13 | CONF_APP_ID = "app_id" 14 | CONF_CLOUDHOOK_URL = "cloudhook_url" 15 | CONF_INSTALLED_APP_ID = "installed_app_id" 16 | CONF_INSTANCE_ID = "instance_id" 17 | CONF_LOCATION_ID = "location_id" 18 | CONF_REFRESH_TOKEN = "refresh_token" 19 | 20 | CONF_RESETTING_ENTITIES = "resetting_entities" 21 | #CONF_ENABLE_DEFAULT_ENTITIES = "enable_default_entities" 22 | CONF_ENABLE_SYNTAX_PROPERTY = "enable_syntax_property" 23 | 24 | DATA_MANAGER = "manager" 25 | DATA_BROKERS = "brokers" 26 | EVENT_BUTTON = "smartthings_customize.button" 27 | 28 | SIGNAL_SMARTTHINGS_UPDATE = "smartthings_customize_update" 29 | SIGNAL_SMARTAPP_PREFIX = "smartthings_customize_smartap_" 30 | 31 | SETTINGS_INSTANCE_ID = "hassInstanceId" 32 | 33 | SUBSCRIPTION_WARNING_LIMIT = 40 34 | 35 | STORAGE_KEY = DOMAIN 36 | STORAGE_VERSION = 1 37 | 38 | GLOBAL_SETTING = "globals" 39 | DEVICE_SETTING = "devices" 40 | 41 | CONF_NAME = "name" 42 | CONF_COMPONENT = "component" 43 | CONF_CAPABILITY = "capability" 44 | CONF_ATTRIBUTE = "attribute" 45 | CONF_COMMAND = "command" 46 | CONF_ARGUMENT = "argument" 47 | CONF_PARENT_ENTITY_ID = "parent_entity_id" 48 | CONF_ENTITY_ID_FORMAT = "entity_id_format" 49 | CONF_OPTIONS = "options" 50 | 51 | CONF_DEFAULT_UNIT = "default_unit" 52 | CONF_DEVICE_CLASS = "device_class" 53 | CONF_STATE_CLASS = "state_class" 54 | CONF_ENTITY_CATEGORY = "entity_category" 55 | 56 | CONF_STATE = "state" 57 | 58 | CONF_LAST_TIMESTAMP = "last_timestamp" 59 | 60 | CAPA_SWITCH = "switch" 61 | 62 | ATTR_SYNTAX = "syntax" 63 | 64 | ON_STATE = "on_state" 65 | 66 | CONF_STATE_MAPPING = "s2h_state_mapping" 67 | CONF_MODE_MAPPING = "s2h_mode_mapping" 68 | CONF_ACTION_MAPPING = "s2h_action_mapping" 69 | 70 | CONF_SPEED_LIST = "speed_list" 71 | 72 | DEFAULT = "default" 73 | 74 | # Ordered 'specific to least-specific platform' in order for capabilities 75 | # to be drawn-down and represented by the most appropriate platform. 76 | PLATFORMS = [ 77 | Platform.CLIMATE, 78 | Platform.FAN, 79 | Platform.LIGHT, 80 | Platform.LOCK, 81 | Platform.COVER, 82 | Platform.SWITCH, 83 | Platform.BINARY_SENSOR, 84 | Platform.SENSOR, 85 | Platform.SCENE, 86 | Platform.NUMBER, 87 | Platform.SELECT, 88 | Platform.BUTTON, 89 | Platform.TEXT, 90 | Platform.VACUUM, 91 | Platform.VALVE, 92 | Platform.UPDATE, 93 | ] 94 | 95 | IGNORED_CAPABILITIES = [ 96 | "execute", 97 | "healthCheck", 98 | "ocf", 99 | ] 100 | 101 | TOKEN_REFRESH_INTERVAL = timedelta(hours=12) 102 | 103 | VAL_UID = "^(?:([0-9a-fA-F]{32})|([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}))$" 104 | VAL_UID_MATCHER = re.compile(VAL_UID) 105 | 106 | DEFAULT_UNIQUE_ID_FORMAT = "st_custom_%{device_id}_%{device_type}_%{label}_%{component}_%{capability}_%{attribute}_%{command}_%{name}" 107 | DEFAULT_ENTITY_ID_FORMAT = "st_custom_%{device_id}_%{device_type}_%{label}_%{component}_%{capability}_%{attribute}_%{command}_%{name}" 108 | -------------------------------------------------------------------------------- /custom_components/smartthings_customize/cover.py: -------------------------------------------------------------------------------- 1 | """Support for covers through the SmartThings cloud API.""" 2 | from __future__ import annotations 3 | 4 | from collections.abc import Sequence 5 | from typing import Any 6 | 7 | from .pysmartthings import Attribute, Capability 8 | 9 | from homeassistant.components.cover import ( 10 | ATTR_POSITION, 11 | DOMAIN as COVER_DOMAIN, 12 | STATE_CLOSED, 13 | STATE_CLOSING, 14 | STATE_OPEN, 15 | STATE_OPENING, 16 | CoverDeviceClass, 17 | CoverEntity, 18 | CoverEntityFeature, 19 | ) 20 | from homeassistant.config_entries import ConfigEntry 21 | from homeassistant.const import ATTR_BATTERY_LEVEL 22 | from homeassistant.core import HomeAssistant 23 | from homeassistant.helpers.entity_platform import AddEntitiesCallback 24 | 25 | from .common import * 26 | 27 | VALUE_TO_STATE = { 28 | "closed": STATE_CLOSED, 29 | "closing": STATE_CLOSING, 30 | "open": STATE_OPEN, 31 | "opening": STATE_OPENING, 32 | "partially open": STATE_OPEN, 33 | "unknown": None, 34 | } 35 | 36 | 37 | async def async_setup_entry( 38 | hass: HomeAssistant, 39 | config_entry: ConfigEntry, 40 | async_add_entities: AddEntitiesCallback, 41 | ) -> None: 42 | """Add covers for a config entry.""" 43 | broker = hass.data[DOMAIN][DATA_BROKERS][config_entry.entry_id] 44 | if SettingManager.enable_default_entities(): 45 | async_add_entities( 46 | [ 47 | SmartThingsCover(device) 48 | for device in broker.devices.values() 49 | if broker.any_assigned(device.device_id, COVER_DOMAIN) and SettingManager.allow_device(device.device_id) 50 | ], 51 | True, 52 | ) 53 | 54 | 55 | def get_capabilities(capabilities: Sequence[str]) -> Sequence[str] | None: 56 | """Return all capabilities supported if minimum required are present.""" 57 | min_required = [ 58 | Capability.door_control, 59 | Capability.garage_door_control, 60 | Capability.window_shade, 61 | ] 62 | # Must have one of the min_required 63 | if any(capability in capabilities for capability in min_required): 64 | # Return all capabilities supported/consumed 65 | return min_required + [ 66 | Capability.battery, 67 | Capability.switch_level, 68 | Capability.window_shade_level, 69 | ] 70 | 71 | return None 72 | 73 | 74 | class SmartThingsCover(SmartThingsEntity, CoverEntity): 75 | """Define a SmartThings cover.""" 76 | 77 | def __init__(self, device): 78 | """Initialize the cover class.""" 79 | super().__init__(device) 80 | self._device_class = None 81 | self._current_cover_position = None 82 | self._state = None 83 | self._state_attrs = None 84 | self._attr_supported_features = ( 85 | CoverEntityFeature.OPEN | CoverEntityFeature.CLOSE 86 | ) 87 | if ( 88 | Capability.switch_level in device.capabilities 89 | or Capability.window_shade_level in device.capabilities 90 | ): 91 | self._attr_supported_features |= CoverEntityFeature.SET_POSITION 92 | 93 | async def async_close_cover(self, **kwargs: Any) -> None: 94 | """Close cover.""" 95 | # Same command for all 3 supported capabilities 96 | await self._device.close(set_status=True) 97 | # State is set optimistically in the commands above, therefore update 98 | # the entity state ahead of receiving the confirming push updates 99 | self.async_schedule_update_ha_state(True) 100 | 101 | async def async_open_cover(self, **kwargs: Any) -> None: 102 | """Open the cover.""" 103 | # Same for all capability types 104 | await self._device.open(set_status=True) 105 | # State is set optimistically in the commands above, therefore update 106 | # the entity state ahead of receiving the confirming push updates 107 | self.async_schedule_update_ha_state(True) 108 | 109 | async def async_set_cover_position(self, **kwargs: Any) -> None: 110 | """Move the cover to a specific position.""" 111 | if not self.supported_features & CoverEntityFeature.SET_POSITION: 112 | return 113 | # Do not set_status=True as device will report progress. 114 | if Capability.window_shade_level in self._device.capabilities: 115 | await self._device.set_window_shade_level( 116 | kwargs[ATTR_POSITION], set_status=False 117 | ) 118 | else: 119 | await self._device.set_level(kwargs[ATTR_POSITION], set_status=False) 120 | 121 | async def async_update(self) -> None: 122 | """Update the attrs of the cover.""" 123 | if Capability.door_control in self._device.capabilities: 124 | self._device_class = CoverDeviceClass.DOOR 125 | self._state = VALUE_TO_STATE.get(self._device.status.door) 126 | elif Capability.window_shade in self._device.capabilities: 127 | self._device_class = CoverDeviceClass.SHADE 128 | self._state = VALUE_TO_STATE.get(self._device.status.window_shade) 129 | elif Capability.garage_door_control in self._device.capabilities: 130 | self._device_class = CoverDeviceClass.GARAGE 131 | self._state = VALUE_TO_STATE.get(self._device.status.door) 132 | 133 | if Capability.window_shade_level in self._device.capabilities: 134 | self._current_cover_position = self._device.status.shade_level 135 | elif Capability.switch_level in self._device.capabilities: 136 | self._current_cover_position = self._device.status.level 137 | 138 | self._state_attrs = {} 139 | battery = self._device.status.attributes[Attribute.battery].value 140 | if battery is not None: 141 | self._state_attrs[ATTR_BATTERY_LEVEL] = battery 142 | 143 | @property 144 | def is_opening(self) -> bool: 145 | """Return if the cover is opening or not.""" 146 | return self._state == STATE_OPENING 147 | 148 | @property 149 | def is_closing(self) -> bool: 150 | """Return if the cover is closing or not.""" 151 | return self._state == STATE_CLOSING 152 | 153 | @property 154 | def is_closed(self) -> bool | None: 155 | """Return if the cover is closed or not.""" 156 | if self._state == STATE_CLOSED: 157 | return True 158 | return None if self._state is None else False 159 | 160 | @property 161 | def current_cover_position(self) -> int | None: 162 | """Return current position of cover.""" 163 | return self._current_cover_position 164 | 165 | @property 166 | def device_class(self) -> CoverDeviceClass | None: 167 | """Define this cover as a garage door.""" 168 | return self._device_class 169 | 170 | @property 171 | def extra_state_attributes(self) -> dict[str, Any]: 172 | """Get additional state attributes.""" 173 | return self._state_attrs -------------------------------------------------------------------------------- /custom_components/smartthings_customize/custom_api.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import aiohttp 3 | import json 4 | from homeassistant.util import ssl 5 | import traceback 6 | 7 | import json 8 | import os 9 | 10 | from .pysmartthings import App 11 | 12 | import traceback 13 | from .const import DOMAIN 14 | 15 | _LOGGER = logging.getLogger(__name__) 16 | 17 | BASE = "custom_components/" + DOMAIN + "/" 18 | 19 | async def async_remove_app_info(app_id): 20 | try: 21 | path = BASE + app_id + ".json" 22 | os.remove(path) 23 | except: 24 | """""" 25 | 26 | async def async_get_app_info(hass, app_id, token): 27 | 28 | path = BASE + app_id + ".json" 29 | 30 | app = App() 31 | try: 32 | # 파일이 있는지 먼저 확인 33 | if os.path.isfile(path): 34 | def load(): 35 | with open(path, "r") as f: 36 | data = json.load(f) 37 | app.apply_data(data) 38 | return app 39 | app = await hass.async_add_executor_job(load) 40 | return app 41 | else: 42 | url = "https://api.smartthings.com/v1/apps/" + app_id 43 | custom_ssl_context = ssl.get_default_context() 44 | custom_ssl_context.options |= 0x00040000 45 | headers={"Authorization": "Bearer " + token} 46 | 47 | connector = aiohttp.TCPConnector(ssl=custom_ssl_context) 48 | async with aiohttp.ClientSession(connector=connector, headers=headers) as session: 49 | async with session.get(url, headers=headers) as response: 50 | if response.status == 200: 51 | raw_data = await response.read() 52 | data = json.loads(raw_data) 53 | app.apply_data(data) 54 | def save(data): 55 | with open(path, "w") as f: 56 | json.dump(obj=data, fp=f, sort_keys=True, indent=4) 57 | await hass.async_add_executor_job(save, data) 58 | return app 59 | 60 | except Exception as e: 61 | _LOGGER.error("get_app_info failed - " + traceback.format_exc()) 62 | 63 | 64 | -------------------------------------------------------------------------------- /custom_components/smartthings_customize/device.py: -------------------------------------------------------------------------------- 1 | from .pysmartthings import SmartThings, DeviceEntity, Device, DeviceStatus, DeviceStatusBase 2 | from typing import Any, Dict, Mapping, Optional, Sequence, Tuple 3 | from .pysmartthings.entity import Entity 4 | from aiohttp import ClientSession 5 | from typing import List, Optional, Sequence 6 | from .pysmartthings.api import Api 7 | from .pysmartthings.device import Status 8 | 9 | import logging 10 | _LOGGER = logging.getLogger(__name__) 11 | 12 | class DeviceStatus_custom(DeviceStatus): 13 | def __init__(self, api: Api, device_id: str, data=None): 14 | """Create a new instance of the DeviceStatusEntity class.""" 15 | super().__init__(api, device_id, data) 16 | self._api = api 17 | self._device_id = device_id 18 | self._components = {} 19 | self._status = {} 20 | if data: 21 | self.apply_data(data) 22 | 23 | def apply_attribute_update( 24 | self, 25 | component_id: str, 26 | capability: str, 27 | attribute: str, 28 | value: Any, 29 | unit: Optional[str] = None, 30 | data: Optional[Dict] = None, 31 | ): 32 | super().apply_attribute_update(component_id=component_id, capability=capability, attribute=attribute, value=value, unit=unit, data=data) 33 | old_status = self._status[component_id][capability][attribute] 34 | if component_id not in self._status: 35 | self._status[component_id] = {} 36 | if capability not in self._status[component_id]: 37 | self._status[component_id][capability] = {} 38 | self._status[component_id][capability][attribute] = Status(value, unit or old_status.unit, data) 39 | 40 | 41 | def apply_data(self, data: dict): 42 | super().apply_data(data=data) 43 | """Apply the values from the given data structure.""" 44 | self._status.clear() 45 | for component_id, component in data["components"].items(): 46 | #_LOGGER.error("component_id : " + str(component_id) + ", component : " + str(component)) 47 | for capa, attributes in component.items(): 48 | for attribute, value in attributes.items(): 49 | if component_id not in self._status: 50 | self._status[component_id] = {} 51 | if capa not in self._status[component_id]: 52 | self._status[component_id][capa] = {} 53 | self._status[component_id][capa][attribute] = Status(value.get("value"), value.get("unit"), value.get("data")) 54 | 55 | 56 | class DeviceEntity_custom(DeviceEntity): 57 | def __init__( 58 | self, api: Api, data: Optional[dict] = None, device_id: Optional[str] = None 59 | ): 60 | """Create a new instance of the DeviceEntity class.""" 61 | Entity.__init__(self, api) 62 | Device.__init__(self) 63 | if data: 64 | self.apply_data(data) 65 | if device_id: 66 | self._device_id = device_id 67 | self._status = DeviceStatus_custom(api, self._device_id) 68 | 69 | 70 | class SmartThings_custom(SmartThings): 71 | 72 | async def devices( 73 | self, 74 | *, 75 | location_ids: Optional[Sequence[str]] = None, 76 | capabilities: Optional[Sequence[str]] = None, 77 | device_ids: Optional[Sequence[str]] = None 78 | ) -> List: 79 | """Retrieve SmartThings devices.""" 80 | params = [] 81 | if location_ids: 82 | params.extend([("locationId", lid) for lid in location_ids]) 83 | if capabilities: 84 | params.extend([("capability", cap) for cap in capabilities]) 85 | if device_ids: 86 | params.extend([("deviceId", did) for did in device_ids]) 87 | resp = await self._service.get_devices(params) 88 | return [DeviceEntity_custom(self._service, entity) for entity in resp] 89 | 90 | async def device(self, device_id: str) -> DeviceEntity_custom: 91 | """Retrieve a device with the specified ID.""" 92 | entity = await self._service.get_device(device_id) 93 | return DeviceEntity_custom(self._service, entity) 94 | -------------------------------------------------------------------------------- /custom_components/smartthings_customize/example/climate/bridge_hub_climate(zzuggu).yaml: -------------------------------------------------------------------------------- 1 | # 브릿지 허브 연동 난방기 2 | devices: 3 | - device_id: d8824158-6cd5-328a-b168-5008e3ccc610 4 | climate: 5 | - name: 서재난방 6 | capability: thermostatMode 7 | component: main 8 | capabilities: 9 | - switch: 10 | capability: switch 11 | component: main 12 | command: 13 | "on": "on" 14 | "off": "off" 15 | state: 16 | attribute: switch 17 | on_state: ["on"] 18 | - mode: 19 | capability: thermostatMode 20 | options: ["heat","off"] 21 | command: setThermostatMode 22 | state: 23 | attribute: thermostatMode 24 | - target_temp: 25 | capability: thermostatHeatingSetpoint 26 | min: 10 27 | max: 40 28 | step: 1 29 | command: setHeatingSetpoint 30 | state: 31 | attribute: heatingSetpoint 32 | - current_temperature: 33 | capability: temperatureMeasurement 34 | state: 35 | attribute: temperature 36 | 37 | sensor: 38 | - name: 현재온도 39 | capability: temperatureMeasurement 40 | component: main 41 | state: 42 | attribute: temperature 43 | 44 | -------------------------------------------------------------------------------- /custom_components/smartthings_customize/example/climate/navien_heat_mat.yaml: -------------------------------------------------------------------------------- 1 | # 나비엔 온수매트 2 | devices: 3 | - device_id: d8824158-6cd5-328a-b168-5008e3ccc610 4 | switch: 5 | - name: "메인 전원" 6 | capability: switch 7 | component: main 8 | state: 9 | attribute: switch 10 | on_state: ["on"] 11 | climate: 12 | - name: 좌측 13 | capability: thermostatHeatingSetpoint 14 | component: Left 15 | capabilities: 16 | - switch: 17 | capability: switch 18 | state: 19 | attribute: switch 20 | on_state: ["on"] 21 | - mode: 22 | capability: switch 23 | state: 24 | attribute: switch 25 | options: ["on", "off"] 26 | s2h_mode_mapping: [{ "on": "heat" }] 27 | s2h_action_mapping: [{ "on": "heating", "off": "off" }] 28 | - target_temp: 29 | capability: thermostatHeatingSetpoint 30 | min: 28 31 | max: 45 32 | step: 0.5 33 | command: setHeatingSetpoint 34 | state: 35 | attribute: heatingSetpoint 36 | - current_temperature: 37 | capability: temperatureMeasurement 38 | state: 39 | attribute: temperature 40 | - name: 우측 41 | capability: thermostatHeatingSetpoint 42 | component: Right 43 | capabilities: 44 | - switch: 45 | capability: switch 46 | state: 47 | attribute: switch 48 | on_state: ["on"] 49 | - mode: 50 | capability: switch 51 | state: 52 | attribute: switch 53 | options: ["on", "off"] 54 | s2h_mode_mapping: [{ "on": "heat" }] 55 | s2h_action_mapping: [{ "on": "heating", "off": "off" }] 56 | - target_temp: 57 | capability: thermostatHeatingSetpoint 58 | min: 28 59 | max: 45 60 | step: 0.5 61 | command: setHeatingSetpoint 62 | state: 63 | attribute: heatingSetpoint 64 | - current_temperature: 65 | capability: temperatureMeasurement 66 | state: 67 | attribute: temperature 68 | 69 | 70 | -------------------------------------------------------------------------------- /custom_components/smartthings_customize/example/refrigerator/ARTIK051_DONGLE_REF.yaml: -------------------------------------------------------------------------------- 1 | # device info 2 | # 스마트홈 동글 연결 냉장고 3 | devices: 4 | - device_id: b065a858-1927-fd98-a374-7fc1498e8c76 5 | parent_entity_id: binary_sensor.naengjanggo_contact 6 | binary_sensor: 7 | - name: cooler contect 8 | capability: contactSensor 9 | component: cooler 10 | state: 11 | attribute: contact 12 | on_state: ["open"] 13 | device_class: door 14 | 15 | - name: freezer contact 16 | capability: contactSensor 17 | component: freezer 18 | state: 19 | attribute: contact 20 | on_state: ["open"] 21 | climate: 22 | - name: freezer temperature 23 | capability: thermostatCoolingSetpoint 24 | component: freezer 25 | capabilities: 26 | - switch: 27 | capability: refrigeration 28 | component: main 29 | command: 30 | "on": "setRapidFreezing" 31 | "off": "setRapidFreezing" 32 | argument: 33 | "on": ["on"] 34 | "off": ["off"] 35 | state: 36 | attribute: rapidFreezing 37 | - mode: 38 | capability: refrigeration 39 | component: main 40 | command: setRapidFreezing 41 | state: 42 | attribute: rapidFreezing 43 | options: ["cool", "off"] 44 | s2h_mode_mapping: [{ "on": "cool" }] 45 | s2h_action_mapping: [{ "on": "cooling", "off": "off" }] 46 | - target_temp: 47 | capability: thermostatCoolingSetpoint 48 | min: 49 | capability: custom.thermostatSetpointControl 50 | attribute: minimumSetpoint 51 | max: 52 | capability: custom.thermostatSetpointControl 53 | attribute: maximumSetpoint 54 | step: 1 55 | command: setCoolingSetpoint 56 | state: 57 | attribute: coolingSetpoint 58 | argument: 59 | type: float 60 | - current_temperature: 61 | capability: temperatureMeasurement 62 | state: 63 | attribute: temperature 64 | 65 | - name: cooler temperature 66 | capability: thermostatCoolingSetpoint 67 | component: cooler 68 | capabilities: 69 | - switch: 70 | capability: refrigeration 71 | component: main 72 | command: 73 | "on": "setRapidCooling" 74 | "off": "setRapidCooling" 75 | argument: 76 | "on": ["on"] 77 | "off": ["off"] 78 | state: 79 | attribute: rapidCooling 80 | - mode: 81 | capability: refrigeration 82 | component: main 83 | command: setRapidCooling 84 | state: 85 | attribute: rapidCooling 86 | options: ["cool", "off"] 87 | s2h_mode_mapping: [{ "on": "cool" }] 88 | s2h_action_mapping: [{ "on": "cooling", "off": "off" }] 89 | 90 | - target_temp: 91 | capability: thermostatCoolingSetpoint 92 | min: 93 | capability: custom.thermostatSetpointControl 94 | attribute: minimumSetpoint 95 | max: 96 | capability: custom.thermostatSetpointControl 97 | attribute: maximumSetpoint 98 | step: 1 99 | command: setCoolingSetpoint 100 | state: 101 | attribute: coolingSetpoint 102 | argument: 103 | type: float 104 | - current_temperature: 105 | capability: temperatureMeasurement 106 | state: 107 | attribute: temperature -------------------------------------------------------------------------------- /custom_components/smartthings_customize/example/refrigerator/kimchi_refrigerator.yaml: -------------------------------------------------------------------------------- 1 | # model num 2 | # 김치냉장고 3 | # RQ49C94Y1AP 4 | devics: 5 | - device_id: bc98a847-3fbb-e0fa-96f9-e9a8157c16ae 6 | parent_entity_id: binary_sensor.gimcinaengjanggo_contact 7 | binary_sensor: 8 | - name: top contact 9 | capability: contactSensor 10 | component: top 11 | state: 12 | attribute: contact 13 | on_state: ["open"] 14 | device_class: door 15 | - name: middle contact 16 | capability: contactSensor 17 | component: middle 18 | state: 19 | attribute: contact 20 | on_state: ["open"] 21 | device_class: door 22 | - name: bottom contact 23 | capability: contactSensor 24 | component: bottom 25 | state: 26 | attribute: contact 27 | on_state: ["open"] 28 | device_class: door 29 | 30 | sensor: 31 | - name: top state 32 | capability: samsungce.kimchiRefrigeratorOperatingState 33 | component: top 34 | state: 35 | attribute: operatingState 36 | key: mode 37 | s2h_state_mapping: 38 | [ 39 | { 40 | "fridgeMediumCooling": "냉장(표준-2℃)", 41 | "fridgeHighCooling": "냉장(강냉0℃)", 42 | "fridgeLowCooling": "냉장(약냉5℃)", 43 | "kimchiStorageMediumCooling": "표준김치", 44 | "kimchiStorageHighCooling": "강냉김치", 45 | "kimchiStorageLowCooling": "약냉김치", 46 | "kimchiStorageLowSalt": "저염김치", 47 | "kimchiStorageCrunch": "아삭김치", 48 | "kimchiStoragePurchased": "구입김치", 49 | "kimchiRipeLowTemp": "저온쿨링숙성", 50 | "kimchiRipeRoomTemp": "상온숙성", 51 | "kimchiRipeDongchimi": "동치미숙성", 52 | "kimchiRipeKkakdugi": "깍두기숙성", 53 | }, 54 | ] 55 | - name: middle state 56 | capability: samsungce.kimchiRefrigeratorOperatingState 57 | component: middle 58 | state: 59 | attribute: operatingState 60 | key: mode 61 | s2h_state_mapping: 62 | [ 63 | { 64 | "fridgeMediumCooling": "냉장(표준-2℃)", 65 | "fridgeHighCooling": "냉장(강냉0℃)", 66 | "fridgeLowCooling": "냉장(약냉5℃)", 67 | "kimchiStorageMediumCooling": "표준김치", 68 | "kimchiStorageHighCooling": "강냉김치", 69 | "kimchiStorageLowCooling": "약냉김치", 70 | "kimchiStorageLowSalt": "저염김치", 71 | "kimchiStorageCrunch": "아삭김치", 72 | "kimchiStoragePurchased": "구입김치", 73 | "kimchiRipeLowTemp": "저온쿨링숙성", 74 | "kimchiRipeRoomTemp": "상온숙성", 75 | "kimchiRipeDongchimi": "동치미숙성", 76 | "kimchiRipeKkakdugi": "깍두기숙성", 77 | "storageMeat": "육류/생선", 78 | "meatRipeNormal": "참맛육류", 79 | "storageVegetables": "채소/과일", 80 | "fridgeDrink": "음료", 81 | "freshGrain": "곡류", 82 | "freshWine": "쌀/와인", 83 | "freshPotatoBanana": "감자/바나나", 84 | }, 85 | ] 86 | - name: bottom state 87 | capability: samsungce.kimchiRefrigeratorOperatingState 88 | component: bottom 89 | state: 90 | attribute: operatingState 91 | key: mode 92 | s2h_state_mapping: 93 | [ 94 | { 95 | "freezerMediumCooling": "냉동(표준-19℃)", 96 | "freezerHighCooling": "냉동(강냉-21℃)", 97 | "freezerLowCooling": "냉동(약냉-17℃)", 98 | }, 99 | ] -------------------------------------------------------------------------------- /custom_components/smartthings_customize/example/vacuum/zetbot_ai.yaml: -------------------------------------------------------------------------------- 1 | # POWERBOT_9500_19P 제트봇 AI 삼성 로봇 청소기 2 | devices: 3 | # 로봇청소기 4 | - device_id: "46955634-09e8-6bc7-0167-a73b2e9182e6" 5 | parent_entity_id: switch.maeceondaeg 6 | sensor: 7 | - name: "청소 면적" 8 | component: main 9 | capability: samsungce.robotCleanerMapCleaningInfo 10 | state: 11 | attribute: cleanedExtent 12 | vacuum: 13 | - name: "operating" 14 | component: main 15 | capability: samsungce.robotCleanerOperatingState 16 | capabilities: 17 | - commands: 18 | capability: samsungce.robotCleanerOperatingState 19 | command: setOperatingState 20 | argument: 21 | "return": ["homing"] 22 | "start": ["cleaning"] 23 | "stop": ["paused"] 24 | state: 25 | attribute: operatingState 26 | s2h_state_mapping: 27 | [ 28 | { 29 | "charging": "docked", 30 | "charged": "docked", 31 | "returning": "homing", 32 | }, 33 | ] 34 | - fan_speed: 35 | capability: robotCleanerTurboMode 36 | options: ["on", "off"] 37 | command: setRobotCleanerTurboMode 38 | s2h_fan_speed_mapping: [{ "on": "turbo", "off": "normal" }] 39 | state: 40 | attribute: robotCleanerTurboMode -------------------------------------------------------------------------------- /custom_components/smartthings_customize/fan.py: -------------------------------------------------------------------------------- 1 | """Support for fans through the SmartThings cloud API.""" 2 | from __future__ import annotations 3 | 4 | from collections.abc import Sequence 5 | import math 6 | from typing import Any 7 | 8 | from .pysmartthings import Capability 9 | 10 | from homeassistant.components.fan import FanEntity, FanEntityFeature 11 | from homeassistant.config_entries import ConfigEntry 12 | from homeassistant.core import HomeAssistant 13 | from homeassistant.helpers.entity_platform import AddEntitiesCallback 14 | from homeassistant.util.percentage import ( 15 | int_states_in_range, 16 | percentage_to_ranged_value, 17 | ranged_value_to_percentage, 18 | ) 19 | 20 | from .common import * 21 | from .fan_custom import SmartThingsFan_custom 22 | 23 | import logging 24 | _LOGGER = logging.getLogger(__name__) 25 | 26 | SPEED_RANGE = (1, 3) # off is not included 27 | 28 | 29 | async def async_setup_entry( 30 | hass: HomeAssistant, 31 | config_entry: ConfigEntry, 32 | async_add_entities: AddEntitiesCallback, 33 | ) -> None: 34 | """Add fans for a config entry.""" 35 | broker = hass.data[DOMAIN][DATA_BROKERS][config_entry.entry_id] 36 | if SettingManager.enable_default_entities(): 37 | async_add_entities( 38 | [ 39 | SmartThingsFan(device) 40 | for device in broker.devices.values() 41 | if broker.any_assigned(device.device_id, "fan") and SettingManager.allow_device(device.device_id) 42 | ] 43 | ) 44 | 45 | broker = hass.data[DOMAIN][DATA_BROKERS][config_entry.entry_id] 46 | entities = [] 47 | settings = SettingManager.get_capa_settings(broker, Platform.FAN) 48 | for s in settings: 49 | _LOGGER.debug("cap setting : " + str(s[1])) 50 | entities.append(SmartThingsFan_custom(hass=hass, setting=s)) 51 | 52 | async_add_entities(entities) 53 | 54 | 55 | def get_capabilities(capabilities: Sequence[str]) -> Sequence[str] | None: 56 | """Return all capabilities supported if minimum required are present.""" 57 | supported = [Capability.switch, Capability.fan_speed] 58 | # Must have switch and fan_speed 59 | if all(capability in capabilities for capability in supported): 60 | return supported 61 | return None 62 | 63 | 64 | class SmartThingsFan(SmartThingsEntity, FanEntity): 65 | """Define a SmartThings Fan.""" 66 | 67 | _attr_supported_features = FanEntityFeature.SET_SPEED 68 | 69 | async def async_set_percentage(self, percentage: int) -> None: 70 | """Set the speed percentage of the fan.""" 71 | await self._async_set_percentage(percentage) 72 | 73 | async def _async_set_percentage(self, percentage: int | None) -> None: 74 | if percentage is None: 75 | await self._device.switch_on(set_status=True) 76 | elif percentage == 0: 77 | await self._device.switch_off(set_status=True) 78 | else: 79 | value = math.ceil(percentage_to_ranged_value(SPEED_RANGE, percentage)) 80 | await self._device.set_fan_speed(value, set_status=True) 81 | # State is set optimistically in the command above, therefore update 82 | # the entity state ahead of receiving the confirming push updates 83 | self.async_write_ha_state() 84 | 85 | async def async_turn_on( 86 | self, 87 | percentage: int | None = None, 88 | preset_mode: str | None = None, 89 | **kwargs: Any, 90 | ) -> None: 91 | """Turn the fan on.""" 92 | await self._async_set_percentage(percentage) 93 | 94 | async def async_turn_off(self, **kwargs: Any) -> None: 95 | """Turn the fan off.""" 96 | await self._device.switch_off(set_status=True) 97 | # State is set optimistically in the command above, therefore update 98 | # the entity state ahead of receiving the confirming push updates 99 | self.async_write_ha_state() 100 | 101 | @property 102 | def is_on(self) -> bool: 103 | """Return true if fan is on.""" 104 | return self._device.status.switch 105 | 106 | @property 107 | def percentage(self) -> int: 108 | """Return the current speed percentage.""" 109 | return ranged_value_to_percentage(SPEED_RANGE, self._device.status.fan_speed) 110 | 111 | @property 112 | def speed_count(self) -> int: 113 | """Return the number of speeds the fan supports.""" 114 | return int_states_in_range(SPEED_RANGE) -------------------------------------------------------------------------------- /custom_components/smartthings_customize/httpsig/__init__.py: -------------------------------------------------------------------------------- 1 | from .sign import Signer, HeaderSigner 2 | from .verify import Verifier, HeaderVerifier 3 | 4 | from ._version import get_versions 5 | __version__ = get_versions()['version'] 6 | del get_versions 7 | -------------------------------------------------------------------------------- /custom_components/smartthings_customize/httpsig/requests_auth.py: -------------------------------------------------------------------------------- 1 | from requests.auth import AuthBase 2 | try: 3 | # Python 3 4 | from urllib.parse import urlparse 5 | except ImportError: 6 | # Python 2 7 | from urlparse import urlparse 8 | 9 | from .sign import HeaderSigner 10 | 11 | 12 | class HTTPSignatureAuth(AuthBase): 13 | ''' 14 | Sign a request using the http-signature scheme. 15 | https://github.com/joyent/node-http-signature/blob/master/http_signing.md 16 | 17 | key_id is the mandatory label indicating to the server which secret to use 18 | secret is the filename of a pem file in the case of rsa, a password string in the case of an hmac algorithm 19 | algorithm is one of the six specified algorithms 20 | headers is a list of http headers to be included in the signing string, defaulting to "Date" alone. 21 | ''' 22 | def __init__(self, key_id='', secret='', algorithm=None, headers=None): 23 | headers = headers or [] 24 | self.header_signer = HeaderSigner(key_id=key_id, secret=secret, 25 | algorithm=algorithm, headers=headers) 26 | self.uses_host = 'host' in [h.lower() for h in headers] 27 | 28 | def __call__(self, r): 29 | headers = self.header_signer.sign( 30 | r.headers, 31 | # 'Host' header unavailable in request object at this point 32 | # if 'host' header is needed, extract it from the url 33 | host=urlparse(r.url).netloc if self.uses_host else None, 34 | method=r.method, 35 | path=r.path_url) 36 | r.headers.update(headers) 37 | return r 38 | -------------------------------------------------------------------------------- /custom_components/smartthings_customize/httpsig/sign.py: -------------------------------------------------------------------------------- 1 | import base64 2 | import six 3 | 4 | from Crypto.Hash import HMAC 5 | from Crypto.PublicKey import RSA 6 | from Crypto.Signature import PKCS1_v1_5 7 | 8 | from .utils import * 9 | 10 | 11 | DEFAULT_SIGN_ALGORITHM = "hmac-sha256" 12 | 13 | 14 | class Signer(object): 15 | """ 16 | When using an RSA algo, the secret is a PEM-encoded private key. 17 | When using an HMAC algo, the secret is the HMAC signing secret. 18 | 19 | Password-protected keyfiles are not supported. 20 | """ 21 | def __init__(self, secret, algorithm=None): 22 | if algorithm is None: 23 | algorithm = DEFAULT_SIGN_ALGORITHM 24 | 25 | assert algorithm in ALGORITHMS, "Unknown algorithm" 26 | if isinstance(secret, six.string_types): secret = secret.encode("ascii") 27 | 28 | self._rsa = None 29 | self._hash = None 30 | self.sign_algorithm, self.hash_algorithm = algorithm.split('-') 31 | 32 | if self.sign_algorithm == 'rsa': 33 | try: 34 | rsa_key = RSA.importKey(secret) 35 | self._rsa = PKCS1_v1_5.new(rsa_key) 36 | self._hash = HASHES[self.hash_algorithm] 37 | except ValueError: 38 | raise HttpSigException("Invalid key.") 39 | 40 | elif self.sign_algorithm == 'hmac': 41 | self._hash = HMAC.new(secret, digestmod=HASHES[self.hash_algorithm]) 42 | 43 | @property 44 | def algorithm(self): 45 | return '%s-%s' % (self.sign_algorithm, self.hash_algorithm) 46 | 47 | def _sign_rsa(self, data): 48 | if isinstance(data, six.string_types): data = data.encode("ascii") 49 | h = self._hash.new() 50 | h.update(data) 51 | return self._rsa.sign(h) 52 | 53 | def _sign_hmac(self, data): 54 | if isinstance(data, six.string_types): data = data.encode("ascii") 55 | hmac = self._hash.copy() 56 | hmac.update(data) 57 | return hmac.digest() 58 | 59 | def _sign(self, data): 60 | if isinstance(data, six.string_types): data = data.encode("ascii") 61 | signed = None 62 | if self._rsa: 63 | signed = self._sign_rsa(data) 64 | elif self._hash: 65 | signed = self._sign_hmac(data) 66 | if not signed: 67 | raise SystemError('No valid encryptor found.') 68 | return base64.b64encode(signed).decode("ascii") 69 | 70 | 71 | class HeaderSigner(Signer): 72 | ''' 73 | Generic object that will sign headers as a dictionary using the http-signature scheme. 74 | https://github.com/joyent/node-http-signature/blob/master/http_signing.md 75 | 76 | :arg key_id: the mandatory label indicating to the server which secret to use 77 | :arg secret: a PEM-encoded RSA private key or an HMAC secret (must match the algorithm) 78 | :arg algorithm: one of the six specified algorithms 79 | :arg headers: a list of http headers to be included in the signing string, defaulting to ['date']. 80 | ''' 81 | def __init__(self, key_id, secret, algorithm=None, headers=None): 82 | if algorithm is None: 83 | algorithm = DEFAULT_SIGN_ALGORITHM 84 | 85 | super(HeaderSigner, self).__init__(secret=secret, algorithm=algorithm) 86 | self.headers = headers or ['date'] 87 | self.signature_template = build_signature_template(key_id, algorithm, headers) 88 | 89 | def sign(self, headers, host=None, method=None, path=None): 90 | """ 91 | Add Signature Authorization header to case-insensitive header dict. 92 | 93 | headers is a case-insensitive dict of mutable headers. 94 | host is a override for the 'host' header (defaults to value in headers). 95 | method is the HTTP method (required when using '(request-target)'). 96 | path is the HTTP path (required when using '(request-target)'). 97 | """ 98 | headers = CaseInsensitiveDict(headers) 99 | required_headers = self.headers or ['date'] 100 | signable = generate_message(required_headers, headers, host, method, path) 101 | 102 | signature = self._sign(signable) 103 | headers['authorization'] = self.signature_template % signature 104 | 105 | return headers 106 | 107 | -------------------------------------------------------------------------------- /custom_components/smartthings_customize/httpsig/utils.py: -------------------------------------------------------------------------------- 1 | import re 2 | import struct 3 | import hashlib 4 | import base64 5 | import six 6 | 7 | try: 8 | # Python 3 9 | from urllib.request import parse_http_list 10 | except ImportError: 11 | # Python 2 12 | from urllib2 import parse_http_list 13 | 14 | from Crypto.PublicKey import RSA 15 | from Crypto.Hash import SHA, SHA256, SHA512 16 | 17 | ALGORITHMS = frozenset(['rsa-sha1', 'rsa-sha256', 'rsa-sha512', 'hmac-sha1', 'hmac-sha256', 'hmac-sha512']) 18 | HASHES = {'sha1': SHA, 19 | 'sha256': SHA256, 20 | 'sha512': SHA512} 21 | 22 | 23 | class HttpSigException(Exception): 24 | pass 25 | 26 | """ 27 | Constant-time string compare. 28 | http://codahale.com/a-lesson-in-timing-attacks/ 29 | """ 30 | def ct_bytes_compare(a, b): 31 | if not isinstance(a, six.binary_type): 32 | a = a.decode('utf8') 33 | if not isinstance(b, six.binary_type): 34 | b = b.decode('utf8') 35 | 36 | if len(a) != len(b): 37 | return False 38 | 39 | result = 0 40 | for x, y in zip(a, b): 41 | if six.PY2: 42 | result |= ord(x) ^ ord(y) 43 | else: 44 | result |= x ^ y 45 | 46 | return (result == 0) 47 | 48 | def generate_message(required_headers, headers, host=None, method=None, path=None): 49 | headers = CaseInsensitiveDict(headers) 50 | 51 | if not required_headers: 52 | required_headers = ['date'] 53 | 54 | signable_list = [] 55 | for h in required_headers: 56 | h = h.lower() 57 | if h == '(request-target)': 58 | if not method or not path: 59 | raise Exception('method and path arguments required when using "(request-target)"') 60 | signable_list.append('%s: %s %s' % (h, method.lower(), path)) 61 | 62 | elif h == 'host': 63 | # 'host' special case due to requests lib restrictions 64 | # 'host' is not available when adding auth so must use a param 65 | # if no param used, defaults back to the 'host' header 66 | if not host: 67 | if 'host' in headers: 68 | host = headers[h] 69 | else: 70 | raise Exception('missing required header "%s"' % (h)) 71 | signable_list.append('%s: %s' % (h, host)) 72 | else: 73 | if h not in headers: 74 | raise Exception('missing required header "%s"' % (h)) 75 | 76 | signable_list.append('%s: %s' % (h, headers[h])) 77 | 78 | signable = '\n'.join(signable_list).encode("ascii") 79 | return signable 80 | 81 | 82 | def parse_authorization_header(header): 83 | if not isinstance(header, six.string_types): 84 | header = header.decode("ascii") #HTTP headers cannot be Unicode. 85 | 86 | auth = header.split(" ", 1) 87 | if len(auth) > 2: 88 | raise ValueError('Invalid authorization header. (eg. Method key1=value1,key2="value, \"2\"")') 89 | 90 | # Split up any args into a dictionary. 91 | values = {} 92 | if len(auth) == 2: 93 | auth_value = auth[1] 94 | if auth_value and len(auth_value): 95 | # This is tricky string magic. Let urllib do it. 96 | fields = parse_http_list(auth_value) 97 | 98 | for item in fields: 99 | # Only include keypairs. 100 | if '=' in item: 101 | # Split on the first '=' only. 102 | key, value = item.split('=', 1) 103 | if not (len(key) and len(value)): 104 | continue 105 | 106 | # Unquote values, if quoted. 107 | if value[0] == '"': 108 | value = value[1:-1] 109 | 110 | values[key] = value 111 | 112 | # ("Signature", {"headers": "date", "algorithm": "hmac-sha256", ... }) 113 | return (auth[0], CaseInsensitiveDict(values)) 114 | 115 | def build_signature_template(key_id, algorithm, headers): 116 | """ 117 | Build the Signature template for use with the Authorization header. 118 | 119 | key_id is the mandatory label indicating to the server which secret to use 120 | algorithm is one of the six specified algorithms 121 | headers is a list of http headers to be included in the signing string. 122 | 123 | The signature must be interpolated into the template to get the final Authorization header value. 124 | """ 125 | param_map = {'keyId': key_id, 126 | 'algorithm': algorithm, 127 | 'signature': '%s'} 128 | if headers: 129 | headers = [h.lower() for h in headers] 130 | param_map['headers'] = ' '.join(headers) 131 | kv = map('{0[0]}="{0[1]}"'.format, param_map.items()) 132 | kv_string = ','.join(kv) 133 | sig_string = 'Signature {0}'.format(kv_string) 134 | return sig_string 135 | 136 | 137 | def lkv(d): 138 | parts = [] 139 | while d: 140 | len = struct.unpack('>I', d[:4])[0] 141 | bits = d[4:len+4] 142 | parts.append(bits) 143 | d = d[len+4:] 144 | return parts 145 | 146 | def sig(d): 147 | return lkv(d)[1] 148 | 149 | def is_rsa(keyobj): 150 | return lkv(keyobj.blob)[0] == "ssh-rsa" 151 | 152 | # based on http://stackoverflow.com/a/2082169/151401 153 | class CaseInsensitiveDict(dict): 154 | def __init__(self, d=None, **kwargs): 155 | super(CaseInsensitiveDict, self).__init__(**kwargs) 156 | if d: 157 | self.update((k.lower(), v) for k, v in six.iteritems(d)) 158 | 159 | def __setitem__(self, key, value): 160 | super(CaseInsensitiveDict, self).__setitem__(key.lower(), value) 161 | 162 | def __getitem__(self, key): 163 | return super(CaseInsensitiveDict, self).__getitem__(key.lower()) 164 | 165 | def __contains__(self, key): 166 | return super(CaseInsensitiveDict, self).__contains__(key.lower()) 167 | 168 | # currently busted... 169 | def get_fingerprint(key): 170 | """ 171 | Takes an ssh public key and generates the fingerprint. 172 | 173 | See: http://tools.ietf.org/html/rfc4716 for more info 174 | """ 175 | if key.startswith('ssh-rsa'): 176 | key = key.split(' ')[1] 177 | else: 178 | regex = r'\-{4,5}[\w|| ]+\-{4,5}' 179 | key = re.split(regex, key)[1] 180 | 181 | key = key.replace('\n', '') 182 | key = key.strip().encode('ascii') 183 | key = base64.b64decode(key) 184 | fp_plain = hashlib.md5(key).hexdigest() 185 | return ':'.join(a+b for a,b in zip(fp_plain[::2], fp_plain[1::2])) 186 | 187 | -------------------------------------------------------------------------------- /custom_components/smartthings_customize/httpsig/verify.py: -------------------------------------------------------------------------------- 1 | """ 2 | Module to assist in verifying a signed header. 3 | """ 4 | import six 5 | 6 | from Crypto.Hash import HMAC 7 | from Crypto.PublicKey import RSA 8 | from Crypto.Signature import PKCS1_v1_5 9 | from base64 import b64decode 10 | 11 | from .sign import Signer 12 | from .utils import * 13 | 14 | 15 | class Verifier(Signer): 16 | """ 17 | Verifies signed text against a secret. 18 | For HMAC, the secret is the shared secret. 19 | For RSA, the secret is the PUBLIC key. 20 | """ 21 | def _verify(self, data, signature): 22 | """ 23 | Verifies the data matches a signed version with the given signature. 24 | `data` is the message to verify 25 | `signature` is a base64-encoded signature to verify against `data` 26 | """ 27 | 28 | if isinstance(data, six.string_types): data = data.encode("ascii") 29 | if isinstance(signature, six.string_types): signature = signature.encode("ascii") 30 | 31 | if self.sign_algorithm == 'rsa': 32 | h = self._hash.new() 33 | h.update(data) 34 | return self._rsa.verify(h, b64decode(signature)) 35 | 36 | elif self.sign_algorithm == 'hmac': 37 | h = self._sign_hmac(data) 38 | s = b64decode(signature) 39 | return ct_bytes_compare(h, s) 40 | 41 | else: 42 | raise HttpSigException("Unsupported algorithm.") 43 | 44 | 45 | class HeaderVerifier(Verifier): 46 | """ 47 | Verifies an HTTP signature from given headers. 48 | """ 49 | def __init__(self, headers, secret, required_headers=None, method=None, path=None, host=None): 50 | """ 51 | Instantiate a HeaderVerifier object. 52 | 53 | :param headers: A dictionary of headers from the HTTP request. 54 | :param secret: The HMAC secret or RSA *public* key. 55 | :param required_headers: Optional. A list of headers required to be present to validate, even if the signature is otherwise valid. Defaults to ['date']. 56 | :param method: Optional. The HTTP method used in the request (eg. "GET"). Required for the '(request-target)' header. 57 | :param path: Optional. The HTTP path requested, exactly as sent (including query arguments and fragments). Required for the '(request-target)' header. 58 | :param host: Optional. The value to use for the Host header, if not supplied in :param:headers. 59 | """ 60 | required_headers = required_headers or ['date'] 61 | 62 | auth = parse_authorization_header(headers['authorization']) 63 | if len(auth) == 2: 64 | self.auth_dict = auth[1] 65 | else: 66 | raise HttpSigException("Invalid authorization header.") 67 | 68 | self.headers = CaseInsensitiveDict(headers) 69 | self.required_headers = [s.lower() for s in required_headers] 70 | self.method = method 71 | self.path = path 72 | self.host = host 73 | 74 | super(HeaderVerifier, self).__init__(secret, algorithm=self.auth_dict['algorithm']) 75 | 76 | def verify(self): 77 | """ 78 | Verify the headers based on the arguments passed at creation and current properties. 79 | 80 | Raises an Exception if a required header (:param:required_headers) is not found in the signature. 81 | Returns True or False. 82 | """ 83 | auth_headers = self.auth_dict.get('headers', 'date').split(' ') 84 | 85 | if len(set(self.required_headers) - set(auth_headers)) > 0: 86 | raise Exception('{} is a required header(s)'.format(', '.join(set(self.required_headers)-set(auth_headers)))) 87 | 88 | signing_str = generate_message(auth_headers, self.headers, self.host, self.method, self.path) 89 | 90 | return self._verify(signing_str, self.auth_dict['signature']) 91 | -------------------------------------------------------------------------------- /custom_components/smartthings_customize/lock.py: -------------------------------------------------------------------------------- 1 | """Support for locks through the SmartThings cloud API.""" 2 | from __future__ import annotations 3 | 4 | from collections.abc import Sequence 5 | from typing import Any 6 | 7 | from .pysmartthings import Attribute, Capability 8 | 9 | from homeassistant.components.lock import LockEntity 10 | from homeassistant.config_entries import ConfigEntry 11 | from homeassistant.core import HomeAssistant 12 | from homeassistant.helpers.entity_platform import AddEntitiesCallback 13 | 14 | from .common import * 15 | 16 | import logging 17 | _LOGGER = logging.getLogger(__name__) 18 | 19 | 20 | ST_STATE_LOCKED = "locked" 21 | ST_LOCK_ATTR_MAP = { 22 | "codeId": "code_id", 23 | "codeName": "code_name", 24 | "lockName": "lock_name", 25 | "method": "method", 26 | "timeout": "timeout", 27 | "usedCode": "used_code", 28 | } 29 | 30 | from .lock_custom import SmartThingsLock_custom 31 | 32 | async def async_setup_entry( 33 | hass: HomeAssistant, 34 | config_entry: ConfigEntry, 35 | async_add_entities: AddEntitiesCallback, 36 | ) -> None: 37 | """Add locks for a config entry.""" 38 | broker = hass.data[DOMAIN][DATA_BROKERS][config_entry.entry_id] 39 | entities = [] 40 | if SettingManager.enable_default_entities(): 41 | async_add_entities( 42 | [ 43 | SmartThingsLock(device) 44 | for device in broker.devices.values() 45 | if broker.any_assigned(device.device_id, "lock") and SettingManager.allow_device(device.device_id) 46 | ] 47 | ) 48 | 49 | settings = SettingManager.get_capa_settings(broker, Platform.LOCK) 50 | for s in settings: 51 | _LOGGER.debug("cap setting : " + str(s[1])) 52 | entities.append(SmartThingsLock_custom(hass=hass, setting=s)) 53 | 54 | async_add_entities(entities) 55 | 56 | 57 | def get_capabilities(capabilities: Sequence[str]) -> Sequence[str] | None: 58 | """Return all capabilities supported if minimum required are present.""" 59 | if Capability.lock in capabilities: 60 | return [Capability.lock] 61 | return None 62 | 63 | 64 | class SmartThingsLock(SmartThingsEntity, LockEntity): 65 | """Define a SmartThings lock.""" 66 | 67 | async def async_lock(self, **kwargs: Any) -> None: 68 | """Lock the device.""" 69 | await self._device.lock(set_status=True) 70 | self.async_write_ha_state() 71 | 72 | async def async_unlock(self, **kwargs: Any) -> None: 73 | """Unlock the device.""" 74 | await self._device.unlock(set_status=True) 75 | self.async_write_ha_state() 76 | 77 | @property 78 | def is_locked(self) -> bool: 79 | """Return true if lock is locked.""" 80 | return self._device.status.lock == ST_STATE_LOCKED 81 | 82 | @property 83 | def extra_state_attributes(self) -> dict[str, Any]: 84 | """Return device specific state attributes.""" 85 | state_attrs = {} 86 | status = self._device.status.attributes[Attribute.lock] 87 | if status.value: 88 | state_attrs["lock_state"] = status.value 89 | if isinstance(status.data, dict): 90 | for st_attr, ha_attr in ST_LOCK_ATTR_MAP.items(): 91 | if (data_val := status.data.get(st_attr)) is not None: 92 | state_attrs[ha_attr] = data_val 93 | return state_attrs -------------------------------------------------------------------------------- /custom_components/smartthings_customize/lock_custom.py: -------------------------------------------------------------------------------- 1 | """Support for locks through the SmartThings cloud API.""" 2 | from __future__ import annotations 3 | 4 | 5 | from collections.abc import Sequence 6 | from typing import Any 7 | 8 | from .pysmartthings import Attribute, Capability 9 | 10 | from homeassistant.components.lock import LockEntity 11 | from homeassistant.config_entries import ConfigEntry 12 | from homeassistant.core import HomeAssistant 13 | from homeassistant.helpers.entity_platform import AddEntitiesCallback 14 | 15 | 16 | ST_CMD_LOCK = "lock" 17 | ST_CMD_UNLOCK = "unlock" 18 | 19 | from .common import * 20 | 21 | from .lock import ST_LOCK_ATTR_MAP 22 | 23 | import logging 24 | _LOGGER = logging.getLogger(__name__) 25 | 26 | 27 | async def async_setup_entry( 28 | hass: HomeAssistant, 29 | config_entry: ConfigEntry, 30 | async_add_entities: AddEntitiesCallback, 31 | ) -> None: 32 | """Add switches for a config entry.""" 33 | broker = hass.data[DOMAIN][DATA_BROKERS][config_entry.entry_id] 34 | entities = [] 35 | settings = SettingManager.get_capa_settings(broker, Platform.LOCK) 36 | for s in settings: 37 | _LOGGER.debug("cap setting : " + str(s[1])) 38 | entities.append(SmartThingsLock_custom(hass=hass, setting=s)) 39 | 40 | async_add_entities(entities) 41 | 42 | 43 | class SmartThingsLock_custom(SmartThingsEntity_custom, LockEntity): 44 | def __init__(self, hass, setting) -> None: 45 | super().__init__(hass, platform=Platform.LOCK, setting=setting) 46 | 47 | async def async_lock(self, **kwargs: Any) -> None: 48 | """Lock the device.""" 49 | await self.send_command(Platform.LOCK, self.get_command(Platform.LOCK, {ST_CMD_LOCK: ST_CMD_LOCK}).get(ST_CMD_LOCK), self.get_argument(Platform.LOCK, {ST_CMD_LOCK: []}).get(ST_CMD_LOCK, [])) 50 | 51 | async def async_unlock(self, **kwargs: Any) -> None: 52 | """Unlock the device.""" 53 | await self.send_command(Platform.LOCK, self.get_command(Platform.LOCK, {ST_CMD_UNLOCK: ST_CMD_UNLOCK}).get(ST_CMD_UNLOCK), self.get_argument(Platform.LOCK, {ST_CMD_UNLOCK: []}).get(ST_CMD_UNLOCK, [])) 54 | 55 | @property 56 | def is_locked(self) -> bool: 57 | """Return true if lock is locked.""" 58 | value = self.get_attr_value(Platform.LOCK, CONF_STATE) 59 | lock_state = self.get_attr_value(Platform.LOCK, "lock_state") 60 | return value in lock_state 61 | 62 | @property 63 | def extra_state_attributes(self) -> dict[str, Any]: 64 | """Return device specific state attributes.""" 65 | 66 | conf = self._capability[Platform.LOCK].get(CONF_STATE, []) 67 | status = self.get_attr_status(conf, Platform.LOCK) 68 | if status.value: 69 | self._extra_state_attributes["lock_state"] = status.value 70 | if isinstance(status.data, dict): 71 | for st_attr, ha_attr in ST_LOCK_ATTR_MAP.items(): 72 | if (data_val := status.data.get(st_attr)) is not None: 73 | self._extra_state_attributes[ha_attr] = data_val 74 | return self._extra_state_attributes 75 | 76 | -------------------------------------------------------------------------------- /custom_components/smartthings_customize/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "domain": "smartthings_customize", 3 | "name": "SmartThings Customize", 4 | "config_flow": true, 5 | "documentation": "https://github.com/oukene/smartthings_customize", 6 | "requirements": [], 7 | "ssdp": [], 8 | "zeroconf": [], 9 | "homekit": {}, 10 | "dependencies": [], 11 | "codeowners": [ 12 | "@oukene" 13 | ], 14 | "iot_class": "cloud_push", 15 | "version": "2025.1.5" 16 | } -------------------------------------------------------------------------------- /custom_components/smartthings_customize/number.py: -------------------------------------------------------------------------------- 1 | """Support for switches through the SmartThings cloud API.""" 2 | from __future__ import annotations 3 | 4 | from collections.abc import Sequence 5 | from typing import Any 6 | 7 | from .pysmartthings import Capability 8 | 9 | from homeassistant.components.number import NumberEntity, NumberMode, DEFAULT_MAX_VALUE, DEFAULT_MIN_VALUE, DEFAULT_STEP, ATTR_MIN, ATTR_MAX, ATTR_MODE, ATTR_STEP 10 | from homeassistant.config_entries import ConfigEntry 11 | from homeassistant.core import HomeAssistant 12 | from homeassistant.helpers.entity_platform import AddEntitiesCallback 13 | 14 | from .common import * 15 | 16 | import logging 17 | _LOGGER = logging.getLogger(__name__) 18 | 19 | async def async_setup_entry( 20 | hass: HomeAssistant, 21 | config_entry: ConfigEntry, 22 | async_add_entities: AddEntitiesCallback, 23 | ) -> None: 24 | """Add switches for a config entry.""" 25 | broker = hass.data[DOMAIN][DATA_BROKERS][config_entry.entry_id] 26 | 27 | entities = [] 28 | settings = SettingManager.get_capa_settings(broker, Platform.NUMBER) 29 | for s in settings: 30 | _LOGGER.debug("cap setting : " + str(s[1])) 31 | entities.append(SmartThingsNumber_custom(hass=hass, setting=s)) 32 | 33 | async_add_entities(entities) 34 | 35 | class SmartThingsNumber_custom(SmartThingsEntity_custom, NumberEntity): 36 | def __init__(self, hass, setting) -> None: 37 | super().__init__(hass, platform=Platform.NUMBER, setting=setting) 38 | 39 | @property 40 | def mode(self) -> NumberMode: 41 | return self.get_attr_value(Platform.NUMBER, ATTR_MODE) 42 | 43 | @property 44 | def native_min_value(self) -> float: 45 | return self.get_attr_value(Platform.NUMBER, ATTR_MIN, DEFAULT_MIN_VALUE) 46 | 47 | @property 48 | def native_max_value(self) -> float: 49 | return self.get_attr_value(Platform.NUMBER, ATTR_MAX, DEFAULT_MAX_VALUE) 50 | 51 | @property 52 | def native_step(self) -> float | None: 53 | return self.get_attr_value(Platform.NUMBER, ATTR_STEP, DEFAULT_STEP) 54 | 55 | @property 56 | def native_value(self) -> float | None: 57 | return self.get_attr_value(Platform.NUMBER, CONF_STATE) 58 | 59 | async def async_set_native_value(self, value: float) -> None: 60 | arg = [] 61 | v = int(value) if value.is_integer() else value 62 | arg.append(v) 63 | await self.send_command(Platform.NUMBER, self.get_command(Platform.NUMBER), arg) 64 | 65 | 66 | -------------------------------------------------------------------------------- /custom_components/smartthings_customize/pysmartapp/__init__.py: -------------------------------------------------------------------------------- 1 | """Define the pysmartapp package.""" 2 | 3 | from .config import ( 4 | ConfigInitResponse, ConfigPageResponse, ConfigRequest) 5 | 6 | from .const import __title__, __version__ # noqa 7 | from .dispatch import Dispatcher 8 | from .errors import ( 9 | SignatureVerificationError, SmartAppNotRegisteredError) 10 | from .event import Event, EventRequest 11 | from .install import InstallRequest 12 | from .oauthcallback import OAuthCallbackRequest 13 | from .ping import PingRequest, PingResponse 14 | from .request import EmptyDataResponse, Request, Response 15 | from .smartapp import SmartApp, SmartAppManager 16 | from .uninstall import UninstallRequest 17 | from .update import UpdateRequest 18 | 19 | __all__ = [ 20 | # config 21 | 'ConfigInitResponse', 22 | 'ConfigPageResponse', 23 | 'ConfigRequest', 24 | # dispatch 25 | 'Dispatcher', 26 | # errors 27 | 'SignatureVerificationError', 28 | 'SmartAppNotRegisteredError', 29 | # event 30 | 'Event', 31 | 'EventRequest', 32 | # install 33 | 'InstallRequest', 34 | # oauthcallback 35 | 'OAuthCallbackRequest', 36 | # ping 37 | 'PingRequest', 38 | 'PingResponse', 39 | # request 40 | 'EmptyDataResponse', 41 | 'Request', 42 | 'Response', 43 | # smartapp 44 | 'SmartApp', 45 | 'SmartAppManager', 46 | # unisntall 47 | 'UninstallRequest', 48 | 'UpdateRequest' 49 | ] 50 | -------------------------------------------------------------------------------- /custom_components/smartthings_customize/pysmartapp/const.py: -------------------------------------------------------------------------------- 1 | """Define constants used in the SmartApp.""" 2 | 3 | __title__ = "pysmartapp" 4 | __version__ = "0.3.5" 5 | 6 | LIFECYCLE_PING = 'PING' 7 | LIFECYCLE_CONFIG = 'CONFIGURATION' 8 | LIFECYCLE_CONFIG_INIT = 'INITIALIZE' 9 | LIFECYCLE_CONFIG_PAGE = 'PAGE' 10 | LIFECYCLE_INSTALL = 'INSTALL' 11 | LIFECYCLE_UPDATE = 'UPDATE' 12 | LIFECYCLE_EVENT = 'EVENT' 13 | LIFECYCLE_OAUTH_CALLBACK = 'OAUTH_CALLBACK' 14 | LIFECYCLE_UNINSTALL = 'UNINSTALL' 15 | EVENT_TYPE_DEVICE = 'DEVICE_EVENT' 16 | EVENT_TYPE_TIMER = 'TIMER_EVENT' 17 | SETTINGS_APP_ID = 'appId' 18 | -------------------------------------------------------------------------------- /custom_components/smartthings_customize/pysmartapp/dispatch.py: -------------------------------------------------------------------------------- 1 | """Defines the dispatch component for notifying others of signals.""" 2 | 3 | import asyncio 4 | from collections import defaultdict 5 | import functools 6 | from typing import Any, Callable, Dict, List, Sequence 7 | 8 | TargetType = Callable[..., Any] 9 | DisconnectType = Callable[[], None] 10 | ConnectType = Callable[[str, TargetType], DisconnectType] 11 | SendType = Callable[..., Sequence[asyncio.Future]] 12 | 13 | 14 | class Dispatcher: 15 | """Define the dispatch class.""" 16 | 17 | def __init__(self, *, connect: ConnectType = None, send: SendType = None, 18 | signal_prefix: str = '', loop=None): 19 | """Create a new instance of the dispatch component.""" 20 | self._signal_prefix = signal_prefix 21 | self._signals = defaultdict(list) 22 | self._loop = loop or asyncio.get_event_loop() 23 | self._connect = connect or self._default_connect 24 | self._send = send or self._default_send 25 | self._last_sent = [] 26 | self._disconnects = [] 27 | 28 | def connect(self, signal: str, target: TargetType) \ 29 | -> DisconnectType: 30 | """Connect function to signal. Must be ran in the event loop.""" 31 | disconnect = self._connect(self._signal_prefix + signal, target) 32 | self._disconnects.append(disconnect) 33 | return disconnect 34 | 35 | def send(self, signal: str, *args: Any) -> Sequence[asyncio.Future]: 36 | """Fire a signal. Must be ran in the event loop.""" 37 | sent = self._last_sent = self._send( 38 | self._signal_prefix + signal, *args) 39 | return sent 40 | 41 | def disconnect_all(self): 42 | """Disconnect all connected.""" 43 | disconnects = self._disconnects.copy() 44 | self._disconnects.clear() 45 | for disconnect in disconnects: 46 | disconnect() 47 | 48 | def _default_connect(self, signal: str, target: TargetType) \ 49 | -> DisconnectType: 50 | """Connect function to signal. Must be ran in the event loop.""" 51 | self._signals[signal].append(target) 52 | 53 | def remove_dispatcher() -> None: 54 | """Remove signal listener.""" 55 | try: 56 | self._signals[signal].remove(target) 57 | except ValueError: 58 | # signal was already removed 59 | pass 60 | return remove_dispatcher 61 | 62 | def _default_send(self, signal: str, *args: Any) -> \ 63 | Sequence[asyncio.Future]: 64 | """Fire a signal. Must be ran in the event loop.""" 65 | targets = self._signals[signal] 66 | futures = [] 67 | for target in targets: 68 | task = self._call_target(target, *args) 69 | futures.append(task) 70 | return futures 71 | 72 | def _call_target(self, target, *args) -> asyncio.Future: 73 | check_target = target 74 | while isinstance(check_target, functools.partial): 75 | check_target = check_target.func 76 | if asyncio.iscoroutinefunction(check_target): 77 | return self._loop.create_task(target(*args)) 78 | return self._loop.run_in_executor(None, target, *args) 79 | 80 | @property 81 | def signals(self) -> Dict[str, List[TargetType]]: 82 | """Get the dictionary of registered signals and callbaks.""" 83 | return self._signals 84 | 85 | @property 86 | def last_sent(self) -> Sequence[asyncio.Future]: 87 | """Get the last sent asyncio tasks.""" 88 | return self._last_sent 89 | -------------------------------------------------------------------------------- /custom_components/smartthings_customize/pysmartapp/errors.py: -------------------------------------------------------------------------------- 1 | """Define the errors module.""" 2 | 3 | 4 | class SignatureVerificationError(Exception): 5 | """Defines an error for signature verification failures.""" 6 | 7 | pass 8 | 9 | 10 | class SmartAppNotRegisteredError(Exception): 11 | """Defines an error when a SmartApp isn't registered.""" 12 | 13 | _message = "SmartApp handler for installed app '{}' was not found." 14 | 15 | def __init__(self, installed_app_id: str): 16 | """Create a new instance of the error.""" 17 | Exception.__init__(self, self._message.format(installed_app_id)) 18 | self._installed_app_id = installed_app_id 19 | 20 | @property 21 | def installed_app_id(self) -> str: 22 | """Get the installed app id not found.""" 23 | return self._installed_app_id 24 | -------------------------------------------------------------------------------- /custom_components/smartthings_customize/pysmartapp/event.py: -------------------------------------------------------------------------------- 1 | """Define the event module.""" 2 | 3 | from typing import Any, Dict, Optional, Sequence 4 | 5 | from .const import EVENT_TYPE_DEVICE, EVENT_TYPE_TIMER 6 | from .request import EmptyDataResponse, Request, Response 7 | 8 | 9 | class Event: 10 | """Define an event.""" 11 | 12 | def __init__(self, data: dict): 13 | """Create a new instance of the event class.""" 14 | self._event_type = data['eventType'] 15 | self._subscription_name = None 16 | self._event_id = None 17 | self._location_id = None 18 | self._device_id = None 19 | self._component_id = None 20 | self._capability = None 21 | self._attribute = None 22 | self._value = None 23 | self._value_type = None 24 | self._data = None 25 | self._state_change = None 26 | if self._event_type == EVENT_TYPE_DEVICE: 27 | device_event = data['deviceEvent'] 28 | self._subscription_name = device_event['subscriptionName'] 29 | self._event_id = device_event['eventId'] 30 | self._location_id = device_event['locationId'] 31 | self._device_id = device_event['deviceId'] 32 | self._component_id = device_event['componentId'] 33 | self._capability = device_event['capability'] 34 | self._attribute = device_event['attribute'] 35 | self._value = device_event['value'] 36 | self._value_type = device_event['valueType'] 37 | self._data = device_event.get('data') 38 | self._state_change = device_event['stateChange'] 39 | self._timer_name = None 40 | self._timer_type = None 41 | self._timer_time = None 42 | self._timer_expression = None 43 | if self._event_type == EVENT_TYPE_TIMER: 44 | timer_event = data['timerEvent'] 45 | self._event_id = timer_event['eventId'] 46 | self._timer_name = timer_event['name'] 47 | self._timer_type = timer_event['type'] 48 | self._timer_time = timer_event['time'] 49 | self._timer_expression = timer_event['expression'] 50 | 51 | @property 52 | def event_type(self) -> str: 53 | """Get the type of the event.""" 54 | return self._event_type 55 | 56 | @property 57 | def subscription_name(self) -> str: 58 | """Get the subscription name.""" 59 | return self._subscription_name 60 | 61 | @property 62 | def event_id(self) -> str: 63 | """Get the event id.""" 64 | return self._event_id 65 | 66 | @property 67 | def location_id(self) -> str: 68 | """Get the location id.""" 69 | return self._location_id 70 | 71 | @property 72 | def device_id(self) -> str: 73 | """Get the device id.""" 74 | return self._device_id 75 | 76 | @property 77 | def component_id(self) -> str: 78 | """Get the component id.""" 79 | return self._component_id 80 | 81 | @property 82 | def capability(self) -> str: 83 | """Get the capability.""" 84 | return self._capability 85 | 86 | @property 87 | def attribute(self) -> str: 88 | """Get the attribute.""" 89 | return self._attribute 90 | 91 | @property 92 | def value(self) -> Optional[Any]: 93 | """Get the value.""" 94 | return self._value 95 | 96 | @property 97 | def value_type(self) -> str: 98 | """Get the type of the value.""" 99 | return self._value_type 100 | 101 | @property 102 | def data(self) -> Optional[Dict[str, Any]]: 103 | """Get the data associated with the event.""" 104 | return self._data 105 | 106 | @property 107 | def state_change(self) -> bool: 108 | """Get whether this is a new state change.""" 109 | return self._state_change 110 | 111 | @property 112 | def timer_name(self) -> str: 113 | """Get the name of the timer schedule.""" 114 | return self._timer_name 115 | 116 | @property 117 | def timer_type(self) -> str: 118 | """Get the type of time.""" 119 | return self._timer_type 120 | 121 | @property 122 | def timer_time(self) -> str: 123 | """Get the time the timer fired.""" 124 | return self._timer_time 125 | 126 | @property 127 | def timer_expression(self) -> str: 128 | """Get the timer firing expression.""" 129 | return self._timer_expression 130 | 131 | 132 | class EventRequest(Request): 133 | """Define the EventRequest class.""" 134 | 135 | def __init__(self, data: dict): 136 | """Create a new instance of the EventRequest.""" 137 | super().__init__(data) 138 | event_data = self._event_data_raw = data['eventData'] 139 | self._auth_token = event_data['authToken'] 140 | self._init_installed_app(event_data['installedApp']) 141 | self._events = [Event(item) for item in event_data['events']] 142 | 143 | async def _process(self, app) -> Response: 144 | resp = EmptyDataResponse('eventData') 145 | return resp 146 | 147 | @property 148 | def event_data_raw(self) -> dict: 149 | """Get the raw event data.""" 150 | return self._event_data_raw 151 | 152 | @property 153 | def auth_token(self) -> str: 154 | """Get the auth token.""" 155 | return self._auth_token 156 | 157 | @property 158 | def events(self) -> Sequence[Event]: 159 | """Get the events.""" 160 | return self._events 161 | -------------------------------------------------------------------------------- /custom_components/smartthings_customize/pysmartapp/install.py: -------------------------------------------------------------------------------- 1 | """Define the install module.""" 2 | 3 | from .request import EmptyDataResponse, Request, Response 4 | 5 | 6 | class InstallRequest(Request): 7 | """Define the InstallRequest class.""" 8 | 9 | def __init__(self, data: dict): 10 | """Create a new instance of the InstallRequest.""" 11 | super().__init__(data) 12 | install_data = self._install_data_raw = data['installData'] 13 | self._init_installed_app(install_data['installedApp']) 14 | self._auth_token = install_data['authToken'] 15 | self._refresh_token = install_data['refreshToken'] 16 | 17 | async def _process(self, app) -> Response: 18 | resp = EmptyDataResponse('installData') 19 | return resp 20 | 21 | @property 22 | def install_data_raw(self) -> dict: 23 | """Get the raw installation data.""" 24 | return self._install_data_raw 25 | 26 | @property 27 | def auth_token(self): 28 | """Get the auth token.""" 29 | return self._auth_token 30 | 31 | @property 32 | def refresh_token(self): 33 | """Get the refresh token.""" 34 | return self._refresh_token 35 | -------------------------------------------------------------------------------- /custom_components/smartthings_customize/pysmartapp/oauthcallback.py: -------------------------------------------------------------------------------- 1 | """Define the oauthcallback module.""" 2 | 3 | from .request import EmptyDataResponse, Request, Response 4 | 5 | 6 | class OAuthCallbackRequest(Request): 7 | """Define the OAuthCallbackRequest class.""" 8 | 9 | def __init__(self, data: dict): 10 | """Create a new instance of the OAuthCallbackRequest.""" 11 | super().__init__(data) 12 | callback_data = self._oauth_callback_data_raw = \ 13 | data['oAuthCallbackData'] 14 | self._installed_app_id = callback_data['installedAppId'] 15 | self._url_path = callback_data['urlPath'] 16 | 17 | async def _process(self, app) -> Response: 18 | resp = EmptyDataResponse('oAuthCallbackData') 19 | return resp 20 | 21 | @property 22 | def oauth_callback_data_raw(self) -> dict: 23 | """Get the raw OAuth Callback data.""" 24 | return self._oauth_callback_data_raw 25 | 26 | @property 27 | def url_path(self) -> str: 28 | """Get the url path of the OAuth callback.""" 29 | return self._url_path 30 | -------------------------------------------------------------------------------- /custom_components/smartthings_customize/pysmartapp/ping.py: -------------------------------------------------------------------------------- 1 | """Define the ping module.""" 2 | 3 | from .request import Request, Response 4 | 5 | 6 | class PingRequest(Request): 7 | """Defines a ping request.""" 8 | 9 | def __init__(self, data: dict): 10 | """Create a new instance of the PingRequest class.""" 11 | super().__init__(data) 12 | self._supports_validation = False 13 | self._ping_data_raw = data['pingData'] 14 | 15 | async def _process(self, app): 16 | response = PingResponse() 17 | response.ping_challenge = self.ping_challenge 18 | return response 19 | 20 | @property 21 | def ping_data_raw(self) -> dict: 22 | """Get the raw data structure of the ping request.""" 23 | return self._ping_data_raw 24 | 25 | @property 26 | def ping_challenge(self): 27 | """Get the challenge code as part of the request.""" 28 | return self._ping_data_raw['challenge'] 29 | 30 | 31 | class PingResponse(Response): 32 | """Defines a ping response.""" 33 | 34 | def __init__(self): 35 | """Create a new instance of the PingResponse.""" 36 | self._ping_challenge = None 37 | 38 | def to_data(self) -> dict: 39 | """Create a data structure for the response.""" 40 | return {'pingData': {'challenge': self.ping_challenge}} 41 | 42 | @property 43 | def ping_challenge(self): 44 | """Get the ping challenge in the response.""" 45 | return self._ping_challenge 46 | 47 | @ping_challenge.setter 48 | def ping_challenge(self, value: str): 49 | """Set the ping challenge response.""" 50 | self._ping_challenge = value 51 | -------------------------------------------------------------------------------- /custom_components/smartthings_customize/pysmartapp/request.py: -------------------------------------------------------------------------------- 1 | """Define the request module.""" 2 | import os 3 | import sys 4 | sys.path.append(os.path.dirname(os.path.abspath(os.path.dirname(__file__)))) 5 | 6 | from httpsig.verify import HeaderVerifier 7 | from .errors import SignatureVerificationError 8 | 9 | 10 | class Response: 11 | """Defines a response.""" 12 | 13 | def to_data(self) -> dict: 14 | """Create a data structure for the response.""" 15 | raise NotImplementedError 16 | 17 | 18 | class EmptyDataResponse(Response): 19 | """Defines a response with an empty data structure.""" 20 | 21 | def __init__(self, name: str): 22 | """Create a new instance of the EmptyDataResponse class.""" 23 | self._name = name 24 | 25 | def to_data(self) -> dict: 26 | """Return a data structure representing this request.""" 27 | return {self.name: {}} 28 | 29 | @property 30 | def name(self) -> str: 31 | """Get the name of the empty data tag.""" 32 | return self._name 33 | 34 | @name.setter 35 | def name(self, value: str): 36 | """Set the name of the empty data tag.""" 37 | self._name = value 38 | 39 | 40 | class Request: 41 | """Defines a request to process.""" 42 | 43 | def __init__(self, data: dict): 44 | """Create a new instance of the Request class.""" 45 | self._lifecycle = data['lifecycle'] 46 | self._execution_id = data['executionId'] 47 | self._locale = data['locale'] 48 | self._version = data['version'] 49 | self._installed_app_id = None 50 | self._location_id = None 51 | self._installed_app_config = {} 52 | self._settings = data.get('settings', {}) 53 | self._supports_validation = True 54 | 55 | def _init_installed_app(self, installed_app): 56 | self._installed_app_id = installed_app['installedAppId'] 57 | self._location_id = installed_app['locationId'] 58 | self._installed_app_config = installed_app['config'] 59 | 60 | async def process(self, app, headers: list = None, 61 | validate_signature: bool = True) -> Response: 62 | """Process the request with the SmartApp.""" 63 | if validate_signature and self._supports_validation: 64 | try: 65 | verifier = HeaderVerifier( 66 | headers=headers, secret=app.public_key, method='POST', 67 | path=app.path) 68 | result = verifier.verify() 69 | except Exception as ex: 70 | raise SignatureVerificationError from ex 71 | if not result: 72 | raise SignatureVerificationError 73 | response = await self._process(app) 74 | app.dispatcher.send(self.lifecycle, self, response, app) 75 | return response 76 | 77 | async def _process(self, app) -> Response: 78 | raise NotImplementedError 79 | 80 | @property 81 | def lifecycle(self) -> str: 82 | """Get the lifecycle of the request.""" 83 | return self._lifecycle 84 | 85 | @property 86 | def execution_id(self) -> str: 87 | """Get the execution id of the request.""" 88 | return self._execution_id 89 | 90 | @property 91 | def locale(self) -> str: 92 | """Get the locale of the request.""" 93 | return self._locale 94 | 95 | @property 96 | def version(self) -> str: 97 | """Get the version of the request.""" 98 | return self._version 99 | 100 | @property 101 | def installed_app_id(self) -> str: 102 | """Get the installed app id the request is for.""" 103 | return self._installed_app_id 104 | 105 | @property 106 | def location_id(self) -> str: 107 | """Get the installed app location id.""" 108 | return self._location_id 109 | 110 | @property 111 | def installed_app_config(self) -> dict: 112 | """Get the installed app configuration.""" 113 | return self._installed_app_config 114 | 115 | @property 116 | def settings(self): 117 | """Get the settings associated with the request.""" 118 | return self._settings 119 | -------------------------------------------------------------------------------- /custom_components/smartthings_customize/pysmartapp/uninstall.py: -------------------------------------------------------------------------------- 1 | """Define the uninstall module.""" 2 | 3 | from .request import EmptyDataResponse, Request, Response 4 | 5 | 6 | class UninstallRequest(Request): 7 | """Define the UninstallRequest class.""" 8 | 9 | def __init__(self, data: dict): 10 | """Create a new instance of the UninstallRequest.""" 11 | super().__init__(data) 12 | uninstall_data = self._uninstall_data_raw = data['uninstallData'] 13 | self._init_installed_app(uninstall_data['installedApp']) 14 | 15 | async def _process(self, app) -> Response: 16 | resp = EmptyDataResponse('uninstallData') 17 | return resp 18 | 19 | @property 20 | def uninstall_data_raw(self) -> dict: 21 | """Get the raw update data.""" 22 | return self._uninstall_data_raw 23 | -------------------------------------------------------------------------------- /custom_components/smartthings_customize/pysmartapp/update.py: -------------------------------------------------------------------------------- 1 | """Define the update module.""" 2 | 3 | from .request import EmptyDataResponse, Request, Response 4 | 5 | 6 | class UpdateRequest(Request): 7 | """Define the UpdateRequest class.""" 8 | 9 | def __init__(self, data: dict): 10 | """Create a new instance of the UpdateRequest.""" 11 | super().__init__(data) 12 | update_data = self._update_data_raw = data['updateData'] 13 | self._init_installed_app(update_data['installedApp']) 14 | self._auth_token = update_data['authToken'] 15 | self._refresh_token = update_data['refreshToken'] 16 | 17 | async def _process(self, app) -> Response: 18 | resp = EmptyDataResponse('updateData') 19 | return resp 20 | 21 | @property 22 | def update_data_raw(self) -> dict: 23 | """Get the raw update data.""" 24 | return self._update_data_raw 25 | 26 | @property 27 | def auth_token(self): 28 | """Get the auth token.""" 29 | return self._auth_token 30 | 31 | @property 32 | def refresh_token(self): 33 | """Get the refresh token.""" 34 | return self._refresh_token 35 | -------------------------------------------------------------------------------- /custom_components/smartthings_customize/pysmartapp/utilities.py: -------------------------------------------------------------------------------- 1 | """Define the utilities class.""" 2 | 3 | from .config import ConfigRequest 4 | from .const import ( 5 | LIFECYCLE_CONFIG, LIFECYCLE_EVENT, LIFECYCLE_INSTALL, 6 | LIFECYCLE_OAUTH_CALLBACK, LIFECYCLE_PING, LIFECYCLE_UNINSTALL, 7 | LIFECYCLE_UPDATE) 8 | from .event import EventRequest 9 | from .install import InstallRequest 10 | from .oauthcallback import OAuthCallbackRequest 11 | from .ping import PingRequest 12 | from .uninstall import UninstallRequest 13 | from .update import UpdateRequest 14 | 15 | 16 | def create_request(data: dict): 17 | """Create a request from the given dictionary and headers.""" 18 | lifecycle = data['lifecycle'] 19 | if lifecycle == LIFECYCLE_PING: 20 | return PingRequest(data) 21 | if lifecycle == LIFECYCLE_CONFIG: 22 | return ConfigRequest(data) 23 | if lifecycle == LIFECYCLE_INSTALL: 24 | return InstallRequest(data) 25 | if lifecycle == LIFECYCLE_UPDATE: 26 | return UpdateRequest(data) 27 | if lifecycle == LIFECYCLE_EVENT: 28 | return EventRequest(data) 29 | if lifecycle == LIFECYCLE_OAUTH_CALLBACK: 30 | return OAuthCallbackRequest(data) 31 | if lifecycle == LIFECYCLE_UNINSTALL: 32 | return UninstallRequest(data) 33 | raise ValueError('The specified lifecycle event was not recognized.') 34 | -------------------------------------------------------------------------------- /custom_components/smartthings_customize/pysmartthings/__init__.py: -------------------------------------------------------------------------------- 1 | """A python library for interacting with the SmartThings cloud API.""" 2 | 3 | from .app import ( 4 | APP_TYPE_LAMBDA, 5 | APP_TYPE_WEBHOOK, 6 | CLASSIFICATION_AUTOMATION, 7 | App, 8 | AppEntity, 9 | AppOAuth, 10 | AppOAuthClient, 11 | AppOAuthClientEntity, 12 | AppOAuthEntity, 13 | AppSettings, 14 | AppSettingsEntity, 15 | ) 16 | from .capability import ( 17 | ATTRIBUTES, 18 | CAPABILITIES, 19 | CAPABILITIES_TO_ATTRIBUTES, 20 | Attribute, 21 | Capability, 22 | ) 23 | from .const import __title__, __version__ # noqa 24 | from .device import ( 25 | DEVICE_TYPE_DTH, 26 | DEVICE_TYPE_ENDPOINT_APP, 27 | DEVICE_TYPE_OCF, 28 | DEVICE_TYPE_UNKNOWN, 29 | DEVICE_TYPE_VIPER, 30 | Command, 31 | Device, 32 | DeviceEntity, 33 | DeviceStatus, 34 | DeviceStatusBase, 35 | ) 36 | from .errors import APIErrorDetail, APIInvalidGrant, APIResponseError 37 | from .installedapp import ( 38 | InstalledApp, 39 | InstalledAppEntity, 40 | InstalledAppStatus, 41 | InstalledAppType, 42 | ) 43 | from .location import Location, LocationEntity 44 | from .oauthtoken import OAuthToken 45 | from .room import Room, RoomEntity 46 | from .scene import Scene, SceneEntity 47 | from .smartthings import SmartThings 48 | from .subscription import SourceType, Subscription, SubscriptionEntity 49 | 50 | __all__ = [ 51 | # app 52 | "APP_TYPE_LAMBDA", 53 | "APP_TYPE_WEBHOOK", 54 | "CLASSIFICATION_AUTOMATION", 55 | "App", 56 | "AppEntity", 57 | "AppOAuth", 58 | "AppOAuthClient", 59 | "AppOAuthClientEntity", 60 | "AppOAuthEntity", 61 | "AppSettings", 62 | "AppSettingsEntity", 63 | # capability 64 | "ATTRIBUTES", 65 | "CAPABILITIES", 66 | "CAPABILITIES_TO_ATTRIBUTES", 67 | "Attribute", 68 | "Capability", 69 | # device 70 | "DEVICE_TYPE_DTH", 71 | "DEVICE_TYPE_ENDPOINT_APP", 72 | "DEVICE_TYPE_OCF", 73 | "DEVICE_TYPE_UNKNOWN", 74 | "DEVICE_TYPE_VIPER", 75 | "Command", 76 | "Device", 77 | "DeviceEntity", 78 | "DeviceStatus", 79 | "DeviceStatusBase", 80 | # error 81 | "APIErrorDetail", 82 | "APIInvalidGrant", 83 | "APIResponseError", 84 | # installed app 85 | "InstalledApp", 86 | "InstalledAppEntity", 87 | "InstalledAppStatus", 88 | "InstalledAppType", 89 | # location 90 | "Location", 91 | "LocationEntity", 92 | # room 93 | "Room", 94 | "RoomEntity", 95 | # oauthtoken 96 | "OAuthToken", 97 | # scene 98 | "Scene", 99 | "SceneEntity", 100 | # smartthings 101 | "SmartThings", 102 | # subscription 103 | "SourceType", 104 | "Subscription", 105 | "SubscriptionEntity", 106 | ] 107 | -------------------------------------------------------------------------------- /custom_components/smartthings_customize/pysmartthings/const.py: -------------------------------------------------------------------------------- 1 | """Define consts for the pysmartthings package.""" 2 | 3 | __title__ = "pysmartthings" 4 | __version__ = "0.7.8" 5 | -------------------------------------------------------------------------------- /custom_components/smartthings_customize/pysmartthings/entity.py: -------------------------------------------------------------------------------- 1 | """Defines the entity module.""" 2 | 3 | from .api import Api 4 | 5 | 6 | class Entity: 7 | """Define an entity from the SmartThings API.""" 8 | 9 | def __init__(self, api: Api): 10 | """Initialize a new instance of the entity.""" 11 | self._api = api 12 | 13 | async def refresh(self): 14 | """Retrieve the latest values from the API.""" 15 | raise NotImplementedError 16 | 17 | async def save(self): 18 | """Update or create the entity.""" 19 | raise NotImplementedError 20 | -------------------------------------------------------------------------------- /custom_components/smartthings_customize/pysmartthings/errors.py: -------------------------------------------------------------------------------- 1 | """Define errors that can be returned from the SmartThings API.""" 2 | import json 3 | from typing import Optional, Sequence 4 | 5 | from aiohttp import ClientResponseError 6 | 7 | UNAUTHORIZED_ERROR = ( 8 | "Authorization for the API is required, but the request has not been " 9 | "authenticated." 10 | ) 11 | FORBIDDEN_ERROR = ( 12 | "The request has been authenticated but does not have appropriate" 13 | "permissions, or a requested resource is not found." 14 | ) 15 | UNKNOWN_ERROR = "An unknown API error occurred." 16 | 17 | 18 | class APIErrorDetail: 19 | """Define details about an error.""" 20 | 21 | def __init__(self, data): 22 | """Create a new instance of the error detail.""" 23 | self._code = data.get("code") 24 | self._message = data.get("message") 25 | self._target = data.get("target") 26 | self._details = [] 27 | details = data.get("details") 28 | if isinstance(details, list): 29 | self._details.extend([APIErrorDetail(detail) for detail in details]) 30 | 31 | @property 32 | def code(self) -> Optional[str]: 33 | """Get the SmartThings-defined error code.""" 34 | return self._code 35 | 36 | @property 37 | def message(self) -> Optional[str]: 38 | """Get a description of the error.""" 39 | return self._message 40 | 41 | @property 42 | def target(self) -> Optional[str]: 43 | """Get the target of the particular error.""" 44 | return self._target 45 | 46 | @property 47 | def details(self) -> Sequence: 48 | """Get an array of errors that represent related errors.""" 49 | return self._details 50 | 51 | 52 | class APIResponseError(ClientResponseError): 53 | """Define an error from the API.""" 54 | 55 | def __init__( 56 | self, request_info, history, *, status=None, message="", headers=None, data=None 57 | ): 58 | """Create a new instance of the API Error.""" 59 | super().__init__( 60 | request_info, history, status=status, message=message, headers=headers 61 | ) 62 | self._raw_error_response = data 63 | self._request_id = data.get("requestId") 64 | self._error = APIErrorDetail(data.get("error", {})) 65 | 66 | def __str__(self): 67 | """Return a string represenation of the error.""" 68 | return f"{self.message} ({self.status}): {json.dumps(self._raw_error_response)}" 69 | 70 | @property 71 | def raw_error_response(self): 72 | """Get the raw error response returned.""" 73 | return self._raw_error_response 74 | 75 | @property 76 | def request_id(self) -> Optional[str]: 77 | """Get request correlation id.""" 78 | return self._request_id 79 | 80 | @property 81 | def error(self) -> APIErrorDetail: 82 | """Get the API error document.""" 83 | return self._error 84 | 85 | def is_target_error(self): 86 | """Determine if the error is due to an issue with the target.""" 87 | return ( 88 | self.error.code == "ConstraintViolationError" 89 | and len(self.error.details) == 1 90 | and self.error.details[0].code 91 | and self.error.details[0].code.startswith("Target") 92 | ) 93 | 94 | 95 | class APIInvalidGrant(Exception): 96 | """Define an invalid grant error.""" 97 | 98 | pass 99 | -------------------------------------------------------------------------------- /custom_components/smartthings_customize/pysmartthings/installedapp.py: -------------------------------------------------------------------------------- 1 | """Define the installedapp module.""" 2 | 3 | from enum import Enum 4 | from typing import Sequence 5 | 6 | from .api import Api 7 | from .entity import Entity 8 | from .subscription import SubscriptionEntity 9 | 10 | 11 | def format_install_url(app_id: str, location_id: str) -> str: 12 | """Return a web-based URL to auth and install a SmartApp.""" 13 | return f"https://account.smartthings.com/login?redirect=https%3A%2F%2Fstrongman-regional.api.smartthings.com%2F%3FappId%3D{app_id}%26locationId%3D{location_id}%26appType%3DENDPOINTAPP%26language%3Den%26clientOS%3Dweb" 14 | 15 | 16 | class InstalledAppType(Enum): 17 | """Define the type of installed app.""" 18 | 19 | UNKNOWN = "UNKNOWN" 20 | LAMBDA_SMART_APP = "LAMBDA_SMART_APP" 21 | WEBHOOK_SMART_APP = "WEBHOOK_SMART_APP" 22 | BEHAVIOR = "BEHAVIOR" 23 | 24 | 25 | class InstalledAppStatus(Enum): 26 | """Define the installed app status.""" 27 | 28 | UNKNOWN = "UNKNOWN" 29 | PENDING = "PENDING" 30 | AUTHORIZED = "AUTHORIZED" 31 | REVOKED = "REVOKED" 32 | DISABLED = "DISABLED" 33 | 34 | 35 | class InstalledApp: 36 | """Define the InstalledApp class.""" 37 | 38 | def __init__(self): 39 | """Create a new instance of the InstalledApp class.""" 40 | self._installed_app_id = None 41 | self._installed_app_type = InstalledAppType.UNKNOWN 42 | self._installed_app_status = InstalledAppStatus.UNKNOWN 43 | self._display_name = None 44 | self._app_id = None 45 | self._reference_id = None 46 | self._location_id = None 47 | self._created_date = None 48 | self._last_updated_date = None 49 | self._classifications = [] 50 | 51 | def apply_data(self, data: dict): 52 | """Apply the data structure to the properties.""" 53 | self._installed_app_id = data["installedAppId"] 54 | self._installed_app_type = InstalledAppType(data["installedAppType"]) 55 | self._installed_app_status = InstalledAppStatus(data["installedAppStatus"]) 56 | self._display_name = data["displayName"] 57 | self._app_id = data["appId"] 58 | self._reference_id = data["referenceId"] 59 | self._location_id = data["locationId"] 60 | self._created_date = data["createdDate"] 61 | self._last_updated_date = data["lastUpdatedDate"] 62 | self._classifications = data["classifications"] 63 | 64 | @property 65 | def installed_app_id(self) -> str: 66 | """Get the ID of the installed app.""" 67 | return self._installed_app_id 68 | 69 | @property 70 | def installed_app_type(self) -> InstalledAppType: 71 | """Get the type of installed app.""" 72 | return self._installed_app_type 73 | 74 | @property 75 | def installed_app_status(self) -> InstalledAppStatus: 76 | """Get the current state of an install.""" 77 | return self._installed_app_status 78 | 79 | @property 80 | def display_name(self) -> str: 81 | """Get the user defined name for the installed app.""" 82 | return self._display_name 83 | 84 | @property 85 | def app_id(self) -> str: 86 | """Get the ID of the app.""" 87 | return self._app_id 88 | 89 | @property 90 | def reference_id(self) -> str: 91 | """Get a reference to an upstream system.""" 92 | return self._reference_id 93 | 94 | @property 95 | def location_id(self) -> str: 96 | """Get the ID of the location to which the installed app.""" 97 | return self._location_id 98 | 99 | @property 100 | def created_date(self) -> str: 101 | """Get the date the installed app was created.""" 102 | return self._created_date 103 | 104 | @property 105 | def last_updated_date(self) -> str: 106 | """Get the date the installed app was updated.""" 107 | return self._last_updated_date 108 | 109 | @property 110 | def classifications(self) -> Sequence[str]: 111 | """Get the collection of classifications.""" 112 | return self._classifications 113 | 114 | 115 | class InstalledAppEntity(Entity, InstalledApp): 116 | """Define the InstalledAppEntity class.""" 117 | 118 | def __init__(self, api: Api, data=None, installed_app_id=None): 119 | """Create a new instance of the InstalledAppEntity class.""" 120 | Entity.__init__(self, api) 121 | InstalledApp.__init__(self) 122 | if data: 123 | self.apply_data(data) 124 | if installed_app_id: 125 | self._installed_app_id = installed_app_id 126 | 127 | async def refresh(self): 128 | """Refresh the installedapp information using the API.""" 129 | data = await self._api.get_installed_app(self._installed_app_id) 130 | self.apply_data(data) 131 | 132 | async def save(self): 133 | """Save the changes made to the app.""" 134 | raise NotImplementedError 135 | 136 | async def subscriptions(self) -> Sequence[SubscriptionEntity]: 137 | """Get the subscriptions for the installedapp.""" 138 | data = await self._api.get_subscriptions(self._installed_app_id) 139 | return [SubscriptionEntity(self._api, entity) for entity in data] 140 | -------------------------------------------------------------------------------- /custom_components/smartthings_customize/pysmartthings/location.py: -------------------------------------------------------------------------------- 1 | """Define the SmartThing location.""" 2 | 3 | from typing import List, Optional 4 | 5 | from .api import Api 6 | from .entity import Entity 7 | from .room import RoomEntity 8 | 9 | 10 | class Location: 11 | """Represents a SmartThings Location.""" 12 | 13 | def __init__(self): 14 | """Initialize a new location.""" 15 | self._name = None 16 | self._location_id = None 17 | self._latitude = None 18 | self._longitude = None 19 | self._region_radius = None 20 | self._temperature_scale = None 21 | self._locale = None 22 | self._country_code = None 23 | self._timezone_id = None 24 | 25 | def apply_data(self, data: dict): 26 | """Apply the given data structure to the location.""" 27 | self._name = data["name"] 28 | self._location_id = data["locationId"] 29 | self._latitude = data.get("latitude", None) 30 | self._longitude = data.get("longitude", None) 31 | self._region_radius = data.get("regionRadius", None) 32 | self._temperature_scale = data.get("temperatureScale", None) 33 | self._locale = data.get("locale", None) 34 | self._country_code = data.get("countryCode", None) 35 | self._timezone_id = data.get("timeZoneId", None) 36 | 37 | @property 38 | def name(self) -> str: 39 | """Get nickname given for the location.""" 40 | return self._name 41 | 42 | @property 43 | def location_id(self) -> str: 44 | """Get the ID of the location.""" 45 | return self._location_id 46 | 47 | @property 48 | def latitude(self) -> float: 49 | """Get the geographical latitude.""" 50 | return self._latitude 51 | 52 | @property 53 | def longitude(self) -> float: 54 | """Get the geographical longitude.""" 55 | return self._longitude 56 | 57 | @property 58 | def region_radius(self) -> int: 59 | """Get the radius in meters which defines this location.""" 60 | return self._region_radius 61 | 62 | @property 63 | def temperature_scale(self) -> str: 64 | """Get the temperature scale of the location (F or C).""" 65 | return self._temperature_scale 66 | 67 | @property 68 | def locale(self) -> str: 69 | """Get the IETF BCP 47 language tag of the location.""" 70 | return self._locale 71 | 72 | @property 73 | def country_code(self) -> str: 74 | """Get the country code of the location.""" 75 | return self._country_code 76 | 77 | @property 78 | def timezone_id(self) -> str: 79 | """Get the ID matching the Java Time Zone ID of the location.""" 80 | return self._timezone_id 81 | 82 | 83 | class LocationEntity(Entity, Location): 84 | """Define a location entity.""" 85 | 86 | def __init__( 87 | self, api: Api, data: Optional[dict] = None, location_id: Optional[str] = None 88 | ): 89 | """Create a new instance of the LocationEntity.""" 90 | Entity.__init__(self, api) 91 | Location.__init__(self) 92 | if data: 93 | self.apply_data(data) 94 | if location_id: 95 | self._location_id = location_id 96 | 97 | async def refresh(self): 98 | """Refresh the location information.""" 99 | data = await self._api.get_location(self._location_id) 100 | if data: 101 | self.apply_data(data) 102 | 103 | async def save(self): 104 | """Location does not support updating at this time.""" 105 | raise NotImplementedError("Location does not support updating at this time.") 106 | 107 | async def rooms(self) -> List[RoomEntity]: 108 | """Get the rooms contained within the location.""" 109 | resp = await self._api.get_rooms(self._location_id) 110 | return [RoomEntity(self._api, entity) for entity in resp] 111 | -------------------------------------------------------------------------------- /custom_components/smartthings_customize/pysmartthings/oauthtoken.py: -------------------------------------------------------------------------------- 1 | """Define the oauth module.""" 2 | 3 | from datetime import datetime, timedelta 4 | from typing import List, Optional 5 | 6 | from .api import Api 7 | 8 | 9 | class OAuthToken: 10 | """Define oauth token information.""" 11 | 12 | def __init__( 13 | self, api: Api, data: Optional[dict] = None, refresh_token: Optional[str] = None 14 | ): 15 | """Create a new instance of the OAuthToken class.""" 16 | self._api = api 17 | self._access_token = None 18 | self._refresh_token = refresh_token 19 | self._expires_in = 0 20 | self._token_type = None 21 | self._scope = [] 22 | self._expiration_date = datetime.now() 23 | if data: 24 | self.apply_data(data) 25 | 26 | def apply_data(self, data: dict): 27 | """Apply the given data to the entity.""" 28 | self._access_token = data["access_token"] 29 | self._token_type = data["token_type"] 30 | self._refresh_token = data["refresh_token"] 31 | self._expires_in = data["expires_in"] 32 | self._expiration_date = datetime.now() + timedelta(0, self._expires_in) 33 | data_scope = data["scope"] 34 | if isinstance(data_scope, list): 35 | self._scope = data_scope 36 | if isinstance(data_scope, str): 37 | self._scope.clear() 38 | self._scope.append(data_scope) 39 | 40 | async def refresh(self, client_id: str, client_secret: str): 41 | """Refresh the auth and refresh tokens.""" 42 | data = await self._api.generate_tokens( 43 | client_id, client_secret, self._refresh_token 44 | ) 45 | if data: 46 | self.apply_data(data) 47 | 48 | @property 49 | def access_token(self) -> str: 50 | """Get the access token for authentication.""" 51 | return self._access_token 52 | 53 | @property 54 | def refresh_token(self) -> str: 55 | """Get the refresh token for obtaining new access tokens.""" 56 | return self._refresh_token 57 | 58 | @property 59 | def expires_in(self) -> int: 60 | """Get the amount of time in seconds until the token expires.""" 61 | return self._expires_in 62 | 63 | @property 64 | def token_type(self) -> str: 65 | """Get the type of token.""" 66 | return self._token_type 67 | 68 | @property 69 | def scope(self) -> List[str]: 70 | """Get the scopes the token has permission to.""" 71 | return self._scope 72 | 73 | @property 74 | def expiration_date(self) -> datetime: 75 | """Get the date when the token expires.""" 76 | return self._expiration_date 77 | 78 | @property 79 | def is_expired(self): 80 | """Return True if the token has expired.""" 81 | return datetime.now() > self._expiration_date 82 | -------------------------------------------------------------------------------- /custom_components/smartthings_customize/pysmartthings/room.py: -------------------------------------------------------------------------------- 1 | """Defines the rooms module.""" 2 | from typing import Dict, Optional 3 | 4 | from .api import Api 5 | from .entity import Entity 6 | 7 | 8 | class Room: 9 | """Defines a SmartThings room.""" 10 | 11 | def __init__(self): 12 | """Initialize the room.""" 13 | self._room_id = None 14 | self._location_id = None 15 | self._name = None 16 | self._background_image = None 17 | 18 | def apply_data(self, data: dict): 19 | """Apply the data structure to the class.""" 20 | self._room_id = data["roomId"] 21 | self._location_id = data["locationId"] 22 | self._name = data["name"] 23 | self._background_image = data["backgroundImage"] 24 | 25 | def to_data(self) -> dict: 26 | """Get a data structure representing this entity.""" 27 | data = { 28 | "name": self._name, 29 | "backgroundImage": self._background_image, 30 | } 31 | return data 32 | 33 | @property 34 | def room_id(self) -> str: 35 | """Get the id of the room.""" 36 | return self._room_id 37 | 38 | @room_id.setter 39 | def room_id(self, value: str): 40 | """Set the id of the room.""" 41 | self._room_id = value 42 | 43 | @property 44 | def location_id(self) -> str: 45 | """Get the location id the room is part of.""" 46 | return self._location_id 47 | 48 | @location_id.setter 49 | def location_id(self, value: str): 50 | """Set the location id of the room.""" 51 | self._location_id = value 52 | 53 | @property 54 | def name(self) -> str: 55 | """Get the name of the room.""" 56 | return self._name 57 | 58 | @name.setter 59 | def name(self, value: str): 60 | """Set the name of the room.""" 61 | self._name = value 62 | 63 | @property 64 | def background_image(self) -> str: 65 | """Get the background image of the room.""" 66 | return self._background_image 67 | 68 | @background_image.setter 69 | def background_image(self, value: str): 70 | """Set the background image.""" 71 | self._background_image = value 72 | 73 | 74 | class RoomEntity(Room, Entity): 75 | """Defines a SmartThings room entity.""" 76 | 77 | def __init__( 78 | self, 79 | api: Api, 80 | data: Optional[Dict] = None, 81 | *, 82 | location_id: str = None, 83 | room_id: str = None 84 | ): 85 | """Initialize the room.""" 86 | Entity.__init__(self, api) 87 | Room.__init__(self) 88 | if data: 89 | self.apply_data(data) 90 | if location_id: 91 | self._location_id = location_id 92 | if room_id: 93 | self._room_id = room_id 94 | 95 | async def refresh(self): 96 | """Refresh the room.""" 97 | data = await self._api.get_room(self._location_id, self._room_id) 98 | if data: 99 | self.apply_data(data) 100 | 101 | async def save(self): 102 | """Save changes to the room.""" 103 | data = await self._api.update_room( 104 | self._location_id, self._room_id, self.to_data() 105 | ) 106 | if data: 107 | self.apply_data(data) 108 | -------------------------------------------------------------------------------- /custom_components/smartthings_customize/pysmartthings/scene.py: -------------------------------------------------------------------------------- 1 | """Define the scene module.""" 2 | from typing import Dict, Optional 3 | 4 | from .api import Api 5 | from .entity import Entity 6 | 7 | 8 | class Scene: 9 | """Define a scene data entity.""" 10 | 11 | def __init__(self): 12 | """Create a new instance of the Scene class.""" 13 | self._color = None 14 | self._icon = None 15 | self._location_id = None 16 | self._name = None 17 | self._scene_id = None 18 | 19 | def apply_data(self, data: dict): 20 | """Apply the data structure to the class.""" 21 | self._color = data["sceneColor"] 22 | self._icon = data["sceneIcon"] 23 | self._location_id = data["locationId"] 24 | self._name = data["sceneName"] 25 | self._scene_id = data["sceneId"] 26 | 27 | @property 28 | def color(self) -> str: 29 | """Get the color of the scene.""" 30 | return self._color 31 | 32 | @property 33 | def icon(self) -> str: 34 | """Get the icon of the scene.""" 35 | return self._icon 36 | 37 | @property 38 | def location_id(self) -> str: 39 | """Get the location this scene is in.""" 40 | return self._location_id 41 | 42 | @property 43 | def name(self) -> str: 44 | """Get the name of the scene.""" 45 | return self._name 46 | 47 | @property 48 | def scene_id(self) -> str: 49 | """Get the id of the scene.""" 50 | return self._scene_id 51 | 52 | 53 | class SceneEntity(Entity, Scene): 54 | """Define a scene entity.""" 55 | 56 | def __init__(self, api: Api, data: Optional[Dict] = None): 57 | """Create a new instance of the class.""" 58 | Entity.__init__(self, api) 59 | Scene.__init__(self) 60 | if data: 61 | self.apply_data(data) 62 | 63 | async def execute(self): 64 | """Execute the scene.""" 65 | result = await self._api.execute_scene(self._scene_id) 66 | return result == {"status": "success"} 67 | 68 | async def refresh(self): 69 | """Refresh is not implemented.""" 70 | raise NotImplementedError() 71 | 72 | async def save(self): 73 | """Save is not implemented.""" 74 | raise NotImplementedError() 75 | -------------------------------------------------------------------------------- /custom_components/smartthings_customize/scene.py: -------------------------------------------------------------------------------- 1 | """Support for scenes through the SmartThings cloud API.""" 2 | from typing import Any 3 | 4 | from homeassistant.components.scene import Scene 5 | from homeassistant.config_entries import ConfigEntry 6 | from homeassistant.core import HomeAssistant 7 | from homeassistant.helpers.entity_platform import AddEntitiesCallback 8 | 9 | from .common import * 10 | 11 | async def async_setup_entry( 12 | hass: HomeAssistant, 13 | config_entry: ConfigEntry, 14 | async_add_entities: AddEntitiesCallback, 15 | ) -> None: 16 | """Add switches for a config entry.""" 17 | broker = hass.data[DOMAIN][DATA_BROKERS][config_entry.entry_id] 18 | if SettingManager.enable_default_entities(): 19 | async_add_entities([SmartThingsScene(scene) for scene in broker.scenes.values()]) 20 | 21 | 22 | class SmartThingsScene(Scene): 23 | """Define a SmartThings scene.""" 24 | 25 | def __init__(self, scene): 26 | """Init the scene class.""" 27 | self._scene = scene 28 | 29 | async def async_activate(self, **kwargs: Any) -> None: 30 | """Activate scene.""" 31 | await self._scene.execute() 32 | 33 | @property 34 | def extra_state_attributes(self): 35 | """Get attributes about the state.""" 36 | return { 37 | "icon": self._scene.icon, 38 | "color": self._scene.color, 39 | "location_id": self._scene.location_id, 40 | } 41 | 42 | @property 43 | def name(self) -> str: 44 | """Return the name of the device.""" 45 | return self._scene.name 46 | 47 | @property 48 | def unique_id(self) -> str: 49 | """Return a unique ID.""" 50 | return self._scene.scene_id -------------------------------------------------------------------------------- /custom_components/smartthings_customize/select.py: -------------------------------------------------------------------------------- 1 | """Support for switches through the SmartThings cloud API.""" 2 | from __future__ import annotations 3 | 4 | from collections.abc import Sequence 5 | from typing import Any 6 | 7 | from .pysmartthings import Capability 8 | 9 | from homeassistant.components.select import SelectEntity 10 | from homeassistant.config_entries import ConfigEntry 11 | from homeassistant.core import HomeAssistant 12 | from homeassistant.helpers.entity_platform import AddEntitiesCallback 13 | 14 | from .common import * 15 | 16 | import logging 17 | _LOGGER = logging.getLogger(__name__) 18 | 19 | async def async_setup_entry( 20 | hass: HomeAssistant, 21 | config_entry: ConfigEntry, 22 | async_add_entities: AddEntitiesCallback, 23 | ) -> None: 24 | """Add switches for a config entry.""" 25 | broker = hass.data[DOMAIN][DATA_BROKERS][config_entry.entry_id] 26 | 27 | entities = [] 28 | settings = SettingManager.get_capa_settings(broker, Platform.SELECT) 29 | for s in settings: 30 | _LOGGER.debug("cap setting : " + str(s[1])) 31 | entities.append(SmartThingsSelect_custom(hass=hass, setting=s)) 32 | 33 | async_add_entities(entities) 34 | 35 | class SmartThingsSelect_custom(SmartThingsEntity_custom, SelectEntity): 36 | def __init__(self, hass, setting) -> None: 37 | super().__init__(hass, platform=Platform.SELECT, setting=setting) 38 | 39 | @property 40 | def options(self) -> list[str]: 41 | return self.get_attr_value(Platform.SELECT, CONF_OPTIONS, []) 42 | 43 | @property 44 | def current_option(self) -> str | None: 45 | state = self.get_attr_value(Platform.SELECT, CONF_STATE) 46 | return self.get_mapping_value(Platform.SELECT, CONF_STATE_MAPPING, state) 47 | 48 | async def async_select_option(self, option: str) -> None: 49 | option = self.get_mapping_key(Platform.SELECT, CONF_STATE_MAPPING, option) 50 | arg = [option] 51 | await self.send_command(Platform.SELECT, self.get_command(Platform.SELECT), arg) 52 | 53 | -------------------------------------------------------------------------------- /custom_components/smartthings_customize/strings.json: -------------------------------------------------------------------------------- 1 | { 2 | "config": { 3 | "step": { 4 | "user": { 5 | "title": "Confirm Callback URL", 6 | "description": "SmartThings will be configured to send push updates to Home Assistant at:\n> {webhook_url}\n\nIf this is not correct, please update your configuration, restart Home Assistant, and try again." 7 | }, 8 | "pat": { 9 | "title": "Enter Personal Access Token", 10 | "description": "Please enter a SmartThings [Personal Access Token]({token_url}) that has been created per the [instructions]({component_url}). This will be used to create the Home Assistant integration within your SmartThings account.", 11 | "data": { 12 | "access_token": "[%key:common::config_flow::data::access_token%]" 13 | } 14 | }, 15 | "select_location": { 16 | "title": "Select Location", 17 | "description": "Please select the SmartThings Location you wish to add to Home Assistant. We will then open a new window and ask you to login and authorize installation of the Home Assistant integration into the selected location.", 18 | "data": { "location_id": "[%key:common::config_flow::data::location%]" } 19 | }, 20 | "authorize": { "title": "Authorize Home Assistant" } 21 | }, 22 | "abort": { 23 | "invalid_webhook_url": "Home Assistant is not configured correctly to receive updates from SmartThings. The webhook URL is invalid:\n> {webhook_url}\n\nPlease update your configuration per the [instructions]({component_url}), restart Home Assistant, and try again.", 24 | "no_available_locations": "There are no available SmartThings Locations to set up in Home Assistant." 25 | }, 26 | "error": { 27 | "token_invalid_format": "The token must be in the UID/GUID format", 28 | "token_unauthorized": "The token is invalid or no longer authorized.", 29 | "token_forbidden": "The token does not have the required OAuth scopes.", 30 | "app_setup_error": "Unable to set up the SmartApp. Please try again.", 31 | "webhook_error": "SmartThings could not validate the webhook URL. Please ensure the webhook URL is reachable from the internet and try again." 32 | } 33 | }, 34 | "options": { 35 | "step": { 36 | "init": { 37 | "title": "SmartThings Customize Configuration", 38 | "data": { 39 | "enable_default_entities": "enable default entities", 40 | "enable_syntax_property": "enable syntax property", 41 | "resetting_entities": "resetting entities" 42 | } 43 | } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /custom_components/smartthings_customize/switch.py: -------------------------------------------------------------------------------- 1 | """Support for switches through the SmartThings cloud API.""" 2 | from __future__ import annotations 3 | 4 | from collections.abc import Sequence 5 | from typing import Any 6 | 7 | from .pysmartthings import Capability 8 | 9 | from homeassistant.components.switch import SwitchEntity 10 | from homeassistant.config_entries import ConfigEntry 11 | from homeassistant.core import HomeAssistant 12 | from homeassistant.helpers.entity_platform import AddEntitiesCallback 13 | 14 | from homeassistant.const import STATE_ON, STATE_OFF 15 | 16 | from . import SmartThingsEntity 17 | from .const import * 18 | 19 | import logging 20 | _LOGGER = logging.getLogger(__name__) 21 | 22 | from .common import * 23 | 24 | 25 | async def async_setup_entry( 26 | hass: HomeAssistant, 27 | config_entry: ConfigEntry, 28 | async_add_entities: AddEntitiesCallback, 29 | ) -> None: 30 | """Add switches for a config entry.""" 31 | broker = hass.data[DOMAIN][DATA_BROKERS][config_entry.entry_id] 32 | entities = [] 33 | 34 | if SettingManager.enable_default_entities(): 35 | for device in broker.devices.values(): 36 | if SettingManager.allow_device(device.device_id) == False: 37 | continue 38 | if broker.any_assigned(device.device_id, Platform.SWITCH): 39 | entities.append(SmartThingsSwitch(device)) 40 | 41 | settings = SettingManager.get_capa_settings(broker, Platform.SWITCH) 42 | for s in settings: 43 | _LOGGER.debug("cap setting : " + str(s[1])) 44 | entities.append(SmartThingsSwitch_custom(hass=hass, setting=s)) 45 | 46 | async_add_entities(entities) 47 | 48 | def get_capabilities(capabilities: Sequence[str]) -> Sequence[str] | None: 49 | """Return all capabilities supported if minimum required are present.""" 50 | # Must be able to be turned on/off. 51 | capa = [] 52 | if Capability.switch in capabilities: 53 | capa.append(Capability.switch) 54 | if Capability.energy_meter in capabilities: 55 | capa.append(Capability.energy_meter) 56 | if Capability.power_meter in capabilities: 57 | capa.append(Capability.power_meter) 58 | 59 | return capa if len(capa) > 0 else None 60 | 61 | class SmartThingsSwitch(SmartThingsEntity, SwitchEntity): 62 | 63 | async def async_turn_off(self, **kwargs: Any) -> None: 64 | await self._device.switch_off(set_status=True) 65 | self.async_write_ha_state() 66 | 67 | async def async_turn_on(self, **kwargs: Any) -> None: 68 | await self._device.switch_on(set_status=True) 69 | self.async_write_ha_state() 70 | 71 | @property 72 | def is_on(self) -> bool: 73 | return self._device.status.switch 74 | 75 | class SmartThingsSwitch_custom(SmartThingsEntity_custom, SwitchEntity): 76 | def __init__(self, hass, setting) -> None: 77 | super().__init__(hass, platform=Platform.SWITCH,setting=setting) 78 | 79 | async def async_turn_off(self, **kwargs: Any) -> None: 80 | await self.send_command(Platform.SWITCH, self.get_command(Platform.SWITCH, {STATE_OFF:STATE_OFF}).get(STATE_OFF), self.get_argument(Platform.SWITCH, {STATE_OFF:[]}).get(STATE_OFF, [])) 81 | 82 | async def async_turn_on(self, **kwargs: Any) -> None: 83 | await self.send_command(Platform.SWITCH, self.get_command(Platform.SWITCH, {STATE_ON:STATE_ON}).get(STATE_ON), self.get_argument(Platform.SWITCH, {STATE_ON:[]}).get(STATE_ON, [])) 84 | 85 | @property 86 | def is_on(self) -> bool: 87 | value = self.get_attr_value(Platform.SWITCH, CONF_STATE) 88 | on_state = self.get_attr_value(Platform.SWITCH, ON_STATE) 89 | return value in on_state 90 | 91 | -------------------------------------------------------------------------------- /custom_components/smartthings_customize/text.py: -------------------------------------------------------------------------------- 1 | """Support for texts through the SmartThings cloud API.""" 2 | from __future__ import annotations 3 | 4 | from collections.abc import Sequence 5 | from typing import Any 6 | 7 | from .pysmartthings import Capability 8 | from .pysmartthings.device import Status 9 | 10 | from homeassistant.components.text import TextEntity, TextMode, MAX_LENGTH_STATE_STATE, ATTR_MAX, ATTR_MIN, ATTR_MODE, ATTR_PATTERN 11 | from homeassistant.config_entries import ConfigEntry 12 | from homeassistant.core import HomeAssistant 13 | from homeassistant.helpers.entity_platform import AddEntitiesCallback 14 | 15 | from .common import * 16 | 17 | import logging 18 | _LOGGER = logging.getLogger(__name__) 19 | 20 | async def async_setup_entry( 21 | hass: HomeAssistant, 22 | config_entry: ConfigEntry, 23 | async_add_entities: AddEntitiesCallback, 24 | ) -> None: 25 | """Add switches for a config entry.""" 26 | broker = hass.data[DOMAIN][DATA_BROKERS][config_entry.entry_id] 27 | 28 | entities = [] 29 | settings = SettingManager.get_capa_settings(broker, Platform.TEXT) 30 | for s in settings: 31 | _LOGGER.debug("cap setting : " + str(s[1])) 32 | entities.append(SmartThingsText_custom(hass=hass, setting=s)) 33 | 34 | async_add_entities(entities) 35 | 36 | class SmartThingsText_custom(SmartThingsEntity_custom, TextEntity): 37 | def __init__(self, hass, setting) -> None: 38 | super().__init__(hass, platform=Platform.TEXT, setting=setting) 39 | 40 | @property 41 | def mode(self) -> TextMode: 42 | return self.get_attr_value(Platform.TEXT, ATTR_MODE, TextMode.TEXT) 43 | 44 | @property 45 | def native_max(self) -> int: 46 | return self.get_attr_value(Platform.TEXT, ATTR_MAX, MAX_LENGTH_STATE_STATE) 47 | 48 | @property 49 | def native_min(self) -> int: 50 | return self.get_attr_value(Platform.TEXT, ATTR_MIN, 0) 51 | 52 | @property 53 | def pattern(self) -> str | None: 54 | return self.get_attr_value(Platform.TEXT, ATTR_PATTERN, "") 55 | 56 | @property 57 | def native_value(self) -> str | None: 58 | return self.get_attr_value(Platform.TEXT, CONF_STATE, "") 59 | 60 | async def async_set_value(self, value: str) -> None: 61 | arg = [] 62 | if self.get_command(Platform.TEXT) == "bixbyCommand": 63 | arg.append("search_all") 64 | arg.append(value) 65 | await self.send_command(Platform.TEXT, self.get_command(Platform.TEXT), arg) 66 | -------------------------------------------------------------------------------- /custom_components/smartthings_customize/translations/bg.json: -------------------------------------------------------------------------------- 1 | { 2 | "config": { 3 | "error": { 4 | "app_setup_error": "\u041d\u0435\u0443\u0441\u043f\u0435\u0448\u043d\u043e \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u0432\u0430\u043d\u0435 \u043d\u0430 SmartApp. \u041c\u043e\u043b\u044f \u043e\u043f\u0438\u0442\u0430\u0439\u0442\u0435 \u043e\u0442\u043d\u043e\u0432\u043e.", 5 | "token_forbidden": "\u041a\u043e\u0434\u044a\u0442 \u043d\u044f\u043c\u0430 \u0438\u0437\u0438\u0441\u043a\u0443\u0435\u043c\u0438\u0442\u0435 OAuth \u043f\u0440\u0430\u0432\u0430.", 6 | "token_invalid_format": "\u041a\u043e\u0434\u044a\u0442 \u0442\u0440\u044f\u0431\u0432\u0430 \u0434\u0430 \u0435 \u0432\u044a\u0432 \u0444\u043e\u0440\u043c\u0430\u0442 UID/GUID", 7 | "token_unauthorized": "\u041a\u043e\u0434\u044a\u0442 \u0435 \u043d\u0435\u0432\u0430\u043b\u0438\u0434\u0435\u043d \u0438\u043b\u0438 \u0432\u0435\u0447\u0435 \u043d\u0435 \u0435 \u043e\u0442\u043e\u0440\u0438\u0437\u0438\u0440\u0430\u043d.", 8 | "webhook_error": "SmartThings \u043d\u0435 \u043c\u043e\u0436\u0430 \u0434\u0430 \u0432\u0430\u043b\u0438\u0434\u0438\u0440\u0430 \u0430\u0434\u0440\u0435\u0441\u0430, \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u0430\u043d \u0432 `base_url`. \u041c\u043e\u043b\u044f, \u043f\u0440\u0435\u0433\u043b\u0435\u0434\u0430\u0439\u0442\u0435 \u0438\u0437\u0438\u0441\u043a\u0432\u0430\u043d\u0438\u044f\u0442\u0430 \u0437\u0430 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430." 9 | }, 10 | "step": { 11 | "select_location": { 12 | "data": { 13 | "location_id": "\u041c\u0435\u0441\u0442\u043e\u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u0435" 14 | }, 15 | "title": "\u0418\u0437\u0431\u0435\u0440\u0435\u0442\u0435 \u043c\u0435\u0441\u0442\u043e\u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u0435" 16 | }, 17 | "user": { 18 | "description": "\u041c\u043e\u043b\u044f, \u0432\u044a\u0432\u0435\u0434\u0435\u0442\u0435 SmartThings [Personal Access Token] ( {token_url} ), \u043a\u043e\u0439\u0442\u043e \u0435 \u0441\u044a\u0437\u0434\u0430\u0434\u0435\u043d \u0441\u043f\u043e\u0440\u0435\u0434 [\u0443\u043a\u0430\u0437\u0430\u043d\u0438\u044f\u0442\u0430] ( {component_url} ).", 19 | "title": "\u0412\u044a\u0432\u0435\u0434\u0435\u0442\u0435 \u043a\u043e\u0434 \u0437\u0430 \u0434\u043e\u0441\u0442\u044a\u043f (Personal Access Token)" 20 | } 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /custom_components/smartthings_customize/translations/ca.json: -------------------------------------------------------------------------------- 1 | { 2 | "config": { 3 | "abort": { 4 | "invalid_webhook_url": "Home Assistant not est\u00e0 configurat correctament per a rebre actualitzacions de SmartThings.\nEl seg\u00fcent URL webhook no \u00e9s v\u00e0lid:\n> {webhook_url}\n\nActualitza la teva configuraci\u00f3 segons les [instruccions]({component_url}), reinicia Home Assistant i torna-ho a provar.", 5 | "no_available_locations": "No hi ha ubicacions SmartThings configurables amb Home Assistant." 6 | }, 7 | "error": { 8 | "app_setup_error": "No s'ha pogut configurar SmartApp. Siusplau, torna-ho a provar.", 9 | "token_forbidden": "El token d'autenticaci\u00f3 no t\u00e9 cont\u00e9 els apartats OAuth obligatoris.", 10 | "token_invalid_format": "El token d'autenticaci\u00f3 ha d'estar en format UID/GUID", 11 | "token_unauthorized": "El token d'autenticaci\u00f3 no \u00e9s v\u00e0lid o ja no est\u00e0 autoritzat.", 12 | "webhook_error": "SmartThings no ha pogut validar l'URL webhook. Comprova que l'URL pot ser accedit des d'Internet i torna-ho a provar." 13 | }, 14 | "step": { 15 | "authorize": { 16 | "title": "Autoritzaci\u00f3 de Home Assistant" 17 | }, 18 | "pat": { 19 | "data": { 20 | "access_token": "[%key::common::config_flow::data::access_token%]" 21 | }, 22 | "description": "Introdueix un [token d'acc\u00e9s personal]({token_url}) d'SmartThings creat a partir de les [instruccions]({component_url}). S'utilitzar\u00e0 per crear la integraci\u00f3 de Home Assistant dins el teu compte de SmartThings.", 23 | "title": "Introdueix el token d'acc\u00e9s personal" 24 | }, 25 | "select_location": { 26 | "data": { 27 | "location_id": "Ubicaci\u00f3" 28 | }, 29 | "description": "Selecciona la ubicaci\u00f3 de SmartThings que vols afegir a Home Assistant. A continuaci\u00f3, s'obrir\u00e0 una finestra on se't demanar\u00e0 que inici\u00efs sessi\u00f3 i autoritzis la instal\u00b7laci\u00f3 de la integraci\u00f3 de Home Assistant a la ubicaci\u00f3 seleccionada.", 30 | "title": "Selecci\u00f3 d'ubicaci\u00f3" 31 | }, 32 | "user": { 33 | "description": "SmartThings es configurar\u00e0 per a enviar actualitzacions push a Home Assistant a:\n> {webhook_url}\n\nSi no \u00e9s correcte, actualitza la teva configuraci\u00f3, reinicia Home Assistant i torna-ho a provar.", 34 | "title": "Introdueix el token d'autenticaci\u00f3 personal" 35 | } 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /custom_components/smartthings_customize/translations/cs.json: -------------------------------------------------------------------------------- 1 | { 2 | "config": { 3 | "abort": { 4 | "invalid_webhook_url": "Home Assistant nen\u00ed spr\u00e1vn\u011b nastaven pro p\u0159\u00edjem aktualizac\u00ed ze SmartThings. URL je neplatn\u00e1:\n> {webhook_url}\n\nAktualizujte sv\u00e9 nastaven\u00ed podle [pokyn\u016f]({component_url}), restartujte Home Assistant a zkuste to znovu.", 5 | "no_available_locations": "V dom\u00e1c\u00ed asistenti nejsou k dispozici \u017e\u00e1dn\u00e1 um\u00edst\u011bn\u00ed SmartThings." 6 | }, 7 | "error": { 8 | "app_setup_error": "Nelze nastavit SmartApp. Pros\u00edm zkuste to znovu.", 9 | "token_forbidden": "Token nem\u00e1 po\u017eadovan\u00e9 rozsahy OAuth.", 10 | "token_invalid_format": "Token mus\u00ed b\u00fdt ve form\u00e1tu UID/GUID.", 11 | "token_unauthorized": "Token je neplatn\u00fd nebo ji\u017e nen\u00ed autorizov\u00e1n.", 12 | "webhook_error": "SmartThings nemohly ov\u011b\u0159it webhook URL. Zkontrolujte, zda je URL dostupn\u00e1 z internetu, a zkuste to znovu." 13 | }, 14 | "step": { 15 | "authorize": { 16 | "title": "Autorizovat Home Assistant" 17 | }, 18 | "pat": { 19 | "data": { 20 | "access_token": "P\u0159\u00edstupov\u00fd token" 21 | }, 22 | "description": "Zadejte [osobn\u00ed p\u0159\u00edstupov\u00fd token]({token_url}) SmartThings, kter\u00fd byl vytvo\u0159en podle [pokyn\u016f]({component_url}). Ten se pou\u017eije k vytvo\u0159en\u00ed integrace Home Assistant ve va\u0161em \u00fa\u010dtu SmartThings.", 23 | "title": "Zadejte osobn\u00ed p\u0159\u00edstupov\u00fd token" 24 | }, 25 | "select_location": { 26 | "data": { 27 | "location_id": "Um\u00edst\u011bn\u00ed" 28 | }, 29 | "description": "Vyberte um\u00edst\u011bn\u00ed SmartThings, kter\u00e9 chcete p\u0159idat do Home Assistant. Pot\u00e9 otev\u0159eme nov\u00e9 okno a po\u017e\u00e1d\u00e1me v\u00e1s o p\u0159ihl\u00e1\u0161en\u00ed a autorizaci instalace integrace Home Assistant do vybran\u00e9ho um\u00edst\u011bn\u00ed.", 30 | "title": "Vyberte um\u00edst\u011bn\u00ed" 31 | }, 32 | "user": { 33 | "description": "SmartThings bude nastaveno tak, aby odes\u00edlalo aktualizace do Home Assistant na: > {webhook_url}\n\nPokud to nen\u00ed spr\u00e1vn\u011b, zm\u011bnte pros\u00edm sv\u00e9 nastaven\u00ed, restartujte Home Assistant a zkuste to znovu.", 34 | "title": "Potvr\u010fte URL adresu zp\u011btn\u00e9ho vol\u00e1n\u00ed" 35 | } 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /custom_components/smartthings_customize/translations/da.json: -------------------------------------------------------------------------------- 1 | { 2 | "config": { 3 | "error": { 4 | "app_setup_error": "SmartApp kunne ikke konfigureres. Pr\u00f8v igen.", 5 | "token_forbidden": "Adgangstoken er ikke indenfor OAuth", 6 | "token_invalid_format": "Adgangstoken skal v\u00e6re i UID/GUID format", 7 | "token_unauthorized": "Adgangstoken er ugyldigt eller ikke l\u00e6ngere godkendt.", 8 | "webhook_error": "SmartThings kunne ikke validere slutpunktet konfigureret i `base_url`. Gennemg\u00e5 venligst komponentkravene." 9 | }, 10 | "step": { 11 | "user": { 12 | "description": "Indtast venligst en SmartThings [Personal Access Token]({token_url}), som er oprettet if\u00f8lge [instruktionerne]({component_url}).", 13 | "title": "Indtast personlig adgangstoken" 14 | } 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /custom_components/smartthings_customize/translations/de.json: -------------------------------------------------------------------------------- 1 | { 2 | "config": { 3 | "abort": { 4 | "invalid_webhook_url": "Home Assistant ist nicht richtig konfiguriert, um Updates von SmartThings zu erhalten. Die Webhook-URL ist ung\u00fcltig: \n > {webhook_url} \n\nBitte aktualisiere deine Konfiguration gem\u00e4\u00df den [Anweisungen] ({component_url}), starte den Home Assistant neu und versuche es erneut.", 5 | "no_available_locations": "In Home Assistant sind keine SmartThings-Standorte zum Einrichten verf\u00fcgbar." 6 | }, 7 | "error": { 8 | "app_setup_error": "SmartApp kann nicht eingerichtet werden. Bitte versuche es erneut.", 9 | "token_forbidden": "Das Token verf\u00fcgt nicht \u00fcber die erforderlichen OAuth-Bereiche.", 10 | "token_invalid_format": "Das Token muss im UID/GUID-Format vorliegen.", 11 | "token_unauthorized": "Das Token ist ung\u00fcltig oder nicht mehr autorisiert.", 12 | "webhook_error": "SmartThings konnte die Webhook-URL nicht \u00fcberpr\u00fcfen. Bitte stelle sicher, dass die Webhook-URL \u00fcber das Internet erreichbar ist, und versuche es erneut." 13 | }, 14 | "step": { 15 | "authorize": { 16 | "title": "Home Assistant autorisieren" 17 | }, 18 | "pat": { 19 | "data": { 20 | "access_token": "Zugangstoken" 21 | }, 22 | "description": "Bitte gib ein SmartThings [Personal Access Token] ({token_url}) ein, das gem\u00e4\u00df den [Anweisungen] ({component_url}) erstellt wurde. Dies wird zur Erstellung der Home Assistant-Integration in deinem SmartThings-Konto verwendet.", 23 | "title": "Gib den pers\u00f6nlichen Zugangstoken an" 24 | }, 25 | "select_location": { 26 | "data": { 27 | "location_id": "Standort" 28 | }, 29 | "description": "Bitte w\u00e4hle den SmartThings-Standort aus, den du Home Assistant hinzuf\u00fcgen m\u00f6chtest. Wir \u00f6ffnen dann ein neues Fenster und bitten dich, sich anzumelden und die Installation der Home Assistant-Integration am ausgew\u00e4hlten Standort zu autorisieren.", 30 | "title": "Standort ausw\u00e4hlen" 31 | }, 32 | "user": { 33 | "description": "SmartThings wird so konfiguriert, dass Push-Updates an Home Assistant gesendet werden an die URL: \n > {webhook_url} \n\nWenn dies nicht korrekt ist, aktualisiere bitte deine Konfiguration, starte Home Assistant neu und versuche es erneut.", 34 | "title": "R\u00fcckruf-URL best\u00e4tigen" 35 | } 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /custom_components/smartthings_customize/translations/en.json: -------------------------------------------------------------------------------- 1 | { 2 | "config": { 3 | "abort": { 4 | "invalid_webhook_url": "Home Assistant is not configured correctly to receive updates from SmartThings. The webhook URL is invalid:\n> {webhook_url}\n\nPlease update your configuration per the [instructions]({component_url}), restart Home Assistant, and try again.", 5 | "no_available_locations": "There are no available SmartThings Locations to setup in Home Assistant." 6 | }, 7 | "error": { 8 | "app_setup_error": "Unable to setup the SmartApp. Please try again.", 9 | "token_forbidden": "The token does not have the required OAuth scopes.", 10 | "token_invalid_format": "The token must be in the UID/GUID format", 11 | "token_unauthorized": "The token is invalid or no longer authorized.", 12 | "webhook_error": "SmartThings could not validate the webhook URL. Please ensure the webhook URL is reachable from the internet and try again." 13 | }, 14 | "step": { 15 | "authorize": { 16 | "title": "Authorize Home Assistant" 17 | }, 18 | "pat": { 19 | "data": { 20 | "access_token": "Access Token" 21 | }, 22 | "description": "Please enter a SmartThings [Personal Access Token]({token_url}) that has been created per the [instructions]({component_url}). This will be used to create the Home Assistant integration within your SmartThings account.", 23 | "title": "Enter Personal Access Token" 24 | }, 25 | "select_location": { 26 | "data": { 27 | "location_id": "Location" 28 | }, 29 | "description": "Please select the SmartThings Location you wish to add to Home Assistant. We will then open a new window and ask you to login and authorize installation of the Home Assistant integration into the selected location.", 30 | "title": "Select Location" 31 | }, 32 | "user": { 33 | "description": "SmartThings will be configured to send push updates to Home Assistant at:\n> {webhook_url}\n\nIf this is not correct, please update your configuration, restart Home Assistant, and try again.", 34 | "title": "Confirm Callback URL" 35 | } 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /custom_components/smartthings_customize/translations/en_GB.json: -------------------------------------------------------------------------------- 1 | { 2 | "config": { 3 | "error": { 4 | "token_unauthorized": "The token is invalid or no longer authorised." 5 | }, 6 | "step": { 7 | "authorize": { 8 | "title": "Authorise Home Assistant" 9 | }, 10 | "select_location": { 11 | "description": "Please select the SmartThings Location you wish to add to Home Assistant. We will then open a new window and ask you to login and authorise installation of the Home Assistant integration into the selected location." 12 | } 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /custom_components/smartthings_customize/translations/es-419.json: -------------------------------------------------------------------------------- 1 | { 2 | "config": { 3 | "abort": { 4 | "invalid_webhook_url": "Home Assistant no est\u00e1 configurado correctamente para recibir actualizaciones de SmartThings. La URL del webhook no es v\u00e1lida: \n > {webhook_url} \n\nActualice su configuraci\u00f3n seg\u00fan las [instrucciones] ({component_url}), reinicie Home Assistant e intente nuevamente.", 5 | "no_available_locations": "No hay ubicaciones SmartThings disponibles para configurar en Home Assistant." 6 | }, 7 | "error": { 8 | "app_setup_error": "No se puede configurar el SmartApp. Por favor, int\u00e9ntelo de nuevo.", 9 | "token_forbidden": "El token no tiene los \u00e1mbitos de OAuth necesarios.", 10 | "token_invalid_format": "El token debe estar en formato UID/GUID", 11 | "token_unauthorized": "El token no es v\u00e1lido o ya no est\u00e1 autorizado.", 12 | "webhook_error": "SmartThings no pudo validar el endpoint configurado en `base_url`. Por favor, revise los requisitos de los componentes." 13 | }, 14 | "step": { 15 | "authorize": { 16 | "title": "Autorizar Home Assistant" 17 | }, 18 | "pat": { 19 | "description": "Ingrese un SmartThings [Token de acceso personal] ({token_url}) que se ha creado seg\u00fan las [instrucciones] ({component_url}). Esto se usar\u00e1 para crear la integraci\u00f3n de Home Assistant dentro de su cuenta SmartThings.", 20 | "title": "Ingresar token de acceso personal" 21 | }, 22 | "select_location": { 23 | "data": { 24 | "location_id": "Ubicaci\u00f3n" 25 | }, 26 | "description": "Seleccione la ubicaci\u00f3n de SmartThings que desea agregar a Home Assistant. Luego, abriremos una nueva ventana y le pediremos que inicie sesi\u00f3n y autorice la instalaci\u00f3n de la integraci\u00f3n de Home Assistant en la ubicaci\u00f3n seleccionada.", 27 | "title": "Seleccionar ubicaci\u00f3n" 28 | }, 29 | "user": { 30 | "description": "SmartThings se configurar\u00e1 para enviar actualizaciones push a Home Assistant en: \n > {webhook_url} \n\nSi esto no es correcto, actualice su configuraci\u00f3n, reinicie Home Assistant e intente nuevamente.", 31 | "title": "Ingresar token de acceso personal" 32 | } 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /custom_components/smartthings_customize/translations/es.json: -------------------------------------------------------------------------------- 1 | { 2 | "config": { 3 | "abort": { 4 | "invalid_webhook_url": "Home Assistant no est\u00e1 configurado correctamente para recibir actualizaciones de SmartThings. La URL del webhook no es v\u00e1lida: \n > {webhook_url} \n\n Actualiza tu configuraci\u00f3n seg\u00fan las [instrucciones]({component_url}), reinicia Home Assistant e int\u00e9ntalo de nuevo.", 5 | "no_available_locations": "No hay Ubicaciones SmartThings disponibles para configurar en Home Assistant." 6 | }, 7 | "error": { 8 | "app_setup_error": "No se pudo configurar el SmartApp. Por favor, int\u00e9ntelo de nuevo.", 9 | "token_forbidden": "El token no tiene los \u00e1mbitos de OAuth necesarios.", 10 | "token_invalid_format": "El token debe estar en formato UID/GUID", 11 | "token_unauthorized": "El token no es v\u00e1lido o ya no est\u00e1 autorizado.", 12 | "webhook_error": "SmartThings no ha podido validar el endpoint configurado en 'base_url'. Por favor, revisa los requisitos del componente." 13 | }, 14 | "step": { 15 | "authorize": { 16 | "title": "Autorizar a Home Assistant" 17 | }, 18 | "pat": { 19 | "data": { 20 | "access_token": "Token de acceso" 21 | }, 22 | "description": "Por favor, introduce un [token de acceso personal]({token_url}) de SmartThings creado seg\u00fan las [instrucciones]({component_url}). Se usar\u00e1 para crear la integraci\u00f3n de Home Assistant en tu cuenta de SmartThings.", 23 | "title": "Introduce el token de acceso personal" 24 | }, 25 | "select_location": { 26 | "data": { 27 | "location_id": "Ubicaci\u00f3n" 28 | }, 29 | "description": "Selecciona la Ubicaci\u00f3n SmartThings que quieres a\u00f1adir a Home Assistant. Se abrir\u00e1 una nueva ventana que te pedir\u00e1 que inicies sesi\u00f3n y autorices la instalaci\u00f3n de la integraci\u00f3n de Home Assistant en la ubicaci\u00f3n seleccionada.", 30 | "title": "Seleccionar Ubicaci\u00f3n" 31 | }, 32 | "user": { 33 | "description": "Los SmartThings se configurar\u00e1n para enviar las actualizaciones a Home Assistant en:\n> {webhook_url}\n\nSi esto no es correcto, por favor, actualiza tu configuraci\u00f3n, reinicia Home Assistant e int\u00e9ntalo de nuevo.", 34 | "title": "Confirmar URL de devoluci\u00f3n de llamada" 35 | } 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /custom_components/smartthings_customize/translations/et.json: -------------------------------------------------------------------------------- 1 | { 2 | "config": { 3 | "abort": { 4 | "invalid_webhook_url": "Home Assistant pole SmartThingsilt v\u00e4rskenduste saamiseks \u00f5igesti seadistatud. Veebikonksu URL on vale:\n > {webhook_url} \n\n V\u00e4rskenda oma seadeid vastavalt [juhistele] ( {component_url} ), taask\u00e4ivita Home Assistant ja proovi uuesti.", 5 | "no_available_locations": "Home Assistanti seadistamiseks pole saadaval \u00fchtegi SmartThingsi asukohta." 6 | }, 7 | "error": { 8 | "app_setup_error": "SmartAppi pole v\u00f5imalik seadistada. Palun proovi uuesti.", 9 | "token_forbidden": "Fraasil puuduvad n\u00f5utavad OAuthi v\u00e4\u00e4rtused.", 10 | "token_invalid_format": "Fraas peab olema UID / GUID-vormingus", 11 | "token_unauthorized": "Luba ei sobi v\u00f5i on kehtetu.", 12 | "webhook_error": "SmartThings ei saanud veebihaagi URL-i kinnitada. Veendu, et veebihaagi URL oleks Internetis k\u00e4ttesaadav, ja proovi uuesti." 13 | }, 14 | "step": { 15 | "authorize": { 16 | "title": "Volita Home Assistant" 17 | }, 18 | "pat": { 19 | "data": { 20 | "access_token": "Juurdep\u00e4\u00e4sut\u00f5end" 21 | }, 22 | "description": "Sisesta SmartThingsi [isiklik juurdep\u00e4\u00e4suluba] ( {token_url} ), mis on loodud vastavalt [juhistele] ( {component_url} ). Seda kasutatakse Home Assistanti sidumise loomiseks SmartThingsi kontol.", 23 | "title": "Sisesta isiklik juurdep\u00e4\u00e4suluba (PAT)" 24 | }, 25 | "select_location": { 26 | "data": { 27 | "location_id": "Asukoht" 28 | }, 29 | "description": "Vali SmartThingsi asukoht, mille soovid lisada Home Assistanti. Seej\u00e4rel avame uue akna ja palume sisse logida ning anda volitus paigaldada Home Assistanti sidumine valitud asukoha jaoks.", 30 | "title": "Vali asukoht" 31 | }, 32 | "user": { 33 | "description": "SmartThings seadistatakse saatma v\u00e4rskendusi Home Assistanti aadressil:\n > {webhook_url}\n\n Kui see pole \u00f5ige, v\u00e4rskenda oma seadeid, taask\u00e4ivita Home Assistant ja proovi uuesti.", 34 | "title": "Kinnita tagasisideURL" 35 | } 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /custom_components/smartthings_customize/translations/fr.json: -------------------------------------------------------------------------------- 1 | { 2 | "config": { 3 | "abort": { 4 | "invalid_webhook_url": "Home Assistant n'est pas configur\u00e9 correctement pour recevoir les mises \u00e0 jour de SmartThings. L'URL du webhook n'est pas valide: \n > {webhook_url} \n\n Veuillez mettre \u00e0 jour votre configuration en suivant les [instructions] ({component_url}), red\u00e9marrez Home Assistant et r\u00e9essayez.", 5 | "no_available_locations": "Il n'y a pas d'emplacements SmartThings disponibles \u00e0 configurer dans Home Assistant." 6 | }, 7 | "error": { 8 | "app_setup_error": "Impossible de configurer la SmartApp. Veuillez r\u00e9essayer.", 9 | "token_forbidden": "Le jeton n'a pas les port\u00e9es OAuth requises.", 10 | "token_invalid_format": "Le jeton doit \u00eatre au format UID / GUID", 11 | "token_unauthorized": "Le jeton est invalide ou n'est plus autoris\u00e9.", 12 | "webhook_error": "SmartThings n'a pas pu valider le point de terminaison configur\u00e9 en \u00ab\u00a0base_url\u00a0\u00bb. Veuillez consulter les exigences du composant." 13 | }, 14 | "step": { 15 | "authorize": { 16 | "title": "Autoriser Home Assistant" 17 | }, 18 | "pat": { 19 | "data": { 20 | "access_token": "Jeton d'acc\u00e8s" 21 | }, 22 | "description": "Veuillez saisir un [jeton d'acc\u00e8s personnel] {token_url} ( {token_url} ) qui a \u00e9t\u00e9 cr\u00e9\u00e9 conform\u00e9ment aux [instructions] ( {component_url} ). Cela sera utilis\u00e9 pour cr\u00e9er l'int\u00e9gration de Home Assistant dans votre compte SmartThings.", 23 | "title": "Entrer un jeton d'acc\u00e8s personnel" 24 | }, 25 | "select_location": { 26 | "data": { 27 | "location_id": "Emplacement" 28 | }, 29 | "description": "Veuillez s\u00e9lectionner l'emplacement SmartThings que vous souhaitez ajouter \u00e0 Home Assistant. Nous ouvrirons alors une nouvelle fen\u00eatre et vous demanderons de vous connecter et d'autoriser l'installation de l'int\u00e9gration de Home Assistant \u00e0 l'emplacement s\u00e9lectionn\u00e9.", 30 | "title": "S\u00e9lectionnez l'emplacement" 31 | }, 32 | "user": { 33 | "description": "SmartThings sera configur\u00e9 pour envoyer des mises \u00e0 jour push \u00e0 Home Assistant \u00e0 l'adresse: \n > {webhook_url} \n\n Si ce n'est pas le cas, mettez \u00e0 jour votre configuration, red\u00e9marrez Home Assistant et r\u00e9essayez.", 34 | "title": "Entrer un jeton d'acc\u00e8s personnel" 35 | } 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /custom_components/smartthings_customize/translations/he.json: -------------------------------------------------------------------------------- 1 | { 2 | "config": { 3 | "abort": { 4 | "invalid_webhook_url": "\u05ea\u05e6\u05d5\u05e8\u05ea Home Assistant \u05d0\u05d9\u05e0\u05d4 \u05de\u05d5\u05d2\u05d3\u05e8\u05ea \u05db\u05e8\u05d0\u05d5\u05d9 \u05dc\u05e7\u05d1\u05dc\u05ea \u05e2\u05d3\u05db\u05d5\u05e0\u05d9\u05dd \u05de-SmartThings. \u05db\u05ea\u05d5\u05d1\u05ea \u05d4\u05d0\u05ea\u05e8 \u05e9\u05dc webhook \u05d0\u05d9\u05e0\u05d4 \u05d7\u05d5\u05e7\u05d9\u05ea:\n> {webhook_url}\n\n\u05e0\u05d0 \u05dc\u05e2\u05d3\u05db\u05df \u05d0\u05ea \u05d4\u05ea\u05e6\u05d5\u05e8\u05d4 \u05dc\u05e4\u05d9 [\u05d4\u05d4\u05d5\u05e8\u05d0\u05d5\u05ea]({component_url}), \u05dc\u05d0\u05d7\u05e8 \u05de\u05db\u05df \u05dc\u05d4\u05e4\u05e2\u05d9\u05dc \u05de\u05d7\u05d3\u05e9 \u05d0\u05ea Home Assistant \u05d5\u05dc\u05e0\u05e1\u05d5\u05ea \u05e9\u05d5\u05d1.", 5 | "no_available_locations": "\u05d0\u05d9\u05df \u05de\u05d9\u05e7\u05d5\u05de\u05d9 SmartThings \u05d6\u05de\u05d9\u05e0\u05d9\u05dd \u05dc\u05d4\u05ea\u05e7\u05e0\u05d4 \u05d1-Home Assistant." 6 | }, 7 | "error": { 8 | "app_setup_error": "\u05d0\u05d9\u05df \u05d0\u05e4\u05e9\u05e8\u05d5\u05ea \u05dc\u05d4\u05d2\u05d3\u05d9\u05e8 \u05d0\u05ea SmartApp. \u05e0\u05d0 \u05e0\u05e1\u05d4 \u05e9\u05d5\u05d1.", 9 | "token_forbidden": "\u05dc\u05d0\u05e1\u05d9\u05de\u05d5\u05df \u05d0\u05d9\u05df \u05d0\u05ea \u05d8\u05d5\u05d5\u05d7\u05d9 OAuth \u05d4\u05d3\u05e8\u05d5\u05e9\u05d9\u05dd.", 10 | "token_invalid_format": "\u05d4\u05d0\u05e1\u05d9\u05de\u05d5\u05df \u05d7\u05d9\u05d9\u05d1 \u05dc\u05d4\u05d9\u05d5\u05ea \u05d1\u05e4\u05d5\u05e8\u05de\u05d8 UID / GUID", 11 | "token_unauthorized": "\u05d4\u05d0\u05e1\u05d9\u05de\u05d5\u05df \u05d0\u05d9\u05e0\u05d5 \u05d7\u05d5\u05e7\u05d9 \u05d0\u05d5 \u05d0\u05d9\u05e0\u05d5 \u05de\u05d5\u05e8\u05e9\u05d4 \u05e2\u05d5\u05d3.", 12 | "webhook_error": "SmartThings \u05dc\u05d0 \u05d4\u05e6\u05dc\u05d9\u05d7 \u05dc\u05d0\u05de\u05ea \u05d0\u05ea \u05db\u05ea\u05d5\u05d1\u05ea \u05d4\u05d0\u05ea\u05e8 \u05e9\u05dc webhook. \u05e0\u05d0 \u05dc\u05d5\u05d5\u05d3\u05d0 \u05e9\u05db\u05ea\u05d5\u05d1\u05ea \u05d4-webhook \u05e0\u05d2\u05d9\u05e9\u05d4 \u05de\u05d4\u05d0\u05d9\u05e0\u05d8\u05e8\u05e0\u05d8 \u05d5\u05e0\u05e1\u05d4 \u05e9\u05d5\u05d1." 13 | }, 14 | "step": { 15 | "pat": { 16 | "data": { 17 | "access_token": "\u05d0\u05e1\u05d9\u05de\u05d5\u05df \u05d2\u05d9\u05e9\u05d4" 18 | }, 19 | "description": "\u05e0\u05d0 \u05dc\u05d4\u05d6\u05d9\u05df SmartThings [\u05d0\u05e1\u05d9\u05de\u05d5\u05df \u05d2\u05d9\u05e9\u05d4 \u05d0\u05d9\u05e9\u05d9\u05ea]({token_url}) \u05e9\u05e0\u05d5\u05e6\u05e8 \u05dc\u05e4\u05d9 [\u05d4\u05d5\u05e8\u05d0\u05d5\u05ea]({component_url}). \u05e4\u05e2\u05d5\u05dc\u05d4 \u05d6\u05d5 \u05ea\u05e9\u05de\u05e9 \u05dc\u05d9\u05e6\u05d9\u05e8\u05ea \u05d4\u05e9\u05d9\u05dc\u05d5\u05d1 \u05e9\u05dc Home Assistant \u05d1\u05d7\u05e9\u05d1\u05d5\u05df SmartThings \u05e9\u05dc\u05da." 20 | }, 21 | "select_location": { 22 | "data": { 23 | "location_id": "\u05de\u05d9\u05e7\u05d5\u05dd" 24 | }, 25 | "description": "\u05e0\u05d0 \u05dc\u05d1\u05d7\u05d5\u05e8 \u05d0\u05ea \u05de\u05d9\u05e7\u05d5\u05dd \u05d4-SmartThings \u05e9\u05d1\u05e8\u05e6\u05d5\u05e0\u05da \u05dc\u05d4\u05d5\u05e1\u05d9\u05e3 \u05dc-Home Assistant. \u05dc\u05d0\u05d7\u05e8 \u05de\u05db\u05df \u05d9\u05e4\u05ea\u05d7 \u05d7\u05dc\u05d5\u05df \u05d7\u05d3\u05e9 \u05d5\u05e0\u05d1\u05e7\u05e9 \u05de\u05de\u05da \u05dc\u05d4\u05ea\u05d7\u05d1\u05e8 \u05d5\u05dc\u05d0\u05e9\u05e8 \u05d4\u05ea\u05e7\u05e0\u05d4 \u05e9\u05dc \u05e9\u05d9\u05dc\u05d5\u05d1 Home Assistant \u05d1\u05de\u05d9\u05e7\u05d5\u05dd \u05e9\u05e0\u05d1\u05d7\u05e8." 26 | }, 27 | "user": { 28 | "description": "SmartThings \u05d9\u05d5\u05d2\u05d3\u05e8 \u05dc\u05e9\u05dc\u05d5\u05d7 \u05e2\u05d3\u05db\u05d5\u05e0\u05d9 \u05d3\u05d7\u05d9\u05e4\u05d4 \u05dc-Home Assistant \u05d1\u05db\u05ea\u05d5\u05d1\u05ea:\n> {webhook_url}\n\n\u05d0\u05dd \u05d4\u05d3\u05d1\u05e8 \u05d0\u05d9\u05e0\u05d5 \u05e0\u05db\u05d5\u05df, \u05e0\u05d0 \u05dc\u05e2\u05d3\u05db\u05df \u05d0\u05ea \u05d4\u05ea\u05e6\u05d5\u05e8\u05d4, \u05d5\u05dc\u05d4\u05e4\u05e2\u05d9\u05dc \u05de\u05d7\u05d3\u05e9 \u05d0\u05ea Home Assistant \u05d5\u05dc\u05e0\u05e1\u05d5\u05ea \u05e9\u05d5\u05d1.", 29 | "title": "\u05d0\u05d9\u05e9\u05d5\u05e8 \u05e7\u05d9\u05e9\u05d5\u05e8 \u05dc\u05d4\u05ea\u05e7\u05e9\u05e8\u05d5\u05ea \u05d7\u05d6\u05e8\u05d4" 30 | } 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /custom_components/smartthings_customize/translations/hu.json: -------------------------------------------------------------------------------- 1 | { 2 | "config": { 3 | "abort": { 4 | "invalid_webhook_url": "Home Assistant nincs megfelel\u0151en konfigur\u00e1lva a SmartThings friss\u00edt\u00e9seinek fogad\u00e1s\u00e1ra. A webhook URL \u00e9rv\u00e9nytelen:\n > {webhook_url} \n\nK\u00e9rj\u00fck, friss\u00edtse konfigur\u00e1ci\u00f3j\u00e1t az [utas\u00edt\u00e1sok]({component_url}) szerint, ind\u00edtsa \u00fajra a Home Assistant alkalmaz\u00e1st, \u00e9s pr\u00f3b\u00e1lja \u00fajra.", 5 | "no_available_locations": "Nincsenek be\u00e1ll\u00edthat\u00f3 SmartThings helyek a Home Assistant alkalmaz\u00e1sban." 6 | }, 7 | "error": { 8 | "app_setup_error": "A SmartApp be\u00e1ll\u00edt\u00e1sa nem siker\u00fclt. K\u00e9rem, pr\u00f3b\u00e1lja \u00fajra.", 9 | "token_forbidden": "A token nem rendelkezik a sz\u00fcks\u00e9ges OAuth-tartom\u00e1nyokkal.", 10 | "token_invalid_format": "A tokennek UID / GUID form\u00e1tumban kell lennie", 11 | "token_unauthorized": "A token \u00e9rv\u00e9nytelen vagy m\u00e1r nem enged\u00e9lyezett.", 12 | "webhook_error": "SmartThings nem tudta \u00e9rv\u00e9nyes\u00edteni a webhook URL-t. K\u00e9rj\u00fck, ellen\u0151rizze, hogy a webhook URL el\u00e9rhet\u0151-e az internet fel\u0151l, \u00e9s pr\u00f3b\u00e1lja meg \u00fajra." 13 | }, 14 | "step": { 15 | "authorize": { 16 | "title": "HomeAssistant enged\u00e9lyez\u00e9se" 17 | }, 18 | "pat": { 19 | "data": { 20 | "access_token": "Hozz\u00e1f\u00e9r\u00e9si token" 21 | }, 22 | "description": "K\u00e9rj\u00fck, adjon meg egy SmartThings [Szem\u00e9lyes hozz\u00e1f\u00e9r\u00e9si tokent]({token_url}), amelyet az [utas\u00edt\u00e1sok]({component_url}) alapj\u00e1n hoztak l\u00e9tre. Ezt haszn\u00e1ljuk a Home Assistant integr\u00e1ci\u00f3j\u00e1nak l\u00e9trehoz\u00e1s\u00e1hoz a SmartThings-fi\u00f3kban.", 23 | "title": "Adja meg a szem\u00e9lyes hozz\u00e1f\u00e9r\u00e9si Tokent" 24 | }, 25 | "select_location": { 26 | "data": { 27 | "location_id": "Elhelyezked\u00e9s" 28 | }, 29 | "description": "K\u00e9rj\u00fck, v\u00e1lassza ki azt a SmartThings helyet, amelyet hozz\u00e1 szeretne adni a Home Assistant szolg\u00e1ltat\u00e1shoz. Ezut\u00e1n \u00faj ablakot nyitunk, \u00e9s megk\u00e9rj\u00fck, hogy jelentkezzen be, \u00e9s enged\u00e9lyezze a Home Assistant integr\u00e1ci\u00f3j\u00e1nak telep\u00edt\u00e9s\u00e9t a kiv\u00e1lasztott helyre.", 30 | "title": "Hely kiv\u00e1laszt\u00e1sa" 31 | }, 32 | "user": { 33 | "description": "K\u00e9rem adja meg a SmartThings [Personal Access Tokent]({token_url}), amit az [instrukci\u00f3k]({component_url}) alapj\u00e1n hozott l\u00e9tre.", 34 | "title": "Callback URL meger\u0151s\u00edt\u00e9se" 35 | } 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /custom_components/smartthings_customize/translations/id.json: -------------------------------------------------------------------------------- 1 | { 2 | "config": { 3 | "abort": { 4 | "invalid_webhook_url": "Home Assistant tidak dikonfigurasi dengan benar untuk menerima pembaruan dari SmartThings. URL webhook tidak valid:\n> {webhook_url}\n\nPerbarui konfigurasi Anda sesuai [petunjuk]({component_url}), kemudian mulai ulang Home Assistant, dan coba kembali.", 5 | "no_available_locations": "Tidak ada SmartThings Location untuk disiapkan di Home Assistant." 6 | }, 7 | "error": { 8 | "app_setup_error": "Tidak dapat menyiapkan SmartApp. Coba lagi.", 9 | "token_forbidden": "Token tidak memiliki cakupan OAuth yang diperlukan.", 10 | "token_invalid_format": "Token harus dalam format UID/GUID", 11 | "token_unauthorized": "Token tidak valid atau tidak lagi diotorisasi.", 12 | "webhook_error": "SmartThings tidak dapat memvalidasi URL webhook. Pastikan URL webhook dapat dijangkau dari internet, lalu coba lagi." 13 | }, 14 | "step": { 15 | "authorize": { 16 | "title": "Otorisasi Home Assistant" 17 | }, 18 | "pat": { 19 | "data": { 20 | "access_token": "Token Akses" 21 | }, 22 | "description": "Masukkan [Token Akses Pribadi]({token_url}) SmartThings yang telah dibuat sesuai [petunjuk]({component_url}). Ini akan digunakan untuk membuat integrasi Home Assistant dalam akun SmartThings Anda.", 23 | "title": "Masukkan Token Akses Pribadi" 24 | }, 25 | "select_location": { 26 | "data": { 27 | "location_id": "Lokasi" 28 | }, 29 | "description": "Pilih SmartThings Location yang ingin ditambahkan ke Home Assistant. Kami akan membuka jendela baru dan meminta Anda untuk masuk dan mengotorisasi instalasi integrasi Home Assistant ke Location yang dipilih.", 30 | "title": "Pilih Location" 31 | }, 32 | "user": { 33 | "description": "SmartThings akan dikonfigurasi untuk mengirim pembaruan push ke Home Assistant di:\n > {webhook_url} \n\nJika ini tidak benar, perbarui konfigurasi Anda, mulai ulang Home Assistant, dan coba lagi.", 34 | "title": "Konfirmasikan URL Panggilan Balik" 35 | } 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /custom_components/smartthings_customize/translations/it.json: -------------------------------------------------------------------------------- 1 | { 2 | "config": { 3 | "abort": { 4 | "invalid_webhook_url": "Home Assistant non \u00e8 configurato correttamente per ricevere gli aggiornamenti da SmartThings. L'URL del webhook non \u00e8 valido:\n> {webhook_url}\n\nSi prega di aggiornare la configurazione secondo le [istruzioni]({component_url}), riavviare Home Assistant e riprovare.", 5 | "no_available_locations": "Non ci sono posizioni SmartThings disponibili da configurare in Home Assistant." 6 | }, 7 | "error": { 8 | "app_setup_error": "Impossibile configurare SmartApp. Riprovare.", 9 | "token_forbidden": "Il token non dispone degli ambiti OAuth necessari.", 10 | "token_invalid_format": "Il token deve essere nel formato UID/GUID", 11 | "token_unauthorized": "Il token non \u00e8 valido o non \u00e8 pi\u00f9 autorizzato.", 12 | "webhook_error": "SmartThings non \u00e8 riuscito a convalidare l'URL del webhook. Assicurati che l'URL del webhook sia raggiungibile da Internet e riprova." 13 | }, 14 | "step": { 15 | "authorize": { 16 | "title": "Autorizza Home Assistant" 17 | }, 18 | "pat": { 19 | "data": { 20 | "access_token": "Token di accesso" 21 | }, 22 | "description": "Si prega di inserire un SmartThings [Personal Access Token]({token_url}) che \u00e8 stato creato secondo le [istruzioni]({component_url}). Questo verr\u00e0 utilizzato per creare l'integrazione Home Assistant all'interno del vostro account SmartThings.", 23 | "title": "Inserisci il Token di Accesso Personale" 24 | }, 25 | "select_location": { 26 | "data": { 27 | "location_id": "Posizione" 28 | }, 29 | "description": "Selezionare la posizione SmartThings che si desidera aggiungere a Home Assistant. Apriremo quindi una nuova finestra e vi chiederemo di effettuare il login e di autorizzare l'installazione dell'integrazione dell'Home Assistant nella posizione selezionata.", 30 | "title": "Seleziona posizione" 31 | }, 32 | "user": { 33 | "description": "SmartThings sar\u00e0 configurato per inviare aggiornamenti push a Home Assistant su: \n > {webhook_url} \n\nSe ci\u00f2 non fosse corretto, aggiornare la configurazione, riavviare Home Assistant e riprovare.", 34 | "title": "Confermare l'URL di richiamo" 35 | } 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /custom_components/smartthings_customize/translations/ko.json: -------------------------------------------------------------------------------- 1 | { 2 | "config": { 3 | "abort": { 4 | "invalid_webhook_url": "Home Assistant \uac00 SmartThings\uc5d0\uc11c \uc5c5\ub370\uc774\ud2b8\ub97c \uc218\uc2e0\ud558\ub3c4\ub85d \uc62c\ubc14\ub974\uac8c \uad6c\uc131\ub418\uc9c0 \uc54a\uc558\uc2b5\ub2c8\ub2e4. \uc6f9 \ud6c5 URL\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4:\n> {webhook_url} \n\n[\uc548\ub0b4]({component_url})\ub97c \ucc38\uace0\ud558\uc5ec \uad6c\uc131\uc744 \uc5c5\ub370\uc774\ud2b8\ud558\uace0 Home Assistant\ub97c \ub2e4\uc2dc \uc2dc\uc791\ud55c \ud6c4 \ub2e4\uc2dc \uc2dc\ub3c4\ud574\uc8fc\uc138\uc694.", 5 | "no_available_locations": "Home Assistant\uc5d0\uc11c \uc124\uc815\ud560 \uc218 \uc788\ub294 SmartThings \uc704\uce58\uac00 \uc5c6\uc2b5\ub2c8\ub2e4." 6 | }, 7 | "error": { 8 | "app_setup_error": "SmartApp \uc744 \uc124\uc815\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4. \ub2e4\uc2dc \uc2dc\ub3c4\ud574\uc8fc\uc138\uc694.", 9 | "token_forbidden": "\ud1a0\ud070\uc5d0 \ud544\uc694\ud55c OAuth \ubc94\uc704\ubaa9\ub85d\uc774 \uc5c6\uc2b5\ub2c8\ub2e4.", 10 | "token_invalid_format": "\ud1a0\ud070\uc740 UID/GUID \ud615\uc2dd\uc774\uc5b4\uc57c \ud569\ub2c8\ub2e4", 11 | "token_unauthorized": "\ud1a0\ud070\uc774 \uc720\ud6a8\ud558\uc9c0 \uc54a\uac70\ub098 \uc2b9\uc778\ub418\uc9c0 \uc54a\uc558\uc2b5\ub2c8\ub2e4.", 12 | "webhook_error": "SmartThings \uac00 \uc6f9 \ud6c5 URL \uc744 \ud655\uc778\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4. \uc778\ud130\ub137\uc5d0\uc11c \uc6f9 \ud6c5 URL \uc5d0 \uc811\uadfc\ud560 \uc218 \uc788\ub294\uc9c0 \ud655\uc778\ud55c \ud6c4 \ub2e4\uc2dc \uc2dc\ub3c4\ud574\uc8fc\uc138\uc694." 13 | }, 14 | "step": { 15 | "authorize": { 16 | "title": "Home Assistant \uc2b9\uc778\ud558\uae30" 17 | }, 18 | "pat": { 19 | "data": { 20 | "access_token": "\uc561\uc138\uc2a4 \ud1a0\ud070" 21 | }, 22 | "description": "[\uc548\ub0b4]({component_url})\uc5d0 \ub530\ub77c \uc0dd\uc131\ub41c SmartThings [\uac1c\uc778 \uc561\uc138\uc2a4 \ud1a0\ud070]({token_url})\uc744 \uc785\ub825\ud574\uc8fc\uc138\uc694. SmartThings \uacc4\uc815\uc5d0\uc11c Home Assistant \uc5f0\ub3d9\uc744 \ub9cc\ub4dc\ub294\ub370 \uc0ac\uc6a9\ub429\ub2c8\ub2e4.", 23 | "title": "\uac1c\uc778 \uc561\uc138\uc2a4 \ud1a0\ud070\uc744 \uc785\ub825\ud574\uc8fc\uc138\uc694" 24 | }, 25 | "select_location": { 26 | "data": { 27 | "location_id": "\uc704\uce58" 28 | }, 29 | "description": "Home Assistant\uc5d0 \ucd94\uac00\ud558\ub824\ub294 SmartThings \uc704\uce58\ub97c \uc120\ud0dd\ud574\uc8fc\uc138\uc694. \uc0c8\ub86d\uac8c \uc5f4\ub9b0 \ub85c\uadf8\uc778 \ucc3d\uc5d0\uc11c \ub85c\uadf8\uc778\uc744 \ud558\uba74 \uc120\ud0dd\ud55c \uc704\uce58\uc5d0 Home Assistant \uc5f0\ub3d9\uc744 \uc2b9\uc778\ud558\ub77c\ub294 \uba54\uc2dc\uc9c0\uac00 \ud45c\uc2dc\ub429\ub2c8\ub2e4.", 30 | "title": "\uc704\uce58 \uc120\ud0dd\ud558\uae30" 31 | }, 32 | "user": { 33 | "description": "SmartThings\ub294 \uc544\ub798\uc758 \uc6f9 \ud6c5 \uc8fc\uc18c\ub85c Home Assistant\uc5d0 \ud478\uc2dc \uc5c5\ub370\uc774\ud2b8\ub97c \ubcf4\ub0b4\ub3c4\ub85d \uad6c\uc131\ub429\ub2c8\ub2e4. \n > {webhook_url} \n\n\uc774 \uad6c\uc131\uc774 \uc62c\ubc14\ub974\uc9c0 \uc54a\ub2e4\uba74 \uad6c\uc131\uc744 \uc5c5\ub370\uc774\ud2b8\ud558\uace0 Home Assistant\ub97c \ub2e4\uc2dc \uc2dc\uc791\ud55c \ud6c4 \ub2e4\uc2dc \uc2dc\ub3c4\ud574\uc8fc\uc138\uc694.", 34 | "title": "\ucf5c\ubc31 URL \ud655\uc778\ud558\uae30" 35 | } 36 | } 37 | }, 38 | "options": { 39 | "step": { 40 | "init": { 41 | "title": "SmartThings Customize \uad6c\uc131\u0020\uc124\uc815", 42 | "data": { 43 | "enable_default_entities": "\uae30\ubcf8\u0020\uc5d4\ud2f0\ud2f0\u0020\ud65c\uc131\ud654", 44 | "enable_syntax_property": "\uc18d\uc131\uc5d0\u0020\uc124\uc815\u0020\uad6c\ubb38\u0020\ud45c\uc2dc", 45 | "resetting_entities": "\uc5d4\ud2f0\ud2f0\u0020\uc7ac\uc124\uc815\u0028\ucd08\uae30\ud654\u0029" 46 | } 47 | } 48 | } 49 | } 50 | } -------------------------------------------------------------------------------- /custom_components/smartthings_customize/translations/lb.json: -------------------------------------------------------------------------------- 1 | { 2 | "config": { 3 | "abort": { 4 | "invalid_webhook_url": "Home Assistant ass net richteg konfigur\u00e9iert fir Updates vun SmartThings z'empf\u00e4nken.\nWebhook URL ass ong\u00eblteg:\n>{webhook_url}\n\u00c4nner deng Konfiguratioun sou w\u00e9i an den [Instruktioune]({component_url}) start Home Assistant fr\u00ebsch an prob\u00e9ier nach eemol.", 5 | "no_available_locations": "Keng disponibel Smartthings Standuerte fir am Home Assistant anzeriichten." 6 | }, 7 | "error": { 8 | "app_setup_error": "Kann SmartApp net install\u00e9ieren. Prob\u00e9iert w.e.g. nach emol.", 9 | "token_forbidden": "De Jeton huet net d\u00e9i n\u00e9ideg OAuth M\u00e9iglechkeeten.", 10 | "token_invalid_format": "De Jeton muss am UID/GUID Format sinn", 11 | "token_unauthorized": "De Jeton ass ong\u00eblteg oder net m\u00e9i autoris\u00e9iert.", 12 | "webhook_error": "SmartThings konnt d'Webhook URL valid\u00e9ieren. Stell s\u00e9cher dass d'URL vum Internet aus ereechbar ass a prob\u00e9ier nach eemol." 13 | }, 14 | "step": { 15 | "authorize": { 16 | "title": "Home Assistant erlaaben" 17 | }, 18 | "pat": { 19 | "data": { 20 | "access_token": "Acc\u00e8s Jeton" 21 | }, 22 | "description": "G\u00ebff w.e.g. ee pers\u00e9inlechen SmartThings [Acc\u00e8s Jeton]({token_url}) un dee via [d'Instruktiounen] ({component_url}) erstallt gouf. D\u00ebse g\u00ebtte benotzt fir d'Integratioun vum SmartThings Kont am Home Assistant.", 23 | "title": "Pers\u00e9inlechen Acc\u00e8ss Jeton uginn" 24 | }, 25 | "select_location": { 26 | "data": { 27 | "location_id": "Standuert" 28 | }, 29 | "description": "Wiel SmartThings Standuert aus fir en am Home Assistant dob\u00e4i ze setzen. Dann geht eng nei F\u00ebnster op mat engem Login an fir d'Installatioun vun der Integratioun am Home Assistanr z'erlaaben.", 30 | "title": "Standuert auswielen" 31 | }, 32 | "user": { 33 | "description": "SmartThings g\u00ebtt ageriicht fir Push Aktualis\u00e9ierungen un Home Assistant ze sch\u00e9cken un d'Adresse:\n>{webhook_url}\n\nFalls d\u00ebs net korrekt ass, \u00e4nner deng Konfiguratioun, start Home Assistant fr\u00ebsch a prob\u00e9ier nach emol.", 34 | "title": "Callback URL confirm\u00e9ieren" 35 | } 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /custom_components/smartthings_customize/translations/nl.json: -------------------------------------------------------------------------------- 1 | { 2 | "config": { 3 | "abort": { 4 | "invalid_webhook_url": "Home Assistant is niet correct geconfigureerd om updates van SmartThings te ontvangen. De webhook-URL is ongeldig: \n > {webhook_url} \n\n Werk uw configuratie bij volgens de [instructies] ( {component_url} ), start de Home Assistant opnieuw op en probeer het opnieuw.", 5 | "no_available_locations": "Er zijn geen beschikbare SmartThings-locaties om in te stellen in Home Assistant." 6 | }, 7 | "error": { 8 | "app_setup_error": "Instellen van SmartApp mislukt. Probeer het opnieuw.", 9 | "token_forbidden": "Het token heeft niet de vereiste OAuth-scopes.", 10 | "token_invalid_format": "Het token moet de UID/GUID-indeling hebben", 11 | "token_unauthorized": "Het token is ongeldig of niet langer geautoriseerd.", 12 | "webhook_error": "SmartThings kan de webhook URL niet valideren. Zorg ervoor dat de webhook URL bereikbaar is vanaf het internet en probeer het opnieuw." 13 | }, 14 | "step": { 15 | "authorize": { 16 | "title": "Machtig Home Assistant" 17 | }, 18 | "pat": { 19 | "data": { 20 | "access_token": "Toegangstoken" 21 | }, 22 | "description": "Voer een SmartThings [Personal Access Token] ( {token_url} ) in dat is gemaakt volgens de [instructies] ( {component_url} ). Dit wordt gebruikt om de Home Assistant-integratie te cre\u00ebren binnen uw SmartThings-account.", 23 | "title": "Persoonlijk toegangstoken invoeren" 24 | }, 25 | "select_location": { 26 | "data": { 27 | "location_id": "Locatie" 28 | }, 29 | "description": "Selecteer de SmartThings-locatie die u aan de Home Assistant wilt toevoegen. We zullen dan een nieuw venster openen en u vragen om in te loggen en de installatie van de Home Assistant-integratie op de geselecteerde locatie te autoriseren.", 30 | "title": "Locatie selecteren" 31 | }, 32 | "user": { 33 | "description": "SmartThings zal worden geconfigureerd om push updates te sturen naar Home Assistant op:\n> {webhook_url}\n\nAls dit niet correct is, werk dan uw configuratie bij, start Home Assistant opnieuw op en probeer het opnieuw.", 34 | "title": "Bevestig Callback URL" 35 | } 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /custom_components/smartthings_customize/translations/no.json: -------------------------------------------------------------------------------- 1 | { 2 | "config": { 3 | "abort": { 4 | "invalid_webhook_url": "Home Assistant er ikke konfigurert riktig for \u00e5 motta oppdateringer fra SmartThings. URLen til nettkroken er ugyldig: \n > {webhook_url} \n\nVennligst oppdater konfigurasjonen i henhold til [instruksjonene]({component_url}), start Home Assistant p\u00e5 nytt, og pr\u00f8v igjen.", 5 | "no_available_locations": "Det er ingen tilgjengelige SmartThings-plasseringer \u00e5 konfigurere i Home Assistant." 6 | }, 7 | "error": { 8 | "app_setup_error": "Kan ikke konfigurere SmartApp. Vennligst pr\u00f8v p\u00e5 nytt.", 9 | "token_forbidden": "Tokenet har ikke de n\u00f8dvendige OAuth-omfangene", 10 | "token_invalid_format": "Token m\u00e5 v\u00e6re i UID/GUID format", 11 | "token_unauthorized": "Tokenet er ugyldig eller er ikke lenger godkjent", 12 | "webhook_error": "SmartThings kan ikke validere URL-adressen for webhook. Kontroller at URL-adressen for webhook kan n\u00e5s fra Internett, og pr\u00f8v p\u00e5 nytt." 13 | }, 14 | "step": { 15 | "authorize": { 16 | "title": "Godkjenn Home Assistant" 17 | }, 18 | "pat": { 19 | "data": { 20 | "access_token": "Tilgangstoken" 21 | }, 22 | "description": "Vennligst fyll inn en SmartThings [personlig tilgangstoken]({token_url}) som er opprettet etter [instruksjonene]({component_url}).", 23 | "title": "Fyll inn personlig tilgangstoken" 24 | }, 25 | "select_location": { 26 | "data": { 27 | "location_id": "Plassering" 28 | }, 29 | "description": "Vennligst velg SmartThings lokasjon du vil legge til Home Assistant. Vi \u00e5pner deretter et nytt vindu og ber deg om \u00e5 logge inn og godkjenne installasjon av Home Assistant-integrasjonen p\u00e5 det valgte stedet.", 30 | "title": "Velg plassering" 31 | }, 32 | "user": { 33 | "description": "SmartThings konfigureres til \u00e5 sende push-oppdateringer til Home Assistant p\u00e5:\n\" {webhook_url}\n\nHvis dette ikke er riktig, m\u00e5 du oppdatere konfigurasjonen, starte Home Assistant p\u00e5 nytt og pr\u00f8ve p\u00e5 nytt.", 34 | "title": "Bekreft URL-adresse for tilbakeringing" 35 | } 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /custom_components/smartthings_customize/translations/pl.json: -------------------------------------------------------------------------------- 1 | { 2 | "config": { 3 | "abort": { 4 | "invalid_webhook_url": "Home Assistant nie jest poprawnie skonfigurowany do otrzymywania danych od SmartThings. Adres URL webhook jest nieprawid\u0142owy: \n > {webhook_url} \n\n Zaktualizuj konfiguracj\u0119 zgodnie z [instrukcj\u0105]({component_url}), uruchom ponownie Home Assistanta i spr\u00f3buj ponownie.", 5 | "no_available_locations": "Nie ma dost\u0119pnych lokalizacji SmartThings do skonfigurowania w Home Assistant" 6 | }, 7 | "error": { 8 | "app_setup_error": "Nie mo\u017cna skonfigurowa\u0107 SmartApp. Spr\u00f3buj ponownie.", 9 | "token_forbidden": "Token nie ma wymaganych zakres\u00f3w OAuth", 10 | "token_invalid_format": "Token musi by\u0107 w formacie UID/GUID", 11 | "token_unauthorized": "Token jest niewa\u017cny lub nie ma ju\u017c autoryzacji", 12 | "webhook_error": "SmartThings nie m\u00f3g\u0142 sprawdzi\u0107 poprawno\u015bci webhook URL. Upewnij si\u0119, \u017ce adres URL webhook jest dost\u0119pny z internetu i spr\u00f3buj ponownie." 13 | }, 14 | "step": { 15 | "authorize": { 16 | "title": "Autoryzuj Home Assistanta" 17 | }, 18 | "pat": { 19 | "data": { 20 | "access_token": "Token dost\u0119pu" 21 | }, 22 | "description": "Wprowad\u017a [token dost\u0119pu osobistego]({token_url}) SmartThings, kt\u00f3ry zosta\u0142 utworzony zgodnie z [instrukcj\u0105]({component_url}). Umo\u017cliwi to stworzenie integracji Home Assistant w ramach Twojego konta SmartThings.", 23 | "title": "Wprowad\u017a osobisty token dost\u0119pu" 24 | }, 25 | "select_location": { 26 | "data": { 27 | "location_id": "Lokalizacja" 28 | }, 29 | "description": "Wybierz lokalizacj\u0119 SmartThings, kt\u00f3r\u0105 chcesz doda\u0107 do Home Assistanta. Nast\u0119pnie otwarte zostanie nowe okno i zostaniesz poproszony o zalogowanie si\u0119 i autoryzacj\u0119 instalacji integracji Home Assistant w wybranej lokalizacji.", 30 | "title": "Wybierz lokalizacj\u0119" 31 | }, 32 | "user": { 33 | "description": "SmartThings zostanie skonfigurowany, by wysy\u0142a\u0107 aktualizacje push do Home Assistanta na:\n> {webhook_url}\n\nJe\u015bli adres jest nieprawid\u0142owy, popraw swoj\u0105 konfiguracj\u0119, uruchom ponownie Home Assistant i spr\u00f3buj ponownie.", 34 | "title": "Potwierd\u017a Callback URL" 35 | } 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /custom_components/smartthings_customize/translations/pt-BR.json: -------------------------------------------------------------------------------- 1 | { 2 | "config": { 3 | "abort": { 4 | "no_available_locations": "N\u00e3o h\u00e1 Locais SmartThings dispon\u00edveis para configura\u00e7\u00e3o no Home Assistant." 5 | }, 6 | "error": { 7 | "app_setup_error": "N\u00e3o \u00e9 poss\u00edvel configurar o SmartApp. Por favor, tente novamente.", 8 | "token_forbidden": "O token n\u00e3o possui os escopos necess\u00e1rios do OAuth.", 9 | "token_invalid_format": "O token deve estar no formato UID / GUID", 10 | "token_unauthorized": "O token \u00e9 inv\u00e1lido ou n\u00e3o est\u00e1 mais autorizado.", 11 | "webhook_error": "O SmartThings n\u00e3o p\u00f4de validar o terminal configurado em `base_url`. Por favor, revise os requisitos do componente." 12 | }, 13 | "step": { 14 | "user": { 15 | "description": "Por favor, insira um SmartThings [Personal Access Token] ( {token_url} ) que foi criado de acordo com as [instru\u00e7\u00f5es] ( {component_url} ).", 16 | "title": "Digite o token de acesso pessoal" 17 | } 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /custom_components/smartthings_customize/translations/pt.json: -------------------------------------------------------------------------------- 1 | { 2 | "config": { 3 | "abort": { 4 | "no_available_locations": "N\u00e3o h\u00e1 localiza\u00e7\u00f5es SmartThings dispon\u00edveis para configura\u00e7\u00e3o no Home Assistant." 5 | }, 6 | "error": { 7 | "app_setup_error": "N\u00e3o \u00e9 poss\u00edvel configurar o SmartApp. Por favor, tente novamente.", 8 | "token_forbidden": "O token n\u00e3o tem tem a cobertura OAuth necess\u00e1ria.", 9 | "token_invalid_format": "O token deve estar no formato UID/GUID", 10 | "token_unauthorized": "O token \u00e9 inv\u00e1lido ou ja n\u00e3o est\u00e1 autorizado.", 11 | "webhook_error": "O SmartThings n\u00e3o p\u00f4de validar o URL do webhook. Verifique se o URL do webhook est\u00e1 acess\u00edvel pela Internet e tente novamente." 12 | }, 13 | "step": { 14 | "authorize": { 15 | "title": "Autorizar o Home Assistant" 16 | }, 17 | "pat": { 18 | "data": { 19 | "access_token": "Token de Acesso" 20 | }, 21 | "title": "Insira o Token de acesso pessoal" 22 | }, 23 | "select_location": { 24 | "data": { 25 | "location_id": "Localiza\u00e7\u00e3o" 26 | }, 27 | "description": "Por favor, selecione a localiza\u00e7\u00e3o do SmartThings que voc\u00ea deseja adicionar ao Home Assistant. Em seguida, abriremos uma nova janela e solicitaremos que voc\u00ea fa\u00e7a o login e autorize a instala\u00e7\u00e3o da integra\u00e7\u00e3o do Home Assistant na localiza\u00e7\u00e3o selecionada.", 28 | "title": "Selecionar Localiza\u00e7\u00e3o" 29 | }, 30 | "user": { 31 | "description": "Por favor, insira um SmartThings [Personal Access Token]({token_url} ) que foi criado de acordo com as [instru\u00e7\u00f5es]({component_url}).", 32 | "title": "Insira o Token de acesso pessoal" 33 | } 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /custom_components/smartthings_customize/translations/ru.json: -------------------------------------------------------------------------------- 1 | { 2 | "config": { 3 | "abort": { 4 | "invalid_webhook_url": "Webhook URL, \u0443\u043a\u0430\u0437\u0430\u043d\u043d\u044b\u0439 \u0434\u043b\u044f \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0439 \u043e\u0442 SmartThings, \u043d\u0435\u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0442\u0435\u043b\u0435\u043d: \n> {webhook_url} \n\n\u041e\u0431\u043d\u043e\u0432\u0438\u0442\u0435 \u0432\u0430\u0448\u0443 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044e \u0432 \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0438\u0438 \u0441 [\u0438\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0438\u044f\u043c\u0438]({component_url}), \u0437\u0430\u0442\u0435\u043c \u043f\u0435\u0440\u0435\u0437\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u0435 Home Assistant \u0438 \u043f\u043e\u0432\u0442\u043e\u0440\u0438\u0442\u0435 \u043f\u043e\u043f\u044b\u0442\u043a\u0443.", 5 | "no_available_locations": "\u041d\u0435\u0442 \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u044b\u0445 \u0434\u043b\u044f \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u043c\u0435\u0441\u0442\u043e\u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u0439 SmartThings." 6 | }, 7 | "error": { 8 | "app_setup_error": "\u041d\u0435 \u0443\u0434\u0430\u0435\u0442\u0441\u044f \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c SmartApp. \u041f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430, \u043f\u043e\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0435\u0449\u0435 \u0440\u0430\u0437.", 9 | "token_forbidden": "\u0422\u043e\u043a\u0435\u043d \u043d\u0435 \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d \u0434\u043b\u044f OAuth.", 10 | "token_invalid_format": "\u0422\u043e\u043a\u0435\u043d \u0434\u043e\u043b\u0436\u0435\u043d \u0431\u044b\u0442\u044c \u0432 \u0444\u043e\u0440\u043c\u0430\u0442\u0435 UID / GUID.", 11 | "token_unauthorized": "\u0422\u043e\u043a\u0435\u043d \u043d\u0435\u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0442\u0435\u043b\u0435\u043d \u0438\u043b\u0438 \u0431\u043e\u043b\u044c\u0448\u0435 \u043d\u0435 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u043e\u0432\u0430\u043d.", 12 | "webhook_error": "SmartThings \u043d\u0435 \u043c\u043e\u0436\u0435\u0442 \u043f\u0440\u043e\u0432\u0435\u0440\u0438\u0442\u044c Webhook URL. \u0423\u0431\u0435\u0434\u0438\u0442\u0435\u0441\u044c, \u0447\u0442\u043e \u0443\u043a\u0430\u0437\u0430\u043d\u043d\u044b\u0439 Webhook URL \u0434\u043e\u0441\u0442\u0443\u043f\u0435\u043d \u0438\u0437 \u0438\u043d\u0442\u0435\u0440\u043d\u0435\u0442\u0430 \u0438 \u043f\u043e\u0432\u0442\u043e\u0440\u0438\u0442\u0435 \u043f\u043e\u043f\u044b\u0442\u043a\u0443." 13 | }, 14 | "step": { 15 | "authorize": { 16 | "title": "\u0410\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u044f Home Assistant" 17 | }, 18 | "pat": { 19 | "data": { 20 | "access_token": "\u0422\u043e\u043a\u0435\u043d \u0434\u043e\u0441\u0442\u0443\u043f\u0430" 21 | }, 22 | "description": "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 [\u041b\u0438\u0447\u043d\u044b\u0439 \u0442\u043e\u043a\u0435\u043d \u0434\u043e\u0441\u0442\u0443\u043f\u0430 SmartThings]({token_url}), \u0441\u043e\u0437\u0434\u0430\u043d\u043d\u044b\u0439 \u0432 \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0438\u0438 \u0441 [\u0438\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0438\u044f\u043c\u0438]({component_url}).", 23 | "title": "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u043f\u0435\u0440\u0441\u043e\u043d\u0430\u043b\u044c\u043d\u044b\u0439 \u0442\u043e\u043a\u0435\u043d \u0434\u043e\u0441\u0442\u0443\u043f\u0430" 24 | }, 25 | "select_location": { 26 | "data": { 27 | "location_id": "\u041c\u0435\u0441\u0442\u043e\u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u0435" 28 | }, 29 | "description": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u043c\u0435\u0441\u0442\u043e\u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u0435 SmartThings, \u043a\u043e\u0442\u043e\u0440\u043e\u0435 \u0432\u044b \u0445\u043e\u0442\u0438\u0442\u0435 \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0432 Home Assistant. \u041f\u043e\u0441\u043b\u0435 \u044d\u0442\u043e\u0433\u043e \u043e\u0442\u043a\u0440\u043e\u0435\u0442\u0441\u044f \u043d\u043e\u0432\u043e\u0435 \u043e\u043a\u043d\u043e, \u0433\u0434\u0435 \u043d\u0443\u0436\u043d\u043e \u0431\u0443\u0434\u0435\u0442 \u0432\u043e\u0439\u0442\u0438 \u0432 \u0441\u0438\u0441\u0442\u0435\u043c\u0443 \u0438 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u043e\u0432\u0430\u0442\u044c \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0443 \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u0438 Home Assistant \u0432 \u0432\u044b\u0431\u0440\u0430\u043d\u043d\u043e\u043c \u043c\u0435\u0441\u0442\u043e\u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u0438.", 30 | "title": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u043c\u0435\u0441\u0442\u043e\u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u0435" 31 | }, 32 | "user": { 33 | "description": "SmartThings \u0431\u0443\u0434\u0435\u0442 \u043d\u0430\u0441\u0442\u0440\u043e\u0435\u043d \u0434\u043b\u044f \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0438 push-\u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0439 \u043f\u043e \u0430\u0434\u0440\u0435\u0441\u0443: \n> {webhook_url} \n\n\u0415\u0441\u043b\u0438 \u044d\u0442\u043e \u043d\u0435 \u0442\u0430\u043a, \u043e\u0431\u043d\u043e\u0432\u0438\u0442\u0435 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044e, \u043f\u0435\u0440\u0435\u0437\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u0435 Home Assistant \u0438 \u043f\u043e\u0432\u0442\u043e\u0440\u0438\u0442\u0435 \u043f\u043e\u043f\u044b\u0442\u043a\u0443.", 34 | "title": "\u041f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d\u0438\u0435 Callback URL" 35 | } 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /custom_components/smartthings_customize/translations/sl.json: -------------------------------------------------------------------------------- 1 | { 2 | "config": { 3 | "abort": { 4 | "invalid_webhook_url": "Home Assistant ni pravilno konfiguriran za sprejemanje posodobitev SmartThings. URL webhook-a je neveljaven: \n > {webhook_url} \n\n Posodobite konfiguracijo po [navodilih] ({component_url}), nato znova za\u017eenite Home Assistant-a in poskusite znova.", 5 | "no_available_locations": "Na voljo ni nobenih lokacij SmartThings, ki bi jih bilo mogo\u010de nastaviti v programu Home Assistant." 6 | }, 7 | "error": { 8 | "app_setup_error": "SmartApp ni mogo\u010de nastaviti. Prosim poskusite ponovno.", 9 | "token_forbidden": "\u017deton nima zahtevanih OAuth obsegov.", 10 | "token_invalid_format": "\u017deton mora biti v formatu UID / GUID", 11 | "token_unauthorized": "\u017deton ni veljaven ali ni ve\u010d poobla\u0161\u010den.", 12 | "webhook_error": "SmartThings ni mogel preveriti URL-ja spletnega koda. Prepri\u010dajte se, da je spletni brskalnik dosegljiv iz interneta in poskusite znova." 13 | }, 14 | "step": { 15 | "authorize": { 16 | "title": "Pooblastite Home Assistant" 17 | }, 18 | "pat": { 19 | "data": { 20 | "access_token": "Dostopni \u017eeton" 21 | }, 22 | "description": "Vnesite SmartThings [\u017deton osebnega dostopa] ({token_url}), ki je bil ustvarjen po [navodilih] ({component_url}). To bo uporabljeno za ustvarjanje integracije Home Assistant v va\u0161em ra\u010dunu SmartThings.", 23 | "title": "Vnesite \u017eeton za osebni dostop" 24 | }, 25 | "select_location": { 26 | "data": { 27 | "location_id": "Lokacija" 28 | }, 29 | "description": "Izberite lokacijo SmartThings, ki jo \u017eelite dodati v Home Assistant. Nato odpremo novo okno in vas prosimo, da se prijavite in pooblastite namestitev integracije Home Assistant na izbrano lokacijo.", 30 | "title": "Izberite Lokacijo" 31 | }, 32 | "user": { 33 | "description": "SmartThings bo konfiguriran za po\u0161iljanje push posodobitev na Home Assistant na: \n > {webhook_url} \n\n \u010ce to ni pravilno, posodobite konfiguracijo, znova za\u017eenite Home Assistant in poskusite znova.", 34 | "title": "Potrdite URL za povratni klic" 35 | } 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /custom_components/smartthings_customize/translations/sv.json: -------------------------------------------------------------------------------- 1 | { 2 | "config": { 3 | "error": { 4 | "app_setup_error": "Det gick inte att installera Home Assistant SmartApp. V\u00e4nligen f\u00f6rs\u00f6k igen.", 5 | "token_forbidden": "Token har inte det som kr\u00e4vs inom omf\u00e5ng f\u00f6r OAuth.", 6 | "token_invalid_format": "Token m\u00e5ste vara i UID/GUID-format", 7 | "token_unauthorized": "Denna token \u00e4r ogiltig eller inte l\u00e4ngre auktoriserad.", 8 | "webhook_error": "SmartThings kunde inte validera endpoint konfigurerad i \" base_url`. V\u00e4nligen granska kraven f\u00f6r komponenten." 9 | }, 10 | "step": { 11 | "authorize": { 12 | "title": "Auktorisera Home Assistant" 13 | }, 14 | "select_location": { 15 | "data": { 16 | "location_id": "Position" 17 | }, 18 | "title": "V\u00e4lj plats" 19 | }, 20 | "user": { 21 | "description": "V\u00e4nligen ange en [personlig \u00e5tkomsttoken]({token_url}) f\u00f6r SmartThings som har skapats enligt [instruktionerna]({component_url}).", 22 | "title": "Ange personlig \u00e5tkomsttoken" 23 | } 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /custom_components/smartthings_customize/translations/tr.json: -------------------------------------------------------------------------------- 1 | { 2 | "config": { 3 | "error": { 4 | "webhook_error": "SmartThings, webhook URL'sini do\u011frulayamad\u0131. L\u00fctfen webhook URL'sinin internetten eri\u015filebilir oldu\u011fundan emin olun ve tekrar deneyin." 5 | }, 6 | "step": { 7 | "pat": { 8 | "data": { 9 | "access_token": "Eri\u015fim Belirteci" 10 | } 11 | }, 12 | "select_location": { 13 | "title": "Konum Se\u00e7in" 14 | } 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /custom_components/smartthings_customize/translations/uk.json: -------------------------------------------------------------------------------- 1 | { 2 | "config": { 3 | "abort": { 4 | "invalid_webhook_url": "Webhook URL, \u0432\u043a\u0430\u0437\u0430\u043d\u0438\u0439 \u0434\u043b\u044f \u043e\u0442\u0440\u0438\u043c\u0430\u043d\u043d\u044f \u043e\u043d\u043e\u0432\u043b\u0435\u043d\u044c \u0432\u0456\u0434 SmartThings, \u043d\u0435\u0434\u0456\u0439\u0441\u043d\u0438\u0439:\n > {webhook_url} \n\n\u041e\u043d\u043e\u0432\u0456\u0442\u044c \u0432\u0430\u0448\u0443 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044e \u0432\u0456\u0434\u043f\u043e\u0432\u0456\u0434\u043d\u043e \u0434\u043e [\u0456\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0456\u0439] ({component_url}), \u0430 \u043f\u0456\u0441\u043b\u044f \u043f\u0435\u0440\u0435\u0437\u0430\u043f\u0443\u0441\u043a\u0443 Home Assistant \u0441\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0449\u0435 \u0440\u0430\u0437.", 5 | "no_available_locations": "\u041d\u0435\u043c\u0430\u0454 \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u0438\u0445 \u0434\u043b\u044f \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u043c\u0456\u0441\u0446\u044c \u0440\u043e\u0437\u0442\u0430\u0448\u0443\u0432\u0430\u043d\u043d\u044f SmartThings." 6 | }, 7 | "error": { 8 | "app_setup_error": "\u041d\u0435\u043c\u043e\u0436\u043b\u0438\u0432\u043e \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u0442\u0438 SmartApp. \u0411\u0443\u0434\u044c \u043b\u0430\u0441\u043a\u0430, \u0441\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0449\u0435 \u0440\u0430\u0437.", 9 | "token_forbidden": "\u0422\u043e\u043a\u0435\u043d \u043d\u0435 \u043c\u043e\u0436\u0435 \u0431\u0443\u0442\u0438 \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u0430\u043d\u0438\u0439 \u0434\u043b\u044f OAuth.", 10 | "token_invalid_format": "\u0422\u043e\u043a\u0435\u043d \u043f\u043e\u0432\u0438\u043d\u0435\u043d \u0431\u0443\u0442\u0438 \u0432 \u0444\u043e\u0440\u043c\u0430\u0442\u0456 UID / GUID.", 11 | "token_unauthorized": "\u0422\u043e\u043a\u0435\u043d \u043d\u0435\u0434\u0456\u0439\u0441\u043d\u0438\u0439 \u0430\u0431\u043e \u0431\u0456\u043b\u044c\u0448\u0435 \u043d\u0435 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u043e\u0432\u0430\u043d\u0438\u0439.", 12 | "webhook_error": "SmartThings \u043d\u0435 \u043c\u043e\u0436\u0435 \u043f\u0435\u0440\u0435\u0432\u0456\u0440\u0438\u0442\u0438 Webhook URL. \u041f\u0435\u0440\u0435\u043a\u043e\u043d\u0430\u0439\u0442\u0435\u0441\u044f, \u0449\u043e \u0432\u043a\u0430\u0437\u0430\u043d\u0438\u0439 Webhook URL \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u0438\u0439 \u0437 \u0456\u043d\u0442\u0435\u0440\u043d\u0435\u0442\u0443 \u0456 \u0441\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0449\u0435 \u0440\u0430\u0437." 13 | }, 14 | "step": { 15 | "authorize": { 16 | "title": "\u0410\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0456\u044f Home Assistant" 17 | }, 18 | "pat": { 19 | "data": { 20 | "access_token": "\u0422\u043e\u043a\u0435\u043d \u0434\u043e\u0441\u0442\u0443\u043f\u0443" 21 | }, 22 | "description": "\u0412\u0432\u0435\u0434\u0456\u0442\u044c [\u041e\u0441\u043e\u0431\u0438\u0441\u0442\u0438\u0439 \u0442\u043e\u043a\u0435\u043d \u0434\u043e\u0441\u0442\u0443\u043f\u0443 SmartThings] ({token_url}), \u0441\u0442\u0432\u043e\u0440\u0435\u043d\u0438\u0439 \u0432\u0456\u0434\u043f\u043e\u0432\u0456\u0434\u043d\u043e \u0434\u043e [\u0456\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0456\u0457] ({component_url}).", 23 | "title": "\u0412\u0432\u0435\u0434\u0456\u0442\u044c \u043f\u0435\u0440\u0441\u043e\u043d\u0430\u043b\u044c\u043d\u0438\u0439 \u0442\u043e\u043a\u0435\u043d \u0434\u043e\u0441\u0442\u0443\u043f\u0443" 24 | }, 25 | "select_location": { 26 | "data": { 27 | "location_id": "\u041c\u0456\u0441\u0446\u0435\u0437\u043d\u0430\u0445\u043e\u0434\u0436\u0435\u043d\u043d\u044f" 28 | }, 29 | "description": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u043c\u0456\u0441\u0446\u0435 \u0440\u043e\u0437\u0442\u0430\u0448\u0443\u0432\u0430\u043d\u043d\u044f SmartThings, \u044f\u043a\u0438\u0439 \u0432\u0438 \u0445\u043e\u0447\u0435\u0442\u0435 \u0434\u043e\u0434\u0430\u0442\u0438 \u0432 Home Assistant. \u041f\u0456\u0441\u043b\u044f \u0446\u044c\u043e\u0433\u043e \u0432\u0456\u0434\u043a\u0440\u0438\u0454\u0442\u044c\u0441\u044f \u043d\u043e\u0432\u0435 \u0432\u0456\u043a\u043d\u043e, \u0434\u0435 \u043f\u043e\u0442\u0440\u0456\u0431\u043d\u043e \u0431\u0443\u0434\u0435 \u0443\u0432\u0456\u0439\u0442\u0438 \u0432 \u0441\u0438\u0441\u0442\u0435\u043c\u0443 \u0442\u0430 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0443\u0432\u0430\u0442\u0438 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0443 \u0456\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0456\u0457 Home Assistant \u0432 \u043e\u0431\u0440\u0430\u043d\u043e\u043c\u0443 \u043c\u0456\u0441\u0446\u0456 \u0440\u043e\u0437\u0442\u0430\u0448\u0443\u0432\u0430\u043d\u043d\u044f.", 30 | "title": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u043c\u0456\u0441\u0446\u0435\u0437\u043d\u0430\u0445\u043e\u0434\u0436\u0435\u043d\u043d\u044f" 31 | }, 32 | "user": { 33 | "description": "SmartThings \u0431\u0443\u0434\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u043e\u0432\u0430\u043d\u0438\u0439 \u0434\u043b\u044f \u0432\u0456\u0434\u043f\u0440\u0430\u0432\u043a\u0438 push-\u043e\u043d\u043e\u0432\u043b\u0435\u043d\u044c \u0437\u0430 \u0430\u0434\u0440\u0435\u0441\u043e\u044e:\n> {webhook_url} \n\n\u042f\u043a\u0449\u043e \u0446\u0435 \u043d\u0435 \u0442\u0430\u043a, \u043e\u043d\u043e\u0432\u0456\u0442\u044c \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u044e, \u043f\u0435\u0440\u0435\u0437\u0430\u043f\u0443\u0441\u0442\u0456\u0442\u044c Home Assistant \u0456 \u0441\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0449\u0435 \u0440\u0430\u0437.", 34 | "title": "\u041f\u0456\u0434\u0442\u0432\u0435\u0440\u0434\u0436\u0435\u043d\u043d\u044f Callback URL" 35 | } 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /custom_components/smartthings_customize/translations/zh-Hans.json: -------------------------------------------------------------------------------- 1 | { 2 | "config": { 3 | "error": { 4 | "app_setup_error": "\u65e0\u6cd5\u8bbe\u7f6e SmartApp\u3002\u8bf7\u518d\u8bd5\u4e00\u6b21\u3002", 5 | "token_forbidden": "\u4ee4\u724c\u6ca1\u6709\u6240\u9700\u7684 OAuth \u4f5c\u7528\u57df\u3002", 6 | "token_invalid_format": "\u4ee4\u724c\u5fc5\u987b\u7b26\u5408 UID/GUID \u683c\u5f0f", 7 | "token_unauthorized": "\u4ee4\u724c\u65e0\u6548\u6216\u5df2\u5931\u6548\u3002", 8 | "webhook_error": "SmartThings \u65e0\u6cd5\u9a8c\u8bc1 `base_url` \u4e2d\u914d\u7f6e\u7684\u7aef\u70b9\u3002\u8bf7\u67e5\u770b\u7ec4\u4ef6\u9700\u6c42\u3002" 9 | }, 10 | "step": { 11 | "pat": { 12 | "data": { 13 | "access_token": "\u8bbf\u95ee\u4ee4\u724c" 14 | } 15 | }, 16 | "user": { 17 | "description": "\u8bf7\u8f93\u5165\u6309\u7167[\u8bf4\u660e]({component_url})\u521b\u5efa\u7684 SmartThings [\u4e2a\u4eba\u8bbf\u95ee\u4ee4\u724c]({token_url})\u3002", 18 | "title": "\u8f93\u5165\u4e2a\u4eba\u8bbf\u95ee\u4ee4\u724c" 19 | } 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /custom_components/smartthings_customize/translations/zh-Hant.json: -------------------------------------------------------------------------------- 1 | { 2 | "config": { 3 | "abort": { 4 | "invalid_webhook_url": "Home Assistant \u672a\u8a2d\u5b9a\u6b63\u78ba\u4ee5\u63a5\u6536 SmartThings \u66f4\u65b0\u3002Webhook URL \u7121\u6548\uff1a\n> {webhook_url}\n\n\u8acb\u8ddf\u96a8\u6b64[\u6559\u5b78]({component_url}) \u66f4\u65b0\u8a2d\u5b9a\u3002\u91cd\u65b0\u555f\u52d5 Home Assistant \u5f8c\u3001\u518d\n\u8a66\u4e00\u6b21\u3002", 5 | "no_available_locations": "\u6c92\u6709\u53ef\u7528 SmartThings \u4f4d\u7f6e\u4ee5\u8a2d\u5b9a Home Assistant\u3002" 6 | }, 7 | "error": { 8 | "app_setup_error": "\u7121\u6cd5\u8a2d\u5b9a SmartApp\uff0c\u8acb\u518d\u8a66\u4e00\u6b21\u3002", 9 | "token_forbidden": "\u6b0a\u6756\u4e0d\u5177\u6240\u9700\u7684 OAuth \u7bc4\u570d\u3002", 10 | "token_invalid_format": "\u6b0a\u6756\u5fc5\u9808\u70ba UID/GUID \u683c\u5f0f", 11 | "token_unauthorized": "\u6b0a\u6756\u7121\u6548\u6216\u4e0d\u518d\u5177\u6709\u6388\u6b0a\u3002", 12 | "webhook_error": "SmartThings \u7121\u6cd5\u8a8d\u8b49 Webhook URL\u3002\u8acb\u78ba\u8a8d Webhook URL \u53ef\u7531\u7db2\u8def\u5b58\u53d6\u5f8c\u518d\u8a66\u4e00\u6b21\u3002" 13 | }, 14 | "step": { 15 | "authorize": { 16 | "title": "\u8a8d\u8b49 Home Assistant" 17 | }, 18 | "pat": { 19 | "data": { 20 | "access_token": "\u5b58\u53d6\u6b0a\u6756" 21 | }, 22 | "description": "\u8acb\u8f38\u5165\u8ddf\u96a8\u6b64[\u6559\u5b78]({component_url}) \u6240\u5efa\u7acb\u7684 SmartThings [\u500b\u4eba\u5b58\u53d6\u6b0a\u6756]({token_url})\u3002\u5c07\u4f7f\u7528 SmartThings \u5e33\u865f\u65b0\u589e Home Assistant \u6574\u5408\u3002", 23 | "title": "\u8f38\u5165\u500b\u4eba\u5b58\u53d6\u6b0a\u6756" 24 | }, 25 | "select_location": { 26 | "data": { 27 | "location_id": "\u5ea7\u6a19" 28 | }, 29 | "description": "\u8acb\u9078\u64c7\u6240\u8981\u52a0\u5165\u81f3 Home Assistant \u7684 SmartThings \u4f4d\u7f6e\u3002\u5c07\u6703\u958b\u555f\u65b0\u8996\u7a97\u4e26\u8a62\u554f\u767b\u5165\u8207\u8a8d\u8b49\u5b89\u88dd Home Assistant \u6574\u5408\u81f3\u6240\u9078\u4f4d\u7f6e\u3002", 30 | "title": "\u9078\u64c7\u4f4d\u7f6e" 31 | }, 32 | "user": { 33 | "description": "SmartThings \u9700\u8981\u9032\u884c\u8a2d\u5b9a\u4ee5\u50b3\u9001\u63a8\u64a5\u66f4\u65b0\u81f3 Home Assistant\uff0c\u8a2d\u5b9a\u4f4d\u5740\uff1a\n> {webhook_url}\n\n\u5047\u5982\u8cc7\u8a0a\u4e0d\u6b63\u78ba\uff0c\u8acb\u66f4\u65b0\u8a2d\u5b9a\u3001\u91cd\u65b0\u555f\u52d5 Home Assistant \u5f8c\u3001\u518d\u8a66\u4e00\u6b21\u3002", 34 | "title": "\u78ba\u8a8d Callback URL" 35 | } 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /custom_components/smartthings_customize/update.py: -------------------------------------------------------------------------------- 1 | """Support for texts through the SmartThings cloud API.""" 2 | from __future__ import annotations 3 | 4 | from homeassistant.components.update import ( 5 | UpdateEntity, UpdateEntityFeature, ATTR_INSTALLED_VERSION, ATTR_LATEST_VERSION, 6 | ATTR_RELEASE_SUMMARY, ATTR_RELEASE_URL, ATTR_TITLE, ATTR_AUTO_UPDATE, UpdateDeviceClass, ATTR_IN_PROGRESS 7 | ) 8 | from typing import Any 9 | from homeassistant.config_entries import ConfigEntry 10 | from homeassistant.core import HomeAssistant 11 | from homeassistant.helpers.entity_platform import AddEntitiesCallback 12 | 13 | from homeassistant.const import ATTR_DEVICE_CLASS 14 | from .common import * 15 | 16 | from functools import cached_property 17 | 18 | 19 | import logging 20 | _LOGGER = logging.getLogger(__name__) 21 | 22 | async def async_setup_entry( 23 | hass: HomeAssistant, 24 | config_entry: ConfigEntry, 25 | async_add_entities: AddEntitiesCallback, 26 | ) -> None: 27 | """Add update for a config entry.""" 28 | broker = hass.data[DOMAIN][DATA_BROKERS][config_entry.entry_id] 29 | 30 | entities = [] 31 | settings = SettingManager.get_capa_settings(broker, Platform.UPDATE) 32 | for s in settings: 33 | _LOGGER.debug("cap setting : " + str(s[1])) 34 | entities.append(SmartThingsUpdate_custom(hass=hass, setting=s)) 35 | 36 | async_add_entities(entities) 37 | 38 | class SmartThingsUpdate_custom(SmartThingsEntity_custom, UpdateEntity): 39 | def __init__(self, hass, setting) -> None: 40 | super().__init__(hass, platform=Platform.UPDATE, setting=setting) 41 | self._attr_device_class = self.get_attr_value(Platform.UPDATE, ATTR_DEVICE_CLASS, UpdateDeviceClass.FIRMWARE) 42 | self._attr_auto_update = self.get_attr_value(Platform.UPDATE, ATTR_AUTO_UPDATE, False) 43 | self._new_version_available = self.get_attr_value(Platform.UPDATE, "update_available") 44 | 45 | self._attr_supported_features = UpdateEntityFeature(0) 46 | if self.get_command(Platform.UPDATE): 47 | self._attr_supported_features |= UpdateEntityFeature.INSTALL 48 | 49 | if self.get_attr_value(Platform.UPDATE, ATTR_IN_PROGRESS): 50 | self._attr_supported_features |= UpdateEntityFeature.PROGRESS 51 | 52 | if self.get_attr_value(Platform.UPDATE, ATTR_RELEASE_SUMMARY) or self.get_attr_value(Platform.UPDATE, ATTR_RELEASE_URL) != None: 53 | self._attr_supported_features |= UpdateEntityFeature.RELEASE_NOTES 54 | 55 | @cached_property 56 | def installed_version(self) -> str | None: 57 | if self._new_version_available != None: 58 | return False 59 | else: 60 | return self.get_attr_value(Platform.UPDATE, ATTR_INSTALLED_VERSION) 61 | 62 | @cached_property 63 | def latest_version(self) -> str | None: 64 | if self._new_version_available != None: 65 | return self._new_version_available 66 | else: 67 | return self.get_attr_value(Platform.UPDATE, ATTR_LATEST_VERSION) 68 | 69 | @cached_property 70 | def in_progress(self) -> bool | int | None: 71 | return self.get_attr_value(Platform.UPDATE, ATTR_IN_PROGRESS) 72 | 73 | @cached_property 74 | def release_summary(self) -> str | None: 75 | return self.get_attr_value(Platform.UPDATE, ATTR_RELEASE_SUMMARY) 76 | 77 | @cached_property 78 | def release_url(self) -> str | None: 79 | return self.get_attr_value(Platform.UPDATE, ATTR_RELEASE_URL) 80 | 81 | @cached_property 82 | def title(self) -> str | None: 83 | return self.get_attr_value(Platform.UPDATE, ATTR_TITLE) 84 | 85 | async def async_install(self, version: str | None, backup: bool, **kwargs: Any) -> None: 86 | return self.send_command(Platform.UPDATE, self.get_command(Platform.UPDATE), []) 87 | 88 | async def async_release_notes(self) -> str | None: 89 | return self.get_attr_value(Platform.UPDATE, ATTR_RELEASE_URL) 90 | -------------------------------------------------------------------------------- /custom_components/smartthings_customize/vacuum.py: -------------------------------------------------------------------------------- 1 | """Support for texts through the SmartThings cloud API.""" 2 | from __future__ import annotations 3 | 4 | from collections.abc import Sequence 5 | from typing import Any 6 | 7 | from .pysmartthings import Capability 8 | 9 | from homeassistant.components.vacuum import StateVacuumEntity, VacuumEntityFeature 10 | from homeassistant.config_entries import ConfigEntry 11 | from homeassistant.core import HomeAssistant 12 | from homeassistant.helpers.entity_platform import AddEntitiesCallback 13 | 14 | from .common import * 15 | 16 | import logging 17 | _LOGGER = logging.getLogger(__name__) 18 | 19 | CONF_COMMANDS = "commands" 20 | CONF_FAN_SPEED = "fan_speed" 21 | CONF_FAN_SPEED_MAPPING = "s2h_fan_speed_mapping" 22 | 23 | 24 | async def async_setup_entry( 25 | hass: HomeAssistant, 26 | config_entry: ConfigEntry, 27 | async_add_entities: AddEntitiesCallback, 28 | ) -> None: 29 | """Add switches for a config entry.""" 30 | broker = hass.data[DOMAIN][DATA_BROKERS][config_entry.entry_id] 31 | 32 | entities = [] 33 | settings = SettingManager.get_capa_settings(broker, Platform.VACUUM) 34 | for s in settings: 35 | _LOGGER.debug("cap setting : " + str(s[1])) 36 | entities.append(SmartThingsVacuum_custom(hass=hass, setting=s)) 37 | 38 | async_add_entities(entities) 39 | 40 | 41 | class SmartThingsVacuum_custom(SmartThingsEntity_custom, StateVacuumEntity): 42 | def __init__(self, hass, setting) -> None: 43 | super().__init__(hass, platform=Platform.VACUUM, setting=setting) 44 | 45 | self._supported_features = 0 46 | self._fan_speed_list = [] 47 | for capa in setting[1]["capabilities"]: 48 | if "commands" in capa: 49 | self._capability["commands"] = capa 50 | self._supported_features |= VacuumEntityFeature.RETURN_HOME 51 | #self._supported_features |= VacuumEntityFeature.CLEAN_SPOT 52 | self._supported_features |= VacuumEntityFeature.STOP 53 | self._supported_features |= VacuumEntityFeature.START 54 | #self._supported_features |= VacuumEntityFeature.LOCATE 55 | self._supported_features |= VacuumEntityFeature.STATE 56 | self._supported_features |= VacuumEntityFeature.SEND_COMMAND 57 | elif "fan_speed" in capa: 58 | self._capability["fan_speed"] = capa 59 | self._supported_features |= VacuumEntityFeature.FAN_SPEED 60 | 61 | # fan_modes 62 | if self.get_attr_value("fan_speed", CONF_OPTIONS): 63 | mode = self.get_attr_value("fan_speed", CONF_OPTIONS) 64 | # convert hvac_modes 65 | modes = [] 66 | modes.extend(self.get_attr_value(CONF_FAN_SPEED, CONF_OPTIONS)) 67 | modes = list(set(modes)) 68 | fan_modes = self.get_attr_value( 69 | CONF_FAN_SPEED, CONF_FAN_SPEED_MAPPING, [{}]) 70 | for mode in modes: 71 | self._fan_speed_list.append(fan_modes[0].get(mode, mode)) 72 | 73 | @property 74 | def fan_speed_list(self) -> list[str]: 75 | return self._fan_speed_list 76 | 77 | @property 78 | def supported_features(self) -> int | None: 79 | return self._supported_features 80 | 81 | @property 82 | def state(self) -> str | None: 83 | """Return the state of the vacuum cleaner.""" 84 | mode = self.get_attr_value(CONF_COMMANDS, CONF_STATE) 85 | state_modes = self.get_attr_value(CONF_COMMANDS, CONF_STATE_MAPPING, [{}]) 86 | return state_modes[0].get(mode, mode) 87 | 88 | 89 | # @property 90 | # def battery_level(self) -> int | None: 91 | # """Return the battery level of the vacuum cleaner.""" 92 | # return self.get_attr_status("battery", "battery") 93 | 94 | # @property 95 | # def battery_icon(self) -> str: 96 | # """Return the battery icon for the vacuum cleaner.""" 97 | # return icon_for_battery_level( 98 | # battery_level=self.get_attr_status("battery", "battery"), charging=(self.state == STATE_DOCKED) 99 | # ) 100 | 101 | @property 102 | def fan_speed(self) -> str | None: 103 | """Return the fan speed of the vacuum cleaner.""" 104 | speed = self.get_attr_value(CONF_FAN_SPEED, CONF_STATE) 105 | fan_speed = self.get_attr_value(CONF_FAN_SPEED, CONF_FAN_SPEED_MAPPING, [{}]) 106 | return fan_speed[0].get(speed, speed) 107 | 108 | async def async_return_to_base(self, **kwargs: Any) -> None: 109 | await self.send_command(CONF_COMMANDS, self.get_command(CONF_COMMANDS), self.get_argument(CONF_COMMANDS).get("return")) 110 | 111 | async def async_start(self, **kwargs: Any) -> None: 112 | """Turn the vacuum on and start cleaning.""" 113 | await self.send_command(CONF_COMMANDS, self.get_command(CONF_COMMANDS), self.get_argument(CONF_COMMANDS).get("start")) 114 | 115 | async def async_stop(self, **kwargs: Any) -> None: 116 | """Stop the vacuum cleaner.""" 117 | await self.send_command(CONF_COMMANDS, self.get_command(CONF_COMMANDS), self.get_argument(CONF_COMMANDS).get("stop")) 118 | 119 | # def clean_spot(self, **kwargs: Any) -> None: 120 | # """Perform a spot clean-up.""" 121 | # _LOGGER.error("call clean spot, arg: " + str(kwargs)) 122 | # # self.device.run(sucks.Spot()) 123 | 124 | # def locate(self, **kwargs: Any) -> None: 125 | # """Locate the vacuum cleaner.""" 126 | # # self.device.run(sucks.PlaySound()) 127 | 128 | async def async_set_fan_speed(self, fan_speed: str, **kwargs: Any) -> None: 129 | """Set fan speed.""" 130 | fan_modes = self.get_attr_value(CONF_FAN_SPEED, CONF_FAN_SPEED_MAPPING, [{}]) 131 | mode = fan_speed 132 | for k, v in fan_modes[0].items(): 133 | if v == fan_speed: 134 | mode = k 135 | break 136 | if mode != self.get_attr_value(CONF_FAN_SPEED, CONF_STATE): 137 | await self.send_command(CONF_FAN_SPEED, self.get_command(CONF_FAN_SPEED), [mode]) 138 | 139 | 140 | async def async_send_command( 141 | self, 142 | command: str, 143 | params: dict[str, Any] | list[Any] | None = None, 144 | **kwargs: Any, 145 | ) -> None: 146 | """Send a command to a vacuum cleaner.""" 147 | await self.send_command(CONF_COMMANDS, command, params) 148 | 149 | -------------------------------------------------------------------------------- /custom_components/smartthings_customize/valve.py: -------------------------------------------------------------------------------- 1 | """Support for switches through the SmartThings cloud API.""" 2 | from __future__ import annotations 3 | from .common import * 4 | 5 | import math 6 | 7 | from homeassistant.components.valve import * 8 | from homeassistant.config_entries import ConfigEntry 9 | from homeassistant.core import HomeAssistant 10 | from homeassistant.helpers.entity_platform import AddEntitiesCallback 11 | 12 | from homeassistant.const import STATE_CLOSED, STATE_OPEN, STATE_CLOSING 13 | from homeassistant.util.percentage import ranged_value_to_percentage, percentage_to_ranged_value 14 | 15 | from .const import * 16 | 17 | import logging 18 | _LOGGER = logging.getLogger(__name__) 19 | 20 | ATTR_VALVE = "valve" 21 | ATTR_STOP = "stop" 22 | 23 | async def async_setup_entry( 24 | hass: HomeAssistant, 25 | config_entry: ConfigEntry, 26 | async_add_entities: AddEntitiesCallback, 27 | ) -> None: 28 | """Add switches for a config entry.""" 29 | broker = hass.data[DOMAIN][DATA_BROKERS][config_entry.entry_id] 30 | entities = [] 31 | 32 | settings = SettingManager.get_capa_settings(broker, Platform.VALVE) 33 | for s in settings: 34 | 35 | entities.append(SmartThingsValve_custom(hass=hass, setting=s)) 36 | 37 | async_add_entities(entities) 38 | 39 | 40 | class SmartThingsValve_custom(SmartThingsEntity_custom, ValveEntity): 41 | def __init__(self, hass, setting) -> None: 42 | super().__init__(hass, platform=Platform.CLIMATE, setting=setting) 43 | _LOGGER.debug("valve setting : " + str(setting[1])) 44 | self._prev_position = None 45 | self._attr_reports_position = False 46 | self._percent_range = (0, 100) 47 | self._attr_device_class = setting[1].get(CONF_DEVICE_CLASS) 48 | self._target_position = None 49 | 50 | for capa in setting[1]["capabilities"]: 51 | if ATTR_VALVE in capa: 52 | self._capability[ATTR_VALVE] = capa 53 | if capa.get(CONF_COMMAND, {}).get(STATE_OPEN): 54 | self._attr_supported_features |= ValveEntityFeature.OPEN 55 | if capa.get(CONF_COMMAND, {}).get(STATE_CLOSED): 56 | self._attr_supported_features |= ValveEntityFeature.CLOSE 57 | elif ATTR_POSITION in capa: 58 | self._capability[ATTR_POSITION] = capa 59 | self._attr_supported_features |= ValveEntityFeature.SET_POSITION 60 | self._attr_reports_position = True 61 | open = float(self.get_attr_value(ATTR_POSITION, STATE_OPEN, 100)) 62 | close = float(self.get_attr_value(ATTR_POSITION, STATE_CLOSED, 0)) 63 | close = (close + 1 if open > close else close - 1) 64 | self._percent_range = (close, open) 65 | elif ATTR_STOP in capa: 66 | self._capability[ATTR_STOP] = capa 67 | self._attr_supported_features |= ValveEntityFeature.STOP 68 | 69 | # @property 70 | # def is_opening(self) -> bool | None: 71 | # self.get_attr_value(Platform.VALVE, ATTR_CURRENT_POSITION, 0) 72 | 73 | # return super().is_opening 74 | 75 | @property 76 | def is_closed(self) -> bool | None: 77 | state = self.get_attr_value(Platform.VALVE, CONF_STATE) 78 | open_state = self.get_attr_value(Platform.VALVE, "open_state") 79 | self._attr_is_closed = state not in open_state 80 | return self._attr_is_closed 81 | 82 | # @property 83 | # def is_closing(self) -> bool | None: 84 | # return super().is_closing 85 | 86 | @property 87 | def current_valve_position(self) -> int | None: 88 | self._attr_current_valve_position = self.get_attr_value(ATTR_POSITION, CONF_STATE, self.get_attr_value(ATTR_POSITION, STATE_CLOSED)) 89 | 90 | open = self.get_attr_value(ATTR_POSITION, STATE_OPEN) 91 | close = self.get_attr_value(ATTR_POSITION, STATE_CLOSED) 92 | self._attr_is_closed = True 93 | 94 | if eq(self._attr_current_valve_position, close): 95 | self._attr_is_closed = False 96 | 97 | if self._target_position is not None and self._target_position == self._attr_current_valve_position: 98 | self._attr_is_closing = False 99 | self._attr_is_opening = False 100 | self._target_position = None 101 | elif self._target_position is not None and self._target_position != self._attr_current_valve_position and self._prev_position is not None: 102 | if open > close: 103 | if self._attr_current_valve_position < self._prev_position: 104 | self._attr_is_closing = True 105 | self._attr_is_opening = False 106 | else: 107 | self._attr_is_closing = False 108 | self._attr_is_opening = True 109 | else: 110 | if self._attr_current_valve_position < self._prev_position: 111 | self._attr_is_closing = False 112 | self._attr_is_opening = True 113 | else: 114 | self._attr_is_closing = True 115 | self._attr_is_opening = False 116 | 117 | self._prev_position = self._attr_current_valve_position 118 | state = ranged_value_to_percentage(self._percent_range, float(self._attr_current_valve_position)) 119 | return state 120 | 121 | async def async_close_valve(self) -> None: 122 | await self.send_command(ATTR_VALVE, self.get_command(ATTR_VALVE, {STATE_CLOSED: STATE_CLOSED}).get(STATE_CLOSED), self.get_argument(ATTR_VALVE, {STATE_CLOSED: []}).get(STATE_CLOSED, [])) 123 | 124 | async def async_open_valve(self) -> None: 125 | await self.send_command(ATTR_VALVE, self.get_command(ATTR_VALVE, {STATE_OPEN: STATE_OPEN}).get(STATE_OPEN), self.get_argument(ATTR_VALVE, {STATE_OPEN: []}).get(STATE_OPEN, [])) 126 | 127 | async def async_set_valve_position(self, position: int) -> None: 128 | value = int(math.ceil(percentage_to_ranged_value(self._percent_range, position))) 129 | #_LOGGER.error("async_set_position : " + str(position)) 130 | self._target_position = value 131 | await self.send_command(ATTR_POSITION, self.get_command(ATTR_POSITION), [value]) 132 | 133 | if self._capability.get(ATTR_VALVE): 134 | if eq(value, self.get_attr_value(ATTR_POSITION, STATE_CLOSED, 0)): 135 | await self.async_close_valve() 136 | else: 137 | await self.async_open_valve() 138 | 139 | async def async_stop_valve(self) -> None: 140 | self._target_position = None 141 | await self.send_command(ATTR_STOP, self.get_command(ATTR_STOP), self.get_argument(ATTR_STOP)) 142 | 143 | -------------------------------------------------------------------------------- /hacs.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "SmartThings Customize", 3 | "render_readme": true 4 | } 5 | --------------------------------------------------------------------------------