├── requirements-test.txt ├── requirements-dev.txt ├── images ├── device-triggers.gif └── shellies-integration.png ├── .github ├── FUNDING.yml ├── dependabot.yml ├── workflows │ ├── hacs.yml │ ├── release-drafter.yml │ ├── stale.yml │ ├── pr-labels.yml │ ├── lint.yml │ ├── release.yml │ └── pre-commit-update.yml ├── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md └── release-drafter.yml ├── hacs.json ├── .gitignore ├── .pre-commit-config.yaml ├── ruff.toml ├── info.md ├── LICENSE ├── README.md └── python_scripts └── shellies_discovery.py /requirements-test.txt: -------------------------------------------------------------------------------- 1 | ruff==0.14.10 2 | -------------------------------------------------------------------------------- /requirements-dev.txt: -------------------------------------------------------------------------------- 1 | -r requirements-test.txt 2 | homeassistant 3 | pre-commit==4.5.1 4 | -------------------------------------------------------------------------------- /images/device-triggers.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bieniu/ha-shellies-discovery/HEAD/images/device-triggers.gif -------------------------------------------------------------------------------- /images/shellies-integration.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bieniu/ha-shellies-discovery/HEAD/images/shellies-integration.png -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | custom: ["https://www.paypal.me/bieniu79", "https://www.buymeacoffee.com/QnLdxeaqO", "https://revolut.me/maciejbieniek"] 2 | -------------------------------------------------------------------------------- /hacs.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Shellies Discovery", 3 | "homeassistant": "2024.9.0b0", 4 | "zip_release": true, 5 | "filename": "shellies-discovery.zip" 6 | } 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.yaml 2 | *.log* 3 | __pycache__/ 4 | *.db 5 | *.conf 6 | .HA_VERSION 7 | !.gitignore 8 | .cloud 9 | .storage 10 | .vscode 11 | venv/ 12 | home-assistant_v2.db* 13 | !.pre-commit-config.yaml 14 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "pip" 4 | directory: "/" 5 | schedule: 6 | interval: "weekly" 7 | - package-ecosystem: "github-actions" 8 | directory: "/" 9 | schedule: 10 | interval: "weekly" 11 | -------------------------------------------------------------------------------- /.github/workflows/hacs.yml: -------------------------------------------------------------------------------- 1 | name: HACS Action 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | pull_request: 7 | branches: [master] 8 | schedule: 9 | - cron: "0 0 * * *" 10 | 11 | jobs: 12 | validate: 13 | runs-on: "ubuntu-latest" 14 | steps: 15 | - name: Check out repository 16 | uses: "actions/checkout@v6" 17 | with: 18 | fetch-depth: 2 19 | 20 | - name: HACS Action 21 | uses: hacs/action@22.5.0 22 | with: 23 | category: "python_script" 24 | -------------------------------------------------------------------------------- /.github/workflows/release-drafter.yml: -------------------------------------------------------------------------------- 1 | name: Release Drafter 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | 8 | jobs: 9 | update_release_draft: 10 | runs-on: ubuntu-latest 11 | name: Release Drafter 12 | steps: 13 | - name: Checkout the repository 14 | uses: actions/checkout@v6 15 | with: 16 | fetch-depth: 0 17 | 18 | - name: Run Release Drafter 19 | uses: release-drafter/release-drafter@v6.1.0 20 | env: 21 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 22 | -------------------------------------------------------------------------------- /.github/workflows/stale.yml: -------------------------------------------------------------------------------- 1 | name: Mark stale issues and pull requests 2 | 3 | on: 4 | schedule: 5 | - cron: '25 8 * * *' 6 | 7 | jobs: 8 | stale: 9 | runs-on: ubuntu-latest 10 | permissions: 11 | issues: write 12 | pull-requests: write 13 | steps: 14 | - name: Stale 15 | uses: actions/stale@v10 16 | with: 17 | repo-token: ${{ secrets.GITHUB_TOKEN }} 18 | stale-issue-message: 'Stale issue message' 19 | stale-pr-message: 'Stale pull request message' 20 | stale-issue-label: 'no-issue-activity' 21 | stale-pr-label: 'no-pr-activity' 22 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: feature-request 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | 14 | 15 | 16 | **Describe the solution you'd like** 17 | 20 | 21 | 22 | **Additional context** 23 | 26 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/astral-sh/ruff-pre-commit 3 | rev: v0.14.10 4 | hooks: 5 | - id: ruff 6 | args: 7 | - --fix 8 | files: ^((python_scripts)/.+)?[^/]+\.py$ 9 | - id: ruff-format 10 | files: ^((python_scripts)/.+)?[^/]+\.py$ 11 | - repo: https://github.com/pre-commit/pre-commit-hooks 12 | rev: v6.0.0 13 | hooks: 14 | - id: check-toml 15 | - id: check-json 16 | - id: check-yaml 17 | - id: end-of-file-fixer 18 | - id: mixed-line-ending 19 | - id: trailing-whitespace 20 | - id: pretty-format-json 21 | args: 22 | - --no-sort-keys 23 | - id: no-commit-to-branch 24 | args: 25 | - --branch=master 26 | -------------------------------------------------------------------------------- /.github/workflows/pr-labels.yml: -------------------------------------------------------------------------------- 1 | name: PR Labels 2 | 3 | on: 4 | pull_request_target: 5 | types: 6 | - opened 7 | - labeled 8 | - unlabeled 9 | - synchronize 10 | workflow_call: 11 | 12 | jobs: 13 | pr_labels: 14 | name: Verify label 15 | runs-on: ubuntu-latest 16 | steps: 17 | - name: Verify PR has a valid label 18 | uses: jesusvasquez333/verify-pr-label-action@v1.4.0 19 | with: 20 | pull-request-number: "${{ github.event.pull_request.number }}" 21 | github-token: "${{ secrets.GITHUB_TOKEN }}" 22 | valid-labels: >- 23 | breaking-change, bugfix, documentation, enhancement, 24 | refactor, performance, new-feature, maintenance, ci, dependencies 25 | disable-reviews: true 26 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | 14 | 15 | 16 | **Expected behavior** 17 | 20 | 21 | 22 | **Versions:** 23 | - Home Assistant: 24 | - Shellies Discovery: 25 | - Shelly device firmware: 26 | 27 | **Shellies Discovery automation:** 28 | 31 | 32 | ```yaml 33 | 34 | ``` 35 | 36 | 37 | **Debug log:** 38 | 41 | 42 | ```txt 43 | 44 | ``` 45 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: Lint 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | pull_request: 7 | branches: [master] 8 | 9 | jobs: 10 | lint: 11 | name: Lint 12 | runs-on: ubuntu-latest 13 | strategy: 14 | matrix: 15 | python-version: 16 | - "3.13" 17 | 18 | steps: 19 | - name: Check out repository 20 | uses: actions/checkout@v6 21 | 22 | - name: Set up uv 23 | uses: astral-sh/setup-uv@v7 24 | with: 25 | enable-cache: true 26 | activate-environment: true 27 | python-version: 3.13 28 | 29 | - name: Install dependencies 30 | run: uv pip install -r requirements-test.txt 31 | 32 | - name: Lint with ruff 33 | run: ruff check python_scripts 34 | 35 | - name: Check code formatting with ruff 36 | run: ruff format --check python_scripts 37 | -------------------------------------------------------------------------------- /ruff.toml: -------------------------------------------------------------------------------- 1 | target-version = "py313" 2 | 3 | lint.select = ["ALL"] 4 | 5 | lint.ignore = [ 6 | "ANN001", # Missing type annotation for function argument 7 | "ANN201", # Missing return type annotation for public function 8 | "C901", # too complex 9 | "COM812", # Trailing comma missing 10 | "D203", # 1 blank line required before class docstring 11 | "D213", # Multi-line docstring summary should start at the second line 12 | "D404", # First word of the docstring should not be This 13 | "E501", # Line too long 14 | "EM101", # Exception must not use a string literal, assign to variable first 15 | "EM102", # Exception must not use an f-string literal, assign to variable first 16 | "FBT002", # Boolean default value in function definition 17 | "FBT003", # Boolean positional value in function call 18 | "INP001", # Add an `__init__.py` 19 | "TRY003", # Avoid specifying long messages outside the exception class 20 | ] 21 | 22 | [lint.mccabe] 23 | max-complexity = 25 24 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | release: 5 | types: [published] 6 | 7 | jobs: 8 | release_zip_file: 9 | name: Prepare release asset 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Check out repository 13 | uses: actions/checkout@v6 14 | 15 | - name: Set package version 16 | run: | 17 | version="${{ github.event.release.tag_name }}" 18 | cd "${{ github.workspace }}/python_scripts" 19 | sed -i "s/^VERSION = \".*\"/VERSION = \"${version}\"/" shellies_discovery.py 20 | 21 | - name: ZIP files 22 | run: | 23 | cd "${{ github.workspace }}/python_scripts" 24 | zip shellies-discovery.zip -r ./ 25 | 26 | - name: Upload zip to release 27 | uses: svenstaro/upload-release-action@2.11.3 28 | with: 29 | repo_token: ${{ secrets.GITHUB_TOKEN }} 30 | file: "${{ github.workspace }}/python_scripts/shellies-discovery.zip" 31 | asset_name: shellies-discovery.zip 32 | tag: ${{ github.ref }} 33 | overwrite: true 34 | -------------------------------------------------------------------------------- /.github/workflows/pre-commit-update.yml: -------------------------------------------------------------------------------- 1 | name: Pre-commit auto-update 2 | on: 3 | schedule: 4 | # Run on fridays 5 | - cron: "0 0 * * 1" 6 | 7 | jobs: 8 | auto-update: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Checkout the repository 12 | uses: actions/checkout@v6 13 | 14 | - name: Set up uv 15 | uses: astral-sh/setup-uv@v7 16 | with: 17 | enable-cache: true 18 | activate-environment: true 19 | python-version: 3.13 20 | 21 | - name: Install pre-commit 22 | run: uv pip install pre-commit 23 | 24 | - name: Run pre-commit autoupdate 25 | run: pre-commit autoupdate 26 | 27 | - name: Create Pull Request 28 | uses: peter-evans/create-pull-request@v8.0.0 29 | with: 30 | token: ${{ secrets.GITHUB_TOKEN }} 31 | branch: update/pre-commit-autoupdate 32 | title: Auto-update pre-commit hooks 33 | commit-message: Auto-update pre-commit hooks 34 | body: | 35 | Update versions of tools in pre-commit 36 | configs to latest version 37 | labels: dependencies 38 | -------------------------------------------------------------------------------- /.github/release-drafter.yml: -------------------------------------------------------------------------------- 1 | name-template: "Version $RESOLVED_VERSION" 2 | tag-template: "$RESOLVED_VERSION" 3 | 4 | change-template: "- #$NUMBER $TITLE @$AUTHOR" 5 | sort-direction: ascending 6 | 7 | categories: 8 | - title: "🚨 Breaking changes" 9 | label: "breaking-change" 10 | 11 | - title: "✨ New features" 12 | label: "new-feature" 13 | 14 | - title: "🐛 Bug fixes" 15 | label: "bugfix" 16 | 17 | - title: "🚀 Enhancements" 18 | labels: 19 | - "enhancement" 20 | - "refactor" 21 | - "performance" 22 | 23 | - title: "🧰 Maintenance" 24 | labels: 25 | - "maintenance" 26 | - "ci" 27 | 28 | - title: "📚 Documentation" 29 | labels: 30 | - "documentation" 31 | 32 | - title: "⬆️ Dependency updates" 33 | collapse-after: 1 34 | labels: 35 | - "dependencies" 36 | 37 | version-resolver: 38 | major: 39 | labels: 40 | - "breaking-change" 41 | minor: 42 | labels: 43 | - "new-feature" 44 | patch: 45 | labels: 46 | - "bugfix" 47 | - "ci" 48 | - "dependencies" 49 | - "documentation" 50 | - "enhancement" 51 | - "performance" 52 | - "refactor" 53 | default: patch 54 | 55 | template: | 56 | [![Downloads for this release](https://img.shields.io/github/downloads/bieniu/ha-shellies-discovery/$RESOLVED_VERSION/total.svg)](https://github.com/bieniu/ha-shellies-discovery/releases/$RESOLVED_VERSION) 57 | 58 | ## What's changed 59 | 60 | $CHANGES 61 | -------------------------------------------------------------------------------- /info.md: -------------------------------------------------------------------------------- 1 | [![Community Forum][forum-shield]][forum] [![Buy me a coffee][buy-me-a-coffee-shield]][buy-me-a-coffee] [![PayPal_Me][paypal-me-shield]][paypal-me] [![Revolut.Me][revolut-me-shield]][revolut-me] 2 | 3 | ![Screenshot](https://github.com/bieniu/ha-shellies-discovery/blob/master/images/shellies-integration.png?raw=true) 4 | 5 | ## Prerequisites 6 | 7 | This script needs Home Assistant `python_script` component so, if you never used it, I strongly suggest you to follow the [official instruction](https://www.home-assistant.io/integrations/python_script#writing-your-first-script) and check that `python_script` is properly configured and it's working. 8 | 9 | MQTT integration must be configured in Home Assistant. 10 | 11 | ## Supported devices 12 | 13 | - Shelly 1 (with external sensors and external switch) 14 | - Shelly 1L (with external sensors) 15 | - Shelly 1PM (with external sensors) 16 | - Shelly 2 (relays and roller mode) 17 | - Shelly 2.5 (relays and roller mode) 18 | - Shelly 3EM 19 | - Shelly 4Pro 20 | - Shelly Air 21 | - Shelly Button1 (battery or USB powered) 22 | - Shelly Dimmer 23 | - Shelly Dimmer 2 24 | - Shelly Door/Window 25 | - Shelly Door/Window 2 26 | - Shelly DUO 27 | - Shelly DUO RGBW 28 | - Shelly EM 29 | - Shelly Flood 30 | - Shelly Gas (with Valve Add-on) 31 | - Shelly H&T (battery or USB powered) 32 | - Shelly i3 33 | - Shelly Motion (battery or USB powered) 34 | - Shelly Motion 2 (battery or USB powered) 35 | - Shelly Plug 36 | - Shelly Plug S 37 | - Shelly Plug US 38 | - Shelly RGBW2 (color and white mode) 39 | - Shelly Sense (battery or USB powered) 40 | - Shelly Smoke 41 | - Shelly UNI (with external sensors) 42 | - Shelly Valve 43 | - Shelly Vintage 44 | 45 | ## How to debug 46 | 47 | To debug the script add this to your `logger` configuration: 48 | 49 | ```yaml 50 | # configuration.yaml file 51 | logger: 52 | default: warning 53 | logs: 54 | homeassistant.components.python_script: debug 55 | homeassistant.components.automation: info 56 | ``` 57 | 58 | ## Troubleshooting checklist 59 | 60 | - correct MQTT configuration in Home Assistant with `discovery` enabled 61 | - same `discovery_prefix` in Home Assistant configuration and in script configuration 62 | - Shellies firmware updated to current version 63 | - Home Assistant updated to current version 64 | - enabled MQTT in Shellies configuration 65 | - you can't manually run the shellies_discovery.py script ('trigger' is undefined error) 66 | 67 | ## Shelly device name 68 | 69 | The script supports Shelly devices with non-standard names (`Internet & Security` -> `Advanced - developer settings` -> `Custom MQTT prefix` in the Shelly WWW panel). 70 | If you want to change the name of the Shelly device, you must first remove the device from Home Assistant (`Configuration` -> `Integrations` -> `MQTT` -> Device -> `Remove`). Otherwise, all device entities will be duplicated. 71 | 72 | ## Minimal configuration 73 | 74 | ```yaml 75 | # configuration.yaml file 76 | python_script: 77 | 78 | # automations.yaml file 79 | - id: shellies_announce 80 | alias: 'Shellies Announce' 81 | triggers: 82 | - trigger: homeassistant 83 | event: start 84 | - trigger: time_pattern 85 | hours: "/1" # Modifying this if you are using Shelly Motion can drain your device's battery quickly. 86 | actions: 87 | - action: mqtt.publish 88 | data: 89 | topic: shellies/command 90 | payload: announce 91 | 92 | - id: 'shellies_discovery' 93 | alias: 'Shellies Discovery' 94 | mode: queued 95 | max: 999 96 | triggers: 97 | - trigger: mqtt 98 | topic: shellies/announce 99 | conditions: 100 | - condition: template 101 | value_template: "{{ trigger.payload_json.gen is not defined }}" 102 | actions: 103 | - action: python_script.shellies_discovery 104 | data: 105 | id: '{{ trigger.payload_json.id }}' 106 | mac: '{{ trigger.payload_json.mac }}' 107 | fw_ver: '{{ trigger.payload_json.fw_ver }}' 108 | model: '{{ trigger.payload_json.model | default }}' 109 | mode: '{{ trigger.payload_json.mode | default }}' 110 | host: '{{ trigger.payload_json.ip }}' 111 | ``` 112 | 113 | ## Custom configuration example 114 | 115 | ```yaml 116 | # configuration.yaml file 117 | python_script: 118 | 119 | # automations.yaml file 120 | - id: shellies_announce 121 | alias: 'Shellies Announce' 122 | triggers: 123 | - trigger: homeassistant 124 | event: start 125 | - trigger: time_pattern 126 | hours: "/1" # Modifying this if you are using Shelly Motion can drain your device's battery quickly. 127 | actions: 128 | - actions: mqtt.publish 129 | data: 130 | topic: shellies/command 131 | payload: announce 132 | 133 | - id: 'shellies_discovery' 134 | alias: 'Shellies Discovery' 135 | mode: queued 136 | max: 999 137 | triggers: 138 | - trigger: mqtt 139 | topic: shellies/announce 140 | conditions: 141 | - condition: template 142 | value_template: "{{ trigger.payload_json.gen is not defined }}" 143 | actions: 144 | - action: python_script.shellies_discovery 145 | data: 146 | id: '{{ trigger.payload_json.id }}' 147 | mac: '{{ trigger.payload_json.mac }}' 148 | fw_ver: '{{ trigger.payload_json.fw_ver }}' 149 | model: '{{ trigger.payload_json.model | default }}' 150 | mode: '{{ trigger.payload_json.mode | default }}' 151 | host: '{{ trigger.payload_json.ip }}' 152 | discovery_prefix: 'hass' 153 | qos: 2 154 | shellytrv-84FD75: 155 | default_heating_temperature: 21 156 | shelly1-AABB9900: 157 | relay-0: "light" 158 | ext-temperature-0: true 159 | ext-temperature-1: true 160 | ext-temperature-2: true 161 | force_update_sensors: true 162 | ext-switch: true 163 | shelly1pm-aabb9911: 164 | ext-temperature-0: true 165 | ext-humidity-0: true 166 | push_off_delay: false 167 | force_update_sensors: true 168 | shelly1l-ddbb9911: 169 | ext-temperature-0: true 170 | ext-temperature-1: true 171 | ext-temperature-2: true 172 | ext-humidity-0: true 173 | shellyswitch-123409FF: 174 | relay-0: "fan" 175 | relay-0-name: "Bathroom Fan" 176 | relay-1: "light" 177 | relay-1-name: "Livingroom Light" 178 | shellyswitch-123409cc: 179 | relay-1: "fan" 180 | shellydimmer-883409cc: 181 | light-0-name: "Bedroom Lamp" 182 | shellyswitch25-334455AA: 183 | roller-0-name: "Garage" 184 | roller-0-class: "garage" 185 | shellyplug-s-CCBBCCAA: 186 | relay-0: "light" 187 | force_update_sensors: true 188 | shellyht-11AA00CCDD: 189 | force_update_sensors: true 190 | expire_after: 500 191 | shellyht-11AA00CCEE: 192 | powered: "battery" 193 | shellyht-11AA00CCFF: 194 | powered: "ac" 195 | shellyswitch2-AA4455AA: 196 | position_template: "{{ '{% if value | float < 30 %}0{% else %}{{ value }}{% endif %}' }}" 197 | set_position_template: "{{ '{%if position | float < 30 %}0{% else %}{{ position }}{% endif %}' }}" 198 | shellybutton1-112200CCFF: 199 | powered: "ac" 200 | shellymotionsensor-113300CCFF: 201 | powered: "ac" 202 | shellyrgbw2-AA123FF32: 203 | light-1-name: "Living room" 204 | light-2-name: "Bedroom" 205 | light-3-name: "Kitchen" 206 | shellyem-BB23CC45: 207 | force_update_sensors: true 208 | shellygas-AABBCC332211: 209 | valve_connected: true 210 | ignored_devices: 211 | - shelly1-DD0011 212 | - shellyem-EECC22 213 | ``` 214 | 215 | ## Battery powered devices 216 | 217 | For battery powered devices, the script requires you to set the value of 12h for `sleep_mode.period` or to configure `expire_after` yourself. 218 | 219 | Don't send `announce` topic more than once an hour if you're using Shelly Motion! This can quickly drain your device's battery. 220 | 221 | ## How to use device automation triggers? 222 | 223 | ![device_automation](https://github.com/bieniu/ha-shellies-discovery/blob/master/images/device-triggers.gif) 224 | 225 | ## Script arguments 226 | 227 | key | optional | type | default | description 228 | -- | -- | -- | -- | -- 229 | `discovery_prefix` | True | string | `homeassistant` | MQTT discovery prefix 230 | `qos` | True | integer | `0` | MQTT QoS, you can use `0`, `1` or `2` 231 | `ignored_devices` | True | list | `None` | list of devices to ignore 232 | `ignore_device_model` | True | boolean | `false` | ignore device model to generate device name 233 | `optimistic` | True | boolean | `false` | `optimistic` | `optimistic` option for cover entities 234 | 235 | ## Device arguments 236 | 237 | key | optional | type | default | possible values | description 238 | -- | -- | -- | -- | -- | -- 239 | `device_name` | True | string | | | name of the device 240 | `relay-` | True | string | `switch` | `switch`, `light`, `fan` | component to use with the relay number `NUM` 241 | `relay--name` | True | string | None | string | friendly name of the relay number `NUM` 242 | `roller--name` | True | string | None | string | friendly name of the roller number `NUM` 243 | `roller--class` | True | string | None | string | [device_class](https://www.home-assistant.io/integrations/cover/#device-class) of the roller number `NUM` 244 | `light--name` | True | string | None | string | friendly name of the light number `NUM` 245 | `ext-temperature-` | True | boolean | `false` | `true`, `false` | presence of temperature sensor number `NUM` 246 | `ext-humidity-` | True | boolean | `false` | `true`, `false` | presence of humidity sensor number `NUM` 247 | `ext-switch` | True | boolean | `false` | `true`, `false` | presence of external switch 248 | `force_update_sensors` | True | boolean | `false` | `true`, `false` | [force update](https://www.home-assistant.io/integrations/sensor.mqtt/#force_update) for sensors 249 | `powered` | True | string | `battery` | `ac`, `battery` | `ac` or `battery` powered for Shelly H&T, Motion, Sense and Button1 250 | `expire_after` | True | integer | `51840` | | [expire after](https://www.home-assistant.io/integrations/binary_sensor.mqtt/#expire_after) for battery powered sensors in seconds 251 | `use_fahrenheit` | True | boolean | `false` | `true`, `false` | whether the temperature sensor is configured in Fahrenheit for H&T, Flood, Motion2 or DW2 252 | `default_heating_temperature` | True | float | `20` | | default target temperature after changing from OFF to HEAT mode 253 | `minimal_valve_position` | True | int | `0` | | this value should be equal to the MINIMAL VALVE POSITION LIMIT from Shelly Valve configuration 254 | `valve_connected` | True | boolean | `false` | `true`, `false` | is the Valve Add-on connected to Shelly Gas 255 | `humidity_topic` | True | string | | | Topic with humidity value ​​to display in the Shelly Valve climate entity 256 | 257 | [forum]: https://community.home-assistant.io/t/shellies-discovery-script/94048 258 | [forum-shield]: https://img.shields.io/badge/community-forum-brightgreen.svg?style=popout 259 | [buy-me-a-coffee-shield]: https://img.shields.io/static/v1.svg?label=%20&message=Buy%20me%20a%20coffee&color=6f4e37&logo=buy%20me%20a%20coffee&logoColor=white 260 | [buy-me-a-coffee]: https://www.buymeacoffee.com/QnLdxeaqO 261 | [paypal-me-shield]: https://img.shields.io/static/v1.svg?label=%20&message=PayPal.Me&logo=paypal 262 | [paypal-me]: https://www.paypal.me/bieniu79 263 | [revolut-me-shield]: https://img.shields.io/static/v1.svg?label=%20&message=Revolut&logo=revolut 264 | [revolut-me]: https://revolut.me/maciejbieniek 265 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2025 Maciej Bieniek 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Shellies Discovery 2 | 3 | [![GitHub Release][releases-shield]][releases] 4 | [![GitHub All Releases][downloads-total-shield]][releases] 5 | [![hacs_badge][hacs-shield]][hacs] 6 | [![Community Forum][forum-shield]][forum] 7 | [![Buy me a coffee][buy-me-a-coffee-shield]][buy-me-a-coffee] 8 | [![PayPal_Me][paypal-me-shield]][paypal-me] 9 | [![Revolut.Me][revolut-me-shield]][revolut-me] 10 | 11 | ![Screenshot](https://github.com/bieniu/ha-shellies-discovery/blob/master/images/shellies-integration.png?raw=true) 12 | 13 | This script adds MQTT discovery support for Shelly devices in the [Home Assistant](https://home-assistant.io/). 14 | 15 | ## Gen2/3 devices information 16 | 17 | Pro/Plus devies are supported by [Shellies Discovery Gen2 script](https://github.com/bieniu/ha-shellies-discovery-gen2). 18 | 19 | ## Prerequisites 20 | 21 | This script needs Home Assistant `python_script` component so, if you never used it, I strongly suggest you to follow the [official instruction](https://www.home-assistant.io/integrations/python_script#writing-your-first-script) and check that `python_script` is properly configured and it's working. 22 | 23 | MQTT integration must be configured in Home Assistant. 24 | 25 | ## Installation 26 | 27 | You can download `shellies_discovery.py` file and save it in `/python_scripts` folder or install the script via [HACS](https://hacs.xyz/). 28 | You won't find **Shellies Discovery** in the HACS **Integrations** section, nor add it as a custom repository. You must have a properly configured `python_script` component to be able to install the script from the HACS **Automations** section. 29 | 30 | After installing the script and adding automations, run `Shellies Announce` automation or restart Home Assistant twice. 31 | 32 | Go to [HA community](https://community.home-assistant.io/t/shellies-discovery-script/94048) for support and help. 33 | 34 | ## Supported devices 35 | 36 | - Shelly 1 (with external sensors and external switch) 37 | - Shelly 1L (with external sensors) 38 | - Shelly 1PM (with external sensors) 39 | - Shelly 2 (relays and roller mode) 40 | - Shelly 2.5 (relays and roller mode) 41 | - Shelly 3EM 42 | - Shelly 4Pro 43 | - Shelly Air 44 | - Shelly Button1 (battery or USB powered) 45 | - Shelly Dimmer 46 | - Shelly Dimmer 2 47 | - Shelly Door/Window 48 | - Shelly Door/Window 2 49 | - Shelly DUO 50 | - Shelly DUO RGBW 51 | - Shelly EM 52 | - Shelly Flood 53 | - Shelly Gas (with Valve Add-on) 54 | - Shelly H&T (battery or USB powered) 55 | - Shelly i3 56 | - Shelly Motion (battery or USB powered) 57 | - Shelly Motion 2 (battery or USB powered) 58 | - Shelly Plug 59 | - Shelly Plug S 60 | - Shelly Plug US 61 | - Shelly RGBW2 (color and white mode) 62 | - Shelly Sense (battery or USB powered) 63 | - Shelly Smoke 64 | - Shelly UNI (with external sensors) 65 | - Shelly Valve 66 | - Shelly Vintage 67 | 68 | ## How to debug 69 | 70 | To debug the script add this to your `logger` configuration: 71 | 72 | ```yaml 73 | # configuration.yaml file 74 | logger: 75 | default: warning 76 | logs: 77 | homeassistant.components.python_script: debug 78 | homeassistant.components.automation: info 79 | ``` 80 | 81 | ## Troubleshooting checklist 82 | 83 | - correct MQTT configuration in Home Assistant with `discovery` enabled 84 | - same `discovery_prefix` in Home Assistant configuration and in script configuration 85 | - Shellies firmware updated to current version 86 | - Home Assistant updated to current version 87 | - enabled MQTT in Shellies configuration 88 | - you can't manually run the `shellies_discovery.py` script (`'dict object' has no attribute 'payload_json'` error) 89 | 90 | ## Shelly device name 91 | 92 | The script supports Shelly devices with non-standard names (`Internet & Security` -> `Advanced - developer settings` -> `Custom MQTT prefix` in the Shelly WWW panel). 93 | If you want to change the name of the Shelly device, you must first remove the device from Home Assistant (`Configuration` -> `Integrations` -> `MQTT` -> Device -> `Remove`). Otherwise, all device entities will be duplicated. 94 | 95 | ## Minimal configuration 96 | 97 | ```yaml 98 | # configuration.yaml file 99 | python_script: 100 | 101 | # automations.yaml file 102 | - id: shellies_announce 103 | alias: 'Shellies Announce' 104 | triggers: 105 | - trigger: homeassistant 106 | event: start 107 | - trigger: time_pattern 108 | hours: "/1" # Modifying this if you are using Shelly Motion can drain your device's battery quickly. 109 | actions: 110 | - action: mqtt.publish 111 | data: 112 | topic: shellies/command 113 | payload: announce 114 | 115 | - id: 'shellies_discovery' 116 | alias: 'Shellies Discovery' 117 | mode: queued 118 | max: 999 119 | triggers: 120 | - trigger: mqtt 121 | topic: shellies/announce 122 | conditions: 123 | - condition: template 124 | value_template: "{{ trigger.payload_json.gen is not defined }}" 125 | actions: 126 | - action: python_script.shellies_discovery 127 | data: 128 | id: '{{ trigger.payload_json.id }}' 129 | mac: '{{ trigger.payload_json.mac }}' 130 | fw_ver: '{{ trigger.payload_json.fw_ver }}' 131 | model: '{{ trigger.payload_json.model | default }}' 132 | mode: '{{ trigger.payload_json.mode | default }}' 133 | host: '{{ trigger.payload_json.ip }}' 134 | ``` 135 | 136 | ## Custom configuration example 137 | 138 | ```yaml 139 | # configuration.yaml file 140 | python_script: 141 | 142 | # automations.yaml file 143 | - id: shellies_announce 144 | alias: 'Shellies Announce' 145 | triggers: 146 | - trigger: homeassistant 147 | event: start 148 | - trigger: time_pattern 149 | hours: "/1" # Modifying this if you are using Shelly Motion can drain your device's battery quickly. 150 | actions: 151 | - action: mqtt.publish 152 | data: 153 | topic: shellies/command 154 | payload: announce 155 | 156 | - id: 'shellies_discovery' 157 | alias: 'Shellies Discovery' 158 | mode: queued 159 | max: 999 160 | triggers: 161 | - trigger: mqtt 162 | topic: shellies/announce 163 | conditions: 164 | - condition: template 165 | value_template: "{{ trigger.payload_json.gen is not defined }}" 166 | actions: 167 | - action: python_script.shellies_discovery 168 | data: 169 | id: '{{ trigger.payload_json.id }}' 170 | mac: '{{ trigger.payload_json.mac }}' 171 | fw_ver: '{{ trigger.payload_json.fw_ver }}' 172 | model: '{{ trigger.payload_json.model | default }}' 173 | mode: '{{ trigger.payload_json.mode | default }}' 174 | host: '{{ trigger.payload_json.ip }}' 175 | discovery_prefix: 'hass' 176 | qos: 2 177 | shellytrv-84FD75: 178 | default_heating_temperature: 21 179 | shelly1-AABB9900: 180 | relay-0: "light" 181 | ext-temperature-0: true 182 | ext-temperature-1: true 183 | ext-temperature-2: true 184 | force_update_sensors: true 185 | ext-switch: true 186 | shelly1pm-aabb9911: 187 | ext-temperature-0: true 188 | ext-humidity-0: true 189 | push_off_delay: false 190 | force_update_sensors: true 191 | shelly1l-ddbb9911: 192 | ext-temperature-0: true 193 | ext-temperature-1: true 194 | ext-temperature-2: true 195 | ext-humidity-0: true 196 | shellyswitch-123409FF: 197 | relay-0: "fan" 198 | relay-0-name: "Bathroom Fan" 199 | relay-1: "light" 200 | relay-1-name: "Livingroom Light" 201 | shellyswitch-123409cc: 202 | relay-1: "fan" 203 | shellydimmer-883409cc: 204 | light-0-name: "Bedroom Lamp" 205 | shellyswitch25-334455AA: 206 | roller-0-name: "Garage" 207 | roller-0-class: "garage" 208 | shellyplug-s-CCBBCCAA: 209 | relay-0: "light" 210 | force_update_sensors: true 211 | shellyht-11AA00CCDD: 212 | force_update_sensors: true 213 | expire_after: 500 214 | shellyht-11AA00CCEE: 215 | powered: "battery" 216 | shellyht-11AA00CCFF: 217 | powered: "ac" 218 | shellyswitch2-AA4455AA: 219 | position_template: "{{ '{% if value | float < 30 %}0{% else %}{{ value }}{% endif %}' }}" 220 | set_position_template: "{{ '{%if position | float < 30 %}0{% else %}{{ position }}{% endif %}' }}" 221 | shellybutton1-112200CCFF: 222 | powered: "ac" 223 | shellymotionsensor-113300CCFF: 224 | powered: "ac" 225 | shellyrgbw2-AA123FF32: 226 | light-1-name: "Living room" 227 | light-2-name: "Bedroom" 228 | light-3-name: "Kitchen" 229 | shellyem-BB23CC45: 230 | force_update_sensors: true 231 | shellygas-AABBCC332211: 232 | valve_connected: true 233 | ignored_devices: 234 | - shelly1-DD0011 235 | - shellyem-EECC22 236 | ``` 237 | 238 | ## Battery powered devices 239 | 240 | For battery powered devices, the script requires you to set the value of 12h for `sleep_mode.period` or to configure `expire_after` yourself. 241 | 242 | Don't send `announce` topic more than once an hour if you're using Shelly Motion! This can quickly drain your device's battery. 243 | 244 | ## How to use device automation triggers? 245 | 246 | ![device_automation](https://github.com/bieniu/ha-shellies-discovery/blob/master/images/device-triggers.gif) 247 | 248 | 249 | ## Script arguments 250 | 251 | key | optional | type | default | description 252 | -- | -- | -- | -- | -- 253 | `discovery_prefix` | True | string | `homeassistant` | MQTT discovery prefix 254 | `qos` | True | integer | `0` | MQTT QoS, you can use `0`, `1` or `2` 255 | `ignored_devices` | True | list | `None` | list of devices to ignore 256 | `ignore_device_model` | True | boolean | `false` | ignore device model to generate device name 257 | `optimistic` | True | boolean | `false` | `optimistic` | `optimistic` option for cover entities 258 | 259 | ## Device arguments 260 | 261 | key | optional | type | default | possible values | description 262 | -- | -- | -- | -- | -- | -- 263 | `device_name` | True | string | | | name of the device 264 | `relay-` | True | string | `switch` | `switch`, `light`, `fan` | component to use with the relay number `NUM` 265 | `relay--name` | True | string | None | string | friendly name of the relay number `NUM` 266 | `roller--name` | True | string | None | string | friendly name of the roller number `NUM` 267 | `roller--class` | True | string | None | string | [device_class](https://www.home-assistant.io/integrations/cover/#device-class) of the roller number `NUM` 268 | `light--name` | True | string | None | string | friendly name of the light number `NUM` 269 | `ext-temperature-` | True | boolean | `false` | `true`, `false` | presence of temperature sensor number `NUM` 270 | `ext-humidity-` | True | boolean | `false` | `true`, `false` | presence of humidity sensor number `NUM` 271 | `ext-switch` | True | boolean | `false` | `true`, `false` | presence of external switch 272 | `force_update_sensors` | True | boolean | `false` | `true`, `false` | [force update](https://www.home-assistant.io/integrations/sensor.mqtt/#force_update) for sensors 273 | `powered` | True | string | `battery` | `ac`, `battery` | `ac` or `battery` powered for Shelly H&T, Motion, Sense and Button1 274 | `expire_after` | True | integer | `51840` | | [expire after](https://www.home-assistant.io/integrations/binary_sensor.mqtt/#expire_after) for battery powered sensors in seconds 275 | `use_fahrenheit` | True | boolean | `false` | `true`, `false` | whether the temperature sensor is configured in Fahrenheit for H&T, Flood, Motion2 or DW2 276 | `default_heating_temperature` | True | float | `20` | | default target temperature after changing from OFF to HEAT mode 277 | `minimal_valve_position` | True | int | `0` | | this value should be equal to the MINIMAL VALVE POSITION LIMIT from Shelly Valve configuration 278 | `valve_connected` | True | boolean | `false` | `true`, `false` | is the Valve Add-on connected to Shelly Gas 279 | `humidity_topic` | True | string | | | Topic with humidity value ​​to display in the Shelly Valve climate entity 280 | 281 | 282 | [releases]: https://github.com/bieniu/ha-shellies-discovery/releases 283 | [releases-shield]: https://img.shields.io/github/release/bieniu/ha-shellies-discovery.svg?style=popout 284 | [downloads-total-shield]: https://img.shields.io/github/downloads/bieniu/ha-shellies-discovery/total 285 | [forum]: https://community.home-assistant.io/t/shellies-discovery-script/94048 286 | [forum-shield]: https://img.shields.io/badge/community-forum-brightgreen.svg?style=popout 287 | [buy-me-a-coffee-shield]: https://img.shields.io/static/v1.svg?label=%20&message=Buy%20me%20a%20coffee&color=6f4e37&logo=buy%20me%20a%20coffee&logoColor=white 288 | [buy-me-a-coffee]: https://www.buymeacoffee.com/QnLdxeaqO 289 | [paypal-me-shield]: https://img.shields.io/static/v1.svg?label=%20&message=PayPal.Me&logo=paypal 290 | [paypal-me]: https://www.paypal.me/bieniu79 291 | [hacs-shield]: https://img.shields.io/badge/HACS-Default-orange.svg 292 | [hacs]: https://hacs.xyz/docs/default_repositories 293 | [revolut-me-shield]: https://img.shields.io/static/v1.svg?label=%20&message=Revolut&logo=revolut 294 | [revolut-me]: https://revolut.me/maciejbieniek 295 | -------------------------------------------------------------------------------- /python_scripts/shellies_discovery.py: -------------------------------------------------------------------------------- 1 | """This script adds MQTT discovery support for Shellies devices.""" 2 | 3 | VERSION = "0.0.0" 4 | 5 | ATTR_ICON = "icon" 6 | ATTR_MANUFACTURER = "Allterco Robotics" 7 | ATTR_POWER_AC = "ac" 8 | ATTR_RELAY = "relay" 9 | ATTR_ROLLER = "roller" 10 | ATTR_SHELLY = "Shelly" 11 | 12 | ATTR_AVAILABILITY_EXTRA = "availability_extra" 13 | 14 | BUTTON_MUTE = "mute" 15 | BUTTON_RESTART = "restart" 16 | BUTTON_SELF_TEST = "self_test" 17 | BUTTON_UNMUTE = "unmute" 18 | BUTTON_VALVE_CLOSE = "valve_close" 19 | BUTTON_VALVE_OPEN = "valve_open" 20 | 21 | COMP_FAN = "fan" 22 | COMP_LIGHT = "light" 23 | COMP_SWITCH = "switch" 24 | 25 | CONF_DEFAULT_HEAT_TEMP = "default_heating_temperature" 26 | CONF_DEVELOP = "develop" 27 | CONF_DEVICE_NAME = "device_name" 28 | CONF_DISCOVERY_PREFIX = "discovery_prefix" 29 | CONF_EXPIRE_AFTER = "expire_after" 30 | CONF_EXT_SWITCH = "ext-switch" 31 | CONF_FORCE_UPDATE_SENSORS = "force_update_sensors" 32 | CONF_FRIENDLY_NAME = "friendly_name" 33 | CONF_FW_VER = "fw_ver" 34 | CONF_HOST = "host" 35 | CONF_HUMIDITY_TOPIC = "humidity_topic" 36 | CONF_ID = "id" 37 | CONF_IGNORE_DEVICE_MODEL = "ignore_device_model" 38 | CONF_IGNORED_DEVICES = "ignored_devices" 39 | CONF_MAC = "mac" 40 | CONF_MINIMAL_VALVE_POSITION = "minimal_valve_position" 41 | CONF_MODE = "mode" 42 | CONF_MODEL_ID = "model" 43 | CONF_OPTIMISTIC = "optimistic" 44 | CONF_POSITION_TEMPLATE = "position_template" 45 | CONF_POWERED = "powered" 46 | CONF_QOS = "qos" 47 | CONF_SET_POSITION_TEMPLATE = "set_position_template" 48 | CONF_USE_FAHRENHEIT = "use_fahrenheit" 49 | CONF_VALVE_CONNECTED = "valve_connected" 50 | 51 | DEFAULT_DISC_PREFIX = "homeassistant" 52 | 53 | DEVICE_CLASS_AWNING = "awning" 54 | DEVICE_CLASS_BATTERY = "battery" 55 | DEVICE_CLASS_BATTERY_CHARGING = "battery_charging" 56 | DEVICE_CLASS_BLIND = "blind" 57 | DEVICE_CLASS_BUTTON = "button" 58 | DEVICE_CLASS_COLD = "cold" 59 | DEVICE_CLASS_CONNECTIVITY = "connectivity" 60 | DEVICE_CLASS_CURRENT = "current" 61 | DEVICE_CLASS_CURTAIN = "curtain" 62 | DEVICE_CLASS_DAMPER = "damper" 63 | DEVICE_CLASS_DOOR = "door" 64 | DEVICE_CLASS_ENERGY = "energy" 65 | DEVICE_CLASS_ENUM = "enum" 66 | DEVICE_CLASS_FIRMWARE = "firmware" 67 | DEVICE_CLASS_GARAGE = "garage" 68 | DEVICE_CLASS_GARAGE_DOOR = "garage_door" 69 | DEVICE_CLASS_GAS = "gas" 70 | DEVICE_CLASS_GATE = "gate" 71 | DEVICE_CLASS_HEAT = "heat" 72 | DEVICE_CLASS_HUMIDITY = "humidity" 73 | DEVICE_CLASS_ILLUMINANCE = "illuminance" 74 | DEVICE_CLASS_LIGHT = "light" 75 | DEVICE_CLASS_LOCK = "lock" 76 | DEVICE_CLASS_MOISTURE = "moisture" 77 | DEVICE_CLASS_MOTION = "motion" 78 | DEVICE_CLASS_MOVING = "moving" 79 | DEVICE_CLASS_OCCUPANCY = "occupancy" 80 | DEVICE_CLASS_OPENING = "opening" 81 | DEVICE_CLASS_PLUG = "plug" 82 | DEVICE_CLASS_POWER = "power" 83 | DEVICE_CLASS_POWER_FACTOR = "power_factor" 84 | DEVICE_CLASS_PRESENCE = "presence" 85 | DEVICE_CLASS_PRESSURE = "pressure" 86 | DEVICE_CLASS_PROBLEM = "problem" 87 | DEVICE_CLASS_RESTART = "restart" 88 | DEVICE_CLASS_SAFETY = "safety" 89 | DEVICE_CLASS_SHADE = "shade" 90 | DEVICE_CLASS_SHUTTER = "shutter" 91 | DEVICE_CLASS_SIGNAL_STRENGTH = "signal_strength" 92 | DEVICE_CLASS_SMOKE = "smoke" 93 | DEVICE_CLASS_SOUND = "sound" 94 | DEVICE_CLASS_TEMPERATURE = "temperature" 95 | DEVICE_CLASS_TIMESTAMP = "timestamp" 96 | DEVICE_CLASS_UPDATE = "update" 97 | DEVICE_CLASS_VIBRATION = "vibration" 98 | DEVICE_CLASS_VOLTAGE = "voltage" 99 | DEVICE_CLASS_WINDOW = "window" 100 | 101 | ENTITY_CATEGORY_CONFIG = "config" 102 | ENTITY_CATEGORY_DIAGNOSTIC = "diagnostic" 103 | 104 | EXPIRE_AFTER_FOR_BATTERY_POWERED = int(1.2 * 12 * 60 * 60) # 1.2 * 12 h 105 | EXPIRE_AFTER_FOR_AC_POWERED = int(2.2 * 10 * 60) # 2.2 * 10 min 106 | EXPIRE_AFTER_FOR_SHELLY_MOTION = int(1.2 * 60 * 60) # 1.2 * 60 min 107 | EXPIRE_AFTER_FOR_SHELLY_VALVE = int(1.2 * 60 * 60) # 1.2 * 60 min 108 | 109 | KEY_ACTION_TEMPLATE = "act_tpl" 110 | KEY_ACTION_TOPIC = "act_t" 111 | KEY_AUTOMATION_TYPE = "atype" 112 | KEY_AVAILABILITY = "avty" 113 | KEY_BLUE_TEMPLATE = "b_tpl" 114 | KEY_GREEN_TEMPLATE = "g_tpl" 115 | KEY_RED_TEMPLATE = "r_tpl" 116 | KEY_BRIGHTNESS_COMMAND_TEMPLATE = "bri_cmd_tpl" 117 | KEY_BRIGHTNESS_COMMAND_TOPIC = "bri_cmd_t" 118 | KEY_BRIGHTNESS_STATE_TOPIC = "bri_stat_t" 119 | KEY_BRIGHTNESS_TEMPLATE = "bri_tpl" 120 | KEY_BRIGHTNESS_VALUE_TEMPLATE = "bri_val_tpl" 121 | KEY_COLOR_TEMP_TEMPLATE = "clr_temp_tpl" 122 | KEY_COMMAND_OFF_TEMPLATE = "cmd_off_tpl" 123 | KEY_COMMAND_ON_TEMPLATE = "cmd_on_tpl" 124 | KEY_COMMAND_TEMPLATE = "cmd_tpl" 125 | KEY_COMMAND_TOPIC = "cmd_t" 126 | KEY_CONFIGURATION_URL = "cu" 127 | KEY_CONNECTIONS = "cns" 128 | KEY_CURRENT_HUMIDITY_TOPIC = "curr_hum_t" 129 | KEY_CURRENT_TEMPERATURE_TEMPLATE = "curr_temp_tpl" 130 | KEY_CURRENT_TEMPERATURE_TOPIC = "curr_temp_t" 131 | KEY_DEVICE = "dev" 132 | KEY_DEVICE_CLASS = "dev_cla" 133 | KEY_EFFECT_COMMAND_TEMPLATE = "fx_cmd_tpl" 134 | KEY_EFFECT_COMMAND_TOPIC = "fx_cmd_t" 135 | KEY_EFFECT_LIST = "fx_list" 136 | KEY_EFFECT_STATE_TOPIC = "fx_stat_t" 137 | KEY_EFFECT_VALUE_TEMPLATE = "fx_val_tpl" 138 | KEY_EFFECT_TEMPLATE = "fx_tpl" 139 | KEY_ENABLED_BY_DEFAULT = "en" 140 | KEY_ENTITY_CATEGORY = "ent_cat" 141 | KEY_ENTITY_PICTURE = "ent_pic" 142 | KEY_EVENT_TYPES = "evt_typ" 143 | KEY_EXPIRE_AFTER = "exp_aft" 144 | KEY_FORCE_UPDATE = "frc_upd" 145 | KEY_HW_VERSION = "hw" 146 | KEY_ICON = "icon" 147 | KEY_JSON_ATTRIBUTES_TEMPLATE = "json_attr_tpl" 148 | KEY_JSON_ATTRIBUTES_TOPIC = "json_attr_t" 149 | KEY_LATEST_VERSION_TEMPLATE = "l_ver_tpl" 150 | KEY_LATEST_VERSION_TOPIC = "l_ver_t" 151 | KEY_MAC = "mac" 152 | KEY_MANUFACTURER = "mf" 153 | KEY_MAX = "max" 154 | KEY_MAX_MIREDS = "max_mirs" 155 | KEY_MAX_TEMP = "max_temp" 156 | KEY_MIN = "min" 157 | KEY_MIN_MIREDS = "min_mirs" 158 | KEY_MIN_TEMP = "min_temp" 159 | KEY_MODE_COMMAND_TEMPLATE = "mode_cmd_tpl" 160 | KEY_MODE_COMMAND_TOPIC = "mode_cmd_t" 161 | KEY_MODE_STATE_TEMPLATE = "mode_stat_tpl" 162 | KEY_MODE_STATE_TOPIC = "mode_stat_t" 163 | KEY_MODEL = "mdl" 164 | KEY_MODEL_ID = "mdl_id" 165 | KEY_MODES = "modes" 166 | KEY_NAME = "name" 167 | KEY_OFF_DELAY = "off_dly" 168 | KEY_OPTIMISTIC = "opt" 169 | KEY_OPTIONS = "options" 170 | KEY_ORIGIN = "o" 171 | KEY_PAYLOAD = "pl" 172 | KEY_PAYLOAD_AVAILABLE = "pl_avail" 173 | KEY_PAYLOAD_CLOSE = "pl_cls" 174 | KEY_PAYLOAD_INSTALL = "pl_inst" 175 | KEY_PAYLOAD_NOT_AVAILABLE = "pl_not_avail" 176 | KEY_PAYLOAD_OFF = "pl_off" 177 | KEY_PAYLOAD_ON = "pl_on" 178 | KEY_PAYLOAD_OPEN = "pl_open" 179 | KEY_PAYLOAD_PRESS = "pl_prs" 180 | KEY_PAYLOAD_STOP = "pl_stop" 181 | KEY_POSITION_TEMPLATE = "pos_tpl" 182 | KEY_POSITION_TOPIC = "pos_t" 183 | KEY_PRECISION = "precision" 184 | KEY_QOS = "qos" 185 | KEY_RELEASE_URL = "rel_u" 186 | KEY_REPORTS_POSITION = "pos" 187 | KEY_RETAIN = "ret" 188 | KEY_RGBW_COMMAND_TEMPLATE = "rgbw_cmd_tpl" 189 | KEY_RGBW_COMMAND_TOPIC = "rgbw_cmd_t" 190 | KEY_RGBW_STATE_TOPIC = "rgbw_stat_t" 191 | KEY_RGBW_VALUE_TEMPLATE = "rgbw_val_tpl" 192 | KEY_SCHEMA = "schema" 193 | KEY_SET_POSITION_TEMPLATE = "set_pos_tpl" 194 | KEY_SET_POSITION_TOPIC = "set_pos_t" 195 | KEY_STATE_CLASS = "stat_cla" 196 | KEY_STATE_CLOSING = "stat_closing" 197 | KEY_STATE_OFF = "stat_off" 198 | KEY_STATE_ON = "stat_on" 199 | KEY_STATE_OPENING = "stat_opening" 200 | KEY_STATE_STOPPED = "stat_stopped" 201 | KEY_STATE_TEMPLATE = "stat_tpl" 202 | KEY_STATE_TOPIC = "stat_t" 203 | KEY_STATE_VALUE_TEMPLATE = "stat_val_tpl" 204 | KEY_STEP = "step" 205 | KEY_SUBTYPE = "stype" 206 | KEY_SUGGESTED_DISPLAY_PRECISION = "sug_dsp_prc" 207 | KEY_SUPPORT_URL = "url" 208 | KEY_SW_VERSION = "sw" 209 | KEY_TEMP_STEP = "temp_step" 210 | KEY_TEMPERATURE_COMMAND_TEMPLATE = "temp_cmd_tpl" 211 | KEY_TEMPERATURE_COMMAND_TOPIC = "temp_cmd_t" 212 | KEY_TEMPERATURE_STATE_TEMPLATE = "temp_stat_tpl" 213 | KEY_TEMPERATURE_STATE_TOPIC = "temp_stat_t" 214 | KEY_TITLE = "tit" 215 | KEY_TOPIC = "t" 216 | KEY_TYPE = "type" 217 | KEY_UNIQUE_ID = "uniq_id" 218 | KEY_UNIT = "unit_of_meas" 219 | KEY_VALUE_TEMPLATE = "val_tpl" 220 | 221 | LIGHT_COLOR = "color" 222 | LIGHT_WHITE = "white" 223 | 224 | # Maximum light transition time in milliseconds 225 | MAX_TRANSITION = 5000 226 | 227 | # Firmware 1.6.5 release date 228 | MIN_4PRO_FIRMWARE_DATE = 20200408 229 | 230 | # Firmware 1.9.4 release date 231 | MIN_PLUG_FIRMWARE_DATE = 20210115 232 | 233 | # Firmware 1.1.0 release date 234 | MIN_MOTION_FIRMWARE_DATE = 20220517 235 | 236 | # Firmware 2.1.4-rc1 release date 237 | MIN_MOTION2_FIRMWARE_DATE = 20220301 238 | 239 | # Firmware 2.2.1 release date 240 | MIN_VALVE_FIRMWARE_DATE = 20231009 241 | 242 | # Firmware 1.11.7 release date 243 | MIN_DIMMER_FIRMWARE_DATE = 20211109 244 | 245 | # Firmware 1.11.7 release date 246 | MIN_HT_FIRMWARE_DATE = 20211109 247 | 248 | # Firmware 1.11.8 release date 249 | MIN_FIRMWARE_DATE = 20220209 250 | 251 | MAX_MQTT_TOPIC_LENGTH = 32 252 | 253 | MODEL_SHELLY1 = f"{ATTR_SHELLY} 1" 254 | MODEL_SHELLY1L = f"{ATTR_SHELLY} 1L" 255 | MODEL_SHELLY1PM = f"{ATTR_SHELLY} 1PM" 256 | MODEL_SHELLY2 = f"{ATTR_SHELLY} 2" 257 | MODEL_SHELLY25 = f"{ATTR_SHELLY} 2.5" 258 | MODEL_SHELLY3EM = f"{ATTR_SHELLY} 3EM" 259 | MODEL_SHELLY4PRO = f"{ATTR_SHELLY} 4Pro" 260 | MODEL_SHELLYAIR = f"{ATTR_SHELLY} Air" 261 | MODEL_SHELLYBUTTON1 = f"{ATTR_SHELLY} Button1" 262 | MODEL_SHELLYDIMMER = f"{ATTR_SHELLY} Dimmer" 263 | MODEL_SHELLYDIMMER2 = f"{ATTR_SHELLY} Dimmer 2" 264 | MODEL_SHELLYDUO = f"{ATTR_SHELLY} DUO" 265 | MODEL_SHELLYDUORGBW = f"{ATTR_SHELLY} Duo RGBW" 266 | MODEL_SHELLYDW = f"{ATTR_SHELLY} Door/Window" 267 | MODEL_SHELLYDW2 = f"{ATTR_SHELLY} Door/Window 2" 268 | MODEL_SHELLYEM = f"{ATTR_SHELLY} EM" 269 | MODEL_SHELLYFLOOD = f"{ATTR_SHELLY} Flood" 270 | MODEL_SHELLYGAS = f"{ATTR_SHELLY} Gas" 271 | MODEL_SHELLYHT = f"{ATTR_SHELLY} H&T" 272 | MODEL_SHELLYI3 = f"{ATTR_SHELLY} i3" 273 | MODEL_SHELLYMOTION = f"{ATTR_SHELLY} Motion" 274 | MODEL_SHELLYMOTION2 = f"{ATTR_SHELLY} Motion 2" 275 | MODEL_SHELLYPLUG = f"{ATTR_SHELLY} Plug" 276 | MODEL_SHELLYPLUG_S = f"{ATTR_SHELLY} Plug S" 277 | MODEL_SHELLYPLUG_US = f"{ATTR_SHELLY} Plug US" 278 | MODEL_SHELLYRGBW2 = f"{ATTR_SHELLY} RGBW2" 279 | MODEL_SHELLYSENSE = f"{ATTR_SHELLY} Sense" 280 | MODEL_SHELLYSMOKE = f"{ATTR_SHELLY} Smoke" 281 | MODEL_SHELLYUNI = f"{ATTR_SHELLY} UNI" 282 | MODEL_SHELLYVALVE = f"{ATTR_SHELLY} Valve" 283 | MODEL_SHELLYVINTAGE = f"{ATTR_SHELLY} Vintage" 284 | 285 | MODEL_SHELLY1_ID = "SHSW-1" # Shelly 1 286 | MODEL_SHELLY1_PREFIX = "shelly1" 287 | 288 | MODEL_SHELLY1L_ID = "SHSW-L" # Shelly 1L 289 | MODEL_SHELLY1L_PREFIX = "shelly1l" 290 | 291 | MODEL_SHELLY1PM_ID = "SHSW-PM" # Shelly 1PM 292 | MODEL_SHELLY1PM_PREFIX = "shelly1pm" 293 | 294 | MODEL_SHELLY2_ID = "SHSW-21" # Shelly 2 295 | MODEL_SHELLY2_PREFIX = "shellyswitch" 296 | 297 | MODEL_SHELLY25_ID = "SHSW-25" # Shelly 2.5 298 | MODEL_SHELLY25_PREFIX = "shellyswitch25" 299 | 300 | MODEL_SHELLY3EM_ID = "SHEM-3" # Shelly 3EM 301 | MODEL_SHELLY3EM_PREFIX = "shellyem3" 302 | 303 | MODEL_SHELLY4PRO_ID = "SHSW-44" # Shelly 4Pro 304 | MODEL_SHELLY4PRO_PREFIX = "shelly4pro" 305 | 306 | MODEL_SHELLYAIR_ID = "SHAIR-1" # Shelly Air 307 | MODEL_SHELLYAIR_PREFIX = "shellyair" 308 | 309 | MODEL_SHELLYBUTTON1_ID = "SHBTN-1" # Shelly Button1 310 | MODEL_SHELLYBUTTON1V2_ID = "SHBTN-2" # Shelly Button1 v2 311 | MODEL_SHELLYBUTTON1_PREFIX = "shellybutton1" 312 | 313 | MODEL_SHELLYDIMMER_ID = "SHDM-1" # Shelly Dimmer 314 | MODEL_SHELLYDIMMER_PREFIX = "shellydimmer" 315 | 316 | MODEL_SHELLYDIMMER2_ID = "SHDM-2" # Shelly Dimmer 2 317 | MODEL_SHELLYDIMMER2_PREFIX = "shellydimmer2" 318 | 319 | MODEL_SHELLYDUO_ID = "SHBDUO-1" # Shelly Duo 320 | MODEL_SHELLYDUO_PREFIX = "shellybulbduo" 321 | 322 | MODEL_SHELLYDUORGBW_ID = "SHCB-1" # Shelly Duo RGBW 323 | MODEL_SHELLYDUORGBW_PREFIX = "shellycolorbulb" 324 | 325 | MODEL_SHELLYDW_ID = "SHDW-1" # Shelly Door/Window 326 | MODEL_SHELLYDW_PREFIX = "shellydw" 327 | 328 | MODEL_SHELLYDW2_ID = "SHDW-2" # Shelly Door/Window 2 329 | MODEL_SHELLYDW2_PREFIX = "shellydw2" 330 | 331 | MODEL_SHELLYEM_ID = "SHEM" # Shelly EM 332 | MODEL_SHELLYEM_PREFIX = "shellyem" 333 | 334 | MODEL_SHELLYFLOOD_ID = "SHWT-1" # Shelly Flood 335 | MODEL_SHELLYFLOOD_PREFIX = "shellyflood" 336 | 337 | MODEL_SHELLYGAS_ID = "SHGS-1" # Shelly Gas 338 | MODEL_SHELLYGAS_PREFIX = "shellygas" 339 | 340 | MODEL_SHELLYHT_ID = "SHHT-1" # Shelly H&T 341 | MODEL_SHELLYHT_PREFIX = "shellyht" 342 | 343 | MODEL_SHELLYI3_ID = "SHIX3-1" # Shelly i3 344 | MODEL_SHELLYI3_PREFIX = "shellyix3" 345 | 346 | MODEL_SHELLYMOTION_ID = "SHMOS-01" # Shelly Motion 347 | MODEL_SHELLYMOTION_PREFIX = "shellymotionsensor" 348 | 349 | MODEL_SHELLYMOTION2_ID = "SHMOS-02" # Shelly Motion 350 | MODEL_SHELLYMOTION2_PREFIX = "shellymotionsensor2" 351 | 352 | MODEL_SHELLYPLUG_ID = "SHPLG-1" # Shelly Plug 353 | MODEL_SHELLYPLUG_E_ID = "SHPLG2-1" # Shelly Plug E 354 | MODEL_SHELLYPLUG_PREFIX = "shellyplug" 355 | 356 | MODEL_SHELLYPLUG_S_ID = "SHPLG-S" # Shelly Plug S 357 | MODEL_SHELLYPLUG_S_PREFIX = "shellyplug-s" 358 | 359 | MODEL_SHELLYPLUG_US_ID = "SHPLG-U1" # Shelly Plug US 360 | MODEL_SHELLYPLUG_US_PREFIX = "shellyplug-u1" 361 | 362 | MODEL_SHELLYRGBW2_ID = "SHRGBW2" # Shelly RGBW2 363 | MODEL_SHELLYRGBW2_PREFIX = "shellyrgbw2" 364 | 365 | MODEL_SHELLYSENSE_ID = "SHSEN-1" # Shelly Sense 366 | MODEL_SHELLYSENSE_PREFIX = "shellysense" 367 | 368 | MODEL_SHELLYSMOKE_ID = "SHSM-01" # Shelly Smoke 369 | MODEL_SHELLYSMOKE_PREFIX = "shellysmoke" 370 | 371 | MODEL_SHELLYVALVE_ID = "SHTRV-01" # Shelly Valve 372 | MODEL_SHELLYVALVE_PREFIX = "shellytrv" 373 | 374 | MODEL_SHELLYVINTAGE_ID = "SHVIN-1" # Shelly Vintage 375 | MODEL_SHELLYVINTAGE_PREFIX = "shellyvintage" 376 | 377 | MODEL_SHELLYUNI_ID = "SHUNI-1" # Shelly UNI 378 | MODEL_SHELLYUNI_PREFIX = "shellyuni" 379 | 380 | NUMBER_BOOST_TIME = "boost_time" 381 | NUMBER_MINIMAL_VALVE_POSITION = "minimal_valve_position" 382 | NUMBER_VALVE_POSITION = "valve_position" 383 | NUMBER_LIGHT_BRIGHTNESS = "brightness" 384 | 385 | OFF_DELAY = 1 386 | 387 | PL_CLOSE = "close" 388 | PL_MUTE = "mute" 389 | PL_OPEN = "open" 390 | PL_RESTART = "reboot" 391 | PL_SELF_TEST = "self_test" 392 | PL_UNMUTE = "unmute" 393 | PL_UPDATE_FIRMWARE = "update_fw" 394 | 395 | SELECT_PROFILES = "profiles" 396 | 397 | SENSOR_ADC = "adc" 398 | SENSOR_AUTOMATIC_TEMPERATURE_CONTROL = "automatic_temperature_control" 399 | SENSOR_BATTERY = "battery" 400 | SENSOR_CALIBRATED = "calibrated" 401 | SENSOR_CHARGER = "charger" 402 | SENSOR_CLOUD = "cloud" 403 | SENSOR_CONCENTRATION = "concentration" 404 | SENSOR_CURRENT = "current" 405 | SENSOR_ENERGY = "energy" 406 | SENSOR_EXT_HUMIDITY = "ext_humidity" 407 | SENSOR_EXT_SWITCH = "ext_switch" 408 | SENSOR_EXT_TEMPERATURE = "ext_temperature" 409 | SENSOR_FLOOD = "flood" 410 | SENSOR_GAS = "gas" 411 | SENSOR_HUMIDITY = "humidity" 412 | SENSOR_ILLUMINATION = "illumination" 413 | SENSOR_INPUT_0 = "input 0" 414 | SENSOR_INPUT_1 = "input 1" 415 | SENSOR_INPUT_2 = "input 2" 416 | SENSOR_INPUT_3 = "input 3" 417 | SENSOR_IP = "ip" 418 | SENSOR_IX_SUM_CURRENT = "ix_sum_current" 419 | SENSOR_LAST_RESTART = "last_restart" 420 | SENSOR_LOADERROR = "loaderror" 421 | SENSOR_LUX = "lux" 422 | SENSOR_MOTION = "motion" 423 | SENSOR_N_CURRENT = "n_current" 424 | SENSOR_OPENING = "opening" 425 | SENSOR_OPERATION = "operation" 426 | SENSOR_OVERLOAD = "overload" 427 | SENSOR_OVERPOWER = "overpower" 428 | SENSOR_OVERPOWER_VALUE = "overpower_value" 429 | SENSOR_OVERTEMPERATURE = "overtemperature" 430 | SENSOR_POWER = "power" 431 | SENSOR_POWER_FACTOR = "pf" 432 | SENSOR_REACTIVE_POWER = "reactive_power" 433 | SENSOR_REPORTED_WINDOW_STATE = "reported_window_state" 434 | SENSOR_RETURNED_ENERGY = "returned_energy" 435 | SENSOR_RSSI = "rssi" 436 | SENSOR_SELF_TEST = "self_test" 437 | SENSOR_SMOKE = "smoke" 438 | SENSOR_SSID = "ssid" 439 | SENSOR_TEMPERATURE = "temperature" 440 | SENSOR_TEMPERATURE_F = "temperature_f" 441 | SENSOR_TEMPERATURE_STATUS = "temperature_status" 442 | SENSOR_TILT = "tilt" 443 | SENSOR_TOTAL = "total" 444 | SENSOR_TOTAL_RETURNED = "total_returned" 445 | SENSOR_TOTALWORKTIME = "totalworktime" 446 | SENSOR_UPTIME = "uptime" 447 | SENSOR_VALVE = "valve" 448 | SENSOR_VIBRATION = "vibration" 449 | SENSOR_VOLTAGE = "voltage" 450 | SENSOR_WINDOW_STATE_REPORTING = "window_state_reporting" 451 | 452 | UPDATE_FIRMWARE = "firmware" 453 | 454 | VALVE_GAS = "gas" 455 | 456 | STATE_CLASS_MEASUREMENT = "measurement" 457 | STATE_CLASS_TOTAL_INCREASING = "total_increasing" 458 | 459 | SWITCH_ACCELERATED_HEATING = "accelerated_heating" 460 | SWITCH_SCHEDULE = "schedule" 461 | 462 | TOPIC_ADC = "~adc/0" 463 | TOPIC_ANNOUNCE = "~announce" 464 | TOPIC_CHARGER = "~charger" 465 | TOPIC_COLOR_0_STATUS = "~color/0/status" 466 | TOPIC_COMMAND = "~command" 467 | TOPIC_COMMAND_ACCELERATED_HEATING = "~thermostat/0/command/accelerated_heating" 468 | TOPIC_COMMAND_BOOST_MINUTES = "~thermostat/0/command/boost_minutes" 469 | TOPIC_COMMAND_PROFILES = "~thermostat/0/command/schedule_profile" 470 | TOPIC_COMMAND_SCHEDULE = "~thermostat/0/command/schedule" 471 | TOPIC_COMMAND_VALVE_MIN = "~thermostat/0/command/valve_min_percent" 472 | TOPIC_COMMAND_VALVE_POSITION = "~thermostat/0/command/valve_pos" 473 | TOPIC_ENERGY = "~relay/energy" 474 | TOPIC_EXT_SWITCH = "~ext_switch/0" 475 | TOPIC_INFO = "~info" 476 | TOPIC_INPUT_0 = "~input/0" 477 | TOPIC_INPUT_1 = "~input/1" 478 | TOPIC_INPUT_2 = "~input/2" 479 | TOPIC_INPUT_3 = "~input/3" 480 | TOPIC_IX_SUM_CURRENT = "~emeter_n/ixsum" 481 | TOPIC_LIGHT_ENERGY = "~light/{light_id}/energy" 482 | TOPIC_LIGHT_ENERGY_RGBW2_COLOR = "~color/{light_id}/energy" 483 | TOPIC_LIGHT_ENERGY_RGBW2_WHITE = "~white/{light_id}/energy" 484 | TOPIC_LIGHT_OVERPOWER_VALUE = "~light/{light_id}/overpower_value" 485 | TOPIC_LIGHT_POWER = "~light/{light_id}/power" 486 | TOPIC_LIGHT_POWER_RGBW2_COLOR = "~color/{light_id}/power" 487 | TOPIC_LIGHT_POWER_RGBW2_WHITE = "~white/{light_id}/power" 488 | TOPIC_LIGHT_SET = "~light/{light_id}/set" 489 | TOPIC_LIGHT_STATUS = "~light/{light_id}/status" 490 | TOPIC_LOADERROR = "~loaderror" 491 | TOPIC_METER_CURRENT = "~emeter/{meter_id}/current" 492 | TOPIC_METER_ENERGY = "~emeter/{meter_id}/energy" 493 | TOPIC_METER_POWER = "~emeter/{meter_id}/power" 494 | TOPIC_METER_POWER_FACTOR = "~emeter/{meter_id}/pf" 495 | TOPIC_METER_REACTIVE_POWER = "~emeter/{meter_id}/reactive_power" 496 | TOPIC_METER_RETURNED_ENERGY = "~emeter/{meter_id}/returned_energy" 497 | TOPIC_METER_TOTAL = "~emeter/{meter_id}/total" 498 | TOPIC_METER_TOTAL_RETURNED = "~emeter/{meter_id}/total_returned" 499 | TOPIC_METER_VOLTAGE = "~emeter/{meter_id}/voltage" 500 | TOPIC_MUTE = "~sensor/mute" 501 | TOPIC_N_CURRENT = "~emeter_n/current" 502 | TOPIC_ONLINE = "~online" 503 | TOPIC_OVERLOAD = "~overload" 504 | TOPIC_OVERPOWER = "~overpower" 505 | TOPIC_OVERPOWER_VALUE = "overpower_value" 506 | TOPIC_OVERTEMPERATURE = "~overtemperature" 507 | TOPIC_POWER = "~relay/power" 508 | TOPIC_RELAY = "~relay/{relay_id}" 509 | TOPIC_RELAY_ENERGY = "~relay/{relay_id}/energy" 510 | TOPIC_RELAY_POWER = "~relay/{relay_id}/power" 511 | TOPIC_ROLLER_ENERGY = "~roller/0/energy" 512 | TOPIC_ROLLER_POWER = "~roller/0/power" 513 | TOPIC_SELF_TEST = "~sensor/start_self_test" 514 | TOPIC_SENSOR_BATTERY = "~sensor/battery" 515 | TOPIC_SENSOR_CHARGER = "~sensor/charger" 516 | TOPIC_SENSOR_CONCENTRATION = "~sensor/concentration" 517 | TOPIC_SENSOR_GAS = "~sensor/gas" 518 | TOPIC_SENSOR_HUMIDITY = "~sensor/humidity" 519 | TOPIC_SENSOR_LUX = "~sensor/lux" 520 | TOPIC_SENSOR_OPERATION = "~sensor/operation" 521 | TOPIC_SENSOR_SELF_TEST = "~sensor/self_test" 522 | TOPIC_SENSOR_SMOKE = "~sensor/smoke" 523 | TOPIC_SENSOR_STATE = "~sensor/state" 524 | TOPIC_SENSOR_FLOOD = "~sensor/flood" 525 | TOPIC_SENSOR_TEMPERATURE = "~sensor/temperature" 526 | TOPIC_SENSOR_TILT = "~sensor/tilt" 527 | TOPIC_SENSOR_UNMUTE = "~sensor/unmute" 528 | TOPIC_SENSOR_VIBRATION = "~sensor/vibration" 529 | TOPIC_SETTINGS = "~settings" 530 | TOPIC_STATUS = "~status" 531 | TOPIC_TEMPERATURE = "~temperature" 532 | TOPIC_TEMPERATURE_STATUS = "~temperature_status" 533 | TOPIC_TOTAL_WORK_TIME = "~totalworktime" 534 | TOPIC_VALVE = "~valve/0/state" 535 | TOPIC_VALVE_COMMAND = "~valve/0/command" 536 | TOPIC_VOLTAGE = "~voltage" 537 | TOPIC_WHITE_SET = "~white/{light_id}/set" 538 | TOPIC_WHITE_STATUS = "~white/{light_id}/status" 539 | 540 | TPL_ACCELERATED_HEATING = "{{value_json.thermostats.0.target_t.accelerated_heating}}" 541 | TPL_ACTION_TEMPLATE = "{{%if value_json.thermostats.0.target_t.value<={min_temp}%}}off{{%elif value_json.thermostats.0.pos=={min_pos}%}}idle{{%else%}}heating{{%endif%}}" 542 | TPL_AUTOMATIC_TEMPERATURE_CONTROL = ( 543 | "{%if value_json.target_t.enabled%}ON{%else%}OFF{%endif%}" 544 | ) 545 | TPL_BATTERY_FROM_INFO = "{{value_json.bat.value}}" 546 | TPL_BATTERY_FROM_JSON = "{{value_json.bat}}" 547 | TPL_BOOST_MINUTES = "{{value_json.thermostats.0.boost_minutes}}" 548 | TPL_CALIBRATED = "{%if value_json.calibrated%}ON{%else%}OFF{%endif%}" 549 | TPL_CALIBRATED_AVAILABILITY = ( 550 | "{%if value_json.calibrated%}online{%else%}offline{%endif%}" 551 | ) 552 | TPL_CHARGER = "{%if value_json.charger%}ON{%else%}OFF{%endif%}" 553 | TPL_CLOUD = "{%if value_json.cloud.connected%}ON{%else%}OFF{%endif%}" 554 | TPL_COLOR_TEMP_WHITE_LIGHT = ( 555 | "{{((1000000/(value_json.temp|int,2700)|max)|round(0,^floor^))}}" 556 | ) 557 | TPL_COMMAND_ON_WHITE_LIGHT = "{{^turn^:^on^{{%if brightness is defined%}},^brightness^:{{{{brightness|float|multiply(0.3922)|round}}}}{{%endif%}}{{%if transition is defined%}},^transition^:{{{{min(transition|multiply(1000), {max_transition})}}}}{{%endif%}}}}" 558 | TPL_COMMAND_SET_BRIGHTNESS_WHITE_LIGHT = "{{^brightness^:{{{{value|float|round}}}}}}" 559 | TPL_COMMAND_ON_WHITE_LIGHT_DUO = "{{^turn^:^on^{{%if brightness is defined%}},^brightness^:{{{{brightness|float|multiply(0.3922)|round}}}}{{%endif%}}{{%if color_temp is defined%}},^temp^:{{{{(1000000/(color_temp|int))|round(0,^floor^)}}}}{{%endif%}}{{%if transition is defined%}},^transition^:{{{{min(transition|multiply(1000), {max_transition})}}}}{{%endif%}}}}" 560 | TPL_COMMAND_PROFILES = "{{value.split(^ ^)[-1]}}" 561 | TPL_CONCENTRATION = "{%if is_number(value) and 0<=value|int<=65535%}{{value}}{%endif%}" 562 | TPL_CURRENT_TEMPERATURE = "{{value_json.thermostats.0.tmp.value}}" 563 | TPL_ENERGY_WMIN = "{{value|float/60}}" 564 | TPL_EVENT = ( 565 | "{%if value_json.event%}{{{^event_type^:value_json.event}|to_json}}{%endif%}" 566 | ) 567 | TPL_GAS = "{%if value in [^mild^,^heavy^]%}ON{%else%}OFF{%endif%}" 568 | TPL_GAS_TO_JSON = "{{{^status^:value}|tojson}}" 569 | TPL_HUMIDITY = "{%if is_number(value) and 0 MAX_MQTT_TOPIC_LENGTH: 1567 | raise ValueError( 1568 | f"id value {dev_id} is longer than 32 characters, use shorter custom MQTT prefix" 1569 | ) 1570 | if not mac: 1571 | raise ValueError(f"mac value {mac} is not valid, check script configuration") 1572 | if not fw_ver: 1573 | raise ValueError(f"fw_ver value {fw_ver} is not valid, check script configuration") 1574 | 1575 | mac = str(mac).lower() 1576 | 1577 | dev_id_prefix = dev_id.rsplit("-", 1)[0].lower() 1578 | 1579 | # compatibility with old firmware 1580 | if dev_id_prefix == MODEL_SHELLY4PRO_PREFIX: 1581 | model_id = MODEL_SHELLY4PRO_ID 1582 | 1583 | if not model_id: 1584 | raise ValueError("model_id value None is not valid, check script configuration") 1585 | 1586 | try: 1587 | cur_ver_date = parse_version(fw_ver) 1588 | except (IndexError, ValueError) as exc: 1589 | raise ValueError( 1590 | f"Firmware version {fw_ver} is not supported, update your device {dev_id}" 1591 | ) from exc 1592 | 1593 | min_ver_date = DEVICE_FIRMWARE_MAP.get(model_id, 0) 1594 | 1595 | if cur_ver_date < min_ver_date: 1596 | raise ValueError( 1597 | f"Firmware dated {min_ver_date} is required, update your device {dev_id}" 1598 | ) 1599 | 1600 | logger.debug( # noqa: F821 1601 | "id: %s, mac: %s, fw_ver: %s, model: %s", dev_id, mac, fw_ver, model_id 1602 | ) 1603 | 1604 | qos = data.get(CONF_QOS, 0) # noqa: F821 1605 | if qos not in (0, 1, 2): 1606 | raise ValueError(f"QoS value {qos} is not valid, check script configuration") 1607 | 1608 | optimistic = data.get(CONF_OPTIMISTIC, False) # noqa: F821 1609 | if not isinstance(optimistic, bool): 1610 | optimistic = False 1611 | 1612 | disc_prefix = data.get(CONF_DISCOVERY_PREFIX, DEFAULT_DISC_PREFIX) # noqa: F821 1613 | 1614 | ignore_device_model = data.get(CONF_IGNORE_DEVICE_MODEL, False) # noqa: F821 1615 | if not isinstance(ignore_device_model, bool): 1616 | ignore_device_model = False 1617 | 1618 | develop = data.get(CONF_DEVELOP, False) # noqa: F821 1619 | if develop: 1620 | disc_prefix = "develop" 1621 | retain = False 1622 | logger.error("DEVELOP MODE !!!") # noqa: F821 1623 | 1624 | battery_powered = False 1625 | buttons = {} 1626 | climate_entity_option = {} 1627 | ext_humi_sensors = 0 1628 | ext_temp_sensors = 0 1629 | inputs = 0 1630 | inputs_types = [] 1631 | lights_bin_sensors = [] 1632 | lights_bin_sensors_device_classes = [] 1633 | lights_bin_sensors_pl = [] 1634 | lights_bin_sensors_tpls = [] 1635 | meters = 0 1636 | meter_sensors = {} 1637 | model = None 1638 | relay_components = [COMP_SWITCH, COMP_LIGHT, COMP_FAN] 1639 | relays = 0 1640 | relay_binary_sensors = {} 1641 | relay_sensors = {} 1642 | light_binary_sensors = {} 1643 | light_sensors = {} 1644 | light_numbers = {} 1645 | rgbw_lights = 0 1646 | rollers = 0 1647 | updates = {} 1648 | binary_sensors = {} 1649 | numbers = {} 1650 | selectors = {} 1651 | sensors = {} 1652 | switches = {} 1653 | valves = {} 1654 | white_lights = {} 1655 | 1656 | if model_id == MODEL_SHELLY1_ID or dev_id_prefix == MODEL_SHELLY1_PREFIX: 1657 | model = MODEL_SHELLY1 1658 | 1659 | ext_humi_sensors = 1 1660 | ext_temp_sensors = 3 1661 | inputs = 1 1662 | relays = 1 1663 | 1664 | binary_sensors = { 1665 | SENSOR_EXT_SWITCH: OPTIONS_SENSOR_EXT_SWITCH, 1666 | SENSOR_INPUT_0: OPTIONS_SENSOR_INPUT_0, 1667 | } 1668 | inputs_types = [VALUE_BUTTON_LONG_PRESS, VALUE_BUTTON_SHORT_PRESS] 1669 | sensors = { 1670 | SENSOR_RSSI: OPTIONS_SENSOR_RSSI, 1671 | SENSOR_SSID: OPTIONS_SENSOR_SSID, 1672 | SENSOR_UPTIME: OPTIONS_SENSOR_UPTIME, 1673 | SENSOR_IP: OPTIONS_SENSOR_IP, 1674 | } 1675 | buttons = {BUTTON_RESTART: OPTIONS_BUTTON_RESTART} 1676 | updates = {UPDATE_FIRMWARE: OPTIONS_UPDATE_FIRMWARE} 1677 | 1678 | if model_id == MODEL_SHELLY1L_ID or dev_id_prefix == MODEL_SHELLY1L_PREFIX: 1679 | model = MODEL_SHELLY1L 1680 | 1681 | ext_humi_sensors = 1 1682 | ext_temp_sensors = 3 1683 | inputs = 2 1684 | relays = 1 1685 | 1686 | binary_sensors = { 1687 | SENSOR_INPUT_0: OPTIONS_SENSOR_INPUT_0, 1688 | SENSOR_INPUT_1: OPTIONS_SENSOR_INPUT_1, 1689 | SENSOR_OVERTEMPERATURE: OPTIONS_SENSOR_OVERTEMPERATURE, 1690 | } 1691 | inputs_types = [VALUE_BUTTON_LONG_PRESS, VALUE_BUTTON_SHORT_PRESS] 1692 | relay_sensors = { 1693 | SENSOR_POWER: OPTIONS_SENSOR_RELAY_POWER, 1694 | SENSOR_ENERGY: OPTIONS_SENSOR_RELAY_ENERGY, 1695 | } 1696 | sensors = { 1697 | SENSOR_RSSI: OPTIONS_SENSOR_RSSI, 1698 | SENSOR_SSID: OPTIONS_SENSOR_SSID, 1699 | SENSOR_UPTIME: OPTIONS_SENSOR_UPTIME, 1700 | SENSOR_IP: OPTIONS_SENSOR_IP, 1701 | SENSOR_TEMPERATURE: OPTIONS_SENSOR_DEVICE_TEMPERATURE, 1702 | } 1703 | buttons = {BUTTON_RESTART: OPTIONS_BUTTON_RESTART} 1704 | updates = {UPDATE_FIRMWARE: OPTIONS_UPDATE_FIRMWARE} 1705 | 1706 | if model_id == MODEL_SHELLY1PM_ID or dev_id_prefix == MODEL_SHELLY1PM_PREFIX: 1707 | model = MODEL_SHELLY1PM 1708 | 1709 | relays = 1 1710 | inputs = 1 1711 | ext_humi_sensors = 1 1712 | ext_temp_sensors = 3 1713 | 1714 | inputs_types = [VALUE_BUTTON_LONG_PRESS, VALUE_BUTTON_SHORT_PRESS] 1715 | relay_sensors = { 1716 | SENSOR_POWER: OPTIONS_SENSOR_RELAY_POWER, 1717 | SENSOR_ENERGY: OPTIONS_SENSOR_RELAY_ENERGY, 1718 | } 1719 | relay_binary_sensors = {SENSOR_OVERPOWER: OPTIONS_SENSOR_OVERPOWER} 1720 | sensors = { 1721 | SENSOR_RSSI: OPTIONS_SENSOR_RSSI, 1722 | SENSOR_SSID: OPTIONS_SENSOR_SSID, 1723 | SENSOR_UPTIME: OPTIONS_SENSOR_UPTIME, 1724 | SENSOR_IP: OPTIONS_SENSOR_IP, 1725 | SENSOR_TEMPERATURE: OPTIONS_SENSOR_DEVICE_TEMPERATURE, 1726 | SENSOR_TEMPERATURE_STATUS: OPTIONS_SENSOR_TEMPERATURE_STATUS, 1727 | } 1728 | binary_sensors = { 1729 | SENSOR_OVERTEMPERATURE: OPTIONS_SENSOR_OVERTEMPERATURE, 1730 | SENSOR_INPUT_0: OPTIONS_SENSOR_INPUT_0, 1731 | } 1732 | buttons = {BUTTON_RESTART: OPTIONS_BUTTON_RESTART} 1733 | 1734 | if model_id == MODEL_SHELLYAIR_ID or dev_id_prefix == MODEL_SHELLYAIR_PREFIX: 1735 | model = MODEL_SHELLYAIR 1736 | 1737 | relays = 1 1738 | 1739 | relay_sensors = { 1740 | SENSOR_POWER: OPTIONS_SENSOR_RELAY_POWER, 1741 | SENSOR_ENERGY: OPTIONS_SENSOR_RELAY_ENERGY, 1742 | } 1743 | sensors = { 1744 | SENSOR_RSSI: OPTIONS_SENSOR_RSSI, 1745 | SENSOR_SSID: OPTIONS_SENSOR_SSID, 1746 | SENSOR_UPTIME: OPTIONS_SENSOR_UPTIME, 1747 | SENSOR_IP: OPTIONS_SENSOR_IP, 1748 | SENSOR_TEMPERATURE: OPTIONS_SENSOR_DEVICE_TEMPERATURE, 1749 | SENSOR_TOTALWORKTIME: OPTIONS_SENSOR_TOTALWORKTIME, 1750 | } 1751 | binary_sensors = { 1752 | SENSOR_OVERTEMPERATURE: OPTIONS_SENSOR_OVERTEMPERATURE, 1753 | SENSOR_INPUT_0: OPTIONS_SENSOR_INPUT_0, 1754 | } 1755 | ext_temp_sensors = 1 1756 | buttons = {BUTTON_RESTART: OPTIONS_BUTTON_RESTART} 1757 | updates = {UPDATE_FIRMWARE: OPTIONS_UPDATE_FIRMWARE} 1758 | 1759 | if model_id == MODEL_SHELLY2_ID or dev_id_prefix == MODEL_SHELLY2_PREFIX: 1760 | model = MODEL_SHELLY2 1761 | 1762 | relays = 2 1763 | rollers = 1 1764 | inputs = 2 1765 | 1766 | inputs_types = [VALUE_BUTTON_LONG_PRESS, VALUE_BUTTON_SHORT_PRESS] 1767 | relay_binary_sensors = {SENSOR_OVERPOWER: OPTIONS_SENSOR_OVERPOWER} 1768 | binary_sensors = { 1769 | SENSOR_INPUT_0: OPTIONS_SENSOR_INPUT_0, 1770 | SENSOR_INPUT_1: OPTIONS_SENSOR_INPUT_1, 1771 | } 1772 | sensors = { 1773 | SENSOR_ENERGY: OPTIONS_SENSOR_ENERGY, 1774 | SENSOR_IP: OPTIONS_SENSOR_IP, 1775 | SENSOR_POWER: OPTIONS_SENSOR_POWER, 1776 | SENSOR_RSSI: OPTIONS_SENSOR_RSSI, 1777 | SENSOR_SSID: OPTIONS_SENSOR_SSID, 1778 | SENSOR_UPTIME: OPTIONS_SENSOR_UPTIME, 1779 | SENSOR_VOLTAGE: OPTIONS_SENSOR_VOLTAGE, 1780 | } 1781 | if roller_mode: 1782 | sensors[SENSOR_ENERGY] = OPTIONS_SENSOR_ROLLER_ENERGY 1783 | sensors[SENSOR_POWER] = OPTIONS_SENSOR_ROLLER_POWER 1784 | else: 1785 | sensors[SENSOR_ENERGY] = OPTIONS_SENSOR_ENERGY 1786 | sensors[SENSOR_POWER] = OPTIONS_SENSOR_POWER 1787 | buttons = {BUTTON_RESTART: OPTIONS_BUTTON_RESTART} 1788 | updates = {UPDATE_FIRMWARE: OPTIONS_UPDATE_FIRMWARE} 1789 | 1790 | if model_id == MODEL_SHELLY25_ID or dev_id_prefix == MODEL_SHELLY25_PREFIX: 1791 | model = MODEL_SHELLY25 1792 | 1793 | relays = 2 1794 | rollers = 1 1795 | inputs = 2 1796 | 1797 | inputs_types = [VALUE_BUTTON_LONG_PRESS, VALUE_BUTTON_SHORT_PRESS] 1798 | relay_sensors = { 1799 | SENSOR_POWER: OPTIONS_SENSOR_RELAY_POWER, 1800 | SENSOR_ENERGY: OPTIONS_SENSOR_RELAY_ENERGY, 1801 | } 1802 | relay_binary_sensors = {SENSOR_OVERPOWER: OPTIONS_SENSOR_OVERPOWER} 1803 | sensors = { 1804 | SENSOR_RSSI: OPTIONS_SENSOR_RSSI, 1805 | SENSOR_SSID: OPTIONS_SENSOR_SSID, 1806 | SENSOR_UPTIME: OPTIONS_SENSOR_UPTIME, 1807 | SENSOR_IP: OPTIONS_SENSOR_IP, 1808 | SENSOR_TEMPERATURE: OPTIONS_SENSOR_DEVICE_TEMPERATURE, 1809 | SENSOR_TEMPERATURE_STATUS: OPTIONS_SENSOR_TEMPERATURE_STATUS, 1810 | SENSOR_VOLTAGE: OPTIONS_SENSOR_VOLTAGE, 1811 | SENSOR_ENERGY: OPTIONS_SENSOR_ROLLER_ENERGY, 1812 | SENSOR_POWER: OPTIONS_SENSOR_ROLLER_POWER, 1813 | } 1814 | binary_sensors = { 1815 | SENSOR_INPUT_0: OPTIONS_SENSOR_INPUT_0, 1816 | SENSOR_INPUT_1: OPTIONS_SENSOR_INPUT_1, 1817 | SENSOR_OVERTEMPERATURE: OPTIONS_SENSOR_OVERTEMPERATURE, 1818 | } 1819 | buttons = {BUTTON_RESTART: OPTIONS_BUTTON_RESTART} 1820 | updates = {UPDATE_FIRMWARE: OPTIONS_UPDATE_FIRMWARE} 1821 | 1822 | if model_id == MODEL_SHELLYUNI_ID or dev_id_prefix == MODEL_SHELLYUNI_PREFIX: 1823 | model = MODEL_SHELLYUNI 1824 | 1825 | inputs = 2 1826 | relays = 2 1827 | ext_humi_sensors = 1 1828 | ext_temp_sensors = 3 1829 | 1830 | inputs_types = [VALUE_BUTTON_LONG_PRESS, VALUE_BUTTON_SHORT_PRESS] 1831 | relay_binary_sensors = {SENSOR_OVERPOWER: OPTIONS_SENSOR_OVERPOWER} 1832 | sensors = { 1833 | SENSOR_ADC: OPTIONS_SENSOR_ADC, 1834 | SENSOR_IP: OPTIONS_SENSOR_IP, 1835 | SENSOR_RSSI: OPTIONS_SENSOR_RSSI, 1836 | SENSOR_SSID: OPTIONS_SENSOR_SSID, 1837 | SENSOR_UPTIME: OPTIONS_SENSOR_UPTIME, 1838 | } 1839 | binary_sensors = { 1840 | SENSOR_INPUT_0: OPTIONS_SENSOR_INPUT_0, 1841 | SENSOR_INPUT_1: OPTIONS_SENSOR_INPUT_1, 1842 | } 1843 | buttons = {BUTTON_RESTART: OPTIONS_BUTTON_RESTART} 1844 | updates = {UPDATE_FIRMWARE: OPTIONS_UPDATE_FIRMWARE} 1845 | 1846 | if ( 1847 | model_id in (MODEL_SHELLYPLUG_ID, MODEL_SHELLYPLUG_E_ID) 1848 | or dev_id_prefix == MODEL_SHELLYPLUG_PREFIX 1849 | ): 1850 | model = MODEL_SHELLYPLUG 1851 | 1852 | relays = 1 1853 | 1854 | relay_sensors = { 1855 | SENSOR_POWER: OPTIONS_SENSOR_RELAY_POWER, 1856 | SENSOR_ENERGY: OPTIONS_SENSOR_RELAY_ENERGY, 1857 | } 1858 | relay_binary_sensors = {SENSOR_OVERPOWER: OPTIONS_SENSOR_OVERPOWER} 1859 | sensors = { 1860 | SENSOR_RSSI: OPTIONS_SENSOR_RSSI, 1861 | SENSOR_SSID: OPTIONS_SENSOR_SSID, 1862 | SENSOR_UPTIME: OPTIONS_SENSOR_UPTIME, 1863 | SENSOR_IP: OPTIONS_SENSOR_IP, 1864 | } 1865 | updates = {UPDATE_FIRMWARE: OPTIONS_UPDATE_FIRMWARE} 1866 | 1867 | if model_id == MODEL_SHELLYPLUG_US_ID or dev_id_prefix == MODEL_SHELLYPLUG_US_PREFIX: 1868 | model = MODEL_SHELLYPLUG_US 1869 | 1870 | relays = 1 1871 | 1872 | relay_sensors = { 1873 | SENSOR_POWER: OPTIONS_SENSOR_RELAY_POWER, 1874 | SENSOR_ENERGY: OPTIONS_SENSOR_RELAY_ENERGY, 1875 | } 1876 | relay_binary_sensors = {SENSOR_OVERPOWER: OPTIONS_SENSOR_OVERPOWER} 1877 | sensors = { 1878 | SENSOR_RSSI: OPTIONS_SENSOR_RSSI, 1879 | SENSOR_SSID: OPTIONS_SENSOR_SSID, 1880 | SENSOR_UPTIME: OPTIONS_SENSOR_UPTIME, 1881 | SENSOR_IP: OPTIONS_SENSOR_IP, 1882 | } 1883 | buttons = {BUTTON_RESTART: OPTIONS_BUTTON_RESTART} 1884 | updates = {UPDATE_FIRMWARE: OPTIONS_UPDATE_FIRMWARE} 1885 | 1886 | if model_id == MODEL_SHELLYPLUG_S_ID or dev_id_prefix == MODEL_SHELLYPLUG_S_PREFIX: 1887 | model = MODEL_SHELLYPLUG_S 1888 | 1889 | relays = 1 1890 | 1891 | relay_sensors = { 1892 | SENSOR_POWER: OPTIONS_SENSOR_RELAY_POWER, 1893 | SENSOR_ENERGY: OPTIONS_SENSOR_RELAY_ENERGY, 1894 | } 1895 | relay_binary_sensors = {SENSOR_OVERPOWER: OPTIONS_SENSOR_OVERPOWER} 1896 | sensors = { 1897 | SENSOR_RSSI: OPTIONS_SENSOR_RSSI, 1898 | SENSOR_SSID: OPTIONS_SENSOR_SSID, 1899 | SENSOR_UPTIME: OPTIONS_SENSOR_UPTIME, 1900 | SENSOR_IP: OPTIONS_SENSOR_IP, 1901 | SENSOR_TEMPERATURE: OPTIONS_SENSOR_DEVICE_TEMPERATURE, 1902 | } 1903 | binary_sensors = {SENSOR_OVERTEMPERATURE: OPTIONS_SENSOR_OVERTEMPERATURE} 1904 | buttons = {BUTTON_RESTART: OPTIONS_BUTTON_RESTART} 1905 | updates = {UPDATE_FIRMWARE: OPTIONS_UPDATE_FIRMWARE} 1906 | 1907 | if model_id == MODEL_SHELLY4PRO_ID or dev_id_prefix == MODEL_SHELLY4PRO_PREFIX: 1908 | model = MODEL_SHELLY4PRO 1909 | 1910 | relays = 4 1911 | 1912 | relay_sensors = { 1913 | SENSOR_POWER: OPTIONS_SENSOR_RELAY_POWER, 1914 | SENSOR_ENERGY: OPTIONS_SENSOR_RELAY_ENERGY, 1915 | } 1916 | relay_binary_sensors = {SENSOR_OVERPOWER: OPTIONS_SENSOR_OVERPOWER} 1917 | binary_sensors = { 1918 | SENSOR_INPUT_0: OPTIONS_SENSOR_INPUT_0, 1919 | SENSOR_INPUT_1: OPTIONS_SENSOR_INPUT_1, 1920 | SENSOR_INPUT_2: OPTIONS_SENSOR_INPUT_2, 1921 | SENSOR_INPUT_3: OPTIONS_SENSOR_INPUT_3, 1922 | } 1923 | sensors = {SENSOR_IP: OPTIONS_SENSOR_IP} 1924 | updates = {UPDATE_FIRMWARE: OPTIONS_UPDATE_FIRMWARE} 1925 | 1926 | if model_id == MODEL_SHELLYHT_ID or dev_id_prefix == MODEL_SHELLYHT_PREFIX: 1927 | model = MODEL_SHELLYHT 1928 | sensors = { 1929 | SENSOR_BATTERY: OPTIONS_SENSOR_BATTERY, 1930 | SENSOR_HUMIDITY: OPTIONS_SENSOR_HUMIDITY, 1931 | SENSOR_IP: OPTIONS_SENSOR_IP, 1932 | SENSOR_RSSI: OPTIONS_SENSOR_RSSI, 1933 | SENSOR_SSID: OPTIONS_SENSOR_SSID, 1934 | SENSOR_TEMPERATURE_F: OPTIONS_SENSOR_TEMPERATURE_F, 1935 | SENSOR_TEMPERATURE: OPTIONS_SENSOR_TEMPERATURE, 1936 | SENSOR_UPTIME: OPTIONS_SENSOR_UPTIME, 1937 | } 1938 | binary_sensors = {SENSOR_CLOUD: OPTIONS_SENSOR_CLOUD} 1939 | updates = {UPDATE_FIRMWARE: OPTIONS_UPDATE_FIRMWARE_BATTERY_POWERED} 1940 | battery_powered = True 1941 | 1942 | if model_id == MODEL_SHELLYMOTION_ID or dev_id_prefix == MODEL_SHELLYMOTION_PREFIX: 1943 | model = MODEL_SHELLYMOTION 1944 | 1945 | buttons = {BUTTON_RESTART: OPTIONS_BUTTON_RESTART} 1946 | sensors = { 1947 | SENSOR_BATTERY: OPTIONS_SENSOR_BATTERY_MOTION, 1948 | SENSOR_IP: OPTIONS_SENSOR_IP, 1949 | SENSOR_LUX: OPTIONS_SENSOR_LUX_MOTION, 1950 | SENSOR_RSSI: OPTIONS_SENSOR_RSSI, 1951 | SENSOR_SSID: OPTIONS_SENSOR_SSID, 1952 | SENSOR_UPTIME: OPTIONS_SENSOR_UPTIME, 1953 | } 1954 | binary_sensors = { 1955 | SENSOR_MOTION: OPTIONS_SENSOR_MOTION_MOTION, 1956 | SENSOR_VIBRATION: OPTIONS_SENSOR_VIBRATION_MOTION, 1957 | SENSOR_CHARGER: OPTIONS_SENSOR_CHARGER, 1958 | SENSOR_CLOUD: OPTIONS_SENSOR_CLOUD, 1959 | } 1960 | updates = {UPDATE_FIRMWARE: OPTIONS_UPDATE_FIRMWARE} 1961 | 1962 | battery_powered = True 1963 | 1964 | if model_id == MODEL_SHELLYMOTION2_ID or dev_id_prefix == MODEL_SHELLYMOTION2_PREFIX: 1965 | model = MODEL_SHELLYMOTION2 1966 | 1967 | buttons = {BUTTON_RESTART: OPTIONS_BUTTON_RESTART} 1968 | sensors = { 1969 | SENSOR_BATTERY: OPTIONS_SENSOR_BATTERY_MOTION, 1970 | SENSOR_IP: OPTIONS_SENSOR_IP, 1971 | SENSOR_LUX: OPTIONS_SENSOR_LUX_MOTION, 1972 | SENSOR_RSSI: OPTIONS_SENSOR_RSSI, 1973 | SENSOR_SSID: OPTIONS_SENSOR_SSID, 1974 | SENSOR_TEMPERATURE_F: OPTIONS_SENSOR_TEMPERATURE_MOTION_F, 1975 | SENSOR_TEMPERATURE: OPTIONS_SENSOR_TEMPERATURE_MOTION, 1976 | SENSOR_UPTIME: OPTIONS_SENSOR_UPTIME, 1977 | } 1978 | binary_sensors = { 1979 | SENSOR_MOTION: OPTIONS_SENSOR_MOTION_MOTION, 1980 | SENSOR_VIBRATION: OPTIONS_SENSOR_VIBRATION_MOTION, 1981 | SENSOR_CHARGER: OPTIONS_SENSOR_CHARGER, 1982 | SENSOR_CLOUD: OPTIONS_SENSOR_CLOUD, 1983 | } 1984 | updates = {UPDATE_FIRMWARE: OPTIONS_UPDATE_FIRMWARE} 1985 | 1986 | battery_powered = True 1987 | 1988 | if model_id == MODEL_SHELLYGAS_ID or dev_id_prefix == MODEL_SHELLYGAS_PREFIX: 1989 | model = MODEL_SHELLYGAS 1990 | 1991 | sensors = { 1992 | SENSOR_CONCENTRATION: OPTIONS_SENSOR_CONCENTRATION, 1993 | SENSOR_IP: OPTIONS_SENSOR_IP, 1994 | SENSOR_OPERATION: OPTIONS_SENSOR_OPERATION, 1995 | SENSOR_RSSI: OPTIONS_SENSOR_RSSI, 1996 | SENSOR_SELF_TEST: OPTIONS_SENSOR_SELF_TEST, 1997 | SENSOR_SSID: OPTIONS_SENSOR_SSID, 1998 | SENSOR_UPTIME: OPTIONS_SENSOR_UPTIME, 1999 | SENSOR_VALVE: OPTIONS_SENSOR_VALVE, 2000 | } 2001 | binary_sensors = {SENSOR_GAS: OPTIONS_SENSOR_GAS} 2002 | buttons = { 2003 | BUTTON_MUTE: OPTIONS_BUTTON_MUTE, 2004 | BUTTON_RESTART: OPTIONS_BUTTON_RESTART, 2005 | BUTTON_SELF_TEST: OPTIONS_BUTTON_SELF_TEST, 2006 | BUTTON_UNMUTE: OPTIONS_BUTTON_UNMUTE, 2007 | BUTTON_VALVE_CLOSE: {}, 2008 | BUTTON_VALVE_OPEN: {}, 2009 | } 2010 | updates = {UPDATE_FIRMWARE: OPTIONS_UPDATE_FIRMWARE} 2011 | valves = {VALVE_GAS: OPTIONS_VALVE_GAS} 2012 | 2013 | if ( 2014 | model_id in (MODEL_SHELLYBUTTON1_ID, MODEL_SHELLYBUTTON1V2_ID) 2015 | or dev_id_prefix == MODEL_SHELLYBUTTON1_PREFIX 2016 | ): 2017 | model = MODEL_SHELLYBUTTON1 2018 | 2019 | inputs = 1 2020 | 2021 | inputs_types = [ 2022 | VALUE_BUTTON_LONG_PRESS, 2023 | VALUE_BUTTON_SHORT_PRESS, 2024 | VALUE_BUTTON_DOUBLE_PRESS, 2025 | VALUE_BUTTON_TRIPLE_PRESS, 2026 | ] 2027 | sensors = { 2028 | SENSOR_BATTERY: OPTIONS_SENSOR_BATTERY, 2029 | SENSOR_IP: OPTIONS_SENSOR_IP, 2030 | SENSOR_RSSI: OPTIONS_SENSOR_RSSI, 2031 | SENSOR_SSID: OPTIONS_SENSOR_SSID, 2032 | SENSOR_UPTIME: OPTIONS_SENSOR_UPTIME, 2033 | } 2034 | binary_sensors = { 2035 | SENSOR_INPUT_0: OPTIONS_SENSOR_INPUT_0, 2036 | SENSOR_CHARGER: OPTIONS_SENSOR_CHARGER_BUTTON, 2037 | } 2038 | updates = {UPDATE_FIRMWARE: OPTIONS_UPDATE_FIRMWARE_BATTERY_POWERED} 2039 | 2040 | battery_powered = True 2041 | 2042 | if model_id == MODEL_SHELLYDW_ID or dev_id_prefix == MODEL_SHELLYDW_PREFIX: 2043 | model = MODEL_SHELLYDW 2044 | 2045 | sensors = { 2046 | SENSOR_BATTERY: OPTIONS_SENSOR_BATTERY, 2047 | SENSOR_IP: OPTIONS_SENSOR_IP, 2048 | SENSOR_LUX: OPTIONS_SENSOR_LUX, 2049 | SENSOR_RSSI: OPTIONS_SENSOR_RSSI, 2050 | SENSOR_SSID: OPTIONS_SENSOR_SSID, 2051 | SENSOR_TILT: OPTIONS_SENSOR_TILT, 2052 | SENSOR_UPTIME: OPTIONS_SENSOR_UPTIME, 2053 | } 2054 | binary_sensors = { 2055 | SENSOR_OPENING: OPTIONS_SENSOR_OPENING, 2056 | SENSOR_VIBRATION: OPTIONS_SENSOR_VIBRATION_DW, 2057 | } 2058 | updates = {UPDATE_FIRMWARE: OPTIONS_UPDATE_FIRMWARE_BATTERY_POWERED} 2059 | 2060 | battery_powered = True 2061 | 2062 | if model_id == MODEL_SHELLYDW2_ID or dev_id_prefix == MODEL_SHELLYDW2_PREFIX: 2063 | model = MODEL_SHELLYDW2 2064 | 2065 | sensors = { 2066 | SENSOR_BATTERY: OPTIONS_SENSOR_BATTERY, 2067 | SENSOR_IP: OPTIONS_SENSOR_IP, 2068 | SENSOR_LUX: OPTIONS_SENSOR_LUX, 2069 | SENSOR_RSSI: OPTIONS_SENSOR_RSSI, 2070 | SENSOR_SSID: OPTIONS_SENSOR_SSID, 2071 | SENSOR_TEMPERATURE_F: OPTIONS_SENSOR_TEMPERATURE_F, 2072 | SENSOR_TEMPERATURE: OPTIONS_SENSOR_TEMPERATURE, 2073 | SENSOR_TILT: OPTIONS_SENSOR_TILT, 2074 | SENSOR_UPTIME: OPTIONS_SENSOR_UPTIME, 2075 | } 2076 | binary_sensors = { 2077 | SENSOR_OPENING: OPTIONS_SENSOR_OPENING, 2078 | SENSOR_VIBRATION: OPTIONS_SENSOR_VIBRATION_DW, 2079 | } 2080 | updates = {UPDATE_FIRMWARE: OPTIONS_UPDATE_FIRMWARE_BATTERY_POWERED} 2081 | 2082 | battery_powered = True 2083 | 2084 | if model_id == MODEL_SHELLYSMOKE_ID or dev_id_prefix == MODEL_SHELLYSMOKE_PREFIX: 2085 | model = MODEL_SHELLYSMOKE 2086 | 2087 | sensors = { 2088 | SENSOR_BATTERY: OPTIONS_SENSOR_BATTERY, 2089 | SENSOR_IP: OPTIONS_SENSOR_IP, 2090 | SENSOR_RSSI: OPTIONS_SENSOR_RSSI, 2091 | SENSOR_SSID: OPTIONS_SENSOR_SSID, 2092 | SENSOR_TEMPERATURE: OPTIONS_SENSOR_TEMPERATURE, 2093 | SENSOR_UPTIME: OPTIONS_SENSOR_UPTIME, 2094 | } 2095 | binary_sensors = {SENSOR_SMOKE: OPTIONS_SENSOR_SMOKE} 2096 | updates = {UPDATE_FIRMWARE: OPTIONS_UPDATE_FIRMWARE_BATTERY_POWERED} 2097 | 2098 | battery_powered = True 2099 | 2100 | if model_id == MODEL_SHELLYSENSE_ID or dev_id_prefix == MODEL_SHELLYSENSE_PREFIX: 2101 | model = MODEL_SHELLYSENSE 2102 | 2103 | sensors = { 2104 | SENSOR_BATTERY: OPTIONS_SENSOR_BATTERY, 2105 | SENSOR_IP: OPTIONS_SENSOR_IP, 2106 | SENSOR_LUX: OPTIONS_SENSOR_LUX, 2107 | SENSOR_RSSI: OPTIONS_SENSOR_RSSI, 2108 | SENSOR_SSID: OPTIONS_SENSOR_SSID, 2109 | SENSOR_TEMPERATURE: OPTIONS_SENSOR_TEMPERATURE, 2110 | SENSOR_UPTIME: OPTIONS_SENSOR_UPTIME, 2111 | } 2112 | binary_sensors = { 2113 | SENSOR_MOTION: OPTIONS_SENSOR_MOTION, 2114 | SENSOR_CHARGER: OPTIONS_SENSOR_CHARGER_SENSE, 2115 | } 2116 | 2117 | battery_powered = True 2118 | 2119 | if model_id == MODEL_SHELLYRGBW2_ID or dev_id_prefix == MODEL_SHELLYRGBW2_PREFIX: 2120 | if mode not in (LIGHT_COLOR, LIGHT_WHITE): 2121 | raise ValueError(f"mode value {mode} is not valid, check script configuration") 2122 | 2123 | model = MODEL_SHELLYRGBW2 2124 | 2125 | inputs = 1 2126 | rgbw_lights = 1 2127 | white_lights = 4 2128 | 2129 | white_lights = { 2130 | 0: { 2131 | KEY_COMMAND_ON_TEMPLATE: TPL_COMMAND_ON_WHITE_LIGHT, 2132 | KEY_COMMAND_TOPIC: TOPIC_WHITE_SET, 2133 | KEY_STATE_TOPIC: TOPIC_WHITE_STATUS, 2134 | }, 2135 | 1: { 2136 | KEY_COMMAND_ON_TEMPLATE: TPL_COMMAND_ON_WHITE_LIGHT, 2137 | KEY_COMMAND_TOPIC: TOPIC_WHITE_SET, 2138 | KEY_STATE_TOPIC: TOPIC_WHITE_STATUS, 2139 | }, 2140 | 2: { 2141 | KEY_COMMAND_ON_TEMPLATE: TPL_COMMAND_ON_WHITE_LIGHT, 2142 | KEY_COMMAND_TOPIC: TOPIC_WHITE_SET, 2143 | KEY_STATE_TOPIC: TOPIC_WHITE_STATUS, 2144 | }, 2145 | 3: { 2146 | KEY_COMMAND_ON_TEMPLATE: TPL_COMMAND_ON_WHITE_LIGHT, 2147 | KEY_COMMAND_TOPIC: TOPIC_WHITE_SET, 2148 | KEY_STATE_TOPIC: TOPIC_WHITE_STATUS, 2149 | }, 2150 | } 2151 | binary_sensors = {SENSOR_INPUT_0: OPTIONS_SENSOR_INPUT_0} 2152 | inputs_types = [VALUE_BUTTON_LONG_PRESS, VALUE_BUTTON_SHORT_PRESS] 2153 | if mode == LIGHT_COLOR: 2154 | light_binary_sensors = {SENSOR_OVERPOWER: OPTIONS_SENSOR_COLOR_LIGHT_OVERPOWER} 2155 | light_sensors = { 2156 | SENSOR_POWER: OPTIONS_SENSOR_LIGHT_POWER_RGBW2_COLOR, 2157 | SENSOR_ENERGY: OPTIONS_SENSOR_LIGHT_ENERGY_RGBW2_COLOR, 2158 | } 2159 | else: 2160 | light_binary_sensors = {SENSOR_OVERPOWER: OPTIONS_SENSOR_WHITE_LIGHT_OVERPOWER} 2161 | light_sensors = { 2162 | SENSOR_POWER: OPTIONS_SENSOR_LIGHT_POWER_RGBW2_WHITE, 2163 | SENSOR_ENERGY: OPTIONS_SENSOR_LIGHT_ENERGY_RGBW2_WHITE, 2164 | } 2165 | sensors = { 2166 | SENSOR_IP: OPTIONS_SENSOR_IP, 2167 | SENSOR_RSSI: OPTIONS_SENSOR_RSSI, 2168 | SENSOR_SSID: OPTIONS_SENSOR_SSID, 2169 | SENSOR_UPTIME: OPTIONS_SENSOR_UPTIME, 2170 | } 2171 | buttons = {BUTTON_RESTART: OPTIONS_BUTTON_RESTART} 2172 | updates = {UPDATE_FIRMWARE: OPTIONS_UPDATE_FIRMWARE} 2173 | 2174 | if model_id == MODEL_SHELLYDIMMER_ID or dev_id_prefix == MODEL_SHELLYDIMMER_PREFIX: 2175 | model = MODEL_SHELLYDIMMER 2176 | 2177 | inputs = 2 2178 | 2179 | white_lights = { 2180 | 0: { 2181 | KEY_COMMAND_ON_TEMPLATE: TPL_COMMAND_ON_WHITE_LIGHT, 2182 | KEY_COMMAND_TOPIC: TOPIC_LIGHT_SET, 2183 | KEY_STATE_TOPIC: TOPIC_LIGHT_STATUS, 2184 | } 2185 | } 2186 | inputs_types = [VALUE_BUTTON_LONG_PRESS, VALUE_BUTTON_SHORT_PRESS] 2187 | sensors = { 2188 | SENSOR_IP: OPTIONS_SENSOR_IP, 2189 | SENSOR_RSSI: OPTIONS_SENSOR_RSSI, 2190 | SENSOR_SSID: OPTIONS_SENSOR_SSID, 2191 | SENSOR_TEMPERATURE: OPTIONS_SENSOR_DEVICE_TEMPERATURE, 2192 | SENSOR_UPTIME: OPTIONS_SENSOR_UPTIME, 2193 | } 2194 | binary_sensors = { 2195 | SENSOR_OVERTEMPERATURE: OPTIONS_SENSOR_OVERTEMPERATURE, 2196 | SENSOR_OVERLOAD: OPTIONS_SENSOR_OVERLOAD, 2197 | SENSOR_LOADERROR: OPTIONS_SENSOR_LOADERROR, 2198 | SENSOR_INPUT_0: OPTIONS_SENSOR_INPUT_0, 2199 | SENSOR_INPUT_1: OPTIONS_SENSOR_INPUT_1, 2200 | } 2201 | light_sensors = { 2202 | SENSOR_POWER: OPTIONS_SENSOR_LIGHT_POWER, 2203 | SENSOR_ENERGY: OPTIONS_SENSOR_LIGHT_ENERGY, 2204 | SENSOR_OVERPOWER_VALUE: OPTIONS_SENSOR_LIGHT_OVERPOWER_VALUE, 2205 | } 2206 | light_numbers = {NUMBER_LIGHT_BRIGHTNESS: OPTIONS_NUMBER_LIGHT_BRIGHTNESS} 2207 | buttons = {BUTTON_RESTART: OPTIONS_BUTTON_RESTART} 2208 | updates = {UPDATE_FIRMWARE: OPTIONS_UPDATE_FIRMWARE} 2209 | 2210 | if model_id == MODEL_SHELLYDIMMER2_ID or dev_id_prefix == MODEL_SHELLYDIMMER2_PREFIX: 2211 | model = MODEL_SHELLYDIMMER2 2212 | 2213 | inputs = 2 2214 | 2215 | inputs_types = [VALUE_BUTTON_LONG_PRESS, VALUE_BUTTON_SHORT_PRESS] 2216 | white_lights = { 2217 | 0: { 2218 | KEY_COMMAND_ON_TEMPLATE: TPL_COMMAND_ON_WHITE_LIGHT, 2219 | KEY_COMMAND_TOPIC: TOPIC_LIGHT_SET, 2220 | KEY_STATE_TOPIC: TOPIC_LIGHT_STATUS, 2221 | } 2222 | } 2223 | sensors = { 2224 | SENSOR_IP: OPTIONS_SENSOR_IP, 2225 | SENSOR_RSSI: OPTIONS_SENSOR_RSSI, 2226 | SENSOR_SSID: OPTIONS_SENSOR_SSID, 2227 | SENSOR_TEMPERATURE: OPTIONS_SENSOR_DEVICE_TEMPERATURE, 2228 | SENSOR_UPTIME: OPTIONS_SENSOR_UPTIME, 2229 | } 2230 | binary_sensors = { 2231 | SENSOR_OVERTEMPERATURE: OPTIONS_SENSOR_OVERTEMPERATURE, 2232 | SENSOR_OVERLOAD: OPTIONS_SENSOR_OVERLOAD, 2233 | SENSOR_LOADERROR: OPTIONS_SENSOR_LOADERROR, 2234 | SENSOR_INPUT_0: OPTIONS_SENSOR_INPUT_0, 2235 | SENSOR_INPUT_1: OPTIONS_SENSOR_INPUT_1, 2236 | } 2237 | light_sensors = { 2238 | SENSOR_POWER: OPTIONS_SENSOR_LIGHT_POWER, 2239 | SENSOR_ENERGY: OPTIONS_SENSOR_LIGHT_ENERGY, 2240 | SENSOR_OVERPOWER_VALUE: OPTIONS_SENSOR_LIGHT_OVERPOWER_VALUE, 2241 | } 2242 | light_numbers = {NUMBER_LIGHT_BRIGHTNESS: OPTIONS_NUMBER_LIGHT_BRIGHTNESS} 2243 | buttons = {BUTTON_RESTART: OPTIONS_BUTTON_RESTART} 2244 | updates = {UPDATE_FIRMWARE: OPTIONS_UPDATE_FIRMWARE} 2245 | 2246 | if model_id == MODEL_SHELLYDUO_ID or dev_id_prefix == MODEL_SHELLYDUO_PREFIX: 2247 | model = MODEL_SHELLYDUO 2248 | 2249 | white_lights = { 2250 | 0: { 2251 | KEY_COLOR_TEMP_TEMPLATE: TPL_COLOR_TEMP_WHITE_LIGHT, 2252 | KEY_COMMAND_ON_TEMPLATE: TPL_COMMAND_ON_WHITE_LIGHT_DUO, 2253 | KEY_COMMAND_TOPIC: TOPIC_LIGHT_SET, 2254 | KEY_MAX_MIREDS: 370, 2255 | KEY_MIN_MIREDS: 153, 2256 | KEY_STATE_TOPIC: TOPIC_LIGHT_STATUS, 2257 | } 2258 | } 2259 | light_sensors = { 2260 | SENSOR_POWER: OPTIONS_SENSOR_LIGHT_POWER, 2261 | SENSOR_ENERGY: OPTIONS_SENSOR_LIGHT_ENERGY, 2262 | } 2263 | sensors = { 2264 | SENSOR_IP: OPTIONS_SENSOR_IP, 2265 | SENSOR_RSSI: OPTIONS_SENSOR_RSSI, 2266 | SENSOR_SSID: OPTIONS_SENSOR_SSID, 2267 | SENSOR_UPTIME: OPTIONS_SENSOR_UPTIME, 2268 | } 2269 | buttons = {BUTTON_RESTART: OPTIONS_BUTTON_RESTART} 2270 | updates = {UPDATE_FIRMWARE: OPTIONS_UPDATE_FIRMWARE} 2271 | 2272 | if model_id == MODEL_SHELLYDUORGBW_ID or dev_id_prefix == MODEL_SHELLYDUORGBW_PREFIX: 2273 | model = MODEL_SHELLYDUORGBW 2274 | 2275 | rgbw_lights = 1 2276 | 2277 | light_sensors = { 2278 | SENSOR_POWER: OPTIONS_SENSOR_LIGHT_POWER, 2279 | SENSOR_ENERGY: OPTIONS_SENSOR_LIGHT_ENERGY, 2280 | } 2281 | sensors = { 2282 | SENSOR_IP: OPTIONS_SENSOR_IP, 2283 | SENSOR_RSSI: OPTIONS_SENSOR_RSSI, 2284 | SENSOR_SSID: OPTIONS_SENSOR_SSID, 2285 | SENSOR_UPTIME: OPTIONS_SENSOR_UPTIME, 2286 | } 2287 | buttons = {BUTTON_RESTART: OPTIONS_BUTTON_RESTART} 2288 | updates = {UPDATE_FIRMWARE: OPTIONS_UPDATE_FIRMWARE} 2289 | 2290 | if model_id == MODEL_SHELLYVINTAGE_ID or dev_id_prefix == MODEL_SHELLYVINTAGE_PREFIX: 2291 | model = MODEL_SHELLYVINTAGE 2292 | 2293 | white_lights = { 2294 | 0: { 2295 | KEY_COMMAND_ON_TEMPLATE: TPL_COMMAND_ON_WHITE_LIGHT, 2296 | KEY_COMMAND_TOPIC: TOPIC_LIGHT_SET, 2297 | KEY_STATE_TOPIC: TOPIC_LIGHT_STATUS, 2298 | } 2299 | } 2300 | light_sensors = { 2301 | SENSOR_POWER: OPTIONS_SENSOR_LIGHT_POWER, 2302 | SENSOR_ENERGY: OPTIONS_SENSOR_LIGHT_ENERGY, 2303 | } 2304 | sensors = { 2305 | SENSOR_IP: OPTIONS_SENSOR_IP, 2306 | SENSOR_RSSI: OPTIONS_SENSOR_RSSI, 2307 | SENSOR_SSID: OPTIONS_SENSOR_SSID, 2308 | SENSOR_UPTIME: OPTIONS_SENSOR_UPTIME, 2309 | } 2310 | buttons = {BUTTON_RESTART: OPTIONS_BUTTON_RESTART} 2311 | updates = {UPDATE_FIRMWARE: OPTIONS_UPDATE_FIRMWARE} 2312 | 2313 | if model_id == MODEL_SHELLYEM_ID or dev_id_prefix == MODEL_SHELLYEM_PREFIX: 2314 | model = MODEL_SHELLYEM 2315 | 2316 | relays = 1 2317 | meters = 2 2318 | 2319 | relay_binary_sensors = {SENSOR_OVERPOWER: OPTIONS_SENSOR_OVERPOWER} 2320 | meter_sensors = { 2321 | SENSOR_ENERGY: OPTIONS_SENSOR_ENERGY_METER, 2322 | SENSOR_POWER: OPTIONS_SENSOR_POWER_METER, 2323 | SENSOR_REACTIVE_POWER: OPTIONS_SENSOR_REACTIVE_POWER_METER, 2324 | SENSOR_RETURNED_ENERGY: OPTIONS_SENSOR_RETURNED_ENERGY_METER, 2325 | SENSOR_TOTAL_RETURNED: OPTIONS_SENSOR_TOTAL_RETURNED_METER, 2326 | SENSOR_TOTAL: OPTIONS_SENSOR_TOTAL_METER, 2327 | SENSOR_VOLTAGE: OPTIONS_SENSOR_VOLTAGE_METER, 2328 | } 2329 | sensors = { 2330 | SENSOR_IP: OPTIONS_SENSOR_IP, 2331 | SENSOR_RSSI: OPTIONS_SENSOR_RSSI, 2332 | SENSOR_SSID: OPTIONS_SENSOR_SSID, 2333 | SENSOR_UPTIME: OPTIONS_SENSOR_UPTIME, 2334 | } 2335 | buttons = {BUTTON_RESTART: OPTIONS_BUTTON_RESTART} 2336 | updates = {UPDATE_FIRMWARE: OPTIONS_UPDATE_FIRMWARE} 2337 | 2338 | if model_id == MODEL_SHELLY3EM_ID or dev_id_prefix == MODEL_SHELLY3EM_PREFIX: 2339 | model = MODEL_SHELLY3EM 2340 | 2341 | relays = 1 2342 | meters = 3 2343 | 2344 | relay_binary_sensors = {SENSOR_OVERPOWER: OPTIONS_SENSOR_OVERPOWER} 2345 | meter_sensors = { 2346 | SENSOR_CURRENT: OPTIONS_SENSOR_CURRENT_METER, 2347 | SENSOR_ENERGY: OPTIONS_SENSOR_ENERGY_METER, 2348 | SENSOR_POWER_FACTOR: OPTIONS_SENSOR_POWER_FACTOR_METER, 2349 | SENSOR_POWER: OPTIONS_SENSOR_POWER_METER, 2350 | SENSOR_RETURNED_ENERGY: OPTIONS_SENSOR_RETURNED_ENERGY_METER, 2351 | SENSOR_TOTAL_RETURNED: OPTIONS_SENSOR_TOTAL_RETURNED_METER, 2352 | SENSOR_TOTAL: OPTIONS_SENSOR_TOTAL_METER, 2353 | SENSOR_VOLTAGE: OPTIONS_SENSOR_VOLTAGE_METER, 2354 | } 2355 | sensors = { 2356 | SENSOR_IP: OPTIONS_SENSOR_IP, 2357 | SENSOR_RSSI: OPTIONS_SENSOR_RSSI, 2358 | SENSOR_SSID: OPTIONS_SENSOR_SSID, 2359 | SENSOR_UPTIME: OPTIONS_SENSOR_UPTIME, 2360 | SENSOR_N_CURRENT: OPTIONS_SENSOR_N_CURRENT, 2361 | SENSOR_IX_SUM_CURRENT: OPTIONS_SENSOR_IX_SUM_CURRENT, 2362 | } 2363 | buttons = {BUTTON_RESTART: OPTIONS_BUTTON_RESTART} 2364 | updates = {UPDATE_FIRMWARE: OPTIONS_UPDATE_FIRMWARE} 2365 | 2366 | if model_id == MODEL_SHELLYFLOOD_ID or dev_id_prefix == MODEL_SHELLYFLOOD_PREFIX: 2367 | model = MODEL_SHELLYFLOOD 2368 | 2369 | sensors = { 2370 | SENSOR_BATTERY: OPTIONS_SENSOR_BATTERY, 2371 | SENSOR_IP: OPTIONS_SENSOR_IP, 2372 | SENSOR_RSSI: OPTIONS_SENSOR_RSSI, 2373 | SENSOR_SSID: OPTIONS_SENSOR_SSID, 2374 | SENSOR_TEMPERATURE_F: OPTIONS_SENSOR_TEMPERATURE_F, 2375 | SENSOR_TEMPERATURE: OPTIONS_SENSOR_TEMPERATURE, 2376 | SENSOR_UPTIME: OPTIONS_SENSOR_UPTIME, 2377 | } 2378 | binary_sensors = {SENSOR_FLOOD: OPTIONS_SENSOR_FLOOD} 2379 | updates = {UPDATE_FIRMWARE: OPTIONS_UPDATE_FIRMWARE_BATTERY_POWERED} 2380 | 2381 | battery_powered = True 2382 | 2383 | if model_id == MODEL_SHELLYI3_ID or dev_id_prefix == MODEL_SHELLYI3_PREFIX: 2384 | model = MODEL_SHELLYI3 2385 | 2386 | inputs = 3 2387 | 2388 | inputs_types = [ 2389 | VALUE_BUTTON_LONG_PRESS, 2390 | VALUE_BUTTON_SHORT_PRESS, 2391 | VALUE_BUTTON_DOUBLE_PRESS, 2392 | VALUE_BUTTON_TRIPLE_PRESS, 2393 | VALUE_BUTTON_SHORT_LONG_PRESS, 2394 | VALUE_BUTTON_LONG_SHORT_PRESS, 2395 | ] 2396 | binary_sensors = { 2397 | SENSOR_INPUT_0: OPTIONS_SENSOR_INPUT_0, 2398 | SENSOR_INPUT_1: OPTIONS_SENSOR_INPUT_1, 2399 | SENSOR_INPUT_2: OPTIONS_SENSOR_INPUT_2, 2400 | } 2401 | sensors = { 2402 | SENSOR_RSSI: OPTIONS_SENSOR_RSSI, 2403 | SENSOR_SSID: OPTIONS_SENSOR_SSID, 2404 | SENSOR_UPTIME: OPTIONS_SENSOR_UPTIME, 2405 | SENSOR_IP: OPTIONS_SENSOR_IP_VALVE, 2406 | SENSOR_TEMPERATURE_STATUS: OPTIONS_SENSOR_TEMPERATURE_STATUS, 2407 | } 2408 | buttons = {BUTTON_RESTART: OPTIONS_BUTTON_RESTART} 2409 | updates = {UPDATE_FIRMWARE: OPTIONS_UPDATE_FIRMWARE} 2410 | 2411 | if model_id == MODEL_SHELLYVALVE_ID: 2412 | model = MODEL_SHELLYVALVE 2413 | battery_powered = True 2414 | 2415 | climate_entity_option = { 2416 | KEY_MAX_TEMP: 31, 2417 | KEY_MIN_TEMP: 4, 2418 | KEY_MODES: ["heat", "off"], 2419 | KEY_PRECISION: 0.1, 2420 | KEY_TEMP_STEP: 0.5, 2421 | } 2422 | sensors = { 2423 | SENSOR_RSSI: OPTIONS_SENSOR_RSSI, 2424 | SENSOR_SSID: OPTIONS_SENSOR_SSID, 2425 | SENSOR_LAST_RESTART: OPTIONS_SENSOR_UPTIME, 2426 | SENSOR_IP: OPTIONS_SENSOR_IP, 2427 | SENSOR_BATTERY: OPTIONS_SENSOR_BATTERY_VALVE, 2428 | } 2429 | binary_sensors = { 2430 | SENSOR_CHARGER: OPTIONS_SENSOR_CHARGER, 2431 | SENSOR_CLOUD: OPTIONS_SENSOR_CLOUD, 2432 | SENSOR_CALIBRATED: OPTIONS_SENSOR_CALIBRATED, 2433 | SENSOR_REPORTED_WINDOW_STATE: OPTIONS_SENSOR_REPORTED_WINDOW_STATE, 2434 | SENSOR_WINDOW_STATE_REPORTING: OPTIONS_SENSOR_WINDOW_STATE_REPORTING, 2435 | SENSOR_AUTOMATIC_TEMPERATURE_CONTROL: OPTIONS_SENSOR_AUTOMATIC_TEMPERATURE_CONTROL, 2436 | } 2437 | buttons = {BUTTON_RESTART: OPTIONS_BUTTON_RESTART} 2438 | selectors = {SELECT_PROFILES: OPTIONS_SELECT_PROFILES} 2439 | switches = { 2440 | SWITCH_SCHEDULE: OPTIONS_SWITCH_SCHEDULE, 2441 | SWITCH_ACCELERATED_HEATING: OPTIONS_SWITCH_ACCELERATED_HEATING, 2442 | } 2443 | numbers = { 2444 | NUMBER_VALVE_POSITION: OPTIONS_NUMBER_VALVE_POSITION, 2445 | NUMBER_MINIMAL_VALVE_POSITION: OPTIONS_NUMBER_MINIMAL_VALVE_POSITION, 2446 | NUMBER_BOOST_TIME: OPTIONS_BOOST_TIME, 2447 | } 2448 | updates = {UPDATE_FIRMWARE: OPTIONS_UPDATE_FIRMWARE} 2449 | 2450 | device_config = get_device_config(dev_id) 2451 | if device_config.get(CONF_DEVICE_NAME): 2452 | device_name = device_config[CONF_DEVICE_NAME] 2453 | elif ignore_device_model: 2454 | device_name = format_device_name(dev_id) 2455 | else: 2456 | device_name = f"{model} {dev_id.split('-')[-1]}" 2457 | 2458 | device_info = { 2459 | KEY_CONNECTIONS: [[KEY_MAC, format_mac(mac)]], 2460 | KEY_NAME: device_name, 2461 | KEY_MODEL: model, 2462 | KEY_MODEL_ID: model_id, 2463 | KEY_SW_VERSION: fw_ver, 2464 | KEY_HW_VERSION: "gen1", 2465 | KEY_MANUFACTURER: ATTR_MANUFACTURER, 2466 | KEY_CONFIGURATION_URL: f"http://{host}/", 2467 | } 2468 | origin_info = { 2469 | KEY_NAME: "Shellies Discovery", 2470 | KEY_SW_VERSION: VERSION, 2471 | KEY_SUPPORT_URL: "https://github.com/bieniu/ha-shellies-discovery", 2472 | } 2473 | 2474 | default_topic = f"shellies/{dev_id}/" 2475 | 2476 | if battery_powered: 2477 | if model == MODEL_SHELLYMOTION: 2478 | expire_after = device_config.get( 2479 | CONF_EXPIRE_AFTER, EXPIRE_AFTER_FOR_SHELLY_MOTION 2480 | ) 2481 | elif model == MODEL_SHELLYVALVE: 2482 | expire_after = device_config.get( 2483 | CONF_EXPIRE_AFTER, EXPIRE_AFTER_FOR_SHELLY_VALVE 2484 | ) 2485 | elif device_config.get(CONF_POWERED) == ATTR_POWER_AC and model in ( 2486 | MODEL_SHELLYBUTTON1, 2487 | MODEL_SHELLYSENSE, 2488 | ): 2489 | no_battery_sensor = True 2490 | battery_powered = False 2491 | elif device_config.get(CONF_POWERED): 2492 | no_battery_sensor = True 2493 | expire_after = device_config.get(CONF_EXPIRE_AFTER, EXPIRE_AFTER_FOR_AC_POWERED) 2494 | else: 2495 | expire_after = device_config.get( 2496 | CONF_EXPIRE_AFTER, EXPIRE_AFTER_FOR_BATTERY_POWERED 2497 | ) 2498 | 2499 | if expire_after and not isinstance(expire_after, int): 2500 | raise TypeError( 2501 | f"expire_after value {expire_after} is not an integer, check script configuration" 2502 | ) 2503 | 2504 | availability = [ 2505 | { 2506 | KEY_TOPIC: TOPIC_ONLINE, 2507 | KEY_PAYLOAD_AVAILABLE: "true", 2508 | KEY_PAYLOAD_NOT_AVAILABLE: "false", 2509 | }, 2510 | { 2511 | KEY_TOPIC: TOPIC_INFO, 2512 | KEY_VALUE_TEMPLATE: TPL_MQTT_CONNECTED, 2513 | }, 2514 | ] 2515 | 2516 | # updates 2517 | for update, update_options in updates.items(): 2518 | config_topic = f"{disc_prefix}/update/{dev_id}-{update}/config".encode( 2519 | "ascii", "ignore" 2520 | ).decode("utf-8") 2521 | 2522 | payload = { 2523 | KEY_NAME: format_entity_name(update), 2524 | KEY_STATE_TOPIC: update_options[KEY_STATE_TOPIC], 2525 | KEY_VALUE_TEMPLATE: update_options[KEY_VALUE_TEMPLATE], 2526 | KEY_LATEST_VERSION_TOPIC: update_options[KEY_LATEST_VERSION_TOPIC], 2527 | KEY_LATEST_VERSION_TEMPLATE: update_options[KEY_LATEST_VERSION_TEMPLATE], 2528 | KEY_ENTITY_PICTURE: "https://brands.home-assistant.io/_/shelly/icon.png", 2529 | KEY_RELEASE_URL: "https://shelly-api-docs.shelly.cloud/gen1/#changelog", 2530 | KEY_TITLE: "Firmware", 2531 | KEY_DEVICE_CLASS: DEVICE_CLASS_FIRMWARE, 2532 | KEY_ENABLED_BY_DEFAULT: str(update_options[KEY_ENABLED_BY_DEFAULT]).lower(), 2533 | KEY_UNIQUE_ID: f"{dev_id}-{update}".lower(), 2534 | KEY_QOS: qos, 2535 | KEY_DEVICE: device_info, 2536 | KEY_ORIGIN: origin_info, 2537 | "~": default_topic, 2538 | } 2539 | if battery_powered and model not in (MODEL_SHELLYDW, MODEL_SHELLYDW2): 2540 | payload[KEY_EXPIRE_AFTER] = expire_after 2541 | elif not battery_powered: 2542 | payload[KEY_AVAILABILITY] = availability 2543 | if update_options.get(KEY_COMMAND_TOPIC): 2544 | payload[KEY_COMMAND_TOPIC] = update_options[KEY_COMMAND_TOPIC] 2545 | payload[KEY_PAYLOAD_INSTALL] = update_options[KEY_PAYLOAD_INSTALL] 2546 | if update_options.get(KEY_ENTITY_CATEGORY): 2547 | payload[KEY_ENTITY_CATEGORY] = update_options[KEY_ENTITY_CATEGORY] 2548 | if dev_id.lower() in ignored: 2549 | payload = "" 2550 | 2551 | mqtt_publish(config_topic, payload, retain) 2552 | 2553 | # numbers 2554 | for number, number_options in numbers.items(): 2555 | config_topic = f"{disc_prefix}/number/{dev_id}-{number}/config".encode( 2556 | "ascii", "ignore" 2557 | ).decode("utf-8") 2558 | 2559 | if number_options.get(ATTR_AVAILABILITY_EXTRA): 2560 | availability.append(number_options[ATTR_AVAILABILITY_EXTRA]) 2561 | 2562 | payload = { 2563 | KEY_NAME: format_entity_name(number), 2564 | KEY_COMMAND_TOPIC: number_options[KEY_COMMAND_TOPIC], 2565 | KEY_MAX: number_options[KEY_MAX], 2566 | KEY_MIN: number_options[KEY_MIN], 2567 | KEY_STEP: number_options[KEY_STEP], 2568 | KEY_STATE_TOPIC: number_options[KEY_STATE_TOPIC], 2569 | KEY_VALUE_TEMPLATE: number_options[KEY_VALUE_TEMPLATE], 2570 | KEY_UNIT: number_options[KEY_UNIT], 2571 | KEY_ENABLED_BY_DEFAULT: str(number_options[KEY_ENABLED_BY_DEFAULT]).lower(), 2572 | KEY_UNIQUE_ID: f"{dev_id}-{number}".lower(), 2573 | KEY_QOS: qos, 2574 | KEY_AVAILABILITY: availability, 2575 | KEY_DEVICE: device_info, 2576 | KEY_ORIGIN: origin_info, 2577 | "~": default_topic, 2578 | } 2579 | 2580 | if number_options.get(KEY_ENTITY_CATEGORY): 2581 | payload[KEY_ENTITY_CATEGORY] = number_options[KEY_ENTITY_CATEGORY] 2582 | if number_options.get(KEY_DEVICE_CLASS): 2583 | payload[KEY_DEVICE_CLASS] = number_options[KEY_DEVICE_CLASS] 2584 | if number_options.get(ATTR_ICON): 2585 | payload[KEY_ICON] = number_options[ATTR_ICON] 2586 | if dev_id.lower() in ignored: 2587 | payload = "" 2588 | 2589 | mqtt_publish(config_topic, payload, retain) 2590 | 2591 | # switches (not relays) # noqa: ERA001 2592 | for switch, switch_options in switches.items(): 2593 | config_topic = f"{disc_prefix}/switch/{dev_id}-{switch}/config".encode( 2594 | "ascii", "ignore" 2595 | ).decode("utf-8") 2596 | 2597 | payload = { 2598 | KEY_NAME: format_entity_name(switch), 2599 | KEY_COMMAND_TOPIC: switch_options[KEY_COMMAND_TOPIC], 2600 | KEY_PAYLOAD_OFF: switch_options[KEY_PAYLOAD_OFF], 2601 | KEY_PAYLOAD_ON: switch_options[KEY_PAYLOAD_ON], 2602 | KEY_STATE_TOPIC: switch_options[KEY_STATE_TOPIC], 2603 | KEY_STATE_OFF: switch_options[KEY_STATE_OFF], 2604 | KEY_STATE_ON: switch_options[KEY_STATE_ON], 2605 | KEY_VALUE_TEMPLATE: switch_options[KEY_VALUE_TEMPLATE], 2606 | KEY_ENABLED_BY_DEFAULT: str(switch_options[KEY_ENABLED_BY_DEFAULT]).lower(), 2607 | KEY_UNIQUE_ID: f"{dev_id}-{switch}".lower(), 2608 | KEY_QOS: qos, 2609 | KEY_AVAILABILITY: availability, 2610 | KEY_DEVICE: device_info, 2611 | KEY_ORIGIN: origin_info, 2612 | "~": default_topic, 2613 | } 2614 | if switch_options.get(KEY_ENTITY_CATEGORY): 2615 | payload[KEY_ENTITY_CATEGORY] = switch_options[KEY_ENTITY_CATEGORY] 2616 | if switch_options.get(KEY_DEVICE_CLASS): 2617 | payload[KEY_DEVICE_CLASS] = switch_options[KEY_DEVICE_CLASS] 2618 | if switch_options.get(ATTR_ICON): 2619 | payload[KEY_ICON] = switch_options[ATTR_ICON] 2620 | if dev_id.lower() in ignored: 2621 | payload = "" 2622 | 2623 | mqtt_publish(config_topic, payload, retain) 2624 | 2625 | # selectors 2626 | for select, select_options in selectors.items(): 2627 | config_topic = f"{disc_prefix}/select/{dev_id}-{select}/config".encode( 2628 | "ascii", "ignore" 2629 | ).decode("utf-8") 2630 | 2631 | payload = { 2632 | KEY_NAME: format_entity_name(select), 2633 | KEY_COMMAND_TOPIC: select_options[KEY_COMMAND_TOPIC], 2634 | KEY_COMMAND_TEMPLATE: TPL_COMMAND_PROFILES, 2635 | KEY_OPTIONS: select_options[KEY_OPTIONS], 2636 | KEY_STATE_TOPIC: select_options[KEY_STATE_TOPIC], 2637 | KEY_VALUE_TEMPLATE: select_options[KEY_VALUE_TEMPLATE], 2638 | KEY_ENABLED_BY_DEFAULT: str(select_options[KEY_ENABLED_BY_DEFAULT]).lower(), 2639 | KEY_UNIQUE_ID: f"{dev_id}-{select}".lower(), 2640 | KEY_QOS: qos, 2641 | KEY_AVAILABILITY: availability, 2642 | KEY_DEVICE: device_info, 2643 | KEY_ORIGIN: origin_info, 2644 | "~": default_topic, 2645 | } 2646 | if select_options.get(KEY_ENTITY_CATEGORY): 2647 | payload[KEY_ENTITY_CATEGORY] = select_options[KEY_ENTITY_CATEGORY] 2648 | if select_options.get(KEY_DEVICE_CLASS): 2649 | payload[KEY_DEVICE_CLASS] = select_options[KEY_DEVICE_CLASS] 2650 | if select_options.get(ATTR_ICON): 2651 | payload[KEY_ICON] = select_options[ATTR_ICON] 2652 | if dev_id.lower() in ignored: 2653 | payload = "" 2654 | 2655 | mqtt_publish(config_topic, payload, retain) 2656 | 2657 | # buttons 2658 | for button, button_options in buttons.items(): 2659 | config_topic = f"{disc_prefix}/button/{dev_id}-{button}/config".encode( 2660 | "ascii", "ignore" 2661 | ).decode("utf-8") 2662 | 2663 | payload = { 2664 | KEY_NAME: format_entity_name(button), 2665 | KEY_COMMAND_TOPIC: button_options.get(KEY_COMMAND_TOPIC), 2666 | KEY_PAYLOAD_PRESS: button_options.get(KEY_PAYLOAD_PRESS), 2667 | KEY_ENABLED_BY_DEFAULT: str( 2668 | button_options.get(KEY_ENABLED_BY_DEFAULT, False) 2669 | ).lower(), 2670 | KEY_UNIQUE_ID: f"{dev_id}-{button}".lower(), 2671 | KEY_QOS: qos, 2672 | KEY_AVAILABILITY: availability, 2673 | KEY_DEVICE: device_info, 2674 | KEY_ORIGIN: origin_info, 2675 | "~": default_topic, 2676 | } 2677 | if button_options.get(KEY_ENTITY_CATEGORY): 2678 | payload[KEY_ENTITY_CATEGORY] = button_options[KEY_ENTITY_CATEGORY] 2679 | if button_options.get(KEY_DEVICE_CLASS): 2680 | payload[KEY_DEVICE_CLASS] = button_options[KEY_DEVICE_CLASS] 2681 | if button_options.get(ATTR_ICON): 2682 | payload[KEY_ICON] = button_options[ATTR_ICON] 2683 | if dev_id.lower() in ignored: 2684 | payload = "" 2685 | if not button_options: 2686 | payload = "" 2687 | 2688 | mqtt_publish(config_topic, payload, retain) 2689 | 2690 | # climate entities 2691 | if climate_entity_option: 2692 | default_heat_temp = 20 2693 | minimal_valve_position = device_config.get(CONF_MINIMAL_VALVE_POSITION, 0) 2694 | if not isinstance(minimal_valve_position, int): 2695 | raise TypeError( 2696 | f"minimal_valve_position value {minimal_valve_position} is not an integer, check script configuration" 2697 | ) 2698 | if device_config.get(CONF_DEFAULT_HEAT_TEMP): 2699 | value = device_config[CONF_DEFAULT_HEAT_TEMP] 2700 | if ( 2701 | climate_entity_option[KEY_MIN_TEMP] 2702 | < value 2703 | < climate_entity_option[KEY_MAX_TEMP] 2704 | ): 2705 | default_heat_temp = value 2706 | temperature_command_topic = "~thermostat/0/command/target_t" 2707 | config_topic = f"{disc_prefix}/climate/{dev_id}/config".encode( 2708 | "ascii", "ignore" 2709 | ).decode("utf-8") 2710 | 2711 | availability.append( 2712 | { 2713 | KEY_TOPIC: TOPIC_INFO, 2714 | KEY_VALUE_TEMPLATE: TPL_CALIBRATED_AVAILABILITY, 2715 | } 2716 | ) 2717 | 2718 | payload = { 2719 | KEY_ACTION_TOPIC: TOPIC_INFO, 2720 | KEY_ACTION_TEMPLATE: TPL_ACTION_TEMPLATE.format( 2721 | min_temp=climate_entity_option[KEY_MIN_TEMP], 2722 | min_pos=minimal_valve_position, 2723 | ), 2724 | KEY_CURRENT_TEMPERATURE_TOPIC: TOPIC_INFO, 2725 | KEY_CURRENT_TEMPERATURE_TEMPLATE: TPL_CURRENT_TEMPERATURE, 2726 | KEY_TEMPERATURE_STATE_TOPIC: TOPIC_INFO, 2727 | KEY_TEMPERATURE_STATE_TEMPLATE: TPL_TARGET_TEMPERATURE, 2728 | KEY_TEMPERATURE_COMMAND_TOPIC: temperature_command_topic, 2729 | KEY_TEMPERATURE_COMMAND_TEMPLATE: TPL_SET_TARGET_TEMPERATURE, 2730 | KEY_TEMP_STEP: climate_entity_option[KEY_TEMP_STEP], 2731 | KEY_MODE_STATE_TOPIC: TOPIC_INFO, 2732 | KEY_MODE_COMMAND_TOPIC: temperature_command_topic, 2733 | KEY_MODE_COMMAND_TEMPLATE: TPL_MODE_SET.format( 2734 | default_heat_temp=default_heat_temp 2735 | ), 2736 | KEY_MODE_STATE_TEMPLATE: TPL_MODE, 2737 | KEY_UNIQUE_ID: f"{dev_id}".lower(), 2738 | KEY_OPTIMISTIC: VALUE_FALSE, 2739 | KEY_QOS: qos, 2740 | KEY_AVAILABILITY: availability, 2741 | KEY_DEVICE: device_info, 2742 | KEY_ORIGIN: origin_info, 2743 | "~": default_topic, 2744 | } 2745 | 2746 | if device_config.get(CONF_HUMIDITY_TOPIC): 2747 | payload[KEY_CURRENT_HUMIDITY_TOPIC] = device_config[CONF_HUMIDITY_TOPIC] 2748 | 2749 | payload.update(climate_entity_option) 2750 | if dev_id.lower() in ignored: 2751 | payload = "" 2752 | 2753 | mqtt_publish(config_topic, payload, retain) 2754 | 2755 | # rollers 2756 | for roller_id in range(rollers): 2757 | if device_config.get(CONF_POSITION_TEMPLATE): 2758 | position_template = device_config[CONF_POSITION_TEMPLATE] 2759 | else: 2760 | position_template = TPL_POSITION 2761 | set_position_template = device_config.get(CONF_SET_POSITION_TEMPLATE, None) 2762 | if device_config.get(f"roller-{roller_id}-name"): 2763 | roller_name = device_config[f"roller-{roller_id}-name"] 2764 | else: 2765 | roller_name = f"Roller {roller_id}" 2766 | device_class = None 2767 | if device_config.get(f"roller-{roller_id}-class"): 2768 | if device_config[f"roller-{roller_id}-class"] in ROLLER_DEVICE_CLASSES: 2769 | device_class = device_config[f"roller-{roller_id}-class"] 2770 | else: 2771 | wrong_class = device_config[f"roller-{roller_id}-class"] 2772 | logger.error( # noqa: F821 2773 | "%s is the wrong roller class, the default value None was used", 2774 | wrong_class, 2775 | ) 2776 | state_topic = f"~roller/{roller_id}" 2777 | config_topic = f"{disc_prefix}/cover/{dev_id}-roller-{roller_id}/config".encode( 2778 | "ascii", "ignore" 2779 | ).decode("utf-8") 2780 | if roller_mode: 2781 | payload = { 2782 | KEY_NAME: roller_name, 2783 | KEY_COMMAND_TOPIC: f"{state_topic}/command", 2784 | KEY_POSITION_TOPIC: f"{state_topic}/pos", 2785 | KEY_STATE_TOPIC: state_topic, 2786 | KEY_STATE_CLOSING: VALUE_CLOSE, 2787 | KEY_STATE_OPENING: VALUE_OPEN, 2788 | KEY_STATE_STOPPED: VALUE_STOP, 2789 | KEY_POSITION_TEMPLATE: position_template, 2790 | KEY_SET_POSITION_TOPIC: f"{state_topic}/command/pos", 2791 | KEY_PAYLOAD_OPEN: VALUE_OPEN, 2792 | KEY_PAYLOAD_CLOSE: VALUE_CLOSE, 2793 | KEY_PAYLOAD_STOP: VALUE_STOP, 2794 | KEY_AVAILABILITY: availability, 2795 | KEY_UNIQUE_ID: f"{dev_id}-roller-{roller_id}".lower(), 2796 | KEY_OPTIMISTIC: str(optimistic).lower(), 2797 | KEY_QOS: qos, 2798 | KEY_DEVICE: device_info, 2799 | KEY_ORIGIN: origin_info, 2800 | "~": default_topic, 2801 | } 2802 | if set_position_template: 2803 | payload[KEY_SET_POSITION_TEMPLATE] = set_position_template 2804 | if device_class: 2805 | payload[KEY_DEVICE_CLASS] = device_class 2806 | else: 2807 | payload = "" 2808 | if dev_id.lower() in ignored: 2809 | payload = "" 2810 | 2811 | mqtt_publish(config_topic, payload, retain) 2812 | 2813 | # relays 2814 | for relay_id in range(relays): 2815 | if device_config.get(f"relay-{relay_id}-name"): 2816 | relay_name = device_config[f"relay-{relay_id}-name"] 2817 | else: 2818 | relay_name = f"Relay {relay_id}" 2819 | state_topic = f"~relay/{relay_id}" 2820 | config_component = COMP_SWITCH 2821 | if device_config.get(f"relay-{relay_id}"): 2822 | config_component = device_config[f"relay-{relay_id}"] 2823 | for component in relay_components: 2824 | config_topic = ( 2825 | f"{disc_prefix}/{component}/{dev_id}-relay-{relay_id}/config".encode( 2826 | "ascii", "ignore" 2827 | ).decode("utf-8") 2828 | ) 2829 | if component == config_component and not roller_mode: 2830 | payload = { 2831 | KEY_NAME: relay_name, 2832 | KEY_COMMAND_TOPIC: f"{state_topic}/command", 2833 | KEY_STATE_TOPIC: state_topic, 2834 | KEY_PAYLOAD_OFF: VALUE_OFF, 2835 | KEY_PAYLOAD_ON: VALUE_ON, 2836 | KEY_AVAILABILITY: availability, 2837 | KEY_UNIQUE_ID: f"{dev_id}-relay-{relay_id}".lower(), 2838 | KEY_QOS: qos, 2839 | KEY_DEVICE: device_info, 2840 | KEY_ORIGIN: origin_info, 2841 | "~": default_topic, 2842 | } 2843 | else: 2844 | payload = "" 2845 | if dev_id.lower() in ignored: 2846 | payload = "" 2847 | 2848 | mqtt_publish(config_topic, payload, retain) 2849 | 2850 | # relay sensors 2851 | for sensor, sensor_options in relay_sensors.items(): 2852 | force_update = False 2853 | if isinstance(device_config.get(CONF_FORCE_UPDATE_SENSORS), bool): 2854 | force_update = device_config.get(CONF_FORCE_UPDATE_SENSORS) 2855 | config_topic = ( 2856 | f"{disc_prefix}/sensor/{dev_id}-{sensor}-{relay_id}/config".encode( 2857 | "ascii", "ignore" 2858 | ).decode("utf-8") 2859 | ) 2860 | 2861 | payload = { 2862 | KEY_NAME: f"{format_entity_name(sensor)} {relay_id}", 2863 | KEY_STATE_TOPIC: sensor_options[KEY_STATE_TOPIC].format(relay_id=relay_id), 2864 | KEY_AVAILABILITY: availability, 2865 | KEY_FORCE_UPDATE: str(force_update).lower(), 2866 | KEY_ENABLED_BY_DEFAULT: str(sensor_options[KEY_ENABLED_BY_DEFAULT]).lower(), 2867 | KEY_UNIQUE_ID: f"{dev_id}-relay-{sensor}-{relay_id}".lower(), 2868 | KEY_QOS: qos, 2869 | KEY_DEVICE: device_info, 2870 | KEY_ORIGIN: origin_info, 2871 | "~": default_topic, 2872 | } 2873 | if sensor_options.get(KEY_SUGGESTED_DISPLAY_PRECISION): 2874 | payload[KEY_SUGGESTED_DISPLAY_PRECISION] = sensor_options[ 2875 | KEY_SUGGESTED_DISPLAY_PRECISION 2876 | ] 2877 | if sensor_options.get(KEY_ENTITY_CATEGORY): 2878 | payload[KEY_ENTITY_CATEGORY] = sensor_options[KEY_ENTITY_CATEGORY] 2879 | if sensor_options.get(KEY_DEVICE_CLASS): 2880 | payload[KEY_DEVICE_CLASS] = sensor_options[KEY_DEVICE_CLASS] 2881 | if sensor_options.get(KEY_STATE_CLASS): 2882 | payload[KEY_STATE_CLASS] = sensor_options[KEY_STATE_CLASS] 2883 | if sensor_options.get(KEY_UNIT): 2884 | payload[KEY_UNIT] = sensor_options[KEY_UNIT] 2885 | if sensor_options.get(KEY_VALUE_TEMPLATE): 2886 | payload[KEY_VALUE_TEMPLATE] = sensor_options[KEY_VALUE_TEMPLATE] 2887 | if sensor_options.get(ATTR_ICON): 2888 | payload[KEY_ICON] = sensor_options[ATTR_ICON] 2889 | if dev_id.lower() in ignored: 2890 | payload = "" 2891 | if roller_mode: 2892 | payload = "" 2893 | 2894 | mqtt_publish(config_topic, payload, retain) 2895 | 2896 | # relay's binary sensors 2897 | for sensor, sensor_options in relay_binary_sensors.items(): 2898 | config_topic = f"{disc_prefix}/binary_sensor/{dev_id}-{make_id(sensor)}-{relay_id}/config".encode( 2899 | "ascii", "ignore" 2900 | ).decode("utf-8") 2901 | if device_config.get(f"relay-{relay_id}-name"): 2902 | sensor_name = f"{device_config[f'relay-{relay_id}-name']} {format_entity_name(sensor)}" 2903 | else: 2904 | sensor_name = f"{format_entity_name(sensor)} {relay_id}" 2905 | if not roller_mode: 2906 | payload = { 2907 | KEY_NAME: sensor_name, 2908 | KEY_STATE_TOPIC: sensor_options[KEY_STATE_TOPIC].format( 2909 | relay_id=relay_id 2910 | ), 2911 | KEY_ENABLED_BY_DEFAULT: str( 2912 | sensor_options[KEY_ENABLED_BY_DEFAULT] 2913 | ).lower(), 2914 | KEY_AVAILABILITY: availability, 2915 | KEY_UNIQUE_ID: f"{dev_id}-{make_id(sensor)}-{relay_id}".lower(), 2916 | KEY_QOS: qos, 2917 | KEY_DEVICE: device_info, 2918 | KEY_ORIGIN: origin_info, 2919 | "~": default_topic, 2920 | } 2921 | if sensor_options.get(KEY_ENTITY_CATEGORY): 2922 | payload[KEY_ENTITY_CATEGORY] = sensor_options[KEY_ENTITY_CATEGORY] 2923 | if sensor_options.get(KEY_VALUE_TEMPLATE): 2924 | payload[KEY_VALUE_TEMPLATE] = sensor_options[KEY_VALUE_TEMPLATE] 2925 | else: 2926 | payload[KEY_PAYLOAD_ON] = sensor_options[KEY_PAYLOAD_ON] 2927 | payload[KEY_PAYLOAD_OFF] = sensor_options[KEY_PAYLOAD_OFF] 2928 | if sensor_options.get(KEY_DEVICE_CLASS): 2929 | payload[KEY_DEVICE_CLASS] = sensor_options[KEY_DEVICE_CLASS] 2930 | if ( 2931 | model 2932 | in ( 2933 | MODEL_SHELLY1PM, 2934 | MODEL_SHELLY2, 2935 | MODEL_SHELLY25, 2936 | MODEL_SHELLY4PRO, 2937 | MODEL_SHELLYPLUG, 2938 | MODEL_SHELLYPLUG_S, 2939 | MODEL_SHELLYPLUG_US, 2940 | ) 2941 | and sensor == SENSOR_OVERPOWER 2942 | ): 2943 | payload[KEY_JSON_ATTRIBUTES_TOPIC] = ( 2944 | f"~{sensor}/{relay_id}/{TOPIC_OVERPOWER_VALUE}" 2945 | ) 2946 | payload[KEY_JSON_ATTRIBUTES_TEMPLATE] = TPL_OVERPOWER_VALUE_TO_JSON 2947 | else: 2948 | payload = "" 2949 | if dev_id.lower() in ignored: 2950 | payload = "" 2951 | if not sensor_options: 2952 | payload = "" 2953 | 2954 | mqtt_publish(config_topic, payload, retain) 2955 | 2956 | # sensors 2957 | for sensor, sensor_options in sensors.items(): 2958 | use_fahrenheit = device_config.get(CONF_USE_FAHRENHEIT) 2959 | force_update = False 2960 | if isinstance(device_config.get(CONF_FORCE_UPDATE_SENSORS), bool): 2961 | force_update = device_config.get(CONF_FORCE_UPDATE_SENSORS) 2962 | config_topic = f"{disc_prefix}/sensor/{dev_id}-{sensor}/config".encode( 2963 | "ascii", "ignore" 2964 | ).decode("utf-8") 2965 | if model in (MODEL_SHELLY2, MODEL_SHELLY25) and sensor in ( 2966 | SENSOR_ENERGY, 2967 | SENSOR_POWER, 2968 | ): 2969 | unique_id = f"{dev_id}-relay-{sensor}".lower() 2970 | else: 2971 | unique_id = f"{dev_id}-{sensor}".lower() 2972 | if sensor in (SENSOR_SSID, SENSOR_ADC): 2973 | sensor_name = sensor.upper() 2974 | elif sensor == SENSOR_IP: 2975 | sensor_name = "IP address" 2976 | elif sensor == SENSOR_IX_SUM_CURRENT: 2977 | sensor_name = "IX sum current" 2978 | elif sensor == SENSOR_UPTIME: 2979 | sensor_name = "Last restart" 2980 | elif sensor == SENSOR_RSSI: 2981 | sensor_name = "WiFi signal" 2982 | elif sensor == SENSOR_TEMPERATURE_F: 2983 | sensor_name = "Temperature" 2984 | else: 2985 | sensor_name = format_entity_name(sensor) 2986 | 2987 | payload = { 2988 | KEY_NAME: sensor_name, 2989 | KEY_STATE_TOPIC: sensor_options[KEY_STATE_TOPIC], 2990 | KEY_FORCE_UPDATE: str(force_update).lower(), 2991 | KEY_ENABLED_BY_DEFAULT: str(sensor_options[KEY_ENABLED_BY_DEFAULT]).lower(), 2992 | KEY_UNIQUE_ID: unique_id, 2993 | KEY_QOS: qos, 2994 | KEY_DEVICE: device_info, 2995 | KEY_ORIGIN: origin_info, 2996 | "~": default_topic, 2997 | } 2998 | if sensor_options.get(KEY_SUGGESTED_DISPLAY_PRECISION): 2999 | payload[KEY_SUGGESTED_DISPLAY_PRECISION] = sensor_options[ 3000 | KEY_SUGGESTED_DISPLAY_PRECISION 3001 | ] 3002 | if sensor_options.get(KEY_ENTITY_CATEGORY): 3003 | payload[KEY_ENTITY_CATEGORY] = sensor_options[KEY_ENTITY_CATEGORY] 3004 | if sensor_options.get(KEY_DEVICE_CLASS): 3005 | payload[KEY_DEVICE_CLASS] = sensor_options[KEY_DEVICE_CLASS] 3006 | if sensor_options.get(KEY_STATE_CLASS): 3007 | payload[KEY_STATE_CLASS] = sensor_options[KEY_STATE_CLASS] 3008 | if model == MODEL_SHELLYDW2 and sensor == SENSOR_LUX: 3009 | payload[KEY_JSON_ATTRIBUTES_TOPIC] = f"~sensor/{SENSOR_ILLUMINATION}" 3010 | payload[KEY_JSON_ATTRIBUTES_TEMPLATE] = TPL_ILLUMINATION_TO_JSON 3011 | if sensor_options.get(KEY_UNIT): 3012 | payload[KEY_UNIT] = sensor_options[KEY_UNIT] 3013 | if sensor_options.get(KEY_VALUE_TEMPLATE): 3014 | payload[KEY_VALUE_TEMPLATE] = sensor_options[KEY_VALUE_TEMPLATE] 3015 | if sensor_options.get(ATTR_ICON): 3016 | payload[KEY_ICON] = sensor_options[ATTR_ICON] 3017 | if battery_powered and model not in (MODEL_SHELLYDW, MODEL_SHELLYDW2): 3018 | payload[KEY_EXPIRE_AFTER] = expire_after 3019 | elif not battery_powered: 3020 | payload[KEY_AVAILABILITY] = availability 3021 | if no_battery_sensor and sensor == SENSOR_BATTERY: 3022 | payload = "" 3023 | if use_fahrenheit and sensor == SENSOR_TEMPERATURE: 3024 | payload = "" 3025 | if not use_fahrenheit and sensor == SENSOR_TEMPERATURE_F: 3026 | payload = "" 3027 | if ( 3028 | model == MODEL_SHELLY25 3029 | and sensor in (SENSOR_ENERGY, SENSOR_POWER) 3030 | and not roller_mode 3031 | ): 3032 | payload = "" 3033 | if sensor == SENSOR_VALVE and not device_config.get(CONF_VALVE_CONNECTED, False): 3034 | payload = "" 3035 | if dev_id.lower() in ignored: 3036 | payload = "" 3037 | 3038 | mqtt_publish(config_topic, payload, retain) 3039 | 3040 | # inputs 3041 | for input_id in range(inputs): 3042 | config_topic = f"{disc_prefix}/device_automation/{dev_id}-input-{input_id}/button_release/config".encode( 3043 | "ascii", "ignore" 3044 | ).decode("utf-8") 3045 | topic = f"shellies/{dev_id}/input/{input_id}" 3046 | payload = { 3047 | KEY_AUTOMATION_TYPE: VALUE_TRIGGER, 3048 | KEY_TOPIC: topic, 3049 | KEY_PAYLOAD: "0", 3050 | KEY_QOS: qos, 3051 | KEY_DEVICE: device_info, 3052 | KEY_TYPE: VALUE_BUTTON_SHORT_RELEASE, 3053 | KEY_SUBTYPE: f"button_{input_id + 1}", 3054 | } 3055 | if dev_id.lower() in ignored: 3056 | payload = "" 3057 | 3058 | mqtt_publish(config_topic, payload, retain) 3059 | 3060 | topic = f"shellies/{dev_id}/input_event/{input_id}" 3061 | for event in inputs_types: 3062 | config_topic = f"{disc_prefix}/device_automation/{dev_id}-input-{input_id}/{event}/config".encode( 3063 | "ascii", "ignore" 3064 | ).decode("utf-8") 3065 | payload = { 3066 | KEY_AUTOMATION_TYPE: VALUE_TRIGGER, 3067 | KEY_TOPIC: topic, 3068 | KEY_PAYLOAD: DEVICE_TRIGGERS_MAP[event], 3069 | KEY_VALUE_TEMPLATE: "{{value_json.event}}", 3070 | KEY_QOS: qos, 3071 | KEY_DEVICE: device_info, 3072 | KEY_TYPE: event, 3073 | KEY_SUBTYPE: f"button_{input_id + 1}", 3074 | } 3075 | if dev_id.lower() in ignored: 3076 | payload = "" 3077 | 3078 | mqtt_publish(config_topic, payload, retain) 3079 | 3080 | # events 3081 | config_topic = f"{disc_prefix}/event/{dev_id}-input-{input_id}/config".encode( 3082 | "ascii", "ignore" 3083 | ).decode("utf-8") 3084 | unique_id = f"{dev_id}-input-{input_id}".lower() 3085 | 3086 | payload = { 3087 | KEY_NAME: f"Button {input_id}", 3088 | KEY_STATE_TOPIC: f"~input_event/{input_id}", 3089 | KEY_EVENT_TYPES: list(DEVICE_TRIGGERS_MAP.values()), 3090 | KEY_VALUE_TEMPLATE: TPL_EVENT, 3091 | KEY_DEVICE_CLASS: DEVICE_CLASS_BUTTON, 3092 | KEY_AVAILABILITY: availability, 3093 | KEY_UNIQUE_ID: unique_id, 3094 | KEY_QOS: qos, 3095 | KEY_DEVICE: device_info, 3096 | KEY_ORIGIN: origin_info, 3097 | "~": default_topic, 3098 | } 3099 | 3100 | if dev_id.lower() in ignored: 3101 | payload = "" 3102 | 3103 | mqtt_publish(config_topic, payload, retain) 3104 | 3105 | # external temperature sensors 3106 | for sensor_id in range(ext_temp_sensors): 3107 | force_update = False 3108 | if isinstance(device_config.get(CONF_FORCE_UPDATE_SENSORS), bool): 3109 | force_update = device_config.get(CONF_FORCE_UPDATE_SENSORS) 3110 | unique_id = f"{dev_id}-ext-temperature-{sensor_id}".lower() 3111 | config_topic = ( 3112 | f"{disc_prefix}/sensor/{dev_id}-ext-temperature-{sensor_id}/config".encode( 3113 | "ascii", "ignore" 3114 | ).decode("utf-8") 3115 | ) 3116 | sensor_name = f"External temperature {sensor_id}" 3117 | state_topic = f"~{SENSOR_EXT_TEMPERATURE}/{sensor_id}" 3118 | if device_config.get(f"ext-temperature-{sensor_id}"): 3119 | payload = { 3120 | KEY_NAME: sensor_name, 3121 | KEY_STATE_TOPIC: state_topic, 3122 | KEY_VALUE_TEMPLATE: TPL_TEMPERATURE_EXT, 3123 | KEY_SUGGESTED_DISPLAY_PRECISION: 1, 3124 | KEY_STATE_CLASS: STATE_CLASS_MEASUREMENT, 3125 | KEY_UNIT: UNIT_CELSIUS, 3126 | KEY_DEVICE_CLASS: SENSOR_TEMPERATURE, 3127 | KEY_FORCE_UPDATE: str(force_update).lower(), 3128 | KEY_AVAILABILITY: availability, 3129 | KEY_UNIQUE_ID: unique_id, 3130 | KEY_QOS: qos, 3131 | KEY_DEVICE: device_info, 3132 | KEY_ORIGIN: origin_info, 3133 | "~": default_topic, 3134 | } 3135 | else: 3136 | payload = "" 3137 | if dev_id.lower() in ignored: 3138 | payload = "" 3139 | 3140 | mqtt_publish(config_topic, payload, retain) 3141 | 3142 | # external humidity sensors 3143 | for sensor_id in range(ext_humi_sensors): 3144 | force_update = False 3145 | if isinstance(device_config.get(CONF_FORCE_UPDATE_SENSORS), bool): 3146 | force_update = device_config.get(CONF_FORCE_UPDATE_SENSORS) 3147 | unique_id = f"{dev_id}-ext-humidity-{sensor_id}".lower() 3148 | config_topic = ( 3149 | f"{disc_prefix}/sensor/{dev_id}-ext-humidity-{sensor_id}/config".encode( 3150 | "ascii", "ignore" 3151 | ).decode("utf-8") 3152 | ) 3153 | sensor_name = f"External humidity {sensor_id}" 3154 | state_topic = f"~{SENSOR_EXT_HUMIDITY}/{sensor_id}" 3155 | if device_config.get(f"ext-temperature-{sensor_id}"): 3156 | payload = { 3157 | KEY_NAME: sensor_name, 3158 | KEY_STATE_TOPIC: state_topic, 3159 | KEY_VALUE_TEMPLATE: TPL_HUMIDITY_EXT, 3160 | KEY_SUGGESTED_DISPLAY_PRECISION: 1, 3161 | KEY_STATE_CLASS: STATE_CLASS_MEASUREMENT, 3162 | KEY_UNIT: UNIT_PERCENT, 3163 | KEY_DEVICE_CLASS: SENSOR_HUMIDITY, 3164 | KEY_FORCE_UPDATE: str(force_update).lower(), 3165 | KEY_AVAILABILITY: availability, 3166 | KEY_UNIQUE_ID: unique_id, 3167 | KEY_QOS: qos, 3168 | KEY_DEVICE: device_info, 3169 | KEY_ORIGIN: origin_info, 3170 | "~": default_topic, 3171 | } 3172 | else: 3173 | payload = "" 3174 | if dev_id.lower() in ignored: 3175 | payload = "" 3176 | 3177 | mqtt_publish(config_topic, payload, retain) 3178 | 3179 | # binary sensors 3180 | for sensor, sensor_options in binary_sensors.items(): 3181 | config_topic = ( 3182 | f"{disc_prefix}/binary_sensor/{dev_id}-{make_id(sensor)}/config".encode( 3183 | "ascii", "ignore" 3184 | ).decode("utf-8") 3185 | ) 3186 | if sensor == SENSOR_EXT_SWITCH: 3187 | sensor_name = "External switch" 3188 | else: 3189 | sensor_name = format_entity_name(sensor) 3190 | state_topic = sensor_options[KEY_STATE_TOPIC] 3191 | payload = { 3192 | KEY_NAME: sensor_name, 3193 | KEY_STATE_TOPIC: state_topic, 3194 | KEY_ENABLED_BY_DEFAULT: str(sensor_options[KEY_ENABLED_BY_DEFAULT]).lower(), 3195 | KEY_UNIQUE_ID: f"{dev_id}-{make_id(sensor)}".lower(), 3196 | KEY_QOS: qos, 3197 | KEY_DEVICE: device_info, 3198 | KEY_ORIGIN: origin_info, 3199 | "~": default_topic, 3200 | } 3201 | if sensor_options.get(KEY_ICON): 3202 | payload[KEY_ICON] = sensor_options[KEY_ICON] 3203 | if sensor_options.get(KEY_ENTITY_CATEGORY): 3204 | payload[KEY_ENTITY_CATEGORY] = sensor_options[KEY_ENTITY_CATEGORY] 3205 | if sensor_options.get(KEY_VALUE_TEMPLATE): 3206 | payload[KEY_VALUE_TEMPLATE] = sensor_options[KEY_VALUE_TEMPLATE] 3207 | else: 3208 | payload[KEY_PAYLOAD_ON] = sensor_options.get(KEY_PAYLOAD_ON) 3209 | payload[KEY_PAYLOAD_OFF] = sensor_options.get(KEY_PAYLOAD_OFF) 3210 | if battery_powered and model not in (MODEL_SHELLYDW, MODEL_SHELLYDW2): 3211 | payload[KEY_EXPIRE_AFTER] = expire_after 3212 | elif not battery_powered: 3213 | payload[KEY_AVAILABILITY] = availability 3214 | if sensor_options.get(KEY_DEVICE_CLASS): 3215 | payload[KEY_DEVICE_CLASS] = sensor_options[KEY_DEVICE_CLASS] 3216 | if ( 3217 | model == MODEL_SHELLYRGBW2 3218 | and mode == LIGHT_WHITE 3219 | and sensor == SENSOR_OVERPOWER 3220 | ): 3221 | payload = "" 3222 | if model in (MODEL_SHELLYDW, MODEL_SHELLYDW2) and sensor == SENSOR_OPENING: 3223 | payload[KEY_FORCE_UPDATE] = VALUE_TRUE 3224 | if model == MODEL_SHELLYGAS and sensor == SENSOR_GAS: 3225 | payload[KEY_JSON_ATTRIBUTES_TOPIC] = state_topic 3226 | payload[KEY_JSON_ATTRIBUTES_TEMPLATE] = TPL_GAS_TO_JSON 3227 | if ( 3228 | model == MODEL_SHELLY1 3229 | and sensor == SENSOR_EXT_SWITCH 3230 | and not device_config.get(CONF_EXT_SWITCH) 3231 | ): 3232 | payload = "" 3233 | if ( 3234 | model == MODEL_SHELLYHT 3235 | and sensor == SENSOR_CLOUD 3236 | and device_config.get(CONF_POWERED) != ATTR_POWER_AC 3237 | ): 3238 | payload = "" 3239 | if dev_id.lower() in ignored: 3240 | payload = "" 3241 | 3242 | mqtt_publish(config_topic, payload, retain) 3243 | 3244 | # color lights 3245 | for light_id in range(rgbw_lights): 3246 | if device_config.get(f"light-{light_id}-name"): 3247 | light_name = device_config[f"light-{light_id}-name"] 3248 | else: 3249 | light_name = f"Light {light_id}" 3250 | state_topic = f"~color/{light_id}" 3251 | status_topic = f"~color/{light_id}/status" 3252 | set_topic = f"~color/{light_id}/set" 3253 | command_topic = f"~color/{light_id}/command" 3254 | unique_id = f"{dev_id}-light-{light_id}".lower() 3255 | config_topic = f"{disc_prefix}/light/{dev_id}-{light_id}/config".encode( 3256 | "ascii", "ignore" 3257 | ).decode("utf-8") 3258 | if mode == LIGHT_COLOR and model == MODEL_SHELLYRGBW2: 3259 | payload = { 3260 | KEY_NAME: light_name, 3261 | KEY_AVAILABILITY: availability, 3262 | KEY_COMMAND_TOPIC: command_topic, 3263 | KEY_STATE_TOPIC: state_topic, 3264 | KEY_STATE_VALUE_TEMPLATE: "{{value.lower()}}", 3265 | KEY_PAYLOAD_ON: VALUE_ON, 3266 | KEY_PAYLOAD_OFF: VALUE_OFF, 3267 | KEY_RGBW_COMMAND_TOPIC: set_topic, 3268 | KEY_RGBW_COMMAND_TEMPLATE: "{^red^:{{red}},^green^:{{green}},^blue^:{{blue}},^white^:{{white}}}", 3269 | KEY_RGBW_STATE_TOPIC: status_topic, 3270 | KEY_RGBW_VALUE_TEMPLATE: "{{value_json.red}},{{value_json.green}},{{value_json.blue}},{{value_json.white}}", 3271 | KEY_BRIGHTNESS_STATE_TOPIC: status_topic, 3272 | KEY_BRIGHTNESS_VALUE_TEMPLATE: "{{value_json.gain|float|multiply(2.55)|round(0)}}", 3273 | KEY_BRIGHTNESS_COMMAND_TOPIC: set_topic, 3274 | KEY_BRIGHTNESS_COMMAND_TEMPLATE: "{^gain^:{{value|float|multiply(0.3922)|round(0)}}}", 3275 | KEY_EFFECT_COMMAND_TOPIC: set_topic, 3276 | KEY_EFFECT_COMMAND_TEMPLATE: "{ {%if value==^Off^%}^effect^:0{%elif value==^Meteor Shower^%}^effect^:1{%elif value==^Gradual Change^%}^effect^:2{%elif value==^Flash^%}^effect^:3{%endif%} }", 3277 | KEY_EFFECT_LIST: ["Off", "Meteor Shower", "Gradual Change", "Flash"], 3278 | KEY_EFFECT_STATE_TOPIC: status_topic, 3279 | KEY_EFFECT_VALUE_TEMPLATE: "{%if value_json.effect==1%}Meteor Shower{%elif value_json.effect==2%}Gradual Change{%elif value_json.effect==3%}Flash{%else%}Off{%endif%}", 3280 | KEY_UNIQUE_ID: unique_id, 3281 | KEY_QOS: qos, 3282 | KEY_DEVICE: device_info, 3283 | KEY_ORIGIN: origin_info, 3284 | "~": default_topic, 3285 | } 3286 | elif model == MODEL_SHELLYDUORGBW: 3287 | payload = { 3288 | KEY_SCHEMA: VALUE_TEMPLATE, 3289 | KEY_NAME: light_name, 3290 | KEY_AVAILABILITY: availability, 3291 | KEY_STATE_TEMPLATE: "{%if value_json.ison%}on{%else%}off{%endif%}", 3292 | KEY_BRIGHTNESS_STATE_TOPIC: status_topic, 3293 | KEY_BRIGHTNESS_TEMPLATE: "{%if value_json.mode==^color^%}{{value_json.gain|float|multiply(2.55)|round(0)}}{%else%}{{value_json.brightness|float|multiply(2.55)|round(0)}}{%endif%}", 3294 | KEY_BRIGHTNESS_COMMAND_TOPIC: set_topic, 3295 | KEY_BRIGHTNESS_COMMAND_TEMPLATE: "{^gain^:{{value|float|multiply(0.3922)|round(0)}},^brightness^:{{value|float|multiply(0.3922)|round(0)}}}", 3296 | KEY_EFFECT_COMMAND_TOPIC: set_topic, 3297 | KEY_EFFECT_COMMAND_TEMPLATE: "{{%if value==^Off^%}^effect^:0{%elif value==^Meteor Shower^%}^effect^:1{%elif value==^Gradual Change^%}^effect^:2{%elif value==^Flash^%}^effect^:3{%endif%}}", 3298 | KEY_EFFECT_LIST: ["Off", "Meteor Shower", "Gradual Change", "Flash"], 3299 | KEY_EFFECT_STATE_TOPIC: status_topic, 3300 | KEY_EFFECT_TEMPLATE: "{%if value_json.effect==1%}Meteor Shower{%elif value_json.effect==2%}Gradual Change{%elif value_json.effect==3%}Flash{%else%}Off{%endif%}", 3301 | KEY_UNIQUE_ID: unique_id, 3302 | KEY_QOS: qos, 3303 | KEY_DEVICE: device_info, 3304 | KEY_ORIGIN: origin_info, 3305 | KEY_COLOR_TEMP_TEMPLATE: "{{(1000000/(value_json.temp|int))|round(0,^floor^)}}", 3306 | KEY_COMMAND_ON_TEMPLATE: f"{{^turn^:^on^{{%if red is defined and green is defined and blue is defined%}},^mode^:^color^,^red^:{{{{red}}}},^green^:{{{{green}}}},^blue^:{{{{blue}}}}{{%endif%}}{{%if brightness is defined%}},^brightness^:{{{{brightness|float|multiply(0.3922)|round}}}},^gain^:{{{{brightness|float|multiply(0.3922)|round}}}}{{%endif%}}{{%if color_temp is defined%}},^mode^:^white^,^temp^:{{{{(1000000/(color_temp|int))|round(0,^floor^)}}}}{{%endif%}}{{%if transition is defined%}},^transition^:{{{{min(transition|multiply(1000),{MAX_TRANSITION})}}}}{{%endif%}}{{%if effect is defined%}},^effect^:{{%if effect==^Meteor Shower^%}}1{{%elif effect==^Gradual Change^%}}2{{%elif effect==^Flash^%}}3{{%else%}}0{{%endif%}}{{%else%}},^effect^:0{{%endif%}}}}", 3307 | KEY_COMMAND_OFF_TEMPLATE: f"{{^turn^:^off^,^effect^:0{{%if transition is defined%}},^transition^:{{{{min(transition|multiply(1000),{MAX_TRANSITION})}}}}{{%endif%}}}}", 3308 | KEY_COMMAND_TOPIC: set_topic, 3309 | KEY_MAX_MIREDS: 333, 3310 | KEY_MIN_MIREDS: 154, 3311 | KEY_STATE_TOPIC: status_topic, 3312 | KEY_RED_TEMPLATE: "{%if value_json.mode==^color^%}{{value_json.red}}{%else%}{{none}}{%endif%}", 3313 | KEY_GREEN_TEMPLATE: "{%if value_json.mode==^color^%}{{value_json.green}}{%else%}{{none}}{%endif%}", 3314 | KEY_BLUE_TEMPLATE: "{%if value_json.mode==^color^%}{{value_json.blue}}{%else%}{{none}}{%endif%}", 3315 | "~": default_topic, 3316 | } 3317 | else: 3318 | payload = "" 3319 | if dev_id.lower() in ignored: 3320 | payload = "" 3321 | 3322 | mqtt_publish(config_topic, payload, retain, json=True) 3323 | 3324 | # color light's binary sensors 3325 | for sensor, sensor_options in light_binary_sensors.items(): 3326 | config_topic = f"{disc_prefix}/binary_sensor/{dev_id}-color-{sensor}-{light_id}/config".encode( 3327 | "ascii", "ignore" 3328 | ).decode("utf-8") 3329 | if mode == LIGHT_COLOR: 3330 | payload = { 3331 | KEY_NAME: f"{format_entity_name(sensor)} {light_id}", 3332 | KEY_STATE_TOPIC: sensor_options[KEY_STATE_TOPIC], 3333 | KEY_AVAILABILITY: availability, 3334 | KEY_UNIQUE_ID: f"{dev_id}-color-{sensor}-{light_id}".lower(), 3335 | KEY_QOS: qos, 3336 | KEY_DEVICE: device_info, 3337 | KEY_ORIGIN: origin_info, 3338 | "~": default_topic, 3339 | } 3340 | if sensor_options.get(KEY_ENTITY_CATEGORY): 3341 | payload[KEY_ENTITY_CATEGORY] = sensor_options[KEY_ENTITY_CATEGORY] 3342 | if sensor_options.get(KEY_DEVICE_CLASS): 3343 | payload[KEY_DEVICE_CLASS] = sensor_options[KEY_DEVICE_CLASS] 3344 | if sensor_options.get(KEY_VALUE_TEMPLATE): 3345 | payload[KEY_VALUE_TEMPLATE] = sensor_options[KEY_VALUE_TEMPLATE] 3346 | else: 3347 | payload[KEY_PAYLOAD_ON] = sensor_options[KEY_PAYLOAD_ON] 3348 | payload[KEY_PAYLOAD_OFF] = sensor_options[KEY_PAYLOAD_OFF] 3349 | else: 3350 | payload = "" 3351 | if dev_id.lower() in ignored: 3352 | payload = "" 3353 | 3354 | mqtt_publish(config_topic, payload, retain) 3355 | 3356 | # color light sensors 3357 | for sensor, sensor_options in light_sensors.items(): 3358 | force_update = False 3359 | if isinstance(device_config.get(CONF_FORCE_UPDATE_SENSORS), bool): 3360 | force_update = device_config.get(CONF_FORCE_UPDATE_SENSORS) 3361 | config_topic = ( 3362 | f"{disc_prefix}/sensor/{dev_id}-color-{sensor}-{light_id}/config".encode( 3363 | "ascii", "ignore" 3364 | ).decode("utf-8") 3365 | ) 3366 | 3367 | payload = { 3368 | KEY_NAME: f"{format_entity_name(sensor)} {light_id}", 3369 | KEY_STATE_TOPIC: sensor_options[KEY_STATE_TOPIC].format(light_id=light_id), 3370 | KEY_AVAILABILITY: availability, 3371 | KEY_FORCE_UPDATE: str(force_update).lower(), 3372 | KEY_ENABLED_BY_DEFAULT: str(sensor_options[KEY_ENABLED_BY_DEFAULT]).lower(), 3373 | KEY_UNIQUE_ID: f"{dev_id}-color-{sensor}-{light_id}".lower(), 3374 | KEY_QOS: qos, 3375 | KEY_DEVICE: device_info, 3376 | KEY_ORIGIN: origin_info, 3377 | "~": default_topic, 3378 | } 3379 | if sensor_options.get(KEY_SUGGESTED_DISPLAY_PRECISION): 3380 | payload[KEY_SUGGESTED_DISPLAY_PRECISION] = sensor_options[ 3381 | KEY_SUGGESTED_DISPLAY_PRECISION 3382 | ] 3383 | if sensor_options.get(KEY_ENTITY_CATEGORY): 3384 | payload[KEY_ENTITY_CATEGORY] = sensor_options[KEY_ENTITY_CATEGORY] 3385 | if sensor_options.get(KEY_DEVICE_CLASS): 3386 | payload[KEY_DEVICE_CLASS] = sensor_options[KEY_DEVICE_CLASS] 3387 | if sensor_options.get(KEY_STATE_CLASS): 3388 | payload[KEY_STATE_CLASS] = sensor_options[KEY_STATE_CLASS] 3389 | if sensor_options.get(KEY_UNIT): 3390 | payload[KEY_UNIT] = sensor_options[KEY_UNIT] 3391 | if sensor_options.get(KEY_VALUE_TEMPLATE): 3392 | payload[KEY_VALUE_TEMPLATE] = sensor_options[KEY_VALUE_TEMPLATE] 3393 | if sensor_options.get(ATTR_ICON): 3394 | payload[KEY_ICON] = sensor_options[ATTR_ICON] 3395 | if dev_id.lower() in ignored: 3396 | payload = "" 3397 | if mode == LIGHT_WHITE: 3398 | payload = "" 3399 | 3400 | mqtt_publish(config_topic, payload, retain) 3401 | 3402 | # white lights 3403 | for light_id, light_options in white_lights.items(): 3404 | if device_config.get(f"light-{light_id}-name"): 3405 | light_name = device_config[f"light-{light_id}-name"] 3406 | else: 3407 | light_name = f"Light {light_id}" 3408 | 3409 | if model == MODEL_SHELLYRGBW2: 3410 | unique_id = f"{dev_id}-light-white-{light_id}".lower() 3411 | config_topic = f"{disc_prefix}/light/{dev_id}-white-{light_id}/config".encode( 3412 | "ascii", "ignore" 3413 | ).decode("utf-8") 3414 | else: 3415 | unique_id = f"{dev_id}-light-{light_id}".lower() 3416 | config_topic = f"{disc_prefix}/light/{dev_id}-{light_id}/config".encode( 3417 | "ascii", "ignore" 3418 | ).decode("utf-8") 3419 | 3420 | payload = { 3421 | KEY_SCHEMA: VALUE_TEMPLATE, 3422 | KEY_NAME: light_name, 3423 | KEY_COMMAND_TOPIC: light_options[KEY_COMMAND_TOPIC].format(light_id=light_id), 3424 | KEY_STATE_TOPIC: light_options[KEY_STATE_TOPIC].format(light_id=light_id), 3425 | KEY_AVAILABILITY: availability, 3426 | KEY_COMMAND_ON_TEMPLATE: light_options[KEY_COMMAND_ON_TEMPLATE].format( 3427 | max_transition=MAX_TRANSITION 3428 | ), 3429 | KEY_COMMAND_OFF_TEMPLATE: f"{{^turn^:^off^{{%if transition is defined%}},^transition^:{{{{min(transition|multiply(1000),{MAX_TRANSITION})}}}}{{%endif%}}}}", 3430 | KEY_STATE_TEMPLATE: "{%if value_json.ison%}on{%else%}off{%endif%}", 3431 | KEY_BRIGHTNESS_TEMPLATE: "{{value_json.brightness|float|multiply(2.55)|round}}", 3432 | KEY_UNIQUE_ID: unique_id, 3433 | KEY_QOS: str(qos), 3434 | KEY_DEVICE: device_info, 3435 | KEY_ORIGIN: origin_info, 3436 | "~": default_topic, 3437 | } 3438 | 3439 | if light_options.get(KEY_COLOR_TEMP_TEMPLATE): 3440 | payload[KEY_COLOR_TEMP_TEMPLATE] = TPL_COLOR_TEMP_WHITE_LIGHT 3441 | if light_options.get(KEY_MAX_MIREDS): 3442 | payload[KEY_MAX_MIREDS] = light_options[KEY_MAX_MIREDS] 3443 | if light_options.get(KEY_MIN_MIREDS): 3444 | payload[KEY_MIN_MIREDS] = light_options[KEY_MIN_MIREDS] 3445 | 3446 | if model == MODEL_SHELLYRGBW2 and mode == LIGHT_COLOR: 3447 | payload = "" 3448 | if dev_id.lower() in ignored: 3449 | payload = "" 3450 | 3451 | mqtt_publish(config_topic, payload, retain, json=True) 3452 | 3453 | # white light's binary sensors 3454 | for sensor, sensor_options in light_binary_sensors.items(): 3455 | config_topic = f"{disc_prefix}/binary_sensor/{dev_id}-white-{sensor}-{light_id}/config".encode( 3456 | "ascii", "ignore" 3457 | ).decode("utf-8") 3458 | if mode != LIGHT_COLOR: 3459 | payload = { 3460 | KEY_NAME: f"{format_entity_name(sensor)} {light_id}", 3461 | KEY_STATE_TOPIC: sensor_options[KEY_STATE_TOPIC].format( 3462 | light_id=light_id 3463 | ), 3464 | KEY_AVAILABILITY: availability, 3465 | KEY_UNIQUE_ID: f"{dev_id}-white-{sensor}-{light_id}".lower(), 3466 | KEY_QOS: qos, 3467 | KEY_DEVICE: device_info, 3468 | KEY_ORIGIN: origin_info, 3469 | "~": default_topic, 3470 | } 3471 | if sensor_options.get(KEY_ENTITY_CATEGORY): 3472 | payload[KEY_ENTITY_CATEGORY] = sensor_options[KEY_ENTITY_CATEGORY] 3473 | if sensor_options.get(KEY_DEVICE_CLASS): 3474 | payload[KEY_DEVICE_CLASS] = sensor_options[KEY_DEVICE_CLASS] 3475 | if sensor_options.get(KEY_VALUE_TEMPLATE): 3476 | payload[KEY_VALUE_TEMPLATE] = sensor_options[KEY_VALUE_TEMPLATE] 3477 | else: 3478 | payload[KEY_PAYLOAD_ON] = sensor_options[KEY_PAYLOAD_ON] 3479 | payload[KEY_PAYLOAD_OFF] = sensor_options[KEY_PAYLOAD_OFF] 3480 | else: 3481 | payload = "" 3482 | if dev_id.lower() in ignored: 3483 | payload = "" 3484 | 3485 | mqtt_publish(config_topic, payload, retain) 3486 | 3487 | # white light sensors 3488 | for sensor, sensor_options in light_sensors.items(): 3489 | force_update = False 3490 | if isinstance(device_config.get(CONF_FORCE_UPDATE_SENSORS), bool): 3491 | force_update = device_config.get(CONF_FORCE_UPDATE_SENSORS) 3492 | config_topic = ( 3493 | f"{disc_prefix}/sensor/{dev_id}-white-{sensor}-{light_id}/config".encode( 3494 | "ascii", "ignore" 3495 | ).decode("utf-8") 3496 | ) 3497 | 3498 | payload = { 3499 | KEY_NAME: f"{format_entity_name(sensor)} {light_id}", 3500 | KEY_STATE_TOPIC: sensor_options[KEY_STATE_TOPIC].format(light_id=light_id), 3501 | KEY_AVAILABILITY: availability, 3502 | KEY_FORCE_UPDATE: str(force_update).lower(), 3503 | KEY_ENABLED_BY_DEFAULT: str(sensor_options[KEY_ENABLED_BY_DEFAULT]).lower(), 3504 | KEY_UNIQUE_ID: f"{dev_id}-white-{sensor}-{light_id}".lower(), 3505 | KEY_QOS: qos, 3506 | KEY_DEVICE: device_info, 3507 | KEY_ORIGIN: origin_info, 3508 | "~": default_topic, 3509 | } 3510 | if sensor_options.get(KEY_SUGGESTED_DISPLAY_PRECISION): 3511 | payload[KEY_SUGGESTED_DISPLAY_PRECISION] = sensor_options[ 3512 | KEY_SUGGESTED_DISPLAY_PRECISION 3513 | ] 3514 | if sensor_options.get(KEY_ENTITY_CATEGORY): 3515 | payload[KEY_ENTITY_CATEGORY] = sensor_options[KEY_ENTITY_CATEGORY] 3516 | if sensor_options.get(KEY_DEVICE_CLASS): 3517 | payload[KEY_DEVICE_CLASS] = sensor_options[KEY_DEVICE_CLASS] 3518 | if sensor_options.get(KEY_STATE_CLASS): 3519 | payload[KEY_STATE_CLASS] = sensor_options[KEY_STATE_CLASS] 3520 | if sensor_options.get(KEY_UNIT): 3521 | payload[KEY_UNIT] = sensor_options[KEY_UNIT] 3522 | if sensor_options.get(KEY_VALUE_TEMPLATE): 3523 | payload[KEY_VALUE_TEMPLATE] = sensor_options[KEY_VALUE_TEMPLATE] 3524 | if sensor_options.get(ATTR_ICON): 3525 | payload[KEY_ICON] = sensor_options[ATTR_ICON] 3526 | if dev_id.lower() in ignored: 3527 | payload = "" 3528 | if mode == LIGHT_COLOR: 3529 | payload = "" 3530 | 3531 | mqtt_publish(config_topic, payload, retain) 3532 | 3533 | # light numbers 3534 | for number, number_options in light_numbers.items(): 3535 | config_topic = ( 3536 | f"{disc_prefix}/number/{dev_id}-{light_id}-{number}/config".encode( 3537 | "ascii", "ignore" 3538 | ).decode("utf-8") 3539 | ) 3540 | 3541 | payload = { 3542 | KEY_NAME: f"{format_entity_name(number)} {light_id}", 3543 | KEY_COMMAND_TOPIC: number_options[KEY_COMMAND_TOPIC].format( 3544 | light_id=light_id 3545 | ), 3546 | KEY_COMMAND_TEMPLATE: number_options[KEY_COMMAND_TEMPLATE].format(), 3547 | KEY_MAX: number_options[KEY_MAX], 3548 | KEY_MIN: number_options[KEY_MIN], 3549 | KEY_STEP: number_options[KEY_STEP], 3550 | KEY_STATE_TOPIC: number_options[KEY_STATE_TOPIC].format(light_id=light_id), 3551 | KEY_VALUE_TEMPLATE: number_options[KEY_VALUE_TEMPLATE], 3552 | KEY_UNIT: number_options[KEY_UNIT], 3553 | KEY_ENABLED_BY_DEFAULT: str(number_options[KEY_ENABLED_BY_DEFAULT]).lower(), 3554 | KEY_UNIQUE_ID: f"{dev_id}-{number}-{light_id}".lower(), 3555 | KEY_QOS: qos, 3556 | KEY_AVAILABILITY: availability, 3557 | KEY_DEVICE: device_info, 3558 | KEY_ORIGIN: origin_info, 3559 | "~": default_topic, 3560 | } 3561 | if number_options.get(KEY_ENTITY_CATEGORY): 3562 | payload[KEY_ENTITY_CATEGORY] = number_options[KEY_ENTITY_CATEGORY] 3563 | if number_options.get(KEY_DEVICE_CLASS): 3564 | payload[KEY_DEVICE_CLASS] = number_options[KEY_DEVICE_CLASS] 3565 | if number_options.get(ATTR_ICON): 3566 | payload[KEY_ICON] = number_options[ATTR_ICON] 3567 | if dev_id.lower() in ignored: 3568 | payload = "" 3569 | 3570 | mqtt_publish(config_topic, payload, retain, json=True) 3571 | 3572 | # meter sensors 3573 | for meter_id in range(meters): 3574 | force_update = False 3575 | if isinstance(device_config.get(CONF_FORCE_UPDATE_SENSORS), bool): 3576 | force_update = device_config.get(CONF_FORCE_UPDATE_SENSORS) 3577 | for sensor, sensor_options in meter_sensors.items(): 3578 | config_topic = ( 3579 | f"{disc_prefix}/sensor/{dev_id}-emeter-{sensor}-{meter_id}/config".encode( 3580 | "ascii", "ignore" 3581 | ).decode("utf-8") 3582 | ) 3583 | 3584 | payload = { 3585 | KEY_NAME: f"{format_entity_name(sensor)} {meter_id}", 3586 | KEY_STATE_TOPIC: sensor_options[KEY_STATE_TOPIC].format(meter_id=meter_id), 3587 | KEY_AVAILABILITY: availability, 3588 | KEY_FORCE_UPDATE: str(force_update).lower(), 3589 | KEY_ENABLED_BY_DEFAULT: str(sensor_options[KEY_ENABLED_BY_DEFAULT]).lower(), 3590 | KEY_UNIQUE_ID: f"{dev_id}-emeter-{sensor}-{meter_id}".lower(), 3591 | KEY_QOS: qos, 3592 | KEY_DEVICE: device_info, 3593 | KEY_ORIGIN: origin_info, 3594 | "~": default_topic, 3595 | } 3596 | if sensor_options.get(KEY_SUGGESTED_DISPLAY_PRECISION): 3597 | payload[KEY_SUGGESTED_DISPLAY_PRECISION] = sensor_options[ 3598 | KEY_SUGGESTED_DISPLAY_PRECISION 3599 | ] 3600 | if sensor_options.get(KEY_ENTITY_CATEGORY): 3601 | payload[KEY_ENTITY_CATEGORY] = sensor_options[KEY_ENTITY_CATEGORY] 3602 | if sensor_options.get(KEY_DEVICE_CLASS): 3603 | payload[KEY_DEVICE_CLASS] = sensor_options[KEY_DEVICE_CLASS] 3604 | if sensor_options.get(KEY_STATE_CLASS): 3605 | payload[KEY_STATE_CLASS] = sensor_options[KEY_STATE_CLASS] 3606 | if sensor_options.get(KEY_UNIT): 3607 | payload[KEY_UNIT] = sensor_options[KEY_UNIT] 3608 | if sensor_options.get(KEY_VALUE_TEMPLATE): 3609 | payload[KEY_VALUE_TEMPLATE] = sensor_options[KEY_VALUE_TEMPLATE] 3610 | if sensor_options.get(ATTR_ICON): 3611 | payload[KEY_ICON] = sensor_options[ATTR_ICON] 3612 | if dev_id.lower() in ignored: 3613 | payload = "" 3614 | 3615 | mqtt_publish(config_topic, payload, retain) 3616 | 3617 | # valves 3618 | for valve, valve_options in valves.items(): 3619 | config_topic = f"{disc_prefix}/valve/{dev_id}-{make_id(valve)}/config".encode( 3620 | "ascii", "ignore" 3621 | ).decode("utf-8") 3622 | 3623 | if valve_options.get(ATTR_AVAILABILITY_EXTRA): 3624 | availability.append(valve_options[ATTR_AVAILABILITY_EXTRA]) 3625 | 3626 | payload = { 3627 | KEY_NAME: valve_options.get(KEY_NAME), 3628 | KEY_COMMAND_TOPIC: valve_options.get(KEY_COMMAND_TOPIC), 3629 | KEY_STATE_TOPIC: valve_options.get(KEY_STATE_TOPIC), 3630 | KEY_VALUE_TEMPLATE: valve_options.get(KEY_VALUE_TEMPLATE), 3631 | KEY_PAYLOAD_OPEN: valve_options.get(KEY_PAYLOAD_OPEN), 3632 | KEY_PAYLOAD_CLOSE: valve_options.get(KEY_PAYLOAD_CLOSE), 3633 | KEY_REPORTS_POSITION: str( 3634 | valve_options.get(KEY_REPORTS_POSITION, False) 3635 | ).lower(), 3636 | KEY_ENABLED_BY_DEFAULT: str(sensor_options[KEY_ENABLED_BY_DEFAULT]).lower(), 3637 | KEY_AVAILABILITY: availability, 3638 | KEY_UNIQUE_ID: f"{dev_id}-{valve}".lower(), 3639 | KEY_QOS: qos, 3640 | KEY_DEVICE: device_info, 3641 | KEY_ORIGIN: origin_info, 3642 | "~": default_topic, 3643 | } 3644 | 3645 | if valve_options.get(KEY_DEVICE_CLASS): 3646 | payload[KEY_DEVICE_CLASS] = valve_options[KEY_DEVICE_CLASS] 3647 | if model == MODEL_SHELLYGAS and not device_config.get(CONF_VALVE_CONNECTED, False): 3648 | payload = "" 3649 | if not valve_options: 3650 | payload = "" 3651 | if dev_id.lower() in ignored: 3652 | payload = "" 3653 | 3654 | mqtt_publish(config_topic, payload, retain) 3655 | --------------------------------------------------------------------------------