├── cfg ├── hass │ ├── scripts.yaml │ ├── apps │ │ ├── __init__.py │ │ ├── ada │ │ │ ├── __init__.py │ │ │ ├── temp.py │ │ │ └── util.py │ │ └── hello.py │ ├── custom_components │ │ ├── tplink_v2 │ │ │ ├── __init__.py │ │ │ └── manifest.json │ │ ├── huawei_ws7200 │ │ │ ├── __init__.py │ │ │ └── manifest.json │ │ ├── mqtt_dnsmasq │ │ │ ├── __init__.py │ │ │ ├── manifest.json │ │ │ └── device_tracker.py │ │ ├── clicksend_tts_en │ │ │ ├── __init__.py │ │ │ ├── manifest.json │ │ │ └── notify.py │ │ ├── asusrouter │ │ │ ├── modules │ │ │ │ ├── __init__.py │ │ │ │ ├── firmware.py │ │ │ │ └── aura.py │ │ │ ├── manifest.json │ │ │ ├── services.yaml │ │ │ ├── helpers.py │ │ │ ├── compilers.py │ │ │ ├── sensor.py │ │ │ ├── dataclass.py │ │ │ ├── update.py │ │ │ ├── diagnostics.py │ │ │ ├── __init__.py │ │ │ ├── button.py │ │ │ ├── aimesh.py │ │ │ ├── binary_sensor.py │ │ │ ├── device_tracker.py │ │ │ └── light.py │ │ ├── ping_en │ │ │ ├── services.yaml │ │ │ ├── manifest.json │ │ │ ├── const.py │ │ │ ├── __init__.py │ │ │ └── device_tracker.py │ │ ├── variable │ │ │ ├── readme.txt │ │ │ ├── manifest.json │ │ │ └── services.yaml │ │ ├── cloudflare_dns │ │ │ ├── readme.txt │ │ │ ├── manifest.json │ │ │ └── __init__.py │ │ ├── accumulator │ │ │ └── manifest.json │ │ ├── wb_irrigation │ │ │ ├── manifest.json │ │ │ ├── services.yaml │ │ │ └── pyeto │ │ │ │ ├── convert.py │ │ │ │ ├── _check.py │ │ │ │ ├── __init__.py │ │ │ │ └── thornthwaite.py │ │ ├── tplink_router │ │ │ ├── const.py │ │ │ ├── services.yaml │ │ │ ├── manifest.json │ │ │ ├── translations │ │ │ │ ├── ja.json │ │ │ │ ├── cs.json │ │ │ │ ├── es.json │ │ │ │ ├── bg.json │ │ │ │ ├── it.json │ │ │ │ ├── pt.json │ │ │ │ ├── en.json │ │ │ │ ├── sv.json │ │ │ │ ├── sk.json │ │ │ │ ├── nl.json │ │ │ │ └── fr.json │ │ │ ├── strings.json │ │ │ ├── button.py │ │ │ ├── coordinator.py │ │ │ ├── __init__.py │ │ │ ├── config_flow.py │ │ │ ├── sensor.py │ │ │ └── device_tracker.py │ │ ├── mytasmota │ │ │ ├── manifest.json │ │ │ └── __init__.py │ │ ├── hebcal │ │ │ └── manifest.json │ │ └── duckdns_ipv4_ipv6 │ │ │ ├── services.yaml │ │ │ └── manifest.json │ ├── groups.yaml │ ├── www │ │ ├── d.png │ │ ├── n.png │ │ ├── man.png │ │ └── wife.png │ ├── secrets.yaml │ ├── known_devices.yaml │ ├── .gitattributes │ ├── automations.yaml │ ├── customize.yaml │ ├── appdaemon.yaml │ └── pkgs │ │ ├── monitor.yaml │ │ ├── camera.yaml │ │ ├── basement.yaml │ │ ├── media.yaml │ │ ├── bolier.yaml │ │ ├── climate.yaml │ │ ├── water.yaml │ │ ├── ac.yaml │ │ ├── energy.yaml │ │ ├── tracker.yaml │ │ ├── irrigation.yaml │ │ └── lamps.yaml ├── mosquitto │ ├── a.txt │ ├── mosquitto.conf │ ├── mosquitto_pass │ └── .gitattributes ├── agent │ └── config │ │ ├── agent.yaml │ │ └── .gitattributes ├── ping_exporter │ └── config.yml ├── agent2 │ └── config │ │ └── agent.yaml └── influxdb │ └── influxdb.conf ├── store ├── vm │ └── dummy.txt ├── grafana2 │ └── dummy.txt └── mosquitto │ └── log │ └── a.txt ├── netdc ├── store │ └── dnsmasq │ │ └── placeholder ├── .env ├── readme.txt ├── cfg │ ├── dnsmasq │ │ ├── dnsmasq.conf │ │ ├── dns_resolv.conf │ │ ├── dnsmasq_hosts.conf │ │ └── .gitattributes │ └── dnscrypt-proxy │ │ ├── allowed-ips.txt │ │ ├── resolvers │ │ ├── relays.md.minisig │ │ └── public-resolvers.md.minisig │ │ ├── blocked-ips.txt │ │ ├── allowed-names.txt │ │ ├── forwarding-rules.txt │ │ ├── blocked-names.txt │ │ ├── cloaking-rules.txt │ │ ├── captive-portals.txt │ │ └── localhost.pem ├── dnet.service └── docker-compose.yml ├── .env ├── readme.txt ├── services └── s2 │ └── rhasspy │ ├── readme.txt │ └── docker-compose.yml ├── linux_services ├── iphone-fix │ ├── readme.txt │ └── env ├── homeassistant.service ├── appdaemon.service ├── configurator.service └── dnsmasq.sh ├── .gitattributes ├── .gitignore ├── b ├── dhass.service ├── utility.py └── README.asciidoc /cfg/hass/scripts.yaml: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /store/vm/dummy.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /cfg/hass/apps/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /cfg/mosquitto/a.txt: -------------------------------------------------------------------------------- 1 | 1 2 | -------------------------------------------------------------------------------- /cfg/hass/apps/ada/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /store/grafana2/dummy.txt: -------------------------------------------------------------------------------- 1 | 1 2 | -------------------------------------------------------------------------------- /store/mosquitto/log/a.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /netdc/store/dnsmasq/placeholder: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /cfg/hass/custom_components/tplink_v2/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /cfg/hass/custom_components/huawei_ws7200/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /cfg/hass/custom_components/mqtt_dnsmasq/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /cfg/hass/groups.yaml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hhaim/hass/HEAD/.env -------------------------------------------------------------------------------- /netdc/.env: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hhaim/hass/HEAD/netdc/.env -------------------------------------------------------------------------------- /readme.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hhaim/hass/HEAD/readme.txt -------------------------------------------------------------------------------- /netdc/readme.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hhaim/hass/HEAD/netdc/readme.txt -------------------------------------------------------------------------------- /cfg/hass/www/d.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hhaim/hass/HEAD/cfg/hass/www/d.png -------------------------------------------------------------------------------- /cfg/hass/www/n.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hhaim/hass/HEAD/cfg/hass/www/n.png -------------------------------------------------------------------------------- /cfg/hass/secrets.yaml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hhaim/hass/HEAD/cfg/hass/secrets.yaml -------------------------------------------------------------------------------- /cfg/hass/www/man.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hhaim/hass/HEAD/cfg/hass/www/man.png -------------------------------------------------------------------------------- /cfg/hass/www/wife.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hhaim/hass/HEAD/cfg/hass/www/wife.png -------------------------------------------------------------------------------- /cfg/hass/custom_components/clicksend_tts_en/__init__.py: -------------------------------------------------------------------------------- 1 | """The clicksend_tts component.""" 2 | -------------------------------------------------------------------------------- /cfg/agent/config/agent.yaml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hhaim/hass/HEAD/cfg/agent/config/agent.yaml -------------------------------------------------------------------------------- /cfg/hass/known_devices.yaml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hhaim/hass/HEAD/cfg/hass/known_devices.yaml -------------------------------------------------------------------------------- /cfg/mosquitto/mosquitto.conf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hhaim/hass/HEAD/cfg/mosquitto/mosquitto.conf -------------------------------------------------------------------------------- /cfg/mosquitto/mosquitto_pass: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hhaim/hass/HEAD/cfg/mosquitto/mosquitto_pass -------------------------------------------------------------------------------- /netdc/cfg/dnsmasq/dnsmasq.conf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hhaim/hass/HEAD/netdc/cfg/dnsmasq/dnsmasq.conf -------------------------------------------------------------------------------- /services/s2/rhasspy/readme.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hhaim/hass/HEAD/services/s2/rhasspy/readme.txt -------------------------------------------------------------------------------- /cfg/agent/config/.gitattributes: -------------------------------------------------------------------------------- 1 | agent.yaml filter=git-crypt diff=git-crypt 2 | .gitattributes !filter !diff 3 | -------------------------------------------------------------------------------- /cfg/hass/custom_components/asusrouter/modules/__init__.py: -------------------------------------------------------------------------------- 1 | """Complex modules for AsusRouter integration.""" 2 | -------------------------------------------------------------------------------- /netdc/cfg/dnsmasq/dns_resolv.conf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hhaim/hass/HEAD/netdc/cfg/dnsmasq/dns_resolv.conf -------------------------------------------------------------------------------- /linux_services/iphone-fix/readme.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hhaim/hass/HEAD/linux_services/iphone-fix/readme.txt -------------------------------------------------------------------------------- /netdc/cfg/dnsmasq/dnsmasq_hosts.conf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hhaim/hass/HEAD/netdc/cfg/dnsmasq/dnsmasq_hosts.conf -------------------------------------------------------------------------------- /cfg/hass/custom_components/ping_en/services.yaml: -------------------------------------------------------------------------------- 1 | reload: 2 | name: Reload 3 | description: Reload all ping entities. 4 | -------------------------------------------------------------------------------- /cfg/hass/custom_components/variable/readme.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hhaim/hass/HEAD/cfg/hass/custom_components/variable/readme.txt -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | readme.txt filter=git-crypt diff=git-crypt 2 | .env filter=git-crypt diff=git-crypt 3 | 4 | .gitattributes !filter !diff 5 | 6 | -------------------------------------------------------------------------------- /cfg/hass/custom_components/cloudflare_dns/readme.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hhaim/hass/HEAD/cfg/hass/custom_components/cloudflare_dns/readme.txt -------------------------------------------------------------------------------- /cfg/mosquitto/.gitattributes: -------------------------------------------------------------------------------- 1 | mosquitto.conf filter=git-crypt diff=git-crypt 2 | mosquitto_pass filter=git-crypt diff=git-crypt 3 | .gitattributes !filter !diff 4 | 5 | -------------------------------------------------------------------------------- /cfg/hass/.gitattributes: -------------------------------------------------------------------------------- 1 | secrets.yaml filter=git-crypt diff=git-crypt 2 | known_devices.yaml filter=git-crypt diff=git-crypt 3 | 4 | 5 | .gitattributes !filter !diff 6 | 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | hass_proj.* 2 | push_notify* 3 | data 4 | custom_components/media_player/ 5 | apps/_test.__yaml 6 | apps/ada/__pycache__/ 7 | apps/__pycache__/ 8 | picture/ 9 | __pycache__/ 10 | *.pyc 11 | -------------------------------------------------------------------------------- /b: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | if [ -z "$REMOTE_HASS" ] 4 | then 5 | echo "\$REMOTE_HASS should be set to the remote hass ip" 6 | exit -1 7 | fi 8 | 9 | python3 utility.py $@ 10 | 11 | 12 | -------------------------------------------------------------------------------- /netdc/cfg/dnscrypt-proxy/allowed-ips.txt: -------------------------------------------------------------------------------- 1 | ############################## 2 | # Allowed IPs List # 3 | ############################## 4 | 5 | #192.168.0.* 6 | #fe80:53:* # IPv6 prefix example 7 | #81.169.145.105 8 | -------------------------------------------------------------------------------- /netdc/cfg/dnsmasq/.gitattributes: -------------------------------------------------------------------------------- 1 | dnsmasq.conf filter=git-crypt diff=git-crypt 2 | dnsmasq_hosts.conf filter=git-crypt diff=git-crypt 3 | dns_resolv.conf filter=git-crypt diff=git-crypt 4 | 5 | .gitattributes !filter !diff 6 | 7 | -------------------------------------------------------------------------------- /linux_services/iphone-fix/env: -------------------------------------------------------------------------------- 1 | export AWS_DEFAULT_REGION="us-east-1" 2 | export RESTIC_REPOSITORY="s3:s3.us-east-1.amazonaws.com/..b" 3 | export AWS_ACCESS_KEY_ID="xxx" 4 | export AWS_SECRET_ACCESS_KEY="xxx" 5 | export RESTIC_PASSWORD= 6 | 7 | -------------------------------------------------------------------------------- /cfg/hass/custom_components/accumulator/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "domain": "accumulator", 3 | "name": "accumulator", 4 | "documentation": "", 5 | "requirements": [], 6 | "dependencies": [], 7 | "codeowners": [], 8 | "version": "1.0.0" 9 | } 10 | -------------------------------------------------------------------------------- /cfg/hass/custom_components/tplink_v2/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "domain": "tplink_v2", 3 | "name": "tplink_v2", 4 | "documentation": "", 5 | "requirements": [], 6 | "dependencies": [], 7 | "codeowners": [], 8 | "version": "1.0.0" 9 | 10 | } 11 | -------------------------------------------------------------------------------- /cfg/hass/custom_components/mqtt_dnsmasq/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "domain": "mqtt_dnsmasq", 3 | "name": "mqtt_dnsmasq", 4 | "documentation": "", 5 | "requirements": [], 6 | "dependencies": ["mqtt"], 7 | "codeowners": [], 8 | "version": "1.0.0" 9 | 10 | } 11 | -------------------------------------------------------------------------------- /cfg/hass/custom_components/wb_irrigation/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "domain": "wb_irrigation", 3 | "name": "wb_irrigation", 4 | "documentation": "", 5 | "requirements": [], 6 | "dependencies": [], 7 | "codeowners": [], 8 | "version": "1.0.0" 9 | 10 | } 11 | -------------------------------------------------------------------------------- /cfg/hass/automations.yaml: -------------------------------------------------------------------------------- 1 | - alias: "Power state on HA start-up" 2 | trigger: 3 | platform: homeassistant 4 | event: start 5 | action: 6 | - service: mqtt.publish 7 | data: 8 | topic: "cmnd/alarm/TelePeriod" 9 | payload: "60" 10 | 11 | 12 | -------------------------------------------------------------------------------- /linux_services/homeassistant.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Home Assistant 3 | After=network-online.target mosquitto.service 4 | 5 | [Service] 6 | Type=simple 7 | User=hhaim 8 | ExecStart=/home/hhaim/homeassistant/bin/hass -c /home/hhaim/.homeassistant/ 9 | 10 | [Install] 11 | WantedBy=multi-user.target 12 | -------------------------------------------------------------------------------- /cfg/hass/custom_components/tplink_router/const.py: -------------------------------------------------------------------------------- 1 | DEFAULT_NAME = "TP-Link Router" 2 | DOMAIN = "tplink_router" 3 | DEFAULT_USER = "admin" 4 | DEFAULT_HOST = "http://192.168.0.1" 5 | 6 | EVENT_NEW_DEVICE = f"{DOMAIN}_new_device" 7 | EVENT_ONLINE = f"{DOMAIN}_device_online" 8 | EVENT_OFFLINE = f"{DOMAIN}_device_offline" 9 | -------------------------------------------------------------------------------- /cfg/hass/custom_components/tplink_router/services.yaml: -------------------------------------------------------------------------------- 1 | send_sms: 2 | fields: 3 | number: 4 | required: true 5 | selector: 6 | text: 7 | text: 8 | required: true 9 | selector: 10 | text: 11 | device: 12 | required: true 13 | selector: 14 | device: -------------------------------------------------------------------------------- /cfg/hass/custom_components/mytasmota/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "domain": "mytasmota", 3 | "name": "mytasmota", 4 | "documentation": "", 5 | "requirements": [], 6 | "dependencies": ["mqtt"], 7 | "codeowners": [], 8 | "version": "1.0.0", 9 | "iot_class": "local_push", 10 | "integration_type": "device" 11 | } 12 | -------------------------------------------------------------------------------- /linux_services/appdaemon.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=AppDaemon 3 | After=homeassistant.service 4 | Requires=homeassistant.service 5 | 6 | [Service] 7 | Type=simple 8 | User=hhaim 9 | ExecStart=/home/hhaim/ad/bin/appdaemon -c /home/hhaim/.homeassistant/ 10 | 11 | [Install] 12 | WantedBy=multi-user.target 13 | -------------------------------------------------------------------------------- /linux_services/configurator.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Configurator 3 | After=network-online.target 4 | 5 | [Service] 6 | Type=simple 7 | User=hhaim 8 | WorkingDirectory=/home/hhaim/.homeassistant/ 9 | ExecStart=/usr/bin/python3 configurator.py 10 | 11 | [Install] 12 | WantedBy=multi-user.target 13 | 14 | -------------------------------------------------------------------------------- /cfg/hass/custom_components/huawei_ws7200/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "domain": "huawei_ws7200", 3 | "name": "huawei_ws7200", 4 | "documentation": "", 5 | "requirements": [], 6 | "dependencies": [], 7 | "codeowners": [], 8 | "version": "1.0.0", 9 | "iot_class": "local_push", 10 | "integration_type": "helper" 11 | 12 | } 13 | -------------------------------------------------------------------------------- /cfg/hass/custom_components/clicksend_tts_en/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "domain": "clicksend_tts_en", 3 | "name": "ClickSend TTS EN", 4 | "codeowners": [], 5 | "documentation": "https://www.home-assistant.io/integrations/clicksend_tts", 6 | "iot_class": "cloud_push", 7 | "version": "0.1.1", 8 | "integration_type": "helper" 9 | } 10 | -------------------------------------------------------------------------------- /cfg/hass/custom_components/hebcal/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "domain": "hebcal", 3 | "name": "hebcal", 4 | "documentation": "https://github.com/hhaim/hass", 5 | "requirements": ["requests"], 6 | "dependencies": [], 7 | "codeowners": [], 8 | "version": "1.0.0", 9 | "iot_class": "cloud_polling", 10 | "integration_type": "helper" 11 | 12 | } 13 | -------------------------------------------------------------------------------- /netdc/cfg/dnscrypt-proxy/resolvers/relays.md.minisig: -------------------------------------------------------------------------------- 1 | untrusted comment: signature from minisign secret key 2 | RWQf6LRCGA9i52UzKUyOkJjVHeLZbQJdkm9xenLJr3CInVGwJ74srR9xaZkz8w1ku8M6LsnWsHrAQ8HIc2nuUsrS8Nbe7+PKDQk= 3 | trusted comment: timestamp:1639152516 file:relays.md 4 | ZTca4oDuTtXE1D+HBlLwvOBVvzik/T6H/j/52PaWxMAJbVi1InNFLY4vuI+eM+Gf3lGaikRzha9t6P7zzF7gCg== 5 | -------------------------------------------------------------------------------- /netdc/cfg/dnscrypt-proxy/resolvers/public-resolvers.md.minisig: -------------------------------------------------------------------------------- 1 | untrusted comment: signature from minisign secret key 2 | RWQf6LRCGA9i55Lj+VrdEIlaeffKgjZfDxM5u3w43BdRCgv9ijU2u2wlLqbTrWmVjUKFmN8v63xorkzw+UUUTy8z+2CNmb1DNA0= 3 | trusted comment: timestamp:1639470635 file:public-resolvers.md 4 | Z36DXgqA4v0FFZBasYicH6Qxm4SV1NWaeU5NVkKgKyPEiI5Q0Wh5bDdL2Rgl48ImCCfF2wUjo/M6juLO3arkAg== 5 | -------------------------------------------------------------------------------- /cfg/hass/custom_components/ping_en/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "domain": "ping_en", 3 | "name": "Ping (ICMP)", 4 | "codeowners": [], 5 | "documentation": "https://www.home-assistant.io/integrations/ping", 6 | "iot_class": "local_polling", 7 | "loggers": ["icmplib"], 8 | "quality_scale": "internal", 9 | "requirements": ["icmplib==3.0"], 10 | "version": "0.1.1" 11 | } 12 | -------------------------------------------------------------------------------- /cfg/hass/custom_components/cloudflare_dns/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "domain": "cloudflare_dns", 3 | "name": "cloudflare_dns", 4 | "documentation": "https://github.com/hhaim/hass", 5 | "requirements": ["requests","cloudflare"], 6 | "dependencies": [], 7 | "codeowners": [], 8 | "version": "1.0.0", 9 | "iot_class": "cloud_polling", 10 | "integration_type": "helper" 11 | } 12 | -------------------------------------------------------------------------------- /cfg/hass/custom_components/duckdns_ipv4_ipv6/services.yaml: -------------------------------------------------------------------------------- 1 | set_txt: 2 | name: Set TXT 3 | description: Set the TXT record of your DuckDNS subdomain. 4 | fields: 5 | txt: 6 | name: TXT 7 | description: Payload for the TXT record. 8 | required: true 9 | example: "This domain name is reserved for use in documentation" 10 | selector: 11 | text: -------------------------------------------------------------------------------- /cfg/hass/custom_components/variable/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "domain": "variable", 3 | "name": "variable", 4 | "documentation": "https://github.com/rogro82/hass-variables", 5 | "requirements": [], 6 | "dependencies": [], 7 | "codeowners": [ 8 | "@rogro82" 9 | ], 10 | "version": "1.0.0", 11 | "iot_class": "local_push", 12 | "integration_type": "helper" 13 | } 14 | -------------------------------------------------------------------------------- /dhass.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=HassDocker 3 | Requires=snap.docker.dockerd.service 4 | After=snap.docker.dockerd.service 5 | 6 | 7 | [Service] 8 | Type=simple 9 | Restart=always 10 | RestartSec=3 11 | WorkingDirectory=/home/hhaim/hass/ 12 | ExecStart=/usr/local/bin/docker-compose up 13 | ExecStop=/usr/local/bin/docker-compose stop 14 | 15 | [Install] 16 | WantedBy=multi-user.target 17 | 18 | -------------------------------------------------------------------------------- /netdc/dnet.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=NetworkDocker 3 | Requires=snap.docker.dockerd.service 4 | After=snap.docker.dockerd.service 5 | 6 | 7 | [Service] 8 | Type=simple 9 | Restart=always 10 | RestartSec=3 11 | WorkingDirectory=/home/hhaim/hass/netdc 12 | ExecStart=/usr/local/bin/docker-compose up 13 | ExecStop=/usr/local/bin/docker-compose stop 14 | 15 | [Install] 16 | WantedBy=multi-user.target 17 | 18 | -------------------------------------------------------------------------------- /cfg/hass/custom_components/duckdns_ipv4_ipv6/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "domain": "duckdns_ipv4_ipv6", 3 | "name": "Duck DNS IPV4 and IPV6", 4 | "documentation": "https://github.com/lfhohmann/ha-duckdns_ipv4_ipv6#readme", 5 | "issue_tracker": "https://github.com/lfhohmann/ha-duckdns_ipv4_ipv6/issues", 6 | "codeowners": [ 7 | "@lfhohmann" 8 | ], 9 | "version": "1.1.1", 10 | "iot_class": "local_push", 11 | "integration_type": "helper" 12 | } 13 | -------------------------------------------------------------------------------- /cfg/hass/custom_components/asusrouter/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "domain": "asusrouter", 3 | "name": "AsusRouter", 4 | "codeowners": ["@vaskivskyi"], 5 | "config_flow": true, 6 | "documentation": "https://asusrouter.vaskivskyi.com", 7 | "integration_type": "hub", 8 | "iot_class": "local_polling", 9 | "issue_tracker": "https://github.com/Vaskivskyi/ha-asusrouter/issues", 10 | "loggers": ["asusrouter"], 11 | "requirements": ["asusrouter==1.14.2"], 12 | "version": "1.0.0.dev0" 13 | } 14 | -------------------------------------------------------------------------------- /cfg/hass/custom_components/tplink_router/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "domain": "tplink_router", 3 | "name": "TP-Link Router", 4 | "codeowners": ["@AlexandrErohin"], 5 | "config_flow": true, 6 | "documentation": "https://github.com/AlexandrErohin/home-assistant-tplink-router", 7 | "iot_class": "local_polling", 8 | "issue_tracker": "https://github.com/AlexandrErohin/home-assistant-tplink-router/issues", 9 | "requirements": ["tplinkrouterc6u==5.9.1"], 10 | "version": "2.8.2" 11 | } -------------------------------------------------------------------------------- /linux_services/dnsmasq.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # copy this file to secure location and add it to dhcp-script=/usr/local/dap/etc/dnsmasq/mqttpub.sh 3 | 4 | 5 | op="${1:-op}" 6 | mac="${2:-mac}" 7 | ip="${3:-ip}" 8 | hostname="${4}" 9 | 10 | tstamp="`date '+%Y-%m-%d %H:%M:%S'`" 11 | 12 | topic="network/dhcp/${mac}" 13 | payload="{ \"op\":\"${op}\", \"ip\":\"${ip}\", \"tstamp\":\"${tstamp}\", \"host\":\"${hostname}\"}" 14 | 15 | mosquitto_pub -u [mqtt_user] -P [mqtt_pwd] -t "${topic}" -m "${payload}" 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /cfg/ping_exporter/config.yml: -------------------------------------------------------------------------------- 1 | targets: 2 | - 1.1.1.1 3 | - 8.8.8.8 4 | - 8.8.4.4 5 | - 2001:4860:4860::8888 6 | - 2001:4860:4860::8844 7 | - 2606:4700::1111 8 | - 2606:4700:4700::1001 9 | - google.com 10 | - 10.0.0.138 11 | - 10.0.0.145 12 | - 10.0.0.146 13 | - 10.0.0.148 14 | - 10.0.0.151 15 | 16 | dns: 17 | refresh: 2m15s 18 | nameserver: 1.1.1.1 19 | 20 | ping: 21 | interval: 1s 22 | timeout: 5s 23 | history-size: 120 24 | payload-size: 120 25 | 26 | options: 27 | disableIPv6: false 28 | -------------------------------------------------------------------------------- /cfg/hass/customize.yaml: -------------------------------------------------------------------------------- 1 | sensor.se_energy_year: 2 | friendly_name: yeng 3 | unit_of_measurement: Kwh 4 | icon: mdi:solar-power 5 | sensor.se_energy_month: 6 | unit_of_measurement: Kwh 7 | friendly_name: meng 8 | icon: mdi:solar-power 9 | sensor.se_energy_day: 10 | unit_of_measurement: Kwh 11 | friendly_name: deng 12 | icon: mdi:solar-power 13 | sensor.se_energy_cur: 14 | unit_of_measurement: Kwh 15 | friendly_name: ceng 16 | icon: mdi:solar-power 17 | 18 | -------------------------------------------------------------------------------- /cfg/hass/custom_components/ping_en/const.py: -------------------------------------------------------------------------------- 1 | """Tracks devices by sending a ICMP echo request (ping).""" 2 | 3 | from homeassistant.const import Platform 4 | 5 | # The ping binary and icmplib timeouts are not the same 6 | # timeout. ping is an overall timeout, icmplib is the 7 | # time since the data was sent. 8 | 9 | # ping binary 10 | PING_TIMEOUT = 3 11 | 12 | # icmplib timeout 13 | ICMP_TIMEOUT = 1 14 | 15 | PING_ATTEMPTS_COUNT = 3 16 | 17 | DOMAIN = "ping_en" 18 | PLATFORMS = [Platform.BINARY_SENSOR] 19 | 20 | PING_PRIVS = "ping_privs" 21 | -------------------------------------------------------------------------------- /netdc/cfg/dnscrypt-proxy/blocked-ips.txt: -------------------------------------------------------------------------------- 1 | ############################## 2 | # IP blocklist # 3 | ############################## 4 | 5 | ## Rules for IP-based response blocking 6 | ## 7 | ## Sample feeds of suspect IP addresses: 8 | ## - https://github.com/stamparm/ipsum 9 | ## - https://github.com/tg12/bad_packets_blocklist 10 | ## - https://isc.sans.edu/block.txt 11 | ## - https://block.energized.pro/extensions/ips/formats/list.txt 12 | ## - https://www.iblocklist.com/lists 13 | 14 | 163.5.1.4 15 | 94.46.118.* 16 | fe80:53:* # IPv6 prefix example 17 | -------------------------------------------------------------------------------- /cfg/hass/appdaemon.yaml: -------------------------------------------------------------------------------- 1 | 2 | appdaemon: 3 | time_zone: Asia/Jerusalem 4 | latitude: !secret accurate_latitude 5 | longitude: !secret accurate_longitude 6 | elevation: !secret accurate_elevation 7 | #total_threads: 10 8 | #pin_apps: false 9 | #production_mode: true 10 | #pin_threads: 1 11 | #threadpool_workers: 1 12 | threads: 1 13 | plugins: 14 | HASS: 15 | type: hass 16 | ha_url: !secret appd_ha_url 17 | token: !secret api_token 18 | namespace: default 19 | 20 | #http: 21 | # url: http://10.0.0.44:8888 22 | 23 | #api: 24 | #log: 25 | # accessfile: STDOUT 26 | # errorfile: STDERR 27 | # logfile: STDOUT 28 | # log_generations: 3 29 | # log_size: 1000000 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /services/s2/rhasspy/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.5' 2 | 3 | # DOCKER COMPOSE COMMAND REFERENCE 4 | # -------------------------------------------------------------------------- 5 | # Start | docker-compose up -d 6 | # Stop | docker-compose stop 7 | # Update | docker-compose pull 8 | # Logs | docker-compose logs --tail=25 -f 9 | 10 | 11 | services: 12 | rhasspy: 13 | image: "rhasspy/rhasspy" 14 | container_name: rhasspy 15 | restart: unless-stopped 16 | volumes: 17 | - "/profiles:/profiles" 18 | - "/etc/localtime:/etc/localtime:ro" 19 | ports: 20 | - "12101:12101" 21 | devices: 22 | - "/dev/snd:/dev/snd" 23 | command: --user-profiles /profiles --profile en 24 | -------------------------------------------------------------------------------- /cfg/hass/pkgs/monitor.yaml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | group: 6 | 7 | cpu: 8 | name: Cpu 9 | entities: 10 | - sensor.disk_free 11 | - sensor.energy_total 12 | - sensor.energy_yesterday 13 | - sensor.fast_com_download 14 | - sensor.memory_free 15 | - sensor.processor_use 16 | 17 | Debug: 18 | name: Debug 19 | entities: 20 | - group.cpu 21 | - input_boolean.simple_switch0 22 | - input_boolean.disable_ap_c7 23 | - input_boolean.gateway_sound_enable 24 | - input_number.gateway_sound_id 25 | - binary_sensor.cube_158d000276ed22 26 | - sensor.water_total 27 | - sensor.water_total_external 28 | - light.gateway_light_7c49eb193b55 29 | - light.light1 30 | 31 | -------------------------------------------------------------------------------- /cfg/hass/custom_components/tplink_router/translations/ja.json: -------------------------------------------------------------------------------- 1 | { 2 | "config": { 3 | "step": { 4 | "user": { 5 | "description": "統合の設定", 6 | "data": { 7 | "host": "ホスト", 8 | "username": "ログイン", 9 | "password": "パスワード", 10 | "scan_interval": "更新のスキャン間隔(秒)", 11 | "verify_ssl": "https接続でSSLを検証する" 12 | } 13 | } 14 | } 15 | }, 16 | "options": { 17 | "step": { 18 | "init": { 19 | "description": "ルーターのパラメーターを編集します。", 20 | "data": { 21 | "host": "ホスト", 22 | "username": "ログイン", 23 | "password": "パスワード", 24 | "scan_interval": "更新のスキャン間隔(秒)", 25 | "verify_ssl": "https接続でSSLを検証する" 26 | } 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /cfg/agent2/config/agent.yaml: -------------------------------------------------------------------------------- 1 | global: 2 | scrape_interval: 2m 3 | 4 | scrape_configs: 5 | - job_name: ping_exporter 6 | metrics_path: /metrics 7 | scheme: http 8 | static_configs: 9 | - targets: ['10.0.0.140:9427'] 10 | 11 | - job_name: cadvisor 12 | metrics_path: /metrics 13 | scheme: http 14 | static_configs: 15 | - targets: ['10.0.0.140:8091'] 16 | metric_relabel_configs: 17 | # Extract full container ID from cgroup path 18 | - source_labels: [id] 19 | regex: '.*/docker-([0-9a-f]{64})\.scope' 20 | target_label: container_id 21 | replacement: '${1}' 22 | 23 | # Extract short container ID (first 12 chars) 24 | - source_labels: [id] 25 | regex: '.*/docker-([0-9a-f]{12}).*' 26 | target_label: container_id_short 27 | replacement: '${1}' -------------------------------------------------------------------------------- /cfg/hass/custom_components/tplink_router/translations/cs.json: -------------------------------------------------------------------------------- 1 | { 2 | "config": { 3 | "step": { 4 | "user": { 5 | "description": "Nastavení integrace", 6 | "data": { 7 | "host": "Host", 8 | "username": "Login", 9 | "password": "Heslo", 10 | "scan_interval": "Interval skenování v sekundách", 11 | "verify_ssl": "Kontrolovat ssl pro HTTPS připojení" 12 | } 13 | } 14 | } 15 | }, 16 | "options": { 17 | "step": { 18 | "init": { 19 | "description": "Upravit parametry nastavení.", 20 | "data": { 21 | "host": "Host", 22 | "username": "Login", 23 | "password": "Heslo", 24 | "scan_interval": "Interval skenování v sekundách", 25 | "verify_ssl": "Kontrolovat ssl pro HTTPS připojení" 26 | } 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /cfg/hass/custom_components/tplink_router/translations/es.json: -------------------------------------------------------------------------------- 1 | { 2 | "config": { 3 | "step": { 4 | "user": { 5 | "description": "Configuración de la integración", 6 | "data": { 7 | "host": "Host", 8 | "username": "Usuario", 9 | "password": "Contraseña", 10 | "scan_interval": "Intervalo de escaneo para actualizaciones en segundos", 11 | "verify_ssl": "Verificar SSL para conexión https" 12 | } 13 | } 14 | } 15 | }, 16 | "options": { 17 | "step": { 18 | "init": { 19 | "description": "Editar parámetros del router.", 20 | "data": { 21 | "host": "Host", 22 | "username": "Usuario", 23 | "password": "Contraseña", 24 | "scan_interval": "Intervalo de escaneo para actualizaciones en segundos", 25 | "verify_ssl": "Verificar SSL para conexión https" 26 | } 27 | } 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /cfg/hass/custom_components/tplink_router/translations/bg.json: -------------------------------------------------------------------------------- 1 | { 2 | "config": { 3 | "step": { 4 | "user": { 5 | "description": "Конфигурация на интеграцията", 6 | "data": { 7 | "host": "Хост", 8 | "username": "Потребителско име", 9 | "password": "Парола", 10 | "scan_interval": "Интервал за сканиране за актуализации в секунди", 11 | "verify_ssl": "Проверка на SSL за HTTPS връзка" 12 | } 13 | } 14 | } 15 | }, 16 | "options": { 17 | "step": { 18 | "init": { 19 | "description": "Редактиране на параметрите за рутера.", 20 | "data": { 21 | "host": "Хост", 22 | "username": "Потребителско име", 23 | "password": "Парола", 24 | "scan_interval": "Интервал за сканиране за актуализации в секунди", 25 | "verify_ssl": "Проверка на SSL за HTTPS връзка" 26 | } 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /cfg/hass/custom_components/variable/services.yaml: -------------------------------------------------------------------------------- 1 | # Example services.yaml entry 2 | 3 | set_variable: 4 | # Description of the service 5 | description: Update a variables value and/or its attributes. 6 | # Different fields that your service accepts 7 | fields: 8 | # Key of the field 9 | variable: 10 | description: string (required) The name of the variable to update 11 | value: 12 | description: any (optional) New value to set 13 | value_template: 14 | description: template (optional) New value to set from a template 15 | attributes: 16 | description: dictionary (optional) Attributes to set or update 17 | attributes_template: 18 | description: template (optional) Attributes to set or update from a template ( should return a json object ) 19 | replace_attributes: 20 | description: boolean ( optional ) Replace or merge current attributes (default false = merge) 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /cfg/hass/custom_components/tplink_router/translations/it.json: -------------------------------------------------------------------------------- 1 | { 2 | "config": { 3 | "step": { 4 | "user": { 5 | "description": "Configurazione dell'integrazione", 6 | "data": { 7 | "host": "Host", 8 | "username": "Nome utente", 9 | "password": "Password", 10 | "scan_interval": "Intervallo di scansione per aggiornamenti in secondi", 11 | "verify_ssl": "Verifica SSL per la connessione https" 12 | } 13 | } 14 | } 15 | }, 16 | "options": { 17 | "step": { 18 | "init": { 19 | "description": "Modifica parametri del router", 20 | "data": { 21 | "host": "Host", 22 | "username": "Nome utente", 23 | "password": "Password", 24 | "scan_interval": "Intervallo di scansione per aggiornamenti in secondi", 25 | "verify_ssl": "Verifica SSL per la connessione https" 26 | } 27 | } 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /cfg/hass/custom_components/wb_irrigation/services.yaml: -------------------------------------------------------------------------------- 1 | # Example services.yaml entry 2 | 3 | set_variable: 4 | # Description of the service 5 | description: Update a queue size value and/or its attributes. 6 | # Different fields that your service accepts 7 | fields: 8 | # Key of the field 9 | variable: 10 | description: string (required) The name of the variable to update 11 | value: 12 | description: any (optional) New value to set 13 | value_template: 14 | description: template (optional) New value to set from a template 15 | attributes: 16 | description: dictionary (optional) Attributes to set or update 17 | attributes_template: 18 | description: template (optional) Attributes to set or update from a template ( should return a json object ) 19 | replace_attributes: 20 | description: boolean ( optional ) Replace or merge current attributes (default false = merge) 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /cfg/hass/custom_components/tplink_router/translations/pt.json: -------------------------------------------------------------------------------- 1 | { 2 | "config": { 3 | "step": { 4 | "user": { 5 | "description": "Configuração da integração", 6 | "data": { 7 | "host": "Host", 8 | "username": "Utilizador", 9 | "password": "Palavra-passe", 10 | "scan_interval": "Intervalo de verificação para atualizações em segundos", 11 | "verify_ssl": "Verificar SSL para a conexão https" 12 | } 13 | } 14 | } 15 | }, 16 | "options": { 17 | "step": { 18 | "init": { 19 | "description": "Editar parâmetros do router.", 20 | "data": { 21 | "host": "Host", 22 | "username": "Utilizador", 23 | "password": "Palavra-passe", 24 | "scan_interval": "Intervalo de verificação para atualizações em segundos", 25 | "verify_ssl": "Verificar SSL para a conexão https" 26 | } 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /cfg/hass/custom_components/asusrouter/modules/firmware.py: -------------------------------------------------------------------------------- 1 | """Firmware module for AsusRouter integration.""" 2 | 3 | from __future__ import annotations 4 | 5 | from typing import Any, Optional 6 | 7 | 8 | def to_ha(data: Optional[dict[str, Any]]) -> dict[str, Any]: 9 | """Convert AsusRouter firmware data to Home Assistant format.""" 10 | 11 | if not data: 12 | return {} 13 | 14 | # Current firmware 15 | _current = data.get("current") 16 | _current = str(_current) if _current else None 17 | 18 | # Available stable firmware 19 | _latest = data.get("available") 20 | _latest = str(_latest) if _latest else _current 21 | 22 | # Available beta firmware 23 | _latest_beta = data.get("available_beta") 24 | _latest_beta = str(_latest_beta) if _latest_beta else _current 25 | 26 | return { 27 | "current": _current, 28 | "latest": _latest, 29 | "latest_beta": _latest_beta, 30 | "release_note": data.get("release_note"), 31 | } 32 | -------------------------------------------------------------------------------- /cfg/hass/pkgs/camera.yaml: -------------------------------------------------------------------------------- 1 | 2 | group: 3 | 4 | Cameras: 5 | name: Cameras 6 | icon: mdi:cctv 7 | entities: 8 | - device_tracker.cam1 9 | - device_tracker.cam2 10 | - device_tracker.cam3 11 | - device_tracker.cam4 12 | - device_tracker.cam5 13 | - camera.cam5 14 | - camera.cam1 15 | - camera.cam2 16 | - camera.cam3 17 | - camera.cam4 18 | 19 | cam_tracker: 20 | name: Cam Tracker 21 | entities: 22 | - device_tracker.cam1 23 | - device_tracker.cam2 24 | - device_tracker.cam3 25 | - device_tracker.cam4 26 | - device_tracker.cam5 27 | 28 | 29 | switch: 30 | - platform: mytasmota 31 | name: cam6on 32 | stopic: basic7 33 | 34 | - platform: mytasmota 35 | name: kit7on 36 | stopic: basic6 37 | 38 | camera: 39 | 40 | - platform: ffmpeg 41 | name: cam5 42 | input: !secret cam5_url 43 | 44 | - platform: ffmpeg 45 | name: cam1 46 | input: !secret cam1_url 47 | 48 | 49 | -------------------------------------------------------------------------------- /cfg/hass/custom_components/asusrouter/services.yaml: -------------------------------------------------------------------------------- 1 | device_internet_access: 2 | fields: 3 | entities: 4 | required: false 5 | selector: 6 | entity: 7 | integration: asusrouter 8 | domain: device_tracker 9 | multiple: true 10 | state: 11 | required: true 12 | selector: 13 | select: 14 | translation_key: set_pc_rule 15 | mode: dropdown 16 | options: 17 | - "block" 18 | - "allow" 19 | - "remove" 20 | remove_trackers: 21 | name: Remove device trackers 22 | description: This service allows removing device_tracker entities. Please, keep in mind, when run, the service will remove selected trackers from watching and will allow you to remove the entities manually. 23 | fields: 24 | entities: 25 | name: Entities 26 | description: Entities to remove 27 | required: true 28 | selector: 29 | entity: 30 | integration: asusrouter 31 | domain: device_tracker 32 | multiple: true 33 | -------------------------------------------------------------------------------- /netdc/cfg/dnscrypt-proxy/allowed-names.txt: -------------------------------------------------------------------------------- 1 | 2 | ########################### 3 | # Allowlist # 4 | ########################### 5 | 6 | ## Rules for allowing queries based on name, one per line 7 | ## 8 | ## Example of valid patterns: 9 | ## 10 | ## ads.* | matches anything with an "ads." prefix 11 | ## *.example.com | matches example.com and all names within that zone such as www.example.com 12 | ## example.com | identical to the above 13 | ## =example.com | allows example.com but not *.example.com 14 | ## *sex* | matches any name containing that substring 15 | ## ads[0-9]* | matches "ads" followed by one or more digits 16 | ## ads*.example* | *, ? and [] can be used anywhere, but prefixes/suffixes are faster 17 | 18 | 19 | # That one may be blocked due to 'tracker' being in the name. 20 | tracker.debian.org 21 | 22 | # That one may be blocked due to 'ads' being in the name. 23 | # However, blocking it prevents all sponsored links from the Google 24 | # search engine from being opened. 25 | googleadservices.com 26 | 27 | 28 | ## Time-based rules 29 | 30 | # *.youtube.* @time-to-play 31 | # facebook.com @play 32 | -------------------------------------------------------------------------------- /netdc/cfg/dnscrypt-proxy/forwarding-rules.txt: -------------------------------------------------------------------------------- 1 | ################################## 2 | # Forwarding rules # 3 | ################################## 4 | 5 | ## This is used to route specific domain names to specific servers. 6 | ## The general format is: 7 | ## [:port] [, [:port]...] 8 | ## IPv6 addresses can be specified by enclosing the address in square brackets. 9 | 10 | ## In order to enable this feature, the "forwarding_rules" property needs to 11 | ## be set to this file name inside the main configuration file. 12 | 13 | ## Blocking IPv6 may prevent local devices from being discovered. 14 | ## If this happens, set `block_ipv6` to `false` in the main config file. 15 | 16 | ## Forward *.lan, *.local, *.home, *.home.arpa, *.internal and *.localdomain to 192.168.1.1 17 | # lan 192.168.1.1 18 | # local 192.168.1.1 19 | # home 192.168.1.1 20 | # home.arpa 192.168.1.1 21 | # internal 192.168.1.1 22 | # localdomain 192.168.1.1 23 | 24 | ## Forward queries for example.com and *.example.com to 9.9.9.9 and 8.8.8.8 25 | # example.com 9.9.9.9,8.8.8.8 26 | -------------------------------------------------------------------------------- /cfg/hass/pkgs/basement.yaml: -------------------------------------------------------------------------------- 1 | homeassistant: 2 | customize: 3 | light.tz3000_1nenkvye_ts0004_light: 4 | friendly_name: base_l1 5 | 6 | light.tz3000_1nenkvye_ts0004_light_2: 7 | friendly_name: base_l2 8 | 9 | light.tz3000_1nenkvye_ts0004_light_3: 10 | friendly_name: base_l3 11 | 12 | light.tz3000_1nenkvye_ts0004_light_4: 13 | friendly_name: base_l4 14 | 15 | light.tz3000_1nenkvye_ts0004_light: 16 | friendly_name: base_l1 17 | 18 | switch.tz3000_gjnozsaz_ts011f: 19 | friendly_name: base_vent1 20 | 21 | switch.tz3000_gjnozsaz_ts011f_2: 22 | friendly_name: base_vent2 23 | 24 | binary_sensor.lumi_lumi_sensor_wleak_aq1: 25 | friendly_name: base_water 26 | 27 | binary_sensor.lumi_lumi_sensor_motion_aq2_2: 28 | friendly_name: base_motion 29 | 30 | group: 31 | base_light: 32 | name: basement lights 33 | entities: 34 | - light.tz3000_1nenkvye_ts0004_light 35 | - light.tz3000_1nenkvye_ts0004_light_2 36 | - light.tz3000_1nenkvye_ts0004_light_3 37 | - light.tz3000_1nenkvye_ts0004_light_4 38 | - switch.tz3000_gjnozsaz_ts011f 39 | 40 | 41 | 42 | input_boolean: 43 | base_all: 44 | name: base_enable_all 45 | 46 | -------------------------------------------------------------------------------- /netdc/cfg/dnscrypt-proxy/blocked-names.txt: -------------------------------------------------------------------------------- 1 | 2 | ########################### 3 | # Blocklist # 4 | ########################### 5 | 6 | ## Rules for name-based query blocking, one per line 7 | ## 8 | ## Example of valid patterns: 9 | ## 10 | ## ads.* | matches anything with an "ads." prefix 11 | ## *.example.com | matches example.com and all names within that zone such as www.example.com 12 | ## example.com | identical to the above 13 | ## =example.com | block example.com but not *.example.com 14 | ## *sex* | matches any name containing that substring 15 | ## ads[0-9]* | matches "ads" followed by one or more digits 16 | ## ads*.example* | *, ? and [] can be used anywhere, but prefixes/suffixes are faster 17 | 18 | ad.* 19 | ads.* 20 | banner.* 21 | banners.* 22 | creatives.* 23 | oas.* 24 | oascentral.* # inline comments are allowed after a pound sign 25 | stats.* 26 | tag.* 27 | telemetry.* 28 | tracker.* 29 | *.local 30 | eth0.me 31 | *.workgroup 32 | 33 | 34 | ## Prevent usage of Apple private relay, that bypasses DNS 35 | 36 | # mask.apple-dns.net 37 | # mask.icloud.com 38 | # mask-api.icloud.com 39 | # doh.dns.apple.com 40 | 41 | 42 | ## Time-based rules 43 | 44 | # *.youtube.* @time-to-sleep 45 | # facebook.com @work 46 | -------------------------------------------------------------------------------- /cfg/hass/custom_components/wb_irrigation/pyeto/convert.py: -------------------------------------------------------------------------------- 1 | """ 2 | Unit conversion functions. 3 | 4 | :copyright: (c) 2015 by Mark Richards. 5 | :license: BSD 3-Clause, see LICENSE.txt for more details. 6 | """ 7 | 8 | import math 9 | 10 | 11 | def celsius2kelvin(celsius): 12 | """ 13 | Convert temperature in degrees Celsius to degrees Kelvin. 14 | 15 | :param celsius: Degrees Celsius 16 | :return: Degrees Kelvin 17 | :rtype: float 18 | """ 19 | return celsius + 273.15 20 | 21 | 22 | def kelvin2celsius(kelvin): 23 | """ 24 | Convert temperature in degrees Kelvin to degrees Celsius. 25 | 26 | :param kelvin: Degrees Kelvin 27 | :return: Degrees Celsius 28 | :rtype: float 29 | """ 30 | return kelvin - 273.15 31 | 32 | 33 | def deg2rad(degrees): 34 | """ 35 | Convert angular degrees to radians 36 | 37 | :param degrees: Value in degrees to be converted. 38 | :return: Value in radians 39 | :rtype: float 40 | """ 41 | return degrees * (math.pi / 180.0) 42 | 43 | 44 | def rad2deg(radians): 45 | """ 46 | Convert radians to angular degrees 47 | 48 | :param radians: Value in radians to be converted. 49 | :return: Value in angular degrees 50 | :rtype: float 51 | """ 52 | return radians * (180.0 / math.pi) 53 | -------------------------------------------------------------------------------- /cfg/hass/pkgs/media.yaml: -------------------------------------------------------------------------------- 1 | homeassistant: 2 | customize: 3 | switch.tv: 4 | friendly_name: TV 5 | icon: mdi:television-classic 6 | 7 | 8 | switch: 9 | - platform: mytasmota 10 | name: TV 11 | stopic: basic11 12 | 13 | 14 | 15 | 16 | 17 | media_player: 18 | 19 | - platform: universal 20 | name: main 21 | commands: 22 | turn_on: 23 | service: mqtt.publish 24 | data: 25 | topic: cmnd/water/IRsend 26 | payload: '{"Protocol":"NEC","Bits":32,"Data":0x10E03FC}' 27 | 28 | turn_off: 29 | service: mqtt.publish 30 | data: 31 | topic: cmnd/water/IRsend 32 | payload: '{"Protocol":"NEC","Bits":32,"Data":0x10EF906}' 33 | 34 | volume_up: 35 | service: mqtt.publish 36 | data: 37 | topic: cmnd/water/IRsend 38 | payload: '{"Protocol":"NEC","Bits":32,"Data":0x10EE31c}' 39 | 40 | volume_down: 41 | service: mqtt.publish 42 | data: 43 | topic: cmnd/water/IRsend 44 | payload: '{"Protocol":"NEC","Bits":32,"Data":0x10E13ec}' 45 | 46 | volume_mute: 47 | service: mqtt.publish 48 | data: 49 | topic: cmnd/water/IRsend 50 | payload: '{"Protocol":"NEC","Bits":32,"Data":0x10E837c}' 51 | 52 | attributes: 53 | state: switch.tv 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /cfg/hass/custom_components/tplink_router/translations/en.json: -------------------------------------------------------------------------------- 1 | { 2 | "config": { 3 | "step": { 4 | "user": { 5 | "description": "Integration config", 6 | "data": { 7 | "host": "Host", 8 | "username": "Login", 9 | "password": "Password", 10 | "scan_interval": "Scan interval for updates in seconds", 11 | "verify_ssl": "Verify ssl for https connection" 12 | } 13 | } 14 | } 15 | }, 16 | "options": { 17 | "step": { 18 | "init": { 19 | "description": "Edit parameters for the router.", 20 | "data": { 21 | "host": "Host", 22 | "username": "Login", 23 | "password": "Password", 24 | "scan_interval": "Scan interval for updates in seconds", 25 | "verify_ssl": "Verify ssl for https connection" 26 | } 27 | } 28 | } 29 | }, 30 | "services": { 31 | "send_sms": { 32 | "name": "Send SMS", 33 | "description": "Send SMS", 34 | "fields": { 35 | "number": { 36 | "name": "Phone", 37 | "description": "Phone number" 38 | }, 39 | "text": { 40 | "name": "Message", 41 | "description": "Text" 42 | }, 43 | "device": { 44 | "name": "Router", 45 | "description": "Select the TP-Link router to send sms" 46 | } 47 | } 48 | } 49 | } 50 | } -------------------------------------------------------------------------------- /cfg/hass/custom_components/asusrouter/helpers.py: -------------------------------------------------------------------------------- 1 | """AsusRouter helpers module.""" 2 | 3 | from __future__ import annotations 4 | 5 | import re 6 | from typing import Any 7 | 8 | 9 | def clean_dict(raw: dict[str, Any]) -> dict[str, Any]: 10 | """Cleans dictionary from None values.""" 11 | 12 | return {k: v for k, v in raw.items() if v is not None or k.endswith("state")} 13 | 14 | 15 | def flatten_dict(obj: Any, keystring: str = "", delimiter: str = "_"): 16 | """Flatten dictionary.""" 17 | 18 | if isinstance(obj, dict): 19 | keystring = keystring + delimiter if keystring else keystring 20 | for key in obj: 21 | yield from flatten_dict(obj[key], keystring + str(key)) 22 | else: 23 | yield keystring, obj 24 | 25 | 26 | def as_dict(pyobj): 27 | """Return generator object as dictionary.""" 28 | 29 | return dict(pyobj) 30 | 31 | 32 | def list_from_dict(raw: dict[str, Any]) -> list[str]: 33 | """Return dictionary keys as list.""" 34 | 35 | return list(raw.keys()) 36 | 37 | 38 | def to_unique_id(raw: str): 39 | """Convert string to unique_id.""" 40 | 41 | string = ( 42 | re.sub(r"(?<=[a-z0-9:_])(?=[A-Z])|[^a-zA-Z0-9:_]", " ", raw) 43 | .strip() 44 | .replace(" ", "_") 45 | ) 46 | result = "".join(string.lower()) 47 | while "__" in result: 48 | result = result.replace("__", "_") 49 | 50 | return result 51 | -------------------------------------------------------------------------------- /cfg/hass/custom_components/tplink_router/translations/sv.json: -------------------------------------------------------------------------------- 1 | { 2 | "config": { 3 | "step": { 4 | "user": { 5 | "description": "Integrationskonfiguration", 6 | "data": { 7 | "host": "Host", 8 | "username": "Login", 9 | "password": "Lösenord", 10 | "scan_interval": "Uppdateringsintervall i sekunder", 11 | "verify_ssl": "Verifiera ssl för https anslutning" 12 | } 13 | } 14 | } 15 | }, 16 | "options": { 17 | "step": { 18 | "init": { 19 | "description": "Ändra konfiguration för routern.", 20 | "data": { 21 | "host": "Host", 22 | "username": "Login", 23 | "password": "Lösenord", 24 | "scan_interval": "Uppdateringsintervall i sekunder", 25 | "verify_ssl": "Verifiera ssl för https anslutning" 26 | } 27 | } 28 | } 29 | }, 30 | "services": { 31 | "send_sms": { 32 | "name": "Skicka SMS", 33 | "description": "Skicka SMS", 34 | "fields": { 35 | "number": { 36 | "name": "Telefon", 37 | "description": "Telefonnummer" 38 | }, 39 | "text": { 40 | "name": "Meddelande", 41 | "description": "Text" 42 | }, 43 | "device": { 44 | "name": "Router", 45 | "description": "Välj TP-Link router för att skicka sms" 46 | } 47 | } 48 | } 49 | } 50 | } -------------------------------------------------------------------------------- /cfg/hass/custom_components/tplink_router/translations/sk.json: -------------------------------------------------------------------------------- 1 | { 2 | "config": { 3 | "step": { 4 | "user": { 5 | "description": "Nastavenie integrácie", 6 | "data": { 7 | "host": "Hostiteľ", 8 | "username": "Meno", 9 | "password": "Heslo", 10 | "scan_interval": "Interval skenovania aktualizácií v sekundách", 11 | "verify_ssl": "Overte ssl pre pripojenie https" 12 | } 13 | } 14 | } 15 | }, 16 | "options": { 17 | "step": { 18 | "init": { 19 | "description": "Upravte parametre smerovača.", 20 | "data": { 21 | "host": "Hostiteľ", 22 | "username": "Meno", 23 | "password": "Heslo", 24 | "scan_interval": "Interval skenovania aktualizácií v sekundách", 25 | "verify_ssl": "Overte ssl pre pripojenie https" 26 | } 27 | } 28 | } 29 | }, 30 | "services": { 31 | "send_sms": { 32 | "name": "Pošlite SMS", 33 | "description": "Pošlite SMS", 34 | "fields": { 35 | "number": { 36 | "name": "Phone", 37 | "description": "Telefónne číslo" 38 | }, 39 | "text": { 40 | "name": "Správa", 41 | "description": "Text" 42 | }, 43 | "device": { 44 | "name": "Smerovač", 45 | "description": "Vyberte smerovač TP-Link na odosielanie SMS" 46 | } 47 | } 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /netdc/cfg/dnscrypt-proxy/cloaking-rules.txt: -------------------------------------------------------------------------------- 1 | ################################ 2 | # Cloaking rules # 3 | ################################ 4 | 5 | # The following example rules force "safe" (without adult content) search 6 | # results from Google, Bing and YouTube. 7 | # 8 | # This has to be enabled with the `cloaking_rules` parameter in the main 9 | # configuration file 10 | 11 | 12 | www.google.* forcesafesearch.google.com 13 | 14 | www.bing.com strict.bing.com 15 | 16 | yandex.ru familysearch.yandex.ru # inline comments are allowed after a pound sign 17 | 18 | =duckduckgo.com safe.duckduckgo.com 19 | 20 | www.youtube.com restrictmoderate.youtube.com 21 | m.youtube.com restrictmoderate.youtube.com 22 | youtubei.googleapis.com restrictmoderate.youtube.com 23 | youtube.googleapis.com restrictmoderate.youtube.com 24 | www.youtube-nocookie.com restrictmoderate.youtube.com 25 | 26 | # Multiple IP entries for the same name are supported. 27 | # In the following example, the same name maps both to IPv4 and IPv6 addresses: 28 | 29 | localhost 127.0.0.1 30 | localhost ::1 31 | 32 | # For load-balancing, multiple IP addresses of the same class can also be 33 | # provided using the same format, one pair per line. 34 | 35 | # ads.* 192.168.100.1 36 | # ads.* 192.168.100.2 37 | # ads.* ::1 38 | -------------------------------------------------------------------------------- /cfg/hass/custom_components/tplink_router/translations/nl.json: -------------------------------------------------------------------------------- 1 | { 2 | "config": { 3 | "step": { 4 | "user": { 5 | "description": "Integratie configuratie", 6 | "data": { 7 | "host": "Host", 8 | "username": "Gebruikersnaam", 9 | "password": "Wachtwoord", 10 | "scan_interval": "Scaninterval voor updates in seconden", 11 | "verify_ssl": "Verifieer SSL voor https-verbinding" 12 | } 13 | } 14 | } 15 | }, 16 | "options": { 17 | "step": { 18 | "init": { 19 | "description": "Parameters bewerken voor de router.", 20 | "data": { 21 | "host": "Host", 22 | "username": "Gebruikersnaam", 23 | "password": "Wachtwoord", 24 | "scan_interval": "Scaninterval voor updates in seconden", 25 | "verify_ssl": "Verifieer SSL voor https-verbinding" 26 | } 27 | } 28 | } 29 | }, 30 | "services": { 31 | "send_sms": { 32 | "name": "Verzend SMS", 33 | "description": "Een SMS verzenden", 34 | "fields": { 35 | "number": { 36 | "name": "Telefoon", 37 | "description": "Telefoonnummer" 38 | }, 39 | "text": { 40 | "name": "Bericht", 41 | "description": "Tekst" 42 | }, 43 | "device": { 44 | "name": "Router", 45 | "description": "Selecteer de TP-Link-router om een sms te verzenden" 46 | } 47 | } 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /cfg/hass/custom_components/tplink_router/translations/fr.json: -------------------------------------------------------------------------------- 1 | { 2 | "config": { 3 | "step": { 4 | "user": { 5 | "description": "Configuration de l'intégration", 6 | "data": { 7 | "host": "Hôte", 8 | "username": "Utilisateur", 9 | "password": "Mot de passe", 10 | "scan_interval": "Intervalle d'analyse des mises à jour en secondes", 11 | "verify_ssl": "Vérifier SSL pour la connexion https" 12 | } 13 | } 14 | } 15 | }, 16 | "options": { 17 | "step": { 18 | "init": { 19 | "description": "Modifier les paramètres du routeur.", 20 | "data": { 21 | "host": "Hôte", 22 | "username": "Utilisateur", 23 | "password": "Mot de passe", 24 | "scan_interval": "Intervalle d'analyse des mises à jour en secondes", 25 | "verify_ssl": "Vérifier SSL pour la connexion https" 26 | } 27 | } 28 | } 29 | }, 30 | "services": { 31 | "send_sms": { 32 | "name": "Envoi SMS", 33 | "description": "Envoi SMS", 34 | "fields": { 35 | "number": { 36 | "name": "Téléphone", 37 | "description": "Numéro de téléphone" 38 | }, 39 | "text": { 40 | "name": "Message", 41 | "description": "Text" 42 | }, 43 | "device": { 44 | "name": "Router", 45 | "description": "Select the TP-Link router to send sms" 46 | } 47 | } 48 | } 49 | } 50 | } -------------------------------------------------------------------------------- /cfg/hass/custom_components/ping_en/__init__.py: -------------------------------------------------------------------------------- 1 | """The ping component.""" 2 | from __future__ import annotations 3 | 4 | import logging 5 | 6 | from icmplib import SocketPermissionError, ping as icmp_ping 7 | 8 | from homeassistant.core import HomeAssistant 9 | from homeassistant.helpers.reload import async_setup_reload_service 10 | from homeassistant.helpers.typing import ConfigType 11 | 12 | from .const import DOMAIN, PING_PRIVS, PLATFORMS 13 | 14 | _LOGGER = logging.getLogger(__name__) 15 | 16 | 17 | async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: 18 | """Set up the ping integration.""" 19 | await async_setup_reload_service(hass, DOMAIN, PLATFORMS) 20 | hass.data[DOMAIN] = { 21 | PING_PRIVS: await hass.async_add_executor_job(_can_use_icmp_lib_with_privilege), 22 | } 23 | return True 24 | 25 | 26 | def _can_use_icmp_lib_with_privilege() -> None | bool: 27 | """Verify we can create a raw socket.""" 28 | try: 29 | icmp_ping("127.0.0.1", count=0, timeout=0, privileged=True) 30 | except SocketPermissionError: 31 | try: 32 | icmp_ping("127.0.0.1", count=0, timeout=0, privileged=False) 33 | except SocketPermissionError: 34 | _LOGGER.debug( 35 | "Cannot use icmplib because privileges are insufficient to create the" 36 | " socket" 37 | ) 38 | return None 39 | 40 | _LOGGER.debug("Using icmplib in privileged=False mode") 41 | return False 42 | 43 | _LOGGER.debug("Using icmplib in privileged=True mode") 44 | return True 45 | -------------------------------------------------------------------------------- /netdc/cfg/dnscrypt-proxy/captive-portals.txt: -------------------------------------------------------------------------------- 1 | ########################################### 2 | # Captive portal test names # 3 | ########################################### 4 | 5 | ## Some operating systems send queries to these names after a network change, 6 | ## in order to check if connectivity beyond the router is possible without 7 | ## going through a captive portal. 8 | ## 9 | ## This is a list of hard-coded IP addresses that will be returned when queries 10 | ## for these names are received, even before the operating system an interface 11 | ## as usable for reaching the Internet. 12 | ## 13 | ## Note that IPv6 addresses don't need to be specified within brackets, 14 | ## as there are no port numbers. 15 | 16 | captive.apple.com 17.253.109.201, 17.253.113.202 17 | connectivitycheck.gstatic.com 64.233.162.94, 64.233.164.94, 64.233.165.94, 64.233.177.94, 64.233.185.94, 74.125.132.94, 74.125.136.94, 74.125.20.94, 74.125.21.94, 74.125.28.94 18 | connectivitycheck.android.com 64.233.162.100, 64.233.162.101, 64.233.162.102, 64.233.162.113, 64.233.162.138, 64.233.162.139 19 | www.msftncsi.com 2.16.106.89, 2.16.106.91, 23.0.175.137, 23.0.175.146, 23.192.47.155, 23.192.47.203, 23.199.63.160, 23.199.63.184, 23.199.63.208, 23.204.146.160, 23.204.146.163, 23.46.238.243, 23.46.239.24, 23.48.39.16, 23.48.39.48, 23.55.38.139, 23.55.38.146, 23.59.190.185, 23.59.190.195 20 | dns.msftncsi.com 131.107.255.255, fd3e:4f5a:5b81::1 21 | www.msftconnecttest.com 13.107.4.52 22 | ipv6.msftconnecttest.com 2a01:111:2003::52 23 | ipv4only.arpa 192.0.0.170, 192.0.0.171 24 | -------------------------------------------------------------------------------- /cfg/hass/custom_components/tplink_router/strings.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "TP-Link Router", 3 | "config": { 4 | "step": { 5 | "user": { 6 | "description": "description", 7 | "data": { 8 | "host": "[%key:common::config_flow::data::host%]", 9 | "username": "[%key:common::config_flow::data::username%]", 10 | "password": "[%key:common::config_flow::data::password%]", 11 | "scan_interval": "[%key:common::config_flow::data::scan_interval%]", 12 | "verify_ssl": "[%key:common::config_flow::data::verify_ssl%]" 13 | } 14 | } 15 | } 16 | }, 17 | "options": { 18 | "step": { 19 | "init": { 20 | "data": { 21 | "host": "[%key:common::config_flow::data::host%]", 22 | "username": "[%key:common::config_flow::data::username%]", 23 | "password": "[%key:common::config_flow::data::password%]", 24 | "scan_interval": "[%key:common::config_flow::data::scan_interval%]", 25 | "verify_ssl": "[%key:common::config_flow::data::verify_ssl%]" 26 | } 27 | } 28 | } 29 | }, 30 | "services": { 31 | "send_sms": { 32 | "name": "Send SMS", 33 | "description": "Send SMS", 34 | "fields": { 35 | "number": { 36 | "name": "Phone", 37 | "description": "Phone number" 38 | }, 39 | "text": { 40 | "name": "Message", 41 | "description": "Text" 42 | }, 43 | "device": { 44 | "name": "Router", 45 | "description": "Select the TP-Link router to send sms" 46 | } 47 | } 48 | } 49 | } 50 | } -------------------------------------------------------------------------------- /cfg/hass/pkgs/bolier.yaml: -------------------------------------------------------------------------------- 1 | homeassistant: 2 | customize: 3 | switch.b0: 4 | friendly_name: Boiler 5 | icon: mdi:shower 6 | 7 | sensor.btemp0: 8 | friendly_name: Boiler Temperator 9 | icon: mdi:oil-temperature 10 | 11 | group: 12 | 13 | bolier_group: 14 | name: Boiler 15 | entities: 16 | - sensor.btemp0 17 | - switch.b0 18 | - input_boolean.boiler_auto_enable 19 | - input_number.boiler_timer_time 20 | - input_number.boiler_temp_min 21 | - input_number.boiler_temp_max 22 | 23 | Boiler: 24 | name: Boiler 25 | icon: mdi:shower 26 | entities: 27 | - sensor.btemp0 28 | - group.bolier_group 29 | 30 | 31 | input_boolean: 32 | boiler_auto_enable: 33 | name: Enable auto 34 | 35 | 36 | input_number: 37 | boiler_timer_time: 38 | name: Bolier Timer 39 | min: 10 40 | max: 120 41 | step: 10 42 | boiler_temp_min: 43 | name: min-temp 44 | min: 25.0 45 | max: 40.0 46 | step: 1.0 47 | boiler_temp_max: 48 | name: max-temp 49 | min: 32.0 50 | max: 60.0 51 | step: 1.0 52 | 53 | 54 | switch: 55 | 56 | - platform: mytasmota 57 | name: b0 58 | stopic: b0 59 | 60 | variable: 61 | boiler_eff_power0: 62 | value: 0 63 | restore: true 64 | attributes: 65 | friendly_name: 'Boiler power-ef C/hour' 66 | unit_of_measurement: "C" 67 | 68 | boiler_eff_solar0: 69 | value: 0 70 | restore: true 71 | attributes: 72 | friendly_name: 'Boiler solar-ef C/hour' 73 | unit_of_measurement: "C" 74 | 75 | mqtt: 76 | sensor: 77 | - name: "btemp0" 78 | state_topic: "tele/b0/SENSOR" 79 | value_template: "{{value_json['DS18B20'].Temperature }}" 80 | qos: 1 81 | unit_of_measurement : "C" 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /cfg/hass/pkgs/climate.yaml: -------------------------------------------------------------------------------- 1 | 2 | variable: 3 | heat_index0: 4 | value: 0 5 | restore: true 6 | attributes: 7 | friendly_name: 'Heat Index first floor' 8 | unit_of_measurement: "C" 9 | 10 | heat_index1: 11 | value: 0 12 | restore: true 13 | attributes: 14 | friendly_name: 'Heat Index second floor' 15 | unit_of_measurement: "C" 16 | 17 | heat_index2: 18 | value: 0 19 | restore: true 20 | attributes: 21 | friendly_name: 'Heat Index basement floor' 22 | unit_of_measurement: "C" 23 | 24 | heat_index_outside0: 25 | value: 0 26 | restore: true 27 | attributes: 28 | friendly_name: 'Index outside' 29 | unit_of_measurement: "C" 30 | 31 | 32 | # Weather prediction 33 | mqtt: 34 | sensor: 35 | - name: "Temperature" 36 | state_topic: "tele/temp3/SENSOR" 37 | value_template: "{{ value_json['SI7021'].Temperature }}" 38 | unit_of_measurement: "C" 39 | 40 | - name: "Humidity" 41 | state_topic: "tele/temp3/SENSOR" 42 | value_template: "{{ value_json['SI7021'].Humidity }}" 43 | unit_of_measurement: "%" 44 | 45 | - name: "temp1" 46 | state_topic: "tele/temp0/SENSOR" 47 | value_template: "{{ value_json['SI7021'].Temperature }}" 48 | unit_of_measurement: "C" 49 | 50 | - name: "hum1" 51 | state_topic: "tele/temp0/SENSOR" 52 | value_template: "{{ value_json['SI7021'].Humidity }}" 53 | unit_of_measurement: "%" 54 | 55 | - name: "temp2" 56 | state_topic: "tele/temp2/SENSOR" 57 | value_template: "{{ value_json['SI7021'].Temperature }}" 58 | unit_of_measurement: "C" 59 | 60 | - name: "hum2" 61 | state_topic: "tele/temp2/SENSOR" 62 | value_template: "{{ value_json['SI7021'].Humidity }}" 63 | unit_of_measurement: "%" 64 | 65 | 66 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /cfg/hass/custom_components/asusrouter/compilers.py: -------------------------------------------------------------------------------- 1 | """AsusRouter compilers module.""" 2 | 3 | from __future__ import annotations 4 | 5 | from typing import Optional 6 | 7 | from .const import ( 8 | CONF_LABELS_INTERFACES, 9 | MAP_NETWORK_TEMP, 10 | NAME, 11 | NETWORK, 12 | SENSORS_PARAM_NETWORK, 13 | ) 14 | from .dataclass import ARSensorDescription 15 | 16 | 17 | def list_sensors_network( 18 | interfaces: Optional[list[str]] = None, 19 | ) -> list[ARSensorDescription]: 20 | """Compile a list of network sensors.""" 21 | 22 | sensors: list[ARSensorDescription] = [] 23 | 24 | if not interfaces or len(interfaces) < 1: 25 | return sensors 26 | 27 | for intf in interfaces: 28 | interface = MAP_NETWORK_TEMP.get(intf, intf) 29 | for sensor_type, data in SENSORS_PARAM_NETWORK.items(): 30 | key = f"{interface}_{sensor_type}" 31 | sensors.append( 32 | ARSensorDescription( 33 | key=key, 34 | key_group=NETWORK, 35 | name=f"{CONF_LABELS_INTERFACES.get(interface, interface)} {data[NAME]}" 36 | or None, 37 | icon=data["icon"] or None, 38 | state_class=data["state_class"] or None, 39 | device_class=data["device_class"] or None, 40 | native_unit_of_measurement=data["native_unit_of_measurement"] 41 | or None, 42 | suggested_unit_of_measurement=data["suggested_unit_of_measurement"] 43 | or None, 44 | suggested_display_precision=data["suggested_display_precision"] 45 | or None, 46 | entity_registry_enabled_default=data[ 47 | "entity_registry_enabled_default" 48 | ] 49 | or True, 50 | ) 51 | ) 52 | 53 | return sensors 54 | -------------------------------------------------------------------------------- /cfg/hass/apps/hello.py: -------------------------------------------------------------------------------- 1 | import appdaemon.plugins.hass.hassapi as hass 2 | import datetime 3 | 4 | # 5 | # Hello World App 6 | # 7 | # Args: 8 | # 9 | 10 | class HelloWorld(hass.Hass): 11 | 12 | def initialize(self): 13 | self.cnt=1; 14 | self.log("hey this is me"+str(self.args)); 15 | self.log("Hello from AppDaemon") 16 | self.log("You are now ready to run Apps!") 17 | self.log(self.get_now()); 18 | self.log(self.args); 19 | self.notify("my message2") 20 | #self.run_every(self.sunrise_cb, self.get_now(), 2) 21 | #self.listen_state(self.presence_on, "switch.ac1",old = "off", new = "on") 22 | #self.run_in(self.my_turn_off, 1,param=[12,13]) 23 | 24 | #state = self.get_state(entity='sun.sun') 25 | #self.log("State "+state) 26 | #self.log(str(self.__dict__)) 27 | #status = self.set_state("switch.ac1", state = "on"); 28 | #self.log("State "+status) 29 | #self.delay(1) 30 | #status = self.set_state("default","switch.ac1", state = "off"); 31 | 32 | def presence_on(self, entity, attribute, old, new, kwargs): 33 | #self.log("turn on,start timer {0} - {1}-{2}".format(attribute, old, new)); 34 | self.run_in(self.my_turn_off, 5) 35 | #self.run_every(self.sunrise_cb, self.get_now(), 2) 36 | 37 | def my_turn_off(self, kwargs ): 38 | self.log("dargs------" +str(kwargs)); 39 | #self.turn_off("switch.ac1") 40 | 41 | 42 | def sunrise_cb(self, kwargs): 43 | self.log("cb1 --- "+str(self.cnt)+ str(kwargs)); 44 | a=self.get_state("switch.ac1") 45 | t=self.get_state("sensor.temperature") 46 | self.log("state --- "+str(a)+' '+str(t)); 47 | self.cnt=self.cnt+1 48 | #if (self.cnt % 2)==0: 49 | # self.turn_on("switch.ac1") 50 | #else: 51 | # self.turn_off("switch.ac1") 52 | 53 | 54 | 55 | def before_sunset_cb(self, kwargs): 56 | self.turn_off("switch.ac1") 57 | 58 | -------------------------------------------------------------------------------- /cfg/hass/pkgs/water.yaml: -------------------------------------------------------------------------------- 1 | variable: 2 | water_leak_detector: 3 | value: 0 4 | restore: true 5 | attributes: 6 | friendly_name: 'water leaks detector' 7 | unit_of_measurement: "l" 8 | 9 | water_bursts: 10 | value: 0 11 | restore: true 12 | attributes: 13 | friendly_name: 'water usage in bulks' 14 | unit_of_measurement: "l" 15 | 16 | 17 | template: 18 | - sensor: 19 | - name: water_total_external 20 | unit_of_measurement: 'l' 21 | icon: mdi:water-pump 22 | availability: > 23 | {{ states('sensor.water_total') not in ['unavailable', 'unknown'] and 24 | states('sensor.water_total') | float(0) > 0 }} 25 | state_class: measurement 26 | state: > 27 | {% set source = states('sensor.water_total') | float(0) %} 28 | {{ (80600 + 45497 + (source )) | int }} 29 | 30 | - name: water_total_norm 31 | unit_of_measurement: 'l' 32 | availability: > 33 | {{ states('sensor.water_total') not in ['unavailable', 'unknown'] and 34 | states('sensor.water_total') | float(0) > 0 }} 35 | icon: mdi:water-pump 36 | state_class: measurement 37 | state: > 38 | {% set source = states('sensor.water_total') | float(0) %} 39 | {{ (357120 + (source /1.05)) | int }} 40 | 41 | - name: water_total_external_norm 42 | unit_of_measurement: 'l' 43 | icon: mdi:water-pump 44 | availability: > 45 | {{ states('sensor.water_total') not in ['unavailable', 'unknown'] and 46 | states('sensor.water_total') | float(0) > 0 }} 47 | state_class: measurement 48 | state: > 49 | {% set source = states('sensor.water_total') | float(0) %} 50 | {{ (692437 + (source /1.05)) | int }} 51 | 52 | 53 | sensor: 54 | 55 | - platform: mytasmota 56 | name: water_total 57 | stopic: water_out 58 | id: 1 59 | unit_of_measurement: 'l' 60 | icon: mdi:water-pump 61 | expire_after: 300 62 | value_template: "{{ (45497 + (value))|int }}" 63 | 64 | 65 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /cfg/hass/custom_components/asusrouter/sensor.py: -------------------------------------------------------------------------------- 1 | """AsusRouter sensor module.""" 2 | 3 | from __future__ import annotations 4 | 5 | import logging 6 | 7 | from homeassistant.components.sensor import SensorEntity 8 | from homeassistant.config_entries import ConfigEntry 9 | from homeassistant.core import HomeAssistant 10 | from homeassistant.helpers.entity_platform import AddEntitiesCallback 11 | from homeassistant.helpers.update_coordinator import DataUpdateCoordinator 12 | 13 | from .compilers import list_sensors_network 14 | from .const import CONF_INTERFACES, STATIC_SENSORS 15 | from .dataclass import ARSensorDescription 16 | from .entity import AREntity, async_setup_ar_entry 17 | from .router import ARDevice 18 | 19 | _LOGGER = logging.getLogger(__name__) 20 | 21 | 22 | async def async_setup_entry( 23 | hass: HomeAssistant, 24 | config_entry: ConfigEntry, 25 | async_add_entities: AddEntitiesCallback, 26 | ) -> None: 27 | """Set up AsusRouter sensors.""" 28 | 29 | sensors = STATIC_SENSORS.copy() 30 | 31 | interfaces = config_entry.options.get(CONF_INTERFACES, []) 32 | if len(interfaces) > 0: 33 | _LOGGER.debug("Interfaces selected: %s. Initializing sensors", interfaces) 34 | sensors.extend( 35 | list_sensors_network( 36 | config_entry.options[CONF_INTERFACES], 37 | ) 38 | ) 39 | 40 | await async_setup_ar_entry( 41 | hass, config_entry, async_add_entities, sensors, ARSensor 42 | ) 43 | 44 | 45 | class ARSensor(AREntity, SensorEntity): 46 | """AsusRouter sensor.""" 47 | 48 | def __init__( 49 | self, 50 | coordinator: DataUpdateCoordinator, 51 | router: ARDevice, 52 | description: ARSensorDescription, 53 | ) -> None: 54 | """Initialize AsusRouter sensor.""" 55 | 56 | super().__init__(coordinator, router, description) 57 | self.entity_description: ARSensorDescription = description 58 | 59 | @property 60 | def native_value( 61 | self, 62 | ) -> float | str | None: 63 | """Return state.""" 64 | 65 | description = self.entity_description 66 | state = self.coordinator.data.get(description.key) 67 | return state 68 | -------------------------------------------------------------------------------- /cfg/hass/custom_components/wb_irrigation/pyeto/_check.py: -------------------------------------------------------------------------------- 1 | """ 2 | Internal validation functions. 3 | 4 | :copyright: (c) 2015 by Mark Richards. 5 | :license: BSD 3-Clause, see LICENSE.txt for more details. 6 | """ 7 | from .convert import deg2rad 8 | 9 | # Internal constants 10 | # Latitude 11 | _MINLAT_RADIANS = deg2rad(-90.0) 12 | _MAXLAT_RADIANS = deg2rad(90.0) 13 | 14 | # Solar declination 15 | _MINSOLDEC_RADIANS = deg2rad(-23.5) 16 | _MAXSOLDEC_RADIANS = deg2rad(23.5) 17 | 18 | # Sunset hour angle 19 | _MINSHA_RADIANS = 0.0 20 | _MAXSHA_RADIANS = deg2rad(180) 21 | 22 | 23 | def check_day_hours(hours, arg_name): 24 | """ 25 | Check that *hours* is in the range 1 to 24. 26 | """ 27 | if not 0 <= hours <= 24: 28 | raise ValueError( 29 | '{0} should be in range 0-24: {1!r}'.format(arg_name, hours)) 30 | 31 | 32 | def check_doy(doy): 33 | """ 34 | Check day of the year is valid. 35 | """ 36 | if not 1 <= doy <= 366: 37 | raise ValueError( 38 | 'Day of the year (doy) must be in range 1-366: {0!r}'.format(doy)) 39 | 40 | 41 | def check_latitude_rad(latitude): 42 | if not _MINLAT_RADIANS <= latitude <= _MAXLAT_RADIANS: 43 | raise ValueError( 44 | 'latitude outside valid range {0!r} to {1!r} rad: {2!r}' 45 | .format(_MINLAT_RADIANS, _MAXLAT_RADIANS, latitude)) 46 | 47 | 48 | def check_sol_dec_rad(sd): 49 | """ 50 | Solar declination can vary between -23.5 and +23.5 degrees. 51 | 52 | See http://mypages.iit.edu/~maslanka/SolarGeo.pdf 53 | """ 54 | if not _MINSOLDEC_RADIANS <= sd <= _MAXSOLDEC_RADIANS: 55 | raise ValueError( 56 | 'solar declination outside valid range {0!r} to {1!r} rad: {2!r}' 57 | .format(_MINSOLDEC_RADIANS, _MAXSOLDEC_RADIANS, sd)) 58 | 59 | 60 | def check_sunset_hour_angle_rad(sha): 61 | """ 62 | Sunset hour angle has the range 0 to 180 degrees. 63 | 64 | See http://mypages.iit.edu/~maslanka/SolarGeo.pdf 65 | """ 66 | if not _MINSHA_RADIANS <= sha <= _MAXSHA_RADIANS: 67 | raise ValueError( 68 | 'sunset hour angle outside valid range {0!r} to {1!r} rad: {2!r}' 69 | .format(_MINSHA_RADIANS, _MAXSHA_RADIANS, sha)) 70 | -------------------------------------------------------------------------------- /cfg/hass/pkgs/ac.yaml: -------------------------------------------------------------------------------- 1 | 2 | ## Customize 3 | homeassistant: 4 | customize: 5 | input_boolean.heat_app_enable: 6 | friendly_name: Start Automation 7 | icon: mdi:air-conditioner 8 | 9 | input_boolean.heat_app_enable_base: 10 | friendly_name: Start Automation Basement 11 | icon: mdi:air-conditioner 12 | 13 | switch.ac1: 14 | friendly_name: S-A/C-Down 15 | icon: mdi:air-conditioner 16 | 17 | switch.ac2: 18 | friendly_name: S-A/C-Up 19 | icon: mdi:air-conditioner 20 | 21 | switch.ac3: # enable bird 22 | friendly_name: Bird 23 | icon: mdi:bird 24 | 25 | input_boolean.ac1_input: 26 | icon: mdi:air-conditioner 27 | friendly_name: A/C-Down 28 | 29 | input_boolean.ac2_input: 30 | icon: mdi:air-conditioner 31 | friendly_name: A/C-Up 32 | 33 | group: 34 | 35 | air_conditioner: 36 | name: Air Conditioner 37 | entities: 38 | - variable.heat_index0 39 | - variable.heat_index1 40 | - variable.heat_index2 41 | - variable.heat_index_outside0 42 | - input_boolean.ac1_input 43 | - input_boolean.ac2_input 44 | - switch.ac1 45 | - switch.ac2 46 | - switch.tv 47 | - media_player.main 48 | - sensor.water_total_external_norm 49 | 50 | 51 | air_conditioner_automation: 52 | name: Automation 53 | entities: 54 | - input_boolean.heat_app_enable 55 | - input_boolean.heat_timer_0 56 | - input_number.heat_timer_time 57 | 58 | 59 | 60 | input_boolean: 61 | heat_app_enable: 62 | name: Enable AC/1 Heater 63 | heat_app_enable_base: 64 | name: Enable AC/Base Heater 65 | heat_timer_0: 66 | name: Enable timer for heat 67 | ac1_input: 68 | name: A/C-Down 69 | ac2_input: 70 | name: A/C-Up 71 | 72 | input_number: 73 | heat_timer_time: 74 | name: A/C Timer 75 | min: 60 76 | max: 240 77 | step: 30 78 | 79 | 80 | switch: 81 | - platform: mytasmota 82 | name: ac1 83 | index: '1' 84 | stopic: s0 85 | 86 | - platform: mytasmota 87 | name: ac2 88 | index: '1' 89 | stopic: s1 90 | 91 | # basement 92 | - platform: mytasmota 93 | name: ac3 94 | stopic: temp2 95 | 96 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /cfg/hass/custom_components/mqtt_dnsmasq/device_tracker.py: -------------------------------------------------------------------------------- 1 | """ 2 | Support for tracking MQTT enabled devices. 3 | 4 | For more details about this platform, please refer to the documentation at 5 | https://home-assistant.io/components/device_tracker.mqtt/ 6 | """ 7 | import json 8 | import logging 9 | 10 | import voluptuous as vol 11 | 12 | from homeassistant.components import mqtt 13 | from homeassistant.core import callback 14 | from homeassistant.const import CONF_DEVICES 15 | from homeassistant.components.mqtt import CONF_QOS 16 | from homeassistant.components.device_tracker import PLATFORM_SCHEMA 17 | import homeassistant.helpers.config_validation as cv 18 | 19 | DEPENDENCIES = ['mqtt'] 20 | 21 | _LOGGER = logging.getLogger(__name__) 22 | 23 | DMSMASQ_JSON_PAYLOAD_SCHEMA = vol.Schema({ 24 | vol.Required("op"): cv.string, 25 | vol.Required("mac"): cv.string, 26 | vol.Required("host"): cv.string, 27 | }, extra=vol.ALLOW_EXTRA) 28 | 29 | 30 | CONF_MQTT_TOPIC = 'topic' 31 | 32 | PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(mqtt.SCHEMA_BASE).extend({ 33 | vol.Required(CONF_MQTT_TOPIC): cv.string, 34 | }) 35 | 36 | HOME_MODE_AWAY = 'not_home' 37 | HOME_MODE_HOME = 'home' 38 | 39 | async def async_setup_scanner(hass, config, async_see, discovery_info=None): 40 | """Set up the MQTT tracker.""" 41 | topic = config[CONF_MQTT_TOPIC] 42 | qos = config[CONF_QOS] 43 | 44 | @callback 45 | def async_message_received(msg): 46 | payload = msg.payload 47 | """Handle received MQTT message.""" 48 | 49 | try: 50 | d = DMSMASQ_JSON_PAYLOAD_SCHEMA(json.loads(payload)) 51 | except vol.MultipleInvalid: 52 | _LOGGER.error("Skipping update for following data " 53 | "because of missing or malformatted data: %s", 54 | payload) 55 | return 56 | except ValueError: 57 | _LOGGER.error("Error parsing JSON payload: %s", payload) 58 | return 59 | 60 | if d['op']=='del': 61 | _LOGGER.info("mqtt_dnsmask: skip {0}".format(d)) 62 | return 63 | 64 | hass.async_add_job( 65 | async_see(mac=d['mac'], source_type="dhcp")) 66 | 67 | await mqtt.async_subscribe( 68 | hass, topic+"/+", async_message_received, qos) 69 | 70 | return True 71 | -------------------------------------------------------------------------------- /cfg/hass/custom_components/wb_irrigation/pyeto/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | #import ..pyeto 3 | 4 | from .convert import ( 5 | celsius2kelvin, 6 | kelvin2celsius, 7 | deg2rad, 8 | rad2deg, 9 | ) 10 | 11 | from .fao import ( 12 | atm_pressure, 13 | avp_from_tmin, 14 | avp_from_rhmin_rhmax, 15 | avp_from_rhmax, 16 | avp_from_rhmean, 17 | avp_from_tdew, 18 | avp_from_twet_tdry, 19 | cs_rad, 20 | daily_mean_t, 21 | daylight_hours, 22 | delta_svp, 23 | energy2evap, 24 | et_rad, 25 | fao56_penman_monteith, 26 | hargreaves, 27 | inv_rel_dist_earth_sun, 28 | mean_svp, 29 | monthly_soil_heat_flux, 30 | monthly_soil_heat_flux2, 31 | net_in_sol_rad, 32 | net_out_lw_rad, 33 | net_rad, 34 | psy_const, 35 | psy_const_of_psychrometer, 36 | rh_from_avp_svp, 37 | SOLAR_CONSTANT, 38 | sol_dec, 39 | sol_rad_from_sun_hours, 40 | sol_rad_from_t, 41 | sol_rad_island, 42 | STEFAN_BOLTZMANN_CONSTANT, 43 | sunset_hour_angle, 44 | svp_from_t, 45 | wind_speed_2m, 46 | ) 47 | 48 | from .thornthwaite import ( 49 | thornthwaite, 50 | monthly_mean_daylight_hours, 51 | ) 52 | 53 | __all__ = [ 54 | # Unit conversions 55 | 'celsius2kelvin', 56 | 'deg2rad', 57 | 'kelvin2celsius', 58 | 'rad2deg', 59 | 60 | # FAO equations 61 | 'atm_pressure', 62 | 'avp_from_tmin', 63 | 'avp_from_rhmin_rhmax', 64 | 'avp_from_rhmax', 65 | 'avp_from_rhmean', 66 | 'avp_from_tdew', 67 | 'avp_from_twet_tdry', 68 | 'cs_rad', 69 | 'daily_mean_t', 70 | 'daylight_hours', 71 | 'delta_svp', 72 | 'energy2evap', 73 | 'et_rad', 74 | 'fao56_penman_monteith', 75 | 'hargreaves', 76 | 'inv_rel_dist_earth_sun', 77 | 'mean_svp', 78 | 'monthly_soil_heat_flux', 79 | 'monthly_soil_heat_flux2', 80 | 'net_in_sol_rad', 81 | 'net_out_lw_rad', 82 | 'net_rad', 83 | 'psy_const', 84 | 'psy_const_of_psychrometer', 85 | 'rh_from_avp_svp', 86 | 'SOLAR_CONSTANT', 87 | 'sol_dec', 88 | 'sol_rad_from_sun_hours', 89 | 'sol_rad_from_t', 90 | 'sol_rad_island', 91 | 'STEFAN_BOLTZMANN_CONSTANT', 92 | 'sunset_hour_angle', 93 | 'svp_from_t', 94 | 'wind_speed_2m', 95 | 96 | # Thornthwaite method 97 | 'thornthwaite', 98 | 'monthly_mean_daylight_hours', 99 | ] 100 | -------------------------------------------------------------------------------- /cfg/hass/pkgs/energy.yaml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | input_boolean: 5 | simple_switch0: 6 | name: Enable Simple Switch 0 7 | 8 | 9 | 10 | 11 | # Weather prediction 12 | mqtt: 13 | sensor: 14 | - name: "Energy Yesterday" 15 | state_topic: "tele/pow0/SENSOR" 16 | value_template: "{{value_json['ENERGY'].Yesterday }}" 17 | qos: 1 18 | unit_of_measurement : "kWh" 19 | 20 | - name: "Energy Total" 21 | state_topic: "tele/pow0/SENSOR" 22 | value_template: "{{value_json['ENERGY'].Total }}" 23 | qos: 1 24 | unit_of_measurement : "kWh" 25 | 26 | - name: "input_eng_total" 27 | state_topic: "tele/sdm1/SENSOR" 28 | value_template: "{{ (value_json['ENERGY'].Total | float ) | round(0) }}" 29 | qos: 1 30 | unit_of_measurement : "kWh" 31 | availability_topic: "tele/sdm1/LWT" 32 | payload_available: "Online" 33 | payload_not_available: "Offline" 34 | 35 | - name: "input_active_power_L1" 36 | state_topic: "tele/sdm1/SENSOR" 37 | value_template: "{{ (value_json['ENERGY']['ActivePower'][0] | float /1000) | round(1) }}" 38 | qos: 1 39 | unit_of_measurement : "kW" 40 | availability_topic: "tele/sdm1/LWT" 41 | payload_available: "Online" 42 | payload_not_available: "Offline" 43 | 44 | - name: "input_active_power_L2" 45 | state_topic: "tele/sdm1/SENSOR" 46 | value_template: "{{ (value_json['ENERGY']['ActivePower'][1] | float /1000) | round(1) }}" 47 | qos: 1 48 | unit_of_measurement : "kW" 49 | availability_topic: "tele/sdm1/LWT" 50 | payload_available: "Online" 51 | payload_not_available: "Offline" 52 | 53 | - name: "input_active_power_L3" 54 | state_topic: "tele/sdm1/SENSOR" 55 | value_template: "{{ (value_json['ENERGY']['ActivePower'][2] | float /1000) | round(1) }}" 56 | qos: 1 57 | unit_of_measurement : "kW" 58 | availability_topic: "tele/sdm1/LWT" 59 | payload_available: "Online" 60 | payload_not_available: "Offline" 61 | 62 | - name: "input_active_power_ALL" 63 | state_topic: "tele/sdm1/SENSOR" 64 | value_template: "{{ ( (value_json['ENERGY']['ActivePower'][0] + value_json['ENERGY']['ActivePower'][1] +value_json['ENERGY']['ActivePower'][2]) | float /1000) | round(1) }}" 65 | qos: 1 66 | unit_of_measurement : "kW" 67 | availability_topic: "tele/sdm1/LWT" 68 | payload_available: "Online" 69 | payload_not_available: "Offline" 70 | -------------------------------------------------------------------------------- /cfg/hass/apps/ada/temp.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # 4 | # See __doc__ for an explanation of what this module does 5 | # 6 | # See __usage__ for an explanation of runtime arguments. 7 | # 8 | # -Christopher Blunck 9 | # 10 | 11 | import sys, math 12 | 13 | __author__ = 'Christopher Blunck' 14 | __email__ = 'chris@wxnet.org' 15 | __revision__ = '$Revision: 1.6 $' 16 | 17 | __doc__ = 'temperature related conversionfunctions' 18 | __usage__ = 'this module should not be run via the command line' 19 | 20 | 21 | 22 | def celsius_to_fahrenheit(c): 23 | 'Degrees Celsius (C) to degrees Fahrenheit (F)' 24 | return (c * 1.8) + 32.0 25 | 26 | def fahrenheit_to_celsius(f): 27 | 'Degrees Fahrenheit (F) to degrees Celsius (C)' 28 | return (f - 32.0) * 0.555556 29 | 30 | def calc_heat_index_celsius(c, hum): 31 | ''' calculate heat index ''' 32 | f=celsius_to_fahrenheit(c) 33 | rf=calc_heat_index(f, hum) 34 | return fahrenheit_to_celsius(rf) 35 | 36 | 37 | def calc_heat_index_v2(temp, hum): 38 | 39 | return 0.363445176 + 0.988622465 * temp + 4.777114035 * hum - 0.114037667 * \ 40 | temp * hum - 8.50208 * (10 ** -4) * (temp ** 2) - 2.0716198 * \ 41 | (10 ** -2) * (hum ** 2) + 6.87678 * (10 ** -4) * (temp ** 2) * \ 42 | hum + 2.74954 * (10 ** -4) * temp * (hum ** 2) 43 | 44 | 45 | def calc_heat_index(temp, hum): 46 | ''' 47 | calculates the heat index based upon temperature (in F) and humidity. 48 | http://www.srh.noaa.gov/bmx/tables/heat_index.html 49 | 50 | returns the heat index in degrees F. 51 | ''' 52 | 53 | if (temp < 80): 54 | if temp>69.9: 55 | return (calc_heat_index_v2(temp, hum)) 56 | else: 57 | return temp 58 | else: 59 | return -42.379 + 2.04901523 * temp + 10.14333127 * hum - 0.22475541 * \ 60 | temp * hum - 6.83783 * (10 ** -3) * (temp ** 2) - 5.481717 * \ 61 | (10 ** -2) * (hum ** 2) + 1.22874 * (10 ** -3) * (temp ** 2) * \ 62 | hum + 8.5282 * (10 ** -4) * temp * (hum ** 2) - 1.99 * \ 63 | (10 ** -6) * (temp ** 2) * (hum ** 2); 64 | 65 | 66 | def calc_wind_chill(t, windspeed, windspeed10min=None): 67 | ''' 68 | calculates the wind chill value based upon the temperature (F) and 69 | wind. 70 | 71 | returns the wind chill in degrees F. 72 | ''' 73 | 74 | w = max(windspeed10min, windspeed) 75 | return 35.74 + 0.6215 * t - 35.75 * (w ** 0.16) + 0.4275 * t * (w ** 0.16); 76 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /cfg/hass/custom_components/tplink_router/button.py: -------------------------------------------------------------------------------- 1 | """Component providing support for TPLinkRouter button entities.""" 2 | from __future__ import annotations 3 | 4 | from collections.abc import Callable 5 | from dataclasses import dataclass 6 | from typing import Any 7 | from homeassistant.const import EntityCategory 8 | from homeassistant.components.button import ( 9 | ButtonDeviceClass, 10 | ButtonEntity, 11 | ButtonEntityDescription, 12 | ) 13 | from homeassistant.config_entries import ConfigEntry 14 | from homeassistant.core import HomeAssistant 15 | from homeassistant.helpers.entity_platform import AddEntitiesCallback 16 | from homeassistant.helpers.update_coordinator import CoordinatorEntity 17 | from .const import DOMAIN 18 | from .coordinator import TPLinkRouterCoordinator 19 | 20 | 21 | @dataclass 22 | class TPLinkRouterButtonEntityDescriptionMixin: 23 | method: Callable[[TPLinkRouterCoordinator], Any] 24 | 25 | 26 | @dataclass 27 | class TPLinkButtonEntityDescription( 28 | ButtonEntityDescription, TPLinkRouterButtonEntityDescriptionMixin 29 | ): 30 | """A class that describes button entities for the host.""" 31 | 32 | 33 | BUTTON_TYPES = ( 34 | TPLinkButtonEntityDescription( 35 | key="reboot", 36 | name="Reboot", 37 | device_class=ButtonDeviceClass.RESTART, 38 | entity_category=EntityCategory.CONFIG, 39 | method=lambda coordinator: coordinator.reboot(), 40 | ), 41 | ) 42 | 43 | 44 | async def async_setup_entry( 45 | hass: HomeAssistant, 46 | entry: ConfigEntry, 47 | async_add_entities: AddEntitiesCallback, 48 | ) -> None: 49 | coordinator = hass.data[DOMAIN][entry.entry_id] 50 | 51 | buttons = [] 52 | 53 | for description in BUTTON_TYPES: 54 | buttons.append(TPLinkRouterButtonEntity(coordinator, description)) 55 | async_add_entities(buttons, False) 56 | 57 | 58 | class TPLinkRouterButtonEntity(CoordinatorEntity[TPLinkRouterCoordinator], ButtonEntity): 59 | entity_description: TPLinkButtonEntityDescription 60 | 61 | def __init__( 62 | self, 63 | coordinator: TPLinkRouterCoordinator, 64 | description: TPLinkButtonEntityDescription, 65 | ) -> None: 66 | super().__init__(coordinator) 67 | 68 | self._attr_device_info = coordinator.device_info 69 | self._attr_unique_id = f"{coordinator.unique_id}_{DOMAIN}_{description.key}" 70 | self.entity_description = description 71 | 72 | async def async_press(self) -> None: 73 | await self.entity_description.method(self.coordinator) 74 | -------------------------------------------------------------------------------- /cfg/hass/pkgs/tracker.yaml: -------------------------------------------------------------------------------- 1 | 2 | input_boolean: 3 | disable_ap_c7: 4 | name: Disable admin 5 | tracker_neta_enabled: 6 | name: neta_at_home 7 | 8 | 9 | device_tracker: 10 | #- platform: ping 11 | # interval_seconds: 60 12 | # consider_home: 400 13 | # hosts: 14 | # tv : tv.hhaim.home 15 | 16 | 17 | # huawei_ws7200 can scan all the network using arp/ipv6 so there is no need the others 18 | - platform: huawei_ws7200 19 | interval_seconds: 20 20 | consider_home: 400 21 | host: 10.0.0.145 22 | username: !secret ap_user3 23 | password: !secret ap_password3 24 | firmware: ws7200 25 | zname: work 26 | 27 | # huawei_ws7200 can scan all the network using arp/ipv6 so there is no need the others 28 | - platform: huawei_ws7200 29 | interval_seconds: 20 30 | consider_home: 400 31 | host: 10.0.0.146 32 | username: !secret ap_user3 33 | password: !secret ap_password3 34 | firmware: ws7200 35 | zname: tv 36 | 37 | # huawei_ws7200 can scan all the network using arp/ipv6 so there is no need the others 38 | - platform: huawei_ws7200 39 | interval_seconds: 20 40 | consider_home: 400 41 | host: 10.0.0.147 42 | username: !secret ap_user3 43 | password: !secret ap_password3 44 | firmware: ws7200 45 | zname: k1 46 | 47 | - platform: huawei_ws7200 48 | interval_seconds: 20 49 | consider_home: 400 50 | host: 10.0.0.148 51 | username: !secret ap_user3 52 | password: !secret ap_password3 53 | firmware: ws7200 54 | zname: washing 55 | 56 | - platform: luci 57 | interval_seconds: 20 58 | consider_home: 400 59 | host: 10.0.0.138 60 | username: !secret ap_user4 61 | password: !secret ap_password4 62 | 63 | 64 | #dummy 65 | variable: 66 | tracker_neta: 67 | value: "not_home" 68 | restore: true 69 | attributes: 70 | friendly_name: 'Neta' 71 | unit_of_measurement: "" 72 | icon: mdi:human-child 73 | 74 | 75 | group: 76 | 77 | device_tracker: 78 | name: Tracker 79 | entities: 80 | - device_tracker.daniel_phone 81 | - device_tracker.hhaim_phone 82 | - device_tracker.nitay_phone 83 | - device_tracker.nitay_pc 84 | - device_tracker.rivi_phone 85 | - device_tracker.sony_ps 86 | - device_tracker.tv 87 | - device_tracker.tv_wifi 88 | - device_tracker.daniel_phone1 89 | - device_tracker.daniel_phone2 90 | - variable.tracker_neta 91 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /netdc/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.5' 2 | 3 | # network dnsmasq, 4 | # DOCKER COMPOSE COMMAND REFERENCE 5 | # -------------------------------------------------------------------------- 6 | # Start | docker-compose up -d 7 | # Stop | docker-compose stop 8 | # Update | docker-compose pull 9 | # Logs | docker-compose logs --tail=25 -f 10 | # debug : docker-compose exec inlfuxdb bash --> get into one of 11 | # issues: dnsmasq does not use dhcp database to return local hosts (fixed), not sure why. without docker it works fine 12 | # 13 | 14 | 15 | services: 16 | dnsmasq: 17 | container_name: dnsmasq 18 | image: 4km3/dnsmasq:2.85-r2 19 | volumes: 20 | - ./cfg/dnsmasq/dnsmasq.conf:/etc/dnsmasq.conf 21 | - ./cfg/dnsmasq/dns_resolv.conf:/etc/dns_resolv.conf 22 | - ./cfg/dnsmasq/dnsmasq_hosts.conf:/etc/dnsmasq_hosts.conf 23 | - ./store/dnsmasq:/var/lib/misc 24 | restart: unless-stopped 25 | network_mode: host 26 | #user: "${UID}:${GID}" 27 | environment: 28 | TZ: ${TZ} 29 | cap_add: 30 | - NET_ADMIN 31 | - NET_RAW 32 | #command: "-K -d --log-dhcp --domain=hhaim.home" # for debug 33 | wireguard: 34 | container_name: wireguard 35 | image: ghcr.io/linuxserver/wireguard:1.0.20210914 36 | restart: unless-stopped 37 | environment: 38 | - PUID=${UID} 39 | - PGID=${GID} 40 | - TZ=${TZ} 41 | - SERVERURL=${EXT_DNS} 42 | - SERVERPORT=51820 43 | - PEERS=iphone,laptop1,pho1 44 | - PEERDNS=auto 45 | - ALLOWEDIPS=10.13.13.0/24,10.0.0.0/24 46 | ports: 47 | - "51820:51820/udp" 48 | volumes: 49 | - ./cfg/wireguard:/config 50 | - /lib/modules:/lib/modules:ro 51 | cap_add: 52 | - NET_ADMIN 53 | - SYS_MODULE 54 | sysctls: 55 | - net.ipv4.conf.all.src_valid_mark=1 56 | 57 | # for debug 58 | #simple-web: 59 | # depends_on: 60 | # - wireguard 61 | # container_name: simple-web 62 | # image: yeasy/simple-web:latest 63 | # restart: unless-stopped 64 | # environment: 65 | # - PUID=${UID} 66 | # - PGID=${GID} 67 | # ports: 68 | # - "9999:80/tcp" 69 | 70 | dnscrypt: 71 | image: melchor9000/dnscrypt-proxy:2.1.5 72 | user: "${UID}:${GID}" 73 | environment: 74 | TZ: ${TZ} 75 | #ports: 76 | #- "5353:5353/udp" 77 | restart: always 78 | volumes: 79 | - ./cfg/dnscrypt-proxy:/etc/dnscrypt-proxy 80 | network_mode: host 81 | 82 | 83 | 84 | #network_mode: service:wireguard 85 | # networks: 86 | # - backbone 87 | 88 | #networks: 89 | # netipv6: 90 | # enable_ipv6: true 91 | # driver: bridge 92 | 93 | -------------------------------------------------------------------------------- /cfg/hass/custom_components/asusrouter/dataclass.py: -------------------------------------------------------------------------------- 1 | """AsusRouter dataclass module.""" 2 | 3 | from __future__ import annotations 4 | 5 | from collections.abc import Callable 6 | from dataclasses import dataclass 7 | from typing import Any, Optional 8 | 9 | from asusrouter.modules.state import AsusState, AsusStateNone 10 | from homeassistant.components.binary_sensor import BinarySensorEntityDescription 11 | from homeassistant.components.button import ButtonEntityDescription 12 | from homeassistant.components.light import LightEntityDescription 13 | from homeassistant.components.sensor import SensorEntityDescription 14 | from homeassistant.components.switch import SwitchEntityDescription 15 | from homeassistant.components.update import UpdateEntityDescription 16 | from homeassistant.helpers.entity import EntityDescription 17 | 18 | 19 | @dataclass 20 | class AREntityDescription(EntityDescription): 21 | """Describe AsusRouter entity.""" 22 | 23 | capabilities: Optional[dict[str, Any]] = None 24 | key_group: str = "" 25 | value: Callable[[Any], Any] = lambda val: val 26 | extra_state_attributes: Optional[dict[str, Any]] = None 27 | 28 | 29 | @dataclass 30 | class ARBinaryDescription(AREntityDescription, BinarySensorEntityDescription): 31 | """Describe AsusRouter binary entity.""" 32 | 33 | icon_on: Optional[str] = None 34 | icon_off: Optional[str] = None 35 | 36 | 37 | @dataclass 38 | class ARSensorDescription(AREntityDescription, SensorEntityDescription): 39 | """Describe AsusRouter sensor.""" 40 | 41 | factor: Optional[int] = None 42 | precision: int = 3 43 | 44 | 45 | @dataclass 46 | class ARBinarySensorDescription(ARBinaryDescription, BinarySensorEntityDescription): 47 | """Describe AsusRouter sensor.""" 48 | 49 | 50 | @dataclass 51 | class ARLightDescription(ARBinaryDescription, LightEntityDescription): 52 | """Describe AsusRouter light.""" 53 | 54 | 55 | @dataclass 56 | class ARSwitchDescription(AREntityDescription, SwitchEntityDescription): 57 | """Describe AsusRouter switch.""" 58 | 59 | icon_on: Optional[str] = None 60 | icon_off: Optional[str] = None 61 | 62 | state_on: AsusState = AsusStateNone.NONE 63 | state_on_args: Optional[dict[str, Any]] = None 64 | state_off: AsusState = AsusStateNone.NONE 65 | state_off_args: Optional[dict[str, Any]] = None 66 | 67 | state_expect_modify: bool = False 68 | 69 | 70 | @dataclass 71 | class ARButtonDescription(AREntityDescription, ButtonEntityDescription): 72 | """Describe AsusRouter button.""" 73 | 74 | state: AsusState = AsusStateNone.NONE 75 | state_args: Optional[dict[str, Any]] = None 76 | state_expect_modify: bool = False 77 | 78 | 79 | @dataclass 80 | class ARUpdateDescription(AREntityDescription, UpdateEntityDescription): 81 | """Describe AsusRouter update.""" 82 | -------------------------------------------------------------------------------- /netdc/cfg/dnscrypt-proxy/localhost.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDb7g6EQhbfby97 3 | k4oMbZTzdi2TWFBs7qK/QwgOu+L6EhNHPO1ZEU29v0APFBFJO5zyyAk9bZ9k9tPB 4 | bCuVVI9jEUfLH3UCjEQPG6XI2w++uVh0yALvc/uurCvRHVlle/V7cAoikndc2SjE 5 | RQUALbACIqwD5g0F77BYwcsreB4GH253/R6Q2/CJZ4jNHPjkocOJiVr3ejA0kkoN 6 | MXpGUXWcrVVk20M2A1CeO7HAulLRcklEdoHE3v46pjp0iZK0F9LyZX1U1ql+4QL3 7 | iQttoZ4tMg83lFHSt4G9PrpIhzXr9W4NW822faSvrIwwN/JbItUmRa7n/3+MkuJQ 8 | IGGNDayXAgMBAAECggEBANs0fmGSocuXvYL1Pi4+9qxnCOwIpTi97Zam0BwnZwcL 9 | Bw4FCyiwV4UdX1LoFIailT9i49rHLYzre4oZL6OKgdQjQCSTuQOOHLPWQbpdpWba 10 | w/C5/jr+pkemMZIfJ6BAGiArPt7Qj4oKpFhj1qUj5H9sYXkNTcOx8Fm25rLv6TT9 11 | O7wg0oCpyG+iBSbCYBp9mDMz8pfo4P3BhcFiyKCKeiAC6KuHU81dvuKeFB4XQK+X 12 | no2NqDqe6MBkmTqjNNy+wi1COR7lu34LPiWU5Hq5PdIEqBBUMjlMI6oYlhlgNTdx 13 | SvsqFz3Xs6kpAhJTrSiAqscPYosgaMQxo+LI26PJnikCgYEA9n0OERkm0wSBHnHY 14 | Kx8jaxNYg93jEzVnEgI/MBTJZqEyCs9fF6Imv737VawEN/BhesZZX7bGZQfDo8AT 15 | aiSa5upkkSGXEqTu5ytyoKFTb+dJ/qmx3+zP6dPVzDnc8WPYMoUg7vvjZkXXJgZX 16 | +oMlMUW1wWiDNI3wP19W9Is6xssCgYEA5GqkUBEns6eTFJV0JKqbEORJJ7lx5NZe 17 | cIx+jPpLkILG4mOKOg1TBx0wkxa9cELtsNsM+bPtu9OqRMhsfPBmsXDHhJwg0Z6G 18 | eDTfYYPkpRhwZvl6jBZn9sLVR9wfg2hE+n0lfV3mceg336KOkwAehDU84SWZ2e0S 19 | esqkpbHJa+UCgYA7PY0O8POSzcdWkNf6bS5vAqRIdSCpMjGGc4HKRYSuJNnJHVPm 20 | czNK7Bcm3QPaiexzvI4oYd5G09niVjyUSx3rl7P56Y/MjFVau+d90agjAfyXtyMo 21 | BVtnAGGnBtUiMvP4GGT06xcZMnnmCqpEbBaZQ/7N8Bdwnxh5sqlMdtX2hwKBgAhL 22 | hyQRO2vezgyVUN50A6WdZLq4lVZGIq/bqkzcWhopZaebDc4F5doASV9OGBsXkyI1 23 | EkePLTcA/NH6pVX0NQaEnfpG4To7k46R/PrBm3ATbyGONdEYjzX65VvytoJDKx4d 24 | pVrkKhZA5KaOdLcJ7hHHDSrv/qJXZbBn44rQ5guxAoGBAJ6oeUsUUETakxlmIhmK 25 | xuQmWqLf97BKt8r6Z8CqHKWK7vpG2OmgFYCQGaR7angQ8hmAOv6jM56XhoagDBoc 26 | UoaoEyo9/uCk6NRUkUMj7Tk/5UQSiWLceVH27w+icMFhf1b7EmmNfk+APsiathO5 27 | j4edf1AinVCPwRVVu1dtLL5P 28 | -----END PRIVATE KEY----- 29 | -----BEGIN CERTIFICATE----- 30 | MIIDAjCCAeoCCQCptj0+TjjIJjANBgkqhkiG9w0BAQsFADBDMREwDwYDVQQKDAhE 31 | TlNDcnlwdDEaMBgGA1UECwwRTG9jYWwgdGVzdCBzZXJ2ZXIxEjAQBgNVBAMMCWxv 32 | Y2FsaG9zdDAeFw0xOTExMTgxNDA2MzBaFw0zMzA3MjcxNDA2MzBaMEMxETAPBgNV 33 | BAoMCEROU0NyeXB0MRowGAYDVQQLDBFMb2NhbCB0ZXN0IHNlcnZlcjESMBAGA1UE 34 | AwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2+4O 35 | hEIW328ve5OKDG2U83Ytk1hQbO6iv0MIDrvi+hITRzztWRFNvb9ADxQRSTuc8sgJ 36 | PW2fZPbTwWwrlVSPYxFHyx91AoxEDxulyNsPvrlYdMgC73P7rqwr0R1ZZXv1e3AK 37 | IpJ3XNkoxEUFAC2wAiKsA+YNBe+wWMHLK3geBh9ud/0ekNvwiWeIzRz45KHDiYla 38 | 93owNJJKDTF6RlF1nK1VZNtDNgNQnjuxwLpS0XJJRHaBxN7+OqY6dImStBfS8mV9 39 | VNapfuEC94kLbaGeLTIPN5RR0reBvT66SIc16/VuDVvNtn2kr6yMMDfyWyLVJkWu 40 | 5/9/jJLiUCBhjQ2slwIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQA6Vz5HnGuy8jZz 41 | 5i8ipbcDMCZNdpYYnxgD53hEKOfoSv7LaF0ztD8Kmg3s5LHv9EHlkK3+G6FWRGiP 42 | 9f6IbtRITaiVQP3M13T78hpN5Qq5jgsqjR7ZcN7Etr6ZFd7G/0+mzqbyBuW/3szt 43 | RdX/YLy1csvjbZoNNuXGWRohXjg0Mjko2tRLmARvxA/gZV5zWycv3BD2BPzyCdS9 44 | MDMYSF0RPiL8+alfwLNqLcqMA5liHlmZa85uapQyoUI3ksKJkEgU53aD8cYhH9Yn 45 | 6mVpsrvrcRLBiHlbi24QBolhFkCSRK8bXes8XDIPuD8iYRwlrVBwOakMFQWMqNfI 46 | IMOKJomU 47 | -----END CERTIFICATE----- 48 | -------------------------------------------------------------------------------- /utility.py: -------------------------------------------------------------------------------- 1 | 2 | import argparse 3 | import os 4 | import sys 5 | import subprocess 6 | 7 | #--d-dnsmasq 8 | #--d-mq 9 | #+--sync-hass 10 | # 11 | #--sync-all 12 | #--sync-cc 13 | 14 | def SetParserOptions(): 15 | parser = argparse.ArgumentParser(prog="utility.py") 16 | 17 | parser.add_argument("--sync-hass", 18 | dest="sync_hass", 19 | help="sync hass info", 20 | action="store_true", 21 | default=False) 22 | 23 | parser.add_argument("--sync-net", 24 | dest="sync_net", 25 | help="sync ad ", 26 | action="store_true", 27 | default=False) 28 | 29 | parser.add_argument("--d-dnsmasq", 30 | dest="ddns", 31 | help="dump dns_masq ", 32 | action="store_true", 33 | default=False) 34 | 35 | parser.add_argument("--d-mq ", 36 | dest="dmq", 37 | help="dump mosquito log ", 38 | action="store_true", 39 | default=False) 40 | 41 | return parser 42 | 43 | RH = os.getenv('REMOTE_HASS') 44 | if RH == None: 45 | print(" you must define remote addr of hass as raw ip") 46 | exit(-1) 47 | 48 | REMOTE_PATH = '/home/hhaim/' 49 | REMOTE_NETDC_STORE = REMOTE_PATH+'hass/netdc/store/' 50 | REMOTE_HASS_STORE = REMOTE_PATH+'hass/store/' 51 | REMOTE_HASS = REMOTE_PATH+'hass/' 52 | REMOTE_NETDC =REMOTE_HASS+"netdc/" 53 | 54 | 55 | def get_ssh_cmd(): 56 | cmd ='ssh {} '.format(RH) 57 | return cmd 58 | 59 | def get_rsync_cmd(): 60 | cmd ='rsync -avz . - '.format(RH) 61 | return cmd 62 | 63 | def get_r_cmd(cmd): 64 | return "'" + cmd + "'" 65 | 66 | def get_dump_dm(): 67 | cmd=get_ssh_cmd()+get_r_cmd('cat '+REMOTE_NETDC_STORE+'dnsmasq/dnsmasq.leases') 68 | return cmd 69 | 70 | def get_dump_mq(): 71 | cmd=get_ssh_cmd()+get_r_cmd('cat '+REMOTE_HASS_STORE+'mosquitto/log/mosquitto.log') 72 | return cmd 73 | 74 | def get_sync_hass(): 75 | cmd ='rsync -avz --exclude=".git" --exclude="netdc" --exclude="cfg/hass/known_devices.yaml" --exclude="linux_services" --exclude="services" . {}:{} '.format(RH,REMOTE_HASS) 76 | return cmd 77 | 78 | def get_sync_net(): 79 | cmd ='rsync -avz netdc/ {}:{} '.format(RH,REMOTE_NETDC) 80 | return cmd 81 | 82 | def run_cmd(cmd): 83 | print('run :'+cmd) 84 | os.system(cmd) 85 | 86 | def main(args=None): 87 | 88 | parser = SetParserOptions() 89 | if args is None: 90 | opts = parser.parse_args() 91 | else: 92 | opts = parser.parse_args(args) 93 | 94 | if opts.ddns: 95 | run_cmd(get_dump_dm()) 96 | if opts.dmq: 97 | run_cmd(get_dump_mq()) 98 | 99 | if opts.sync_hass: 100 | run_cmd(get_sync_hass()) 101 | if opts.sync_net: 102 | run_cmd(get_sync_net()) 103 | 104 | 105 | 106 | if __name__ == '__main__': 107 | main() -------------------------------------------------------------------------------- /cfg/hass/custom_components/tplink_router/coordinator.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | from datetime import timedelta, datetime 3 | from logging import Logger 4 | from collections.abc import Callable 5 | from homeassistant.helpers.update_coordinator import DataUpdateCoordinator 6 | from tplinkrouterc6u import TplinkRouterProvider, AbstractRouter, Firmware, Status, Connection 7 | from homeassistant.core import HomeAssistant 8 | from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC, DeviceInfo 9 | from .const import ( 10 | DOMAIN, 11 | DEFAULT_NAME, 12 | ) 13 | 14 | 15 | class TPLinkRouterCoordinator(DataUpdateCoordinator): 16 | def __init__( 17 | self, 18 | hass: HomeAssistant, 19 | router: AbstractRouter, 20 | update_interval: int, 21 | firmware: Firmware, 22 | status: Status, 23 | logger: Logger, 24 | unique_id: str 25 | ) -> None: 26 | self.router = router 27 | self.unique_id = unique_id 28 | self.status = status 29 | self.device_info = DeviceInfo( 30 | configuration_url=router.host, 31 | connections={(CONNECTION_NETWORK_MAC, self.status.lan_macaddr)}, 32 | identifiers={(DOMAIN, self.status.lan_macaddr)}, 33 | manufacturer="TPLink", 34 | model=firmware.model, 35 | name=DEFAULT_NAME, 36 | sw_version=firmware.firmware_version, 37 | hw_version=firmware.hardware_version, 38 | ) 39 | 40 | self.scan_stopped_at: datetime | None = None 41 | 42 | super().__init__( 43 | hass, 44 | logger, 45 | name=DOMAIN, 46 | update_interval=timedelta(seconds=update_interval), 47 | ) 48 | 49 | @staticmethod 50 | async def get_client(hass: HomeAssistant, host: str, password: str, username: str, logger: Logger, 51 | verify_ssl: bool) -> AbstractRouter: 52 | return await hass.async_add_executor_job(TplinkRouterProvider.get_client, host, password, username, 53 | logger, verify_ssl) 54 | 55 | @staticmethod 56 | def request(router: AbstractRouter, callback: Callable): 57 | router.authorize() 58 | data = callback() 59 | router.logout() 60 | 61 | return data 62 | 63 | async def reboot(self) -> None: 64 | await self.hass.async_add_executor_job(TPLinkRouterCoordinator.request, self.router, self.router.reboot) 65 | 66 | async def set_wifi(self, wifi: Connection, enable: bool) -> None: 67 | def callback(): 68 | self.router.set_wifi(wifi, enable) 69 | 70 | await self.hass.async_add_executor_job(TPLinkRouterCoordinator.request, self.router, callback) 71 | 72 | async def _async_update_data(self): 73 | """Asynchronous update of all data.""" 74 | if self.scan_stopped_at is not None and self.scan_stopped_at > (datetime.now() - timedelta(minutes=20)): 75 | return 76 | self.scan_stopped_at = None 77 | self.status = await self.hass.async_add_executor_job(TPLinkRouterCoordinator.request, self.router, 78 | self.router.get_status) 79 | -------------------------------------------------------------------------------- /cfg/hass/custom_components/asusrouter/update.py: -------------------------------------------------------------------------------- 1 | """AsusRouter update module.""" 2 | 3 | from __future__ import annotations 4 | 5 | import asyncio 6 | import logging 7 | from typing import Any 8 | 9 | from homeassistant.components.update import UpdateEntity, UpdateEntityFeature 10 | from homeassistant.config_entries import ConfigEntry 11 | from homeassistant.core import HomeAssistant 12 | from homeassistant.helpers.entity_platform import AddEntitiesCallback 13 | from homeassistant.helpers.update_coordinator import DataUpdateCoordinator 14 | 15 | from asusrouter.modules.system import AsusSystem 16 | 17 | from .const import STATIC_UPDATES 18 | from .dataclass import ARUpdateDescription 19 | from .entity import ARBinaryEntity, async_setup_ar_entry 20 | from .router import ARDevice 21 | 22 | _LOGGER = logging.getLogger(__name__) 23 | 24 | 25 | async def async_setup_entry( 26 | hass: HomeAssistant, 27 | config_entry: ConfigEntry, 28 | async_add_entities: AddEntitiesCallback, 29 | ) -> None: 30 | """Set up AsusRouter updates.""" 31 | 32 | updates = STATIC_UPDATES.copy() 33 | 34 | await async_setup_ar_entry( 35 | hass, config_entry, async_add_entities, updates, ARUpdate 36 | ) 37 | 38 | 39 | class ARUpdate(ARBinaryEntity, UpdateEntity): 40 | """AsusRouter update.""" 41 | 42 | def __init__( 43 | self, 44 | coordinator: DataUpdateCoordinator, 45 | router: ARDevice, 46 | description: ARUpdateDescription, 47 | ) -> None: 48 | """Initialize AsusRouter update.""" 49 | 50 | super().__init__(coordinator, router, description) 51 | self.entity_description: ARUpdateDescription = description 52 | 53 | self._attr_installed_version = self.extra_state_attributes.get( 54 | "current" 55 | ) 56 | self._attr_latest_version = self.extra_state_attributes.get("latest") 57 | self._attr_release_summary = self.extra_state_attributes.get( 58 | "release_note" 59 | ) 60 | self._attr_in_progress = False 61 | 62 | self._attr_supported_features = ( 63 | UpdateEntityFeature.RELEASE_NOTES 64 | | UpdateEntityFeature.INSTALL 65 | | UpdateEntityFeature.PROGRESS 66 | ) 67 | 68 | def release_notes(self) -> str | None: 69 | """Return the release notes.""" 70 | 71 | return self.extra_state_attributes.get("release_note") 72 | 73 | async def async_install( 74 | self, version: str | None, backup: bool, **kwargs: Any 75 | ) -> None: 76 | """Install the update.""" 77 | try: 78 | _LOGGER.debug( 79 | "Trying to install Firmware update. This might take several minutes." 80 | ) 81 | result = await self.api.async_set_state( 82 | state=AsusSystem.FIRMWARE_UPGRADE, 83 | ) 84 | if not result: 85 | _LOGGER.debug( 86 | "Something went wrong while trying to install the update." 87 | ) 88 | else: 89 | self._attr_in_progress = True 90 | await asyncio.sleep(120) 91 | self._attr_in_progress = False 92 | 93 | except Exception as ex: # pylint: disable=broad-except 94 | _LOGGER.error( 95 | "An exception occurred while trying to install the update: %s", 96 | ex, 97 | ) 98 | -------------------------------------------------------------------------------- /cfg/hass/custom_components/asusrouter/diagnostics.py: -------------------------------------------------------------------------------- 1 | """AsusRouter diagnostics module.""" 2 | 3 | from __future__ import annotations 4 | 5 | from typing import Any 6 | 7 | from homeassistant.components.diagnostics import async_redact_data 8 | from homeassistant.config_entries import ConfigEntry 9 | from homeassistant.core import HomeAssistant 10 | from homeassistant.helpers import device_registry as dr 11 | from homeassistant.helpers import entity_registry as er 12 | 13 | from .const import ( 14 | ASUSROUTER, 15 | DEVICE_ATTRIBUTE_LAST_ACTIVITY, 16 | DOMAIN, 17 | TO_REDACT, 18 | TO_REDACT_ATTRS, 19 | TO_REDACT_DEV, 20 | TO_REDACT_STATE, 21 | ) 22 | from .router import ARDevice 23 | 24 | 25 | async def async_get_config_entry_diagnostics( 26 | hass: HomeAssistant, 27 | entry: ConfigEntry, 28 | ) -> dict[str, dict[str, Any]]: 29 | """Return diagnostics for a config entry.""" 30 | 31 | data = {"entry": async_redact_data(entry.as_dict(), TO_REDACT)} 32 | 33 | router: ARDevice = hass.data[DOMAIN][entry.entry_id][ASUSROUTER] 34 | 35 | # Gather information how this device is represented in Home Assistant 36 | device_registry = dr.async_get(hass) 37 | entity_registry = er.async_get(hass) 38 | hass_device = device_registry.async_get_device( 39 | identifiers=router.device_info["identifiers"] 40 | ) 41 | if not hass_device: 42 | return data 43 | 44 | data["device"] = { 45 | **async_redact_data(hass_device.dict_repr, TO_REDACT_DEV), 46 | "entities": {}, 47 | "tracked_devices": [], 48 | } 49 | 50 | hass_entities = er.async_entries_for_device( 51 | entity_registry, 52 | device_id=hass_device.id, 53 | include_disabled_entities=True, 54 | ) 55 | 56 | for entity_entry in hass_entities: 57 | state = hass.states.get(entity_entry.entity_id) 58 | state_dict = None 59 | if state: 60 | state_dict = dict(state.as_dict()) 61 | # The entity_id is already provided at root level. 62 | state_dict.pop("entity_id", None) 63 | # The context doesn't provide useful information in this case. 64 | state_dict.pop("context", None) 65 | # Remove sensitive info from attributes. 66 | if "attributes" in state_dict: 67 | state_dict["attributes"] = async_redact_data( 68 | dict(state_dict["attributes"]), TO_REDACT_ATTRS 69 | ) 70 | # Remove sensitive info from sensors states. 71 | if entity_entry.original_name in TO_REDACT_STATE: 72 | state_dict = async_redact_data(state_dict, "state") 73 | 74 | data["device"]["entities"][entity_entry.entity_id] = { 75 | **async_redact_data( 76 | entity_entry.as_partial_dict, 77 | TO_REDACT, 78 | ), 79 | "state": state_dict, 80 | } 81 | 82 | for device in router.devices.values(): 83 | data["device"]["tracked_devices"].append( 84 | { 85 | "name": device.name, 86 | "ip_address": device.ip_address, 87 | "last_activity": device.extra_state_attributes[ 88 | DEVICE_ATTRIBUTE_LAST_ACTIVITY 89 | ] 90 | if DEVICE_ATTRIBUTE_LAST_ACTIVITY in device.extra_state_attributes 91 | else None, 92 | } 93 | ) 94 | 95 | return data 96 | -------------------------------------------------------------------------------- /cfg/hass/custom_components/asusrouter/__init__.py: -------------------------------------------------------------------------------- 1 | """Support for AsusRouter devices.""" 2 | 3 | from __future__ import annotations 4 | 5 | import logging 6 | 7 | from homeassistant.config_entries import ConfigEntry 8 | from homeassistant.const import EVENT_HOMEASSISTANT_STOP 9 | from homeassistant.core import HomeAssistant 10 | from homeassistant.helpers.device_registry import DeviceEntry 11 | 12 | from .const import ASUSROUTER, DOMAIN, PLATFORMS, STOP_LISTENER 13 | from .router import ARDevice 14 | 15 | _LOGGER = logging.getLogger(__name__) 16 | 17 | 18 | async def async_setup_entry( 19 | hass: HomeAssistant, 20 | config_entry: ConfigEntry, 21 | ) -> bool: 22 | """Set up AsusRouter platform.""" 23 | 24 | _LOGGER.debug("Setting up entry") 25 | 26 | router = ARDevice(hass, config_entry) 27 | await router.setup() 28 | 29 | router.async_on_close(config_entry.add_update_listener(update_listener)) 30 | 31 | async def async_close_connection(event): 32 | """Close router connection on HA stop.""" 33 | 34 | await router.close() 35 | 36 | stop_listener = hass.bus.async_listen_once( 37 | EVENT_HOMEASSISTANT_STOP, async_close_connection 38 | ) 39 | 40 | hass.data.setdefault(DOMAIN, {})[config_entry.entry_id] = { 41 | ASUSROUTER: router, 42 | STOP_LISTENER: stop_listener, 43 | } 44 | 45 | await hass.config_entries.async_forward_entry_setups(config_entry, PLATFORMS) 46 | 47 | return True 48 | 49 | 50 | async def async_unload_entry( 51 | hass: HomeAssistant, 52 | config_entry: ConfigEntry, 53 | ) -> bool: 54 | """Unload AsusRouter config entry.""" 55 | 56 | _LOGGER.debug("Unloading entry") 57 | 58 | unload = await hass.config_entries.async_unload_platforms(config_entry, PLATFORMS) 59 | 60 | if unload: 61 | # Close connection 62 | hass.data[DOMAIN][config_entry.entry_id][STOP_LISTENER]() 63 | await hass.data[DOMAIN][config_entry.entry_id][ASUSROUTER].close() 64 | hass.data[DOMAIN].pop(config_entry.entry_id) 65 | 66 | return unload 67 | 68 | 69 | async def update_listener( 70 | hass: HomeAssistant, 71 | config_entry: ConfigEntry, 72 | ) -> None: 73 | """Reload on config entry update.""" 74 | 75 | _LOGGER.debug("Update listener activated") 76 | 77 | router = hass.data[DOMAIN][config_entry.entry_id][ASUSROUTER] 78 | 79 | if router.update_options(config_entry.options): 80 | await hass.config_entries.async_reload(config_entry.entry_id) 81 | 82 | return 83 | 84 | 85 | # Example migration function 86 | async def async_migrate_entry(hass, config_entry: ConfigEntry): 87 | """Migrate old entry.""" 88 | 89 | _LOGGER.debug("Migrating from version %s", config_entry.version) 90 | 91 | if config_entry.version == 4: 92 | new_options = {**config_entry.options} 93 | new_options["interval_network"] = new_options.pop("interval_network_stat", 30) 94 | 95 | config_entry.version = 5 96 | hass.config_entries.async_update_entry(config_entry, options=new_options) 97 | 98 | _LOGGER.debug("Migration to version %s successful", config_entry.version) 99 | 100 | return True 101 | 102 | 103 | async def async_remove_config_entry_device( 104 | hass: HomeAssistant, config_entry: ConfigEntry, device_entry: DeviceEntry 105 | ) -> bool: 106 | """Remove a device.""" 107 | 108 | # This would actually work and should not provide any issues 109 | 110 | _LOGGER.debug("Removing device") 111 | 112 | return True 113 | -------------------------------------------------------------------------------- /cfg/hass/custom_components/asusrouter/button.py: -------------------------------------------------------------------------------- 1 | """AsusRouter button module.""" 2 | 3 | from __future__ import annotations 4 | 5 | import logging 6 | from typing import Any, Optional 7 | 8 | from asusrouter.modules.state import AsusState 9 | from homeassistant.components.button import ButtonEntity 10 | from homeassistant.config_entries import ConfigEntry 11 | from homeassistant.core import HomeAssistant 12 | from homeassistant.helpers.entity_platform import AddEntitiesCallback 13 | 14 | from .const import ( 15 | ASUSROUTER, 16 | CONF_MODE, 17 | DOMAIN, 18 | ROUTER, 19 | STATIC_BUTTONS, 20 | STATIC_BUTTONS_OPTIONAL, 21 | ) 22 | from .dataclass import ARButtonDescription 23 | from .helpers import to_unique_id 24 | from .router import ARDevice 25 | 26 | _LOGGER = logging.getLogger(__name__) 27 | 28 | 29 | async def async_setup_entry( 30 | hass: HomeAssistant, 31 | config_entry: ConfigEntry, 32 | async_add_entities: AddEntitiesCallback, 33 | ) -> None: 34 | """Set up AsusRouter buttons.""" 35 | 36 | buttons = STATIC_BUTTONS.copy() 37 | 38 | router: ARDevice = hass.data[DOMAIN][config_entry.entry_id][ASUSROUTER] 39 | entities = [] 40 | 41 | if config_entry.options.get(CONF_MODE) == ROUTER: 42 | buttons.extend(STATIC_BUTTONS_OPTIONAL) 43 | 44 | for button in buttons: 45 | try: 46 | entities.append(ARButton(router, button)) 47 | except Exception as ex: # pylint: disable=broad-except 48 | _LOGGER.warning(ex) 49 | 50 | async_add_entities(entities) 51 | 52 | 53 | class ARButton(ButtonEntity): 54 | """AsusRouter button.""" 55 | 56 | def __init__( 57 | self, 58 | router: ARDevice, 59 | description: ARButtonDescription, 60 | ) -> None: 61 | """Initialize AsusRouter button.""" 62 | 63 | self.router = router 64 | self.api = router.bridge.api 65 | 66 | self._attr_device_info = router.device_info 67 | self._attr_name = f"{router._conf_name} {description.name}" 68 | self._attr_unique_id = to_unique_id(f"{router.mac}_{description.name}") 69 | self._attr_capability_attributes = description.capabilities 70 | 71 | self._state = description.state 72 | self._state_args = description.state_args 73 | self._state_expect_modify = description.state_expect_modify 74 | 75 | if description.icon: 76 | self._attr_icon = description.icon 77 | 78 | async def async_press( 79 | self, 80 | **kwargs: Any, 81 | ) -> None: 82 | """Press button.""" 83 | 84 | kwargs = self._state_args if self._state_args is not None else {} 85 | 86 | await self._set_state( 87 | state=self._state, 88 | expect_modify=self._state_expect_modify, 89 | **kwargs, 90 | ) 91 | 92 | async def _set_state( 93 | self, 94 | state: AsusState, 95 | expect_modify: bool = False, 96 | **kwargs: Any, 97 | ) -> None: 98 | """Set switch state.""" 99 | 100 | try: 101 | _LOGGER.debug("Pressing %s", state) 102 | result = await self.api.async_set_state( 103 | state=state, expect_modify=expect_modify, **kwargs 104 | ) 105 | if not result: 106 | _LOGGER.debug("Didn't manage to press %s", state) 107 | except Exception as ex: # pylint: disable=broad-except 108 | _LOGGER.error("Pressing %s caused an exception: %s", state, ex) 109 | -------------------------------------------------------------------------------- /cfg/hass/custom_components/asusrouter/modules/aura.py: -------------------------------------------------------------------------------- 1 | """Aura RGB module for AsusRouter integration. 2 | 3 | This module allows converting AsusRouter Aura data to the 4 | Home Assistant format. This is required due to the complex 5 | data structure and effect handling by Home Assistant. 6 | """ 7 | 8 | from __future__ import annotations 9 | 10 | from typing import Any 11 | 12 | from homeassistant.components.light import ColorMode 13 | from homeassistant.const import EntityCategory 14 | 15 | from asusrouter.modules.color import ColorRGBB 16 | from asusrouter.tools.converters import scale_value_int 17 | 18 | from ..const import ICON_LIGHT_OFF, ICON_LIGHT_ON 19 | from ..dataclass import AREntityDescription, ARLightDescription 20 | 21 | AURA_NO_EFFECT = "EFFECT_OFF" 22 | AURA_FALLBACK_BRIGHTNESS = 128 23 | AURA_FALLBACK_COLOR = ColorRGBB( 24 | (128, 128, 128), 25 | AURA_FALLBACK_BRIGHTNESS, 26 | scale=128, 27 | ) 28 | 29 | AURA_EFFECTS = [ 30 | "Gradient", 31 | "Static", 32 | "Breathing", 33 | "Evolution", 34 | "Rainbow", 35 | "Wave", 36 | "Marquee", 37 | ] 38 | 39 | AURA_EFFECTS_MAP = { 40 | 0: AURA_NO_EFFECT, 41 | 1: "Gradient", 42 | 2: "Static", 43 | 3: "Breathing", 44 | 4: "Evolution", 45 | 5: "Rainbow", 46 | 6: "Wave", 47 | 7: "Marquee", 48 | } 49 | 50 | SUPPORTED_COLOR_MODES = { 51 | 0: ColorMode.ONOFF, 52 | 1: ColorMode.RGB, 53 | 2: ColorMode.RGB, 54 | 3: ColorMode.RGB, 55 | 4: ColorMode.ONOFF, 56 | 5: ColorMode.ONOFF, 57 | 6: ColorMode.ONOFF, 58 | 7: ColorMode.RGB, 59 | } 60 | 61 | ACTIVE_BRIGHTNESS = "active_brightness" 62 | ACTIVE_COLOR = "active_color" 63 | BRIGHTNESS = "brightness" 64 | RGB_COLOR = "rgb_color" 65 | SCHEME = "scheme" 66 | STATE = "state" 67 | ZONES = "zones" 68 | 69 | 70 | def aura_to_ha(data: dict[str, Any]) -> dict[str, Any]: 71 | """Convert AsusRouter Aura data to Home Assistant format.""" 72 | 73 | result = { 74 | STATE: data.get(STATE, False), 75 | ZONES: data.get(ZONES, 0), 76 | } 77 | 78 | # Current active effect 79 | _effect = data.get(SCHEME, 0) 80 | result["effect"] = AURA_EFFECTS_MAP.get(_effect, AURA_NO_EFFECT) 81 | 82 | # Colors 83 | if ACTIVE_COLOR in data: 84 | result[RGB_COLOR] = ColorRGBB(data[ACTIVE_COLOR], scale=255).as_tuple() 85 | if ACTIVE_BRIGHTNESS in data: 86 | result[BRIGHTNESS] = scale_value_int( 87 | data.get(ACTIVE_BRIGHTNESS, AURA_FALLBACK_BRIGHTNESS), 88 | 255, 89 | 128, 90 | ) 91 | 92 | for i in range(result["zones"]): 93 | color_key = f"active_{i}_color" 94 | brightness_key = f"active_{i}_brightness" 95 | if color_key in data: 96 | result[f"{RGB_COLOR}_{i}"] = ColorRGBB( 97 | data.get(color_key, AURA_FALLBACK_COLOR), 98 | scale=255, 99 | ).as_tuple() 100 | if brightness_key in data: 101 | result[f"{BRIGHTNESS}_{i}"] = scale_value_int( 102 | data.get(brightness_key, AURA_FALLBACK_BRIGHTNESS), 103 | 255, 104 | 128, 105 | ) 106 | 107 | # Color mode support 108 | result["color_mode"] = SUPPORTED_COLOR_MODES.get(_effect, ColorMode.ONOFF) 109 | 110 | return result 111 | 112 | 113 | def per_zone_light(zones: int = 3) -> list[AREntityDescription]: 114 | """Create a per-zone light entity descriptions.""" 115 | 116 | return [ 117 | ARLightDescription( 118 | key=STATE, 119 | key_group="aura", 120 | name=f"AURA Zone {i+1}", 121 | icon_on=ICON_LIGHT_ON, 122 | icon_off=ICON_LIGHT_OFF, 123 | capabilities={"zone_id": i}, 124 | entity_category=EntityCategory.CONFIG, 125 | entity_registry_enabled_default=False, 126 | extra_state_attributes={ 127 | f"{BRIGHTNESS}_{i}": BRIGHTNESS, 128 | f"{RGB_COLOR}_{i}": RGB_COLOR, 129 | }, 130 | ) 131 | for i in range(zones) 132 | ] 133 | -------------------------------------------------------------------------------- /cfg/hass/pkgs/irrigation.yaml: -------------------------------------------------------------------------------- 1 | 2 | homeassistant: 3 | customize: 4 | switch.wbi_p1: 5 | friendly_name: p1 6 | icon: mdi:water-pump 7 | 8 | switch.wbi_p2: 9 | friendly_name: p2 10 | icon: mdi:water-pump 11 | 12 | switch.wbi_p3: 13 | friendly_name: p3 14 | icon: mdi:water-pump 15 | 16 | 17 | # sensors for weather based irrigation 18 | # calculation factors based on rain and evaporation model 19 | # bug in external rain sensor, for now set it to zero 20 | # rain_min to filter small amount of noise per day 21 | wb_irrigation: 22 | api_key: !secret openweathermap_key 23 | rain_factor: 90 24 | rain_min_value: 7 25 | max_ev: 3000 26 | min_ev: -1500.0 27 | name: "wb_irrigation" 28 | external_rain : "sensor.external_rain" 29 | #debug: true 30 | longitude: !secret accurate_longitude 31 | latitude: !secret accurate_latitude 32 | elevation: !secret accurate_elevation 33 | mon_filter: [4,5,6,7,8,9] 34 | taps: 35 | - name: p1 36 | - name: p2 37 | - name: p3 38 | 39 | # manual duration time in minutes 40 | input_number: 41 | wbi_p1_duration: 42 | name: p1 43 | min: 5 44 | max: 15 45 | step: 1.0 46 | unit_of_measurement: min 47 | icon: mdi:timer-sand 48 | 49 | wbi_p2_duration: 50 | name: p2 51 | min: 5.0 52 | max: 25.0 53 | step: 1.0 54 | unit_of_measurement: min 55 | icon: mdi:timer-sand 56 | 57 | wbi_p3_duration: 58 | name: p3 59 | min: 5.0 60 | max: 25.0 61 | step: 1.0 62 | unit_of_measurement: min 63 | icon: mdi:timer-sand 64 | 65 | wbi_week_p1_duration: #week total time 66 | name: wp1 67 | min: 0.0 68 | max: 400 69 | step: 0.5 70 | unit_of_measurement: min 71 | icon: mdi:timer-sand 72 | 73 | wbi_week_p2_duration: #week total time 74 | name: wp2 75 | min: 0.0 76 | max: 60.0 77 | step: 0.5 78 | unit_of_measurement: min 79 | icon: mdi:timer-sand 80 | 81 | wbi_week_p3_duration: #week total time 82 | name: wp3 83 | min: 0.0 84 | max: 120.0 85 | step: 0.5 86 | unit_of_measurement: min 87 | icon: mdi:timer-sand 88 | 89 | 90 | input_boolean: 91 | wbi_enabled: 92 | name: Enable irrigation 93 | 94 | # switch to manual turn on off 95 | switch: 96 | 97 | - platform: mytasmota 98 | name: wbi_p1 99 | index: '1' 100 | stopic: irrigation 101 | 102 | - platform: mytasmota 103 | name: wbi_p2 104 | index: '2' 105 | stopic: irrigation 106 | 107 | - platform: mytasmota 108 | name: wbi_p3 109 | index: '3' 110 | stopic: irrigation 111 | 112 | 113 | variable: 114 | 115 | wbi_water_p1: 116 | value: 0 117 | restore: true 118 | attributes: 119 | friendly_name: 'water pump 1' 120 | unit_of_measurement: "l" 121 | 122 | wbi_water_p2: 123 | value: 0 124 | restore: true 125 | attributes: 126 | friendly_name: 'water pump 2' 127 | unit_of_measurement: "l" 128 | 129 | wbi_water_p3: 130 | value: 0 131 | restore: true 132 | attributes: 133 | friendly_name: 'water pump 3' 134 | unit_of_measurement: "l" 135 | 136 | wbi_last_duration_p1: 137 | value: 0 138 | restore: true 139 | attributes: 140 | friendly_name: 'last duration pump 1' 141 | unit_of_measurement: "min" 142 | 143 | wbi_last_duration_p2: 144 | value: 0 145 | restore: true 146 | attributes: 147 | friendly_name: 'last duration pump 2' 148 | unit_of_measurement: "min" 149 | 150 | wbi_last_duration_p3: 151 | value: 0 152 | restore: true 153 | attributes: 154 | friendly_name: 'last duration pump 3' 155 | unit_of_measurement: "min" 156 | 157 | 158 | sensor: 159 | 160 | - platform: mytasmota # sensor for mm of rain, going up 161 | name: external_rain 162 | stopic: water_out 163 | id: 2 164 | unit_of_measurement: 'mm' 165 | icon: mdi:weather-rainy 166 | expire_after: 300 167 | value_template: "{{ (0 + (value))|int }}" 168 | -------------------------------------------------------------------------------- /cfg/hass/custom_components/asusrouter/aimesh.py: -------------------------------------------------------------------------------- 1 | """AsusRouter AiMesh module.""" 2 | 3 | from __future__ import annotations 4 | 5 | from typing import Any, Callable, Optional 6 | 7 | from asusrouter.modules.aimesh import AiMeshDevice 8 | from homeassistant.core import callback 9 | from homeassistant.helpers.device_registry import format_mac 10 | 11 | 12 | class AiMeshNode: 13 | """Representation of an AiMesh node.""" 14 | 15 | def __init__( 16 | self, 17 | mac: str, 18 | ) -> None: 19 | """Initialize an AiMesh node.""" 20 | 21 | self._mac: str = mac 22 | self.native = AiMeshDevice() 23 | self.identity: dict[str, Any] = { 24 | "mac": None, 25 | "ip": None, 26 | "alias": None, 27 | "model": None, 28 | "type": None, 29 | "connected": None, 30 | } 31 | self._extra_state_attributes: dict[str, Any] = {} 32 | 33 | @callback 34 | def update( 35 | self, 36 | node_info: Optional[AiMeshDevice] = None, 37 | event_call: Optional[Callable[[str, Optional[dict[str, Any]]], None]] = None, 38 | ) -> None: 39 | """Update AiMesh device.""" 40 | 41 | if node_info: 42 | self.native = node_info 43 | self._mac = self._extra_state_attributes["mac"] = self.identity[ 44 | "mac" 45 | ] = format_mac(node_info.mac) 46 | # Online 47 | if node_info.status: 48 | # State: router / node 49 | self._extra_state_attributes["type"] = self.identity[ 50 | "type" 51 | ] = node_info.type 52 | # IP 53 | self._extra_state_attributes["ip"] = self.identity["ip"] = node_info.ip 54 | # Alias 55 | self._extra_state_attributes["alias"] = self.identity[ 56 | "alias" 57 | ] = node_info.alias 58 | # Model 59 | self._extra_state_attributes["model"] = self.identity[ 60 | "model" 61 | ] = node_info.model 62 | # Product ID 63 | self._extra_state_attributes["product_id"] = node_info.product_id 64 | # Node level 65 | self._extra_state_attributes["level"] = node_info.level 66 | # Node parent 67 | if node_info.parent == {}: 68 | self._extra_state_attributes["parent"] = { 69 | "connection": "wired", 70 | } 71 | else: 72 | self._extra_state_attributes["parent"] = node_info.parent 73 | # Node config 74 | # self._extra_state_attributes[CONFIG] = node_info.config 75 | # Access point 76 | # self._extra_state_attributes[ACCESS_POINT] = node_info.ap 77 | # Notify reconnect 78 | if self.identity["connected"] is False and callable(event_call): 79 | event_call( 80 | "node_reconnected", 81 | self.identity, 82 | ) 83 | # Connection status 84 | self.identity["connected"] = True 85 | else: 86 | # Notify disconnect 87 | if self.identity["connected"] is True and callable(event_call): 88 | event_call( 89 | "node_disconnected", 90 | self.identity, 91 | ) 92 | # Connection status 93 | self.identity["connected"] = False 94 | elif callable(event_call): 95 | # Notify disconnect 96 | event_call( 97 | "node_disconnected", 98 | self.identity, 99 | ) 100 | 101 | @property 102 | def mac(self): 103 | """Return node mac address.""" 104 | 105 | return self._mac 106 | 107 | @property 108 | def extra_state_attributes(self): 109 | """Return extra state attributes.""" 110 | 111 | return self._extra_state_attributes 112 | -------------------------------------------------------------------------------- /cfg/hass/custom_components/tplink_router/__init__.py: -------------------------------------------------------------------------------- 1 | from homeassistant.const import ( 2 | CONF_HOST, 3 | CONF_PASSWORD, 4 | CONF_USERNAME, 5 | CONF_SCAN_INTERVAL, 6 | CONF_VERIFY_SSL, 7 | Platform, 8 | ) 9 | from homeassistant.core import HomeAssistant, ServiceCall 10 | from homeassistant.config_entries import ConfigEntry 11 | from .const import DOMAIN, DEFAULT_USER 12 | import logging 13 | from tplinkrouterc6u import TPLinkMRClient 14 | from .coordinator import TPLinkRouterCoordinator 15 | from homeassistant.helpers import device_registry 16 | 17 | PLATFORMS: list[Platform] = [ 18 | Platform.DEVICE_TRACKER, 19 | Platform.SENSOR, 20 | Platform.SWITCH, 21 | Platform.BUTTON, 22 | ] 23 | 24 | _LOGGER = logging.getLogger(__name__) 25 | 26 | 27 | async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: 28 | # Construct the device 29 | host = entry.data[CONF_HOST] 30 | if not (host.startswith('http://') or host.startswith('https://')): 31 | host = "http://{}".format(host) 32 | verify_ssl = entry.data[CONF_VERIFY_SSL] if CONF_VERIFY_SSL in entry.data else True 33 | client = await TPLinkRouterCoordinator.get_client( 34 | hass=hass, 35 | host=host, 36 | password=entry.data[CONF_PASSWORD], 37 | username=entry.data.get(CONF_USERNAME, DEFAULT_USER), 38 | logger=_LOGGER, 39 | verify_ssl=verify_ssl 40 | ) 41 | 42 | def callback(): 43 | firm = client.get_firmware() 44 | stat = client.get_status() 45 | 46 | return firm, stat 47 | 48 | firmware, status = await hass.async_add_executor_job(TPLinkRouterCoordinator.request, client, callback) 49 | 50 | # Create device coordinator and fetch data 51 | coordinator = TPLinkRouterCoordinator(hass, client, entry.data[CONF_SCAN_INTERVAL], firmware, status, _LOGGER, 52 | entry.entry_id) 53 | 54 | await coordinator.async_config_entry_first_refresh() 55 | hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator 56 | 57 | await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) 58 | entry.async_on_unload(entry.add_update_listener(async_reload_entry)) 59 | 60 | register_services(hass, coordinator) 61 | 62 | return True 63 | 64 | 65 | async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: 66 | unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS) 67 | 68 | if unload_ok: 69 | hass.data[DOMAIN].pop(entry.entry_id) 70 | return unload_ok 71 | 72 | 73 | async def async_reload_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> None: 74 | await hass.config_entries.async_reload(config_entry.entry_id) 75 | 76 | 77 | def register_services(hass: HomeAssistant, coord: TPLinkRouterCoordinator) -> None: 78 | 79 | if not issubclass(coord.router.__class__, TPLinkMRClient): 80 | return 81 | 82 | dr = device_registry.async_get(hass) 83 | 84 | async def send_sms_service(service: ServiceCall) -> None: 85 | device = dr.async_get(service.data.get("device")) 86 | if device is None: 87 | _LOGGER.error('TplinkRouter Integration Exception - device was not found') 88 | return 89 | coordinator = None 90 | for key in device.config_entries: 91 | entry = hass.config_entries.async_get_entry(key) 92 | if not entry: 93 | continue 94 | if entry.domain != DOMAIN or not issubclass(hass.data[DOMAIN][key].router.__class__, TPLinkMRClient): 95 | continue 96 | coordinator = hass.data[DOMAIN][key] 97 | 98 | if coordinator is None: 99 | _LOGGER.error('TplinkRouter Integration Exception - This device cannot send SMS') 100 | return 101 | 102 | def callback(): 103 | coord.router.send_sms(service.data.get("number"), service.data.get("text")) 104 | await hass.async_add_executor_job(TPLinkRouterCoordinator.request, coord.router, callback) 105 | 106 | if not hass.services.has_service(DOMAIN, 'send_sms'): 107 | hass.services.async_register(DOMAIN, 'send_sms', send_sms_service) 108 | -------------------------------------------------------------------------------- /cfg/hass/pkgs/lamps.yaml: -------------------------------------------------------------------------------- 1 | 2 | 3 | switch: 4 | 5 | # turn on and then off will toggle the state of the garage door, need another sensor to understand the state 6 | - platform: mytasmota 7 | name: garage 8 | stopic: garage 9 | 10 | 11 | - platform: mytasmota 12 | name: inputlamp0 13 | stopic: basic10 14 | 15 | - platform: mytasmota 16 | name: door-lamp0 17 | stopic: basic1 18 | 19 | - platform: mytasmota 20 | name: garden-lamp0 21 | stopic: basic0 22 | 23 | - platform: mytasmota 24 | name: gardenlamp1 25 | stopic: basic2 26 | 27 | 28 | - platform: mytasmota 29 | name: r0table 30 | stopic: basic3 31 | 32 | - platform: mytasmota 33 | name: r0tv 34 | stopic: basic4 35 | 36 | - platform: mytasmota 37 | name: shutter2 38 | stopic: shutter2 39 | index: '2' 40 | 41 | - platform: mytasmota 42 | name: shutter3 43 | stopic: shutter3 44 | index: '1' 45 | 46 | - platform: mytasmota 47 | name: shutter4 48 | stopic: shutter4 49 | index: '1' 50 | 51 | - platform: mytasmota 52 | name: rshutter11 53 | stopic: shutter1 54 | index: '1' 55 | 56 | - platform: mytasmota 57 | name: rshutter12 58 | stopic: shutter1 59 | index: '2' 60 | 61 | 62 | 63 | - platform: template 64 | switches: 65 | shutter11: 66 | friendly_name: shutter11 67 | value_template: "{{ is_state('switch.rshutter11', 'off') }}" 68 | turn_on: 69 | service: switch.turn_off 70 | data: 71 | entity_id: switch.rshutter11 72 | turn_off: 73 | service: switch.turn_on 74 | data: 75 | entity_id: switch.rshutter11 76 | shutter12: 77 | friendly_name: shutter12 78 | value_template: "{{ is_state('switch.rshutter12', 'off') }}" 79 | turn_on: 80 | service: switch.turn_off 81 | data: 82 | entity_id: switch.rshutter12 83 | turn_off: 84 | service: switch.turn_on 85 | data: 86 | entity_id: switch.rshutter12 87 | 88 | group: 89 | shutter_r0: 90 | name: sr0 91 | entities: 92 | - switch.shutter11 93 | - switch.shutter12 94 | - switch.shutter3 95 | - switch.shutter2 96 | - switch.shutter4 97 | lamps_r0: 98 | name: lr0 99 | entities: 100 | - switch.r0table 101 | - switch.r0tv 102 | 103 | garden_i0: 104 | name: gr0 105 | entities: 106 | - switch.kit7on 107 | - switch.inputlamp0 108 | - switch.door_lamp0 109 | 110 | garden_i1: 111 | name: gr1 112 | entities: 113 | - switch.garden_lamp0 114 | - switch.gardenlamp1 115 | 116 | 117 | 118 | 119 | 120 | input_boolean: 121 | shutter_app_enable: 122 | name: Enable Shutter App 123 | 124 | mqtt: 125 | binary_sensor: 126 | - name: "alarm16" 127 | payload_on: "OFF" 128 | payload_off: "ON" 129 | availability_topic: "tele/basic1/LWT" 130 | payload_available: "Online" 131 | payload_not_available: "Offline" 132 | state_topic: "cmnd/basic1-status/POWER2" 133 | 134 | 135 | 136 | light: 137 | - name: "Light1" 138 | command_topic: "cmnd/rgb1/POWER" 139 | state_topic: "stat/rgb1/RESULT" 140 | state_value_template: "{{value_json.POWER}}" 141 | availability_topic: "tele/rgb1/LWT" 142 | brightness_command_topic: "cmnd/rgb1/Dimmer" 143 | brightness_state_topic: "tele/rgb1/STATE" 144 | brightness_scale: 100 145 | on_command_type: "brightness" 146 | brightness_value_template: "{{value_json.Dimmer}}" 147 | rgb_command_topic: "cmnd/rgb1/Color2" 148 | rgb_state_topic: "tele/rgb1/STATE" 149 | rgb_value_template: "{{value_json.Color.split(',')[0:3]|join(',')}}" 150 | effect_command_topic: "cmnd/rgb1/Scheme" 151 | effect_state_topic: "tele/rgb1/STATE" 152 | effect_value_template: "{{value_json.Scheme}}" 153 | effect_list: 154 | - 0 155 | - 1 156 | - 2 157 | - 3 158 | - 4 159 | payload_on: "ON" 160 | payload_off: "OFF" 161 | payload_available: "Online" 162 | payload_not_available: "Offline" 163 | qos: 1 164 | retain: false 165 | 166 | 167 | -------------------------------------------------------------------------------- /cfg/influxdb/influxdb.conf: -------------------------------------------------------------------------------- 1 | reporting-disabled = false 2 | bind-address = "127.0.0.1:8088" 3 | 4 | 5 | [meta] 6 | dir = "/var/lib/influxdb/meta" 7 | retention-autocreate = true 8 | logging-enabled = true 9 | 10 | [data] 11 | dir = "/var/lib/influxdb/data" 12 | index-version = "inmem" 13 | wal-dir = "/var/lib/influxdb/wal" 14 | wal-fsync-delay = "0s" 15 | validate-keys = false 16 | strict-error-handling = false 17 | query-log-enabled = false 18 | cache-max-memory-size = 1073741824 19 | cache-snapshot-memory-size = 26214400 20 | cache-snapshot-write-cold-duration = "10m0s" 21 | compact-full-write-cold-duration = "4h0m0s" 22 | compact-throughput = 50331648 23 | compact-throughput-burst = 50331648 24 | max-series-per-database = 1000000 25 | max-values-per-tag = 100000 26 | max-concurrent-compactions = 0 27 | max-index-log-file-size = 1048576 28 | series-id-set-cache-size = 100 29 | series-file-max-concurrent-snapshot-compactions = 0 30 | trace-logging-enabled = false 31 | tsm-use-madv-willneed = false 32 | 33 | [coordinator] 34 | write-timeout = "10s" 35 | max-concurrent-queries = 0 36 | query-timeout = "0s" 37 | log-queries-after = "0s" 38 | max-select-point = 0 39 | max-select-series = 0 40 | max-select-buckets = 0 41 | 42 | [retention] 43 | enabled = true 44 | check-interval = "30m0s" 45 | 46 | [shard-precreation] 47 | enabled = true 48 | check-interval = "10m0s" 49 | advance-period = "30m0s" 50 | 51 | [monitor] 52 | store-enabled = true 53 | store-database = "_internal" 54 | store-interval = "10s" 55 | 56 | [subscriber] 57 | enabled = true 58 | http-timeout = "30s" 59 | insecure-skip-verify = false 60 | ca-certs = "" 61 | write-concurrency = 40 62 | write-buffer-size = 1000 63 | 64 | [http] 65 | enabled = true 66 | bind-address = ":8086" 67 | auth-enabled = false 68 | log-enabled = false 69 | suppress-write-log = false 70 | write-tracing = false 71 | flux-enabled = false 72 | flux-log-enabled = false 73 | pprof-enabled = true 74 | pprof-auth-enabled = false 75 | debug-pprof-enabled = false 76 | ping-auth-enabled = false 77 | prom-read-auth-enabled = false 78 | https-enabled = false 79 | https-certificate = "/etc/ssl/influxdb.pem" 80 | https-private-key = "" 81 | max-row-limit = 0 82 | max-connection-limit = 0 83 | shared-secret = "" 84 | realm = "InfluxDB" 85 | unix-socket-enabled = false 86 | unix-socket-permissions = "0777" 87 | bind-socket = "/var/run/influxdb.sock" 88 | max-body-size = 25000000 89 | access-log-path = "" 90 | max-concurrent-write-limit = 0 91 | max-enqueued-write-limit = 0 92 | enqueued-write-timeout = 30000000000 93 | 94 | [logging] 95 | format = "auto" 96 | level = "info" 97 | suppress-logo = false 98 | 99 | [[graphite]] 100 | enabled = false 101 | bind-address = ":2003" 102 | database = "graphite" 103 | retention-policy = "" 104 | protocol = "tcp" 105 | batch-size = 5000 106 | batch-pending = 10 107 | batch-timeout = "1s" 108 | consistency-level = "one" 109 | separator = "." 110 | udp-read-buffer = 0 111 | 112 | [[collectd]] 113 | enabled = false 114 | bind-address = ":25826" 115 | database = "collectd" 116 | retention-policy = "" 117 | batch-size = 5000 118 | batch-pending = 10 119 | batch-timeout = "10s" 120 | read-buffer = 0 121 | typesdb = "/usr/share/collectd/types.db" 122 | security-level = "none" 123 | auth-file = "/etc/collectd/auth_file" 124 | parse-multivalue-plugin = "split" 125 | 126 | [[opentsdb]] 127 | enabled = false 128 | bind-address = ":4242" 129 | database = "opentsdb" 130 | retention-policy = "" 131 | consistency-level = "one" 132 | tls-enabled = false 133 | certificate = "/etc/ssl/influxdb.pem" 134 | batch-size = 1000 135 | batch-pending = 5 136 | batch-timeout = "1s" 137 | log-point-errors = true 138 | 139 | [[udp]] 140 | enabled = false 141 | bind-address = ":8089" 142 | database = "udp" 143 | retention-policy = "" 144 | batch-size = 5000 145 | batch-pending = 10 146 | read-buffer = 0 147 | batch-timeout = "1s" 148 | precision = "" 149 | 150 | [continuous_queries] 151 | log-enabled = true 152 | enabled = true 153 | query-stats-enabled = false 154 | run-interval = "1s" 155 | 156 | [tls] 157 | min-version = "" 158 | max-version = "" 159 | 160 | -------------------------------------------------------------------------------- /cfg/hass/custom_components/clicksend_tts_en/notify.py: -------------------------------------------------------------------------------- 1 | """clicksend_tts platform for notify component.""" 2 | from __future__ import annotations 3 | 4 | from http import HTTPStatus 5 | import json 6 | import logging 7 | 8 | import requests 9 | import voluptuous as vol 10 | 11 | from homeassistant.components.notify import PLATFORM_SCHEMA, BaseNotificationService 12 | from homeassistant.const import ( 13 | CONF_API_KEY, 14 | CONF_NAME, 15 | CONF_RECIPIENT, 16 | CONF_USERNAME, 17 | CONTENT_TYPE_JSON, 18 | ) 19 | from homeassistant.core import HomeAssistant 20 | import homeassistant.helpers.config_validation as cv 21 | from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType 22 | 23 | _LOGGER = logging.getLogger(__name__) 24 | 25 | BASE_API_URL = "https://rest.clicksend.com/v3" 26 | 27 | HEADERS = {"Content-Type": CONTENT_TYPE_JSON} 28 | 29 | CONF_LANGUAGE = "language" 30 | CONF_VOICE = "voice" 31 | 32 | MALE_VOICE = "male" 33 | FEMALE_VOICE = "female" 34 | 35 | DEFAULT_NAME = "clicksend_tts_en" 36 | DEFAULT_LANGUAGE = "en-us" 37 | DEFAULT_VOICE = FEMALE_VOICE 38 | TIMEOUT = 5 39 | CONF_CALLER = "caller" 40 | 41 | PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( 42 | { 43 | vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, 44 | vol.Required(CONF_USERNAME): cv.string, 45 | vol.Required(CONF_API_KEY): cv.string, 46 | vol.Required(CONF_RECIPIENT): vol.All( 47 | cv.string, vol.Match(r"^\+?[1-9]\d{1,14}$") 48 | ), 49 | vol.Optional(CONF_LANGUAGE, default=DEFAULT_LANGUAGE): cv.string, 50 | vol.Optional(CONF_VOICE, default=DEFAULT_VOICE): vol.In( 51 | [MALE_VOICE, FEMALE_VOICE], 52 | ), 53 | vol.Optional(CONF_CALLER): cv.string, 54 | } 55 | ) 56 | 57 | 58 | def get_service( 59 | hass: HomeAssistant, 60 | config: ConfigType, 61 | discovery_info: DiscoveryInfoType | None = None, 62 | ) -> ClicksendNotificationService | None: 63 | """Get the ClickSend notification service.""" 64 | if not _authenticate(config): 65 | _LOGGER.error("You are not authorized to access ClickSend") 66 | return None 67 | 68 | return ClicksendNotificationService(config) 69 | 70 | 71 | class ClicksendNotificationService(BaseNotificationService): 72 | """Implementation of a notification service for the ClickSend service.""" 73 | 74 | def __init__(self, config): 75 | """Initialize the service.""" 76 | self.username = config[CONF_USERNAME] 77 | self.api_key = config[CONF_API_KEY] 78 | self.recipient = config[CONF_RECIPIENT] 79 | self.language = config[CONF_LANGUAGE] 80 | self.voice = config[CONF_VOICE] 81 | self.caller = config.get(CONF_CALLER) 82 | if self.caller is None: 83 | self.caller = self.recipient 84 | 85 | def send_message(self, message="", **kwargs): 86 | """Send a voice call to a user.""" 87 | data = { 88 | "messages": [ 89 | { 90 | "source": "hass.notify", 91 | "from": self.caller, 92 | "to": self.recipient, 93 | "body": message, 94 | "lang": self.language, 95 | "voice": self.voice, 96 | } 97 | ] 98 | } 99 | api_url = f"{BASE_API_URL}/voice/send" 100 | 101 | resp = requests.post( 102 | api_url, 103 | data=json.dumps(data), 104 | headers=HEADERS, 105 | auth=(self.username, self.api_key), 106 | timeout=TIMEOUT, 107 | ) 108 | 109 | if resp.status_code == HTTPStatus.OK: 110 | return 111 | obj = json.loads(resp.text) 112 | response_msg = obj["response_msg"] 113 | response_code = obj["response_code"] 114 | _LOGGER.error( 115 | "Error %s : %s (Code %s)", resp.status_code, response_msg, response_code 116 | ) 117 | 118 | 119 | def _authenticate(config): 120 | """Authenticate with ClickSend.""" 121 | api_url = f"{BASE_API_URL}/account" 122 | resp = requests.get( 123 | api_url, 124 | headers=HEADERS, 125 | auth=(config.get(CONF_USERNAME), config.get(CONF_API_KEY)), 126 | timeout=TIMEOUT, 127 | ) 128 | 129 | return resp.status_code == HTTPStatus.OK 130 | -------------------------------------------------------------------------------- /cfg/hass/custom_components/tplink_router/config_flow.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import voluptuous as vol 3 | from typing import Any 4 | from homeassistant import config_entries 5 | from homeassistant.core import callback 6 | import homeassistant.helpers.config_validation as cv 7 | from homeassistant.data_entry_flow import FlowResult 8 | from .const import DOMAIN, DEFAULT_USER, DEFAULT_HOST 9 | from .coordinator import TPLinkRouterCoordinator 10 | from homeassistant.const import ( 11 | CONF_HOST, 12 | CONF_PASSWORD, 13 | CONF_USERNAME, 14 | CONF_SCAN_INTERVAL, 15 | CONF_VERIFY_SSL, 16 | ) 17 | 18 | _LOGGER = logging.getLogger(__name__) 19 | 20 | 21 | class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): 22 | 23 | async def async_step_user(self, user_input=None): 24 | """Handle the initial step.""" 25 | errors = {} 26 | schema = vol.Schema( 27 | { 28 | vol.Required(CONF_HOST, default=DEFAULT_HOST): str, 29 | vol.Required(CONF_PASSWORD): cv.string, 30 | vol.Required(CONF_SCAN_INTERVAL, default=30): int, 31 | vol.Required(CONF_VERIFY_SSL, default=True): cv.boolean, 32 | } 33 | ) 34 | if user_input is not None: 35 | try: 36 | router = await TPLinkRouterCoordinator.get_client( 37 | hass=self.hass, 38 | host=user_input[CONF_HOST], 39 | password=user_input[CONF_PASSWORD], 40 | username=user_input.get(CONF_USERNAME, DEFAULT_USER), 41 | logger=_LOGGER, 42 | verify_ssl=user_input[CONF_VERIFY_SSL], 43 | ) 44 | await self.hass.async_add_executor_job(router.authorize) 45 | return self.async_create_entry(title=user_input["host"], data=user_input) 46 | except Exception as error: 47 | _LOGGER.error('TplinkRouter Integration Exception - {}'.format(error)) 48 | errors['base'] = str(error) 49 | schema = vol.Schema( 50 | { 51 | vol.Required(CONF_HOST, default=DEFAULT_HOST): str, 52 | vol.Required(CONF_PASSWORD): cv.string, 53 | vol.Required(CONF_USERNAME, default=DEFAULT_USER): str, 54 | vol.Required(CONF_SCAN_INTERVAL, default=30): int, 55 | vol.Required(CONF_VERIFY_SSL, default=True): cv.boolean, 56 | } 57 | ) 58 | 59 | return self.async_show_form(step_id="user", data_schema=schema, errors=errors) 60 | 61 | @staticmethod 62 | @callback 63 | def async_get_options_flow(config_entry: config_entries.ConfigEntry) -> config_entries.OptionsFlow: 64 | return OptionsFlow(config_entry) 65 | 66 | 67 | class OptionsFlow(config_entries.OptionsFlowWithConfigEntry): 68 | 69 | async def async_step_init(self, user_input: dict[str, Any] | None = None) -> FlowResult: 70 | errors = {} 71 | data = user_input or self.config_entry.data 72 | 73 | if user_input is not None: 74 | try: 75 | router = await TPLinkRouterCoordinator.get_client( 76 | hass=self.hass, 77 | host=user_input[CONF_HOST], 78 | password=user_input[CONF_PASSWORD], 79 | username=user_input[CONF_USERNAME], 80 | logger=_LOGGER, 81 | verify_ssl=user_input[CONF_VERIFY_SSL], 82 | ) 83 | await self.hass.async_add_executor_job(router.authorize) 84 | self.hass.config_entries.async_update_entry(self.config_entry, data=user_input) 85 | return self.async_create_entry(title=user_input["host"], data=user_input) 86 | except Exception as error: 87 | _LOGGER.error('TplinkRouter Integration Exception - {}'.format(error)) 88 | errors['base'] = str(error) 89 | 90 | data_schema = vol.Schema({ 91 | vol.Required(CONF_HOST, default=data.get(CONF_HOST)): cv.string, 92 | vol.Required(CONF_USERNAME, default=data.get(CONF_USERNAME, DEFAULT_USER)): cv.string, 93 | vol.Required(CONF_PASSWORD, default=data.get(CONF_PASSWORD)): cv.string, 94 | vol.Required(CONF_SCAN_INTERVAL, default=data.get(CONF_SCAN_INTERVAL)): int, 95 | vol.Required(CONF_VERIFY_SSL, default=data.get(CONF_VERIFY_SSL)): cv.boolean, 96 | }) 97 | 98 | return self.async_show_form(step_id="init", data_schema=data_schema, errors=errors) 99 | -------------------------------------------------------------------------------- /cfg/hass/custom_components/wb_irrigation/pyeto/thornthwaite.py: -------------------------------------------------------------------------------- 1 | """ 2 | Calculate potential evapotranspiration using the Thornthwaite (1948 method) 3 | 4 | :copyright: (c) 2015 by Mark Richards. 5 | :license: BSD 3-Clause, see LICENSE.txt for more details. 6 | 7 | References 8 | ---------- 9 | Thornthwaite CW (1948) An approach toward a rational classification of 10 | climate. Geographical Review, 38, 55-94. 11 | """ 12 | 13 | import calendar 14 | 15 | from . import fao 16 | from ._check import check_latitude_rad as _check_latitude_rad 17 | 18 | _MONTHDAYS = (31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31) 19 | _LEAP_MONTHDAYS = (31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31) 20 | 21 | 22 | def thornthwaite(monthly_t, monthly_mean_dlh, year=None): 23 | """ 24 | Estimate monthly potential evapotranspiration (PET) using the 25 | Thornthwaite (1948) method. 26 | 27 | Thornthwaite equation: 28 | 29 | *PET* = 1.6 (*L*/12) (*N*/30) (10*Ta* / *I*)***a* 30 | 31 | where: 32 | 33 | * *Ta* is the mean daily air temperature [deg C, if negative use 0] of the 34 | month being calculated 35 | * *N* is the number of days in the month being calculated 36 | * *L* is the mean day length [hours] of the month being calculated 37 | * *a* = (6.75 x 10-7)*I***3 - (7.71 x 10-5)*I***2 + (1.792 x 10-2)*I* + 0.49239 38 | * *I* is a heat index which depends on the 12 monthly mean temperatures and 39 | is calculated as the sum of (*Tai* / 5)**1.514 for each month, where 40 | Tai is the air temperature for each month in the year 41 | 42 | :param monthly_t: Iterable containing mean daily air temperature for each 43 | month of the year [deg C]. 44 | :param monthly_mean_dlh: Iterable containing mean daily daylight 45 | hours for each month of the year (hours]. These can be calculated 46 | using ``monthly_mean_daylight_hours()``. 47 | :param year: Year for which PET is required. The only effect of year is 48 | to change the number of days in February to 29 if it is a leap year. 49 | If it is left as the default (None), then the year is assumed not to 50 | be a leap year. 51 | :return: Estimated monthly potential evaporation of each month of the year 52 | [mm/month] 53 | :rtype: List of floats 54 | """ 55 | if len(monthly_t) != 12: 56 | raise ValueError( 57 | 'monthly_t should be length 12 but is length {0}.' 58 | .format(len(monthly_t))) 59 | if len(monthly_mean_dlh) != 12: 60 | raise ValueError( 61 | 'monthly_mean_dlh should be length 12 but is length {0}.' 62 | .format(len(monthly_mean_dlh))) 63 | 64 | if year is None or not calendar.isleap(year): 65 | month_days = _MONTHDAYS 66 | else: 67 | month_days = _LEAP_MONTHDAYS 68 | 69 | # Negative temperatures should be set to zero 70 | adj_monthly_t = [t * (t >= 0) for t in monthly_t] 71 | 72 | # Calculate the heat index (I) 73 | I = 0.0 74 | for Tai in adj_monthly_t: 75 | if Tai / 5.0 > 0.0: 76 | I += (Tai / 5.0) ** 1.514 77 | 78 | a = (6.75e-07 * I ** 3) - (7.71e-05 * I ** 2) + (1.792e-02 * I) + 0.49239 79 | 80 | pet = [] 81 | for Ta, L, N in zip(adj_monthly_t, monthly_mean_dlh, month_days): 82 | # Multiply by 10 to convert cm/month --> mm/month 83 | pet.append( 84 | 1.6 * (L / 12.0) * (N / 30.0) * ((10.0 * Ta / I) ** a) * 10.0) 85 | 86 | return pet 87 | 88 | 89 | def monthly_mean_daylight_hours(latitude, year=None): 90 | """ 91 | Calculate mean daylight hours for each month of the year for a given 92 | latitude. 93 | 94 | :param latitude: Latitude [radians] 95 | :param year: Year for the daylight hours are required. The only effect of 96 | *year* is to change the number of days in Feb to 29 if it is a leap 97 | year. If left as the default, None, then a normal (non-leap) year is 98 | assumed. 99 | :return: Mean daily daylight hours of each month of a year [hours] 100 | :rtype: List of floats. 101 | """ 102 | _check_latitude_rad(latitude) 103 | 104 | if year is None or not calendar.isleap(year): 105 | month_days = _MONTHDAYS 106 | else: 107 | month_days = _LEAP_MONTHDAYS 108 | monthly_mean_dlh = [] 109 | doy = 1 # Day of the year 110 | for mdays in month_days: 111 | dlh = 0.0 # Cumulative daylight hours for the month 112 | for daynum in range(1, mdays + 1): 113 | sd = fao.sol_dec(doy) 114 | sha = fao.sunset_hour_angle(latitude, sd) 115 | dlh += fao.daylight_hours(sha) 116 | doy += 1 117 | # Calc mean daylight hours of the month 118 | monthly_mean_dlh.append(dlh / mdays) 119 | return monthly_mean_dlh 120 | -------------------------------------------------------------------------------- /cfg/hass/apps/ada/util.py: -------------------------------------------------------------------------------- 1 | """ 2 | Utility functions that are used everywhere inside Heaty. 3 | """ 4 | 5 | import typing as T 6 | 7 | import datetime 8 | import re 9 | 10 | 11 | # matches any character that is not allowed in Python variable names 12 | INVALID_VAR_NAME_CHAR_PATTERN = re.compile(r"[^0-9A-Za-z_]") 13 | # regexp pattern matching a range like 3-7 without spaces 14 | RANGE_PATTERN = re.compile(r"^(\d+)\-(\d+)$") 15 | # strftime-compatible format string for military time 16 | TIME_FORMAT = "%H:%M:%S" 17 | # regular expression for time formats, group 1 is hours, group 2 is minutes, 18 | # optional group 3 is seconds 19 | TIME_REGEXP = re.compile(r"^ *([01]?\d|2[0-3]) *\: *([0-5]\d) *(?:\: *([0-5]\d) *)?$") 20 | 21 | 22 | def escape_var_name(name: str) -> str: 23 | """Converts the given string to a valid Python variable name. 24 | All unsupported characters are replaced by "_". If name would 25 | start with a digit, "_" is put infront.""" 26 | 27 | name = INVALID_VAR_NAME_CHAR_PATTERN.sub("_", name) 28 | digits = tuple([str(i) for i in range(10)]) 29 | if name.startswith(digits): 30 | name = "_" + name 31 | return name 32 | 33 | def expand_range_string(range_string: T.Union[float, int, str]) -> T.Set[int]: 34 | """Expands strings of the form '1,2-4,9,11-12 to set(1,2,3,4,9,11,12). 35 | Any whitespace is ignored. If a float or int is given instead of a 36 | string, a set containing only that, converted to int, is returned.""" 37 | 38 | if isinstance(range_string, (float, int)): 39 | return set([int(range_string)]) 40 | 41 | numbers = set() 42 | for part in "".join(range_string.split()).split(","): 43 | match = RANGE_PATTERN.match(part) 44 | if match is not None: 45 | for i in range(int(match.group(1)), int(match.group(2)) + 1): 46 | numbers.add(i) 47 | else: 48 | numbers.add(int(part)) 49 | return numbers 50 | 51 | def build_date_from_constraint( 52 | constraint: T.Dict[str, int], default_date: datetime.date, 53 | direction: int = 0 54 | ) -> datetime.date: 55 | """Builds and returns a datetime.date object from the given constraint, 56 | taking missing values from the given default_date. 57 | In case the date is not valid (e.g. 2017-02-29), a ValueError is 58 | raised, unless a number has been given for direction, in which case 59 | the next/previous valid date will be chosen, depending on the sign 60 | of direction.""" 61 | 62 | fields = {} 63 | for field in ("year", "month", "day"): 64 | fields[field] = constraint.get(field, getattr(default_date, field)) 65 | 66 | while True: 67 | try: 68 | return datetime.date(**fields) 69 | except ValueError: 70 | if direction > 0: 71 | fields["day"] += 1 72 | elif direction < 0: 73 | fields["day"] -= 1 74 | else: 75 | raise 76 | 77 | # handle month/year transitions correctly 78 | if fields["day"] < 1: 79 | fields["day"] = 31 80 | fields["month"] -= 1 81 | elif fields["day"] > 31: 82 | fields["day"] = 1 83 | fields["month"] += 1 84 | if fields["month"] < 1: 85 | fields["month"] = 12 86 | fields["year"] -= 1 87 | elif fields["month"] > 12: 88 | fields["month"] = 1 89 | fields["year"] += 1 90 | 91 | def format_sensor_value(value: T.Any) -> str: 92 | """Formats values as strings for usage as HA sensor state. 93 | Floats are rounded to 2 decimal digits.""" 94 | 95 | if isinstance(value, float): 96 | state = "{:.2f}".format(value).rstrip("0") 97 | if state.endswith("."): 98 | state += "0" 99 | else: 100 | state = str(value) 101 | 102 | return state 103 | 104 | def format_time(when: datetime.time, format_str: str = TIME_FORMAT) -> str: 105 | """Returns a string representing the given datetime.time object. 106 | If no strftime-compatible format is provided, the default is used.""" 107 | 108 | return when.strftime(format_str) 109 | 110 | def mixin_dict(dest: dict, mixin: dict) -> dict: 111 | """Updates the first dict with the items from the second and returns it.""" 112 | 113 | dest.update(mixin) 114 | return dest 115 | 116 | def parse_time_string(time_str: str) -> datetime.time: 117 | """Parses a string recognizable by TIME_REGEXP format into 118 | a datetime.time object. If the string has an invalid format, a 119 | ValueError is raised.""" 120 | 121 | match = TIME_REGEXP.match(time_str) 122 | if match is None: 123 | raise ValueError("time string {} has an invalid format" 124 | .format(repr(time_str))) 125 | components = [int(comp) for comp in match.groups() if comp is not None] 126 | return datetime.time(*components) # type: ignore 127 | -------------------------------------------------------------------------------- /cfg/hass/custom_components/tplink_router/sensor.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from collections.abc import Callable 3 | from typing import Any 4 | from homeassistant.components.sensor import ( 5 | SensorStateClass, 6 | SensorEntity, 7 | SensorEntityDescription, 8 | ) 9 | from homeassistant.const import PERCENTAGE 10 | from homeassistant.config_entries import ConfigEntry 11 | from homeassistant.core import HomeAssistant, callback 12 | from .const import DOMAIN 13 | from homeassistant.helpers.entity_platform import AddEntitiesCallback 14 | from homeassistant.helpers.update_coordinator import CoordinatorEntity 15 | from .coordinator import TPLinkRouterCoordinator 16 | from tplinkrouterc6u import Status, IPv4Status 17 | 18 | 19 | @dataclass 20 | class TPLinkRouterSensorRequiredKeysMixin: 21 | value: Callable[[Status], Any] 22 | 23 | 24 | @dataclass 25 | class TPLinkRouterIpv4SensorRequiredKeysMixin: 26 | value: Callable[[IPv4Status], Any] 27 | 28 | 29 | @dataclass 30 | class TPLinkRouterSensorEntityDescription( 31 | SensorEntityDescription, TPLinkRouterSensorRequiredKeysMixin 32 | ): 33 | """A class that describes sensor entities.""" 34 | 35 | sensor_type: str = "status" 36 | 37 | 38 | @dataclass 39 | class TPLinkRouterIpv4SensorEntityDescription( 40 | SensorEntityDescription, TPLinkRouterIpv4SensorRequiredKeysMixin 41 | ): 42 | """A class that describes Ipv4Sensor entities.""" 43 | 44 | sensor_type: str = "ipv4_status" 45 | 46 | 47 | SENSOR_TYPES: tuple[TPLinkRouterSensorEntityDescription, ...] = ( 48 | TPLinkRouterSensorEntityDescription( 49 | key="guest_wifi_clients_total", 50 | name="Total guest wifi clients", 51 | icon="mdi:account-multiple", 52 | state_class=SensorStateClass.TOTAL, 53 | value=lambda status: status.guest_clients_total, 54 | ), 55 | TPLinkRouterSensorEntityDescription( 56 | key="wifi_clients_total", 57 | name="Total main wifi clients", 58 | icon="mdi:account-multiple", 59 | state_class=SensorStateClass.TOTAL, 60 | value=lambda status: status.wifi_clients_total, 61 | ), 62 | TPLinkRouterSensorEntityDescription( 63 | key="wired_clients_total", 64 | name="Total wired clients", 65 | icon="mdi:account-multiple", 66 | state_class=SensorStateClass.TOTAL, 67 | value=lambda status: status.wired_total, 68 | ), 69 | TPLinkRouterSensorEntityDescription( 70 | key="iot_clients_total", 71 | name="Total IoT clients", 72 | icon="mdi:account-multiple", 73 | state_class=SensorStateClass.TOTAL, 74 | value=lambda status: status.iot_clients_total, 75 | ), 76 | TPLinkRouterSensorEntityDescription( 77 | key="clients_total", 78 | name="Total clients", 79 | icon="mdi:account-multiple", 80 | state_class=SensorStateClass.TOTAL, 81 | value=lambda status: status.clients_total, 82 | ), 83 | TPLinkRouterSensorEntityDescription( 84 | key="cpu_used", 85 | name="CPU used", 86 | icon="mdi:cpu-64-bit", 87 | state_class=SensorStateClass.MEASUREMENT, 88 | native_unit_of_measurement=PERCENTAGE, 89 | suggested_display_precision=1, 90 | value=lambda status: (status.cpu_usage * 100) if status.cpu_usage is not None else None, 91 | ), 92 | TPLinkRouterSensorEntityDescription( 93 | key="memory_used", 94 | name="Memory used", 95 | icon="mdi:memory", 96 | state_class=SensorStateClass.MEASUREMENT, 97 | native_unit_of_measurement=PERCENTAGE, 98 | suggested_display_precision=1, 99 | value=lambda status: (status.mem_usage * 100) if status.mem_usage is not None else None, 100 | ), 101 | TPLinkRouterSensorEntityDescription( 102 | key="conn_type", 103 | name="Connection Type", 104 | icon="mdi:wan", 105 | value=lambda status: status.conn_type, 106 | ), 107 | ) 108 | 109 | 110 | async def async_setup_entry( 111 | hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback 112 | ) -> None: 113 | coordinator = hass.data[DOMAIN][entry.entry_id] 114 | 115 | sensors = [] 116 | 117 | for description in SENSOR_TYPES: 118 | sensors.append(TPLinkRouterSensor(coordinator, description)) 119 | 120 | async_add_entities(sensors, False) 121 | 122 | 123 | class TPLinkRouterSensor( 124 | CoordinatorEntity[TPLinkRouterCoordinator], SensorEntity 125 | ): 126 | _attr_has_entity_name = True 127 | entity_description: TPLinkRouterSensorEntityDescription 128 | 129 | def __init__( 130 | self, 131 | coordinator: TPLinkRouterCoordinator, 132 | description: TPLinkRouterSensorEntityDescription, 133 | ) -> None: 134 | super().__init__(coordinator) 135 | 136 | self._attr_device_info = coordinator.device_info 137 | self._attr_unique_id = f"{coordinator.unique_id}_{DOMAIN}_{description.key}" 138 | self.entity_description = description 139 | 140 | @callback 141 | def _handle_coordinator_update(self) -> None: 142 | """Handle updated data from the coordinator.""" 143 | self._attr_native_value = self.entity_description.value(self.coordinator.status) 144 | self.async_write_ha_state() 145 | 146 | @property 147 | def available(self) -> bool: 148 | """Return True if entity is available.""" 149 | return self.entity_description.value(self.coordinator.status) is not None 150 | -------------------------------------------------------------------------------- /cfg/hass/custom_components/ping_en/device_tracker.py: -------------------------------------------------------------------------------- 1 | """Tracks devices by sending a ICMP echo request (ping).""" 2 | from __future__ import annotations 3 | 4 | import asyncio 5 | from datetime import timedelta 6 | import logging 7 | import subprocess 8 | 9 | from icmplib import async_multiping 10 | import voluptuous as vol 11 | 12 | from homeassistant import util 13 | from homeassistant.components.device_tracker import ( 14 | CONF_SCAN_INTERVAL, 15 | PLATFORM_SCHEMA as BASE_PLATFORM_SCHEMA, 16 | SCAN_INTERVAL, 17 | AsyncSeeCallback, 18 | SourceType, 19 | ) 20 | from homeassistant.const import CONF_HOSTS 21 | from homeassistant.core import HomeAssistant 22 | import homeassistant.helpers.config_validation as cv 23 | from homeassistant.helpers.event import async_track_point_in_utc_time 24 | from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType 25 | from homeassistant.util.async_ import gather_with_concurrency 26 | from homeassistant.util.process import kill_subprocess 27 | 28 | from .const import DOMAIN, ICMP_TIMEOUT, PING_ATTEMPTS_COUNT, PING_PRIVS, PING_TIMEOUT 29 | 30 | _LOGGER = logging.getLogger(__name__) 31 | 32 | PARALLEL_UPDATES = 0 33 | CONF_PING_COUNT = "count" 34 | CONCURRENT_PING_LIMIT = 6 35 | 36 | PLATFORM_SCHEMA = BASE_PLATFORM_SCHEMA.extend( 37 | { 38 | vol.Required(CONF_HOSTS): {cv.slug: cv.string}, 39 | vol.Optional(CONF_PING_COUNT, default=1): cv.positive_int, 40 | } 41 | ) 42 | 43 | 44 | class HostSubProcess: 45 | """Host object with ping detection.""" 46 | 47 | def __init__(self, ip_address, dev_id, hass, config, privileged): 48 | """Initialize the Host pinger.""" 49 | self.hass = hass 50 | self.ip_address = ip_address 51 | self.dev_id = dev_id 52 | self._count = config[CONF_PING_COUNT] 53 | self._ping_cmd = ["ping", "-n", "-q", "-c1", "-W1", ip_address] 54 | 55 | def ping(self): 56 | """Send an ICMP echo request and return True if success.""" 57 | with subprocess.Popen( 58 | self._ping_cmd, 59 | stdout=subprocess.PIPE, 60 | stderr=subprocess.DEVNULL, 61 | close_fds=False, # required for posix_spawn 62 | ) as pinger: 63 | try: 64 | pinger.communicate(timeout=1 + PING_TIMEOUT) 65 | return pinger.returncode == 0 66 | except subprocess.TimeoutExpired: 67 | kill_subprocess(pinger) 68 | return False 69 | 70 | except subprocess.CalledProcessError: 71 | return False 72 | 73 | def update(self) -> bool: 74 | """Update device state by sending one or more ping messages.""" 75 | failed = 0 76 | while failed < self._count: # check more times if host is unreachable 77 | if self.ping(): 78 | return True 79 | failed += 1 80 | 81 | _LOGGER.debug("No response from %s failed=%d", self.ip_address, failed) 82 | return False 83 | 84 | 85 | async def async_setup_scanner( 86 | hass: HomeAssistant, 87 | config: ConfigType, 88 | async_see: AsyncSeeCallback, 89 | discovery_info: DiscoveryInfoType | None = None, 90 | ) -> bool: 91 | """Set up the Host objects and return the update function.""" 92 | 93 | privileged = hass.data[DOMAIN][PING_PRIVS] 94 | ip_to_dev_id = {ip: dev_id for (dev_id, ip) in config[CONF_HOSTS].items()} 95 | interval = config.get( 96 | CONF_SCAN_INTERVAL, 97 | timedelta(seconds=len(ip_to_dev_id) * config[CONF_PING_COUNT]) + SCAN_INTERVAL, 98 | ) 99 | _LOGGER.debug( 100 | "Started ping tracker with interval=%s on hosts: %s", 101 | interval, 102 | ",".join(ip_to_dev_id.keys()), 103 | ) 104 | 105 | if privileged is None: 106 | hosts = [ 107 | HostSubProcess(ip, dev_id, hass, config, privileged) 108 | for (dev_id, ip) in config[CONF_HOSTS].items() 109 | ] 110 | 111 | async def async_update(now): 112 | """Update all the hosts on every interval time.""" 113 | results = await gather_with_concurrency( 114 | CONCURRENT_PING_LIMIT, 115 | *(hass.async_add_executor_job(host.update) for host in hosts), 116 | ) 117 | await asyncio.gather( 118 | *( 119 | async_see(dev_id=host.dev_id, source_type=SourceType.ROUTER) 120 | for idx, host in enumerate(hosts) 121 | if results[idx] 122 | ) 123 | ) 124 | 125 | else: 126 | 127 | async def async_update(now): 128 | """Update all the hosts on every interval time.""" 129 | responses = await async_multiping( 130 | list(ip_to_dev_id), 131 | count=PING_ATTEMPTS_COUNT, 132 | timeout=ICMP_TIMEOUT, 133 | privileged=privileged, 134 | ) 135 | _LOGGER.debug("Multiping responses: %s", responses) 136 | await asyncio.gather( 137 | *( 138 | async_see(dev_id=dev_id, source_type=SourceType.ROUTER) 139 | for idx, dev_id in enumerate(ip_to_dev_id.values()) 140 | if responses[idx].is_alive 141 | ) 142 | ) 143 | 144 | async def _async_update_interval(now): 145 | try: 146 | await async_update(now) 147 | finally: 148 | if not hass.is_stopping: 149 | async_track_point_in_utc_time( 150 | hass, _async_update_interval, util.dt.utcnow() + interval 151 | ) 152 | 153 | await _async_update_interval(None) 154 | return True 155 | -------------------------------------------------------------------------------- /README.asciidoc: -------------------------------------------------------------------------------- 1 | 2 | == Home Assistant, Tasmota, AppDaemon, Custom Components 3 | 4 | v1.0.x is based on two docker-compose files. 5 | 6 | * netdc for networking (dnsmasq,wireguard,dnscrypt) 7 | * main: for hass component (appd,influx,Grafana..) 8 | 9 | === Hardware Components 10 | 11 | 1. Intel z83 12 | 2. Almost any Tasmota hardware 13 | 3. Xiaomi mii sesnors 14 | 15 | === Main components 16 | 17 | 1. Home water consumption/leakage detection. see https://github.com/hhaim/hass/wiki/Monitor-water-consumption-and-more-with-Home-Assistant[wiki] 18 | ** Tasmota + Wemo d1 19 | ** Sensor link:https://www.revaho.nl/wp-content/uploads/Ch_11_Water_meters.pdf[water sensor ev 1 litter] 20 | 2. Air Conditioner automation (Electra /Israel) 21 | ** Uses SonOff 4ch pro for cold switch on/off 22 | 3. Alarm: Converting old PIMA Alarm + xiaomi mii sensros to be new smart alarm inspired by link:https://konnected.io/products/konnected-alarm-panel-wired-alarm-system-conversion-kit[konnected] 23 | ** Tasmota/Wemo d1/i2c 16 gpio 24 | ** Could scale with gpio/cheaper 25 | ** It uses Tasmota firmware 26 | 4. Media automation 27 | 5. Israel Boiler -- keep the water always hot in specific temperature. see https://github.com/hhaim/hass/wiki/Smart-solar-water-heating-using-Home-Assistant[wiki] 28 | ** Tasmota +Sonoff TH16 + DS18b20 raw chip inside the mechanical termostat of the Boiler 29 | 6. Light -- turn them automaticly using PIR 30 | ** Tasmota + WemoD1 mini 31 | 7. Cameras -- BlueIris as DVR + PIR using Object detection Yolo3 32 | 8. Weather based Irrigation see link:https://github.com/hhaim/hass/wiki/Weather-based-irrigation[wiki] 33 | ** Tasmota +Sonoff 4CH Pro 34 | ** 48vAC Power 35 | ** Taps to control the taps 36 | 37 | 38 | === Home Assistant - custom components 39 | 40 | Used for defining Tasmota sensors/switch/binary sensors 41 | 42 | 43 | ==== How to install custom components into your setup 44 | 45 | Copy this project `/custom_components/` folder to your `` directory 46 | make sure you are in sync with the the right version of hass (see above) 47 | 48 | ==== tasmota_counter (for Pulse counter/Water) 49 | 50 | Using this, you could define a sensor that saves Tasmota counter_id pulses info to none-volatile home assistance database. 51 | See discussion here link:https://github.com/arendst/Sonoff-Tasmota/issues/4681[4681] 52 | 53 | [source,bash] 54 | ------------------- 55 | - platform: mytasmota 56 | name: water_total 57 | stopic: water_out 58 | id: 1 59 | max_valid_diff: 2000 60 | unit_of_measurement: 'l' 61 | icon: mdi:water-pump 62 | expire_after: 300 63 | value_template: "{{ (45497 + (value))|int }}" 64 | ------------------- 65 | 66 | 67 | *stopic*: the short topic. for example the full topic will be `tele/*stopic*/SENSOR` 68 | 69 | *counter_id*: the id of the counter 1..4 70 | 71 | *max_valid_diff*: maximum difference in 60 sec 72 | 73 | 74 | ==== mqtt_tasmota (Switch) 75 | 76 | Define a switch in a simpler way. It just works 77 | 78 | * Always in sync with hass 79 | * No need Option59, 80 | * No need startup script command 81 | * No need to define LWT/Qos. Qos is 1 82 | 83 | See discussion here link:https://github.com/home-assistant/home-assistant/issues/18703[18703] 84 | 85 | [source,yaml] 86 | ------------------- 87 | switch: 88 | - platform: mytasmota 89 | name: HASS_DEVICE 90 | index: '1' 91 | stopic: SHORT_TOPIC 92 | ------------------- 93 | 94 | *stopic*: the short topic. for example the full topic will be `tele/*stopic*/SENSOR` e.g. `tele/irrigation/SENSOR` 95 | 96 | *name*: e.g. wbi_p1 the switch.wbi_p1 the full name 97 | 98 | ==== tasmota_alarm MCP230xx 99 | 100 | Define 16/8 binary sensors based on MCP230xx chipset in a simple way. 101 | This chip has two mqtt async responses(interrupt and status) and it is tedious to define all of them. 102 | 103 | 104 | [source,yaml] 105 | ------------------- 106 | tasmota: 107 | devices: 108 | - name: HASS_NAME 109 | stopic: TOPIC 110 | binary_sensors: 111 | - name: door 112 | polar: true 113 | - name: vol 114 | polar: true 115 | - name: kitchen 116 | polar: true 117 | - name: backdoor 118 | polar: true 119 | ------------------- 120 | 121 | 122 | 123 | ==== dnsmasq tracker 124 | 125 | (does not work with docker-compose version) 126 | Using script to trigger tracker from link:https://jpmens.net/2013/10/21/tracking-dhcp-leases-with-dnsmasq/[tracking-dhcp] 127 | use custom component to let hass known link:custom_components/device_tracker/mqtt_dnsmasq.py[mqtt_dnsmasq.py] 128 | to get the info from link:linux_services/dnsmasq.sh[dnsmasq.sh] 129 | 130 | 131 | ==== Irrigation based on Weather actual data 132 | 133 | see wiki 134 | 135 | ==== HeatApp app 136 | 137 | A/C Type: Electra with SonOff 4ch for enable/disable, connected to CLK input 138 | (plan to reverse engineer modbus signal for better way controling this) 139 | 140 | Keep the A/C at specific temperator, at specific days of the week 141 | 142 | [source,yaml] 143 | ------------------- 144 | heater_ac1: 145 | module: heat_app 146 | class: HeatApp 147 | schedule: 148 | - { mode: a, start: { t: "17:10:00", d: 6}, end: { t: "17:11:00", d: 6} } 149 | - { mode: a, start: { t: "17:15:20", d: 6}, end: { t: "23:30:40", d: 6} } 150 | - { mode: a, start: { t: "08:00:00", d: 7}, end: { t: "13:30:00", d: 7} } 151 | - { mode: a, start: { t: "15:30:00", d: 7}, end: { t: "19:30:00", d: 7} } 152 | 153 | ------------------- 154 | 155 | 156 | ==== CBoilerAutomation app 157 | 158 | see wiki 159 | 160 | ==== CWaterMonitor app 161 | 162 | see wiki 163 | 164 | ==== Weather base irrigation 165 | 166 | see wiki 167 | 168 | -------------------------------------------------------------------------------- /cfg/hass/custom_components/tplink_router/device_tracker.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import TypeAlias 4 | from homeassistant.components.device_tracker.config_entry import ScannerEntity 5 | from homeassistant.components.device_tracker.const import SourceType 6 | from homeassistant.config_entries import ConfigEntry 7 | from homeassistant.core import HomeAssistant, callback 8 | from homeassistant.helpers.entity_platform import AddEntitiesCallback 9 | from homeassistant.helpers.update_coordinator import CoordinatorEntity 10 | from .coordinator import TPLinkRouterCoordinator 11 | from .const import ( 12 | DOMAIN, 13 | EVENT_NEW_DEVICE, 14 | EVENT_ONLINE, 15 | EVENT_OFFLINE, 16 | ) 17 | from tplinkrouterc6u import Device 18 | 19 | MAC_ADDR: TypeAlias = str 20 | 21 | 22 | async def async_setup_entry( 23 | hass: HomeAssistant, 24 | entry: ConfigEntry, 25 | async_add_entities: AddEntitiesCallback, 26 | ) -> None: 27 | coordinator = hass.data[DOMAIN][entry.entry_id] 28 | tracked: dict[MAC_ADDR, TPLinkTracker] = {} 29 | 30 | @callback 31 | def coordinator_updated(): 32 | """Update the status of the device.""" 33 | update_items(coordinator, async_add_entities, tracked) 34 | 35 | entry.async_on_unload(coordinator.async_add_listener(coordinator_updated)) 36 | coordinator_updated() 37 | 38 | 39 | @callback 40 | def update_items( 41 | coordinator: TPLinkRouterCoordinator, 42 | async_add_entities: AddEntitiesCallback, 43 | tracked: dict[MAC_ADDR, TPLinkTracker], 44 | ) -> None: 45 | """Update tracked device state from the hub.""" 46 | new_tracked: list[TPLinkTracker] = [] 47 | active: list[MAC_ADDR] = [] 48 | fire_event = tracked != {} 49 | for device in coordinator.status.devices: 50 | active.append(device.macaddr) 51 | if device.macaddr not in tracked: 52 | tracked[device.macaddr] = TPLinkTracker(coordinator, device) 53 | new_tracked.append(tracked[device.macaddr]) 54 | if fire_event: 55 | coordinator.hass.bus.fire(EVENT_NEW_DEVICE, tracked[device.macaddr].data) 56 | else: 57 | tracked[device.macaddr].device = device 58 | if fire_event and not tracked[device.macaddr].active and device.active: 59 | coordinator.hass.bus.fire(EVENT_ONLINE, tracked[device.macaddr].data) 60 | if fire_event and tracked[device.macaddr].active and not device.active: 61 | coordinator.hass.bus.fire(EVENT_OFFLINE, tracked[device.macaddr].data) 62 | tracked[device.macaddr].active = device.active 63 | 64 | if new_tracked: 65 | async_add_entities(new_tracked) 66 | 67 | for mac in tracked: 68 | if mac not in active and tracked[mac].active: 69 | tracked[mac].active = False 70 | coordinator.hass.bus.fire(EVENT_OFFLINE, tracked[mac].data) 71 | 72 | 73 | class TPLinkTracker(CoordinatorEntity, ScannerEntity): 74 | """Representation of network device.""" 75 | 76 | def __init__( 77 | self, 78 | coordinator: TPLinkRouterCoordinator, 79 | data: Device, 80 | ) -> None: 81 | """Initialize the tracked device.""" 82 | self.device = data 83 | self.active = True 84 | 85 | super().__init__(coordinator) 86 | 87 | @property 88 | def is_connected(self) -> bool: 89 | """Return true if the client is connected to the network.""" 90 | return self.active 91 | 92 | @property 93 | def source_type(self) -> str: 94 | """Return the source type of the client.""" 95 | return SourceType.ROUTER 96 | 97 | @property 98 | def name(self) -> str: 99 | """Return the name of the client.""" 100 | return self.device.hostname if self.device.hostname != '' else self.device.macaddr 101 | 102 | @property 103 | def hostname(self) -> str: 104 | """Return the hostname of the client.""" 105 | return self.device.hostname 106 | 107 | @property 108 | def mac_address(self) -> MAC_ADDR: 109 | """Return the mac address of the client.""" 110 | return self.device.macaddr 111 | 112 | @property 113 | def ip_address(self) -> str: 114 | """Return the ip address of the client.""" 115 | return self.device.ipaddr 116 | 117 | @property 118 | def unique_id(self) -> str: 119 | """Return an unique identifier for this device.""" 120 | return f"{self.coordinator.unique_id}_{DOMAIN}_{self.mac_address}" 121 | 122 | @property 123 | def icon(self) -> str: 124 | """Return device icon.""" 125 | return "mdi:lan-connect" if self.is_connected else "mdi:lan-disconnect" 126 | 127 | @property 128 | def extra_state_attributes(self) -> dict[str, str]: 129 | attributes = { 130 | 'connection': self.device.type.get_type(), 131 | 'band': self.device.type.get_band(), 132 | 'packets_sent': self.device.packets_sent, 133 | 'packets_received': self.device.packets_received 134 | } 135 | if self.device.down_speed is not None or self.device.up_speed is not None: 136 | attributes['up_speed'] = self.device.up_speed 137 | attributes['down_speed'] = self.device.down_speed 138 | return attributes 139 | 140 | @property 141 | def data(self) -> dict[str, str]: 142 | return dict(self.extra_state_attributes.items() | { 143 | 'hostname': self.hostname, 144 | 'ip_address': self.ip_address, 145 | 'mac_address': self.mac_address, 146 | }.items()) 147 | 148 | @property 149 | def entity_registry_enabled_default(self) -> bool: 150 | return True 151 | -------------------------------------------------------------------------------- /cfg/hass/custom_components/asusrouter/binary_sensor.py: -------------------------------------------------------------------------------- 1 | """AsusRouter binary sensor module.""" 2 | 3 | from __future__ import annotations 4 | 5 | from typing import Any 6 | 7 | from homeassistant.components.binary_sensor import ( 8 | BinarySensorDeviceClass, 9 | BinarySensorEntity, 10 | ) 11 | from homeassistant.config_entries import ConfigEntry 12 | from homeassistant.core import HomeAssistant, callback 13 | from homeassistant.helpers.dispatcher import async_dispatcher_connect 14 | from homeassistant.helpers.entity import DeviceInfo 15 | from homeassistant.helpers.entity_platform import AddEntitiesCallback 16 | from homeassistant.helpers.update_coordinator import DataUpdateCoordinator 17 | 18 | from .const import ( 19 | AIMESH, 20 | ASUSROUTER, 21 | CONF_DEFAULT_HIDE_PASSWORDS, 22 | CONF_HIDE_PASSWORDS, 23 | DOMAIN, 24 | MANUFACTURER, 25 | PASSWORD, 26 | STATIC_BINARY_SENSORS, 27 | ) 28 | from .dataclass import ARBinarySensorDescription 29 | from .entity import ARBinaryEntity, async_setup_ar_entry 30 | from .helpers import to_unique_id 31 | from .router import AiMeshNode, ARDevice 32 | 33 | 34 | async def async_setup_entry( 35 | hass: HomeAssistant, 36 | config_entry: ConfigEntry, 37 | async_add_entities: AddEntitiesCallback, 38 | ) -> None: 39 | """Set up AsusRouter binary sensors.""" 40 | 41 | binary_sensors = STATIC_BINARY_SENSORS.copy() 42 | 43 | hide = [] 44 | if config_entry.options.get(CONF_HIDE_PASSWORDS, CONF_DEFAULT_HIDE_PASSWORDS): 45 | hide.append(PASSWORD) 46 | 47 | await async_setup_ar_entry( 48 | hass, config_entry, async_add_entities, binary_sensors, ARBinarySensor, hide 49 | ) 50 | 51 | router = hass.data[DOMAIN][config_entry.entry_id][ASUSROUTER] 52 | tracked: set = set() 53 | 54 | @callback 55 | def update_router(): 56 | """Update the values of the router.""" 57 | 58 | add_entities(router, async_add_entities, tracked) 59 | 60 | router.async_on_close( 61 | async_dispatcher_connect(hass, router.signal_aimesh_new, update_router) 62 | ) 63 | 64 | update_router() 65 | 66 | 67 | class ARBinarySensor(ARBinaryEntity, BinarySensorEntity): 68 | """AsusRouter binary sensor.""" 69 | 70 | def __init__( 71 | self, 72 | coordinator: DataUpdateCoordinator, 73 | router: ARDevice, 74 | description: ARBinarySensorDescription, 75 | ) -> None: 76 | """Initialize AsusRouter binary sensor.""" 77 | 78 | super().__init__(coordinator, router, description) 79 | self.entity_description: ARBinarySensorDescription = description 80 | 81 | 82 | @callback 83 | def add_entities( 84 | router: ARDevice, 85 | async_add_entities: AddEntitiesCallback, 86 | tracked: set[str], 87 | ) -> None: 88 | """Add new tracker entities from the router.""" 89 | 90 | new_tracked = [] 91 | 92 | for mac, node in router.aimesh.items(): 93 | if mac in tracked: 94 | continue 95 | 96 | new_tracked.append(AMBinarySensor(router, node)) 97 | tracked.add(mac) 98 | 99 | if new_tracked: 100 | async_add_entities(new_tracked) 101 | 102 | 103 | class AMBinarySensor(BinarySensorEntity): 104 | """AsusRouter AiMesh sensor.""" 105 | 106 | def __init__( 107 | self, 108 | router: ARDevice, 109 | node: AiMeshNode, 110 | ) -> None: 111 | """Initialize AsusRouter AiMesh sensor.""" 112 | 113 | self._router = router 114 | self._node = node 115 | self._attr_unique_id = to_unique_id(f"{router.mac}_{AIMESH}_{node.mac}") 116 | self._attr_name = f"AiMesh {node.native.model} ({node.native.mac})" 117 | 118 | @property 119 | def is_on(self) -> bool: 120 | """Get the state.""" 121 | 122 | return self._node.native.status 123 | 124 | @property 125 | def device_class(self) -> BinarySensorDeviceClass: 126 | """Device class.""" 127 | 128 | return BinarySensorDeviceClass.CONNECTIVITY 129 | 130 | @property 131 | def extra_state_attributes(self) -> dict[str, Any]: 132 | """Return extra state attributes.""" 133 | 134 | return dict(sorted(self._node.extra_state_attributes.items())) or {} 135 | 136 | @property 137 | def device_info(self) -> DeviceInfo: 138 | """Return device info.""" 139 | 140 | device_info: DeviceInfo = DeviceInfo( 141 | identifiers={ 142 | (DOMAIN, self._node.mac), 143 | }, 144 | name=self._node.native.model, 145 | model=self._node.native.model, 146 | manufacturer=MANUFACTURER, 147 | sw_version=self._node.native.fw, 148 | ) 149 | if self._router.mac != self._node.mac: 150 | device_info = DeviceInfo( 151 | identifiers={ 152 | (DOMAIN, self._node.mac), 153 | }, 154 | name=self._node.native.model, 155 | model=self._node.native.model, 156 | manufacturer=MANUFACTURER, 157 | sw_version=self._node.native.fw, 158 | via_device=(DOMAIN, self._router.mac), 159 | ) 160 | 161 | return device_info 162 | 163 | @callback 164 | def async_on_demand_update(self) -> None: 165 | """Update the state.""" 166 | 167 | if self._node.mac in self._router.aimesh: 168 | self._node = self._router.aimesh[self._node.mac] 169 | self.async_write_ha_state() 170 | 171 | async def async_added_to_hass(self) -> None: 172 | """Register state update callback.""" 173 | 174 | self.async_on_remove( 175 | async_dispatcher_connect( 176 | self.hass, 177 | self._router.signal_aimesh_update, 178 | self.async_on_demand_update, 179 | ) 180 | ) 181 | -------------------------------------------------------------------------------- /cfg/hass/custom_components/asusrouter/device_tracker.py: -------------------------------------------------------------------------------- 1 | """AsusRouter device tracker module.""" 2 | 3 | from __future__ import annotations 4 | 5 | import logging 6 | from typing import Any, Optional 7 | 8 | from homeassistant.components.device_tracker import SourceType 9 | from homeassistant.components.device_tracker.config_entry import ScannerEntity 10 | from homeassistant.config_entries import ConfigEntry 11 | from homeassistant.core import HomeAssistant, callback 12 | from homeassistant.helpers import device_registry as dr 13 | from homeassistant.helpers.dispatcher import async_dispatcher_connect 14 | from homeassistant.helpers.entity import DeviceInfo 15 | from homeassistant.helpers.entity_platform import AddEntitiesCallback 16 | 17 | from .client import ARClient 18 | from .const import ( 19 | ASUSROUTER, 20 | CONF_DEFAULT_TRACK_DEVICES, 21 | CONF_TRACK_DEVICES, 22 | DEFAULT_DEVICE_NAME, 23 | DOMAIN, 24 | ) 25 | from .router import ARDevice 26 | 27 | _LOGGER = logging.getLogger(__name__) 28 | 29 | 30 | async def async_setup_entry( 31 | hass: HomeAssistant, 32 | config_entry: ConfigEntry, 33 | async_add_entities: AddEntitiesCallback, 34 | ) -> None: 35 | """Set up device tracker for AsusRouter component.""" 36 | 37 | # If device tracking is disabled 38 | if ( 39 | config_entry.options.get( 40 | CONF_TRACK_DEVICES, CONF_DEFAULT_TRACK_DEVICES 41 | ) 42 | is False 43 | ): 44 | return 45 | 46 | router = hass.data[DOMAIN][config_entry.entry_id][ASUSROUTER] 47 | tracked: set = set() 48 | 49 | @callback 50 | def update_router(): 51 | """Update the values of the router.""" 52 | 53 | add_entities(router, async_add_entities, tracked) 54 | 55 | router.async_on_close( 56 | async_dispatcher_connect(hass, router.signal_device_new, update_router) 57 | ) 58 | 59 | update_router() 60 | 61 | 62 | @callback 63 | def add_entities( 64 | router: ARDevice, 65 | async_add_entities: AddEntitiesCallback, 66 | tracked: set[str], 67 | ) -> None: 68 | """Add new tracker entities from the router.""" 69 | 70 | new_tracked = [] 71 | 72 | for mac, device in router.devices.items(): 73 | if mac in tracked: 74 | continue 75 | 76 | new_tracked.append(ARDeviceEntity(router, device)) 77 | tracked.add(mac) 78 | 79 | if new_tracked: 80 | async_add_entities(new_tracked) 81 | 82 | 83 | class ARDeviceEntity(ScannerEntity): 84 | """Connected device class.""" 85 | 86 | _attr_should_poll = False 87 | 88 | def __init__( 89 | self, 90 | router: ARDevice, 91 | client: ARClient, 92 | ) -> None: 93 | """Initialize connected device.""" 94 | 95 | self._router = router 96 | self._client = client 97 | self._attr_unique_id = f"{router.mac}_{client.mac_address}" 98 | self._attr_name = client.name or DEFAULT_DEVICE_NAME 99 | self._attr_capability_attributes = { 100 | "mac": client.mac_address, 101 | "name": self._attr_name, 102 | } 103 | 104 | # Assign device info if set up 105 | if router.client_device is True and client.device is True: 106 | self._attr_device_info = self._compile_device_info( 107 | client.mac_address, client.name 108 | ) 109 | 110 | def _compile_device_info( 111 | self, mac_address: str, name: Optional[str] 112 | ) -> DeviceInfo: 113 | """Compile device info.""" 114 | 115 | return DeviceInfo( 116 | connections={(dr.CONNECTION_NETWORK_MAC, mac_address)}, 117 | default_name=name, 118 | via_device=(DOMAIN, self._router.mac), 119 | ) 120 | 121 | @property 122 | def device_info(self) -> DeviceInfo: 123 | """Return device info.""" 124 | 125 | return self._attr_device_info 126 | 127 | @property 128 | def source_type(self) -> SourceType: 129 | """Source type.""" 130 | 131 | return SourceType.ROUTER 132 | 133 | @property 134 | def is_connected(self) -> Optional[bool]: 135 | """Device status.""" 136 | 137 | return self._client.state 138 | 139 | @property 140 | def ip_address(self) -> Optional[str]: 141 | """Device IP address.""" 142 | 143 | return self._client.ip_address 144 | 145 | @property 146 | def mac_address(self) -> str: 147 | """Device MAC address.""" 148 | 149 | return self._client.mac_address 150 | 151 | @property 152 | def hostname(self) -> Optional[str]: 153 | """Device hostname.""" 154 | 155 | return self._client.name 156 | 157 | @property 158 | def icon(self) -> str: 159 | """Device icon.""" 160 | 161 | return ( 162 | "mdi:lan-connect" if self._client.state else "mdi:lan-disconnect" 163 | ) 164 | 165 | @property 166 | def unique_id(self) -> Optional[str]: 167 | """Return unique ID of the entity.""" 168 | 169 | return self._attr_unique_id 170 | 171 | @property 172 | def extra_state_attributes(self) -> dict[str, Any]: 173 | """Return extra state attributes.""" 174 | 175 | return dict(sorted(self._client.extra_state_attributes.items())) or {} 176 | 177 | @callback 178 | def async_on_demand_update(self) -> None: 179 | """Update the state.""" 180 | 181 | if self._client.mac_address in self._router.devices: 182 | self._client = self._router.devices[self._client.mac_address] 183 | self.async_write_ha_state() 184 | 185 | async def async_added_to_hass(self) -> None: 186 | """Register state update callback.""" 187 | 188 | self.async_on_remove( 189 | async_dispatcher_connect( 190 | self.hass, 191 | self._router.signal_device_update, 192 | self.async_on_demand_update, 193 | ) 194 | ) 195 | -------------------------------------------------------------------------------- /cfg/hass/custom_components/asusrouter/light.py: -------------------------------------------------------------------------------- 1 | """AsusRouter light module.""" 2 | 3 | from __future__ import annotations 4 | 5 | import logging 6 | from typing import Any 7 | 8 | from homeassistant.components.light import ( 9 | ColorMode, 10 | LightEntity, 11 | LightEntityFeature, 12 | ) 13 | from homeassistant.config_entries import ConfigEntry 14 | from homeassistant.core import HomeAssistant 15 | from homeassistant.helpers.entity_platform import AddEntitiesCallback 16 | from homeassistant.helpers.update_coordinator import DataUpdateCoordinator 17 | 18 | from asusrouter.modules.aura import AsusAura 19 | from asusrouter.modules.color import ColorRGB, scale_value_int 20 | from asusrouter.modules.led import AsusLED 21 | 22 | from .const import ASUSROUTER, DOMAIN, STATIC_AURA, STATIC_LIGHTS 23 | from .dataclass import ARLightDescription 24 | from .entity import ARBinaryEntity, async_setup_ar_entry 25 | from .modules.aura import AURA_EFFECTS, AURA_NO_EFFECT, per_zone_light 26 | from .router import ARDevice 27 | 28 | _LOGGER = logging.getLogger(__name__) 29 | 30 | EFFECT = "effect" 31 | RGB_COLOR = "rgb_color" 32 | BRIGHTNESS = "brightness" 33 | COLOR_MODE = "color_mode" 34 | ZONE_ID = "zone_id" 35 | 36 | 37 | async def async_setup_entry( 38 | hass: HomeAssistant, 39 | config_entry: ConfigEntry, 40 | async_add_entities: AddEntitiesCallback, 41 | ) -> None: 42 | """Set up AsusRouter lights.""" 43 | 44 | leds = STATIC_LIGHTS.copy() 45 | if ( 46 | hass.data[DOMAIN][config_entry.entry_id][ 47 | ASUSROUTER 48 | ].bridge.identity.led 49 | is True 50 | ): 51 | await async_setup_ar_entry( 52 | hass, config_entry, async_add_entities, leds, ARLightLED 53 | ) 54 | 55 | auras = STATIC_AURA.copy() 56 | if ( 57 | hass.data[DOMAIN][config_entry.entry_id][ 58 | ASUSROUTER 59 | ].bridge.identity.aura 60 | is True 61 | ): 62 | # Create per-zone lights 63 | auras.extend( 64 | per_zone_light( 65 | hass.data[DOMAIN][config_entry.entry_id][ 66 | ASUSROUTER 67 | ].bridge.identity.aura_zone 68 | ) 69 | ) 70 | 71 | await async_setup_ar_entry( 72 | hass, config_entry, async_add_entities, auras, ARLightAura 73 | ) 74 | 75 | 76 | class ARLightLED(ARBinaryEntity, LightEntity): 77 | """AsusRouter LED light.""" 78 | 79 | _attr_color_mode = ColorMode.ONOFF 80 | _attr_supported_color_modes = {ColorMode.ONOFF} 81 | 82 | def __init__( 83 | self, 84 | coordinator: DataUpdateCoordinator, 85 | router: ARDevice, 86 | description: ARLightDescription, 87 | ) -> None: 88 | """Initialize AsusRouter LED light.""" 89 | 90 | super().__init__(coordinator, router, description) 91 | self.entity_description: ARLightDescription = description 92 | 93 | async def async_turn_on( 94 | self, 95 | **kwargs: Any, 96 | ) -> None: 97 | """Turn on LED.""" 98 | 99 | await self._set_state(AsusLED.ON) 100 | 101 | async def async_turn_off( 102 | self, 103 | **kwargs: Any, 104 | ) -> None: 105 | """Turn off LED.""" 106 | 107 | await self._set_state(AsusLED.OFF) 108 | 109 | 110 | class ARLightAura(ARBinaryEntity, LightEntity): 111 | """AsusRouter Aura light.""" 112 | 113 | def __init__( 114 | self, 115 | coordinator: DataUpdateCoordinator, 116 | router: ARDevice, 117 | description: ARLightDescription, 118 | ) -> None: 119 | """Initialize AsusRouter Aura light.""" 120 | 121 | self._attr_supported_features = LightEntityFeature.EFFECT 122 | self._attr_effect_list = AURA_EFFECTS 123 | 124 | super().__init__(coordinator, router, description) 125 | self.entity_description: ARLightDescription = description 126 | 127 | async def async_turn_on( 128 | self, 129 | **kwargs: Any, 130 | ) -> None: 131 | """Turn on Aura.""" 132 | 133 | effect = AsusAura.ON 134 | if "effect" in kwargs: 135 | effect = AsusAura.__members__.get( 136 | kwargs["effect"].upper(), AsusAura.ON 137 | ) 138 | 139 | color = None 140 | if "rgb_color" in kwargs: 141 | rgb = kwargs["rgb_color"] 142 | color = ColorRGB(rgb[0], rgb[1], rgb[2], scale=255) 143 | 144 | brightness = None 145 | if "brightness" in kwargs: 146 | brightness = scale_value_int(kwargs["brightness"], 128, 255) 147 | 148 | zone_id = ( 149 | self.entity_description.capabilities.get("zone_id", None) 150 | if self.entity_description.capabilities 151 | else None 152 | ) 153 | 154 | await self._set_state( 155 | effect, 156 | color=color, 157 | brightness=brightness, 158 | zone=zone_id, 159 | ) 160 | 161 | async def async_turn_off( 162 | self, 163 | **kwargs: Any, 164 | ) -> None: 165 | """Turn off Aura.""" 166 | 167 | await self._set_state(AsusAura.OFF) 168 | 169 | @property 170 | def effect(self) -> str | None: 171 | """Return the current effect.""" 172 | 173 | return self.coordinator.data.get("effect", AURA_NO_EFFECT) 174 | 175 | @property 176 | def supported_color_modes(self) -> set[str] | None: 177 | """Return the supported color modes.""" 178 | 179 | # This is a workaround to avoid a bug in HA 180 | # which does not hide the color picker and brightness slider 181 | # even when the current color mode is set to ONOFF only 182 | _supported_color_modes = self.coordinator.data.get( 183 | "color_mode", ColorMode.RGB 184 | ) 185 | return {_supported_color_modes} 186 | 187 | @property 188 | def color_mode(self) -> ColorMode | str | None: 189 | """Return the color mode.""" 190 | 191 | return self.coordinator.data.get("color_mode", ColorMode.RGB) 192 | -------------------------------------------------------------------------------- /cfg/hass/custom_components/mytasmota/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Support for Tasmota alarm based on MCP23017 I2C device 3 | 4 | """ 5 | import logging 6 | import json 7 | from typing import Dict, Any 8 | import voluptuous as vol 9 | 10 | from homeassistant.core import HomeAssistant 11 | from homeassistant.config_entries import ConfigEntry 12 | from homeassistant.const import ( 13 | CONF_DEVICES, 14 | CONF_BINARY_SENSORS, 15 | CONF_SWITCHES, 16 | CONF_HOST, 17 | CONF_PORT, 18 | CONF_ID, 19 | CONF_NAME, 20 | CONF_TYPE, 21 | CONF_PIN, 22 | CONF_ZONE, 23 | ATTR_ENTITY_ID, 24 | ATTR_STATE, 25 | STATE_ON, 26 | Platform 27 | ) 28 | from homeassistant.helpers import discovery 29 | from homeassistant.helpers import config_validation as cv 30 | from homeassistant.helpers.typing import ConfigType 31 | 32 | _LOGGER = logging.getLogger(__name__) 33 | 34 | DOMAIN = "mytasmota" 35 | 36 | CONF_SHORT_TOPIC ='stopic' # short_topic 37 | CONF_POLAR ='polar' 38 | 39 | PLATFORMS = [Platform.BINARY_SENSOR, Platform.SWITCH] 40 | 41 | 42 | def get_tasmota_avail_topic(topic: str) -> str: 43 | """Get Tasmota availability topic.""" 44 | return f'tele/{topic}/LWT' 45 | 46 | 47 | def get_tasmota_result(topic: str) -> str: 48 | """Get Tasmota result topic.""" 49 | return f'stat/{topic}/RESULT' 50 | 51 | 52 | def get_tasmota_tele(topic: str) -> str: 53 | """Get Tasmota telemetry topic.""" 54 | return f'tele/{topic}/SENSOR' 55 | 56 | 57 | def get_tasmota_state(topic: str) -> str: 58 | """Get Tasmota state topic.""" 59 | return f'tele/{topic}/STATE' 60 | 61 | 62 | def get_tasmota_command(topic: str, _index: str) -> str: 63 | """Get Tasmota command topic.""" 64 | return f'cmnd/{topic}/POWER{_index}' 65 | 66 | _BINARY_SENSOR_SCHEMA = vol.All( 67 | vol.Schema({ 68 | vol.Required(CONF_NAME): cv.string, 69 | vol.Required(CONF_POLAR): cv.boolean, 70 | }), 71 | ) 72 | 73 | _SWITCH_SCHEMA = vol.All( 74 | vol.Schema({ 75 | vol.Required(CONF_NAME): cv.string, 76 | vol.Required(CONF_POLAR): cv.boolean, 77 | }), 78 | ) 79 | 80 | # pylint: disable=no-value-for-parameter 81 | CONFIG_SCHEMA = vol.Schema( 82 | { 83 | DOMAIN: vol.Schema({ 84 | vol.Required(CONF_DEVICES): [{ 85 | vol.Required(CONF_SHORT_TOPIC): cv.string, 86 | vol.Required(CONF_NAME): cv.string, 87 | vol.Optional(CONF_BINARY_SENSORS): vol.All( 88 | cv.ensure_list, [_BINARY_SENSOR_SCHEMA]), 89 | vol.Optional(CONF_SWITCHES): vol.All( 90 | cv.ensure_list, [_SWITCH_SCHEMA]), 91 | }], 92 | }), 93 | }, 94 | extra=vol.ALLOW_EXTRA, 95 | ) 96 | 97 | DEPENDENCIES = ['mqtt'] 98 | 99 | 100 | async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: 101 | """Set up the Tasmota component.""" 102 | cfg = config.get(DOMAIN, {}) 103 | 104 | if DOMAIN not in hass.data: 105 | hass.data[DOMAIN] = {} 106 | 107 | devices = cfg.get(CONF_DEVICES, []) 108 | 109 | if devices: 110 | for device in devices: 111 | configured_device = ConfiguredDevice(hass, device) 112 | await configured_device.async_save_data() 113 | 114 | return True 115 | 116 | 117 | async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: 118 | """Set up Tasmota from a config entry.""" 119 | hass.data.setdefault(DOMAIN, {}) 120 | hass.data[DOMAIN][entry.entry_id] = entry.data 121 | 122 | # Forward setup to platforms 123 | await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) 124 | 125 | return True 126 | 127 | 128 | async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: 129 | """Unload a config entry.""" 130 | unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS) 131 | 132 | if unload_ok: 133 | hass.data[DOMAIN].pop(entry.entry_id) 134 | 135 | return unload_ok 136 | 137 | 138 | class ConfiguredDevice: 139 | """Representation of a configured Tasmota device.""" 140 | 141 | def __init__(self, hass: HomeAssistant, config: Dict[str, Any]): 142 | """Initialize the configured device.""" 143 | self.hass = hass 144 | self.config = config 145 | 146 | @property 147 | def device_id(self) -> str: 148 | """Return the device ID.""" 149 | return self.config.get(CONF_SHORT_TOPIC) 150 | 151 | async def async_save_data(self) -> None: 152 | """Save the device configuration to hass.data and load platforms.""" 153 | device_data = self.config 154 | 155 | if CONF_DEVICES not in self.hass.data[DOMAIN]: 156 | self.hass.data[DOMAIN][CONF_DEVICES] = {} 157 | 158 | self.hass.data[DOMAIN][CONF_DEVICES][self.device_id] = device_data 159 | 160 | # Load binary sensor platform if configured 161 | if CONF_BINARY_SENSORS in device_data: 162 | self.hass.async_create_task( 163 | discovery.async_load_platform( 164 | self.hass, 165 | Platform.BINARY_SENSOR, 166 | DOMAIN, 167 | {'device_id': self.device_id}, 168 | self.config 169 | ) 170 | ) 171 | 172 | # Load switch platform if configured 173 | if CONF_SWITCHES in device_data: 174 | self.hass.async_create_task( 175 | discovery.async_load_platform( 176 | self.hass, 177 | Platform.SWITCH, 178 | DOMAIN, 179 | {'device_id': self.device_id}, 180 | self.config 181 | ) 182 | ) 183 | 184 | 185 | def get_device_config(hass: HomeAssistant, device_id: str) -> Dict[str, Any]: 186 | """Get device configuration by device ID.""" 187 | return hass.data[DOMAIN][CONF_DEVICES].get(device_id, {}) 188 | 189 | 190 | def get_all_devices(hass: HomeAssistant) -> Dict[str, Dict[str, Any]]: 191 | """Get all configured devices.""" 192 | return hass.data[DOMAIN].get(CONF_DEVICES, {}) 193 | -------------------------------------------------------------------------------- /cfg/hass/custom_components/cloudflare_dns/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | 3 | sync openWrt multi-wan ipv4 public ip and local public ipv6 to cloudflare domains specified spec using API and token. 4 | 5 | How it works: 6 | at startup it just sync local to remote after that it pool the local and in case it was changed update the remote 7 | 8 | """ 9 | import logging 10 | import voluptuous as vol 11 | 12 | from homeassistant.core import HomeAssistant, callback 13 | from homeassistant.const import ( 14 | CONF_API_KEY, 15 | CONF_NAME 16 | ) 17 | 18 | from homeassistant.helpers import discovery 19 | from homeassistant.helpers import config_validation as cv 20 | from homeassistant.helpers.entity import Entity 21 | from homeassistant.helpers.event import async_call_later 22 | from .sync import IPInterfaceV4, IPInterfaceV6, IPSynchronizer 23 | from homeassistant.helpers.entity_component import EntityComponent 24 | 25 | 26 | _LOGGER = logging.getLogger(__name__) 27 | 28 | DOMAIN = "cloudflare_dns" 29 | 30 | DEFAULT_NAME = 'cloudflare_dns' 31 | CONF_DEBUG = "debug" 32 | 33 | CONF_OWRT_HOST ="owrt_host" # router openwrt user 34 | CONF_OWRT_USER ="owrt_user" # router openwrt user 35 | CONF_OWRT_PASS ="owrt_pass" # router openwrt 36 | CONF_CF_DOMAIN ="domain" 37 | CONF_CF_IPV4_DOMAIN ="ipv4_domain" 38 | CONF_CF_IPV6_DOMAIN ="ipv6_domain" 39 | CONF_SYNC_CNT ="sync_count" 40 | CONF_SYNC_SEC ="sync_sec" 41 | 42 | 43 | 44 | # pylint: disable=no-value-for-parameter 45 | CONFIG_SCHEMA = vol.Schema( 46 | { 47 | DOMAIN: vol.Schema({ 48 | vol.Required(CONF_API_KEY): cv.string, # cloudflare API 49 | vol.Required(CONF_NAME): cv.string, 50 | vol.Required(CONF_OWRT_HOST): cv.string, 51 | vol.Required(CONF_OWRT_USER): cv.string, 52 | vol.Required(CONF_OWRT_PASS): cv.string, 53 | vol.Required(CONF_CF_DOMAIN):cv.string, 54 | vol.Required(CONF_CF_IPV4_DOMAIN):cv.string, 55 | vol.Required(CONF_CF_IPV6_DOMAIN):cv.string, 56 | 57 | vol.Optional(CONF_DEBUG, default=False): cv.boolean, 58 | vol.Optional(CONF_SYNC_SEC, default=10.0): vol.Coerce(float), 59 | vol.Optional(CONF_SYNC_CNT, default=3600): vol.Coerce(int), 60 | }), 61 | }, 62 | extra=vol.ALLOW_EXTRA, 63 | ) 64 | 65 | ENTITY_ID = "cloudflare_dns.cloudflare_dns" 66 | DATA_KEY = 'cloudflare_dns.devices' 67 | 68 | DEPENDENCIES = ['discovery'] 69 | 70 | async def async_setup(hass, config): 71 | """Track the state of the sun.""" 72 | cfg = config.get(DOMAIN) 73 | if DOMAIN not in hass.data: 74 | hass.data[DOMAIN] = { 75 | } 76 | 77 | 78 | component = EntityComponent(_LOGGER, DOMAIN, hass) 79 | 80 | # Create your entities 81 | entities = [ 82 | CFDnsSensor(hass, cfg, False), 83 | CFDnsSensor(hass, cfg, True) 84 | ] 85 | 86 | # Add entities through the component 87 | await component.async_add_entities(entities) 88 | 89 | return True 90 | 91 | 92 | class CFDnsSensor(Entity): 93 | 94 | 95 | """""" 96 | def __init__(self, hass, conf,ipv6): 97 | """Initialize the sensor.""" 98 | super().__init__() 99 | self.hass = hass 100 | _synco =None 101 | self._ipv6 =ipv6 102 | 103 | if ipv6 == False: 104 | _synco = IPInterfaceV4( conf.get(CONF_OWRT_HOST), 105 | conf.get(CONF_OWRT_USER), 106 | conf.get(CONF_OWRT_PASS), 107 | conf.get(CONF_API_KEY) , 108 | conf.get(CONF_CF_DOMAIN), 109 | conf.get(CONF_CF_IPV4_DOMAIN)) 110 | self._name ='cf_ipv4' 111 | 112 | else: 113 | _synco = IPInterfaceV6( 114 | conf.get(CONF_API_KEY) , 115 | conf.get(CONF_CF_DOMAIN), 116 | conf.get(CONF_CF_IPV6_DOMAIN)) 117 | self._name ='cf_ipv6' 118 | 119 | self.entity_id = f"{DOMAIN}.cloudflare_dns_{self._name}" 120 | self._sync= IPSynchronizer(_synco) 121 | 122 | self._timer_sec = conf.get(CONF_SYNC_SEC) 123 | 124 | self._debug = conf.get(CONF_DEBUG) 125 | self._cnt_max = conf.get(CONF_SYNC_CNT) 126 | self._cnt = 0 127 | self._timer_handler = None 128 | self._state =0 129 | self.start_timer() 130 | 131 | 132 | def start_timer(self): 133 | if self._timer_handler != None : 134 | self._timer_handler() 135 | self._timer_handler= None 136 | 137 | self._timer_handler = async_call_later( 138 | self.hass, self._timer_sec, self._timer_callback) 139 | 140 | 141 | async def _timer_callback(self, now): 142 | 143 | try: 144 | self._timer_handler = None 145 | await self.hass.async_add_executor_job(self.blocking_sync) # run blocking job 146 | except Exception as err: 147 | self._sync.do_backoff() 148 | self._state = self._state + 1 149 | self.async_write_ha_state() 150 | _LOGGER.error("Error while trying to get : %s", err) 151 | 152 | self.start_timer() 153 | 154 | 155 | def blocking_sync(self): 156 | self._sync.do_sync() 157 | self._cnt =self._cnt +1 158 | if self._cnt > self._cnt_max: 159 | self._cnt =0 160 | self._sync.force_sync() # from time to time force sync to make sure 161 | 162 | 163 | @property 164 | def name(self): 165 | """Return the name.""" 166 | return self._name 167 | 168 | @property 169 | def state(self): 170 | """Return the state of the sun.""" 171 | return self._state 172 | 173 | @property 174 | def should_poll(self): 175 | """No polling needed.""" 176 | return False 177 | 178 | @property 179 | def unit_of_measurement(self): 180 | """Return the unit this state is expressed in.""" 181 | return None 182 | 183 | @property 184 | def state_attributes(self): 185 | """Return the state attributes.""" 186 | return { 187 | "is_ipv6" : self._ipv6} 188 | --------------------------------------------------------------------------------