Compare commits
114 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
ee7be37ba6 | |
|
|
8034ada12f | |
|
|
99d25bfd56 | |
|
|
af87fd0719 | |
|
|
0ab5321170 | |
|
|
fdcd28f96a | |
|
|
093a7915f0 | |
|
|
ea0a5e34e3 | |
|
|
fdd4cacddf | |
|
|
601d1f3984 | |
|
|
287e74673e | |
|
|
397e44e5f2 | |
|
|
f9d80d7a00 | |
|
|
2b57eeb4fa | |
|
|
dc5c45483e | |
|
|
3d112757d0 | |
|
|
189777fbdb | |
|
|
8e97fc7404 | |
|
|
908a60ade4 | |
|
|
47143d9ac2 | |
|
|
41e2415e69 | |
|
|
7ca4578c0f | |
|
|
37c3e2e77f | |
|
|
cd5abc0db3 | |
|
|
0c33ac02da | |
|
|
3cd21e0143 | |
|
|
1dd51f4ed9 | |
|
|
7257587b2f | |
|
|
c131eddf39 | |
|
|
a3dc4f9986 | |
|
|
5395103c01 | |
|
|
808fad1d97 | |
|
|
bbb6a23b6c | |
|
|
d9cb2179d5 | |
|
|
25bb26bb4d | |
|
|
3e27b889c4 | |
|
|
f295db69ff | |
|
|
4add593bf4 | |
|
|
8d1995721b | |
|
|
a6707b849b | |
|
|
6a913cf447 | |
|
|
407bc40219 | |
|
|
1f2c553d6a | |
|
|
c56ef9c2ce | |
|
|
6c127342aa | |
|
|
66a9993dfc | |
|
|
9b9fd16513 | |
|
|
fb9830cc1e | |
|
|
615b384487 | |
|
|
4548d413d5 | |
|
|
3229c40387 | |
|
|
e18762579b | |
|
|
8b0cc2d25e | |
|
|
b58ff1b2e5 | |
|
|
b0e6348e6f | |
|
|
23b5608727 | |
|
|
8199b38aa0 | |
|
|
050b326865 | |
|
|
ea47c808b0 | |
|
|
acebf1b268 | |
|
|
0409955801 | |
|
|
e84ec8f69e | |
|
|
c92d2f53e5 | |
|
|
b6edb63051 | |
|
|
782e04cd47 | |
|
|
964ba623b2 | |
|
|
a691dc42dc | |
|
|
0c0e9e145d | |
|
|
0610cf7966 | |
|
|
f49f77acd9 | |
|
|
5eeda36f6d | |
|
|
2271564048 | |
|
|
edc63b83cb | |
|
|
88a4f2af49 | |
|
|
4f2e93e63e | |
|
|
6a9daa063d | |
|
|
4f9ef266e0 | |
|
|
6b4ce5f49f | |
|
|
cd60d224f4 | |
|
|
d1b7986550 | |
|
|
3f1869e6a9 | |
|
|
be93b490e0 | |
|
|
5dc35ec7ad | |
|
|
a916e342f7 | |
|
|
d0a0af17c8 | |
|
|
156cbc266b | |
|
|
5f847a3bb7 | |
|
|
c1a5afb510 | |
|
|
dc263083c4 | |
|
|
2ee8cc5ed3 | |
|
|
69909e228c | |
|
|
fa6caf4c22 | |
|
|
6b3e83f4ca | |
|
|
34a3a968b0 | |
|
|
93193b2489 | |
|
|
54844f4755 | |
|
|
d580c21055 | |
|
|
0b9780fb97 | |
|
|
6f29a50e5d | |
|
|
df420369a2 | |
|
|
640924ffcc | |
|
|
80e5f5a4ed | |
|
|
ac7284770a | |
|
|
5fdd594bae | |
|
|
70bfd56268 | |
|
|
b62e6abccf | |
|
|
086c34042f | |
|
|
735c70b391 | |
|
|
72a5c70ab3 | |
|
|
502bf782a7 | |
|
|
781011e8ee | |
|
|
9f3140c496 | |
|
|
accc0c1ca8 | |
|
|
5690372665 |
|
|
@ -0,0 +1,5 @@
|
||||||
|
# These are supported funding model platforms
|
||||||
|
|
||||||
|
github: schizza
|
||||||
|
ko_fi: schizza
|
||||||
|
buy_me_a_coffee: schizza
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
---
|
||||||
|
name: Bug report
|
||||||
|
about: Create a report to help us improve
|
||||||
|
title: "[BUG]"
|
||||||
|
labels: bug
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Describe the bug**
|
||||||
|
A clear and concise description of what the bug is.
|
||||||
|
|
||||||
|
**To Reproduce**
|
||||||
|
Steps to reproduce the behavior:
|
||||||
|
|
||||||
|
**Expected behavior**
|
||||||
|
A clear and concise description of what you expected to happen.
|
||||||
|
|
||||||
|
**Screenshots**
|
||||||
|
If applicable, add screenshots to help explain your problem.
|
||||||
|
|
||||||
|
**Logs**
|
||||||
|
Provide `Developer log` if applicable
|
||||||
|
|
||||||
|
**Provide information about your station:**
|
||||||
|
- Weather station type:
|
||||||
|
- firmware version:
|
||||||
|
|
||||||
|
- [ ] Using PWS protocol
|
||||||
|
- [ ] Using WSLink API
|
||||||
|
- [ ] Using WSLink proxy Add-on
|
||||||
|
|
||||||
|
**Additional context**
|
||||||
|
Add any other context about the problem here.
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
blank_issues_enabled: false
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
---
|
||||||
|
name: Feature request
|
||||||
|
about: Suggest an idea for this project
|
||||||
|
title: ''
|
||||||
|
labels: ''
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Is your feature request related to a problem? Please describe.**
|
||||||
|
A clear and concise description of what the problem is.
|
||||||
|
|
||||||
|
**Describe the solution you'd like**
|
||||||
|
A clear and concise description of what you want to happen.
|
||||||
|
|
||||||
|
**Describe alternatives you've considered**
|
||||||
|
A clear and concise description of any alternative solutions or features you've considered.
|
||||||
|
|
||||||
|
**Is your feature request a new addition**
|
||||||
|
Describe what you want to achieve. How new feature should work.
|
||||||
|
|
||||||
|
**Additional context**
|
||||||
|
Add any other context or screenshots about the feature request here.
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
---
|
||||||
|
name: Issue
|
||||||
|
about: A minor issue that does not significantly affect functionality.
|
||||||
|
title: "[ISSUE]"
|
||||||
|
labels: issue
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Describe the issue**
|
||||||
|
A clear and concise description of what the issue is.
|
||||||
|
|
||||||
|
**To Reproduce**
|
||||||
|
Steps to reproduce the behavior if any:
|
||||||
|
|
||||||
|
**Expected behavior**
|
||||||
|
A clear and concise description of what you expected to happen.
|
||||||
|
|
||||||
|
**Screenshots**
|
||||||
|
If applicable, add screenshots to help explain your problem.
|
||||||
|
|
||||||
|
**Logs**
|
||||||
|
Provide `Developer log` if applicable
|
||||||
|
|
||||||
|
**Provide information about your station:**
|
||||||
|
- Weather station type:
|
||||||
|
- firmware version:
|
||||||
|
|
||||||
|
- [ ] Using PWS protocol
|
||||||
|
- [ ] Using WSLink API
|
||||||
|
- [ ] Using WSLink proxy Add-on
|
||||||
|
|
||||||
|
**Additional context**
|
||||||
|
Add any other context about the problem here.
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
name: Validate with hassfest
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
pull_request:
|
||||||
|
schedule:
|
||||||
|
- cron: "0 0 * * *"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
validate:
|
||||||
|
runs-on: "ubuntu-latest"
|
||||||
|
steps:
|
||||||
|
- uses: "actions/checkout@v3"
|
||||||
|
- uses: home-assistant/actions/hassfest@master
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
name: HACS validate
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
pull_request:
|
||||||
|
schedule:
|
||||||
|
- cron: "0 0 * * *"
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
validate-hacs:
|
||||||
|
runs-on: "ubuntu-latest"
|
||||||
|
steps:
|
||||||
|
- uses: "actions/checkout@v3"
|
||||||
|
- name: HACS validation
|
||||||
|
uses: "hacs/action@main"
|
||||||
|
with:
|
||||||
|
category: "integration"
|
||||||
|
|
@ -160,3 +160,8 @@ cython_debug/
|
||||||
#.idea/
|
#.idea/
|
||||||
|
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
HA
|
||||||
|
.devcontainer
|
||||||
|
.gitignore
|
||||||
|
.vscode
|
||||||
|
|
||||||
|
|
|
||||||
74
README.md
74
README.md
|
|
@ -1,18 +1,46 @@
|
||||||
|
# Integrates your Sencor SWS 12500 or 16600, GARNI, BRESSER weather stations seamlessly into Home Assistant
|
||||||
# Integrates your SWS 12500 weather station seamlessly into Home Assistant
|
|
||||||
|
|
||||||
This integration will listen for data from your station and passes them to respective sensors. It also provides the ability to push data to Windy API.
|
This integration will listen for data from your station and passes them to respective sensors. It also provides the ability to push data to Windy API.
|
||||||
|
|
||||||
*This custom component replaces [old integration via Node-RED and proxy server](https://github.com/schizza/WeatherStation-SWS12500).*
|
_This custom component replaces [old integration via Node-RED and proxy server](https://github.com/schizza/WeatherStation-SWS12500)._
|
||||||
|
|
||||||
|
## Warning - WSLink APP (applies also for SWS 12500 with firmware >3.0)
|
||||||
|
|
||||||
|
For stations that are using WSLink app to setup station and WSLink API for resending data (SWS 12500 manufactured in 2024 and later). You will need to install [WSLink SSL proxy addon](https://github.com/schizza/wslink-addon) to your Home Assistant if you are not running your Home Assistant instance in SSL mode or you do not have SSL proxy for your Home Assistant.
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
|
|
||||||
- [Sencor SWS 12500 Weather Station](https://www.sencor.cz/profesionalni-meteorologicka-stanice/sws-12500).
|
- Weather station that supports sending data to custom server in their API [(list of supported stations.)](#list-of-supported-stations)
|
||||||
- Configure station to send data directly to Home Assistant.
|
- Configure station to send data directly to Home Assistant.
|
||||||
- If you want to push data to Windy, you have to create an account at [Windy](https://stations.windy.com).
|
- If you want to push data to Windy, you have to create an account at [Windy](https://stations.windy.com).
|
||||||
|
|
||||||
|
## List of supported stations
|
||||||
|
|
||||||
|
- [Sencor SWS 12500 Weather Station](https://www.sencor.cz/profesionalni-meteorologicka-stanice/sws-12500)
|
||||||
|
- [Sencor SWS 16600 WiFi SH](https://www.sencor.cz/meteorologicka-stanice/sws-16600)
|
||||||
|
- Bresser stations that support custom server upload. [for example, this is known to work](https://www.bresser.com/p/bresser-wi-fi-clearview-weather-station-with-7-in-1-sensor-7002586)
|
||||||
|
- Garni stations with WSLink support or custom server support.
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
|
### If your SWS12500 station's firmware is 1.0 or your station is configured as described in this README and you still can not see any data incoming to Home Assistant please [read here](https://github.com/schizza/SWS-12500-custom-component/issues/17) and [here](firmware_bug.md)
|
||||||
|
|
||||||
|
### For stations that send through WSLink API
|
||||||
|
|
||||||
|
Make sure you have your Home Assistant cofigured in SSL mode or use [WSLink SSL proxy addon](https://github.com/schizza/wslink-addon) to bypass SSL configuration of whole Home Assistant.
|
||||||
|
|
||||||
|
### HACS installation
|
||||||
|
|
||||||
|
For installation with HACS, you have to first add a [custom repository](https://hacs.xyz/docs/faq/custom_repositories/).
|
||||||
|
You will need to enter the URL of this repository when prompted: `https://github.com/schizza/SWS-12500-custom-component`.
|
||||||
|
|
||||||
|
After adding this repository to HACS:
|
||||||
|
|
||||||
|
- Go to HACS -> Integrations
|
||||||
|
- Search for the integration `Sencor SWS 12500 Weather station` and download the integration.
|
||||||
|
- Restart Home Assistant
|
||||||
|
- Now go to `Integrations` and add new integration. Search for `Sencor SWS 12500 Weather station` and select it.
|
||||||
|
|
||||||
### Manual installation
|
### Manual installation
|
||||||
|
|
||||||
For manual installation you must have an access to your Home Assistant's `/config` folder.
|
For manual installation you must have an access to your Home Assistant's `/config` folder.
|
||||||
|
|
@ -21,17 +49,21 @@ For manual installation you must have an access to your Home Assistant's `/conf
|
||||||
|
|
||||||
- Copy the `custom_components/sws12500-custom-component` folder to your `config/custom_components` folder in Home Assistant.
|
- Copy the `custom_components/sws12500-custom-component` folder to your `config/custom_components` folder in Home Assistant.
|
||||||
- Restart Home Assistant.
|
- Restart Home Assistant.
|
||||||
- Now go to `Integrations` and add new integration `SWS 12500`
|
- Now go to `Integrations` and add new integration `Sencor SWS 12500 Weather station`
|
||||||
|
|
||||||
## Configure your station in AP mode
|
## Configure your station in AP mode
|
||||||
|
|
||||||
|
> This configuration example is for Sencor SWS12500 with FW < 3.0
|
||||||
|
|
||||||
|
> For WSLink read [this notes.](#wslink-notes)
|
||||||
|
|
||||||
1. Hold the Wi-Fi button on the back of the station for 6 seconds until the AP will flash on the display.
|
1. Hold the Wi-Fi button on the back of the station for 6 seconds until the AP will flash on the display.
|
||||||
2. Select your station from available APs on your computer.
|
2. Select your station from available APs on your computer.
|
||||||
3. Connect to the station's setup page: `http://192.168.1.1` from your browser.
|
3. Connect to the station's setup page: `http://192.168.1.1` from your browser.
|
||||||
4. In the third URL section fill in the address to your local Home Assistant installation.
|
4. In the third URL section fill in the address to your local Home Assistant installation.
|
||||||
5. Create new `ID` and `KEY`. You can use [online tool](https://www.allkeysgenerator.com/Random/Security-Encryption-Key-Generator.aspx) to generate random keys. *(you will need them to configure integation to Home Assistatnt)*
|
5. Create new `ID` and `KEY`. You can use [online tool](https://randomkeygen.com) to generate random keys. _(you will need them to configure integration to Home Assistant)_
|
||||||
6. Save your configuration.
|
6. Save your configuration.
|
||||||

|

|
||||||
|
|
||||||
Once integration is added to Home Assistant, configuration dialog will ask you for `API_ID` and `API_KEY` as you set them in your station:
|
Once integration is added to Home Assistant, configuration dialog will ask you for `API_ID` and `API_KEY` as you set them in your station:
|
||||||
|
|
||||||
|
|
@ -42,7 +74,7 @@ API_KEY: PASSWORD in station's config
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
If you chanage `API ID` or `API KEY` in the station, you have to reconfigure integration to accept data from your station.
|
If you change `API ID` or `API KEY` in the station, you have to reconfigure integration to accept data from your station.
|
||||||
|
|
||||||
- In `Settings` -> `Devices & services` find SWS12500 and click `Configure`.
|
- In `Settings` -> `Devices & services` find SWS12500 and click `Configure`.
|
||||||
- In dialog box choose `Basic - Configure credentials`
|
- In dialog box choose `Basic - Configure credentials`
|
||||||
|
|
@ -55,14 +87,34 @@ As soon as the integration is added into Home Assistant it will listen for incom
|
||||||
|
|
||||||
- First of all you need to create account at [Windy stations](https://stations.windy.com).
|
- First of all you need to create account at [Windy stations](https://stations.windy.com).
|
||||||
- Once you have an account created, copy your Windy API Key.
|
- Once you have an account created, copy your Windy API Key.
|
||||||

|

|
||||||
|
|
||||||
- In `Settings` -> `Devices & services` find SWS12500 and click `Configure`.
|
- In `Settings` -> `Devices & services` find SWS12500 and click `Configure`.
|
||||||
- In dialog box choose `Windy configuration`.
|
- In dialog box choose `Windy configuration`.
|
||||||

|

|
||||||
|
|
||||||
- Fill in `Key` you were provided at `Windy stations`.
|
- Fill in `Key` you were provided at `Windy stations`.
|
||||||
- Tick `Enable` checkbox.
|
- Tick `Enable` checkbox.
|
||||||

|

|
||||||
|
|
||||||
- You are done.
|
- You are done.
|
||||||
|
|
||||||
|
## WSLink notes
|
||||||
|
|
||||||
|
While your station is using WSLink you have to have Home Assistant in SSL mode or behind SSL proxy server.
|
||||||
|
You can bypass whole SSL settings by using [WSLink SSL proxy addon](https://github.com/schizza/wslink-addon) which is made exactly for this integration to support WSLink on unsecured installations of Home Assistant.
|
||||||
|
|
||||||
|
### Configuration
|
||||||
|
|
||||||
|
- Set your station as [mentioned above](#configure-your-station-in-ap-mode) while changing `HA port` to be the port number you set in the addon (443 for example) not port of your Home Assistant instance. And that will do the trick!
|
||||||
|
|
||||||
|
```plain
|
||||||
|
HomeAssistant is at 192.0.0.2:8123
|
||||||
|
WSLink proxy addon listening on port 4443
|
||||||
|
|
||||||
|
you will set URL in station to: 192.0.0.2:4443
|
||||||
|
```
|
||||||
|
|
||||||
|
- Your station will be sending data to this SSL proxy and addon will handle the rest.
|
||||||
|
|
||||||
|
_Most of the stations does not care about self-signed certificates on the server side._
|
||||||
|
|
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 221 KiB |
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 263 KiB |
|
|
@ -1,4 +1,5 @@
|
||||||
"""The Sencor SWS 12500 Weather Station integration."""
|
"""The Sencor SWS 12500 Weather Station integration."""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
import aiohttp
|
import aiohttp
|
||||||
|
|
@ -8,15 +9,34 @@ from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import Platform
|
from homeassistant.const import Platform
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.exceptions import InvalidStateError, PlatformNotReady
|
from homeassistant.exceptions import InvalidStateError, PlatformNotReady
|
||||||
from homeassistant.helpers.typing import ConfigType
|
|
||||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
|
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
|
||||||
|
|
||||||
from .const import API_ID, API_KEY, DEFAULT_URL, DEV_DBG, DOMAIN, WINDY_ENABLED
|
from .const import (
|
||||||
from .utils import anonymize, remap_items
|
API_ID,
|
||||||
|
API_KEY,
|
||||||
|
DEFAULT_URL,
|
||||||
|
DEV_DBG,
|
||||||
|
DOMAIN,
|
||||||
|
SENSORS_TO_LOAD,
|
||||||
|
WINDY_ENABLED,
|
||||||
|
WSLINK,
|
||||||
|
WSLINK_URL,
|
||||||
|
)
|
||||||
|
from .routes import Routes, unregistred
|
||||||
|
from .utils import (
|
||||||
|
anonymize,
|
||||||
|
check_disabled,
|
||||||
|
loaded_sensors,
|
||||||
|
remap_items,
|
||||||
|
remap_wslink_items,
|
||||||
|
translated_notification,
|
||||||
|
translations,
|
||||||
|
update_options,
|
||||||
|
)
|
||||||
from .windy_func import WindyPush
|
from .windy_func import WindyPush
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
PLATFORMS = [Platform.SENSOR]
|
PLATFORMS: list[Platform] = [Platform.SENSOR]
|
||||||
|
|
||||||
|
|
||||||
class IncorrectDataError(InvalidStateError):
|
class IncorrectDataError(InvalidStateError):
|
||||||
|
|
@ -35,13 +55,23 @@ class WeatherDataUpdateCoordinator(DataUpdateCoordinator):
|
||||||
|
|
||||||
async def recieved_data(self, webdata):
|
async def recieved_data(self, webdata):
|
||||||
"""Handle incoming data query."""
|
"""Handle incoming data query."""
|
||||||
|
_wslink = self.config_entry.options.get(WSLINK)
|
||||||
data = webdata.query
|
data = webdata.query
|
||||||
|
|
||||||
response = None
|
response = None
|
||||||
|
|
||||||
if "ID" not in data or "PASSWORD" not in data:
|
if not _wslink and ("ID" not in data or "PASSWORD" not in data):
|
||||||
_LOGGER.error("Invalid request. No security data provided!")
|
_LOGGER.error("Invalid request. No security data provided!")
|
||||||
raise HTTPUnauthorized
|
raise HTTPUnauthorized
|
||||||
|
|
||||||
|
if _wslink and ("wsid" not in data or "wspw" not in data):
|
||||||
|
_LOGGER.error("Invalid request. No security data provided!")
|
||||||
|
raise HTTPUnauthorized
|
||||||
|
|
||||||
|
if _wslink:
|
||||||
|
id_data = data["wsid"]
|
||||||
|
key_data = data["wspw"]
|
||||||
|
else:
|
||||||
id_data = data["ID"]
|
id_data = data["ID"]
|
||||||
key_data = data["PASSWORD"]
|
key_data = data["PASSWORD"]
|
||||||
|
|
||||||
|
|
@ -55,52 +85,117 @@ class WeatherDataUpdateCoordinator(DataUpdateCoordinator):
|
||||||
if self.config_entry.options.get(WINDY_ENABLED):
|
if self.config_entry.options.get(WINDY_ENABLED):
|
||||||
response = await self.windy.push_data_to_windy(data)
|
response = await self.windy.push_data_to_windy(data)
|
||||||
|
|
||||||
self.async_set_updated_data(remap_items(data))
|
remaped_items = (
|
||||||
|
remap_wslink_items(data)
|
||||||
|
if self.config_entry.options.get(WSLINK)
|
||||||
|
else remap_items(data)
|
||||||
|
)
|
||||||
|
|
||||||
|
if sensors := check_disabled(self.hass, remaped_items, self.config):
|
||||||
|
translate_sensors = [
|
||||||
|
await translations(
|
||||||
|
self.hass, DOMAIN, f"sensor.{t_key}", key="name", category="entity"
|
||||||
|
)
|
||||||
|
for t_key in sensors
|
||||||
|
if await translations(
|
||||||
|
self.hass, DOMAIN, f"sensor.{t_key}", key="name", category="entity"
|
||||||
|
)
|
||||||
|
is not None
|
||||||
|
]
|
||||||
|
human_readable = "\n".join(translate_sensors)
|
||||||
|
|
||||||
|
await translated_notification(
|
||||||
|
self.hass,
|
||||||
|
DOMAIN,
|
||||||
|
"added",
|
||||||
|
{"added_sensors": f"{human_readable}\n"},
|
||||||
|
)
|
||||||
|
if _loaded_sensors := loaded_sensors(self.config_entry):
|
||||||
|
sensors.extend(_loaded_sensors)
|
||||||
|
await update_options(self.hass, self.config_entry, SENSORS_TO_LOAD, sensors)
|
||||||
|
# await self.hass.config_entries.async_reload(self.config.entry_id)
|
||||||
|
|
||||||
|
self.async_set_updated_data(remaped_items)
|
||||||
|
|
||||||
if self.config_entry.options.get(DEV_DBG):
|
if self.config_entry.options.get(DEV_DBG):
|
||||||
_LOGGER.info("Dev log: %s", anonymize(data))
|
_LOGGER.info("Dev log: %s", anonymize(data))
|
||||||
|
|
||||||
response = response if response else "OK"
|
response = response or "OK"
|
||||||
return aiohttp.web.Response(body=f"{response}", status=200)
|
return aiohttp.web.Response(body=f"{response or 'OK'}", status=200)
|
||||||
|
|
||||||
|
|
||||||
def register_path(
|
def register_path(
|
||||||
hass: HomeAssistant, url_path: str, coordinator: WeatherDataUpdateCoordinator
|
hass: HomeAssistant,
|
||||||
|
url_path: str,
|
||||||
|
coordinator: WeatherDataUpdateCoordinator,
|
||||||
|
config: ConfigEntry,
|
||||||
):
|
):
|
||||||
"""Register path to handle incoming data."""
|
"""Register path to handle incoming data."""
|
||||||
|
|
||||||
|
hass_data = hass.data.setdefault(DOMAIN, {})
|
||||||
|
debug = config.options.get(DEV_DBG)
|
||||||
|
_wslink = config.options.get(WSLINK)
|
||||||
|
|
||||||
|
routes: Routes = hass_data.get("routes") if "routes" in hass_data else None
|
||||||
|
|
||||||
|
if routes is None:
|
||||||
|
routes = Routes()
|
||||||
|
_LOGGER.info("Routes not found, creating new routes")
|
||||||
|
|
||||||
|
if debug:
|
||||||
|
_LOGGER.debug("Enabled route is: %s, WSLink is %s", url_path, _wslink)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
route = hass.http.app.router.add_route(
|
default_route = hass.http.app.router.add_get(
|
||||||
"GET", url_path, coordinator.recieved_data
|
DEFAULT_URL,
|
||||||
|
coordinator.recieved_data if not _wslink else unregistred,
|
||||||
|
name="weather_default_url",
|
||||||
)
|
)
|
||||||
except Exception: # pylint: disable=(broad-except)
|
if debug:
|
||||||
_LOGGER.error("Unable to register URL handler!")
|
_LOGGER.debug("Default route: %s", default_route)
|
||||||
|
|
||||||
|
wslink_route = hass.http.app.router.add_get(
|
||||||
|
WSLINK_URL,
|
||||||
|
coordinator.recieved_data if _wslink else unregistred,
|
||||||
|
name="weather_wslink_url",
|
||||||
|
)
|
||||||
|
if debug:
|
||||||
|
_LOGGER.debug("WSLink route: %s", wslink_route)
|
||||||
|
|
||||||
|
routes.add_route(
|
||||||
|
DEFAULT_URL,
|
||||||
|
default_route,
|
||||||
|
coordinator.recieved_data if not _wslink else unregistred,
|
||||||
|
not _wslink,
|
||||||
|
)
|
||||||
|
routes.add_route(
|
||||||
|
WSLINK_URL, wslink_route, coordinator.recieved_data, _wslink
|
||||||
|
)
|
||||||
|
|
||||||
|
hass_data["routes"] = routes
|
||||||
|
|
||||||
|
except RuntimeError as Ex: # pylint: disable=(broad-except)
|
||||||
|
if (
|
||||||
|
"Added route will never be executed, method GET is already registered"
|
||||||
|
in Ex.args
|
||||||
|
):
|
||||||
|
_LOGGER.info("Handler to URL (%s) already registred", url_path)
|
||||||
|
return False
|
||||||
|
|
||||||
|
_LOGGER.error("Unable to register URL handler! (%s)", Ex.args)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
_LOGGER.info(
|
_LOGGER.info(
|
||||||
"Registered path to handle weather data: %s",
|
"Registered path to handle weather data: %s",
|
||||||
route.get_info(), # pylint: disable=used-before-assignment
|
routes.get_enabled(), # pylint: disable=used-before-assignment
|
||||||
)
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
def unregister_path():
|
|
||||||
"""Unregister path to handle incoming data."""
|
|
||||||
_LOGGER.error(
|
|
||||||
"Unable to delete webhook from API! Restart HA before adding integration!"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if _wslink:
|
||||||
|
routes.switch_route(coordinator.recieved_data, WSLINK_URL)
|
||||||
|
else:
|
||||||
|
routes.switch_route(coordinator.recieved_data, DEFAULT_URL)
|
||||||
|
|
||||||
class Weather(WeatherDataUpdateCoordinator):
|
return routes
|
||||||
"""Weather class."""
|
|
||||||
|
|
||||||
def __init__(self, hass: HomeAssistant, config) -> None:
|
|
||||||
"""Init class."""
|
|
||||||
self.hass = hass
|
|
||||||
super().__init__(hass, config)
|
|
||||||
|
|
||||||
async def setup_update_listener(self, hass: HomeAssistant, entry: ConfigEntry):
|
|
||||||
"""Update setup listener."""
|
|
||||||
_LOGGER.info("Settings updated")
|
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||||
|
|
@ -108,38 +203,45 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||||
|
|
||||||
coordinator = WeatherDataUpdateCoordinator(hass, entry)
|
coordinator = WeatherDataUpdateCoordinator(hass, entry)
|
||||||
|
|
||||||
hass.data.setdefault(DOMAIN, {})
|
hass_data = hass.data.setdefault(DOMAIN, {})
|
||||||
|
hass_data[entry.entry_id] = coordinator
|
||||||
|
|
||||||
hass.data[DOMAIN][entry.entry_id] = coordinator
|
_wslink = entry.options.get(WSLINK)
|
||||||
|
debug = entry.options.get(DEV_DBG)
|
||||||
|
|
||||||
weather = Weather(hass, entry)
|
if debug:
|
||||||
|
_LOGGER.debug("WS Link is %s", "enbled" if _wslink else "disabled")
|
||||||
|
|
||||||
if not register_path(hass, DEFAULT_URL, coordinator):
|
route = register_path(
|
||||||
|
hass, DEFAULT_URL if not _wslink else WSLINK_URL, coordinator, entry
|
||||||
|
)
|
||||||
|
|
||||||
|
if not route:
|
||||||
_LOGGER.error("Fatal: path not registered!")
|
_LOGGER.error("Fatal: path not registered!")
|
||||||
raise PlatformNotReady
|
raise PlatformNotReady
|
||||||
|
|
||||||
|
hass_data["route"] = route
|
||||||
|
|
||||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||||
|
|
||||||
entry.async_on_unload(entry.add_update_listener(weather.setup_update_listener))
|
entry.async_on_unload(entry.add_update_listener(update_listener))
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
async def update_listener(hass: HomeAssistant, entry: ConfigEntry):
|
||||||
|
"""Update setup listener."""
|
||||||
|
|
||||||
|
await hass.config_entries.async_reload(entry.entry_id)
|
||||||
|
|
||||||
|
_LOGGER.info("Settings updated")
|
||||||
|
|
||||||
|
|
||||||
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||||
"""Unload a config entry."""
|
"""Unload a config entry."""
|
||||||
|
|
||||||
_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
|
_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
|
||||||
if _ok:
|
if _ok:
|
||||||
hass.data[DOMAIN].pop(entry.entry_id)
|
hass.data[DOMAIN].pop(entry.entry_id)
|
||||||
unregister_path()
|
|
||||||
|
|
||||||
return _ok
|
return _ok
|
||||||
|
|
||||||
|
|
||||||
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
|
||||||
"""Set up the component.
|
|
||||||
|
|
||||||
This component can only be configured through the Integrations UI.
|
|
||||||
"""
|
|
||||||
hass.data.setdefault(DOMAIN, {})
|
|
||||||
return True
|
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,15 @@
|
||||||
"""Config flow for Sencor SWS 12500 Weather Station integration."""
|
"""Config flow for Sencor SWS 12500 Weather Station integration."""
|
||||||
|
|
||||||
|
import logging
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant import config_entries
|
from homeassistant.config_entries import ConfigFlow, OptionsFlow
|
||||||
|
from homeassistant.const import UnitOfPrecipitationDepth, UnitOfVolumetricFlux
|
||||||
from homeassistant.core import callback
|
from homeassistant.core import callback
|
||||||
from homeassistant.exceptions import HomeAssistantError
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
|
import homeassistant.helpers.entity_registry as er
|
||||||
|
|
||||||
from .const import (
|
from .const import (
|
||||||
API_ID,
|
API_ID,
|
||||||
|
|
@ -13,10 +17,18 @@ from .const import (
|
||||||
DEV_DBG,
|
DEV_DBG,
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
INVALID_CREDENTIALS,
|
INVALID_CREDENTIALS,
|
||||||
|
MIG_FROM,
|
||||||
|
MIG_TO,
|
||||||
|
SENSOR_TO_MIGRATE,
|
||||||
|
SENSORS_TO_LOAD,
|
||||||
WINDY_API_KEY,
|
WINDY_API_KEY,
|
||||||
WINDY_ENABLED,
|
WINDY_ENABLED,
|
||||||
WINDY_LOGGER_ENABLED,
|
WINDY_LOGGER_ENABLED,
|
||||||
|
WSLINK,
|
||||||
)
|
)
|
||||||
|
from .utils import long_term_units_in_statistics_meta, migrate_data
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class CannotConnect(HomeAssistantError):
|
class CannotConnect(HomeAssistantError):
|
||||||
|
|
@ -27,50 +39,115 @@ class InvalidAuth(HomeAssistantError):
|
||||||
"""Invalid auth exception."""
|
"""Invalid auth exception."""
|
||||||
|
|
||||||
|
|
||||||
class ConfigOptionsFlowHandler(config_entries.OptionsFlow):
|
class ConfigOptionsFlowHandler(OptionsFlow):
|
||||||
"""Handle WeatherStation ConfigFlow."""
|
"""Handle WeatherStation ConfigFlow."""
|
||||||
|
|
||||||
def __init__(self, config_entry: config_entries.ConfigEntry) -> None:
|
def __init__(self) -> None:
|
||||||
"""Initialize flow."""
|
"""Initialize flow."""
|
||||||
self.config_entry = config_entry
|
super().__init__()
|
||||||
|
|
||||||
self.user_data: dict[str, str] = {
|
self.windy_data: dict[str, Any] = {}
|
||||||
|
self.windy_data_schema = {}
|
||||||
|
self.user_data: dict[str, Any] = {}
|
||||||
|
self.user_data_schema = {}
|
||||||
|
self.sensors: dict[str, Any] = {}
|
||||||
|
self.migrate_schema = {}
|
||||||
|
self.migrate_sensor_select = {}
|
||||||
|
self.migrate_unit_selection = {}
|
||||||
|
self.count = 0
|
||||||
|
self.selected_sensor = ""
|
||||||
|
|
||||||
|
self.unit_values = [unit.value for unit in UnitOfVolumetricFlux]
|
||||||
|
self.unit_values.extend([unit.value for unit in UnitOfPrecipitationDepth])
|
||||||
|
|
||||||
|
@property
|
||||||
|
def config_entry(self):
|
||||||
|
return self.hass.config_entries.async_get_entry(self.handler)
|
||||||
|
|
||||||
|
async def _get_entry_data(self):
|
||||||
|
"""Get entry data."""
|
||||||
|
|
||||||
|
self.user_data: dict[str, Any] = {
|
||||||
API_ID: self.config_entry.options.get(API_ID),
|
API_ID: self.config_entry.options.get(API_ID),
|
||||||
API_KEY: self.config_entry.options.get(API_KEY),
|
API_KEY: self.config_entry.options.get(API_KEY),
|
||||||
DEV_DBG: self.config_entry.options.get(DEV_DBG),
|
WSLINK: self.config_entry.options.get(WSLINK, False),
|
||||||
|
DEV_DBG: self.config_entry.options.get(DEV_DBG, False),
|
||||||
|
}
|
||||||
|
|
||||||
|
self.user_data_schema = {
|
||||||
|
vol.Required(API_ID, default=self.user_data.get(API_ID, "")): str,
|
||||||
|
vol.Required(API_KEY, default=self.user_data.get(API_KEY, "")): str,
|
||||||
|
vol.Optional(WSLINK, default=self.user_data.get(WSLINK, False)): bool,
|
||||||
|
vol.Optional(DEV_DBG, default=self.user_data.get(DEV_DBG, False)): bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
self.sensors: dict[str, Any] = {
|
||||||
|
SENSORS_TO_LOAD: self.config_entry.options.get(SENSORS_TO_LOAD)
|
||||||
|
if isinstance(self.config_entry.options.get(SENSORS_TO_LOAD), list)
|
||||||
|
else []
|
||||||
}
|
}
|
||||||
|
|
||||||
self.windy_data: dict[str, Any] = {
|
self.windy_data: dict[str, Any] = {
|
||||||
WINDY_API_KEY: self.config_entry.options.get(WINDY_API_KEY),
|
WINDY_API_KEY: self.config_entry.options.get(WINDY_API_KEY),
|
||||||
WINDY_ENABLED: self.config_entry.options.get(WINDY_ENABLED) if isinstance(self.config_entry.options.get(WINDY_ENABLED), bool) else False,
|
WINDY_ENABLED: self.config_entry.options.get(WINDY_ENABLED, False),
|
||||||
WINDY_LOGGER_ENABLED: self.config_entry.options.get(WINDY_LOGGER_ENABLED) if isinstance(self.config_entry.options.get(WINDY_LOGGER_ENABLED), bool) else False,
|
WINDY_LOGGER_ENABLED: self.config_entry.options.get(
|
||||||
}
|
WINDY_LOGGER_ENABLED, False
|
||||||
|
),
|
||||||
self.user_data_schema = {
|
|
||||||
vol.Required(API_ID, default=self.user_data[API_ID] or ""): str,
|
|
||||||
vol.Required(API_KEY, default=self.user_data[API_KEY] or ""): str,
|
|
||||||
vol.Optional(DEV_DBG, default=self.user_data[DEV_DBG]): bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
self.windy_data_schema = {
|
self.windy_data_schema = {
|
||||||
vol.Optional(
|
vol.Optional(
|
||||||
WINDY_API_KEY, default=self.windy_data[WINDY_API_KEY] or ""
|
WINDY_API_KEY, default=self.windy_data.get(WINDY_API_KEY, "")
|
||||||
): str,
|
): str,
|
||||||
vol.Optional(WINDY_ENABLED, default=self.windy_data[WINDY_ENABLED]): bool,
|
vol.Optional(WINDY_ENABLED, default=self.windy_data[WINDY_ENABLED]): bool
|
||||||
|
or False,
|
||||||
vol.Optional(
|
vol.Optional(
|
||||||
WINDY_LOGGER_ENABLED,
|
WINDY_LOGGER_ENABLED,
|
||||||
default=self.windy_data[WINDY_LOGGER_ENABLED],
|
default=self.windy_data[WINDY_LOGGER_ENABLED],
|
||||||
): bool,
|
): bool or False,
|
||||||
|
}
|
||||||
|
|
||||||
|
self.migrate_sensor_select = {
|
||||||
|
vol.Required(SENSOR_TO_MIGRATE): vol.In(
|
||||||
|
await self.load_sensors_to_migrate() or {}
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
self.migrate_unit_selection = {
|
||||||
|
vol.Required(MIG_FROM): vol.In(self.unit_values),
|
||||||
|
vol.Required(MIG_TO): vol.In(self.unit_values),
|
||||||
|
vol.Optional("trigger_action", default=False): bool,
|
||||||
|
}
|
||||||
|
# "mm/d", "mm/h", "mm", "in/d", "in/h", "in"
|
||||||
|
|
||||||
|
async def load_sensors_to_migrate(self) -> dict[str, Any]:
|
||||||
|
"""Load sensors to migrate."""
|
||||||
|
|
||||||
|
sensor_statistics = await long_term_units_in_statistics_meta(self.hass)
|
||||||
|
|
||||||
|
entity_registry = er.async_get(self.hass)
|
||||||
|
sensors = entity_registry.entities.get_entries_for_config_entry_id(
|
||||||
|
self.config_entry.entry_id
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
sensor.entity_id: f"{sensor.name or sensor.original_name} (current settings: {sensor.unit_of_measurement}, longterm stats unit: {sensor_statistics.get(sensor.entity_id)})"
|
||||||
|
for sensor in sensors
|
||||||
|
if sensor.unique_id in {"rain", "daily_rain"}
|
||||||
}
|
}
|
||||||
|
|
||||||
async def async_step_init(self, user_input=None):
|
async def async_step_init(self, user_input=None):
|
||||||
"""Manage the options - show menu first."""
|
"""Manage the options - show menu first."""
|
||||||
return self.async_show_menu(step_id="init", menu_options=["basic", "windy"])
|
return self.async_show_menu(
|
||||||
|
step_id="init", menu_options=["basic", "windy", "migration"]
|
||||||
|
)
|
||||||
|
|
||||||
async def async_step_basic(self, user_input=None):
|
async def async_step_basic(self, user_input=None):
|
||||||
"""Manage basic options - credentials."""
|
"""Manage basic options - credentials."""
|
||||||
errors = {}
|
errors = {}
|
||||||
|
|
||||||
|
await self._get_entry_data()
|
||||||
|
|
||||||
if user_input is None:
|
if user_input is None:
|
||||||
return self.async_show_form(
|
return self.async_show_form(
|
||||||
step_id="basic",
|
step_id="basic",
|
||||||
|
|
@ -85,17 +162,12 @@ class ConfigOptionsFlowHandler(config_entries.OptionsFlow):
|
||||||
elif user_input[API_KEY] == user_input[API_ID]:
|
elif user_input[API_KEY] == user_input[API_ID]:
|
||||||
errors["base"] = "valid_credentials_match"
|
errors["base"] = "valid_credentials_match"
|
||||||
else:
|
else:
|
||||||
# retain Windy options
|
|
||||||
data: dict = {}
|
|
||||||
data[WINDY_API_KEY] = self.config_entry.options.get(WINDY_API_KEY)
|
|
||||||
data[WINDY_ENABLED] = self.config_entry.options.get(WINDY_ENABLED)
|
|
||||||
data[WINDY_LOGGER_ENABLED] = self.config_entry.options.get(
|
|
||||||
WINDY_LOGGER_ENABLED
|
|
||||||
)
|
|
||||||
|
|
||||||
# retain windy data
|
# retain windy data
|
||||||
user_input.update(self.windy_data)
|
user_input.update(self.windy_data)
|
||||||
|
|
||||||
|
# retain sensors
|
||||||
|
user_input.update(self.sensors)
|
||||||
|
|
||||||
return self.async_create_entry(title=DOMAIN, data=user_input)
|
return self.async_create_entry(title=DOMAIN, data=user_input)
|
||||||
|
|
||||||
self.user_data = user_input
|
self.user_data = user_input
|
||||||
|
|
@ -111,6 +183,8 @@ class ConfigOptionsFlowHandler(config_entries.OptionsFlow):
|
||||||
"""Manage windy options."""
|
"""Manage windy options."""
|
||||||
errors = {}
|
errors = {}
|
||||||
|
|
||||||
|
await self._get_entry_data()
|
||||||
|
|
||||||
if user_input is None:
|
if user_input is None:
|
||||||
return self.async_show_form(
|
return self.async_show_form(
|
||||||
step_id="windy",
|
step_id="windy",
|
||||||
|
|
@ -122,26 +196,171 @@ class ConfigOptionsFlowHandler(config_entries.OptionsFlow):
|
||||||
errors[WINDY_API_KEY] = "windy_key_required"
|
errors[WINDY_API_KEY] = "windy_key_required"
|
||||||
return self.async_show_form(
|
return self.async_show_form(
|
||||||
step_id="windy",
|
step_id="windy",
|
||||||
data_schema=self.windy_data_schema,
|
data_schema=vol.Schema(self.windy_data_schema),
|
||||||
description_placeholders={
|
|
||||||
WINDY_ENABLED: True,
|
|
||||||
WINDY_LOGGER_ENABLED: user_input[WINDY_LOGGER_ENABLED],
|
|
||||||
},
|
|
||||||
errors=errors,
|
errors=errors,
|
||||||
)
|
)
|
||||||
|
|
||||||
# retain user_data
|
# retain user_data
|
||||||
user_input.update(self.user_data)
|
user_input.update(self.user_data)
|
||||||
|
|
||||||
|
# retain senors
|
||||||
|
user_input.update(self.sensors)
|
||||||
|
|
||||||
|
return self.async_create_entry(title=DOMAIN, data=user_input)
|
||||||
|
|
||||||
|
async def async_step_migration(self, user_input=None):
|
||||||
|
"""Migrate sensors."""
|
||||||
|
|
||||||
|
errors = {}
|
||||||
|
|
||||||
|
data_schema = vol.Schema(self.migrate_sensor_select)
|
||||||
|
data_schema.schema.update()
|
||||||
|
|
||||||
|
await self._get_entry_data()
|
||||||
|
|
||||||
|
if user_input is None:
|
||||||
|
return self.async_show_form(
|
||||||
|
step_id="migration",
|
||||||
|
data_schema=vol.Schema(self.migrate_sensor_select),
|
||||||
|
errors=errors,
|
||||||
|
description_placeholders={
|
||||||
|
"migration_status": "-",
|
||||||
|
"migration_count": "-",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
self.selected_sensor = user_input.get(SENSOR_TO_MIGRATE)
|
||||||
|
|
||||||
|
return await self.async_step_migration_units()
|
||||||
|
|
||||||
|
async def async_step_migration_units(self, user_input=None):
|
||||||
|
"""Migrate unit step."""
|
||||||
|
|
||||||
|
registry = er.async_get(self.hass)
|
||||||
|
sensor_entry = registry.async_get(self.selected_sensor)
|
||||||
|
sensor_stats = await long_term_units_in_statistics_meta(self.hass)
|
||||||
|
|
||||||
|
default_unit = sensor_entry.unit_of_measurement if sensor_entry else None
|
||||||
|
|
||||||
|
if default_unit not in self.unit_values:
|
||||||
|
default_unit = self.unit_values[0]
|
||||||
|
|
||||||
|
data_schema = vol.Schema({
|
||||||
|
vol.Required(MIG_FROM, default=default_unit): vol.In(self.unit_values),
|
||||||
|
vol.Required(MIG_TO): vol.In(self.unit_values),
|
||||||
|
vol.Optional("trigger_action", default=False): bool,
|
||||||
|
})
|
||||||
|
|
||||||
|
if user_input is None:
|
||||||
|
return self.async_show_form(
|
||||||
|
step_id="migration_units",
|
||||||
|
data_schema=data_schema,
|
||||||
|
errors={},
|
||||||
|
description_placeholders={
|
||||||
|
"migration_sensor": sensor_entry.original_name,
|
||||||
|
"migration_stats": sensor_stats.get(self.selected_sensor),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
if user_input.get("trigger_action"):
|
||||||
|
self.count = await migrate_data(
|
||||||
|
self.hass,
|
||||||
|
self.selected_sensor,
|
||||||
|
user_input.get(MIG_FROM),
|
||||||
|
user_input.get(MIG_TO),
|
||||||
|
)
|
||||||
|
|
||||||
|
registry.async_update_entity(self.selected_sensor,
|
||||||
|
unit_of_measurement=user_input.get(MIG_TO),
|
||||||
|
)
|
||||||
|
|
||||||
|
state = self.hass.states.get(self.selected_sensor)
|
||||||
|
if state:
|
||||||
|
_LOGGER.info("State attributes before update: %s", state.attributes)
|
||||||
|
attributes = dict(state.attributes)
|
||||||
|
attributes["unit_of_measurement"] = user_input.get(MIG_TO)
|
||||||
|
self.hass.states.async_set(self.selected_sensor, state.state, attributes)
|
||||||
|
_LOGGER.info("State attributes after update: %s", attributes)
|
||||||
|
|
||||||
|
options = {**self.config_entry.options, "reload_sensor": self.selected_sensor}
|
||||||
|
self.hass.config_entries.async_update_entry(self.config_entry, options=options)
|
||||||
|
|
||||||
|
await self.hass.config_entries.async_reload(self.config_entry.entry_id)
|
||||||
|
|
||||||
|
await self.hass.async_block_till_done()
|
||||||
|
|
||||||
|
_LOGGER.info("Migration complete for sensor %s: %s row updated, new measurement unit: %s, ",
|
||||||
|
self.selected_sensor,
|
||||||
|
self.count,
|
||||||
|
user_input.get(MIG_TO),
|
||||||
|
)
|
||||||
|
|
||||||
|
await self._get_entry_data()
|
||||||
|
sensor_entry = er.async_get(self.hass).async_get(self.selected_sensor)
|
||||||
|
sensor_stat = await self.load_sensors_to_migrate()
|
||||||
|
|
||||||
|
return self.async_show_form(
|
||||||
|
step_id="migration_complete",
|
||||||
|
data_schema=vol.Schema({}),
|
||||||
|
errors={},
|
||||||
|
description_placeholders={
|
||||||
|
"migration_sensor": sensor_entry.unit_of_measurement,
|
||||||
|
"migration_stats": sensor_stat.get(self.selected_sensor),
|
||||||
|
"migration_count": self.count,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
# retain windy data
|
||||||
|
user_input.update(self.windy_data)
|
||||||
|
|
||||||
|
# retain user_data
|
||||||
|
user_input.update(self.user_data)
|
||||||
|
|
||||||
|
# retain senors
|
||||||
|
user_input.update(self.sensors)
|
||||||
|
|
||||||
|
return self.async_create_entry(title=DOMAIN, data=user_input)
|
||||||
|
|
||||||
|
async def async_step_migration_complete(self, user_input=None):
|
||||||
|
"""Migration complete."""
|
||||||
|
|
||||||
|
errors = {}
|
||||||
|
|
||||||
|
await self._get_entry_data()
|
||||||
|
sensor_entry = er.async_get(self.hass).async_get(self.selected_sensor)
|
||||||
|
sensor_stat = await self.load_sensors_to_migrate()
|
||||||
|
|
||||||
|
if user_input is None:
|
||||||
|
return self.async_show_form(
|
||||||
|
step_id="migration_complete",
|
||||||
|
data_schema=vol.Schema({}),
|
||||||
|
errors=errors,
|
||||||
|
description_placeholders={
|
||||||
|
"migration_sensor": sensor_entry.unit_of_measurement,
|
||||||
|
"migration_stats": sensor_stat.get(self.selected_sensor),
|
||||||
|
"migration_count": self.count,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
# retain windy data
|
||||||
|
user_input.update(self.windy_data)
|
||||||
|
|
||||||
|
# retain user_data
|
||||||
|
user_input.update(self.user_data)
|
||||||
|
|
||||||
|
# retain senors
|
||||||
|
user_input.update(self.sensors)
|
||||||
|
|
||||||
return self.async_create_entry(title=DOMAIN, data=user_input)
|
return self.async_create_entry(title=DOMAIN, data=user_input)
|
||||||
|
|
||||||
|
|
||||||
class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
class ConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||||
"""Handle a config flow for Sencor SWS 12500 Weather Station."""
|
"""Handle a config flow for Sencor SWS 12500 Weather Station."""
|
||||||
|
|
||||||
data_schema = {
|
data_schema = {
|
||||||
vol.Required(API_ID): str,
|
vol.Required(API_ID): str,
|
||||||
vol.Required(API_KEY): str,
|
vol.Required(API_KEY): str,
|
||||||
|
vol.Optional(WSLINK): bool,
|
||||||
vol.Optional(DEV_DBG): bool,
|
vol.Optional(DEV_DBG): bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -167,7 +386,9 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||||
elif user_input[API_KEY] == user_input[API_ID]:
|
elif user_input[API_KEY] == user_input[API_ID]:
|
||||||
errors["base"] = "valid_credentials_match"
|
errors["base"] = "valid_credentials_match"
|
||||||
else:
|
else:
|
||||||
return self.async_create_entry(title=DOMAIN, data=user_input, options=user_input)
|
return self.async_create_entry(
|
||||||
|
title=DOMAIN, data=user_input, options=user_input
|
||||||
|
)
|
||||||
|
|
||||||
return self.async_show_form(
|
return self.async_show_form(
|
||||||
step_id="user",
|
step_id="user",
|
||||||
|
|
@ -177,8 +398,6 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@callback
|
@callback
|
||||||
def async_get_options_flow(
|
def async_get_options_flow(config_entry) -> ConfigOptionsFlowHandler:
|
||||||
config_entry: config_entries.ConfigEntry,
|
|
||||||
) -> ConfigOptionsFlowHandler:
|
|
||||||
"""Get the options flow for this handler."""
|
"""Get the options flow for this handler."""
|
||||||
return ConfigOptionsFlowHandler(config_entry)
|
return ConfigOptionsFlowHandler()
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,24 @@
|
||||||
"""Constants."""
|
"""Constants."""
|
||||||
|
|
||||||
|
from enum import StrEnum
|
||||||
from typing import Final
|
from typing import Final
|
||||||
|
|
||||||
DOMAIN = "sws12500"
|
DOMAIN = "sws12500"
|
||||||
DEFAULT_URL = "/weatherstation/updateweatherstation.php"
|
DEFAULT_URL = "/weatherstation/updateweatherstation.php"
|
||||||
|
WSLINK_URL = "/data/upload.php"
|
||||||
WINDY_URL = "https://stations.windy.com/pws/update/"
|
WINDY_URL = "https://stations.windy.com/pws/update/"
|
||||||
|
DATABASE_PATH = "/config/home-assistant_v2.db"
|
||||||
|
|
||||||
ICON = "mdi:weather"
|
ICON = "mdi:weather"
|
||||||
|
|
||||||
API_KEY = "API_KEY"
|
API_KEY = "API_KEY"
|
||||||
API_ID = "API_ID"
|
API_ID = "API_ID"
|
||||||
|
|
||||||
|
SENSORS_TO_LOAD: Final = "sensors_to_load"
|
||||||
|
SENSOR_TO_MIGRATE: Final = "sensor_to_migrate"
|
||||||
|
|
||||||
DEV_DBG: Final = "dev_debug_checkbox"
|
DEV_DBG: Final = "dev_debug_checkbox"
|
||||||
|
WSLINK: Final = "wslink"
|
||||||
|
|
||||||
WINDY_API_KEY = "WINDY_API_KEY"
|
WINDY_API_KEY = "WINDY_API_KEY"
|
||||||
WINDY_ENABLED: Final = "windy_enabled_checkbox"
|
WINDY_ENABLED: Final = "windy_enabled_checkbox"
|
||||||
|
|
@ -50,14 +57,21 @@ PURGE_DATA: Final = [
|
||||||
"dailyrainin",
|
"dailyrainin",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
BARO_PRESSURE: Final = "baro_pressure"
|
BARO_PRESSURE: Final = "baro_pressure"
|
||||||
OUTSIDE_TEMP: Final = "outside_temp"
|
OUTSIDE_TEMP: Final = "outside_temp"
|
||||||
DEW_POINT: Final = "dew_point"
|
DEW_POINT: Final = "dew_point"
|
||||||
OUTSIDE_HUMIDITY: Final = "outside_humidity"
|
OUTSIDE_HUMIDITY: Final = "outside_humidity"
|
||||||
|
OUTSIDE_CONNECTION: Final = "outside_connection"
|
||||||
WIND_SPEED: Final = "wind_speed"
|
WIND_SPEED: Final = "wind_speed"
|
||||||
WIND_GUST: Final = "wind_gust"
|
WIND_GUST: Final = "wind_gust"
|
||||||
WIND_DIR: Final = "wind_dir"
|
WIND_DIR: Final = "wind_dir"
|
||||||
|
WIND_AZIMUT: Final = "wind_azimut"
|
||||||
RAIN: Final = "rain"
|
RAIN: Final = "rain"
|
||||||
|
HOURLY_RAIN: Final = "hourly_rain"
|
||||||
|
WEEKLY_RAIN: Final = "weekly_rain"
|
||||||
|
MONTHLY_RAIN: Final = "monthly_rain"
|
||||||
|
YEARLY_RAIN: Final = "yearly_rain"
|
||||||
DAILY_RAIN: Final = "daily_rain"
|
DAILY_RAIN: Final = "daily_rain"
|
||||||
SOLAR_RADIATION: Final = "solar_radiation"
|
SOLAR_RADIATION: Final = "solar_radiation"
|
||||||
INDOOR_TEMP: Final = "indoor_temp"
|
INDOOR_TEMP: Final = "indoor_temp"
|
||||||
|
|
@ -65,6 +79,15 @@ INDOOR_HUMIDITY: Final = "indoor_humidity"
|
||||||
UV: Final = "uv"
|
UV: Final = "uv"
|
||||||
CH2_TEMP: Final = "ch2_temp"
|
CH2_TEMP: Final = "ch2_temp"
|
||||||
CH2_HUMIDITY: Final = "ch2_humidity"
|
CH2_HUMIDITY: Final = "ch2_humidity"
|
||||||
|
CH2_CONNECTION: Final = "ch2_connection"
|
||||||
|
CH3_TEMP: Final = "ch3_temp"
|
||||||
|
CH3_HUMIDITY: Final = "ch3_humidity"
|
||||||
|
CH3_CONNECTION: Final = "ch3_connection"
|
||||||
|
CH4_TEMP: Final = "ch4_temp"
|
||||||
|
CH4_HUMIDITY: Final = "ch4_humidity"
|
||||||
|
CH4_CONNECTION: Final = "ch4_connection"
|
||||||
|
HEAT_INDEX: Final = "heat_index"
|
||||||
|
CHILL_INDEX: Final = "chill_index"
|
||||||
|
|
||||||
|
|
||||||
REMAP_ITEMS: dict = {
|
REMAP_ITEMS: dict = {
|
||||||
|
|
@ -83,4 +106,92 @@ REMAP_ITEMS: dict = {
|
||||||
"UV": UV,
|
"UV": UV,
|
||||||
"soiltempf": CH2_TEMP,
|
"soiltempf": CH2_TEMP,
|
||||||
"soilmoisture": CH2_HUMIDITY,
|
"soilmoisture": CH2_HUMIDITY,
|
||||||
|
"soiltemp2f": CH3_TEMP,
|
||||||
|
"soilmoisture2": CH3_HUMIDITY,
|
||||||
|
"soiltemp3f": CH4_TEMP,
|
||||||
|
"soilmoisture3": CH4_HUMIDITY,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
REMAP_WSLINK_ITEMS: dict = {
|
||||||
|
"intem": INDOOR_TEMP,
|
||||||
|
"inhum": INDOOR_HUMIDITY,
|
||||||
|
"t1tem": OUTSIDE_TEMP,
|
||||||
|
"t1hum": OUTSIDE_HUMIDITY,
|
||||||
|
"t1dew": DEW_POINT,
|
||||||
|
"t1wdir": WIND_DIR,
|
||||||
|
"t1ws": WIND_SPEED,
|
||||||
|
"t1wgust": WIND_GUST,
|
||||||
|
"t1rainra": RAIN,
|
||||||
|
"t1raindy": DAILY_RAIN,
|
||||||
|
"t1solrad": SOLAR_RADIATION,
|
||||||
|
"rbar": BARO_PRESSURE,
|
||||||
|
"t1uvi": UV,
|
||||||
|
"t234c1tem": CH2_TEMP,
|
||||||
|
"t234c1hum": CH2_HUMIDITY,
|
||||||
|
"t1cn": OUTSIDE_CONNECTION,
|
||||||
|
"t234c1cn": CH2_CONNECTION,
|
||||||
|
"t234c2cn": CH3_CONNECTION,
|
||||||
|
"t1chill": CHILL_INDEX,
|
||||||
|
"t1heat": HEAT_INDEX,
|
||||||
|
"t1rainhr": HOURLY_RAIN,
|
||||||
|
"t1rainwy": WEEKLY_RAIN,
|
||||||
|
"t1rainmth": MONTHLY_RAIN,
|
||||||
|
"t1rainyr": YEARLY_RAIN,
|
||||||
|
}
|
||||||
|
|
||||||
|
# TODO: Add more sensors
|
||||||
|
#
|
||||||
|
# 'inbat' indoor battery level (1 normal, 0 low)
|
||||||
|
# 't1bat': outdoor battery level (1 normal, 0 low)
|
||||||
|
# 't234c1bat': CH2 battery level (1 normal, 0 low) CH2 in integration is CH1 in WSLink
|
||||||
|
|
||||||
|
|
||||||
|
DISABLED_BY_DEFAULT: Final = [
|
||||||
|
CH2_TEMP,
|
||||||
|
CH2_HUMIDITY,
|
||||||
|
CH3_TEMP,
|
||||||
|
CH3_HUMIDITY,
|
||||||
|
CH4_TEMP,
|
||||||
|
CH4_HUMIDITY,
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class UnitOfDir(StrEnum):
|
||||||
|
"""Wind direrction azimut."""
|
||||||
|
|
||||||
|
NNE = "nne"
|
||||||
|
NE = "ne"
|
||||||
|
ENE = "ene"
|
||||||
|
E = "e"
|
||||||
|
ESE = "ese"
|
||||||
|
SE = "se"
|
||||||
|
SSE = "sse"
|
||||||
|
S = "s"
|
||||||
|
SSW = "ssw"
|
||||||
|
SW = "sw"
|
||||||
|
WSW = "wsw"
|
||||||
|
W = "w"
|
||||||
|
WNW = "wnw"
|
||||||
|
NW = "nw"
|
||||||
|
NNW = "nnw"
|
||||||
|
N = "n"
|
||||||
|
|
||||||
|
|
||||||
|
AZIMUT: list[UnitOfDir] = [
|
||||||
|
UnitOfDir.NNE,
|
||||||
|
UnitOfDir.NE,
|
||||||
|
UnitOfDir.ENE,
|
||||||
|
UnitOfDir.E,
|
||||||
|
UnitOfDir.ESE,
|
||||||
|
UnitOfDir.SE,
|
||||||
|
UnitOfDir.SSE,
|
||||||
|
UnitOfDir.S,
|
||||||
|
UnitOfDir.SSW,
|
||||||
|
UnitOfDir.SW,
|
||||||
|
UnitOfDir.WSW,
|
||||||
|
UnitOfDir.W,
|
||||||
|
UnitOfDir.WNW,
|
||||||
|
UnitOfDir.NW,
|
||||||
|
UnitOfDir.NNW,
|
||||||
|
UnitOfDir.N,
|
||||||
|
]
|
||||||
|
|
|
||||||
|
|
@ -3,13 +3,13 @@
|
||||||
"name": "Sencor SWS 12500 Weather Station",
|
"name": "Sencor SWS 12500 Weather Station",
|
||||||
"codeowners": ["@schizza"],
|
"codeowners": ["@schizza"],
|
||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
"issue_tracker": "https://github.com/schizza/SWS-12500-custom-component/issues",
|
|
||||||
"dependencies": ["http"],
|
"dependencies": ["http"],
|
||||||
"documentation": "https://github.com/schizza/SWS-12500-custom-component",
|
"documentation": "https://github.com/schizza/SWS-12500-custom-component",
|
||||||
"homekit": {},
|
"homekit": {},
|
||||||
"iot_class": "local_push",
|
"iot_class": "local_push",
|
||||||
|
"issue_tracker": "https://github.com/schizza/SWS-12500-custom-component/issues",
|
||||||
"requirements": [],
|
"requirements": [],
|
||||||
"ssdp": [],
|
"ssdp": [],
|
||||||
"version": "0.1.1",
|
"version": "1.6.2",
|
||||||
"zeroconf": []
|
"zeroconf": []
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,46 +0,0 @@
|
||||||
@property
|
|
||||||
def translation_key(self):
|
|
||||||
"""Return translation key."""
|
|
||||||
return self.entity_description.translation_key
|
|
||||||
|
|
||||||
@property
|
|
||||||
def device_class(self):
|
|
||||||
"""Return device class."""
|
|
||||||
return self.entity_description.device_class
|
|
||||||
|
|
||||||
@property
|
|
||||||
def name(self) -> str:
|
|
||||||
"""Return the name of the switch."""
|
|
||||||
return str(self.entity_description.name)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def unique_id(self) -> str:
|
|
||||||
"""Return a unique, Home Assistant friendly identifier for this entity."""
|
|
||||||
return self.entity_description.key
|
|
||||||
|
|
||||||
@property
|
|
||||||
def native_value(self):
|
|
||||||
"""Return value of entity."""
|
|
||||||
return self._state
|
|
||||||
|
|
||||||
@property
|
|
||||||
def icon(self) -> str:
|
|
||||||
"""Return icon of entity."""
|
|
||||||
return str(self.entity_description.icon)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def native_unit_of_measurement(self) -> str:
|
|
||||||
"""Return unit of measurement."""
|
|
||||||
return str(self.entity_description.native_unit_of_measurement)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def state_class(self) -> str:
|
|
||||||
"""Return stateClass."""
|
|
||||||
|
|
||||||
return str(self.entity_description.state_class)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def suggested_unit_of_measurement(self) -> str:
|
|
||||||
"""Return sugestet_unit_of_measurement."""
|
|
||||||
return str(self.entity_description.suggested_unit_of_measurement)
|
|
||||||
|
|
||||||
|
|
@ -0,0 +1,76 @@
|
||||||
|
"""Store routes info."""
|
||||||
|
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from logging import getLogger
|
||||||
|
|
||||||
|
from aiohttp.web import AbstractRoute, Response
|
||||||
|
|
||||||
|
_LOGGER = getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Route:
|
||||||
|
"""Store route info."""
|
||||||
|
|
||||||
|
url_path: str
|
||||||
|
route: AbstractRoute
|
||||||
|
handler: callable
|
||||||
|
enabled: bool = False
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
"""Return string representation."""
|
||||||
|
return f"{self.url_path} -> {self.handler}"
|
||||||
|
|
||||||
|
|
||||||
|
class Routes:
|
||||||
|
"""Store routes info."""
|
||||||
|
|
||||||
|
def __init__(self) -> None:
|
||||||
|
"""Initialize routes."""
|
||||||
|
self.routes = {}
|
||||||
|
|
||||||
|
def switch_route(self, coordinator: callable, url_path: str):
|
||||||
|
"""Switch route."""
|
||||||
|
|
||||||
|
for url, route in self.routes.items():
|
||||||
|
if url == url_path:
|
||||||
|
_LOGGER.info("New coordinator to route: %s", route.url_path)
|
||||||
|
route.enabled = True
|
||||||
|
route.handler = coordinator
|
||||||
|
route.route._handler = coordinator # noqa: SLF001
|
||||||
|
else:
|
||||||
|
route.enabled = False
|
||||||
|
route.handler = unregistred
|
||||||
|
route.route._handler = unregistred # noqa: SLF001
|
||||||
|
|
||||||
|
def add_route(
|
||||||
|
self,
|
||||||
|
url_path: str,
|
||||||
|
route: AbstractRoute,
|
||||||
|
handler: callable,
|
||||||
|
enabled: bool = False,
|
||||||
|
):
|
||||||
|
"""Add route."""
|
||||||
|
self.routes[url_path] = Route(url_path, route, handler, enabled)
|
||||||
|
|
||||||
|
def get_route(self, url_path: str) -> Route:
|
||||||
|
"""Get route."""
|
||||||
|
return self.routes.get(url_path)
|
||||||
|
|
||||||
|
def get_enabled(self) -> str:
|
||||||
|
"""Get enabled routes."""
|
||||||
|
enabled_routes = [
|
||||||
|
route.url_path for route in self.routes.values() if route.enabled
|
||||||
|
]
|
||||||
|
return "".join(enabled_routes) if enabled_routes else "None"
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
"""Return string representation."""
|
||||||
|
return "\n".join([str(route) for route in self.routes.values()])
|
||||||
|
|
||||||
|
|
||||||
|
async def unregistred(*args, **kwargs):
|
||||||
|
"""Unregister path to handle incoming data."""
|
||||||
|
|
||||||
|
_LOGGER.error("Recieved data to unregistred webhook. Check your settings")
|
||||||
|
return Response(body=f"{'Unregistred webhook.'}", status=404)
|
||||||
|
|
@ -1,194 +1,73 @@
|
||||||
"""Sensors definition for SWS12500."""
|
"""Sensors definition for SWS12500."""
|
||||||
from dataclasses import dataclass
|
|
||||||
from collections.abc import Callable
|
|
||||||
from typing import Any
|
|
||||||
|
|
||||||
from homeassistant.components.sensor import (
|
import logging
|
||||||
SensorDeviceClass,
|
|
||||||
SensorEntity,
|
from homeassistant.components.sensor import RestoreSensor, SensorEntity
|
||||||
SensorEntityDescription,
|
|
||||||
SensorStateClass,
|
|
||||||
)
|
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import (
|
|
||||||
UnitOfIrradiance,
|
|
||||||
UnitOfPrecipitationDepth,
|
|
||||||
UnitOfVolumetricFlux,
|
|
||||||
UnitOfPressure,
|
|
||||||
UnitOfSpeed,
|
|
||||||
UnitOfTemperature,
|
|
||||||
DEGREE,
|
|
||||||
UV_INDEX,
|
|
||||||
PERCENTAGE
|
|
||||||
)
|
|
||||||
from homeassistant.core import HomeAssistant, callback
|
from homeassistant.core import HomeAssistant, callback
|
||||||
from homeassistant.helpers.device_registry import DeviceEntryType
|
from homeassistant.helpers.device_registry import DeviceEntryType
|
||||||
from homeassistant.helpers.entity import DeviceInfo
|
from homeassistant.helpers.entity import DeviceInfo, generate_entity_id
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
from homeassistant.helpers.typing import StateType
|
|
||||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||||
|
|
||||||
from . import WeatherDataUpdateCoordinator
|
from . import WeatherDataUpdateCoordinator
|
||||||
from .const import (
|
from .const import (
|
||||||
BARO_PRESSURE,
|
CHILL_INDEX,
|
||||||
CH2_HUMIDITY,
|
|
||||||
CH2_TEMP,
|
|
||||||
DAILY_RAIN,
|
|
||||||
DEW_POINT,
|
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
INDOOR_HUMIDITY,
|
HEAT_INDEX,
|
||||||
INDOOR_TEMP,
|
|
||||||
OUTSIDE_HUMIDITY,
|
OUTSIDE_HUMIDITY,
|
||||||
OUTSIDE_TEMP,
|
OUTSIDE_TEMP,
|
||||||
RAIN,
|
SENSORS_TO_LOAD,
|
||||||
SOLAR_RADIATION,
|
WIND_AZIMUT,
|
||||||
UV,
|
|
||||||
WIND_DIR,
|
WIND_DIR,
|
||||||
WIND_GUST,
|
|
||||||
WIND_SPEED,
|
WIND_SPEED,
|
||||||
|
WSLINK,
|
||||||
)
|
)
|
||||||
|
from .sensors_common import WeatherSensorEntityDescription
|
||||||
|
from .sensors_weather import SENSOR_TYPES_WEATHER_API
|
||||||
|
from .sensors_wslink import SENSOR_TYPES_WSLINK
|
||||||
|
from .utils import chill_index, heat_index
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
@dataclass
|
|
||||||
class WeatherSensorEntityDescription(SensorEntityDescription):
|
|
||||||
"""Describe Weather Sensor entities."""
|
|
||||||
|
|
||||||
attr_fn: Callable[[dict[str, Any]], dict[str, StateType]] = lambda _: {}
|
|
||||||
unit_fn: Callable[[bool], str | None] = lambda _: None
|
|
||||||
|
|
||||||
SENSOR_TYPES: tuple[WeatherSensorEntityDescription, ...] = (
|
|
||||||
WeatherSensorEntityDescription(
|
|
||||||
key=INDOOR_TEMP,
|
|
||||||
native_unit_of_measurement=UnitOfTemperature.FAHRENHEIT,
|
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
|
||||||
icon="mdi:thermometer",
|
|
||||||
device_class=SensorDeviceClass.TEMPERATURE,
|
|
||||||
translation_key=INDOOR_TEMP,
|
|
||||||
),
|
|
||||||
WeatherSensorEntityDescription(
|
|
||||||
key=INDOOR_HUMIDITY,
|
|
||||||
native_unit_of_measurement=PERCENTAGE,
|
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
|
||||||
icon="mdi:thermometer",
|
|
||||||
device_class=SensorDeviceClass.HUMIDITY,
|
|
||||||
translation_key=INDOOR_HUMIDITY,
|
|
||||||
),
|
|
||||||
WeatherSensorEntityDescription(
|
|
||||||
key=OUTSIDE_TEMP,
|
|
||||||
native_unit_of_measurement=UnitOfTemperature.FAHRENHEIT,
|
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
|
||||||
icon="mdi:thermometer",
|
|
||||||
device_class=SensorDeviceClass.TEMPERATURE,
|
|
||||||
translation_key=OUTSIDE_TEMP,
|
|
||||||
),
|
|
||||||
WeatherSensorEntityDescription(
|
|
||||||
key=OUTSIDE_HUMIDITY,
|
|
||||||
native_unit_of_measurement=PERCENTAGE,
|
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
|
||||||
icon="mdi:thermometer",
|
|
||||||
device_class=SensorDeviceClass.HUMIDITY,
|
|
||||||
translation_key=OUTSIDE_HUMIDITY,
|
|
||||||
),
|
|
||||||
WeatherSensorEntityDescription(
|
|
||||||
key=DEW_POINT,
|
|
||||||
native_unit_of_measurement=UnitOfTemperature.FAHRENHEIT,
|
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
|
||||||
icon="mdi:thermometer-lines",
|
|
||||||
device_class=SensorDeviceClass.TEMPERATURE,
|
|
||||||
translation_key=DEW_POINT,
|
|
||||||
),
|
|
||||||
WeatherSensorEntityDescription(
|
|
||||||
key=BARO_PRESSURE,
|
|
||||||
native_unit_of_measurement=UnitOfPressure.INHG,
|
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
|
||||||
icon="mdi:thermometer-lines",
|
|
||||||
device_class=SensorDeviceClass.ATMOSPHERIC_PRESSURE,
|
|
||||||
suggested_unit_of_measurement=UnitOfPressure.HPA,
|
|
||||||
translation_key=BARO_PRESSURE,
|
|
||||||
),
|
|
||||||
WeatherSensorEntityDescription(
|
|
||||||
key=WIND_SPEED,
|
|
||||||
native_unit_of_measurement=UnitOfSpeed.MILES_PER_HOUR,
|
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
|
||||||
device_class=SensorDeviceClass.WIND_SPEED,
|
|
||||||
suggested_unit_of_measurement=UnitOfSpeed.KILOMETERS_PER_HOUR,
|
|
||||||
icon="mdi:weather-windy",
|
|
||||||
translation_key=WIND_SPEED,
|
|
||||||
),
|
|
||||||
WeatherSensorEntityDescription(
|
|
||||||
key=WIND_GUST,
|
|
||||||
native_unit_of_measurement=UnitOfSpeed.MILES_PER_HOUR,
|
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
|
||||||
device_class=SensorDeviceClass.WIND_SPEED,
|
|
||||||
suggested_unit_of_measurement=UnitOfSpeed.KILOMETERS_PER_HOUR,
|
|
||||||
icon="mdi:windsock",
|
|
||||||
translation_key=WIND_GUST,
|
|
||||||
),
|
|
||||||
WeatherSensorEntityDescription(
|
|
||||||
key=WIND_DIR,
|
|
||||||
native_unit_of_measurement=DEGREE,
|
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
|
||||||
suggested_display_precision=None,
|
|
||||||
icon="mdi:sign-direction",
|
|
||||||
translation_key=WIND_DIR,
|
|
||||||
),
|
|
||||||
WeatherSensorEntityDescription(
|
|
||||||
key=RAIN,
|
|
||||||
native_unit_of_measurement=UnitOfPrecipitationDepth.INCHES,
|
|
||||||
device_class=SensorDeviceClass.PRECIPITATION,
|
|
||||||
state_class=SensorStateClass.TOTAL,
|
|
||||||
suggested_unit_of_measurement=UnitOfPrecipitationDepth.MILLIMETERS,
|
|
||||||
suggested_display_precision=2,
|
|
||||||
icon="mdi:weather-pouring",
|
|
||||||
translation_key=RAIN,
|
|
||||||
),
|
|
||||||
WeatherSensorEntityDescription(
|
|
||||||
key=DAILY_RAIN,
|
|
||||||
native_unit_of_measurement=UnitOfVolumetricFlux.INCHES_PER_DAY,
|
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
|
||||||
device_class=SensorDeviceClass.PRECIPITATION_INTENSITY,
|
|
||||||
suggested_unit_of_measurement=UnitOfVolumetricFlux.MILLIMETERS_PER_DAY,
|
|
||||||
suggested_display_precision=2,
|
|
||||||
icon="mdi:weather-pouring",
|
|
||||||
translation_key=DAILY_RAIN,
|
|
||||||
),
|
|
||||||
WeatherSensorEntityDescription(
|
|
||||||
key=SOLAR_RADIATION,
|
|
||||||
native_unit_of_measurement=UnitOfIrradiance.WATTS_PER_SQUARE_METER,
|
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
|
||||||
device_class=SensorDeviceClass.IRRADIANCE,
|
|
||||||
icon="mdi:weather-sunny",
|
|
||||||
translation_key=SOLAR_RADIATION,
|
|
||||||
),
|
|
||||||
WeatherSensorEntityDescription(
|
|
||||||
key=UV,
|
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
|
||||||
native_unit_of_measurement=UV_INDEX,
|
|
||||||
icon="mdi:sunglasses",
|
|
||||||
translation_key=UV,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(
|
async def async_setup_entry(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
config_entry: ConfigEntry,
|
config_entry: ConfigEntry,
|
||||||
async_add_entities: AddEntitiesCallback,
|
async_add_entities: AddEntitiesCallback,
|
||||||
discovery_info=None,
|
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Set up Weather Station sensors."""
|
"""Set up Weather Station sensors."""
|
||||||
|
|
||||||
coordinator: WeatherDataUpdateCoordinator = hass.data[DOMAIN][config_entry.entry_id]
|
coordinator: WeatherDataUpdateCoordinator = hass.data[DOMAIN][config_entry.entry_id]
|
||||||
sensors = []
|
|
||||||
for description in SENSOR_TYPES:
|
sensors_to_load: list = []
|
||||||
sensors.append(WeatherSensor(hass, description, coordinator))
|
sensors: list = []
|
||||||
|
_wslink = config_entry.data.get(WSLINK)
|
||||||
|
|
||||||
|
SENSOR_TYPES = SENSOR_TYPES_WSLINK if _wslink else SENSOR_TYPES_WEATHER_API
|
||||||
|
|
||||||
|
# Check if we have some sensors to load.
|
||||||
|
if sensors_to_load := config_entry.options.get(SENSORS_TO_LOAD):
|
||||||
|
if WIND_DIR in sensors_to_load:
|
||||||
|
sensors_to_load.append(WIND_AZIMUT)
|
||||||
|
if (OUTSIDE_HUMIDITY in sensors_to_load) and (OUTSIDE_TEMP in sensors_to_load):
|
||||||
|
sensors_to_load.append(HEAT_INDEX)
|
||||||
|
|
||||||
|
if (WIND_SPEED in sensors_to_load) and (OUTSIDE_TEMP in sensors_to_load):
|
||||||
|
sensors_to_load.append(CHILL_INDEX)
|
||||||
|
sensors = [
|
||||||
|
WeatherSensor(hass, description, coordinator)
|
||||||
|
for description in SENSOR_TYPES
|
||||||
|
if description.key in sensors_to_load
|
||||||
|
]
|
||||||
async_add_entities(sensors)
|
async_add_entities(sensors)
|
||||||
|
|
||||||
|
|
||||||
class WeatherSensor(CoordinatorEntity[WeatherDataUpdateCoordinator], SensorEntity):
|
class WeatherSensor(
|
||||||
|
CoordinatorEntity[WeatherDataUpdateCoordinator], RestoreSensor, SensorEntity
|
||||||
|
):
|
||||||
"""Implementation of Weather Sensor entity."""
|
"""Implementation of Weather Sensor entity."""
|
||||||
|
|
||||||
entity_description: WeatherSensorEntityDescription
|
|
||||||
_attr_has_entity_name = True
|
_attr_has_entity_name = True
|
||||||
_attr_should_poll = False
|
_attr_should_poll = False
|
||||||
|
|
||||||
|
|
@ -203,37 +82,60 @@ class WeatherSensor(CoordinatorEntity[WeatherDataUpdateCoordinator], SensorEntit
|
||||||
self.hass = hass
|
self.hass = hass
|
||||||
self.coordinator = coordinator
|
self.coordinator = coordinator
|
||||||
self.entity_description = description
|
self.entity_description = description
|
||||||
self._state: StateType = None
|
self._attr_unique_id = description.key
|
||||||
self._available = False
|
self._data = None
|
||||||
self._attr_unique_id = f"{DOMAIN}.{description.key}"
|
|
||||||
|
async def async_added_to_hass(self) -> None:
|
||||||
|
"""Handle listeners to reloaded sensors."""
|
||||||
|
|
||||||
|
await super().async_added_to_hass()
|
||||||
|
|
||||||
|
self.coordinator.async_add_listener(self._handle_coordinator_update)
|
||||||
|
|
||||||
|
# prev_state_data = await self.async_get_last_sensor_data()
|
||||||
|
# prev_state = await self.async_get_last_state()
|
||||||
|
# if not prev_state:
|
||||||
|
# return
|
||||||
|
# self._data = prev_state_data.native_value
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def _handle_coordinator_update(self) -> None:
|
def _handle_coordinator_update(self) -> None:
|
||||||
"""Handle updated data from the coordinator."""
|
"""Handle updated data from the coordinator."""
|
||||||
self._state = self.coordinator.data.get(self.entity_description.key)
|
self._data = self.coordinator.data.get(self.entity_description.key)
|
||||||
|
|
||||||
|
super()._handle_coordinator_update()
|
||||||
|
|
||||||
self.async_write_ha_state()
|
self.async_write_ha_state()
|
||||||
self.async_registry_entry_updated()
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def unique_id(self) -> str:
|
def native_value(self) -> str | int | float | None:
|
||||||
"""Return a unique, Home Assistant friendly identifier for this entity."""
|
|
||||||
return self.entity_description.key
|
|
||||||
|
|
||||||
@property
|
|
||||||
def native_value(self) -> None:
|
|
||||||
"""Return value of entity."""
|
"""Return value of entity."""
|
||||||
return self._state
|
|
||||||
|
_wslink = self.coordinator.config.options.get(WSLINK)
|
||||||
|
|
||||||
|
if self.coordinator.data and (WIND_AZIMUT in self.entity_description.key):
|
||||||
|
return self.entity_description.value_fn(self.coordinator.data.get(WIND_DIR))
|
||||||
|
|
||||||
|
if (
|
||||||
|
self.coordinator.data
|
||||||
|
and (HEAT_INDEX in self.entity_description.key)
|
||||||
|
and not _wslink
|
||||||
|
):
|
||||||
|
return self.entity_description.value_fn(heat_index(self.coordinator.data))
|
||||||
|
|
||||||
|
if (
|
||||||
|
self.coordinator.data
|
||||||
|
and (CHILL_INDEX in self.entity_description.key)
|
||||||
|
and not _wslink
|
||||||
|
):
|
||||||
|
return self.entity_description.value_fn(chill_index(self.coordinator.data))
|
||||||
|
|
||||||
|
return None if self._data == "" else self.entity_description.value_fn(self._data)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def native_unit_of_measurement(self) -> str:
|
def suggested_entity_id(self) -> str:
|
||||||
"""Return unit of measurement."""
|
"""Return name."""
|
||||||
return str(self.entity_description.native_unit_of_measurement)
|
return generate_entity_id("sensor.{}", self.entity_description.key)
|
||||||
|
|
||||||
@property
|
|
||||||
def state_class(self) -> str:
|
|
||||||
"""Return stateClass."""
|
|
||||||
return str(self.entity_description.state_class)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def device_info(self) -> DeviceInfo:
|
def device_info(self) -> DeviceInfo:
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
"""Common classes for sensors."""
|
||||||
|
|
||||||
|
from collections.abc import Callable
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from homeassistant.components.sensor import SensorEntityDescription
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True, kw_only=True)
|
||||||
|
class WeatherSensorEntityDescription(SensorEntityDescription):
|
||||||
|
"""Describe Weather Sensor entities."""
|
||||||
|
|
||||||
|
value_fn: Callable[[Any], int | float | str | None]
|
||||||
|
|
@ -0,0 +1,258 @@
|
||||||
|
"""Sensor entities for the SWS12500 integration for old endpoint."""
|
||||||
|
|
||||||
|
from typing import cast
|
||||||
|
|
||||||
|
from homeassistant.components.sensor import SensorDeviceClass, SensorStateClass
|
||||||
|
from homeassistant.const import (
|
||||||
|
DEGREE,
|
||||||
|
PERCENTAGE,
|
||||||
|
UV_INDEX,
|
||||||
|
UnitOfIrradiance,
|
||||||
|
UnitOfPrecipitationDepth,
|
||||||
|
UnitOfPressure,
|
||||||
|
UnitOfSpeed,
|
||||||
|
UnitOfTemperature,
|
||||||
|
UnitOfVolumetricFlux,
|
||||||
|
)
|
||||||
|
|
||||||
|
from .const import (
|
||||||
|
BARO_PRESSURE,
|
||||||
|
CH2_HUMIDITY,
|
||||||
|
CH2_TEMP,
|
||||||
|
CH3_HUMIDITY,
|
||||||
|
CH3_TEMP,
|
||||||
|
CH4_HUMIDITY,
|
||||||
|
CH4_TEMP,
|
||||||
|
CHILL_INDEX,
|
||||||
|
DAILY_RAIN,
|
||||||
|
DEW_POINT,
|
||||||
|
HEAT_INDEX,
|
||||||
|
INDOOR_HUMIDITY,
|
||||||
|
INDOOR_TEMP,
|
||||||
|
OUTSIDE_HUMIDITY,
|
||||||
|
OUTSIDE_TEMP,
|
||||||
|
RAIN,
|
||||||
|
SOLAR_RADIATION,
|
||||||
|
UV,
|
||||||
|
WIND_AZIMUT,
|
||||||
|
WIND_DIR,
|
||||||
|
WIND_GUST,
|
||||||
|
WIND_SPEED,
|
||||||
|
UnitOfDir,
|
||||||
|
)
|
||||||
|
from .sensors_common import WeatherSensorEntityDescription
|
||||||
|
from .utils import wind_dir_to_text
|
||||||
|
|
||||||
|
SENSOR_TYPES_WEATHER_API: tuple[WeatherSensorEntityDescription, ...] = (
|
||||||
|
WeatherSensorEntityDescription(
|
||||||
|
key=INDOOR_TEMP,
|
||||||
|
native_unit_of_measurement=UnitOfTemperature.FAHRENHEIT,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
icon="mdi:thermometer",
|
||||||
|
device_class=SensorDeviceClass.TEMPERATURE,
|
||||||
|
translation_key=INDOOR_TEMP,
|
||||||
|
value_fn=lambda data: cast("float", data),
|
||||||
|
),
|
||||||
|
WeatherSensorEntityDescription(
|
||||||
|
key=INDOOR_HUMIDITY,
|
||||||
|
native_unit_of_measurement=PERCENTAGE,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
icon="mdi:thermometer",
|
||||||
|
device_class=SensorDeviceClass.HUMIDITY,
|
||||||
|
translation_key=INDOOR_HUMIDITY,
|
||||||
|
value_fn=lambda data: cast("int", data),
|
||||||
|
),
|
||||||
|
WeatherSensorEntityDescription(
|
||||||
|
key=OUTSIDE_TEMP,
|
||||||
|
native_unit_of_measurement=UnitOfTemperature.FAHRENHEIT,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
icon="mdi:thermometer",
|
||||||
|
device_class=SensorDeviceClass.TEMPERATURE,
|
||||||
|
translation_key=OUTSIDE_TEMP,
|
||||||
|
value_fn=lambda data: cast("float", data),
|
||||||
|
),
|
||||||
|
WeatherSensorEntityDescription(
|
||||||
|
key=OUTSIDE_HUMIDITY,
|
||||||
|
native_unit_of_measurement=PERCENTAGE,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
icon="mdi:thermometer",
|
||||||
|
device_class=SensorDeviceClass.HUMIDITY,
|
||||||
|
translation_key=OUTSIDE_HUMIDITY,
|
||||||
|
value_fn=lambda data: cast("int", data),
|
||||||
|
),
|
||||||
|
WeatherSensorEntityDescription(
|
||||||
|
key=DEW_POINT,
|
||||||
|
native_unit_of_measurement=UnitOfTemperature.FAHRENHEIT,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
icon="mdi:thermometer-lines",
|
||||||
|
device_class=SensorDeviceClass.TEMPERATURE,
|
||||||
|
translation_key=DEW_POINT,
|
||||||
|
value_fn=lambda data: cast("float", data),
|
||||||
|
),
|
||||||
|
WeatherSensorEntityDescription(
|
||||||
|
key=BARO_PRESSURE,
|
||||||
|
native_unit_of_measurement=UnitOfPressure.INHG,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
icon="mdi:thermometer-lines",
|
||||||
|
device_class=SensorDeviceClass.ATMOSPHERIC_PRESSURE,
|
||||||
|
suggested_unit_of_measurement=UnitOfPressure.HPA,
|
||||||
|
translation_key=BARO_PRESSURE,
|
||||||
|
value_fn=lambda data: cast("float", data),
|
||||||
|
),
|
||||||
|
WeatherSensorEntityDescription(
|
||||||
|
key=WIND_SPEED,
|
||||||
|
native_unit_of_measurement=UnitOfSpeed.MILES_PER_HOUR,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
device_class=SensorDeviceClass.WIND_SPEED,
|
||||||
|
suggested_unit_of_measurement=UnitOfSpeed.KILOMETERS_PER_HOUR,
|
||||||
|
icon="mdi:weather-windy",
|
||||||
|
translation_key=WIND_SPEED,
|
||||||
|
value_fn=lambda data: cast("int", data),
|
||||||
|
),
|
||||||
|
WeatherSensorEntityDescription(
|
||||||
|
key=WIND_GUST,
|
||||||
|
native_unit_of_measurement=UnitOfSpeed.MILES_PER_HOUR,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
device_class=SensorDeviceClass.WIND_SPEED,
|
||||||
|
suggested_unit_of_measurement=UnitOfSpeed.KILOMETERS_PER_HOUR,
|
||||||
|
icon="mdi:windsock",
|
||||||
|
translation_key=WIND_GUST,
|
||||||
|
value_fn=lambda data: cast("float", data),
|
||||||
|
),
|
||||||
|
WeatherSensorEntityDescription(
|
||||||
|
key=WIND_DIR,
|
||||||
|
native_unit_of_measurement=DEGREE,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
suggested_display_precision=None,
|
||||||
|
icon="mdi:sign-direction",
|
||||||
|
translation_key=WIND_DIR,
|
||||||
|
value_fn=lambda data: cast("int", data),
|
||||||
|
),
|
||||||
|
WeatherSensorEntityDescription(
|
||||||
|
key=WIND_AZIMUT,
|
||||||
|
icon="mdi:sign-direction",
|
||||||
|
value_fn=lambda data: cast("str", wind_dir_to_text(data)),
|
||||||
|
device_class=SensorDeviceClass.ENUM,
|
||||||
|
options=list(UnitOfDir),
|
||||||
|
translation_key=WIND_AZIMUT,
|
||||||
|
),
|
||||||
|
WeatherSensorEntityDescription(
|
||||||
|
key=RAIN,
|
||||||
|
native_unit_of_measurement=UnitOfPrecipitationDepth.INCHES,
|
||||||
|
device_class=SensorDeviceClass.PRECIPITATION,
|
||||||
|
state_class=SensorStateClass.TOTAL,
|
||||||
|
suggested_unit_of_measurement=UnitOfPrecipitationDepth.MILLIMETERS,
|
||||||
|
suggested_display_precision=2,
|
||||||
|
icon="mdi:weather-pouring",
|
||||||
|
translation_key=RAIN,
|
||||||
|
value_fn=lambda data: cast("float", data),
|
||||||
|
),
|
||||||
|
WeatherSensorEntityDescription(
|
||||||
|
key=DAILY_RAIN,
|
||||||
|
native_unit_of_measurement=UnitOfVolumetricFlux.INCHES_PER_DAY,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
device_class=SensorDeviceClass.PRECIPITATION_INTENSITY,
|
||||||
|
suggested_unit_of_measurement=UnitOfVolumetricFlux.MILLIMETERS_PER_DAY,
|
||||||
|
suggested_display_precision=2,
|
||||||
|
icon="mdi:weather-pouring",
|
||||||
|
translation_key=DAILY_RAIN,
|
||||||
|
value_fn=lambda data: cast("float", data),
|
||||||
|
),
|
||||||
|
WeatherSensorEntityDescription(
|
||||||
|
key=SOLAR_RADIATION,
|
||||||
|
native_unit_of_measurement=UnitOfIrradiance.WATTS_PER_SQUARE_METER,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
device_class=SensorDeviceClass.IRRADIANCE,
|
||||||
|
icon="mdi:weather-sunny",
|
||||||
|
translation_key=SOLAR_RADIATION,
|
||||||
|
value_fn=lambda data: cast("float", data),
|
||||||
|
),
|
||||||
|
WeatherSensorEntityDescription(
|
||||||
|
key=UV,
|
||||||
|
name=UV,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
native_unit_of_measurement=UV_INDEX,
|
||||||
|
icon="mdi:sunglasses",
|
||||||
|
translation_key=UV,
|
||||||
|
value_fn=lambda data: cast("float", data),
|
||||||
|
),
|
||||||
|
WeatherSensorEntityDescription(
|
||||||
|
key=CH2_TEMP,
|
||||||
|
native_unit_of_measurement=UnitOfTemperature.FAHRENHEIT,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
device_class=SensorDeviceClass.TEMPERATURE,
|
||||||
|
suggested_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
||||||
|
icon="mdi:weather-sunny",
|
||||||
|
translation_key=CH2_TEMP,
|
||||||
|
value_fn=lambda data: cast("float", data),
|
||||||
|
),
|
||||||
|
WeatherSensorEntityDescription(
|
||||||
|
key=CH2_HUMIDITY,
|
||||||
|
native_unit_of_measurement=PERCENTAGE,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
device_class=SensorDeviceClass.HUMIDITY,
|
||||||
|
icon="mdi:weather-sunny",
|
||||||
|
translation_key=CH2_HUMIDITY,
|
||||||
|
value_fn=lambda data: cast("int", data),
|
||||||
|
),
|
||||||
|
WeatherSensorEntityDescription(
|
||||||
|
key=CH3_TEMP,
|
||||||
|
native_unit_of_measurement=UnitOfTemperature.FAHRENHEIT,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
device_class=SensorDeviceClass.TEMPERATURE,
|
||||||
|
suggested_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
||||||
|
icon="mdi:weather-sunny",
|
||||||
|
translation_key=CH3_TEMP,
|
||||||
|
value_fn=lambda data: cast("float", data),
|
||||||
|
),
|
||||||
|
WeatherSensorEntityDescription(
|
||||||
|
key=CH3_HUMIDITY,
|
||||||
|
native_unit_of_measurement=PERCENTAGE,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
device_class=SensorDeviceClass.HUMIDITY,
|
||||||
|
icon="mdi:weather-sunny",
|
||||||
|
translation_key=CH3_HUMIDITY,
|
||||||
|
value_fn=lambda data: cast("int", data),
|
||||||
|
),
|
||||||
|
WeatherSensorEntityDescription(
|
||||||
|
key=CH4_TEMP,
|
||||||
|
native_unit_of_measurement=UnitOfTemperature.FAHRENHEIT,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
device_class=SensorDeviceClass.TEMPERATURE,
|
||||||
|
suggested_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
||||||
|
icon="mdi:weather-sunny",
|
||||||
|
translation_key=CH4_TEMP,
|
||||||
|
value_fn=lambda data: cast("float", data),
|
||||||
|
),
|
||||||
|
WeatherSensorEntityDescription(
|
||||||
|
key=CH4_HUMIDITY,
|
||||||
|
native_unit_of_measurement=PERCENTAGE,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
device_class=SensorDeviceClass.HUMIDITY,
|
||||||
|
icon="mdi:weather-sunny",
|
||||||
|
translation_key=CH4_HUMIDITY,
|
||||||
|
value_fn=lambda data: cast("int", data),
|
||||||
|
),
|
||||||
|
WeatherSensorEntityDescription(
|
||||||
|
key=HEAT_INDEX,
|
||||||
|
native_unit_of_measurement=UnitOfTemperature.FAHRENHEIT,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
device_class=SensorDeviceClass.TEMPERATURE,
|
||||||
|
suggested_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
||||||
|
suggested_display_precision=2,
|
||||||
|
icon="mdi:weather-sunny",
|
||||||
|
translation_key=HEAT_INDEX,
|
||||||
|
value_fn=lambda data: cast("int", data),
|
||||||
|
),
|
||||||
|
WeatherSensorEntityDescription(
|
||||||
|
key=CHILL_INDEX,
|
||||||
|
native_unit_of_measurement=UnitOfTemperature.FAHRENHEIT,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
device_class=SensorDeviceClass.TEMPERATURE,
|
||||||
|
suggested_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
||||||
|
suggested_display_precision=2,
|
||||||
|
icon="mdi:weather-sunny",
|
||||||
|
translation_key=CHILL_INDEX,
|
||||||
|
value_fn=lambda data: cast("int", data),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,306 @@
|
||||||
|
"""Sensor entities for the SWS12500 integration for old endpoint."""
|
||||||
|
|
||||||
|
from typing import cast
|
||||||
|
|
||||||
|
from homeassistant.components.sensor import SensorDeviceClass, SensorStateClass
|
||||||
|
from homeassistant.const import (
|
||||||
|
DEGREE,
|
||||||
|
PERCENTAGE,
|
||||||
|
UV_INDEX,
|
||||||
|
UnitOfIrradiance,
|
||||||
|
UnitOfPrecipitationDepth,
|
||||||
|
UnitOfPressure,
|
||||||
|
UnitOfSpeed,
|
||||||
|
UnitOfTemperature,
|
||||||
|
UnitOfVolumetricFlux,
|
||||||
|
)
|
||||||
|
|
||||||
|
from .const import (
|
||||||
|
BARO_PRESSURE,
|
||||||
|
CH2_HUMIDITY,
|
||||||
|
CH2_TEMP,
|
||||||
|
CH3_HUMIDITY,
|
||||||
|
CH3_TEMP,
|
||||||
|
CH4_HUMIDITY,
|
||||||
|
CH4_TEMP,
|
||||||
|
CHILL_INDEX,
|
||||||
|
DAILY_RAIN,
|
||||||
|
DEW_POINT,
|
||||||
|
HEAT_INDEX,
|
||||||
|
INDOOR_HUMIDITY,
|
||||||
|
INDOOR_TEMP,
|
||||||
|
OUTSIDE_HUMIDITY,
|
||||||
|
OUTSIDE_TEMP,
|
||||||
|
RAIN,
|
||||||
|
SOLAR_RADIATION,
|
||||||
|
UV,
|
||||||
|
WIND_AZIMUT,
|
||||||
|
WIND_DIR,
|
||||||
|
WIND_GUST,
|
||||||
|
WIND_SPEED,
|
||||||
|
UnitOfDir,
|
||||||
|
MONTHLY_RAIN,
|
||||||
|
YEARLY_RAIN,
|
||||||
|
HOURLY_RAIN,
|
||||||
|
WEEKLY_RAIN,
|
||||||
|
)
|
||||||
|
from .sensors_common import WeatherSensorEntityDescription
|
||||||
|
from .utils import wind_dir_to_text
|
||||||
|
|
||||||
|
SENSOR_TYPES_WSLINK: tuple[WeatherSensorEntityDescription, ...] = (
|
||||||
|
WeatherSensorEntityDescription(
|
||||||
|
key=INDOOR_TEMP,
|
||||||
|
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
icon="mdi:thermometer",
|
||||||
|
device_class=SensorDeviceClass.TEMPERATURE,
|
||||||
|
translation_key=INDOOR_TEMP,
|
||||||
|
value_fn=lambda data: cast("float", data),
|
||||||
|
),
|
||||||
|
WeatherSensorEntityDescription(
|
||||||
|
key=INDOOR_HUMIDITY,
|
||||||
|
native_unit_of_measurement=PERCENTAGE,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
icon="mdi:thermometer",
|
||||||
|
device_class=SensorDeviceClass.HUMIDITY,
|
||||||
|
translation_key=INDOOR_HUMIDITY,
|
||||||
|
value_fn=lambda data: cast("int", data),
|
||||||
|
),
|
||||||
|
WeatherSensorEntityDescription(
|
||||||
|
key=OUTSIDE_TEMP,
|
||||||
|
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
icon="mdi:thermometer",
|
||||||
|
device_class=SensorDeviceClass.TEMPERATURE,
|
||||||
|
translation_key=OUTSIDE_TEMP,
|
||||||
|
value_fn=lambda data: cast("float", data),
|
||||||
|
),
|
||||||
|
WeatherSensorEntityDescription(
|
||||||
|
key=OUTSIDE_HUMIDITY,
|
||||||
|
native_unit_of_measurement=PERCENTAGE,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
icon="mdi:thermometer",
|
||||||
|
device_class=SensorDeviceClass.HUMIDITY,
|
||||||
|
translation_key=OUTSIDE_HUMIDITY,
|
||||||
|
value_fn=lambda data: cast("int", data),
|
||||||
|
),
|
||||||
|
WeatherSensorEntityDescription(
|
||||||
|
key=DEW_POINT,
|
||||||
|
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
icon="mdi:thermometer-lines",
|
||||||
|
device_class=SensorDeviceClass.TEMPERATURE,
|
||||||
|
translation_key=DEW_POINT,
|
||||||
|
value_fn=lambda data: cast("float", data),
|
||||||
|
),
|
||||||
|
WeatherSensorEntityDescription(
|
||||||
|
key=BARO_PRESSURE,
|
||||||
|
native_unit_of_measurement=UnitOfPressure.HPA,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
icon="mdi:thermometer-lines",
|
||||||
|
device_class=SensorDeviceClass.ATMOSPHERIC_PRESSURE,
|
||||||
|
suggested_unit_of_measurement=UnitOfPressure.HPA,
|
||||||
|
translation_key=BARO_PRESSURE,
|
||||||
|
value_fn=lambda data: cast("float", data),
|
||||||
|
),
|
||||||
|
WeatherSensorEntityDescription(
|
||||||
|
key=WIND_SPEED,
|
||||||
|
native_unit_of_measurement=UnitOfSpeed.METERS_PER_SECOND,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
device_class=SensorDeviceClass.WIND_SPEED,
|
||||||
|
suggested_unit_of_measurement=UnitOfSpeed.KILOMETERS_PER_HOUR,
|
||||||
|
icon="mdi:weather-windy",
|
||||||
|
translation_key=WIND_SPEED,
|
||||||
|
value_fn=lambda data: cast("int", data),
|
||||||
|
),
|
||||||
|
WeatherSensorEntityDescription(
|
||||||
|
key=WIND_GUST,
|
||||||
|
native_unit_of_measurement=UnitOfSpeed.METERS_PER_SECOND,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
device_class=SensorDeviceClass.WIND_SPEED,
|
||||||
|
suggested_unit_of_measurement=UnitOfSpeed.KILOMETERS_PER_HOUR,
|
||||||
|
icon="mdi:windsock",
|
||||||
|
translation_key=WIND_GUST,
|
||||||
|
value_fn=lambda data: cast("float", data),
|
||||||
|
),
|
||||||
|
WeatherSensorEntityDescription(
|
||||||
|
key=WIND_DIR,
|
||||||
|
native_unit_of_measurement=DEGREE,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
suggested_display_precision=None,
|
||||||
|
icon="mdi:sign-direction",
|
||||||
|
translation_key=WIND_DIR,
|
||||||
|
value_fn=lambda data: cast("int", data),
|
||||||
|
),
|
||||||
|
WeatherSensorEntityDescription(
|
||||||
|
key=WIND_AZIMUT,
|
||||||
|
icon="mdi:sign-direction",
|
||||||
|
value_fn=lambda data: cast("str", wind_dir_to_text(data)),
|
||||||
|
device_class=SensorDeviceClass.ENUM,
|
||||||
|
options=list(UnitOfDir),
|
||||||
|
translation_key=WIND_AZIMUT,
|
||||||
|
),
|
||||||
|
WeatherSensorEntityDescription(
|
||||||
|
key=RAIN,
|
||||||
|
native_unit_of_measurement=UnitOfVolumetricFlux.MILLIMETERS_PER_HOUR,
|
||||||
|
device_class=SensorDeviceClass.PRECIPITATION,
|
||||||
|
state_class=SensorStateClass.TOTAL,
|
||||||
|
suggested_unit_of_measurement=UnitOfVolumetricFlux.MILLIMETERS_PER_HOUR,
|
||||||
|
suggested_display_precision=2,
|
||||||
|
icon="mdi:weather-pouring",
|
||||||
|
translation_key=RAIN,
|
||||||
|
value_fn=lambda data: cast("float", data),
|
||||||
|
),
|
||||||
|
WeatherSensorEntityDescription(
|
||||||
|
key=DAILY_RAIN,
|
||||||
|
native_unit_of_measurement=UnitOfPrecipitationDepth.MILLIMETERS,
|
||||||
|
device_class=SensorDeviceClass.PRECIPITATION,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
suggested_unit_of_measurement=UnitOfPrecipitationDepth.MILLIMETERS,
|
||||||
|
suggested_display_precision=2,
|
||||||
|
icon="mdi:weather-pouring",
|
||||||
|
translation_key=DAILY_RAIN,
|
||||||
|
value_fn=lambda data: cast("float", data),
|
||||||
|
),
|
||||||
|
WeatherSensorEntityDescription(
|
||||||
|
key=HOURLY_RAIN,
|
||||||
|
native_unit_of_measurement=UnitOfPrecipitationDepth.MILLIMETERS,
|
||||||
|
device_class=SensorDeviceClass.PRECIPITATION,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
suggested_unit_of_measurement=UnitOfPrecipitationDepth.MILLIMETERS,
|
||||||
|
suggested_display_precision=2,
|
||||||
|
icon="mdi:weather-pouring",
|
||||||
|
translation_key=HOURLY_RAIN,
|
||||||
|
value_fn=lambda data: cast("float", data),
|
||||||
|
),
|
||||||
|
WeatherSensorEntityDescription(
|
||||||
|
key=WEEKLY_RAIN,
|
||||||
|
native_unit_of_measurement=UnitOfPrecipitationDepth.MILLIMETERS,
|
||||||
|
device_class=SensorDeviceClass.PRECIPITATION,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
suggested_unit_of_measurement=UnitOfPrecipitationDepth.MILLIMETERS,
|
||||||
|
suggested_display_precision=2,
|
||||||
|
icon="mdi:weather-pouring",
|
||||||
|
translation_key=WEEKLY_RAIN,
|
||||||
|
value_fn=lambda data: cast("float", data),
|
||||||
|
),
|
||||||
|
WeatherSensorEntityDescription(
|
||||||
|
key=MONTHLY_RAIN,
|
||||||
|
native_unit_of_measurement=UnitOfPrecipitationDepth.MILLIMETERS,
|
||||||
|
device_class=SensorDeviceClass.PRECIPITATION,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
suggested_unit_of_measurement=UnitOfPrecipitationDepth.MILLIMETERS,
|
||||||
|
suggested_display_precision=2,
|
||||||
|
icon="mdi:weather-pouring",
|
||||||
|
translation_key=MONTHLY_RAIN,
|
||||||
|
value_fn=lambda data: cast("float", data),
|
||||||
|
),
|
||||||
|
WeatherSensorEntityDescription(
|
||||||
|
key=YEARLY_RAIN,
|
||||||
|
native_unit_of_measurement=UnitOfPrecipitationDepth.MILLIMETERS,
|
||||||
|
device_class=SensorDeviceClass.PRECIPITATION,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
suggested_unit_of_measurement=UnitOfPrecipitationDepth.MILLIMETERS,
|
||||||
|
suggested_display_precision=2,
|
||||||
|
icon="mdi:weather-pouring",
|
||||||
|
translation_key=YEARLY_RAIN,
|
||||||
|
value_fn=lambda data: cast("float", data),
|
||||||
|
),
|
||||||
|
WeatherSensorEntityDescription(
|
||||||
|
key=SOLAR_RADIATION,
|
||||||
|
native_unit_of_measurement=UnitOfIrradiance.WATTS_PER_SQUARE_METER,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
device_class=SensorDeviceClass.IRRADIANCE,
|
||||||
|
icon="mdi:weather-sunny",
|
||||||
|
translation_key=SOLAR_RADIATION,
|
||||||
|
value_fn=lambda data: cast("float", data),
|
||||||
|
),
|
||||||
|
WeatherSensorEntityDescription(
|
||||||
|
key=UV,
|
||||||
|
name=UV,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
native_unit_of_measurement=UV_INDEX,
|
||||||
|
icon="mdi:sunglasses",
|
||||||
|
translation_key=UV,
|
||||||
|
value_fn=lambda data: cast("float", data),
|
||||||
|
),
|
||||||
|
WeatherSensorEntityDescription(
|
||||||
|
key=CH2_TEMP,
|
||||||
|
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
device_class=SensorDeviceClass.TEMPERATURE,
|
||||||
|
suggested_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
||||||
|
icon="mdi:weather-sunny",
|
||||||
|
translation_key=CH2_TEMP,
|
||||||
|
value_fn=lambda data: cast("float", data),
|
||||||
|
),
|
||||||
|
WeatherSensorEntityDescription(
|
||||||
|
key=CH2_HUMIDITY,
|
||||||
|
native_unit_of_measurement=PERCENTAGE,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
device_class=SensorDeviceClass.HUMIDITY,
|
||||||
|
icon="mdi:weather-sunny",
|
||||||
|
translation_key=CH2_HUMIDITY,
|
||||||
|
value_fn=lambda data: cast("int", data),
|
||||||
|
),
|
||||||
|
# WeatherSensorEntityDescription(
|
||||||
|
# key=CH3_TEMP,
|
||||||
|
# native_unit_of_measurement=UnitOfTemperature.FAHRENHEIT,
|
||||||
|
# state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
# device_class=SensorDeviceClass.TEMPERATURE,
|
||||||
|
# suggested_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
||||||
|
# icon="mdi:weather-sunny",
|
||||||
|
# translation_key=CH3_TEMP,
|
||||||
|
# value_fn=lambda data: cast(float, data),
|
||||||
|
# ),
|
||||||
|
# WeatherSensorEntityDescription(
|
||||||
|
# key=CH3_HUMIDITY,
|
||||||
|
# native_unit_of_measurement=PERCENTAGE,
|
||||||
|
# state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
# device_class=SensorDeviceClass.HUMIDITY,
|
||||||
|
# icon="mdi:weather-sunny",
|
||||||
|
# translation_key=CH3_HUMIDITY,
|
||||||
|
# value_fn=lambda data: cast(int, data),
|
||||||
|
# ),
|
||||||
|
# WeatherSensorEntityDescription(
|
||||||
|
# key=CH4_TEMP,
|
||||||
|
# native_unit_of_measurement=UnitOfTemperature.FAHRENHEIT,
|
||||||
|
# state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
# device_class=SensorDeviceClass.TEMPERATURE,
|
||||||
|
# suggested_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
||||||
|
# icon="mdi:weather-sunny",
|
||||||
|
# translation_key=CH4_TEMP,
|
||||||
|
# value_fn=lambda data: cast(float, data),
|
||||||
|
# ),
|
||||||
|
# WeatherSensorEntityDescription(
|
||||||
|
# key=CH4_HUMIDITY,
|
||||||
|
# native_unit_of_measurement=PERCENTAGE,
|
||||||
|
# state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
# device_class=SensorDeviceClass.HUMIDITY,
|
||||||
|
# icon="mdi:weather-sunny",
|
||||||
|
# translation_key=CH4_HUMIDITY,
|
||||||
|
# value_fn=lambda data: cast(int, data),
|
||||||
|
# ),
|
||||||
|
WeatherSensorEntityDescription(
|
||||||
|
key=HEAT_INDEX,
|
||||||
|
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
device_class=SensorDeviceClass.TEMPERATURE,
|
||||||
|
suggested_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
||||||
|
suggested_display_precision=2,
|
||||||
|
icon="mdi:weather-sunny",
|
||||||
|
translation_key=HEAT_INDEX,
|
||||||
|
value_fn=lambda data: cast("int", data),
|
||||||
|
),
|
||||||
|
WeatherSensorEntityDescription(
|
||||||
|
key=CHILL_INDEX,
|
||||||
|
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
device_class=SensorDeviceClass.TEMPERATURE,
|
||||||
|
suggested_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
||||||
|
suggested_display_precision=2,
|
||||||
|
icon="mdi:weather-sunny",
|
||||||
|
translation_key=CHILL_INDEX,
|
||||||
|
value_fn=lambda data: cast("int", data),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
@ -5,21 +5,27 @@
|
||||||
"valid_credentials_key": "Provide valid API KEY.",
|
"valid_credentials_key": "Provide valid API KEY.",
|
||||||
"valid_credentials_match": "API ID and API KEY should not be the same."
|
"valid_credentials_match": "API ID and API KEY should not be the same."
|
||||||
},
|
},
|
||||||
|
|
||||||
"step": {
|
"step": {
|
||||||
"user": {
|
"user": {
|
||||||
|
"description": "Provide API ID and API KEY so the Weather Station can access HomeAssistant",
|
||||||
|
"title": "Configure access for Weather Station",
|
||||||
"data": {
|
"data": {
|
||||||
"API_ID": "API ID / Station ID",
|
"API_ID": "API ID / Station ID",
|
||||||
"API_KEY": "API KEY / Password",
|
"API_KEY": "API KEY / Password",
|
||||||
|
"WSLINK": "WSLink API",
|
||||||
"dev_debug_checkbox": "Developer log"
|
"dev_debug_checkbox": "Developer log"
|
||||||
},
|
},
|
||||||
"description": "Provide API ID and API KEY so the Weather Station can access HomeAssistant",
|
|
||||||
"title": "Configure access for Weather Station",
|
|
||||||
"data_description": {
|
"data_description": {
|
||||||
"dev_debug_checkbox": " Enable only if you want to send debuging data to the developer."
|
"dev_debug_checkbox": " Enable only if you want to send debuging data to the developer.",
|
||||||
|
"API_ID": "API ID is the Station ID you set in the Weather Station.",
|
||||||
|
"API_KEY": "API KEY is the password you set in the Weather Station.",
|
||||||
|
"WSLINK": "Enable WSLink API if the station is set to send data via WSLink."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
"options": {
|
"options": {
|
||||||
"error": {
|
"error": {
|
||||||
"valid_credentials_api": "Provide valid API ID.",
|
"valid_credentials_api": "Provide valid API ID.",
|
||||||
|
|
@ -27,35 +33,58 @@
|
||||||
"valid_credentials_match": "API ID and API KEY should not be the same.",
|
"valid_credentials_match": "API ID and API KEY should not be the same.",
|
||||||
"windy_key_required": "Windy API key is required if you want to enable this function."
|
"windy_key_required": "Windy API key is required if you want to enable this function."
|
||||||
},
|
},
|
||||||
|
|
||||||
"step": {
|
"step": {
|
||||||
"basic": {
|
|
||||||
"data": {
|
|
||||||
"API_ID": "API ID / Station ID",
|
|
||||||
"API_KEY": "API KEY / Password",
|
|
||||||
"dev_debug_checkbox": "Developer log"
|
|
||||||
},
|
|
||||||
"description": "Provide API ID and API KEY so the Weather Station can access HomeAssistant",
|
|
||||||
"title": "Configure credentials",
|
|
||||||
"data_description": {
|
|
||||||
"dev_debug_checkbox": " Enable only if you want to send debuging data to the developer."
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"init": {
|
"init": {
|
||||||
|
"title": "Configure SWS12500 Integration",
|
||||||
"description": "Choose what do you want to configure. If basic access or resending data for Windy site",
|
"description": "Choose what do you want to configure. If basic access or resending data for Windy site",
|
||||||
"menu_options": {
|
"menu_options": {
|
||||||
"basic": "Basic - configure credentials for Weather Station",
|
"basic": "Basic - configure credentials for Weather Station",
|
||||||
"windy": "Windy configuration"
|
"windy": "Windy configuration"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"title": "Configure SWS12500 Integration"
|
|
||||||
|
"basic": {
|
||||||
|
"description": "Provide API ID and API KEY so the Weather Station can access HomeAssistant",
|
||||||
|
"title": "Configure credentials",
|
||||||
|
"data": {
|
||||||
|
"API_ID": "API ID / Station ID",
|
||||||
|
"API_KEY": "API KEY / Password",
|
||||||
|
"WSLINK": "WSLink API",
|
||||||
|
"dev_debug_checkbox": "Developer log"
|
||||||
},
|
},
|
||||||
|
"data_description": {
|
||||||
|
"dev_debug_checkbox": " Enable only if you want to send debuging data to the developer.",
|
||||||
|
"API_ID": "API ID is the Station ID you set in the Weather Station.",
|
||||||
|
"API_KEY": "API KEY is the password you set in the Weather Station.",
|
||||||
|
"WSLINK": "Enable WSLink API if the station is set to send data via WSLink."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
"windy": {
|
"windy": {
|
||||||
|
"description": "Resend weather data to your Windy stations.",
|
||||||
|
"title": "Configure Windy",
|
||||||
"data": {
|
"data": {
|
||||||
"WINDY_API_KEY": "API KEY provided by Windy",
|
"WINDY_API_KEY": "API KEY provided by Windy",
|
||||||
"windy_enabled_checkbox": "Enable resending data to Windy",
|
"windy_enabled_checkbox": "Enable resending data to Windy",
|
||||||
"windy_logger_checkbox": "Log Windy data and responses"
|
"windy_logger_checkbox": "Log Windy data and responses"
|
||||||
},
|
},
|
||||||
"description": "Resend weather data to your Windy stations.",
|
"data_description": {
|
||||||
"title": "Configure Windy"
|
"WINDY_API_KEY": "Windy API KEY obtained from https://https://api.windy.com/keys",
|
||||||
|
"windy_logger_checkbox": "Enable only if you want to send debuging data to the developer."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"migration": {
|
||||||
|
"title": "Statistic migration.",
|
||||||
|
"description": "For the correct functioning of long-term statistics, it is necessary to migrate the sensor unit in the long-term statistics. The original unit of long-term statistics for daily precipitation was in mm/d, however, the station only sends data in mm without time differentiation.\n\n The sensor to be migrated is for daily precipitation. If the correct value is already in the list for the daily precipitation sensor (mm), then the migration is already complete.\n\n Migration result for the sensor: {migration_status}, a total of {migration_count} rows converted.",
|
||||||
|
"data": {
|
||||||
|
"sensor_to_migrate": "Sensor to migrate",
|
||||||
|
"trigger_action": "Trigger migration"
|
||||||
|
},
|
||||||
|
"data_description": {
|
||||||
|
"sensor_to_migrate": "Select the correct sensor for statistics migration.\nThe sensor values will be preserved, they will not be recalculated, only the unit in the long-term statistics will be changed.",
|
||||||
|
"trigger_action": "Trigger the sensor statistics migration after checking."
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -76,7 +105,40 @@
|
||||||
"daily_rain": { "name": "Daily precipitation" },
|
"daily_rain": { "name": "Daily precipitation" },
|
||||||
"solar_radiation": { "name": "Solar irradiance" },
|
"solar_radiation": { "name": "Solar irradiance" },
|
||||||
"ch2_temp": { "name": "Channel 2 temperature" },
|
"ch2_temp": { "name": "Channel 2 temperature" },
|
||||||
"ch2_humidity": { "name": "Channel 2 humidity" }
|
"ch2_humidity": { "name": "Channel 2 humidity" },
|
||||||
|
"ch3_temp": { "name": "Channel 3 temperature" },
|
||||||
|
"ch3_humidity": { "name": "Channel 3 humidity" },
|
||||||
|
"ch4_temp": { "name": "Channel 4 temperature" },
|
||||||
|
"ch4_humidity": { "name": "Channel 4 humidity" },
|
||||||
|
"heat_index": { "name": "Apparent temperature" },
|
||||||
|
"chill_index": { "name": "Wind chill" },
|
||||||
|
"wind_azimut": {
|
||||||
|
"name": "Bearing",
|
||||||
|
"state": {
|
||||||
|
"n": "N",
|
||||||
|
"nne": "NNE",
|
||||||
|
"ne": "NE",
|
||||||
|
"ene": "ENE",
|
||||||
|
"e": "E",
|
||||||
|
"ese": "ESE",
|
||||||
|
"se": "SE",
|
||||||
|
"sse": "SSE",
|
||||||
|
"s": "S",
|
||||||
|
"ssw": "SSW",
|
||||||
|
"sw": "SW",
|
||||||
|
"wsw": "WSW",
|
||||||
|
"w": "W",
|
||||||
|
"wnw": "WNW",
|
||||||
|
"nw": "NW",
|
||||||
|
"nnw": "NNW"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notify": {
|
||||||
|
"added": {
|
||||||
|
"title": "New sensors for SWS 12500 found.",
|
||||||
|
"message": "{added_sensors}\n"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,19 +7,24 @@
|
||||||
},
|
},
|
||||||
"step": {
|
"step": {
|
||||||
"user": {
|
"user": {
|
||||||
|
"description": "Zadejte API ID a API KEY, aby meteostanice mohla komunikovat s HomeAssistantem",
|
||||||
|
"title": "Nastavení přihlášení",
|
||||||
"data": {
|
"data": {
|
||||||
"API_ID": "API ID / ID stanice",
|
"API_ID": "API ID / ID Stanice",
|
||||||
"API_KEY": "API KEY / Heslo",
|
"API_KEY": "API KEY / Heslo",
|
||||||
|
"wslink": "WSLink API",
|
||||||
"dev_debug_checkbox": "Developer log"
|
"dev_debug_checkbox": "Developer log"
|
||||||
},
|
},
|
||||||
"description": "Zadejte API ID a API KEY, aby meteostanice mohla komunikovat s HomeAssistantem",
|
|
||||||
"title": "Nastavení přístpu pro metostanici",
|
|
||||||
"data_description": {
|
"data_description": {
|
||||||
"dev_debug_checkbox": " Enable only if you want to send debuging data to the developer."
|
"dev_debug_checkbox": "Zapnout pouze v případě, že chcete poslat ladící informace vývojáři.",
|
||||||
|
"API_ID": "API ID je ID stanice, které jste nastavili v meteostanici.",
|
||||||
|
"API_KEY": "API KEY je heslo, které jste nastavili v meteostanici.",
|
||||||
|
"wslink": "WSLink API zapněte, pokud je stanice nastavena na zasílání dat přes WSLink."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
"options": {
|
"options": {
|
||||||
"error": {
|
"error": {
|
||||||
"valid_credentials_api": "Vyplňte platné API ID",
|
"valid_credentials_api": "Vyplňte platné API ID",
|
||||||
|
|
@ -27,38 +32,63 @@
|
||||||
"valid_credentials_match": "API ID a API KEY nesmějí být stejné!",
|
"valid_credentials_match": "API ID a API KEY nesmějí být stejné!",
|
||||||
"windy_key_required": "Je vyžadován Windy API key, pokud chcete aktivovat přeposílání dat na Windy"
|
"windy_key_required": "Je vyžadován Windy API key, pokud chcete aktivovat přeposílání dat na Windy"
|
||||||
},
|
},
|
||||||
|
|
||||||
"step": {
|
"step": {
|
||||||
|
"init": {
|
||||||
|
"title": "Nastavení integrace SWS12500",
|
||||||
|
"description": "Vyberte, co chcete konfigurovat. Zda přihlašovací údaje nebo nastavení pro přeposílání dat na Windy.",
|
||||||
|
"menu_options": {
|
||||||
|
"basic": "Základní - přístupové údaje (přihlášení)",
|
||||||
|
"windy": "Nastavení pro přeposílání dat na Windy",
|
||||||
|
"migration": "Migrace statistiky senzoru"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
"basic": {
|
"basic": {
|
||||||
|
"description": "Zadejte API ID a API KEY, aby meteostanice mohla komunikovat s HomeAssistantem",
|
||||||
|
"title": "Nastavení přihlášení",
|
||||||
"data": {
|
"data": {
|
||||||
"API_ID": "API ID / ID Stanice",
|
"API_ID": "API ID / ID Stanice",
|
||||||
"API_KEY": "API KEY / Heslo",
|
"API_KEY": "API KEY / Heslo",
|
||||||
|
"wslink": "WSLink API",
|
||||||
"dev_debug_checkbox": "Developer log"
|
"dev_debug_checkbox": "Developer log"
|
||||||
},
|
},
|
||||||
"description": "Zadejte API ID a API KEY, aby meteostanice mohla komunikovat s HomeAssistantem",
|
|
||||||
"title": "Nastavení přihlášení",
|
|
||||||
"data_description": {
|
"data_description": {
|
||||||
"dev_debug_checkbox": " Enable only if you want to send debuging data to the developer."
|
"dev_debug_checkbox": "Zapnout pouze v případě, že chcete poslat ladící informace vývojáři.",
|
||||||
|
"API_ID": "API ID je ID stanice, které jste nastavili v meteostanici.",
|
||||||
|
"API_KEY": "API KEY je heslo, které jste nastavili v meteostanici.",
|
||||||
|
"wslink": "WSLink API zapněte, pokud je stanice nastavena na zasílání dat přes WSLink."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"init": {
|
|
||||||
"description": "Vyberte, co chcete konfigurovat. Zda přihlašovací údaje nebo nastavení pro přeposílání dat na Windy.",
|
|
||||||
"menu_options": {
|
|
||||||
"basic": "Základní - přístupové údaje (úřihlášení)",
|
|
||||||
"windy": "Nastavení pro přeposílání dat na Windy"
|
|
||||||
},
|
|
||||||
"title": "Nastavení integrace SWS12500"
|
|
||||||
},
|
|
||||||
"windy": {
|
"windy": {
|
||||||
|
"description": "Přeposílání dat z metostanice na Windy",
|
||||||
|
"title": "Konfigurace Windy",
|
||||||
"data": {
|
"data": {
|
||||||
"WINDY_API_KEY": "Klíč API KEY získaný z Windy",
|
"WINDY_API_KEY": "Klíč API KEY získaný z Windy",
|
||||||
"windy_enabled_checkbox": "Povolit přeposílání dat na Windy",
|
"windy_enabled_checkbox": "Povolit přeposílání dat na Windy",
|
||||||
"windy_logger_checkbox": "Logovat data a odpovědi z Windy"
|
"windy_logger_checkbox": "Logovat data a odpovědi z Windy"
|
||||||
},
|
},
|
||||||
"description": "Přeposílání dat z metostanice na Windy",
|
"data_description": {
|
||||||
"title": "Konfigurace Windy"
|
"WINDY_API_KEY": "Klíč API KEY získaný z https://https://api.windy.com/keys",
|
||||||
|
"windy_logger_checkbox": "Zapnout pouze v případě, že chcete poslat ladící informace vývojáři."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"migration": {
|
||||||
|
"title": "Migrace statistiky senzoru.",
|
||||||
|
"description": "Pro správnou funkci dlouhodobé statistiky je nutné provést migraci jednotky senzoru v dlouhodobé statistice. Původní jednotka dlouhodobé statistiky pro denní úhrn srážek byla v mm/d, nicméně stanice zasílá pouze data v mm bez časového rozlišení.\n\n Senzor, který má být migrován je pro denní úhrn srážek. Pokud je v seznamu již správná hodnota u senzoru pro denní úhrn (mm), pak je již migrace hotová.\n\n Výsledek migrace pro senzor: {migration_status}, přepvedeno celkem {migration_count} řádků.",
|
||||||
|
"data": {
|
||||||
|
"sensor_to_migrate": "Senzor pro migraci",
|
||||||
|
"trigger_action": "Spustit migraci"
|
||||||
|
},
|
||||||
|
"data_description": {
|
||||||
|
"sensor_to_migrate": "Vyberte správný senzor pri migraci statistiky. \n Hodnoty senzoru budou zachovány, nepřepočítají se, pouze se změní jednotka v dlouhodobé statistice. ",
|
||||||
|
"trigger_action": "Po zaškrtnutí se spustí migrace statistiky senzoru."
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
"entity": {
|
"entity": {
|
||||||
"sensor": {
|
"sensor": {
|
||||||
"indoor_temp": { "name": "Vnitřní teplota" },
|
"indoor_temp": { "name": "Vnitřní teplota" },
|
||||||
|
|
@ -68,14 +98,51 @@
|
||||||
"uv": { "name": "UV index" },
|
"uv": { "name": "UV index" },
|
||||||
"baro_pressure": { "name": "Tlak vzduchu" },
|
"baro_pressure": { "name": "Tlak vzduchu" },
|
||||||
"dew_point": { "name": "Rosný bod" },
|
"dew_point": { "name": "Rosný bod" },
|
||||||
"wind_speed": { "name": "Rychlost větrtu" },
|
"wind_speed": { "name": "Rychlost větru" },
|
||||||
"wind_dir": { "name": "Směr větru" },
|
"wind_dir": { "name": "Směr větru" },
|
||||||
"wind_gust": { "name": "Poryvy větru" },
|
"wind_gust": { "name": "Poryvy větru" },
|
||||||
"rain": { "name": "Srážky" },
|
"rain": { "name": "Srážky" },
|
||||||
"daily_rain": { "name": "Denní úhrn srážek" },
|
"daily_rain": { "name": "Denní úhrn srážek" },
|
||||||
"solar_radiation": { "name": "Sluneční osvit" },
|
"solar_radiation": { "name": "Sluneční osvit" },
|
||||||
"ch2_temp": { "name": "Teplota senzoru 2" },
|
"ch2_temp": { "name": "Teplota senzoru 2" },
|
||||||
"ch2_humidity": { "name": "Vlhkost sensoru 2" }
|
"ch2_humidity": { "name": "Vlhkost sensoru 2" },
|
||||||
|
"ch3_temp": { "name": "Teplota senzoru 3" },
|
||||||
|
"ch3_humidity": { "name": "Vlhkost sensoru 3" },
|
||||||
|
"ch4_temp": { "name": "Teplota senzoru 4" },
|
||||||
|
"ch4_humidity": { "name": "Vlhkost sensoru 4" },
|
||||||
|
"heat_index": { "name": "Tepelný index" },
|
||||||
|
"chill_index": { "name": "Pocitová teplota" },
|
||||||
|
"hourly_rain": { "name": "Hodinový úhrn srážek" },
|
||||||
|
"weekly_rain": { "name": "Týdenní úhrn srážek" },
|
||||||
|
"monthly_rain": { "name": "Měsíční úhrn srážek" },
|
||||||
|
"yearly_rain": { "name": "Roční úhrn srážek" },
|
||||||
|
"wind_azimut": {
|
||||||
|
"name": "Azimut",
|
||||||
|
"state": {
|
||||||
|
"n": "S",
|
||||||
|
"nne": "SSV",
|
||||||
|
"ne": "SV",
|
||||||
|
"ene": "VVS",
|
||||||
|
"e": "V",
|
||||||
|
"ese": "VVJ",
|
||||||
|
"se": "JV",
|
||||||
|
"sse": "JJV",
|
||||||
|
"s": "J",
|
||||||
|
"ssw": "JJZ",
|
||||||
|
"sw": "JZ",
|
||||||
|
"wsw": "JZZ",
|
||||||
|
"w": "Z",
|
||||||
|
"wnw": "ZZS",
|
||||||
|
"nw": "SZ",
|
||||||
|
"nnw": "SSZ"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notify": {
|
||||||
|
"added": {
|
||||||
|
"title": "Nalezeny nové senzory pro SWS 12500.",
|
||||||
|
"message": "{added_sensors}\n"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,21 +5,27 @@
|
||||||
"valid_credentials_key": "Provide valid API KEY.",
|
"valid_credentials_key": "Provide valid API KEY.",
|
||||||
"valid_credentials_match": "API ID and API KEY should not be the same."
|
"valid_credentials_match": "API ID and API KEY should not be the same."
|
||||||
},
|
},
|
||||||
|
|
||||||
"step": {
|
"step": {
|
||||||
"user": {
|
"user": {
|
||||||
|
"description": "Provide API ID and API KEY so the Weather Station can access HomeAssistant",
|
||||||
|
"title": "Configure access for Weather Station",
|
||||||
"data": {
|
"data": {
|
||||||
"API_ID": "API ID / Station ID",
|
"API_ID": "API ID / Station ID",
|
||||||
"API_KEY": "API KEY / Password",
|
"API_KEY": "API KEY / Password",
|
||||||
|
"WSLINK": "WSLink API",
|
||||||
"dev_debug_checkbox": "Developer log"
|
"dev_debug_checkbox": "Developer log"
|
||||||
},
|
},
|
||||||
"description": "Provide API ID and API KEY so the Weather Station can access HomeAssistant",
|
|
||||||
"title": "Configure access for Weather Station",
|
|
||||||
"data_description": {
|
"data_description": {
|
||||||
"dev_debug_checkbox": " Enable only if you want to send debuging data to the developer."
|
"dev_debug_checkbox": " Enable only if you want to send debuging data to the developer.",
|
||||||
|
"API_ID": "API ID is the Station ID you set in the Weather Station.",
|
||||||
|
"API_KEY": "API KEY is the password you set in the Weather Station.",
|
||||||
|
"WSLINK": "Enable WSLink API if the station is set to send data via WSLink."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
"options": {
|
"options": {
|
||||||
"error": {
|
"error": {
|
||||||
"valid_credentials_api": "Provide valid API ID.",
|
"valid_credentials_api": "Provide valid API ID.",
|
||||||
|
|
@ -27,35 +33,58 @@
|
||||||
"valid_credentials_match": "API ID and API KEY should not be the same.",
|
"valid_credentials_match": "API ID and API KEY should not be the same.",
|
||||||
"windy_key_required": "Windy API key is required if you want to enable this function."
|
"windy_key_required": "Windy API key is required if you want to enable this function."
|
||||||
},
|
},
|
||||||
|
|
||||||
"step": {
|
"step": {
|
||||||
"basic": {
|
|
||||||
"data": {
|
|
||||||
"API_ID": "API ID / Station ID",
|
|
||||||
"API_KEY": "API KEY / Password",
|
|
||||||
"dev_debug_checkbox": "Developer log"
|
|
||||||
},
|
|
||||||
"description": "Provide API ID and API KEY so the Weather Station can access HomeAssistant",
|
|
||||||
"title": "Configure credentials",
|
|
||||||
"data_description": {
|
|
||||||
"dev_debug_checkbox": " Enable only if you want to send debuging data to the developer."
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"init": {
|
"init": {
|
||||||
|
"title": "Configure SWS12500 Integration",
|
||||||
"description": "Choose what do you want to configure. If basic access or resending data for Windy site",
|
"description": "Choose what do you want to configure. If basic access or resending data for Windy site",
|
||||||
"menu_options": {
|
"menu_options": {
|
||||||
"basic": "Basic - configure credentials for Weather Station",
|
"basic": "Basic - configure credentials for Weather Station",
|
||||||
"windy": "Windy configuration"
|
"windy": "Windy configuration"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"title": "Configure SWS12500 Integration"
|
|
||||||
|
"basic": {
|
||||||
|
"description": "Provide API ID and API KEY so the Weather Station can access HomeAssistant",
|
||||||
|
"title": "Configure credentials",
|
||||||
|
"data": {
|
||||||
|
"API_ID": "API ID / Station ID",
|
||||||
|
"API_KEY": "API KEY / Password",
|
||||||
|
"WSLINK": "WSLink API",
|
||||||
|
"dev_debug_checkbox": "Developer log"
|
||||||
},
|
},
|
||||||
|
"data_description": {
|
||||||
|
"dev_debug_checkbox": " Enable only if you want to send debuging data to the developer.",
|
||||||
|
"API_ID": "API ID is the Station ID you set in the Weather Station.",
|
||||||
|
"API_KEY": "API KEY is the password you set in the Weather Station.",
|
||||||
|
"WSLINK": "Enable WSLink API if the station is set to send data via WSLink."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
"windy": {
|
"windy": {
|
||||||
|
"description": "Resend weather data to your Windy stations.",
|
||||||
|
"title": "Configure Windy",
|
||||||
"data": {
|
"data": {
|
||||||
"WINDY_API_KEY": "API KEY provided by Windy",
|
"WINDY_API_KEY": "API KEY provided by Windy",
|
||||||
"windy_enabled_checkbox": "Enable resending data to Windy",
|
"windy_enabled_checkbox": "Enable resending data to Windy",
|
||||||
"windy_logger_checkbox": "Log Windy data and responses"
|
"windy_logger_checkbox": "Log Windy data and responses"
|
||||||
},
|
},
|
||||||
"description": "Resend weather data to your Windy stations.",
|
"data_description": {
|
||||||
"title": "Configure Windy"
|
"WINDY_API_KEY": "Windy API KEY obtained from https://https://api.windy.com/keys",
|
||||||
|
"windy_logger_checkbox": "Enable only if you want to send debuging data to the developer."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"migration": {
|
||||||
|
"title": "Statistic migration.",
|
||||||
|
"description": "For the correct functioning of long-term statistics, it is necessary to migrate the sensor unit in the long-term statistics. The original unit of long-term statistics for daily precipitation was in mm/d, however, the station only sends data in mm without time differentiation.\n\n The sensor to be migrated is for daily precipitation. If the correct value is already in the list for the daily precipitation sensor (mm), then the migration is already complete.\n\n Migration result for the sensor: {migration_status}, a total of {migration_count} rows converted.",
|
||||||
|
"data": {
|
||||||
|
"sensor_to_migrate": "Sensor to migrate",
|
||||||
|
"trigger_action": "Trigger migration"
|
||||||
|
},
|
||||||
|
"data_description": {
|
||||||
|
"sensor_to_migrate": "Select the correct sensor for statistics migration.\nThe sensor values will be preserved, they will not be recalculated, only the unit in the long-term statistics will be changed.",
|
||||||
|
"trigger_action": "Trigger the sensor statistics migration after checking."
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -76,7 +105,44 @@
|
||||||
"daily_rain": { "name": "Daily precipitation" },
|
"daily_rain": { "name": "Daily precipitation" },
|
||||||
"solar_radiation": { "name": "Solar irradiance" },
|
"solar_radiation": { "name": "Solar irradiance" },
|
||||||
"ch2_temp": { "name": "Channel 2 temperature" },
|
"ch2_temp": { "name": "Channel 2 temperature" },
|
||||||
"ch2_humidity": { "name": "Channel 2 humidity" }
|
"ch2_humidity": { "name": "Channel 2 humidity" },
|
||||||
|
"ch3_temp": { "name": "Channel 3 temperature" },
|
||||||
|
"ch3_humidity": { "name": "Channel 3 humidity" },
|
||||||
|
"ch4_temp": { "name": "Channel 4 temperature" },
|
||||||
|
"ch4_humidity": { "name": "Channel 4 humidity" },
|
||||||
|
"heat_index": { "name": "Apparent temperature" },
|
||||||
|
"chill_index": { "name": "Wind chill" },
|
||||||
|
"hourly_rain": { "name": "Hourly precipitation" },
|
||||||
|
"weekly_rain": { "name": "Weekly precipitation" },
|
||||||
|
"monthly_rain": { "name": "Monthly precipitation" },
|
||||||
|
"yearly_rain": { "name": "Yearly precipitation" },
|
||||||
|
"wind_azimut": {
|
||||||
|
"name": "Bearing",
|
||||||
|
"state": {
|
||||||
|
"n": "N",
|
||||||
|
"nne": "NNE",
|
||||||
|
"ne": "NE",
|
||||||
|
"ene": "ENE",
|
||||||
|
"e": "E",
|
||||||
|
"ese": "ESE",
|
||||||
|
"se": "SE",
|
||||||
|
"sse": "SSE",
|
||||||
|
"s": "S",
|
||||||
|
"ssw": "SSW",
|
||||||
|
"sw": "SW",
|
||||||
|
"wsw": "WSW",
|
||||||
|
"w": "W",
|
||||||
|
"wnw": "WNW",
|
||||||
|
"nw": "NW",
|
||||||
|
"nnw": "NNW"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notify": {
|
||||||
|
"added": {
|
||||||
|
"title": "New sensors for SWS 12500 found.",
|
||||||
|
"message": "{added_sensors}\n"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,23 +1,107 @@
|
||||||
"""Utils for SWS12500."""
|
"""Utils for SWS12500."""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import math
|
||||||
|
from pathlib import Path
|
||||||
|
import sqlite3
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
from homeassistant.components import persistent_notification
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
|
from homeassistant.const import (
|
||||||
|
UnitOfPrecipitationDepth,
|
||||||
|
UnitOfTemperature,
|
||||||
|
UnitOfVolumetricFlux,
|
||||||
|
)
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.helpers.translation import async_get_translations
|
||||||
|
|
||||||
from .const import REMAP_ITEMS
|
from .const import (
|
||||||
|
AZIMUT,
|
||||||
|
DATABASE_PATH,
|
||||||
|
DEV_DBG,
|
||||||
|
OUTSIDE_HUMIDITY,
|
||||||
|
OUTSIDE_TEMP,
|
||||||
|
REMAP_ITEMS,
|
||||||
|
REMAP_WSLINK_ITEMS,
|
||||||
|
SENSORS_TO_LOAD,
|
||||||
|
WIND_SPEED,
|
||||||
|
UnitOfDir,
|
||||||
|
)
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def update_options(
|
async def translations(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
translation_domain: str,
|
||||||
|
translation_key: str,
|
||||||
|
*,
|
||||||
|
key: str = "message",
|
||||||
|
category: str = "notify",
|
||||||
|
) -> str:
|
||||||
|
"""Get translated keys for domain."""
|
||||||
|
|
||||||
|
localize_key = f"component.{translation_domain}.{category}.{translation_key}.{key}"
|
||||||
|
|
||||||
|
language = hass.config.language
|
||||||
|
|
||||||
|
_translations = await async_get_translations(
|
||||||
|
hass, language, category, [translation_domain]
|
||||||
|
)
|
||||||
|
if localize_key in _translations:
|
||||||
|
return _translations[localize_key]
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
async def translated_notification(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
translation_domain: str,
|
||||||
|
translation_key: str,
|
||||||
|
translation_placeholders: dict[str, str] | None = None,
|
||||||
|
notification_id: str | None = None,
|
||||||
|
*,
|
||||||
|
key: str = "message",
|
||||||
|
category: str = "notify",
|
||||||
|
) -> str:
|
||||||
|
"""Translate notification."""
|
||||||
|
|
||||||
|
localize_key = f"component.{translation_domain}.{category}.{translation_key}.{key}"
|
||||||
|
|
||||||
|
localize_title = (
|
||||||
|
f"component.{translation_domain}.{category}.{translation_key}.title"
|
||||||
|
)
|
||||||
|
|
||||||
|
language = hass.config.language
|
||||||
|
|
||||||
|
_translations = await async_get_translations(
|
||||||
|
hass, language, category, [translation_domain]
|
||||||
|
)
|
||||||
|
if localize_key in _translations:
|
||||||
|
if not translation_placeholders:
|
||||||
|
persistent_notification.async_create(
|
||||||
|
hass,
|
||||||
|
_translations[localize_key],
|
||||||
|
_translations[localize_title],
|
||||||
|
notification_id,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
message = _translations[localize_key].format(**translation_placeholders)
|
||||||
|
persistent_notification.async_create(
|
||||||
|
hass, message, _translations[localize_title], notification_id
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def update_options(
|
||||||
hass: HomeAssistant, entry: ConfigEntry, update_key, update_value
|
hass: HomeAssistant, entry: ConfigEntry, update_key, update_value
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Update config.options entry."""
|
"""Update config.options entry."""
|
||||||
conf = {}
|
conf = {**entry.options}
|
||||||
|
|
||||||
for k in entry.options:
|
|
||||||
conf[k] = entry.options[k]
|
|
||||||
|
|
||||||
conf[update_key] = update_value
|
conf[update_key] = update_value
|
||||||
|
|
||||||
hass.config_entries.async_update_entry(entry, options=conf)
|
return hass.config_entries.async_update_entry(entry, options=conf)
|
||||||
|
|
||||||
|
|
||||||
def anonymize(data):
|
def anonymize(data):
|
||||||
|
|
@ -25,7 +109,7 @@ def anonymize(data):
|
||||||
|
|
||||||
anonym = {}
|
anonym = {}
|
||||||
for k in data:
|
for k in data:
|
||||||
if k not in ("ID", "PASSWORD"):
|
if k not in {"ID", "PASSWORD", "wsid", "wspw"}:
|
||||||
anonym[k] = data[k]
|
anonym[k] = data[k]
|
||||||
|
|
||||||
return anonym
|
return anonym
|
||||||
|
|
@ -39,3 +123,238 @@ def remap_items(entities):
|
||||||
items[REMAP_ITEMS[item]] = entities[item]
|
items[REMAP_ITEMS[item]] = entities[item]
|
||||||
|
|
||||||
return items
|
return items
|
||||||
|
|
||||||
|
|
||||||
|
def remap_wslink_items(entities):
|
||||||
|
"""Remap items in query for WSLink API."""
|
||||||
|
items = {}
|
||||||
|
for item in entities:
|
||||||
|
if item in REMAP_WSLINK_ITEMS:
|
||||||
|
items[REMAP_WSLINK_ITEMS[item]] = entities[item]
|
||||||
|
|
||||||
|
return items
|
||||||
|
|
||||||
|
|
||||||
|
def loaded_sensors(config_entry: ConfigEntry) -> list | None:
|
||||||
|
"""Get loaded sensors."""
|
||||||
|
|
||||||
|
return config_entry.options.get(SENSORS_TO_LOAD) or []
|
||||||
|
|
||||||
|
|
||||||
|
def check_disabled(
|
||||||
|
hass: HomeAssistant, items, config_entry: ConfigEntry
|
||||||
|
) -> list | None:
|
||||||
|
"""Check if we have data for unloaded sensors.
|
||||||
|
|
||||||
|
If so, then add sensor to load queue.
|
||||||
|
|
||||||
|
Returns list of found sensors or None
|
||||||
|
"""
|
||||||
|
|
||||||
|
log: bool = config_entry.options.get(DEV_DBG)
|
||||||
|
entityFound: bool = False
|
||||||
|
_loaded_sensors = loaded_sensors(config_entry)
|
||||||
|
missing_sensors: list = []
|
||||||
|
|
||||||
|
for item in items:
|
||||||
|
if log:
|
||||||
|
_LOGGER.info("Checking %s", item)
|
||||||
|
|
||||||
|
if item not in _loaded_sensors:
|
||||||
|
missing_sensors.append(item)
|
||||||
|
entityFound = True
|
||||||
|
if log:
|
||||||
|
_LOGGER.info("Add sensor (%s) to loading queue", item)
|
||||||
|
|
||||||
|
return missing_sensors if entityFound else None
|
||||||
|
|
||||||
|
|
||||||
|
def wind_dir_to_text(deg: float) -> UnitOfDir | None:
|
||||||
|
"""Return wind direction in text representation.
|
||||||
|
|
||||||
|
Returns UnitOfDir or None
|
||||||
|
"""
|
||||||
|
|
||||||
|
if deg:
|
||||||
|
return AZIMUT[int(abs((float(deg) - 11.25) % 360) / 22.5)]
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def fahrenheit_to_celsius(fahrenheit: float) -> float:
|
||||||
|
"""Convert Fahrenheit to Celsius."""
|
||||||
|
return (fahrenheit - 32) * 5.0 / 9.0
|
||||||
|
|
||||||
|
|
||||||
|
def celsius_to_fahrenheit(celsius: float) -> float:
|
||||||
|
"""Convert Celsius to Fahrenheit."""
|
||||||
|
return celsius * 9.0 / 5.0 + 32
|
||||||
|
|
||||||
|
|
||||||
|
def heat_index(data: Any, convert: bool = False) -> UnitOfTemperature:
|
||||||
|
"""Calculate heat index from temperature.
|
||||||
|
|
||||||
|
data: dict with temperature and humidity
|
||||||
|
convert: bool, convert recieved data from Celsius to Fahrenheit
|
||||||
|
"""
|
||||||
|
|
||||||
|
temp = float(data[OUTSIDE_TEMP])
|
||||||
|
rh = float(data[OUTSIDE_HUMIDITY])
|
||||||
|
adjustment = None
|
||||||
|
|
||||||
|
if convert:
|
||||||
|
temp = celsius_to_fahrenheit(temp)
|
||||||
|
|
||||||
|
simple = 0.5 * (temp + 61.0 + ((temp - 68.0) * 1.2) + (rh * 0.094))
|
||||||
|
if ((simple + temp) / 2) > 80:
|
||||||
|
full_index = (
|
||||||
|
-42.379
|
||||||
|
+ 2.04901523 * temp
|
||||||
|
+ 10.14333127 * rh
|
||||||
|
- 0.22475541 * temp * rh
|
||||||
|
- 0.00683783 * temp * temp
|
||||||
|
- 0.05481717 * rh * rh
|
||||||
|
+ 0.00122874 * temp * temp * rh
|
||||||
|
+ 0.00085282 * temp * rh * rh
|
||||||
|
- 0.00000199 * temp * temp * rh * rh
|
||||||
|
)
|
||||||
|
if rh < 13 and (temp in np.arange(80, 112, 0.1)):
|
||||||
|
adjustment = ((13 - rh) / 4) * math.sqrt((17 - abs(temp - 95)) / 17)
|
||||||
|
|
||||||
|
if rh > 80 and (temp in np.arange(80, 87, 0.1)):
|
||||||
|
adjustment = ((rh - 85) / 10) * ((87 - temp) / 5)
|
||||||
|
|
||||||
|
return round((full_index + adjustment if adjustment else full_index), 2)
|
||||||
|
|
||||||
|
return simple
|
||||||
|
|
||||||
|
|
||||||
|
def chill_index(data: Any, convert: bool = False) -> UnitOfTemperature:
|
||||||
|
"""Calculate wind chill index from temperature and wind speed.
|
||||||
|
|
||||||
|
data: dict with temperature and wind speed
|
||||||
|
convert: bool, convert recieved data from Celsius to Fahrenheit
|
||||||
|
"""
|
||||||
|
|
||||||
|
temp = float(data[OUTSIDE_TEMP])
|
||||||
|
wind = float(data[WIND_SPEED])
|
||||||
|
|
||||||
|
if convert:
|
||||||
|
temp = celsius_to_fahrenheit(temp)
|
||||||
|
|
||||||
|
return (
|
||||||
|
round(
|
||||||
|
(
|
||||||
|
(35.7 + (0.6215 * temp))
|
||||||
|
- (35.75 * (wind**0.16))
|
||||||
|
+ (0.4275 * (temp * (wind**0.16)))
|
||||||
|
),
|
||||||
|
2,
|
||||||
|
)
|
||||||
|
if temp < 50 and wind > 3
|
||||||
|
else temp
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def long_term_units_in_statistics_meta():
|
||||||
|
"""Get units in long term statitstics."""
|
||||||
|
|
||||||
|
if not Path(DATABASE_PATH).exists():
|
||||||
|
_LOGGER.error("Database file not found: %s", DATABASE_PATH)
|
||||||
|
return False
|
||||||
|
|
||||||
|
conn = sqlite3.connect(DATABASE_PATH)
|
||||||
|
db = conn.cursor()
|
||||||
|
|
||||||
|
try:
|
||||||
|
db.execute("""
|
||||||
|
SELECT statistic_id, unit_of_measurement from statistics_meta
|
||||||
|
WHERE statistic_id LIKE 'sensor.weather_station_sws%'
|
||||||
|
""")
|
||||||
|
rows = db.fetchall()
|
||||||
|
sensor_units = {
|
||||||
|
statistic_id: f"{statistic_id} ({unit})" for statistic_id, unit in rows
|
||||||
|
}
|
||||||
|
|
||||||
|
except sqlite3.Error as e:
|
||||||
|
_LOGGER.error("Error during data migration: %s", e)
|
||||||
|
finally:
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
return sensor_units
|
||||||
|
|
||||||
|
|
||||||
|
async def migrate_data(hass: HomeAssistant, sensor_id: str | None = None) -> bool:
|
||||||
|
"""Migrate data from mm/d to mm."""
|
||||||
|
|
||||||
|
_LOGGER.debug("Sensor %s is required for data migration", sensor_id)
|
||||||
|
updated_rows = 0
|
||||||
|
|
||||||
|
if not Path(DATABASE_PATH).exists():
|
||||||
|
_LOGGER.error("Database file not found: %s", DATABASE_PATH)
|
||||||
|
return False
|
||||||
|
|
||||||
|
conn = sqlite3.connect(DATABASE_PATH)
|
||||||
|
db = conn.cursor()
|
||||||
|
|
||||||
|
try:
|
||||||
|
_LOGGER.info(sensor_id)
|
||||||
|
db.execute(
|
||||||
|
"""
|
||||||
|
UPDATE statistics_meta
|
||||||
|
SET unit_of_measurement = 'mm'
|
||||||
|
WHERE statistic_id = ?
|
||||||
|
AND unit_of_measurement = 'mm/d';
|
||||||
|
""",
|
||||||
|
(sensor_id,),
|
||||||
|
)
|
||||||
|
updated_rows = db.rowcount
|
||||||
|
conn.commit()
|
||||||
|
_LOGGER.info(
|
||||||
|
"Data migration completed successfully. Updated rows: %s for %s",
|
||||||
|
updated_rows,
|
||||||
|
sensor_id,
|
||||||
|
)
|
||||||
|
|
||||||
|
except sqlite3.Error as e:
|
||||||
|
_LOGGER.error("Error during data migration: %s", e)
|
||||||
|
finally:
|
||||||
|
conn.close()
|
||||||
|
return updated_rows
|
||||||
|
|
||||||
|
|
||||||
|
def migrate_data_old(sensor_id: str | None = None):
|
||||||
|
"""Migrate data from mm/d to mm."""
|
||||||
|
updated_rows = 0
|
||||||
|
|
||||||
|
if not Path(DATABASE_PATH).exists():
|
||||||
|
_LOGGER.error("Database file not found: %s", DATABASE_PATH)
|
||||||
|
return False
|
||||||
|
|
||||||
|
conn = sqlite3.connect(DATABASE_PATH)
|
||||||
|
db = conn.cursor()
|
||||||
|
|
||||||
|
try:
|
||||||
|
_LOGGER.info(sensor_id)
|
||||||
|
db.execute(
|
||||||
|
"""
|
||||||
|
UPDATE statistics_meta
|
||||||
|
SET unit_of_measurement = 'mm'
|
||||||
|
WHERE statistic_id = ?
|
||||||
|
AND unit_of_measurement = 'mm/d';
|
||||||
|
""",
|
||||||
|
(sensor_id,),
|
||||||
|
)
|
||||||
|
updated_rows = db.rowcount
|
||||||
|
conn.commit()
|
||||||
|
_LOGGER.info(
|
||||||
|
"Data migration completed successfully. Updated rows: %s for %s",
|
||||||
|
updated_rows,
|
||||||
|
sensor_id,
|
||||||
|
)
|
||||||
|
|
||||||
|
except sqlite3.Error as e:
|
||||||
|
_LOGGER.error("Error during data migration: %s", e)
|
||||||
|
finally:
|
||||||
|
conn.close()
|
||||||
|
return updated_rows
|
||||||
|
|
|
||||||
|
|
@ -3,10 +3,9 @@
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
import aiohttp
|
|
||||||
|
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||||
|
|
||||||
from .const import (
|
from .const import (
|
||||||
PURGE_DATA,
|
PURGE_DATA,
|
||||||
|
|
@ -69,6 +68,9 @@ class WindyPush:
|
||||||
) -> WindyNotInserted | WindySuccess | WindyApiKeyError | None:
|
) -> WindyNotInserted | WindySuccess | WindyApiKeyError | None:
|
||||||
"""Verify answer form Windy."""
|
"""Verify answer form Windy."""
|
||||||
|
|
||||||
|
if self.log:
|
||||||
|
_LOGGER.info("Windy response raw response: %s", response)
|
||||||
|
|
||||||
if "NOTICE" in response:
|
if "NOTICE" in response:
|
||||||
raise WindyNotInserted
|
raise WindyNotInserted
|
||||||
|
|
||||||
|
|
@ -78,6 +80,9 @@ class WindyPush:
|
||||||
if "Invalid API key" in response:
|
if "Invalid API key" in response:
|
||||||
raise WindyApiKeyError
|
raise WindyApiKeyError
|
||||||
|
|
||||||
|
if "Unauthorized" in response:
|
||||||
|
raise WindyApiKeyError
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
async def push_data_to_windy(self, data):
|
async def push_data_to_windy(self, data):
|
||||||
|
|
@ -116,10 +121,7 @@ class WindyPush:
|
||||||
|
|
||||||
if self.log:
|
if self.log:
|
||||||
_LOGGER.info("Dataset for windy: %s", purged_data)
|
_LOGGER.info("Dataset for windy: %s", purged_data)
|
||||||
|
session = async_get_clientsession(self.hass, verify_ssl=False)
|
||||||
async with aiohttp.ClientSession(
|
|
||||||
connector=aiohttp.TCPConnector(ssl=False), trust_env=True
|
|
||||||
) as session: # verify_ssl=False; intended to be False
|
|
||||||
try:
|
try:
|
||||||
async with session.get(request_url, params=purged_data) as resp:
|
async with session.get(request_url, params=purged_data) as resp:
|
||||||
status = await resp.text()
|
status = await resp.text()
|
||||||
|
|
@ -143,7 +145,7 @@ class WindyPush:
|
||||||
_LOGGER.info(WINDY_SUCCESS)
|
_LOGGER.info(WINDY_SUCCESS)
|
||||||
text_for_test = WINDY_SUCCESS
|
text_for_test = WINDY_SUCCESS
|
||||||
|
|
||||||
except aiohttp.ClientConnectionError as ex:
|
except session.ClientError as ex:
|
||||||
_LOGGER.critical("Invalid response from Windy: %s", str(ex))
|
_LOGGER.critical("Invalid response from Windy: %s", str(ex))
|
||||||
self.invalid_response_count += 1
|
self.invalid_response_count += 1
|
||||||
if self.invalid_response_count > 3:
|
if self.invalid_response_count > 3:
|
||||||
|
|
@ -156,6 +158,7 @@ class WindyPush:
|
||||||
|
|
||||||
if self.log:
|
if self.log:
|
||||||
_LOGGER.info("Next update: %s", str(self.next_update))
|
_LOGGER.info("Next update: %s", str(self.next_update))
|
||||||
|
|
||||||
if RESPONSE_FOR_TEST and text_for_test:
|
if RESPONSE_FOR_TEST and text_for_test:
|
||||||
return text_for_test
|
return text_for_test
|
||||||
return None
|
return None
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
"""Shared keys and helpers for storing integration runtime state in hass.data.
|
||||||
|
|
||||||
|
This integration uses `hass.data[DOMAIN][entry_id]` as a per-entry dictionary.
|
||||||
|
Keeping keys in one place prevents subtle bugs where different modules store
|
||||||
|
different value types under the same key.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import Final
|
||||||
|
|
||||||
|
|
||||||
|
# Per-entry dict keys stored under hass.data[DOMAIN][entry_id]
|
||||||
|
ENTRY_COORDINATOR: Final[str] = "coordinator"
|
||||||
|
ENTRY_ADD_ENTITIES: Final[str] = "async_add_entities"
|
||||||
|
ENTRY_DESCRIPTIONS: Final[str] = "sensor_descriptions"
|
||||||
|
ENTRY_LAST_OPTIONS: Final[str] = "last_options"
|
||||||
|
|
@ -0,0 +1,75 @@
|
||||||
|
# :bug: Firmware version 1.0 bug
|
||||||
|
|
||||||
|
In station's `firmware 1.0` is a bug preventing data to be sent to Home Assistant.
|
||||||
|
|
||||||
|
This might be a problem even on firmwares > 1.0.
|
||||||
|
|
||||||
|
|
||||||
|
## :thinking: The issue
|
||||||
|
|
||||||
|
Once you have set an URL in station with port as `url:port`, the bug will cause that data will not be sent to the Home Station at all.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
If you ommit the `:port`, station will send data to designated port `:80` of your URL.
|
||||||
|
|
||||||
|
## :adhesive_bandage: Workaround
|
||||||
|
|
||||||
|
:bulb: There is a solution to this!
|
||||||
|
|
||||||
|
You have to redirect incoming data on `port 80` from station's IP address to `port 8123` or what ever port your instance of Home Assistant is running. To achive this, you have to run `iptables` to redirect ports.
|
||||||
|
|
||||||
|
Eg. `192.168.1.2.:any (station) -> 192.168:1.1:80 ---> 192.168.1.1:8123 (HA)`
|
||||||
|
|
||||||
|
So I provide a script that will handle this bug. But you must run this script every time you restarted Home Assistant, because `iptables` will not remain on reboot.
|
||||||
|
|
||||||
|
Ok, now how to do it?
|
||||||
|
|
||||||
|
### Step one
|
||||||
|
|
||||||
|
Install [Advanced SSH & Web Terminal](https://github.com/hassio-addons/addon-ssh/blob/main/ssh/DOCS.md) from add-ons. Yes, it **has to be** `Advanced SSH & Web Terminal` not regular `Terminal & SSH`, as regular `Terminal & SSH` do not have such functions, capabilities and privileges.
|
||||||
|
|
||||||
|
### Step two
|
||||||
|
|
||||||
|
After you have installed `Advanced SSH & Web Terminal` [configure ](https://github.com/hassio-addons/addon-ssh/blob/main/ssh/DOCS.md) it to run on some free port (eg. 23). Make sure you have configured `username` and `authorized_keys`. Please be advised, that this script will not work with `password`.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### Step three
|
||||||
|
|
||||||
|
Open `Web Terminal`. Copy and paste this command:
|
||||||
|
|
||||||
|
```
|
||||||
|
bash <(wget -q -O - https://raw.githubusercontent.com/schizza/SWS-12500-custom-component/main/install_iptables.sh)
|
||||||
|
```
|
||||||
|
|
||||||
|
This will download and run installation script of this workaround.
|
||||||
|
Follow the instructions in the script.
|
||||||
|
|
||||||
|
You will be asked for:
|
||||||
|
|
||||||
|
* your station's IP
|
||||||
|
* Home Assistant IP
|
||||||
|
* Home Assistatn port
|
||||||
|
* username in SSH addon (this is one you setup upon installation of the addon)
|
||||||
|
* SSH port (also one you setup with the addon)
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
The scritp will set all you need to modify iptables on every Home Assistant start.
|
||||||
|
|
||||||
|
## :warning: Scrip will modify `configuration.yaml` and `automations.yaml` so, please look at it and check that it does not contain any error.
|
||||||
|
If you have already set `shell_command` in your `configuration.yaml` then you have to transfer created one to your already set one.
|
||||||
|
|
||||||
|
### Step four
|
||||||
|
|
||||||
|
Script files are stored in your `config/iptables_redirect` directory.
|
||||||
|
There is also your public key for your SSH server configuration in `ssh` directory. It is important to add public key to your SSH server configuration.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
https://github.com/schizza/SWS-12500-custom-component/assets/4604900/bf0a3438-e23b-4425-8de1-6329e4d74319
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
So you are no all set! :tada:
|
||||||
|
|
@ -0,0 +1,292 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Installation script for iptables redirect
|
||||||
|
|
||||||
|
RED_COLOR='\033[0;31m'
|
||||||
|
GREEN_COLOR='\033[0;32m'
|
||||||
|
GREEN_YELLOW='\033[1;33m'
|
||||||
|
NO_COLOR='\033[0m'
|
||||||
|
|
||||||
|
ST_PORT=80
|
||||||
|
|
||||||
|
LINK="https://raw.githubusercontent.com/schizza/SWS-12500-custom-component/main/iptables_redirect.sh"
|
||||||
|
FILENAME="iptables_redirect.sh"
|
||||||
|
SCRIPT_DIR="iptables_redirect"
|
||||||
|
|
||||||
|
P_HA=true
|
||||||
|
P_ST=true
|
||||||
|
|
||||||
|
declare -a HA_PATHS=(
|
||||||
|
"/homeassistant"
|
||||||
|
"$PWD"
|
||||||
|
"$PWD/config"
|
||||||
|
"/config"
|
||||||
|
"$HOME/.homeassistant"
|
||||||
|
"/usr/share/hassio/homeassistant"
|
||||||
|
)
|
||||||
|
|
||||||
|
function info() { echo -e $2 "${GREEN_COLOR}$1${NO_COLOR}"; }
|
||||||
|
function warn() { echo -e $2 "${GREEN_YELLOW}$1${NO_COLOR}"; }
|
||||||
|
function error() {
|
||||||
|
echo -e "${RED_COLOR}$1${NO_COLOR}"
|
||||||
|
if [ "$2" != "false" ]; then exit 1; fi
|
||||||
|
}
|
||||||
|
|
||||||
|
function check() {
|
||||||
|
echo -n "Checking dependencies: '$1' ... "
|
||||||
|
if [ -z "$(command -v "$1")" ]; then
|
||||||
|
error "not installed" $2
|
||||||
|
false
|
||||||
|
else
|
||||||
|
info "OK."
|
||||||
|
true
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
function validate_ip() {
|
||||||
|
|
||||||
|
if [[ "$1" =~ ^(([1-9]?[0-9]|1[0-9][0-9]|2([0-4][0-9]|5[0-5]))\.){3}([1-9]?[0-9]|1[0-9][0-9]|2([0-4][0-9]|5[0-5]))$ ]]; then
|
||||||
|
true
|
||||||
|
else
|
||||||
|
false
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
function validate_num() {
|
||||||
|
if [[ "$1" =~ ^[0-9]+$ ]]; then true; else false; fi
|
||||||
|
}
|
||||||
|
|
||||||
|
function validate_dest() {
|
||||||
|
echo -n "Validating host '$1' ... "
|
||||||
|
if ping -c 2 $1 >/dev/null 2>&1; then
|
||||||
|
info "OK"
|
||||||
|
true
|
||||||
|
else
|
||||||
|
error "cannot reach" false
|
||||||
|
false
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
function exit_status() {
|
||||||
|
# argv 1 - status
|
||||||
|
# 2 - called function
|
||||||
|
# 3 - error message
|
||||||
|
# 4 - success message
|
||||||
|
# 5 - exit on error bool
|
||||||
|
|
||||||
|
if [ $1 -ne 0 ]; then
|
||||||
|
warn "$2 exited with error: $1"
|
||||||
|
error "$3" $5
|
||||||
|
else
|
||||||
|
info "$4"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
function cont() {
|
||||||
|
|
||||||
|
while true; do
|
||||||
|
warn "$1"
|
||||||
|
warn "Do you want to continue? [y/N]: " -n
|
||||||
|
read -n 1 YN
|
||||||
|
YN=${YN:-N}
|
||||||
|
case $YN in
|
||||||
|
[Yy])
|
||||||
|
echo -e "\n"
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
|
[Nn]) error "\nExiting." ;;
|
||||||
|
*) error "\nInvalid response.\n" false ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "**************************************************************"
|
||||||
|
echo "* *"
|
||||||
|
echo -e "* ${GREEN_YELLOW}Installation for iptables_redirect.sh ${NO_COLOR} *"
|
||||||
|
echo "* *"
|
||||||
|
echo "**************************************************************"
|
||||||
|
echo
|
||||||
|
|
||||||
|
check "wget"
|
||||||
|
check "sed"
|
||||||
|
check "ping" false && { PING=true; } || { PING=false; }
|
||||||
|
check "ssh-keygen" false && { KEYGEN=true; } || { KEYGEN=false; }
|
||||||
|
|
||||||
|
echo -n "Trying to find Home Assitant ... "
|
||||||
|
for _PATH in "${HA_PATHS[@]}"; do
|
||||||
|
if [ -n "$HA_PATH" ]; then
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -f "$_PATH/.HA_VERSION" ]; then
|
||||||
|
HA_PATH="$_PATH"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
COMPLETE_PATH="$HA_PATH/$SCRIPT_DIR"
|
||||||
|
FILENAME="$COMPLETE_PATH/$FILENAME"
|
||||||
|
|
||||||
|
[ -z $HA_PATH ] && { error "Home Assistant not found!"; }
|
||||||
|
info "found at $HA_PATH"
|
||||||
|
|
||||||
|
[ -d $COMPLETE_PATH ] && {
|
||||||
|
warn "Previous version of script exists ... removing directory ($COMPLETE_PATH)"
|
||||||
|
rm -r $COMPLETE_PATH
|
||||||
|
}
|
||||||
|
|
||||||
|
mkdir -p $COMPLETE_PATH
|
||||||
|
|
||||||
|
while true; do
|
||||||
|
read -r -p "Your station's IP: " ST_IP
|
||||||
|
if validate_ip $ST_IP; then break; fi
|
||||||
|
warn "Provide valid IP address."
|
||||||
|
done
|
||||||
|
|
||||||
|
while true; do
|
||||||
|
read -r -p "Home Assistant's IP: " HA_IP
|
||||||
|
if validate_ip $HA_IP; then break; fi
|
||||||
|
warn "Provide valid IP address."
|
||||||
|
done
|
||||||
|
|
||||||
|
while true; do
|
||||||
|
read -r -p "Home Assistant's port [8123]: " HA_PORT
|
||||||
|
HA_PORT=${HA_PORT:-8123}
|
||||||
|
if validate_num $HA_PORT && ((HA_PORT >= 1 && HA_PORT <= 65535)); then
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
warn "Provide valid port number."
|
||||||
|
done
|
||||||
|
|
||||||
|
read -r -p "SSH server username: " SSH_USER
|
||||||
|
read -r -p "SSH server port: " SSH_PORT
|
||||||
|
|
||||||
|
if $PING; then
|
||||||
|
validate_dest $HA_IP || {
|
||||||
|
cont "Home Assistant host is unreachable."
|
||||||
|
P_HA=false
|
||||||
|
}
|
||||||
|
validate_dest $ST_IP || {
|
||||||
|
cont "Station is unreachable."
|
||||||
|
P_ST=false
|
||||||
|
}
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -n "Downloading 'iptables_redirect.sh' ... "
|
||||||
|
wget -q -O - "$LINK" | sed -e "s/\[_STATION_IP_\]/$ST_IP/" \
|
||||||
|
-e "s/\[_HA_\]/$HA_IP/" \
|
||||||
|
-e "s/\[_SRC_PORT_\]/$ST_PORT/" \
|
||||||
|
-e "s/\[_DST_PORT_\]/$HA_PORT/" >$FILENAME
|
||||||
|
|
||||||
|
exit_status $? "wget" \
|
||||||
|
"Could not download 'iptables_redirect.sh'." \
|
||||||
|
"iptables_redirect.sh downloaded successffully."
|
||||||
|
|
||||||
|
if $KEYGEN; then
|
||||||
|
echo -n "Generating ssh key-pairs ... "
|
||||||
|
mkdir -p "$COMPLETE_PATH/ssh"
|
||||||
|
ssh-keygen -t ecdsa -b 521 -N "" -f "$COMPLETE_PATH/ssh/ipt_dsa" -q
|
||||||
|
exit_status $? "ssh-keygen" \
|
||||||
|
"Could not create ssh key-pairs." \
|
||||||
|
"SSH key-pairs created successfully (at $COMPLETE_PATH/ssh/)" \
|
||||||
|
false
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -n "Creating 'exec.sh' script ... "
|
||||||
|
cat >$COMPLETE_PATH/exec.sh <<-EOF
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
cat iptables_redirect/iptables_redirect.sh | ssh -i iptables_redirect/ssh/ipt_dsa -o StrictHostKeyChecking=no -p $SSH_PORT -l $SSH_USER $HA_IP /bin/zsh
|
||||||
|
EOF
|
||||||
|
|
||||||
|
exit_status $? "cat" \
|
||||||
|
"Could not write '$COMPLETE_PATH/exec.sh'" \
|
||||||
|
"OK."
|
||||||
|
|
||||||
|
echo -n "Setting 'exec.sh' script right privileges ... "
|
||||||
|
chmod -f a+rx "$COMPLETE_PATH/exec.sh"
|
||||||
|
exit_status $? "chmod" \
|
||||||
|
"Filed to set +x on exec.sh" \
|
||||||
|
"OK."
|
||||||
|
|
||||||
|
echo -n "Setting 'iptables_redirect.sh' script right privileges ... "
|
||||||
|
chmod -f a+rx "$COMPLETE_PATH/iptables_redirect.sh"
|
||||||
|
exit_status $? "chmod" \
|
||||||
|
"Filed to set +x on exec.sh" \
|
||||||
|
"OK."
|
||||||
|
|
||||||
|
echo -n "Creating 'runscript' ... "
|
||||||
|
cat >$COMPLETE_PATH/runscript <<-"EOF"
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
SCRIPT=$(find /homeassistant -name "iptables_redirect.sh" | sed -n 1p)
|
||||||
|
sudo $SCRIPT
|
||||||
|
EOF
|
||||||
|
|
||||||
|
exit_status $? "cat" \
|
||||||
|
"Could not write 'runscript'" \
|
||||||
|
"OK."
|
||||||
|
|
||||||
|
echo -n "Modifying configuration.yaml ... "
|
||||||
|
cat >>$HA_PATH/configuration.yaml <<EOF
|
||||||
|
|
||||||
|
shell_command:
|
||||||
|
iptables_script: iptables_redirect/exec.sh
|
||||||
|
EOF
|
||||||
|
|
||||||
|
exit_status $? "cat" \
|
||||||
|
"Could not modify configuration.yaml" \
|
||||||
|
"OK." \
|
||||||
|
false
|
||||||
|
|
||||||
|
echo -n "Modifying automations.yaml ... "
|
||||||
|
cat >>$HA_PATH/automations.yaml <<EOF
|
||||||
|
|
||||||
|
- id: '1714725977432'
|
||||||
|
alias: Run iptables_redirect on HA start
|
||||||
|
description: On every start we will run iptables_redirect script to ensure accepting
|
||||||
|
data from station with firmware 1.0
|
||||||
|
trigger:
|
||||||
|
- platform: homeassistant
|
||||||
|
event: start
|
||||||
|
condition: []
|
||||||
|
action:
|
||||||
|
- service: shell_command.iptables_script
|
||||||
|
metadata: {}
|
||||||
|
data: {}
|
||||||
|
mode: single
|
||||||
|
EOF
|
||||||
|
|
||||||
|
exit_status $? "cat" \
|
||||||
|
"Could not modify automations.yaml" \
|
||||||
|
"OK." \
|
||||||
|
false
|
||||||
|
|
||||||
|
echo "Executing 'iptables_redirecet.sh' ... "
|
||||||
|
|
||||||
|
/bin/bash $FILENAME
|
||||||
|
FIRST_RUN=$?
|
||||||
|
exit_status $FIRST_RUN "iptables_redirect.sh" \
|
||||||
|
"iptables_redirect scritp did not run successfully.\n But is installed in $FILENAME.\n Please run it again a look at the log." \
|
||||||
|
"First run of 'iptables_redirect.sh' was successfful. Your iptables are set." \
|
||||||
|
false
|
||||||
|
|
||||||
|
info "\nYour configuration:"
|
||||||
|
info " Home Assistant at: $HA_PATH"
|
||||||
|
info " Home Assistant server at: $HA_IP:$HA_PORT" -n
|
||||||
|
if $PING; then
|
||||||
|
if $P_HA; then info " (ping OK)"; else error " (unreachable)" false; fi
|
||||||
|
else
|
||||||
|
error " (not tested)" false
|
||||||
|
fi
|
||||||
|
info " Station at: ${ST_IP}:$ST_PORT" -n
|
||||||
|
if $PING; then
|
||||||
|
if $P_ST; then info " (ping OK)"; else error " (unreachable)" false; fi
|
||||||
|
else
|
||||||
|
error " (not tested)" false
|
||||||
|
fi
|
||||||
|
|
||||||
|
info " First run of 'iptables_redirect.sh' script " -n
|
||||||
|
[ $FIRST_RUN -ne 0 ] && { error " failed." false; } || { info " passed."; }
|
||||||
|
|
||||||
|
info " SSH pub_key: at $COMPLETE_PATH/ssh/ipt_dsa.pub"
|
||||||
|
|
@ -0,0 +1,90 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Script for frowarding SWS 12500 station's destination port 80
|
||||||
|
# to your Home Assistant's instance port (8123)
|
||||||
|
#
|
||||||
|
# Workaround for station's firmware 1.0 bug
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# Script pro přesměrování portu pro stanici SWS12500
|
||||||
|
|
||||||
|
STATION_IP=[_STATION_IP_]
|
||||||
|
HA=[_HA_]
|
||||||
|
SRC_PORT=[_SRC_PORT_]
|
||||||
|
DST_PORT=[_DST_PORT_]
|
||||||
|
|
||||||
|
INSTALL_IPTABLES=0
|
||||||
|
APK_MISSING=0
|
||||||
|
|
||||||
|
RED_COLOR='\033[0;31m'
|
||||||
|
GREEN_COLOR='\033[0;32m'
|
||||||
|
GREEN_YELLOW='\033[1;33m'
|
||||||
|
NO_COLOR='\033[0m'
|
||||||
|
|
||||||
|
function info() { echo -e "${GREEN_COLOR}$1${NO_COLOR}"; }
|
||||||
|
function warn() { echo -e "${GREEN_YELLOW}$1${NO_COLOR}"; }
|
||||||
|
function error() {
|
||||||
|
echo -e "${RED_COLOR}$1${NO_COLOR}"
|
||||||
|
if [ "$2" != "false" ]; then exit 1; fi
|
||||||
|
}
|
||||||
|
|
||||||
|
function check() {
|
||||||
|
echo -n "Checking dependencies: '$1' ... "
|
||||||
|
if [ -z "$(command -v "$1")" ]; then
|
||||||
|
error "not installed" $2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
info "OK."
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "**************************************************************"
|
||||||
|
echo "* *"
|
||||||
|
echo -e "* ${GREEN_YELLOW}Running iptables forward for port $SRC_PORT -> $DST_PORT ${NO_COLOR} *"
|
||||||
|
echo "* *"
|
||||||
|
echo "**************************************************************"
|
||||||
|
echo
|
||||||
|
|
||||||
|
# Check for dependencies
|
||||||
|
|
||||||
|
check "iptables" false
|
||||||
|
INSTALL_IPTABLES=$?
|
||||||
|
|
||||||
|
check "apk" false
|
||||||
|
APK_MISSING=$?
|
||||||
|
|
||||||
|
if [ $APK_MISSING -eq 1 ] && [ $INSTALL_IPTABLES -eq 1 ]; then
|
||||||
|
error "Could not install and run iptables.\n'apk' installer is missing and 'iptables' are not installed.\n"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ $INSTALL_IPTABLES -eq 1 ] && [ $APK_MISSING -eq 0 ]; then
|
||||||
|
echo -n "Installing 'iptables' ... ${RUNINSTALL[@]} ... "
|
||||||
|
sudo apk add iptables
|
||||||
|
EXIT_STATUS=$?
|
||||||
|
if [ $EXIT_STATUS -ne 0 ]; then
|
||||||
|
warn "apk error code: $EXIT_STATUS"
|
||||||
|
error "Installation of iptables failed!"
|
||||||
|
else
|
||||||
|
info "'iptables' installed successfully."
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
declare -a RULE=(PREROUTING -t nat -s $STATION_IP -d $HA -p tcp -m tcp --dport $SRC_PORT -j REDIRECT --to-ports $DST_PORT)
|
||||||
|
echo -n "Chceking for existing rule in iptables ... "
|
||||||
|
sudo iptables -C ${RULE[@]} 2>/dev/null
|
||||||
|
if [ $? -eq 0 ]; then
|
||||||
|
warn "Rule is already present in PREROUTING chain."
|
||||||
|
else
|
||||||
|
info "not found."
|
||||||
|
echo -n "Inserting iptables rule to PREROUTING chain ... "
|
||||||
|
sudo iptables -I ${RULE[@]} 2>/dev/null
|
||||||
|
fi
|
||||||
|
EXIT_STATUS=$?
|
||||||
|
if [ $EXIT_STATUS -ne 0 ]; then
|
||||||
|
warn "iptables error code: ${EXIT_STATUS} "
|
||||||
|
error "Rule could not be added!"
|
||||||
|
else
|
||||||
|
info "OK."
|
||||||
|
fi
|
||||||
|
|
||||||
|
info "iptables are now set to redirect incoming connections from $STATION_IP:Any -> $HA:$SRC_PORT to $HA:$DST_PORT"
|
||||||
Loading…
Reference in New Issue