├── nfws ├── icon.png ├── logo.png ├── help │ ├── addon_config.jpg │ ├── netatmo_ids.jpg │ ├── netatmo_accept.jpg │ ├── netatmo_add_fav.jpg │ ├── netatmo_new_app.jpg │ ├── netatmo_screenshot.png │ ├── netatmo_accept_code.jpg │ ├── netatmo_favorites_list.png │ ├── options.yaml │ └── stations_example.yaml ├── rootfs │ ├── usr │ │ └── bin │ │ │ ├── stations.yaml │ │ │ └── nfws.py │ └── etc │ │ └── services.d │ │ └── nfws │ │ ├── finish │ │ └── run ├── translations │ └── en.yaml ├── Dockerfile ├── build.yaml ├── CHANGELOG.md ├── config.yaml ├── README.md └── DOCS.md ├── PIDtabule ├── priklad.png ├── appdaemon.yaml ├── markdown.yaml ├── flexcard.yaml ├── README.md ├── apps.yaml └── PIDtabule.py ├── .github ├── dependabot.yaml └── workflows │ ├── lint.yaml │ └── builder.yaml ├── etrel_inch ├── configuration.yaml └── etrel-modbus.yaml ├── repository.yaml ├── ElectricConsumptionDbRepair ├── appdaemon.yaml ├── README.md ├── apps.yaml ├── electric_consumption_db_repair.log └── ElectricConsumptionDbRepair.py ├── wattsonic ├── configuration.yaml ├── sunways_gen2 │ ├── configuration.yaml │ ├── sunways_sql_postgre.yaml │ └── lovelace.yaml ├── wattsonic_sql_postgre.yaml ├── wgraph.yaml ├── wattsonic_sql_mariadb.yaml ├── sunsynk.yaml ├── README.md ├── lovelace.yaml ├── wattsonic_Variables+History.yaml ├── wattsonic_gen2.yaml └── wattsonic.yaml ├── .vscode └── tasks.json ├── .devcontainer.json ├── README.md └── LICENSE /nfws/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GiZMoSK1221/hass-addons/HEAD/nfws/icon.png -------------------------------------------------------------------------------- /nfws/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GiZMoSK1221/hass-addons/HEAD/nfws/logo.png -------------------------------------------------------------------------------- /PIDtabule/priklad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GiZMoSK1221/hass-addons/HEAD/PIDtabule/priklad.png -------------------------------------------------------------------------------- /nfws/help/addon_config.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GiZMoSK1221/hass-addons/HEAD/nfws/help/addon_config.jpg -------------------------------------------------------------------------------- /nfws/help/netatmo_ids.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GiZMoSK1221/hass-addons/HEAD/nfws/help/netatmo_ids.jpg -------------------------------------------------------------------------------- /nfws/help/netatmo_accept.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GiZMoSK1221/hass-addons/HEAD/nfws/help/netatmo_accept.jpg -------------------------------------------------------------------------------- /nfws/help/netatmo_add_fav.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GiZMoSK1221/hass-addons/HEAD/nfws/help/netatmo_add_fav.jpg -------------------------------------------------------------------------------- /nfws/help/netatmo_new_app.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GiZMoSK1221/hass-addons/HEAD/nfws/help/netatmo_new_app.jpg -------------------------------------------------------------------------------- /nfws/help/netatmo_screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GiZMoSK1221/hass-addons/HEAD/nfws/help/netatmo_screenshot.png -------------------------------------------------------------------------------- /nfws/help/netatmo_accept_code.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GiZMoSK1221/hass-addons/HEAD/nfws/help/netatmo_accept_code.jpg -------------------------------------------------------------------------------- /nfws/help/netatmo_favorites_list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GiZMoSK1221/hass-addons/HEAD/nfws/help/netatmo_favorites_list.png -------------------------------------------------------------------------------- /nfws/rootfs/usr/bin/stations.yaml: -------------------------------------------------------------------------------- 1 | netatmo_stations: 2 | "70:ee:50:17:6d:a6": 3 | name: test_station 4 | sensors: 5 | - Temperature 6 | -------------------------------------------------------------------------------- /nfws/translations/en.yaml: -------------------------------------------------------------------------------- 1 | configuration: 2 | message: 3 | name: Message 4 | description: The message that will be printed to the log when starting this example add-on. 5 | -------------------------------------------------------------------------------- /.github/dependabot.yaml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: weekly 7 | time: "06:00" 8 | -------------------------------------------------------------------------------- /etrel_inch/configuration.yaml: -------------------------------------------------------------------------------- 1 | # Configure a default setup of Home Assistant (frontend, api, etc) 2 | default_config: 3 | 4 | homeassistant: 5 | packages: 6 | wattsonic: !include etrel-modbus.yaml 7 | -------------------------------------------------------------------------------- /repository.yaml: -------------------------------------------------------------------------------- 1 | # https://developers.home-assistant.io/docs/add-ons/repository#repository-configuration 2 | name: GiZMo's Home Assistant add-on repository 3 | url: 'https://github.com/GiZMoSK1221/hass-addons' 4 | maintainer: GiZMo -------------------------------------------------------------------------------- /PIDtabule/appdaemon.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | logs: #reload appdaemon addon after editing 3 | pidtabule_log: 4 | name: pidtabule_log_name 5 | filename: /share/pidtabule.log 6 | log_size: 1024000 7 | appdaemon: 8 | admin: 9 | api: 10 | hadashboard: 11 | -------------------------------------------------------------------------------- /PIDtabule/markdown.yaml: -------------------------------------------------------------------------------- 1 | Odjazdy 2 | {% set data = state_attr('sensor.pidtabule','data') %} 3 | 4 | {% for i in range(0, data | count ) %} 5 | {{ data[i]['arrival_timestamp_predicted_time'] }} {{ data[i]['trip_short_name'] }} {{ data[i]['trip_headsign'] }} 6 | 7 | {% endfor %} -------------------------------------------------------------------------------- /ElectricConsumptionDbRepair/appdaemon.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | secrets: /config/secrets.yaml 3 | logs: 4 | electric_consumption_db_repair_log: 5 | name: electric_consumption_db_repair_log_name 6 | filename: /share/electric_consumption_db_repair.log 7 | log_size: 10240 8 | appdaemon: 9 | admin: 10 | api: 11 | hadashboard: 12 | -------------------------------------------------------------------------------- /wattsonic/configuration.yaml: -------------------------------------------------------------------------------- 1 | # Configure a default setup of Home Assistant (frontend, api, etc) 2 | default_config: 3 | 4 | homeassistant: 5 | packages: 6 | wattsonic: !include wattsonic.yaml 7 | #sunways: !include sunways.yaml 8 | #wattsonic: !include wattsonic_gen2.yaml 9 | #wattsonic_db: !include wattsonic_sql_mariadb.yaml 10 | #wattsonic_db: !include wattsonic_sql_postgre.yaml 11 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "Start Home Assistant", 6 | "type": "shell", 7 | "command": "supervisor_run", 8 | "group": { 9 | "kind": "test", 10 | "isDefault": true 11 | }, 12 | "presentation": { 13 | "reveal": "always", 14 | "panel": "new" 15 | }, 16 | "problemMatcher": [] 17 | } 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /wattsonic/sunways_gen2/configuration.yaml: -------------------------------------------------------------------------------- 1 | # Loads default set of integrations. Do not remove. 2 | default_config: 3 | 4 | # Load frontend themes from the themes folder 5 | frontend: 6 | themes: !include_dir_merge_named themes 7 | 8 | automation: !include automations.yaml 9 | script: !include scripts.yaml 10 | scene: !include scenes.yaml 11 | 12 | homeassistant: 13 | packages: 14 | sunways: !include sunways.yaml 15 | sunways_db: !include sunways_sql_postgre.yaml -------------------------------------------------------------------------------- /nfws/Dockerfile: -------------------------------------------------------------------------------- 1 | # https://developers.home-assistant.io/docs/add-ons/configuration#add-on-dockerfile 2 | ARG BUILD_FROM 3 | FROM $BUILD_FROM 4 | 5 | # Install requirements for add-on 6 | RUN \ 7 | apk add --no-cache \ 8 | python3 \ 9 | py3-pip 10 | 11 | RUN pip install --upgrade jsonpath-ng requests paho-mqtt pyyaml 12 | 13 | # Execute during the build of the image 14 | 15 | # Copy root filesystem 16 | COPY rootfs / 17 | RUN chmod a+x /etc/services.d/nfws/run /etc/services.d/nfws/finish 18 | -------------------------------------------------------------------------------- /PIDtabule/flexcard.yaml: -------------------------------------------------------------------------------- 1 | type: custom:flex-table-card 2 | entities: 3 | include: sensor.pidtabule 4 | columns: 5 | - data: data 6 | modify: >- 7 | x.arrival_timestamp_predicted_time+"+"+x.delay+"''/ za 8 | "+x.arrival_timestamp_predicted_est+"''" 9 | name: Cas 10 | - data: data 11 | modify: x.route_short_name 12 | name: Linka 13 | - data: data 14 | modify: x.last_stop_name 15 | name: Poloha 16 | title: Odjazdy smer Praha 17 | css: 18 | tbody tr td+: 'font-weight: bold;' 19 | tbody tr td:nth-child(odd)+: 'font-weight: normal;' 20 | -------------------------------------------------------------------------------- /nfws/rootfs/etc/services.d/nfws/finish: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bashio 2 | # ============================================================================== 3 | # Take down the S6 supervision tree when example fails 4 | # s6-overlay docs: https://github.com/just-containers/s6-overlay 5 | # ============================================================================== 6 | 7 | declare APP_EXIT_CODE=${1} 8 | 9 | if [[ "${APP_EXIT_CODE}" -ne 0 ]] && [[ "${APP_EXIT_CODE}" -ne 256 ]]; then 10 | bashio::log.warning "Halt add-on with exit code ${APP_EXIT_CODE}" 11 | echo "${APP_EXIT_CODE}" > /run/s6-linux-init-container-results/exitcode 12 | exec /run/s6/basedir/bin/halt 13 | fi 14 | 15 | bashio::log.info "Service restart after closing" 16 | -------------------------------------------------------------------------------- /nfws/build.yaml: -------------------------------------------------------------------------------- 1 | # https://developers.home-assistant.io/docs/add-ons/configuration#add-on-dockerfile 2 | build_from: 3 | aarch64: "ghcr.io/home-assistant/aarch64-base:3.16" 4 | amd64: "ghcr.io/home-assistant/amd64-base:3.16" 5 | armhf: "ghcr.io/home-assistant/armhf-base:3.16" 6 | armv7: "ghcr.io/home-assistant/armv7-base:3.16" 7 | i386: "ghcr.io/home-assistant/i386-base:3.16" 8 | labels: 9 | org.opencontainers.image.title: "Home Assistant Add-on: Netatmo Favorite Weather Stations" 10 | org.opencontainers.image.description: "your Netatmo Favorites Weather Stations and even more!" 11 | org.opencontainers.image.source: "https://github.com/GiZMoSK1221/hass-addons/tree/main/nfws" 12 | org.opencontainers.image.licenses: "Apache License 2.0" 13 | -------------------------------------------------------------------------------- /nfws/rootfs/etc/services.d/nfws/run: -------------------------------------------------------------------------------- 1 | #!/usr/bin/with-contenv bashio 2 | # ============================================================================== 3 | # Start the example service 4 | # s6-overlay docs: https://github.com/just-containers/s6-overlay 5 | # ============================================================================== 6 | 7 | # Add your code here 8 | 9 | # Declare variables 10 | #declare message 11 | 12 | ## Get the 'message' key from the user config options. 13 | #message=$(bashio::config 'message') 14 | 15 | ## Print the message the user supplied, defaults to "Hello World..." 16 | #bashio::log.info "${message:="Hello World..."}" 17 | #bashio::log.info "Starting nfws from run ..." 18 | 19 | ## Run your program 20 | exec python3 /usr/bin/nfws.py 21 | -------------------------------------------------------------------------------- /nfws/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ## 0.9.8 5 | Changes reflecting new config storage for addons according to: https://developers.home-assistant.io/blog/2023/11/06/public-addon-config/ 6 | 7 | Until now, the configs (stations.yaml and netatmo_token.yaml) were stored in /config/nwfs/ directory. 8 | 9 | New storage will be /addon-config/slug_nfws/ 10 | 11 | After update, during first run, when addon detects both config files in old storage, it will copy them to the new storage. When everyting goes well, addon will start and continue working. Then you can delete the old directory. 12 | 13 | If migrations fails, try to copy files manually. Otherwise follow instructions in Troubleshooting section in [docs.md](https://github.com/GiZMoSK1221/hass-addons/blob/main/nfws/DOCS.md) 14 | -------------------------------------------------------------------------------- /nfws/help/options.yaml: -------------------------------------------------------------------------------- 1 | netatmo: 2 | client_id: "xx" 3 | client_secret: "xx" 4 | refresh_interval: 2 #refresh time in minutes 5 | oauth_code: "yy" 6 | redirect_uri: "hassio" 7 | state: "nfws_hass" #SOME_ARBITRARY_BUT_UNIQUE_STRING 8 | show_response: "Truee" 9 | #granted rights can be checked here: https://home.netatmo.com/settings/my-account 10 | #documentation: https://dev.netatmo.com/apidocumentation/oauth#errors 11 | #apps: https://dev.netatmo.com/apps/ 12 | mqtt: 13 | address: "192.168.11.44" 14 | port: 1883 15 | username: "user" 16 | password: "pwd" 17 | client: "nwsclient12" 18 | nfws: 19 | deleteRetain: fTrue #deletes all nfws retained configuration messages on start, default= False 20 | log_level: debug #normal or debug 21 | -------------------------------------------------------------------------------- /.devcontainer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Example Home Assistant add-on repository", 3 | "image": "ghcr.io/home-assistant/devcontainer:addons", 4 | "appPort": ["7123:8123", "7357:4357"], 5 | "postStartCommand": "bash devcontainer_bootstrap", 6 | "runArgs": ["-e", "GIT_EDITOR=code --wait", "--privileged"], 7 | "containerEnv": { 8 | "WORKSPACE_DIRECTORY": "${containerWorkspaceFolder}" 9 | }, 10 | "extensions": ["timonwong.shellcheck", "esbenp.prettier-vscode"], 11 | "mounts": ["type=volume,target=/var/lib/docker"], 12 | "settings": { 13 | "terminal.integrated.profiles.linux": { 14 | "zsh": { 15 | "path": "/usr/bin/zsh" 16 | } 17 | }, 18 | "terminal.integrated.defaultProfile.linux": "zsh", 19 | "editor.formatOnPaste": false, 20 | "editor.formatOnSave": true, 21 | "editor.formatOnType": true, 22 | "files.trimTrailingWhitespace": true 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /PIDtabule/README.md: -------------------------------------------------------------------------------- 1 | ## PID tabule 2 | 3 | Script kazdu minutu stiahne pozadovane odchody a posle ich do HA 4 | zdroj dat: golemio API 5 | 6 | ![](https://github.com/GiZMoSK1221/hass-addons/blob/main/PIDtabule/priklad.png) 7 | 8 | **Preconditions:** 9 | - vlastny token z [golemio](https://api.golemio.cz/api-keys/auth/sign-in) 10 | - je potrebny HA addon Appdaemon 11 | - zobrazenie je cez flexCards https://github.com/custom-cards/flex-table-card 12 | 13 | **Instalacia** 14 | 1. ak chcete logovat do extra suboru, pridajte potrebnu sekciu do addon_configs\axx_appdaemon\ [appdaemon.yaml](appdaemon.yaml) 15 | 2. pridajte sekciu PIDtabule do addon_configs\axx_appdaemon\apps\ [apps.yaml](apps.yaml) 16 | 3. nakopirujte [PIDtabule.py](pidtabule.py) do addon_configs\axx_appdaemon\apps 17 | 4. script vytvori entitu sensor.PIDtabule 18 | json s odchodmi je v atribute data 19 | state entity je este v designe + dalsie atributy alebo entity 20 | 21 | **Priklad:** 22 | - [flexcard.yaml](flexcard.yaml) 23 | - [markdown.yaml](markdown.yaml) 24 | -------------------------------------------------------------------------------- /PIDtabule/apps.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | hello_world: 3 | module: hello 4 | class: HelloWorld 5 | 6 | PIDtabule: 7 | module: PIDtabule #define in appdaemon.yaml, .py filename 8 | class: PIDtabuleClass 9 | config: 10 | log: pidtabule_log #define for logging to separate file, not to AppDaemon log. Also edit appdaemon.yaml 11 | #add your token from https://api.golemio.cz/api-keys/auth/sign-in 12 | golemio_token: 13 | api_param: '&mode=departures&order=real&skip=canceled&includeMetroTrains=true&airCondition=true' 14 | connection_limit: 5 #number of connections for golemio API 15 | gtfsIds: #list of stations, get your gtfsId from here: http://data.pid.cz/stops/xml/StopsByName.xml 16 | - U454Z301 17 | only_destination_names: #returns only connections to this destinations, delete when not needed 18 | - Praha Masarykovo nádr. 19 | - Praha hl.n. 20 | # only_connection_names: #returns only this connection names, delete when not needed 21 | # - S11 22 | -------------------------------------------------------------------------------- /ElectricConsumptionDbRepair/README.md: -------------------------------------------------------------------------------- 1 | Script tries to repair statistics, wrong received data from sensor. Typically when one value is too high and scrambles charts in energy dashboard 2 | 3 | Script runs every hour and checks sum and state column in statistics table if new values for checked sensor are correct and repairs sum column. 4 | 5 | example UPDATE statistics SET sum = sum - (168296.98-89.52) WHERE metadata_id = 36 AND sum > 89.52 6 | and same to statistic_short_term table 7 | 8 | Preconditions: 9 | - appdaemon installed 10 | - statistics data for all sensor are correct (script checks only new values) 11 | 12 | Installation 13 | 1. add pymysql package to appdaemon config 14 | python_packages: 15 | - pymysql 16 | 2. add log section to appdaemon.yaml 17 | 3. add ECDbRepair section to apps.yaml and configure it 18 | 4. copy ElectricConsumtionDbRepair.py to appdaemon\apps\ 19 | 5. run 20 | 6. check electric_consumption_db_repair.log 21 | look for lines Watching entities:... & DB connected ... 22 | 23 | Examples are in github 24 | 25 | 26 | -------------------------------------------------------------------------------- /nfws/config.yaml: -------------------------------------------------------------------------------- 1 | name: "Netatmo Favorites Weather Stations" 2 | description: "your Netatmo Favorites Weather Stations and even more!" 3 | version: "0.9.8" 4 | slug: "nfws" 5 | init: false 6 | url: "https://github.com/GiZMoSK1221/hass-addons/nfws" 7 | homeassistant_api: true 8 | arch: 9 | - aarch64 10 | - amd64 11 | - armhf 12 | - armv7 13 | - i386 14 | map: 15 | - type: addon_config 16 | read_only: False 17 | - type: homeassistant_config 18 | read_only: True 19 | options: 20 | netatmo: 21 | client_id: "" 22 | client_secret: "" 23 | refresh_interval: 2 24 | oauth_code: "" 25 | nfws: 26 | deleteRetain: false 27 | log_level: "debug" 28 | schema: 29 | netatmo: 30 | client_id: "str?" 31 | client_secret: "str?" 32 | refresh_interval: "int(1,)" 33 | oauth_code: "str?" 34 | show_response: bool? 35 | nfws: 36 | deleteRetain: bool 37 | log_level: "list(debug|info|warning|error|critical)" 38 | services: 39 | - mqtt:need 40 | image: "ghcr.io/gizmosk1221/{arch}-addon-nfws" 41 | -------------------------------------------------------------------------------- /.github/workflows/lint.yaml: -------------------------------------------------------------------------------- 1 | name: Lint 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - main 10 | schedule: 11 | - cron: "0 0 * * *" 12 | 13 | jobs: 14 | find: 15 | name: Find add-ons 16 | runs-on: ubuntu-latest 17 | outputs: 18 | addons: ${{ steps.addons.outputs.addons_list }} 19 | steps: 20 | - name: ⤵️ Check out code from GitHub 21 | uses: actions/checkout@v6.0.1 22 | 23 | - name: 🔍 Find add-on directories 24 | id: addons 25 | uses: home-assistant/actions/helpers/find-addons@master 26 | 27 | lint: 28 | name: Lint add-on ${{ matrix.path }} 29 | runs-on: ubuntu-latest 30 | needs: find 31 | strategy: 32 | matrix: 33 | path: ${{ fromJson(needs.find.outputs.addons) }} 34 | steps: 35 | - name: ⤵️ Check out code from GitHub 36 | uses: actions/checkout@v6.0.1 37 | 38 | - name: 🚀 Run Home Assistant Add-on Lint 39 | uses: frenck/action-addon-linter@v2.21 40 | with: 41 | path: "./${{ matrix.path }}" 42 | -------------------------------------------------------------------------------- /ElectricConsumptionDbRepair/apps.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | hello_world: 3 | module: hello 4 | class: HelloWorld 5 | 6 | ECDbRepair: 7 | module: ElectricConsumptionDbRepair #define in appdaemon.yaml 8 | class: RunElectricConsumptionDbRepair 9 | config: 10 | log: electric_consumption_db_repair_log 11 | only_logging: 0 #1=only log detected errors in statistics table; 0=also execute updates 12 | every_hour_info: 0 #1=every hour inserts a new line into log 13 | # sqlitedb: '/homeassistant/home-assistant_v2.db' #when defined default sqlite db, db part is ignored 14 | db: #mariadb 15 | host: '192.168.11.44' 16 | port: '3307' 17 | db: 'mariadb_db' 18 | user: !secret MariaDBuser 19 | password: !secret MariaDBpwd 20 | charset: 'utf8' 21 | entities: #entities to watch 22 | - sensor.zw_powerplug_pracka_kwh 23 | - sensor.zw_powerplug_pracovna_stol_kwh 24 | - sensor.zw_powerplug_tvsat_kwh 25 | - sensor.zw_powerplug_kotol_kwh 26 | - sensor.zw_light_obyvacka_kwh 27 | - sensor.zw_light_stol_kwh 28 | - sensor.zw_light_kuchyna_kwh 29 | - sensor.zw_powerplug_chladnicka_kwh 30 | - sensor.zw_northq_power_reader_kwh 31 | -------------------------------------------------------------------------------- /wattsonic/wattsonic_sql_postgre.yaml: -------------------------------------------------------------------------------- 1 | sql: 2 | - name: wattsonic_battery_soc_5min_state 3 | query: > 4 | SELECT round(s.state, 1) as state, datetime(s.last_updated_ts, 'unixepoch', 'localtime') as updated from states s 5 | inner join states_meta sm on s.metadata_id=sm.metadata_id and sm.entity_id = 'sensor.wattsonic_battery_soc' 6 | where 7 | s.state != 'unknown' and 8 | s.last_updated_ts <= UNIXEPOCH(datetime('now'))-5*60 9 | ORDER BY s.last_updated_ts DESC LIMIT 1 10 | column: "state" 11 | - name: wattsonic_battery_soc_5min_last_updated 12 | query: > 13 | SELECT s.state, datetime(s.last_updated_ts, 'unixepoch', 'localtime') as updated from states s 14 | inner join states_meta sm on s.metadata_id=sm.metadata_id and sm.entity_id = 'sensor.wattsonic_battery_soc' 15 | where 16 | s.state != 'unknown' and 17 | s.last_updated_ts <= UNIXEPOCH(datetime('now'))-5*60 18 | ORDER BY s.last_updated_ts DESC LIMIT 1 19 | column: "updated" 20 | - name: wattsonic_grid_injection_energy_on_that_day_15min_state 21 | query: > 22 | SELECT round(s.state, 1) as state, datetime(s.last_updated_ts, 'unixepoch', 'localtime') as updated from states s 23 | inner join states_meta sm on s.metadata_id=sm.metadata_id and sm.entity_id = 'sensor.wattsonic_grid_injection_energy_on_that_day' 24 | where 25 | s.state != 'unknown' and 26 | s.last_updated_ts <= UNIXEPOCH(datetime('now'))-15*60 27 | ORDER BY s.last_updated_ts DESC LIMIT 1 28 | column: "state" 29 | 30 | - name: wattsonic_grid_injection_energy_on_that_day_15min_last_updated 31 | query: > 32 | SELECT s.state, datetime(s.last_updated_ts, 'unixepoch', 'localtime') as updated from states s 33 | inner join states_meta sm on s.metadata_id=sm.metadata_id and sm.entity_id = 'sensor.wattsonic_grid_injection_energy_on_that_day' 34 | where 35 | s.state != 'unknown' and 36 | s.last_updated_ts <= UNIXEPOCH(datetime('now'))-15*60 37 | ORDER BY s.last_updated_ts DESC LIMIT 1 38 | column: "updated" 39 | 40 | -------------------------------------------------------------------------------- /wattsonic/sunways_gen2/sunways_sql_postgre.yaml: -------------------------------------------------------------------------------- 1 | # modified by Jan Jirousek/25.01.2024 2 | sql: 3 | - name: sunways_battery_soc_5min_state 4 | query: > 5 | SELECT round(s.state, 1) as state, datetime(s.last_updated_ts, 'unixepoch', 'localtime') as updated from states s 6 | inner join states_meta sm on s.metadata_id=sm.metadata_id and sm.entity_id = 'sensor.sunways_battery_soc' 7 | where 8 | s.state != 'unknown' and 9 | s.last_updated_ts <= UNIXEPOCH(datetime('now'))-5*60 10 | ORDER BY s.last_updated_ts DESC LIMIT 1 11 | column: "state" 12 | - name: sunways_battery_soc_5min_last_updated 13 | query: > 14 | SELECT s.state, datetime(s.last_updated_ts, 'unixepoch', 'localtime') as updated from states s 15 | inner join states_meta sm on s.metadata_id=sm.metadata_id and sm.entity_id = 'sensor.sunways_battery_soc' 16 | where 17 | s.state != 'unknown' and 18 | s.last_updated_ts <= UNIXEPOCH(datetime('now'))-5*60 19 | ORDER BY s.last_updated_ts DESC LIMIT 1 20 | column: "updated" 21 | 22 | - name: sunways_grid_injection_energy_on_that_day_15min_state 23 | query: > 24 | SELECT round(s.state, 1) as state, datetime(s.last_updated_ts, 'unixepoch', 'localtime') as updated from states s 25 | inner join states_meta sm on s.metadata_id=sm.metadata_id and sm.entity_id = 'sensor.sunways_grid_injection_energy_on_that_day' 26 | where 27 | s.state != 'unknown' and 28 | s.last_updated_ts <= UNIXEPOCH(datetime('now'))-15*60 29 | ORDER BY s.last_updated_ts DESC LIMIT 1 30 | column: "state" 31 | - name: sunways_grid_injection_energy_on_that_day_15min_last_updated 32 | query: > 33 | SELECT s.state, datetime(s.last_updated_ts, 'unixepoch', 'localtime') as updated from states s 34 | inner join states_meta sm on s.metadata_id=sm.metadata_id and sm.entity_id = 'sensor.sunways_grid_injection_energy_on_that_day' 35 | where 36 | s.state != 'unknown' and 37 | s.last_updated_ts <= UNIXEPOCH(datetime('now'))-15*60 38 | ORDER BY s.last_updated_ts DESC LIMIT 1 39 | column: "updated" 40 | -------------------------------------------------------------------------------- /wattsonic/wgraph.yaml: -------------------------------------------------------------------------------- 1 | - theme: Backend-selected 2 | title: Wattsonic cloud graph 3 | path: wgraph 4 | subview: true 5 | type: sidebar 6 | badges: [] 7 | cards: 8 | - type: custom:apexcharts-card 9 | graph_span: 24h 10 | span: 11 | start: day 12 | header: 13 | show: true 14 | colorize_states: true 15 | yaxis: 16 | - id: first 17 | - id: second 18 | opposite: true 19 | series: 20 | - entity: sensor.wattsonic_battery_soc 21 | name: SOC 22 | color: yellow 23 | yaxis_id: first 24 | type: line 25 | stroke_width: 3 26 | extend_to: now 27 | group_by: 28 | func: first 29 | duration: 5m 30 | - entity: sensor.wattsonic_pv_input_total_power 31 | name: PV power 32 | color: cyan 33 | yaxis_id: second 34 | type: line 35 | stroke_width: 3 36 | extend_to: now 37 | group_by: 38 | func: first 39 | duration: 5m 40 | - entity: sensor.wattsonic_total_backup_p 41 | name: Load power 42 | color: orange 43 | yaxis_id: second 44 | type: line 45 | stroke_width: 3 46 | extend_to: now 47 | group_by: 48 | func: first 49 | duration: 5m 50 | - entity: sensor.wattsonic_battery_p 51 | name: Battery power 52 | color: green 53 | yaxis_id: second 54 | type: line 55 | stroke_width: 3 56 | extend_to: now 57 | group_by: 58 | func: first 59 | duration: 5m 60 | - entity: sensor.wattsonic_total_power_on_meter 61 | name: Grid power 62 | color: blue 63 | yaxis_id: second 64 | type: line 65 | stroke_width: 3 66 | extend_to: now 67 | group_by: 68 | func: first 69 | duration: 5m 70 | -------------------------------------------------------------------------------- /ElectricConsumptionDbRepair/electric_consumption_db_repair.log: -------------------------------------------------------------------------------- 1 | 2023-04-25 20:42:24.592567 INFO ECDbRepair: Electric consumption db repair start------------------------------------------------------------ 2 | 2023-04-25 20:42:24.600006 INFO ECDbRepair: Watching entities: 3 | 2023-04-25 20:42:24.601098 INFO ECDbRepair: ['sensor.zw_powerplug_pracka_kwh', 'sensor.zw_powerplug_pracovna_stol_kwh', 'sensor.zw_powerplug_tvsat_kwh', 'sensor.zw_powerplug_kotol_kwh', 'sensor.zw_light_obyvacka_kwh', 'sensor.zw_light_stol_kwh', 'sensor.zw_light_kuchyna_kwh', 'sensor.zw_powerplug_chladnicka_kwh', 'sensor.zw_northq_power_reader_kwh'] 4 | 2023-04-25 20:42:24.609226 INFO ECDbRepair: DB connected ... 5 | 2023-04-25 21:05:00.688000 INFO ECDbRepair: I'm running every hour.... 6 | 2023-04-25 22:05:00.716616 INFO ECDbRepair: I'm running every hour.... 7 | 2023-04-25 22:05:00.778619 INFO ECDbRepair: 2023-04-25 22:00:10.697479 sensor.zw_northq_power_reader_kwh not ok 8 | 2023-04-25 22:05:00.779672 INFO ECDbRepair: {'id': 1889920, 'metadata_id': 35, 'state': 12686.9, 'sum': 132369.67997436627, 'statistic_id': 'sensor.zw_northq_power_reader_kwh', 'created_ts': 1682452810.6974788} 9 | 2023-04-25 22:05:00.780398 INFO ECDbRepair: {'id': 1889702, 'metadata_id': 35, 'state': 12686.9, 'sum': 5500.639974366268, 'statistic_id': 'sensor.zw_northq_power_reader_kwh', 'created_ts': 1682449210.7203116} 10 | 2023-04-25 22:05:00.782509 INFO ECDbRepair: UPDATE statistics SET sum = sum - (132369.67997436627-5500.639974366268) WHERE metadata_id = 35 AND sum > 5500.639974366268; 11 | 2023-04-25 22:05:00.784703 INFO ECDbRepair: UPDATE statistics_short_term SET sum = sum - (132369.67997436627-5500.639974366268) WHERE metadata_id = 35 AND sum > 5500.639974366268; 12 | 2023-04-25 22:05:13.492184 INFO ECDbRepair: affected_rows = 1 13 | 2023-04-25 22:05:13.494489 INFO ECDbRepair: Number of rows is: 1 14 | 2023-04-25 22:05:30.903108 INFO ECDbRepair: affected_rows = 6 15 | 2023-04-25 22:05:30.905628 INFO ECDbRepair: Number of rows is: 6 16 | 2023-04-25 23:05:00.690634 INFO ECDbRepair: I'm running every hour.... 17 | 2023-04-26 18:05:00.918286 INFO ECDbRepair: I'm running every hour.... 18 | 2023-04-26 19:05:00.688256 INFO ECDbRepair: I'm running every hour.... 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # GiZMo's Home Assistant add-on repository 4 | 5 | 6 | [![Open your Home Assistant instance and show the add add-on repository dialog with a specific repository URL pre-filled.](https://my.home-assistant.io/badges/supervisor_add_addon_repository.svg)](https://my.home-assistant.io/redirect/supervisor_add_addon_repository/?repository_url=https://github.com/GiZMoSK1221/hass-addons) 7 | 8 | ## Add-ons 9 | 10 | This repository contains the following add-ons 11 | 12 | ### [Netatmo Favorites Weather Stations add-on](./nfws) 13 | 14 | ![Supports aarch64 Architecture][aarch64-shield] ![Supports amd64 Architecture][amd64-shield] ![Supports armhf Architecture][armhf-shield] ![Supports armv7 Architecture][armv7-shield] ![Supports i386 Architecture][i386-shield] 15 | 16 | _Netatmo integration for Home Assistant. You can get data from your favorite weather stations in Netatmo._ 17 | 18 | ### [Wattsonic gen3 modbus configs](./wattsonic) 19 | Forum: https://community.home-assistant.io/t/wattsonic-photovoltaic-power-plant-fve-integration/406135 20 | 21 | ### [Etrel inch modbus configs](./etrel_inch) 22 | Forum: https://community.home-assistant.io/t/etrel-inch-modbus-tcp-communication/548968 23 | 24 | ### [Appdaemon Electric Consumption Db Repair tool](./ElectricConsumptionDbRepair) 25 | Script tries to repair statistics, wrong received data from sensor. Typically when one value is too high and scrambles charts in energy dashboard 26 | Forum: N/A 27 | 28 | ### [Appdaemon PIDtabule](./PIDtabule) 29 | data z golemio API pre PID tabule - zastavkove odchody spojov 30 | 31 | ### [ParadoxPRT3toMQTT add-on](./) 32 | tbd, based on [ParadoxHassMQTT](https://github.com/DaveOke/ParadoxHassMQTT) project, integrated into Home Assistant as add-on 33 | Integrates Paradox Digiplex DGP-848 control panel with PRT3 printer module->USB->RPi4 using ASCII PRT3 protocol 34 | 35 | [aarch64-shield]: https://img.shields.io/badge/aarch64-yes-green.svg 36 | [amd64-shield]: https://img.shields.io/badge/amd64-yes-green.svg 37 | [armhf-shield]: https://img.shields.io/badge/armhf-yes-green.svg 38 | [armv7-shield]: https://img.shields.io/badge/armv7-yes-green.svg 39 | [i386-shield]: https://img.shields.io/badge/i386-yes-green.svg 40 | -------------------------------------------------------------------------------- /nfws/help/stations_example.yaml: -------------------------------------------------------------------------------- 1 | netatmo_stations: 2 | "aa:aa:aa:aa:aa:a1": 3 | name: station1 #name will be nfws_name_station1_... 4 | sensors: #WindAngle, WindAngleCompass, WindAngleCompassSymbol, GustAngle, GustAngleCompass, GustAngleCompassSymbol, Humidity, Pressure, rain, sum_rain_1, sum_rain_24, WindStrength, GustStrength, Temperature, min_temp, max_temp 5 | - WindStrength #name will be nfws_name_station1_WindStrength 6 | - WindAngleCompass 7 | - WindAngleCompassSymbol 8 | - GustStrength 9 | - GustAngleCompass 10 | - GustAngleCompassSymbol 11 | - Humidity 12 | - rain 13 | - sum_rain_1 14 | - sum_rain_24 15 | "aa:aa:aa:aa:aa:a2": 16 | name: station2 17 | sensors: 18 | - Temperature 19 | - min_temp 20 | - WindAngleCompass 21 | - WindAngleCompassSymbol 22 | - WindStrength #name will be nfws_name_station2_WindStrength 23 | - GustStrength 24 | - GustAngleCompass 25 | calculated_sensors: #sensor name = nfws_function_sensor_suffix 26 | - function: min #min, max, avg, first 27 | sensors: 28 | - Temperature #any sensor 29 | - Humidity 30 | suffix: "" #define if you need to have more sensors of one kind/function 31 | stations: 32 | - "aa:aa:aa:aa:aa:a1" 33 | - "aa:aa:aa:aa:aa:a2" 34 | - "aa:aa:aa:aa:aa:a3" 35 | - function: first #especially for wind station, which often disappears 36 | suffix: "" 37 | timeDelta: 30 #netatmo sensor data shoudn't be older than timeDelta (in min). Compared to dashboard_data.time_utc. Default is 30min 38 | sensors: 39 | - WindStrength #only first sensor will be evaluated 40 | - WindAngleCompass #next sensors will be taken from same station+module 41 | - WindAngleCompassSymbol 42 | - WindAngle 43 | - GustStrength 44 | - GustAngleCompass 45 | - GustAngleCompassSymbol 46 | stations: #ordered list 47 | - "aa:aa:aa:aa:aa:a1" 48 | - "aa:aa:aa:aa:aa:a2" 49 | - "aa:aa:aa:aa:aa:a3" 50 | - "aa:aa:aa:aa:aa:a4" 51 | - "aa:aa:aa:aa:aa:a5" 52 | -------------------------------------------------------------------------------- /wattsonic/wattsonic_sql_mariadb.yaml: -------------------------------------------------------------------------------- 1 | sql: 2 | - name: wattsonic_battery_soc_5min_state 3 | db_url: !secret URL_MariaDB 4 | query: > 5 | SELECT round(s.state, 1) as state, FROM_UNIXTIME(s.last_updated_ts) as updated from states s 6 | inner join states_meta sm on s.metadata_id=sm.metadata_id and sm.entity_id = 'sensor.wattsonic_battery_soc' 7 | where 8 | s.state != 'unknown' and 9 | s.last_updated_ts <= UNIX_TIMESTAMP(NOW())-5*60 10 | ORDER BY s.last_updated_ts DESC LIMIT 1 11 | column: "state" 12 | 13 | - name: wattsonic_battery_soc_5min_last_updated 14 | db_url: !secret URL_MariaDB 15 | query: > 16 | SELECT s.state, FROM_UNIXTIME(s.last_updated_ts) as updated from states s 17 | inner join states_meta sm on s.metadata_id=sm.metadata_id and sm.entity_id = 'sensor.wattsonic_battery_soc' 18 | where 19 | s.state != 'unknown' and 20 | s.last_updated_ts <= UNIX_TIMESTAMP(NOW())-5*60 21 | ORDER BY s.last_updated_ts DESC LIMIT 1 22 | column: "updated" 23 | 24 | - name: wattsonic_grid_injection_energy_on_that_day_15min_state 25 | db_url: !secret URL_MariaDB 26 | query: > 27 | SELECT round(s.state, 1) as state, FROM_UNIXTIME(s.last_updated_ts) as updated from states s 28 | inner join states_meta sm on s.metadata_id=sm.metadata_id and sm.entity_id = 'sensor.wattsonic_grid_injection_energy_on_that_day' 29 | where 30 | s.state != 'unknown' and 31 | s.last_updated_ts <= UNIX_TIMESTAMP(NOW())-15*60 32 | ORDER BY s.last_updated_ts DESC LIMIT 1 33 | column: "state" 34 | 35 | - name: wattsonic_grid_injection_energy_on_that_day_15min_last_updated 36 | db_url: !secret URL_MariaDB 37 | query: > 38 | SELECT s.state, FROM_UNIXTIME(s.last_updated_ts) as updated from states s 39 | inner join states_meta sm on s.metadata_id=sm.metadata_id and sm.entity_id = 'sensor.wattsonic_grid_injection_energy_on_that_day' 40 | where 41 | s.state != 'unknown' and 42 | s.last_updated_ts <= UNIX_TIMESTAMP(NOW())-15*60 43 | ORDER BY s.last_updated_ts DESC LIMIT 1 44 | column: "updated" 45 | 46 | # - name: wattsonic_battery_last_full_charged 47 | # db_url: !secret URL_MariaDB 48 | # scan_interval: 21600 #every 6 hours 49 | # query: > 50 | # SELECT from_unixtime(sst.created_ts) as dt, sst.mean*100 as soc FROM statistics_short_term sst 51 | # inner join statistics_meta sm on sm.id=sst.metadata_id and sm.statistic_id='sensor.wattsonic_battery_soc' 52 | # where sst.mean>=0.9990 order by sst.id desc LIMIT 1 53 | # column: "dt" 54 | -------------------------------------------------------------------------------- /wattsonic/sunsynk.yaml: -------------------------------------------------------------------------------- 1 | type: custom:sunsynk-power-flow-card 2 | cardstyle: lite 3 | show_solar: true 4 | decimal_places: 2 5 | inverter: 6 | model: wattsonic 7 | modern: true 8 | colour: grey 9 | autarky: 'no' 10 | auto_scale: true 11 | three_phase: true 12 | battery: 13 | shutdown_soc: 20 14 | xxfull_capacity: 99 15 | xxempty_capacity: 20 16 | show_remaining_energy: true 17 | show_daily: true 18 | show: true 19 | energy: 11500 20 | invert_power: false 21 | auto_scale: true 22 | solar: 23 | show_daily: true 24 | mppts: 2 25 | efficiency: 3 26 | pv1_name: Horny 27 | pv1_max_power: 4400 28 | pv2_name: Dolny 29 | pv2_max_power: 4400 30 | max_power: 8800 31 | auto_scale: true 32 | load: 33 | show_daily: true 34 | grid: 35 | show_daily_buy: true 36 | show_daily_sell: true 37 | show_nonessential: false 38 | invert_grid: false 39 | additional_loads: 0 40 | auto_scale: true 41 | entities: 42 | use_timer_248: none 43 | priority_load_243: none 44 | inverter_voltage_154: none 45 | load_frequency_192: none 46 | inverter_current_164: none 47 | inverter_power_175: sensor.wattsonic_battery_p 48 | grid_connected_status_194: sensor.wattsonic_sunsync_grid_connected 49 | inverter_status_59: none 50 | day_battery_charge_70: sensor.wattsonic_battery_charge_energy_on_that_day 51 | day_battery_discharge_71: sensor.wattsonic_battery_discharge_energy_on_that_day 52 | battery_voltage_183: sensor.wattsonic_battery_v 53 | battery_soc_184: sensor.wattsonic_battery_soc 54 | battery_power_190: sensor.wattsonic_battery_p 55 | battery_current_191: sensor.wattsonic_battery_i 56 | day_grid_import_76: sensor.wattsonic_grid_purchasing_energy_on_that_day 57 | day_grid_export_77: sensor.wattsonic_grid_injection_energy_on_that_day 58 | grid_ct_power_total: sensor.wattsonic_total_power_on_meter_w 59 | grid_ct_power_172: sensor.wattsonic_phase_a_power_on_meter 60 | grid_ct_power_L2: sensor.wattsonic_phase_b_power_on_meter 61 | grid_ct_power_L3: sensor.wattsonic_phase_c_power_on_meter 62 | grid_power_169: sensor.wattsonic_total_power_on_meter_w 63 | day_load_energy_84: sensor.wattsonic_home_consumption_today 64 | essential_power: sensor.wattsonic_home_consumption_now 65 | nonessential_power: none 66 | aux_power_166: sensor.sunsynk_aux_power 67 | day_pv_energy_108: sensor.wattsonic_pv_generation_energy_on_that_day 68 | pv1_power_186: sensor.wattsonic_pv1_input_power 69 | pv2_power_187: sensor.wattsonic_pv2_input_power 70 | pv1_voltage_109: sensor.wattsonic_pv1_voltage 71 | pv1_current_110: sensor.wattsonic_pv1_current 72 | pv2_voltage_111: sensor.wattsonic_pv2_voltage 73 | pv2_current_112: sensor.wattsonic_pv2_current 74 | energy_cost: none 75 | battery_temp_182: sensor.wattsonic_bms_pack_temperature 76 | radiator_temp_91: sensor.wattsonic_temperature_sensor_1 77 | environment_temp: sensor.zw_temp_outdoor_air_temperature 78 | -------------------------------------------------------------------------------- /wattsonic/README.md: -------------------------------------------------------------------------------- 1 | ## Home Assistant Wattsonic GEN3 MODBUS integration 2 | 3 | **Installation**: 4 | 1. edit configuration.yaml 5 | include packages according to minimal/complete installation, check my [configuration.yaml](configuration.yaml) 6 | 7 | 2. copy wattsonic files 8 | all files copy to your config dir (where is configuration.yaml) 9 | 10 | a) **minimal Wattsonic gen3** configuration 11 | start with this config. When your setup starts returning data, switch to latest configuration. minimal config has almost all sensors from modbus + 3 translated template sensor 12 | - copy just this older [wattsonic.yaml](https://raw.githubusercontent.com/GiZMoSK1221/hass-addons/7c4f86199650526064935fac353a233ae6daa0ea/wattsonic/wattsonic.yaml) 13 | 14 | 15 | b) **my latest gen3** configuration 16 | - copy [wattsonic_sql_postgre.yaml](wattsonic_sql_postgre.yaml) or [wattsonic_sql_mariadb.yaml](wattsonic_sql_mariadb.yaml) for sql sensors according to your db. Default HA is postgre 17 | - edit configuration.yaml and include corresponding db package 18 | - copy actual [wattsonic.yaml](wattsonic.yaml) 19 | 20 | this configuration consist of (but still does not has all registers): 21 | - almost all read modbus registers 22 | - switches for RW registers 23 | - can read Ecomode period 1,2 structure and has an example for writing structure 24 | - template sensors 25 | - Battery SOC ETA - time for charging/discharging battery 26 | - Grid Injection 15min - for Czech, grid injection in last 15minutes 27 | - Home Consumption Now and Today calculation 28 | - scripts for 29 | - setting economy/general/off grid mode 30 | - setting Grid Injection Power Limit Setting 31 | 32 | c) **GEN2** configuration 33 | as GEN2 has differrent registers, you cannot use my wattsonic.yaml. 34 | but, Ivan has made this basic one [wattsonic_gen2.yaml](wattsonic_gen2.yaml). enjoy and feel free to modify here on github 35 | 36 | d) **Sunways GEN2** configuration from Martin Kremla 37 | - based on my latest gen3 config 38 | - source is here in [sunways_gen2 directory](sunways_gen2) 39 | 40 | 4. edit modbus configuration in wattsonic.yaml 41 | default is connection over serial and RS485. Change port according to your RS485 module. Slave adress is 247. 42 | if you are running over TCP/IP, just delete serial config and uncomment tcp config. Slave adress is 255. 43 | 44 | 5. restart HASS 45 | 6. edit your lovelace dashboard, you can take mine [lovelace](lovelace.yaml) and just remove/use sensors 46 | - added [sunsynk config](sunsynk.yaml) 47 | 48 | **MODBUS wiring GEN3** 49 | check [wattsonic manual](https://www.wattsonic.com/Ftp/EN/Wattsonic%20Li-HV%20Residential%20Three%20Phase%20Hybrid%20Series_UM_EN.pdf), page 65, PIN 13,14 50 | 51 | **MODBUS wiring GEN2** 52 | Sunways manual for STH 3~8KTL-HS, page 49, PIN 3,4 53 | 54 | **RS485 converter** 55 | i'm using this [converter](https://www.aliexpress.com/item/1005003207091292.html) 56 | 57 | **Questions:** 58 | place here: [Hass community forum](https://community.home-assistant.io/t/wattsonic-photovoltaic-power-plant-fve-integration/406135) 59 | -------------------------------------------------------------------------------- /nfws/README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | # drawing Home Assistant Add-on: Netatmo Favorites Weather Station 5 | [![Donate](https://www.paypalobjects.com/en_US/i/btn/btn_donate_SM.gif)](https://www.paypal.com/donate/?business=XTWWUQFKVX2XJ&no_recurring=1&item_name=Home+Assistent+Addons¤cy_code=CZK) 6 | 7 | ![Supports aarch64 Architecture][aarch64-shield] ![Supports amd64 Architecture][amd64-shield] ![Supports armhf Architecture][armhf-shield] ![Supports armv7 Architecture][armv7-shield] ![Supports i386 Architecture][i386-shield] 8 | 9 | Netatmo public weather stations integration for Home Assistant . You can receive data from your favourite weather stations in your Netatmo account, 10 | ![][netatmo_favorites_list] 11 | like: 12 | - Netatmo data: WindAngle, GustAngle, Humidity, Pressure, rain, sum_rain_1, sum_rain_24, WindStrength, GustStrength, Temperature, min_temp, max_temp 13 | - Wind directions(NESW)/Symbols(↓↙→): WindAngleCompass, WindAngleCompassSymbol, GustAngleCompass, GustAngleCompassSymbol 14 | - Calculated values from station list: first available value, minimal/maximal/average value 15 | 16 | This addon enables user without Netatmo hardware to use public weather data and use them in automations, like 17 | 18 | - automate irrigation according to rain, temperature 19 | - automate blinds according to wind, wind gust and direction 20 | - many more 21 | 22 | 23 | ![][netatmo_screenshot] 24 | 25 | Config example: [stations_example.yaml](https://github.com/GiZMoSK1221/hass-addons/blob/main/nfws/help/stations_example.yaml) 26 | 27 | **Background**: 28 | Netatmo offers a [getpublicdata API](https://dev.netatmo.com/apidocumentation/weather#getpublicdata), which allows you to retrieve publicly shared weather data from Outdoors Modules within a predefined area. There are two main issues I was facing: 29 | 1. you don't know what you get - when you have a favourite weather station and you trust it's data, even when you provide exact coordinates, you must not get this station in answer 30 | 2. from my observation, wind modules often disappear or don't return data. When you need wind gust to raise venetian blinds to protect them, you need to trust your system and be sure, that you always get this value 31 | 32 | **Solution - NFWS addon:** 33 | 1. Netatmo also offers [getstationsdata API](https://dev.netatmo.com/apidocumentation/weather#getstationsdata), which returns user Weather Stations Data. Using parameter get_favorites you get your favourites station from your Netatmo account. Maximum is five stations. 34 | 4. Calculated values - function first - retrieves desired value from first available station defined in your list. You just find 5 stations in you surroundings and you have a good chance, that at least one works :) 35 | 36 | **Requirements, installation and configuration:** 37 | read [DOCS.md](https://github.com/GiZMoSK1221/hass-addons/blob/main/nfws/DOCS.md) 38 | 39 | Forum: https://community.home-assistant.io/t/netatmo-favorites-weather-station-addon/467534 40 | 41 | [aarch64-shield]: https://img.shields.io/badge/aarch64-yes-green.svg 42 | [amd64-shield]: https://img.shields.io/badge/amd64-yes-green.svg 43 | [armhf-shield]: https://img.shields.io/badge/armhf-yes-green.svg 44 | [armv7-shield]: https://img.shields.io/badge/armv7-yes-green.svg 45 | [i386-shield]: https://img.shields.io/badge/i386-yes-green.svg 46 | [netatmo_favorites_list]: https://raw.githubusercontent.com/GiZMoSK1221/hass-addons/main/nfws/help/netatmo_favorites_list.png 47 | [netatmo_screenshot]: https://raw.githubusercontent.com/GiZMoSK1221/hass-addons/main/nfws/help/netatmo_screenshot.png 48 | 49 | -------------------------------------------------------------------------------- /.github/workflows/builder.yaml: -------------------------------------------------------------------------------- 1 | name: Builder 2 | 3 | env: 4 | BUILD_ARGS: "--test" 5 | MONITORED_FILES: "build.yaml config.yaml Dockerfile rootfs" 6 | 7 | on: 8 | push: 9 | branches: 10 | - main 11 | pull_request: 12 | branches: 13 | - main 14 | 15 | jobs: 16 | init: 17 | runs-on: ubuntu-latest 18 | name: Initialize builds 19 | outputs: 20 | changed_addons: ${{ steps.changed_addons.outputs.addons }} 21 | changed: ${{ steps.changed_addons.outputs.changed }} 22 | steps: 23 | - name: Check out the repository 24 | uses: actions/checkout@v6.0.1 25 | 26 | - name: Get changed files 27 | id: changed_files 28 | uses: jitterbit/get-changed-files@v1 29 | 30 | - name: Find add-on directories 31 | id: addons 32 | uses: home-assistant/actions/helpers/find-addons@master 33 | 34 | - name: Get changed add-ons 35 | id: changed_addons 36 | run: | 37 | declare -a changed_addons 38 | for addon in ${{ steps.addons.outputs.addons }}; do 39 | if [[ "${{ steps.changed_files.outputs.all }}" =~ $addon ]]; then 40 | for file in ${{ env.MONITORED_FILES }}; do 41 | if [[ "${{ steps.changed_files.outputs.all }}" =~ $addon/$file ]]; then 42 | if [[ ! "${changed_addons[@]}" =~ $addon ]]; then 43 | changed_addons+=("\"${addon}\","); 44 | fi 45 | fi 46 | done 47 | fi 48 | done 49 | 50 | changed=$(echo ${changed_addons[@]} | rev | cut -c 2- | rev) 51 | 52 | if [[ -n ${changed} ]]; then 53 | echo "Changed add-ons: $changed"; 54 | echo "::set-output name=changed::true"; 55 | echo "::set-output name=addons::[$changed]"; 56 | else 57 | echo "No add-on had any monitored files changed (${{ env.MONITORED_FILES }})"; 58 | fi 59 | build: 60 | needs: init 61 | runs-on: ubuntu-latest 62 | if: needs.init.outputs.changed == 'true' 63 | name: Build ${{ matrix.arch }} ${{ matrix.addon }} add-on 64 | strategy: 65 | matrix: 66 | addon: ${{ fromJson(needs.init.outputs.changed_addons) }} 67 | arch: ["aarch64", "amd64", "armhf", "armv7", "i386"] 68 | 69 | steps: 70 | - name: Check out repository 71 | uses: actions/checkout@v6.0.1 72 | 73 | - name: Get information 74 | id: info 75 | uses: home-assistant/actions/helpers/info@master 76 | with: 77 | path: "./${{ matrix.addon }}" 78 | 79 | - name: Check if add-on should be built 80 | id: check 81 | run: | 82 | if [[ "${{ steps.info.outputs.architectures }}" =~ ${{ matrix.arch }} ]]; then 83 | echo "::set-output name=build_arch::true"; 84 | echo "::set-output name=image::$(echo ${{ steps.info.outputs.image }} | cut -d'/' -f3)"; 85 | if [[ -z "${{ github.head_ref }}" ]] && [[ "${{ github.event_name }}" == "push" ]]; then 86 | echo "BUILD_ARGS=" >> $GITHUB_ENV; 87 | fi 88 | else 89 | echo "${{ matrix.arch }} is not a valid arch for ${{ matrix.addon }}, skipping build"; 90 | echo "::set-output name=build_arch::false"; 91 | fi 92 | 93 | - name: Login to GitHub Container Registry 94 | if: env.BUILD_ARGS != '--test' 95 | uses: docker/login-action@v3.6.0 96 | with: 97 | registry: ghcr.io 98 | username: ${{ github.repository_owner }} 99 | password: ${{ secrets.GITHUB_TOKEN }} 100 | 101 | - name: Build ${{ matrix.addon }} add-on 102 | if: steps.check.outputs.build_arch == 'true' 103 | uses: home-assistant/builder@2025.11.0 104 | with: 105 | args: | 106 | ${{ env.BUILD_ARGS }} \ 107 | --${{ matrix.arch }} \ 108 | --target /data/${{ matrix.addon }} \ 109 | --image "${{ steps.check.outputs.image }}" \ 110 | --docker-hub "ghcr.io/${{ github.repository_owner }}" \ 111 | --addon 112 | -------------------------------------------------------------------------------- /nfws/DOCS.md: -------------------------------------------------------------------------------- 1 | 2 | # Home Assistant Add-on: Netatmo Favorites Weather Station 3 | 4 | ## Preconditions 5 | 6 | ### Home Assistant 7 | - installed MQTT broker addon 8 | - enabled MQTT discovery 9 | 10 | ### Netatmo 11 | - Netatmo [account](https://auth.netatmo.com/access/checklogin) 12 | - Netatmo App created at [developer page](https://dev.netatmo.com/) 13 | ![](https://raw.githubusercontent.com/GiZMoSK1221/hass-addons/main/nfws/help/netatmo_new_app.jpg) 14 | - client_id and client_secret from your app 15 | ![](https://raw.githubusercontent.com/GiZMoSK1221/hass-addons/main/nfws/help/netatmo_ids.jpg) 16 | - at least one [favorite station](https://weathermap.netatmo.com/?zoom=15&maplayer=Map) 17 | ![](https://raw.githubusercontent.com/GiZMoSK1221/hass-addons/main/nfws/help/netatmo_add_fav.jpg) 18 | 19 | 20 | ## Installation and first run 21 | 22 | - install the addon 23 | [![Open your Home Assistant instance and show the add add-on repository dialog with a specific repository URL pre-filled.](https://my.home-assistant.io/badges/supervisor_add_addon_repository.svg)](https://my.home-assistant.io/redirect/supervisor_add_addon_repository/?repository_url=https://github.com/GiZMoSK1221/hass-addons) 24 | 25 | - go to config tab and enter your client_id and client_secret. Run addon, wait few seconds, stop addon 26 | - go to log tab. You will see there this message: 27 | >Missing Netatmo authorisation OAUTH code! 28 | When access granted, copy code value from returned url to config.yaml 29 | Example of returned URL: https://app.netatmo.net/oauth2/hassio?state=nfws_hass&code=5ebbe91cdd804326ddde4336c7e9b6b8 30 | Calling...https://api.netatmo.com/oauth2/authorize?client_id=60e5c04fef24f51a5d36c03a&redirect_uri=hassio&scope=read_station&state=nfws_hass 31 | 32 | - copy&paste URL to a new window and grant access 33 | ![](https://raw.githubusercontent.com/GiZMoSK1221/hass-addons/main/nfws/help/netatmo_accept.jpg) 34 | 35 | - Netatmo will redirect your browser to app.netatmo.net with error 404. Just copy OAUTH code from code section from URL (code=OAUTH code) 36 | ![](https://raw.githubusercontent.com/GiZMoSK1221/hass-addons/main/nfws/help/netatmo_accept_code.jpg) 37 | 38 | - goto config tab and enter OAUTH code. 39 | - run addon 40 | do last 3 steps quickly. Otherwise you might get: 41 | Netatmo authorization_code: Wrong response code 400 42 | {'error': 'expired_token'} 43 | 44 | - go to log tab. You should see 45 | >11.11.2022 11:11:16 Starting Netatmo service 46 | Run mode: hass 47 | Config dir: /config/nfws/ 48 | { 49 | "access_token": 50 | } 51 | 11.11.2022 11:11:16 get data 52 | 11.11.2022 11:11:17 Registering: .... 53 | Not used station id: 70:ee:50:2a:70:14, name: 54 | Not used station id: 70:ee:50:3c:25:2e, name: 55 | Not used station id: 70:ee:50:24:18:3a, name: 56 | 57 | - Not used station section shows MAC address of your favourites stations 58 | 59 | You were able tu run successfully the addon for the first time. 60 | Now, go to \\addon_configs\adaeefb4_nfws\ directory and edit stations.yaml using MAC addresses you got. 61 | ![](https://raw.githubusercontent.com/GiZMoSK1221/hass-addons/main/nfws/help/addon_config.jpg) 62 | 63 | 64 | to edit stations.yaml in \\addon_configs\adaeefb4_nfws\ directory install Samba share addon and grant it access addon_configs directory. 65 | 66 | ## Configuring add-on 67 | ### stations.yaml 68 | [stations.yaml example](https://github.com/GiZMoSK1221/hass-addons/blob/main/nfws/help/stations_example.yaml) with parameters description 69 | 70 | ### Addon configuration tab 71 | - netatmo->refresh_interval: how often in minutes should addon retrieve data 72 | - nfws->deleteRetain: true deletes all sensors on startup created by addon. Use once when your configuration is finished to delete orphaned sensors 73 | - netatmo->log_level: possible values are: debug|info|warning|error|critical. Change to warning or info when addon runs correctly 74 | 75 | ## Troubleshooting 76 | ### Any problem with token, token not valid, new cannot be obtained 77 | 1. stop addon 78 | 2. delete netatmo_token.yaml 79 | 3. in config tab set oauth_code: "" 80 | 4. run addon. stop addon 81 | 5. go to log, copy url and open it in a new browser tab/window. Allow plugin to access netatmo data 82 | 6. get code value from url, close tab/window 83 | 7. go to log, enter oauth_code: 'code' 84 | 8. start addon 85 | 86 | ### Device not found - response error from Netatmo service 87 | means Station was not added to favorite stations 88 | -------------------------------------------------------------------------------- /PIDtabule/PIDtabule.py: -------------------------------------------------------------------------------- 1 | #https://www.homeassistant-cz.cz/viewtopic.php?t=751 2 | #API kluc - https://api.golemio.cz/api-keys/auth/sign-in 3 | #swagger - https://api.golemio.cz/v2/pid/docs/openapi/ 4 | #ID zastavky - http://data.pid.cz/stops/xml/StopsByName.xml 5 | import appdaemon.plugins.hass.hassapi as hass 6 | import datetime 7 | import requests 8 | 9 | class PIDtabuleClass(hass.Hass): 10 | 11 | def mylog(self, text): 12 | log = "" 13 | if "log" in self.args["config"]: 14 | log = self.args["config"]["log"] 15 | if log !="": 16 | try: 17 | self.log(text, log=f"{log}") 18 | except BaseException as err: 19 | self.log(text) 20 | else: 21 | self.log(text) 22 | 23 | 24 | def main_pid(self, cb_args): 25 | config = self.args["config"] 26 | if "gtfsIds" not in config: 27 | self.mylog(f"Missing gtfsIds in config") 28 | return 29 | 30 | gtfsIds = config["gtfsIds"] 31 | gtfsIds_url = "" 32 | for gtfsId in gtfsIds: 33 | gtfsIds_url = gtfsIds_url + f"ids={gtfsId}&" 34 | api_url = f'https://api.golemio.cz/v2/pid/departureboards?{gtfsIds_url}&preferredTimezone=Europe%2FPrague&limit={config["connection_limit"]}{config["api_param"]}' 35 | headers = {"Content-Type":"application/json; charset=utf-8", "X-Access-Token":f"{config['golemio_token']}"} 36 | try: 37 | response = requests.get(api_url, headers=headers) 38 | except BaseException as err: 39 | self.mylog(f"Unexpected request result {err=}, {type(err)=}") 40 | self.mylog(api_url) 41 | return 42 | json = response.json() 43 | #self.mylog(response.headers) 44 | #self.mylog(json) 45 | if response.status_code != requests.codes.ok: 46 | self.mylog(f"Wrong result code: {response}") 47 | #401=wrong token 48 | return 49 | 50 | connection_list = [] 51 | for departure in json["departures"]: 52 | trip_headsign = departure["trip"]["headsign"] #cielova stanica 53 | if "only_destination_names" not in config or trip_headsign in config["only_destination_names"]: 54 | route_short_name = departure["route"]["short_name"] #cislo spoja 55 | if "only_connection_names" not in config or route_short_name in config["only_connection_names"]: 56 | trip_short_name = departure["trip"]["short_name"] #cislo vlaku 57 | departure_timestamp_in_minutes = departure["departure_timestamp"]["minutes"] 58 | arrival_predicted = departure["arrival_timestamp"]["predicted"] 59 | arrival_predicted_time = datetime.datetime.fromisoformat(arrival_predicted).time() 60 | if arrival_predicted_time.minute < 10: 61 | arrival_predicted_time_rounded = f"{arrival_predicted_time.hour}:0{arrival_predicted_time.minute}" 62 | else: 63 | arrival_predicted_time_rounded = f"{arrival_predicted_time.hour}:{arrival_predicted_time.minute}" 64 | 65 | arrival_scheduled = departure["arrival_timestamp"]["scheduled"] 66 | arrival_scheduled_time = datetime.datetime.fromisoformat(arrival_scheduled).time() 67 | if arrival_scheduled_time.minute < 10: 68 | arrival_scheduled_time_rounded = f"{arrival_scheduled_time.hour}:0{arrival_scheduled_time.minute}" 69 | else: 70 | arrival_scheduled_time_rounded = f"{arrival_scheduled_time.hour}:{arrival_scheduled_time.minute}" 71 | 72 | 73 | arrival_predicted_est = round((datetime.datetime.fromisoformat(arrival_predicted).timestamp()-datetime.datetime.now().timestamp()) // 60) 74 | last_stop_name = departure["last_stop"]["name"] #nazov poslednej zastavky 75 | if last_stop_name is None: 76 | last_stop_name = "" 77 | delay_minutes = 0 78 | if departure["delay"]["is_available"] == True: 79 | delay_minutes = departure["delay"]["minutes"] 80 | #self.mylog(f'{route_short_name} to {trip_headsign} at {arrival_predicted_time_rounded}, in {arrival_predicted_est} min, meska: {delay_minutes} minut') 81 | connection_list.append({"arrival_timestamp_predicted_time":f"{arrival_predicted_time_rounded}", 82 | "arrival_timestamp_predicted_est":f"{arrival_predicted_est}", #cas prichodu za - vypocitana hodnota 83 | "arrival_timestamp_scheduled_time":f"{arrival_scheduled_time_rounded}", 84 | "departure_timestamp_in_minutes":f"{departure_timestamp_in_minutes}", #cas prichodu za 85 | "route_short_name":f"{route_short_name}", #cislo spoja - S22 86 | "trip_short_name":f"{trip_short_name}", #cislo vlaku - os8123 87 | "trip_headsign":f"{trip_headsign}", #cielova stanica 88 | "delay":f"{delay_minutes}", 89 | "last_stop_name":f"{last_stop_name}" 90 | }) 91 | 92 | state_attr = {"friendly_name": "PID tabule", 93 | "updated": f"{datetime.datetime.now()}", 94 | "data": connection_list 95 | } 96 | #, "unique_id": "PIDtabule"} 97 | # state_state = f'{connection_list[0]["route_short_name"]}/{connection_list[0]["trip_short_name"]} do {connection_list[0]["trip_headsign"]} za {connection_list[0]["predicted_est"]} min' 98 | # state_state = f'{connection_list[0]["route_short_name"]} za {connection_list[0]["arrival_timestamp_predicted_est"]} min' #S2 za 22m 99 | # state_state = f'{connection_list[0]["arrival_timestamp_predicted_time"]}+{connection_list[0]["delay"]}/{connection_list[0]["arrival_timestamp_predicted_est"]}' #22:14+2/6 100 | # state_state = f'{connection_list[0]["route_short_name"]}: za {connection_list[0]["arrival_timestamp_predicted_est"]} v {connection_list[0]["arrival_timestamp_predicted_time"]}+{connection_list[0]["delay"]} v {connection_list[0]["last_stop_name"]}' #S22: 22:14+2/za 6 Celakovice 101 | state_state = f"{connection_list[0]['route_short_name']} za {connection_list[0]['arrival_timestamp_predicted_est']}' v {connection_list[0]['arrival_timestamp_predicted_time']}+{connection_list[0]['delay']}' / {connection_list[0]['last_stop_name']}" #S22: 22:14+2/za 6 Celakovice 102 | #self.mylog(state_state) 103 | self.set_state("sensor.PIDtabule", state = state_state, attributes = state_attr) 104 | 105 | def initialize(self): 106 | self.mylog("PIDTabule start --------------------------------------------------------") 107 | 108 | self.main_pid(self) 109 | 110 | runtime = datetime.time(0, 0, 0) 111 | self.run_minutely(self.main_pid, runtime) 112 | -------------------------------------------------------------------------------- /etrel_inch/etrel-modbus.yaml: -------------------------------------------------------------------------------- 1 | modbus: 2 | - type: tcp 3 | host: 192.168.11.141 4 | port: 502 5 | name: etrel_inch 6 | sensors: 7 | # inputs 8 | - name: etrel_con1_connection_status 9 | unique_id: etrel_con1_connection_status 10 | address: 0 11 | input_type: input 12 | data_type: int16 13 | scan_interval: 60 14 | lazy_error_count: 10 15 | - name: etrel_con1_measured_vehicle_number_of_phases 16 | unique_id: etrel_con1_measured_vehicle_number_of_phases 17 | address: 1 18 | input_type: input 19 | data_type: int16 20 | - name: etrel_con1_ev_max_phase_current 21 | unique_id: etrel_con1_ev_max_phase_current 22 | unit_of_measurement: A 23 | address: 2 24 | input_type: input 25 | data_type: float32 26 | device_class: current 27 | scan_interval: 60 28 | lazy_error_count: 10 29 | - name: etrel_con1_target_current 30 | unique_id: etrel_con1_target_current 31 | unit_of_measurement: A 32 | address: 4 33 | input_type: input 34 | data_type: float32 35 | device_class: current 36 | scan_interval: 60 37 | lazy_error_count: 10 38 | - name: etrel_con1_current_l1 39 | unique_id: etrel_con1_current_l1 40 | unit_of_measurement: A 41 | address: 14 42 | input_type: input 43 | data_type: float32 44 | device_class: current 45 | scan_interval: 60 46 | lazy_error_count: 10 47 | 48 | - name: etrel_con1_active_power_l1 49 | unique_id: etrel_con1_active_power_l1 50 | unit_of_measurement: kW 51 | address: 20 52 | input_type: input 53 | data_type: float32 54 | device_class: power 55 | scan_interval: 60 56 | lazy_error_count: 10 57 | - name: etrel_con1_active_power_l2 58 | unique_id: etrel_con1_active_power_l2 59 | unit_of_measurement: kW 60 | address: 22 61 | input_type: input 62 | data_type: float32 63 | device_class: power 64 | scan_interval: 60 65 | lazy_error_count: 10 66 | - name: etrel_con1_active_power_l3 67 | unique_id: etrel_con1_active_power_l3 68 | unit_of_measurement: kW 69 | address: 24 70 | input_type: input 71 | data_type: float32 72 | device_class: power 73 | scan_interval: 60 74 | lazy_error_count: 10 75 | - name: etrel_con1_active_power_total 76 | unique_id: etrel_con1_active_power_total 77 | unit_of_measurement: kW 78 | address: 26 79 | input_type: input 80 | data_type: float32 81 | device_class: power 82 | scan_interval: 60 83 | lazy_error_count: 10 84 | - name: etrel_con1_session_imported_energy_total 85 | unique_id: etrel_con1_session_imported_energy_total 86 | unit_of_measurement: kWh 87 | address: 30 88 | input_type: input 89 | data_type: float32 90 | precision: 1 91 | device_class: energy 92 | state_class: total_increasing 93 | scan_interval: 60 94 | lazy_error_count: 10 95 | - name: etrel_con1_session_duration 96 | unique_id: etrel_con1_session_duration 97 | unit_of_measurement: s 98 | address: 32 99 | input_type: input 100 | data_type: int64 101 | precision: 2 102 | device_class: duration 103 | scan_interval: 60 104 | lazy_error_count: 10 105 | - name: etrel_con1_session_departure_time_ts 106 | unique_id: etrel_con1_session_departure_time_ts 107 | address: 36 108 | input_type: input 109 | data_type: int64 110 | - name: etrel_con1_ev_max_power 111 | unique_id: etrel_con1_ev_max_power 112 | unit_of_measurement: kW 113 | address: 44 114 | input_type: input 115 | data_type: float32 116 | precision: 1 117 | device_class: power 118 | - name: etrel_con1_ev_planned_energy 119 | unique_id: etrel_con1_ev_planned_energy 120 | unit_of_measurement: kWh 121 | address: 46 122 | input_type: input 123 | data_type: float32 124 | device_class: energy 125 | 126 | - name: etrel_charger_serial_no 127 | unique_id: etrel_charger_serial_no 128 | address: 990 129 | input_type: input 130 | data_type: string 131 | count: 10 132 | - name: etrel_charger_serial_model 133 | unique_id: etrel_charger_serial_model 134 | address: 1000 135 | input_type: input 136 | data_type: string 137 | count: 10 138 | - name: etrel_charger_hw_version 139 | unique_id: etrel_charger_hw_version 140 | address: 1010 141 | input_type: input 142 | data_type: string 143 | count: 5 144 | - name: etrel_charger_sw_version 145 | unique_id: etrel_charger_sw_version 146 | address: 1015 147 | input_type: input 148 | data_type: string 149 | count: 5 150 | - name: etrel_charger_number_of_connectors 151 | unique_id: etrel_charger_number_of_connectors 152 | address: 1020 153 | input_type: input 154 | data_type: int32 155 | 156 | # 1 : SocketType2 157 | # 2 : PlugType2 158 | - name: etrel_con1_connector_type 159 | unique_id: etrel_con1_connector_type 160 | address: 1022 161 | input_type: input 162 | data_type: int16 163 | scan_interval: 60 164 | lazy_error_count: 10 165 | - name: etrel_con1_number_of_phases 166 | unique_id: etrel_con1_number_of_phases 167 | address: 1023 168 | input_type: input 169 | data_type: int16 170 | scan_interval: 60 171 | lazy_error_count: 10 172 | - name: etrel_con1_l1_connected_to_phase 173 | unique_id: etrel_con1_l1_connected_to_phase 174 | address: 1024 175 | input_type: input 176 | data_type: int16 177 | scan_interval: 60 178 | lazy_error_count: 10 179 | - name: etrel_con1_l2_connected_to_phase 180 | unique_id: etrel_con1_l2_connected_to_phase 181 | address: 1025 182 | input_type: input 183 | data_type: int16 184 | scan_interval: 60 185 | lazy_error_count: 10 186 | - name: etrel_con1_l3_connected_to_phase 187 | unique_id: etrel_con1_l3_connected_to_phase 188 | address: 1026 189 | input_type: input 190 | data_type: int16 191 | scan_interval: 60 192 | lazy_error_count: 10 193 | - name: etrel_con1_custom_max_current 194 | unique_id: etrel_con1_custom_max_current 195 | unit_of_measurement: A 196 | address: 1028 197 | input_type: input 198 | data_type: float32 199 | device_class: current 200 | scan_interval: 60 201 | lazy_error_count: 10 202 | 203 | sensor: 204 | - platform: template 205 | sensors: 206 | etrel_con1_connection_status_text: 207 | friendly_name: Etrel Con1 Connection Status 208 | unique_id: etrel_con1_connection_status_text 209 | value_template: > 210 | {% if states('sensor.etrel_con1_connection_status') == '0' %} 211 | Unknown 212 | {% elif states('sensor.etrel_con1_connection_status') == '1' %} 213 | SocketAvailable 214 | {% elif states('sensor.etrel_con1_connection_status') == '2' %} 215 | WaitingForVehicleToBeConnected 216 | {% elif states('sensor.etrel_con1_connection_status') == '3' %} 217 | WaitingForVehicleToStart 218 | {% elif states('sensor.etrel_con1_connection_status') == '4' %} 219 | Charging 220 | {% elif states('sensor.etrel_con1_connection_status') == '5' %} 221 | ChargingPausedByEv 222 | {% elif states('sensor.etrel_con1_connection_status') == '6' %} 223 | ChargingPausedByEvse 224 | {% elif states('sensor.etrel_con1_connection_status') == '7' %} 225 | ChargingEnded 226 | {% elif states('sensor.etrel_con1_connection_status') == '8' %} 227 | ChargingFault 228 | {% elif states('sensor.etrel_con1_connection_status') == '9' %} 229 | UnpausingCharging 230 | {% elif states('sensor.etrel_con1_connection_status') == '10' %} 231 | Unavailable 232 | {% endif %} 233 | etrel_con1_session_departure_time: 234 | friendly_name: Etrel Con1 Session Departure Time 235 | unique_id: etrel_con1_session_departure_time 236 | value_template: > 237 | {{ states('sensor.etrel_con1_session_departure_time_ts') | int | timestamp_custom("%d.%m.%Y %H:%M") }} 238 | 239 | -------------------------------------------------------------------------------- /ElectricConsumptionDbRepair/ElectricConsumptionDbRepair.py: -------------------------------------------------------------------------------- 1 | import appdaemon.plugins.hass.hassapi as hass 2 | import datetime 3 | import pymysql.cursors 4 | import sqlite3 5 | 6 | class RunElectricConsumptionDbRepair(hass.Hass): 7 | 8 | def mylog(self, text): 9 | log = "" 10 | if "log" in self.args["config"]: 11 | log = self.args["config"]["log"] 12 | if log !="": 13 | try: 14 | self.log(text, log=f"{log}") 15 | except BaseException as err: 16 | self.log(text) 17 | else: 18 | self.log(text) 19 | 20 | def check_entity_test(self, entity, connection): 21 | config = self.args["config"] 22 | cursor = connection.cursor() 23 | #sql = f"""SELECT sst.id as id, sst.metadata_id, sst.state, sst.sum, sst.created_ts FROM statistics sst LIMIT 1""" 24 | sql = f"""SELECT sm.id FROM statistics_meta sm where sm.statistic_id='{entity}'""" 25 | cursor.execute(sql) 26 | result_id = cursor.fetchone() 27 | #self.mylog(result_id) 28 | if result_id == None: 29 | self.mylog(f"Entity not found: {entity}" ) 30 | return 31 | if 'sqlitedb' in config: 32 | result_id = { 33 | "id": result_id[0] 34 | } 35 | sm_id = result_id["id"] 36 | #self.mylog(sm_id) 37 | #return 38 | sql = f"""SELECT sst.id as id, sst.metadata_id, sst.state, sst.sum, '{entity}' as statistic_id, sst.created_ts FROM statistics sst 39 | where sst.metadata_id ={sm_id} 40 | order by sst.start_ts desc limit 1""" 41 | #self.mylog(sql) 42 | cursor.execute(sql) 43 | result_last = cursor.fetchone() 44 | #self.mylog(result_last) 45 | if result_last == None: 46 | self.mylog(f"No data found: {entity}" ) 47 | return 48 | 49 | 50 | def check_entity(self, entity, connection): 51 | config = self.args["config"] 52 | cursor = connection.cursor() 53 | sql = f"""SELECT sst.id as id, sst.metadata_id, sst.state, sst.sum, sm.statistic_id, sst.start_ts FROM statistics sst 54 | inner join statistics_meta sm on sm.id=sst.metadata_id and sm.statistic_id='{entity}' 55 | order by sst.start_ts desc LIMIT 1""" 56 | #self.mylog( "c1 start" ) 57 | cursor.execute(sql) 58 | #self.mylog( "c1 stop" ) 59 | result_last = cursor.fetchone() 60 | #self.mylog(result_last) 61 | sql = f"""select id, metadata_id, state, sum, statistic_id, start_ts from 62 | (SELECT sst.id as id, sst.metadata_id, sst.state, sst.sum, sm.statistic_id, sst.start_ts, sst.start_ts FROM statistics sst 63 | inner join statistics_meta sm on sm.id=sst.metadata_id and sm.statistic_id='{entity}' 64 | order by start_ts desc LIMIT 2) two 65 | order by start_ts ASC limit 1""" 66 | #self.mylog( "c2 start" ) 67 | cursor.execute(sql) 68 | #self.mylog( "c2 stop" ) 69 | result_prev = cursor.fetchone() 70 | if result_last == None or result_prev == None: 71 | self.mylog(f"No data found: {entity}" ) 72 | return 73 | 74 | if 'sqlitedb' in config: 75 | result_last = { 76 | "id": result_last[0], 77 | "metadata_id": result_last[1], 78 | "state": result_last[2], 79 | "sum": result_last[3], 80 | "statistic_id": result_last[4], 81 | "start_ts": result_last[5] 82 | } 83 | result_prev = { 84 | "id": result_prev[0], 85 | "metadata_id": result_prev[1], 86 | "state": result_prev[2], 87 | "sum": result_prev[3], 88 | "statistic_id": result_prev[4], 89 | "start_ts": result_prev[5] 90 | } 91 | 92 | sql = f"""SELECT sst.id as id, sst.metadata_id, sst.state, sst.sum, sm.statistic_id, sst.start_ts FROM statistics sst 93 | inner join statistics_meta sm on sm.id=sst.metadata_id and sm.statistic_id='{entity}' 94 | and sst.id<{result_prev['id']} 95 | order by sst.start_ts desc LIMIT 1""" 96 | cursor.execute(sql) 97 | result_prev_prev = cursor.fetchone() 98 | if result_prev_prev == None: 99 | self.mylog(f"No data found prev_prev: {entity}" ) 100 | return 101 | if 'sqlitedb' in config: 102 | result_prev_prev = { 103 | "id": result_prev_prev[0], 104 | "metadata_id": result_prev_prev[1], 105 | "state": result_prev_prev[2], 106 | "sum": result_prev_prev[3], 107 | "statistic_id": result_prev_prev[4], 108 | "start_ts": result_prev_prev[5] 109 | } 110 | 111 | dt_object = datetime.datetime.fromtimestamp(result_last['start_ts']) 112 | sum_diff = result_last['sum']-result_prev['sum'] 113 | state_diff = result_last['state']-result_prev['state'] 114 | # if sum_diff > state_diff+1: 115 | if abs(sum_diff - state_diff) > 0.3: 116 | if result_last['state']= {result_prev['start_ts']};""" 122 | update_sst = f"""UPDATE statistics_short_term SET sum = {result_prev_prev['sum']}, state = {result_prev_prev['state']} WHERE metadata_id = {result_last['metadata_id']} AND start_ts >= {result_prev['start_ts']};""" 123 | self.mylog(update_s) 124 | self.mylog(update_sst) 125 | else: 126 | self.mylog(f"{dt_object} {entity} not ok") 127 | self.mylog(result_prev_prev) 128 | self.mylog(result_prev) 129 | self.mylog(result_last) 130 | update_s = f"""UPDATE statistics SET sum = {result_prev['sum']}, state = {result_prev['state']} WHERE metadata_id = {result_last['metadata_id']} AND start_ts > {result_prev['start_ts']};""" 131 | update_sst = f"""UPDATE statistics_short_term SET sum = {result_prev['sum']}, state = {result_prev['state']} WHERE metadata_id = {result_last['metadata_id']} AND start_ts > {result_prev['start_ts']};""" 132 | try: 133 | self.mylog(update_s) 134 | self.mylog(update_sst) 135 | if config["only_logging"] == 0: 136 | affected_rows = cursor.execute(update_s) 137 | self.mylog(f"affected_rows = {affected_rows}") 138 | self.mylog(f"Number of rows is: {cursor.rowcount}") 139 | affected_rows = cursor.execute(update_sst) 140 | self.mylog(f"affected_rows = {affected_rows}") 141 | self.mylog(f"Number of rows is: {cursor.rowcount}") 142 | connection.commit() 143 | cursor.close() 144 | except MySQLError as e: 145 | self.mylog(f"{e} -- {e.args[0]}") 146 | 147 | 148 | def initialize(self): 149 | self.mylog("Electric consumption db repair start------------------------------------------------------------") 150 | self.mylog("Watching entities:") 151 | config = self.args["config"] 152 | self.mylog(config["entities"]) 153 | try: 154 | if 'sqlitedb' in config: 155 | connection = sqlite3.connect(config["sqlitedb"]) 156 | else: 157 | connection = pymysql.connect(host=f'{config["db"]["host"]}', 158 | port=int(f'{config["db"]["port"]}'), 159 | user=f'{config["db"]["user"]}', 160 | password=f'{config["db"]["password"]}', 161 | db=f'{config["db"]["db"]}', 162 | charset=f'{config["db"]["charset"]}', 163 | cursorclass=pymysql.cursors.DictCursor) 164 | # except pymysql.Error as err: 165 | except BaseException as err: 166 | self.mylog(f"Unexpected {method} {err=}, {type(err)=}") 167 | self.mylog("DB not connected ...") 168 | raise MySQLConnectionError 169 | 170 | self.mylog("DB connected ...") 171 | for entity in config["entities"]: 172 | #self.mylog(entity) #logovanie spracovania sensoru, len pri initialize 173 | self.check_entity(entity, connection) 174 | connection.close() 175 | 176 | runtime = datetime.time(0, 5, 0) 177 | self.run_hourly(self.run_ElectricConsumptionDbRepair, runtime) 178 | 179 | def run_ElectricConsumptionDbRepair(self, cb_args): 180 | config = self.args["config"] 181 | if 'sqlitedb' in config: 182 | connection = sqlite3.connect(config["sqlitedb"]) 183 | else: 184 | connection = pymysql.connect(host=f'{config["db"]["host"]}', 185 | port=int(f'{config["db"]["port"]}'), 186 | user=f'{config["db"]["user"]}', 187 | password=f'{config["db"]["password"]}', 188 | db=f'{config["db"]["db"]}', 189 | charset=f'{config["db"]["charset"]}', 190 | cursorclass=pymysql.cursors.DictCursor) 191 | if config["every_hour_info"] == 1: 192 | self.mylog("I'm running every hour....") 193 | for entity in config["entities"]: 194 | self.check_entity(entity, connection) 195 | connection.close() 196 | -------------------------------------------------------------------------------- /wattsonic/lovelace.yaml: -------------------------------------------------------------------------------- 1 | - theme: Backend-selected 2 | title: W 3 | path: w 4 | layout: {} 5 | badges: [] 6 | cards: 7 | - type: custom:power-flow-card-plus 8 | title: Aktualna spotreba 9 | w_decimals: 2 10 | kw_decimals: 2 11 | watt_threshold: 999 12 | clickable_entities: true 13 | entities: 14 | home: 15 | entity: sensor.wattsonic_home_consumption_now 16 | subtract_individual: false 17 | override_state: true 18 | battery: 19 | entity: sensor.wattsonic_battery_p 20 | state_of_charge: sensor.wattsonic_battery_soc 21 | display_state: one_way 22 | state_of_charge_unit_white_space: false 23 | use_metadata: false 24 | color_circle: true 25 | grid: 26 | entity: sensor.wattsonic_total_power_on_meter 27 | display_state: one_way 28 | invert_state: true 29 | secondary_info: 30 | entity: sensor.wattsonic_grid_injection_energy_on_that_day_15min 31 | unit_of_measurement: kW 32 | color_circle: true 33 | solar: 34 | entity: sensor.wattsonic_pv_input_total_power 35 | inverted_entities: grid 36 | use_new_flow_rate_model: false 37 | - type: entities 38 | entities: 39 | - entity: sensor.wattsonic_pv_generation_energy_on_that_day 40 | - entity: sensor.wattsonic_grid_injection_energy_on_that_day 41 | name: Predaj 42 | - entity: sensor.wattsonic_grid_purchasing_energy_on_that_day 43 | - entity: sensor.wattsonic_battery_soc_eta 44 | - entity: sensor.wattsonic_home_consumption_today 45 | - entity: sensor.wattsonic_home_consumption_today2 46 | - type: button 47 | name: Graf 48 | icon: mdi:information 49 | tap_action: 50 | action: navigate 51 | navigation_path: /lovelace/wgraph 52 | title: Today 53 | show_header_toggle: false 54 | - type: entities 55 | entities: 56 | - entity: sensor.wattsonic_inverter_sn 57 | - entity: sensor.wattsonic_inverter_running_status_text 58 | - entity: sensor.wattsonic_hybrid_inverter_working_mode_setting_text 59 | - entity: sensor.wattsonic_total_purchasing_energy_from_grid_on_meter 60 | - entity: sensor.wattsonic_total_power_on_meter 61 | - entity: sensor.wattsonic_total_pv_generation_from_installation 62 | - entity: sensor.wattsonic_total_pv_generation_on_that_day 63 | name: asi priamo do domu nie do batt 64 | - entity: sensor.wattsonic_pv_input_total_power 65 | - entity: sensor.wattsonic_phase_a_power_on_meter 66 | - entity: sensor.wattsonic_phase_b_power_on_meter 67 | - entity: sensor.wattsonic_phase_c_power_on_meter 68 | - entity: sensor.wattsonic_total_backup_p 69 | - entity: sensor.wattsonic_p_ac 70 | - entity: sensor.wattsonic_arm_fault_flag1 71 | - entity: sensor.wattsonic_fault_flag1 72 | - entity: sensor.wattsonic_fault_flag2 73 | - entity: sensor.wattsonic_fault_flag3 74 | - entity: sensor.wattsonic_firmware_version 75 | title: Wattsonic basic 76 | - type: entities 77 | entities: 78 | - entity: sensor.wattsonic_grid_injection_energy_on_that_day 79 | - entity: sensor.wattsonic_grid_purchasing_energy_on_that_day 80 | - entity: sensor.wattsonic_backup_output_energy_on_that_day 81 | - entity: sensor.wattsonic_battery_charge_energy_on_that_day 82 | - entity: sensor.wattsonic_battery_discharge_energy_on_that_day 83 | - entity: sensor.wattsonic_pv_generation_energy_on_that_day 84 | - entity: sensor.wattsonic_loading_energy_on_that_day 85 | name: asi import do batt today-nie 86 | - entity: sensor.wattsonic_energy_purchased_from_grid_on_that_day 87 | name: grid import do batt 88 | - entity: sensor.wattsonic_total_energy_injected_to_grid 89 | - entity: sensor.wattsonic_total_energy_purchased_from_grid_from_meter 90 | - entity: sensor.wattsonic_total_output_energy_on_backup_port 91 | - entity: sensor.wattsonic_total_energy_charged_to_battery 92 | - entity: sensor.wattsonic_total_energy_discharged_from_battery 93 | - entity: sensor.wattsonic_total_pv_generation 94 | - entity: sensor.wattsonic_total_loading_energy_consumed_at_grid_side 95 | - entity: sensor.wattsonic_total_energy_purchased_from_grid_at_inverter_side 96 | name: celkovo kune z grid do batt 97 | title: Wattsonic 3100 98 | - type: entities 99 | entities: 100 | - sensor.wattsonic_grid_lines_a_b_voltage 101 | - sensor.wattsonic_grid_lines_b_c_voltage 102 | - sensor.wattsonic_grid_lines_c_a_voltage 103 | - sensor.wattsonic_grid_phase_a_voltage 104 | - sensor.wattsonic_grid_phase_b_voltage 105 | - sensor.wattsonic_grid_phase_c_voltage 106 | - sensor.wattsonic_grid_phase_a_current 107 | - sensor.wattsonic_grid_phase_b_current 108 | - sensor.wattsonic_grid_phase_c_current 109 | title: Grid 110 | - type: entities 111 | entities: 112 | - entity: sensor.wattsonic_battery_soc 113 | - entity: sensor.wattsonic_battery_soc_eta 114 | - entity: sensor.wattsonic_battery_mode_text 115 | - entity: sensor.wattsonic_battery_p 116 | - entity: sensor.wattsonic_battery_i 117 | - entity: sensor.wattsonic_battery_v 118 | - entity: sensor.wattsonic_bms_pack_temperature 119 | - entity: sensor.wattsonic_battery_soh 120 | - entity: sensor.wattsonic_bms_error_code 121 | - entity: sensor.wattsonic_bms_warn_code 122 | - entity: sensor.wattsonic_rw_bms_errorcode 123 | - entity: sensor.wattsonic_rw_bms_protectioncode 124 | - entity: sensor.wattsonic_rw_bms_status 125 | - entity: sensor.wattsonic_rw_bms_warncode 126 | title: Battery 127 | - type: entities 128 | entities: 129 | - entity: sensor.wattsonic_pv1_input_power 130 | - entity: sensor.wattsonic_pv2_input_power 131 | - entity: sensor.wattsonic_temperature_sensor_1 132 | name: Inverter Temperature 133 | - entity: sensor.wattsonic_temperature_sensor_2 134 | - entity: sensor.wattsonic_temperature_sensor_3 135 | - entity: sensor.wattsonic_temperature_sensor_4 136 | - entity: sensor.wattsonic_pv1_voltage 137 | - entity: sensor.wattsonic_pv2_voltage 138 | - entity: sensor.wattsonic_pv1_current 139 | - entity: sensor.wattsonic_pv2_current 140 | title: Panel 141 | - hours_to_show: 12 142 | graph: line 143 | type: sensor 144 | entity: sensor.wattsonic_battery_p 145 | detail: 2 146 | name: Battery power 147 | - hours_to_show: 12 148 | graph: line 149 | type: sensor 150 | entity: sensor.wattsonic_pv_input_total_power 151 | detail: 2 152 | name: PV power 153 | - hours_to_show: 24 154 | graph: line 155 | type: sensor 156 | entity: sensor.wattsonic_total_backup_p 157 | detail: 2 158 | name: Load power 159 | - hours_to_show: 24 160 | graph: line 161 | type: sensor 162 | entity: sensor.wattsonic_total_power_on_meter 163 | detail: 2 164 | name: Grid power 165 | - type: entities 166 | entities: 167 | - entity: switch.wattsonic_grid_injection_power_limit_switch 168 | - entity: sensor.wattsonic_grid_injection_power_limit_setting 169 | - entity: switch.wattsonic_on_grid_battery_soc_protection_switch 170 | - entity: sensor.wattsonic_on_grid_battery_end_soc 171 | - entity: switch.wattsonic_off_grid_battery_soc_protection_switch 172 | - entity: sensor.wattsonic_off_grid_battery_end_soc 173 | - entity: switch.wattsonic_priority_power_output_setting 174 | title: Wattsonic RW 175 | show_header_toggle: false 176 | - type: entities 177 | entities: 178 | - entity: sensor.wattsonic_ecomode_period_enable_flag 179 | - entity: sensor.wattsonic_ecomode_period_enable_flag_bin 180 | - entity: sensor.wattsonic_ecomode_period1_chargedischarge_setting 181 | - entity: sensor.wattsonic_ecomode_period1_battery_charge_by 182 | - entity: sensor.wattsonic_ecomode_period1_power_limit 183 | - entity: sensor.wattsonic_ecomode_period1_start_time 184 | - entity: sensor.wattsonic_ecomode_period1_end_time 185 | show_header_toggle: false 186 | - type: custom:history-explorer-card 187 | cardName: historycard-91888117 188 | combineSameUnits: true 189 | defaultTimeRange: 12h 190 | showCurrentValues: true 191 | legendVisible: true 192 | decimation: fast 193 | header: hide 194 | labelsVisible: true 195 | labelAreaWidth: 0 196 | uiLayout: 197 | toolbar: top 198 | selector: hide 199 | graphs: 200 | - type: line 201 | entities: 202 | - entity: sensor.wattsonic_pv1_input_power 203 | - entity: sensor.wattsonic_pv2_input_power 204 | -------------------------------------------------------------------------------- /wattsonic/sunways_gen2/lovelace.yaml: -------------------------------------------------------------------------------- 1 | - title: Fve 2 | path: fve 3 | icon: mdi:solar-power-variant 4 | badges: [] 5 | cards: 6 | - type: custom:history-explorer-card 7 | cardName: historycard-91888117 8 | combineSameUnits: true 9 | defaultTimeRange: 12h 10 | showCurrentValues: true 11 | legendVisible: true 12 | decimation: fast 13 | header: hide 14 | labelsVisible: true 15 | labelAreaWidth: 0 16 | uiLayout: 17 | toolbar: top 18 | selector: hide 19 | graphs: 20 | - type: line 21 | entities: 22 | - entity: sensor.wattsonic_pv1_input_power 23 | - entity: sensor.wattsonic_pv2_input_power 24 | - type: vertical-stack 25 | cards: 26 | - type: entities 27 | entities: 28 | - entity: sensor.wattsonic_pv_input_total_power 29 | name: Aktualni vyroba 30 | - entity: sensor.wattsonic_p_ac 31 | name: 'Aktuální odběr ' 32 | - entity: sensor.wattsonic_pv_generation_energy_on_that_day 33 | name: Denní vyroba 34 | - type: entities 35 | entities: 36 | - entity: sensor.wattsonic_pv_generation_energy_on_that_day 37 | name: Celkova vyroba 38 | - entity: sensor.wattsonic_grid_injection_energy_on_that_day 39 | name: Prodej do site 40 | - entity: sensor.wattsonic_grid_purchasing_energy_on_that_day 41 | name: Nakoupeno ze site 42 | - entity: sensor.denni_spotreba 43 | name: Celkova denni spotreba 44 | - entity: sensor.wattsonic_battery_soc_eta 45 | - entity: sensor.wattsonic_home_consumption_now 46 | - entity: sensor.wattsonic_home_consumption_today 47 | - entity: sensor.wattsonic_home_consumption_today2 48 | - type: button 49 | name: Graf 50 | icon: mdi:information 51 | tap_action: 52 | action: navigate 53 | navigation_path: /martin-tel/wgraph 54 | show_header_toggle: false 55 | title: 'Výroba dnes ' 56 | - type: entities 57 | entities: 58 | - entity: sensor.wattsonic_inverter_running_status_text 59 | - entity: sensor.wattsonic_hybrid_inverter_working_mode_setting_text 60 | - entity: script.wattsonic_mode_economic 61 | name: 'Sunways economic mode ' 62 | icon: mdi:button-pointer 63 | secondary_info: none 64 | - entity: script.wattsonic_mode_general 65 | name: Sunways general mode 66 | icon: mdi:button-pointer 67 | - entity: script.wattsonic_mode_offgrid 68 | name: Sunways offgrid mode 69 | icon: mdi:button-pointer 70 | - entity: script.wattsonic_set_grid_injection_power_limit_setting 71 | name: Sunways grid injection power limit setting 72 | icon: mdi:button-pointer 73 | title: Sunways mode 74 | - type: entities 75 | entities: 76 | - entity: sensor.wattsonic_inverter_sn 77 | - entity: sensor.wattsonic_inverter_running_status_text 78 | - entity: sensor.wattsonic_hybrid_inverter_working_mode_setting_text 79 | - entity: sensor.wattsonic_total_purchasing_energy_from_grid_on_meter 80 | - entity: sensor.wattsonic_total_power_on_meter 81 | - entity: sensor.wattsonic_total_pv_generation_from_installation 82 | - entity: sensor.wattsonic_total_pv_generation_on_that_day 83 | name: Primo do baterie 84 | - entity: sensor.wattsonic_pv_input_total_power 85 | - entity: sensor.wattsonic_phase_a_power_on_meter 86 | - entity: sensor.wattsonic_phase_b_power_on_meter 87 | - entity: sensor.wattsonic_phase_c_power_on_meter 88 | - entity: sensor.wattsonic_total_backup_p 89 | - entity: sensor.wattsonic_p_ac 90 | - entity: sensor.wattsonic_arm_fault_flag1 91 | - entity: sensor.wattsonic_fault_flag1 92 | - entity: sensor.wattsonic_fault_flag2 93 | - entity: sensor.wattsonic_fault_flag3 94 | - entity: sensor.wattsonic_firmware_version 95 | title: Sunways info 96 | - type: entities 97 | entities: 98 | - entity: sensor.wattsonic_grid_injection_energy_on_that_day 99 | - entity: sensor.wattsonic_grid_purchasing_energy_on_that_day 100 | - entity: sensor.wattsonic_backup_output_energy_on_that_day 101 | - entity: sensor.wattsonic_battery_charge_energy_on_that_day 102 | - entity: sensor.wattsonic_battery_discharge_energy_on_that_day 103 | - entity: sensor.wattsonic_pv_generation_energy_on_that_day 104 | - entity: sensor.wattsonic_loading_energy_on_that_day 105 | name: asi import do batt today-nie 106 | - entity: sensor.wattsonic_energy_purchased_from_grid_on_that_day 107 | name: grid import do batt 108 | - entity: sensor.wattsonic_total_energy_injected_to_grid 109 | - entity: sensor.wattsonic_total_energy_purchased_from_grid_from_meter 110 | - entity: sensor.wattsonic_total_output_energy_on_backup_port 111 | - entity: sensor.wattsonic_total_energy_charged_to_battery 112 | - entity: sensor.wattsonic_total_energy_discharged_from_battery 113 | - entity: sensor.wattsonic_total_pv_generation 114 | - entity: sensor.wattsonic_total_loading_energy_consumed_at_grid_side 115 | - entity: sensor.wattsonic_total_energy_purchased_from_grid_at_inverter_side 116 | name: Celkova zaloha z grid do batt 117 | title: Sunways 118 | - type: entities 119 | entities: 120 | - sensor.wattsonic_grid_lines_a_b_voltage 121 | - sensor.wattsonic_grid_lines_b_c_voltage 122 | - sensor.wattsonic_grid_lines_c_a_voltage 123 | - sensor.wattsonic_grid_phase_a_voltage 124 | - sensor.wattsonic_grid_phase_b_voltage 125 | - sensor.wattsonic_grid_phase_c_voltage 126 | - sensor.wattsonic_grid_phase_a_current 127 | - sensor.wattsonic_grid_phase_b_current 128 | - sensor.wattsonic_grid_phase_c_current 129 | title: Grid 130 | - type: entities 131 | entities: 132 | - entity: sensor.wattsonic_battery_soc 133 | - entity: sensor.wattsonic_battery_soc_eta 134 | - entity: sensor.wattsonic_battery_mode_text 135 | - entity: sensor.wattsonic_battery_p 136 | - entity: sensor.wattsonic_battery_i 137 | - entity: sensor.wattsonic_battery_v 138 | - entity: sensor.wattsonic_bms_pack_temperature 139 | - entity: sensor.wattsonic_battery_soh 140 | - entity: sensor.wattsonic_bms_error_code 141 | - entity: sensor.wattsonic_bms_warn_code 142 | - entity: sensor.wattsonic_rw_bms_errorcode 143 | - entity: sensor.wattsonic_rw_bms_protectioncode 144 | - entity: sensor.wattsonic_rw_bms_status 145 | - entity: sensor.wattsonic_rw_bms_warncode 146 | title: Battery 147 | - type: entities 148 | entities: 149 | - entity: sensor.wattsonic_pv1_input_power 150 | - entity: sensor.wattsonic_pv2_input_power 151 | - entity: sensor.wattsonic_temperature_sensor_1 152 | name: Inverter Temperature 153 | - entity: sensor.wattsonic_temperature_sensor_2 154 | - entity: sensor.wattsonic_temperature_sensor_3 155 | - entity: sensor.wattsonic_temperature_sensor_4 156 | - entity: sensor.wattsonic_pv1_voltage 157 | - entity: sensor.wattsonic_pv2_voltage 158 | - entity: sensor.wattsonic_pv1_current 159 | - entity: sensor.wattsonic_pv2_current 160 | title: Panel 161 | - type: entities 162 | entities: 163 | - entity: switch.wattsonic_omezovac_vykonu_pro_vkladani_do_site 164 | - entity: sensor.wattsonic_grid_injection_power_limit_setting 165 | - entity: >- 166 | switch.wattsonic_spinac_ochrany_stavu_nabiti_baterie_pri_pripojeni_k_siti 167 | - entity: sensor.wattsonic_on_grid_battery_end_soc 168 | - entity: switch.wattsonic_spinac_ochrany_stavu_nabiti_baterie_mimo_sit 169 | - entity: sensor.wattsonic_off_grid_battery_end_soc 170 | - entity: switch.wattsonic_nastaveni_prioritniho_vystupniho_vykonu 171 | show_header_toggle: false 172 | title: Sunways 173 | - type: entities 174 | entities: 175 | - entity: sensor.wattsonic_ecomode_period_enable_flag 176 | - entity: sensor.wattsonic_ecomode_period_enable_flag_bin 177 | - entity: sensor.wattsonic_ecomode_period1_chargedischarge_setting 178 | - entity: sensor.wattsonic_ecomode_period1_battery_charge_by 179 | - entity: sensor.wattsonic_ecomode_period1_power_limit 180 | - entity: sensor.wattsonic_ecomode_period1_start_time 181 | - entity: sensor.wattsonic_ecomode_period1_end_time 182 | show_header_toggle: false 183 | - type: entities 184 | entities: 185 | - entity: sensor.fve_backup_a_f 186 | - entity: sensor.fve_backup_a_i 187 | - entity: sensor.fve_backup_a_p 188 | - entity: sensor.fve_backup_a_v 189 | - entity: sensor.fve_backup_b_f 190 | - entity: sensor.fve_backup_b_i 191 | - entity: sensor.fve_backup_b_p 192 | - entity: sensor.fve_backup_b_v 193 | - entity: sensor.fve_backup_c_f 194 | - entity: sensor.fve_backup_c_i 195 | - entity: sensor.fve_backup_c_p 196 | - entity: sensor.fve_backup_c_v 197 | - entity: sensor.fve_grid_frequency 198 | - entity: sensor.fve_invt_a_p 199 | - entity: sensor.fve_invt_b_p 200 | - entity: sensor.fve_invt_c_p 201 | - entity: sensor.fve_max_cell_temperature 202 | - entity: sensor.fve_min_cell_temperature 203 | title: FVE 204 | - hours_to_show: 12 205 | graph: line 206 | type: sensor 207 | entity: sensor.wattsonic_pv_input_total_power 208 | detail: 2 209 | name: PV power 210 | - hours_to_show: 12 211 | graph: line 212 | type: sensor 213 | entity: sensor.wattsonic_battery_p 214 | detail: 2 215 | name: Battery power 216 | - hours_to_show: 24 217 | graph: line 218 | type: sensor 219 | entity: sensor.wattsonic_total_backup_p 220 | detail: 2 221 | name: Load power 222 | - hours_to_show: 24 223 | graph: line 224 | type: sensor 225 | entity: sensor.wattsonic_total_power_on_meter 226 | detail: 2 227 | name: Grid power -------------------------------------------------------------------------------- /wattsonic/wattsonic_Variables+History.yaml: -------------------------------------------------------------------------------- 1 | #hacks addon https://github.com/enkama/hass-variables 2 | #install addon in hacks 3 | #restart hass os 4 | #in Devices and services add Variables+History 5 | #include package in configuration.yaml 6 | #homeassistant: 7 | # packages: 8 | # wattsonic_history: !include wattsonic_Variables+History.yaml 9 | 10 | variable: 11 | wattsonic_battery_soc_5min_history: 12 | value: 0 13 | restore: true 14 | exclude_from_recorder: true 15 | unique_id: wattsonic_battery_soc_5min_history 16 | attributes: 17 | icon: mdi:history 18 | wattsonic_battery_soc_eta: 19 | value: "init" 20 | restore: true 21 | exclude_from_recorder: true 22 | unique_id: wattsonic_battery_soc_eta 23 | friendly_name: Wattsonic Battery SOC ETA 24 | attributes: 25 | icon: mdi:clock 26 | 27 | wattsonic_grid_injection_energy_on_that_day_15min_history: 28 | value: 0 29 | restore: true 30 | exclude_from_recorder: true 31 | unique_id: wattsonic_grid_injection_energy_on_that_day_15min_history 32 | attributes: 33 | icon: mdi:history 34 | wattsonic_grid_injection_energy_on_that_day_15min: 35 | value: 0 36 | restore: true 37 | exclude_from_recorder: true 38 | unique_id: wattsonic_grid_injection_energy_on_that_day_15min 39 | friendly_name: Wattsonic Grid Injection Energy on that Day 15min 40 | unit_of_measurement: kW 41 | device_class: power 42 | # attributes: 43 | # icon: mdi:clock 44 | 45 | 46 | automation: 47 | - id: '1713384461619' 48 | alias: wattsonic_battery_soc_5min_history_update 49 | description: '' 50 | trigger: 51 | - platform: state 52 | entity_id: 53 | - sensor.wattsonic_battery_soc 54 | condition: [] 55 | action: 56 | - service: variable.update_sensor 57 | target: 58 | entity_id: sensor.wattsonic_battery_soc_5min_history 59 | data: 60 | replace_attributes: true 61 | value: '{{ now().strftime("%d.%m.%Y %H:%M:%S") }}' 62 | attributes: 63 | hist: > 64 | {% set maxTime = 5 %} 65 | {% set hist_sensor = "sensor.wattsonic_battery_soc_5min_history" %} 66 | {% set json_new = 67 | {"timestamp": as_timestamp(now()), 68 | "date": now().strftime("%d.%m.%Y %H:%M:%S"), 69 | "value": states('sensor.wattsonic_battery_soc')| float(0) } 70 | %} 71 | {% set hist_new = namespace(data=[]) %} 72 | {% set hist_old = namespace(data=[]) %} 73 | {% if not is_state_attr(hist_sensor, "hist", "unknown") and not state_attr(hist_sensor, "hist") is none %} 74 | {% set hist_old.data = state_attr(hist_sensor, "hist") %} 75 | {% endif%} 76 | 77 | {% for json1 in hist_old.data %} 78 | {% if json_new.timestamp-json1.timestamp <= maxTime*60+15 %} 79 | {% set hist_new.data = hist_new.data + [(json1)] %} 80 | {% endif %} 81 | {% endfor %} 82 | {% set hist_new.data = hist_new.data + [json_new] %} 83 | {{ hist_new.data }} 84 | - service: variable.update_sensor 85 | target: 86 | entity_id: sensor.wattsonic_battery_soc_eta 87 | data: 88 | replace_attributes: true 89 | value: > 90 | {% set battery_SOC = float(states("sensor.wattsonic_battery_soc"), default=100) %} 91 | {% if state_attr("sensor.wattsonic_battery_soc_5min_history","hist") is none 92 | or states("sensor.wattsonic_battery_soc") is none 93 | or state_attr("sensor.wattsonic_battery_soc_5min_history","hist")[0].value is none 94 | or state_attr("sensor.wattsonic_battery_soc_5min_history","hist")[0].timestamp is none %} 95 | {% set battery_SOC_last = 0 %} 96 | {% set timediff = 1 %} 97 | {% else %} 98 | {% set battery_SOC_last = state_attr("sensor.wattsonic_battery_soc_5min_history","hist")[0].value %} 99 | {% set timediff = (states.sensor.wattsonic_battery_soc_5min_history.last_updated.timestamp()|float(0) - state_attr("sensor.wattsonic_battery_soc_5min_history","hist")[0].timestamp )/60 %} 100 | {% endif %} 101 | {% set percdiff = battery_SOC - battery_SOC_last %} 102 | {% set discharge_value = float(states("sensor.wattsonic_on_grid_battery_end_soc"), default=20) %} 103 | {% if battery_SOC>99: %} 104 | full 105 | {% elif percdiff==0 or timediff==0: %} 106 | {{ states('sensor.wattsonic_battery_soc_eta') }} 107 | {% elif battery_SOC<=discharge_value: %} 108 | almost dicharged 109 | {% elif percdiff<=0: %} 110 | {% set percmissing = battery_SOC-discharge_value %} 111 | {% set timemissing = (percmissing / (percdiff/timediff))|round %} 112 | ↓{{ as_local(as_datetime(as_timestamp(utcnow()) - timemissing*60)).strftime("%d.%m.%Y %H:%M") }}↓ 113 | {% else %} 114 | {% set percmissing = 100-battery_SOC %} 115 | {% set timemissing = (percmissing / (percdiff/timediff))|round %} 116 | ↑{{ as_local(as_datetime(as_timestamp(utcnow()) + timemissing*60)).strftime("%d.%m.%Y %H:%M") }}↑ 117 | {% endif %} 118 | attributes: 119 | timediff: > 120 | {% if state_attr("sensor.wattsonic_battery_soc_5min_history","hist") is none 121 | or states("sensor.wattsonic_battery_soc") is none 122 | or state_attr("sensor.wattsonic_battery_soc_5min_history","hist")[0].timestamp is none %} 123 | {% set timediff = 0 %} 124 | {% else %} 125 | {% set timediff = (states.sensor.wattsonic_battery_soc_5min_history.last_updated.timestamp()|float(0) - float(state_attr("sensor.wattsonic_battery_soc_5min_history","hist")[0].timestamp | default(0)) )/60 %} 126 | {% endif %} 127 | {{ timediff }} 128 | percdiff: > 129 | {% set battery_SOC = float(states("sensor.wattsonic_battery_soc"), default=100) %} 130 | {% if state_attr("sensor.wattsonic_battery_soc_5min_history","hist") is none 131 | or state_attr("sensor.wattsonic_battery_soc_5min_history","hist")[0].value is none %} 132 | {% set battery_SOC_last = 0 %} 133 | {% else %} 134 | {% set battery_SOC_last = float(state_attr("sensor.wattsonic_battery_soc_5min_history","hist")[0].value, default=0) %} 135 | {% endif %} 136 | {% set percdiff = battery_SOC - battery_SOC_last %} 137 | {{ percdiff }} 138 | 139 | - id: '1713466950344' 140 | alias: wattsonic_grid_injection_energy_on_that_day_15min_history_update 141 | description: '' 142 | trigger: 143 | - platform: state 144 | entity_id: 145 | - sensor.wattsonic_battery_soc 146 | - sensor.wattsonic_grid_injection_energy_on_that_day 147 | condition: 148 | - condition: sun 149 | before: sunset 150 | after: sunrise 151 | after_offset: '1:00:00' 152 | before_offset: '-1:00:00' 153 | action: 154 | - service: variable.update_sensor 155 | target: 156 | entity_id: sensor.wattsonic_grid_injection_energy_on_that_day_15min_history 157 | data: 158 | replace_attributes: true 159 | value: '{{ now().strftime("%d.%m.%Y %H:%M:%S") }}' 160 | attributes: 161 | hist: > 162 | {% set maxTime = 15 %} {% set hist_sensor = 163 | "sensor.wattsonic_grid_injection_energy_on_that_day_15min_history" %} 164 | {% set json_new = 165 | {"timestamp": as_timestamp(now()), 166 | "date": now().strftime("%d.%m.%Y %H:%M:%S"), 167 | "value": float(states('sensor.wattsonic_grid_injection_energy_on_that_day'))| default(0) } 168 | %} 169 | {% set hist_new = namespace(data=[]) %} 170 | {% set hist_old = namespace(data=[]) %} 171 | {% if not is_state_attr(hist_sensor, "hist", "unknown") and not state_attr(hist_sensor, "hist") is none %} 172 | {% set hist_old.data = state_attr(hist_sensor, "hist") %} 173 | {% endif%} 174 | 175 | {% for json1 in hist_old.data %} 176 | {% if json_new.timestamp-json1.timestamp <= maxTime*60+15 %} 177 | {% set hist_new.data = hist_new.data + [(json1)] %} 178 | {% endif %} 179 | {% endfor %} 180 | 181 | {% set hist_new.data = hist_new.data + [json_new] %} 182 | {{ hist_new.data }} 183 | - service: variable.update_sensor 184 | target: 185 | entity_id: sensor.wattsonic_grid_injection_energy_on_that_day_15min 186 | data: 187 | replace_attributes: true 188 | value: > 189 | {% if state_attr("sensor.wattsonic_grid_injection_energy_on_that_day_15min_history","hist") is none 190 | or state_attr("sensor.wattsonic_grid_injection_energy_on_that_day_15min_history","hist")[0].value is none 191 | or state_attr("sensor.wattsonic_grid_injection_energy_on_that_day_15min_history","hist")[0].timestamp is none %} 192 | {% set diff = 0 %} 193 | {% set timediff = 0 %} 194 | {% else %} 195 | {% set diff = float(states("sensor.wattsonic_grid_injection_energy_on_that_day"), default=0) - state_attr("sensor.wattsonic_grid_injection_energy_on_that_day_15min_history","hist")[0].value %} 196 | {% set timediff = (states.sensor.wattsonic_grid_injection_energy_on_that_day_15min_history.last_updated.timestamp() - state_attr("sensor.wattsonic_grid_injection_energy_on_that_day_15min_history","hist")[0].timestamp)/60 %} 197 | {% endif %} 198 | {% if timediff==0 or diff<=0: %} 199 | 0 200 | {% elif timediff < 10: %} 201 | 0 202 | {% else %} 203 | {% set res = (diff/timediff*60) %} 204 | {{ res| round(1) }} 205 | {% endif %} 206 | attributes: 207 | updated: > 208 | {{ now().strftime("%d.%m.%Y %H:%M:%S") }} 209 | diff: > 210 | {% if state_attr("sensor.wattsonic_grid_injection_energy_on_that_day_15min_history","hist") is none 211 | or state_attr("sensor.wattsonic_grid_injection_energy_on_that_day_15min_history","hist")[0].value is none %} 212 | {% set diff = 0 %} 213 | {% else %} 214 | {% set diff = (float(states("sensor.wattsonic_grid_injection_energy_on_that_day"), default=0) - state_attr("sensor.wattsonic_grid_injection_energy_on_that_day_15min_history","hist")[0].value ) %} 215 | {% endif %} 216 | {{ diff }} 217 | timediff: > 218 | {% if state_attr("sensor.wattsonic_grid_injection_energy_on_that_day_15min_history","hist") is none 219 | or state_attr("sensor.wattsonic_grid_injection_energy_on_that_day_15min_history","hist")[0].timestamp is none %} 220 | {% set timediff = 0 %} 221 | {% else %} 222 | {% set timediff = (states.sensor.wattsonic_grid_injection_energy_on_that_day_15min_history.last_updated.timestamp() - state_attr("sensor.wattsonic_grid_injection_energy_on_that_day_15min_history","hist")[0].timestamp)/60 %} 223 | {% endif %} 224 | {{ timediff }} 225 | mode: single 226 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /wattsonic/wattsonic_gen2.yaml: -------------------------------------------------------------------------------- 1 | modbus: 2 | - name: wattsonic 3 | type: serial 4 | baudrate: 9600 5 | bytesize: 8 6 | method: rtu 7 | parity: N 8 | port: /dev/ttyUSB0 9 | stopbits: 1 10 | sensors: 11 | 12 | - name: PV Input total Power 13 | unique_id: PV Input total Power 14 | slave: 247 15 | address: 11028 16 | input_type: holding 17 | state_class: measurement 18 | unit_of_measurement: kWh 19 | device_class: power 20 | scale: 0.001 21 | precision: 3 22 | data_type: int32 23 | scan_interval: 30 24 | lazy_error_count: 10 25 | - name: PV1 Input Power 26 | unique_id: PV1 Input Power 27 | slave: 247 28 | address: 11062 29 | input_type: holding 30 | state_class: measurement 31 | unit_of_measurement: kW 32 | device_class: power 33 | scale: 0.001 34 | precision: 3 35 | data_type: int32 36 | scan_interval: 10 37 | lazy_error_count: 10 38 | - name: SOC 39 | unique_id: soc 40 | slave: 247 41 | address: 43000 42 | input_type: holding 43 | state_class: measurement 44 | #unit_of_measurement: % 45 | device_class: battery 46 | scale: 0.01 47 | precision: 2 48 | data_type: int16 49 | scan_interval: 60 50 | lazy_error_count: 10 51 | - name: Temperature Sensor 1 of Inverter 52 | unique_id: Temperature Sensor 1 of Inverter 53 | slave: 247 54 | address: 11032 55 | input_type: holding 56 | state_class: measurement 57 | unit_of_measurement: °C 58 | device_class: temperature 59 | scale: 0.1 60 | precision: 1 61 | data_type: int16 62 | scan_interval: 60 63 | lazy_error_count: 10 64 | - name: Wattsonic P AC 65 | unique_id: wattsonic_p_ac 66 | slave: 247 67 | address: 11016 68 | input_type: holding 69 | state_class: measurement 70 | unit_of_measurement: kW 71 | device_class: power 72 | scale: 0.001 73 | precision: 3 74 | data_type: int32 75 | scan_interval: 10 76 | lazy_error_count: 10 77 | - name: Total PV Generation on that day 78 | unique_id: Total PV Generation on that day 79 | slave: 247 80 | address: 11018 81 | input_type: holding 82 | state_class: total_increasing #measurement 83 | device_class: energy 84 | unit_of_measurement: kWh 85 | scale: 0.1 86 | precision: 1 87 | data_type: int32 88 | scan_interval: 300 89 | lazy_error_count: 10 90 | - name: Phase L1 Power on Energy-Meter 91 | unique_id: Phase L1 Power on Energy-Meter 92 | slave: 247 93 | address: 10994 94 | input_type: holding 95 | state_class: measurement 96 | unit_of_measurement: kW 97 | device_class: power 98 | scale: 0.001 99 | precision: 3 100 | data_type: int32 101 | scan_interval: 20 102 | lazy_error_count: 10 103 | - name: Phase L2 Power on Energy-Meter 104 | unique_id: Phase L2 Power on Energy-Meter 105 | slave: 247 106 | address: 10996 107 | input_type: holding 108 | state_class: measurement 109 | unit_of_measurement: kW 110 | device_class: power 111 | scale: 0.001 112 | precision: 3 113 | data_type: int32 114 | scan_interval: 20 115 | lazy_error_count: 10 116 | - name: Phase L3 Power on Energy-Meter 117 | unique_id: Phase L3 Power on Energy-Meter 118 | slave: 247 119 | address: 10998 120 | input_type: holding 121 | state_class: measurement 122 | unit_of_measurement: kW 123 | device_class: power 124 | scale: 0.001 125 | precision: 3 126 | data_type: int32 127 | scan_interval: 20 128 | lazy_error_count: 10 129 | - name: Wattsonic Total Power on Meter #Grid power 130 | unique_id: wattsonic_total_power_on_meter 131 | slave: 247 132 | address: 11000 133 | input_type: holding 134 | state_class: measurement 135 | unit_of_measurement: kW 136 | device_class: power 137 | scale: 0.001 138 | precision: 3 139 | data_type: int32 140 | scan_interval: 10 141 | lazy_error_count: 10 142 | - name: Total Grid-Injection Power on Energy-Meter 143 | unique_id: Total Grid-Injection Power on Energy-Meter 144 | slave: 247 145 | address: 11002 146 | input_type: holding 147 | state_class: total_increasing 148 | unit_of_measurement: kWh 149 | device_class: energy 150 | scale: 0.01 151 | precision: 2 152 | data_type: int32 153 | scan_interval: 300 154 | lazy_error_count: 10 155 | - name: Total Purchasing Power from grid on Energy-Meter 156 | unique_id: Total Purchasing Power from grid on Energy-Meter 157 | slave: 247 158 | address: 11004 159 | input_type: holding 160 | state_class: total_increasing 161 | unit_of_measurement: kWh 162 | device_class: energy 163 | scale: 0.01 164 | precision: 2 165 | data_type: int32 166 | scan_interval: 300 167 | lazy_error_count: 10 168 | - name: Battery_Power 169 | unique_id: Battery_Power 170 | slave: 247 171 | address: 40258 172 | input_type: holding 173 | state_class: measurement 174 | unit_of_measurement: kW 175 | device_class: power 176 | scale: 0.001 177 | precision: 3 178 | data_type: int32 179 | scan_interval: 10 180 | lazy_error_count: 10 181 | - name: Grid injection energy on that day [Meter] 182 | unique_id: Grid injection energy on that day [Meter] 183 | slave: 247 184 | address: 41000 185 | scale: 0.1 186 | precision: 1 187 | input_type: holding 188 | unit_of_measurement: kWh 189 | state_class: total_increasing #measurement 190 | device_class: energy 191 | data_type: int16 192 | scan_interval: 300 193 | lazy_error_count: 10 194 | - name: Grid Purchasing energy on that day [Meter] 195 | unique_id: Grid Purchasing energy on that day [Meter] 196 | slave: 247 197 | address: 41001 198 | scale: 0.1 199 | precision: 1 200 | input_type: holding 201 | unit_of_measurement: kWh 202 | state_class: total_increasing #measurement 203 | device_class: energy 204 | data_type: int16 205 | scan_interval: 300 206 | lazy_error_count: 10 207 | - name: Wattsonic Battery Charge Energy on that day 208 | unique_id: wattsonic_battery_charge_energy_on_that_day 209 | slave: 247 210 | address: 41003 211 | scale: 0.1 212 | precision: 1 213 | input_type: holding 214 | unit_of_measurement: kWh 215 | state_class: total_increasing #measurement 216 | device_class: energy 217 | data_type: int16 218 | scan_interval: 300 219 | lazy_error_count: 10 220 | - name: Wattsonic Battery Discharge Energy on that day 221 | unique_id: wattsonic_battery_discharge_energy_on_that_day 222 | slave: 247 223 | address: 41004 224 | scale: 0.1 225 | precision: 1 226 | input_type: holding 227 | unit_of_measurement: kWh 228 | state_class: total_increasing #measurement 229 | device_class: energy 230 | data_type: int16 231 | scan_interval: 300 232 | lazy_error_count: 10 233 | - name: Wattsonic PV Generation Energy on that day #E-today from GUI 234 | unique_id: wattsonic_pv_generation_energy_on_that_day 235 | slave: 247 236 | address: 41005 237 | scale: 0.1 238 | precision: 1 239 | input_type: holding 240 | unit_of_measurement: kWh 241 | state_class: total_increasing #measurement 242 | device_class: energy 243 | data_type: int16 244 | scan_interval: 300 245 | lazy_error_count: 10 246 | - name: Wattsonic Loading Energy on that day 247 | unique_id: wattsonic_loading_energy_on_that_day 248 | slave: 247 249 | address: 41006 250 | scale: 0.1 251 | precision: 1 252 | input_type: holding 253 | unit_of_measurement: kWh 254 | state_class: total_increasing #measurement 255 | device_class: energy 256 | data_type: int16 257 | scan_interval: 300 258 | lazy_error_count: 10 259 | - name: Wattsonic Energy Purchased from Grid on that day 260 | unique_id: wattsonic_energy_purchased_from_grid_on_that_day 261 | slave: 247 262 | address: 41008 263 | scale: 0.1 264 | precision: 1 265 | input_type: holding 266 | unit_of_measurement: kWh 267 | state_class: total_increasing #measurement 268 | device_class: energy 269 | data_type: int16 270 | scan_interval: 300 271 | lazy_error_count: 10 272 | - name: Wattsonic Total Energy Charged to Battery 273 | unique_id: wattsonic_total_energy_charged_to_battery 274 | slave: 247 275 | address: 41108 276 | scale: 0.1 277 | precision: 1 278 | input_type: holding 279 | unit_of_measurement: kWh 280 | state_class: total_increasing 281 | device_class: energy 282 | data_type: int32 283 | scan_interval: 300 284 | lazy_error_count: 10 285 | - name: Wattsonic Total Energy Discharged from Battery 286 | unique_id: wattsonic_total_energy_discharged_from_battery 287 | slave: 247 288 | address: 41110 289 | scale: 0.1 290 | precision: 1 291 | input_type: holding 292 | unit_of_measurement: kWh 293 | state_class: total_increasing 294 | device_class: energy 295 | data_type: int32 296 | scan_interval: 300 297 | lazy_error_count: 10 298 | - name: Wattsonic Total PV Generation 299 | unique_id: wattsonic_total_pv_generation 300 | slave: 247 301 | address: 41112 302 | scale: 0.1 303 | precision: 1 304 | input_type: holding 305 | unit_of_measurement: kWh 306 | state_class: total_increasing 307 | device_class: energy 308 | data_type: int32 309 | scan_interval: 300 310 | lazy_error_count: 10 311 | - name: Wattsonic Total Loading Energy consumed at grid side 312 | unique_id: wattsonic_total_loading_energy_consumed_at_grid_side 313 | slave: 247 314 | address: 41114 315 | scale: 0.1 316 | precision: 1 317 | input_type: holding 318 | unit_of_measurement: kWh 319 | state_class: total_increasing 320 | device_class: energy 321 | data_type: int32 322 | scan_interval: 3600 323 | lazy_error_count: 10 324 | - name: Wattsonic Total Output Energy on backup port 325 | unique_id: Wattsonic Total Output Energy on backup port 326 | slave: 247 327 | address: 41106 328 | input_type: holding 329 | state_class: measurement 330 | unit_of_measurement: kW 331 | device_class: power 332 | scale: 0.001 333 | precision: 3 334 | data_type: int32 335 | scan_interval: 10 336 | lazy_error_count: 10 337 | - name: Wattsonic Total Energy Purchased from Grid at inverter side 338 | unique_id: wattsonic_total_energy_purchased_from_grid_at_inverter_side 339 | slave: 247 340 | address: 41118 341 | scale: 0.1 342 | precision: 1 343 | input_type: holding 344 | unit_of_measurement: kWh 345 | state_class: total_increasing 346 | device_class: energy 347 | data_type: int32 348 | scan_interval: 600 349 | lazy_error_count: 10 350 | - name: Wattsonic BMS Warn Code 351 | unique_id: wattsonic_bms_warn_code 352 | slave: 247 353 | address: 43018 354 | input_type: holding 355 | data_type: int32 356 | scan_interval: 60 357 | lazy_error_count: 10 358 | - name: Wattsonic Battery Mode #0:discharge;1:charge 359 | unique_id: wattsonic_battery_mode 360 | slave: 247 361 | address: 40256 362 | input_type: holding 363 | data_type: int16 364 | scan_interval: 10 365 | lazy_error_count: 10 366 | 367 | template: 368 | - sensor: 369 | - default_entity_id: sensor.wattsonic_home_consumption_now 370 | name: Wattsonic Home Consumption Now 371 | unique_id: wattsonic_home_consumption_now 372 | unit_of_measurement: kW 373 | device_class: power 374 | state: > 375 | {% set diff = (float(states("sensor.wattsonic_p_ac"), default=0) 376 | - float(states("sensor.wattsonic_total_power_on_meter"), default=0)) | round(3) %} 377 | {% if diff>0: %} 378 | {{ diff }} 379 | {% else %} 380 | {{ (float(states("sensor.wattsonic_home_consumption_now"), default=0)) }} 381 | {% endif %} 382 | -------------------------------------------------------------------------------- /nfws/rootfs/usr/bin/nfws.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import json 3 | import paho.mqtt.client as paho 4 | from jsonpath_ng.ext import parse 5 | from datetime import datetime, timezone 6 | import time 7 | import yaml 8 | import webbrowser 9 | import logging 10 | import os 11 | import shutil 12 | 13 | #---global constants 14 | # netatmo_module_config= {"Temperature" : ["Temperature"], 15 | # "Humidity" : ["Humidity"], 16 | # "Rain" : ["Rain", "sum_rain_1", "sum_rain_24"], 17 | # "Wind" : ["WindStrength", "WindAngle", "GustStrength", "GustAngle"] 18 | # } 19 | 20 | #---global variables 21 | registered_entity = {} 22 | netatmo_not_used_stations = [] 23 | config_dir = "" 24 | run_mode = "" #local, hass 25 | global mqtt_client 26 | 27 | def get_dict_value(dictv, key, default = "None"): 28 | if key in dictv: 29 | return dictv[key] 30 | else: 31 | return default 32 | 33 | def debug_log(text): 34 | if get_dict_value(config["nfws"], "log_level") == "debug": 35 | logger.debug(snow() + text) 36 | 37 | def prepare_hass_addon(): 38 | 39 | if run_mode != "hass": 40 | return False 41 | 42 | ##delete when copy from old location ot needed 43 | try: 44 | res_old_config = os.listdir(rf'/homeassistant/nfws/') 45 | except BaseException as err: 46 | res_old_config = [] 47 | ##delete 48 | 49 | try: 50 | # logger.critical(f"Directory1: {os.listdir(r'/config/')}") 51 | res_config = os.listdir(rf'{config_dir}') 52 | if not "stations.yaml" in res_config: 53 | ##delete 54 | if "stations.yaml" in res_old_config and "netatmo_token.yaml" in res_old_config: #migration from old config location 55 | try: 56 | logger.debug(f'Old config files found in /homeassistant/nfws/') 57 | shutil.copyfile('/homeassistant/nfws/stations.yaml', f'{config_dir}stations.yaml') 58 | shutil.copyfile('/homeassistant/nfws/netatmo_token.yaml', f'{config_dir}netatmo_token.yaml') 59 | except BaseException as err: 60 | logger.critical(f'Cannot copy old config files from /config/nfws to {config_dir}') 61 | logger.critical(f'Copy netatmo_token.yaml and stations.yaml manually and restart addon') 62 | exit() 63 | else: #first run stations.yaml doesn't exists 64 | ##delete 65 | try: 66 | logger.debug(f'New installation, copy default stations.yaml, please change it.') 67 | shutil.copyfile('/usr/bin/stations.yaml', f'{config_dir}stations.yaml') 68 | except BaseException as err: 69 | logger.critical(f'Cannot copy stations.yaml to {config_dir}') 70 | exit() 71 | except BaseException as err: 72 | return False 73 | 74 | return True 75 | 76 | def load_config(): 77 | global config 78 | global netatmo_stations 79 | global params 80 | global config_dir 81 | global run_mode 82 | 83 | # logger.debug(f"Run mode: {os.environ}") 84 | 85 | if "SUPERVISOR_TOKEN" in os.environ: 86 | config_dir = "/config/" 87 | # config_dir = os.getenv('HOSTNAME')+"/" 88 | run_mode = "hass" 89 | else: 90 | run_mode = "local" 91 | 92 | prepare_hass_addon() 93 | 94 | if run_mode == "hass": 95 | try: 96 | with open('/data/options.json', 'r') as file: 97 | config = json.load(file) 98 | except BaseException as err: 99 | logger.critical(f"{snow()}/data/options.json missing {err=}, {type(err)=}") 100 | exit() 101 | else: 102 | try: 103 | with open(r'options.yaml') as file: 104 | config = yaml.load(file, Loader=yaml.FullLoader) 105 | except BaseException as err: 106 | logger.critical(f"{snow()}options.yaml missing {err=}, {type(err)=}") 107 | exit() 108 | 109 | #logger.critical(config) 110 | #exit() 111 | 112 | try: 113 | with open(config_dir+r'stations.yaml') as file: 114 | config_stations = yaml.load(file, Loader=yaml.FullLoader) 115 | except BaseException as err: 116 | logger.critical(f"{snow()}{config_dir}stations.yaml missing {err=}, {type(err)=}") 117 | logger.debug(f"Run mode: {run_mode}") 118 | logger.debug(f"Config dir: {config_dir}") 119 | exit() 120 | config.update(config_stations) 121 | 122 | if get_dict_value(config["nfws"], "log_level") != "None": 123 | logger.setLevel(get_dict_value(config["nfws"], "log_level").upper()) 124 | 125 | logger.debug(f"Run mode: {run_mode}") 126 | logger.debug(f"Config dir: {config_dir}") 127 | 128 | if "nfws" not in config: 129 | config["nfws"] = "None" 130 | if "netatmo" not in config: 131 | logger.critical("netatmo section in config missing") 132 | exit() 133 | if run_mode != "hass" and "mqtt" not in config: 134 | logger.critical("mqtt section in config missing") 135 | exit() 136 | if "netatmo_stations" not in config: 137 | logger.critical("netatmo_stations section in stations.yaml missing") 138 | exit() 139 | 140 | if config["netatmo"]["client_id"] == "": 141 | logger.critical("Netatmo client_id is empty!") 142 | exit() 143 | if config["netatmo"]["client_secret"] == "": 144 | logger.critical("Netatmo client_secret is empty!") 145 | exit() 146 | 147 | if "redirect_uri" not in config["netatmo"]: 148 | (config["netatmo"])["redirect_uri"] = "hassio" 149 | if "state" not in config["netatmo"]: 150 | (config["netatmo"])["state"] = "nfws_hass" 151 | 152 | netatmo_stations = config["netatmo_stations"] 153 | params = { 154 | 'device_id': '00:00:00:00:00:00', 155 | 'get_favorites': 'true', 156 | } 157 | params["device_id"] = next(iter(netatmo_stations)) #get first station from list 158 | #print(next(iter(netatmo_stations))) 159 | 160 | return True 161 | 162 | def load_netatmo_token(): 163 | global netatmo_token 164 | 165 | try: 166 | with open(config_dir+r'netatmo_token.yaml') as file: 167 | netatmo_token = yaml.load(file, Loader=yaml.FullLoader) 168 | except BaseException as err: 169 | netatmo_token = {} 170 | 171 | #print(netatmo_token) 172 | 173 | if "refresh_token" not in netatmo_token: 174 | netatmo_token["refresh_token"] = "" 175 | if "access_token" not in netatmo_token: 176 | netatmo_token["access_token"] = "" 177 | 178 | return True 179 | 180 | 181 | def degToCompass(num): 182 | #https://stackoverflow.com/questions/7490660/converting-wind-direction-in-angles-to-text-words 183 | val = int((num/45)+.5) 184 | arr = ["N", "NE", "E", "SE", "S", "SW", "W", "NW"] 185 | return arr[(val % 8)] 186 | def degToCompassSymbol(num): 187 | val = int((num/45)+.5) 188 | # arr = ["↑", "↗", "→", "↘", "↓", "↙", "←", "↖"] 189 | arr = ["↓", "↙", "←", "↖", "↑", "↗", "→", "↘"] 190 | return arr[(val % 8)] 191 | 192 | def snow(): 193 | #return datetime.now().strftime("%d.%m.%Y %H:%M:%S")+" " 194 | utc_dt = datetime.now(timezone.utc) 195 | return utc_dt.astimezone().strftime("%d.%m.%Y %H:%M:%S") + " " 196 | 197 | def netatmo_check_oauth_code(): 198 | client = get_dict_value(config["netatmo"], "oauth_code", "") 199 | if client == "": 200 | logger.critical(f"{snow()}Missing Netatmo authorisation OAUTH code!") 201 | logger.critical(f"When access granted, copy code value from returned url to config.yaml") 202 | logger.critical(f"Example of returned URL: https://app.netatmo.net/oauth2/hassio?state=nfws_hass&code=5ebbe91cdd814823ddfe4336a7e9b6b8") 203 | client_id = config["netatmo"]["client_id"] 204 | uri = config["netatmo"]["redirect_uri"] 205 | state = config["netatmo"]["state"] 206 | url = f"https://api.netatmo.com/oauth2/authorize?client_id={client_id}&redirect_uri={uri}&scope=read_station&state={state}" 207 | logger.critical(f"") 208 | logger.critical(f"Calling...{url}") 209 | #webbrowser.open_new(url) 210 | logger.critical(f"Copy&paste the url to a new window and get your OAUTH code") 211 | logger.critical(f"Delete netatmo_token.yaml if it exists in config directory.") 212 | logger.critical(f"when done, restart addon...") 213 | time.sleep(600) 214 | exit() 215 | 216 | return 1 217 | 218 | def netatmo_get_oauth_token(): 219 | global netatmo_token 220 | client_id = config["netatmo"]["client_id"] 221 | client_secret = config["netatmo"]["client_secret"] 222 | uri = config["netatmo"]["redirect_uri"] 223 | code = config["netatmo"]["oauth_code"] 224 | refresh_token = netatmo_token["refresh_token"] 225 | 226 | if refresh_token != "": 227 | data = f"grant_type=refresh_token&client_id={client_id}&client_secret={client_secret}&refresh_token={refresh_token}" 228 | method = "Netatmo refresh_token" 229 | else: 230 | data = f"grant_type=authorization_code&client_id={client_id}&client_secret={client_secret}&code={code}&redirect_uri={uri}&scope=read_station" 231 | method = "Netatmo authorization_code" 232 | 233 | #logger.debug(data) 234 | headers = {'Content-Type': 'application/x-www-form-urlencoded',} 235 | response_ok = False 236 | 237 | while not response_ok: 238 | try: 239 | response = requests.post('https://api.netatmo.com/oauth2/token', headers=headers, data=data) 240 | except BaseException as err: 241 | logger.error(f"{snow()}Unexpected {method} {err=}, {type(err)=}") 242 | logger.error(" Retry in 1 min again") 243 | time.sleep(60) 244 | continue 245 | 246 | if response.status_code != requests.codes.ok: 247 | logger.warning(f"{method}: Wrong response code {response.status_code}") 248 | json_token=response.json() 249 | logger.warning(json_token) 250 | logger.warning(" Retry in 1 min again") 251 | time.sleep(60) 252 | continue 253 | 254 | json_token = response.json() 255 | logger.debug(json.dumps(json_token, indent = 4, sort_keys=True)) 256 | if "access_token" not in json_token: 257 | logger.warning(snow() + f"{method}: Acces token is missing in response") 258 | logger.warning(" Retry in 1 min again") 259 | time.sleep(60) 260 | continue 261 | response_ok = True 262 | netatmo_token = response.json() 263 | 264 | #access_token = json_token["access_token"] 265 | #refresh_token = json_token["refresh_token"] 266 | 267 | try: 268 | with open(config_dir+r'netatmo_token.yaml', 'w') as file: 269 | documents = yaml.dump(json_token, file) 270 | except BaseException as err: 271 | logger.critical(f"{snow()}Cannot write netatmo_token.yaml {err=}, {type(err)=}") 272 | time.sleep(60) 273 | exit() 274 | 275 | return 1 276 | 277 | def netatmo_refresh_token(): 278 | global netatmo_token 279 | client_id = config["netatmo"]["client_id"] 280 | client_secret = config["netatmo"]["client_secret"] 281 | refresh_token = netatmo_token["refresh_token"] 282 | 283 | data = f"grant_type=refresh_token&client_id={client_id}&client_secret={client_secret}&refresh_token={refresh_token}" 284 | print(data) 285 | headers = {'Content-Type': 'application/x-www-form-urlencoded',} 286 | response_ok = False 287 | 288 | while not response_ok: 289 | try: 290 | response = requests.post('https://api.netatmo.com/oauth2/token', headers=headers, data=data) 291 | except BaseException as err: 292 | print(f"{snow()}Unexpected netatmo_refresh_token {err=}, {type(err)=}") 293 | json_token=response.json() 294 | print(json_token) 295 | print(" Retry in 1 min again") 296 | time.sleep(60) 297 | continue 298 | 299 | if response.status_code != requests.codes.ok: 300 | print(f"Netatmo_refresh_token: Wrong response code {response.status_code}") 301 | json_token=response.json() 302 | print(json_token) 303 | print(" Retry in 1 min again") 304 | time.sleep(60) 305 | continue 306 | 307 | json_token=response.json() 308 | print(json_token) 309 | print(json.dumps(json_token, indent = 4, sort_keys=True)) 310 | if "access_token" not in json_token: 311 | print(snow() + "Netatmo_getdata: Acces token is missing in response") 312 | print(" Retry in 1 min again") 313 | time.sleep(60) 314 | continue 315 | response_ok = True 316 | 317 | #access_token = json_token["access_token"] 318 | netatmo_token = response.json() 319 | 320 | try: 321 | with open(config_dir+r'netatmo_token.yaml', 'w') as file: 322 | documents = yaml.dump(json_token, file) 323 | except BaseException as err: 324 | print(f"{snow()}Cannot write netatmo_token.yaml {err=}, {type(err)=}") 325 | exit() 326 | 327 | return 1 328 | 329 | 330 | def mqtt_on_connect(client, userdata, flags, rc): 331 | 332 | if rc == 0: 333 | debug_log("Connected to mqtt broker - mqtt_on_connect") 334 | registered_entity = {} 335 | else: 336 | debug_log("Connection to mqtt broker failed - mqtt_on_connect") 337 | logger.warning(" Retry in 10 sec again") 338 | time.sleep(10) 339 | #mqtt_client.disconnect() 340 | #mqtt_connect() 341 | 342 | def mqtt_disconnect(): 343 | if run_mode != "hass": 344 | mqtt_client.disconnect() 345 | return True 346 | 347 | def mqtt_connect(): 348 | if run_mode == "hass": 349 | return True 350 | 351 | global mqtt_client 352 | response_ok = False 353 | while not response_ok: 354 | try: 355 | client = get_dict_value(config["mqtt"], "client", "nwsclient") 356 | mqtt_client = paho.Client(client) 357 | if get_dict_value(config["mqtt"], "username", "") != "": 358 | mqtt_client.username_pw_set(config["mqtt"]["username"], config["mqtt"]["password"]) 359 | mqtt_client.on_connect = mqtt_on_connect 360 | res = mqtt_client.connect(config["mqtt"]["address"], config["mqtt"]["port"]) 361 | mqtt_client.loop_start() 362 | if res != 0: 363 | logger.error(snow()+ "Cannot connect to mqtt broker: " + str(res)) 364 | logger.error(" Retry in 1 min again") 365 | time.sleep(60) 366 | else: 367 | debug_log("Connected to mqtt broker") 368 | response_ok = True 369 | registered_entity = {} 370 | except BaseException as err: 371 | logger.error(f"{snow()}Unexpected mqtt_connect {err=}, {type(err)=}") 372 | logger.error(" Retry in 1 min again") 373 | time.sleep(60) 374 | 375 | return True 376 | 377 | def hass_mqtt_publish(topic, value, qos, retain): 378 | 379 | headers = {'Authorization': f"Bearer {os.getenv('SUPERVISOR_TOKEN')}",'content-type': 'application/json' } 380 | data = {'payload': f"{value}", 'topic': f"{topic}", 'retain': f"{retain}"} 381 | #logger.debug(data) 382 | 383 | response_ok = False 384 | while not response_ok: 385 | if run_mode == "hass": 386 | try: 387 | res = requests.request('POST', 'http://supervisor/core/api/services/mqtt/publish', headers=headers, json=data) 388 | if res.status_code != 200: 389 | logger.error(f"{snow()}Error hass_mqtt_publish, not nonnected? =>reconnect? code: {res.rc} status_code: {res.status_code}") 390 | logger.error(f" {topic}={value}") 391 | time.sleep(60) 392 | else: 393 | response_ok = True 394 | except BaseException as err: 395 | logger.error(f"{snow()}Unexpected mqtt_publish {err=}, {type(err)=}") 396 | logger.error(" Retry in 1 min again") 397 | time.sleep(60) 398 | 399 | else: 400 | try: 401 | res = mqtt_client.publish(topic, value, qos, retain) 402 | if res.rc != 0: 403 | logger.error(f"{snow()}Error hass_mqtt_publish, not nonnected? =>reconnect? code: {res.rc}") 404 | logger.error(f" {topic}={value}") 405 | mqtt_connect() 406 | else: 407 | response_ok = True 408 | #print(f" {topic}={value}") 409 | except BaseException as err: 410 | logger.error(f"{snow()}Unexpected mqtt_publish {err=}, {type(err)=}") 411 | logger.error(" Retry in 1 min again") 412 | time.sleep(60) 413 | 414 | return res 415 | 416 | def hass_register_sensor(entity_name, sensor): 417 | #nfws_name_temperature, sensor name 418 | global registered_entity 419 | 420 | if entity_name in registered_entity: 421 | return False 422 | 423 | registered_entity[entity_name] = True 424 | hass_conf = {} 425 | hass_conf["unique_id"] = entity_name 426 | hass_conf["name"] = entity_name 427 | hass_conf["state_topic"] = "nfws/sensor/" + entity_name + "/state" 428 | hass_conf["json_attributes_topic"] = "nfws/sensor/" + entity_name + "/state" #new 429 | hass_conf["value_template"] = "{{ value_json.value }}" #new 430 | hass_conf["device"] = { 431 | "identifiers": ["Netatmo weather station_70ee50"], 432 | "name": "Netatmo Favourite Weather Stations", 433 | "manufacturer": "Netatmo", 434 | "model": "Weather Stations" 435 | } 436 | 437 | if sensor.lower() != "windanglecompass" and sensor.lower() != "windanglecompasssymbol" and sensor.lower() != "gustanglecompass" and sensor.lower() != "gustanglecompasssymbol": 438 | hass_conf["state_class"] = "measurement" 439 | if sensor.lower() == "temperature" or sensor.lower() == "min_temp" or sensor.lower() == "max_temp": 440 | hass_conf["device_class"] = "temperature" 441 | hass_conf["unit_of_measurement"] = "°C" 442 | if sensor.lower() == "humidity": 443 | hass_conf["device_class"] = "humidity" 444 | hass_conf["unit_of_measurement"] = "%" 445 | if sensor.lower() == "pressure": 446 | hass_conf["device_class"] = "pressure" 447 | hass_conf["unit_of_measurement"] = "hPa" 448 | if sensor.lower() == "guststrength" or sensor.lower() == "windstrength": 449 | hass_conf["unit_of_measurement"] = "km/h" 450 | if sensor.lower() == "sum_rain_24" or sensor.lower() == "sum_rain_1" or sensor.lower() == "rain": 451 | hass_conf["unit_of_measurement"] = "mm" 452 | # if sensor.lower() == "windanglecompass" or sensor.lower() == "windanglecompasssymbol" or sensor.lower() == "gustanglecompass" or sensor.lower() == "gustanglecompasssymbol": 453 | # hass_conf["state_class"] = "" 454 | 455 | logger.info( snow() + "Registering: " + str(hass_conf)) 456 | ret = hass_mqtt_publish("homeassistant/sensor/nfws/" + entity_name + "/config", json.dumps(hass_conf), qos=0, retain=True) 457 | #print(ret.rc) 458 | 459 | return True 460 | 461 | def hass_publish_station_sensor(station, sensor, value): 462 | #station from config, sensor name, value 463 | 464 | if sensor in station["sensors"]: #is sensor configured? 465 | hass_register_sensor("nfws_" + station["name"] + "_" + sensor, sensor) 466 | 467 | hass_data = {} 468 | hass_data["value"] = value 469 | hass_data["updated_when"] = snow() 470 | ret = hass_mqtt_publish(f"nfws/sensor/nfws_{station['name']}_{sensor}/state", json.dumps(hass_data, ensure_ascii=False), qos = 0, retain = False) 471 | #print(ret.rc) 472 | 473 | return True 474 | 475 | def hass_publish_calculated_station_sensor(entity_name, sensor, value): 476 | #calculated station name, sensor name, value 477 | 478 | hass_register_sensor(entity_name, sensor) 479 | 480 | value["updated_when"] = snow() 481 | ret = hass_mqtt_publish(f"nfws/sensor/{entity_name}/state", json.dumps(value, ensure_ascii=False), qos = 0, retain = False) 482 | #print(ret.rc) 483 | 484 | return True 485 | 486 | def netatmo_getdata(): 487 | response_ok = False; 488 | while not response_ok: 489 | access_token = netatmo_token["access_token"] 490 | headers = {'Authorization': f"Bearer {access_token}", } 491 | try: 492 | response = requests.get('https://api.netatmo.com/api/getstationsdata', params=params, headers=headers) 493 | except BaseException as err: 494 | logger.warning(f"{snow()}Unexpected netatmo_getdata {err=}, {type(err)=}") 495 | logger.warning(" Retry in 1 min again") 496 | time.sleep(60) 497 | continue 498 | json_netatmo = response.json() 499 | #print(json_netatmo) 500 | if get_dict_value(config["netatmo"], "show_response", False) == True: 501 | #print(json.dumps(json_netatmo, indent = 4, sort_keys=True)) 502 | logger.debug(json_netatmo) 503 | time.sleep(60) 504 | 505 | if "error" in json_netatmo: 506 | if json_netatmo["error"]["message"] in {"Invalid access token", "Access token expired"}: 507 | logger.warning(snow() + "Invalid access token or expired:" + json_netatmo["error"]["message"]) 508 | time.sleep(60) 509 | netatmo_get_oauth_token() 510 | else: 511 | logger.error(snow() + json_netatmo["error"]["message"]) 512 | logger.error(" Retry in 1 min again") 513 | time.sleep(60) 514 | else: 515 | response_ok = True 516 | return json_netatmo 517 | 518 | def netatmo_handle_favourite_stations_sensors(): 519 | for device in json_netatmo_devices: 520 | if device["_id"] not in netatmo_stations: 521 | if device["_id"] not in netatmo_not_used_stations: 522 | logger.info(f"Not used station id: {device['_id']}, name: {device['station_name']}") 523 | netatmo_not_used_stations.append(device["_id"]) 524 | continue 525 | if device["reachable"] == False: 526 | continue 527 | 528 | #print(device["station_name"]) 529 | #print(netatmo_stations[device["_id"]]) 530 | 531 | hass_publish_station_sensor(netatmo_stations[device["_id"]], "Pressure", device["dashboard_data"]["Pressure"]) 532 | #print(device["dashboard_data"]["Pressure"]) 533 | for module in device["modules"]: 534 | #print(module["data_type"]) 535 | if module["reachable"] == False: 536 | continue 537 | if "data_type" not in module: 538 | continue 539 | if "dashboard_data" not in module: 540 | continue 541 | 542 | if module["data_type"].count("Temperature")!=0: 543 | if "Temperature" in module["dashboard_data"]: 544 | hass_publish_station_sensor(netatmo_stations[device["_id"]], "Temperature", module["dashboard_data"]["Temperature"]) 545 | if "min_temp" in module["dashboard_data"]: 546 | hass_publish_station_sensor(netatmo_stations[device["_id"]], "min_temp", module["dashboard_data"]["min_temp"]) 547 | if "max_temp" in module["dashboard_data"]: 548 | hass_publish_station_sensor(netatmo_stations[device["_id"]], "max_temp", module["dashboard_data"]["max_temp"]) 549 | if module["data_type"].count("Humidity")!=0: 550 | if "Humidity" in module["dashboard_data"]: 551 | hass_publish_station_sensor(netatmo_stations[device["_id"]], "Humidity", module["dashboard_data"]["Humidity"]) 552 | #print(module["dashboard_data"]["Humidity"]) 553 | if module["data_type"].count("Rain")!=0: 554 | if "Rain" in module["dashboard_data"]: 555 | hass_publish_station_sensor(netatmo_stations[device["_id"]], "rain", module["dashboard_data"]["Rain"]) 556 | #print(module["dashboard_data"]["Rain"]) 557 | if "sum_rain_1" in module["dashboard_data"]: 558 | hass_publish_station_sensor(netatmo_stations[device["_id"]], "sum_rain_1", module["dashboard_data"]["sum_rain_1"]) 559 | #print(module["dashboard_data"]["sum_rain_1"]) 560 | if "sum_rain_24" in module["dashboard_data"]: 561 | hass_publish_station_sensor(netatmo_stations[device["_id"]], "sum_rain_24", module["dashboard_data"]["sum_rain_24"]) 562 | #print(module["dashboard_data"]["sum_rain_24"]) 563 | if module["data_type"].count("Wind")!=0: 564 | if "WindStrength" in module["dashboard_data"]: 565 | hass_publish_station_sensor(netatmo_stations[device["_id"]], "WindStrength", module["dashboard_data"]["WindStrength"]) 566 | #print(module["dashboard_data"]["WindStrength"]) 567 | if "WindAngle" in module["dashboard_data"]: 568 | hass_publish_station_sensor(netatmo_stations[device["_id"]], "WindAngle", module["dashboard_data"]["WindAngle"]) 569 | hass_publish_station_sensor(netatmo_stations[device["_id"]], "WindAngleCompass", degToCompass(module["dashboard_data"]["WindAngle"])) ##odkial fuka 570 | hass_publish_station_sensor(netatmo_stations[device["_id"]], "WindAngleCompassSymbol", degToCompassSymbol(module["dashboard_data"]["WindAngle"])) ##odkial fuka 571 | #print(module["dashboard_data"]["WindAngle"]) 572 | if "GustStrength" in module["dashboard_data"]: 573 | hass_publish_station_sensor(netatmo_stations[device["_id"]], "GustStrength", module["dashboard_data"]["GustStrength"]) 574 | #print(module["dashboard_data"]["GustStrength"]) 575 | if "GustAngle" in module["dashboard_data"]: 576 | hass_publish_station_sensor(netatmo_stations[device["_id"]], "GustAngle", module["dashboard_data"]["GustAngle"]) 577 | hass_publish_station_sensor(netatmo_stations[device["_id"]], "GustAngleCompass", degToCompass(module["dashboard_data"]["GustAngle"])) 578 | hass_publish_station_sensor(netatmo_stations[device["_id"]], "GustAngleCompassSymbol", degToCompassSymbol(module["dashboard_data"]["GustAngle"])) 579 | #print(module["dashboard_data"]["GustAngle"]) 580 | 581 | def netatmo_handle_calculated_sensors_function_minmaxavg(function_sensor): 582 | def Average(lst): 583 | return round(sum(lst) / len(lst), 1) 584 | 585 | #print(function_sensor) 586 | if "sensors" not in function_sensor: 587 | return 588 | if "stations" not in function_sensor: 589 | return 590 | for sensor in function_sensor["sensors"]: 591 | #print(sensor) 592 | values = [] 593 | 594 | for station_id in function_sensor["stations"]: 595 | #print(station_id) 596 | 597 | if sensor == "Pressure": 598 | station = parse(f"$.devices[?(@._id == '{station_id}')].dashboard_data.Pressure") 599 | else: 600 | station = parse(f"$.devices[?(@._id == '{station_id}')].modules[*].dashboard_data.{sensor}") 601 | 602 | for match in station.find(json_netatmo_body): 603 | if get_dict_value(match.context.context.value, "reachable", "False") != True: 604 | break 605 | 606 | dashboard_data = match.context.value 607 | #print(dashboard_data[sensor]) 608 | values.append(dashboard_data[sensor]) 609 | value = "" 610 | if values != []: 611 | if function_sensor["function"] == "min": 612 | value = min(values) 613 | elif function_sensor["function"] == "max": 614 | value = max(values) 615 | elif function_sensor["function"] == "avg": 616 | value = Average(values) 617 | 618 | #print(value) 619 | suffix = "" 620 | if get_dict_value(function_sensor, "suffix", "") != "": 621 | suffix = f"_function_sensor['suffix']" 622 | hass_sensor = f"nfws_{function_sensor['function']}_{sensor}{suffix}" 623 | hass_sensor_value = {} 624 | hass_sensor_value["value"] = value 625 | hass_publish_calculated_station_sensor(hass_sensor, sensor, hass_sensor_value) 626 | #print(f"{hass_sensor}: {value}") 627 | 628 | 629 | def netatmo_handle_calculated_sensors_function_first(function_sensor): 630 | if "sensors" not in function_sensor: 631 | return 632 | if "stations" not in function_sensor: 633 | return 634 | #print(function_sensor) 635 | first_sensor = next(iter(function_sensor["sensors"])) 636 | #print(first_sensor) 637 | 638 | found = False 639 | for station_id in function_sensor["stations"]: 640 | #print(station_id) 641 | station = parse(f"$.devices[?(@._id == '{station_id}')].modules[*].dashboard_data.{first_sensor}") 642 | 643 | for match in station.find(json_netatmo_body): 644 | station_name = f"{match.context.context.context.context.value['station_name']} in {match.context.context.context.context.value['place']['city']}" 645 | if get_dict_value(match.context.context.value, "reachable", "False") != True: 646 | debug_log(f"{station_name}: is not reachable") 647 | break 648 | if int(get_dict_value(match.context.value, "WindAngle", "500")) < 0: 649 | debug_log(f"{station_name}: WindAngle is negative") 650 | break 651 | timestampdelta = datetime.timestamp(datetime.now(timezone.utc))-int(get_dict_value(match.context.value, 'time_utc', '500')) 652 | if timestampdelta>=60*int(get_dict_value(function_sensor, "timeDelta", "30")): 653 | debug_log(f"{station_name}: last update too long") 654 | break 655 | 656 | found = True 657 | dashboard_data = match.context.value 658 | #print(dashboard_data) 659 | 660 | if first_sensor.lower()[:4] in {"wind", "gust"}: 661 | dashboard_data["WindAngleCompass"] = degToCompass(dashboard_data["WindAngle"]) 662 | dashboard_data["WindAngleCompassSymbol"] = degToCompassSymbol(dashboard_data["WindAngle"]) 663 | dashboard_data["GustAngleCompass"] = degToCompass(dashboard_data["GustAngle"]) 664 | dashboard_data["GustAngleCompassSymbol"] = degToCompassSymbol(dashboard_data["GustAngle"]) 665 | 666 | suffix = "" 667 | if get_dict_value(function_sensor, "suffix", "") != "": 668 | suffix = f"_function_sensor['suffix']" 669 | hass_sensor = f"nfws_{function_sensor['function']}_station_name{suffix}" 670 | #station_name = f"{match.context.context.context.context.value['station_name']} in {match.context.context.context.context.value['place']['city']}" 671 | 672 | for sensor in function_sensor["sensors"]: 673 | hass_sensor = f"nfws_{function_sensor['function']}_{sensor}{suffix}" 674 | if sensor in dashboard_data: 675 | hass_sensor_value = {} 676 | hass_sensor_value["value"] = f"{dashboard_data[sensor]}" 677 | hass_sensor_value["station_name"] = station_name 678 | hass_publish_calculated_station_sensor(hass_sensor, sensor, hass_sensor_value) 679 | #print(f"{hass_sensor}: {hass_sensor_value}") 680 | break 681 | if found == True: 682 | break 683 | debug_log(f"{station_id}: not found in response") 684 | 685 | def netatmo_handle_calculated_sensors(): 686 | if "calculated_sensors" not in config: 687 | return 688 | 689 | for function_sensor in config["calculated_sensors"]: 690 | if function_sensor["function"] == "first": 691 | netatmo_handle_calculated_sensors_function_first(function_sensor) 692 | if function_sensor["function"] in {"min", "max", "avg"}: 693 | netatmo_handle_calculated_sensors_function_minmaxavg(function_sensor) 694 | 695 | def hass_mqtt_delete_retain_messages(): 696 | def on_message(client, userdata, msg): 697 | if msg.retain == 1: 698 | if get_dict_value(config["nfws"], "log_level") == "debug": 699 | logger.info(f"Deleting retain topic {msg.topic}") 700 | hass_mqtt_publish(msg.topic, "", 0, True) 701 | # hass_mqtt_publish("homeassistant/sensor/nfws/test", "", 0, True) 702 | global mqtt_client 703 | 704 | if run_mode == "hass": 705 | return 706 | if get_dict_value(config["nfws"], "deleteRetain", False) != True: 707 | return 708 | logger.info(snow() + "Deleting retain config messages") 709 | mqtt_client.subscribe("homeassistant/sensor/nfws/#") 710 | mqtt_client.on_message = on_message 711 | time.sleep(5) 712 | mqtt_client.unsubscribe("homeassistant/sensor/nfws/#") 713 | 714 | 715 | logging.basicConfig(format='%(message)s') #DEBUG, INFO, WARNING, ERROR, CRITICAL 716 | logger = logging.getLogger('nfws') 717 | logger.setLevel('DEBUG') 718 | logger.info("-------------------------------------------------------------------------------------------------------------") 719 | logger.info(snow() + "Starting Netatmo service") 720 | 721 | load_config() 722 | load_netatmo_token() 723 | netatmo_check_oauth_code() 724 | 725 | mqtt_connect() 726 | netatmo_get_oauth_token() 727 | hass_mqtt_delete_retain_messages() 728 | mqtt_disconnect() 729 | 730 | while True: 731 | logger.debug(snow() + "get data") 732 | 733 | mqtt_connect() 734 | 735 | json_netatmo = netatmo_getdata() 736 | json_netatmo_body = json_netatmo["body"] 737 | #print(json_netatmo_body) 738 | json_netatmo_devices = json_netatmo_body["devices"] 739 | #print(json_netatmo_devices) 740 | 741 | netatmo_handle_favourite_stations_sensors() 742 | netatmo_handle_calculated_sensors() 743 | 744 | mqtt_disconnect() 745 | time.sleep(60*get_dict_value(config["netatmo"], "refresh_interval", 1)) 746 | -------------------------------------------------------------------------------- /wattsonic/wattsonic.yaml: -------------------------------------------------------------------------------- 1 | modbus: 2 | - name: wattsonic 3 | # type: tcp 4 | # host: 192.168.11.104 5 | # port: 502 6 | type: serial 7 | baudrate: 9600 8 | bytesize: 8 9 | method: rtu 10 | parity: N 11 | port: /dev/ttyUSB1 12 | stopbits: 1 13 | switches: 14 | - name: Wattsonic Grid Injection Power Limit Switch #0:Off;1:ON 15 | unique_id: wattsonic_grid_injection_power_limit_switch #RW 16 | slave: 247 17 | address: 25100 18 | write_type: holding 19 | scan_interval: 360 20 | command_on: 1 21 | command_off: 0 22 | verify: 23 | input_type: holding 24 | address: 25100 25 | state_on: 1 26 | state_off: 0 27 | - name: Wattsonic Off-grid Battery SOC Protection Switch #0:Off;1:ON 28 | unique_id: wattsonic_off_grid_battery_soc_protection_switch #RW 29 | slave: 247 30 | address: 52504 31 | write_type: holding 32 | scan_interval: 360 33 | command_on: 1 34 | command_off: 0 35 | verify: 36 | input_type: holding 37 | address: 52504 38 | state_on: 1 39 | state_off: 0 40 | - name: Wattsonic On-grid Battery SOC Protection Switch #0:Off;1:ON 41 | unique_id: wattsonic_on_grid_battery_soc_protection_switch #RW 42 | slave: 247 43 | address: 52502 44 | write_type: holding 45 | scan_interval: 360 46 | command_on: 1 47 | command_off: 0 48 | verify: 49 | input_type: holding 50 | address: 52502 51 | state_on: 1 52 | state_off: 0 53 | - name: Wattsonic Priority Power Output Setting #0:PV Output Priority ; 1:Battery Output Priority 54 | unique_id: wattsonic_priority_power_output_setting #RW 55 | slave: 247 56 | address: 50210 57 | write_type: holding 58 | scan_interval: 360 59 | command_on: 1 60 | command_off: 0 61 | verify: 62 | input_type: holding 63 | address: 50210 64 | state_on: 1 65 | state_off: 0 66 | 67 | 68 | sensors: 69 | - name: Wattsonic Inverter SN 70 | unique_id: wattsonic_inverter_sn 71 | slave: 247 72 | address: 10000 73 | input_type: holding 74 | count: 10 75 | data_type: string 76 | scan_interval: 86400 77 | - name: Wattsonic Firmware Version1 78 | unique_id: wattsonic_firmware_version1 79 | slave: 247 80 | address: 10011 81 | input_type: holding 82 | count: 2 83 | data_type: custom 84 | structure: ">BBBB" 85 | precision: 0 86 | scan_interval: 600 87 | - name: Wattsonic Firmware Version2 88 | unique_id: wattsonic_firmware_version2 89 | slave: 247 90 | address: 10013 91 | input_type: holding 92 | count: 2 93 | data_type: custom 94 | structure: ">BBBB" 95 | scan_interval: 600 96 | precision: 0 97 | - name: Wattsonic DateTime 98 | unique_id: wattsonic_datetime 99 | slave: 247 100 | address: 10100 101 | input_type: holding 102 | count: 3 103 | data_type: custom 104 | structure: ">BBBBBB" 105 | scan_interval: 300 106 | precision: 0 107 | # - name: Wattsonic DateTime2 108 | # unique_id: wattsonic_datetime2 109 | # slave: 247 110 | # address: 20000 111 | # input_type: holding 112 | # count: 3 113 | # data_type: custom 114 | # structure: ">BBBBBB" 115 | # scan_interval: 300 116 | # precision: 0 117 | - name: Wattsonic Inverter Running Status #0:wait, wait for on-grid 1:check, self-check 2:On Grid 3:fault 4:flash, firmware update 5.Off Grid 118 | unique_id: wattsonic_inverter_running_status 119 | slave: 247 120 | address: 10105 121 | input_type: holding 122 | data_type: int16 123 | scan_interval: 10 124 | - name: Wattsonic Phase A Power on Meter 125 | unique_id: wattsonic_phase_a_power_on_meter 126 | slave: 247 127 | address: 10994 128 | input_type: holding 129 | state_class: measurement 130 | unit_of_measurement: kW 131 | device_class: power 132 | scale: 0.001 133 | precision: 3 134 | data_type: int32 135 | scan_interval: 10 136 | - name: Wattsonic Phase B Power on Meter 137 | unique_id: wattsonic_phase_b_power_on_meter 138 | slave: 247 139 | address: 10996 140 | input_type: holding 141 | state_class: measurement 142 | unit_of_measurement: kW 143 | device_class: power 144 | scale: 0.001 145 | precision: 3 146 | data_type: int32 147 | scan_interval: 10 148 | - name: Wattsonic Phase C Power on Meter 149 | unique_id: wattsonic_phase_c_power_on_meter 150 | slave: 247 151 | address: 10998 152 | input_type: holding 153 | state_class: measurement 154 | unit_of_measurement: kW 155 | device_class: power 156 | scale: 0.001 157 | precision: 3 158 | data_type: int32 159 | scan_interval: 10 160 | - name: Wattsonic Total Power on Meter #Grid power 161 | unique_id: wattsonic_total_power_on_meter 162 | slave: 247 163 | address: 11000 164 | input_type: holding 165 | state_class: measurement 166 | unit_of_measurement: kW 167 | device_class: power 168 | scale: 0.001 169 | precision: 3 170 | data_type: int32 171 | scan_interval: 10 172 | 173 | - name: Wattsonic Grid Injection Energy on that day #export/day 174 | unique_id: wattsonic_grid_injection_energy_on_that_day 175 | slave: 247 176 | address: 31000 177 | scale: 0.1 178 | precision: 2 179 | input_type: holding 180 | unit_of_measurement: kWh 181 | state_class: total_increasing 182 | device_class: energy 183 | data_type: int16 184 | scan_interval: 60 185 | - name: Wattsonic Grid Purchasing Energy on that day 186 | unique_id: wattsonic_grid_purchasing_energy_on_that_day 187 | slave: 247 188 | address: 31001 189 | scale: 0.1 190 | precision: 1 191 | input_type: holding 192 | unit_of_measurement: kWh 193 | state_class: total_increasing 194 | device_class: energy 195 | data_type: int16 196 | scan_interval: 60 197 | - name: Wattsonic Backup Output Energy on that day 198 | unique_id: wattsonic_backup_output_energy_on_that_day 199 | slave: 247 200 | address: 31002 201 | scale: 0.1 202 | precision: 1 203 | input_type: holding 204 | unit_of_measurement: kWh 205 | state_class: total_increasing 206 | device_class: energy 207 | data_type: int16 208 | scan_interval: 300 209 | - name: Wattsonic Battery Charge Energy on that day 210 | unique_id: wattsonic_battery_charge_energy_on_that_day 211 | slave: 247 212 | address: 31003 213 | scale: 0.1 214 | precision: 1 215 | input_type: holding 216 | unit_of_measurement: kWh 217 | state_class: total_increasing 218 | device_class: energy 219 | data_type: int16 220 | scan_interval: 60 221 | - name: Wattsonic Battery Discharge Energy on that day 222 | unique_id: wattsonic_battery_discharge_energy_on_that_day 223 | slave: 247 224 | address: 31004 225 | scale: 0.1 226 | precision: 1 227 | input_type: holding 228 | unit_of_measurement: kWh 229 | state_class: total_increasing 230 | device_class: energy 231 | data_type: int16 232 | scan_interval: 60 233 | - name: Wattsonic PV Generation Energy on that day #E-today from GUI 234 | unique_id: wattsonic_pv_generation_energy_on_that_day 235 | slave: 247 236 | address: 31005 237 | scale: 0.1 238 | precision: 1 239 | input_type: holding 240 | unit_of_measurement: kWh 241 | state_class: total_increasing 242 | device_class: energy 243 | data_type: int16 244 | scan_interval: 60 245 | - name: Wattsonic Loading Energy on that day 246 | unique_id: wattsonic_loading_energy_on_that_day 247 | slave: 247 248 | address: 31006 249 | scale: 0.1 250 | precision: 1 251 | input_type: holding 252 | unit_of_measurement: kWh 253 | state_class: total_increasing 254 | device_class: energy 255 | data_type: int16 256 | scan_interval: 300 257 | - name: Wattsonic Energy Purchased from Grid on that day #on inverter side 258 | unique_id: wattsonic_energy_purchased_from_grid_on_that_day 259 | slave: 247 260 | address: 31008 261 | scale: 0.1 262 | precision: 1 263 | input_type: holding 264 | unit_of_measurement: kWh 265 | state_class: total_increasing 266 | device_class: energy 267 | data_type: int16 268 | scan_interval: 300 269 | - name: Wattsonic Total Energy injected to grid 270 | unique_id: wattsonic_total_energy_injected_to_grid 271 | slave: 247 272 | address: 31102 273 | scale: 0.1 274 | precision: 1 275 | input_type: holding 276 | unit_of_measurement: kWh 277 | state_class: total_increasing 278 | device_class: energy 279 | data_type: int32 280 | scan_interval: 600 281 | - name: Wattsonic Total Energy Purchased from Grid from Meter 282 | unique_id: wattsonic_total_energy_purchased_from_grid_from_meter 283 | slave: 247 284 | address: 31104 285 | scale: 0.1 286 | precision: 1 287 | input_type: holding 288 | unit_of_measurement: kWh 289 | state_class: total_increasing 290 | device_class: energy 291 | data_type: int32 292 | scan_interval: 600 293 | - name: Wattsonic Total Output Energy on backup port 294 | unique_id: wattsonic_total_output_energy_on_backup_port 295 | slave: 247 296 | address: 31106 297 | scale: 0.1 298 | precision: 1 299 | input_type: holding 300 | unit_of_measurement: kWh 301 | state_class: total_increasing 302 | device_class: energy 303 | data_type: int32 304 | scan_interval: 3600 305 | - name: Wattsonic Total Energy Charged to Battery 306 | unique_id: wattsonic_total_energy_charged_to_battery 307 | slave: 247 308 | address: 31108 309 | scale: 0.1 310 | precision: 1 311 | input_type: holding 312 | unit_of_measurement: kWh 313 | state_class: total_increasing 314 | device_class: energy 315 | data_type: int32 316 | scan_interval: 600 317 | - name: Wattsonic Total Energy Discharged from Battery 318 | unique_id: wattsonic_total_energy_discharged_from_battery 319 | slave: 247 320 | address: 31110 321 | scale: 0.1 322 | precision: 1 323 | input_type: holding 324 | unit_of_measurement: kWh 325 | state_class: total_increasing 326 | device_class: energy 327 | data_type: int32 328 | scan_interval: 600 329 | - name: Wattsonic Total PV Generation 330 | unique_id: wattsonic_total_pv_generation 331 | slave: 247 332 | address: 31112 333 | scale: 0.1 334 | precision: 1 335 | input_type: holding 336 | unit_of_measurement: kWh 337 | state_class: total_increasing 338 | device_class: energy 339 | data_type: int32 340 | scan_interval: 600 341 | - name: Wattsonic Total Loading Energy consumed at grid side #totoal load consumption 342 | unique_id: wattsonic_total_loading_energy_consumed_at_grid_side 343 | slave: 247 344 | address: 31114 345 | scale: 0.1 346 | precision: 1 347 | input_type: holding 348 | unit_of_measurement: kWh 349 | state_class: total_increasing 350 | device_class: energy 351 | data_type: int32 352 | scan_interval: 3600 353 | - name: Wattsonic Total Energy Purchased from Grid at inverter side 354 | unique_id: wattsonic_total_energy_purchased_from_grid_at_inverter_side 355 | slave: 247 356 | address: 31118 357 | scale: 0.1 358 | precision: 1 359 | input_type: holding 360 | unit_of_measurement: kWh 361 | state_class: total_increasing 362 | device_class: energy 363 | data_type: int32 364 | scan_interval: 3600 365 | 366 | - name: Wattsonic Total Grid-Injection Energy on Meter 367 | unique_id: wattsonic_total_grid_injection_energy_on_meter 368 | slave: 247 369 | address: 11002 370 | input_type: holding 371 | state_class: total_increasing 372 | unit_of_measurement: kWh 373 | device_class: energy 374 | scale: 0.01 375 | precision: 2 376 | data_type: int32 377 | scan_interval: 3600 378 | - name: Wattsonic Total Purchasing Energy from Grid on Meter 379 | unique_id: wattsonic_total_purchasing_energy_from_grid_on_meter 380 | slave: 247 381 | address: 11004 382 | input_type: holding 383 | state_class: total_increasing 384 | unit_of_measurement: kWh 385 | device_class: energy 386 | scale: 0.01 387 | precision: 2 388 | data_type: int32 389 | scan_interval: 3600 390 | 391 | - name: Wattsonic Grid Lines A/B Voltage 392 | unique_id: wattsonic_grid_lines_a_b_voltage 393 | slave: 247 394 | address: 11006 395 | input_type: holding 396 | unit_of_measurement: V 397 | state_class: measurement 398 | device_class: voltage 399 | scale: 0.1 400 | precision: 1 401 | data_type: int16 402 | scan_interval: 60 403 | - name: Wattsonic Grid Lines B/C Voltage 404 | unique_id: wattsonic_grid_lines_b_c_voltage 405 | slave: 247 406 | address: 11007 407 | input_type: holding 408 | unit_of_measurement: V 409 | state_class: measurement 410 | device_class: voltage 411 | scale: 0.1 412 | precision: 1 413 | data_type: int16 414 | scan_interval: 60 415 | - name: Wattsonic Grid Lines C/A Voltage 416 | unique_id: wattsonic_grid_lines_c_a_voltage 417 | slave: 247 418 | address: 11008 419 | input_type: holding 420 | unit_of_measurement: V 421 | state_class: measurement 422 | device_class: voltage 423 | scale: 0.1 424 | precision: 1 425 | data_type: int16 426 | scan_interval: 60 427 | - name: Wattsonic Grid Phase A Voltage 428 | unique_id: wattsonic_grid_phase_a_voltage 429 | slave: 247 430 | address: 11009 431 | input_type: holding 432 | unit_of_measurement: V 433 | state_class: measurement 434 | device_class: voltage 435 | scale: 0.1 436 | precision: 1 437 | data_type: int16 438 | scan_interval: 60 439 | - name: Wattsonic Grid Phase B Voltage 440 | unique_id: wattsonic_grid_phase_b_voltage 441 | slave: 247 442 | address: 11011 443 | input_type: holding 444 | unit_of_measurement: V 445 | state_class: measurement 446 | device_class: voltage 447 | scale: 0.1 448 | precision: 1 449 | data_type: int16 450 | scan_interval: 60 451 | - name: Wattsonic Grid Phase C Voltage 452 | unique_id: wattsonic_grid_pase_c_voltage 453 | slave: 247 454 | address: 11013 455 | input_type: holding 456 | unit_of_measurement: V 457 | state_class: measurement 458 | device_class: voltage 459 | scale: 0.1 460 | precision: 1 461 | data_type: int16 462 | scan_interval: 60 463 | - name: Wattsonic Grid Phase A Current 464 | unique_id: wattsonic_grid_phase_a_current 465 | slave: 247 466 | address: 11010 467 | input_type: holding 468 | state_class: measurement 469 | unit_of_measurement: A 470 | device_class: current 471 | scale: 0.1 472 | precision: 1 473 | data_type: int16 474 | scan_interval: 60 475 | - name: Wattsonic Grid Phase B Current 476 | unique_id: wattsonic_grid_phase_b_current 477 | slave: 247 478 | address: 11012 479 | input_type: holding 480 | state_class: measurement 481 | unit_of_measurement: A 482 | device_class: current 483 | scale: 0.1 484 | precision: 1 485 | data_type: int16 486 | scan_interval: 60 487 | - name: Wattsonic Grid Phase C Current 488 | unique_id: wattsonic_grid_phase_c_current 489 | slave: 247 490 | address: 11014 491 | input_type: holding 492 | state_class: measurement 493 | unit_of_measurement: A 494 | device_class: current 495 | scale: 0.1 496 | precision: 1 497 | data_type: int16 498 | scan_interval: 60 499 | - name: Wattsonic P AC 500 | unique_id: wattsonic_p_ac 501 | slave: 247 502 | address: 11016 503 | input_type: holding 504 | state_class: measurement 505 | unit_of_measurement: kW 506 | device_class: power 507 | scale: 0.001 508 | precision: 3 509 | data_type: int32 510 | scan_interval: 10 511 | - name: Wattsonic Total PV Generation on that day #energy today 512 | unique_id: wattsonic_total_pv_generation_on_that_day 513 | slave: 247 514 | address: 11018 515 | input_type: holding 516 | state_class: total_increasing 517 | device_class: energy 518 | unit_of_measurement: kWh 519 | scale: 0.1 520 | precision: 1 521 | data_type: int32 522 | scan_interval: 1800 523 | - name: Wattsonic Total PV Generation from Installation #energy total 524 | unique_id: wattsonic_total_pv_generation_from_installation 525 | slave: 247 526 | address: 11020 527 | input_type: holding 528 | state_class: total_increasing 529 | device_class: energy 530 | unit_of_measurement: kWh 531 | scale: 0.1 532 | precision: 1 533 | data_type: int32 534 | scan_interval: 3600 535 | - name: Wattsonic PV Input Total Power #PV power, panel actual power 536 | unique_id: wattsonic_pv_input_total_power 537 | slave: 247 538 | address: 11028 539 | input_type: holding 540 | state_class: measurement 541 | device_class: power 542 | unit_of_measurement: kW 543 | scale: 0.001 544 | precision: 3 545 | data_type: int32 546 | scan_interval: 10 547 | - name: Wattsonic Temperature Sensor 1 548 | unique_id: wattsonic_temperature_sensor_1 549 | slave: 247 550 | address: 11032 551 | input_type: holding 552 | state_class: measurement 553 | unit_of_measurement: °C 554 | device_class: temperature 555 | scale: 0.1 556 | precision: 1 557 | data_type: int16 558 | scan_interval: 300 559 | - name: Wattsonic Temperature Sensor 2 560 | unique_id: wattsonic_temperature_sensor_2 561 | slave: 247 562 | address: 11033 563 | input_type: holding 564 | state_class: measurement 565 | unit_of_measurement: °C 566 | device_class: temperature 567 | scale: 0.1 568 | precision: 1 569 | data_type: int16 570 | scan_interval: 600 571 | - name: Wattsonic Temperature Sensor 3 572 | unique_id: wattsonic_temperature_sensor_3 573 | slave: 247 574 | address: 11034 575 | input_type: holding 576 | state_class: measurement 577 | unit_of_measurement: °C 578 | device_class: temperature 579 | scale: 0.1 580 | precision: 1 581 | data_type: int16 582 | scan_interval: 600 583 | - name: Wattsonic Temperature Sensor 4 584 | unique_id: wattsonic_temperature_sensor_4 585 | slave: 247 586 | address: 11035 587 | input_type: holding 588 | state_class: measurement 589 | unit_of_measurement: °C 590 | device_class: temperature 591 | scale: 0.1 592 | precision: 1 593 | data_type: int16 594 | scan_interval: 600 595 | - name: Wattsonic PV1 Voltage 596 | unique_id: wattsonic_pv1_voltage 597 | slave: 247 598 | address: 11038 599 | input_type: holding 600 | unit_of_measurement: V 601 | state_class: measurement 602 | device_class: voltage 603 | scale: 0.1 604 | precision: 1 605 | data_type: int16 606 | scan_interval: 300 607 | - name: Wattsonic PV2 Voltage 608 | unique_id: wattsonic_pv2_voltage 609 | slave: 247 610 | address: 11040 611 | input_type: holding 612 | unit_of_measurement: V 613 | state_class: measurement 614 | device_class: voltage 615 | scale: 0.1 616 | precision: 1 617 | data_type: int16 618 | scan_interval: 300 619 | - name: Wattsonic PV1 Current 620 | unique_id: wattsonic_pv1_current 621 | slave: 247 622 | address: 11039 623 | input_type: holding 624 | state_class: measurement 625 | unit_of_measurement: A 626 | device_class: current 627 | scale: 0.1 628 | precision: 1 629 | data_type: int16 630 | scan_interval: 300 631 | - name: Wattsonic PV2 Current 632 | unique_id: wattsonic_pv2_current 633 | slave: 247 634 | address: 11041 635 | input_type: holding 636 | state_class: measurement 637 | unit_of_measurement: A 638 | device_class: current 639 | scale: 0.1 640 | precision: 1 641 | data_type: int16 642 | scan_interval: 300 643 | - name: Wattsonic PV1 Input Power 644 | unique_id: wattsonic_pv1_input_power 645 | slave: 247 646 | address: 11062 647 | input_type: holding 648 | state_class: measurement 649 | unit_of_measurement: kW 650 | device_class: power 651 | scale: 0.001 652 | precision: 3 653 | data_type: int32 654 | scan_interval: 10 655 | - name: Wattsonic PV2 Input Power 656 | unique_id: wattsonic_pv2_input_power 657 | slave: 247 658 | address: 11064 659 | input_type: holding 660 | state_class: measurement 661 | unit_of_measurement: kW 662 | device_class: power 663 | scale: 0.001 664 | precision: 3 665 | data_type: int32 666 | scan_interval: 10 667 | - name: Wattsonic ARM Fault Flag1 #Table 3.3 Troubleshooting 668 | unique_id: wattsonic_arm_fault_flag1 669 | slave: 247 670 | address: 18000 671 | input_type: holding 672 | data_type: int32 673 | scan_interval: 60 674 | - name: Wattsonic Fault Flag1 #Table 3.3 Troubleshooting 675 | unique_id: wattsonic_fault_flag1 676 | slave: 247 677 | address: 10112 678 | input_type: holding 679 | data_type: int32 680 | scan_interval: 60 681 | - name: Wattsonic Fault Flag2 #Table 3.3 Troubleshooting 682 | unique_id: wattsonic_fault_flag2 683 | slave: 247 684 | address: 10114 685 | input_type: holding 686 | data_type: int32 687 | scan_interval: 60 688 | - name: Wattsonic Fault Flag3 #Table 3.3 Troubleshooting 689 | unique_id: wattsonic_fault_flag3 690 | slave: 247 691 | address: 10116 692 | input_type: holding 693 | data_type: int32 694 | scan_interval: 60 695 | 696 | - name: Wattsonic Total Backup P #load power, total active power 697 | unique_id: wattsonic_total_backup_p 698 | slave: 247 699 | address: 30230 700 | input_type: holding 701 | state_class: measurement 702 | unit_of_measurement: kW 703 | device_class: power 704 | scale: 0.001 705 | precision: 3 706 | data_type: int32 707 | scan_interval: 10 708 | 709 | - name: Wattsonic Battery V #DC Voltage 710 | unique_id: wattsonic_battery_v 711 | slave: 247 712 | address: 30254 713 | input_type: holding 714 | unit_of_measurement: V 715 | state_class: measurement 716 | device_class: voltage 717 | scale: 0.1 718 | precision: 1 719 | data_type: int16 720 | scan_interval: 300 721 | - name: Wattsonic Battery I #DC Current 722 | unique_id: wattsonic_battery_i 723 | slave: 247 724 | address: 30255 725 | input_type: holding 726 | state_class: measurement 727 | unit_of_measurement: A 728 | device_class: current 729 | scale: 0.1 730 | precision: 1 731 | data_type: int16 732 | scan_interval: 300 733 | - name: Wattsonic Battery P #Battery Power 734 | unique_id: wattsonic_battery_p 735 | slave: 247 736 | address: 30258 737 | input_type: holding 738 | state_class: measurement 739 | unit_of_measurement: kW 740 | device_class: power 741 | scale: 0.001 742 | precision: 3 743 | data_type: int32 744 | scan_interval: 10 745 | - name: Wattsonic Battery Mode #0:discharge;1:charge 746 | unique_id: wattsonic_battery_mode 747 | slave: 247 748 | address: 30256 749 | input_type: holding 750 | data_type: int16 751 | scan_interval: 10 752 | #BMS registers 753 | - name: Wattsonic BMS Software Version 754 | unique_id: wattsonic_bms_software_version 755 | slave: 247 756 | address: 32003 757 | input_type: holding 758 | data_type: int16 759 | scan_interval: 600 760 | - name: Wattsonic BMS Hardware Version 761 | unique_id: wattsonic_bms_hardware_version 762 | slave: 247 763 | address: 32004 764 | input_type: holding 765 | data_type: int16 766 | scan_interval: 600 767 | - name: Wattsonic Battery SOC 768 | unique_id: wattsonic_battery_soc 769 | slave: 247 770 | address: 33000 771 | input_type: holding 772 | state_class: measurement 773 | unit_of_measurement: '%' 774 | device_class: battery 775 | scale: 0.01 776 | precision: 2 777 | data_type: int16 778 | scan_interval: 60 779 | - name: Wattsonic Battery SOH 780 | unique_id: wattsonic_battery_soh 781 | slave: 247 782 | address: 33001 783 | input_type: holding 784 | state_class: measurement 785 | unit_of_measurement: '%' 786 | device_class: battery 787 | scale: 0.01 788 | precision: 2 789 | data_type: int16 790 | scan_interval: 3600 791 | - name: Wattsonic BMS Status 792 | unique_id: wattsonic_bms_status 793 | slave: 247 794 | address: 33002 795 | input_type: holding 796 | data_type: int16 797 | scan_interval: 60 798 | - name: Wattsonic BMS Pack Temperature 799 | unique_id: wattsonic_bms_pack_temperature 800 | slave: 247 801 | address: 33003 802 | input_type: holding 803 | state_class: measurement 804 | unit_of_measurement: °C 805 | device_class: temperature 806 | scale: 0.1 807 | precision: 1 808 | data_type: int16 809 | scan_interval: 300 810 | - name: Wattsonic Max Cell Voltage 811 | unique_id: wattsonic_max_cell_voltage 812 | slave: 247 813 | address: 33013 814 | input_type: holding 815 | unit_of_measurement: V 816 | state_class: measurement 817 | device_class: voltage 818 | scale: 0.001 819 | precision: 3 820 | data_type: int16 821 | scan_interval: 300 822 | - name: Wattsonic Min Cell Voltage 823 | unique_id: wattsonic_min_cell_voltage 824 | slave: 247 825 | address: 33015 826 | input_type: holding 827 | unit_of_measurement: V 828 | state_class: measurement 829 | device_class: voltage 830 | scale: 0.001 831 | precision: 3 832 | data_type: int16 833 | scan_interval: 300 834 | - name: Wattsonic BMS Error Code 835 | unique_id: wattsonic_bms_error_code 836 | slave: 247 837 | address: 33016 838 | input_type: holding 839 | data_type: int32 840 | scan_interval: 60 841 | - name: Wattsonic BMS Warn Code 842 | unique_id: wattsonic_bms_warn_code 843 | slave: 247 844 | address: 33018 845 | input_type: holding 846 | data_type: int32 847 | scan_interval: 60 848 | 849 | #RW values 850 | - name: Wattsonic Hybrid Inverter Working Mode Setting #257=General mode; 258=Economic mode; 259=UPS mode; 512=Off grid mode 851 | unique_id: wattsonic_hybrid_inverter_working_mode_setting #RW 852 | slave: 247 853 | address: 50000 854 | input_type: holding 855 | data_type: int16 856 | scan_interval: 60 857 | 858 | - name: Wattsonic Grid Injection Power Limit Setting #[0.0%-100.0%] 859 | unique_id: wattsonic_grid_injection_power_limit_setting #RW 860 | slave: 247 861 | address: 25103 862 | input_type: holding 863 | scale: 0.1 #spec: 1000 864 | precision: 1 865 | data_type: int16 866 | scan_interval: 110 867 | - name: Wattsonic On-grid Battery End SOC #[0.0%-100.0%] 868 | unique_id: wattsonic_on_grid_battery_end_soc #RW 869 | slave: 247 870 | address: 52503 871 | input_type: holding 872 | scale: 0.1 #spec: 1000 873 | precision: 1 874 | data_type: int16 875 | scan_interval: 360 876 | - name: Wattsonic Off-grid Battery End SOC #[0.0%-100.0%] 877 | unique_id: wattsonic_off_grid_battery_end_soc #RW 878 | slave: 247 879 | address: 52505 880 | input_type: holding 881 | scale: 0.1 #spec: 1000 882 | precision: 1 883 | data_type: int16 884 | scan_interval: 360 885 | - name: Wattsonic Battery Power Setting 886 | unique_id: wattsonic_battery_power_setting 887 | slave: 247 888 | address: 50207 889 | input_type: holding 890 | unit_of_measurement: kW 891 | scale: 0.01 892 | precision: 1 893 | data_type: int16 894 | scan_interval: 60 895 | - name: Wattsonic Battery Max. AC Power Limit Setting 896 | unique_id: wattsonic_battery_power_max_up_limit 897 | slave: 247 898 | address: 50208 899 | input_type: holding 900 | scale: 0.01 #spec: 100 901 | precision: 1 902 | unit_of_measurement: kW 903 | data_type: int16 904 | scan_interval: 60 905 | - name: Wattsonic Battery Min. AC Power Limit Setting 906 | unique_id: wattsonic_battery_power_min_up_limit 907 | slave: 247 908 | address: 50209 909 | input_type: holding 910 | unit_of_measurement: kW 911 | scale: 0.01 912 | precision: 1 913 | data_type: int16 914 | scan_interval: 60 915 | - name: Wattsonic Battery Configuration # Table 3.7 916 | unique_id: wattsonic_battery_configuration 917 | slave: 247 918 | address: 52500 919 | input_type: holding 920 | data_type: int16 921 | scan_interval: 60 922 | - name: Wattsonic RW BMS Status #Table 3.8 923 | unique_id: wattsonic_rw_bms_status 924 | slave: 247 925 | address: 53508 926 | input_type: holding 927 | data_type: int16 928 | scan_interval: 60 929 | - name: Wattsonic RW BMS ErrorCode #Table 3.9 930 | unique_id: wattsonic_rw_bms_errorcode 931 | slave: 247 932 | address: 53509 933 | input_type: holding 934 | data_type: int32 935 | scan_interval: 60 936 | - name: Wattsonic RW BMS ProtectionCode #Table 3.9 937 | unique_id: wattsonic_rw_bms_protectioncode 938 | slave: 247 939 | address: 53511 940 | input_type: holding 941 | data_type: int32 942 | scan_interval: 60 943 | - name: Wattsonic RW BMS WarnCode #Table 3.9 944 | unique_id: wattsonic_rw_bms_warncode 945 | slave: 247 946 | address: 53513 947 | input_type: holding 948 | data_type: int32 949 | scan_interval: 60 950 | 951 | - name: Wattsonic Ecomode Period Enable Flag #bit0- bit5 stands for period1-period6, bit7-bit15 reserved; 0: disable 1: enable 952 | #1=one line; #3=2 lines; 7=4lines; 15=5lines; 31=6lines 953 | unique_id: wattsonic_ecomode_period_enable_flag 954 | slave: 247 955 | address: 53006 956 | input_type: holding 957 | data_type: int16 958 | scan_interval: 60 959 | - name: Wattsonic Ecomode Period1 Struct 960 | unique_id: wattsonic_ecomode_period1_struct 961 | slave: 247 962 | address: 53007 963 | input_type: holding 964 | count: 7 965 | data_type: custom 966 | structure: ">HHHHHHH" 967 | scan_interval: 60 968 | - name: Wattsonic Ecomode Period2 Struct 969 | unique_id: wattsonic_ecomode_period2_struct 970 | slave: 247 971 | address: 53014 972 | input_type: holding 973 | count: 7 974 | data_type: custom 975 | structure: ">HHHHHHH" 976 | scan_interval: 60 977 | #how to write data: 978 | #service: modbus.write_register 979 | #data: 980 | # hub: wattsonic 981 | # address: 53007 982 | # slave: 247 983 | # value: 984 | # - 2 985 | # - 65535 986 | # - 65535 987 | # - 800 988 | # - 65535 989 | # - 3 990 | # - 5 991 | #or value: [2,65535,65535,800,65535,3,5] 992 | 993 | template: 994 | - sensor: 995 | - default_entity_id: sensor.wattsonic_battery_configuration_text 996 | name: Wattsonic Battery Configuration 997 | unique_id: wattsonic_battery_configuration_text 998 | state: > 999 | {% if states('sensor.wattsonic_battery_configuration') == '1' %} 1000 | Solinteg 1001 | {% elif states('sensor.wattsonic_battery_configuration') == '2' %} 1002 | EMS 1003 | {% elif states('sensor.wattsonic_battery_configuration') == '10' %} 1004 | Wattsonic Li-HV 1005 | {% else %} 1006 | Reserved 1007 | {% endif %} 1008 | 1009 | - default_entity_id: sensor.wattsonic_battery_mode_text 1010 | name: Wattsonic Battery Mode 1011 | unique_id: wattsonic_battery_mode_text 1012 | state: > 1013 | {% if states('sensor.wattsonic_battery_mode') == '0' %} 1014 | discharge 1015 | {% else %} 1016 | charge 1017 | {% endif %} 1018 | - default_entity_id: sensor.wattsonic_hybrid_inverter_working_mode_setting_text 1019 | name: Wattsonic Hybrid Inverter Working Mode 1020 | state: > 1021 | {% if states('sensor.wattsonic_hybrid_inverter_working_mode_setting') == '257' %} 1022 | General mode 1023 | {% elif states('sensor.wattsonic_hybrid_inverter_working_mode_setting') == '258' %} 1024 | Economic mode 1025 | {% elif states('sensor.wattsonic_hybrid_inverter_working_mode_setting') == '259' %} 1026 | UPS mode 1027 | {% elif states('sensor.wattsonic_hybrid_inverter_working_mode_setting') == '512' %} 1028 | Offgrid mode 1029 | {% elif states('sensor.wattsonic_hybrid_inverter_working_mode_setting') == '769' %} 1030 | EMS_ACCtrlMode 1031 | {% elif states('sensor.wattsonic_hybrid_inverter_working_mode_setting') == '770' %} 1032 | EMS_GeneralMode 1033 | {% elif states('sensor.wattsonic_hybrid_inverter_working_mode_setting') == '771' %} 1034 | EMS_BattCtrlMode 1035 | {% endif %} 1036 | - default_entity_id: sensor.wattsonic_inverter_running_status_text 1037 | name: Wattsonic Inverter Running Status 1038 | state: > 1039 | {% if states('sensor.wattsonic_inverter_running_status') == '0' %} 1040 | wait 1041 | {% elif states('sensor.wattsonic_inverter_running_status') == '1' %} 1042 | check 1043 | {% elif states('sensor.wattsonic_inverter_running_status') == '2' %} 1044 | On Grid 1045 | {% elif states('sensor.wattsonic_inverter_running_status') == '3' %} 1046 | fault 1047 | {% elif states('sensor.wattsonic_inverter_running_status') == '4' %} 1048 | flash 1049 | {% elif states('sensor.wattsonic_inverter_running_status') == '5' %} 1050 | Off Grid 1051 | {% endif %} 1052 | 1053 | - default_entity_id: sensor.wattsonic_home_consumption_now 1054 | name: Wattsonic Home Consumption Now 1055 | unique_id: wattsonic_home_consumption_now 1056 | unit_of_measurement: kW 1057 | device_class: power 1058 | state: > 1059 | {% set diff = (float(states("sensor.wattsonic_p_ac"), default=0) 1060 | - float(states("sensor.wattsonic_total_power_on_meter"), default=0)) | round(3) %} 1061 | {% if diff>0: %} 1062 | {{ diff }} 1063 | {% else %} 1064 | {{ (float(states("sensor.wattsonic_home_consumption_now"), default=0)) }} 1065 | {% endif %} 1066 | 1067 | - default_entity_id: sensor.wattsonic_home_consumption_today 1068 | name: Wattsonic Home Consumption Today 1069 | unique_id: wattsonic_home_consumption_today 1070 | unit_of_measurement: kWh 1071 | device_class: energy 1072 | state: '{{ float(states(''sensor.wattsonic_loading_energy_on_that_day''), default= float(states(''sensor.wattsonic_home_consumption_today''), default=-10)) 1073 | + float(states(''sensor.wattsonic_backup_output_energy_on_that_day''), default=0) }}' 1074 | - default_entity_id: sensor.wattsonic_home_consumption_today2 1075 | name: Wattsonic Home Consumption Today2 1076 | unique_id: wattsonic_home_consumption_today2 1077 | unit_of_measurement: kWh 1078 | device_class: energy 1079 | state: '{{ (float(states(''sensor.wattsonic_pv_generation_energy_on_that_day''), default= float(states(''sensor.wattsonic_home_consumption_today2''), default=-10)) 1080 | + float(states(''sensor.wattsonic_grid_purchasing_energy_on_that_day''), default=0) 1081 | - float(states(''sensor.wattsonic_grid_injection_energy_on_that_day''), default=0) 1082 | - float(states(''sensor.wattsonic_battery_charge_energy_on_that_day''), default=0) 1083 | + float(states(''sensor.wattsonic_battery_discharge_energy_on_that_day''), default=0)) | round(3) }}' 1084 | 1085 | - default_entity_id: sensor.wattsonic_total_power_on_meter_w 1086 | name: Wattsonic Total Power On Meter Watt 1087 | unique_id: wattsonic_total_power_on_meter_w 1088 | unit_of_measurement: W 1089 | device_class: power 1090 | state: '{{ (states(''sensor.wattsonic_total_power_on_meter'')|float(0)*-1000)|round(0) }}' 1091 | - default_entity_id: sensor.wattsonic_sunsync_grid_connected 1092 | name: Wattsonic Sunsync Grid Connected 1093 | unique_id: wattsonic_sunsync_grid_connected 1094 | state: > 1095 | {% if states('sensor.wattsonic_inverter_running_status') == '2' %} 1096 | 1 1097 | {% else %} 1098 | 0 1099 | {% endif %} 1100 | 1101 | #jeden riadok 1 - 2,65535,65535,860,65535,1,7; cas je 00:01-00:07 1102 | #druhy riadok 3 - 1,0,65535,235,65535,5915,5937; cas je 23:27-23:49; 23.5% 1103 | #druhy riadok 3 - 2,65535,65535,860,65535,0,256; cas je 00:00-01:00 1104 | #druhy riadok 3 - 2,65535,65535,860,65535,512,768; cas je 02:00-03:00 1105 | 1106 | - default_entity_id: sensor.wattsonic_ecomode_period1_chargedischarge_setting 1107 | name: Wattsonic Ecomode Period1 ChargeDischarge Setting #Period1: 0:NONE 1:charge 2:discharge 1108 | unique_id: wattsonic_ecomode_period1_chargedischarge_setting 1109 | state: '{{ states(''sensor.wattsonic_ecomode_period1_struct'').split('','')[0]|default(-1) }}' 1110 | - default_entity_id: sensor.wattsonic_ecomode_period1_battery_charge_by 1111 | name: Wattsonic Ecomode Period1 Battery Charge By #0:PV 1:PV+GRID 1112 | unique_id: wattsonic_ecomode_period1_battery_charge_by 1113 | state: '{{ states(''sensor.wattsonic_ecomode_period1_struct'').split('','')[1]|default(-1) }}' 1114 | - default_entity_id: sensor.wattsonic_ecomode_period1_power_limit 1115 | name: Wattsonic Ecomode Period1 Power Limit #[0.0-100.0%] 1116 | unique_id: wattsonic_ecomode_period1_power_limit 1117 | unit_of_measurement: '%' 1118 | state: '{{ states(''sensor.wattsonic_ecomode_period1_struct'').split('','')[3]|default(-10)|int(0)/10 }}' 1119 | - default_entity_id: sensor.wattsonic_ecomode_period1_start_time 1120 | name: Wattsonic Ecomode Period1 Start Time #High 8bits(Hour):[0,23] Low 8bits(Mins):[0,59] 1121 | unique_id: wattsonic_ecomode_period1_start_time 1122 | state: '{{ ((states(''sensor.wattsonic_ecomode_period1_struct'').split('','')[5]|default(0)|int(0)//256)|string).zfill(2) }}:{{ ((states(''sensor.wattsonic_ecomode_period1_struct'').split('','')[5]|default(0)|int%256)|string).zfill(2) }}' 1123 | - default_entity_id: sensor.wattsonic_ecomode_period1_end_time 1124 | name: Wattsonic Ecomode Period1 End Time #High 8bits(Hour):[0,23] Low 8bits(Mins):[0,59] 1125 | unique_id: wattsonic_ecomode_period1_end_time 1126 | state: '{{ ((states(''sensor.wattsonic_ecomode_period1_struct'').split('','')[6]|default(0)|int(0)//256)|string).zfill(2) }}:{{ ((states(''sensor.wattsonic_ecomode_period1_struct'').split('','')[6]|default(0)|int%256)|string).zfill(2) }}' 1127 | - default_entity_id: sensor.wattsonic_ecomode_period_enable_flag_bin 1128 | name: Wattsonic Ecomode Period Enable Flag Binary 1129 | unique_id: wattsonic_ecomode_period_enable_flag_bin 1130 | state: '{{ (''{:b}''.format(states(''sensor.wattsonic_ecomode_period_enable_flag'')|int(0))).ljust(6, ''0'') }}' 1131 | 1132 | - default_entity_id: sensor.wattsonic_datetime_dt 1133 | name: Wattsonic DateTime DT 1134 | unique_id: wattsonic_datetime_dt 1135 | state: '{{ strptime(states(''sensor.wattsonic_datetime''),"%y,%m,%d,%H,%M,%S") }}' 1136 | 1137 | 1138 | #Normal help 1139 | #PV power is sufficient, power from the PV will firstly supply loads, then excess power charge battery, 1140 | #and any surplus power will be fed to the grid. 1141 | #PV power is insufficient to satisfy loads, the battery will discharge power to fill the power gap, 1142 | #and the grid will join in if it’s still not enough. 1143 | 1144 | #Economic help 1145 | #This mode is typically used in areas where with peak and valley electricity prices to assist clients in optimizing their energy costs. 1146 | #By configuring the App, customers can charge power from the grid during valley hours. 1147 | #Customers can also discharge power during peak hours by configuring the App, 1148 | #and the battery will discharge electricity to supply loads or feed to the grid in this instance. 1149 | #standardne bez casov vobec nepouziva baterku / nenabija/nevybija 1150 | 1151 | #Off-grid mode 1152 | #In the pure off-grid mode, power from PV will supply the back-up loads first and then charge the battery if there's surplus power. 1153 | #When the power from PV isn’t enough, the battery will discharge to supply back-up loads together with PV. 1154 | #Note: If there’s no grid connection when configuring the hybrid system, please set the work mode to “Off-grid” to enable 1155 | #the power output in back-up port and switch to other modes when grid comes back. 1156 | 1157 | # UPS mode 1158 | # Power grid is connected, power from PV or grid will firstly charge the battery until it’s full, 1159 | # and loads will be supplied by the grid during charging period. Battery will not discharge power when the grid is connected. 1160 | # Power grid fails, and PV power is insufficient to meet the loads' consumption, the battery will discharge power to supply 1161 | # loads connected to the back-up port. 1162 | 1163 | #Wattsonic Minimal import from grid alghoritm 1164 | #aim is not to buy energy for the inverter when not needed. Inverter is switched to Off-grid mode when the battery SOC is between 30-99 1165 | #inverter takes cca 1,7kWh/day or 70W/h 1166 | #usefull when household consumption is low and battery storage can last for whole night 1167 | #you can save ~600Wh-1100Wh/day 1168 | #by default is this script disabled (should be) 1169 | #preconditions, not checked in alghoritm: 1170 | # none 1171 | #turn on Off-grid mode => battery charges from PV, energy to household goes from PV and battery 1172 | #- *after sunset 1173 | #- or *solar production=0 for 1h - todo 1174 | #- solar forecast for next day > 5kWh - todo 1175 | #- battery SOC > 60 1176 | #- is General mode 1177 | #turn on General mode => when battery full, energy goes also to grid 1178 | #- is not General mode 1179 | #- *battery is below 30% -> we will need grid 1180 | #- *battery is below 25% -> just for sure, critical event 1181 | #- *battery is over 99% -> we can export to grid 1182 | #* = event, withou asterix are conditions 1183 | 1184 | 1185 | automation: 1186 | - id: '1679907434474' 1187 | alias: Wattsonic Minimal import from grid set General Mode 1188 | description: turn on General mode => when battery full, energy goes also to grid 1189 | trigger: 1190 | - platform: numeric_state 1191 | entity_id: sensor.wattsonic_battery_soc 1192 | above: 99.8 1193 | - platform: numeric_state 1194 | entity_id: sensor.wattsonic_battery_soc 1195 | below: 30 1196 | - platform: numeric_state 1197 | entity_id: sensor.wattsonic_battery_soc 1198 | below: 25 1199 | condition: 1200 | - condition: not 1201 | conditions: 1202 | - condition: state 1203 | entity_id: sensor.wattsonic_hybrid_inverter_working_mode_setting_text 1204 | state: General mode 1205 | action: 1206 | - service: script.wattsonic_mode_general 1207 | data: {} 1208 | enabled: false 1209 | mode: single 1210 | - id: '1679935088587' 1211 | alias: Wattsonic Minimal import from grid set Off-grid Mode 1212 | description: '' 1213 | trigger: 1214 | - platform: sun 1215 | event: sunset 1216 | offset: -01:00:00 1217 | condition: 1218 | - condition: numeric_state 1219 | entity_id: sensor.wattsonic_battery_soc 1220 | above: 60 1221 | - condition: state 1222 | entity_id: sensor.wattsonic_hybrid_inverter_working_mode_setting_text 1223 | state: General mode 1224 | action: 1225 | - service: script.wattsonic_mode_offgrid 1226 | data: {} 1227 | enabled: false 1228 | mode: single 1229 | 1230 | script: 1231 | wattsonic_mode_ups: 1232 | alias: wattsonic_mode_ups 1233 | sequence: 1234 | - service: modbus.write_register 1235 | data: 1236 | address: 50000 1237 | hub: wattsonic 1238 | slave: 247 1239 | value: 259 1240 | mode: single 1241 | icon: mdi:alpha-w-box 1242 | wattsonic_mode_economic: 1243 | alias: wattsonic_mode_economic 1244 | sequence: 1245 | - service: modbus.write_register 1246 | data: 1247 | address: 50000 1248 | hub: wattsonic 1249 | slave: 247 1250 | value: 258 1251 | mode: single 1252 | icon: mdi:alpha-w-box 1253 | wattsonic_mode_general: 1254 | alias: wattsonic_mode_general 1255 | sequence: 1256 | - service: modbus.write_register 1257 | data: 1258 | address: 50000 1259 | hub: wattsonic 1260 | slave: 247 1261 | value: 257 1262 | mode: single 1263 | icon: mdi:alpha-w-box 1264 | wattsonic_mode_offgrid: 1265 | alias: wattsonic_mode_offgrid 1266 | sequence: 1267 | - service: modbus.write_register 1268 | data: 1269 | address: 50000 1270 | hub: wattsonic 1271 | slave: 247 1272 | value: 512 1273 | mode: single 1274 | icon: mdi:alpha-w-box 1275 | # wattsonic_set_grid_injection_power_limit_switch: 1276 | # alias: Wattsonic Set Grid Injection Power Limit Switch 1277 | # sequence: 1278 | # - service: modbus.write_register 1279 | # data: 1280 | # address: 25100 1281 | # hub: wattsonic 1282 | # slave: 247 1283 | # value: '{{ switch }}' 1284 | # mode: single 1285 | # icon: mdi:alpha-w-box 1286 | 1287 | wattsonic_set_grid_injection_power_limit_setting: 1288 | alias: Wattsonic Set Grid Injection Power Limit Setting 1289 | sequence: 1290 | - service: modbus.write_register 1291 | data: 1292 | address: 25103 1293 | hub: wattsonic 1294 | slave: 247 1295 | value: '{{ float(limit)*10 }}' 1296 | mode: single 1297 | icon: mdi:alpha-w-box 1298 | 1299 | wattsonic_charge_battery_from_grid: 1300 | alias: wattsonic_charge_battery_from_grid 1301 | sequence: 1302 | - service: modbus.write_register 1303 | data: 1304 | hub: wattsonic 1305 | slave: 247 1306 | address: 53006 1307 | value: 1 1308 | - service: modbus.write_register 1309 | data: 1310 | hub: wattsonic 1311 | slave: 247 1312 | address: 53007 1313 | value: 1314 | - 1 1315 | - 1 1316 | - 65535 1317 | - 800 1318 | - 65535 1319 | - 0 1320 | - 5947 1321 | - service: script.wattsonic_mode_economic 1322 | data: {} 1323 | - wait_for_trigger: 1324 | - platform: state 1325 | entity_id: 1326 | - sensor.wattsonic_hybrid_inverter_working_mode_setting 1327 | to: '258' 1328 | timeout: 1329 | hours: 0 1330 | minutes: 10 1331 | seconds: 0 1332 | milliseconds: 0 1333 | - if: 1334 | - condition: state 1335 | entity_id: sensor.wattsonic_battery_mode #0:discharge;1:charge 1336 | state: '0' 1337 | then: 1338 | - wait_for_trigger: 1339 | - platform: state 1340 | entity_id: 1341 | - sensor.wattsonic_battery_mode 1342 | to: '1' 1343 | timeout: 1344 | hours: 0 1345 | minutes: 10 1346 | seconds: 0 1347 | milliseconds: 0 1348 | - wait_for_trigger: 1349 | - platform: state 1350 | entity_id: 1351 | - sensor.wattsonic_battery_mode 1352 | to: '0' 1353 | timeout: 1354 | hours: 3 1355 | minutes: 0 1356 | seconds: 0 1357 | milliseconds: 0 1358 | - service: script.wattsonic_mode_general 1359 | data: {} 1360 | mode: single 1361 | --------------------------------------------------------------------------------