From b173ae7f444a330f92c25dfb5e3d581616a768cd Mon Sep 17 00:00:00 2001 From: Erik Montnemery <erik@montnemery.com> Date: Wed, 28 Sep 2022 17:31:48 +0200 Subject: [PATCH] Add support for integrations v2 (#78801) Co-authored-by: Martin Hjelmare <marhje52@gmail.com> --- homeassistant/brands/google.json | 5 + .../components/websocket_api/commands.py | 12 + homeassistant/generated/integrations.json | 5290 +++++++++++++++++ homeassistant/loader.py | 39 + script/hassfest/__main__.py | 2 +- script/hassfest/brand.py | 71 + script/hassfest/config_flow.py | 115 +- script/hassfest/model.py | 62 + script/hassfest/translations.py | 22 +- .../components/websocket_api/test_commands.py | 17 + 10 files changed, 5623 insertions(+), 12 deletions(-) create mode 100644 homeassistant/brands/google.json create mode 100644 homeassistant/generated/integrations.json create mode 100644 script/hassfest/brand.py diff --git a/homeassistant/brands/google.json b/homeassistant/brands/google.json new file mode 100644 index 00000000000..c50b0819827 --- /dev/null +++ b/homeassistant/brands/google.json @@ -0,0 +1,5 @@ +{ + "domain": "google", + "name": "Google", + "integrations": ["google", "google_sheets"] +} diff --git a/homeassistant/components/websocket_api/commands.py b/homeassistant/components/websocket_api/commands.py index c42d48a604e..a78099e6065 100644 --- a/homeassistant/components/websocket_api/commands.py +++ b/homeassistant/components/websocket_api/commands.py @@ -35,6 +35,7 @@ from homeassistant.loader import ( Integration, IntegrationNotFound, async_get_integration, + async_get_integration_descriptions, async_get_integrations, ) from homeassistant.setup import DATA_SETUP_TIME, async_get_loaded_integrations @@ -75,6 +76,7 @@ def async_register_commands( async_reg(hass, handle_subscribe_entities) async_reg(hass, handle_supported_brands) async_reg(hass, handle_supported_features) + async_reg(hass, handle_integration_descriptions) def pong_message(iden: int) -> dict[str, Any]: @@ -741,3 +743,13 @@ def handle_supported_features( """Handle setting supported features.""" connection.supported_features = msg["features"] connection.send_result(msg["id"]) + + +@decorators.require_admin +@decorators.websocket_command({"type": "integration/descriptions"}) +@decorators.async_response +async def handle_integration_descriptions( + hass: HomeAssistant, connection: ActiveConnection, msg: dict[str, Any] +) -> None: + """Get metadata for all brands and integrations.""" + connection.send_result(msg["id"], await async_get_integration_descriptions(hass)) diff --git a/homeassistant/generated/integrations.json b/homeassistant/generated/integrations.json new file mode 100644 index 00000000000..a2279f3396c --- /dev/null +++ b/homeassistant/generated/integrations.json @@ -0,0 +1,5290 @@ +{ + "integration": { + "abode": { + "config_flow": true, + "iot_class": "cloud_push", + "name": "Abode" + }, + "accuweather": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "AccuWeather" + }, + "acer_projector": { + "config_flow": false, + "iot_class": "local_polling", + "name": "Acer Projector" + }, + "acmeda": { + "config_flow": true, + "iot_class": "local_push", + "name": "Rollease Acmeda Automate" + }, + "actiontec": { + "config_flow": false, + "iot_class": "local_polling", + "name": "Actiontec" + }, + "adax": { + "config_flow": true, + "iot_class": "local_polling", + "name": "Adax" + }, + "adguard": { + "config_flow": true, + "iot_class": "local_polling", + "name": "AdGuard Home" + }, + "ads": { + "config_flow": false, + "iot_class": "local_push", + "name": "ADS" + }, + "advantage_air": { + "config_flow": true, + "iot_class": "local_polling", + "name": "Advantage Air" + }, + "aemet": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "AEMET OpenData" + }, + "aftership": { + "config_flow": false, + "iot_class": "cloud_polling", + "name": "AfterShip" + }, + "agent_dvr": { + "config_flow": true, + "iot_class": "local_polling", + "name": "Agent DVR" + }, + "airly": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "Airly" + }, + "airnow": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "AirNow" + }, + "airthings": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "Airthings" + }, + "airtouch4": { + "config_flow": true, + "iot_class": "local_polling", + "name": "AirTouch 4" + }, + "airvisual": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "AirVisual" + }, + "airzone": { + "config_flow": true, + "iot_class": "local_polling", + "name": "Airzone" + }, + "aladdin_connect": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "Aladdin Connect" + }, + "alarm_control_panel": { + "config_flow": false, + "iot_class": null + }, + "alarmdecoder": { + "config_flow": true, + "iot_class": "local_push", + "name": "AlarmDecoder" + }, + "alert": { + "config_flow": false, + "iot_class": "local_push", + "name": "Alert" + }, + "alexa": { + "config_flow": false, + "iot_class": "cloud_push", + "name": "Amazon Alexa" + }, + "almond": { + "config_flow": true, + "iot_class": "local_polling", + "name": "Almond" + }, + "alpha_vantage": { + "config_flow": false, + "iot_class": "cloud_polling", + "name": "Alpha Vantage" + }, + "amazon_polly": { + "config_flow": false, + "iot_class": "cloud_push", + "name": "Amazon Polly" + }, + "amberelectric": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "Amber Electric" + }, + "ambiclimate": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "Ambiclimate" + }, + "ambient_station": { + "config_flow": true, + "iot_class": "cloud_push", + "name": "Ambient Weather Station" + }, + "amcrest": { + "config_flow": false, + "iot_class": "local_polling", + "name": "Amcrest" + }, + "ampio": { + "config_flow": false, + "iot_class": "cloud_polling", + "name": "Ampio Smart Smog System" + }, + "android_ip_webcam": { + "config_flow": true, + "iot_class": "local_polling", + "name": "Android IP Webcam" + }, + "androidtv": { + "config_flow": true, + "iot_class": "local_polling", + "name": "Android TV" + }, + "anel_pwrctrl": { + "config_flow": false, + "iot_class": "local_polling", + "name": "Anel NET-PwrCtrl" + }, + "anthemav": { + "config_flow": true, + "iot_class": "local_push", + "name": "Anthem A/V Receivers" + }, + "apache_kafka": { + "config_flow": false, + "iot_class": "local_push", + "name": "Apache Kafka" + }, + "apcupsd": { + "config_flow": true, + "iot_class": "local_polling", + "name": "APC UPS Daemon" + }, + "api": { + "config_flow": false, + "iot_class": null, + "name": "Home Assistant API" + }, + "apple_tv": { + "config_flow": true, + "iot_class": "local_push", + "name": "Apple TV" + }, + "application_credentials": { + "config_flow": false, + "iot_class": null + }, + "apprise": { + "config_flow": false, + "iot_class": "cloud_push", + "name": "Apprise" + }, + "aprs": { + "config_flow": false, + "iot_class": "cloud_push", + "name": "APRS" + }, + "aqualogic": { + "config_flow": false, + "iot_class": "local_push", + "name": "AquaLogic" + }, + "aquostv": { + "config_flow": false, + "iot_class": "local_polling", + "name": "Sharp Aquos TV" + }, + "arcam_fmj": { + "config_flow": true, + "iot_class": "local_polling", + "name": "Arcam FMJ Receivers" + }, + "arest": { + "config_flow": false, + "iot_class": "local_polling", + "name": "aREST" + }, + "arris_tg2492lg": { + "config_flow": false, + "iot_class": "local_polling", + "name": "Arris TG2492LG" + }, + "aruba": { + "config_flow": false, + "iot_class": "local_polling", + "name": "Aruba" + }, + "arwn": { + "config_flow": false, + "iot_class": "local_polling", + "name": "Ambient Radio Weather Network" + }, + "aseko_pool_live": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "Aseko Pool Live" + }, + "asterisk_cdr": { + "config_flow": false, + "iot_class": "local_polling", + "name": "Asterisk Call Detail Records" + }, + "asterisk_mbox": { + "config_flow": false, + "iot_class": "local_push", + "name": "Asterisk Voicemail" + }, + "asuswrt": { + "config_flow": true, + "iot_class": "local_polling", + "name": "ASUSWRT" + }, + "atag": { + "config_flow": true, + "iot_class": "local_polling", + "name": "Atag" + }, + "aten_pe": { + "config_flow": false, + "iot_class": "local_polling", + "name": "ATEN Rack PDU" + }, + "atome": { + "config_flow": false, + "iot_class": "cloud_polling", + "name": "Atome Linky" + }, + "august": { + "config_flow": true, + "iot_class": "cloud_push", + "name": "August" + }, + "aurora": { + "config_flow": true, + "iot_class": "cloud_polling" + }, + "aurora_abb_powerone": { + "config_flow": true, + "iot_class": "local_polling", + "name": "Aurora ABB PowerOne Solar PV" + }, + "aussie_broadband": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "Aussie Broadband" + }, + "auth": { + "config_flow": false, + "iot_class": null, + "name": "Auth" + }, + "automation": { + "config_flow": false, + "iot_class": null + }, + "avea": { + "config_flow": false, + "iot_class": "local_polling", + "name": "Elgato Avea" + }, + "avion": { + "config_flow": false, + "iot_class": "assumed_state", + "name": "Avi-on" + }, + "awair": { + "config_flow": true, + "iot_class": "local_polling", + "name": "Awair" + }, + "aws": { + "config_flow": false, + "iot_class": "cloud_push", + "name": "Amazon Web Services (AWS)" + }, + "axis": { + "config_flow": true, + "iot_class": "local_push", + "name": "Axis" + }, + "azure_devops": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "Azure DevOps" + }, + "azure_event_hub": { + "config_flow": true, + "iot_class": "cloud_push", + "name": "Azure Event Hub" + }, + "azure_service_bus": { + "config_flow": false, + "iot_class": "cloud_push", + "name": "Azure Service Bus" + }, + "backup": { + "config_flow": false, + "iot_class": "calculated", + "name": "Backup" + }, + "baf": { + "config_flow": true, + "iot_class": "local_push", + "name": "Big Ass Fans" + }, + "baidu": { + "config_flow": false, + "iot_class": "cloud_push", + "name": "Baidu" + }, + "balboa": { + "config_flow": true, + "iot_class": "local_push", + "name": "Balboa Spa Client" + }, + "bayesian": { + "config_flow": false, + "iot_class": "local_polling", + "name": "Bayesian" + }, + "bbox": { + "config_flow": false, + "iot_class": "local_polling", + "name": "Bbox" + }, + "beewi_smartclim": { + "config_flow": false, + "iot_class": "local_polling", + "name": "BeeWi SmartClim BLE sensor" + }, + "binary_sensor": { + "config_flow": false, + "iot_class": null + }, + "bitcoin": { + "config_flow": false, + "iot_class": "cloud_polling", + "name": "Bitcoin" + }, + "bizkaibus": { + "config_flow": false, + "iot_class": "cloud_polling", + "name": "Bizkaibus" + }, + "blackbird": { + "config_flow": false, + "iot_class": "local_polling", + "name": "Monoprice Blackbird Matrix Switch" + }, + "blebox": { + "config_flow": true, + "iot_class": "local_polling", + "name": "BleBox devices" + }, + "blink": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "Blink" + }, + "blinksticklight": { + "config_flow": false, + "iot_class": "local_polling", + "name": "BlinkStick" + }, + "blockchain": { + "config_flow": false, + "iot_class": "cloud_polling", + "name": "Blockchain.com" + }, + "bloomsky": { + "config_flow": false, + "iot_class": "cloud_polling", + "name": "BloomSky" + }, + "bluemaestro": { + "config_flow": true, + "iot_class": "local_push", + "name": "BlueMaestro" + }, + "blueprint": { + "config_flow": false, + "iot_class": null, + "name": "Blueprint" + }, + "bluesound": { + "config_flow": false, + "iot_class": "local_polling", + "name": "Bluesound" + }, + "bluetooth": { + "config_flow": true, + "iot_class": "local_push", + "name": "Bluetooth" + }, + "bluetooth_le_tracker": { + "config_flow": false, + "iot_class": "local_push", + "name": "Bluetooth LE Tracker" + }, + "bluetooth_tracker": { + "config_flow": false, + "iot_class": "local_polling", + "name": "Bluetooth Tracker" + }, + "bmw_connected_drive": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "BMW Connected Drive" + }, + "bond": { + "config_flow": true, + "iot_class": "local_push", + "name": "Bond" + }, + "bosch_shc": { + "config_flow": true, + "iot_class": "local_push", + "name": "Bosch SHC" + }, + "braviatv": { + "config_flow": true, + "iot_class": "local_polling", + "name": "Sony Bravia TV" + }, + "broadlink": { + "config_flow": true, + "iot_class": "local_polling", + "name": "Broadlink" + }, + "brother": { + "config_flow": true, + "iot_class": "local_polling", + "name": "Brother Printer" + }, + "brottsplatskartan": { + "config_flow": false, + "iot_class": "cloud_polling", + "name": "Brottsplatskartan" + }, + "browser": { + "config_flow": false, + "iot_class": "local_push", + "name": "Browser" + }, + "brunt": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "Brunt Blind Engine" + }, + "bsblan": { + "config_flow": true, + "iot_class": "local_polling", + "name": "BSB-Lan" + }, + "bt_home_hub_5": { + "config_flow": false, + "iot_class": "local_polling", + "name": "BT Home Hub 5" + }, + "bt_smarthub": { + "config_flow": false, + "iot_class": "local_polling", + "name": "BT Smart Hub" + }, + "bthome": { + "config_flow": true, + "iot_class": "local_push", + "name": "BTHome" + }, + "buienradar": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "Buienradar" + }, + "button": { + "config_flow": false, + "iot_class": null + }, + "caldav": { + "config_flow": false, + "iot_class": "cloud_polling", + "name": "CalDAV" + }, + "calendar": { + "config_flow": false, + "iot_class": null + }, + "camera": { + "config_flow": false, + "iot_class": null + }, + "canary": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "Canary" + }, + "cast": { + "config_flow": true, + "iot_class": "local_polling", + "name": "Google Cast" + }, + "cert_expiry": { + "config_flow": true, + "iot_class": "cloud_polling" + }, + "channels": { + "config_flow": false, + "iot_class": "local_polling", + "name": "Channels" + }, + "circuit": { + "config_flow": false, + "iot_class": "cloud_push", + "name": "Unify Circuit" + }, + "cisco_ios": { + "config_flow": false, + "iot_class": "local_polling", + "name": "Cisco IOS" + }, + "cisco_mobility_express": { + "config_flow": false, + "iot_class": "local_polling", + "name": "Cisco Mobility Express" + }, + "cisco_webex_teams": { + "config_flow": false, + "iot_class": "cloud_push", + "name": "Cisco Webex Teams" + }, + "citybikes": { + "config_flow": false, + "iot_class": "cloud_polling", + "name": "CityBikes" + }, + "clementine": { + "config_flow": false, + "iot_class": "local_polling", + "name": "Clementine Music Player" + }, + "clickatell": { + "config_flow": false, + "iot_class": "cloud_push", + "name": "Clickatell" + }, + "clicksend": { + "config_flow": false, + "iot_class": "cloud_push", + "name": "ClickSend SMS" + }, + "clicksend_tts": { + "config_flow": false, + "iot_class": "cloud_push", + "name": "ClickSend TTS" + }, + "climate": { + "config_flow": false, + "iot_class": null + }, + "cloud": { + "config_flow": false, + "iot_class": "cloud_push", + "name": "Home Assistant Cloud" + }, + "cloudflare": { + "config_flow": true, + "iot_class": "cloud_push", + "name": "Cloudflare" + }, + "cmus": { + "config_flow": false, + "iot_class": "local_polling", + "name": "cmus" + }, + "co2signal": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "CO2 Signal" + }, + "coinbase": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "Coinbase" + }, + "color_extractor": { + "config_flow": false, + "iot_class": null, + "name": "ColorExtractor" + }, + "comed_hourly_pricing": { + "config_flow": false, + "iot_class": "cloud_polling", + "name": "ComEd Hourly Pricing" + }, + "comfoconnect": { + "config_flow": false, + "iot_class": "local_push", + "name": "Zehnder ComfoAir Q" + }, + "command_line": { + "config_flow": false, + "iot_class": "local_polling", + "name": "Command Line" + }, + "compensation": { + "config_flow": false, + "iot_class": "calculated", + "name": "Compensation" + }, + "concord232": { + "config_flow": false, + "iot_class": "local_polling", + "name": "Concord232" + }, + "config": { + "config_flow": false, + "iot_class": null, + "name": "Configuration" + }, + "configurator": { + "config_flow": false, + "iot_class": null + }, + "control4": { + "config_flow": true, + "iot_class": "local_polling", + "name": "Control4" + }, + "conversation": { + "config_flow": false, + "iot_class": "local_push" + }, + "coolmaster": { + "config_flow": true, + "iot_class": "local_polling", + "name": "CoolMasterNet" + }, + "coronavirus": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "Coronavirus (COVID-19)" + }, + "cover": { + "config_flow": false, + "iot_class": null + }, + "cppm_tracker": { + "config_flow": false, + "iot_class": "local_polling", + "name": "Aruba ClearPass" + }, + "cpuspeed": { + "config_flow": true, + "iot_class": "local_push" + }, + "crownstone": { + "config_flow": true, + "iot_class": "cloud_push", + "name": "Crownstone" + }, + "cups": { + "config_flow": false, + "iot_class": "local_polling", + "name": "CUPS" + }, + "currencylayer": { + "config_flow": false, + "iot_class": "cloud_polling", + "name": "currencylayer" + }, + "daikin": { + "config_flow": true, + "iot_class": "local_polling", + "name": "Daikin AC" + }, + "danfoss_air": { + "config_flow": false, + "iot_class": "local_polling", + "name": "Danfoss Air" + }, + "darksky": { + "config_flow": false, + "iot_class": "cloud_polling", + "name": "Dark Sky" + }, + "datadog": { + "config_flow": false, + "iot_class": "local_push", + "name": "Datadog" + }, + "ddwrt": { + "config_flow": false, + "iot_class": "local_polling", + "name": "DD-WRT" + }, + "debugpy": { + "config_flow": false, + "iot_class": "local_push", + "name": "Remote Python Debugger" + }, + "deconz": { + "config_flow": true, + "iot_class": "local_push", + "name": "deCONZ" + }, + "decora": { + "config_flow": false, + "iot_class": "local_polling", + "name": "Leviton Decora" + }, + "decora_wifi": { + "config_flow": false, + "iot_class": "cloud_polling", + "name": "Leviton Decora Wi-Fi" + }, + "default_config": { + "config_flow": false, + "iot_class": null, + "name": "Default Config" + }, + "delijn": { + "config_flow": false, + "iot_class": "cloud_polling", + "name": "De Lijn" + }, + "deluge": { + "config_flow": true, + "iot_class": "local_polling", + "name": "Deluge" + }, + "demo": { + "config_flow": false, + "iot_class": "calculated" + }, + "denon": { + "config_flow": false, + "iot_class": "local_polling", + "name": "Denon Network Receivers" + }, + "denonavr": { + "config_flow": true, + "iot_class": "local_polling", + "name": "Denon AVR Network Receivers" + }, + "deutsche_bahn": { + "config_flow": false, + "iot_class": "cloud_polling", + "name": "Deutsche Bahn" + }, + "device_automation": { + "config_flow": false, + "iot_class": null, + "name": "Device Automation" + }, + "device_sun_light_trigger": { + "config_flow": false, + "iot_class": "calculated", + "name": "Presence-based Lights" + }, + "device_tracker": { + "config_flow": false, + "iot_class": null + }, + "devolo_home_control": { + "config_flow": true, + "iot_class": "local_push", + "name": "devolo Home Control" + }, + "devolo_home_network": { + "config_flow": true, + "iot_class": "local_polling", + "name": "devolo Home Network" + }, + "dexcom": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "Dexcom" + }, + "dhcp": { + "config_flow": false, + "iot_class": "local_push", + "name": "DHCP Discovery" + }, + "diagnostics": { + "config_flow": false, + "iot_class": null + }, + "dialogflow": { + "config_flow": true, + "iot_class": "cloud_push", + "name": "Dialogflow" + }, + "digital_ocean": { + "config_flow": false, + "iot_class": "local_polling", + "name": "Digital Ocean" + }, + "directv": { + "config_flow": true, + "iot_class": "local_polling", + "name": "DirecTV" + }, + "discogs": { + "config_flow": false, + "iot_class": "cloud_polling", + "name": "Discogs" + }, + "discord": { + "config_flow": true, + "iot_class": "cloud_push", + "name": "Discord" + }, + "discovery": { + "config_flow": false, + "iot_class": null, + "name": "Discovery" + }, + "dlib_face_detect": { + "config_flow": false, + "iot_class": "local_push", + "name": "Dlib Face Detect" + }, + "dlib_face_identify": { + "config_flow": false, + "iot_class": "local_push", + "name": "Dlib Face Identify" + }, + "dlink": { + "config_flow": false, + "iot_class": "local_polling", + "name": "D-Link Wi-Fi Smart Plugs" + }, + "dlna_dmr": { + "config_flow": true, + "iot_class": "local_push", + "name": "DLNA Digital Media Renderer" + }, + "dlna_dms": { + "config_flow": true, + "iot_class": "local_polling", + "name": "DLNA Digital Media Server" + }, + "dnsip": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "DNS IP" + }, + "dominos": { + "config_flow": false, + "iot_class": "cloud_polling", + "name": "Dominos Pizza" + }, + "doods": { + "config_flow": false, + "iot_class": "local_polling", + "name": "DOODS - Dedicated Open Object Detection Service" + }, + "doorbird": { + "config_flow": true, + "iot_class": "local_push", + "name": "DoorBird" + }, + "dovado": { + "config_flow": false, + "iot_class": "local_polling", + "name": "Dovado" + }, + "downloader": { + "config_flow": false, + "iot_class": null, + "name": "Downloader" + }, + "dsmr": { + "config_flow": true, + "iot_class": "local_push", + "name": "DSMR Slimme Meter" + }, + "dsmr_reader": { + "config_flow": true, + "iot_class": "local_push", + "name": "DSMR Reader" + }, + "dte_energy_bridge": { + "config_flow": false, + "iot_class": "local_polling", + "name": "DTE Energy Bridge" + }, + "dublin_bus_transport": { + "config_flow": false, + "iot_class": "cloud_polling", + "name": "Dublin Bus" + }, + "duckdns": { + "config_flow": false, + "iot_class": "cloud_polling", + "name": "Duck DNS" + }, + "dunehd": { + "config_flow": true, + "iot_class": "local_polling", + "name": "Dune HD" + }, + "dwd_weather_warnings": { + "config_flow": false, + "iot_class": "cloud_polling", + "name": "Deutscher Wetterdienst (DWD) Weather Warnings" + }, + "dweet": { + "config_flow": false, + "iot_class": "cloud_polling", + "name": "dweet.io" + }, + "dynalite": { + "config_flow": true, + "iot_class": "local_push", + "name": "Philips Dynalite" + }, + "eafm": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "Environment Agency Flood Gauges" + }, + "ebox": { + "config_flow": false, + "iot_class": "cloud_polling", + "name": "EBox" + }, + "ebusd": { + "config_flow": false, + "iot_class": "local_polling", + "name": "ebusd" + }, + "ecoal_boiler": { + "config_flow": false, + "iot_class": "local_polling", + "name": "eSterownik eCoal.pl Boiler" + }, + "ecobee": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "ecobee" + }, + "econet": { + "config_flow": true, + "iot_class": "cloud_push", + "name": "Rheem EcoNet Products" + }, + "ecovacs": { + "config_flow": false, + "iot_class": "cloud_push", + "name": "Ecovacs" + }, + "ecowitt": { + "config_flow": true, + "iot_class": "local_push", + "name": "Ecowitt" + }, + "eddystone_temperature": { + "config_flow": false, + "iot_class": "local_polling", + "name": "Eddystone" + }, + "edimax": { + "config_flow": false, + "iot_class": "local_polling", + "name": "Edimax" + }, + "edl21": { + "config_flow": false, + "iot_class": "local_push", + "name": "EDL21" + }, + "efergy": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "Efergy" + }, + "egardia": { + "config_flow": false, + "iot_class": "local_polling", + "name": "Egardia" + }, + "eight_sleep": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "Eight Sleep" + }, + "elgato": { + "config_flow": true, + "iot_class": "local_polling", + "name": "Elgato Light" + }, + "eliqonline": { + "config_flow": false, + "iot_class": "cloud_polling", + "name": "Eliqonline" + }, + "elkm1": { + "config_flow": true, + "iot_class": "local_push", + "name": "Elk-M1 Control" + }, + "elmax": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "Elmax" + }, + "elv": { + "config_flow": false, + "iot_class": "local_polling", + "name": "ELV PCA" + }, + "emby": { + "config_flow": false, + "iot_class": "local_push", + "name": "Emby" + }, + "emoncms": { + "config_flow": false, + "iot_class": "local_polling", + "name": "Emoncms" + }, + "emoncms_history": { + "config_flow": false, + "iot_class": "local_polling", + "name": "Emoncms History" + }, + "emonitor": { + "config_flow": true, + "iot_class": "local_polling", + "name": "SiteSage Emonitor" + }, + "emulated_hue": { + "config_flow": false, + "iot_class": "local_push", + "name": "Emulated Hue" + }, + "emulated_kasa": { + "config_flow": false, + "iot_class": "local_push", + "name": "Emulated Kasa" + }, + "emulated_roku": { + "config_flow": true, + "iot_class": "local_push" + }, + "energy": { + "config_flow": false, + "iot_class": "calculated" + }, + "enigma2": { + "config_flow": false, + "iot_class": "local_polling", + "name": "Enigma2 (OpenWebif)" + }, + "enocean": { + "config_flow": true, + "iot_class": "local_push", + "name": "EnOcean" + }, + "enphase_envoy": { + "config_flow": true, + "iot_class": "local_polling", + "name": "Enphase Envoy" + }, + "entur_public_transport": { + "config_flow": false, + "iot_class": "cloud_polling", + "name": "Entur" + }, + "environment_canada": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "Environment Canada" + }, + "envisalink": { + "config_flow": false, + "iot_class": "local_push", + "name": "Envisalink" + }, + "ephember": { + "config_flow": false, + "iot_class": "local_polling", + "name": "EPH Controls" + }, + "epson": { + "config_flow": true, + "iot_class": "local_polling", + "name": "Epson" + }, + "epsonworkforce": { + "config_flow": false, + "iot_class": "local_polling", + "name": "Epson Workforce" + }, + "eq3btsmart": { + "config_flow": false, + "iot_class": "local_polling", + "name": "eQ-3 Bluetooth Smart Thermostats" + }, + "escea": { + "config_flow": true, + "iot_class": "local_push", + "name": "Escea" + }, + "esphome": { + "config_flow": true, + "iot_class": "local_push", + "name": "ESPHome" + }, + "etherscan": { + "config_flow": false, + "iot_class": "cloud_polling", + "name": "Etherscan" + }, + "eufy": { + "config_flow": false, + "iot_class": "local_polling", + "name": "eufy" + }, + "everlights": { + "config_flow": false, + "iot_class": "local_polling", + "name": "EverLights" + }, + "evil_genius_labs": { + "config_flow": true, + "iot_class": "local_polling", + "name": "Evil Genius Labs" + }, + "evohome": { + "config_flow": false, + "iot_class": "cloud_polling", + "name": "Honeywell Total Connect Comfort (Europe)" + }, + "ezviz": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "EZVIZ" + }, + "faa_delays": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "FAA Delays" + }, + "facebook": { + "config_flow": false, + "iot_class": "cloud_push", + "name": "Facebook Messenger" + }, + "facebox": { + "config_flow": false, + "iot_class": "local_push", + "name": "Facebox" + }, + "fail2ban": { + "config_flow": false, + "iot_class": "local_polling", + "name": "Fail2Ban" + }, + "familyhub": { + "config_flow": false, + "iot_class": "local_polling", + "name": "Samsung Family Hub" + }, + "fan": { + "config_flow": false, + "iot_class": null + }, + "fastdotcom": { + "config_flow": false, + "iot_class": "cloud_polling", + "name": "Fast.com" + }, + "feedreader": { + "config_flow": false, + "iot_class": "cloud_polling", + "name": "Feedreader" + }, + "ffmpeg": { + "config_flow": false, + "iot_class": null, + "name": "FFmpeg" + }, + "ffmpeg_motion": { + "config_flow": false, + "iot_class": "calculated", + "name": "FFmpeg Motion" + }, + "ffmpeg_noise": { + "config_flow": false, + "iot_class": "calculated", + "name": "FFmpeg Noise" + }, + "fibaro": { + "config_flow": true, + "iot_class": "local_push", + "name": "Fibaro" + }, + "fido": { + "config_flow": false, + "iot_class": "cloud_polling", + "name": "Fido" + }, + "file": { + "config_flow": false, + "iot_class": "local_polling", + "name": "File" + }, + "file_upload": { + "config_flow": false, + "iot_class": null, + "name": "File Upload" + }, + "filesize": { + "config_flow": true, + "iot_class": "local_polling" + }, + "filter": { + "config_flow": false, + "iot_class": "local_push", + "name": "Filter" + }, + "fints": { + "config_flow": false, + "iot_class": "cloud_polling", + "name": "FinTS" + }, + "fireservicerota": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "FireServiceRota" + }, + "firmata": { + "config_flow": false, + "iot_class": "local_push", + "name": "Firmata" + }, + "fitbit": { + "config_flow": false, + "iot_class": "cloud_polling", + "name": "Fitbit" + }, + "fivem": { + "config_flow": true, + "iot_class": "local_polling", + "name": "FiveM" + }, + "fixer": { + "config_flow": false, + "iot_class": "cloud_polling", + "name": "Fixer" + }, + "fjaraskupan": { + "config_flow": true, + "iot_class": "local_polling", + "name": "Fj\u00e4r\u00e5skupan" + }, + "fleetgo": { + "config_flow": false, + "iot_class": "cloud_polling", + "name": "FleetGO" + }, + "flexit": { + "config_flow": false, + "iot_class": "local_polling", + "name": "Flexit" + }, + "flic": { + "config_flow": false, + "iot_class": "local_push", + "name": "Flic" + }, + "flick_electric": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "Flick Electric" + }, + "flipr": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "Flipr" + }, + "flo": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "Flo" + }, + "flock": { + "config_flow": false, + "iot_class": "cloud_push", + "name": "Flock" + }, + "flume": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "Flume" + }, + "flux": { + "config_flow": false, + "iot_class": "calculated", + "name": "Flux" + }, + "flux_led": { + "config_flow": true, + "iot_class": "local_push", + "name": "Magic Home" + }, + "folder": { + "config_flow": false, + "iot_class": "local_polling", + "name": "Folder" + }, + "folder_watcher": { + "config_flow": false, + "iot_class": "local_polling", + "name": "Folder Watcher" + }, + "foobot": { + "config_flow": false, + "iot_class": "cloud_polling", + "name": "Foobot" + }, + "forecast_solar": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "Forecast.Solar" + }, + "forked_daapd": { + "config_flow": true, + "iot_class": "local_push", + "name": "forked-daapd" + }, + "fortios": { + "config_flow": false, + "iot_class": "local_polling", + "name": "FortiOS" + }, + "foscam": { + "config_flow": true, + "iot_class": "local_polling", + "name": "Foscam" + }, + "foursquare": { + "config_flow": false, + "iot_class": "cloud_push", + "name": "Foursquare" + }, + "free_mobile": { + "config_flow": false, + "iot_class": "cloud_push", + "name": "Free Mobile" + }, + "freebox": { + "config_flow": true, + "iot_class": "local_polling", + "name": "Freebox" + }, + "freedns": { + "config_flow": false, + "iot_class": "cloud_push", + "name": "FreeDNS" + }, + "freedompro": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "Freedompro" + }, + "fritz": { + "config_flow": true, + "iot_class": "local_polling", + "name": "AVM FRITZ!Box Tools" + }, + "fritzbox": { + "config_flow": true, + "iot_class": "local_polling", + "name": "AVM FRITZ!SmartHome" + }, + "fritzbox_callmonitor": { + "config_flow": true, + "iot_class": "local_polling", + "name": "AVM FRITZ!Box Call Monitor" + }, + "fronius": { + "config_flow": true, + "iot_class": "local_polling", + "name": "Fronius" + }, + "frontend": { + "config_flow": false, + "iot_class": null, + "name": "Home Assistant Frontend" + }, + "frontier_silicon": { + "config_flow": false, + "iot_class": "local_polling", + "name": "Frontier Silicon" + }, + "fully_kiosk": { + "config_flow": true, + "iot_class": "local_polling", + "name": "Fully Kiosk Browser" + }, + "futurenow": { + "config_flow": false, + "iot_class": "local_polling", + "name": "P5 FutureNow" + }, + "garadget": { + "config_flow": false, + "iot_class": "cloud_polling", + "name": "Garadget" + }, + "garages_amsterdam": { + "config_flow": true, + "iot_class": "cloud_polling" + }, + "gc100": { + "config_flow": false, + "iot_class": "local_polling", + "name": "Global Cach\u00e9 GC-100" + }, + "gdacs": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "Global Disaster Alert and Coordination System (GDACS)" + }, + "generic": { + "config_flow": true, + "iot_class": "local_push", + "name": "Generic Camera" + }, + "generic_hygrostat": { + "config_flow": false, + "iot_class": "local_polling", + "name": "Generic hygrostat" + }, + "generic_thermostat": { + "config_flow": false, + "iot_class": "local_polling", + "name": "Generic Thermostat" + }, + "geniushub": { + "config_flow": false, + "iot_class": "local_polling", + "name": "Genius Hub" + }, + "geo_json_events": { + "config_flow": false, + "iot_class": "cloud_polling", + "name": "GeoJSON" + }, + "geo_location": { + "config_flow": false, + "iot_class": null, + "name": "Geolocation" + }, + "geo_rss_events": { + "config_flow": false, + "iot_class": "cloud_polling", + "name": "GeoRSS" + }, + "geocaching": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "Geocaching" + }, + "geofency": { + "config_flow": true, + "iot_class": "cloud_push", + "name": "Geofency" + }, + "geonetnz_quakes": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "GeoNet NZ Quakes" + }, + "geonetnz_volcano": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "GeoNet NZ Volcano" + }, + "gios": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "GIO\u015a" + }, + "github": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "GitHub" + }, + "gitlab_ci": { + "config_flow": false, + "iot_class": "cloud_polling", + "name": "GitLab-CI" + }, + "gitter": { + "config_flow": false, + "iot_class": "cloud_polling", + "name": "Gitter" + }, + "glances": { + "config_flow": true, + "iot_class": "local_polling", + "name": "Glances" + }, + "goalfeed": { + "config_flow": false, + "iot_class": "cloud_push", + "name": "Goalfeed" + }, + "goalzero": { + "config_flow": true, + "iot_class": "local_polling", + "name": "Goal Zero Yeti" + }, + "gogogate2": { + "config_flow": true, + "iot_class": "local_polling", + "name": "Gogogate2 and ismartgate" + }, + "goodwe": { + "config_flow": true, + "iot_class": "local_polling", + "name": "GoodWe Inverter" + }, + "google": { + "name": "Google", + "integrations": { + "google": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "Google Calendars" + }, + "google_sheets": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "Google Sheets" + } + } + }, + "google_assistant": { + "config_flow": false, + "iot_class": "cloud_push", + "name": "Google Assistant" + }, + "google_cloud": { + "config_flow": false, + "iot_class": "cloud_push", + "name": "Google Cloud Platform" + }, + "google_domains": { + "config_flow": false, + "iot_class": "cloud_polling", + "name": "Google Domains" + }, + "google_maps": { + "config_flow": false, + "iot_class": "cloud_polling", + "name": "Google Maps" + }, + "google_pubsub": { + "config_flow": false, + "iot_class": "cloud_push", + "name": "Google Pub/Sub" + }, + "google_translate": { + "config_flow": false, + "iot_class": "cloud_push", + "name": "Google Translate Text-to-Speech" + }, + "google_travel_time": { + "config_flow": true, + "iot_class": "cloud_polling" + }, + "google_wifi": { + "config_flow": false, + "iot_class": "local_polling", + "name": "Google Wifi" + }, + "govee_ble": { + "config_flow": true, + "iot_class": "local_push", + "name": "Govee Bluetooth" + }, + "gpsd": { + "config_flow": false, + "iot_class": "local_polling", + "name": "GPSD" + }, + "gpslogger": { + "config_flow": true, + "iot_class": "cloud_push", + "name": "GPSLogger" + }, + "graphite": { + "config_flow": false, + "iot_class": "local_push", + "name": "Graphite" + }, + "gree": { + "config_flow": true, + "iot_class": "local_polling", + "name": "Gree Climate" + }, + "greeneye_monitor": { + "config_flow": false, + "iot_class": "local_push", + "name": "GreenEye Monitor (GEM)" + }, + "greenwave": { + "config_flow": false, + "iot_class": "local_polling", + "name": "Greenwave Reality" + }, + "growatt_server": { + "config_flow": true, + "iot_class": "cloud_polling" + }, + "gstreamer": { + "config_flow": false, + "iot_class": "local_push", + "name": "GStreamer" + }, + "gtfs": { + "config_flow": false, + "iot_class": "local_polling", + "name": "General Transit Feed Specification (GTFS)" + }, + "guardian": { + "config_flow": true, + "iot_class": "local_polling", + "name": "Elexa Guardian" + }, + "habitica": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "Habitica" + }, + "hangouts": { + "config_flow": true, + "iot_class": "cloud_push", + "name": "Google Chat" + }, + "hardware": { + "config_flow": false, + "iot_class": null, + "name": "Hardware" + }, + "harman_kardon_avr": { + "config_flow": false, + "iot_class": "local_polling", + "name": "Harman Kardon AVR" + }, + "harmony": { + "config_flow": true, + "iot_class": "local_push", + "name": "Logitech Harmony Hub" + }, + "hassio": { + "config_flow": false, + "iot_class": "local_polling", + "name": "Home Assistant Supervisor" + }, + "haveibeenpwned": { + "config_flow": false, + "iot_class": "cloud_polling", + "name": "HaveIBeenPwned" + }, + "hddtemp": { + "config_flow": false, + "iot_class": "local_polling", + "name": "hddtemp" + }, + "hdmi_cec": { + "config_flow": false, + "iot_class": "local_push", + "name": "HDMI-CEC" + }, + "heatmiser": { + "config_flow": false, + "iot_class": "local_polling", + "name": "Heatmiser" + }, + "heos": { + "config_flow": true, + "iot_class": "local_push", + "name": "Denon HEOS" + }, + "here_travel_time": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "HERE Travel Time" + }, + "hikvision": { + "config_flow": false, + "iot_class": "local_push", + "name": "Hikvision" + }, + "hikvisioncam": { + "config_flow": false, + "iot_class": "local_polling", + "name": "Hikvision" + }, + "hisense_aehw4a1": { + "config_flow": true, + "iot_class": "local_polling", + "name": "Hisense AEH-W4A1" + }, + "history": { + "config_flow": false, + "iot_class": null, + "name": "History" + }, + "history_stats": { + "config_flow": false, + "iot_class": "local_polling", + "name": "History Stats" + }, + "hitron_coda": { + "config_flow": false, + "iot_class": "local_polling", + "name": "Rogers Hitron CODA" + }, + "hive": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "Hive" + }, + "hlk_sw16": { + "config_flow": true, + "iot_class": "local_push", + "name": "Hi-Link HLK-SW16" + }, + "home_connect": { + "config_flow": true, + "iot_class": "cloud_push", + "name": "Home Connect" + }, + "home_plus_control": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "Legrand Home+ Control" + }, + "homeassistant": { + "config_flow": false, + "iot_class": null, + "name": "Home Assistant Core Integration" + }, + "homeassistant_alerts": { + "config_flow": false, + "iot_class": null, + "name": "Home Assistant Alerts" + }, + "homekit": { + "config_flow": true, + "iot_class": "local_push", + "name": "HomeKit" + }, + "homekit_controller": { + "config_flow": true, + "iot_class": "local_push" + }, + "homematic": { + "config_flow": false, + "iot_class": "local_push", + "name": "Homematic" + }, + "homematicip_cloud": { + "config_flow": true, + "iot_class": "cloud_push", + "name": "HomematicIP Cloud" + }, + "homewizard": { + "config_flow": true, + "iot_class": "local_polling", + "name": "HomeWizard Energy" + }, + "homeworks": { + "config_flow": false, + "iot_class": "local_push", + "name": "Lutron Homeworks" + }, + "honeywell": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "Honeywell Total Connect Comfort (US)" + }, + "horizon": { + "config_flow": false, + "iot_class": "local_polling", + "name": "Unitymedia Horizon HD Recorder" + }, + "hp_ilo": { + "config_flow": false, + "iot_class": "local_polling", + "name": "HP Integrated Lights-Out (ILO)" + }, + "html5": { + "config_flow": false, + "iot_class": "cloud_push", + "name": "HTML5 Push Notifications" + }, + "http": { + "config_flow": false, + "iot_class": "local_push", + "name": "HTTP" + }, + "huawei_lte": { + "config_flow": true, + "iot_class": "local_polling", + "name": "Huawei LTE" + }, + "hue": { + "config_flow": true, + "iot_class": "local_push", + "name": "Philips Hue" + }, + "huisbaasje": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "Huisbaasje" + }, + "humidifier": { + "config_flow": false, + "iot_class": null + }, + "hunterdouglas_powerview": { + "config_flow": true, + "iot_class": "local_polling", + "name": "Hunter Douglas PowerView" + }, + "hvv_departures": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "HVV Departures" + }, + "hydrawise": { + "config_flow": false, + "iot_class": "cloud_polling", + "name": "Hunter Hydrawise" + }, + "hyperion": { + "config_flow": true, + "iot_class": "local_push", + "name": "Hyperion" + }, + "ialarm": { + "config_flow": true, + "iot_class": "local_polling", + "name": "Antifurto365 iAlarm" + }, + "iammeter": { + "config_flow": false, + "iot_class": "local_polling", + "name": "IamMeter" + }, + "iaqualink": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "Jandy iAqualink" + }, + "ibeacon": { + "config_flow": true, + "iot_class": "local_push", + "name": "iBeacon Tracker" + }, + "icloud": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "Apple iCloud" + }, + "idteck_prox": { + "config_flow": false, + "iot_class": "local_push", + "name": "IDTECK Proximity Reader" + }, + "ifttt": { + "config_flow": true, + "iot_class": "cloud_push", + "name": "IFTTT" + }, + "iglo": { + "config_flow": false, + "iot_class": "local_polling", + "name": "iGlo" + }, + "ign_sismologia": { + "config_flow": false, + "iot_class": "cloud_polling", + "name": "IGN Sismolog\u00eda" + }, + "ihc": { + "config_flow": false, + "iot_class": "local_push", + "name": "IHC Controller" + }, + "image": { + "config_flow": false, + "iot_class": null, + "name": "Image" + }, + "image_processing": { + "config_flow": false, + "iot_class": null + }, + "imap": { + "config_flow": false, + "iot_class": "cloud_push", + "name": "IMAP" + }, + "imap_email_content": { + "config_flow": false, + "iot_class": "cloud_push", + "name": "IMAP Email Content" + }, + "incomfort": { + "config_flow": false, + "iot_class": "local_polling", + "name": "Intergas InComfort/Intouch Lan2RF gateway" + }, + "influxdb": { + "config_flow": false, + "iot_class": "local_push", + "name": "InfluxDB" + }, + "inkbird": { + "config_flow": true, + "iot_class": "local_push", + "name": "INKBIRD" + }, + "insteon": { + "config_flow": true, + "iot_class": "local_push", + "name": "Insteon" + }, + "intellifire": { + "config_flow": true, + "iot_class": "local_polling", + "name": "IntelliFire" + }, + "intent": { + "config_flow": false, + "iot_class": null, + "name": "Intent" + }, + "intent_script": { + "config_flow": false, + "iot_class": null, + "name": "Intent Script" + }, + "intesishome": { + "config_flow": false, + "iot_class": "cloud_push", + "name": "IntesisHome" + }, + "ios": { + "config_flow": true, + "iot_class": "cloud_push", + "name": "Home Assistant iOS" + }, + "iotawatt": { + "config_flow": true, + "iot_class": "local_polling", + "name": "IoTaWatt" + }, + "iperf3": { + "config_flow": false, + "iot_class": "local_polling", + "name": "Iperf3" + }, + "ipma": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "Instituto Portugu\u00eas do Mar e Atmosfera (IPMA)" + }, + "ipp": { + "config_flow": true, + "iot_class": "local_polling", + "name": "Internet Printing Protocol (IPP)" + }, + "iqvia": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "IQVIA" + }, + "irish_rail_transport": { + "config_flow": false, + "iot_class": "cloud_polling", + "name": "Irish Rail Transport" + }, + "islamic_prayer_times": { + "config_flow": true, + "iot_class": "cloud_polling" + }, + "iss": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "International Space Station (ISS)" + }, + "isy994": { + "config_flow": true, + "iot_class": "local_push", + "name": "Universal Devices ISY994" + }, + "itach": { + "config_flow": false, + "iot_class": "assumed_state", + "name": "Global Cach\u00e9 iTach TCP/IP to IR" + }, + "itunes": { + "config_flow": false, + "iot_class": "local_polling", + "name": "Apple iTunes" + }, + "izone": { + "config_flow": true, + "iot_class": "local_polling", + "name": "iZone" + }, + "jellyfin": { + "config_flow": true, + "iot_class": "local_polling", + "name": "Jellyfin" + }, + "jewish_calendar": { + "config_flow": false, + "iot_class": "calculated", + "name": "Jewish Calendar" + }, + "joaoapps_join": { + "config_flow": false, + "iot_class": "cloud_push", + "name": "Joaoapps Join" + }, + "juicenet": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "JuiceNet" + }, + "justnimbus": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "JustNimbus" + }, + "kaiterra": { + "config_flow": false, + "iot_class": "cloud_polling", + "name": "Kaiterra" + }, + "kaleidescape": { + "config_flow": true, + "iot_class": "local_push", + "name": "Kaleidescape" + }, + "kankun": { + "config_flow": false, + "iot_class": "local_polling", + "name": "Kankun" + }, + "keba": { + "config_flow": false, + "iot_class": "local_polling", + "name": "Keba Charging Station" + }, + "keenetic_ndms2": { + "config_flow": true, + "iot_class": "local_polling", + "name": "Keenetic NDMS2 Router" + }, + "kef": { + "config_flow": false, + "iot_class": "local_polling", + "name": "KEF" + }, + "kegtron": { + "config_flow": true, + "iot_class": "local_push", + "name": "Kegtron" + }, + "keyboard": { + "config_flow": false, + "iot_class": "local_push", + "name": "Keyboard" + }, + "keyboard_remote": { + "config_flow": false, + "iot_class": "local_push", + "name": "Keyboard Remote" + }, + "keymitt_ble": { + "config_flow": true, + "iot_class": "assumed_state", + "name": "Keymitt MicroBot Push" + }, + "kira": { + "config_flow": false, + "iot_class": "local_push", + "name": "Kira" + }, + "kiwi": { + "config_flow": false, + "iot_class": "cloud_polling", + "name": "KIWI" + }, + "kmtronic": { + "config_flow": true, + "iot_class": "local_push", + "name": "KMtronic" + }, + "knx": { + "config_flow": true, + "iot_class": "local_push", + "name": "KNX" + }, + "kodi": { + "config_flow": true, + "iot_class": "local_push", + "name": "Kodi" + }, + "konnected": { + "config_flow": true, + "iot_class": "local_push", + "name": "Konnected.io" + }, + "kostal_plenticore": { + "config_flow": true, + "iot_class": "local_polling", + "name": "Kostal Plenticore Solar Inverter" + }, + "kraken": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "Kraken" + }, + "kulersky": { + "config_flow": true, + "iot_class": "local_polling", + "name": "Kuler Sky" + }, + "kwb": { + "config_flow": false, + "iot_class": "local_polling", + "name": "KWB Easyfire" + }, + "lacrosse": { + "config_flow": false, + "iot_class": "local_polling", + "name": "LaCrosse" + }, + "lacrosse_view": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "LaCrosse View" + }, + "lametric": { + "config_flow": true, + "iot_class": "local_polling", + "name": "LaMetric" + }, + "landisgyr_heat_meter": { + "config_flow": true, + "iot_class": "local_polling", + "name": "Landis+Gyr Heat Meter" + }, + "lannouncer": { + "config_flow": false, + "iot_class": "local_push", + "name": "LANnouncer" + }, + "lastfm": { + "config_flow": false, + "iot_class": "cloud_polling", + "name": "Last.fm" + }, + "launch_library": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "Launch Library" + }, + "laundrify": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "laundrify" + }, + "lcn": { + "config_flow": false, + "iot_class": "local_push", + "name": "LCN" + }, + "led_ble": { + "config_flow": true, + "iot_class": "local_polling", + "name": "LED BLE" + }, + "lg_netcast": { + "config_flow": false, + "iot_class": "local_polling", + "name": "LG Netcast" + }, + "lg_soundbar": { + "config_flow": true, + "iot_class": "local_polling", + "name": "LG Soundbars" + }, + "lidarr": { + "config_flow": true, + "iot_class": "local_polling", + "name": "Lidarr" + }, + "life360": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "Life360" + }, + "lifx": { + "config_flow": true, + "iot_class": "local_polling", + "name": "LIFX" + }, + "lifx_cloud": { + "config_flow": false, + "iot_class": "cloud_push", + "name": "LIFX Cloud" + }, + "light": { + "config_flow": false, + "iot_class": null + }, + "lightwave": { + "config_flow": false, + "iot_class": "assumed_state", + "name": "Lightwave" + }, + "limitlessled": { + "config_flow": false, + "iot_class": "assumed_state", + "name": "LimitlessLED" + }, + "linksys_smart": { + "config_flow": false, + "iot_class": "local_polling", + "name": "Linksys Smart Wi-Fi" + }, + "linode": { + "config_flow": false, + "iot_class": "cloud_polling", + "name": "Linode" + }, + "linux_battery": { + "config_flow": false, + "iot_class": "local_polling", + "name": "Linux Battery" + }, + "lirc": { + "config_flow": false, + "iot_class": "local_push", + "name": "LIRC" + }, + "litejet": { + "config_flow": true, + "iot_class": "local_push", + "name": "LiteJet" + }, + "litterrobot": { + "config_flow": true, + "iot_class": "cloud_push", + "name": "Litter-Robot" + }, + "llamalab_automate": { + "config_flow": false, + "iot_class": "cloud_push", + "name": "LlamaLab Automate" + }, + "local_file": { + "config_flow": false, + "iot_class": "local_polling", + "name": "Local File" + }, + "local_ip": { + "config_flow": true, + "iot_class": "local_polling" + }, + "locative": { + "config_flow": true, + "iot_class": "local_push", + "name": "Locative" + }, + "lock": { + "config_flow": false, + "iot_class": null + }, + "logbook": { + "config_flow": false, + "iot_class": null, + "name": "Logbook" + }, + "logentries": { + "config_flow": false, + "iot_class": "cloud_push", + "name": "Logentries" + }, + "logger": { + "config_flow": false, + "iot_class": null, + "name": "Logger" + }, + "logi_circle": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "Logi Circle" + }, + "london_air": { + "config_flow": false, + "iot_class": "cloud_polling", + "name": "London Air" + }, + "london_underground": { + "config_flow": false, + "iot_class": "cloud_polling", + "name": "London Underground" + }, + "lookin": { + "config_flow": true, + "iot_class": "local_push", + "name": "LOOKin" + }, + "lovelace": { + "config_flow": false, + "iot_class": null, + "name": "Dashboards" + }, + "luci": { + "config_flow": false, + "iot_class": "local_polling", + "name": "OpenWrt (luci)" + }, + "luftdaten": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "Sensor.Community" + }, + "lupusec": { + "config_flow": false, + "iot_class": "local_polling", + "name": "Lupus Electronics LUPUSEC" + }, + "lutron": { + "config_flow": false, + "iot_class": "local_polling", + "name": "Lutron" + }, + "lutron_caseta": { + "config_flow": true, + "iot_class": "local_push", + "name": "Lutron Cas\u00e9ta" + }, + "lw12wifi": { + "config_flow": false, + "iot_class": "local_polling", + "name": "LAGUTE LW-12" + }, + "lyric": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "Honeywell Lyric" + }, + "magicseaweed": { + "config_flow": false, + "iot_class": "cloud_polling", + "name": "Magicseaweed" + }, + "mailbox": { + "config_flow": false, + "iot_class": null + }, + "mailgun": { + "config_flow": true, + "iot_class": "cloud_push", + "name": "Mailgun" + }, + "manual": { + "config_flow": false, + "iot_class": "calculated", + "name": "Manual Alarm Control Panel" + }, + "manual_mqtt": { + "config_flow": false, + "iot_class": "local_push", + "name": "Manual MQTT Alarm Control Panel" + }, + "map": { + "config_flow": false, + "iot_class": null, + "name": "Map" + }, + "marytts": { + "config_flow": false, + "iot_class": "local_push", + "name": "MaryTTS" + }, + "mastodon": { + "config_flow": false, + "iot_class": "cloud_push", + "name": "Mastodon" + }, + "matrix": { + "config_flow": false, + "iot_class": "cloud_push", + "name": "Matrix" + }, + "maxcube": { + "config_flow": false, + "iot_class": "local_polling", + "name": "eQ-3 MAX!" + }, + "mazda": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "Mazda Connected Services" + }, + "meater": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "Meater" + }, + "media_extractor": { + "config_flow": false, + "iot_class": "calculated", + "name": "Media Extractor" + }, + "media_player": { + "config_flow": false, + "iot_class": null + }, + "media_source": { + "config_flow": false, + "iot_class": null, + "name": "Media Source" + }, + "mediaroom": { + "config_flow": false, + "iot_class": "local_polling", + "name": "Mediaroom" + }, + "melcloud": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "MELCloud" + }, + "melissa": { + "config_flow": false, + "iot_class": "cloud_polling", + "name": "Melissa" + }, + "melnor": { + "config_flow": true, + "iot_class": "local_polling", + "name": "Melnor Bluetooth" + }, + "meraki": { + "config_flow": false, + "iot_class": "cloud_polling", + "name": "Meraki" + }, + "message_bird": { + "config_flow": false, + "iot_class": "cloud_push", + "name": "MessageBird" + }, + "met": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "Meteorologisk institutt (Met.no)" + }, + "met_eireann": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "Met \u00c9ireann" + }, + "meteo_france": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "M\u00e9t\u00e9o-France" + }, + "meteoalarm": { + "config_flow": false, + "iot_class": "cloud_polling", + "name": "MeteoAlarm" + }, + "meteoclimatic": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "Meteoclimatic" + }, + "metoffice": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "Met Office" + }, + "mfi": { + "config_flow": false, + "iot_class": "local_polling", + "name": "Ubiquiti mFi mPort" + }, + "microsoft": { + "config_flow": false, + "iot_class": "cloud_push", + "name": "Microsoft Text-to-Speech (TTS)" + }, + "microsoft_face": { + "config_flow": false, + "iot_class": "cloud_push", + "name": "Microsoft Face" + }, + "microsoft_face_detect": { + "config_flow": false, + "iot_class": "cloud_push", + "name": "Microsoft Face Detect" + }, + "microsoft_face_identify": { + "config_flow": false, + "iot_class": "cloud_push", + "name": "Microsoft Face Identify" + }, + "miflora": { + "config_flow": false, + "iot_class": "local_polling", + "name": "Mi Flora" + }, + "mikrotik": { + "config_flow": true, + "iot_class": "local_polling", + "name": "Mikrotik" + }, + "mill": { + "config_flow": true, + "iot_class": "local_polling", + "name": "Mill" + }, + "minecraft_server": { + "config_flow": true, + "iot_class": "local_polling", + "name": "Minecraft Server" + }, + "minio": { + "config_flow": false, + "iot_class": "cloud_push", + "name": "Minio" + }, + "mitemp_bt": { + "config_flow": false, + "iot_class": "local_polling", + "name": "Xiaomi Mijia BLE Temperature and Humidity Sensor" + }, + "mjpeg": { + "config_flow": true, + "iot_class": "local_push", + "name": "MJPEG IP Camera" + }, + "moat": { + "config_flow": true, + "iot_class": "local_push", + "name": "Moat" + }, + "mobile_app": { + "config_flow": true, + "iot_class": "local_push" + }, + "mochad": { + "config_flow": false, + "iot_class": "local_polling", + "name": "Mochad" + }, + "modbus": { + "config_flow": false, + "iot_class": "local_polling", + "name": "Modbus" + }, + "modem_callerid": { + "config_flow": true, + "iot_class": "local_polling", + "name": "Phone Modem" + }, + "modern_forms": { + "config_flow": true, + "iot_class": "local_polling", + "name": "Modern Forms" + }, + "moehlenhoff_alpha2": { + "config_flow": true, + "iot_class": "local_push" + }, + "mold_indicator": { + "config_flow": false, + "iot_class": "local_polling", + "name": "Mold Indicator" + }, + "monoprice": { + "config_flow": true, + "iot_class": "local_polling", + "name": "Monoprice 6-Zone Amplifier" + }, + "moon": { + "config_flow": true, + "iot_class": "local_polling" + }, + "motion_blinds": { + "config_flow": true, + "iot_class": "local_push", + "name": "Motion Blinds" + }, + "motioneye": { + "config_flow": true, + "iot_class": "local_polling", + "name": "motionEye" + }, + "mpd": { + "config_flow": false, + "iot_class": "local_polling", + "name": "Music Player Daemon (MPD)" + }, + "mqtt": { + "config_flow": true, + "iot_class": "local_push", + "name": "MQTT" + }, + "mqtt_eventstream": { + "config_flow": false, + "iot_class": "local_polling", + "name": "MQTT Eventstream" + }, + "mqtt_json": { + "config_flow": false, + "iot_class": "local_push", + "name": "MQTT JSON" + }, + "mqtt_room": { + "config_flow": false, + "iot_class": "local_push", + "name": "MQTT Room Presence" + }, + "mqtt_statestream": { + "config_flow": false, + "iot_class": "local_push", + "name": "MQTT Statestream" + }, + "msteams": { + "config_flow": false, + "iot_class": "cloud_push", + "name": "Microsoft Teams" + }, + "mullvad": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "Mullvad VPN" + }, + "mutesync": { + "config_flow": true, + "iot_class": "local_polling", + "name": "mutesync" + }, + "mvglive": { + "config_flow": false, + "iot_class": "cloud_polling", + "name": "MVG" + }, + "my": { + "config_flow": false, + "iot_class": null, + "name": "My Home Assistant" + }, + "mycroft": { + "config_flow": false, + "iot_class": "local_push", + "name": "Mycroft" + }, + "myq": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "MyQ" + }, + "mysensors": { + "config_flow": true, + "iot_class": "local_push", + "name": "MySensors" + }, + "mystrom": { + "config_flow": false, + "iot_class": "local_polling", + "name": "myStrom" + }, + "mythicbeastsdns": { + "config_flow": false, + "iot_class": "cloud_push", + "name": "Mythic Beasts DNS" + }, + "nad": { + "config_flow": false, + "iot_class": "local_polling", + "name": "NAD" + }, + "nam": { + "config_flow": true, + "iot_class": "local_polling", + "name": "Nettigo Air Monitor" + }, + "namecheapdns": { + "config_flow": false, + "iot_class": "cloud_push", + "name": "Namecheap FreeDNS" + }, + "nanoleaf": { + "config_flow": true, + "iot_class": "local_push", + "name": "Nanoleaf" + }, + "neato": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "Neato Botvac" + }, + "nederlandse_spoorwegen": { + "config_flow": false, + "iot_class": "cloud_polling", + "name": "Nederlandse Spoorwegen (NS)" + }, + "ness_alarm": { + "config_flow": false, + "iot_class": "local_push", + "name": "Ness Alarm" + }, + "nest": { + "config_flow": true, + "iot_class": "cloud_push", + "name": "Nest" + }, + "netatmo": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "Netatmo" + }, + "netdata": { + "config_flow": false, + "iot_class": "local_polling", + "name": "Netdata" + }, + "netgear": { + "config_flow": true, + "iot_class": "local_polling", + "name": "NETGEAR" + }, + "netgear_lte": { + "config_flow": false, + "iot_class": "local_polling", + "name": "NETGEAR LTE" + }, + "netio": { + "config_flow": false, + "iot_class": "local_polling", + "name": "Netio" + }, + "network": { + "config_flow": false, + "iot_class": "local_push", + "name": "Network Configuration" + }, + "neurio_energy": { + "config_flow": false, + "iot_class": "cloud_polling", + "name": "Neurio energy" + }, + "nexia": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "Nexia/American Standard/Trane" + }, + "nextbus": { + "config_flow": false, + "iot_class": "local_polling", + "name": "NextBus" + }, + "nextcloud": { + "config_flow": false, + "iot_class": "cloud_polling", + "name": "Nextcloud" + }, + "nextdns": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "NextDNS" + }, + "nfandroidtv": { + "config_flow": true, + "iot_class": "local_push", + "name": "Notifications for Android TV / Fire TV" + }, + "nibe_heatpump": { + "config_flow": true, + "iot_class": "local_polling", + "name": "Nibe Heat Pump" + }, + "nightscout": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "Nightscout" + }, + "niko_home_control": { + "config_flow": false, + "iot_class": "local_polling", + "name": "Niko Home Control" + }, + "nilu": { + "config_flow": false, + "iot_class": "cloud_polling", + "name": "Norwegian Institute for Air Research (NILU)" + }, + "nina": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "NINA" + }, + "nissan_leaf": { + "config_flow": false, + "iot_class": "cloud_polling", + "name": "Nissan Leaf" + }, + "nmap_tracker": { + "config_flow": true, + "iot_class": "local_polling" + }, + "nmbs": { + "config_flow": false, + "iot_class": "cloud_polling", + "name": "NMBS" + }, + "no_ip": { + "config_flow": false, + "iot_class": "cloud_polling", + "name": "No-IP.com" + }, + "noaa_tides": { + "config_flow": false, + "iot_class": "cloud_polling", + "name": "NOAA Tides" + }, + "nobo_hub": { + "config_flow": true, + "iot_class": "local_push", + "name": "Nob\u00f8 Ecohub" + }, + "norway_air": { + "config_flow": false, + "iot_class": "cloud_polling", + "name": "Om Luftkvalitet i Norge (Norway Air)" + }, + "notify": { + "config_flow": false, + "iot_class": null + }, + "notify_events": { + "config_flow": false, + "iot_class": "cloud_push", + "name": "Notify.Events" + }, + "notion": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "Notion" + }, + "nsw_fuel_station": { + "config_flow": false, + "iot_class": "cloud_polling", + "name": "NSW Fuel Station Price" + }, + "nsw_rural_fire_service_feed": { + "config_flow": false, + "iot_class": "cloud_polling", + "name": "NSW Rural Fire Service Incidents" + }, + "nuheat": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "NuHeat" + }, + "nuki": { + "config_flow": true, + "iot_class": "local_polling", + "name": "Nuki" + }, + "numato": { + "config_flow": false, + "iot_class": "local_push", + "name": "Numato USB GPIO Expander" + }, + "number": { + "config_flow": false, + "iot_class": null + }, + "nut": { + "config_flow": true, + "iot_class": "local_polling", + "name": "Network UPS Tools (NUT)" + }, + "nws": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "National Weather Service (NWS)" + }, + "nx584": { + "config_flow": false, + "iot_class": "local_push", + "name": "NX584" + }, + "nzbget": { + "config_flow": true, + "iot_class": "local_polling", + "name": "NZBGet" + }, + "oasa_telematics": { + "config_flow": false, + "iot_class": "cloud_polling", + "name": "OASA Telematics" + }, + "obihai": { + "config_flow": false, + "iot_class": "local_polling", + "name": "Obihai" + }, + "octoprint": { + "config_flow": true, + "iot_class": "local_polling", + "name": "OctoPrint" + }, + "oem": { + "config_flow": false, + "iot_class": "local_polling", + "name": "OpenEnergyMonitor WiFi Thermostat" + }, + "ohmconnect": { + "config_flow": false, + "iot_class": "cloud_polling", + "name": "OhmConnect" + }, + "ombi": { + "config_flow": false, + "iot_class": "local_polling", + "name": "Ombi" + }, + "omnilogic": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "Hayward Omnilogic" + }, + "onboarding": { + "config_flow": false, + "iot_class": null, + "name": "Home Assistant Onboarding" + }, + "oncue": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "Oncue by Kohler" + }, + "ondilo_ico": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "Ondilo ICO" + }, + "onewire": { + "config_flow": true, + "iot_class": "local_polling", + "name": "1-Wire" + }, + "onkyo": { + "config_flow": false, + "iot_class": "local_polling", + "name": "Onkyo" + }, + "onvif": { + "config_flow": true, + "iot_class": "local_push", + "name": "ONVIF" + }, + "open_meteo": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "Open-Meteo" + }, + "openalpr_cloud": { + "config_flow": false, + "iot_class": "cloud_push", + "name": "OpenALPR Cloud" + }, + "openalpr_local": { + "config_flow": false, + "iot_class": "local_push", + "name": "OpenALPR Local" + }, + "opencv": { + "config_flow": false, + "iot_class": "local_push", + "name": "OpenCV" + }, + "openerz": { + "config_flow": false, + "iot_class": "cloud_polling", + "name": "Open ERZ" + }, + "openevse": { + "config_flow": false, + "iot_class": "local_polling", + "name": "OpenEVSE" + }, + "openexchangerates": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "Open Exchange Rates" + }, + "opengarage": { + "config_flow": true, + "iot_class": "local_polling", + "name": "OpenGarage" + }, + "openhardwaremonitor": { + "config_flow": false, + "iot_class": "local_polling", + "name": "Open Hardware Monitor" + }, + "openhome": { + "config_flow": false, + "iot_class": "local_polling", + "name": "Linn / OpenHome" + }, + "opensensemap": { + "config_flow": false, + "iot_class": "cloud_polling", + "name": "openSenseMap" + }, + "opensky": { + "config_flow": false, + "iot_class": "cloud_polling", + "name": "OpenSky Network" + }, + "opentherm_gw": { + "config_flow": true, + "iot_class": "local_push", + "name": "OpenTherm Gateway" + }, + "openuv": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "OpenUV" + }, + "openweathermap": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "OpenWeatherMap" + }, + "opnsense": { + "config_flow": false, + "iot_class": "local_polling", + "name": "OPNSense" + }, + "opple": { + "config_flow": false, + "iot_class": "local_polling", + "name": "Opple" + }, + "oru": { + "config_flow": false, + "iot_class": "cloud_polling", + "name": "Orange and Rockland Utility (ORU)" + }, + "orvibo": { + "config_flow": false, + "iot_class": "local_push", + "name": "Orvibo" + }, + "osramlightify": { + "config_flow": false, + "iot_class": "local_polling", + "name": "Osramlightify" + }, + "otp": { + "config_flow": false, + "iot_class": "local_polling", + "name": "One-Time Password (OTP)" + }, + "overkiz": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "Overkiz" + }, + "ovo_energy": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "OVO Energy" + }, + "owntracks": { + "config_flow": true, + "iot_class": "local_push", + "name": "OwnTracks" + }, + "p1_monitor": { + "config_flow": true, + "iot_class": "local_polling", + "name": "P1 Monitor" + }, + "panasonic_bluray": { + "config_flow": false, + "iot_class": "local_polling", + "name": "Panasonic Blu-Ray Player" + }, + "panasonic_viera": { + "config_flow": true, + "iot_class": "local_polling", + "name": "Panasonic Viera" + }, + "pandora": { + "config_flow": false, + "iot_class": "local_polling", + "name": "Pandora" + }, + "panel_custom": { + "config_flow": false, + "iot_class": null, + "name": "Custom Panel" + }, + "panel_iframe": { + "config_flow": false, + "iot_class": null, + "name": "iframe Panel" + }, + "peco": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "PECO Outage Counter" + }, + "pencom": { + "config_flow": false, + "iot_class": "local_polling", + "name": "Pencom" + }, + "persistent_notification": { + "config_flow": false, + "iot_class": "local_push", + "name": "Persistent Notification" + }, + "person": { + "config_flow": false, + "iot_class": "calculated" + }, + "philips_js": { + "config_flow": true, + "iot_class": "local_polling", + "name": "Philips TV" + }, + "pi_hole": { + "config_flow": true, + "iot_class": "local_polling", + "name": "Pi-hole" + }, + "picnic": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "Picnic" + }, + "picotts": { + "config_flow": false, + "iot_class": "local_push", + "name": "Pico TTS" + }, + "pilight": { + "config_flow": false, + "iot_class": "local_push", + "name": "Pilight" + }, + "ping": { + "config_flow": false, + "iot_class": "local_polling", + "name": "Ping (ICMP)" + }, + "pioneer": { + "config_flow": false, + "iot_class": "local_polling", + "name": "Pioneer" + }, + "pjlink": { + "config_flow": false, + "iot_class": "local_polling", + "name": "PJLink" + }, + "plaato": { + "config_flow": true, + "iot_class": "cloud_push", + "name": "Plaato" + }, + "plant": { + "config_flow": false, + "iot_class": null + }, + "plex": { + "config_flow": true, + "iot_class": "local_push", + "name": "Plex Media Server" + }, + "plugwise": { + "config_flow": true, + "iot_class": "local_polling", + "name": "Plugwise" + }, + "plum_lightpad": { + "config_flow": true, + "iot_class": "local_push", + "name": "Plum Lightpad" + }, + "pocketcasts": { + "config_flow": false, + "iot_class": "cloud_polling", + "name": "Pocket Casts" + }, + "point": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "Minut Point" + }, + "poolsense": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "PoolSense" + }, + "powerwall": { + "config_flow": true, + "iot_class": "local_polling", + "name": "Tesla Powerwall" + }, + "profiler": { + "config_flow": true, + "iot_class": null, + "name": "Profiler" + }, + "progettihwsw": { + "config_flow": true, + "iot_class": "local_polling", + "name": "ProgettiHWSW Automation" + }, + "proliphix": { + "config_flow": false, + "iot_class": "local_polling", + "name": "Proliphix" + }, + "prometheus": { + "config_flow": false, + "iot_class": "assumed_state", + "name": "Prometheus" + }, + "prosegur": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "Prosegur Alarm" + }, + "prowl": { + "config_flow": false, + "iot_class": "cloud_push", + "name": "Prowl" + }, + "proximity": { + "config_flow": false, + "iot_class": "calculated" + }, + "proxmoxve": { + "config_flow": false, + "iot_class": "local_polling", + "name": "Proxmox VE" + }, + "proxy": { + "config_flow": false, + "iot_class": null, + "name": "Camera Proxy" + }, + "prusalink": { + "config_flow": true, + "iot_class": "local_polling", + "name": "PrusaLink" + }, + "ps4": { + "config_flow": true, + "iot_class": "local_polling", + "name": "Sony PlayStation 4" + }, + "pulseaudio_loopback": { + "config_flow": false, + "iot_class": "local_polling", + "name": "PulseAudio Loopback" + }, + "pure_energie": { + "config_flow": true, + "iot_class": "local_polling", + "name": "Pure Energie" + }, + "push": { + "config_flow": false, + "iot_class": "local_push", + "name": "Push" + }, + "pushbullet": { + "config_flow": false, + "iot_class": "cloud_polling", + "name": "Pushbullet" + }, + "pushover": { + "config_flow": true, + "iot_class": "cloud_push", + "name": "Pushover" + }, + "pushsafer": { + "config_flow": false, + "iot_class": "cloud_push", + "name": "Pushsafer" + }, + "pvoutput": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "PVOutput" + }, + "pvpc_hourly_pricing": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "Spain electricity hourly pricing (PVPC)" + }, + "pyload": { + "config_flow": false, + "iot_class": "local_polling", + "name": "pyLoad" + }, + "python_script": { + "config_flow": false, + "iot_class": null, + "name": "Python Scripts" + }, + "qbittorrent": { + "config_flow": false, + "iot_class": "local_polling", + "name": "qBittorrent" + }, + "qingping": { + "config_flow": true, + "iot_class": "local_push", + "name": "Qingping" + }, + "qld_bushfire": { + "config_flow": false, + "iot_class": "cloud_polling", + "name": "Queensland Bushfire Alert" + }, + "qnap": { + "config_flow": false, + "iot_class": "local_polling", + "name": "QNAP" + }, + "qnap_qsw": { + "config_flow": true, + "iot_class": "local_polling", + "name": "QNAP QSW" + }, + "qrcode": { + "config_flow": false, + "iot_class": "calculated", + "name": "QR Code" + }, + "quantum_gateway": { + "config_flow": false, + "iot_class": "local_polling", + "name": "Quantum Gateway" + }, + "qvr_pro": { + "config_flow": false, + "iot_class": "local_polling", + "name": "QVR Pro" + }, + "qwikswitch": { + "config_flow": false, + "iot_class": "local_push", + "name": "QwikSwitch QSUSB" + }, + "rachio": { + "config_flow": true, + "iot_class": "cloud_push", + "name": "Rachio" + }, + "radarr": { + "config_flow": true, + "iot_class": "local_polling", + "name": "Radarr" + }, + "radio_browser": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "Radio Browser" + }, + "radiotherm": { + "config_flow": true, + "iot_class": "local_polling", + "name": "Radio Thermostat" + }, + "rainbird": { + "config_flow": false, + "iot_class": "local_polling", + "name": "Rain Bird" + }, + "raincloud": { + "config_flow": false, + "iot_class": "cloud_polling", + "name": "Melnor RainCloud" + }, + "rainforest_eagle": { + "config_flow": true, + "iot_class": "local_polling", + "name": "Rainforest Eagle" + }, + "rainmachine": { + "config_flow": true, + "iot_class": "local_polling", + "name": "RainMachine" + }, + "random": { + "config_flow": false, + "iot_class": "local_polling", + "name": "Random" + }, + "raspyrfm": { + "config_flow": false, + "iot_class": "assumed_state", + "name": "RaspyRFM" + }, + "rdw": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "RDW" + }, + "recollect_waste": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "ReCollect Waste" + }, + "recorder": { + "config_flow": false, + "iot_class": "local_push", + "name": "Recorder" + }, + "recswitch": { + "config_flow": false, + "iot_class": "local_polling", + "name": "Ankuoo REC Switch" + }, + "reddit": { + "config_flow": false, + "iot_class": "cloud_polling", + "name": "Reddit" + }, + "rejseplanen": { + "config_flow": false, + "iot_class": "cloud_polling", + "name": "Rejseplanen" + }, + "remember_the_milk": { + "config_flow": false, + "iot_class": "cloud_push", + "name": "Remember The Milk" + }, + "remote": { + "config_flow": false, + "iot_class": null + }, + "remote_rpi_gpio": { + "config_flow": false, + "iot_class": "local_push", + "name": "remote_rpi_gpio" + }, + "renault": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "Renault" + }, + "repairs": { + "config_flow": false, + "iot_class": null, + "name": "Repairs" + }, + "repetier": { + "config_flow": false, + "iot_class": "local_polling", + "name": "Repetier-Server" + }, + "rest": { + "config_flow": false, + "iot_class": "local_polling", + "name": "RESTful" + }, + "rest_command": { + "config_flow": false, + "iot_class": "local_push", + "name": "RESTful Command" + }, + "rflink": { + "config_flow": false, + "iot_class": "assumed_state", + "name": "RFLink" + }, + "rfxtrx": { + "config_flow": true, + "iot_class": "local_push", + "name": "RFXCOM RFXtrx" + }, + "rhasspy": { + "config_flow": true, + "iot_class": "local_push", + "name": "Rhasspy" + }, + "ridwell": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "Ridwell" + }, + "ring": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "Ring" + }, + "ripple": { + "config_flow": false, + "iot_class": "cloud_polling", + "name": "Ripple" + }, + "risco": { + "config_flow": true, + "iot_class": "local_push", + "name": "Risco" + }, + "rituals_perfume_genie": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "Rituals Perfume Genie" + }, + "rmvtransport": { + "config_flow": false, + "iot_class": "cloud_polling", + "name": "RMV" + }, + "rocketchat": { + "config_flow": false, + "iot_class": "cloud_push", + "name": "Rocket.Chat" + }, + "roku": { + "config_flow": true, + "iot_class": "local_polling", + "name": "Roku" + }, + "roomba": { + "config_flow": true, + "iot_class": "local_push", + "name": "iRobot Roomba and Braava" + }, + "roon": { + "config_flow": true, + "iot_class": "local_push", + "name": "RoonLabs music player" + }, + "route53": { + "config_flow": false, + "iot_class": "cloud_push", + "name": "AWS Route53" + }, + "rova": { + "config_flow": false, + "iot_class": "cloud_polling", + "name": "ROVA" + }, + "rpi_camera": { + "config_flow": false, + "iot_class": "local_polling", + "name": "Raspberry Pi Camera" + }, + "rpi_power": { + "config_flow": true, + "iot_class": "local_polling" + }, + "rss_feed_template": { + "config_flow": false, + "iot_class": "local_push", + "name": "RSS Feed Template" + }, + "rtorrent": { + "config_flow": false, + "iot_class": "local_polling", + "name": "rTorrent" + }, + "rtsp_to_webrtc": { + "config_flow": true, + "iot_class": "local_push", + "name": "RTSPtoWebRTC" + }, + "ruckus_unleashed": { + "config_flow": true, + "iot_class": "local_polling", + "name": "Ruckus Unleashed" + }, + "russound_rio": { + "config_flow": false, + "iot_class": "local_push", + "name": "Russound RIO" + }, + "russound_rnet": { + "config_flow": false, + "iot_class": "local_polling", + "name": "Russound RNET" + }, + "sabnzbd": { + "config_flow": true, + "iot_class": "local_polling", + "name": "SABnzbd" + }, + "safe_mode": { + "config_flow": false, + "iot_class": null, + "name": "Safe Mode" + }, + "saj": { + "config_flow": false, + "iot_class": "local_polling", + "name": "SAJ Solar Inverter" + }, + "samsungtv": { + "config_flow": true, + "iot_class": "local_push", + "name": "Samsung Smart TV" + }, + "satel_integra": { + "config_flow": false, + "iot_class": "local_push", + "name": "Satel Integra" + }, + "scene": { + "config_flow": false, + "iot_class": null + }, + "schluter": { + "config_flow": false, + "iot_class": "cloud_polling", + "name": "Schluter" + }, + "scrape": { + "config_flow": false, + "iot_class": "cloud_polling", + "name": "Scrape" + }, + "screenlogic": { + "config_flow": true, + "iot_class": "local_polling", + "name": "Pentair ScreenLogic" + }, + "script": { + "config_flow": false, + "iot_class": null + }, + "scsgate": { + "config_flow": false, + "iot_class": "local_polling", + "name": "SCSGate" + }, + "search": { + "config_flow": false, + "iot_class": null, + "name": "Search" + }, + "season": { + "config_flow": true, + "iot_class": "local_polling", + "name": "Season" + }, + "select": { + "config_flow": false, + "iot_class": null + }, + "sendgrid": { + "config_flow": false, + "iot_class": "cloud_push", + "name": "SendGrid" + }, + "sense": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "Sense" + }, + "senseme": { + "config_flow": true, + "iot_class": "local_push", + "name": "SenseME" + }, + "sensibo": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "Sensibo" + }, + "sensor": { + "config_flow": false, + "iot_class": null + }, + "sensorpro": { + "config_flow": true, + "iot_class": "local_push", + "name": "SensorPro" + }, + "sensorpush": { + "config_flow": true, + "iot_class": "local_push", + "name": "SensorPush" + }, + "sentry": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "Sentry" + }, + "senz": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "nVent RAYCHEM SENZ" + }, + "serial": { + "config_flow": false, + "iot_class": "local_polling", + "name": "Serial" + }, + "serial_pm": { + "config_flow": false, + "iot_class": "local_polling", + "name": "Serial Particulate Matter" + }, + "sesame": { + "config_flow": false, + "iot_class": "cloud_polling", + "name": "Sesame Smart Lock" + }, + "seven_segments": { + "config_flow": false, + "iot_class": "local_polling", + "name": "Seven Segments OCR" + }, + "seventeentrack": { + "config_flow": false, + "iot_class": "cloud_polling", + "name": "17TRACK" + }, + "sharkiq": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "Shark IQ" + }, + "shell_command": { + "config_flow": false, + "iot_class": "local_push", + "name": "Shell Command" + }, + "shelly": { + "config_flow": true, + "iot_class": "local_push", + "name": "Shelly" + }, + "shiftr": { + "config_flow": false, + "iot_class": "cloud_push", + "name": "shiftr.io" + }, + "shodan": { + "config_flow": false, + "iot_class": "cloud_polling", + "name": "Shodan" + }, + "shopping_list": { + "config_flow": true, + "iot_class": "local_push" + }, + "sia": { + "config_flow": true, + "iot_class": "local_push", + "name": "SIA Alarm Systems" + }, + "sigfox": { + "config_flow": false, + "iot_class": "cloud_polling", + "name": "Sigfox" + }, + "sighthound": { + "config_flow": false, + "iot_class": "cloud_polling", + "name": "Sighthound" + }, + "signal_messenger": { + "config_flow": false, + "iot_class": "cloud_push", + "name": "Signal Messenger" + }, + "simplepush": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "Simplepush" + }, + "simplisafe": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "SimpliSafe" + }, + "simulated": { + "config_flow": false, + "iot_class": "local_polling", + "name": "Simulated" + }, + "sinch": { + "config_flow": false, + "iot_class": "cloud_push", + "name": "Sinch SMS" + }, + "siren": { + "config_flow": false, + "iot_class": null + }, + "sisyphus": { + "config_flow": false, + "iot_class": "local_push", + "name": "Sisyphus" + }, + "sky_hub": { + "config_flow": false, + "iot_class": "local_polling", + "name": "Sky Hub" + }, + "skybeacon": { + "config_flow": false, + "iot_class": "local_polling", + "name": "Skybeacon" + }, + "skybell": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "SkyBell" + }, + "slack": { + "config_flow": true, + "iot_class": "cloud_push", + "name": "Slack" + }, + "sleepiq": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "SleepIQ" + }, + "slide": { + "config_flow": false, + "iot_class": "cloud_polling", + "name": "Slide" + }, + "slimproto": { + "config_flow": true, + "iot_class": "local_push", + "name": "SlimProto (Squeezebox players)" + }, + "sma": { + "config_flow": true, + "iot_class": "local_polling", + "name": "SMA Solar" + }, + "smappee": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "Smappee" + }, + "smart_meter_texas": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "Smart Meter Texas" + }, + "smartthings": { + "config_flow": true, + "iot_class": "cloud_push", + "name": "SmartThings" + }, + "smarttub": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "SmartTub" + }, + "smarty": { + "config_flow": false, + "iot_class": "local_polling", + "name": "Salda Smarty" + }, + "smhi": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "SMHI" + }, + "sms": { + "config_flow": true, + "iot_class": "local_polling", + "name": "SMS notifications via GSM-modem" + }, + "smtp": { + "config_flow": false, + "iot_class": "cloud_push", + "name": "SMTP" + }, + "snapcast": { + "config_flow": false, + "iot_class": "local_polling", + "name": "Snapcast" + }, + "snips": { + "config_flow": false, + "iot_class": "local_push", + "name": "Snips" + }, + "snmp": { + "config_flow": false, + "iot_class": "local_polling", + "name": "SNMP" + }, + "solaredge": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "SolarEdge" + }, + "solaredge_local": { + "config_flow": false, + "iot_class": "local_polling", + "name": "SolarEdge Local" + }, + "solarlog": { + "config_flow": true, + "iot_class": "local_polling", + "name": "Solar-Log" + }, + "solax": { + "config_flow": true, + "iot_class": "local_polling", + "name": "SolaX Power" + }, + "soma": { + "config_flow": true, + "iot_class": "local_polling", + "name": "Soma Connect" + }, + "somfy_mylink": { + "config_flow": true, + "iot_class": "assumed_state", + "name": "Somfy MyLink" + }, + "sonarr": { + "config_flow": true, + "iot_class": "local_polling", + "name": "Sonarr" + }, + "songpal": { + "config_flow": true, + "iot_class": "local_push", + "name": "Sony Songpal" + }, + "sonos": { + "config_flow": true, + "iot_class": "local_push", + "name": "Sonos" + }, + "sony_projector": { + "config_flow": false, + "iot_class": "local_polling", + "name": "Sony Projector" + }, + "soundtouch": { + "config_flow": true, + "iot_class": "local_polling", + "name": "Bose SoundTouch" + }, + "spaceapi": { + "config_flow": false, + "iot_class": "cloud_polling", + "name": "Space API" + }, + "spc": { + "config_flow": false, + "iot_class": "local_push", + "name": "Vanderbilt SPC" + }, + "speedtestdotnet": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "Speedtest.net" + }, + "spider": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "Itho Daalderop Spider" + }, + "splunk": { + "config_flow": false, + "iot_class": "local_push", + "name": "Splunk" + }, + "spotify": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "Spotify" + }, + "sql": { + "config_flow": true, + "iot_class": "local_polling", + "name": "SQL" + }, + "squeezebox": { + "config_flow": true, + "iot_class": "local_polling", + "name": "Squeezebox (Logitech Media Server)" + }, + "srp_energy": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "SRP Energy" + }, + "ssdp": { + "config_flow": false, + "iot_class": "local_push", + "name": "Simple Service Discovery Protocol (SSDP)" + }, + "starline": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "StarLine" + }, + "starlingbank": { + "config_flow": false, + "iot_class": "cloud_polling", + "name": "Starling Bank" + }, + "startca": { + "config_flow": false, + "iot_class": "cloud_polling", + "name": "Start.ca" + }, + "statistics": { + "config_flow": false, + "iot_class": "local_polling", + "name": "Statistics" + }, + "statsd": { + "config_flow": false, + "iot_class": "local_push", + "name": "StatsD" + }, + "steam_online": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "Steam" + }, + "steamist": { + "config_flow": true, + "iot_class": "local_polling", + "name": "Steamist" + }, + "stiebel_eltron": { + "config_flow": false, + "iot_class": "local_polling", + "name": "STIEBEL ELTRON" + }, + "stookalert": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "RIVM Stookalert" + }, + "stream": { + "config_flow": false, + "iot_class": "local_push", + "name": "Stream" + }, + "streamlabswater": { + "config_flow": false, + "iot_class": "cloud_polling", + "name": "StreamLabs" + }, + "stt": { + "config_flow": false, + "iot_class": null, + "name": "Speech-to-Text (STT)" + }, + "subaru": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "Subaru" + }, + "suez_water": { + "config_flow": false, + "iot_class": "cloud_polling", + "name": "Suez Water" + }, + "sun": { + "config_flow": true, + "iot_class": "calculated" + }, + "supervisord": { + "config_flow": false, + "iot_class": "local_polling", + "name": "Supervisord" + }, + "supla": { + "config_flow": false, + "iot_class": "cloud_polling", + "name": "Supla" + }, + "surepetcare": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "Sure Petcare" + }, + "swiss_hydrological_data": { + "config_flow": false, + "iot_class": "cloud_polling", + "name": "Swiss Hydrological Data" + }, + "swiss_public_transport": { + "config_flow": false, + "iot_class": "cloud_polling", + "name": "Swiss public transport" + }, + "swisscom": { + "config_flow": false, + "iot_class": "local_polling", + "name": "Swisscom Internet-Box" + }, + "switch": { + "config_flow": false, + "iot_class": null + }, + "switchbee": { + "config_flow": true, + "iot_class": "local_polling", + "name": "SwitchBee" + }, + "switchbot": { + "config_flow": true, + "iot_class": "local_push", + "name": "SwitchBot" + }, + "switcher_kis": { + "config_flow": true, + "iot_class": "local_push", + "name": "Switcher" + }, + "switchmate": { + "config_flow": false, + "iot_class": "local_polling", + "name": "Switchmate SimplySmart Home" + }, + "syncthing": { + "config_flow": true, + "iot_class": "local_polling", + "name": "Syncthing" + }, + "syncthru": { + "config_flow": true, + "iot_class": "local_polling", + "name": "Samsung SyncThru Printer" + }, + "synology_chat": { + "config_flow": false, + "iot_class": "cloud_push", + "name": "Synology Chat" + }, + "synology_dsm": { + "config_flow": true, + "iot_class": "local_polling", + "name": "Synology DSM" + }, + "synology_srm": { + "config_flow": false, + "iot_class": "local_polling", + "name": "Synology SRM" + }, + "syslog": { + "config_flow": false, + "iot_class": "local_push", + "name": "Syslog" + }, + "system_bridge": { + "config_flow": true, + "iot_class": "local_push", + "name": "System Bridge" + }, + "system_health": { + "config_flow": false, + "iot_class": null + }, + "system_log": { + "config_flow": false, + "iot_class": null, + "name": "System Log" + }, + "systemmonitor": { + "config_flow": false, + "iot_class": "local_push", + "name": "System Monitor" + }, + "tado": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "Tado" + }, + "tag": { + "config_flow": false, + "iot_class": null + }, + "tailscale": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "Tailscale" + }, + "tank_utility": { + "config_flow": false, + "iot_class": "cloud_polling", + "name": "Tank Utility" + }, + "tankerkoenig": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "Tankerkoenig" + }, + "tapsaff": { + "config_flow": false, + "iot_class": "local_polling", + "name": "Taps Aff" + }, + "tasmota": { + "config_flow": true, + "iot_class": "local_push", + "name": "Tasmota" + }, + "tautulli": { + "config_flow": true, + "iot_class": "local_polling", + "name": "Tautulli" + }, + "tcp": { + "config_flow": false, + "iot_class": "local_polling", + "name": "TCP" + }, + "ted5000": { + "config_flow": false, + "iot_class": "local_polling", + "name": "The Energy Detective TED5000" + }, + "telegram": { + "config_flow": false, + "iot_class": "cloud_polling", + "name": "Telegram" + }, + "telegram_bot": { + "config_flow": false, + "iot_class": "cloud_push", + "name": "Telegram bot" + }, + "tellduslive": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "Telldus Live" + }, + "tellstick": { + "config_flow": false, + "iot_class": "assumed_state", + "name": "TellStick" + }, + "telnet": { + "config_flow": false, + "iot_class": "local_polling", + "name": "Telnet" + }, + "temper": { + "config_flow": false, + "iot_class": "local_polling", + "name": "TEMPer" + }, + "template": { + "config_flow": false, + "iot_class": "local_push", + "name": "Template" + }, + "tensorflow": { + "config_flow": false, + "iot_class": "local_polling", + "name": "TensorFlow" + }, + "tesla_wall_connector": { + "config_flow": true, + "iot_class": "local_polling", + "name": "Tesla Wall Connector" + }, + "tfiac": { + "config_flow": false, + "iot_class": "local_polling", + "name": "Tfiac" + }, + "thermobeacon": { + "config_flow": true, + "iot_class": "local_push", + "name": "ThermoBeacon" + }, + "thermopro": { + "config_flow": true, + "iot_class": "local_push", + "name": "ThermoPro" + }, + "thermoworks_smoke": { + "config_flow": false, + "iot_class": "cloud_polling", + "name": "ThermoWorks Smoke" + }, + "thethingsnetwork": { + "config_flow": false, + "iot_class": "local_push", + "name": "The Things Network" + }, + "thingspeak": { + "config_flow": false, + "iot_class": "cloud_push", + "name": "ThingSpeak" + }, + "thinkingcleaner": { + "config_flow": false, + "iot_class": "local_polling", + "name": "Thinking Cleaner" + }, + "thomson": { + "config_flow": false, + "iot_class": "local_polling", + "name": "Thomson" + }, + "tibber": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "Tibber" + }, + "tikteck": { + "config_flow": false, + "iot_class": "local_polling", + "name": "Tikteck" + }, + "tile": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "Tile" + }, + "tilt_ble": { + "config_flow": true, + "iot_class": "local_push", + "name": "Tilt Hydrometer BLE" + }, + "time_date": { + "config_flow": false, + "iot_class": "local_push", + "name": "Time & Date" + }, + "tmb": { + "config_flow": false, + "iot_class": "local_polling", + "name": "Transports Metropolitans de Barcelona" + }, + "todoist": { + "config_flow": false, + "iot_class": "cloud_polling", + "name": "Todoist" + }, + "tolo": { + "config_flow": true, + "iot_class": "local_polling", + "name": "TOLO Sauna" + }, + "tomato": { + "config_flow": false, + "iot_class": "local_polling", + "name": "Tomato" + }, + "tomorrowio": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "Tomorrow.io" + }, + "toon": { + "config_flow": true, + "iot_class": "cloud_push", + "name": "Toon" + }, + "torque": { + "config_flow": false, + "iot_class": "cloud_polling", + "name": "Torque" + }, + "totalconnect": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "Total Connect" + }, + "touchline": { + "config_flow": false, + "iot_class": "local_polling", + "name": "Roth Touchline" + }, + "tplink": { + "config_flow": true, + "iot_class": "local_polling", + "name": "TP-Link Kasa Smart" + }, + "tplink_lte": { + "config_flow": false, + "iot_class": "local_polling", + "name": "TP-Link LTE" + }, + "traccar": { + "config_flow": true, + "iot_class": "local_polling", + "name": "Traccar" + }, + "trace": { + "config_flow": false, + "iot_class": null, + "name": "Trace" + }, + "tractive": { + "config_flow": true, + "iot_class": "cloud_push", + "name": "Tractive" + }, + "tradfri": { + "config_flow": true, + "iot_class": "local_polling", + "name": "IKEA TR\u00c5DFRI" + }, + "trafikverket_ferry": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "Trafikverket Ferry" + }, + "trafikverket_train": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "Trafikverket Train" + }, + "trafikverket_weatherstation": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "Trafikverket Weather Station" + }, + "transmission": { + "config_flow": true, + "iot_class": "local_polling", + "name": "Transmission" + }, + "transport_nsw": { + "config_flow": false, + "iot_class": "cloud_polling", + "name": "Transport NSW" + }, + "travisci": { + "config_flow": false, + "iot_class": "cloud_polling", + "name": "Travis-CI" + }, + "trend": { + "config_flow": false, + "iot_class": "local_push", + "name": "Trend" + }, + "tts": { + "config_flow": false, + "iot_class": null, + "name": "Text-to-Speech (TTS)" + }, + "tuya": { + "config_flow": true, + "iot_class": "cloud_push", + "name": "Tuya" + }, + "twentemilieu": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "Twente Milieu" + }, + "twilio": { + "config_flow": true, + "iot_class": "cloud_push", + "name": "Twilio" + }, + "twilio_call": { + "config_flow": false, + "iot_class": "cloud_push", + "name": "Twilio Call" + }, + "twilio_sms": { + "config_flow": false, + "iot_class": "cloud_push", + "name": "Twilio SMS" + }, + "twinkly": { + "config_flow": true, + "iot_class": "local_polling", + "name": "Twinkly" + }, + "twitch": { + "config_flow": false, + "iot_class": "cloud_polling", + "name": "Twitch" + }, + "twitter": { + "config_flow": false, + "iot_class": "cloud_push", + "name": "Twitter" + }, + "ubus": { + "config_flow": false, + "iot_class": "local_polling", + "name": "OpenWrt (ubus)" + }, + "ue_smart_radio": { + "config_flow": false, + "iot_class": "cloud_polling", + "name": "Logitech UE Smart Radio" + }, + "uk_transport": { + "config_flow": false, + "iot_class": "cloud_polling", + "name": "UK Transport" + }, + "ukraine_alarm": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "Ukraine Alarm" + }, + "unifi": { + "config_flow": true, + "iot_class": "local_push", + "name": "UniFi Network" + }, + "unifi_direct": { + "config_flow": false, + "iot_class": "local_polling", + "name": "Ubiquiti UniFi AP" + }, + "unifiled": { + "config_flow": false, + "iot_class": "local_polling", + "name": "Ubiquiti UniFi LED" + }, + "unifiprotect": { + "config_flow": true, + "iot_class": "local_push", + "name": "UniFi Protect" + }, + "universal": { + "config_flow": false, + "iot_class": "calculated", + "name": "Universal Media Player" + }, + "upb": { + "config_flow": true, + "iot_class": "local_push", + "name": "Universal Powerline Bus (UPB)" + }, + "upc_connect": { + "config_flow": false, + "iot_class": "local_polling", + "name": "UPC Connect Box" + }, + "upcloud": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "UpCloud" + }, + "update": { + "config_flow": false, + "iot_class": null + }, + "upnp": { + "config_flow": true, + "iot_class": "local_polling", + "name": "UPnP/IGD" + }, + "uptime": { + "config_flow": true, + "iot_class": "local_push" + }, + "uptimerobot": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "UptimeRobot" + }, + "usb": { + "config_flow": false, + "iot_class": "local_push", + "name": "USB Discovery" + }, + "usgs_earthquakes_feed": { + "config_flow": false, + "iot_class": "cloud_polling", + "name": "U.S. Geological Survey Earthquake Hazards (USGS)" + }, + "uvc": { + "config_flow": false, + "iot_class": "local_polling", + "name": "Ubiquiti UniFi Video" + }, + "vacuum": { + "config_flow": false, + "iot_class": null + }, + "vallox": { + "config_flow": true, + "iot_class": "local_polling", + "name": "Vallox" + }, + "vasttrafik": { + "config_flow": false, + "iot_class": "cloud_polling", + "name": "V\u00e4sttrafik" + }, + "velbus": { + "config_flow": true, + "iot_class": "local_push", + "name": "Velbus" + }, + "velux": { + "config_flow": false, + "iot_class": "local_polling", + "name": "Velux" + }, + "venstar": { + "config_flow": true, + "iot_class": "local_polling", + "name": "Venstar" + }, + "vera": { + "config_flow": true, + "iot_class": "local_polling", + "name": "Vera" + }, + "verisure": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "Verisure" + }, + "versasense": { + "config_flow": false, + "iot_class": "local_polling", + "name": "VersaSense" + }, + "version": { + "config_flow": true, + "iot_class": "local_push", + "name": "Version" + }, + "vesync": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "VeSync" + }, + "viaggiatreno": { + "config_flow": false, + "iot_class": "cloud_polling", + "name": "Trenitalia ViaggiaTreno" + }, + "vicare": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "Viessmann ViCare" + }, + "vilfo": { + "config_flow": true, + "iot_class": "local_polling", + "name": "Vilfo Router" + }, + "vivotek": { + "config_flow": false, + "iot_class": "local_polling", + "name": "VIVOTEK" + }, + "vizio": { + "config_flow": true, + "iot_class": "local_polling", + "name": "VIZIO SmartCast" + }, + "vlc": { + "config_flow": false, + "iot_class": "local_polling", + "name": "VLC media player" + }, + "vlc_telnet": { + "config_flow": true, + "iot_class": "local_polling", + "name": "VLC media player via Telnet" + }, + "voicerss": { + "config_flow": false, + "iot_class": "cloud_push", + "name": "VoiceRSS" + }, + "volkszaehler": { + "config_flow": false, + "iot_class": "local_polling", + "name": "Volkszaehler" + }, + "volumio": { + "config_flow": true, + "iot_class": "local_polling", + "name": "Volumio" + }, + "volvooncall": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "Volvo On Call" + }, + "vulcan": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "Uonet+ Vulcan" + }, + "vultr": { + "config_flow": false, + "iot_class": "cloud_polling", + "name": "Vultr" + }, + "w800rf32": { + "config_flow": false, + "iot_class": "local_push", + "name": "WGL Designs W800RF32" + }, + "wake_on_lan": { + "config_flow": false, + "iot_class": "local_push", + "name": "Wake on LAN" + }, + "wallbox": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "Wallbox" + }, + "waqi": { + "config_flow": false, + "iot_class": "cloud_polling", + "name": "World Air Quality Index (WAQI)" + }, + "water_heater": { + "config_flow": false, + "iot_class": null, + "name": "Water Heater" + }, + "waterfurnace": { + "config_flow": false, + "iot_class": "cloud_polling", + "name": "WaterFurnace" + }, + "watson_iot": { + "config_flow": false, + "iot_class": "cloud_push", + "name": "IBM Watson IoT Platform" + }, + "watson_tts": { + "config_flow": false, + "iot_class": "cloud_push", + "name": "IBM Watson TTS" + }, + "watttime": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "WattTime" + }, + "waze_travel_time": { + "config_flow": true, + "iot_class": "cloud_polling" + }, + "weather": { + "config_flow": false, + "iot_class": null, + "name": "Weather" + }, + "webhook": { + "config_flow": false, + "iot_class": null, + "name": "Webhook" + }, + "webostv": { + "config_flow": true, + "iot_class": "local_push", + "name": "LG webOS Smart TV" + }, + "websocket_api": { + "config_flow": false, + "iot_class": null, + "name": "Home Assistant WebSocket API" + }, + "wemo": { + "config_flow": true, + "iot_class": "local_push", + "name": "Belkin WeMo" + }, + "whirlpool": { + "config_flow": true, + "iot_class": "cloud_push", + "name": "Whirlpool Sixth Sense" + }, + "whois": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "Whois" + }, + "wiffi": { + "config_flow": true, + "iot_class": "local_push", + "name": "Wiffi" + }, + "wilight": { + "config_flow": true, + "iot_class": "local_polling", + "name": "WiLight" + }, + "wirelesstag": { + "config_flow": false, + "iot_class": "cloud_push", + "name": "Wireless Sensor Tags" + }, + "withings": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "Withings" + }, + "wiz": { + "config_flow": true, + "iot_class": "local_push", + "name": "WiZ" + }, + "wled": { + "config_flow": true, + "iot_class": "local_push", + "name": "WLED" + }, + "wolflink": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "Wolf SmartSet Service" + }, + "workday": { + "config_flow": false, + "iot_class": "local_polling", + "name": "Workday" + }, + "worldclock": { + "config_flow": false, + "iot_class": "local_push", + "name": "Worldclock" + }, + "worldtidesinfo": { + "config_flow": false, + "iot_class": "cloud_polling", + "name": "World Tides" + }, + "worxlandroid": { + "config_flow": false, + "iot_class": "local_polling", + "name": "Worx Landroid" + }, + "ws66i": { + "config_flow": true, + "iot_class": "local_polling", + "name": "Soundavo WS66i 6-Zone Amplifier" + }, + "wsdot": { + "config_flow": false, + "iot_class": "cloud_polling", + "name": "Washington State Department of Transportation (WSDOT)" + }, + "x10": { + "config_flow": false, + "iot_class": "local_polling", + "name": "Heyu X10" + }, + "xbox": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "Xbox" + }, + "xbox_live": { + "config_flow": false, + "iot_class": "cloud_polling", + "name": "Xbox Live" + }, + "xeoma": { + "config_flow": false, + "iot_class": "local_polling", + "name": "Xeoma" + }, + "xiaomi": { + "config_flow": false, + "iot_class": "local_polling", + "name": "Xiaomi" + }, + "xiaomi_aqara": { + "config_flow": true, + "iot_class": "local_push", + "name": "Xiaomi Gateway (Aqara)" + }, + "xiaomi_ble": { + "config_flow": true, + "iot_class": "local_push", + "name": "Xiaomi BLE" + }, + "xiaomi_miio": { + "config_flow": true, + "iot_class": "local_polling", + "name": "Xiaomi Miio" + }, + "xiaomi_tv": { + "config_flow": false, + "iot_class": "assumed_state", + "name": "Xiaomi TV" + }, + "xmpp": { + "config_flow": false, + "iot_class": "cloud_push", + "name": "Jabber (XMPP)" + }, + "xs1": { + "config_flow": false, + "iot_class": "local_polling", + "name": "EZcontrol XS1" + }, + "yale_smart_alarm": { + "config_flow": true, + "iot_class": "cloud_polling", + "name": "Yale Smart Living" + }, + "yalexs_ble": { + "config_flow": true, + "iot_class": "local_push", + "name": "Yale Access Bluetooth" + }, + "yamaha": { + "config_flow": false, + "iot_class": "local_polling", + "name": "Yamaha Network Receivers" + }, + "yamaha_musiccast": { + "config_flow": true, + "iot_class": "local_push", + "name": "MusicCast" + }, + "yandex_transport": { + "config_flow": false, + "iot_class": "cloud_polling", + "name": "Yandex Transport" + }, + "yandextts": { + "config_flow": false, + "iot_class": "cloud_push", + "name": "Yandex TTS" + }, + "yeelight": { + "config_flow": true, + "iot_class": "local_push", + "name": "Yeelight" + }, + "yeelightsunflower": { + "config_flow": false, + "iot_class": "local_polling", + "name": "Yeelight Sunflower" + }, + "yi": { + "config_flow": false, + "iot_class": "local_polling", + "name": "Yi Home Cameras" + }, + "yolink": { + "config_flow": true, + "iot_class": "cloud_push", + "name": "YoLink" + }, + "youless": { + "config_flow": true, + "iot_class": "local_polling", + "name": "YouLess" + }, + "zabbix": { + "config_flow": false, + "iot_class": "local_polling", + "name": "Zabbix" + }, + "zamg": { + "config_flow": false, + "iot_class": "cloud_polling", + "name": "Zentralanstalt f\u00fcr Meteorologie und Geodynamik (ZAMG)" + }, + "zengge": { + "config_flow": false, + "iot_class": "local_polling", + "name": "Zengge" + }, + "zeroconf": { + "config_flow": false, + "iot_class": "local_push", + "name": "Zero-configuration networking (zeroconf)" + }, + "zerproc": { + "config_flow": true, + "iot_class": "local_polling", + "name": "Zerproc" + }, + "zestimate": { + "config_flow": false, + "iot_class": "cloud_polling", + "name": "Zestimate" + }, + "zha": { + "config_flow": true, + "iot_class": "local_polling", + "name": "Zigbee Home Automation" + }, + "zhong_hong": { + "config_flow": false, + "iot_class": "local_push", + "name": "ZhongHong" + }, + "ziggo_mediabox_xl": { + "config_flow": false, + "iot_class": "local_polling", + "name": "Ziggo Mediabox XL" + }, + "zodiac": { + "config_flow": false, + "iot_class": "local_polling", + "name": "Zodiac" + }, + "zone": { + "config_flow": false, + "iot_class": null, + "name": "Zone" + }, + "zoneminder": { + "config_flow": false, + "iot_class": "local_polling", + "name": "ZoneMinder" + }, + "zwave_js": { + "config_flow": true, + "iot_class": "local_push", + "name": "Z-Wave" + }, + "zwave_me": { + "config_flow": true, + "iot_class": "local_push", + "name": "Z-Wave.Me" + } + }, + "hardware": { + "hardkernel": { + "config_flow": false, + "iot_class": null, + "name": "Hardkernel" + }, + "homeassistant_sky_connect": { + "config_flow": false, + "iot_class": null, + "name": "Home Assistant Sky Connect" + }, + "homeassistant_yellow": { + "config_flow": false, + "iot_class": null, + "name": "Home Assistant Yellow" + }, + "raspberry_pi": { + "config_flow": false, + "iot_class": null, + "name": "Raspberry Pi" + } + }, + "helper": { + "counter": { + "config_flow": false, + "iot_class": null, + "name": "Counter" + }, + "derivative": { + "config_flow": true, + "iot_class": "calculated" + }, + "group": { + "config_flow": true, + "iot_class": "calculated" + }, + "input_boolean": { + "config_flow": false, + "iot_class": null + }, + "input_button": { + "config_flow": false, + "iot_class": null, + "name": "Input Button" + }, + "input_datetime": { + "config_flow": false, + "iot_class": null + }, + "input_number": { + "config_flow": false, + "iot_class": null + }, + "input_select": { + "config_flow": false, + "iot_class": null + }, + "input_text": { + "config_flow": false, + "iot_class": null + }, + "integration": { + "config_flow": true, + "iot_class": "local_push" + }, + "min_max": { + "config_flow": true, + "iot_class": "local_push" + }, + "schedule": { + "config_flow": false, + "iot_class": null + }, + "switch_as_x": { + "config_flow": true, + "iot_class": "calculated" + }, + "threshold": { + "config_flow": true, + "iot_class": "local_polling" + }, + "timer": { + "config_flow": false, + "iot_class": null, + "name": "Timer" + }, + "tod": { + "config_flow": true, + "iot_class": "local_push" + }, + "utility_meter": { + "config_flow": true, + "iot_class": "local_push" + } + }, + "translated_name": [ + "alarm_control_panel", + "application_credentials", + "aurora", + "automation", + "binary_sensor", + "button", + "calendar", + "camera", + "cert_expiry", + "climate", + "configurator", + "conversation", + "cover", + "cpuspeed", + "demo", + "derivative", + "device_tracker", + "diagnostics", + "emulated_roku", + "energy", + "fan", + "filesize", + "garages_amsterdam", + "google_travel_time", + "group", + "growatt_server", + "homekit_controller", + "humidifier", + "image_processing", + "input_boolean", + "input_datetime", + "input_number", + "input_select", + "input_text", + "integration", + "islamic_prayer_times", + "light", + "local_ip", + "lock", + "mailbox", + "media_player", + "min_max", + "mobile_app", + "moehlenhoff_alpha2", + "moon", + "nmap_tracker", + "notify", + "number", + "person", + "plant", + "proximity", + "remote", + "rpi_power", + "scene", + "schedule", + "script", + "select", + "sensor", + "shopping_list", + "siren", + "sun", + "switch", + "switch_as_x", + "system_health", + "tag", + "threshold", + "tod", + "update", + "uptime", + "utility_meter", + "vacuum", + "waze_travel_time" + ] +} diff --git a/homeassistant/loader.py b/homeassistant/loader.py index 4eda2f9f651..f2a7948f4f5 100644 --- a/homeassistant/loader.py +++ b/homeassistant/loader.py @@ -23,6 +23,7 @@ from awesomeversion import ( AwesomeVersionStrategy, ) +from . import generated from .generated.application_credentials import APPLICATION_CREDENTIALS from .generated.bluetooth import BLUETOOTH from .generated.dhcp import DHCP @@ -250,6 +251,44 @@ async def async_get_config_flows( return flows +async def async_get_integration_descriptions( + hass: HomeAssistant, +) -> dict[str, Any]: + """Return cached list of integrations.""" + base = generated.__path__[0] + config_flow_path = pathlib.Path(base) / "integrations.json" + + flow = await hass.async_add_executor_job(config_flow_path.read_text) + core_flows: dict[str, Any] = json_loads(flow) + custom_integrations = await async_get_custom_components(hass) + custom_flows: dict[str, Any] = { + "integration": {}, + "hardware": {}, + "helper": {}, + } + + for integration in custom_integrations.values(): + # Remove core integration with same domain as the custom integration + if integration.integration_type in ("entity", "system"): + continue + + for integration_type in ("integration", "hardware", "helper"): + if integration.domain not in core_flows[integration_type]: + continue + del core_flows[integration_type][integration.domain] + if integration.domain in core_flows["translated_name"]: + core_flows["translated_name"].remove(integration.domain) + + metadata = { + "config_flow": integration.config_flow, + "iot_class": integration.iot_class, + "name": integration.name, + } + custom_flows[integration.integration_type][integration.domain] = metadata + + return {"core": core_flows, "custom": custom_flows} + + async def async_get_application_credentials(hass: HomeAssistant) -> list[str]: """Return cached list of application credentials.""" integrations = await async_get_custom_components(hass) diff --git a/script/hassfest/__main__.py b/script/hassfest/__main__.py index 233abda4ed8..7fb6ad2d8d0 100644 --- a/script/hassfest/__main__.py +++ b/script/hassfest/__main__.py @@ -31,7 +31,6 @@ INTEGRATION_PLUGINS = [ application_credentials, bluetooth, codeowners, - config_flow, dependencies, dhcp, json, @@ -44,6 +43,7 @@ INTEGRATION_PLUGINS = [ translations, usb, zeroconf, + config_flow, ] HASS_PLUGINS = [ coverage, diff --git a/script/hassfest/brand.py b/script/hassfest/brand.py new file mode 100644 index 00000000000..64217da1592 --- /dev/null +++ b/script/hassfest/brand.py @@ -0,0 +1,71 @@ +"""Brand validation.""" +from __future__ import annotations + +import voluptuous as vol +from voluptuous.humanize import humanize_error + +from .model import Brand, Config, Integration + +BRAND_SCHEMA = vol.Schema( + { + vol.Required("domain"): str, + vol.Required("name"): str, + vol.Optional("integrations"): [str], + vol.Optional("iot_standards"): [vol.Any("homekit", "zigbee", "zwave")], + } +) + + +def _validate_brand( + brand: Brand, integrations: dict[str, Integration], config: Config +) -> None: + """Validate brand file.""" + try: + BRAND_SCHEMA(brand.brand) + except vol.Invalid as err: + config.add_error( + "brand", + f"Invalid brand file {brand.path.name}: {humanize_error(brand.brand, err)}", + ) + return + + if brand.domain != brand.path.stem: + config.add_error( + "brand", + f"Domain '{brand.domain}' does not match file name {brand.path.name}", + ) + + if not brand.integrations and not brand.iot_standards: + config.add_error( + "brand", + f"Invalid brand file {brand.path.name}: At least one of integrations or " + "iot_standards must be non-empty", + ) + + if brand.integrations: + for sub_integration in brand.integrations: + if sub_integration not in integrations: + config.add_error( + "brand", + f"Invalid brand file {brand.path.name}: Can't add non core domain " + f"'{sub_integration}' to 'integrations'", + ) + + if ( + brand.domain in integrations + and not brand.integrations + or brand.domain not in brand.integrations + ): + config.add_error( + "brand", + f"Invalid brand file {brand.path.name}: Brand '{brand.brand['domain']}' " + f"is an integration but is missing in the brand's 'integrations' list'", + ) + + +def validate( + brands: dict[str, Brand], integrations: dict[str, Integration], config: Config +) -> None: + """Handle all integrations' brands.""" + for brand in brands.values(): + _validate_brand(brand, integrations, config) diff --git a/script/hassfest/config_flow.py b/script/hassfest/config_flow.py index b5af4da6cb4..81cc5fae829 100644 --- a/script/hassfest/config_flow.py +++ b/script/hassfest/config_flow.py @@ -1,9 +1,13 @@ """Generate config flow file.""" from __future__ import annotations +import json +import pathlib + import black -from .model import Config, Integration +from .brand import validate as validate_brands +from .model import Brand, Config, Integration from .serializer import to_string BASE = """ @@ -87,14 +91,107 @@ def _generate_and_validate(integrations: dict[str, Integration], config: Config) return black.format_str(BASE.format(to_string(domains)), mode=black.Mode()) +def _populate_brand_integrations( + integration_data: dict, + integrations: dict[str, Integration], + brand_metadata: dict, + sub_integrations: list[str], +) -> None: + """Add referenced integrations to a brand's metadata.""" + brand_metadata.setdefault("integrations", {}) + for domain in sub_integrations: + integration = integrations.get(domain) + if not integration or integration.integration_type in ("entity", "system"): + continue + metadata = {} + metadata["config_flow"] = integration.config_flow + metadata["iot_class"] = integration.iot_class + if integration.translated_name: + integration_data["translated_name"].add(domain) + else: + metadata["name"] = integration.name + brand_metadata["integrations"][domain] = metadata + + +def _generate_integrations( + brands: dict[str, Brand], integrations: dict[str, Integration], config: Config +): + """Generate integrations data.""" + + result = { + "integration": {}, + "hardware": {}, + "helper": {}, + "translated_name": set(), + } + + # Not all integrations will have an item in the brands collection. + # The config flow data index will be the union of the integrations without a brands item + # and the brand domain names from the brands collection. + + # Compile a set of integrations which are referenced from at least one brand's + # integrations list. These integrations will not be present in the root level of the + # generated config flow index. + brand_integration_domains = { + brand_integration_domain + for brand in brands.values() + for brand_integration_domain in brand.integrations or [] + } + + # Compile a set of integrations which are not referenced from any brand's + # integrations list. + primary_domains = { + domain + for domain, integration in integrations.items() + if integration.manifest and domain not in brand_integration_domains + } + # Add all brands to the set + primary_domains |= set(brands) + + # Generate the config flow index + for domain in sorted(primary_domains): + metadata = {} + + if brand := brands.get(domain): + metadata["name"] = brand.name + if brand.integrations: + # Add the integrations which are referenced from the brand's + # integrations list + _populate_brand_integrations( + result, integrations, metadata, brand.integrations + ) + if brand.iot_standards: + metadata["iot_standards"] = brand.iot_standards + result["integration"][domain] = metadata + else: # integration + integration = integrations[domain] + if integration.integration_type in ("entity", "system"): + continue + metadata["config_flow"] = integration.config_flow + metadata["iot_class"] = integration.iot_class + if integration.translated_name: + result["translated_name"].add(domain) + else: + metadata["name"] = integration.name + result[integration.integration_type][domain] = metadata + + return json.dumps( + result | {"translated_name": sorted(result["translated_name"])}, indent=2 + ) + + def validate(integrations: dict[str, Integration], config: Config): """Validate config flow file.""" config_flow_path = config.root / "homeassistant/generated/config_flows.py" + integrations_path = config.root / "homeassistant/generated/integrations.json" config.cache["config_flow"] = content = _generate_and_validate(integrations, config) if config.specific_integrations: return + brands = Brand.load_dir(pathlib.Path(config.root / "homeassistant/brands"), config) + validate_brands(brands, integrations, config) + with open(str(config_flow_path)) as fp: if fp.read() != content: config.add_error( @@ -103,11 +200,25 @@ def validate(integrations: dict[str, Integration], config: Config): "Run python3 -m script.hassfest", fixable=True, ) - return + + config.cache["integrations"] = content = _generate_integrations( + brands, integrations, config + ) + with open(str(integrations_path)) as fp: + if fp.read() != content + "\n": + config.add_error( + "config_flow", + "File integrations.json is not up to date. " + "Run python3 -m script.hassfest", + fixable=True, + ) def generate(integrations: dict[str, Integration], config: Config): """Generate config flow file.""" config_flow_path = config.root / "homeassistant/generated/config_flows.py" + integrations_path = config.root / "homeassistant/generated/integrations.json" with open(str(config_flow_path), "w") as fp: fp.write(f"{config.cache['config_flow']}") + with open(str(integrations_path), "w") as fp: + fp.write(f"{config.cache['integrations']}\n") diff --git a/script/hassfest/model.py b/script/hassfest/model.py index ab0de56077d..6a7ab64c14f 100644 --- a/script/hassfest/model.py +++ b/script/hassfest/model.py @@ -39,6 +39,62 @@ class Config: self.errors.append(Error(*args, **kwargs)) +@attr.s +class Brand: + """Represent a brand in our validator.""" + + @classmethod + def load_dir(cls, path: pathlib.Path, config: Config): + """Load all brands in a directory.""" + assert path.is_dir() + brands = {} + for fil in path.iterdir(): + brand = cls(fil) + brand.load_brand(config) + brands[brand.domain] = brand + + return brands + + path: pathlib.Path = attr.ib() + brand: dict[str, Any] | None = attr.ib(default=None) + + @property + def domain(self) -> str: + """Integration domain.""" + return self.path.stem + + @property + def name(self) -> str | None: + """Return name of the integration.""" + return self.brand.get("name") + + @property + def integrations(self) -> list[str]: + """Return the sub integrations of this brand.""" + return self.brand.get("integrations") + + @property + def iot_standards(self) -> list[str]: + """Return list of supported IoT standards.""" + return self.brand.get("iot_standards", []) + + def load_brand(self, config: Config) -> None: + """Load brand file.""" + if not self.path.is_file(): + config.add_error("model", f"Brand file {self.path} not found") + return + + try: + brand = json.loads(self.path.read_text()) + except ValueError as err: + config.add_error( + "model", f"Brand file {self.path.name} contains invalid JSON: {err}" + ) + return + + self.brand = brand + + @attr.s class Integration: """Represent an integration in our validator.""" @@ -71,6 +127,7 @@ class Integration: manifest: dict[str, Any] | None = attr.ib(default=None) errors: list[Error] = attr.ib(factory=list) warnings: list[Error] = attr.ib(factory=list) + translated_name: bool = attr.ib(default=False) @property def domain(self) -> str: @@ -122,6 +179,11 @@ class Integration: """Get integration_type.""" return self.manifest.get("integration_type", "integration") + @property + def iot_class(self) -> str | None: + """Return the integration IoT Class.""" + return self.manifest.get("iot_class") + def add_error(self, *args: Any, **kwargs: Any) -> None: """Add an error.""" self.errors.append(Error(*args, **kwargs)) diff --git a/script/hassfest/translations.py b/script/hassfest/translations.py index 9c4f75f1b2d..ab2961e0506 100644 --- a/script/hassfest/translations.py +++ b/script/hassfest/translations.py @@ -312,7 +312,9 @@ def gen_platform_strings_schema(config: Config, integration: Integration): ONBOARDING_SCHEMA = vol.Schema({vol.Required("area"): {str: cv.string_with_no_html}}) -def validate_translation_file(config: Config, integration: Integration, all_strings): +def validate_translation_file( # noqa: C901 + config: Config, integration: Integration, all_strings +): """Validate translation files for integration.""" if config.specific_integrations: check_translations_directory_name(integration) @@ -363,14 +365,16 @@ def validate_translation_file(config: Config, integration: Integration, all_stri if strings_file.name == "strings.json": find_references(strings, name, references) - if strings.get( - "title" - ) == integration.name and not allow_name_translation(integration): - integration.add_error( - "translations", - "Don't specify title in translation strings if it's a brand name " - "or add exception to ALLOW_NAME_TRANSLATION", - ) + if (title := strings.get("title")) is not None: + integration.translated_name = True + if title == integration.name and not allow_name_translation( + integration + ): + integration.add_error( + "translations", + "Don't specify title in translation strings if it's a brand " + "name or add exception to ALLOW_NAME_TRANSLATION", + ) platform_string_schema = gen_platform_strings_schema(config, integration) platform_strings = [integration.path.glob("strings.*.json")] diff --git a/tests/components/websocket_api/test_commands.py b/tests/components/websocket_api/test_commands.py index a5905a809a9..27ae21db6e2 100644 --- a/tests/components/websocket_api/test_commands.py +++ b/tests/components/websocket_api/test_commands.py @@ -2014,3 +2014,20 @@ async def test_client_message_coalescing(hass, websocket_client, hass_admin_user hass.states.async_set("light.permitted", "on", {"color": "blue"}) await websocket_client.close() await hass.async_block_till_done() + + +async def test_integration_descriptions(hass, hass_ws_client): + """Test we can get integration descriptions.""" + assert await async_setup_component(hass, "config", {}) + ws_client = await hass_ws_client(hass) + + await ws_client.send_json( + { + "id": 1, + "type": "integration/descriptions", + } + ) + response = await ws_client.receive_json() + + assert response["success"] + assert response["result"] -- GitLab