initial commit
This commit is contained in:
@@ -0,0 +1 @@
|
|||||||
|
2023.11.2
|
||||||
@@ -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-----
|
||||||
@@ -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
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"version": 1,
|
||||||
|
"minor_version": 1,
|
||||||
|
"key": "application_credentials",
|
||||||
|
"data": {
|
||||||
|
"items": []
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
@@ -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"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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": {}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"version": 1,
|
||||||
|
"minor_version": 1,
|
||||||
|
"key": "core.analytics",
|
||||||
|
"data": {
|
||||||
|
"preferences": {},
|
||||||
|
"onboarded": true,
|
||||||
|
"uuid": null
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"version": 1,
|
||||||
|
"minor_version": 1,
|
||||||
|
"key": "core.logger",
|
||||||
|
"data": {
|
||||||
|
"logs": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"version": 1,
|
||||||
|
"minor_version": 1,
|
||||||
|
"key": "core.uuid",
|
||||||
|
"data": {
|
||||||
|
"uuid": "d23214b4d35f41ea952f92e345af2bcd"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -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
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"version": 1,
|
||||||
|
"minor_version": 1,
|
||||||
|
"key": "http.auth",
|
||||||
|
"data": {
|
||||||
|
"content_user": "3044f6b530044a0fbfa1f21ecb939be5"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"version": 1,
|
||||||
|
"minor_version": 1,
|
||||||
|
"key": "input_boolean",
|
||||||
|
"data": {
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"name": "Alok Home",
|
||||||
|
"icon": "mdi:cellphone",
|
||||||
|
"id": "alok_home"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"version": 1,
|
||||||
|
"minor_version": 1,
|
||||||
|
"key": "input_button",
|
||||||
|
"data": {
|
||||||
|
"items": []
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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 "
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"version": 1,
|
||||||
|
"minor_version": 1,
|
||||||
|
"key": "mobile_app",
|
||||||
|
"data": {
|
||||||
|
"deleted_ids": [
|
||||||
|
"871f7587ec3fbb7252a8b64a73a48942300786cfd440493b438c400682921435",
|
||||||
|
"351167e82916c56e0b1fc82631f7f4d9ccd30e6030cb0e2ce8e0dd32e997441d",
|
||||||
|
"b8cff991e8bf47345320c5345a3b2449ec1ac02ded399f2f903217cdcedf59eb",
|
||||||
|
"cf9c9cb834ed91fa62c0e46f03718712fb3e578de94b2a325fe982425a167c39",
|
||||||
|
"654634ef85b1a07c5bb613e704179ecb0292d51d67acc55a6f2ab76028bf91d0",
|
||||||
|
"1fbfc3733f716fccb5a1af1b672af15a6317e826ae968d37f5fd79b278e64a62"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"version": 4,
|
||||||
|
"minor_version": 1,
|
||||||
|
"key": "onboarding",
|
||||||
|
"data": {
|
||||||
|
"done": [
|
||||||
|
"user",
|
||||||
|
"core_config",
|
||||||
|
"analytics",
|
||||||
|
"integration"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
@@ -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
|
||||||
@@ -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)
|
||||||
@@ -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()
|
||||||
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user