initial commit

This commit is contained in:
2023-11-21 12:23:38 +01:00
commit 0ef3211104
729 changed files with 546434 additions and 0 deletions
+1
View File
@@ -0,0 +1 @@
2023.11.2
+28
View File
@@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCV65U5UIJDDUCg
6hoPedwa+ufSscBsam2y9Z31nE1q90kkz8l57Y+yoky37Ab8YLV7Oni2Wo9wuw4B
02jgKJs3kyPN1CcBCXItUryN0t7l8EKieipgZce5qc00SdDL3lQK8A6ZAdh0+5Xb
SKQO7r/v73fHMquTdRBht2Qef2GgzDl/dFEJA3zDnZdfYnBek7J1sFcM/G7FD5V/
lgmD/uU7ou+9ZosBAu1WixruSx0k9W26IS4qflUrDWIzzTYCGDbBb1tShqjVINCy
wFS/Qto52ajda8ojSSvn4PCY9ph3IgWAuerrkEU0Ev6AO0aDfBpWD7zRlewdCQxf
3lVaT1tdAgMBAAECggEACrzi5UYE7nSGin/5+d3KjAwoioi8tYcx8D1zYOIfEWhA
zugrEBaZ0juZrxqyrWTn2rmnZ7g4yeQ77pmLy3JdG6/oQpfNa1+X0/M3Ghleaa25
uBVz74+arIui7PnUgKWd82KaSQ8+x7I1W41qYr9q8+5878GLVHZwA++0thJ9lZyu
+P2VyJ3uDZd9/lCB37WnJUxKIF5M1eRG1iXvB4Ap8X/SxPwXHq1TOmL5PGtykADM
fxXJgPvFwdDzy+oRjqjmyf+vCFjVkzDXkUBSYztPoIoYi+7LNRXJqQJUYsAO02zf
vGSBCWPvbYR6nHg7ML5adVpf0UXQ4D18BwRTtLw2QQKBgQDFxltxP/78qnPXLIem
GjyHAlJfnblTbLNM8PNhqBKbJ1qYw74GsLM6XuV2pTcZrRpbw0jJ9i+sJgNyEiaH
rTdj1ZhSSAlayhgSGN6L+j2KbXioS88mKPtf5tEPaZ+47ohFKgYq7BzgsZTmc+Lj
GmHT4kbnx9L4EsDFZC6RPjfS5QKBgQDCDpT1ssglJ83SpJfOuirykgxM6fLe6CuY
mGj/6e282E3HY04yWzHRTlna2zOzb9Gpldyc/ZdDmhsxa5yQ12qdwJ7OI3mZvXVz
jVgmmUxF4XIRXWaWBc/GLgBshj+N3UJtezq1GJgbPavKHdxHbyBtTJS9R2i9Y+wz
+gf3+MGHGQKBgQCMBV5yNKvTuN4gxk8Xg1W9BCDLv0AZV0kuPR0i07qwjFEw/MyJ
sgqCVfNBZ8E19X9G3mCnVBv2Vkd0OHN89V7U2MFp58RvucsG7QKKudVeLxkQlfOI
AdXO1PzIW7CQXpit1w3cz6O2VSOlcQB+ZwxB1d77dFbh+Wzvk3kArbsaHQKBgAkG
dJcYwYY427G4RfcvYV/2uuu41brYQW7PZ9K/Jndp9/QWAkUFSMPbSQKMv1LRGIcy
mRgUEzzppeoefyGdYzADDV+49qbX0OaW5EqnRozZd1ynY2YGzysAaLSjacFHUReW
a5yQkEJ0E9FKojwQRwnOTfyfnZdxVkL0lfpKb3XBAoGBAKbcgNL3Codl/n0XDc1Y
VLSfQM6Xne9qTQtuTDDmjdNJCFoD7nL0ZhDkMvy4knN0FlDBpXIwGbq3EnHgbcr9
N1+tJY9Vy/BEOWqbVb4NQIZl2VEiWCZYM7PaQeShGtZzqoNEguuRFzNLomUpLs8v
Gn9p5X3mwNd2dvC7vKqP66EU
-----END PRIVATE KEY-----
+1
View File
@@ -0,0 +1 @@
QAAAAAsvBZRdW09aVd5fDAkd7JXRvA9WGnyDRjuA/hI0RZDr6rmABSJ3mPaY8ODnK0kjymvdqNk52kK/VMCy0CDVqIZSW2/BNhgCNs0zYg0rVX4qLiG6bfUkHUvuGotW7QIBi2a976I75f6DCZZ/lQ/FbvwMV7B1spNecGJfl53DfAMJUXR/OcygYX8eZLdhEHWTqzLHd+/vv+4OpEjblft02AGZDvAKVN7L0Ek0zam5x2VgKnqiQvDl3tKNvFItcgkBJ9TNI5M3myjgaNMBDrtwj1q2eDp7tWD8Buy3TKKyj+15yc8kSfdqTZz1nfWybWpswLHS5/oa3HkPGuqgQA1DglA5leuVIKA5JTwDIq+q2A90OOIq7cnC6XCC0d97pMdwpZmSW2vCYl9XWPOz2SjauSnInLy2aAXSL/BNmf5bH3/6g+k5Sn3lX9vKU+tP/3LKCXA01GN1ywe2glAs6cT9Wpy3+oZ6+1Kxvf+wOikHZMubHCzJqBxztzi4E+IkaqOrDtnKuIz20UO0Eu+txy1cYx++/B5P8ssXdJfo6xvg2ewWHQW0Vb2wdg6E8Q4k7WSa+jN7jONfJdvnKiHPva0sv94Az7KKzjbcY6y/viZnR4B13kjo/lRb7KyoILWdbD+fMuHLGOe+eEFy9KfADtTtABc7DmWDunxIWfqhwA/4tk2ks5JzRQEAAQA= unknown@homeassistant
+8
View File
@@ -0,0 +1,8 @@
{
"version": 1,
"minor_version": 1,
"key": "application_credentials",
"data": {
"items": []
}
}
+24
View File
@@ -0,0 +1,24 @@
{
"version": 1,
"minor_version": 2,
"key": "assist_pipeline.pipelines",
"data": {
"items": [
{
"conversation_engine": "homeassistant",
"conversation_language": "de",
"id": "01gzwrs2xf13mg2d4m89wp5swq",
"language": "de",
"name": "Home Assistant",
"stt_engine": null,
"stt_language": null,
"tts_engine": "google_translate",
"tts_language": "de",
"tts_voice": null,
"wake_word_entity": null,
"wake_word_id": null
}
],
"preferred_item": "01gzwrs2xf13mg2d4m89wp5swq"
}
}
+842
View File
@@ -0,0 +1,842 @@
{
"version": 1,
"minor_version": 1,
"key": "auth",
"data": {
"users": [
{
"id": "3044f6b530044a0fbfa1f21ecb939be5",
"group_ids": [
"system-read-only"
],
"is_owner": false,
"is_active": true,
"name": "Home Assistant Content",
"system_generated": true,
"local_only": false
},
{
"id": "7b2d06001c034891a51bd39c8d5da6db",
"group_ids": [
"system-admin"
],
"is_owner": true,
"is_active": true,
"name": "gorden",
"system_generated": false,
"local_only": false
}
],
"groups": [
{
"id": "system-admin",
"name": "Administrators"
},
{
"id": "system-users",
"name": "Users"
},
{
"id": "system-read-only",
"name": "Read Only"
}
],
"credentials": [
{
"id": "4c7a5e40e57549f6994f42e7e7bf199e",
"user_id": "7b2d06001c034891a51bd39c8d5da6db",
"auth_provider_type": "homeassistant",
"auth_provider_id": null,
"data": {
"username": "gorden"
}
}
],
"refresh_tokens": [
{
"id": "445aef289a8148bb8b522d08ed24409c",
"user_id": "3044f6b530044a0fbfa1f21ecb939be5",
"client_id": null,
"client_name": null,
"client_icon": null,
"token_type": "system",
"created_at": "2022-05-09T10:26:42.645155+00:00",
"access_token_expiration": 1800.0,
"token": "feaa3caa43c96799f18b7400785994a658f792bd7ec1be919045bbab2db75d8a18e943923bd1465e548ec05fa6452ba86ce00a4abd00274871f0b61b5c0a12ab",
"jwt_key": "80949e38132e0209fb924e5000f98f40da112dcf3bf41e2fc9b369b399f3ecbf3b46aaaa100412ad30986431b3049df807d45f3c25277d819a48d2b7007e746d",
"last_used_at": null,
"last_used_ip": null,
"credential_id": null,
"version": "2022.5.3"
},
{
"id": "7df80a7380bb4b8db41076a8e6dbfefe",
"user_id": "7b2d06001c034891a51bd39c8d5da6db",
"client_id": "https://home-assistant.io/android",
"client_name": null,
"client_icon": null,
"token_type": "normal",
"created_at": "2022-10-11T08:48:51.623772+00:00",
"access_token_expiration": 1800.0,
"token": "5e171038cf974ecc0432b837615b49c0cc7ef703f3c58ad2a6306c3a2eaf7c74ee44f967ddbce953876ac3f8e80e85d48b00f795a55a77a5e07ada6fd224b020",
"jwt_key": "1e126b228e5869c363e635ed839be4b364458c54330f526e4fcdafc905ff395ff4e0ca94963c4eaaf8e959d2c7855d51c603f0a9ce53fa5942f1d494451eb35a",
"last_used_at": "2023-08-11T11:58:12.624371+00:00",
"last_used_ip": "192.168.0.180",
"credential_id": "4c7a5e40e57549f6994f42e7e7bf199e",
"version": "2022.10.2"
},
{
"id": "c41398fa24ca4b23b1047dc224d839d6",
"user_id": "7b2d06001c034891a51bd39c8d5da6db",
"client_id": "https://home-assistant.io/android",
"client_name": null,
"client_icon": null,
"token_type": "normal",
"created_at": "2022-12-29T10:29:30.985270+00:00",
"access_token_expiration": 1800.0,
"token": "8614ffdcc8ab3abf3dddfbfc98920b8e6343b0b9064181668db1f54a57a094dc51ebfdab79c30a836eb6628679bf654c2b271de89298ff717e8f25044e519945",
"jwt_key": "98428c4aa8d69aa1b48c2b48b459e7755f0c95556cb786d599086938514d6d98216196d54b8aae0c5a33d49b9670e59648e6ba539501fd6e0279f536b4cb8499",
"last_used_at": "2023-11-18T14:07:01.994212+00:00",
"last_used_ip": "192.168.0.178",
"credential_id": "4c7a5e40e57549f6994f42e7e7bf199e",
"version": "2022.12.8"
},
{
"id": "367e32eaded44577a273e3f6413d9ec6",
"user_id": "7b2d06001c034891a51bd39c8d5da6db",
"client_id": "https://home-assistant.io/android",
"client_name": null,
"client_icon": null,
"token_type": "normal",
"created_at": "2023-01-10T14:42:20.627061+00:00",
"access_token_expiration": 1800.0,
"token": "d6ebb405950a3446b8adcb4183768a4c2b87969b8ce9e2d932244f21898444fb4cdc740d56e1534840cc6f548e34a56d2193ed697d8c782c75852da6bf5581af",
"jwt_key": "2463f10bf3b7cf205545d8277630dd0174250944de274e25971dab3a9addfa5efd0a71f63c332f030163aadad0d675fb7720a9fdb23fa7e494455b699ba01269",
"last_used_at": "2023-11-07T16:17:11.761638+00:00",
"last_used_ip": "192.168.0.28",
"credential_id": "4c7a5e40e57549f6994f42e7e7bf199e",
"version": "2023.1.2"
},
{
"id": "d813ed399af14fce9960460a415c1212",
"user_id": "7b2d06001c034891a51bd39c8d5da6db",
"client_id": "http://192.168.0.40:8123/",
"client_name": null,
"client_icon": null,
"token_type": "normal",
"created_at": "2023-02-14T11:21:15.450628+00:00",
"access_token_expiration": 1800.0,
"token": "587f5f694a8ef1919d7d5971ec6906ca655c1f9c27478a4eb553f5deaf4701c12c5dd3d62224c9901de45d7194bb802f84973bd7b461fb9fd6e88fab9ae6ca56",
"jwt_key": "6bd079ea8b5ae5bca360eabec940acee9f816cd7e152f13b1a6195df7a1fa9632ce0ba354c0ccf3e5aedc94e96f94f5ddf4f1626f34e11800fc72c341ba67667",
"last_used_at": "2023-02-14T11:21:15.451030+00:00",
"last_used_ip": "192.168.0.28",
"credential_id": "4c7a5e40e57549f6994f42e7e7bf199e",
"version": "2023.2.4"
},
{
"id": "9610179f22ab4075aac94945b518665c",
"user_id": "7b2d06001c034891a51bd39c8d5da6db",
"client_id": "http://192.168.0.40:8123/",
"client_name": null,
"client_icon": null,
"token_type": "normal",
"created_at": "2023-02-15T20:12:46.738757+00:00",
"access_token_expiration": 1800.0,
"token": "17eaf2897aa5288e449d6647e6c8326aa743f7130305271ea3119f9d06d748add9abbc6797f42b02cbea7a646079a997792a3cfd7aa2633dc49e4e16b039063e",
"jwt_key": "8d91bf9756d3daafa536f18b552482adbe86a57ddd61f4328c49a3fe24c9883dc6bb9b4ba05db82356a8f7385974d1ae2efebedb0e9b6680515c9b93607359cb",
"last_used_at": "2023-02-15T20:12:46.739259+00:00",
"last_used_ip": "192.168.0.127",
"credential_id": "4c7a5e40e57549f6994f42e7e7bf199e",
"version": "2023.2.4"
},
{
"id": "699aafd868e6429399b66a6cea89ede2",
"user_id": "7b2d06001c034891a51bd39c8d5da6db",
"client_id": "http://192.168.0.40:8123/",
"client_name": null,
"client_icon": null,
"token_type": "normal",
"created_at": "2023-02-16T08:45:12.863490+00:00",
"access_token_expiration": 1800.0,
"token": "38b407147c073495a2abaadd54434ad5ea705e909b82d7b9285a4fba4191ec2d8b33927297d21b12c4e64efaf7144c26db0477196e3f11e84e11deabcf8f0f56",
"jwt_key": "e197485101e104bd4f65c3767e554d6f2daa5f0d8c8d8827ce962af590c71c255319fcecf9210ad07a25b0de09ba87524e33c97c9b1f93a75bb276d67a043a98",
"last_used_at": "2023-02-16T15:00:43.346152+00:00",
"last_used_ip": "192.168.0.28",
"credential_id": "4c7a5e40e57549f6994f42e7e7bf199e",
"version": "2023.2.4"
},
{
"id": "7b4cf31f0f524a63bd5b745e7af3b8a9",
"user_id": "7b2d06001c034891a51bd39c8d5da6db",
"client_id": "http://192.168.0.40:8123/",
"client_name": null,
"client_icon": null,
"token_type": "normal",
"created_at": "2023-02-16T15:00:52.596471+00:00",
"access_token_expiration": 1800.0,
"token": "c94688f2de38621ee3635b36e4a35f4b69ad73699e5c8360a43d2f8daa3dd699b6f5cccb45bc786e1c5355fb8633525d9a2cb83f631a8ddeae0da87408168ce1",
"jwt_key": "e6f8ed7fae5f552c9174c205eaa8de2958fd59763f5b87438c26410e9b0d430bcff3ef398e2f2ee687ad0623d5ee8ced4c4f93dfbc201f9944d8a9b733d9eb53",
"last_used_at": "2023-02-16T15:00:52.596857+00:00",
"last_used_ip": "192.168.0.28",
"credential_id": "4c7a5e40e57549f6994f42e7e7bf199e",
"version": "2023.2.4"
},
{
"id": "3f8e4b1fc83f47a794144c776efa1bbc",
"user_id": "7b2d06001c034891a51bd39c8d5da6db",
"client_id": "http://192.168.0.40:8123/",
"client_name": null,
"client_icon": null,
"token_type": "normal",
"created_at": "2023-02-17T16:54:49.872841+00:00",
"access_token_expiration": 1800.0,
"token": "cc8070e2a7364228dbe6c991de675be6c9c827c08440aa5496b0f84ed7b2831b623f71599b2b9861b6b02ede95609568a2db908881bbc8d70f71be8028dafb79",
"jwt_key": "902e35fcc18c54c1904476b34c5f2444c447832b43a490f6bbad8173b7d2b3f3d71791bf5f4b5c91232b598b53eae103eceb2d8ea295751fb3b6a062c4cfca60",
"last_used_at": "2023-02-17T16:54:49.873070+00:00",
"last_used_ip": "192.168.0.126",
"credential_id": "4c7a5e40e57549f6994f42e7e7bf199e",
"version": "2023.2.4"
},
{
"id": "5a16f1c8ee824f92a2c199eacb382d25",
"user_id": "7b2d06001c034891a51bd39c8d5da6db",
"client_id": "http://192.168.0.40:8123/",
"client_name": null,
"client_icon": null,
"token_type": "normal",
"created_at": "2023-02-28T12:37:57.448935+00:00",
"access_token_expiration": 1800.0,
"token": "9c16a3f4a2f27c416d313940ae3efb30466319813cfcc364acfde100b91a4771c94bad166fa991ac8eb670802fdf6883619b982c46729fd43018d4bf9b10512f",
"jwt_key": "c637540abc3229aa363ba8ba865dd8bd96465d82b3cf011ba2023fd736281354109020b254e609f8b7e97372bd4241a36cec47a9bcd68fbca7890e31656eac93",
"last_used_at": "2023-02-28T12:37:57.449184+00:00",
"last_used_ip": "192.168.0.28",
"credential_id": "4c7a5e40e57549f6994f42e7e7bf199e",
"version": "2023.2.5"
},
{
"id": "85bebe4be2dd4809a1f6bfc25b3eee3b",
"user_id": "7b2d06001c034891a51bd39c8d5da6db",
"client_id": "http://192.168.0.40:8123/",
"client_name": null,
"client_icon": null,
"token_type": "normal",
"created_at": "2023-03-06T12:12:20.512897+00:00",
"access_token_expiration": 1800.0,
"token": "dd7cbb9843fb64525142026e682291811661c6d5a6d4ffda09ddbd7eb7806ca4550ebeac6cb21d65d5efbfa9006152c8d39058738dc93405d83ec2742c63e838",
"jwt_key": "43c6070a4c42cfe9c7ffc8f097266513b736d8a6c5bbc0972bc4f0ac7f886f52779872484f6c453543c89e51d1ba509c484473e0fd2092aae857861b65451a52",
"last_used_at": "2023-03-06T12:12:20.513306+00:00",
"last_used_ip": "192.168.0.28",
"credential_id": "4c7a5e40e57549f6994f42e7e7bf199e",
"version": "2023.3.1"
},
{
"id": "7d23a256065d44b4ab5ad578691b7b4b",
"user_id": "7b2d06001c034891a51bd39c8d5da6db",
"client_id": "http://192.168.0.40:8123/",
"client_name": null,
"client_icon": null,
"token_type": "normal",
"created_at": "2023-03-06T16:26:53.206439+00:00",
"access_token_expiration": 1800.0,
"token": "90c6e9917e6e955ca2726425fd7bdad047917dd109a5f1630e9a01ef118d0bf90b67bc7edac3a86bbd8b5b200bf0d3b02d33d8252d73938a4b413e7cebe6303e",
"jwt_key": "757a7470b0deb081c82c97becb25a1911571ad2c6cbb3423e99a09a7ba4f491554a7e140459e9148991502f12ee515d083a00a41b7dc8ec9dc16bf15b2af0ad7",
"last_used_at": "2023-03-06T16:26:53.206642+00:00",
"last_used_ip": "192.168.0.28",
"credential_id": "4c7a5e40e57549f6994f42e7e7bf199e",
"version": "2023.3.1"
},
{
"id": "2df279c3a2c14f7c80430d62f01d8de0",
"user_id": "7b2d06001c034891a51bd39c8d5da6db",
"client_id": "http://192.168.0.40:8123/",
"client_name": null,
"client_icon": null,
"token_type": "normal",
"created_at": "2023-03-15T11:43:44.173381+00:00",
"access_token_expiration": 1800.0,
"token": "f20f0df6f2fc4a78b72cc341f89ba7c2edb3884e135c8ed4df195ef92520a0840832793129b8247435a3c3ee0d5bccb03e69db3946f88fe3cbfa7eb6415a8888",
"jwt_key": "ee8c75baf49c4e29ba1164625063545db44934d9a0a1dad34b67992c398e95a342bf593f50334287ed057899138b16ec0f10a3beb9dca04867616d067cbe87a7",
"last_used_at": "2023-03-15T11:43:44.173593+00:00",
"last_used_ip": "192.168.0.28",
"credential_id": "4c7a5e40e57549f6994f42e7e7bf199e",
"version": "2023.3.4"
},
{
"id": "d295098e8f7e4b82838ac0e81e2764c7",
"user_id": "7b2d06001c034891a51bd39c8d5da6db",
"client_id": "http://192.168.0.40:8123/",
"client_name": null,
"client_icon": null,
"token_type": "normal",
"created_at": "2023-03-28T07:19:41.777599+00:00",
"access_token_expiration": 1800.0,
"token": "42934de3a1faddbf9ac18550317b6fd07282622433b12340acfc961ee46f020c0e6a8714c9abb261a331dbfa92f8f6a6e5f16ee90a3cb161ca8bea410f4a17b1",
"jwt_key": "fe672f94598677bf63a6bb0e508adc0b5531434d89fbc5f8cb32b416a08afde2b2386191a74f6a0edb504e1ab6cf8bccb9ee1e05ad0c96f6aca417f408fad867",
"last_used_at": "2023-03-28T07:19:41.779456+00:00",
"last_used_ip": "192.168.0.28",
"credential_id": "4c7a5e40e57549f6994f42e7e7bf199e",
"version": "2023.3.6"
},
{
"id": "91b21d493df8437c86c47442b17f7aaf",
"user_id": "7b2d06001c034891a51bd39c8d5da6db",
"client_id": "https://homeassistant.realm.local/",
"client_name": null,
"client_icon": null,
"token_type": "normal",
"created_at": "2023-03-28T07:26:51.592883+00:00",
"access_token_expiration": 1800.0,
"token": "bd61e2dc08de10c32ce4c0fd2bcaec4dc42da380a52f537f64adb6fd259ed01047a93320aeab1ec21645fec89715254490de73f8bded6d9db4062ddd446b7eb2",
"jwt_key": "178a3c5ecfd6096dfd09586bb49f6748c270b500bb3cc0c9261d0d42a1c0109b97ed238714a7b7d2348cba1eb4a51ffbc05f6abace24aa5abe90c265781c82aa",
"last_used_at": "2023-03-28T07:26:51.593081+00:00",
"last_used_ip": "10.6.0.5",
"credential_id": "4c7a5e40e57549f6994f42e7e7bf199e",
"version": "2023.3.6"
},
{
"id": "dc541f9337b249c6831ec5a41d3627a4",
"user_id": "7b2d06001c034891a51bd39c8d5da6db",
"client_id": "http://192.168.0.40:8123/",
"client_name": null,
"client_icon": null,
"token_type": "normal",
"created_at": "2023-03-28T07:29:35.086745+00:00",
"access_token_expiration": 1800.0,
"token": "efa0b47fe601d8852eb9bdf1cc90f9389df3e0c1bc75aa0ac600cd75cd04cca9d1a9138f0acb65b5d235655001f6793ed05b609d4019a66643db0b28cadd9c50",
"jwt_key": "15bc9e6d1002ec1a59adf9a3edd80dd54725bfad4ae592ed31c42df45def8aa429b8679db85ccf2b55b9a144b2ed714a45b167dd1e144d0714f081e6c1d5f974",
"last_used_at": "2023-03-28T07:29:35.088701+00:00",
"last_used_ip": "192.168.0.28",
"credential_id": "4c7a5e40e57549f6994f42e7e7bf199e",
"version": "2023.3.6"
},
{
"id": "bf9c6de83d7046c884a589b1e8bd0ff6",
"user_id": "7b2d06001c034891a51bd39c8d5da6db",
"client_id": "http://192.168.0.40:8123/",
"client_name": null,
"client_icon": null,
"token_type": "normal",
"created_at": "2023-03-28T07:35:27.265644+00:00",
"access_token_expiration": 1800.0,
"token": "7e2a8fa653f4af2119a2035f221e49cb19139e42471c2b70f192a07f215a21c9ceb7d4fc8be717ceb2fd901ae9eb3bd48668d0c1148cbf93b8e816ce3dbcce3e",
"jwt_key": "eb60bb33de4d16968b40a4dd6cc50eac7a8f67764fe4a62afa379c79a3953385a338561a0ac3a6d460974996498353aafeb08d07ec29b46ea1ef0cca1c6ec687",
"last_used_at": "2023-06-12T08:37:32.229319+00:00",
"last_used_ip": "192.168.0.28",
"credential_id": "4c7a5e40e57549f6994f42e7e7bf199e",
"version": "2023.3.6"
},
{
"id": "1055fafd616b465a810603936f2f334e",
"user_id": "7b2d06001c034891a51bd39c8d5da6db",
"client_id": "http://192.168.0.40:8123/",
"client_name": null,
"client_icon": null,
"token_type": "normal",
"created_at": "2023-04-15T11:42:14.115724+00:00",
"access_token_expiration": 1800.0,
"token": "30e583c59a0c5780ddb29f9e6d7eb895d07af470d0748ab8b092ae44008a0f9726130121bae687b7983255ce5809dd48f2cc1e0da512278dd26d0b7ee7ff69d2",
"jwt_key": "c9728fe8715531dda2089a7d1fd3fea74415542012d32fa351a2085b75d37956ba253780753782618f38ba17715be660c14d4373bf5a78a59ce640573d4cb962",
"last_used_at": "2023-04-15T11:42:14.115964+00:00",
"last_used_ip": "192.168.0.28",
"credential_id": "4c7a5e40e57549f6994f42e7e7bf199e",
"version": "2023.4.2"
},
{
"id": "1bdb80270a684df68d89992291442611",
"user_id": "7b2d06001c034891a51bd39c8d5da6db",
"client_id": "http://192.168.0.40:8123/",
"client_name": null,
"client_icon": null,
"token_type": "normal",
"created_at": "2023-04-15T13:12:27.764667+00:00",
"access_token_expiration": 1800.0,
"token": "04f66d3c387d053ddc365007c84ecc950c784cd7972b24991550b22bf90fec6325eb7505837596f144342d83fcf6697978455eb2bef7c51bfd266840a0fa86d6",
"jwt_key": "d537772b288090b89c7b0513b7752edc621e0acb18954f8c8b3ac24dbcc1dce0e94175858e9027237965966d6bbba283552b1f2205c1d96bdd89387b7f73b765",
"last_used_at": "2023-04-15T13:12:27.764876+00:00",
"last_used_ip": "192.168.0.28",
"credential_id": "4c7a5e40e57549f6994f42e7e7bf199e",
"version": "2023.4.4"
},
{
"id": "63a51d5701bf44cf9af7063170b27689",
"user_id": "7b2d06001c034891a51bd39c8d5da6db",
"client_id": "http://192.168.0.40:8123/",
"client_name": null,
"client_icon": null,
"token_type": "normal",
"created_at": "2023-04-24T07:25:45.916778+00:00",
"access_token_expiration": 1800.0,
"token": "52e700d4262541eec3708d3016af7320949e389a70f0c6858395e704a63d598f370ce28fdd061b3f6ed583b1aeea533c97772821c40bc91f36487269211744f0",
"jwt_key": "2ecdff84cf809aeec6257b0b40076f64c572d19902fccf0104895f4b7230bf4158d47aca1c15261b21fab39d37f7bd7c141775648fee998c7600cb133fb97a58",
"last_used_at": "2023-04-24T11:21:43.259686+00:00",
"last_used_ip": "192.168.0.130",
"credential_id": "4c7a5e40e57549f6994f42e7e7bf199e",
"version": "2023.4.6"
},
{
"id": "3cfbace8006443e2af16fcb561ee9b76",
"user_id": "7b2d06001c034891a51bd39c8d5da6db",
"client_id": "http://192.168.0.40:8123/",
"client_name": null,
"client_icon": null,
"token_type": "normal",
"created_at": "2023-05-04T13:06:19.328075+00:00",
"access_token_expiration": 1800.0,
"token": "2aa4f793eab1f4a96f3681904cad4266b7340eea6dfd82042baec7ec48e4c88296c40f651f08549ac491c8149e6e5917453945aedacc102ffa677b95a25dd318",
"jwt_key": "2587295846e176bbbaf56c3df44f573ce129706bd9e30b7a8073b3388274b36fb4a87ae54097929991bfcd4f517bc871752a1a13b63cb8c4d327475b154929e8",
"last_used_at": "2023-05-04T13:06:19.328296+00:00",
"last_used_ip": "192.168.0.28",
"credential_id": "4c7a5e40e57549f6994f42e7e7bf199e",
"version": "2023.4.6"
},
{
"id": "9d64352767c3413a834601c8d5200656",
"user_id": "7b2d06001c034891a51bd39c8d5da6db",
"client_id": "http://192.168.0.40:8123/",
"client_name": null,
"client_icon": null,
"token_type": "normal",
"created_at": "2023-05-10T05:27:51.683342+00:00",
"access_token_expiration": 1800.0,
"token": "de617e192d7f9d638753813ee23ef1d8dfca79b160da9a24438a6e1917d0c2f6eb8f613f7af78b586dc8fe7b80f8a3b652120e6e93bcfba1d201bf01af3db8c9",
"jwt_key": "a2e554664e50d68513a1be29ce05d63b685b3a2900793083d16e9f1566f86b01391c39451ae7aad10235a6a08c44b3c8bb2886ebbddad79a65b709e549bf7a5a",
"last_used_at": "2023-05-10T12:01:15.036398+00:00",
"last_used_ip": "192.168.0.130",
"credential_id": "4c7a5e40e57549f6994f42e7e7bf199e",
"version": "2023.5.2"
},
{
"id": "ba466cf992dc4862ab9e98161e268052",
"user_id": "7b2d06001c034891a51bd39c8d5da6db",
"client_id": "http://192.168.0.40:8123/",
"client_name": null,
"client_icon": null,
"token_type": "normal",
"created_at": "2023-05-15T05:38:05.854523+00:00",
"access_token_expiration": 1800.0,
"token": "da510f57a786f08314084bfa722a90a737d19e08e6768a613777fa21bf05209e5f4c0d6056962d1fb406245a12d86af03a16918c8a07982b79b5f60cda64f2fa",
"jwt_key": "642e77430e135ee3d8e39629665edfff357c5dafa1df6693e09da2ed6ca6de5f4c6cd43bcbd6183e4913f0a0886848ac969d8612af668956927d8bc0e736b57c",
"last_used_at": "2023-05-15T05:38:05.854746+00:00",
"last_used_ip": "192.168.0.130",
"credential_id": "4c7a5e40e57549f6994f42e7e7bf199e",
"version": "2023.5.3"
},
{
"id": "9c4e0090e0234446a87e1a9b996d8661",
"user_id": "7b2d06001c034891a51bd39c8d5da6db",
"client_id": "https://home-assistant.io/android",
"client_name": null,
"client_icon": null,
"token_type": "normal",
"created_at": "2023-05-17T04:40:12.960197+00:00",
"access_token_expiration": 1800.0,
"token": "bb132f696e7e1d1a62ca37162248468a79e1e8907d0a7a1d2d96dbf41c364944cdef7343991e7d380b23432c3d0255d0cd1bd6a758e40099e01dfe994d6958f1",
"jwt_key": "6f56ecba49f124c14ef41cd11e3f643fc2c78c8b02fd71c8722d1ec19f31f1bd73795691e8aeffb1aad48ae7ea82b66bae5a8e2f97147e85bbf44b748018215d",
"last_used_at": "2023-11-19T08:09:34.650530+00:00",
"last_used_ip": "192.168.0.129",
"credential_id": "4c7a5e40e57549f6994f42e7e7bf199e",
"version": "2023.5.3"
},
{
"id": "ab4e19aefa604798b0d66b277d639961",
"user_id": "7b2d06001c034891a51bd39c8d5da6db",
"client_id": "http://192.168.0.40:8123/",
"client_name": null,
"client_icon": null,
"token_type": "normal",
"created_at": "2023-05-29T16:18:33.107766+00:00",
"access_token_expiration": 1800.0,
"token": "5691e2b79a292071ae4f82f979ee9e755df934e5215279dec41dd2a41bb9d916ea844c1a6a7d58ec34f1cc002f77172f0800b495e5ab6024334f1ebd96691fb7",
"jwt_key": "acf9b91f527f4dc654d68db0c81b99f11fd21c7166a7a851640b69cc2c257c8b65f1eb4cd87f55152aee72f96a25fb6c9c1ec438c94e761c0a219715903c4c5a",
"last_used_at": "2023-05-29T16:18:33.107989+00:00",
"last_used_ip": "192.168.0.128",
"credential_id": "4c7a5e40e57549f6994f42e7e7bf199e",
"version": "2023.5.4"
},
{
"id": "89af5a23636c4f2496e84942a8feb0c4",
"user_id": "7b2d06001c034891a51bd39c8d5da6db",
"client_id": "http://192.168.0.40:8123/",
"client_name": null,
"client_icon": null,
"token_type": "normal",
"created_at": "2023-06-27T12:30:28.442526+00:00",
"access_token_expiration": 1800.0,
"token": "859747fbfac93716d1086f9cc71af69ea6665205cbfa45e54e9d2c3f8def7920010c4bf01b7fe5297990f14f756cf165146a94e14a60e9201f284bd8b360f8b9",
"jwt_key": "b2b191e0845c59a3272b5803b202c0c955aaec89cfdffd4dd905cb2e65ad78e506bf8040db4a12657fa63980623fbb6497b35b6efc813540e8828283a551d7bb",
"last_used_at": "2023-06-27T13:10:57.422494+00:00",
"last_used_ip": "192.168.0.28",
"credential_id": "4c7a5e40e57549f6994f42e7e7bf199e",
"version": "2023.6.3"
},
{
"id": "c928d082232f4cfa9acd64290f52806c",
"user_id": "7b2d06001c034891a51bd39c8d5da6db",
"client_id": "http://192.168.0.40:8123/",
"client_name": null,
"client_icon": null,
"token_type": "normal",
"created_at": "2023-06-28T08:53:42.746592+00:00",
"access_token_expiration": 1800.0,
"token": "280caf2fb6f53dd467b0522912100f551cde36c02c578791670e4987ea2ba6ea31179b2f74fc630c0eb5a85538af3065fba32a34c14b20cfae96a37a7bad5352",
"jwt_key": "4dbc2adfdc4f7397de46a0da9d8f99bbf7d0ca4f848d4cc5811a9c66d73e67378e36abadae2341b627c90f419f28bd1b09940153f61ed708c9c5feb611aefd8b",
"last_used_at": "2023-06-28T08:53:42.746778+00:00",
"last_used_ip": "192.168.0.28",
"credential_id": "4c7a5e40e57549f6994f42e7e7bf199e",
"version": "2023.6.3"
},
{
"id": "5a919e4b65d042af8bbce8bcf83f7895",
"user_id": "7b2d06001c034891a51bd39c8d5da6db",
"client_id": "http://192.168.0.40:8123/",
"client_name": null,
"client_icon": null,
"token_type": "normal",
"created_at": "2023-07-18T08:32:58.643893+00:00",
"access_token_expiration": 1800.0,
"token": "7470fe8262bb5f5ce967462c1111f7d89539cffc971f0d7cb5709d94434386ffb3983c947053f9e729070953b3050e39a8ad0f0a1f7ff9f93e59f5e16a81c285",
"jwt_key": "498df94c22828b84daa169a55aa8d9c8ec49e96be511aae67630afede69b8226e3b0b879e57e002c9a76e345bb7ae0612df3b779de180bd70167d9e3edb71c05",
"last_used_at": "2023-07-18T08:32:58.644079+00:00",
"last_used_ip": "192.168.0.28",
"credential_id": "4c7a5e40e57549f6994f42e7e7bf199e",
"version": "2023.7.2"
},
{
"id": "4093b4005fcc4fbdbd0586972911f221",
"user_id": "7b2d06001c034891a51bd39c8d5da6db",
"client_id": "http://192.168.0.40:8123/",
"client_name": null,
"client_icon": null,
"token_type": "normal",
"created_at": "2023-07-18T08:46:20.874756+00:00",
"access_token_expiration": 1800.0,
"token": "07e74c7aa69eb20b73411d52be1fc6649eadc1a725226370915eed763b286f1e2ab43d01765e2e4772a3395f65dd5e868a7ec533b3c8e618dd059f738f402751",
"jwt_key": "a7bf4b8296254e567383d2c1669419659d57f834b33c03fda7ca981b58fddf59f39ece7f243fb812d293c72e22f5efbbd6af5cdbb7f30e0fd9d7a256872414f9",
"last_used_at": "2023-07-18T08:46:20.874942+00:00",
"last_used_ip": "192.168.0.28",
"credential_id": "4c7a5e40e57549f6994f42e7e7bf199e",
"version": "2023.7.2"
},
{
"id": "8b5b9f96c6b54ce98455a3ff095f2c8c",
"user_id": "7b2d06001c034891a51bd39c8d5da6db",
"client_id": "http://192.168.0.40:8123/",
"client_name": null,
"client_icon": null,
"token_type": "normal",
"created_at": "2023-07-18T11:07:32.871267+00:00",
"access_token_expiration": 1800.0,
"token": "9aa0ba9a334c794e2acc69500bb0ea45aa64c3dacda5369194edc34eb37d34e5ba6f3a2b573887bfa5b144a235f772128bcef08c41bd28184fa863652e42cb7c",
"jwt_key": "2f848a659ed2d2c17ec2ccb61b840b494c10b3a94ab82aa9b78d56801b98a4e158d6cb62a63f6a724aa2106f90b2fc24767f8bb37199feea64d4ab31187f59ab",
"last_used_at": "2023-07-18T11:07:32.871451+00:00",
"last_used_ip": "192.168.0.28",
"credential_id": "4c7a5e40e57549f6994f42e7e7bf199e",
"version": "2023.7.2"
},
{
"id": "0d5e34fd09f54890a4758191c3b73c79",
"user_id": "7b2d06001c034891a51bd39c8d5da6db",
"client_id": "http://192.168.0.40:8123/",
"client_name": null,
"client_icon": null,
"token_type": "normal",
"created_at": "2023-08-09T11:36:44.102561+00:00",
"access_token_expiration": 1800.0,
"token": "e20577f79ea572393a174ec7b57c000882b7e718bf796d2201b356c10a690c0978a557a5658ac5b0bedab410566cfdc5f1aa0bcb28a18db787ee533a84c4d6f0",
"jwt_key": "c6f86d33fa95c0340e519ba977f247a8133cc5fa2c8352271400476393083afca9c583ddc5a46ad3b4c448db576f9bcee71c174e2327bf66f70ecd4be4c6d97a",
"last_used_at": "2023-08-09T11:36:44.102765+00:00",
"last_used_ip": "192.168.0.130",
"credential_id": "4c7a5e40e57549f6994f42e7e7bf199e",
"version": "2023.8.1"
},
{
"id": "d088b2104a7e4d558a014252a629e509",
"user_id": "7b2d06001c034891a51bd39c8d5da6db",
"client_id": "http://192.168.0.40:8123/",
"client_name": null,
"client_icon": null,
"token_type": "normal",
"created_at": "2023-08-11T08:53:25.429033+00:00",
"access_token_expiration": 1800.0,
"token": "1b7da75027030675a78f4c37c5bf07dc8020eaf22df372ba189117398e8a74293b69d805580a7bc3f2684249b77a65e0ddcaf857f639363a7ec2c5d39f6f7e15",
"jwt_key": "1c8e8667be8971706e2ac474712b53c75ac8ba6e436b48dcd3b8a52d091b77e055fb37656f1256fce962eaebbfa4a87d57204a42aa2e8023a68c033638544815",
"last_used_at": "2023-08-11T12:23:21.173467+00:00",
"last_used_ip": "192.168.0.130",
"credential_id": "4c7a5e40e57549f6994f42e7e7bf199e",
"version": "2023.8.1"
},
{
"id": "9a2c320b94054a369289cd219a80390b",
"user_id": "7b2d06001c034891a51bd39c8d5da6db",
"client_id": "http://192.168.0.40:8123/",
"client_name": null,
"client_icon": null,
"token_type": "normal",
"created_at": "2023-08-14T08:47:21.934665+00:00",
"access_token_expiration": 1800.0,
"token": "094966d5844d59bf38a6eecd4ec10b3ab802f62e0642963eab51174acdec8f42aef1aeca5f7c8517a7675922e448a881549024f6458a75e4110649232fca980a",
"jwt_key": "c80ba7bac8b5813cb70c3a6ece5d9b19c34603345ec51eb3e6871bb4a6c2147681cba4f7ee1d889f5ea94f1135fcb53f49ef54afc7ecbe74985f735d156420ab",
"last_used_at": "2023-08-14T13:22:06.565937+00:00",
"last_used_ip": "192.168.0.130",
"credential_id": "4c7a5e40e57549f6994f42e7e7bf199e",
"version": "2023.8.2"
},
{
"id": "ac9793d2f4be457daf326dcf91d68a5d",
"user_id": "7b2d06001c034891a51bd39c8d5da6db",
"client_id": "http://192.168.0.40:8123/",
"client_name": null,
"client_icon": null,
"token_type": "normal",
"created_at": "2023-08-17T08:22:47.944990+00:00",
"access_token_expiration": 1800.0,
"token": "372510e546f8f41a4c8065ca71cac935fc6517e6973cdd2506fc48b1fd09fd9189ae0cdf66ab404415496a0176671628a78dd1ad9fc9746457d2e2efbc170b84",
"jwt_key": "ba81b85b15881ccb3e99bda15ad5a6dc99f7388c9632a755bfa7092bd634c88edb251ce9f5590fcd2ec5214f7dcfdda78120c2803791266f8eb3a35b7f6c78f3",
"last_used_at": "2023-08-17T08:22:47.945164+00:00",
"last_used_ip": "192.168.0.28",
"credential_id": "4c7a5e40e57549f6994f42e7e7bf199e",
"version": "2023.8.2"
},
{
"id": "cbb0efd0cb084341a995eb9692f6ad54",
"user_id": "7b2d06001c034891a51bd39c8d5da6db",
"client_id": "http://192.168.0.40:8123/",
"client_name": null,
"client_icon": null,
"token_type": "normal",
"created_at": "2023-08-17T08:51:12.003060+00:00",
"access_token_expiration": 1800.0,
"token": "28c74fbd2b5fc4b956a388c137b38b0d55fbeedf30a4a1da3e3ce96f6509a7ed8924b48fba92d57fbe5e0abf10881690c361699152d1a52e1973be0bfb3b14ca",
"jwt_key": "170df3307ea8275e905b6028419796c1bc2d7abf0a00df32ce3315e97419327b3aa45c96e798f4542707ee9a7ccf7bae51fa991ac8fd65354aaaf187c884eee5",
"last_used_at": "2023-08-17T08:51:12.003239+00:00",
"last_used_ip": "192.168.0.28",
"credential_id": "4c7a5e40e57549f6994f42e7e7bf199e",
"version": "2023.8.2"
},
{
"id": "e7412fb2bc66434cba359c73beb3a72b",
"user_id": "7b2d06001c034891a51bd39c8d5da6db",
"client_id": "http://192.168.0.40:8123/",
"client_name": null,
"client_icon": null,
"token_type": "normal",
"created_at": "2023-08-17T08:51:24.991004+00:00",
"access_token_expiration": 1800.0,
"token": "36b9795dfe2ebdd78cf5974c799b3da088d961faf3899c2f3fcef2323a77aee38113db6ea456d59a50e78b97cef212e9221d5c86ba39c4ca22cd33b1bed7a814",
"jwt_key": "0fc70195f03e932d693a461b3b39b2c9a79e79ae4c8d0ef9aa475a94aaa2b4bed92035d84724d4a6099fec8b56ef10c8c3301e87dc9f5a74fa11db6b19207b4a",
"last_used_at": "2023-08-17T08:51:24.991184+00:00",
"last_used_ip": "192.168.0.28",
"credential_id": "4c7a5e40e57549f6994f42e7e7bf199e",
"version": "2023.8.2"
},
{
"id": "283af2ecb31e4cfb9d2be015a9342499",
"user_id": "7b2d06001c034891a51bd39c8d5da6db",
"client_id": "http://192.168.0.40:8123/",
"client_name": null,
"client_icon": null,
"token_type": "normal",
"created_at": "2023-08-17T08:52:18.747963+00:00",
"access_token_expiration": 1800.0,
"token": "cd9c49f773f03cf3e3d47854aedd7cc027ac658ccf6906246ac14fd7fdc2b730185bf77662bd7347911a1c0519159fcb42b4dc3790d6c6024d56b0c2965a5e19",
"jwt_key": "844089aa63908f25cf3bfba8bb9e8cf32b4a45207462d83cb38357cbdb8d6d800a5a4972afe567425bef48690044fdc2db5afc36da7817ab369a60debf033773",
"last_used_at": "2023-08-17T11:42:14.613701+00:00",
"last_used_ip": "192.168.0.28",
"credential_id": "4c7a5e40e57549f6994f42e7e7bf199e",
"version": "2023.8.2"
},
{
"id": "93a23bb33237453681b414da15e88a0a",
"user_id": "7b2d06001c034891a51bd39c8d5da6db",
"client_id": "http://192.168.0.40:8123/",
"client_name": null,
"client_icon": null,
"token_type": "normal",
"created_at": "2023-08-18T08:01:09.157844+00:00",
"access_token_expiration": 1800.0,
"token": "47418a6cfc80b444204df355db0a2d8039f5909f2c627a4a3c53a6d6db0efe76dc033b99d58c47a7d14f0e5a4b44ee3eca271415c8a2b1370819d3df24feb26d",
"jwt_key": "4c51116d78a8cdfe9e26510fce00751f83b8e97bf76a8769be9c83485668bd6c9fbf335a5885947e203e5162d8cb6fbcbfe144fb363fe5dca13a9090bb7ba06d",
"last_used_at": "2023-08-18T08:01:09.158026+00:00",
"last_used_ip": "192.168.0.155",
"credential_id": "4c7a5e40e57549f6994f42e7e7bf199e",
"version": "2023.8.2"
},
{
"id": "46c908bc088141b2a09bfcdb304c887b",
"user_id": "7b2d06001c034891a51bd39c8d5da6db",
"client_id": "http://192.168.0.40:8123/",
"client_name": null,
"client_icon": null,
"token_type": "normal",
"created_at": "2023-08-22T12:44:24.875599+00:00",
"access_token_expiration": 1800.0,
"token": "81fe3612da11e2110d118afd2a9a0d4e77c46bf3db4a98b33f9ad5d906dbf4e3fd2ced1a088389629aa18db8cc1228bd98bd3b676731760a334591ae4d9cbddb",
"jwt_key": "c1f67f8ea5939dd30ee1a8d826d1d1a0f42e370aaa9616d4679677e06ee19104944d1b7bf6370b2dd6aba694ad6897b0e732b57c276093357f02f49873303e42",
"last_used_at": "2023-08-22T13:46:23.652627+00:00",
"last_used_ip": "192.168.0.28",
"credential_id": "4c7a5e40e57549f6994f42e7e7bf199e",
"version": "2023.8.3"
},
{
"id": "e7495c35701a46d29a8d36b548eb02df",
"user_id": "7b2d06001c034891a51bd39c8d5da6db",
"client_id": "http://192.168.0.40:8123/",
"client_name": null,
"client_icon": null,
"token_type": "normal",
"created_at": "2023-08-25T10:58:05.238232+00:00",
"access_token_expiration": 1800.0,
"token": "22859358bb6fbdb6dc8e3d14ded0a4a543028be3170f32e72197aad48ce638132d8ad39b1b24adfb7be1ce537fdb5279b78c1c908409c8f635a5f28a0df858e5",
"jwt_key": "3b312149d301f591203500535cf7e92c4366fae941135bc8ec2390ccc388a4a287921dad70e61353e5f05f30c940fcbac60100d02e8608fb924c53101f89d5f1",
"last_used_at": "2023-08-25T12:12:57.885151+00:00",
"last_used_ip": "192.168.0.130",
"credential_id": "4c7a5e40e57549f6994f42e7e7bf199e",
"version": "2023.8.3"
},
{
"id": "4678136a925d4ba3b49be48a03819af0",
"user_id": "7b2d06001c034891a51bd39c8d5da6db",
"client_id": "http://192.168.0.40:8123/",
"client_name": null,
"client_icon": null,
"token_type": "normal",
"created_at": "2023-08-28T06:56:42.290272+00:00",
"access_token_expiration": 1800.0,
"token": "2e873972f28577e8a241306b82ba55dd17120be16a22732863bd3fc53a89877ce19b873a639a1c02ad8fee8041879ae80edea2df8165a121dccd30d65446f4f5",
"jwt_key": "b15f0ccfdb1dfc566b27be550f1c9ba0b6c0aa4cc48bd20174df73f13eae5259f99cad4102998a8ba02f9581b4b9e28151e5e6d91a3fb0c75f42f52f3724f8d2",
"last_used_at": "2023-08-28T06:56:42.290450+00:00",
"last_used_ip": "192.168.0.28",
"credential_id": "4c7a5e40e57549f6994f42e7e7bf199e",
"version": "2023.8.4"
},
{
"id": "82b80ca22a4547e89ea53267f5538254",
"user_id": "7b2d06001c034891a51bd39c8d5da6db",
"client_id": "http://192.168.0.40:8123/",
"client_name": null,
"client_icon": null,
"token_type": "normal",
"created_at": "2023-09-29T13:57:24.999422+00:00",
"access_token_expiration": 1800.0,
"token": "72496a12aebdb3f4894dedc12afc11067d5acc73da5593d02e3e846b0b7038ac1a62c0db7802372d1db93ad0368de665cea5fe93cba24949c67fb39de2c7d040",
"jwt_key": "5c29f3f7bad9b119aa0b285c62bf8ba7c69340a18e162e9b831ccc56f6790adb33818a332da08d77cea101cece4fa2f48c3d6411c9fea4e90ad3ec1b0d4b7487",
"last_used_at": "2023-09-29T14:28:21.281118+00:00",
"last_used_ip": "192.168.0.130",
"credential_id": "4c7a5e40e57549f6994f42e7e7bf199e",
"version": "2023.9.3"
},
{
"id": "c0e9579394134d0493b1c38ef3715649",
"user_id": "7b2d06001c034891a51bd39c8d5da6db",
"client_id": "http://192.168.0.40:8123/",
"client_name": null,
"client_icon": null,
"token_type": "normal",
"created_at": "2023-10-05T08:18:36.038795+00:00",
"access_token_expiration": 1800.0,
"token": "8438c613a94bc3483235cd5d3a5f059a3192c42cda5f4819a9cba4a23b16aef2a895ecf4ea0e76fba07464bf8f6e0ab413d66a1965fa56b562d23db93dd8b462",
"jwt_key": "b2fc289677b6c3f01a0752123e4bc177ae75ca97b59431db7d1112e0162f86c6922d36b83c5942a41476f26c0e3d9accc013568301b31b40f3b442be3aa34643",
"last_used_at": "2023-10-05T13:47:46.182507+00:00",
"last_used_ip": "192.168.0.28",
"credential_id": "4c7a5e40e57549f6994f42e7e7bf199e",
"version": "2023.10.0"
},
{
"id": "414cfd342f1c4d0199a4dae0429ddfe9",
"user_id": "7b2d06001c034891a51bd39c8d5da6db",
"client_id": "http://192.168.0.40:8123/",
"client_name": null,
"client_icon": null,
"token_type": "normal",
"created_at": "2023-10-09T09:52:43.251335+00:00",
"access_token_expiration": 1800.0,
"token": "bf8df06783baf5e67f9c74df534db2a9ea9f6eedf6b7779ef517addd4613fe229b1826bdb5130cdbe510bac744a7db2b0d00db7256a95868784ae3c1fdb611de",
"jwt_key": "01181d375192128fb212a1823d77d7a5ee75dcf600effab872b8ce1c097cd471a2258ba1c214fc99c86624299bfbf4bf0ec7cff8ff6f9adc0c48f9ccbe5f8dfc",
"last_used_at": "2023-10-09T12:47:11.601408+00:00",
"last_used_ip": "192.168.0.28",
"credential_id": "4c7a5e40e57549f6994f42e7e7bf199e",
"version": "2023.10.1"
},
{
"id": "ad2d4ba079f54b71bffc3f61f2c90ca7",
"user_id": "7b2d06001c034891a51bd39c8d5da6db",
"client_id": "http://192.168.0.40:8123/",
"client_name": null,
"client_icon": null,
"token_type": "normal",
"created_at": "2023-10-13T05:52:35.030385+00:00",
"access_token_expiration": 1800.0,
"token": "f63045b9fb395c476b2e97c53628aabffe3374542e9c1a0a33e029b9ba6d47137832c5902ee9f2cacae254c3ca320dc353ffeae4d02e0032d6d52a5c74645074",
"jwt_key": "8303da5d9579366a673e93628756866725ea2376d99d3e35c99c2e20d27e9a705da19ae5c3ccc1309f92e295ca9a9a0f680cb989e0554c162415e5862cb57a06",
"last_used_at": "2023-10-13T05:52:35.030661+00:00",
"last_used_ip": "192.168.0.130",
"credential_id": "4c7a5e40e57549f6994f42e7e7bf199e",
"version": "2023.10.1"
},
{
"id": "81103af1098b48a6a1c688d6652a3c60",
"user_id": "7b2d06001c034891a51bd39c8d5da6db",
"client_id": "http://192.168.0.40:8123/",
"client_name": null,
"client_icon": null,
"token_type": "normal",
"created_at": "2023-10-13T12:05:11.425579+00:00",
"access_token_expiration": 1800.0,
"token": "381345733cf0a2af88d70bb85d0faf48129c77838b3e2c014546cf5319680558abb2d31ae9cf13e0001bb18be0fdf12324a4e526cfe1bddcb00a0ce029f0d88d",
"jwt_key": "595b1d4edad88f5a36c353652a54e460c223ab1460b4762c81d29b6fe5e03d02079e3410d552336021ae3e80849eb8e044b4feac5b690cbd4294760d6baef2fb",
"last_used_at": "2023-10-13T12:05:11.425753+00:00",
"last_used_ip": "192.168.0.130",
"credential_id": "4c7a5e40e57549f6994f42e7e7bf199e",
"version": "2023.10.1"
},
{
"id": "b553eaedb73e42368e681aeb87302543",
"user_id": "7b2d06001c034891a51bd39c8d5da6db",
"client_id": "http://192.168.0.40:8123/",
"client_name": null,
"client_icon": null,
"token_type": "normal",
"created_at": "2023-10-13T12:06:22.952904+00:00",
"access_token_expiration": 1800.0,
"token": "08cd0310f57efb2d62fe454af3af13d21136222d948d0cb766673408fcb310df9bee5a0dc436f8513c32077afdeab4ab94aa162caeae569707bed3fc0f287594",
"jwt_key": "d3b36200307ebd3af5989c95139bcdcc8a271e27b943eddc27138d112a3ecd6fd6b3552d53292c34ef50e8e381bdb754d533a3bd785ebf02ecbd8efba11289d8",
"last_used_at": "2023-10-13T13:50:14.503204+00:00",
"last_used_ip": "192.168.0.130",
"credential_id": "4c7a5e40e57549f6994f42e7e7bf199e",
"version": "2023.10.1"
},
{
"id": "17615fff32dd4147be56a0fd77fe10de",
"user_id": "7b2d06001c034891a51bd39c8d5da6db",
"client_id": "http://192.168.0.40:8123/",
"client_name": null,
"client_icon": null,
"token_type": "normal",
"created_at": "2023-11-07T12:29:22.317716+00:00",
"access_token_expiration": 1800.0,
"token": "0e4d1dee819844d0c479245e1de6158f1ae0b186e30f626f39c1351df830a5a38076248fe3ec7b21d872cdb62c1d548e7619ae68b8b13bccc043e3372194c3c4",
"jwt_key": "0a22dcc7cf470dfef90f9f8979cb98a2b265296914d6de9c223af5b3de9f8c1d4c29caada33fde565945b759ba443c08ef908ef138c2a03a5868ab625fa8f979",
"last_used_at": "2023-11-07T14:19:21.131524+00:00",
"last_used_ip": "192.168.0.28",
"credential_id": "4c7a5e40e57549f6994f42e7e7bf199e",
"version": "2023.11.1"
},
{
"id": "a555482fbdc04d26af058b56c3c4b7d5",
"user_id": "7b2d06001c034891a51bd39c8d5da6db",
"client_id": "http://192.168.0.40:8123/",
"client_name": null,
"client_icon": null,
"token_type": "normal",
"created_at": "2023-11-13T15:14:35.478442+00:00",
"access_token_expiration": 1800.0,
"token": "52fb7e057cae5ad2291f3b1e2af6cdc19ea618ff441c37aabb81f76157376086aa820949f1856ec1f14c0f65b80d68686c2f459c29467f0a0175bf723e8b8930",
"jwt_key": "eb6fecdce715043a595c66af3d7935ec282f89f996ed176afc48a64aab1872d458ba7d31bf46c549f4909de49cce5cadbc614b2ccbe5734d1338f3db6a33571f",
"last_used_at": "2023-11-13T15:14:35.478625+00:00",
"last_used_ip": "192.168.0.28",
"credential_id": "4c7a5e40e57549f6994f42e7e7bf199e",
"version": "2023.11.2"
}
]
}
}
+13
View File
@@ -0,0 +1,13 @@
{
"version": 1,
"minor_version": 1,
"key": "auth_provider.homeassistant",
"data": {
"users": [
{
"username": "gorden",
"password": "JDJiJDEyJE1RYThScnVtUjkuVnVoSkJzSEVmbC5JZGUxOHVyWlgxN05CNkxmamdQUWVvcjRqWVhkT3Vt"
}
]
}
}
@@ -0,0 +1,6 @@
{
"version": 1,
"minor_version": 1,
"key": "bluetooth.passive_update_processor",
"data": {}
}
+10
View File
@@ -0,0 +1,10 @@
{
"version": 1,
"minor_version": 1,
"key": "core.analytics",
"data": {
"preferences": {},
"onboarded": true,
"uuid": null
}
}
+81
View File
@@ -0,0 +1,81 @@
{
"version": 1,
"minor_version": 3,
"key": "core.area_registry",
"data": {
"areas": [
{
"name": "Wohnzimmer",
"id": "wohnzimmer",
"picture": "/api/image/serve/ed9611ede795c578e5b28a68986b847d/512x512",
"aliases": []
},
{
"name": "Küche",
"id": "kuche",
"picture": "/api/image/serve/a3e57b93383e55dd5b6e2f85f0f5a4a1/512x512",
"aliases": []
},
{
"name": "Schlafzimmer",
"id": "schlafzimmer",
"picture": "/api/image/serve/832ab454dd38e62ca698da627eee43d1/512x512",
"aliases": []
},
{
"name": "Büro",
"id": "buro",
"picture": "/api/image/serve/f1149839fd2bbf2c3b1e5dabaf69c22c/512x512",
"aliases": []
},
{
"name": "Esszimmer",
"id": "esszimmer",
"picture": "/api/image/serve/ab0be2fdca543d9e9dff5f500cbb952b/512x512",
"aliases": []
},
{
"name": "Gäste-WC",
"id": "gaste_wc",
"picture": "/api/image/serve/c7ed0abb2978b6c12ad5432a879fcaf4/512x512",
"aliases": []
},
{
"name": "Hauswirtschaftsraum",
"id": "hauswirtschaftsraum",
"picture": "/api/image/serve/394267756098de5e3dd05e787a7437a5/512x512",
"aliases": []
},
{
"name": "Gästezimmer",
"id": "gastezimmer",
"picture": "/api/image/serve/5811b89d86ae6193acadbf81bc7f13dc/512x512",
"aliases": []
},
{
"name": "Flur (oben)",
"id": "flur_oben",
"picture": "/api/image/serve/bb6540b318280b1ddffb9c5f1860107e/512x512",
"aliases": []
},
{
"name": "Grundstück",
"id": "grundstuck",
"picture": "/api/image/serve/a14f3dd748d1d27a10cfd78b4ab77d05/512x512",
"aliases": []
},
{
"name": "Flur",
"id": "flur",
"picture": "/api/image/serve/3935001279ca2f042969771e9fcb6c7e/512x512",
"aliases": []
},
{
"name": "Badezimmer",
"id": "badezimmer",
"picture": "/api/image/serve/d6125680f69720a79b4ee376a9820958/512x512",
"aliases": []
}
]
}
}
+18
View File
@@ -0,0 +1,18 @@
{
"version": 1,
"minor_version": 3,
"key": "core.config",
"data": {
"latitude": 52.48403470519901,
"longitude": 10.103688240051271,
"elevation": 0,
"unit_system": "metric",
"location_name": "Home Assistant",
"time_zone": "Europe/Berlin",
"external_url": "http://homeassistant.realm.local",
"internal_url": "http://192.168.0.40:8123",
"currency": "EUR",
"unit_system_v2": "metric",
"language": "en"
}
}
+816
View File
@@ -0,0 +1,816 @@
{
"version": 1,
"minor_version": 1,
"key": "core.config_entries",
"data": {
"entries": [
{
"entry_id": "2493f2032b07feab4d724156c6b8ef94",
"version": 1,
"domain": "sun",
"title": "Sun",
"data": {},
"options": {},
"pref_disable_new_entities": false,
"pref_disable_polling": false,
"source": "import",
"unique_id": null,
"disabled_by": null
},
{
"entry_id": "b6dbdaa6c6120cde8070a3463a8ecd32",
"version": 1,
"domain": "met",
"title": "Home",
"data": {
"track_home": true
},
"options": {},
"pref_disable_new_entities": false,
"pref_disable_polling": false,
"source": "onboarding",
"unique_id": null,
"disabled_by": null
},
{
"entry_id": "4d7a1277817bfda6f489615be4bdf33d",
"version": 1,
"domain": "fritz",
"title": "FRITZ!Box 7590",
"data": {
"host": "192.168.0.1",
"password": "#gentoo#",
"port": 49000,
"username": "admin"
},
"options": {
"consider_home": 180.0,
"old_discovery": false
},
"pref_disable_new_entities": false,
"pref_disable_polling": false,
"source": "ssdp",
"unique_id": "123402409-bccb-40e7-8e6c-2C91AB499924",
"disabled_by": null
},
{
"entry_id": "990c40337418a92d9e989800e743164d",
"version": 1,
"domain": "synology_dsm",
"title": "192.168.0.40",
"data": {
"host": "192.168.0.40",
"port": "5001",
"ssl": true,
"verify_ssl": false,
"username": "gorden",
"password": "$Gentoo12",
"mac": [
"90-09-D0-0B-70-C4",
"90-09-D0-0B-70-C5"
],
"device_token": "nww0D-MkQmVzbdvUtY_Zn2SaS5ZmUwk-5cC8SYqDdQjI77bRFGqcNTn1AQKKAARZIP0JVtlEWknMDbiwqdJ38g"
},
"options": {},
"pref_disable_new_entities": false,
"pref_disable_polling": false,
"source": "ssdp",
"unique_id": "21B0RLR0NGA6B",
"disabled_by": null
},
{
"entry_id": "673c97cb58167c3a682a5c051575f323",
"version": 3,
"domain": "blink",
"title": "blink",
"data": {
"username": "gorden.indra@solusar.de",
"password": "Schaf0815!",
"device_id": "Home Assistant",
"uid": "BlinkCamera_759baa2f-862e-58f8-8d68-021288793195",
"token": "DbvHa5lVi_ecnHGsYBHyVA",
"host": "e004.immedia-semi.com",
"region_id": "e004",
"client_id": 314597,
"account_id": 972
},
"options": {
"scan_interval": 300
},
"pref_disable_new_entities": false,
"pref_disable_polling": false,
"source": "user",
"unique_id": "gorden.indra@solusar.de",
"disabled_by": null
},
{
"entry_id": "1b1b2bbd12b64f633540689f25da4e99",
"version": 1,
"domain": "ring",
"title": "gorden.mende@solusar.de",
"data": {
"username": "gorden.mende@solusar.de",
"token": {
"access_token": "eyJhbGciOiJSUzI1NiIsImprdSI6Ii9vYXV0aC9pbnRlcm5hbC9qd2tzIiwia2lkIjoiNGRjODQyZGIiLCJ0eXAiOiJKV1QifQ.eyJhcHBfaWQiOiJyaW5nX29mZmljaWFsX2FuZHJvaWQiLCJjaWQiOiJyaW5nX29mZmljaWFsX2FuZHJvaWQiLCJleHAiOjE3MDA1NzU5MjQsImhhcmR3YXJlX2lkIjoiOTVhMmVhZGYtYTYxZC00MmU5LWI3YWUtZGExYThhMWE0OTM4IiwiaWF0IjoxNzAwNTYxNTI0LCJpc3MiOiJSaW5nT2F1dGhTZXJ2aWNlLXByb2Q6dXMtZWFzdC0xOmE4OThmZGQ5Iiwicm5kIjoiU2U2NWdoMURqbyIsInNjb3BlcyI6WyJjbGllbnQiXSwic2Vzc2lvbl9pZCI6IjYwZjE1OTkwLTYyOTYtNDI4NC04ZTFhLTc2NTdkMjM4MWQ3YyIsInVzZXJfaWQiOjIyNTA1NjkzfQ.lwBDtCYj5lkCToMlboiYiWY5Nwf7s-ymIY_Zmm8cHXA6y1JX16jGoWFMFCSrXT_IyWP36lx_4KHEt3kQ6YFM3HNRC023fiEvXOPV_-RUYT9vVcyxO9n4yUqTn4oLkc446kpdAWL1jx08GgwCAupSR9FttJWZlCFfMOck2UWN1MaiUZwBWxa5Ffz0GZCjWMWYmhDcYRj9J0h23UIRO8JQG4mRTokLsYVesE4ZIs_kUlYvus_yO8p9tUGmrgnpdxSSzpd6fokuN-oM4qW7riFVcM6HqUWO0c1OOqai4bJnrUgMq-PGO5cRnvZu2hNLDqcwUlgeuWSlJeQvqOope7Wm-Q",
"expires_in": 14400,
"refresh_token": "eyJhbGciOiJSUzI1NiIsImprdSI6Ii9vYXV0aC9pbnRlcm5hbC9qd2tzIiwia2lkIjoiNGRjODQyZGIiLCJ0eXAiOiJKV1QifQ.eyJpYXQiOjE3MDA1NjE1MjQsImlzcyI6IlJpbmdPYXV0aFNlcnZpY2UtcHJvZDp1cy1lYXN0LTE6YTg5OGZkZDkiLCJyZWZyZXNoX2NpZCI6InJpbmdfb2ZmaWNpYWxfYW5kcm9pZCIsInJlZnJlc2hfc2NvcGVzIjpbImNsaWVudCJdLCJyZWZyZXNoX3VzZXJfaWQiOjIyNTA1NjkzLCJybmQiOiJ4bHVSaVZfeTZKIiwic2Vzc2lvbl9pZCI6IjYwZjE1OTkwLTYyOTYtNDI4NC04ZTFhLTc2NTdkMjM4MWQ3YyIsInR5cGUiOiJyZWZyZXNoLXRva2VuIn0.brJzH9BAjDfHt-1GSBaI0vDcJ3WcT9CD31c7SGxV_r1SsUiMh8tsSVrp0msJqV9uZE8G9KyqtQSvzMsChLTnjBXHqq0y1BSFnSXoHxdTeyS28TsQLbX4mL47e7VarGObHtFRUcg1dMYDNUzgtwW3XlV-ZMuwoz9dTJSxRXyjqJNyto1zBimbG1IMHHnaK79Ccxs8zSL19AzLH4NjJbgA1Uph8vASrF_qwHeTCk7H-rXlrAkLKFftNk0ae0pxm96BUGcs-NxyW7_arf8PQQHI3O0cN-q-XkeqlFIJrzxBX8AJaG-NPQOjcM1g-uBH2AGqLm23zj8tYc89AK-k11XQIw",
"scope": [
"client"
],
"token_type": "Bearer",
"expires_at": 1700575924.3938837
}
},
"options": {},
"pref_disable_new_entities": false,
"pref_disable_polling": false,
"source": "user",
"unique_id": "gorden.mende@solusar.de",
"disabled_by": null
},
{
"entry_id": "fbe65fd921046b2a092cc671c4973346",
"version": 1,
"domain": "homekit_controller",
"title": "Somfy TaHoma",
"data": {
"AccessoryIP": "192.168.0.21",
"AccessoryPort": 32151
},
"options": {},
"pref_disable_new_entities": false,
"pref_disable_polling": false,
"source": "ignore",
"unique_id": "77:c5:af:75:9f:c9",
"disabled_by": null
},
{
"entry_id": "cc2d752331d13ff59d64a5285fa63f70",
"version": 1,
"domain": "shelly",
"title": "shellyht-F93CEC",
"data": {
"host": "192.168.0.111",
"sleep_period": 43200,
"model": "SHHT-1",
"gen": 1
},
"options": {},
"pref_disable_new_entities": false,
"pref_disable_polling": false,
"source": "zeroconf",
"unique_id": "68C63AF93CEC",
"disabled_by": null
},
{
"entry_id": "41e870fb2ed076e202d649b1e3af18d6",
"version": 1,
"domain": "fritzbox",
"title": "AVM FRITZ!Box: FRITZ!Powerline 1260E",
"data": {
"host": "192.168.0.113"
},
"options": {},
"pref_disable_new_entities": false,
"pref_disable_polling": false,
"source": "ignore",
"unique_id": "123402409-bccb-40e7-8e6c-DC396F8EC353",
"disabled_by": null
},
{
"entry_id": "9aa24358457e841797d510c47c2d2de5",
"version": 1,
"domain": "fritz",
"title": "FRITZ!Powerline 1260E",
"data": {
"host": "192.168.0.113",
"password": "#gentoo#",
"port": 49000,
"username": "admin"
},
"options": {
"consider_home": 180.0,
"old_discovery": false
},
"pref_disable_new_entities": false,
"pref_disable_polling": false,
"source": "ssdp",
"unique_id": "123402409-bccb-40e7-8e6c-DC396F8EC353",
"disabled_by": null
},
{
"entry_id": "450a38d1fc30dabd7cb0f4e1075788f4",
"version": 1,
"domain": "fritzbox",
"title": "FRITZ!Box 7590",
"data": {
"host": "192.168.0.1",
"password": "#gentoo#",
"username": "admin"
},
"options": {},
"pref_disable_new_entities": false,
"pref_disable_polling": false,
"source": "ssdp",
"unique_id": "123402409-bccb-40e7-8e6c-2C91AB499924",
"disabled_by": null
},
{
"entry_id": "c80af580e90f6a18cc53e461be0431bf",
"version": 1,
"domain": "esphome",
"title": "office",
"data": {
"host": "192.168.0.108",
"port": 6053,
"password": "",
"noise_psk": "",
"device_name": "office"
},
"options": {},
"pref_disable_new_entities": false,
"pref_disable_polling": false,
"source": "zeroconf",
"unique_id": "3c:61:05:d3:12:de",
"disabled_by": null
},
{
"entry_id": "05f3755586908e7f9aca9d6f523fba3f",
"version": 1,
"domain": "upnp",
"title": "FRITZ!Box 7590",
"data": {
"mac_address": "2c:91:ab:49:99:24",
"location": "http://192.168.0.1:49000/igddesc.xml",
"host": "192.168.0.1",
"st": "urn:schemas-upnp-org:device:InternetGatewayDevice:1"
},
"options": {},
"pref_disable_new_entities": false,
"pref_disable_polling": false,
"source": "ignore",
"unique_id": "uuid:75802409-bccb-40e7-8e6c-2C91AB499924::urn:schemas-upnp-org:device:InternetGatewayDevice:1",
"disabled_by": null
},
{
"entry_id": "270d16dcc42c691928bfc37b04cdd38e",
"version": 1,
"domain": "upnp",
"title": "InternetGatewayDeviceV2 - FRITZ!Box 7590",
"data": {
"mac_address": "2c:91:ab:49:99:24",
"location": "http://192.168.0.1:49000/igd2desc.xml",
"host": "192.168.0.1",
"st": "urn:schemas-upnp-org:device:InternetGatewayDevice:2"
},
"options": {},
"pref_disable_new_entities": false,
"pref_disable_polling": false,
"source": "ignore",
"unique_id": "uuid:75802409-bccb-40e7-9f6c-2C91AB499924::urn:schemas-upnp-org:device:InternetGatewayDevice:2",
"disabled_by": null
},
{
"entry_id": "cbde35c1ed0c174d6b491f429991d218",
"version": 1,
"domain": "spotify",
"title": "Spotify",
"data": {},
"options": {},
"pref_disable_new_entities": false,
"pref_disable_polling": false,
"source": "ignore",
"unique_id": null,
"disabled_by": null
},
{
"entry_id": "a6a0162fed94362561681e7be27a9613",
"version": 1,
"domain": "overkiz",
"title": "gorden.indra@solusar.de",
"data": {
"username": "gorden.indra@solusar.de",
"password": "Schaf0815!",
"hub": "somfy_europe"
},
"options": {},
"pref_disable_new_entities": false,
"pref_disable_polling": false,
"source": "zeroconf",
"unique_id": "1206-2140-8817",
"disabled_by": null
},
{
"entry_id": "6c2b759e0e93f54c7c028cc506242749",
"version": 1,
"domain": "deconz",
"title": "00212E083EB9",
"data": {
"host": "192.168.0.123",
"port": 80,
"api_key": "BE38D7042D"
},
"options": {
"master": true
},
"pref_disable_new_entities": false,
"pref_disable_polling": false,
"source": "ssdp",
"unique_id": "00212E083EB9",
"disabled_by": null
},
{
"entry_id": "3bc045bdd7dfa9a240fb4ec0d8fa9722",
"version": 1,
"domain": "group",
"title": "Flur (Oben)",
"data": {},
"options": {
"group_type": "light",
"name": "Flur (Oben)",
"entities": [
"light.flur_oben1",
"light.flur_oben2"
],
"hide_members": false
},
"pref_disable_new_entities": false,
"pref_disable_polling": false,
"source": "user",
"unique_id": null,
"disabled_by": null
},
{
"entry_id": "d4899927c0b2bfa4a201b4797370a69d",
"version": 1,
"domain": "group",
"title": "cover_group",
"data": {},
"options": {
"group_type": "cover",
"name": "cover_group",
"entities": [
"cover.hauswirtschaftsraum_2",
"cover.gaste_wc_2",
"cover.kuche_2",
"cover.wohnzimmer_links_2",
"cover.wohnzimmer_rechts_2"
],
"hide_members": false
},
"pref_disable_new_entities": false,
"pref_disable_polling": false,
"source": "user",
"unique_id": null,
"disabled_by": null
},
{
"entry_id": "fa75c8d31043dc3a578e7e4a8d05229e",
"version": 1,
"domain": "group",
"title": "Flur",
"data": {},
"options": {
"group_type": "light",
"name": "Flur",
"entities": [
"light.extended_color_light_9",
"light.extended_color_light_10",
"light.color_temperature_light_8",
"light.color_temperature_light_12"
],
"hide_members": false
},
"pref_disable_new_entities": false,
"pref_disable_polling": false,
"source": "user",
"unique_id": null,
"disabled_by": null
},
{
"entry_id": "4d46db63bb0dfb2099ebd8962883db2a",
"version": 1,
"domain": "group",
"title": "Gästezimmer",
"data": {},
"options": {
"group_type": "light",
"name": "Gästezimmer",
"entities": [
"light.guest1",
"light.guest2",
"light.guest3",
"light.guest4_2"
],
"hide_members": false
},
"pref_disable_new_entities": false,
"pref_disable_polling": false,
"source": "user",
"unique_id": null,
"disabled_by": null
},
{
"entry_id": "95be8d0caf9a9bbab8f45ca56dcb0b53",
"version": 1,
"domain": "group",
"title": "Küche",
"data": {},
"options": {
"group_type": "light",
"name": "Küche",
"entities": [
"light.kitchen1",
"light.kitchen2",
"light.kitchen3",
"light.kitchen4"
],
"hide_members": false
},
"pref_disable_new_entities": false,
"pref_disable_polling": false,
"source": "user",
"unique_id": null,
"disabled_by": null
},
{
"entry_id": "cc197ed4246deb792fcca2e15825c80d",
"version": 1,
"domain": "group",
"title": "Büro",
"data": {},
"options": {
"group_type": "light",
"name": "Büro",
"entities": [
"light.office1",
"light.office2",
"light.office3"
],
"hide_members": false
},
"pref_disable_new_entities": false,
"pref_disable_polling": false,
"source": "user",
"unique_id": null,
"disabled_by": null
},
{
"entry_id": "9db3294fe3434ebcec854584f9423359",
"version": 1,
"domain": "hacs",
"title": "",
"data": {
"token": "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"
},
"options": {},
"pref_disable_new_entities": false,
"pref_disable_polling": false,
"source": "user",
"unique_id": null,
"disabled_by": null
},
{
"entry_id": "ab60c2516b30eece89daeedfba4963d9",
"version": 2,
"domain": "forecast_solar",
"title": "Home Assistant",
"data": {
"latitude": 52.48403470519901,
"longitude": 10.103688240051271
},
"options": {
"azimuth": 215,
"declination": 30,
"modules_power": 820,
"damping_morning": 0.0,
"damping_evening": 0.0
},
"pref_disable_new_entities": false,
"pref_disable_polling": false,
"source": "user",
"unique_id": null,
"disabled_by": null
},
{
"entry_id": "346c921fdc153e8a50a7f247b04645bf",
"version": 2,
"domain": "omnik_inverter",
"title": "Mi300-Modul1",
"data": {
"host": "192.168.0.116",
"source_type": "html",
"username": "admin",
"password": "admin"
},
"options": {},
"pref_disable_new_entities": false,
"pref_disable_polling": false,
"source": "user",
"unique_id": null,
"disabled_by": null
},
{
"entry_id": "954ba55ff5b99f326089d88599978721",
"version": 2,
"domain": "omnik_inverter",
"title": "Mi300-Modul2",
"data": {
"host": "192.168.0.121",
"source_type": "html",
"username": "admin",
"password": "admin"
},
"options": {},
"pref_disable_new_entities": false,
"pref_disable_polling": false,
"source": "user",
"unique_id": null,
"disabled_by": null
},
{
"entry_id": "e0f1594a3021da2d180d56c9b2a816aa",
"version": 1,
"domain": "denonavr",
"title": "Living Room",
"data": {
"host": "192.168.0.152",
"type": "avr-x-2016",
"model": "Marantz NR1711",
"manufacturer": "Marantz",
"serial_number": "MBBX052275414"
},
"options": {},
"pref_disable_new_entities": false,
"pref_disable_polling": false,
"source": "ssdp",
"unique_id": "Marantz NR1711-MBBX052275414",
"disabled_by": null
},
{
"entry_id": "1c44bb11df73c7fe76ce9cee499b0758",
"version": 1,
"domain": "heos",
"title": "Controller (192.168.0.124)",
"data": {
"host": "192.168.0.124"
},
"options": {},
"pref_disable_new_entities": false,
"pref_disable_polling": false,
"source": "ssdp",
"unique_id": "heos",
"disabled_by": null
},
{
"entry_id": "3850385675afb551d7d2d931d86ec871",
"version": 1,
"domain": "tuya",
"title": "astartusfi@gmail.com",
"data": {
"endpoint": "https://openapi.tuyaeu.com",
"auth_type": 0,
"access_id": "xh8kmj9gfg8d9xqhm5vy",
"access_secret": "475637c84c1d481c9701b32ee950dd9e",
"username": "astartusfi@gmail.com",
"password": "Moppel+Kotze4",
"country_code": "49",
"tuya_app_type": "tuyaSmart"
},
"options": {},
"pref_disable_new_entities": false,
"pref_disable_polling": false,
"source": "user",
"unique_id": null,
"disabled_by": null
},
{
"entry_id": "21e9cbe98fda2ef67d9efe9eca957e6a",
"version": 1,
"domain": "mobile_app",
"title": "Lenovo TB-J606F",
"data": {
"app_id": "io.homeassistant.companion.android",
"app_name": "Home Assistant",
"app_version": "2023.10.2-full (11484)",
"device_name": "Lenovo TB-J606F",
"manufacturer": "LENOVO",
"model": "Lenovo TB-J606F",
"os_name": "Android",
"os_version": "30",
"supports_encryption": false,
"app_data": {
"push_websocket_channel": true,
"push_url": "https://mobile-apps.home-assistant.io/api/sendPush/android/v1",
"push_token": "c98ixhFAQQ2VM0tdIDAq5U:APA91bH_VOxh4lRFKN_47baK-oCT6Of1xEtRlF3HRwlNyb4QhT0EpGoKawZ0an7Gbi6QO_4nPoxRnHAEg1JrNfko6JneeW2mAngHkjwlOS8TWR9ViKPW8GcQ0KYSl7dR_AQu7KfaBigH"
},
"device_id": "7b6f77f84755ff4d",
"webhook_id": "e1c9b9bd9293dafd9991704a7b3081fc8d8971b0ee2c2ee624b813669e2d5da3",
"user_id": "7b2d06001c034891a51bd39c8d5da6db"
},
"options": {},
"pref_disable_new_entities": false,
"pref_disable_polling": false,
"source": "registration",
"unique_id": "io.homeassistant.companion.android-7b6f77f84755ff4d",
"disabled_by": null
},
{
"entry_id": "b37d6115257b337dcf7e324cf320bf29",
"version": 1,
"domain": "mobile_app",
"title": "Phone(1)",
"data": {
"app_id": "io.homeassistant.companion.android",
"app_name": "Home Assistant",
"app_version": "2023.10.2-full (11484)",
"device_name": "Phone(1)",
"manufacturer": "Nothing",
"model": "A063",
"os_name": "Android",
"os_version": "33",
"supports_encryption": false,
"app_data": {
"push_websocket_channel": true,
"push_url": "https://mobile-apps.home-assistant.io/api/sendPush/android/v1",
"push_token": "djo5IqPaSomfPLJLwu-_kn:APA91bHnQmz3JEVnSup-ROCoyOTW0eQTMgFoHcjFJlbGW7PSSJ-2dOHkRCl7e0UVoxEID5l0RmfmA-fHD_DoUoE66h41c_zizRmFNwm1ACT4nZ9iUXLcHaC85_NNTErFc22nZBEyk7-d"
},
"device_id": "dfed66a4c55ad9e2",
"webhook_id": "d01cdf4a2f71442a4f6827a3b1f286f8eba08ba9cb04a8323d33710f182db56e",
"user_id": "7b2d06001c034891a51bd39c8d5da6db"
},
"options": {},
"pref_disable_new_entities": false,
"pref_disable_polling": false,
"source": "registration",
"unique_id": "io.homeassistant.companion.android-dfed66a4c55ad9e2",
"disabled_by": null
},
{
"entry_id": "8ccac653d869c1c4383d1ecc44f502f5",
"version": 1,
"domain": "mqtt",
"title": "localhost",
"data": {
"broker": "localhost",
"port": 1883,
"username": "mqtt",
"password": "mqtt5",
"discovery": true,
"discovery_prefix": "homeassistant"
},
"options": {},
"pref_disable_new_entities": false,
"pref_disable_polling": false,
"source": "user",
"unique_id": null,
"disabled_by": null
},
{
"entry_id": "b49fa9f4abad46f8769612fa4c2788a6",
"version": 1,
"domain": "tasmota",
"title": "Tasmota",
"data": {
"discovery_prefix": "tasmota/discovery"
},
"options": {},
"pref_disable_new_entities": false,
"pref_disable_polling": false,
"source": "user",
"unique_id": null,
"disabled_by": null
},
{
"entry_id": "23e0a870e4bd3890a0cc854aa08515fb",
"version": 1,
"domain": "thread",
"title": "Thread",
"data": {},
"options": {},
"pref_disable_new_entities": false,
"pref_disable_polling": false,
"source": "zeroconf",
"unique_id": null,
"disabled_by": null
},
{
"entry_id": "db85b300710333d4eba1aa43751abae3",
"version": 1,
"domain": "purei9",
"title": "gorden.indra@solusar.de",
"data": {
"email": "gorden.indra@solusar.de",
"password": "Schaf0815!"
},
"options": {},
"pref_disable_new_entities": false,
"pref_disable_polling": false,
"source": "user",
"unique_id": null,
"disabled_by": null
},
{
"entry_id": "2c36b2c6517c1072e0f924dc0940312b",
"version": 1,
"domain": "mobile_app",
"title": "M2102J20SG",
"data": {
"app_id": "io.homeassistant.companion.android",
"app_name": "Home Assistant",
"app_version": "2023.10.2-full (11484)",
"device_name": "M2102J20SG",
"manufacturer": "Xiaomi",
"model": "M2102J20SG",
"os_name": "Android",
"os_version": "30",
"supports_encryption": false,
"app_data": {
"push_websocket_channel": true,
"push_url": "https://mobile-apps.home-assistant.io/api/sendPush/android/v1",
"push_token": "e-2sI-lzS5GOhVzmblZWuh:APA91bGChqAKCCr5FMDsxYsWuKMyIai17s4ToB4ofBGSGFILyTNafeyazhYUP4P8Rb2x8_BL6kX5kGlmDQeXRTOeGGJB0zMeR1kR5niGpQsP4qFJkuf_v7nEuFa7zfbysvBF58n3r8vN"
},
"device_id": "5fa7c3fa2b283d8d",
"webhook_id": "71ec20e4d93ccb59da42ab742087819cd2fd3dee3c1dda3c94f34566748afaf5",
"user_id": "7b2d06001c034891a51bd39c8d5da6db"
},
"options": {},
"pref_disable_new_entities": false,
"pref_disable_polling": false,
"source": "registration",
"unique_id": "io.homeassistant.companion.android-5fa7c3fa2b283d8d",
"disabled_by": null
},
{
"entry_id": "7d14654e1a39b06831b8be57fce84d89",
"version": 1,
"domain": "gardena_smart_system",
"title": "",
"data": {
"id": "7f4cf1e6-41f3-44f3-8515-b02792c0bf17",
"client_id": "7f4cf1e6-41f3-44f3-8515-b02792c0bf17",
"client_secret": "c3bd4c9d-fec7-4d05-b161-31ef53fb385f"
},
"options": {},
"pref_disable_new_entities": false,
"pref_disable_polling": false,
"source": "user",
"unique_id": "7f4cf1e6-41f3-44f3-8515-b02792c0bf17",
"disabled_by": null
},
{
"entry_id": "fe5c683d86837019bd95483cc79151c6",
"version": 1,
"domain": "ipp",
"title": "EPSON XP-8600 Series",
"data": {
"host": "192.168.0.100",
"port": 631,
"ssl": true,
"verify_ssl": false,
"base_path": "/ipp/print",
"name": "EPSON XP-8600 Series",
"uuid": "cfe92100-67c4-11d4-a45f-50579c70d247"
},
"options": {},
"pref_disable_new_entities": false,
"pref_disable_polling": false,
"source": "zeroconf",
"unique_id": "cfe92100-67c4-11d4-a45f-50579c70d247",
"disabled_by": null
},
{
"entry_id": "a11791d555aeeac725a8a36c3af0c882",
"version": 1,
"domain": "workday",
"title": "Workday Sensor",
"data": {},
"options": {
"name": "Workday Sensor",
"country": "DE",
"excludes": [
"sat",
"sun",
"holiday"
],
"days_offset": 0.0,
"workdays": [
"mon",
"tue",
"wed",
"thu",
"fri"
],
"add_holidays": [],
"remove_holidays": [],
"province": "NI"
},
"pref_disable_new_entities": false,
"pref_disable_polling": false,
"source": "user",
"unique_id": null,
"disabled_by": null
}
]
}
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+8
View File
@@ -0,0 +1,8 @@
{
"version": 1,
"minor_version": 1,
"key": "core.logger",
"data": {
"logs": {}
}
}
File diff suppressed because it is too large Load Diff
+8
View File
@@ -0,0 +1,8 @@
{
"version": 1,
"minor_version": 1,
"key": "core.uuid",
"data": {
"uuid": "d23214b4d35f41ea952f92e345af2bcd"
}
}
+44
View File
@@ -0,0 +1,44 @@
{
"version": 1,
"minor_version": 1,
"key": "energy",
"data": {
"energy_sources": [
{
"type": "grid",
"flow_from": [
{
"stat_energy_from": "sensor.lepus3_060_zahlerstand_total",
"stat_cost": null,
"entity_energy_price": null,
"number_energy_price": null
}
],
"flow_to": [
{
"stat_energy_to": "sensor.lepus3_060_wirkenergie_total",
"stat_compensation": null,
"entity_energy_price": null,
"number_energy_price": null
}
],
"cost_adjustment_day": 0.0
},
{
"type": "solar",
"stat_energy_from": "sensor.mi300_modul1_solar_energy_today",
"config_entry_solar_forecast": [
"ab60c2516b30eece89daeedfba4963d9"
]
},
{
"type": "solar",
"stat_energy_from": "sensor.mi300_modul2_solar_energy_today",
"config_entry_solar_forecast": [
"ab60c2516b30eece89daeedfba4963d9"
]
}
],
"device_consumption": []
}
}
@@ -0,0 +1,63 @@
{
"version": 1,
"minor_version": 1,
"key": "esphome.c80af580e90f6a18cc53e461be0431bf",
"data": {
"device_info": {
"uses_password": false,
"name": "office",
"friendly_name": "",
"mac_address": "3C:61:05:D3:12:DE",
"compilation_time": "Nov 21 2023, 10:43:22",
"model": "nodemcuv2",
"manufacturer": "Espressif",
"has_deep_sleep": false,
"esphome_version": "2023.11.2",
"project_name": "",
"project_version": "",
"webserver_port": 0,
"voice_assistant_version": 0,
"legacy_bluetooth_proxy_version": 0,
"bluetooth_proxy_feature_flags": 0,
"suggested_area": ""
},
"services": [],
"api_version": {
"major": 1,
"minor": 9
},
"sensor": [
{
"object_id": "office_room_temperature",
"key": 3758260046,
"name": "Office Room Temperature",
"unique_id": "officesensoroffice_room_temperature",
"disabled_by_default": false,
"icon": "",
"entity_category": 0,
"device_class": "temperature",
"unit_of_measurement": "°C",
"accuracy_decimals": 1,
"force_update": false,
"state_class": 1,
"last_reset_type": 0
},
{
"object_id": "office_room_humidity",
"key": 1465591175,
"name": "Office Room Humidity",
"unique_id": "officesensoroffice_room_humidity",
"disabled_by_default": false,
"icon": "",
"entity_category": 0,
"device_class": "humidity",
"unit_of_measurement": "%",
"accuracy_decimals": 0,
"force_update": false,
"state_class": 1,
"last_reset_type": 0
}
],
"text_sensor": []
}
}
@@ -0,0 +1,18 @@
{
"version": 1,
"minor_version": 1,
"key": "frontend.user_data_7b2d06001c034891a51bd39c8d5da6db",
"data": {
"language": {
"language": "en",
"number_format": "decimal_comma",
"time_format": "24",
"date_format": "YMD",
"time_zone": "local",
"first_weekday": "monday"
},
"core": {
"showAdvanced": true
}
}
}
+13
View File
@@ -0,0 +1,13 @@
{
"version": "6",
"minor_version": 1,
"key": "hacs.critical",
"data": [
{
"repository": "test/test",
"reason": "Security issues, known to steal auth tokens.",
"link": "https://github.com/hacs/default/pull/2",
"acknowledged": true
}
]
}
+62
View File
@@ -0,0 +1,62 @@
{
"version": "6",
"minor_version": 1,
"key": "hacs.hacs",
"data": {
"archived_repositories": [
"gjohansson-ST/stl",
"JuanMTech/amoled_blue",
"custom-components/sensor.custom_aftership",
"marcomow/ble-bulb-card",
"thebino/rki_covid",
"spycle/microbot_push",
"peternijssen/home-assistant-jumbo",
"natekspencer/hacs-litterrobot",
"JuanMTech/green_light_mode",
"lukich48/hass_mqtt_template_switch",
"walthowd/ha-automower",
"tikismoke/home-assistant-nespressoble",
"JuanMTech/green_dark_mode",
"DavidMStraub/homeassistant-homeconnect",
"KTibow/lovelace-light-soft-ui-theme",
"marrobHD/firetv-card",
"NemesisRE/lovelace-swipe-navigation",
"Sholofly/arrisdcx960",
"cgarwood/homeassistant-fullykiosk",
"custom-components/wienerlinien",
"AitorDB/home-assistant-sun-card",
"nagyrobi/home-assistant-custom-components-cover-rf-time-based",
"frenck/home-assistant-theme-outline",
"ThomasPrior/EthermineInfo",
"JuanMTech/orange_dark",
"custom-components/sensor.untappd",
"clayauld/lovelace-darksky-card",
"robmarkcole/HASS-Deepstack-face",
"georgezhao2010/climate_ewelink",
"JuanMTech/orange_light",
"robmarkcole/HASS-Deepstack-object",
"hacf-fr/hassRenaultZE",
"gerardag/person-entity-card"
],
"renamed_repositories": {
"9a4gl/hass-peltec": "9a4gl/hass-centrometal-boiler",
"dmitry-k/yandex_smart_home": "dext0r/yandex_smart_home",
"nikrolls/homeassistant-goldair-climate": "nicole-ashley/homeassistant-goldair-climate",
"DarkSir23/sensor.mylar": "WillowMist/sensor.mylar",
"78wesley/Darkish-Theme": "78wesley/Home-Assistant-Darkish-Theme",
"wills106/homsassistant-solax-modbus": "wills106/homeassistant-solax-modbus",
"wernerhp/ha_integration_load_shedding": "wernerhp/ha.integration.load_shedding",
"nbogojevic/homeassistant-midea-dehumidifier-lan": "nbogojevic/homeassistant-midea-air-appliances-lan",
"TheLastProject/lovelace-valetudo-map-card": "Hypfer/lovelace-valetudo-map-card",
"fuatakgun/kia_uvo": "Hyundai-Kia-Connect/kia_uvo",
"jadson179/consul": "gtjadsonsantos/consul",
"Farfar/homeassistant-skodaconnect": "skodaconnect/homeassistant-skodaconnect",
"lendy007/homeassistant-skodaconnect": "skodaconnect/homeassistant-skodaconnect",
"JoshuaMulliken/ha-wyzeapi": "SecKatie/ha-wyzeapi",
"mtarjoianu/ha_lektrico": "Lektrico/ha_lektrico",
"Wibias/hass-variables": "enkama/hass-variables",
"alexander0042/pirate-weather-ha": "Pirate-Weather/pirate-weather-ha"
},
"ignored_repositories": []
}
}
File diff suppressed because it is too large Load Diff
+171
View File
@@ -0,0 +1,171 @@
{
"version": 1,
"minor_version": 1,
"key": "homeassistant.exposed_entities",
"data": {
"assistants": {},
"exposed_entities": {
"zone.home": {
"assistants": {
"conversation": {
"should_expose": false
}
}
},
"sun.sun": {
"assistants": {
"conversation": {
"should_expose": false
}
}
},
"sensor.isap_speed": {
"assistants": {
"conversation": {
"should_expose": false
}
}
},
"sensor.isap_particle_sensor": {
"assistants": {
"conversation": {
"should_expose": false
}
}
},
"sensor.isap_filter_remaining_days": {
"assistants": {
"conversation": {
"should_expose": false
}
}
},
"sensor.isap_replace_filter": {
"assistants": {
"conversation": {
"should_expose": false
}
}
},
"sensor.isap_current_speed": {
"assistants": {
"conversation": {
"should_expose": false
}
}
},
"sensor.isap_airquality": {
"assistants": {
"conversation": {
"should_expose": false
}
}
},
"persistent_notification.http_login": {
"assistants": {
"conversation": {
"should_expose": false
}
}
},
"binary_sensor.indego_903601155_update_available": {
"assistants": {
"conversation": {
"should_expose": false
}
}
},
"sensor.indego_903601155_mower_state": {
"assistants": {
"conversation": {
"should_expose": false
}
}
},
"sensor.indego_903601155_mower_state_detail": {
"assistants": {
"conversation": {
"should_expose": false
}
}
},
"sensor.indego_903601155_lawn_mowed": {
"assistants": {
"conversation": {
"should_expose": false
}
}
},
"sensor.indego_903601155_runtime_total": {
"assistants": {
"conversation": {
"should_expose": false
}
}
},
"sensor.indego_903601155_last_completed": {
"assistants": {
"conversation": {
"should_expose": false
}
}
},
"sensor.indego_903601155_mowing_mode": {
"assistants": {
"conversation": {
"should_expose": false
}
}
},
"binary_sensor.indego_903601155_alert": {
"assistants": {
"conversation": {
"should_expose": false
}
}
},
"binary_sensor.indego_903601155_online": {
"assistants": {
"conversation": {
"should_expose": false
}
}
},
"sensor.indego_903601155_battery_percentage": {
"assistants": {
"conversation": {
"should_expose": false
}
}
},
"sensor.wetterstation": {
"assistants": {
"conversation": {
"should_expose": false
}
}
},
"sensor.wetterstation_vortag": {
"assistants": {
"conversation": {
"should_expose": false
}
}
},
"sensor.niederschlag_letzte_stunde": {
"assistants": {
"conversation": {
"should_expose": false
}
}
},
"sensor.gasincubicmeter": {
"assistants": {
"conversation": {
"should_expose": false
}
}
}
}
}
}
+20
View File
@@ -0,0 +1,20 @@
{
"version": 1,
"minor_version": 1,
"key": "http",
"data": {
"use_x_forwarded_for": true,
"trusted_proxies": [
"192.168.0.28",
"192.168.0.0"
],
"use_x_frame_options": true,
"ssl_profile": "modern",
"ip_ban_enabled": true,
"cors_allowed_origins": [
"https://cast.home-assistant.io"
],
"login_attempts_threshold": -1,
"server_port": 8123
}
}
+8
View File
@@ -0,0 +1,8 @@
{
"version": 1,
"minor_version": 1,
"key": "http.auth",
"data": {
"content_user": "3044f6b530044a0fbfa1f21ecb939be5"
}
}
+93
View File
@@ -0,0 +1,93 @@
{
"version": 1,
"minor_version": 1,
"key": "image",
"data": {
"items": [
{
"id": "f1149839fd2bbf2c3b1e5dabaf69c22c",
"filesize": 2704889,
"content_type": "image/jpeg",
"name": "pexels-lukas-317385.jpg",
"uploaded_at": "2022-10-12T08:49:57.025064+00:00"
},
{
"id": "ab0be2fdca543d9e9dff5f500cbb952b",
"filesize": 1084047,
"content_type": "image/jpeg",
"name": "pexels-mark-mccammon-1080696.jpg",
"uploaded_at": "2022-10-12T08:52:07.871722+00:00"
},
{
"id": "3935001279ca2f042969771e9fcb6c7e",
"filesize": 1468729,
"content_type": "image/jpeg",
"name": "pexels-gerritt-tisdale-922796.jpg",
"uploaded_at": "2022-10-12T08:55:55.869160+00:00"
},
{
"id": "bb6540b318280b1ddffb9c5f1860107e",
"filesize": 2328990,
"content_type": "image/jpeg",
"name": "pexels-max-vakhtbovych-5997960.jpg",
"uploaded_at": "2022-10-12T08:56:56.798896+00:00"
},
{
"id": "c7ed0abb2978b6c12ad5432a879fcaf4",
"filesize": 359883,
"content_type": "image/jpeg",
"name": "pexels-monstera-6781117.jpg",
"uploaded_at": "2022-10-12T08:59:11.271325+00:00"
},
{
"id": "394267756098de5e3dd05e787a7437a5",
"filesize": 23098,
"content_type": "image/jpeg",
"name": "istockphoto-1301030625-612x612.jpg",
"uploaded_at": "2022-10-12T09:01:32.800153+00:00"
},
{
"id": "5811b89d86ae6193acadbf81bc7f13dc",
"filesize": 996041,
"content_type": "image/jpeg",
"name": "pexels-pixabay-271618.jpg",
"uploaded_at": "2022-10-12T09:02:48.365314+00:00"
},
{
"id": "a3e57b93383e55dd5b6e2f85f0f5a4a1",
"filesize": 624734,
"content_type": "image/jpeg",
"name": "pexels-rene-asmussen-1358900.jpg",
"uploaded_at": "2022-10-12T09:03:43.483868+00:00"
},
{
"id": "832ab454dd38e62ca698da627eee43d1",
"filesize": 1991568,
"content_type": "image/jpeg",
"name": "pexels-jonathan-borba-3144580.jpg",
"uploaded_at": "2022-10-12T09:05:19.604328+00:00"
},
{
"id": "ed9611ede795c578e5b28a68986b847d",
"filesize": 846778,
"content_type": "image/jpeg",
"name": "pexels-jean-van-der-meulen-1457842.jpg",
"uploaded_at": "2022-10-12T09:06:28.336736+00:00"
},
{
"id": "a14f3dd748d1d27a10cfd78b4ab77d05",
"filesize": 707671,
"content_type": "image/jpeg",
"name": "pexels-matheus-bertelli-1144694.jpg",
"uploaded_at": "2022-10-12T09:07:47.300962+00:00"
},
{
"id": "d6125680f69720a79b4ee376a9820958",
"filesize": 1209568,
"content_type": "image/jpeg",
"name": "pexels-karolina-grabowska-4239013.jpg",
"uploaded_at": "2022-10-12T09:19:07.374600+00:00"
}
]
}
}
+14
View File
@@ -0,0 +1,14 @@
{
"version": 1,
"minor_version": 1,
"key": "input_boolean",
"data": {
"items": [
{
"name": "Alok Home",
"icon": "mdi:cellphone",
"id": "alok_home"
}
]
}
}
+8
View File
@@ -0,0 +1,8 @@
{
"version": 1,
"minor_version": 1,
"key": "input_button",
"data": {
"items": []
}
}
+22
View File
@@ -0,0 +1,22 @@
{
"version": 1,
"minor_version": 2,
"key": "input_select",
"data": {
"items": [
{
"name": "FanSpeed",
"icon": "mdi:air-purifier",
"options": [
"auto",
"speed_1",
"speed_2",
"speed_3",
"speed_4",
"off"
],
"id": "fanspeed"
}
]
}
}
+471
View File
@@ -0,0 +1,471 @@
{
"version": 1,
"minor_version": 1,
"key": "lovelace",
"data": {
"config": {
"views": [
{
"theme": "Backend-selected",
"title": "Home",
"subview": false,
"icon": "mdi:information-outline",
"badges": [],
"cards": [
{
"type": "vertical-stack",
"cards": [
{
"show_current": true,
"show_forecast": true,
"type": "weather-forecast",
"entity": "weather.synology",
"forecast_type": "legacy",
"secondary_info_attribute": "pressure"
},
{
"type": "entity",
"entity": "sensor.lepus3_060_zahlerstand_total_kwh",
"state_color": false,
"name": "Zählerstand",
"unit": "kWh"
},
{
"type": "entity",
"entity": "sensor.lepus3_060_wirkenergie_total_kwh",
"name": "Einspeisung",
"unit": "kWh"
},
{
"type": "custom:mini-graph-card",
"entities": [
{
"entity": "sensor.mi300_modul1_solar_current_power",
"name": "Modul 1"
},
{
"entity": "sensor.mi300_modul2_solar_current_power",
"name": "Modul 2"
}
],
"name": "Solarstromproduktion",
"hours_to_show": 168,
"points_per_hour": 0.25
}
]
}
]
},
{
"theme": "Backend-selected",
"title": "Erdgeschoss",
"path": "erdgeschoss",
"icon": "mdi:home-floor-0",
"badges": [],
"cards": [
{
"camera_view": "auto",
"type": "picture-glance",
"title": "Flur",
"entities": [
{
"entity": "light.flur_2"
}
],
"image": "https://st.hzcdn.com/simgs/pictures/entryways/entrance-hall-victoria-waters-design-pty-ltd-img~17617bbd0a7d4621_4-7017-1-581a97f.jpg"
},
{
"camera_view": "auto",
"type": "picture-glance",
"title": "Gäste-WC",
"image": "https://imageio.forbes.com/specials-images/imageserve/5f6f3f339a75cdaabef2bf63/best-public-bathrooms/960x0.jpg?format=jpg&width=960",
"entities": [
{
"entity": "light.gaste_wc"
},
{
"entity": "sensor.window_guestwc_temperature"
},
{
"entity": "climate.thermostat_34"
},
{
"entity": "cover.gaste_wc_2"
},
{
"entity": "binary_sensor.window_guestwc"
}
]
},
{
"camera_view": "auto",
"type": "picture-glance",
"title": "Hauswirtschaftsraum",
"image": "https://www.thespruce.com/thmb/k6Gcmi7O188IIOYvXO15ZqxVf_Q=/750x0/filters:no_upscale():max_bytes(150000):strip_icc():format(webp)/CathieHongInteriors_062520_36-535272eb6cc14b81a556c8de3e040144.JPG",
"entities": [
{
"entity": "light.hwr"
},
{
"entity": "sensor.window_hwr_temperature"
},
{
"entity": "cover.hauswirtschaftsraum_2"
},
{
"entity": "binary_sensor.window_hwr"
}
]
},
{
"type": "vertical-stack",
"cards": [
{
"camera_view": "auto",
"type": "picture-glance",
"entities": [
{
"entity": "light.esszimmer"
},
{
"entity": "sensor.window_dinner_temperature"
},
{
"entity": "climate.thermostat2_ez"
},
{
"entity": "cover.wohnzimmer_links_2"
},
{
"entity": "binary_sensor.window_dinner"
}
],
"image": "https://www.thespruce.com/thmb/9_vRMP6AqSbDpiZ9QakYz1OCJiA=/750x0/filters:no_upscale():max_bytes(150000):strip_icc():format(webp)/201105-MV-CandaceMaryLongfellow_008-1-0cb57a2a4481490689638c43b9d4f613.jpg",
"title": "Esszimmer"
},
{
"type": "custom:vacuum-card",
"entity": "vacuum.alfred"
}
]
},
{
"type": "vertical-stack",
"cards": [
{
"camera_view": "auto",
"type": "picture-glance",
"title": "Küche",
"image": "https://demo.home-assistant.io/stub_config/kitchen.png",
"entities": [
{
"entity": "light.kuche"
},
{
"entity": "light.dimmable_light_32"
},
{
"entity": "climate.thermostat4_k"
},
{
"entity": "switch.on_off_plug_in_unit_27"
},
{
"entity": "switch.on_off_plug_in_unit_14"
},
{
"entity": "binary_sensor.gas_kuche"
},
{
"entity": "sensor.gas_kuche"
},
{
"entity": "cover.kuche_2"
},
{
"entity": "binary_sensor.openclose_17"
}
],
"entity": "sensor.gas_kuche"
},
{
"show_name": true,
"show_icon": true,
"type": "button",
"tap_action": {
"action": "toggle"
},
"entity": "switch.wasserkocher_start",
"show_state": true
},
{
"type": "entities",
"entities": [
{
"entity": "sensor.wasserkocher_current_temperature"
},
{
"entity": "number.wasserkocher_temperature"
},
{
"entity": "switch.wasserkocher_heat_preservation"
},
{
"entity": "number.wasserkocher_heat_preservation_time"
}
]
}
]
},
{
"camera_view": "auto",
"type": "picture-glance",
"title": "Wohnzimmer",
"entities": [
{
"entity": "light.wohnzimmer"
},
{
"entity": "sensor.temperature_38"
},
{
"entity": "climate.thermostat3_wz"
},
{
"entity": "sensor.airquality1_ppb"
},
{
"entity": "cover.wohnzimmer_rechts_2"
},
{
"entity": "binary_sensor.window_living"
},
{
"entity": "switch.on_off_plug_in_unit_28"
}
],
"camera_image": "camera.flur"
}
]
},
{
"theme": "Backend-selected",
"title": "1. Etage",
"path": "1-etage",
"icon": "mdi:home-floor-1",
"badges": [],
"cards": [
{
"type": "vertical-stack",
"cards": [
{
"camera_view": "auto",
"type": "picture-glance",
"title": "Flur (Oben)",
"image": "https://cdn.mos.cms.futurecdn.net/Jcb3HbuSP6Sp3DKDjmCHo3-1024-80.gif",
"entities": [
{
"entity": "light.flur_oben_2"
}
]
},
{
"type": "custom:vacuum-card",
"entity": "vacuum.henry"
}
]
},
{
"camera_view": "auto",
"type": "picture-glance",
"title": "Büro",
"image": "https://hips.hearstapps.com/hmg-prod/images/work-from-home-upgrades-1672775120.jpg?crop=0.987xw:0.743xh;0,0.0831xh&resize=1200:*",
"entities": [
{
"entity": "light.buro"
},
{
"entity": "binary_sensor.fritz_box_7590_link"
},
{
"entity": "sensor.realmdrive_volume_1_status"
},
{
"entity": "sensor.office_room_temperature"
},
{
"entity": "sensor.office_room_humidity"
}
]
},
{
"camera_view": "auto",
"type": "picture-glance",
"title": "Gästezimmer",
"image": "https://res.cloudinary.com/spacejoy/image/upload/fl_lossy,q_auto/c_scale,w_1500/v1604961438/server/designs/render/5fa9c49d9e468c0023fc3205.jpg",
"entities": [
{
"entity": "light.gastezimmer"
}
]
},
{
"camera_view": "auto",
"type": "picture-glance",
"title": "Schlafzimmer",
"image": "https://www.thespruce.com/thmb/8QkHUPAbichOIdWN5Rm-lK-AVu4=/750x0/filters:no_upscale():max_bytes(150000):strip_icc():format(webp)/GIRONA20-f9044b00403540cb90ddb703fc836c1a.jpg",
"entities": [
{
"entity": "light.schlafzimmer"
},
{
"entity": "switch.onoff_schlafzimmer"
}
]
}
]
},
{
"theme": "Backend-selected",
"title": "Garten",
"path": "garten",
"icon": "mdi:shovel",
"badges": [],
"cards": [
{
"type": "vertical-stack",
"cards": [
{
"camera_view": "auto",
"type": "picture-glance",
"entities": [
{
"entity": "switch.gardenpump"
},
{
"entity": "camera.haustur"
},
{
"entity": "camera.blink_carport"
},
{
"entity": "camera.blink_grundstuck"
}
],
"title": "Grundstück",
"camera_image": "camera.garten"
},
{
"type": "alarm-panel",
"states": [
"arm_home",
"arm_away"
],
"entity": "alarm_control_panel.blink_blink_kameras"
}
]
},
{
"type": "entities",
"entities": [
{
"entity": "sensor.sensor1_battery_level",
"name": "Battery level"
},
{
"entity": "sensor.sensor1_soil_humidity",
"name": "Soil humidity"
},
{
"entity": "sensor.sensor1_soil_temperature",
"name": "Soil temperature"
}
],
"title": "Sensor1"
},
{
"type": "entities",
"entities": [
{
"entity": "switch.irrigation_control_strang_1",
"name": "- Strang 1"
},
{
"entity": "switch.irrigation_control_strang_2",
"name": "- Strang 2"
},
{
"entity": "switch.irrigation_control_strang_3",
"name": "- Strang 3"
},
{
"entity": "switch.irrigation_control_strang_4",
"name": "- Strang 4"
}
],
"title": "Irrigation Control"
}
]
},
{
"title": "Wetter",
"path": "wetter",
"icon": "mdi:weather-partly-cloudy",
"badges": [],
"cards": [
{
"type": "custom:compass-card",
"indicator_sensors": [
{
"sensor": "sensor.pws_windrichtung"
}
],
"value_sensors": [
{
"sensor": "sensor.pws_windgeschwindigkeit"
}
],
"language": "de",
"compass": {
"north": {
"show": true
}
},
"header": {
"title": {
"value": "Wind"
}
}
},
{
"type": "custom:mini-graph-card",
"entities": [
"sensor.pws_temperatur_aktuell"
],
"hours_to_show": 24,
"points_per_hour": 1
},
{
"type": "custom:mini-graph-card",
"entities": [
"sensor.pws_luftdruck"
],
"hours_to_show": 24,
"points_per_hour": 1
},
{
"type": "custom:rain-gauge-card",
"entity": "sensor.pws_regenmenge_24h",
"name": "Regen",
"language": "de",
"is_imperial": false,
"hourly_rate_entity": "sensor.pws_regen_letzte_h",
"show_warning": false,
"show_error": false
}
]
}
]
}
}
}
+46
View File
@@ -0,0 +1,46 @@
{
"version": 1,
"minor_version": 1,
"key": "lovelace.lovelace_fenster",
"data": {
"config": {
"views": [
{
"title": "Home",
"cards": [
{
"type": "entity",
"entity": "binary_sensor.window_guestwc",
"state_color": true,
"name": "G\u00e4ste-WC"
},
{
"type": "entity",
"entity": "binary_sensor.window_hwr",
"state_color": true,
"name": "Hauswirtschaftsraum"
},
{
"type": "entity",
"entity": "binary_sensor.window_dinner",
"state_color": true,
"name": "Esszimmer"
},
{
"type": "entity",
"entity": "binary_sensor.window_living",
"state_color": true,
"name": "Wohnzimmer"
},
{
"type": "entity",
"entity": "binary_sensor.openclose_17",
"name": "K\u00fcche",
"state_color": true
}
]
}
]
}
}
}
+132
View File
@@ -0,0 +1,132 @@
{
"version": 1,
"minor_version": 1,
"key": "lovelace.lovelace_kameras",
"data": {
"config": {
"views": [
{
"title": "Home",
"cards": [
{
"square": false,
"columns": 2,
"type": "grid",
"cards": [
{
"show_state": true,
"show_name": true,
"camera_view": "auto",
"type": "picture-entity",
"entity": "camera.haustur",
"camera_image": "camera.haustur"
},
{
"show_state": true,
"show_name": true,
"camera_view": "auto",
"type": "picture-entity",
"entity": "camera.garten",
"name": "Garten",
"camera_image": "camera.garten",
"hold_action": {
"action": "call-service",
"service": "ring.update",
"target": {}
}
},
{
"show_state": true,
"show_name": true,
"camera_view": "auto",
"type": "picture-entity",
"entity": "camera.flur",
"name": "Wohnzimmer",
"camera_image": "camera.flur",
"hold_action": {
"action": "call-service",
"service": "ring.update",
"target": {}
}
},
{
"show_state": true,
"show_name": true,
"camera_view": "auto",
"type": "picture-entity",
"entity": "camera.blink_grundstuck",
"camera_image": "camera.blink_grundstuck",
"name": "Terrasse",
"hold_action": {
"action": "call-service",
"service": "blink.trigger_camera",
"target": {
"entity_id": "camera.blink_grundstuck"
},
"data": {}
}
},
{
"show_state": true,
"show_name": true,
"camera_view": "auto",
"type": "picture-entity",
"camera_image": "camera.blink_terrasse",
"entity": "camera.blink_carport",
"name": "Hofseite",
"hold_action": {
"action": "call-service",
"service": "blink.trigger_camera",
"target": {
"entity_id": "camera.blink_carport"
},
"data": {}
}
}
]
},
{
"type": "alarm-panel",
"states": [
"arm_home",
"arm_away"
],
"entity": "alarm_control_panel.blink_blink_kameras"
},
{
"type": "entities",
"entities": [
{
"entity": "binary_sensor.blink_grundstuck_battery"
},
{
"entity": "binary_sensor.blink_carport_battery"
},
{
"entity": "sensor.garten_battery"
},
{
"entity": "sensor.haustur_battery"
}
]
},
{
"hours_to_show": 24,
"graph": "line",
"type": "sensor",
"entity": "sensor.blink_grundstuck_temperature",
"detail": 1
},
{
"graph": "line",
"hours_to_show": 24,
"type": "sensor",
"entity": "sensor.blink_carport_temperature",
"detail": 1
}
]
}
]
}
}
}
+73
View File
@@ -0,0 +1,73 @@
{
"version": 1,
"minor_version": 1,
"key": "lovelace.lovelace_lights",
"data": {
"config": {
"views": [
{
"title": "Home",
"cards": [
{
"type": "light",
"entity": "light.buro"
},
{
"type": "light",
"entity": "light.kuche",
"icon": "mdi:ceiling-light-outline"
},
{
"type": "light",
"entity": "light.gastezimmer",
"icon": "mdi:ceiling-light-outline"
},
{
"type": "light",
"entity": "light.flur_oben",
"icon": "mdi:ceiling-light-outline"
},
{
"type": "light",
"entity": "light.color_temperature_light_21",
"icon": "mdi:ceiling-light-outline",
"name": "Gäste-WC"
},
{
"type": "light",
"entity": "light.schlafzimmer",
"icon": "mdi:ceiling-light-outline"
},
{
"type": "light",
"entity": "light.dimmable_light_7",
"icon": "mdi:ceiling-light-outline",
"name": "Esszimmer"
},
{
"type": "light",
"entity": "light.flur",
"name": "Flur EG",
"icon": "mdi:ceiling-light-outline"
},
{
"type": "light",
"entity": "light.hwr",
"icon": "mdi:ceiling-light-outline"
},
{
"type": "light",
"entity": "light.wohnzimmer",
"icon": "mdi:ceiling-light-outline"
},
{
"type": "light",
"entity": "light.treppe",
"icon": "mdi:stairs"
}
]
}
]
}
}
}
+142
View File
@@ -0,0 +1,142 @@
{
"version": 1,
"minor_version": 1,
"key": "lovelace.lovelace_network",
"data": {
"config": {
"views": [
{
"title": "Home",
"cards": [
{
"type": "vertical-stack",
"cards": [
{
"type": "entity",
"entity": "sensor.fritz_box_7590_external_ip_2",
"state_color": false
},
{
"hours_to_show": 24,
"graph": "line",
"type": "sensor",
"entity": "sensor.fritz_box_7590_download_throughput",
"detail": 1
},
{
"hours_to_show": 24,
"graph": "line",
"type": "sensor",
"entity": "sensor.fritz_box_7590_upload_throughput",
"detail": 1
},
{
"show_name": true,
"show_icon": true,
"type": "button",
"tap_action": {
"action": "toggle"
},
"entity": "switch.fritz_box_7590_wi_fi_realm_gast"
},
{
"show_name": true,
"show_icon": true,
"type": "button",
"tap_action": {
"action": "toggle"
},
"entity": "switch.fritz_box_7590_wi_fi_realm_network"
},
{
"show_name": true,
"show_icon": true,
"type": "button",
"tap_action": {
"action": "toggle"
},
"entity": "switch.fritz_box_7590_wi_fi_realm_network_5g",
"show_state": false
}
]
},
{
"type": "vertical-stack",
"cards": [
{
"type": "gauge",
"entity": "sensor.realmdrive_cpu_utilization_total"
},
{
"type": "gauge",
"entity": "sensor.realmdrive_memory_usage_real"
},
{
"type": "gauge",
"entity": "sensor.realmdrive_volume_1_volume_used",
"needle": true,
"severity": {
"green": 1,
"yellow": 80,
"red": 90
}
},
{
"hours_to_show": 24,
"graph": "line",
"type": "sensor",
"entity": "sensor.realmdrive_temperature",
"detail": 1
},
{
"hours_to_show": 24,
"graph": "line",
"type": "sensor",
"entity": "sensor.realmdrive_download_throughput",
"detail": 1
},
{
"hours_to_show": 24,
"graph": "line",
"type": "sensor",
"entity": "sensor.realmdrive_upload_throughput",
"detail": 1
}
]
},
{
"type": "vertical-stack",
"cards": [
{
"type": "entities",
"entities": [
{
"entity": "sensor.epson_xp_8600_series_black_ink"
},
{
"entity": "sensor.epson_xp_8600_series_cyan_ink"
},
{
"entity": "sensor.epson_xp_8600_series_light_cyan_ink"
},
{
"entity": "sensor.epson_xp_8600_series_magenta_ink"
},
{
"entity": "sensor.epson_xp_8600_series_light_magenta_ink"
},
{
"entity": "sensor.epson_xp_8600_series_yellow_ink"
}
],
"title": "EPSON XP-8600 Tintentstände",
"show_header_toggle": false
}
]
}
]
}
]
}
}
}
+51
View File
@@ -0,0 +1,51 @@
{
"version": 1,
"minor_version": 1,
"key": "lovelace.lovelace_rollladen",
"data": {
"config": {
"views": [
{
"title": "Home",
"cards": [
{
"type": "custom:shutter-card",
"title": "Hauswirtschaftsraum",
"entities": [
"cover.hauswirtschaftsraum_2"
]
},
{
"type": "custom:shutter-card",
"title": "Gäste-WC",
"entities": [
"cover.gaste_wc_2"
]
},
{
"type": "custom:shutter-card",
"title": "Küche",
"entities": [
"cover.kuche_2"
]
},
{
"type": "custom:shutter-card",
"title": "Esszimmer",
"entities": [
"cover.wohnzimmer_links_2"
]
},
{
"type": "custom:shutter-card",
"title": "Wohnzimmer",
"entities": [
"cover.wohnzimmer_rechts_2"
]
}
]
}
]
}
}
}
+94
View File
@@ -0,0 +1,94 @@
{
"version": 1,
"minor_version": 1,
"key": "lovelace.lovelace_temperaturen",
"data": {
"config": {
"views": [
{
"title": "Home",
"cards": [
{
"type": "vertical-stack",
"cards": [
{
"type": "thermostat",
"entity": "climate.thermostat_34",
"name": "Gäste-WC"
},
{
"type": "thermostat",
"entity": "climate.thermostat2_ez",
"name": "Esszimmer"
},
{
"type": "thermostat",
"entity": "climate.thermostat3_wz",
"name": "Wohnzimmer "
},
{
"type": "thermostat",
"entity": "climate.thermostat4_k",
"name": "Küche"
}
]
},
{
"type": "vertical-stack",
"cards": [
{
"show_name": false,
"show_icon": true,
"show_state": true,
"type": "glance",
"entities": [
{
"entity": "sensor.temperature_38"
},
{
"entity": "sensor.humidity_39"
},
{
"entity": "sensor.airquality1_ppb"
}
],
"title": "Wohnzimmer"
},
{
"show_name": false,
"show_icon": true,
"show_state": true,
"type": "glance",
"entities": [
{
"entity": "sensor.office_room_temperature"
},
{
"entity": "sensor.office_room_humidity"
}
],
"title": "Büro"
},
{
"show_name": false,
"show_icon": true,
"show_state": true,
"type": "glance",
"entities": [
{
"entity": "sensor.shellyht_f93cec_temperature"
},
{
"entity": "sensor.shellyht_f93cec_humidity"
}
],
"title": "Schlafzimmer "
}
]
}
]
}
]
}
}
}
+114
View File
@@ -0,0 +1,114 @@
{
"version": 1,
"minor_version": 1,
"key": "lovelace.pv_anlate",
"data": {
"config": {
"views": [
{
"title": "Home",
"cards": [
{
"type": "vertical-stack",
"cards": [
{
"type": "gauge",
"entity": "sensor.mi300_modul1_solar_current_power",
"name": "Modul 1",
"min": 0,
"max": 300,
"severity": {
"green": 200,
"yellow": 100,
"red": 50
}
},
{
"type": "entities",
"entities": [
{
"entity": "sensor.mi300_modul1_solar_energy_today"
},
{
"entity": "sensor.mi300_modul1_solar_energy_total"
},
{
"entity": "sensor.mi300_modul1_signal_quality"
}
]
},
{
"hours_to_show": 24,
"graph": "line",
"type": "sensor",
"entity": "sensor.mi300_modul1_solar_current_power",
"detail": 2,
"name": "Power Production History (24h)",
"icon": "mdi:solar-panel",
"unit": "W"
}
]
},
{
"type": "vertical-stack",
"cards": [
{
"type": "gauge",
"entity": "sensor.mi300_modul2_solar_current_power",
"name": "Modul 2",
"min": 0,
"max": 300,
"severity": {
"green": 200,
"yellow": 100,
"red": 50
}
},
{
"type": "entities",
"entities": [
{
"entity": "sensor.mi300_modul2_solar_energy_today"
},
{
"entity": "sensor.mi300_modul2_solar_energy_total"
},
{
"entity": "sensor.mi300_modul2_signal_quality"
}
]
},
{
"hours_to_show": 24,
"graph": "line",
"type": "sensor",
"entity": "sensor.mi300_modul2_solar_current_power",
"detail": 2,
"name": "Power Production History (24h)",
"icon": "mdi:solar-panel",
"unit": "W"
}
]
},
{
"show_name": true,
"show_icon": true,
"show_state": true,
"type": "glance",
"entities": [
{
"entity": "sensor.power_highest_peak_time_today"
},
{
"entity": "sensor.power_production_now"
}
],
"title": "Solar Production Forecast",
"state_color": false
}
]
}
]
}
}
}
+72
View File
@@ -0,0 +1,72 @@
{
"version": 1,
"minor_version": 1,
"key": "lovelace_dashboards",
"data": {
"items": [
{
"show_in_sidebar": true,
"icon": "mdi:cctv",
"title": "Kameras",
"require_admin": false,
"mode": "storage",
"url_path": "lovelace-kameras",
"id": "lovelace_kameras"
},
{
"show_in_sidebar": true,
"icon": "mdi:home-lightbulb",
"title": "Lights",
"require_admin": false,
"mode": "storage",
"url_path": "lovelace-lights",
"id": "lovelace_lights"
},
{
"show_in_sidebar": true,
"icon": "mdi:console-network",
"title": "Network",
"require_admin": false,
"mode": "storage",
"url_path": "lovelace-network",
"id": "lovelace_network"
},
{
"show_in_sidebar": true,
"icon": "mdi:window-shutter-open",
"title": "Rollläden",
"require_admin": false,
"mode": "storage",
"url_path": "lovelace-rollladen",
"id": "lovelace_rollladen"
},
{
"show_in_sidebar": true,
"icon": "mdi:thermometer",
"title": "Temperaturen",
"require_admin": false,
"mode": "storage",
"url_path": "lovelace-temperaturen",
"id": "lovelace_temperaturen"
},
{
"show_in_sidebar": true,
"icon": "mdi:window-closed",
"title": "Fenster",
"require_admin": false,
"mode": "storage",
"url_path": "lovelace-fenster",
"id": "lovelace_fenster"
},
{
"show_in_sidebar": true,
"icon": "mdi:solar-panel",
"title": "PV-Anlage",
"require_admin": false,
"mode": "storage",
"url_path": "pv-anlate",
"id": "pv_anlate"
}
]
}
}
+39
View File
@@ -0,0 +1,39 @@
{
"version": 1,
"minor_version": 1,
"key": "lovelace_resources",
"data": {
"items": [
{
"url": "/hacsfiles/hass-shutter-card/hass-shutter-card.js?hacstag=269011342140",
"type": "module",
"id": "208915834e3b4195a9f104f3eebffffb"
},
{
"id": "96ebe5f272524851b86711a2284307da",
"url": "/hacsfiles/compass-card/compass-card.js?hacstag=286038496200",
"type": "module"
},
{
"id": "4849aa4132144edf86b0705f14fea2c4",
"url": "/hacsfiles/mini-graph-card/mini-graph-card-bundle.js?hacstag=1512800620110",
"type": "module"
},
{
"id": "82347b960f8e4ab787da4f82b7d58b93",
"url": "/hacsfiles/rain-gauge-card/rain-gauge-card.js?hacstag=609593305131",
"type": "module"
},
{
"id": "c037757bd9694caba3ff2e21e4de7527",
"url": "/hacsfiles/vacuum-card/vacuum-card.js?hacstag=261291295276",
"type": "module"
},
{
"id": "5b7f994bee414016996f0f17fa7e41e0",
"url": "/hacsfiles/lovelace-mushroom/mushroom.js?hacstag=444350375310",
"type": "module"
}
]
}
}
+15
View File
@@ -0,0 +1,15 @@
{
"version": 1,
"minor_version": 1,
"key": "mobile_app",
"data": {
"deleted_ids": [
"871f7587ec3fbb7252a8b64a73a48942300786cfd440493b438c400682921435",
"351167e82916c56e0b1fc82631f7f4d9ccd30e6030cb0e2ce8e0dd32e997441d",
"b8cff991e8bf47345320c5345a3b2449ec1ac02ded399f2f903217cdcedf59eb",
"cf9c9cb834ed91fa62c0e46f03718712fb3e578de94b2a325fe982425a167c39",
"654634ef85b1a07c5bb613e704179ecb0292d51d67acc55a6f2ab76028bf91d0",
"1fbfc3733f716fccb5a1af1b672af15a6317e826ae968d37f5fd79b278e64a62"
]
}
}
+13
View File
@@ -0,0 +1,13 @@
{
"version": 4,
"minor_version": 1,
"key": "onboarding",
"data": {
"done": [
"user",
"core_config",
"analytics",
"integration"
]
}
}
+20
View File
@@ -0,0 +1,20 @@
{
"version": 2,
"minor_version": 1,
"key": "person",
"data": {
"items": [
{
"name": "gorden",
"user_id": "7b2d06001c034891a51bd39c8d5da6db",
"device_trackers": [
"device_tracker.lenovo_tb_j606f",
"device_tracker.m2102j20sg",
"device_tracker.poco_x3_pro",
"device_tracker.phone_1"
],
"id": "gorden"
}
]
}
}
+16
View File
@@ -0,0 +1,16 @@
{
"version": 1,
"minor_version": 2,
"key": "repairs.issue_registry",
"data": {
"issues": [
{
"created": "2023-02-02T10:25:31.373210+00:00",
"dismissed_version": "2023.2.0",
"domain": "esphome",
"is_persistent": false,
"issue_id": "api_password_deprecated-58:BF:25:DA:2A:FF"
}
]
}
}
File diff suppressed because it is too large Load Diff
+377
View File
@@ -0,0 +1,377 @@
- id: '1652787175779'
alias: Close Blinds
description: ''
trigger:
- type: temperature
platform: device
device_id: f9ab2c1e2365cd6c6808dd2d36585e69
entity_id: sensor.shellyht_f93cec_temperature
domain: sensor
above: 25
condition: []
action:
- device_id: 95a511fb852cbcc156864ddcf5f4a083
domain: cover
entity_id: cover.wohnzimmer_links
type: set_position
position: 12
- device_id: 993d8bb19b84c0667522153016d6cb05
domain: cover
entity_id: cover.wohnzimmer_rechts
type: set_position
position: 12
mode: single
- id: '1652892911485'
alias: Dunstabzug an
description: ''
trigger:
- type: opened
platform: device
device_id: 925011b73a5e493591e69fb225b7cfd7
entity_id: binary_sensor.openclose_17
domain: binary_sensor
condition: []
action:
- type: turn_on
device_id: 501d9510266fae1bef92308c34718b38
entity_id: switch.on_off_plug_in_unit_27
domain: switch
mode: single
- id: '1652892963309'
alias: Dunstabzug aus
description: ''
trigger:
- type: not_opened
platform: device
device_id: 925011b73a5e493591e69fb225b7cfd7
entity_id: binary_sensor.openclose_17
domain: binary_sensor
condition: []
action:
- type: turn_off
device_id: 501d9510266fae1bef92308c34718b38
entity_id: switch.on_off_plug_in_unit_27
domain: switch
mode: single
- id: '1654177411866'
alias: arm_blink-system
description: ''
trigger:
- platform: zone
entity_id: device_tracker.mi_9t
zone: zone.home
event: leave
- platform: zone
entity_id: device_tracker.poco_x3_pro
zone: zone.home
event: leave
condition: []
action:
- device_id: 6836ccb6a93416b9743169b0a1567027
domain: alarm_control_panel
entity_id: alarm_control_panel.blink_blink_kameras
type: arm_away
mode: single
- id: '1654177458993'
alias: unarm_blink_system
description: ''
trigger:
- platform: zone
entity_id: device_tracker.poco_x3_pro
zone: zone.home
event: enter
- platform: zone
entity_id: device_tracker.mi_9t
zone: zone.home
event: enter
condition: []
action:
- device_id: 6836ccb6a93416b9743169b0a1567027
domain: alarm_control_panel
entity_id: alarm_control_panel.blink_blink_kameras
type: disarm
mode: single
- id: '1675426194975'
alias: Henry starten
description: Starten AEG Saugroboter im Standard-Modus
trigger:
- device_id: 3a7d8ce472e3d48d7530661e0a2b73dd
domain: deconz
platform: device
type: remote_button_short_release
subtype: left
condition: []
action:
- device_id: 26a7579f7cc57661a35064dfb47cff05
domain: vacuum
entity_id: vacuum.henry
type: clean
mode: single
- id: '1675426263026'
alias: Henry zur Basis
description: Sendet AEG Saugroboter zur Basis zurück
trigger:
- device_id: 3a7d8ce472e3d48d7530661e0a2b73dd
domain: deconz
platform: device
type: remote_button_short_release
subtype: right
condition: []
action:
- device_id: 26a7579f7cc57661a35064dfb47cff05
domain: vacuum
entity_id: vacuum.henry
type: dock
mode: single
- id: '1679989579054'
alias: Info_Terrasse_Battery
description: ''
trigger:
- type: bat_low
platform: device
device_id: 5c23ac91f65c341bd9424ea4305baea2
entity_id: binary_sensor.blink_terrasse_battery
domain: binary_sensor
condition: []
action:
- service: notify.mobile_app_phone_1
data:
message: Battery Blink Terrasse is low
title: Attention
mode: single
- id: '1679989643189'
alias: info_carport_battery
description: ''
trigger:
- type: bat_low
platform: device
device_id: 6ceb8b6e12fe699960c86a2733e3625d
entity_id: binary_sensor.blink_carport_battery
domain: binary_sensor
condition: []
action:
- service: notify.mobile_app_phone_1
data:
message: Battery Blink Carport is low
title: Attention
mode: single
- id: '1679989681294'
alias: info_grundstueck_battery
description: ''
trigger:
- type: bat_low
platform: device
device_id: de1028940093c9c8648b3f8de481d3a8
entity_id: binary_sensor.blink_grundstuck_battery
domain: binary_sensor
condition: []
action:
- service: notify.mobile_app_phone_1
data:
message: Battery Blink Grundstück is low
title: Attention
mode: single
- id: '1679989779284'
alias: blink_2h_newimage
description: Triggers every 2 hours to refresh the picture
trigger:
- platform: time_pattern
hours: '2'
condition: []
action:
- service: blink.trigger_camera
data: {}
target:
entity_id:
- camera.blink_carport
- camera.blink_grundstuck
- camera.blink_terrasse
mode: single
- id: '1679990307886'
alias: ring_30min_Wohnen_new_snapshot
description: ''
trigger:
- platform: time_pattern
minutes: '30'
condition: []
action:
- service: camera.snapshot
data:
filename: /tmp/wohnen_snap.jpg
target:
entity_id: camera.flur
mode: single
- id: '1697198876699'
alias: Open_Window_Recognition_GWC
description: Fenster öffen schaltet thermostat aus
trigger:
- type: opened
platform: device
device_id: 3d9bbd20d917477dba1d762a3d8e57d0
entity_id: 63d4831aedc671a64c86af2bcefdd983
domain: binary_sensor
condition: []
action:
- device_id: a152eac711fe7d8599834c5016e8ec79
domain: climate
entity_id: 234b20c3d47cc9b257eb39806a8df372
type: set_hvac_mode
hvac_mode: 'off'
mode: single
- id: '1697198957606'
alias: Heizen_GWC
description: Schaltet Thermostat auf heizen, wenn Fenster schließt
trigger:
- type: not_opened
platform: device
device_id: 3d9bbd20d917477dba1d762a3d8e57d0
entity_id: 63d4831aedc671a64c86af2bcefdd983
domain: binary_sensor
condition: []
action:
- device_id: a152eac711fe7d8599834c5016e8ec79
domain: climate
entity_id: 234b20c3d47cc9b257eb39806a8df372
type: set_hvac_mode
hvac_mode: heat
mode: single
- id: '1697219751213'
alias: OpenWindow_EZ
description: ''
trigger:
- type: opened
platform: device
device_id: 938becb0cb270ff5ba696295c9725265
entity_id: 037cd57a6b208ae6111efdd18c4b646f
domain: binary_sensor
condition: []
action:
- device_id: 4310fd4e6889633f88fc76c2c040b2a2
domain: climate
entity_id: 36ed9ff3613857c403d338429d180187
type: set_hvac_mode
hvac_mode: 'off'
mode: single
- id: '1697219805799'
alias: CloseWindow_EZ
description: ''
trigger:
- type: not_opened
platform: device
device_id: 938becb0cb270ff5ba696295c9725265
entity_id: 037cd57a6b208ae6111efdd18c4b646f
domain: binary_sensor
condition: []
action:
- device_id: 4310fd4e6889633f88fc76c2c040b2a2
domain: climate
entity_id: 36ed9ff3613857c403d338429d180187
type: set_hvac_mode
hvac_mode: heat
mode: single
- id: '1697219876457'
alias: OpenWindow_K
description: ''
trigger:
- type: opened
platform: device
device_id: 925011b73a5e493591e69fb225b7cfd7
entity_id: 3cd23ba868711451231a06df2c8b2a70
domain: binary_sensor
condition: []
action:
- device_id: d1b6e8d6cb808a38ac2b0e02c8263748
domain: climate
entity_id: f377ead71021311ed369a790fc19115c
type: set_hvac_mode
hvac_mode: 'off'
mode: single
- id: '1697219906412'
alias: CloseWindow_K
description: ''
trigger:
- type: not_opened
platform: device
device_id: 925011b73a5e493591e69fb225b7cfd7
entity_id: 3cd23ba868711451231a06df2c8b2a70
domain: binary_sensor
condition: []
action:
- device_id: d1b6e8d6cb808a38ac2b0e02c8263748
domain: climate
entity_id: f377ead71021311ed369a790fc19115c
type: set_hvac_mode
hvac_mode: heat
mode: single
- id: '1697219964028'
alias: OpenWindow_WZ
description: ''
trigger:
- type: opened
platform: device
device_id: 8579bd30fee6b1e2f4fdd308b10f1294
entity_id: b44da533c2091c7e686b2981886e8520
domain: binary_sensor
condition: []
action:
- device_id: 4e5bc694a9c4d1cb2c9acd8da493aa33
domain: climate
entity_id: 7d001a74f7572603920f3ee67fd4f05a
type: set_hvac_mode
hvac_mode: 'off'
mode: single
- id: '1697219992895'
alias: CloseWindow_WZ
description: ''
trigger:
- type: not_opened
platform: device
device_id: 8579bd30fee6b1e2f4fdd308b10f1294
entity_id: b44da533c2091c7e686b2981886e8520
domain: binary_sensor
condition: []
action:
- device_id: 4e5bc694a9c4d1cb2c9acd8da493aa33
domain: climate
entity_id: 7d001a74f7572603920f3ee67fd4f05a
type: set_hvac_mode
hvac_mode: heat
mode: single
- id: '1699360571026'
alias: Wohnraum_aufheizen
description: Thermostaten im Wohnbereich auf "On" werktags um 16:00 Uhr
trigger:
- platform: time
at: '16:00:00'
condition:
- condition: state
entity_id: binary_sensor.workday_sensor
state: 'on'
action:
- service: climate.set_temperature
data:
temperature: 30
target:
device_id:
- 4310fd4e6889633f88fc76c2c040b2a2
- 4e5bc694a9c4d1cb2c9acd8da493aa33
mode: single
- id: '1699360994119'
alias: Wohnraum_normal
description: Thermostaten im Wohnbereich auf 23°C werktags ab 18:00 Uhr
trigger:
- platform: time
at: '18:00:00'
condition:
- condition: state
entity_id: binary_sensor.workday_sensor
state: 'on'
action:
- service: climate.set_temperature
data:
temperature: 23
target:
device_id:
- 4310fd4e6889633f88fc76c2c040b2a2
- 4e5bc694a9c4d1cb2c9acd8da493aa33
mode: single
@@ -0,0 +1,46 @@
blueprint:
name: Zone Notification
description: Send a notification to a device when a person leaves a specific zone.
domain: automation
source_url: https://github.com/home-assistant/core/blob/dev/homeassistant/components/automation/blueprints/notify_leaving_zone.yaml
input:
person_entity:
name: Person
selector:
entity:
domain: person
zone_entity:
name: Zone
selector:
entity:
domain: zone
notify_device:
name: Device to notify
description: Device needs to run the official Home Assistant app to receive notifications.
selector:
device:
integration: mobile_app
trigger:
platform: state
entity_id: !input person_entity
variables:
zone_entity: !input zone_entity
# This is the state of the person when it's in this zone.
zone_state: "{{ states[zone_entity].name }}"
person_entity: !input person_entity
person_name: "{{ states[person_entity].name }}"
condition:
condition: template
# The first case handles leaving the Home zone which has a special state when zoning called 'home'.
# The second case handles leaving all other zones.
value_template: "{{ zone_entity == 'zone.home' and trigger.from_state.state == 'home' and trigger.to_state.state != 'home' or trigger.from_state.state == zone_state and trigger.to_state.state != zone_state }}"
action:
- alias: "Notify that a person has left the zone"
domain: mobile_app
type: notify
device_id: !input notify_device
message: "{{ person_name }} has left {{ zone_state }}"
@@ -0,0 +1,84 @@
blueprint:
name: Confirmable Notification
description: >-
A script that sends an actionable notification with a confirmation before
running the specified action.
domain: script
source_url: https://github.com/home-assistant/core/blob/master/homeassistant/components/script/blueprints/confirmable_notification.yaml
input:
notify_device:
name: Device to notify
description: Device needs to run the official Home Assistant app to receive notifications.
selector:
device:
integration: mobile_app
title:
name: "Title"
description: "The title of the button shown in the notification."
default: ""
selector:
text:
message:
name: "Message"
description: "The message body"
selector:
text:
confirm_text:
name: "Confirmation Text"
description: "Text to show on the confirmation button"
default: "Confirm"
selector:
text:
confirm_action:
name: "Confirmation Action"
description: "Action to run when notification is confirmed"
default: []
selector:
action:
dismiss_text:
name: "Dismiss Text"
description: "Text to show on the dismiss button"
default: "Dismiss"
selector:
text:
dismiss_action:
name: "Dismiss Action"
description: "Action to run when notification is dismissed"
default: []
selector:
action:
mode: restart
sequence:
- alias: "Set up variables"
variables:
action_confirm: "{{ 'CONFIRM_' ~ context.id }}"
action_dismiss: "{{ 'DISMISS_' ~ context.id }}"
- alias: "Send notification"
domain: mobile_app
type: notify
device_id: !input notify_device
title: !input title
message: !input message
data:
actions:
- action: "{{ action_confirm }}"
title: !input confirm_text
- action: "{{ action_dismiss }}"
title: !input dismiss_text
- alias: "Awaiting response"
wait_for_trigger:
- platform: event
event_type: mobile_app_notification_action
event_data:
action: "{{ action_confirm }}"
- platform: event
event_type: mobile_app_notification_action
event_data:
action: "{{ action_dismiss }}"
- choose:
- conditions: "{{ wait.trigger.event.data.action == action_confirm }}"
sequence: !input confirm_action
- conditions: "{{ wait.trigger.event.data.action == action_dismiss }}"
sequence: !input dismiss_action
+47
View File
@@ -0,0 +1,47 @@
# Loads default set of integrations. Do not remove.
default_config:
homeassistant:
internal_url: "http://192.168.0.40:8123"
external_url: "http://homeassistant.realm.local"
country: DE
auth_providers:
- type: trusted_networks
trusted_networks:
- 192.168.0.0/24
- 127.0.0.1
- ::1
allow_bypass_login: true
- type: homeassistant
customize:
sensor.lepus3_060_zahlerstand_total:
device_class: energy
unit_of_measurement: "Wh"
state_class: total_increasing
sensor.lepus3_060_wirkenergie_total:
device_class: energy
unit_of_measurement: "Wh"
state_class: total_increasing
allowlist_external_dirs:
- /tmp
# Text to speech
tts:
- platform: google_translate
http:
use_x_forwarded_for: true
trusted_proxies:
- 192.168.0.28 # Add the IP address of the proxy server
- 192.168.0.0/24 # trusted network
# includes
automation: !include automations.yaml
script: !include scripts.yaml
scene: !include scenes.yaml
sensor: !include sensors.yaml
rest: !include rest.yaml
template: !include template.yaml
@@ -0,0 +1,91 @@
"""The better_thermostat component."""
import logging
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant, Config
from homeassistant.config_entries import ConfigEntry
import voluptuous as vol
from .const import (
CONF_FIX_CALIBRATION,
CONF_CALIBRATION_MODE,
CONF_HEATER,
CONF_NO_SYSTEM_MODE_OFF,
CONF_WINDOW_TIMEOUT,
)
_LOGGER = logging.getLogger(__name__)
DOMAIN = "better_thermostat"
PLATFORMS = [Platform.CLIMATE]
CONFIG_SCHEMA = vol.Schema({DOMAIN: vol.Schema({})}, extra=vol.ALLOW_EXTRA)
async def async_setup(hass: HomeAssistant, config: Config):
"""Set up this integration using YAML is not supported."""
hass.data[DOMAIN] = {}
return True
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
hass.data[DOMAIN] = {}
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
entry.async_on_unload(entry.add_update_listener(config_entry_update_listener))
return True
async def config_entry_update_listener(hass: HomeAssistant, entry: ConfigEntry) -> None:
"""Handle options update."""
await hass.config_entries.async_reload(entry.entry_id)
await async_unload_entry(hass, entry)
await async_setup_entry(hass, entry)
async def async_unload_entry(hass, entry):
"""Unload a config entry."""
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
return unload_ok
async def async_reload_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> None:
await async_unload_entry(hass, config_entry)
await async_setup_entry(hass, config_entry)
async def async_migrate_entry(hass, config_entry: ConfigEntry):
"""Migrate old entry."""
_LOGGER.debug("Migrating from version %s", config_entry.version)
if config_entry.version == 1:
new = {**config_entry.data}
for trv in new[CONF_HEATER]:
trv["advanced"].update({CONF_FIX_CALIBRATION: False})
config_entry.version = 2
hass.config_entries.async_update_entry(config_entry, data=new)
if config_entry.version == 2:
new = {**config_entry.data}
new[CONF_WINDOW_TIMEOUT] = 0
config_entry.version = 3
hass.config_entries.async_update_entry(config_entry, data=new)
if config_entry.version == 3:
new = {**config_entry.data}
for trv in new[CONF_HEATER]:
if (
CONF_FIX_CALIBRATION in trv["advanced"]
and trv["advanced"][CONF_FIX_CALIBRATION]
):
trv["advanced"].update({CONF_CALIBRATION_MODE: CONF_FIX_CALIBRATION})
else:
trv["advanced"].update({CONF_CALIBRATION_MODE: "default"})
config_entry.version = 4
hass.config_entries.async_update_entry(config_entry, data=new)
if config_entry.version == 4:
new = {**config_entry.data}
for trv in new[CONF_HEATER]:
trv["advanced"].update({CONF_NO_SYSTEM_MODE_OFF: False})
config_entry.version = 5
hass.config_entries.async_update_entry(config_entry, data=new)
_LOGGER.info("Migration to version %s successful", config_entry.version)
return True
@@ -0,0 +1,66 @@
import logging
from .generic import (
set_temperature as generic_set_temperature,
set_hvac_mode as generic_set_hvac_mode,
)
_LOGGER = logging.getLogger(__name__)
async def get_info(self, entity_id):
"""Get info from TRV."""
_offset = self.hass.states.get(entity_id).attributes.get("offset", None)
if _offset is None:
return {"support_offset": False, "support_valve": False}
return {"support_offset": True, "support_valve": False}
async def init(self, entity_id):
return None
async def set_temperature(self, entity_id, temperature):
"""Set new target temperature."""
return await generic_set_temperature(self, entity_id, temperature)
async def set_hvac_mode(self, entity_id, hvac_mode):
"""Set new target hvac mode."""
return await generic_set_hvac_mode(self, entity_id, hvac_mode)
async def get_current_offset(self, entity_id):
"""Get current offset."""
return float(str(self.hass.states.get(entity_id).attributes.get("offset", 0)))
async def get_offset_steps(self, entity_id):
"""Get offset steps."""
return float(1.0)
async def get_min_offset(self, entity_id):
"""Get min offset."""
return -6
async def get_max_offset(self, entity_id):
"""Get max offset."""
return 6
async def set_offset(self, entity_id, offset):
"""Set new target offset."""
await self.hass.services.async_call(
"deconz",
"configure",
{"entity": entity_id, "field": "/config", "data": {"offset": offset}},
blocking=True,
context=self.context,
)
self.real_trvs[entity_id]["last_calibration"] = offset
async def set_valve(self, entity_id, valve):
"""Set new target valve."""
return None
@@ -0,0 +1,171 @@
import asyncio
import logging
from homeassistant.components.number.const import SERVICE_SET_VALUE
from ..utils.helpers import find_local_calibration_entity
from homeassistant.const import STATE_UNAVAILABLE, STATE_UNKNOWN
_LOGGER = logging.getLogger(__name__)
async def get_info(self, entity_id):
"""Get info from TRV."""
support_offset = False
offset = await find_local_calibration_entity(self, entity_id)
if offset is not None:
support_offset = True
return {"support_offset": support_offset, "support_valve": False}
async def init(self, entity_id):
if (
self.real_trvs[entity_id]["local_temperature_calibration_entity"] is None
and self.real_trvs[entity_id]["calibration"] == 0
):
self.real_trvs[entity_id][
"local_temperature_calibration_entity"
] = await find_local_calibration_entity(self, entity_id)
_LOGGER.debug(
"better_thermostat %s: uses local calibration entity %s",
self.name,
self.real_trvs[entity_id]["local_temperature_calibration_entity"],
)
# Wait for the entity to be available
_ready = True
while _ready:
if self.hass.states.get(
self.real_trvs[entity_id]["local_temperature_calibration_entity"]
).state in (STATE_UNAVAILABLE, STATE_UNKNOWN, None):
_LOGGER.info(
"better_thermostat %s: waiting for TRV/climate entity with id '%s' to become fully available...",
self.name,
self.real_trvs[entity_id]["local_temperature_calibration_entity"],
)
await asyncio.sleep(5)
continue
_ready = False
return
return None
async def get_current_offset(self, entity_id):
"""Get current offset."""
if self.real_trvs[entity_id]["local_temperature_calibration_entity"] is not None:
return float(
str(
self.hass.states.get(
self.real_trvs[entity_id]["local_temperature_calibration_entity"]
).state
)
)
else:
return None
async def get_offset_steps(self, entity_id):
"""Get offset steps."""
if self.real_trvs[entity_id]["local_temperature_calibration_entity"] is not None:
return float(
str(
self.hass.states.get(
self.real_trvs[entity_id]["local_temperature_calibration_entity"]
).attributes.get("step", 1)
)
)
else:
return None
async def get_min_offset(self, entity_id):
"""Get min offset."""
if self.real_trvs[entity_id]["local_temperature_calibration_entity"] is not None:
return float(
str(
self.hass.states.get(
self.real_trvs[entity_id]["local_temperature_calibration_entity"]
).attributes.get("min", -10)
)
)
else:
return -6
async def get_max_offset(self, entity_id):
"""Get max offset."""
if self.real_trvs[entity_id]["local_temperature_calibration_entity"] is not None:
return float(
str(
self.hass.states.get(
self.real_trvs[entity_id]["local_temperature_calibration_entity"]
).attributes.get("max", 10)
)
)
else:
return 6
async def set_temperature(self, entity_id, temperature):
"""Set new target temperature."""
await self.hass.services.async_call(
"climate",
"set_temperature",
{"entity_id": entity_id, "temperature": temperature},
blocking=True,
context=self.context,
)
async def set_hvac_mode(self, entity_id, hvac_mode):
"""Set new target hvac mode."""
await self.hass.services.async_call(
"climate",
"set_hvac_mode",
{"entity_id": entity_id, "hvac_mode": hvac_mode},
blocking=True,
context=self.context,
)
async def set_offset(self, entity_id, offset):
"""Set new target offset."""
if self.real_trvs[entity_id]["local_temperature_calibration_entity"] is not None:
max_calibration = await get_max_offset(self, entity_id)
min_calibration = await get_min_offset(self, entity_id)
if offset >= max_calibration:
offset = max_calibration
if offset <= min_calibration:
offset = min_calibration
await self.hass.services.async_call(
"number",
SERVICE_SET_VALUE,
{
"entity_id": self.real_trvs[entity_id][
"local_temperature_calibration_entity"
],
"value": offset,
},
blocking=True,
context=self.context,
)
self.real_trvs[entity_id]["last_calibration"] = offset
if (
self.real_trvs[entity_id]["last_hvac_mode"] is not None
and self.real_trvs[entity_id]["last_hvac_mode"] != "off"
):
await asyncio.sleep(3)
await set_hvac_mode(
self, entity_id, self.real_trvs[entity_id]["last_hvac_mode"]
)
return offset
else:
return # Not supported
async def set_valve(self, entity_id, valve):
"""Set new target valve."""
return # Not supported
@@ -0,0 +1,173 @@
import asyncio
import logging
from homeassistant.components.number.const import SERVICE_SET_VALUE
from ..utils.helpers import find_local_calibration_entity, find_valve_entity
from .generic import (
set_hvac_mode as generic_set_hvac_mode,
set_temperature as generic_set_temperature,
)
from homeassistant.const import STATE_UNAVAILABLE, STATE_UNKNOWN
_LOGGER = logging.getLogger(__name__)
async def get_info(self, entity_id):
"""Get info from TRV."""
support_offset = False
support_valve = False
offset = await find_local_calibration_entity(self, entity_id)
if offset is not None:
support_offset = True
valve = await find_valve_entity(self, entity_id)
if valve is not None:
support_valve = True
return {"support_offset": support_offset, "support_valve": support_valve}
async def init(self, entity_id):
if (
self.real_trvs[entity_id]["local_temperature_calibration_entity"] is None
and self.real_trvs[entity_id]["calibration"] == 0
):
self.real_trvs[entity_id][
"local_temperature_calibration_entity"
] = await find_local_calibration_entity(self, entity_id)
_LOGGER.debug(
"better_thermostat %s: uses local calibration entity %s",
self.name,
self.real_trvs[entity_id]["local_temperature_calibration_entity"],
)
# Wait for the entity to be available
_ready = True
while _ready:
if self.hass.states.get(
self.real_trvs[entity_id]["local_temperature_calibration_entity"]
).state in (STATE_UNAVAILABLE, STATE_UNKNOWN, None):
_LOGGER.info(
"better_thermostat %s: waiting for TRV/climate entity with id '%s' to become fully available...",
self.name,
self.real_trvs[entity_id]["local_temperature_calibration_entity"],
)
await asyncio.sleep(5)
continue
_ready = False
return
_has_preset = self.hass.states.get(entity_id).attributes.get(
"preset_modes", None
)
if _has_preset is not None:
await self.hass.services.async_call(
"climate",
"set_preset_mode",
{"entity_id": entity_id, "preset_mode": "manual"},
blocking=True,
context=self.context,
)
async def set_temperature(self, entity_id, temperature):
"""Set new target temperature."""
return await generic_set_temperature(self, entity_id, temperature)
async def set_hvac_mode(self, entity_id, hvac_mode):
"""Set new target hvac mode."""
await generic_set_hvac_mode(self, entity_id, hvac_mode)
await asyncio.sleep(3)
async def get_current_offset(self, entity_id):
"""Get current offset."""
return float(
str(
self.hass.states.get(
self.real_trvs[entity_id]["local_temperature_calibration_entity"]
).state
)
)
async def get_offset_steps(self, entity_id):
"""Get offset steps."""
return float(
str(
self.hass.states.get(
self.real_trvs[entity_id]["local_temperature_calibration_entity"]
).attributes.get("step", 1)
)
)
async def get_min_offset(self, entity_id):
"""Get min offset."""
return float(
str(
self.hass.states.get(
self.real_trvs[entity_id]["local_temperature_calibration_entity"]
).attributes.get("min", -10)
)
)
async def get_max_offset(self, entity_id):
"""Get max offset."""
return float(
str(
self.hass.states.get(
self.real_trvs[entity_id]["local_temperature_calibration_entity"]
).attributes.get("max", 10)
)
)
async def set_offset(self, entity_id, offset):
"""Set new target offset."""
max_calibration = await get_max_offset(self, entity_id)
min_calibration = await get_min_offset(self, entity_id)
if offset >= max_calibration:
offset = max_calibration
if offset <= min_calibration:
offset = min_calibration
await self.hass.services.async_call(
"number",
SERVICE_SET_VALUE,
{
"entity_id": self.real_trvs[entity_id][
"local_temperature_calibration_entity"
],
"value": offset,
},
blocking=True,
context=self.context,
)
self.real_trvs[entity_id]["last_calibration"] = offset
if (
self.real_trvs[entity_id]["last_hvac_mode"] is not None
and self.real_trvs[entity_id]["last_hvac_mode"] != "off"
):
await asyncio.sleep(3)
return await generic_set_hvac_mode(
self, entity_id, self.real_trvs[entity_id]["last_hvac_mode"]
)
async def set_valve(self, entity_id, valve):
"""Set new target valve."""
_LOGGER.debug(
f"better_thermostat {self.name}: TO TRV {entity_id} set_valve: {valve}"
)
await self.hass.services.async_call(
"number",
SERVICE_SET_VALUE,
{
"entity_id": self.real_trvs[entity_id]["valve_position_entity"],
"value": valve,
},
blocking=True,
context=self.context,
)
@@ -0,0 +1,69 @@
import logging
from .generic import (
set_temperature as generic_set_temperature,
set_hvac_mode as generic_set_hvac_mode,
)
_LOGGER = logging.getLogger(__name__)
async def get_info(self, entity_id):
"""Get info from TRV."""
return {"support_offset": True, "support_valve": False}
async def init(self, entity_id):
return None
async def set_temperature(self, entity_id, temperature):
"""Set new target temperature."""
return await generic_set_temperature(self, entity_id, temperature)
async def set_hvac_mode(self, entity_id, hvac_mode):
"""Set new target hvac mode."""
return await generic_set_hvac_mode(self, entity_id, hvac_mode)
async def get_current_offset(self, entity_id):
"""Get current offset."""
return float(
str(self.hass.states.get(entity_id).attributes.get("offset_celsius", 0))
)
async def get_offset_steps(self, entity_id):
"""Get offset steps."""
return float(0.01)
async def get_min_offset(self, entity_id):
"""Get min offset."""
return -10
async def get_max_offset(self, entity_id):
"""Get max offset."""
return 10
async def set_offset(self, entity_id, offset):
"""Set new target offset."""
if offset >= 10:
offset = 10
if offset <= -10:
offset = -10
await self.hass.services.async_call(
"tado",
"set_climate_temperature_offset",
{"entity_id": entity_id, "offset": offset},
blocking=True,
context=self.context,
)
self.real_trvs[entity_id]["last_calibration"] = offset
async def set_valve(self, entity_id, valve):
"""Set new target valve."""
return None
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,639 @@
import logging
import voluptuous as vol
from collections import OrderedDict
from .utils.bridge import load_adapter
from .utils.helpers import get_device_model, get_trv_intigration
from .const import (
CONF_PROTECT_OVERHEATING,
CONF_CALIBRATION,
CONF_CHILD_LOCK,
CONF_HEAT_AUTO_SWAPPED,
CONF_HEATER,
CONF_HOMATICIP,
CONF_HUMIDITY,
CONF_MODEL,
CONF_NO_SYSTEM_MODE_OFF,
CONF_OFF_TEMPERATURE,
CONF_OUTDOOR_SENSOR,
CONF_SENSOR,
CONF_SENSOR_WINDOW,
CONF_VALVE_MAINTENANCE,
CONF_WEATHER,
CONF_WINDOW_TIMEOUT,
CONF_CALIBRATION_MODE,
CONF_TOLERANCE,
CalibrationMode,
CalibrationType,
)
from homeassistant import config_entries
from homeassistant.const import CONF_NAME
from homeassistant.core import callback
from homeassistant.helpers import selector
from homeassistant.components.climate.const import HVACMode
from homeassistant.helpers import config_validation as cv
from . import DOMAIN # pylint:disable=unused-import
_LOGGER = logging.getLogger(__name__)
CALIBRATION_TYPE_SELECTOR = selector.SelectSelector(
selector.SelectSelectorConfig(
options=[
selector.SelectOptionDict(
value=CalibrationType.TARGET_TEMP_BASED,
label="Target Temperature Based",
)
],
mode=selector.SelectSelectorMode.DROPDOWN,
)
)
CALIBRATION_TYPE_ALL_SELECTOR = selector.SelectSelector(
selector.SelectSelectorConfig(
options=[
selector.SelectOptionDict(
value=CalibrationType.TARGET_TEMP_BASED,
label="Target Temperature Based",
),
selector.SelectOptionDict(
value=CalibrationType.LOCAL_BASED, label="Offset Based"
),
],
mode=selector.SelectSelectorMode.DROPDOWN,
)
)
CALIBRATION_MODE_SELECTOR = selector.SelectSelector(
selector.SelectSelectorConfig(
options=[
selector.SelectOptionDict(value=CalibrationMode.DEFAULT, label="Normal"),
selector.SelectOptionDict(
value=CalibrationMode.FIX_CALIBRATION, label="Agressive"
),
selector.SelectOptionDict(
value=CalibrationMode.HEATING_POWER_CALIBRATION, label="AI Time Based"
),
selector.SelectOptionDict(
value=CalibrationMode.NO_CALIBRATION, label="No Calibration"
),
],
mode=selector.SelectSelectorMode.DROPDOWN,
)
)
class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
VERSION = 5
CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_POLL
def __init__(self):
"""Initialize the config flow."""
self.name = ""
self.data = None
self.model = None
self.heater_entity_id = None
self.trv_bundle = []
self.integration = None
self.i = 0
@staticmethod
@callback
def async_get_options_flow(
config_entry: config_entries.ConfigEntry,
) -> config_entries.OptionsFlow:
"""Get the options flow for this handler."""
return OptionsFlowHandler(config_entry)
"""Get the options flow for this handler."""
async def async_step_confirm(self, user_input=None, confirm_type=None):
"""Handle user-confirmation of discovered node."""
errors = {}
self.data[CONF_HEATER] = self.trv_bundle
if user_input is not None:
if self.data is not None:
_LOGGER.debug("Confirm: %s", self.data[CONF_HEATER])
await self.async_set_unique_id(self.data["name"])
return self.async_create_entry(title=self.data["name"], data=self.data)
if confirm_type is not None:
errors["base"] = confirm_type
_trvs = ",".join([x["trv"] for x in self.data[CONF_HEATER]])
return self.async_show_form(
step_id="confirm",
errors=errors,
description_placeholders={"name": self.data[CONF_NAME], "trv": _trvs},
)
async def async_step_advanced(self, user_input=None, _trv_config=None):
"""Handle options flow."""
if user_input is not None:
self.trv_bundle[self.i]["advanced"] = user_input
self.trv_bundle[self.i]["adapter"] = None
self.i += 1
if len(self.trv_bundle) > self.i:
return await self.async_step_advanced(None, self.trv_bundle[self.i])
_has_off_mode = True
for trv in self.trv_bundle:
if HVACMode.OFF not in self.hass.states.get(
trv.get("trv")
).attributes.get("hvac_modes"):
_has_off_mode = False
if _has_off_mode is False:
return await self.async_step_confirm(None, "no_off_mode")
return await self.async_step_confirm()
user_input = user_input or {}
homematic = False
if _trv_config.get("integration").find("homematic") != -1:
homematic = True
fields = OrderedDict()
_default_calibration = "target_temp_based"
_adapter = _trv_config.get("adapter", None)
if _adapter is not None:
_info = await _adapter.get_info(self, _trv_config.get("trv"))
if _info.get("support_offset", False):
_default_calibration = "local_calibration_based"
if _default_calibration == "local_calibration_based":
fields[
vol.Required(
CONF_CALIBRATION,
default=user_input.get(CONF_CALIBRATION, _default_calibration),
)
] = CALIBRATION_TYPE_ALL_SELECTOR
else:
fields[
vol.Required(
CONF_CALIBRATION,
default=user_input.get(CONF_CALIBRATION, _default_calibration),
)
] = CALIBRATION_TYPE_SELECTOR
fields[
vol.Required(
CONF_CALIBRATION_MODE,
default=user_input.get(
CONF_CALIBRATION_MODE, CalibrationMode.HEATING_POWER_CALIBRATION
),
)
] = CALIBRATION_MODE_SELECTOR
fields[
vol.Optional(
CONF_PROTECT_OVERHEATING,
default=user_input.get(CONF_PROTECT_OVERHEATING, False),
)
] = bool
fields[
vol.Optional(
CONF_NO_SYSTEM_MODE_OFF,
default=user_input.get(CONF_NO_SYSTEM_MODE_OFF, False),
)
] = bool
fields[
vol.Optional(
CONF_HEAT_AUTO_SWAPPED,
default=user_input.get(CONF_HEAT_AUTO_SWAPPED, False),
)
] = bool
if _info.get("support_valve", False):
fields[
vol.Optional(
CONF_VALVE_MAINTENANCE,
default=user_input.get(CONF_VALVE_MAINTENANCE, False),
)
] = bool
fields[
vol.Optional(
CONF_CHILD_LOCK, default=user_input.get(CONF_CHILD_LOCK, False)
)
] = bool
fields[
vol.Optional(
CONF_HOMATICIP, default=user_input.get(CONF_HOMATICIP, homematic)
)
] = bool
return self.async_show_form(
step_id="advanced",
data_schema=vol.Schema(fields),
last_step=False,
description_placeholders={"trv": _trv_config.get("trv")},
)
async def async_step_user(self, user_input=None):
errors = {}
if user_input is not None:
if self.data is None:
self.data = user_input
self.heater_entity_id = self.data[CONF_HEATER]
if self.data[CONF_NAME] == "":
errors["base"] = "no_name"
if CONF_SENSOR_WINDOW not in self.data:
self.data[CONF_SENSOR_WINDOW] = None
if CONF_HUMIDITY not in self.data:
self.data[CONF_HUMIDITY] = None
if CONF_OUTDOOR_SENSOR not in self.data:
self.data[CONF_OUTDOOR_SENSOR] = None
if CONF_WEATHER not in self.data:
self.data[CONF_WEATHER] = None
if CONF_WINDOW_TIMEOUT in self.data:
self.data[CONF_WINDOW_TIMEOUT] = (
int(
cv.time_period_dict(
user_input.get(CONF_WINDOW_TIMEOUT, None)
).total_seconds()
)
or 0
)
else:
self.data[CONF_WINDOW_TIMEOUT] = 0
if "base" not in errors:
for trv in self.heater_entity_id:
_intigration = await get_trv_intigration(self, trv)
self.trv_bundle.append(
{
"trv": trv,
"integration": _intigration,
"model": await get_device_model(self, trv),
"adapter": load_adapter(self, _intigration, trv),
}
)
self.data[CONF_MODEL] = "/".join([x["model"] for x in self.trv_bundle])
return await self.async_step_advanced(None, self.trv_bundle[0])
user_input = user_input or {}
return self.async_show_form(
step_id="user",
data_schema=vol.Schema(
{
vol.Optional(CONF_NAME, default=user_input.get(CONF_NAME, "")): str,
vol.Required(CONF_HEATER): selector.EntitySelector(
selector.EntitySelectorConfig(domain="climate", multiple=True)
),
vol.Required(CONF_SENSOR): selector.EntitySelector(
selector.EntitySelectorConfig(
domain=["sensor", "number", "input_number"],
device_class="temperature",
multiple=False,
)
),
vol.Optional(CONF_HUMIDITY): selector.EntitySelector(
selector.EntitySelectorConfig(
domain=["sensor", "number", "input_number"],
device_class="humidity",
multiple=False,
)
),
vol.Optional(CONF_OUTDOOR_SENSOR): selector.EntitySelector(
selector.EntitySelectorConfig(
domain=["sensor", "input_number", "number"],
device_class="temperature",
multiple=False,
)
),
vol.Optional(CONF_SENSOR_WINDOW): selector.EntitySelector(
selector.EntitySelectorConfig(
domain=[
"group",
"sensor",
"input_boolean",
"binary_sensor",
],
multiple=False,
)
),
vol.Optional(CONF_WEATHER): selector.EntitySelector(
selector.EntitySelectorConfig(domain="weather", multiple=False)
),
vol.Optional(CONF_WINDOW_TIMEOUT): selector.DurationSelector(),
vol.Optional(
CONF_OFF_TEMPERATURE,
default=user_input.get(CONF_OFF_TEMPERATURE, 20),
): int,
vol.Optional(
CONF_TOLERANCE,
default=user_input.get(CONF_TOLERANCE, 0.0),
): float,
}
),
errors=errors,
last_step=False,
)
class OptionsFlowHandler(config_entries.OptionsFlow):
"""Handle a option flow for a config entry."""
def __init__(self, config_entry: config_entries.ConfigEntry) -> None:
"""Initialize options flow."""
self.config_entry = config_entry
self.options = dict(config_entry.options)
self.i = 0
self.trv_bundle = []
self.name = ""
self._last_step = False
self.updated_config = {}
async def async_step_init(self, user_input=None):
"""Manage the options."""
return await self.async_step_user()
async def async_step_advanced(
self, user_input=None, _trv_config=None, _update_config=None
):
"""Manage the advanced options."""
if user_input is not None:
self.trv_bundle[self.i]["advanced"] = user_input
self.trv_bundle[self.i]["adapter"] = None
self.i += 1
if len(self.trv_bundle) - 1 >= self.i:
self._last_step = True
if len(self.trv_bundle) > self.i:
return await self.async_step_advanced(
None, self.trv_bundle[self.i], _update_config
)
self.updated_config[CONF_HEATER] = self.trv_bundle
_LOGGER.debug("Updated config: %s", self.updated_config)
self.hass.config_entries.async_update_entry(
self.config_entry, data=self.updated_config
)
return self.async_create_entry(
title=self.updated_config["name"], data=self.updated_config
)
user_input = user_input or {}
homematic = False
if _trv_config.get("integration").find("homematic") != -1:
homematic = True
fields = OrderedDict()
_default_calibration = "target_temp_based"
self.name = user_input.get(CONF_NAME, "-")
_adapter = load_adapter(
self, _trv_config.get("integration"), _trv_config.get("trv")
)
if _adapter is not None:
_info = await _adapter.get_info(self, _trv_config.get("trv"))
if _info.get("support_offset", False):
_default_calibration = "local_calibration_based"
if _default_calibration == "local_calibration_based":
fields[
vol.Required(
CONF_CALIBRATION,
default=user_input.get(
CONF_CALIBRATION,
_trv_config["advanced"].get(
CONF_CALIBRATION, _default_calibration
),
),
)
] = CALIBRATION_TYPE_ALL_SELECTOR
else:
fields[
vol.Required(
CONF_CALIBRATION,
default=user_input.get(
CONF_CALIBRATION,
_trv_config["advanced"].get(
CONF_CALIBRATION, _default_calibration
),
),
)
] = CALIBRATION_TYPE_SELECTOR
fields[
vol.Required(
CONF_CALIBRATION_MODE,
default=_trv_config["advanced"].get(
CONF_CALIBRATION_MODE, CalibrationMode.HEATING_POWER_CALIBRATION
),
)
] = CALIBRATION_MODE_SELECTOR
fields[
vol.Optional(
CONF_PROTECT_OVERHEATING,
default=_trv_config["advanced"].get(CONF_PROTECT_OVERHEATING, False),
)
] = bool
fields[
vol.Optional(
CONF_NO_SYSTEM_MODE_OFF,
default=_trv_config["advanced"].get(CONF_NO_SYSTEM_MODE_OFF, False),
)
] = bool
has_auto = False
trv = self.hass.states.get(_trv_config.get("trv"))
if HVACMode.AUTO in trv.attributes.get("hvac_modes"):
has_auto = True
fields[
vol.Optional(
CONF_HEAT_AUTO_SWAPPED,
default=_trv_config["advanced"].get(CONF_HEAT_AUTO_SWAPPED, has_auto),
)
] = bool
if _info.get("support_valve", False):
fields[
vol.Optional(
CONF_VALVE_MAINTENANCE,
default=_trv_config["advanced"].get(CONF_VALVE_MAINTENANCE, False),
)
] = bool
fields[
vol.Optional(
CONF_CHILD_LOCK,
default=_trv_config["advanced"].get(CONF_CHILD_LOCK, False),
)
] = bool
fields[
vol.Optional(
CONF_HOMATICIP,
default=_trv_config["advanced"].get(CONF_HOMATICIP, homematic),
)
] = bool
return self.async_show_form(
step_id="advanced",
data_schema=vol.Schema(fields),
last_step=self._last_step,
description_placeholders={"trv": _trv_config.get("trv")},
)
async def async_step_user(self, user_input=None):
if user_input is not None:
current_config = self.config_entry.data
self.updated_config = dict(current_config)
self.updated_config[CONF_SENSOR] = user_input.get(CONF_SENSOR, None)
self.updated_config[CONF_SENSOR_WINDOW] = user_input.get(
CONF_SENSOR_WINDOW, None
)
self.updated_config[CONF_HUMIDITY] = user_input.get(CONF_HUMIDITY, None)
self.updated_config[CONF_OUTDOOR_SENSOR] = user_input.get(
CONF_OUTDOOR_SENSOR, None
)
self.updated_config[CONF_WEATHER] = user_input.get(CONF_WEATHER, None)
if CONF_WINDOW_TIMEOUT in self.updated_config:
self.updated_config[CONF_WINDOW_TIMEOUT] = (
int(
cv.time_period_dict(
user_input.get(CONF_WINDOW_TIMEOUT, None)
).total_seconds()
)
or 0
)
else:
self.updated_config[CONF_WINDOW_TIMEOUT] = 0
self.updated_config[CONF_OFF_TEMPERATURE] = user_input.get(
CONF_OFF_TEMPERATURE
)
self.updated_config[CONF_TOLERANCE] = user_input.get(CONF_TOLERANCE, 0.0)
for trv in self.updated_config[CONF_HEATER]:
trv["adapter"] = None
self.trv_bundle.append(trv)
return await self.async_step_advanced(
None, self.trv_bundle[0], self.updated_config
)
fields = OrderedDict()
fields[
vol.Optional(
CONF_SENSOR,
description={
"suggested_value": self.config_entry.data.get(CONF_SENSOR, "")
},
)
] = selector.EntitySelector(
selector.EntitySelectorConfig(
domain=["sensor", "number", "input_number"],
device_class="temperature",
multiple=False,
)
)
fields[
vol.Optional(
CONF_HUMIDITY,
description={
"suggested_value": self.config_entry.data.get(CONF_HUMIDITY, "")
},
)
] = selector.EntitySelector(
selector.EntitySelectorConfig(
domain=["sensor", "number", "input_number"],
device_class="humidity",
multiple=False,
)
)
fields[
vol.Optional(
CONF_SENSOR_WINDOW,
description={
"suggested_value": self.config_entry.data.get(
CONF_SENSOR_WINDOW, ""
)
},
)
] = selector.EntitySelector(
selector.EntitySelectorConfig(
domain=["group", "sensor", "input_boolean", "binary_sensor"],
multiple=False,
)
)
fields[
vol.Optional(
CONF_OUTDOOR_SENSOR,
description={
"suggested_value": self.config_entry.data.get(
CONF_OUTDOOR_SENSOR, ""
)
},
)
] = selector.EntitySelector(
selector.EntitySelectorConfig(
domain=["sensor", "input_number", "number"],
device_class="temperature",
multiple=False,
)
)
fields[
vol.Optional(
CONF_WEATHER,
description={
"suggested_value": self.config_entry.data.get(CONF_WEATHER, "")
},
)
] = selector.EntitySelector(
selector.EntitySelectorConfig(domain="weather", multiple=False)
)
_timeout = self.config_entry.data.get(CONF_WINDOW_TIMEOUT, 0)
_timeout = str(cv.time_period_seconds(_timeout))
_timeout = {
"hours": int(_timeout.split(":", maxsplit=1)[0]),
"minutes": int(_timeout.split(":")[1]),
"seconds": int(_timeout.split(":")[2]),
}
fields[
vol.Optional(
CONF_WINDOW_TIMEOUT,
default=_timeout,
description={"suggested_value": _timeout},
)
] = selector.DurationSelector()
fields[
vol.Optional(
CONF_OFF_TEMPERATURE,
default=self.config_entry.data.get(CONF_OFF_TEMPERATURE, 5),
)
] = int
fields[
vol.Optional(
CONF_TOLERANCE,
default=self.config_entry.data.get(CONF_TOLERANCE, 0.0),
)
] = float
return self.async_show_form(
step_id="user", data_schema=vol.Schema(fields), last_step=False
)
@@ -0,0 +1,100 @@
""""""
import json
from enum import IntEnum
from homeassistant.backports.enum import StrEnum
import logging
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.config_validation import ( # noqa: F401
make_entity_service_schema,
)
from homeassistant.components.climate.const import SUPPORT_TARGET_TEMPERATURE
from homeassistant.const import ATTR_TEMPERATURE
_LOGGER = logging.getLogger(__name__)
DEFAULT_NAME = "Better Thermostat"
VERSION = "master"
try:
with open("custom_components/better_thermostat/manifest.json") as manifest_file:
manifest = json.load(manifest_file)
VERSION = manifest["version"]
except (FileNotFoundError, KeyError, json.JSONDecodeError) as e:
_LOGGER.error("better_thermostat %s: could not read version from manifest file.", e)
CONF_HEATER = "thermostat"
CONF_SENSOR = "temperature_sensor"
CONF_HUMIDITY = "humidity_sensor"
CONF_SENSOR_WINDOW = "window_sensors"
CONF_TARGET_TEMP = "target_temp"
CONF_WEATHER = "weather"
CONF_OFF_TEMPERATURE = "off_temperature"
CONF_WINDOW_TIMEOUT = "window_off_delay"
CONF_OUTDOOR_SENSOR = "outdoor_sensor"
CONF_VALVE_MAINTENANCE = "valve_maintenance"
CONF_MIN_TEMP = "min_temp"
CONF_MAX_TEMP = "max_temp"
CONF_PRECISION = "precision"
CONF_CALIBRATION = "calibration"
CONF_CHILD_LOCK = "child_lock"
CONF_PROTECT_OVERHEATING = "protect_overheating"
CONF_CALIBRATION_MODE = "calibration_mode"
CONF_FIX_CALIBRATION = "fix_calibration"
CONF_HEATING_POWER_CALIBRATION = "heating_power_calibration"
CONF_NO_CALIBRATION = "no_calibration"
CONF_HEAT_AUTO_SWAPPED = "heat_auto_swapped"
CONF_MODEL = "model"
CONF_HOMATICIP = "homaticip"
CONF_INTEGRATION = "integration"
CONF_NO_SYSTEM_MODE_OFF = "no_off_system_mode"
CONF_TOLERANCE = "tolerance"
SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE
ATTR_STATE_WINDOW_OPEN = "window_open"
ATTR_STATE_CALL_FOR_HEAT = "call_for_heat"
ATTR_STATE_LAST_CHANGE = "last_change"
ATTR_STATE_SAVED_TEMPERATURE = "saved_temperature"
ATTR_VALVE_POSITION = "valve_position"
ATTR_STATE_HUMIDIY = "humidity"
ATTR_STATE_MAIN_MODE = "main_mode"
ATTR_STATE_HEATING_POWER = "heating_power"
ATTR_STATE_HEATING_STATS = "heating_stats"
ATTR_STATE_ERRORS = "errors"
ATTR_STATE_BATTERIES = "batteries"
SERVICE_RESTORE_SAVED_TARGET_TEMPERATURE = "restore_saved_target_temperature"
SERVICE_SET_TEMP_TARGET_TEMPERATURE = "set_temp_target_temperature"
SERVICE_RESET_HEATING_POWER = "reset_heating_power"
BETTERTHERMOSTAT_SET_TEMPERATURE_SCHEMA = vol.All(
cv.has_at_least_one_key(ATTR_TEMPERATURE),
make_entity_service_schema(
{vol.Exclusive(ATTR_TEMPERATURE, "temperature"): vol.Coerce(float)}
),
)
class BetterThermostatEntityFeature(IntEnum):
"""Supported features of the climate entity."""
TARGET_TEMPERATURE = 1
TARGET_TEMPERATURE_RANGE = 2
class CalibrationType(StrEnum):
"""Calibration type"""
TARGET_TEMP_BASED = "target_temp_based"
LOCAL_BASED = "local_calibration_based"
class CalibrationMode(StrEnum):
"""Calibration mode."""
DEFAULT = "default"
FIX_CALIBRATION = "fix_calibration"
HEATING_POWER_CALIBRATION = "heating_power_calibration"
NO_CALIBRATION = "no_calibration"
@@ -0,0 +1,152 @@
from __future__ import annotations
import voluptuous as vol
from homeassistant.core import CALLBACK_TYPE, HomeAssistant
from homeassistant.helpers import config_validation as cv, entity_registry
from homeassistant.components.homeassistant.triggers import (
numeric_state as numeric_state_trigger,
state as state_trigger,
)
from . import DOMAIN
from homeassistant.helpers.trigger import TriggerActionType, TriggerInfo
from homeassistant.helpers.typing import ConfigType
from homeassistant.components.climate.const import (
ATTR_CURRENT_TEMPERATURE,
ATTR_CURRENT_HUMIDITY,
HVAC_MODES,
)
from homeassistant.const import (
CONF_ABOVE,
CONF_BELOW,
CONF_DEVICE_ID,
CONF_DOMAIN,
CONF_ENTITY_ID,
CONF_FOR,
CONF_PLATFORM,
CONF_TYPE,
PERCENTAGE,
)
async def async_get_triggers(
hass: HomeAssistant, device_id: str
) -> list[dict[str, str]]:
"""List device triggers for Climate devices."""
registry = entity_registry.async_get(hass)
triggers = []
# Get all the integrations entities for this device
for entry in entity_registry.async_entries_for_device(registry, device_id):
if entry.domain != DOMAIN:
continue
state = hass.states.get(entry.entity_id)
# Add triggers for each entity that belongs to this integration
base_trigger = {
CONF_PLATFORM: "device",
CONF_DEVICE_ID: device_id,
CONF_DOMAIN: DOMAIN,
CONF_ENTITY_ID: entry.entity_id,
}
triggers.append({**base_trigger, CONF_TYPE: "hvac_mode_changed"})
if state and ATTR_CURRENT_TEMPERATURE in state.attributes:
triggers.append({**base_trigger, CONF_TYPE: "current_temperature_changed"})
if state and ATTR_CURRENT_HUMIDITY in state.attributes:
triggers.append({**base_trigger, CONF_TYPE: "current_humidity_changed"})
return triggers
async def async_attach_trigger(
hass: HomeAssistant,
config: ConfigType,
action: TriggerActionType,
trigger_info: TriggerInfo,
) -> CALLBACK_TYPE:
"""Attach a trigger."""
if (trigger_type := config[CONF_TYPE]) == "hvac_mode_changed":
state_config = {
state_trigger.CONF_PLATFORM: "state",
state_trigger.CONF_ENTITY_ID: config[CONF_ENTITY_ID],
state_trigger.CONF_TO: config[state_trigger.CONF_TO],
state_trigger.CONF_FROM: [
mode for mode in HVAC_MODES if mode != config[state_trigger.CONF_TO]
],
}
if CONF_FOR in config:
state_config[CONF_FOR] = config[CONF_FOR]
state_config = await state_trigger.async_validate_trigger_config(
hass, state_config
)
return await state_trigger.async_attach_trigger(
hass, state_config, action, trigger_info, platform_type="device"
)
numeric_state_config = {
numeric_state_trigger.CONF_PLATFORM: "numeric_state",
numeric_state_trigger.CONF_ENTITY_ID: config[CONF_ENTITY_ID],
}
if trigger_type == "current_temperature_changed":
numeric_state_config[
numeric_state_trigger.CONF_VALUE_TEMPLATE
] = "{{ state.attributes.current_temperature }}"
else:
numeric_state_config[
numeric_state_trigger.CONF_VALUE_TEMPLATE
] = "{{ state.attributes.current_humidity }}"
if CONF_ABOVE in config:
numeric_state_config[CONF_ABOVE] = config[CONF_ABOVE]
if CONF_BELOW in config:
numeric_state_config[CONF_BELOW] = config[CONF_BELOW]
if CONF_FOR in config:
numeric_state_config[CONF_FOR] = config[CONF_FOR]
numeric_state_config = await numeric_state_trigger.async_validate_trigger_config(
hass, numeric_state_config
)
return await numeric_state_trigger.async_attach_trigger(
hass, numeric_state_config, action, trigger_info, platform_type="device"
)
async def async_get_trigger_capabilities(
hass: HomeAssistant, config: ConfigType
) -> dict[str, vol.Schema]:
"""List trigger capabilities."""
trigger_type = config[CONF_TYPE]
if trigger_type == "hvac_action_changed":
return {}
if trigger_type == "hvac_mode_changed":
return {
"extra_fields": vol.Schema(
{vol.Optional(CONF_FOR): cv.positive_time_period_dict}
)
}
if trigger_type == "current_temperature_changed":
unit_of_measurement = hass.config.units.temperature_unit
else:
unit_of_measurement = PERCENTAGE
return {
"extra_fields": vol.Schema(
{
vol.Optional(
CONF_ABOVE, description={"suffix": unit_of_measurement}
): vol.Coerce(float),
vol.Optional(
CONF_BELOW, description={"suffix": unit_of_measurement}
): vol.Coerce(float),
vol.Optional(CONF_FOR): cv.positive_time_period_dict,
}
)
}
@@ -0,0 +1,51 @@
"""Diagnostics support for Brother."""
from __future__ import annotations
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from .utils.bridge import load_adapter
from .const import CONF_HEATER, CONF_SENSOR, CONF_SENSOR_WINDOW
async def async_get_config_entry_diagnostics(
hass: HomeAssistant, config_entry: ConfigEntry
) -> dict:
"""Return diagnostics for a config entry."""
trvs = {}
for trv_id in config_entry.data[CONF_HEATER]:
trv = hass.states.get(trv_id["trv"])
if trv is None:
continue
_adapter_name = load_adapter(hass, trv_id["integration"], trv_id["trv"], True)
trv_id["adapter"] = _adapter_name
trvs[trv_id["trv"]] = {
"name": trv.name,
"state": trv.state,
"attributes": trv.attributes,
"bt_config": trv_id["advanced"],
"bt_adapter": trv_id["adapter"],
"bt_integration": trv_id["integration"],
"model": trv_id["model"],
}
external_temperature = hass.states.get(config_entry.data[CONF_SENSOR])
window = "-"
window_entity_id = config_entry.data.get(CONF_SENSOR_WINDOW, False)
if window_entity_id:
try:
window = hass.states.get(window_entity_id)
except KeyError:
pass
_cleaned_data = dict(config_entry.data.copy())
del _cleaned_data[CONF_HEATER]
diagnostics_data = {
"info": _cleaned_data,
"thermostat": trvs,
"external_temperature_sensor": external_temperature,
"window_sensor": window,
}
return diagnostics_data
@@ -0,0 +1,62 @@
import logging
from custom_components.better_thermostat.const import CONF_HOMATICIP
from ..utils.helpers import convert_to_float
from datetime import datetime
from homeassistant.const import STATE_UNAVAILABLE, STATE_UNKNOWN
from homeassistant.core import callback
_LOGGER = logging.getLogger(__name__)
@callback
async def trigger_temperature_change(self, event):
"""Handle temperature changes.
Parameters
----------
self :
self instance of better_thermostat
event :
Event object from the eventbus. Contains the current trigger time.
Returns
-------
None
"""
if self.startup_running:
return
new_state = event.data.get("new_state")
if new_state is None or new_state.state in (STATE_UNAVAILABLE, STATE_UNKNOWN, None):
return
_incoming_temperature = convert_to_float(
str(new_state.state), self.name, "external_temperature"
)
_time_diff = 5
try:
for trv in self.all_trvs:
if trv["advanced"][CONF_HOMATICIP]:
_time_diff = 600
except KeyError:
pass
if (
_incoming_temperature != self.cur_temp
and (datetime.now() - self.last_external_sensor_change).total_seconds()
> _time_diff
):
_LOGGER.debug(
"better_thermostat %s: external_temperature changed from %s to %s",
self.name,
self.cur_temp,
_incoming_temperature,
)
self.cur_temp = _incoming_temperature
self.last_external_sensor_change = datetime.now()
self.async_write_ha_state()
await self.control_queue_task.put(self)
@@ -0,0 +1,396 @@
import asyncio
from datetime import datetime
import logging
from typing import Union
from custom_components.better_thermostat.const import CONF_HOMATICIP
from homeassistant.components.climate.const import (
HVACMode,
ATTR_HVAC_ACTION,
HVACAction,
)
from homeassistant.core import State, callback
from homeassistant.components.group.util import find_state_attributes
from ..utils.helpers import (
calculate_local_setpoint_delta,
calculate_setpoint_override,
convert_to_float,
mode_remap,
round_to_half_degree,
)
from custom_components.better_thermostat.utils.bridge import get_current_offset
_LOGGER = logging.getLogger(__name__)
@callback
async def trigger_trv_change(self, event):
"""Trigger a change in the trv state."""
if self.startup_running:
return
if self.control_queue_task is None:
return
asyncio.create_task(update_hvac_action(self))
_main_change = False
old_state = event.data.get("old_state")
new_state = event.data.get("new_state")
entity_id = event.data.get("entity_id")
if None in (new_state, old_state, new_state.attributes):
_LOGGER.debug(
f"better_thermostat {self.name}: TRV {entity_id} update contained not all necessary data for processing, skipping"
)
return
if not isinstance(new_state, State) or not isinstance(old_state, State):
_LOGGER.debug(
f"better_thermostat {self.name}: TRV {entity_id} update contained not a State, skipping"
)
return
# set context HACK TO FIND OUT IF AN EVENT WAS SEND BY BT
# Check if the update is coming from the code
if self.context == event.context:
return
_LOGGER.debug(f"better_thermostat {self.name}: TRV {entity_id} update received")
_org_trv_state = self.hass.states.get(entity_id)
child_lock = self.real_trvs[entity_id]["advanced"].get("child_lock")
_new_current_temp = convert_to_float(
str(_org_trv_state.attributes.get("current_temperature", None)),
self.name,
"TRV_current_temp",
)
_time_diff = 5
try:
for trv in self.all_trvs:
if trv["advanced"][CONF_HOMATICIP]:
_time_diff = 600
except KeyError:
pass
if (
_new_current_temp is not None
and self.real_trvs[entity_id]["current_temperature"] != _new_current_temp
and (
(datetime.now() - self.last_internal_sensor_change).total_seconds()
> _time_diff
or (
self.real_trvs[entity_id]["calibration_received"] is False
and self.real_trvs[entity_id]["calibration"] == 0
)
)
):
_old_temp = self.real_trvs[entity_id]["current_temperature"]
self.real_trvs[entity_id]["current_temperature"] = _new_current_temp
_LOGGER.debug(
f"better_thermostat {self.name}: TRV {entity_id} sends new internal temperature from {_old_temp} to {_new_current_temp}"
)
self.last_internal_sensor_change = datetime.now()
_main_change = True
# TODO: async def in controlling?
if self.real_trvs[entity_id]["calibration_received"] is False:
self.real_trvs[entity_id]["calibration_received"] = True
_LOGGER.debug(
f"better_thermostat {self.name}: calibration accepted by TRV {entity_id}"
)
_main_change = False
self.old_internal_temp = self.real_trvs[entity_id]["current_temperature"]
self.old_external_temp = self.cur_temp
if self.real_trvs[entity_id]["calibration"] == 0:
self.real_trvs[entity_id][
"last_calibration"
] = await get_current_offset(self, entity_id)
if self.ignore_states:
return
try:
mapped_state = convert_inbound_states(self, entity_id, _org_trv_state)
except TypeError:
_LOGGER.debug(
f"better_thermostat {self.name}: remapping TRV {entity_id} state failed, skipping"
)
return
if mapped_state in (HVACMode.OFF, HVACMode.HEAT):
if (
self.real_trvs[entity_id]["hvac_mode"] != _org_trv_state.state
and not child_lock
):
_old = self.real_trvs[entity_id]["hvac_mode"]
_LOGGER.debug(
f"better_thermostat {self.name}: TRV {entity_id} decoded TRV mode changed from {_old} to {_org_trv_state.state} - converted {new_state.state}"
)
self.real_trvs[entity_id]["hvac_mode"] = _org_trv_state.state
_main_change = True
if (
child_lock is False
and self.real_trvs[entity_id]["system_mode_received"] is True
and self.real_trvs[entity_id]["last_hvac_mode"] != _org_trv_state.state
):
self.bt_hvac_mode = mapped_state
_old_heating_setpoint = convert_to_float(
str(old_state.attributes.get("temperature", None)),
self.name,
"trigger_trv_change()",
)
_new_heating_setpoint = convert_to_float(
str(new_state.attributes.get("temperature", None)),
self.name,
"trigger_trv_change()",
)
if (
_new_heating_setpoint is not None
and _old_heating_setpoint is not None
and self.bt_hvac_mode is not HVACMode.OFF
):
_LOGGER.debug(
f"better_thermostat {self.name}: trigger_trv_change / _old_heating_setpoint: {_old_heating_setpoint} - _new_heating_setpoint: {_new_heating_setpoint} - _last_temperature: {self.real_trvs[entity_id]['last_temperature']}"
)
if (
_new_heating_setpoint < self.bt_min_temp
or self.bt_max_temp < _new_heating_setpoint
):
_LOGGER.warning(
f"better_thermostat {self.name}: New TRV {entity_id} setpoint outside of range, overwriting it"
)
if _new_heating_setpoint < self.bt_min_temp:
_new_heating_setpoint = self.bt_min_temp
else:
_new_heating_setpoint = self.bt_max_temp
if (
self.bt_target_temp != _new_heating_setpoint
and _old_heating_setpoint != _new_heating_setpoint
and self.real_trvs[entity_id]["last_temperature"] != _new_heating_setpoint
and not child_lock
and self.real_trvs[entity_id]["target_temp_received"] is True
and self.real_trvs[entity_id]["system_mode_received"] is True
and self.real_trvs[entity_id]["hvac_mode"] is not HVACMode.OFF
and self.window_open is False
):
_LOGGER.debug(
f"better_thermostat {self.name}: TRV {entity_id} decoded TRV target temp changed from {self.bt_target_temp} to {_new_heating_setpoint}"
)
self.bt_target_temp = _new_heating_setpoint
_main_change = True
if self.real_trvs[entity_id]["advanced"].get("no_off_system_mode", False):
if _new_heating_setpoint == self.real_trvs[entity_id]["min_temp"]:
self.bt_hvac_mode = HVACMode.OFF
else:
self.bt_hvac_mode = HVACMode.HEAT
_main_change = True
if _main_change is True:
self.async_write_ha_state()
return await self.control_queue_task.put(self)
self.async_write_ha_state()
return
async def update_hvac_action(self):
"""Update the hvac action."""
if self.startup_running or self.control_queue_task is None:
return
hvac_action = None
# i don't know why this is here just for hometicip / wtom - 2023-08-23
# for trv in self.all_trvs:
# if trv["advanced"][CONF_HOMATICIP]:
# entity_id = trv["trv"]
# state = self.hass.states.get(entity_id)
# if state is None:
# continue
# if state.attributes.get(ATTR_HVAC_ACTION) == HVACAction.HEATING:
# hvac_action = HVACAction.HEATING
# break
# elif state.attributes.get(ATTR_HVAC_ACTION) == HVACAction.IDLE:
# hvac_action = HVACAction.IDLE
# return the most common action if it is not off
states = [
state
for entity_id in self.real_trvs
if (state := self.hass.states.get(entity_id)) is not None
]
hvac_actions = list(find_state_attributes(states, ATTR_HVAC_ACTION))
if not hvac_actions:
self.attr_hvac_action = None
# return action off if all are off
elif all(a == HVACAction.OFF for a in hvac_actions):
hvac_action = HVACAction.OFF
# else check if is heating
elif (
self.bt_target_temp > self.cur_temp + self.tolerance
and self.window_open is False
):
hvac_action = HVACAction.HEATING
elif (
self.bt_target_temp > self.cur_temp
and self.attr_hvac_action == HVACAction.HEATING
and self.window_open is False
):
hvac_action = HVACAction.HEATING
else:
hvac_action = HVACAction.IDLE
if self.hvac_action != hvac_action:
self.attr_hvac_action = hvac_action
await self.async_update_ha_state(force_refresh=True)
def convert_inbound_states(self, entity_id, state: State) -> str:
"""Convert hvac mode in a thermostat state from HA
Parameters
----------
self :
self instance of better_thermostat
state : State
Inbound thermostat state, which will be modified
Returns
-------
Modified state
"""
if state is None:
raise TypeError("convert_inbound_states() received None state, cannot convert")
if state.attributes is None or state.state is None:
raise TypeError("convert_inbound_states() received None state, cannot convert")
remapped_state = mode_remap(self, entity_id, str(state.state), True)
if remapped_state not in (HVACMode.OFF, HVACMode.HEAT):
return None
return remapped_state
def convert_outbound_states(self, entity_id, hvac_mode) -> Union[dict, None]:
"""Creates the new outbound thermostat state.
Parameters
----------
self :
self instance of better_thermostat
hvac_mode :
the HA mode to convert to
Returns
-------
dict
A dictionary containing the new outbound thermostat state containing the following keys:
temperature: float
local_temperature: float
local_temperature_calibration: float
system_mode: string
None
In case of an error.
"""
_new_local_calibration = None
_new_heating_setpoint = None
try:
_calibration_type = self.real_trvs[entity_id].get("calibration", 1)
if _calibration_type is None:
_LOGGER.warning(
"better_thermostat %s: no calibration type found in device config, talking to the TRV using fallback mode",
self.name,
)
_new_heating_setpoint = self.bt_target_temp
_new_local_calibration = round_to_half_degree(
calculate_local_setpoint_delta(self, entity_id)
)
if _new_local_calibration is None:
return None
else:
if _calibration_type == 0:
_round_calibration = self.real_trvs[entity_id]["advanced"].get(
"calibration_round"
)
if _round_calibration is not None and (
(
isinstance(_round_calibration, str)
and _round_calibration.lower() == "true"
)
or _round_calibration is True
):
_new_local_calibration = round_to_half_degree(
calculate_local_setpoint_delta(self, entity_id)
)
else:
_new_local_calibration = calculate_local_setpoint_delta(
self, entity_id
)
_new_heating_setpoint = self.bt_target_temp
elif _calibration_type == 1:
_round_calibration = self.real_trvs[entity_id]["advanced"].get(
"calibration_round"
)
if _round_calibration is not None and (
(
isinstance(_round_calibration, str)
and _round_calibration.lower() == "true"
)
or _round_calibration is True
):
_new_heating_setpoint = round_to_half_degree(
calculate_setpoint_override(self, entity_id)
)
else:
_new_heating_setpoint = calculate_setpoint_override(self, entity_id)
_system_modes = self.real_trvs[entity_id]["hvac_modes"]
_has_system_mode = False
if _system_modes is not None:
_has_system_mode = True
# Handling different devices with or without system mode reported or contained in the device config
hvac_mode = mode_remap(self, entity_id, str(hvac_mode), False)
if _has_system_mode is False:
_LOGGER.debug(
f"better_thermostat {self.name}: device config expects no system mode, while the device has one. Device system mode will be ignored"
)
if hvac_mode == HVACMode.OFF:
_new_heating_setpoint = self.real_trvs[entity_id]["min_temp"]
hvac_mode = None
if (
HVACMode.OFF not in _system_modes
or self.real_trvs[entity_id]["advanced"].get(
"no_off_system_mode", False
)
is True
):
if hvac_mode == HVACMode.OFF:
_LOGGER.debug(
f"better_thermostat {self.name}: sending 5°C to the TRV because this device has no system mode off and heater should be off"
)
_new_heating_setpoint = self.real_trvs[entity_id]["min_temp"]
hvac_mode = None
return {
"temperature": _new_heating_setpoint,
"local_temperature": self.real_trvs[entity_id]["current_temperature"],
"system_mode": hvac_mode,
"local_temperature_calibration": _new_local_calibration,
}
except Exception as e:
_LOGGER.error(e)
return None
@@ -0,0 +1,90 @@
import asyncio
import logging
from homeassistant.core import callback
from homeassistant.const import STATE_OFF
_LOGGER = logging.getLogger(__name__)
@callback
async def trigger_window_change(self, event) -> None:
"""Triggered by window sensor event from HA to check if the window is open.
Parameters
----------
self :
self instance of better_thermostat
event :
Event object from the eventbus. Contains the new and old state from the window (group).
Returns
-------
None
"""
new_state = event.data.get("new_state")
if None in (self.hass.states.get(self.window_id), self.window_id, new_state):
return
new_state = new_state.state
old_window_open = self.window_open
if new_state in ("on", "unknown", "unavailable"):
new_window_open = True
if new_state == "unknown":
_LOGGER.warning(
"better_thermostat %s: Window sensor state is unknown, assuming window is open",
self.name,
)
# window was opened, disable heating power calculation for this period
self.heating_start_temp = None
self.async_write_ha_state()
elif new_state == "off":
new_window_open = False
else:
_LOGGER.error(
f"better_thermostat {self.name}: New window sensor state '{new_state}' not recognized"
)
return
# make sure to skip events which do not change the saved window state:
if new_window_open == old_window_open:
_LOGGER.debug(
f"better_thermostat {self.name}: Window state did not change, skipping event"
)
return
await self.window_queue_task.put(new_window_open)
async def window_queue(self):
while True:
window_event_to_process = await self.window_queue_task.get()
if window_event_to_process is not None:
if window_event_to_process:
# TODO: window open delay
await asyncio.sleep(self.window_delay)
else:
# TODO: window close delay
await asyncio.sleep(self.window_delay)
# remap off on to true false
current_window_state = True
if self.hass.states.get(self.window_id).state == STATE_OFF:
current_window_state = False
# make sure the current state is the suggested change state to prevent a false positive:
if current_window_state == window_event_to_process:
self.window_open = window_event_to_process
self.async_write_ha_state()
if not self.control_queue_task.empty():
empty_queue(self.control_queue_task)
await self.control_queue_task.put(self)
self.window_queue_task.task_done()
def empty_queue(q: asyncio.Queue):
for _ in range(q.qsize()):
q.get_nowait()
q.task_done()
@@ -0,0 +1,20 @@
{
"domain": "better_thermostat",
"name": "Better Thermostat",
"after_dependencies": [
"climate"
],
"codeowners": [
"@kartoffeltoby"
],
"config_flow": true,
"dependencies": [
"climate",
"recorder"
],
"documentation": "https://github.com/KartoffelToby/better_thermostat",
"iot_class": "local_push",
"issue_tracker": "https://github.com/KartoffelToby/better_thermostat/issues",
"requirements": [],
"version": "1.3.0"
}
@@ -0,0 +1,31 @@
"""
This model fix is due to any floating point number being set to +/- 1 million by Zigbee2MQTT for the local_calibration
"""
import math
def fix_local_calibration(self, entity_id, offset):
"""
If still heating, round UP the offset
This creates a lower "fake" thermostat temperature, making it heat the room
"""
if self.cur_temp < self.bt_target_temp:
offset = math.ceil(offset)
else:
offset = math.floor(offset)
return offset
def fix_target_temperature_calibration(self, entity_id, temperature):
return temperature
async def override_set_hvac_mode(self, entity_id, hvac_mode):
return False
async def override_set_temperature(self, entity_id, temperature):
return False
@@ -0,0 +1,29 @@
def fix_local_calibration(self, entity_id, offset):
# device SEA802 fix
if (self.cur_temp - self.bt_target_temp) < -0.2:
offset -= 2.5
return offset
def fix_target_temperature_calibration(self, entity_id, temperature):
# device SEA802 fix
_cur_trv_temp = float(
self.hass.states.get(entity_id).attributes["current_temperature"]
)
if _cur_trv_temp is None:
return temperature
if (
round(temperature, 1) > round(_cur_trv_temp, 1)
and temperature - _cur_trv_temp < 2.5
):
temperature += 2.5
return temperature
async def override_set_hvac_mode(self, entity_id, hvac_mode):
return False
async def override_set_temperature(self, entity_id, temperature):
return False
@@ -0,0 +1,18 @@
def fix_local_calibration(self, entity_id, offset):
if offset > 5:
offset = 5
elif offset < -5:
offset = -5
return offset
def fix_target_temperature_calibration(self, entity_id, temperature):
return temperature
async def override_set_hvac_mode(self, entity_id, hvac_mode):
return False
async def override_set_temperature(self, entity_id, temperature):
return False
@@ -0,0 +1,30 @@
def fix_local_calibration(self, entity_id, offset):
if (self.cur_temp - 0.5) <= self.bt_target_temp:
offset -= 2.5
elif (self.cur_temp + 0.10) >= self.bt_target_temp:
offset = round(offset + 0.5, 1)
return offset
def fix_target_temperature_calibration(self, entity_id, temperature):
_cur_trv_temp = float(
self.hass.states.get(entity_id).attributes["current_temperature"]
)
if _cur_trv_temp is None:
return temperature
if (
round(temperature, 1) > round(_cur_trv_temp, 1)
and temperature - _cur_trv_temp < 1.5
):
temperature += 1.5
return temperature
async def override_set_hvac_mode(self, entity_id, hvac_mode):
return False
async def override_set_temperature(self, entity_id, temperature):
return False
@@ -0,0 +1,30 @@
def fix_local_calibration(self, entity_id, offset):
if (self.cur_temp - 0.5) <= self.bt_target_temp:
offset -= 2.5
elif (self.cur_temp + 0.10) >= self.bt_target_temp:
offset = round(offset + 0.5, 1)
return offset
def fix_target_temperature_calibration(self, entity_id, temperature):
_cur_trv_temp = float(
self.hass.states.get(entity_id).attributes["current_temperature"]
)
if _cur_trv_temp is None:
return temperature
if (
round(temperature, 1) > round(_cur_trv_temp, 1)
and temperature - _cur_trv_temp < 1.5
):
temperature += 1.5
return temperature
async def override_set_hvac_mode(self, entity_id, hvac_mode):
return False
async def override_set_temperature(self, entity_id, temperature):
return False
@@ -0,0 +1,86 @@
# Quirks for TV02-Zigbee
import logging
from homeassistant.components.climate.const import HVACMode
_LOGGER = logging.getLogger(__name__)
def fix_local_calibration(self, entity_id, offset):
return offset
def fix_target_temperature_calibration(self, entity_id, temperature):
return temperature
async def override_set_hvac_mode(self, entity_id, hvac_mode):
"""Enable specific device quirks while setting hvac mode
Parameters
----------
self :
self instance of better_thermostat
entity_id :
Entity id of the TRV.
hvac_mode:
HVAC mode to be set.
Returns
-------
None
"""
await self.hass.services.async_call(
"climate",
"set_hvac_mode",
{"entity_id": entity_id, "hvac_mode": hvac_mode},
blocking=True,
context=self.context,
)
model = self.real_trvs[entity_id]["model"]
if model == "TV02-Zigbee" and hvac_mode != HVACMode.OFF:
_LOGGER.debug(
f"better_thermostat {self.name}: TRV {entity_id} device quirk hvac trv02-zigbee active"
)
await self.hass.services.async_call(
"climate",
"set_preset_mode",
{"entity_id": entity_id, "preset_mode": "manual"},
blocking=True,
context=self.context,
)
return True
async def override_set_temperature(self, entity_id, temperature):
"""Enable specific device quirks while setting temperature
Parameters
----------
self :
self instance of better_thermostat
entity_id :
Entity id of the TRV.
temperature:
Temperature to be set.
Returns
-------
None
"""
model = self.real_trvs[entity_id]["model"]
if model == "TV02-Zigbee":
_LOGGER.debug(
f"better_thermostat {self.name}: TRV {entity_id} device quirk trv02-zigbee active"
)
await self.hass.services.async_call(
"climate",
"set_preset_mode",
{"entity_id": entity_id, "preset_mode": "manual"},
blocking=True,
context=self.context,
)
await self.hass.services.async_call(
"climate",
"set_temperature",
{"entity_id": entity_id, "temperature": temperature},
blocking=True,
context=self.context,
)
return True
@@ -0,0 +1,14 @@
def fix_local_calibration(self, entity_id, offset):
return offset
def fix_target_temperature_calibration(self, entity_id, temperature):
return temperature
async def override_set_hvac_mode(self, entity_id, hvac_mode):
return False
async def override_set_temperature(self, entity_id, temperature):
return False
@@ -0,0 +1,52 @@
save_current_target_temperature:
name: Save current Temperature
description: Save the current target temperature for later restore.
target:
entity:
domain: climate
integration: better_thermostat
device:
integration: better_thermostat
restore_saved_target_temperature:
name: Restore temperature
description: Restore the saved target temperature.
target:
entity:
domain: climate
integration: better_thermostat
device:
integration: better_thermostat
reset_heating_power:
name: Reset heating power
description: Reset heating power to default value.
target:
entity:
domain: climate
integration: better_thermostat
device:
integration: better_thermostat
set_temp_target_temperature:
name: Set eco temperature
description: Set the target temperature to a temporay like night mode, and save the old one.
fields:
temperature:
name: Target temperature
description: The target temperature to set.
example: 18.5
required: true
advanced: true
selector:
number:
min: 0
max: 35
step: 0.5
mode: box
target:
entity:
domain: climate
integration: better_thermostat
device:
integration: better_thermostat
@@ -0,0 +1,126 @@
{
"config": {
"step": {
"user": {
"description": "Setup your Better Thermostat to integrate with Home Assistant\n**If you need more info: https://better-thermostat.org/configuration#first-step** ",
"data": {
"name": "Name",
"thermostat": "The real thermostat",
"temperature_sensor": "Temperature sensor",
"humidity_sensor": "Humidity sensor",
"window_sensors": "Window sensor",
"off_temperature": "The outdoor temperature when the thermostat turn off",
"tolerance": "Tolerance, to prevent the thermostat from turning on and off too often.",
"window_off_delay": "Delay before the thermostat turn off when the window is open and on when the window is closed",
"outdoor_sensor": "If you have an outdoor sensor, you can use it to get the outdoor temperature",
"weather": "Your weather entity to get the outdoor temperature"
}
},
"advanced": {
"description": "Advanced configuration {trv}\n***Info about calibration types: https://better-thermostat.org/configuration#second-step***",
"data": {
"protect_overheating": "Overheating protection?",
"heat_auto_swapped": "If the auto means heat for your TRV and you want to swap it",
"child_lock": "Ignore all inputs on the TRV like a child lock",
"homaticip": "If you use HomaticIP, you should enable this to slow down the requests to prevent the duty cycle",
"valve_maintenance": "If your thermostat has no own maintenance mode, you can use this one",
"calibration": "Calibration Type",
"calibration_mode": "Calibration mode",
"no_off_system_mode": "If your TRV can't handle the off mode, you can enable this to use target temperature 5°C instead"
},
"data_description": {
"protect_overheating": "Some TRVs doest close the valve completly when the temperature is reached. Or the radiator have a lot of rest heat. This can cause overheating. This option can prevent this.",
"calibration_mode": "The kind how the calibration should be calculated\n***Normal***: In this mode the TRV internal temperature sensor is fixed by the external temperature sensor.\n***Aggresive***: In this mode the TRV internal temperature sensor is fixed by the external temperature sensor but set much lower/higher to get a quicker boost.\n***AI Time Based***: In this mode the TRV internal temperature sensor is fixed by the external temperature sensor, but the value is calculated by a custom algorithm to improve the TRV internal algorithm.",
"calibration": "How the calibration should be applied on the TRV (Target temp or offset)\n***Target Temperature Based***: Apply the calibration to the target temperature.\n***Offset Based***: Apply the calibration to the offset."
}
},
"confirm": {
"title": "Confirm adding a Better Thermostat",
"description": "You are about to add `{name}` to Home Assistant.\nWith {trv} as the real Thermostat"
}
},
"error": {
"failed": "something went wrong.",
"no_name": "Please enter a name.",
"no_off_mode": "You device is very special and has no off mode :(\nBetter Thermostat will use the minimal target temp instead.",
"no_outside_temp": "You have no outside temperature sensor. Better Thermostat will use the weather entity instead."
},
"abort": {
"single_instance_allowed": "Only a single Thermostat for each real is allowed.",
"no_devices_found": "No thermostat entity found, make sure you have a climate entity in your home assistant"
}
},
"options": {
"step": {
"user": {
"description": "Update your Better Thermostat settings",
"data": {
"name": "Name",
"thermostat": "The real thermostat",
"temperature_sensor": "Temperature Sensor",
"humidity_sensor": "Humidity sensor",
"window_sensors": "Window Sensor",
"off_temperature": "The outdoor temperature when the thermostat turn off",
"tolerance": "Tolerance, to prevent the thermostat from turning on and off too often.",
"window_off_delay": "Delay before the thermostat turn off when the window is open and on when the window is closed",
"outdoor_sensor": "If you have an outdoor sensor, you can use it to get the outdoor temperature",
"valve_maintenance": "If your thermostat has no own maintenance mode, you can use this one",
"calibration": "The sort of calibration https://better-thermostat.org/configuration#second-step",
"weather": "Your weather entity to get the outdoor temperature",
"heat_auto_swapped": "If the auto means heat for your TRV and you want to swap it",
"child_lock": "Ignore all inputs on the TRV like a child lock",
"homaticip": "If you use HomaticIP, you should enable this to slow down the requests to prevent the duty cycle"
}
},
"advanced": {
"description": "Advanced configuration {trv}\n***Info about calibration types: https://better-thermostat.org/configuration#second-step***",
"data": {
"protect_overheating": "Overheating protection?",
"heat_auto_swapped": "If the auto means heat for your TRV and you want to swap it",
"child_lock": "Ignore all inputs on the TRV like a child lock",
"homaticip": "If you use HomaticIP, you should enable this to slow down the requests to prevent the duty cycle",
"valve_maintenance": "If your thermostat has no own maintenance mode, you can use this one",
"calibration": "The sort of calibration you want to use",
"calibration_mode": "Calibration mode",
"no_off_system_mode": "If your TRV can't handle the off mode, you can enable this to use target temperature 5°C instead"
},
"data_description": {
"protect_overheating": "Some TRVs doest close the valve completly when the temperature is reached. Or the radiator have a lot of rest heat. This can cause overheating. This option can prevent this.",
"calibration_mode": "The kind how the calibration should be calculated\n***Normal***: In this mode the TRV internal temperature sensor is fixed by the external temperature sensor.\n***Aggresive***: In this mode the TRV internal temperature sensor is fixed by the external temperature sensor but set much lower/higher to get a quicker boost.\n***AI Time Based***: In this mode the TRV internal temperature sensor is fixed by the external temperature sensor, but the value is calculated by a custom algorithm to improve the TRV internal algorithm.",
"calibration": "How the calibration should be applied on the TRV (Target temp or offset)\n***Target Temperature Based***: Apply the calibration to the target temperature.\n***Offset Based***: Apply the calibration to the offset."
}
}
}
},
"issues": {
"missing_entity": {
"title": "BT: {name} - related entity is missing",
"fix_flow": {
"step": {
"confirm": {
"title": "The related entity {entity} is missing",
"description": "The reason for this is that the entity ({entity}) is not available in your Home Assistant.\n\nYou can fix this by checking if the batterie of the device is full or reconnect it to HA. Make sure that the entity is back in HA before you continue."
}
}
}
}
},
"services": {
"save_current_target_temperature": {
"name": "Save current Temperature",
"description": "Save the current target temperature for later restore."
},
"restore_saved_target_temperature": {
"name": "Restore temperature",
"description": "Restore the saved target temperature."
},
"reset_heating_power": {
"name": "Reset heating power",
"description": "Reset heating power to default value."
},
"set_temp_target_temperature": {
"name": "Set eco temperature",
"description": "Set the target temperature to a temporay like night mode, and save the old one."
}
}
}
@@ -0,0 +1,107 @@
{
"title": "Better Thermostat",
"config": {
"step": {
"user": {
"description": "Konfigurer din Better Thermostat til at integrere med Home Assistant\n**Hvis du har brug for mere info: https://better-thermostat.org/configuration#first-step**",
"data": {
"name": "Navn",
"thermostat": "Den rigtige termostat",
"temperature_sensor": "Temperatur måler",
"humidity_sensor": "Fugtighedssensor",
"window_sensors": "Vinduessensor",
"off_temperature": "Udetemperaturen, når termostaten slukker",
"window_off_delay": "Forsinkelse, før termostaten slukker, når vinduet er åbent, og tændt, når vinduet er lukket",
"outdoor_sensor": "Hvis du har en udeføler, kan du bruge den til at få udetemperaturen",
"weather": "Din vejrentitet for at få udendørstemperaturen"
}
},
"advanced": {
"description": "Avanceret konfiguration\n\n**Info om kalibreringstyper: https://better-thermostat.org/configuration#second-step** ",
"data": {
"heat_auto_swapped": "Hvis auto betyder varme for din TRV og du vil bytte det",
"child_lock": "Ignorer alle input på TRV som en børnesikring",
"homaticip": "Hvis du bruger HomaticIP, bør du aktivere dette for at bremse anmodningerne for at forhindre arbejdscyklus",
"valve_maintenance": "Hvis din termostat ikke har nogen egen vedligeholdelsestilstand, kan du bruge denne",
"calibration": "Den slags kalibrering du vil bruge",
"no_off_system_mode": "Hvis din TRV ikke kan håndtere den slukkede tilstand, kan du aktivere denne for at bruge måltemperatur 5°C i stedet",
"calibration_mode": "Kalibreringstilstand",
"protect_overheating": "Overophedningsbeskyttelse?"
},
"data_description": {
"protect_overheating": "Nogle TRV'er lukker ikke ventilen helt, når temperaturen er nået. Eller radiatoren har meget hvilevarme. Dette kan forårsage overophedning. Denne mulighed kan forhindre dette.",
"calibration_mode": "Den slags, hvordan kalibreringen skal beregnes\n***Normal***: I denne tilstand er TRV's interne temperaturføler fastgjort af den eksterne temperaturføler.\n***Aggressiv***: I denne tilstand er TRV's interne temperaturføler fikseret af den eksterne temperaturføler, men indstillet meget lavere/højere for at få et hurtigere løft.\n***AI-tidsbaseret***: I denne tilstand er TRV's interne temperatursensor fastsat af den eksterne temperatursensor, men værdien beregnes af en brugerdefineret algoritme for at forbedre TRV's interne algoritme.",
"calibration": "Hvordan kalibreringen skal anvendes på TRV (Target temp or offset)\n***Måltemperaturbaseret***: Anvend kalibreringen til måltemperaturen.\n***Offset-baseret***: Anvend kalibreringen til offset."
}
},
"confirm": {
"title": "Bekræft tilføjelse af en Better Thermostat",
"description": "Du er ved at tilføje {name} til Home Assistant.\nMed {trv} som en rigtige termostat"
}
},
"error": {
"no_outside_temp": "Du har ingen udetemperaturføler. Bedre termostat vil bruge vejret i stedet.",
"failed": "noget gik galt.",
"no_name": "Indtast venligst et navn.",
"no_off_mode": "Din enhed er meget speciel og har ingen slukket tilstand :(\nDet virker alligevel, men du skal oprette en automatisering, der passer til dine specialer baseret på enhedsbegivenhederne"
},
"abort": {
"single_instance_allowed": "Kun en enkelt termostat for hver virkelige er tilladt.",
"no_devices_found": "Der blev ikke fundet nogen termostatenhed, sørg for at have en klimaenhed i home assistant"
}
},
"options": {
"step": {
"user": {
"description": "Opdater dine Better Thermostat indstillinger",
"data": {
"temperature_sensor": "Temperatur måler",
"humidity_sensor": "Fugtighedssensor",
"window_sensors": "Vinduessensor",
"off_temperature": "Udendørstemperaturen, når termostaten skal slukker",
"window_off_delay": "Forsinkelse, før termostaten slukker, når vinduet er åbent, og tændt, når vinduet er lukket",
"outdoor_sensor": "Har du en udendørsføler, kan du bruge den til at få udetemperaturen",
"valve_maintenance": "Hvis din termostat ikke har nogen egen vedligeholdelsestilstand, kan du bruge denne",
"calibration": "Den slags kalibrering https://better-thermostat.org/configuration#second-step",
"weather": "Din vejrentitet for at få udendørstemperaturen",
"heat_auto_swapped": "Hvis auto betyder varme til din TRV, og du ønsker at bytte den",
"child_lock": "Ignorer alle input på TRV som en børnesikring",
"homaticip": "Hvis du bruger HomaticIP, bør du aktivere dette for at bremse anmodningerne for at forhindre arbejdscyklus",
"name": "Navn",
"thermostat": "Den rigtige termostat"
}
},
"advanced": {
"description": "Avanceret konfiguration\n\n**Info om kalibreringstyper: https://better-thermostat.org/configuration#second-step** ",
"data": {
"heat_auto_swapped": "Hvis auto betyder varme for din TRV og du vil bytte det",
"child_lock": "Ignorer alle input på TRV som en børnesikring",
"homaticip": "Hvis du bruger HomaticIP, bør du aktivere dette for at bremse anmodningerne for at forhindre arbejdscyklus",
"valve_maintenance": "Hvis din termostat ikke har nogen egen vedligeholdelsestilstand, kan du bruge denne",
"calibration": "Den slags kalibrering du vil bruge",
"protect_overheating": "Overophedningsbeskyttelse?",
"calibration_mode": "Kalibreringstilstand",
"no_off_system_mode": "Hvis din TRV ikke kan håndtere den slukkede tilstand, kan du aktivere denne for at bruge måltemperatur 5°C i stedet"
},
"data_description": {
"protect_overheating": "Nogle TRV'er lukker ikke ventilen helt, når temperaturen er nået. Eller radiatoren har meget hvilevarme. Dette kan forårsage overophedning. Denne mulighed kan forhindre dette.",
"calibration_mode": "Den slags, hvordan kalibreringen skal beregnes\n***Normal***: I denne tilstand er TRV's interne temperaturføler fikseret af den eksterne temperaturføler.\n***Aggressiv***: I denne tilstand er TRV's interne temperaturføler fikseret af den eksterne temperaturføler, men indstillet meget lavere/højere for at få et hurtigere løft.\n***AI-tidsbaseret***: I denne tilstand er TRV's interne temperatursensor fastsat af den eksterne temperatursensor, men værdien beregnes af en brugerdefineret algoritme for at forbedre TRV's interne algoritme.",
"calibration": "Hvordan kalibreringen skal anvendes på TRV (Target temp or offset)\n***Måltemperaturbaseret***: Anvend kalibreringen til måltemperaturen.\n***Offset-baseret***: Anvend kalibreringen til offset."
}
}
}
},
"issues": {
"missing_entity": {
"title": "BT: {name} - relateret enhed mangler",
"fix_flow": {
"step": {
"confirm": {
"title": "Den relaterede {entity} mangler",
"description": "Årsagen til dette er, at entiteten ({entity}) ikke er tilgængelig i din Home Assistant.\n\nDu kan rette op på dette ved at kontrollere, om enhedens batteri er fuld, eller tilslutte den til HA igen. Sørg for, at entiteten er tilbage i HA, før du fortsætter."
}
}
}
}
}
}
@@ -0,0 +1,127 @@
{
"title": "Better Thermostat",
"config": {
"step": {
"user": {
"description": "Einrichtung von Better Thermostat mit Home Assistant\n**Für mehr Informationen: https://better-thermostat.org/configuration#first-step** ",
"data": {
"name": "Name",
"thermostat": "Das reale Thermostat",
"temperature_sensor": "Externer Temperatursensor",
"humidity_sensor": "Luftfeuchtigkeitssensor",
"window_sensors": "Fenstersensor(en)",
"off_temperature": "Außentemperatur, bei welcher das Thermostat abgeschaltet wird.",
"tolerance": "Toleranz, um zu verhindern, dass das Thermostat zu oft ein- und ausgeschaltet wird.",
"window_off_delay": "Wartezeit, bevor das Thermostat bei geöffnetem Fenster abgeschaltet bzw. bei geschlossendem Fenter angeschaltet wird.",
"outdoor_sensor": "Wenn ein Außentemperaturssensor vorhanden ist, kann dieser anstelle der Wetter-Entität genutzt werden.",
"weather": "Die Wetter-Entität für die Außentemperatur."
}
},
"advanced": {
"description": "Einstellungen für {trv}\n\n***Infos über die Kalibrierungstypen: https://better-thermostat.org/configuration#second-step*** ",
"data": {
"protect_overheating": "Überhitzung verhindern?",
"heat_auto_swapped": "Tauscht die Modi auto und heat, falls diese bei dem realen Thermostat vertauscht sind.",
"child_lock": "Ignoriere alle manuellen Einstellungen am realen Thermostat (Kindersicherung).",
"homaticip": "Wenn du HomaticIP nutzt, solltest du diese Option aktivieren, um die Funk-übertragung zu reduzieren.",
"valve_maintenance": "Soll BT die Wartung des Thermostats übernehmen?",
"calibration": "Kalibrierungstyp",
"calibration_mode": "Kalibrierungsmodus",
"no_off_system_mode": "Wenn das TRV keinen Aus Modus nutzen kann, kann diese Option aktiviert werden, um das TRV stattdessen auf 5°C zu setzen."
},
"data_description": {
"protect_overheating": "Manche TRVs schließen auch nach Erreichen der Temperatur das Ventil nicht vollständig, dies kann zu Überhitzungen führen. Ebenso falls der Radiator viel Restwärme abstrahlt. Diese Option kann dies verhindern.",
"calibration_mode": "Wie die Kalibrierung berechnet wird\n***Normal***: In diesem Modus wird die interne TRV-Temperatur an die des externen Sensors angeglichen.\n\n***Aggresive***: In diesem Modus wird die interne TRV-Temperatur an die des externen Sensors angeglichen allerdings mit größeren Werten, dies ist hilfreich wenn ein Raum schnell aufgeheizt werden soll, oder das TRV träge ist.\n\n***AI Time Based***: In diesem Modus wird ein eigener Algorithmus genutzt anhand des externen Temperatursensors, um die Kalibrierung zu berechnen. Dieser Modus versucht den TRV internen Algorithmus zu optimieren.",
"calibration": "Wie die Kalibrierung auf das TRV angewendet werden soll.\n\n***Target Temperature Based***: Kalibiert das TRV über die Zieltemperatur.\n\n***Offset Based***: Kalibiert das TRV über eine Offset Funktion im TRV selbst. (Empfohlen)"
}
},
"confirm": {
"title": "Bestätige das Hinzufügen eines Better Thermostat",
"description": "Du bist dabei ein Gerät mit dem Namen `{name}` zu Home Assistant hinzuzufügen.\nMit {trv} als reales Thermostat\nund dem Kalibrierungsmodus:"
}
},
"error": {
"no_outside_temp": "Es kann keine Außentemperatur geladen werden",
"failed": "Ups, hier stimmt was nicht.",
"no_name": "Du musst einen Namen vergeben.",
"no_off_mode": "Dein Gerät ist ein Sonderfall, es hat keinen OFF Modus :(\nBetter Thermostat wird stattdessen das TRV auf den Minimalwert setzen."
},
"abort": {
"single_instance_allowed": "Es ist nur ein einzelnes BT je realem Thermostat erlaubt.",
"no_devices_found": "Es konnten keine Climate-Entitäten in Home Assistant gefunden werden. Stelle sicher, dass dein reales Thermostat in Home Assistant vorhanden ist."
}
},
"options": {
"step": {
"user": {
"description": "Aktualisiere die Better Thermostat Einstellungen",
"data": {
"name": "Name",
"thermostat": "Das reale Thermostat",
"temperature_sensor": "Externer Temperatursensor",
"humidity_sensor": "Luftfeuchtigkeitssensor",
"window_sensors": "Fenstersensor(en)",
"off_temperature": "Außentemperatur, bei welcher das Thermostat abgeschaltet wird.",
"tolerance": "Toleranz, um zu verhindern, dass das Thermostat zu oft ein- und ausgeschaltet wird.",
"window_off_delay": "Wartezeit, bevor das Thermostat bei geöffnetem Fenster abgeschaltet bzw. bei geschlossenem Fenter angeschaltet wird.",
"outdoor_sensor": "Wenn ein Außentemperatursensor vorhanden ist, kann dieser anstelle der Wetter-Entität genutzt werden.",
"weather": "Die Wetter-Entität für die Außentemperatur.",
"valve_maintenance": "Wenn Ihr Thermostat keinen eigenen Wartungsmodus hat, können Sie diesen verwenden",
"child_lock": "Ignorieren Sie alle Eingaben am TRV wie eine Kindersicherung",
"homaticip": "Wenn Sie HomaticIP verwenden, sollten Sie dies aktivieren, um die Anfragen zu verlangsamen und den Duty Cycle zu verhindern",
"heat_auto_swapped": "Wenn das Auto Wärme für Ihr TRV bedeutet und Sie es austauschen möchten",
"calibration": "Die Art der Kalibrierung https://better-thermostat.org/configuration#second-step"
}
},
"advanced": {
"description": "Aktuallisere die Einstellungen für {trv}\n\n***Infos über die Kalibrierungstypen: https://better-thermostat.org/configuration#second-step*** ",
"data": {
"protect_overheating": "Überhitzung verhindern?",
"heat_auto_swapped": "Tauscht die Modi auto und heat, falls diese bei dem realen Thermostat vertauscht sind.",
"child_lock": "Ignoriere alle manuellen Einstellungen am realen Thermostat (Kindersicherung).",
"homaticip": "Wenn du HomaticIP nutzt, solltest du diese Option aktivieren, um die Funk-übertragung zu reduzieren.",
"valve_maintenance": "Soll BT die Wartung des Thermostats übernehmen?",
"calibration": "Kalibrierungstyp",
"calibration_mode": "Kalibrierungsmodus",
"no_off_system_mode": "Wenn das TRV keinen Aus Modus nutzen kann, kann diese Option aktiviert werden, um das TRV stattdessen auf 5°C zu setzen."
},
"data_description": {
"protect_overheating": "Manche TRVs schließen auch nach Erreichen der Temperatur das Ventil nicht vollständig, dies kann zu Überhitzungen führen. Ebenso falls der Radiator viel Restwärme abstrahlt. Diese Option kann dies verhindern.",
"calibration_mode": "Wie die Kalibrierung berechnet wird\n***Normal***: In diesem Modus wird die interne TRV-Temperatur an die des externen Sensors angeglichen.\n\n***Aggresive***: In diesem Modus wird die interne TRV-Temperatur an die des externen Sensors angeglichen allerdings mit größeren Werten, dies ist hilfreich wenn ein Raum schnell aufgeheizt werden soll, oder das TRV träge ist.\n\n***AI Time Based***: In diesem Modus wird ein eigener Algorithmus genutzt anhand des externen Temperatursensors, um die Kalibrierung zu berechnen. Dieser Modus versucht den TRV internen Algorithmus zu optimieren.",
"calibration": "Wie die Kalibrierung auf das TRV angewendet werden soll.\n\n***Target Temperature Based***: Kalibiert das TRV über die Zieltemperatur.\n\n***Offset Based***: Kalibiert das TRV über eine Offset Funktion im TRV selbst. (Empfohlen)"
}
}
}
},
"issues": {
"missing_entity": {
"title": "BT: {name} zugehörige Entität fehlt",
"fix_flow": {
"step": {
"confirm": {
"title": "Die zugehörige Entität {entity} fehlt",
"description": "Der Grund dafür ist, dass die Entität ({entity}) in Ihrem Home Assistant nicht verfügbar ist.\n\nSie können dies beheben, indem Sie prüfen, ob der Akku des Geräts voll ist, oder es erneut an HA anschließen. Stellen Sie sicher, dass sich die Entität wieder in HA befindet, bevor Sie fortfahren."
}
}
}
}
},
"services": {
"save_current_target_temperature": {
"name": "Speichere die aktuelle Zieltemperatur",
"description": "Speichert die aktuelle Zieltemperatur um sie zu einem späteren Zeitpunkt wiederherzustellen."
},
"restore_saved_target_temperature": {
"name": "Stelle die Zieltemperatur wieder her",
"description": "Stellt die Zieltemperatur aus dem letzen gespeicherten wert wieder zurück."
},
"reset_heating_power": {
"name": "Setze 'Heating Power' zurück",
"description": "Stellt die Heating Power auf den startwert zurück."
},
"set_temp_target_temperature": {
"name": "Setzte die Temperatur für ECO",
"description": "Speichert eine ECO Temperatur, die für den ECO Modus genutzt wird."
}
}
}
@@ -0,0 +1,127 @@
{
"title": "Better Thermostat",
"config": {
"step": {
"user": {
"description": "Setup your Better Thermostat to integrate with Home Assistant\n**If you need more info: https://better-thermostat.org/configuration#first-step** ",
"data": {
"name": "Name",
"thermostat": "The real thermostat",
"temperature_sensor": "Temperature sensor",
"humidity_sensor": "Humidity sensor",
"window_sensors": "Window sensor",
"off_temperature": "The outdoor temperature when the thermostat turn off",
"tolerance": "Tolerance, to prevent the thermostat from turning on and off too often.",
"window_off_delay": "Delay before the thermostat turn off when the window is open and on when the window is closed",
"outdoor_sensor": "If you have an outdoor sensor, you can use it to get the outdoor temperature",
"weather": "Your weather entity to get the outdoor temperature"
}
},
"advanced": {
"description": "Advanced configuration {trv}\n***Info about calibration types: https://better-thermostat.org/configuration#second-step***",
"data": {
"protect_overheating": "Overheating protection?",
"heat_auto_swapped": "If the auto means heat for your TRV and you want to swap it",
"child_lock": "Ignore all inputs on the TRV like a child lock",
"homaticip": "If you use HomaticIP, you should enable this to slow down the requests to prevent the duty cycle",
"valve_maintenance": "If your thermostat has no own maintenance mode, you can use this one",
"calibration": "Calibration Type",
"calibration_mode": "Calibration mode",
"no_off_system_mode": "If your TRV can't handle the off mode, you can enable this to use target temperature 5°C instead"
},
"data_description": {
"protect_overheating": "Some TRVs doest close the valve completly when the temperature is reached. Or the radiator have a lot of rest heat. This can cause overheating. This option can prevent this.",
"calibration_mode": "The kind how the calibration should be calculated\n***Normal***: In this mode the TRV internal temperature sensor is fixed by the external temperature sensor.\n***Aggresive***: In this mode the TRV internal temperature sensor is fixed by the external temperature sensor but set much lower/higher to get a quicker boost.\n***AI Time Based***: In this mode the TRV internal temperature sensor is fixed by the external temperature sensor, but the value is calculated by a custom algorithm to improve the TRV internal algorithm.",
"calibration": "How the calibration should be applied on the TRV (Target temp or offset)\n***Target Temperature Based***: Apply the calibration to the target temperature.\n***Offset Based***: Apply the calibration to the offset."
}
},
"confirm": {
"title": "Confirm adding a Better Thermostat",
"description": "You are about to add `{name}` to Home Assistant.\nWith {trv} as the real Thermostat"
}
},
"error": {
"failed": "something went wrong.",
"no_name": "Please enter a name.",
"no_off_mode": "You device is very special and has no off mode :(\nBetter Thermostat will use the minimal target temp instead.",
"no_outside_temp": "You have no outside temperature sensor. Better Thermostat will use the weather entity instead."
},
"abort": {
"single_instance_allowed": "Only a single Thermostat for each real is allowed.",
"no_devices_found": "No thermostat entity found, make sure you have a climate entity in your home assistant"
}
},
"options": {
"step": {
"user": {
"description": "Update your Better Thermostat settings",
"data": {
"name": "Name",
"thermostat": "The real thermostat",
"temperature_sensor": "Temperature Sensor",
"humidity_sensor": "Humidity sensor",
"window_sensors": "Window Sensor",
"off_temperature": "The outdoor temperature when the thermostat turn off",
"tolerance": "Tolerance, to prevent the thermostat from turning on and off too often.",
"window_off_delay": "Delay before the thermostat turn off when the window is open and on when the window is closed",
"outdoor_sensor": "If you have an outdoor sensor, you can use it to get the outdoor temperature",
"valve_maintenance": "If your thermostat has no own maintenance mode, you can use this one",
"calibration": "The sort of calibration https://better-thermostat.org/configuration#second-step",
"weather": "Your weather entity to get the outdoor temperature",
"heat_auto_swapped": "If the auto means heat for your TRV and you want to swap it",
"child_lock": "Ignore all inputs on the TRV like a child lock",
"homaticip": "If you use HomaticIP, you should enable this to slow down the requests to prevent the duty cycle"
}
},
"advanced": {
"description": "Advanced configuration {trv}\n***Info about calibration types: https://better-thermostat.org/configuration#second-step***",
"data": {
"protect_overheating": "Overheating protection?",
"heat_auto_swapped": "If the auto means heat for your TRV and you want to swap it",
"child_lock": "Ignore all inputs on the TRV like a child lock",
"homaticip": "If you use HomaticIP, you should enable this to slow down the requests to prevent the duty cycle",
"valve_maintenance": "If your thermostat has no own maintenance mode, you can use this one",
"calibration": "The sort of calibration you want to use",
"calibration_mode": "Calibration mode",
"no_off_system_mode": "If your TRV can't handle the off mode, you can enable this to use target temperature 5°C instead"
},
"data_description": {
"protect_overheating": "Some TRVs doest close the valve completly when the temperature is reached. Or the radiator have a lot of rest heat. This can cause overheating. This option can prevent this.",
"calibration_mode": "The kind how the calibration should be calculated\n***Normal***: In this mode the TRV internal temperature sensor is fixed by the external temperature sensor.\n***Aggresive***: In this mode the TRV internal temperature sensor is fixed by the external temperature sensor but set much lower/higher to get a quicker boost.\n***AI Time Based***: In this mode the TRV internal temperature sensor is fixed by the external temperature sensor, but the value is calculated by a custom algorithm to improve the TRV internal algorithm.",
"calibration": "How the calibration should be applied on the TRV (Target temp or offset)\n***Target Temperature Based***: Apply the calibration to the target temperature.\n***Offset Based***: Apply the calibration to the offset."
}
}
}
},
"issues": {
"missing_entity": {
"title": "BT: {name} - related entity is missing",
"fix_flow": {
"step": {
"confirm": {
"title": "The related entity {entity} is missing",
"description": "The reason for this is that the entity ({entity}) is not available in your Home Assistant.\n\nYou can fix this by checking if the batterie of the device is full or reconnect it to HA. Make sure that the entity is back in HA before you continue."
}
}
}
}
},
"services": {
"save_current_target_temperature": {
"name": "Save current Temperature",
"description": "Save the current target temperature for later restore."
},
"restore_saved_target_temperature": {
"name": "Restore temperature",
"description": "Restore the saved target temperature."
},
"reset_heating_power": {
"name": "Reset heating power",
"description": "Reset heating power to default value."
},
"set_temp_target_temperature": {
"name": "Set eco temperature",
"description": "Set the target temperature to a temporay like night mode, and save the old one."
}
}
}
@@ -0,0 +1,107 @@
{
"title": "Better Thermostat",
"config": {
"step": {
"user": {
"description": "Configuration de Better Thermostat pour l'intégrer à Home Assistant\n**If you need more info: https://better-thermostat.org/configuration#first-step** ",
"data": {
"name": "Nom",
"thermostat": "Le véritable thermostat",
"temperature_sensor": "Capteur de température",
"humidity_sensor": "Capteur d'humidité",
"window_sensors": "Capteur de fenêtre",
"off_temperature": "La température extérieure lorsque le thermostat s'éteint",
"window_off_delay": "Délai avant que le thermostat ne s'éteigne lorsque la fenêtre est ouverte et ne s'allume lorsque la fenêtre est fermée",
"outdoor_sensor": "Si vous avez un capteur extérieur, vous pouvez l'utiliser pour obtenir la température extérieure",
"weather": "Votre entité météo pour obtenir la température extérieure"
}
},
"advanced": {
"description": "Configuration avancée\n\n**Informations sur les types d'étalonnage : https://better-thermostat.org/configuration#second-step**",
"data": {
"heat_auto_swapped": "Si auto signifie chauffer pour votre TRV et que vous voulez l'inverser (pour les utilisateurs de Google Home)",
"child_lock": "Ignorer toutes les entrées sur le TRV comme un vérouillage enfant",
"homaticip": "Si vous utilisez HomaticIP, vous devez l'activer pour ralentir les requêtes afin d'éviter le cycle de service",
"valve_maintenance": "Si votre thermostat n'a pas de mode de maintenance intégré, vous pouvez utiliser celui-ci",
"calibration": "The sort of calibration",
"no_off_system_mode": "Si votre TRV ne peut pas gérer le mode arrêt, vous pouvez l'activer pour utiliser la température cible de 5 °C à la place",
"calibration_mode": "Mode d'étalonnage",
"protect_overheating": "Protection contre la surchauffe ?"
},
"data_description": {
"protect_overheating": "Certaines VTR ferment complètement la vanne lorsque la température est atteinte. Ou le radiateur a beaucoup de chaleur résiduelle. Cela peut provoquer une surchauffe. Cette option peut empêcher cela.",
"calibration_mode": "Le type de calcul de l'étalonnage\n***Normal*** : Dans ce mode, le capteur de température interne TRV est fixé par le capteur de température externe.\n***Agressif*** : dans ce mode, le capteur de température interne TRV est fixé par le capteur de température externe mais réglé beaucoup plus bas/plus haut pour obtenir une accélération plus rapide.\n*** AI Time Based *** : Dans ce mode, le capteur de température interne TRV est fixé par le capteur de température externe, mais la valeur est calculée par un algorithme personnalisé pour améliorer l'algorithme interne TRV.",
"calibration": "Comment l'étalonnage doit être appliqué sur la VTR (température cible ou décalage)\n***Basé sur la température cible*** : Appliquez l'étalonnage à la température cible.\n***Basé sur le décalage*** : Appliquez l'étalonnage au décalage."
}
},
"confirm": {
"title": "Confirmer l'ajout d'un meilleur thermostat",
"description": "Vous êtes sur le point d'ajouter \"{name}\" à Home Assistant.\nAvec {trv} comme vrai thermostat"
}
},
"error": {
"no_name": "Veuillez entrer un nom.",
"failed": "quelque chose s'est mal passé.",
"no_outside_temp": "Vous devez définir un capteur de température extérieure ou une entité météorologique.",
"no_off_mode": "You device is very special and has no off mode :(\nIt work anyway, but you have to create a automation to fit your specials based on the device events"
},
"abort": {
"single_instance_allowed": "Un seul et unique thermostat est autorisé pour chaque thermostat réel",
"no_devices_found": "Aucune entité thermostat n'a été trouvée, assurez-vous d'avoir une entité climat dans votre home assistant"
}
},
"options": {
"step": {
"user": {
"description": "Mettez à jour vos paramètres de Better Thermostat",
"data": {
"temperature_sensor": "Capteur de température",
"humidity_sensor": "Capteur d'humidité",
"window_sensors": "Capteur de fenêtre",
"off_temperature": "La température extérieure lorsque le thermostat s'éteint",
"window_off_delay": "Délai avant que le thermostat ne s'éteigne lorsque la fenêtre est ouverte et ne s'allume lorsque la fenêtre est fermée",
"outdoor_sensor": "Si vous avez un capteur extérieur, vous pouvez l'utiliser pour obtenir la température extérieure",
"valve_maintenance": "Si votre thermostat n'a pas de mode de maintenance intégré, vous pouvez utiliser celui-ci",
"calibration": "Le type d'étalonnage https://better-thermostat.org/configuration#second-step",
"weather": "Votre entité météo pour obtenir la température extérieure",
"heat_auto_swapped": "Si auto signifie chauffer pour votre TRV et que vous voulez l'inverser",
"child_lock": "Ignorer toutes les entrées sur le TRV comme un vérouillage enfant",
"homaticip": "Si vous utilisez HomaticIP, vous devez l'activer pour ralentir les requêtes afin d'éviter le cycle de service",
"name": "Nom",
"thermostat": "Le vrai thermostat"
}
},
"advanced": {
"description": "Configuration avancée {trv}\n\n***Informations sur les types d'étalonnage : https://better-thermostat.org/configuration#second-step***",
"data": {
"heat_auto_swapped": "Si auto signifie chauffer pour votre TRV et que vous voulez l'inverser",
"child_lock": "Ignorer toutes les entrées sur le TRV comme un vérouillage enfant",
"homaticip": "Si vous utilisez HomaticIP, vous devez l'activer pour ralentir les requêtes afin d'éviter le cycle de service",
"valve_maintenance": "Si votre thermostat n'a pas de mode de maintenance intégré, vous pouvez utiliser celui-ci",
"calibration": "The sort of calibration",
"protect_overheating": "Protection contre la surchauffe ?",
"calibration_mode": "Mode d'étalonnage",
"no_off_system_mode": "Si votre TRV ne peut pas gérer le mode arrêt, vous pouvez l'activer pour utiliser la température cible de 5 °C à la place"
},
"data_description": {
"protect_overheating": "Certaines VTR ferment complètement la vanne lorsque la température est atteinte. Ou le radiateur a beaucoup de chaleur résiduelle. Cela peut provoquer une surchauffe. Cette option peut empêcher cela.",
"calibration_mode": "Le type de calcul de l'étalonnage\n***Normal*** : Dans ce mode, le capteur de température interne TRV est fixé par le capteur de température externe.\n***Agressif*** : dans ce mode, le capteur de température interne TRV est fixé par le capteur de température externe mais réglé beaucoup plus bas/plus haut pour obtenir une accélération plus rapide.\n*** AI Time Based *** : Dans ce mode, le capteur de température interne TRV est fixé par le capteur de température externe, mais la valeur est calculée par un algorithme personnalisé pour améliorer l'algorithme interne TRV.",
"calibration": "Comment l'étalonnage doit être appliqué sur la VTR (température cible ou décalage)\n***Basé sur la température cible*** : Appliquez l'étalonnage à la température cible.\n***Basé sur le décalage*** : Appliquez l'étalonnage au décalage."
}
}
}
},
"issues": {
"missing_entity": {
"title": "BT : {name} - l'entité associée est manquante",
"fix_flow": {
"step": {
"confirm": {
"title": "L'entité associée est {entity} manquante",
"description": "La raison en est que l'entité ({entity}) n'est pas disponible dans votre Home Assistant.\n\nVous pouvez résoudre ce problème en vérifiant si la batterie de l'appareil est pleine ou en le reconnectant à HA. Assurez-vous que l'entité est de retour dans HA avant de continuer."
}
}
}
}
}
}
@@ -0,0 +1,107 @@
{
"title": "Better Thermostat",
"config": {
"step": {
"user": {
"description": "Skonfiguruj swój Better Thermostat do integracji z Home Assistant\n**Więcej informacji znajdziesz na: https://better-thermostat.org/configuration#first-step** ",
"data": {
"name": "Nazwa",
"thermostat": "Twój termostat",
"temperature_sensor": "Sensor temperatury",
"humidity_sensor": "Sensor wilgotności",
"window_sensors": "Sensor okna",
"off_temperature": "Temperatura zewnętrzna, przy której termostat ma się wyłączyć",
"window_off_delay": "Opóźnienie wyłączenia termostatu, kiedy otworzysz okno lub je zamkniesz (w sekundach)",
"outdoor_sensor": "Jeśli masz czujnik zewnętrzny, możesz go użyć, aby uzyskać temperaturę zewnętrzną",
"weather": "Twoja jednostka pogodowa, aby uzyskać temperaturę zewnętrzną"
}
},
"advanced": {
"description": "Zaawansowana konfiguracja\n\n**Informacja o typach kalibracji: https://better-thermostat.org/configuration#second-step** ",
"data": {
"heat_auto_swapped": "Jeżeli tryb auto oznacza grzanie dla Twojego TRV i chcesz to zmienić",
"child_lock": "Ignoruj wszystkie wejścia w TRV jak np. Blokada dziecięca",
"homaticip": "Jeżeli używasz HomaticIP, powinieneś włączyć tę opcję, żeby spowolnić żądania cyklu pracy",
"valve_maintenance": "Jeżeli Twój termostat nie ma trybu konserwacji, możesz użyć tej opcji.",
"calibration": "Rodzaj kalibracji, której chcesz użyć",
"no_off_system_mode": "Jeśli Twój TRV nie obsługuje trybu wyłączenia, możesz go włączyć, aby zamiast tego używać temperatury docelowej 5°C",
"calibration_mode": "Tryb kalibracji",
"protect_overheating": "Zabezpieczenie przed przegrzaniem?"
},
"data_description": {
"protect_overheating": "Niektóre TRV całkowicie zamykają zawór po osiągnięciu temperatury. Lub grzejnik ma dużo ciepła resztkowego. Może to spowodować przegrzanie. Ta opcja może temu zapobiec.",
"calibration_mode": "Rodzaj sposobu obliczania kalibracji\n***Normalny***: W tym trybie wewnętrzny czujnik temperatury TRV jest zależny od zewnętrznego czujnika temperatury.\n***Agresywny***: W tym trybie wewnętrzny czujnik temperatury TRV jest ustalany przez zewnętrzny czujnik temperatury, ale jest ustawiony znacznie niżej/wyżej, aby uzyskać szybsze przyspieszenie.\n***AI Time Based***: W tym trybie wewnętrzny czujnik temperatury TRV jest ustalany przez zewnętrzny czujnik temperatury, ale wartość jest obliczana przez niestandardowy algorytm w celu ulepszenia wewnętrznego algorytmu TRV.",
"calibration": "Jak kalibracja powinna być zastosowana w TRV (temperatura docelowa lub przesunięcie)\n***Target Temperature Based***: Zastosuj kalibrację do temperatury docelowej.\n***Oparte na przesunięciu***: Zastosuj kalibrację do przesunięcia."
}
},
"confirm": {
"title": "Potwierdź dodanie Better Thermostat",
"description": "Zamierzasz dodać `{name}` do Home Assistant.\nUżywając {trv} jako termostatu"
}
},
"error": {
"no_outside_temp": "Nie masz czujnika temperatury zewnętrznej. Lepszy termostat zamiast tego użyje jednostki pogodowej.",
"failed": "coś poszło nie tak.",
"no_name": "Proszę podać nazwę.",
"no_off_mode": "Twoje urządzenie jest inne i nie ma trybu wyłączenia :(\nTo będzie działać, ale musisz stworzyć automatyzację, aby ustawić funkcje pod swoje urządzenie"
},
"abort": {
"single_instance_allowed": "Dozwolony jest tylko jeden termostat dla jednego urządzenia.",
"no_devices_found": "Nie znaleziono jednostki termostatu, upewnij się, że masz jednostkę klimatyczną w swoim asystencie domowym."
}
},
"options": {
"step": {
"user": {
"description": "Zaktualizuj ustawienia Better Thermostat",
"data": {
"temperature_sensor": "Sensor temperatury",
"humidity_sensor": "Sensor wilgotności",
"window_sensors": "Sensor okna",
"off_temperature": "Temperatura zewnętrzna, przy której termostat ma się wyłączyć",
"window_off_delay": "Opóźnienie wyłączenia termostatu, kiedy otworzysz okno lub je zamkniesz (w sekundach)",
"outdoor_sensor": "Jeśli masz czujnik zewnętrzny, możesz go użyć, aby uzyskać temperaturę zewnętrzną",
"valve_maintenance": "Jeżeli Twój termostat nie ma własnego trybu konserwacji, możesz użyć tej opcji.",
"calibration": "Rodzaje kalibracji https://better-thermostat.org/configuration#second-step",
"weather": "Twoja jednostka pogodowa, aby uzyskać temperaturę zewnętrzną",
"heat_auto_swapped": "Jeżeli tryb auto oznacza grzanie dla Twojego TRV i chcesz to zmienić",
"child_lock": "Ignoruj wszystkie wejścia w TRV jak np. Blokada dziecięca",
"homaticip": "Jeżeli używasz HomaticIP, powinienieś włączyć tę opcję żeby spowolnić żądania",
"name": "Nazwa",
"thermostat": "Prawdziwy termostat"
}
},
"advanced": {
"description": "Zaawansowana konfiguracja**Informacja o typach kalibracji: https://better-thermostat.org/configuration#second-step** ",
"data": {
"heat_auto_swapped": "Jeżeli tryb auto oznacza grzanie dla Twojego TRV i chcesz to zmienić",
"child_lock": "Ignoruj wszystkie wejścia w TRV jak np. Blokada dziecięca",
"homaticip": "Jeżeli używasz HomaticIP, powinieneś włączyć tę opcję, żeby spowolnić żądania spowolnienia cyklu pracy",
"valve_maintenance": "Jeżeli Twój termostat nie ma trybu konserwacji, możesz użyć tej opcji.",
"calibration": "Rodzaj kalibracji, której chcesz użyć",
"protect_overheating": "Zabezpieczenie przed przegrzaniem?",
"calibration_mode": "Tryb kalibracji",
"no_off_system_mode": "Jeśli Twój TRV nie obsługuje trybu wyłączenia, możesz go włączyć, aby zamiast tego używać temperatury docelowej 5°C"
},
"data_description": {
"protect_overheating": "Niektóre TRV całkowicie zamykają zawór po osiągnięciu temperatury. Lub grzejnik ma dużo ciepła resztkowego. Może to spowodować przegrzanie. Ta opcja może temu zapobiec.",
"calibration_mode": "Rodzaj sposobu obliczania kalibracji\n***Normalny***: W tym trybie wewnętrzny czujnik temperatury TRV jest zależny od zewnętrznego czujnika temperatury.\n***Agresywny***: W tym trybie wewnętrzny czujnik temperatury TRV jest ustalany przez zewnętrzny czujnik temperatury, ale jest ustawiony znacznie niżej/wyżej, aby uzyskać szybsze przyspieszenie.\n***AI Time Based***: W tym trybie wewnętrzny czujnik temperatury TRV jest ustalany przez zewnętrzny czujnik temperatury, ale wartość jest obliczana przez niestandardowy algorytm w celu ulepszenia wewnętrznego algorytmu TRV.",
"calibration": "Jak kalibracja powinna być zastosowana w TRV (temperatura docelowa lub przesunięcie)\n***Target Temperature Based***: Zastosuj kalibrację do temperatury docelowej.\n***Oparte na przesunięciu***: Zastosuj kalibrację do przesunięcia."
}
}
}
},
"issues": {
"missing_entity": {
"title": "BT: {name} - brak podmiotu powiązanego",
"fix_flow": {
"step": {
"confirm": {
"title": "Brak {entity} powiązanej",
"description": "Powodem tego jest to, że podmiot ({entity}) nie jest dostępny w Twoim Home Assistant.\n\nMożesz to naprawić, sprawdzając, czy bateria urządzenia jest pełna lub ponownie podłączając je do HA. Zanim przejdziesz dalej, upewnij się, że jednostka wróciła do HA."
}
}
}
}
}
}
@@ -0,0 +1,98 @@
from importlib import import_module
import logging
_LOGGER = logging.getLogger(__name__)
def load_adapter(self, integration, entity_id, get_name=False):
"""Load adapter."""
if get_name:
self.name = "-"
if integration == "generic_thermostat":
integration = "generic"
try:
self.adapter = import_module(
"custom_components.better_thermostat.adapters." + integration,
package="better_thermostat",
)
_LOGGER.debug(
"better_thermostat %s: uses adapter %s for trv %s",
self.name,
integration,
entity_id,
)
except Exception:
self.adapter = import_module(
"custom_components.better_thermostat.adapters.generic",
package="better_thermostat",
)
_LOGGER.info(
"better_thermostat %s: intigration: %s isn't native supported, feel free to open an issue, fallback adapter %s",
self.name,
integration,
"generic",
)
pass
if get_name:
return integration
return self.adapter
async def init(self, entity_id):
"""Init adapter."""
return await self.real_trvs[entity_id]["adapter"].init(self, entity_id)
async def get_info(self, entity_id):
return await self.real_trvs[entity_id]["adapter"].get_info(self, entity_id)
async def get_current_offset(self, entity_id):
"""Get current offset."""
return await self.real_trvs[entity_id]["adapter"].get_current_offset(
self, entity_id
)
async def get_offset_steps(self, entity_id):
"""get offset setps."""
return await self.real_trvs[entity_id]["adapter"].get_offset_steps(self, entity_id)
async def get_min_offset(self, entity_id):
"""Get min offset."""
return await self.real_trvs[entity_id]["adapter"].get_min_offset(self, entity_id)
async def get_max_offset(self, entity_id):
"""Get max offset."""
return await self.real_trvs[entity_id]["adapter"].get_max_offset(self, entity_id)
async def set_temperature(self, entity_id, temperature):
"""Set new target temperature."""
return await self.real_trvs[entity_id]["adapter"].set_temperature(
self, entity_id, temperature
)
async def set_hvac_mode(self, entity_id, hvac_mode):
"""Set new target hvac mode."""
return await self.real_trvs[entity_id]["adapter"].set_hvac_mode(
self, entity_id, hvac_mode
)
async def set_offset(self, entity_id, offset):
"""Set new target offset."""
return await self.real_trvs[entity_id]["adapter"].set_offset(
self, entity_id, offset
)
async def set_valve(self, entity_id, valve):
"""Set new target valve."""
return await self.real_trvs[entity_id]["adapter"].set_valve(self, entity_id, valve)
@@ -0,0 +1,296 @@
import asyncio
import logging
from custom_components.better_thermostat.utils.model_quirks import (
override_set_hvac_mode,
override_set_temperature,
)
from .bridge import (
set_offset,
get_current_offset,
get_offset_steps,
set_temperature,
set_hvac_mode,
)
from ..events.trv import convert_outbound_states, update_hvac_action
from homeassistant.components.climate.const import HVACMode
from .helpers import convert_to_float, calibration_round
_LOGGER = logging.getLogger(__name__)
async def control_queue(self):
"""The accutal control loop.
Parameters
----------
self :
instance of better_thermostat
Returns
-------
None
"""
while True:
if self.ignore_states or self.startup_running:
await asyncio.sleep(1)
continue
else:
controls_to_process = await self.control_queue_task.get()
if controls_to_process is not None:
self.ignore_states = True
result = True
for trv in self.real_trvs.keys():
try:
_temp = await control_trv(self, trv)
if _temp is False:
result = False
except Exception:
_LOGGER.exception(
"better_thermostat %s: ERROR controlling: %s",
self.name,
trv,
)
result = False
# Retry task if some TRVs failed. Discard the task if the queue is full
# to avoid blocking and therefore deadlocking this function.
if result is False:
try:
self.control_queue_task.put_nowait(self)
except asyncio.QueueFull:
_LOGGER.debug(
"better_thermostat %s: control queue is full, discarding task"
)
self.control_queue_task.task_done()
self.ignore_states = False
async def control_trv(self, heater_entity_id=None):
"""This is the main controller for the real TRV
Parameters
----------
self :
instance of better_thermostat
Returns
-------
None
"""
async with self._temp_lock:
self.real_trvs[heater_entity_id]["ignore_trv_states"] = True
update_hvac_action(self)
await self.calculate_heating_power()
_trv = self.hass.states.get(heater_entity_id)
_current_set_temperature = convert_to_float(
str(_trv.attributes.get("temperature", None)), self.name, "controlling()"
)
_remapped_states = convert_outbound_states(
self, heater_entity_id, self.bt_hvac_mode
)
if not isinstance(_remapped_states, dict):
_LOGGER.debug(
f"better_thermostat {self.name}: ERROR {heater_entity_id} {_remapped_states}"
)
await asyncio.sleep(10)
self.ignore_states = False
self.real_trvs[heater_entity_id]["ignore_trv_states"] = False
return False
_temperature = _remapped_states.get("temperature", None)
_calibration = _remapped_states.get("local_temperature_calibration", None)
_new_hvac_mode = handle_window_open(self, _remapped_states)
# if we don't need ot heat, we force HVACMode to be off
if self.call_for_heat is False:
_new_hvac_mode = HVACMode.OFF
# Manage TRVs with no HVACMode.OFF
_no_off_system_mode = (
HVACMode.OFF not in self.real_trvs[heater_entity_id]["hvac_modes"]
) or (
self.real_trvs[heater_entity_id]["advanced"].get(
"no_off_system_mode", False
)
is True
)
if _no_off_system_mode is True and _new_hvac_mode == HVACMode.OFF:
_min_temp = self.real_trvs[heater_entity_id]["min_temp"]
_LOGGER.debug(
f"better_thermostat {self.name}: sending {_min_temp}°C to the TRV because this device has no system mode off and heater should be off"
)
_temperature = _min_temp
# send new HVAC mode to TRV, if it changed
if (
_new_hvac_mode is not None
and _new_hvac_mode != _trv.state
and (
(_no_off_system_mode is True and _new_hvac_mode != HVACMode.OFF)
or (_no_off_system_mode is False)
)
):
_LOGGER.debug(
f"better_thermostat {self.name}: TO TRV set_hvac_mode: {heater_entity_id} from: {_trv.state} to: {_new_hvac_mode}"
)
self.real_trvs[heater_entity_id]["last_hvac_mode"] = _new_hvac_mode
_tvr_has_quirk = await override_set_hvac_mode(
self, heater_entity_id, _new_hvac_mode
)
if _tvr_has_quirk is False:
await set_hvac_mode(self, heater_entity_id, _new_hvac_mode)
if self.real_trvs[heater_entity_id]["system_mode_received"] is True:
self.real_trvs[heater_entity_id]["system_mode_received"] = False
asyncio.create_task(check_system_mode(self, heater_entity_id))
# set new calibration offset
if _calibration is not None and _new_hvac_mode != HVACMode.OFF:
old_calibration = await get_current_offset(self, heater_entity_id)
step_calibration = await get_offset_steps(self, heater_entity_id)
if old_calibration is None or step_calibration is None:
_LOGGER.error(
"better_thermostat %s: calibration fatal error %s",
self.name,
heater_entity_id,
)
# this should not be before, set_hvac_mode (because if it fails, the new hvac mode will never be sent)
self.ignore_states = False
self.real_trvs[heater_entity_id]["ignore_trv_states"] = False
return True
current_calibration = convert_to_float(
str(old_calibration), self.name, "controlling()"
)
if step_calibration.is_integer():
_calibration = calibration_round(
float(str(format(float(_calibration), ".1f")))
)
else:
_calibration = float(str(format(float(_calibration), ".1f")))
old = self.real_trvs[heater_entity_id].get(
"last_calibration", current_calibration
)
if self.real_trvs[heater_entity_id][
"calibration_received"
] is True and float(old) != float(_calibration):
_LOGGER.debug(
f"better_thermostat {self.name}: TO TRV set_local_temperature_calibration: {heater_entity_id} from: {old} to: {_calibration}"
)
await set_offset(self, heater_entity_id, _calibration)
self.real_trvs[heater_entity_id]["calibration_received"] = False
# set new target temperature
if (
_temperature is not None
and _new_hvac_mode != HVACMode.OFF
or _no_off_system_mode
):
if _temperature != _current_set_temperature:
old = self.real_trvs[heater_entity_id].get("last_temperature", "?")
_LOGGER.debug(
f"better_thermostat {self.name}: TO TRV set_temperature: {heater_entity_id} from: {old} to: {_temperature}"
)
self.real_trvs[heater_entity_id]["last_temperature"] = _temperature
_tvr_has_quirk = await override_set_temperature(
self, heater_entity_id, _temperature
)
if _tvr_has_quirk is False:
await set_temperature(self, heater_entity_id, _temperature)
if self.real_trvs[heater_entity_id]["target_temp_received"] is True:
self.real_trvs[heater_entity_id]["target_temp_received"] = False
asyncio.create_task(
check_target_temperature(self, heater_entity_id)
)
await asyncio.sleep(3)
self.real_trvs[heater_entity_id]["ignore_trv_states"] = False
return True
def handle_window_open(self, _remapped_states):
"""handle window open"""
_converted_hvac_mode = _remapped_states.get("system_mode", None)
_hvac_mode_send = _converted_hvac_mode
if self.window_open is True and self.last_window_state is False:
# if the window is open or the sensor is not available, we're done
self.last_main_hvac_mode = _hvac_mode_send
_hvac_mode_send = HVACMode.OFF
self.last_window_state = True
_LOGGER.debug(
f"better_thermostat {self.name}: control_trv: window is open or status of window is unknown, setting window open"
)
elif self.window_open is False and self.last_window_state is True:
_hvac_mode_send = self.last_main_hvac_mode
self.last_window_state = False
_LOGGER.debug(
f"better_thermostat {self.name}: control_trv: window is closed, setting window closed restoring mode: {_hvac_mode_send}"
)
# Force off on window open
if self.window_open is True:
_hvac_mode_send = HVACMode.OFF
return _hvac_mode_send
async def check_system_mode(self, heater_entity_id=None):
"""check system mode"""
_timeout = 0
_real_trv = self.real_trvs[heater_entity_id]
while _real_trv["hvac_mode"] != _real_trv["last_hvac_mode"]:
if _timeout > 360:
_LOGGER.debug(
f"better_thermostat {self.name}: {heater_entity_id} the real TRV did not respond to the system mode change"
)
_timeout = 0
break
await asyncio.sleep(1)
_timeout += 1
await asyncio.sleep(2)
_real_trv["system_mode_received"] = True
return True
async def check_target_temperature(self, heater_entity_id=None):
"""Check if target temperature is reached."""
_timeout = 0
_real_trv = self.real_trvs[heater_entity_id]
while True:
_current_set_temperature = convert_to_float(
str(
self.hass.states.get(heater_entity_id).attributes.get(
"temperature", None
)
),
self.name,
"check_target_temperature()",
)
if _timeout == 0:
_LOGGER.debug(
f"better_thermostat {self.name}: {heater_entity_id} / check_target_temp / _last: {_real_trv['last_temperature']} - _current: {_current_set_temperature}"
)
if (
_current_set_temperature is None
or _real_trv["last_temperature"] == _current_set_temperature
):
_timeout = 0
break
if _timeout > 120:
_LOGGER.debug(
f"better_thermostat {self.name}: {heater_entity_id} the real TRV did not respond to the target temperature change"
)
_timeout = 0
break
await asyncio.sleep(1)
_timeout += 1
await asyncio.sleep(2)
_real_trv["target_temp_received"] = True
return True
@@ -0,0 +1,667 @@
"""Helper functions for the Better Thermostat component."""
import re
import logging
from datetime import datetime
from typing import Union
from homeassistant.helpers import device_registry as dr, entity_registry as er
from homeassistant.helpers.entity_registry import async_entries_for_config_entry
from homeassistant.components.climate.const import (
HVACMode,
HVACAction,
)
from custom_components.better_thermostat.utils.model_quirks import (
fix_local_calibration,
fix_target_temperature_calibration,
)
from ..const import (
CONF_HEAT_AUTO_SWAPPED,
CONF_HEATING_POWER_CALIBRATION,
CONF_FIX_CALIBRATION,
CONF_PROTECT_OVERHEATING,
)
_LOGGER = logging.getLogger(__name__)
def mode_remap(self, entity_id, hvac_mode: str, inbound: bool = False) -> str:
"""Remap HVAC mode to correct mode if nessesary.
Parameters
----------
self :
FIXME
hvac_mode : str
HVAC mode to be remapped
inbound : bool
True if the mode is coming from the device, False if it is coming from the HA.
Returns
-------
str
remapped mode according to device's quirks
"""
_heat_auto_swapped = self.real_trvs[entity_id]["advanced"].get(
CONF_HEAT_AUTO_SWAPPED, False
)
if _heat_auto_swapped:
if hvac_mode == HVACMode.HEAT and inbound is False:
return HVACMode.AUTO
elif hvac_mode == HVACMode.AUTO and inbound is True:
return HVACMode.HEAT
else:
return hvac_mode
else:
if hvac_mode != HVACMode.AUTO:
return hvac_mode
else:
_LOGGER.error(
f"better_thermostat {self.name}: {entity_id} HVAC mode {hvac_mode} is not supported by this device, is it possible that you forgot to set the heat auto swapped option?"
)
return HVACMode.OFF
def calculate_local_setpoint_delta(self, entity_id) -> Union[float, None]:
"""Calculate local delta to adjust the setpoint of the TRV based on the air temperature of the external sensor.
This calibration is for devices with local calibration option, it syncs the current temperature of the TRV to the target temperature of
the external sensor.
Parameters
----------
self :
self instance of better_thermostat
Returns
-------
float
new local calibration delta
"""
_context = "calculate_local_setpoint_delta()"
if None in (self.cur_temp, self.bt_target_temp, self.old_internal_temp):
return None
# check if we need to calculate
if (
self.real_trvs[entity_id]["current_temperature"] == self.old_internal_temp
and self.cur_temp == self.old_external_temp
):
return None
_cur_trv_temp = convert_to_float(
str(self.real_trvs[entity_id]["current_temperature"]), self.name, _context
)
_calibration_delta = float(
str(format(float(abs(_cur_trv_temp - self.cur_temp)), ".1f"))
)
if _calibration_delta <= 0.5:
return None
self.old_internal_temp = self.real_trvs[entity_id]["current_temperature"]
self.old_external_temp = self.cur_temp
_current_trv_calibration = round_to_half_degree(
convert_to_float(
str(self.real_trvs[entity_id]["last_calibration"]), self.name, _context
)
)
if None in (_current_trv_calibration, self.cur_temp, _cur_trv_temp):
_LOGGER.warning(
f"better thermostat {self.name}: {entity_id} Could not calculate local setpoint delta in {_context}:"
f" current_trv_calibration: {_current_trv_calibration}, current_trv_temp: {_cur_trv_temp}, cur_temp: {self.cur_temp}"
)
return None
_new_local_calibration = (self.cur_temp - _cur_trv_temp) + _current_trv_calibration
_calibration_mode = self.real_trvs[entity_id]["advanced"].get(
"calibration_mode", "default"
)
if _calibration_mode == CONF_FIX_CALIBRATION:
# _temp_diff = float(float(self.bt_target_temp) - float(self.cur_temp))
# if _temp_diff > 0.30 and _new_local_calibration > -2.5:
# _new_local_calibration -= 2.5
if self.attr_hvac_action == HVACAction.HEATING:
if _new_local_calibration > -2.5:
_new_local_calibration -= 2.5
elif _calibration_mode == CONF_HEATING_POWER_CALIBRATION:
# _temp_diff = float(float(self.bt_target_temp) - float(self.cur_temp))
# if _temp_diff > 0.0:
# valve_position = heating_power_valve_position(self, entity_id)
# _new_local_calibration = _current_trv_calibration - (
# (self.real_trvs[entity_id]["local_calibration_min"] + _cur_trv_temp)
# * valve_position
# )
if self.attr_hvac_action == HVACAction.HEATING:
# if _temp_diff > 0.0:
valve_position = heating_power_valve_position(self, entity_id)
_new_local_calibration = _current_trv_calibration - (
(self.real_trvs[entity_id]["local_calibration_min"] + _cur_trv_temp)
* valve_position
)
elif self.attr_hvac_action == HVACAction.IDLE:
if _new_local_calibration < 0.0:
_new_local_calibration += self.tolerance
_new_local_calibration = fix_local_calibration(
self, entity_id, _new_local_calibration
)
_overheating_protection = self.real_trvs[entity_id]["advanced"].get(
CONF_PROTECT_OVERHEATING, False
)
if _overheating_protection is True:
if self.cur_temp >= self.bt_target_temp:
_new_local_calibration += (self.cur_temp - self.bt_target_temp) * 10.0
_new_local_calibration = round_down_to_half_degree(_new_local_calibration)
if _new_local_calibration > float(
self.real_trvs[entity_id]["local_calibration_max"]
):
_new_local_calibration = float(
self.real_trvs[entity_id]["local_calibration_max"]
)
elif _new_local_calibration < float(
self.real_trvs[entity_id]["local_calibration_min"]
):
_new_local_calibration = float(
self.real_trvs[entity_id]["local_calibration_min"]
)
_new_local_calibration = convert_to_float(
str(_new_local_calibration), self.name, _context
)
_LOGGER.debug(
"better_thermostat %s: %s - output calib: %s",
self.name,
entity_id,
_new_local_calibration,
)
return convert_to_float(str(_new_local_calibration), self.name, _context)
def calculate_setpoint_override(self, entity_id) -> Union[float, None]:
"""Calculate new setpoint for the TRV based on its own temperature measurement and the air temperature of the external sensor.
This calibration is for devices with no local calibration option, it syncs the target temperature of the TRV to a new target
temperature based on the current temperature of the external sensor.
Parameters
----------
self :
self instance of better_thermostat
Returns
-------
float
new target temp with calibration
"""
if None in (self.cur_temp, self.bt_target_temp):
return None
_cur_trv_temp = self.hass.states.get(entity_id).attributes["current_temperature"]
if None in (self.bt_target_temp, self.cur_temp, _cur_trv_temp):
return None
_calibrated_setpoint = (self.bt_target_temp - self.cur_temp) + _cur_trv_temp
_calibration_mode = self.real_trvs[entity_id]["advanced"].get(
"calibration_mode", "default"
)
if _calibration_mode == CONF_FIX_CALIBRATION:
# _temp_diff = float(float(self.bt_target_temp) - float(self.cur_temp))
if self.attr_hvac_action == HVACAction.HEATING:
if _calibrated_setpoint - _cur_trv_temp < 2.5:
_calibrated_setpoint += 2.5
elif self.attr_hvac_action == HVACAction.IDLE:
if _calibrated_setpoint - _cur_trv_temp > 0.0:
_calibrated_setpoint -= self.tolerance
elif _calibration_mode == CONF_HEATING_POWER_CALIBRATION:
# _temp_diff = float(float(self.bt_target_temp) - float(self.cur_temp))
if self.attr_hvac_action == HVACAction.HEATING:
# if _temp_diff > 0.0:
valve_position = heating_power_valve_position(self, entity_id)
_calibrated_setpoint = _cur_trv_temp + (
(self.real_trvs[entity_id]["max_temp"] - _cur_trv_temp) * valve_position
)
elif self.attr_hvac_action == HVACAction.IDLE:
if _calibrated_setpoint - _cur_trv_temp > 0.0:
_calibrated_setpoint -= self.tolerance
_calibrated_setpoint = fix_target_temperature_calibration(
self, entity_id, _calibrated_setpoint
)
_overheating_protection = self.real_trvs[entity_id]["advanced"].get(
CONF_PROTECT_OVERHEATING, False
)
if _overheating_protection is True:
if self.cur_temp >= self.bt_target_temp:
_calibrated_setpoint -= (self.cur_temp - self.bt_target_temp) * 10.0
_calibrated_setpoint = round_down_to_half_degree(_calibrated_setpoint)
# check if new setpoint is inside the TRV's range, else set to min or max
if _calibrated_setpoint < self.real_trvs[entity_id]["min_temp"]:
_calibrated_setpoint = self.real_trvs[entity_id]["min_temp"]
if _calibrated_setpoint > self.real_trvs[entity_id]["max_temp"]:
_calibrated_setpoint = self.real_trvs[entity_id]["max_temp"]
return _calibrated_setpoint
def heating_power_valve_position(self, entity_id):
_temp_diff = float(float(self.bt_target_temp) - float(self.cur_temp))
valve_pos = (_temp_diff / self.heating_power) / 100
if valve_pos < 0.0:
valve_pos = 0.0
if valve_pos > 1.0:
valve_pos = 1.0
_LOGGER.debug(
f"better_thermostat {self.name}: {entity_id} / heating_power_valve_position - temp diff: {round(_temp_diff, 1)} - heating power: {round(self.heating_power, 4)} - expected valve position: {round(valve_pos * 100)}%"
)
return valve_pos
def convert_to_float(
value: Union[str, int, float], instance_name: str, context: str
) -> Union[float, None]:
"""Convert value to float or print error message.
Parameters
----------
value : str, int, float
the value to convert to float
instance_name : str
the name of the instance thermostat
context : str
the name of the function which is using this, for printing an error message
Returns
-------
float
the converted value
None
If error occurred and cannot convert the value.
"""
if isinstance(value, float):
return round(value, 1)
elif value is None or value == "None":
return None
else:
try:
return round(float(str(format(float(value), ".1f"))), 1)
except (ValueError, TypeError, AttributeError, KeyError):
_LOGGER.debug(
f"better thermostat {instance_name}: Could not convert '{value}' to float in {context}"
)
return None
def calibration_round(value: Union[int, float, None]) -> Union[float, int, None]:
"""Round the calibration value to the nearest 0.5.
Parameters
----------
value : float
the value to round
Returns
-------
float
the rounded value
"""
if value is None:
return None
split = str(float(str(value))).split(".", 1)
decimale = int(split[1])
if decimale > 8:
return float(str(split[0])) + 1.0
else:
return float(str(split[0]))
def round_down_to_half_degree(
value: Union[int, float, None]
) -> Union[float, int, None]:
"""Round the value down to the nearest 0.5.
Parameters
----------
value : float
the value to round
Returns
-------
float
the rounded value
"""
if value is None:
return None
split = str(float(str(value))).split(".", 1)
decimale = int(split[1])
if decimale >= 5:
if float(split[0]) > 0:
return float(str(split[0])) + 0.5
else:
return float(str(split[0])) - 0.5
else:
return float(str(split[0]))
def round_to_half_degree(value: Union[int, float, None]) -> Union[float, int, None]:
"""Rounds numbers to the nearest n.5/n.0
Parameters
----------
value : int, float
input value
Returns
-------
float, int
either an int, if input was an int, or a float rounded to n.5/n.0
"""
if value is None:
return None
elif isinstance(value, float):
return round(value * 2) / 2
elif isinstance(value, int):
return value
def round_to_hundredth_degree(
value: Union[int, float, None]
) -> Union[float, int, None]:
"""Rounds numbers to the nearest n.nn0
Parameters
----------
value : int, float
input value
Returns
-------
float, int
either an int, if input was an int, or a float rounded to n.nn0
"""
if value is None:
return None
elif isinstance(value, float):
return round(value * 100) / 100
elif isinstance(value, int):
return value
def check_float(potential_float):
"""Check if a string is a float.
Parameters
----------
potential_float :
the value to check
Returns
-------
bool
True if the value is a float, False otherwise.
"""
try:
float(potential_float)
return True
except ValueError:
return False
def convert_time(time_string):
"""Convert a time string to a datetime object.
Parameters
----------
time_string :
a string representing a time
Returns
-------
datetime
the converted time as a datetime object.
None
If the time string is not a valid time.
"""
try:
_current_time = datetime.now()
_get_hours_minutes = datetime.strptime(time_string, "%H:%M")
return _current_time.replace(
hour=_get_hours_minutes.hour,
minute=_get_hours_minutes.minute,
second=0,
microsecond=0,
)
except ValueError:
return None
async def find_valve_entity(self, entity_id):
"""Find the local calibration entity for the TRV.
This is a hacky way to find the local calibration entity for the TRV. It is not possible to find the entity
automatically, because the entity_id is not the same as the friendly_name. The friendly_name is the same for all
thermostats of the same brand, but the entity_id is different.
Parameters
----------
self :
self instance of better_thermostat
Returns
-------
str
the entity_id of the local calibration entity
None
if no local calibration entity was found
"""
entity_registry = er.async_get(self.hass)
reg_entity = entity_registry.async_get(entity_id)
if reg_entity is None:
return None
entity_entries = async_entries_for_config_entry(
entity_registry, reg_entity.config_entry_id
)
for entity in entity_entries:
uid = entity.unique_id
# Make sure we use the correct device entities
if entity.device_id == reg_entity.device_id:
if "_valve_position" in uid or "_position" in uid:
_LOGGER.debug(
f"better thermostat: Found valve position entity {entity.entity_id} for {entity_id}"
)
return entity.entity_id
_LOGGER.debug(
f"better thermostat: Could not find valve position entity for {entity_id}"
)
return None
async def find_battery_entity(self, entity_id):
entity_registry = er.async_get(self.hass)
entity_info = entity_registry.entities.get(entity_id)
if entity_info is None:
return None
device_id = entity_info.device_id
for entity in entity_registry.entities.values():
if entity.device_id == device_id and (
entity.device_class == "battery"
or entity.original_device_class == "battery"
):
return entity.entity_id
return None
async def find_local_calibration_entity(self, entity_id):
"""Find the local calibration entity for the TRV.
This is a hacky way to find the local calibration entity for the TRV. It is not possible to find the entity
automatically, because the entity_id is not the same as the friendly_name. The friendly_name is the same for all
thermostats of the same brand, but the entity_id is different.
Parameters
----------
self :
self instance of better_thermostat
Returns
-------
str
the entity_id of the local calibration entity
None
if no local calibration entity was found
"""
entity_registry = er.async_get(self.hass)
reg_entity = entity_registry.async_get(entity_id)
if reg_entity is None:
return None
entity_entries = async_entries_for_config_entry(
entity_registry, reg_entity.config_entry_id
)
for entity in entity_entries:
uid = entity.unique_id + " " + entity.entity_id
# Make sure we use the correct device entities
if entity.device_id == reg_entity.device_id:
if "temperature_calibration" in uid or "temperature_offset" in uid:
_LOGGER.debug(
f"better thermostat: Found local calibration entity {entity.entity_id} for {entity_id}"
)
return entity.entity_id
_LOGGER.debug(
f"better thermostat: Could not find local calibration entity for {entity_id}"
)
return None
async def get_trv_intigration(self, entity_id):
"""Get the integration of the TRV.
Parameters
----------
self :
self instance of better_thermostat
Returns
-------
str
the integration of the TRV
"""
entity_reg = er.async_get(self.hass)
entry = entity_reg.async_get(entity_id)
try:
return entry.platform
except AttributeError:
return "generic_thermostat"
def get_max_value(obj, value, default):
"""Get the max value of an dict object."""
try:
_raw = []
for key in obj.keys():
_temp = obj[key].get(value, 0)
if _temp is not None:
_raw.append(_temp)
return max(_raw, key=lambda x: float(x))
except (KeyError, ValueError):
return default
def get_min_value(obj, value, default):
"""Get the min value of an dict object."""
try:
_raw = []
for key in obj.keys():
_temp = obj[key].get(value, 999)
if _temp is not None:
_raw.append(_temp)
return min(_raw, key=lambda x: float(x))
except (KeyError, ValueError):
return default
async def get_device_model(self, entity_id):
"""Fetches the device model from HA.
Parameters
----------
self :
self instance of better_thermostat
Returns
-------
string
the name of the thermostat model
"""
if self.model is None:
try:
entity_reg = er.async_get(self.hass)
entry = entity_reg.async_get(entity_id)
dev_reg = dr.async_get(self.hass)
device = dev_reg.async_get(entry.device_id)
_LOGGER.debug(f"better_thermostat {self.name}: found device:")
_LOGGER.debug(device)
try:
# Z2M reports the device name as a long string with the actual model name in braces, we need to extract it
return re.search("\\((.+?)\\)", device.model).group(1)
except AttributeError:
# Other climate integrations might report the model name plainly, need more infos on this
return device.model
except (
RuntimeError,
ValueError,
AttributeError,
KeyError,
TypeError,
NameError,
IndexError,
):
try:
return (
self.hass.states.get(entity_id)
.attributes.get("device")
.get("model", "generic")
)
except (
RuntimeError,
ValueError,
AttributeError,
KeyError,
TypeError,
NameError,
IndexError,
):
return "generic"
else:
return self.model
@@ -0,0 +1,55 @@
from importlib import import_module
import logging
_LOGGER = logging.getLogger(__name__)
def load_model_quirks(self, model, entity_id):
"""Load model."""
# remove / from model
model = model.replace("/", "_")
try:
self.model_quirks = import_module(
"custom_components.better_thermostat.model_fixes." + model,
package="better_thermostat",
)
_LOGGER.debug(
"better_thermostat %s: uses quirks fixes for model %s for trv %s",
self.name,
model,
entity_id,
)
except Exception:
self.model_quirks = import_module(
"custom_components.better_thermostat.model_fixes.default",
package="better_thermostat",
)
pass
return self.model_quirks
def fix_local_calibration(self, entity_id, offset):
return self.real_trvs[entity_id]["model_quirks"].fix_local_calibration(
self, entity_id, offset
)
def fix_target_temperature_calibration(self, entity_id, temperature):
return self.real_trvs[entity_id]["model_quirks"].fix_target_temperature_calibration(
self, entity_id, temperature
)
async def override_set_hvac_mode(self, entity_id, hvac_mode):
return await self.real_trvs[entity_id]["model_quirks"].override_set_hvac_mode(
self, entity_id, hvac_mode
)
async def override_set_temperature(self, entity_id, temperature):
return await self.real_trvs[entity_id]["model_quirks"].override_set_temperature(
self, entity_id, temperature
)
@@ -0,0 +1,72 @@
from __future__ import annotations
from homeassistant.helpers import issue_registry as ir
from homeassistant.const import STATE_UNAVAILABLE, STATE_UNKNOWN
import logging
DOMAIN = "better_thermostat"
_LOGGER = logging.getLogger(__name__)
async def check_entity(self, entity) -> bool:
if entity is None:
return False
entity_states = self.hass.states.get(entity)
if entity_states is None:
return False
state = entity_states.state
if state in (
STATE_UNAVAILABLE,
STATE_UNKNOWN,
None,
"missing",
"unknown",
"unavail",
"unavailable",
):
_LOGGER.debug(
f"better_thermostat {self.name}: {entity} is unavailable. with state {state}"
)
return False
if entity in self.devices_errors:
self.devices_errors.remove(entity)
self.async_write_ha_state()
ir.async_delete_issue(self.hass, DOMAIN, f"missing_entity_{entity}")
self.hass.async_create_task(get_battery_status(self, entity))
return True
async def get_battery_status(self, entity):
if entity in self.devices_states:
battery_id = self.devices_states[entity].get("battery_id")
if battery_id is not None:
new_battery = self.hass.states.get(battery_id)
if new_battery is not None:
battery = new_battery.state
self.devices_states[entity] = {
"battery": battery,
"battery_id": battery_id,
}
self.async_write_ha_state()
return
async def check_all_entities(self) -> bool:
entities = self.all_entities
for entity in entities:
if not await check_entity(self, entity):
name = entity
self.devices_errors.append(name)
self.async_write_ha_state()
ir.async_create_issue(
hass=self.hass,
domain=DOMAIN,
issue_id=f"missing_entity_{name}",
is_fixable=True,
is_persistent=False,
learn_more_url="https://better-thermostat.org/qanda/missing_entity",
severity=ir.IssueSeverity.WARNING,
translation_key="missing_entity",
translation_placeholders={"entity": str(name), "name": str(self.name)},
)
return False
return True
@@ -0,0 +1,243 @@
from collections import deque
import logging
from datetime import timedelta, datetime
import homeassistant.util.dt as dt_util
from homeassistant.components.recorder import get_instance, history
from contextlib import suppress
# from datetime import datetime, timedelta
# import homeassistant.util.dt as dt_util
# from homeassistant.components.recorder.history import state_changes_during_period
from .helpers import convert_to_float
from statistics import median
_LOGGER = logging.getLogger(__name__)
def check_weather(self) -> bool:
"""check weather predictions or ambient air temperature if available
Parameters
----------
self :
self instance of better_thermostat
Returns
-------
bool
true if call_for_heat was changed
"""
old_call_for_heat = self.call_for_heat
_call_for_heat_weather = False
_call_for_heat_outdoor = False
self.call_for_heat = True
if self.weather_entity is not None:
_call_for_heat_weather = check_weather_prediction(self)
self.call_for_heat = _call_for_heat_weather
if self.outdoor_sensor is not None:
if None in (self.last_avg_outdoor_temp, self.off_temperature):
# TODO: add condition if heating period (oct-mar) then set it to true?
_LOGGER.warning(
"better_thermostat %s: no outdoor sensor data found. fallback to heat",
self.name,
)
_call_for_heat_outdoor = True
else:
_call_for_heat_outdoor = self.last_avg_outdoor_temp < self.off_temperature
self.call_for_heat = _call_for_heat_outdoor
if self.weather_entity is None and self.outdoor_sensor is None:
self.call_for_heat = True
return True
if old_call_for_heat != self.call_for_heat:
return True
else:
return False
def check_weather_prediction(self) -> bool:
"""Checks configured weather entity for next two days of temperature predictions.
Returns
-------
bool
True if the maximum forcast temperature is lower than the off temperature
None
if not successful
"""
if self.weather_entity is None:
_LOGGER.warning(f"better_thermostat {self.name}: weather entity not available.")
return None
if self.off_temperature is None or not isinstance(self.off_temperature, float):
_LOGGER.warning(
f"better_thermostat {self.name}: off_temperature not set or not a float."
)
return None
try:
forecast = self.hass.states.get(self.weather_entity).attributes.get("forecast")
if len(forecast) > 0:
cur_outside_temp = convert_to_float(
str(
self.hass.states.get(self.weather_entity).attributes.get(
"temperature"
)
),
self.name,
"check_weather_prediction()",
)
max_forecast_temp = int(
round(
(
convert_to_float(
str(forecast[0]["temperature"]),
self.name,
"check_weather_prediction()",
)
+ convert_to_float(
str(forecast[1]["temperature"]),
self.name,
"check_weather_prediction()",
)
)
/ 2
)
)
return (
cur_outside_temp < self.off_temperature
or max_forecast_temp < self.off_temperature
)
else:
raise TypeError
except TypeError:
_LOGGER.warning(f"better_thermostat {self.name}: no weather entity data found.")
return None
async def check_ambient_air_temperature(self):
"""Gets the history for two days and evaluates the necessary for heating.
Returns
-------
bool
True if the average temperature is lower than the off temperature
None
if not successful
"""
if self.outdoor_sensor is None:
return None
if self.off_temperature is None or not isinstance(self.off_temperature, float):
_LOGGER.warning(
f"better_thermostat {self.name}: off_temperature not set or not a float."
)
return None
self.last_avg_outdoor_temp = convert_to_float(
self.hass.states.get(self.outdoor_sensor).state,
self.name,
"check_ambient_air_temperature()",
)
if "recorder" in self.hass.config.components:
_temp_history = DailyHistory(2)
start_date = dt_util.utcnow() - timedelta(days=2)
entity_id = self.outdoor_sensor
if entity_id is None:
_LOGGER.debug(
"Not reading the history from the database as "
"there is no outdoor sensor configured"
)
return
_LOGGER.debug(
"Initializing values for %s from the database", self.outdoor_sensor
)
lower_entity_id = entity_id.lower()
history_list = await get_instance(self.hass).async_add_executor_job(
history.state_changes_during_period,
self.hass,
start_date,
dt_util.utcnow(),
lower_entity_id,
)
for item in history_list.get(lower_entity_id):
# filter out all None, NaN and "unknown" states
# only keep real values
with suppress(ValueError):
if item.state != "unknown":
_temp_history.add_measurement(
convert_to_float(
item.state, self.name, "check_ambient_air_temperature()"
),
datetime.fromtimestamp(item.last_updated.timestamp()),
)
avg_temp = _temp_history.min
_LOGGER.debug("Initializing from database completed")
else:
avg_temp = self.last_avg_outdoor_temp
_LOGGER.debug(
f"better_thermostat {self.name}: avg outdoor temp: {avg_temp}, threshold is {self.off_temperature}"
)
if avg_temp is not None:
self.call_for_heat = avg_temp < self.off_temperature
else:
self.call_for_heat = True
self.last_avg_outdoor_temp = avg_temp
class DailyHistory:
"""Stores one measurement per day for a maximum number of days.
At the moment only the maximum value per day is kept.
"""
def __init__(self, max_length):
"""Create new DailyHistory with a maximum length of the history."""
self.max_length = max_length
self._days = None
self._max_dict = {}
self.min = None
def add_measurement(self, value, timestamp=None):
"""Add a new measurement for a certain day."""
day = (timestamp or datetime.now()).date()
if not isinstance(value, (int, float)):
return
if self._days is None:
self._days = deque()
self._add_day(day, value)
else:
current_day = self._days[-1]
if day == current_day:
self._max_dict[day] = min(value, self._max_dict[day])
elif day > current_day:
self._add_day(day, value)
else:
_LOGGER.warning("Received old measurement, not storing it")
self.min = median(self._max_dict.values())
def _add_day(self, day, value):
"""Add a new day to the history.
Deletes the oldest day, if the queue becomes too long.
"""
if len(self._days) == self.max_length:
oldest = self._days.popleft()
del self._max_dict[oldest]
self._days.append(day)
if not isinstance(value, (int, float)):
return
self._max_dict[day] = value
@@ -0,0 +1,130 @@
"""electrolux status integration."""
from pyelectroluxconnect import Session
import asyncio
import logging
from datetime import timedelta
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import Config
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryNotReady, ConfigEntryAuthFailed
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
from homeassistant.helpers.update_coordinator import UpdateFailed
from .pyelectroluxconnect_util import pyelectroluxconnect_util
from .api import Appliance, Appliances, ElectroluxLibraryEntity
from .const import CONF_PASSWORD, CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL, CONF_REGION, DEFAULT_REGION
from .const import CONF_LANGUAGE, DEFAULT_LANGUAGE
from .const import CONF_USERNAME
from .const import DOMAIN
from .const import PLATFORMS
from .const import languages
_LOGGER: logging.Logger = logging.getLogger(__package__)
# noinspection PyUnusedLocal
async def async_setup(hass: HomeAssistant, config: Config):
"""Set up this integration using YAML is not supported."""
return True
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
"""Set up this integration using UI."""
if hass.data.get(DOMAIN) is None:
hass.data.setdefault(DOMAIN, {})
if entry.options.get(CONF_SCAN_INTERVAL):
update_interval = timedelta(seconds=entry.options[CONF_SCAN_INTERVAL])
else:
update_interval = timedelta(seconds=DEFAULT_SCAN_INTERVAL)
username = entry.data.get(CONF_USERNAME)
password = entry.data.get(CONF_PASSWORD)
region = entry.data.get(CONF_REGION, DEFAULT_REGION)
language = languages.get(entry.data.get(CONF_LANGUAGE, DEFAULT_LANGUAGE),"eng")
client = pyelectroluxconnect_util.get_session(username, password, region, language)
coordinator = ElectroluxStatusDataUpdateCoordinator(hass, client=client, update_interval=update_interval)
if not await coordinator.async_login():
raise ConfigEntryAuthFailed
await coordinator.async_config_entry_first_refresh()
if not coordinator.last_update_success:
raise ConfigEntryNotReady
hass.data[DOMAIN][entry.entry_id] = coordinator
coordinator.platforms.extend(PLATFORMS)
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
entry.add_update_listener(async_reload_entry)
return True
class ElectroluxStatusDataUpdateCoordinator(DataUpdateCoordinator):
"""Class to manage fetching data from the API."""
def __init__(self, hass: HomeAssistant, client: Session, update_interval: timedelta) -> None:
"""Initialize."""
self.api = client
self.platforms = []
super().__init__(hass, _LOGGER, name=DOMAIN, update_interval=update_interval)
async def async_login(self) -> bool:
try:
await self.hass.async_add_executor_job(self.api.login)
except Exception as ex:
_LOGGER.error("Could not log in to ElectroluxStatus, %s", ex)
return False
return True
async def _async_update_data(self):
"""Update data via library."""
await self.async_login()
found_appliances = {}
try:
appliances_json = await self.hass.async_add_executor_job(self.api.getAppliances)
for appliance in appliances_json:
connection_state = await self.hass.async_add_executor_job(self.api.getApplianceConnectionState, appliance)
appliance_state = await self.hass.async_add_executor_job(self.api.getApplianceState, appliance)
appliance_profile = await self.hass.async_add_executor_job(self.api.getApplianceProfile, appliance)
appliance_name = appliances_json[appliance]['alias'] or appliance
appliance_model = appliances_json[appliance]['model'] or appliances_json[appliance]['pnc']
app = Appliance(appliance_name, appliance, appliances_json[appliance]['brand'], appliance_model)
app.setup(ElectroluxLibraryEntity(appliance_name, connection_state, appliance_state, appliance_profile))
found_appliances[appliance_name] = app
return {
"appliances": Appliances(found_appliances)
}
except Exception as exception:
_LOGGER.exception(exception)
raise UpdateFailed() from exception
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Handle removal of an entry."""
coordinator = hass.data[DOMAIN][entry.entry_id]
unloaded = all(
await asyncio.gather(
*[
hass.config_entries.async_forward_entry_unload(entry, platform)
for platform in PLATFORMS
if platform in coordinator.platforms
]
)
)
if unloaded:
hass.data[DOMAIN].pop(entry.entry_id)
return unloaded
async def async_reload_entry(hass: HomeAssistant, entry: ConfigEntry) -> None:
"""Reload config entry."""
await async_unload_entry(hass, entry)
await async_setup_entry(hass, entry)
+317
View File
@@ -0,0 +1,317 @@
import logging
import math
from homeassistant.components.binary_sensor import BinarySensorDeviceClass
from homeassistant.components.sensor import SensorDeviceClass
from homeassistant.helpers.entity import EntityCategory
from .const import BINARY_SENSOR, SENSOR, BUTTON, icon_mapping
from .const import sensors, sensors_binary
_LOGGER: logging.Logger = logging.getLogger(__package__)
HEADERS = {"Content-type": "application/json; charset=UTF-8"}
class ElectroluxLibraryEntity:
def __init__(self, name, status, last_states, appliance_profile):
self.name = name
self.status: dict = status
self.states = last_states
self.profile = appliance_profile
def get_name(self):
return self.name
def get_value(self, attr_name, field=None, source=None):
if attr_name in ["LinkQualityIndicator"]:
return self.num_to_dbm(attr_name, field, source)
if attr_name in ['StartTime', 'TimeToEnd', 'RunningTime', 'DryingTime', 'ApplianceTotalWorkingTime',
"FCTotalWashingTime"]:
return self.time_to_end_in_minutes(attr_name, field, source)
if attr_name in self.status:
return self.status.get(attr_name)
if attr_name in [self.states[k].get("name") for k in self.states]:
val = self.get_from_states(attr_name, field, source)
if field == "container":
if val["1"]["name"] == "Coefficient" and val["3"]["name"] == "Exponent":
return val["1"]["numberValue"] * (10 ** val["3"]["numberValue"])
else:
return val
if attr_name in [self.states[st]["container"][cr].get("name") for st in self.states for cr in
self.states[st].get("container", [])]:
return self.get_from_states(attr_name, field, source)
return None
def time_to_end_in_minutes(self, attr_name, field, source):
seconds = self.get_from_states(attr_name, field, source)
if seconds is not None:
if seconds == -1:
return -1
return int(math.ceil((int(seconds) / 60)))
return None
def num_to_dbm(self, attr_name, field, source):
number_from_0_to_5 = self.get_from_states(attr_name, field, source)
if number_from_0_to_5 is not None:
if int(number_from_0_to_5) == 0:
return -110
if int(number_from_0_to_5) == 1:
return -80
if int(number_from_0_to_5) == 2:
return -70
if int(number_from_0_to_5) == 3:
return -60
if int(number_from_0_to_5) == 4:
return -55
if int(number_from_0_to_5) == 5:
return -20
return None
def get_from_states(self, attr_name, field, source):
for k in self.states:
if attr_name == self.states[k].get("name") and source == self.states[k].get("source"):
return self._get_states(self.states[k], field) if field else self._get_states(self.states[k])
for c in self.states[k].get("container", []):
if attr_name == self.states[k]["container"][c].get("name"):
return self._get_states(self.states[k]["container"][c], field) if field else self._get_states(
self.states[k]["container"][c])
return None
@staticmethod
def _get_states(states, field=None):
if field:
if field in states.keys():
return states.get(field)
if field == "string":
if "valueTransl" in states.keys():
return states.get("valueTransl").strip(" :.")
if "valTransl" in states.keys():
return states.get("valTransl").strip(" :.")
if "stringValue" in states.keys():
return states.get("stringValue").strip(" :.")
return ""
else:
if "valueTransl" in states.keys():
return states.get("valueTransl").strip(" :.")
if "valTransl" in states.keys():
return states.get("valTransl").strip(" :.")
if "stringValue" in states.keys():
return states.get("stringValue").strip(" :.")
if "numberValue" in states.keys():
return states.get("numberValue")
def get_sensor_name(self, attr_name, source):
for k in self.states:
if attr_name == self.states[k].get("name") and source == self.states[k].get("source"):
if "nameTransl" in self.states[k].keys():
return self.states[k].get("nameTransl").strip(" :.")
else:
return self.states[k].get("name").strip(" :.")
if "container" in self.states[k]:
for c in self.states[k].get("container", []):
if attr_name == self.states[k]["container"][c].get("name"):
if "nameTransl" in self.states[k]["container"][c].keys():
return self.states[k]["container"][c].get("nameTransl").strip(" :.")
else:
return self.states[k]["container"][c].get("name").strip(" :.")
return None
def value_exists(self, attr_name, source):
_container_attr = []
for k in self.states:
for c in self.states[k].get("container", []):
_container_attr.append(self.states[k]["container"][c].get("name"))
return (attr_name in self.status) or \
(attr_name in [self.states[k].get("name") for k in self.states if
self.states[k].get("source") == source]) or \
(attr_name in [self.profile[k].get("name") for k in self.profile if
self.profile[k].get("source") == source]) or \
(attr_name in _container_attr)
def sources_list(self):
return list(
{self.states[k].get("source") for k in self.states if self.states[k].get("source") not in ["NIU", "APL"]}
)
def commands_list(self, source):
commands = list(self.profile[k].get("steps") for k in self.profile if
self.profile[k].get("source") == source and self.profile[k].get("name") == "ExecuteCommand")
if len(commands) > 0:
return commands[0]
else:
return {}
def get_command_name(self, command_desc):
if "transl" in command_desc:
return command_desc["transl"]
elif "key" in command_desc:
return command_desc["key"]
return None
def get_suffix(self, attr_name, source):
res = list({self.states[k].get("source") for k in self.states if self.states[k].get("name") == attr_name})
if len(res) == 1:
return ""
else:
if source in res:
return f" ({source})"
return ""
class ApplianceEntity:
entity_type = None
def __init__(self, name, attr, device_class=None, entity_category=None, field=None, source=None) -> None:
self.attr = attr
self.name = name
self.device_class = device_class
self.entity_category = entity_category
self.field = field
self.source = source
self.val_to_send = None
self.icon = None
self._state = None
def setup(self, data: ElectroluxLibraryEntity):
self._state = data.get_value(self.attr, self.field, self.source)
return self
def clear_state(self):
self._state = None
class ApplianceSensor(ApplianceEntity):
entity_type = SENSOR
def __init__(self, name, attr, unit=None, device_class=None, entity_category=None, field=None, source=None) -> None:
super().__init__(name, attr, device_class, entity_category, field, source)
self.unit = unit
@property
def state(self):
return self._state
class ApplianceBinary(ApplianceEntity):
entity_type = BINARY_SENSOR
def __init__(self, name, attr, device_class=None, entity_category=None, field=None, invert=False,
source=None) -> None:
super().__init__(name, attr, device_class, entity_category, field, source)
self.invert = invert
@property
def state(self):
state = self._state in [1, 'enabled', True, 'Connected', 'connect']
return not state if self.invert else state
class ApplianceButton(ApplianceEntity):
entity_type = BUTTON
def __init__(self, name, attr, unit=None, device_class=None, entity_category=None, source=None, val_to_send=None,
icon=None) -> None:
super().__init__(name, attr, device_class, entity_category, None, source)
self.val_to_send = val_to_send
self.icon = icon
def setup(self, data: ElectroluxLibraryEntity):
return self
class Appliance:
brand: str
device: str
entities: []
def __init__(self, name, pnc_id, brand, model) -> None:
self.model = model
self.pnc_id = pnc_id
self.name = name
self.brand = brand
def get_entity(self, entity_type, entity_attr, entity_source, val_to_send):
return next(
entity
for entity in self.entities
if
entity.attr == entity_attr and entity.entity_type == entity_type and entity.source == entity_source and entity.val_to_send == val_to_send
)
def setup(self, data: ElectroluxLibraryEntity):
entities = [
ApplianceBinary(
name=data.get_name(),
attr='status',
device_class=BinarySensorDeviceClass.CONNECTIVITY,
entity_category=EntityCategory.DIAGNOSTIC,
source='APL',
),
ApplianceSensor(
name=f"{data.get_name()} SSID",
attr='Ssid',
entity_category=EntityCategory.DIAGNOSTIC,
source='NIU',
),
ApplianceSensor(
name=f"{data.get_name()} {data.get_sensor_name('LinkQualityIndicator', 'NIU')}",
attr='LinkQualityIndicator',
field='numberValue',
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
unit="dBm",
entity_category=EntityCategory.DIAGNOSTIC,
source='NIU',
),
]
sources = data.sources_list()
for src in sources:
for sensor_type, sensors_list in sensors.items():
for sensorName, params in sensors_list.items():
entities.append(
ApplianceSensor(
name=f"{data.get_name()} {data.get_sensor_name(sensorName, src)}{data.get_suffix(sensorName, src)}",
attr=sensorName,
field=params[0],
device_class=params[1],
entity_category=sensor_type,
unit=params[2],
source=src,
)
)
for sensor_type, sensors_list in sensors_binary.items():
for sensorName, params in sensors_list.items():
entities.append(
ApplianceBinary(
name=f"{data.get_name()} {data.get_sensor_name(sensorName, src)}{data.get_suffix(sensorName, src)}",
attr=sensorName,
field=params[0],
device_class=params[1],
entity_category=sensor_type,
invert=params[2],
source=src,
)
)
for key, command in data.commands_list(src).items():
entities.append(
ApplianceButton(
name=f"{data.get_name()} {data.get_command_name(command)}{data.get_suffix('ExecuteCommand', src)}",
attr='ExecuteCommand',
val_to_send=key,
source=src,
icon=icon_mapping.get(key, "mdi:gesture-tap-button"),
)
)
self.entities = [
entity.setup(data)
for entity in entities if data.value_exists(entity.attr, entity.source)
]
class Appliances:
def __init__(self, found_appliances) -> None:
self.found_appliances = found_appliances
def get_appliance(self, pnc_id):
return self.found_appliances.get(pnc_id, None)
@@ -0,0 +1,30 @@
"""Binary sensor platform for Electrolux Status."""
from homeassistant.components.binary_sensor import BinarySensorEntity
from .entity import ElectroluxStatusEntity
from .const import BINARY_SENSOR
from .const import DOMAIN
async def async_setup_entry(hass, entry, async_add_devices):
"""Setup binary sensor platform."""
coordinator = hass.data[DOMAIN][entry.entry_id]
appliances = coordinator.data.get('appliances', None)
if appliances is not None:
for appliance_id, appliance in appliances.found_appliances.items():
async_add_devices(
[
ElectroluxBinarySensor(coordinator, entry, appliance_id, entity.entity_type, entity.attr, entity.source)
for entity in appliance.entities if entity.entity_type == BINARY_SENSOR
]
)
class ElectroluxBinarySensor(ElectroluxStatusEntity, BinarySensorEntity):
"""Electrolux Status binary_sensor class."""
@property
def is_on(self):
"""Return true if the binary_sensor is on."""
return self.get_entity.state
@@ -0,0 +1,30 @@
"""Button platform for Electrolux Status."""
from homeassistant.components.button import ButtonEntity
from .entity import ElectroluxButtonEntity
from .const import BUTTON
from .const import DOMAIN
async def async_setup_entry(hass, entry, async_add_devices):
"""Setup button platform."""
coordinator = hass.data[DOMAIN][entry.entry_id]
appliances = coordinator.data.get('appliances', None)
if appliances is not None:
for appliance_id, appliance in appliances.found_appliances.items():
async_add_devices(
[
ElectroluxButton(coordinator, entry, appliance_id, entity.entity_type, entity.attr, entity.source, entity.val_to_send, entity.icon)
for entity in appliance.entities if entity.entity_type == BUTTON
]
)
class ElectroluxButton(ElectroluxButtonEntity, ButtonEntity):
"""Electrolux Status button class."""
async def async_press(self) -> None:
if self.entity_attr == "ExecuteCommand":
await self.hass.async_add_executor_job(self.coordinator.api.setHacl, self.get_appliance.pnc_id, "0x0403", self.val_to_send, self.entity_source)
@@ -0,0 +1,169 @@
"""Adds config flow for Electrolux Status."""
import logging
import homeassistant.helpers.config_validation as cv
from homeassistant.data_entry_flow import FlowResult
from homeassistant.helpers.selector import selector
import voluptuous as vol
from homeassistant import config_entries
from homeassistant.core import callback
from typing import Mapping, Any
from .pyelectroluxconnect_util import pyelectroluxconnect_util
from .const import CONF_PASSWORD, CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL, CONF_REGION
from .const import CONF_LANGUAGE, DEFAULT_LANGUAGE
from .const import CONF_USERNAME
from .const import DOMAIN
from .const import languages
_LOGGER = logging.getLogger(__name__)
class ElectroluxStatusFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
"""Config flow for Electrolux Status."""
VERSION = 1
CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_POLL
def __init__(self):
"""Initialize."""
self._errors = {}
async def async_step_user(self, user_input=None):
"""Handle a flow initialized by the user."""
self._errors = {}
# Uncomment the next 2 lines if only a single instance of the integration is allowed:
# if self._async_current_entries():
# return self.async_abort(reason="single_instance_allowed")
if user_input is not None:
valid = await self._test_credentials(
user_input[CONF_USERNAME],
user_input[CONF_PASSWORD],
user_input[CONF_REGION],
)
if valid:
return self.async_create_entry(
title=user_input[CONF_USERNAME], data=user_input
)
else:
self._errors["base"] = "auth"
return await self._show_config_form(user_input)
return await self._show_config_form(user_input)
async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult:
"""Handle configuration by re-auth."""
return await self.async_step_reauth_validate(entry_data)
async def async_step_reauth_validate(self, user_input=None):
"""Handle reauth and validation."""
self._errors = {}
if user_input is not None:
valid = await self._test_credentials(
user_input[CONF_USERNAME],
user_input[CONF_PASSWORD],
user_input[CONF_REGION],
)
if valid:
return self.async_create_entry(
title=user_input[CONF_USERNAME], data=user_input
)
else:
self._errors["base"] = "auth"
return await self._show_config_form(user_input)
return await self._show_config_form(user_input)
@staticmethod
@callback
def async_get_options_flow(config_entry):
return ElectroluxStatusOptionsFlowHandler(config_entry)
async def _show_config_form(self, user_input): # pylint: disable=unused-argument
"""Show the configuration form to edit location data."""
data_schema = {
vol.Required(CONF_USERNAME): str,
vol.Required(CONF_PASSWORD): str,
vol.Optional(CONF_REGION, default="EMEA"): selector({
"select": {
"options": ["APAC", "EMEA", "LATAM", "NA", "Frigidaire"],
"mode": "dropdown",
}
}),
vol.Optional(CONF_LANGUAGE, default = DEFAULT_LANGUAGE): selector({
"select": {
"options": list(languages.keys()),
"mode": "dropdown"}
}),
}
if self.show_advanced_options:
data_schema = {
vol.Required(CONF_USERNAME): str,
vol.Required(CONF_PASSWORD): str,
vol.Optional(CONF_REGION, default="EMEA"): selector({
"select": {
"options": ["APAC", "EMEA", "LATAM", "NA", "Frigidaire"],
"mode": "dropdown"}
}),
vol.Optional(CONF_LANGUAGE, default = DEFAULT_LANGUAGE): selector({
"select": {
"options": list(languages.keys()),
"mode": "dropdown"}
}),
}
return self.async_show_form(
step_id="user",
data_schema=vol.Schema(data_schema),
errors=self._errors,
)
async def _test_credentials(self, username, password, region):
"""Return true if credentials is valid."""
try:
client = pyelectroluxconnect_util.get_session(username, password, region)
await self.hass.async_add_executor_job(client.login)
return True
except Exception as inst: # pylint: disable=broad-except
_LOGGER.exception(inst)
return False
class ElectroluxStatusOptionsFlowHandler(config_entries.OptionsFlow):
"""Config flow options handler for Electrolux Status."""
def __init__(self, config_entry):
"""Initialize HACS options flow."""
self.config_entry = config_entry
self.options = dict(config_entry.options)
async def async_step_init(self, user_input=None): # pylint: disable=unused-argument
"""Manage the options."""
return await self.async_step_user()
async def async_step_user(self, user_input=None):
"""Handle a flow initialized by the user."""
if user_input is not None:
self.options.update(user_input)
return await self._update_options()
return self.async_show_form(
step_id="user",
data_schema=vol.Schema(
{
vol.Optional(
CONF_SCAN_INTERVAL,
default=self.config_entry.options.get(
CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL
),
): cv.positive_int,
}
),
)
async def _update_options(self):
"""Update config entry options."""
return self.async_create_entry(
title=self.config_entry.data.get(CONF_USERNAME), data=self.options
)
@@ -0,0 +1,159 @@
"""The electrolux Status constants."""
from homeassistant.const import TIME_MINUTES, TEMP_CELSIUS, PERCENTAGE, POWER_WATT
from homeassistant.components.sensor import SensorDeviceClass
from homeassistant.components.binary_sensor import BinarySensorDeviceClass
from homeassistant.helpers.entity import EntityCategory
# Base component constants
NAME = "Elettrolux status"
DOMAIN = "electrolux_status"
DOMAIN_DATA = f"{DOMAIN}_data"
# Icons
ICON = "mdi:format-quote-close"
# Device classes
BINARY_SENSOR_DEVICE_CLASS = "connectivity"
# Platforms
BINARY_SENSOR = "binary_sensor"
SENSOR = "sensor"
BUTTON = "button"
PLATFORMS = [BINARY_SENSOR, SENSOR, BUTTON]
# Configuration and options
CONF_ENABLED = "enabled"
CONF_USERNAME = "username"
CONF_PASSWORD = "password"
CONF_REGION = "region"
CONF_LANGUAGE = "language"
CONF_SCAN_INTERVAL = "scan_interval"
# Defaults
DEFAULT_NAME = DOMAIN
DEFAULT_SCAN_INTERVAL = 30
DEFAULT_REGION = "emea"
DEFAULT_LANGUAGE = "English"
sensors = {
# Sensor Name: [value field, device class, unit]
# Device state sensors
None: {
"TimeToEnd": [None, None, TIME_MINUTES],
"RunningTime": [None, None, TIME_MINUTES],
"CyclePhase": [None, None, None],
"CycleSubPhase": ["string", None, None],
"ApplianceState": [None, None, None],
"DisplayTemperature": ["container", SensorDeviceClass.TEMPERATURE, TEMP_CELSIUS],
"DisplayFoodProbeTemperature": ["container", SensorDeviceClass.TEMPERATURE, TEMP_CELSIUS],
"SensorTemperature": ["container", SensorDeviceClass.TEMPERATURE, TEMP_CELSIUS],
"DefrostTemperature": ["container", SensorDeviceClass.TEMPERATURE, TEMP_CELSIUS],
"TargetMicrowavePower": ["numberValue", SensorDeviceClass.ENERGY, POWER_WATT],
"OvenProcessIdentifier": ["valueTransl", None, None],
},
# Device config sensors
EntityCategory.CONFIG : {
"RemoteControl": [None, None, None],
"DefaultExtraRinse": ["numberValue", None, None],
"TargetTemperature": ["container", SensorDeviceClass.TEMPERATURE, TEMP_CELSIUS],
"StartTime": [None, None, TIME_MINUTES],
"AnalogTemperature": ["numberValue", SensorDeviceClass.TEMPERATURE, TEMP_CELSIUS],
"WaterSoftenerMode": [None, None, None],
"SteamValue": ["valTransl", None, None],
"ELUXTimeManagerLevel": ["valTransl", None, None],
"AnalogSpinSpeed": ["valTransl", None, None],
"WaterTankWarningMode": [None, None, None],
"DryingTime": [None, None, None],
"HumidityTarget": ["valTransl", None, None],
"AntiCreaseValue": [None, None, None],
"DrynessValue": ["valTransl", None, None],
"ProgramUID": ["valTransl", None, None]
},
# Device diagnostic sensors
EntityCategory.DIAGNOSTIC : {
"SensorHumidity": ["numberValue", SensorDeviceClass.HUMIDITY, PERCENTAGE, None],
"AmbientTemperature": ["container", SensorDeviceClass.TEMPERATURE, TEMP_CELSIUS, None],
"ApplianceTotalWorkingTime": [None, None, TIME_MINUTES],
"TotalCycleCounter": [None, None, None],
"RinseAidLevel": [None, None, None],
"WaterHardness": ["valueTransl", None, None],
"FCTotalWashCyclesCount": [None, None, None],
"FCTotalWashingTime": [None, None, None],
"ApplianceMode": [None, None, None],
}
}
sensors_binary = {
# Sensor Name: [value field, device class, invert]
# Device state sensors
None: {
"DoorState": ["numberValue", BinarySensorDeviceClass.DOOR, None],
"DoorLock": ["numberValue", BinarySensorDeviceClass.LOCK, True],
"UiLockMode": ["numberValue", None, None],
},
# Device config sensors
EntityCategory.CONFIG : {
"EndOfCycleSound": ["numberValue", None, None],
"DefaultSoftPlus": ["numberValue", None, None],
"PreWashPhase": ["numberValue", None, None],
"RinseHold": ["numberValue", None, None],
"NightCycle": ["numberValue", None, None],
"Stain": ["numberValue", None, None],
"WMEconomy": ["numberValue", None, None],
"AnticreaseWSteam": ["numberValue", None, None],
"AnticreaseNoSteam": ["numberValue", None, None],
"Refresh": ["numberValue", None, None],
"ReversePlus": ["numberValue", None, None],
"Delicate": ["numberValue", None, None],
"TDEnergyLabel": ["numberValue", None, None],
"TDEconomy_Eco": ["numberValue", None, None],
"TDEconomy_Night": ["numberValue", None, None],
},
# Device diagnostic sensors
EntityCategory.DIAGNOSTIC : {
"TankA_reserve": ["numberValue", BinarySensorDeviceClass.PROBLEM, False],
"TankB_reserve": ["numberValue", BinarySensorDeviceClass.PROBLEM, False],
}
}
icon_mapping = {
"0": "mdi:power-off",
"1": "mdi:power-on",
"2": "mdi:play",
"3": "mdi:stop",
"4": "mdi:pause",
"5": "mdi:play-pause",
}
# List of supported Mobile App languages (from https://emea-production.api.electrolux.net/masterdata-service/api/v1/languages)
languages = {
"български": "bul",
"český": "ces",
"Dansk": "dan",
"Deutsch": "deu",
"ελληνικός": "ell",
"English": "eng",
"eesti": "est",
"Soome": "fin",
"Français": "fra",
"Hrvatski": "hrv",
"magyar": "hun",
"Italiano": "ita",
"lettone": "lav",
"lituano": "lit",
"Luxembourgish": "ltz",
"nederlands": "nld",
"Norsk": "nor",
"Polski": "pol",
"Português": "por",
"Română": "ron",
"rusesc": "rus",
"slovenský": "slk",
"slovinský": "slv",
"Español": "spa",
"Svenska": "swe",
"Türk": "tur",
"Ukrayna": "ukr",
}
@@ -0,0 +1,130 @@
from homeassistant.components.sensor import ENTITY_ID_FORMAT
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from homeassistant.helpers.entity import Entity
from . import ElectroluxStatusDataUpdateCoordinator
from .api import Appliance, ApplianceEntity
from .const import DOMAIN
class ElectroluxStatusEntity(CoordinatorEntity):
def __init__(self, coordinator: ElectroluxStatusDataUpdateCoordinator, config_entry, pnc_id, entity_type, entity_attr, entity_source):
super().__init__(coordinator)
self.api = coordinator.api
self.entity_attr = entity_attr
self.entity_type = entity_type
self.entity_source = entity_source
self.config_entry = config_entry
self.pnc_id = pnc_id
self.entity_id = ENTITY_ID_FORMAT.format(f"{self.get_appliance.brand}_{self.get_appliance.name}_{self.entity_source}_{self.entity_attr}")
@property
def name(self):
"""Return the name of the sensor."""
return self.get_entity.name
@property
def get_entity(self) -> ApplianceEntity:
return self.get_appliance.get_entity(self.entity_type, self.entity_attr, self.entity_source, None)
@property
def get_appliance(self) -> Appliance:
return self.coordinator.data['appliances'].get_appliance(self.pnc_id)
@property
def unique_id(self):
"""Return a unique ID to use for this entity."""
return f"{self.config_entry.entry_id}-{self.entity_attr}-{self.entity_source}-{self.pnc_id}"
@property
def device_info(self):
return {
"identifiers": {(DOMAIN, self.get_appliance.name)},
"name": self.get_appliance.name,
"model": self.get_appliance.model,
"manufacturer": self.get_appliance.brand,
}
@property
def extra_state_attributes(self):
"""Return the state attributes."""
return {
"integration": DOMAIN,
}
@property
def device_class(self):
"""Return the device class of the sensor."""
return self.get_entity.device_class
@property
def entity_category(self):
"""Return the entity category of the sensor."""
return self.get_entity.entity_category
class ElectroluxButtonEntity(Entity):
def __init__(self, coordinator, config_entry, pnc_id, entity_type, entity_attr, entity_source, val_to_send, icon):
self.coordinator = coordinator
self.entity_attr = entity_attr
self.entity_type = entity_type
self.entity_source = entity_source
self.config_entry = config_entry
self.pnc_id = pnc_id
self.val_to_send = val_to_send
self.button_icon = icon
self.entity_id = ENTITY_ID_FORMAT.format(f"{self.get_appliance.brand}_{self.get_appliance.name}_{self.entity_source}_{self.entity_attr}_{self.val_to_send}")
@property
def name(self):
"""Return the name of the button."""
return self.get_entity.name
@property
def get_entity(self) -> ApplianceEntity:
return self.get_appliance.get_entity(self.entity_type, self.entity_attr, self.entity_source, self.val_to_send)
@property
def get_appliance(self) -> Appliance:
return self.coordinator.data['appliances'].get_appliance(self.pnc_id)
@property
def unique_id(self):
"""Return a unique ID to use for this entity."""
return f"{self.config_entry.entry_id}-{self.val_to_send}-{self.entity_attr}-{self.entity_source}-{self.pnc_id}"
@property
def device_info(self):
return {
"identifiers": {(DOMAIN, self.get_appliance.name)},
"name": self.get_appliance.name,
"model": self.get_appliance.model,
"manufacturer": self.get_appliance.brand,
}
@property
def extra_state_attributes(self):
"""Return the state attributes."""
return {
"integration": DOMAIN,
}
@property
def device_class(self):
"""Return the device class of the button."""
return self.get_entity.device_class
@property
def entity_category(self):
"""Return the entity category of the button."""
return self.get_entity.entity_category
@property
def icon(self):
"""Return the icon of the button."""
return self.button_icon
@property
def available(self):
# available state should depends on connect state
return True
@@ -0,0 +1,19 @@
{
"domain": "electrolux_status",
"name": "Electrolux Care Integration",
"codeowners": [
"@mauro-midolo"
],
"config_flow": true,
"dependencies": [],
"documentation": "https://github.com/mauro-midolo/homeassistant_electrolux_status",
"homekit": {},
"iot_class": "cloud_polling",
"issue_tracker": "https://github.com/mauro-midolo/homeassistant_electrolux_status/issues",
"requirements": [
"pyelectroluxconnect==0.3.19"
],
"ssdp": [],
"version": "1.2.0",
"zeroconf": []
}
@@ -0,0 +1,9 @@
import pyelectroluxconnect
class pyelectroluxconnect_util:
@staticmethod
def get_session(username, password, region="emea", language ="eng"):
return pyelectroluxconnect.Session(
username, password, deviceId="ElectroluxHomeAssistant", language=language, region=region,
)
@@ -0,0 +1,37 @@
from typing import cast
from .api import ApplianceSensor
from .const import DOMAIN
from .const import SENSOR
from .entity import ElectroluxStatusEntity
from . import ElectroluxStatusDataUpdateCoordinator
from homeassistant.components.sensor import SensorEntity
async def async_setup_entry(hass, entry, async_add_devices):
"""Setup sensor platform."""
coordinator: ElectroluxStatusDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]
appliances = coordinator.data.get('appliances', None)
if appliances is not None:
for appliance_id, appliance in appliances.found_appliances.items():
async_add_devices(
[
ElectroluxStatusSensor(coordinator, entry, appliance_id, entity.entity_type, entity.attr, entity.source)
for entity in appliance.entities if entity.entity_type == SENSOR
]
)
class ElectroluxStatusSensor(ElectroluxStatusEntity, SensorEntity):
"""Electrolux Status Sensor class."""
@property
def native_value(self):
"""Return the state of the sensor."""
return self.get_entity.state
@property
def native_unit_of_measurement(self):
return cast(ApplianceSensor, self.get_entity).unit
@@ -0,0 +1,31 @@
{
"config": {
"step": {
"user": {
"title": "Electrolux Status",
"description": "If you need help with the configuration have a look here: https://github.com/mauro-midolo/homeassistant_electrolux_status",
"data": {
"username": "Username",
"password": "Password",
"region": "Region (emea, apac, na, latam or frigidaire)",
"language": "Electrolux provided sensor names and values language"
}
}
},
"error": {
"auth": "Username/Password is wrong."
},
"abort": {
"single_instance_allowed": "Only a single instance is allowed."
}
},
"options": {
"step": {
"user": {
"data": {
"scan_interval": "API update interval (seconds)"
}
}
}
}
}
@@ -0,0 +1,31 @@
{
"config": {
"step": {
"user": {
"title": "Electrolux Status",
"description": "Se hai bisogno di aiuto con la configurazione, dai un'occhiata qui: https://github.com/mauro-midolo/homeassistant_electrolux_status",
"data": {
"username": "Username",
"password": "Password",
"region": "Regione (emea, apac, na, latam o frigidaire)",
"language": "Lingua dei nomi e dei valori dei sensori forniti da Electrolux"
}
}
},
"error": {
"auth": "Username o password errati."
},
"abort": {
"single_instance_allowed": "È consentita una sola istanza."
}
},
"options": {
"step": {
"user": {
"data": {
"scan_interval": "Intervallo di aggiornamento API in secondi"
}
}
}
}
}
@@ -0,0 +1,31 @@
{
"config": {
"step": {
"user": {
"title": "Electrolux Status",
"description": "Jeśli potrzebujesz pomocy przy konfiguracji, zerknij tutaj: https://github.com/mauro-midolo/homeassistant_electrolux_status",
"data": {
"username": "Nazwa użytkownika",
"password": "Hasło",
"region": "Region (emea, apac, na, latam or frigidaire)",
"language": "Język nazw czujników i wartości, dostarczony przez Electrolux."
}
}
},
"error": {
"auth": "Nazwa użytkownika lub hasło jest nieprawidłowe."
},
"abort": {
"single_instance_allowed": "Dozwolona jest tylko jedna instancja."
}
},
"options": {
"step": {
"user": {
"data": {
"scan_interval": "Interwał aktualizacji API (sekundy)"
}
}
}
}
}
@@ -0,0 +1,31 @@
{
"config": {
"step": {
"user": {
"title": "Electrolux Status",
"description": "Ak potrebujete pomoc s konfiguráciou, pozrite sa sem:https://github.com/mauro-midolo/homeassistant_electrolux_status",
"data": {
"username": "Užívateľské meno",
"password": "Heslo",
"region": "Región (emea, apac, na, latam alebo frigidaire)",
"language": "Electrolux poskytol názvy senzorov a jazyk hodnôt"
}
}
},
"error": {
"auth": "Používateľské meno/heslo je nesprávne."
},
"abort": {
"single_instance_allowed": "Povolený je len jeden prípad."
}
},
"options": {
"step": {
"user": {
"data": {
"scan_interval": "Interval aktualizácie API (sekundy)"
}
}
}
}
}
@@ -0,0 +1,106 @@
"""Support for Gardena Smart System devices."""
import asyncio
import logging
from gardena.exceptions.authentication_exception import AuthenticationException
from gardena.smart_system import SmartSystem
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
CONF_CLIENT_ID,
CONF_CLIENT_SECRET,
EVENT_HOMEASSISTANT_STOP,
)
from homeassistant.core import HomeAssistant
from oauthlib.oauth2.rfc6749.errors import (
AccessDeniedError,
InvalidClientError,
MissingTokenError,
)
from .const import (
DOMAIN,
GARDENA_LOCATION,
GARDENA_SYSTEM,
)
_LOGGER = logging.getLogger(__name__)
PLATFORMS = ["vacuum", "sensor", "switch", "binary_sensor"]
async def async_setup(hass: HomeAssistant, config: dict):
"""Set up the Gardena Smart System integration."""
if DOMAIN not in hass.data:
hass.data[DOMAIN] = {}
return True
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
_LOGGER.debug("Setting up Gardena Smart System component")
gardena_system = GardenaSmartSystem(
hass,
client_id=entry.data[CONF_CLIENT_ID],
client_secret=entry.data[CONF_CLIENT_SECRET],
)
try:
await gardena_system.start()
except AccessDeniedError as ex:
_LOGGER.error('Got Access Denied Error when setting up Gardena Smart System: %s', ex)
return False
except InvalidClientError as ex:
_LOGGER.error('Got Invalid Client Error when setting up Gardena Smart System: %s', ex)
return False
except MissingTokenError as ex:
_LOGGER.error('Got Missing Token Error when setting up Gardena Smart System: %s', ex)
return False
hass.data[DOMAIN][GARDENA_SYSTEM] = gardena_system
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, lambda event: hass.async_create_task(gardena_system.stop()))
for component in PLATFORMS:
hass.async_create_task(
hass.config_entries.async_forward_entry_setup(entry, component))
_LOGGER.debug("Gardena Smart System component setup finished")
return True
class GardenaSmartSystem:
"""A Gardena Smart System wrapper class."""
def __init__(self, hass, client_id, client_secret):
"""Initialize the Gardena Smart System."""
self._hass = hass
self.smart_system = SmartSystem(
client_id=client_id,
client_secret=client_secret)
async def start(self):
try:
_LOGGER.debug("Starting GardenaSmartSystem")
await self.smart_system.authenticate()
await self.smart_system.update_locations()
if len(self.smart_system.locations) < 1:
_LOGGER.error("No locations found")
raise Exception("No locations found")
# currently gardena supports only one location and gateway, so we can take the first
location = list(self.smart_system.locations.values())[0]
_LOGGER.debug(f"Using location: {location.name} ({location.id})")
await self.smart_system.update_devices(location)
self._hass.data[DOMAIN][GARDENA_LOCATION] = location
_LOGGER.debug("Starting GardenaSmartSystem websocket")
asyncio.create_task(self.smart_system.start_ws(self._hass.data[DOMAIN][GARDENA_LOCATION]))
_LOGGER.debug("Websocket thread launched !")
except AuthenticationException as ex:
_LOGGER.error(
f"Authentication failed : {ex.message}. You may need to check your token or create a new app in the gardena api and use the new token.")
async def stop(self):
_LOGGER.debug("Stopping GardenaSmartSystem")
await self.smart_system.quit()

Some files were not shown because too many files have changed in this diff Show More