diff --git a/.HA_VERSION b/.HA_VERSION index 8c53e4e..c107ae9 100644 --- a/.HA_VERSION +++ b/.HA_VERSION @@ -1 +1 @@ -0.107.7 \ No newline at end of file +0.118.4 \ No newline at end of file diff --git a/.gitignore b/.gitignore index 5c8486c..f106a7f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,7 @@ -/.storage/ -/.vscode/ /.cloud/ -*.code-workspace *.js.gz *.log *.db +.gitsecret/keys/random_seed +!*.secret +secrets.yaml diff --git a/.gitsecret/keys/pubring.kbx b/.gitsecret/keys/pubring.kbx new file mode 100644 index 0000000..c207002 Binary files /dev/null and b/.gitsecret/keys/pubring.kbx differ diff --git a/.gitsecret/keys/pubring.kbx~ b/.gitsecret/keys/pubring.kbx~ new file mode 100644 index 0000000..b0d9e91 Binary files /dev/null and b/.gitsecret/keys/pubring.kbx~ differ diff --git a/.gitsecret/keys/trustdb.gpg b/.gitsecret/keys/trustdb.gpg new file mode 100644 index 0000000..bf0b97f Binary files /dev/null and b/.gitsecret/keys/trustdb.gpg differ diff --git a/.gitsecret/paths/mapping.cfg b/.gitsecret/paths/mapping.cfg new file mode 100644 index 0000000..03cdeec --- /dev/null +++ b/.gitsecret/paths/mapping.cfg @@ -0,0 +1 @@ +secrets.yaml diff --git a/.storage/auth b/.storage/auth new file mode 100644 index 0000000..0936a04 --- /dev/null +++ b/.storage/auth @@ -0,0 +1,1073 @@ +{ + "version": 1, + "key": "auth", + "data": { + "users": [ + { + "id": "5dc5beb72dbc49d59080804cea6f026c", + "group_ids": [ + "system-admin" + ], + "is_owner": false, + "is_active": true, + "name": "Hass.io", + "system_generated": true + }, + { + "id": "99685dc45d9f40dc8183e11ce5128038", + "group_ids": [ + "system-admin" + ], + "is_owner": true, + "is_active": true, + "name": "sysadmin", + "system_generated": false + }, + { + "id": "5270c6dbb87642d29fb41212df19da0c", + "group_ids": [ + "system-users" + ], + "is_owner": false, + "is_active": true, + "name": "Trish", + "system_generated": false + }, + { + "id": "4b1c5a6ccf0e4be786b2f3014ce38f07", + "group_ids": [ + "system-users" + ], + "is_owner": false, + "is_active": true, + "name": "irrigation", + "system_generated": false + }, + { + "id": "13f54fb27e7d4b95b2d0143f16b432ab", + "group_ids": [ + "system-users" + ], + "is_owner": false, + "is_active": true, + "name": "test", + "system_generated": false + } + ], + "groups": [ + { + "id": "system-admin", + "name": "Administrators" + }, + { + "id": "system-users", + "name": "Users" + }, + { + "id": "system-read-only", + "name": "Read Only" + } + ], + "credentials": [ + { + "id": "9517ce6555254cc98f5cc02075cc76de", + "user_id": "99685dc45d9f40dc8183e11ce5128038", + "auth_provider_type": "homeassistant", + "auth_provider_id": null, + "data": { + "username": "sysadmin" + } + }, + { + "id": "a3de8c3e5e024eb189a8a3611b498bdf", + "user_id": "99685dc45d9f40dc8183e11ce5128038", + "auth_provider_type": "trusted_networks", + "auth_provider_id": null, + "data": { + "user_id": "99685dc45d9f40dc8183e11ce5128038" + } + }, + { + "id": "5307d1a23c074afd997249f76790b4d1", + "user_id": "5270c6dbb87642d29fb41212df19da0c", + "auth_provider_type": "homeassistant", + "auth_provider_id": null, + "data": { + "username": "trish" + } + }, + { + "id": "28f47788b547482fb4e862894a2136ea", + "user_id": "4b1c5a6ccf0e4be786b2f3014ce38f07", + "auth_provider_type": "homeassistant", + "auth_provider_id": null, + "data": { + "username": "irrigation" + } + }, + { + "id": "bb354430270f473083d53e3d87141e7e", + "user_id": "13f54fb27e7d4b95b2d0143f16b432ab", + "auth_provider_type": "homeassistant", + "auth_provider_id": null, + "data": { + "username": "test" + } + } + ], + "refresh_tokens": [ + { + "id": "c3c130e79d924d2b9332bbbfaa568637", + "user_id": "5dc5beb72dbc49d59080804cea6f026c", + "client_id": null, + "client_name": null, + "client_icon": null, + "token_type": "system", + "created_at": "2020-03-04T21:26:52.295437+00:00", + "access_token_expiration": 1800.0, + "token": "552d438c538636357742acea47d8a022fcaab47c74a20435614f69540088948934d66767b0348eeec4c4de979691f12f3ce28c20c3854dfd0b5b263efd07f40f", + "jwt_key": "0ed3886ff667ce6e5f17c621f1c8b8c1f14fe258f2669022c078f14e5e5c3906a8b37ad6c949aa6c1af800a5fbc94fd6e581de258a01c884e368cf6d0d5c741f", + "last_used_at": "2020-12-09T03:15:11.893278+00:00", + "last_used_ip": "172.30.32.2" + }, + { + "id": "68908cc7ad2542b592072bcb66373d0a", + "user_id": "99685dc45d9f40dc8183e11ce5128038", + "client_id": "http://trantor.kebler.net:8123/", + "client_name": null, + "client_icon": null, + "token_type": "normal", + "created_at": "2020-03-04T21:38:53.214446+00:00", + "access_token_expiration": 1800.0, + "token": "64474b9bff00a999d32e7289a4fb83a3b4c23c254d6003a3142d9a4c9f870a8d745c54f224a4ab855a5f924b8ea1c9ddd1f69b69dffa625693e2589bd415f247", + "jwt_key": "c310f8632678d72405a54e393af1cd416b3993e6136179db94227fd31c12b1660fa9c9f90866e74c79367c0bb34bcf6088e5610cd7347a4d183d95b4c607b15a", + "last_used_at": "2020-03-04T22:53:33.125756+00:00", + "last_used_ip": "10.0.0.115" + }, + { + "id": "f8a48606f80e40f388fbcc4857afc1e7", + "user_id": "99685dc45d9f40dc8183e11ce5128038", + "client_id": "https://ha.kebler.net/", + "client_name": null, + "client_icon": null, + "token_type": "normal", + "created_at": "2020-03-04T21:52:19.229241+00:00", + "access_token_expiration": 1800.0, + "token": "cc9825a2e869f5242c6d4b58e597fa13cb78fc337a2b0a1ba08a8153d4242e90f0e797724e6731936e7024c0f324afa4edf1f8131d7c5eed35fa02ce75826601", + "jwt_key": "96265a3af78c6bb2a3f48cce1c13c3e36e57c646e97e9fc981a8920bd0f7ebe7cb00318f6a48cf1be6e8ff2dea9ea229ffb8859694042f54b8deba90ece29716", + "last_used_at": "2020-03-04T21:52:19.229306+00:00", + "last_used_ip": "10.0.0.3" + }, + { + "id": "4c4b98a76b9f4cd4b12d183cb10dd400", + "user_id": "99685dc45d9f40dc8183e11ce5128038", + "client_id": "https://ha.kebler.net/", + "client_name": null, + "client_icon": null, + "token_type": "normal", + "created_at": "2020-03-04T22:03:10.636697+00:00", + "access_token_expiration": 1800.0, + "token": "3eb272dc0b1de0127fd28c94770242991226f5dfb3a71c0a3b0e6ab3d2894035e434a04efca48a3345bc35a61195aac4c48b9e60d1b77639b76df6a445fd0c91", + "jwt_key": "f063d7465b0dd7f77b987c59584761304186b48cb52dd6ef675c7bee8cf99c458271a56fab239ff67d3feab73f9917b7a079338be297e50c64c9be1d8a71c416", + "last_used_at": "2020-03-05T16:43:06.014398+00:00", + "last_used_ip": "10.0.0.3" + }, + { + "id": "5cd6fe35490b46a18d5882ea936b4282", + "user_id": "99685dc45d9f40dc8183e11ce5128038", + "client_id": "https://auth.homehabit.app", + "client_name": null, + "client_icon": null, + "token_type": "normal", + "created_at": "2020-03-04T23:24:06.335849+00:00", + "access_token_expiration": 1800.0, + "token": "3b578f862380ffa13b66eea0c79560bad957bd175951bd6fc9e0c0cb55c5cd4c62870062cac8ceebd521ee839ecced23dcf467891bd98ce5a30cae9390e67f2f", + "jwt_key": "f5eea8a9d02df911b0c8e3d84f7c787b64c3503afcb5c7bef74843d4ec72b41b6326c2a3ede7f20570c78b53177e5cc5525aeb12cc71b01e34c781abd2353c3e", + "last_used_at": "2020-03-05T15:12:51.684684+00:00", + "last_used_ip": "10.0.0.163" + }, + { + "id": "17dcd0bf125941e598a83a62e32dee98", + "user_id": "99685dc45d9f40dc8183e11ce5128038", + "client_id": "https://ha.kebler.net/", + "client_name": null, + "client_icon": null, + "token_type": "normal", + "created_at": "2020-03-05T03:22:31.187855+00:00", + "access_token_expiration": 1800.0, + "token": "1b86787d3c1972c8525cc93c6ec5d343bf034ac3e1d1327046a2ad60ee6f9a9dfdf78903523b63bc9445384728fc8d870f544bece73b4f94b319ee6529fa74bf", + "jwt_key": "945932d05a8c3ba6ec5d233cf59fae7d93eaa4ef99c2b9a4bc3bef7a8ec85540b955dfb62f27e9bc3dc1b1ddaa0decbc35ec7cbe8b15f3b242635f0eae6c9554", + "last_used_at": "2020-03-05T03:22:31.187982+00:00", + "last_used_ip": "10.0.0.3" + }, + { + "id": "0574511b671f406381e3ebde755b8203", + "user_id": "99685dc45d9f40dc8183e11ce5128038", + "client_id": "https://ha.kebler.net/", + "client_name": null, + "client_icon": null, + "token_type": "normal", + "created_at": "2020-03-05T03:25:03.730397+00:00", + "access_token_expiration": 1800.0, + "token": "d0245d14815b11d58377bc9a57a234df8ea495d9b36eaba4a695203999b6954fb47e7d54a8571ca864b1a3a05ba18cec292ae814ef418d1b33d97207838012e4", + "jwt_key": "ac629ae63694f99ee343a0b92fa5db8d8cc446b473faf8e24500d9fae62c37259087205de336a591a323190d0d8099561367193de765910770215f9c89bd9ec4", + "last_used_at": "2020-03-05T06:32:24.794242+00:00", + "last_used_ip": "10.0.0.3" + }, + { + "id": "ea43f8eac8f1473a8632a56c618f13e5", + "user_id": "99685dc45d9f40dc8183e11ce5128038", + "client_id": "https://ha.kebler.net/", + "client_name": null, + "client_icon": null, + "token_type": "normal", + "created_at": "2020-03-05T21:05:24.179983+00:00", + "access_token_expiration": 1800.0, + "token": "bf9e13e3cf9820d40388f315f1b22a35f85618e75f4fda36fafe7508ecec93273f96b6931da8caccf89068b7b323bfa680864af2360af08486ce280873aa0ee3", + "jwt_key": "5d1a75d21f93982f764c7ebfd86518040109af0c59157e9d221ab91d170f9267d3e40716f140c3b51d62d6409b0a2aa8ba5052b1877b0168a247ee0cadb6f6e2", + "last_used_at": "2020-03-16T06:15:53.900410+00:00", + "last_used_ip": "10.0.0.3" + }, + { + "id": "7113e01c36684b6da26c8f3f9a9e8398", + "user_id": "99685dc45d9f40dc8183e11ce5128038", + "client_id": "https://auth.homehabit.app", + "client_name": null, + "client_icon": null, + "token_type": "normal", + "created_at": "2020-03-05T21:18:03.648806+00:00", + "access_token_expiration": 1800.0, + "token": "eace45ddfba84f410003a81d8549c245a6fa7f8e6d993d8a16233e9227a6a3e90d8f0f674c10769b42958b2ecba05421dcf0d4971f8a97558ecbdb3c30eba931", + "jwt_key": "f65abcf38b58ae06b40d0bdee03f0dfacaa7b8a6161f517e445e6632191d73e7d6302af12709d9d58d1da7b56abea7797265ffa26993e6f6ca00833564dc9979", + "last_used_at": "2020-03-06T14:47:44.660567+00:00", + "last_used_ip": "10.0.2.2" + }, + { + "id": "8a668124c978419092fb161217af7f8a", + "user_id": "99685dc45d9f40dc8183e11ce5128038", + "client_id": "https://home-assistant.io/android", + "client_name": null, + "client_icon": null, + "token_type": "normal", + "created_at": "2020-03-06T06:58:36.198363+00:00", + "access_token_expiration": 1800.0, + "token": "c8ecf0cd2885b994b7bd6624f4864bea056849c869d7182fffdaafe852f58137e78798cf10ce30b898cba83583dfe719e75898635205940132d78fa6977a6dc5", + "jwt_key": "0ba56edf61e172922d626680c780a2eed5abf849695fb2ad55c14bfe49e62fc1a4270cf7e2ff084e146c0517743281f809198a67f5b316dbc22659cb9c8c2a47", + "last_used_at": "2020-10-25T14:53:39.070735+00:00", + "last_used_ip": "10.0.0.2" + }, + { + "id": "d3df5a90f35b4786916524d2a11f8a29", + "user_id": "99685dc45d9f40dc8183e11ce5128038", + "client_id": "https://auth.homehabit.app", + "client_name": null, + "client_icon": null, + "token_type": "normal", + "created_at": "2020-03-06T17:26:51.632065+00:00", + "access_token_expiration": 1800.0, + "token": "67154748b468ae62cff3021f566ce8f1d94d89f7e223b3dfb8432226347f1e1f0ba05674e2e089fa98ee08add0be40d38e70f3af9dc74cf9dbb5f9e93d358cb0", + "jwt_key": "50e901c4783bb84711838442d27a1ea4d44213eea590113bb1154bd69efc18d98cc8892287520a35a5154b087996bdeba2e131697f28bade44e8f5cc75fa6235", + "last_used_at": "2020-12-08T05:25:01.537324+00:00", + "last_used_ip": "10.0.0.2" + }, + { + "id": "44339c66429d4a49ab834e79aa270c29", + "user_id": "99685dc45d9f40dc8183e11ce5128038", + "client_id": "http://10.0.0.4:8234/", + "client_name": null, + "client_icon": null, + "token_type": "normal", + "created_at": "2020-03-07T15:41:00.621806+00:00", + "access_token_expiration": 1800.0, + "token": "a102c00eb250f793c4bf3ae48b6d220126ca92c1a159c81f6c76cbe23b2511052c71d85c431b29927394f4b88f50cc8f0d7d3191e36dda26c84418cd9f3f078b", + "jwt_key": "0731282ee61499c5bddadb95c5d3a3e84f15d0badd6f440b3202aa1f178789a4428afcd9978bec9c406d2f54546cd6a4608c5a990004e203c30d19e850c260d3", + "last_used_at": "2020-03-07T17:00:56.650397+00:00", + "last_used_ip": "10.0.0.3" + }, + { + "id": "c949462aa7b74004af6b5de93c68ef35", + "user_id": "99685dc45d9f40dc8183e11ce5128038", + "client_id": "https://ha.kebler.net/", + "client_name": null, + "client_icon": null, + "token_type": "normal", + "created_at": "2020-03-10T14:39:51.170223+00:00", + "access_token_expiration": 1800.0, + "token": "7fe147fad68448c61ebc9a4b8fb6fc3bc3423fd3c8c5de7e342fcf64a895baab0ed88594bbf8c9a12060b57666e8377c158c8173c0aa8d525966439cb93e4fd3", + "jwt_key": "bec22e1d7799fdd9153343c56e96b55da28ace015e26fe4096692120a10a726579a0ec161bd883619c9e9c27f6eea4c02579409a089a5214aebf5dbc4c50e6f8", + "last_used_at": "2020-03-12T05:40:05.461367+00:00", + "last_used_ip": "10.0.0.3" + }, + { + "id": "cdd00b3839c94c369812d926da41378f", + "user_id": "99685dc45d9f40dc8183e11ce5128038", + "client_id": "https://ha.kebler.net/", + "client_name": null, + "client_icon": null, + "token_type": "normal", + "created_at": "2020-03-12T05:40:19.668366+00:00", + "access_token_expiration": 1800.0, + "token": "f99bce28bb9015992d9e32c037c76560ec3a9cf4360f38f078fccd54cda2d86ed0dab51b3856eec135c85efae5a1b16e2e6981cf0e7fb6efbf5c5e20cb909513", + "jwt_key": "d5fafa8832421cea1d37459b3b7cfe7f929f46743f511a034ebaaac6eecd21404fad9da17732ecc4755a9273c250b00faf8f7b3746bea09679df70bfd363a9a1", + "last_used_at": "2020-05-18T13:28:46.277538+00:00", + "last_used_ip": "10.0.0.2" + }, + { + "id": "cd87e544ddb64cada583ab8aae735168", + "user_id": "99685dc45d9f40dc8183e11ce5128038", + "client_id": "https://ha.kebler.net/", + "client_name": null, + "client_icon": null, + "token_type": "normal", + "created_at": "2020-03-16T06:16:09.319032+00:00", + "access_token_expiration": 1800.0, + "token": "296ad3ce1d7f1e59f966f9a1e5a8dc8f4332b04d1ad26488f43d878c1af919f3b9a9af70e1f4118310b20468b0450a55ee2fada93cf917aeebc8cc29a33765a7", + "jwt_key": "89aa9c1b58750fe76b6f42d2f54157be5b6433a3e332b647132f4bb100dc2f1f90c2f2413aa9479dae6560c6e540b51e89d3934e5310bc903f194c4a90120fd5", + "last_used_at": "2020-03-16T06:16:09.319135+00:00", + "last_used_ip": "10.0.0.3" + }, + { + "id": "5eb2895192504c44ae35113f10745b2d", + "user_id": "99685dc45d9f40dc8183e11ce5128038", + "client_id": "https://ha.kebler.net/", + "client_name": null, + "client_icon": null, + "token_type": "normal", + "created_at": "2020-03-17T01:47:27.638034+00:00", + "access_token_expiration": 1800.0, + "token": "3e1a33470cebe931908d0b3bc7f24b9134da73273562dead8d4661fe0caa7d16d390eacb337581466d1fe58e7d231959e3b77c7a274f6da49f3a5793a8e0eb9a", + "jwt_key": "1f993b8aeccef97dd8b7ab3cf1be968635f6484b5cdc87aed3da942a5906234d73e2a12d426172d45c5cbfabde482b67b485c1b717b0ab77587b1459d0f203ea", + "last_used_at": "2020-03-17T01:47:27.638162+00:00", + "last_used_ip": "10.0.0.3" + }, + { + "id": "a040bf4229c4475da7e38a7ab51f684b", + "user_id": "99685dc45d9f40dc8183e11ce5128038", + "client_id": "https://ha.kebler.net/", + "client_name": null, + "client_icon": null, + "token_type": "normal", + "created_at": "2020-03-17T03:07:17.070700+00:00", + "access_token_expiration": 1800.0, + "token": "33449bcba285ed134667a121519fe37c1cb43a3bc008984f9ac7904e1691f7b8ad31b3da0ffa563c345f1fb5b4858f550d0e5ac1df5e296d9a3a44edd282f466", + "jwt_key": "115c2944fbc923d56ad8a8a747054582a28bc27019f44caef127bcaffa8e17eef652f4111fa54dd16d44c4b05ee538520b07d42fd811d6ecff3f7ac998bffacc", + "last_used_at": "2020-03-17T03:07:17.070831+00:00", + "last_used_ip": "10.0.0.3" + }, + { + "id": "be86646600e3428d8e319e2cab96f43f", + "user_id": "99685dc45d9f40dc8183e11ce5128038", + "client_id": "https://ha.kebler.net/", + "client_name": null, + "client_icon": null, + "token_type": "normal", + "created_at": "2020-03-17T03:08:02.962987+00:00", + "access_token_expiration": 1800.0, + "token": "e9541eb773b7b3091d9bed9ff7913bc594b869e0b26b7d939427d23f76ee9606397a63dc52836c59d2feacfd792f9cce329ac501e7128425518137dfb7692740", + "jwt_key": "f0dbf596e6518825d45737c74517d70b4add5fe26e7fcb95ba286786b4ce190901947ba743fe6afce18ac576b9d207cabe960e617f0c7e279edf8519433dbb02", + "last_used_at": "2020-03-17T03:08:02.963063+00:00", + "last_used_ip": "10.0.0.3" + }, + { + "id": "176b821dff55495ea5820260139f0b50", + "user_id": "99685dc45d9f40dc8183e11ce5128038", + "client_id": "https://ha.kebler.net/", + "client_name": null, + "client_icon": null, + "token_type": "normal", + "created_at": "2020-03-17T03:08:45.178542+00:00", + "access_token_expiration": 1800.0, + "token": "5ea306ab1552457ebe01b7a632f008417ee813749b8c9e8a605abcda6df8145d6922caeca209e9a0fef78487981656434ed6d563a0dd74f6c16538a498591e94", + "jwt_key": "f4bba52687f6c7f9b9094db72b688a5c6083438a8d72ac9aa9cc3e53362cb3211106ec8dcac9de6e09a54aa131a0064a3e67a46d842f1f24d8ab42f0cca321d4", + "last_used_at": "2020-12-08T05:25:00.817801+00:00", + "last_used_ip": "10.0.0.2" + }, + { + "id": "1d9e6369c1eb486593116492a426c528", + "user_id": "99685dc45d9f40dc8183e11ce5128038", + "client_id": "https://ha.kebler.net/", + "client_name": null, + "client_icon": null, + "token_type": "normal", + "created_at": "2020-03-24T23:14:39.371597+00:00", + "access_token_expiration": 1800.0, + "token": "191f3bfc52e874262b4b2472023da600083889f43158afcced7c3b5a08891bfbd65c7d51d53450b006204e9869c639763e1c4f5db0de73e2db36de192cb7888e", + "jwt_key": "addcf210fb2da2196f8d705dad7c16bcba547733e44cfeac7208b1def0d3e6fe1872d451680e5b8d7578a09edf980b8d0813086198af02f25916438451a5f450", + "last_used_at": "2020-03-26T21:31:49.564986+00:00", + "last_used_ip": "10.0.0.3" + }, + { + "id": "a61040b88efb49e8a0a9d5dfbe7f5314", + "user_id": "99685dc45d9f40dc8183e11ce5128038", + "client_id": "https://ha.kebler.net/", + "client_name": null, + "client_icon": null, + "token_type": "normal", + "created_at": "2020-03-27T03:02:01.188121+00:00", + "access_token_expiration": 1800.0, + "token": "49ececeac07606c2dbc8401a8539d40e55f420efa8e3d67593309170721e9875de82f89a6968f58cd11ac4d9e5569588d1f64496b17b797e9eaab2669cafe4a8", + "jwt_key": "e4c96e040d63713817933429eb50dde357ea289d02d1403dcf65ee95c9c8656492daca9fa5ae01b6b0d172562a790cfe9313c2b26c71acb2740911f740fa6129", + "last_used_at": "2020-05-23T01:10:25.412504+00:00", + "last_used_ip": "10.0.0.2" + }, + { + "id": "295a7a18dee4415d9950e99687e9e5f5", + "user_id": "99685dc45d9f40dc8183e11ce5128038", + "client_id": null, + "client_name": "vscode", + "client_icon": null, + "token_type": "long_lived_access_token", + "created_at": "2020-03-27T03:30:16.318851+00:00", + "access_token_expiration": 315360000.0, + "token": "337eb54ec985c524b101e5258e7a6329cbf1bed6e197bc08d146475c83bd74078b48f826ef6e074a335c44c266a785b71d5c76759b79207da4fd197cfdeeed28", + "jwt_key": "f51f8e88b180339dc7b7c296e7a014b5e53fc1af68c8b4ac1e5c94b40a9d9ed6ce8744c154337ad4a89d7560dc6784cf628adfef421074ed7e33a0c9af77224d", + "last_used_at": "2020-03-27T03:30:16.319050+00:00", + "last_used_ip": null + }, + { + "id": "cdc46bf325b445e39b3cbe8d2ec087c2", + "user_id": "99685dc45d9f40dc8183e11ce5128038", + "client_id": "https://ha.kebler.net/", + "client_name": null, + "client_icon": null, + "token_type": "normal", + "created_at": "2020-04-20T20:00:48.378204+00:00", + "access_token_expiration": 1800.0, + "token": "8fa5024cb3bfbf776dea1cbc21e67b241e8d1f34ab7595aa6695375976a0cdbbc21cee15df84e5d38c9163f6f1210506e835c887f8210454e9bcd18d65ad8cbb", + "jwt_key": "3d765206fe3b561243d7ba902851ac1b821bfc4d6d688f6c03b1a9a81a64022187dfc016b4ccae47ce22a17b0cf95ecab0b5e6d74d09f145614881bcdde55453", + "last_used_at": "2020-07-03T23:41:16.726648+00:00", + "last_used_ip": "10.0.0.2" + }, + { + "id": "5ff048a3c6ba4cdd95662a7eccd7e7e7", + "user_id": "99685dc45d9f40dc8183e11ce5128038", + "client_id": "https://ha.kebler.net/", + "client_name": null, + "client_icon": null, + "token_type": "normal", + "created_at": "2020-04-22T03:58:00.767500+00:00", + "access_token_expiration": 1800.0, + "token": "bd450081a5861e5653609548043044ab989b9d4c4378d75c9abb0fb63d84d98944e70c64befc46f775818dd826e86fa9624769d6b2392021cb45e2514b8bb126", + "jwt_key": "9e674e940f3fdedbfa600af816a49ec8300bbe9ecee69d4bff713feb39c3bad07ab0529610b4a546f9b1f87bf9994494f2ebbbfe4fe1919966470500f8213321", + "last_used_at": "2020-07-03T18:20:56.305661+00:00", + "last_used_ip": "10.0.0.2" + }, + { + "id": "7bf20e1c49ec44ad986fc65f412d17d2", + "user_id": "99685dc45d9f40dc8183e11ce5128038", + "client_id": "https://home-assistant.io/android", + "client_name": null, + "client_icon": null, + "token_type": "normal", + "created_at": "2020-04-22T23:46:11.970863+00:00", + "access_token_expiration": 1800.0, + "token": "469513033d12f74aba270a2ca521203ec4636028a426fed4fa08173cfa34ae6ea7e350ce7d71003e7f070417c1201ad48ec1ee69818f6a93f24e952225a28e69", + "jwt_key": "ebba8de5ce660dcbef52abdfec805ba9e8ec704d91a305a4ad447e6c67d808df03a872fc0773c92c201fbd664ef36ed654562163a5c3b4ec8d69140604894be5", + "last_used_at": "2020-04-22T23:46:11.971048+00:00", + "last_used_ip": "10.0.0.3" + }, + { + "id": "4d5febf201554c9385203091f45c6111", + "user_id": "99685dc45d9f40dc8183e11ce5128038", + "client_id": null, + "client_name": "uci-nodejs", + "client_icon": null, + "token_type": "long_lived_access_token", + "created_at": "2020-05-05T16:11:45.816042+00:00", + "access_token_expiration": 315360000.0, + "token": "68d14025b0fa69c73f8156c20d865c5f976dadf8d3b673f05e9c9876c1c8c57a6088393a0afdeb0e39601d66acb7e541d3e3eaed87142c846aafb3921d99cc85", + "jwt_key": "e07a119ec57634064db76b7d4cd6069fb53c5dab67d4dc5292ac87c9ff8d829fb9e57d55eabed9e5ff15dae57b713b7e99d58acf2d07a0e2b015a08d7a56ca9f", + "last_used_at": "2020-05-05T16:11:45.816162+00:00", + "last_used_ip": null + }, + { + "id": "9c056a7ff2884d498f5788e8d24af623", + "user_id": "99685dc45d9f40dc8183e11ce5128038", + "client_id": "https://ha.238.kebler.net/", + "client_name": null, + "client_icon": null, + "token_type": "normal", + "created_at": "2020-05-08T17:28:54.146379+00:00", + "access_token_expiration": 1800.0, + "token": "8e47f1dbc67228dd16bb0dcd1e226473115c9314d2a2ca90d894e4386982d7872001ffeccb66dcda7dd32d4d401ad8ae8208f44ba2e36072e7f6521a080c61a2", + "jwt_key": "925aa23e49505bf5090ae37490d25bbfab174166ca0e78209cba8ef0e9cf21259296524a87f8a8bb76a273953067483bf245fd9be3392c13a5c202313c61359d", + "last_used_at": "2020-05-08T17:28:54.146469+00:00", + "last_used_ip": "10.0.0.40" + }, + { + "id": "72239ba5b64c4eaabdf081915f7dadf6", + "user_id": "99685dc45d9f40dc8183e11ce5128038", + "client_id": "https://ha.238.kebler.net/", + "client_name": null, + "client_icon": null, + "token_type": "normal", + "created_at": "2020-05-08T17:53:48.292012+00:00", + "access_token_expiration": 1800.0, + "token": "01ea1e3b6c80b26840493e9677878b93d73298c3502287a06a1780aecc268a30af898c554af16cabef4ee86059ecf61d2292d9efc23fbf98282258573717e40f", + "jwt_key": "d0555cd2a345675d5b31d8d0ddb46ec8e3c73b63c09dd41d45295023469df0588f7d221e212fb3079dbe9a9d6d495797cf4675bd60f4a9a6cf3cfda07f1c5725", + "last_used_at": "2020-05-17T23:36:19.011267+00:00", + "last_used_ip": "10.0.0.2" + }, + { + "id": "402265610efe4be984275227f857688d", + "user_id": "99685dc45d9f40dc8183e11ce5128038", + "client_id": "https://home-assistant.io/android", + "client_name": null, + "client_icon": null, + "token_type": "normal", + "created_at": "2020-05-12T19:05:55.243033+00:00", + "access_token_expiration": 1800.0, + "token": "c68a524cd9f6cf52a42632417c041e136cf6a019d594f44ebe68a9c9081de5470315a11bc48fec78ce59f97c412dff87879d3853f60f3b817166ff8c88128a5e", + "jwt_key": "16d44c9908b6304131ebc9bd9471f3e89b876c824656205e4937eb894abfd3fe2b66b0c18b6b79d6d6974034d42523b7f1de1d94054fde0ccef0080cf2db86d1", + "last_used_at": "2020-07-11T17:30:10.703159+00:00", + "last_used_ip": "10.0.0.2" + }, + { + "id": "0bd7ec183c28448e9d60a2885b3d7a60", + "user_id": "99685dc45d9f40dc8183e11ce5128038", + "client_id": "https://ha.kebler.net/", + "client_name": null, + "client_icon": null, + "token_type": "normal", + "created_at": "2020-05-18T17:17:25.838176+00:00", + "access_token_expiration": 1800.0, + "token": "8ec6f1a38355e63bdef895a21e14255818e6cc52fc215e36fffe0df5fbebf299bc8440e5b9bb00a97ddfa182e422b08634dd6dd4cbbd2803df49fdbc0d93ebb1", + "jwt_key": "8093b0f4731dc107d19a51b962996bf0f43fb4743d3058ff457a163a904725c53981efca63984f86cc1c7ba73a539ccdd348ea92c4cfc8a1e01ceb0323d344e1", + "last_used_at": "2020-05-18T17:17:25.838343+00:00", + "last_used_ip": "10.0.0.2" + }, + { + "id": "bd050386ada5413595cbbe1ad8214560", + "user_id": "99685dc45d9f40dc8183e11ce5128038", + "client_id": "https://ha.kebler.net/", + "client_name": null, + "client_icon": null, + "token_type": "normal", + "created_at": "2020-05-18T17:18:37.456835+00:00", + "access_token_expiration": 1800.0, + "token": "267ec8930cd7ef788d37140fa34ee8f5e4bae28f8f2e250681d2a07c46714a7f736ca7c018459477e789089ec31439212697c48389a469df1681439c774c8128", + "jwt_key": "ee283ec7a5eb65347442d3192c060cecfa85a6a909f3da1d0170ede1141241f0a4c14e9b51aec7c83ce5b7813aa2b8d9410c0dd571db42f5795afc96c04b6313", + "last_used_at": "2020-05-18T17:18:37.457027+00:00", + "last_used_ip": "10.0.0.2" + }, + { + "id": "7ae6208a51d04e9a8b364001548bdf8b", + "user_id": "99685dc45d9f40dc8183e11ce5128038", + "client_id": "https://ha.kebler.net/", + "client_name": null, + "client_icon": null, + "token_type": "normal", + "created_at": "2020-05-18T17:19:51.998740+00:00", + "access_token_expiration": 1800.0, + "token": "9903198e547b1fdc0f13a93e9640c15edadb7f7445b34f49f163fd17b933b01754f5116ae3ade38decd94fc5e7e3cd65372348df98502138ff028d0ae4df4c0d", + "jwt_key": "cea67eff9927b5f52df11b7ac328b7569663bb56f651745ffe7eb423545d3cb4a59df382f76b271419e8dc1498b55fa27ac9557c74b6054a026b4955e502037d", + "last_used_at": "2020-05-18T17:19:51.998928+00:00", + "last_used_ip": "10.0.0.2" + }, + { + "id": "40a795f50f6b410d9a58c3b9403f158e", + "user_id": "99685dc45d9f40dc8183e11ce5128038", + "client_id": "https://ha.kebler.net/", + "client_name": null, + "client_icon": null, + "token_type": "normal", + "created_at": "2020-05-23T01:04:07.712795+00:00", + "access_token_expiration": 1800.0, + "token": "db2cff2204b9c01c8a5b75c973b66a3c5cc98996f27716a7fa233334ba25a85d654152c284b17b0cedd7282b86c4e76f666f34b94e5a9e07a7aba2b2a5281809", + "jwt_key": "78a66e6a1dd20bad73a81d5f772c7264dfb8ad28a77db42de7a6891f3754cbd92f9aa7506fab30eef1faa57bb2c920b643537ab64ddb55fda758fb2b8971a746", + "last_used_at": "2020-05-23T01:04:07.713009+00:00", + "last_used_ip": "10.0.0.2" + }, + { + "id": "372f97c712e34fbfa8dc114ac3c70c46", + "user_id": "99685dc45d9f40dc8183e11ce5128038", + "client_id": "https://ha.kebler.net/", + "client_name": null, + "client_icon": null, + "token_type": "normal", + "created_at": "2020-05-23T01:16:00.431201+00:00", + "access_token_expiration": 1800.0, + "token": "19de10c6b54392fc8d3403919fc911c0d530e86d1cd90aea0e1ce2db16af1d0c1ee297d6fddb7efa8ff024e7987a8fe7d5c7c50ddc1edb5fcc77af61a0020800", + "jwt_key": "9005db000388bb3ef44b260f1263b8d77c082a6e1bae4b1b05edefa54bfc69c0f47ca56676e98e702a4bfe9d7cdd9ecfad4cca36cc3150bef4c56030d6b31314", + "last_used_at": "2020-05-23T01:16:00.431349+00:00", + "last_used_ip": "10.0.0.2" + }, + { + "id": "7f12647c7b6245ee96c6c33420916ac5", + "user_id": "99685dc45d9f40dc8183e11ce5128038", + "client_id": "https://ha.kebler.net/", + "client_name": null, + "client_icon": null, + "token_type": "normal", + "created_at": "2020-05-23T01:16:43.154455+00:00", + "access_token_expiration": 1800.0, + "token": "a48e8f44fe3d621e9b6f668b244bc90d7caafdbd613f47e64316a96d6433293dffff82be3801083c226ae8731d9ff5792756172a7528f7ff73afd451e2aa2586", + "jwt_key": "c5548f6c000bbd3960473b16ffbc0347b980b47bb71acdf3ca65d72d2dc22778f8a0d0318b05423f8e7997dde74ddd8778461d3ca15cccea05754b61dd2eeaf8", + "last_used_at": "2020-07-27T16:32:30.569384+00:00", + "last_used_ip": "10.0.0.2" + }, + { + "id": "1d871c8299d54ee5a9655d02afa5a5f8", + "user_id": "99685dc45d9f40dc8183e11ce5128038", + "client_id": "https://ha.kebler.net/", + "client_name": null, + "client_icon": null, + "token_type": "normal", + "created_at": "2020-05-24T23:17:37.724064+00:00", + "access_token_expiration": 1800.0, + "token": "934ea83bc07a342b687e16a91be0df7ac44b28e39812036e652f508e58934e1b9380dd498b1bd0997537e346974e925694850761659fb382de03950c6de32fd5", + "jwt_key": "04b92d557d2dcdd84ba1a60548fe1596e113854aa6a6a3b7d498dc80b955784b6619a2d5d955418f639e7f626f739731b97888a2a291a753f4f5f1589436371e", + "last_used_at": "2020-05-24T23:17:37.724153+00:00", + "last_used_ip": "10.0.0.2" + }, + { + "id": "232bd5fc76554fdd800ae83360c701db", + "user_id": "99685dc45d9f40dc8183e11ce5128038", + "client_id": "https://ha.kebler.net/", + "client_name": null, + "client_icon": null, + "token_type": "normal", + "created_at": "2020-06-04T01:30:55.690817+00:00", + "access_token_expiration": 1800.0, + "token": "893a7e73b48ac95d998767a2370989794386c78db1efba0f3cfe04f3a10066f0231e28f730d71fc155ef8293aac3f23cc7359fa2f0c58281916b2ee807ceeb58", + "jwt_key": "16b3183bad57c2072fc7d5bc37c8c02a6835af59ef37ec9680cf7af8c763e8d8cf18c25820152ff8ee0193fec6deb8260a3095e8d692900de41f587e38928f57", + "last_used_at": "2020-07-05T19:52:57.721095+00:00", + "last_used_ip": "10.0.0.2" + }, + { + "id": "a6a996e644b24b7cb1592fe9d42d4b57", + "user_id": "99685dc45d9f40dc8183e11ce5128038", + "client_id": "https://ha.238.kebler.net/", + "client_name": null, + "client_icon": null, + "token_type": "normal", + "created_at": "2020-06-21T17:57:04.471125+00:00", + "access_token_expiration": 1800.0, + "token": "760face629b549519652d944de2072105e3a72ed219044389b11782c17cf77d7716263730c298218be2c84257b084c052e2284eb563c2d7ceb0b00787c355be8", + "jwt_key": "2c4e225a7f1669fd736208374241f7aa7366ba9cc055b4e30407017ecd8af6600ea074a81c4a084093bfc3a455d1c115a5c8691f01049757728f2d18fdf2a2e5", + "last_used_at": "2020-06-21T17:57:04.471308+00:00", + "last_used_ip": "10.0.0.2" + }, + { + "id": "605773d42dff40029ceb242715b997a8", + "user_id": "99685dc45d9f40dc8183e11ce5128038", + "client_id": "https://home-assistant.io/android", + "client_name": null, + "client_icon": null, + "token_type": "normal", + "created_at": "2020-07-05T01:43:10.686955+00:00", + "access_token_expiration": 1800.0, + "token": "bdd0c54b8ab68bb7c01c3e8f7ac60ad3bcd148b8f5ef068b8d13fa3bc1078940ad0e8c5c72186194602fa82bd08d3a90793faa553cd984ce0f105312a1d51774", + "jwt_key": "4412b5628aae25c302e8b28d241ba276b950d146061c4080961add8c67fbfa7c565b961469d103defbdabc11b0f1c7806c4aa8b1df83cd2727c8bcc5f72ae4b1", + "last_used_at": "2020-07-10T04:29:14.487954+00:00", + "last_used_ip": "10.0.0.81" + }, + { + "id": "34d882d73b05432fa03e7395f9cb6b53", + "user_id": "99685dc45d9f40dc8183e11ce5128038", + "client_id": "https://ha.kebler.net/", + "client_name": null, + "client_icon": null, + "token_type": "normal", + "created_at": "2020-07-06T14:29:45.688348+00:00", + "access_token_expiration": 1800.0, + "token": "966708386e4cb84118d8144dc752db50b88a74f507408b894373f260556e7a7f65127637d98ec00bc5ec5f6774c3246879efe70cc383139f6128538f75f2ac4f", + "jwt_key": "18ae9f29ec0eb6f658261eecd6e3d03a470460c25c26417e03fa75575945c53de0e2b5b715231de15375d8065eca3f60e2407eb43f0940721ff5fe91a1d7dc7a", + "last_used_at": "2020-07-06T14:29:45.688539+00:00", + "last_used_ip": "10.0.0.2" + }, + { + "id": "d09627110ecd4fffb06c20700fe5392d", + "user_id": "99685dc45d9f40dc8183e11ce5128038", + "client_id": "https://ha.kebler.net/", + "client_name": null, + "client_icon": null, + "token_type": "normal", + "created_at": "2020-07-06T14:30:43.942948+00:00", + "access_token_expiration": 1800.0, + "token": "02d9cb5abcdd603812630e8d8787120eb26ee1e432afd76eea142c8dc92114509c56939c9d4f019ae7033da1da326f5b671bdb48e07e7e322f8336ca798e4a98", + "jwt_key": "0f04659cc034804449dc2272db88f6ceec46956e68b9cd6a41dd7e92832f2021993f45a3c4bbb02f13540b939bbe893e1da254239633ca7f413eed05a3afd825", + "last_used_at": "2020-07-06T14:30:43.943120+00:00", + "last_used_ip": "10.0.0.2" + }, + { + "id": "72e4e005b12b4c86be0eadb96df4d234", + "user_id": "99685dc45d9f40dc8183e11ce5128038", + "client_id": "https://ha.kebler.net/", + "client_name": null, + "client_icon": null, + "token_type": "normal", + "created_at": "2020-07-06T23:58:34.022752+00:00", + "access_token_expiration": 1800.0, + "token": "197f211e8e222c1a3aadebc7e6791a2dd082ac3bded60c6a524105ddac34cd63d35c17a2e7e998f813ec26715f095bcc6bf723519af26d22578647711ee421e2", + "jwt_key": "6811bf21e0c216eeca6fdbca663887391f98d4fa4988ba7daf1d990165598360290c5e0775a52f7ed8befc41e594b350baea5b35a0f3b2f281a79478cea0827a", + "last_used_at": "2020-07-27T20:57:38.927886+00:00", + "last_used_ip": "10.0.0.2" + }, + { + "id": "ebc73d8ed0a94ef4a73ce3d21dd9c2b2", + "user_id": "99685dc45d9f40dc8183e11ce5128038", + "client_id": "https://ha.kebler.net/", + "client_name": null, + "client_icon": null, + "token_type": "normal", + "created_at": "2020-07-09T02:36:13.347361+00:00", + "access_token_expiration": 1800.0, + "token": "4a330220321ae27f3736287ac7773fb99473cf1adf2f6984d9d28d7b38a30713ab973ace6ea805cb28c70f274cbc88aeb68c314460d50f1ecb7503c7e2601e16", + "jwt_key": "a98ae51a6cec19ae525bb1becef863a618a3d99bd6fbeb5bf132ab5d0fe50899ad0d7cb1f5ba3f1c9de0762277c52cb5bc460bce91be447385bb3c44a003d393", + "last_used_at": "2020-07-09T02:36:13.347458+00:00", + "last_used_ip": "10.0.0.2" + }, + { + "id": "c0a73200d0aa437bb5202cf26081da5e", + "user_id": "99685dc45d9f40dc8183e11ce5128038", + "client_id": "https://ha.kebler.net/", + "client_name": null, + "client_icon": null, + "token_type": "normal", + "created_at": "2020-07-09T02:37:12.953554+00:00", + "access_token_expiration": 1800.0, + "token": "3d015444cda09096e32a3e4a1994ce2612bc35342789a09e59bdbda0b87efc5f2872cbfd1fcda3e951b3c447f0ba517ce948d265fb980887b9c107efc63c2852", + "jwt_key": "0702d98585f7cfe30392d4cf87c4b2774f8376c5c06679be9e0ea30013bb766b09e02ec2f804234f3ddd08f8c32acd451d1b0ab2254706bf18bbee86193ccd16", + "last_used_at": "2020-10-08T21:05:30.979982+00:00", + "last_used_ip": "10.0.0.2" + }, + { + "id": "3646df1cf71241a3b6ed86591516ce4d", + "user_id": "99685dc45d9f40dc8183e11ce5128038", + "client_id": "https://ha.kebler.net/", + "client_name": null, + "client_icon": null, + "token_type": "normal", + "created_at": "2020-07-12T03:46:38.289526+00:00", + "access_token_expiration": 1800.0, + "token": "c2cc10498ebad0f845ffda3d6975b13cf236492cb9ae5f347be3e00ef4e0969769856014adfa01ef85aaf01f02e319fc147c9dcef7c114520ed3886be131ca11", + "jwt_key": "48005573b3a2c289baa21eb319675b62f91137bd92bda46b193723ce45908087358d2240e7f664ceef9ace43987d880ca1a5000b51bfae7d0a8b2e2b5db0b03b", + "last_used_at": "2020-08-23T01:22:25.481519+00:00", + "last_used_ip": "10.0.0.2" + }, + { + "id": "50bb1eb79cd94431b994afd53e9683d4", + "user_id": "99685dc45d9f40dc8183e11ce5128038", + "client_id": "https://ha.kebler.net/", + "client_name": null, + "client_icon": null, + "token_type": "normal", + "created_at": "2020-07-12T16:09:36.528206+00:00", + "access_token_expiration": 1800.0, + "token": "fa888311df096ee4d80019f60694b5fb08baec76118f6b0ae92ec86c320f5dc0536de0e10a95b53a2bdf3ba698585813b5dfdab405ca9d5d461fff475678610d", + "jwt_key": "6ea0008fb70267d819e06692fb0ede2f2dbb143291bcad62b6f0b4063634b85afc417746c3084ea55a7931798e0f50100cba5c5e9f81a428ba1ecc533bf212a5", + "last_used_at": "2020-07-12T16:09:36.528392+00:00", + "last_used_ip": "10.0.0.2" + }, + { + "id": "a3dd25cbbfb747208820d94791857de1", + "user_id": "99685dc45d9f40dc8183e11ce5128038", + "client_id": "https://ha.kebler.net/", + "client_name": null, + "client_icon": null, + "token_type": "normal", + "created_at": "2020-07-12T16:10:07.411264+00:00", + "access_token_expiration": 1800.0, + "token": "83a310db806ee644f2017ecf27b2873f754afe273d153d0030c41fc6261016934d05e9cd9a8c3748405d99d1aaf77808910d261fec6002767327aa938ea5a284", + "jwt_key": "d00d8d4cda9f473272f4f3ac85117d5b4fd0808a9263de40a70ae051cc239543d3297f8fb7022b63f0a7e90827cfbcda2839bb5f7ccbaec33e7cc777f97e0fe3", + "last_used_at": "2020-11-04T17:24:13.759395+00:00", + "last_used_ip": "10.0.0.2" + }, + { + "id": "3eec23ccfcf94c918ac73c602ae26a17", + "user_id": "99685dc45d9f40dc8183e11ce5128038", + "client_id": "https://home-assistant.io/android", + "client_name": null, + "client_icon": null, + "token_type": "normal", + "created_at": "2020-07-12T16:15:35.254451+00:00", + "access_token_expiration": 1800.0, + "token": "7f1d1dbb3d1cab5c9c652d96d36bada6086883dd7c17beebec10f3531c1412e44c1333d7eb0048038f08281e70a4087c0277d307770944f51ad235372adabd42", + "jwt_key": "7674eac988ea5cffc794a801c974a46eb2e294c2ee6780d1aed53ad28c79e02ed415e4a1e191c768c63912f7f618aa71d1c23025c5feb6770a2e6e447a39b976", + "last_used_at": "2020-07-13T01:29:57.266169+00:00", + "last_used_ip": "10.0.0.2" + }, + { + "id": "ae5b7c6d6ff1449292ce81f172a11f25", + "user_id": "99685dc45d9f40dc8183e11ce5128038", + "client_id": "https://home-assistant.io/android", + "client_name": null, + "client_icon": null, + "token_type": "normal", + "created_at": "2020-07-17T05:57:06.994807+00:00", + "access_token_expiration": 1800.0, + "token": "5967e9c53e776e5e6c17e468c16b8bc7d8159973e07f8cfd92d2859bc11ec4037ef6c99a8df0d8e5d56f5c01c932bfc8391d3e27f831b91726a0cbeaece1d0b0", + "jwt_key": "bcc49807c6c52bf6cc520a9a9d44b983d8c8fb56ed5bd5fa4957de6e72e6cbef56ec37c8ec5f528a33ea0499543bdc468d1dcb09eb245946e549c5bca2f1082c", + "last_used_at": "2020-07-17T13:21:55.484206+00:00", + "last_used_ip": "10.0.0.50" + }, + { + "id": "6eac5beec6864041b5e06ae32d3e46c0", + "user_id": "99685dc45d9f40dc8183e11ce5128038", + "client_id": "https://ha.238.kebler.net/", + "client_name": null, + "client_icon": null, + "token_type": "normal", + "created_at": "2020-07-29T23:40:38.783628+00:00", + "access_token_expiration": 1800.0, + "token": "6bf19f64ecbe962466d07c1ae7c84cc0dbbc77fa33adad2308b93161f2a83edf7554c382267ca8654043209890fa276ca3631f1b52581c2aa9194decacbe59e8", + "jwt_key": "8344a4d57507b64a58686f0b69f754bc72f2821237840eea71e715b9494b96a9f0bf6aeb4f95da21a22e66ad2936dab41564550c724dbd4812776192d8c42395", + "last_used_at": "2020-08-01T21:17:52.004403+00:00", + "last_used_ip": "10.0.0.2" + }, + { + "id": "ec4a0258afb8475ea0dfe0a4ba9e98ea", + "user_id": "99685dc45d9f40dc8183e11ce5128038", + "client_id": "https://ha.kebler.net/", + "client_name": null, + "client_icon": null, + "token_type": "normal", + "created_at": "2020-08-03T00:59:37.625979+00:00", + "access_token_expiration": 1800.0, + "token": "5a07217d1be991a862d98db60c459120b59024656a3d783b5fca65798f93f60b99d04329ee63065b3a746bd7d0ba179eb78689cc7cc05af5c509ff15138dacbb", + "jwt_key": "680de2272b974928239de643e230c06e58afee8e465845d9f351159e0bf5669e8f71a16fcf3ccc05f13a6e778a6a3603ce7e44e03c9fcae90ee6b8d9f6641587", + "last_used_at": "2020-08-03T00:59:37.626126+00:00", + "last_used_ip": "10.0.0.2" + }, + { + "id": "4482ff0899b34bb89c760a8f2d4613ab", + "user_id": "99685dc45d9f40dc8183e11ce5128038", + "client_id": "https://ha.kebler.net/", + "client_name": null, + "client_icon": null, + "token_type": "normal", + "created_at": "2020-08-03T01:03:01.289239+00:00", + "access_token_expiration": 1800.0, + "token": "ef92f003dcdb31d60995abfcfef359c764e26d232c30aef63dec49e4a0faa52ff9c8f8303700710a72bdeffb91b9a31e13a664f2deca5efd489804717354521e", + "jwt_key": "d487996d250f3094f4626fec85800fe2495a9819479228d22ceb433acc4c737e4e885d42ce75071e507635311f35d264671fbdd126ccb27e41be8ac61b014103", + "last_used_at": "2020-10-11T19:19:39.744782+00:00", + "last_used_ip": "10.0.0.2" + }, + { + "id": "a4482e10593044ffa2fd6273fa672339", + "user_id": "99685dc45d9f40dc8183e11ce5128038", + "client_id": "https://ha.kebler.net/", + "client_name": null, + "client_icon": null, + "token_type": "normal", + "created_at": "2020-08-05T14:17:11.368716+00:00", + "access_token_expiration": 1800.0, + "token": "46d540bf2741ae57b9c41eeec6dccc4b0d8c764718b8d98bd92b1d5dc912dbe45a8ff92705590c7f4bac5f9e02df341ad67d34ddb13a7b299e1c5174606d1fd9", + "jwt_key": "7a21b2744a112754c6941f07a8aada1391788805946db768f6337c8d05081b0de9c7b0a5c66d56cb241b8e537e4ad696be65c833ef22717697ba1a78f83c9132", + "last_used_at": "2020-12-09T03:21:59.845672+00:00", + "last_used_ip": "10.0.0.2" + }, + { + "id": "5bf29a15f3334d41b07dddebc52ed127", + "user_id": "99685dc45d9f40dc8183e11ce5128038", + "client_id": "https://ha.kebler.net/", + "client_name": null, + "client_icon": null, + "token_type": "normal", + "created_at": "2020-08-30T03:40:04.690298+00:00", + "access_token_expiration": 1800.0, + "token": "924a81cdc2c5c5afc95f40a4e97cd3cca57de62b36bb805db51b8efeeaf5192a9da9bd0511c50dd7ec3e3154414d130d195e85b460c061891cd3867e18eb5e2b", + "jwt_key": "023283686a7a1c1d6fafa329fca25c0aa8e8987bdd7c1fa5dba0da6369fb2db904f0e08a219ef0318caebb01c0e2fd13c2a278e4d9385362924781abde9a4d80", + "last_used_at": "2020-11-13T20:07:39.769993+00:00", + "last_used_ip": "10.0.0.2" + }, + { + "id": "9035cf2579df4b0e922693248d85e28a", + "user_id": "99685dc45d9f40dc8183e11ce5128038", + "client_id": "https://auth.homehabit.app", + "client_name": null, + "client_icon": null, + "token_type": "normal", + "created_at": "2020-11-25T15:01:59.405857+00:00", + "access_token_expiration": 1800.0, + "token": "6151f0a7d40f1f0fbada720af1cf404d71166699f7f4b3361a4a249617118f6de4db07fe4a77c13d6c9ba2a1709542dd252c8091250f96cb705cb23d253ec6fb", + "jwt_key": "396706de0ac97edf831411bdd410900a16e7efd5db56fe115523732fc2d8c1b2307e70d7fe0a991bfe3aa1fa77cd55997f7c1a948e137728a2223a24b4a45ff7", + "last_used_at": "2020-11-25T15:01:59.406207+00:00", + "last_used_ip": "10.0.0.177" + }, + { + "id": "8939761e39a54299a664bfcaa240dc9e", + "user_id": "99685dc45d9f40dc8183e11ce5128038", + "client_id": "https://ha.kebler.net/", + "client_name": null, + "client_icon": null, + "token_type": "normal", + "created_at": "2020-12-06T16:27:13.151351+00:00", + "access_token_expiration": 1800.0, + "token": "e559ac22f009c8ebb7dd77eb5910eb9bf7f033078053605260ade0288da3b130fed5e7b6d7f2f744a5bf7da1f58ea30c7dfe08e4ae245c068e41592dead8adb7", + "jwt_key": "ea22e65251d533073e2421aa93edf45d2f8a4ff7704391570749f8b6f016ec5300556e35f3ddb705cb5f3bec795b7012941aaca80fd4785098931af3d0735d54", + "last_used_at": "2020-12-06T16:27:13.151511+00:00", + "last_used_ip": "10.0.0.2" + }, + { + "id": "4ceb46fd6839478d8f0445f1965b01d6", + "user_id": "99685dc45d9f40dc8183e11ce5128038", + "client_id": "https://ha.kebler.net/", + "client_name": null, + "client_icon": null, + "token_type": "normal", + "created_at": "2020-12-07T04:32:12.604141+00:00", + "access_token_expiration": 1800.0, + "token": "3f474a977d94cc4f87d71db8a47a574f1163b231277e92110667a284b2e4ff0314347b26865be56cc42bf30e6b6f4aa0e30c74202860a23524f4ef56a7445ff8", + "jwt_key": "b918861665e665eb9dd76cf4270b9c72fed9e566bc6b2ef55f47f213da998a3c40cac4f38d2830077b24df62541a0085c1c2471a278ff2ebfb842b3f6eabeff7", + "last_used_at": "2020-12-07T04:32:12.604333+00:00", + "last_used_ip": "10.0.0.2" + }, + { + "id": "4deb1c92841543cbb4d86079a5320f99", + "user_id": "5270c6dbb87642d29fb41212df19da0c", + "client_id": "https://ha.kebler.net/", + "client_name": null, + "client_icon": null, + "token_type": "normal", + "created_at": "2020-04-23T00:10:25.160477+00:00", + "access_token_expiration": 1800.0, + "token": "badea2e70e36f408ce6975151605e56c4289bf119e8a6490a6b9218050a00ec2dd524204b1cc54be77ef00da042f9d430e371b65459d03efbb6ece59521acbfd", + "jwt_key": "e0f3e5f1f657e83d5f2a9ef2d5217ba574855ce10990f0be87b4546e15e14e4ae80345b1b71d461cdc31321d2c05c0afff3c1e71f49af535c2fb5e6fa52eb4ed", + "last_used_at": "2020-04-23T19:18:19.322650+00:00", + "last_used_ip": "10.0.0.3" + }, + { + "id": "ca6946fc5c5f4961a6034bd8d7d23856", + "user_id": "4b1c5a6ccf0e4be786b2f3014ce38f07", + "client_id": "https://ha.kebler.net/", + "client_name": null, + "client_icon": null, + "token_type": "normal", + "created_at": "2020-04-23T20:33:30.284580+00:00", + "access_token_expiration": 1800.0, + "token": "84ebc9b18f09c510b9797db1051bed73d52cb4ae0bb54306068af0ec6380d8ccc479bb7794413bec4177a4ab76470c8e594546a8393962524aae7b028f2e2395", + "jwt_key": "627d204b97561f6ab5942f41caf6afc209bf7b64f655b82cc87e447a6d54706518aa7b2a53b8219737f250365373341d0814a6968748fd073ad25ac8e2065123", + "last_used_at": "2020-07-12T14:19:03.655482+00:00", + "last_used_ip": "10.0.0.2" + }, + { + "id": "8361a84b6c6341bf922c47bad3681fd8", + "user_id": "13f54fb27e7d4b95b2d0143f16b432ab", + "client_id": "https://ha.kebler.net/", + "client_name": null, + "client_icon": null, + "token_type": "normal", + "created_at": "2020-04-26T17:39:53.974788+00:00", + "access_token_expiration": 1800.0, + "token": "4e9d9b215424c6c66905ba937c64baa34e91d4708dde30a5cf37ae59baf1b4d4b62f755cedfc74dcfed5dd2c891c62e75a6b64a7aacf9d09815b10eaeffc383f", + "jwt_key": "2463c0680c7d35b775f8cc4193a81e1bffcaf9573b2e502a3c38f3fea172f131f22e1f4505a4d100fb8c7b60a2a23b68d83e69d22d830e96155289f426d785b7", + "last_used_at": "2020-04-26T17:39:53.974866+00:00", + "last_used_ip": "10.0.0.3" + }, + { + "id": "219bdd5b88014758848fbe023b48ac6c", + "user_id": "13f54fb27e7d4b95b2d0143f16b432ab", + "client_id": "https://ha.kebler.net/", + "client_name": null, + "client_icon": null, + "token_type": "normal", + "created_at": "2020-04-26T18:04:04.808018+00:00", + "access_token_expiration": 1800.0, + "token": "3bf34a424465335eb140042a13f6cc8591e90df01dc2920838c4d935753a3bf923b5e14d904fd3d08f564274947176a05ac4dd44fec070dd7d7451adab309b0c", + "jwt_key": "acbd9b9188fd280eabdbf303948bbf73963e25b7e2c7416f8300f41aa346e8953794f6fc18def56eaafbf0e1fc20d7470290aa2950fae1532de2945398aaf7f6", + "last_used_at": "2020-04-26T18:04:04.808169+00:00", + "last_used_ip": "10.0.0.3" + }, + { + "id": "a2f8b2727fc8495784506a9d33d95f40", + "user_id": "13f54fb27e7d4b95b2d0143f16b432ab", + "client_id": "https://ha.kebler.net/", + "client_name": null, + "client_icon": null, + "token_type": "normal", + "created_at": "2020-04-26T18:05:00.413200+00:00", + "access_token_expiration": 1800.0, + "token": "532c1a2ebe7fb5bac77c0326734a7f67fba47d858965b7416cde6f7ac36c65d548961c3d0b0f6925bc60d70acfbe798d5992dbeaff749e51848540c6ff178d22", + "jwt_key": "aa2c44a1423a07033f00cb5c972ff25dcbb2f7d3e1fd18458f3e1283860e6ecd3b5e3d5b2d752c7c37fbd807bf9a06119e0e6801adc7ff6f964245ef798ff212", + "last_used_at": "2020-04-26T18:05:00.413349+00:00", + "last_used_ip": "10.0.0.3" + }, + { + "id": "0a483a651ae441558d452a317d70285c", + "user_id": "13f54fb27e7d4b95b2d0143f16b432ab", + "client_id": "https://ha.kebler.net/", + "client_name": null, + "client_icon": null, + "token_type": "normal", + "created_at": "2020-04-26T18:19:59.964034+00:00", + "access_token_expiration": 1800.0, + "token": "d0018905578cdb208f2a26b85638fa3efdfac31d75f3dbd5db132d4f70929dbe0ed1abe5b5cbdaeda4bfc7265f1d88af5f087205b44955f06d37b80d75381409", + "jwt_key": "ec9752d1f44320be4673fe74507f9848aa219ac50518248f39a1d40773aa9c2b3154212ae35c3c137bc4cf9596d8d247f28128a14bc9e8f77876af23149ab1b8", + "last_used_at": "2020-04-26T18:19:59.964183+00:00", + "last_used_ip": "10.0.0.3" + }, + { + "id": "a0948ecf69ba416c8b6507ffc6b20d9e", + "user_id": "13f54fb27e7d4b95b2d0143f16b432ab", + "client_id": "https://ha.kebler.net/", + "client_name": null, + "client_icon": null, + "token_type": "normal", + "created_at": "2020-04-27T21:15:36.147251+00:00", + "access_token_expiration": 1800.0, + "token": "8b196fc9940245c1783e3ae1b0aa930ae840d023e6e2965a9809a0a164c61d07254b3b13a3f74df5846dedb229c110f7fa27f19a4b74fa66e6abba797194e9b4", + "jwt_key": "61c29cb0b31d2985c1c8d5b2b842de31eea7d3d8b8d5a29bee8e97904473a6ddac8cf24399b80b90c2d4c0f1cd26ced56f8a33932070307f75c4d00c960933f6", + "last_used_at": "2020-04-27T21:57:18.747111+00:00", + "last_used_ip": "10.0.0.3" + }, + { + "id": "21ff77617e494c6793008a38e2d9b3e7", + "user_id": "13f54fb27e7d4b95b2d0143f16b432ab", + "client_id": "https://ha.kebler.net/", + "client_name": null, + "client_icon": null, + "token_type": "normal", + "created_at": "2020-04-27T22:34:43.521746+00:00", + "access_token_expiration": 1800.0, + "token": "a9dbc4137a3d1a2b767c720bc1fbb01ad1b2a6f739c26c77d41d13ad827409761e98e58f3f1d992a5a21f296784289f30762c942d5a73de1aa0061c29ea5eaff", + "jwt_key": "1f8926eb9b30590d7588bed25f330549e5a2bb22db94fbbf10c2c0b018983ed9b30a8c087017128856c176b9a490470b967e88104bd559d52ee2757030e9f754", + "last_used_at": "2020-07-29T02:00:31.423743+00:00", + "last_used_ip": "10.0.0.2" + }, + { + "id": "05f1e1afcbb64583ac3fcce10e560e9d", + "user_id": "13f54fb27e7d4b95b2d0143f16b432ab", + "client_id": "https://home-assistant.io/android", + "client_name": null, + "client_icon": null, + "token_type": "normal", + "created_at": "2020-05-04T14:19:13.776902+00:00", + "access_token_expiration": 1800.0, + "token": "d64d271a9debd9aad3e55d94a5c63a11ba4762280d8484fd061b7a9285a11527a06716708d4d36af45a0c4d871a8859e818e6221ad0c892705603eecdd28d36b", + "jwt_key": "69c8a86b8be36ed2b0afb1226f94e88e9dd2044967316f5beb1f5beda756b025d94f48c9ee855fe5d49dc1887177dc61604b40d760e6dfdeabc25caa620e507c", + "last_used_at": "2020-05-04T14:19:13.777086+00:00", + "last_used_ip": "10.0.0.228" + }, + { + "id": "718952d4286247bf8d96d5d8dc2a94c2", + "user_id": "13f54fb27e7d4b95b2d0143f16b432ab", + "client_id": "https://home-assistant.io/android", + "client_name": null, + "client_icon": null, + "token_type": "normal", + "created_at": "2020-05-04T14:26:52.742697+00:00", + "access_token_expiration": 1800.0, + "token": "ba8efb7813f01bff7333dddacd5195afe3175ac6d54fc4b04481f10c7319df1e77c20fb9d047e7a3e47b83fd0e0721b62439fa47bae4259381fa3b27ac3baed6", + "jwt_key": "e8ce1bbe5de0e3c9392a7581347aae2f1b4ecd2a407ac4d07a766d723b91b5c6a0b2ef8320314692e169b5b4041cd1be679a31076072ff7bcc5c13dd5b7f8a36", + "last_used_at": "2020-05-04T14:26:52.742871+00:00", + "last_used_ip": "10.0.0.3" + }, + { + "id": "ccf4e031925f45ccbcfe31904e0e3dc3", + "user_id": "13f54fb27e7d4b95b2d0143f16b432ab", + "client_id": "https://ha.kebler.net/", + "client_name": null, + "client_icon": null, + "token_type": "normal", + "created_at": "2020-05-04T14:50:48.822306+00:00", + "access_token_expiration": 1800.0, + "token": "4383e6b873a84ec975076a0d8d64ca23c8ab7fabd3b394a35cad4434e474c2a992ca7d15bda3e29f011782f011e7b888a81a8f82d988ffbead68b4c11e97be5f", + "jwt_key": "f18a94dfa0270259bb9deb169599deef12e2a948fcced55337c5b40688e8b08a87287b4eddcf982f6ec4097eea25d1708f3029cb9a987400fb6d61272fe08b4d", + "last_used_at": "2020-05-04T17:12:36.668523+00:00", + "last_used_ip": "10.0.0.3" + } + ] + } +} \ No newline at end of file diff --git a/.storage/auth_provider.homeassistant b/.storage/auth_provider.homeassistant new file mode 100644 index 0000000..5b25536 --- /dev/null +++ b/.storage/auth_provider.homeassistant @@ -0,0 +1,24 @@ +{ + "data": { + "users": [ + { + "password": "JDJiJDEyJEROU05qSFRuRE5RRHJ6RUFjQ1pwUy5qcGN3TlNnaDU0ZUdjUnFxdWFZQVFZamEzTnZ3LjBT", + "username": "sysadmin" + }, + { + "password": "JDJiJDEyJFd3Tnh0ZlVZb21iQmlKaGs1RnVhai5OSlFLNWNKVU92a2ZNTWVKdEhzb0hQTWF0eDRXTjRl", + "username": "trish" + }, + { + "password": "JDJiJDEyJDR6elRTeWxORHNMclFzSFJ3N0ZiNC4ucmUyUmZ4TEVFRHNJekpmcXNWUm1EOS45cGoveFAy", + "username": "irrigation" + }, + { + "password": "JDJiJDEyJDZMclNZSy5pSGY5QVdNZ0JhVWF4UHUwUVdsbGhsRGJHeUhDWlZ0ZHVLdUtOdFdDTGRJVlZl", + "username": "test" + } + ] + }, + "key": "auth_provider.homeassistant", + "version": 1 +} \ No newline at end of file diff --git a/.storage/core.area_registry b/.storage/core.area_registry new file mode 100644 index 0000000..9b8ed2a --- /dev/null +++ b/.storage/core.area_registry @@ -0,0 +1,20 @@ +{ + "data": { + "areas": [ + { + "id": "5c6c9f7bb2af43a4afa8851308f8e10a", + "name": "Living Room" + }, + { + "id": "82805bb0656b443ea5c7fdc7a762a7aa", + "name": "Kitchen" + }, + { + "id": "13ef8995699f4a04b79b02119ba2d2dd", + "name": "Bedroom" + } + ] + }, + "key": "core.area_registry", + "version": 1 +} \ No newline at end of file diff --git a/.storage/core.config b/.storage/core.config new file mode 100644 index 0000000..a3975b6 --- /dev/null +++ b/.storage/core.config @@ -0,0 +1,14 @@ +{ + "version": 1, + "key": "core.config", + "data": { + "latitude": 44.46097018572946, + "longitude": -118.71158838272095, + "elevation": 0, + "unit_system": "imperial", + "location_name": "238 McHaley", + "time_zone": "America/Los_Angeles", + "external_url": null, + "internal_url": null + } +} \ No newline at end of file diff --git a/.storage/core.config_entries b/.storage/core.config_entries new file mode 100644 index 0000000..ff2b658 --- /dev/null +++ b/.storage/core.config_entries @@ -0,0 +1,221 @@ +{ + "version": 1, + "key": "core.config_entries", + "data": { + "entries": [ + { + "entry_id": "2872c3dbbf1246c2b7aab8c616984183", + "version": 1, + "domain": "mobile_app", + "title": "700T1C", + "data": { + "app_data": { + "push_token": "fCP75bxzpBY:APA91bGOZjg0ObYy1-QJs8VJxqU-AQbNpQ1fz2Gc485DjxBsZ1LAsdLaZTJ3vgbgQ98Qt3jO0Xv4yfaLM4uiGunzKs4_iCrV-KAU2Qul5zN_dmQ6LVqo2PDMPSUZkAqRF7Ip5u9zz88F", + "push_url": "https://mobile-apps.home-assistant.io/api/sendPush/android/v1" + }, + "app_id": "io.homeassistant.companion.android", + "app_name": "Home Assistant", + "app_version": "2.5.0-full (400)", + "device_id": "6cb92e1b94d47217", + "device_name": "700T1C", + "manufacturer": "SAMSUNG ELECTRONICS CO., LTD.", + "model": "700T1C", + "os_name": "Android", + "os_version": "28", + "supports_encryption": false, + "user_id": "99685dc45d9f40dc8183e11ce5128038", + "webhook_id": "972369ddfba8b95dfc4b3ab68f74f19e19227919102c4720d1fc38d9fd45a0b4" + }, + "options": {}, + "system_options": { + "disable_new_entities": false + }, + "source": "registration", + "connection_class": "cloud_push", + "unique_id": "io.homeassistant.companion.android-6cb92e1b94d47217" + }, + { + "entry_id": "f2587c6a3ce34da5a1209b077a39d931", + "version": 1, + "domain": "mqtt", + "title": "broker.kebler.net", + "data": { + "broker": "broker.kebler.net", + "discovery": true, + "port": 1883 + }, + "options": {}, + "system_options": { + "disable_new_entities": false + }, + "source": "user", + "connection_class": "local_push", + "unique_id": null + }, + { + "entry_id": "9436e71647e64581a19a2cc8172f9a35", + "version": 1, + "domain": "esphome", + "title": "inside_temp_humd", + "data": { + "host": "10.0.0.180", + "password": "", + "port": 6053 + }, + "options": {}, + "system_options": { + "disable_new_entities": false + }, + "source": "zeroconf", + "connection_class": "local_push", + "unique_id": "inside_temp_humd" + }, + { + "entry_id": "cf5266d3fa4a4824850935818193ac48", + "version": 1, + "domain": "esphome", + "title": "outside_temp_humd", + "data": { + "host": "10.0.0.222", + "password": "", + "port": 6053 + }, + "options": {}, + "system_options": { + "disable_new_entities": false + }, + "source": "user", + "connection_class": "local_push", + "unique_id": "outside_temp_humd" + }, + { + "entry_id": "3ed789ad543542018f094a6d3d209d9a", + "version": 1, + "domain": "nws", + "title": "KGCD", + "data": { + "api_key": "kebler.net@gmail.com", + "latitude": 44.46097018572946, + "longitude": -118.71158838272095, + "station": "KGCD" + }, + "options": {}, + "system_options": { + "disable_new_entities": false + }, + "source": "user", + "connection_class": "cloud_poll", + "unique_id": "44.46097018572946_-118.71158838272095" + }, + { + "entry_id": "b7c6f2ecbb0d48ce83a92d218b4e129c", + "version": 1, + "domain": "mobile_app", + "title": "SM-G935R4", + "data": { + "app_data": { + "push_token": "dR6iDAsFIAI:APA91bHbf9uAX51rcCn1eD8J__in-9K7sqaXbnFgfVxE4wEpNmD3lcwBR0JeG-sVnWx1dUYhj4BRP6BHQy1uFAHWjs890GAIPvYU-AWKRRR-GuDhaWvXnLYX94yLQp80GZMxbpOG9-Ke", + "push_url": "https://mobile-apps.home-assistant.io/api/sendPush/android/v1" + }, + "app_id": "io.homeassistant.companion.android", + "app_name": "Home Assistant", + "app_version": "1.11.0-215 (215)", + "device_id": "ea51283736a711a9", + "device_name": "SM-G935R4", + "manufacturer": "samsung", + "model": "SM-G935R4", + "os_name": "Android", + "os_version": "26", + "supports_encryption": false, + "user_id": "99685dc45d9f40dc8183e11ce5128038", + "webhook_id": "b0bd2f0affedb208d53f95e0f264ec0ba55e5c867ee3a864dd94e29c3f8a8db0" + }, + "options": {}, + "system_options": { + "disable_new_entities": false + }, + "source": "registration", + "connection_class": "cloud_push", + "unique_id": "io.homeassistant.companion.android-ea51283736a711a9" + }, + { + "entry_id": "ea9548ce249e4e388ef42aa16cedfa29", + "version": 1, + "domain": "mobile_app", + "title": "White Moto G", + "data": { + "app_data": { + "push_token": "f-HooFTWTWad3h8a1EJswU:APA91bE8CUndwczKWO6Cb59jnXddyUwL5zYtFO9CJxhUqBnbOJvPYRaLMeLIC4CIJAdFkZ6ep82_7RxoIVRtYfhP_qOByjOgYImrapNEUjSayCmiyjTZaEH1eCzshzeaauD16NO8THyj", + "push_url": "https://mobile-apps.home-assistant.io/api/sendPush/android/v1" + }, + "app_id": "io.homeassistant.companion.android", + "app_name": "Home Assistant", + "app_version": "1.11.0-215 (215)", + "device_id": "764372054dcb3823", + "device_name": "XT1064", + "manufacturer": "motorola", + "model": "XT1064", + "os_name": "Android", + "os_version": "23", + "supports_encryption": false, + "user_id": "99685dc45d9f40dc8183e11ce5128038", + "webhook_id": "5f53d4c3439e791aab47663319361609868cb300c034204db0e25e40147d27be" + }, + "options": {}, + "system_options": { + "disable_new_entities": false + }, + "source": "registration", + "connection_class": "cloud_push", + "unique_id": "io.homeassistant.companion.android-764372054dcb3823" + }, + { + "entry_id": "0f3b31063a5d4293a373c98d6c5d5c77", + "version": 1, + "domain": "mobile_app", + "title": "LGUS215", + "data": { + "app_data": { + "push_token": "dHFyVftSS7GU1tZWdpLnZp:APA91bH9Jy7ZBQ-vmUQwZKk4ymzjrbFyxuXJRcVlNnbPcDMFlbG8zV5VOX2wbbOYW7N72LbSLLYjMbnQy5zDsknYEI7QaNXfcNnGHbF3d7AsD2NrvJ9pYmsde5e3tpTaBYAkBMAqDnSa", + "push_url": "https://mobile-apps.home-assistant.io/api/sendPush/android/v1" + }, + "app_id": "io.homeassistant.companion.android", + "app_name": "Home Assistant", + "app_version": "1.11.0-215 (215)", + "device_id": "315c682d3b2d88cc", + "device_name": "LGUS215", + "manufacturer": "LGE", + "model": "LGUS215", + "os_name": "Android", + "os_version": "24", + "supports_encryption": false, + "user_id": "99685dc45d9f40dc8183e11ce5128038", + "webhook_id": "05937d05f5a6239334725f7b14a462ab0e409dbbe11c2cf4b2f20747f403edf2" + }, + "options": {}, + "system_options": { + "disable_new_entities": false + }, + "source": "registration", + "connection_class": "cloud_push", + "unique_id": "io.homeassistant.companion.android-315c682d3b2d88cc" + }, + { + "entry_id": "d00c44239e11a6b5cc8a1f44eb4c23e2", + "version": 1, + "domain": "hacs", + "title": "", + "data": { + "token": "ab27da1f11e3827fa78258ac8ed11795c6261c34" + }, + "options": {}, + "system_options": { + "disable_new_entities": false + }, + "source": "user", + "connection_class": "cloud_poll", + "unique_id": null + } + ] + } +} \ No newline at end of file diff --git a/.storage/core.device_registry b/.storage/core.device_registry new file mode 100644 index 0000000..50c97fc --- /dev/null +++ b/.storage/core.device_registry @@ -0,0 +1,177 @@ +{ + "version": 1, + "key": "core.device_registry", + "data": { + "devices": [ + { + "config_entries": [ + "2872c3dbbf1246c2b7aab8c616984183" + ], + "connections": [], + "identifiers": [ + [ + "mobile_app", + "6cb92e1b94d47217" + ] + ], + "manufacturer": "SAMSUNG ELECTRONICS CO., LTD.", + "model": "700T1C", + "name": "700T1C", + "sw_version": "28", + "entry_type": null, + "id": "515c6f88684b452e86231c4411e3001e", + "via_device_id": null, + "area_id": null, + "name_by_user": null + }, + { + "config_entries": [ + "f2587c6a3ce34da5a1209b077a39d931" + ], + "connections": [], + "identifiers": [ + [ + "mqtt", + "84f3eb3b8492" + ] + ], + "manufacturer": "espressif", + "model": "PLATFORMIO_D1_MINI", + "name": "outside_temp_humd", + "sw_version": "esphome v1.14.3 Apr 23 2020, 12:14:31", + "entry_type": null, + "id": "93560a2f0c64414e8c1a69dbbf3bf97f", + "via_device_id": null, + "area_id": null, + "name_by_user": null + }, + { + "config_entries": [ + "9436e71647e64581a19a2cc8172f9a35" + ], + "connections": [ + [ + "mac", + "84:f3:eb:3b:77:30" + ] + ], + "identifiers": [], + "manufacturer": "espressif", + "model": "PLATFORMIO_D1_MINI", + "name": "inside_temp_humd", + "sw_version": "1.15.3 (Dec 1 2020, 11:20:56)", + "entry_type": null, + "id": "1883f8cbd6b548b992e9a1974008f81f", + "via_device_id": null, + "area_id": null, + "name_by_user": null + }, + { + "config_entries": [ + "cf5266d3fa4a4824850935818193ac48" + ], + "connections": [ + [ + "mac", + "84:f3:eb:3b:84:92" + ] + ], + "identifiers": [], + "manufacturer": "espressif", + "model": "PLATFORMIO_D1_MINI", + "name": "outside_temp_humd", + "sw_version": "1.15.3 (Dec 1 2020, 10:59:50)", + "entry_type": null, + "id": "207200b764c24bb2ac697f37d7c8b4e2", + "via_device_id": null, + "area_id": null, + "name_by_user": null + }, + { + "config_entries": [ + "b7c6f2ecbb0d48ce83a92d218b4e129c" + ], + "connections": [], + "identifiers": [ + [ + "mobile_app", + "ea51283736a711a9" + ] + ], + "manufacturer": "samsung", + "model": "SM-G935R4", + "name": "SM-G935R4", + "sw_version": "26", + "entry_type": null, + "id": "379851aebc71474585da2f6ce68a5745", + "via_device_id": null, + "area_id": null, + "name_by_user": null + }, + { + "config_entries": [ + "ea9548ce249e4e388ef42aa16cedfa29" + ], + "connections": [], + "identifiers": [ + [ + "mobile_app", + "764372054dcb3823" + ] + ], + "manufacturer": "motorola", + "model": "XT1064", + "name": "XT1064", + "sw_version": "23", + "entry_type": null, + "id": "f16e11491ac04282ab179f5727d4f5dc", + "via_device_id": null, + "area_id": null, + "name_by_user": null + }, + { + "config_entries": [ + "0f3b31063a5d4293a373c98d6c5d5c77" + ], + "connections": [], + "identifiers": [ + [ + "mobile_app", + "315c682d3b2d88cc" + ] + ], + "manufacturer": "LGE", + "model": "LGUS215", + "name": "LGUS215", + "sw_version": "24", + "entry_type": null, + "id": "ff1bf04ce1b2439b8a7ebc9237a010d9", + "via_device_id": null, + "area_id": null, + "name_by_user": null + }, + { + "config_entries": [ + "d00c44239e11a6b5cc8a1f44eb4c23e2" + ], + "connections": [], + "identifiers": [ + [ + "hacs", + "0717a0cd-745c-48fd-9b16-c8534c9704f9-bc944b0f-fd42-4a58-a072-ade38d1444cd" + ] + ], + "manufacturer": "hacs.xyz", + "model": "", + "name": "HACS", + "sw_version": "1.8.0", + "entry_type": "service", + "id": "da36236384f2c8816377b036cf723ece", + "via_device_id": null, + "area_id": null, + "name_by_user": null + } + ], + "deleted_devices": [] + } +} \ No newline at end of file diff --git a/.storage/core.entity_registry b/.storage/core.entity_registry new file mode 100644 index 0000000..fcc017f --- /dev/null +++ b/.storage/core.entity_registry @@ -0,0 +1,1776 @@ +{ + "version": 1, + "key": "core.entity_registry", + "data": { + "entities": [ + { + "entity_id": "binary_sensor.updater", + "config_entry_id": null, + "device_id": null, + "area_id": null, + "unique_id": "updater", + "platform": "updater", + "name": null, + "icon": null, + "disabled_by": null, + "capabilities": {}, + "supported_features": 0, + "device_class": null, + "unit_of_measurement": null, + "original_name": "Updater", + "original_icon": null + }, + { + "entity_id": "person.sysadmin", + "config_entry_id": null, + "device_id": null, + "area_id": null, + "unique_id": "sysadmin", + "platform": "person", + "name": null, + "icon": null, + "disabled_by": null, + "capabilities": {}, + "supported_features": 0, + "device_class": null, + "unit_of_measurement": null, + "original_name": "sysadmin", + "original_icon": null + }, + { + "entity_id": "input_number.fan_on_temp", + "config_entry_id": null, + "device_id": null, + "area_id": null, + "unique_id": "fan_on_temp", + "platform": "input_number", + "name": null, + "icon": null, + "disabled_by": null, + "capabilities": {}, + "supported_features": 0, + "device_class": null, + "unit_of_measurement": "Celcuis", + "original_name": "Fan On Temp", + "original_icon": "mdi:target" + }, + { + "entity_id": "input_datetime.security_lights_interior_time_off", + "config_entry_id": null, + "device_id": null, + "area_id": null, + "unique_id": "security_lights_interior_time_off", + "platform": "input_datetime", + "name": null, + "icon": null, + "disabled_by": null, + "capabilities": {}, + "supported_features": 0, + "device_class": null, + "unit_of_measurement": null, + "original_name": null, + "original_icon": null + }, + { + "entity_id": "input_boolean.zone_1_reset_zone", + "config_entry_id": null, + "device_id": null, + "area_id": null, + "unique_id": "zone_1_reset_zone", + "platform": "input_boolean", + "name": null, + "icon": null, + "disabled_by": null, + "capabilities": {}, + "supported_features": 0, + "device_class": null, + "unit_of_measurement": null, + "original_name": "Reset Next Run Timer", + "original_icon": "mdi:lock-reset" + }, + { + "entity_id": "input_number.node_test_first_number", + "config_entry_id": null, + "device_id": null, + "area_id": null, + "unique_id": "node_test_first_number", + "platform": "input_number", + "name": null, + "icon": null, + "disabled_by": null, + "capabilities": {}, + "supported_features": 0, + "device_class": null, + "unit_of_measurement": null, + "original_name": "First Number", + "original_icon": null + }, + { + "entity_id": "input_number.node_test_second_number", + "config_entry_id": null, + "device_id": null, + "area_id": null, + "unique_id": "node_test_second_number", + "platform": "input_number", + "name": null, + "icon": null, + "disabled_by": null, + "capabilities": {}, + "supported_features": 0, + "device_class": null, + "unit_of_measurement": null, + "original_name": "Second Number", + "original_icon": null + }, + { + "entity_id": "input_number.test_schedule_base_hour", + "config_entry_id": null, + "device_id": null, + "area_id": null, + "unique_id": "test_schedule_base_hour", + "platform": "input_number", + "name": null, + "icon": null, + "disabled_by": null, + "capabilities": {}, + "supported_features": 0, + "device_class": null, + "unit_of_measurement": "hours", + "original_name": "Hour of Day", + "original_icon": "mdi:camera-timer" + }, + { + "entity_id": "input_number.test_schedule_base_minute", + "config_entry_id": null, + "device_id": null, + "area_id": null, + "unique_id": "test_schedule_base_minute", + "platform": "input_number", + "name": null, + "icon": null, + "disabled_by": null, + "capabilities": {}, + "supported_features": 0, + "device_class": null, + "unit_of_measurement": "minutes", + "original_name": "Minute of Day", + "original_icon": "mdi:camera-timer" + }, + { + "entity_id": "input_number.test_timer_duration", + "config_entry_id": null, + "device_id": null, + "area_id": null, + "unique_id": "test_timer_duration", + "platform": "input_number", + "name": null, + "icon": null, + "disabled_by": null, + "capabilities": {}, + "supported_features": 0, + "device_class": null, + "unit_of_measurement": "seconds", + "original_name": "Duration", + "original_icon": "mdi:camera-timer" + }, + { + "entity_id": "input_number.zone_1_timer_minutes", + "config_entry_id": null, + "device_id": null, + "area_id": null, + "unique_id": "zone_1_timer_minutes", + "platform": "input_number", + "name": null, + "icon": null, + "disabled_by": null, + "capabilities": {}, + "supported_features": 0, + "device_class": null, + "unit_of_measurement": null, + "original_name": "Minutes", + "original_icon": "mdi:timer" + }, + { + "entity_id": "input_number.zone_1_timer_hours", + "config_entry_id": null, + "device_id": null, + "area_id": null, + "unique_id": "zone_1_timer_hours", + "platform": "input_number", + "name": null, + "icon": null, + "disabled_by": null, + "capabilities": {}, + "supported_features": 0, + "device_class": null, + "unit_of_measurement": null, + "original_name": "Hour", + "original_icon": "mdi:timer" + }, + { + "entity_id": "input_number.zone_1_duration", + "config_entry_id": null, + "device_id": null, + "area_id": null, + "unique_id": "zone_1_duration", + "platform": "input_number", + "name": null, + "icon": null, + "disabled_by": null, + "capabilities": {}, + "supported_features": 0, + "device_class": null, + "unit_of_measurement": null, + "original_name": "Duration", + "original_icon": "mdi:camera-timer" + }, + { + "entity_id": "input_number.zone_1_repeat", + "config_entry_id": null, + "device_id": null, + "area_id": null, + "unique_id": "zone_1_repeat", + "platform": "input_number", + "name": null, + "icon": null, + "disabled_by": null, + "capabilities": {}, + "supported_features": 0, + "device_class": null, + "unit_of_measurement": null, + "original_name": "Repeat Every - HH", + "original_icon": "mdi:repeat" + }, + { + "entity_id": "device_tracker.700t1c", + "config_entry_id": "2872c3dbbf1246c2b7aab8c616984183", + "device_id": "515c6f88684b452e86231c4411e3001e", + "area_id": null, + "unique_id": "6cb92e1b94d47217", + "platform": "mobile_app", + "name": null, + "icon": null, + "disabled_by": null, + "capabilities": {}, + "supported_features": 0, + "device_class": null, + "unit_of_measurement": null, + "original_name": "700T1C", + "original_icon": null + }, + { + "entity_id": "sensor.outside_temperature_2", + "config_entry_id": "f2587c6a3ce34da5a1209b077a39d931", + "device_id": "93560a2f0c64414e8c1a69dbbf3bf97f", + "area_id": null, + "unique_id": "ESPsensoroutside_temperature", + "platform": "mqtt", + "name": null, + "icon": null, + "disabled_by": null, + "capabilities": {}, + "supported_features": 0, + "device_class": null, + "unit_of_measurement": "\u00b0C", + "original_name": "Outside Temperature", + "original_icon": "mdi:thermometer" + }, + { + "entity_id": "sensor.outside_humidity_2", + "config_entry_id": "f2587c6a3ce34da5a1209b077a39d931", + "device_id": "93560a2f0c64414e8c1a69dbbf3bf97f", + "area_id": null, + "unique_id": "ESPsensoroutside_humidity", + "platform": "mqtt", + "name": null, + "icon": null, + "disabled_by": null, + "capabilities": {}, + "supported_features": 0, + "device_class": null, + "unit_of_measurement": "%", + "original_name": "Outside Humidity", + "original_icon": "mdi:water-percent" + }, + { + "entity_id": "sensor.inside_temperature", + "config_entry_id": "9436e71647e64581a19a2cc8172f9a35", + "device_id": "1883f8cbd6b548b992e9a1974008f81f", + "area_id": null, + "unique_id": "inside_temp_humdsensorinside_temperature", + "platform": "esphome", + "name": null, + "icon": null, + "disabled_by": null, + "capabilities": {}, + "supported_features": 0, + "device_class": null, + "unit_of_measurement": "\u00b0C", + "original_name": "Inside Temperature", + "original_icon": "mdi:thermometer" + }, + { + "entity_id": "sensor.inside_humidity", + "config_entry_id": "9436e71647e64581a19a2cc8172f9a35", + "device_id": "1883f8cbd6b548b992e9a1974008f81f", + "area_id": null, + "unique_id": "inside_temp_humdsensorinside_humidity", + "platform": "esphome", + "name": null, + "icon": null, + "disabled_by": null, + "capabilities": {}, + "supported_features": 0, + "device_class": null, + "unit_of_measurement": "%", + "original_name": "Inside Humidity", + "original_icon": "mdi:water-percent" + }, + { + "entity_id": "sensor.outside_temperature", + "config_entry_id": "cf5266d3fa4a4824850935818193ac48", + "device_id": "207200b764c24bb2ac697f37d7c8b4e2", + "area_id": null, + "unique_id": "outside_temp_humdsensoroutside_temperature", + "platform": "esphome", + "name": null, + "icon": null, + "disabled_by": null, + "capabilities": {}, + "supported_features": 0, + "device_class": null, + "unit_of_measurement": "\u00b0C", + "original_name": "Outside Temperature", + "original_icon": "mdi:thermometer" + }, + { + "entity_id": "sensor.outside_humidity", + "config_entry_id": "cf5266d3fa4a4824850935818193ac48", + "device_id": "207200b764c24bb2ac697f37d7c8b4e2", + "area_id": null, + "unique_id": "outside_temp_humdsensoroutside_humidity", + "platform": "esphome", + "name": null, + "icon": null, + "disabled_by": null, + "capabilities": {}, + "supported_features": 0, + "device_class": null, + "unit_of_measurement": "%", + "original_name": "Outside Humidity", + "original_icon": "mdi:water-percent" + }, + { + "entity_id": "sensor.700t1c_battery_level", + "config_entry_id": "2872c3dbbf1246c2b7aab8c616984183", + "device_id": "515c6f88684b452e86231c4411e3001e", + "area_id": null, + "unique_id": "972369ddfba8b95dfc4b3ab68f74f19e19227919102c4720d1fc38d9fd45a0b4_battery_level", + "platform": "mobile_app", + "name": null, + "icon": null, + "disabled_by": null, + "capabilities": {}, + "supported_features": 0, + "device_class": "battery", + "unit_of_measurement": "%", + "original_name": "700T1C Battery Level", + "original_icon": "mdi:battery" + }, + { + "entity_id": "sensor.700t1c_battery_state", + "config_entry_id": "2872c3dbbf1246c2b7aab8c616984183", + "device_id": "515c6f88684b452e86231c4411e3001e", + "area_id": null, + "unique_id": "972369ddfba8b95dfc4b3ab68f74f19e19227919102c4720d1fc38d9fd45a0b4_battery_state", + "platform": "mobile_app", + "name": null, + "icon": null, + "disabled_by": null, + "capabilities": {}, + "supported_features": 0, + "device_class": "battery", + "unit_of_measurement": null, + "original_name": "700T1C Battery State", + "original_icon": "mdi:battery-charging" + }, + { + "entity_id": "sensor.700t1c_wifi_connection", + "config_entry_id": "2872c3dbbf1246c2b7aab8c616984183", + "device_id": "515c6f88684b452e86231c4411e3001e", + "area_id": null, + "unique_id": "972369ddfba8b95dfc4b3ab68f74f19e19227919102c4720d1fc38d9fd45a0b4_wifi_connection", + "platform": "mobile_app", + "name": null, + "icon": null, + "disabled_by": null, + "capabilities": {}, + "supported_features": 0, + "device_class": null, + "unit_of_measurement": null, + "original_name": "700T1C Wifi Connection", + "original_icon": "mdi:wifi-strength-off" + }, + { + "entity_id": "weather.kgcd_daynight", + "config_entry_id": "3ed789ad543542018f094a6d3d209d9a", + "device_id": null, + "area_id": null, + "unique_id": "44.46097018572946_-118.71158838272095_daynight", + "platform": "nws", + "name": null, + "icon": null, + "disabled_by": null, + "capabilities": {}, + "supported_features": 0, + "device_class": null, + "unit_of_measurement": null, + "original_name": "KGCD Daynight", + "original_icon": null + }, + { + "entity_id": "weather.kgcd_hourly", + "config_entry_id": "3ed789ad543542018f094a6d3d209d9a", + "device_id": null, + "area_id": null, + "unique_id": "44.46097018572946_-118.71158838272095_hourly", + "platform": "nws", + "name": null, + "icon": null, + "disabled_by": null, + "capabilities": {}, + "supported_features": 0, + "device_class": null, + "unit_of_measurement": null, + "original_name": "KGCD Hourly", + "original_icon": null + }, + { + "entity_id": "input_number.irrigation_zone_1_schedule_base_hour", + "config_entry_id": null, + "device_id": null, + "area_id": null, + "unique_id": "irrigation_zone_1_schedule_base_hour", + "platform": "input_number", + "name": null, + "icon": null, + "disabled_by": null, + "capabilities": {}, + "supported_features": 0, + "device_class": null, + "unit_of_measurement": "hours", + "original_name": "Hour of Day", + "original_icon": "mdi:clock" + }, + { + "entity_id": "input_number.irrigation_zone_1_schedule_base_minute", + "config_entry_id": null, + "device_id": null, + "area_id": null, + "unique_id": "irrigation_zone_1_schedule_base_minute", + "platform": "input_number", + "name": null, + "icon": null, + "disabled_by": null, + "capabilities": {}, + "supported_features": 0, + "device_class": null, + "unit_of_measurement": "minutes", + "original_name": "Minute of Day", + "original_icon": "mdi:clock" + }, + { + "entity_id": "input_boolean.irrigation_zone_1_schedule_enable", + "config_entry_id": null, + "device_id": null, + "area_id": null, + "unique_id": "irrigation_zone_1_schedule_enable", + "platform": "input_boolean", + "name": null, + "icon": null, + "disabled_by": null, + "capabilities": {}, + "supported_features": 0, + "device_class": null, + "unit_of_measurement": null, + "original_name": null, + "original_icon": null + }, + { + "entity_id": "input_boolean.irrigation_zone_1_manual", + "config_entry_id": null, + "device_id": null, + "area_id": null, + "unique_id": "irrigation_zone_1_manual", + "platform": "input_boolean", + "name": null, + "icon": null, + "disabled_by": null, + "capabilities": {}, + "supported_features": 0, + "device_class": null, + "unit_of_measurement": null, + "original_name": null, + "original_icon": null + }, + { + "entity_id": "input_number.irrigation_zone_1_duration", + "config_entry_id": null, + "device_id": null, + "area_id": null, + "unique_id": "irrigation_zone_1_duration", + "platform": "input_number", + "name": null, + "icon": null, + "disabled_by": null, + "capabilities": {}, + "supported_features": 0, + "device_class": null, + "unit_of_measurement": null, + "original_name": "Duration in minutes", + "original_icon": "mdi:timer-sand" + }, + { + "entity_id": "input_boolean.irrigation_zone_3_schedule_enable", + "config_entry_id": null, + "device_id": null, + "area_id": null, + "unique_id": "irrigation_zone_3_schedule_enable", + "platform": "input_boolean", + "name": null, + "icon": null, + "disabled_by": null, + "capabilities": {}, + "supported_features": 0, + "device_class": null, + "unit_of_measurement": null, + "original_name": null, + "original_icon": null + }, + { + "entity_id": "input_boolean.irrigation_zone_3_manual", + "config_entry_id": null, + "device_id": null, + "area_id": null, + "unique_id": "irrigation_zone_3_manual", + "platform": "input_boolean", + "name": null, + "icon": null, + "disabled_by": null, + "capabilities": {}, + "supported_features": 0, + "device_class": null, + "unit_of_measurement": null, + "original_name": null, + "original_icon": null + }, + { + "entity_id": "input_number.irrigation_zone_3_duration", + "config_entry_id": null, + "device_id": null, + "area_id": null, + "unique_id": "irrigation_zone_3_duration", + "platform": "input_number", + "name": null, + "icon": null, + "disabled_by": null, + "capabilities": {}, + "supported_features": 0, + "device_class": null, + "unit_of_measurement": null, + "original_name": "Duration in minutes", + "original_icon": "mdi:timer-sand" + }, + { + "entity_id": "input_number.irrigation_zone_3_schedule_base_hour", + "config_entry_id": null, + "device_id": null, + "area_id": null, + "unique_id": "irrigation_zone_3_schedule_base_hour", + "platform": "input_number", + "name": null, + "icon": null, + "disabled_by": null, + "capabilities": {}, + "supported_features": 0, + "device_class": null, + "unit_of_measurement": null, + "original_name": "Hour of Day", + "original_icon": "mdi:clock" + }, + { + "entity_id": "input_number.irrigation_zone_3_schedule_base_minute", + "config_entry_id": null, + "device_id": null, + "area_id": null, + "unique_id": "irrigation_zone_3_schedule_base_minute", + "platform": "input_number", + "name": null, + "icon": null, + "disabled_by": null, + "capabilities": {}, + "supported_features": 0, + "device_class": null, + "unit_of_measurement": null, + "original_name": "Minute of Day", + "original_icon": "mdi:clock" + }, + { + "entity_id": "input_boolean.irrigation_zone_3_schedule_enabled", + "config_entry_id": null, + "device_id": null, + "area_id": null, + "unique_id": "irrigation_zone_3_schedule_enabled", + "platform": "input_boolean", + "name": null, + "icon": null, + "disabled_by": null, + "capabilities": {}, + "supported_features": 0, + "device_class": null, + "unit_of_measurement": null, + "original_name": null, + "original_icon": null + }, + { + "entity_id": "input_boolean.irrigation_zone_3_state", + "config_entry_id": null, + "device_id": null, + "area_id": null, + "unique_id": "irrigation_zone_3_state", + "platform": "input_boolean", + "name": null, + "icon": null, + "disabled_by": null, + "capabilities": {}, + "supported_features": 0, + "device_class": null, + "unit_of_measurement": null, + "original_name": null, + "original_icon": null + }, + { + "entity_id": "input_boolean.irrigation_zone_3_active", + "config_entry_id": null, + "device_id": null, + "area_id": null, + "unique_id": "irrigation_zone_3_active", + "platform": "input_boolean", + "name": null, + "icon": null, + "disabled_by": null, + "capabilities": {}, + "supported_features": 0, + "device_class": null, + "unit_of_measurement": null, + "original_name": null, + "original_icon": null + }, + { + "entity_id": "input_boolean.irrigation_zone_1_schedule_enabled", + "config_entry_id": null, + "device_id": null, + "area_id": null, + "unique_id": "irrigation_zone_1_schedule_enabled", + "platform": "input_boolean", + "name": null, + "icon": null, + "disabled_by": null, + "capabilities": {}, + "supported_features": 0, + "device_class": null, + "unit_of_measurement": null, + "original_name": null, + "original_icon": null + }, + { + "entity_id": "input_boolean.irrigation_zone_1_active", + "config_entry_id": null, + "device_id": null, + "area_id": null, + "unique_id": "irrigation_zone_1_active", + "platform": "input_boolean", + "name": null, + "icon": null, + "disabled_by": null, + "capabilities": {}, + "supported_features": 0, + "device_class": null, + "unit_of_measurement": null, + "original_name": null, + "original_icon": null + }, + { + "entity_id": "input_boolean.irrigation_zone_2_schedule_enabled", + "config_entry_id": null, + "device_id": null, + "area_id": null, + "unique_id": "irrigation_zone_2_schedule_enabled", + "platform": "input_boolean", + "name": null, + "icon": null, + "disabled_by": null, + "capabilities": {}, + "supported_features": 0, + "device_class": null, + "unit_of_measurement": null, + "original_name": null, + "original_icon": null + }, + { + "entity_id": "input_boolean.irrigation_zone_2_active", + "config_entry_id": null, + "device_id": null, + "area_id": null, + "unique_id": "irrigation_zone_2_active", + "platform": "input_boolean", + "name": null, + "icon": null, + "disabled_by": null, + "capabilities": {}, + "supported_features": 0, + "device_class": null, + "unit_of_measurement": null, + "original_name": null, + "original_icon": null + }, + { + "entity_id": "input_boolean.irrigation_zone_4_schedule_enabled", + "config_entry_id": null, + "device_id": null, + "area_id": null, + "unique_id": "irrigation_zone_4_schedule_enabled", + "platform": "input_boolean", + "name": null, + "icon": null, + "disabled_by": null, + "capabilities": {}, + "supported_features": 0, + "device_class": null, + "unit_of_measurement": null, + "original_name": null, + "original_icon": null + }, + { + "entity_id": "input_boolean.irrigation_zone_4_active", + "config_entry_id": null, + "device_id": null, + "area_id": null, + "unique_id": "irrigation_zone_4_active", + "platform": "input_boolean", + "name": null, + "icon": null, + "disabled_by": null, + "capabilities": {}, + "supported_features": 0, + "device_class": null, + "unit_of_measurement": null, + "original_name": null, + "original_icon": null + }, + { + "entity_id": "input_boolean.irrigation_zone_5_schedule_enabled", + "config_entry_id": null, + "device_id": null, + "area_id": null, + "unique_id": "irrigation_zone_5_schedule_enabled", + "platform": "input_boolean", + "name": null, + "icon": null, + "disabled_by": null, + "capabilities": {}, + "supported_features": 0, + "device_class": null, + "unit_of_measurement": null, + "original_name": null, + "original_icon": null + }, + { + "entity_id": "input_boolean.irrigation_zone_5_active", + "config_entry_id": null, + "device_id": null, + "area_id": null, + "unique_id": "irrigation_zone_5_active", + "platform": "input_boolean", + "name": null, + "icon": null, + "disabled_by": null, + "capabilities": {}, + "supported_features": 0, + "device_class": null, + "unit_of_measurement": null, + "original_name": null, + "original_icon": null + }, + { + "entity_id": "input_boolean.irrigation_zone_6_schedule_enabled", + "config_entry_id": null, + "device_id": null, + "area_id": null, + "unique_id": "irrigation_zone_6_schedule_enabled", + "platform": "input_boolean", + "name": null, + "icon": null, + "disabled_by": null, + "capabilities": {}, + "supported_features": 0, + "device_class": null, + "unit_of_measurement": null, + "original_name": null, + "original_icon": null + }, + { + "entity_id": "input_boolean.irrigation_zone_6_active", + "config_entry_id": null, + "device_id": null, + "area_id": null, + "unique_id": "irrigation_zone_6_active", + "platform": "input_boolean", + "name": null, + "icon": null, + "disabled_by": null, + "capabilities": {}, + "supported_features": 0, + "device_class": null, + "unit_of_measurement": null, + "original_name": null, + "original_icon": null + }, + { + "entity_id": "input_boolean.irrigation_zone_7_schedule_enabled", + "config_entry_id": null, + "device_id": null, + "area_id": null, + "unique_id": "irrigation_zone_7_schedule_enabled", + "platform": "input_boolean", + "name": null, + "icon": null, + "disabled_by": null, + "capabilities": {}, + "supported_features": 0, + "device_class": null, + "unit_of_measurement": null, + "original_name": null, + "original_icon": null + }, + { + "entity_id": "input_boolean.irrigation_zone_7_active", + "config_entry_id": null, + "device_id": null, + "area_id": null, + "unique_id": "irrigation_zone_7_active", + "platform": "input_boolean", + "name": null, + "icon": null, + "disabled_by": null, + "capabilities": {}, + "supported_features": 0, + "device_class": null, + "unit_of_measurement": null, + "original_name": null, + "original_icon": null + }, + { + "entity_id": "input_number.irrigation_zone_2_duration", + "config_entry_id": null, + "device_id": null, + "area_id": null, + "unique_id": "irrigation_zone_2_duration", + "platform": "input_number", + "name": null, + "icon": null, + "disabled_by": null, + "capabilities": {}, + "supported_features": 0, + "device_class": null, + "unit_of_measurement": null, + "original_name": "Duration in minutes", + "original_icon": "mdi:timer-sand" + }, + { + "entity_id": "input_number.irrigation_zone_2_schedule_base_hour", + "config_entry_id": null, + "device_id": null, + "area_id": null, + "unique_id": "irrigation_zone_2_schedule_base_hour", + "platform": "input_number", + "name": null, + "icon": null, + "disabled_by": null, + "capabilities": {}, + "supported_features": 0, + "device_class": null, + "unit_of_measurement": null, + "original_name": "Hour of Day", + "original_icon": "mdi:clock" + }, + { + "entity_id": "input_number.irrigation_zone_2_schedule_base_minute", + "config_entry_id": null, + "device_id": null, + "area_id": null, + "unique_id": "irrigation_zone_2_schedule_base_minute", + "platform": "input_number", + "name": null, + "icon": null, + "disabled_by": null, + "capabilities": {}, + "supported_features": 0, + "device_class": null, + "unit_of_measurement": null, + "original_name": "Minute of Day", + "original_icon": "mdi:clock" + }, + { + "entity_id": "input_number.irrigation_zone_4_duration", + "config_entry_id": null, + "device_id": null, + "area_id": null, + "unique_id": "irrigation_zone_4_duration", + "platform": "input_number", + "name": null, + "icon": null, + "disabled_by": null, + "capabilities": {}, + "supported_features": 0, + "device_class": null, + "unit_of_measurement": null, + "original_name": "Duration in minutes", + "original_icon": "mdi:timer-sand" + }, + { + "entity_id": "input_number.irrigation_zone_4_schedule_base_hour", + "config_entry_id": null, + "device_id": null, + "area_id": null, + "unique_id": "irrigation_zone_4_schedule_base_hour", + "platform": "input_number", + "name": null, + "icon": null, + "disabled_by": null, + "capabilities": {}, + "supported_features": 0, + "device_class": null, + "unit_of_measurement": null, + "original_name": "Hour of Day", + "original_icon": "mdi:clock" + }, + { + "entity_id": "input_number.irrigation_zone_4_schedule_base_minute", + "config_entry_id": null, + "device_id": null, + "area_id": null, + "unique_id": "irrigation_zone_4_schedule_base_minute", + "platform": "input_number", + "name": null, + "icon": null, + "disabled_by": null, + "capabilities": {}, + "supported_features": 0, + "device_class": null, + "unit_of_measurement": null, + "original_name": "Minute of Day", + "original_icon": "mdi:clock" + }, + { + "entity_id": "input_number.irrigation_zone_5_duration", + "config_entry_id": null, + "device_id": null, + "area_id": null, + "unique_id": "irrigation_zone_5_duration", + "platform": "input_number", + "name": null, + "icon": null, + "disabled_by": null, + "capabilities": {}, + "supported_features": 0, + "device_class": null, + "unit_of_measurement": null, + "original_name": "Duration in minutes", + "original_icon": "mdi:timer-sand" + }, + { + "entity_id": "input_number.irrigation_zone_5_schedule_base_hour", + "config_entry_id": null, + "device_id": null, + "area_id": null, + "unique_id": "irrigation_zone_5_schedule_base_hour", + "platform": "input_number", + "name": null, + "icon": null, + "disabled_by": null, + "capabilities": {}, + "supported_features": 0, + "device_class": null, + "unit_of_measurement": null, + "original_name": "Hour of Day", + "original_icon": "mdi:clock" + }, + { + "entity_id": "input_number.irrigation_zone_5_schedule_base_minute", + "config_entry_id": null, + "device_id": null, + "area_id": null, + "unique_id": "irrigation_zone_5_schedule_base_minute", + "platform": "input_number", + "name": null, + "icon": null, + "disabled_by": null, + "capabilities": {}, + "supported_features": 0, + "device_class": null, + "unit_of_measurement": null, + "original_name": "Minute of Day", + "original_icon": "mdi:clock" + }, + { + "entity_id": "input_number.irrigation_zone_6_duration", + "config_entry_id": null, + "device_id": null, + "area_id": null, + "unique_id": "irrigation_zone_6_duration", + "platform": "input_number", + "name": null, + "icon": null, + "disabled_by": null, + "capabilities": {}, + "supported_features": 0, + "device_class": null, + "unit_of_measurement": null, + "original_name": "Duration in minutes", + "original_icon": "mdi:timer-sand" + }, + { + "entity_id": "input_number.irrigation_zone_6_schedule_base_hour", + "config_entry_id": null, + "device_id": null, + "area_id": null, + "unique_id": "irrigation_zone_6_schedule_base_hour", + "platform": "input_number", + "name": null, + "icon": null, + "disabled_by": null, + "capabilities": {}, + "supported_features": 0, + "device_class": null, + "unit_of_measurement": null, + "original_name": "Hour of Day", + "original_icon": "mdi:clock" + }, + { + "entity_id": "input_number.irrigation_zone_6_schedule_base_minute", + "config_entry_id": null, + "device_id": null, + "area_id": null, + "unique_id": "irrigation_zone_6_schedule_base_minute", + "platform": "input_number", + "name": null, + "icon": null, + "disabled_by": null, + "capabilities": {}, + "supported_features": 0, + "device_class": null, + "unit_of_measurement": null, + "original_name": "Minute of Day", + "original_icon": "mdi:clock" + }, + { + "entity_id": "input_number.irrigation_zone_7_duration", + "config_entry_id": null, + "device_id": null, + "area_id": null, + "unique_id": "irrigation_zone_7_duration", + "platform": "input_number", + "name": null, + "icon": null, + "disabled_by": null, + "capabilities": {}, + "supported_features": 0, + "device_class": null, + "unit_of_measurement": null, + "original_name": "Duration in minutes", + "original_icon": "mdi:timer-sand" + }, + { + "entity_id": "input_number.irrigation_zone_7_schedule_base_hour", + "config_entry_id": null, + "device_id": null, + "area_id": null, + "unique_id": "irrigation_zone_7_schedule_base_hour", + "platform": "input_number", + "name": null, + "icon": null, + "disabled_by": null, + "capabilities": {}, + "supported_features": 0, + "device_class": null, + "unit_of_measurement": null, + "original_name": "Hour of Day", + "original_icon": "mdi:clock" + }, + { + "entity_id": "input_number.irrigation_zone_7_schedule_base_minute", + "config_entry_id": null, + "device_id": null, + "area_id": null, + "unique_id": "irrigation_zone_7_schedule_base_minute", + "platform": "input_number", + "name": null, + "icon": null, + "disabled_by": null, + "capabilities": {}, + "supported_features": 0, + "device_class": null, + "unit_of_measurement": null, + "original_name": "Minute of Day", + "original_icon": "mdi:clock" + }, + { + "entity_id": "input_boolean.irrigation_pump", + "config_entry_id": null, + "device_id": null, + "area_id": null, + "unique_id": "irrigation_pump", + "platform": "input_boolean", + "name": null, + "icon": null, + "disabled_by": null, + "capabilities": {}, + "supported_features": 0, + "device_class": null, + "unit_of_measurement": null, + "original_name": null, + "original_icon": null + }, + { + "entity_id": "input_boolean.irrigation_enabled", + "config_entry_id": null, + "device_id": null, + "area_id": null, + "unique_id": "irrigation_enabled", + "platform": "input_boolean", + "name": null, + "icon": null, + "disabled_by": null, + "capabilities": {}, + "supported_features": 0, + "device_class": null, + "unit_of_measurement": null, + "original_name": null, + "original_icon": null + }, + { + "entity_id": "input_boolean.irrigation_zone_1_state", + "config_entry_id": null, + "device_id": null, + "area_id": null, + "unique_id": "irrigation_zone_1_state", + "platform": "input_boolean", + "name": null, + "icon": null, + "disabled_by": null, + "capabilities": {}, + "supported_features": 0, + "device_class": null, + "unit_of_measurement": null, + "original_name": null, + "original_icon": null + }, + { + "entity_id": "input_boolean.irrigation_zone_2_state", + "config_entry_id": null, + "device_id": null, + "area_id": null, + "unique_id": "irrigation_zone_2_state", + "platform": "input_boolean", + "name": null, + "icon": null, + "disabled_by": null, + "capabilities": {}, + "supported_features": 0, + "device_class": null, + "unit_of_measurement": null, + "original_name": null, + "original_icon": null + }, + { + "entity_id": "input_boolean.irrigation_zone_4_state", + "config_entry_id": null, + "device_id": null, + "area_id": null, + "unique_id": "irrigation_zone_4_state", + "platform": "input_boolean", + "name": null, + "icon": null, + "disabled_by": null, + "capabilities": {}, + "supported_features": 0, + "device_class": null, + "unit_of_measurement": null, + "original_name": null, + "original_icon": null + }, + { + "entity_id": "input_boolean.irrigation_zone_5_state", + "config_entry_id": null, + "device_id": null, + "area_id": null, + "unique_id": "irrigation_zone_5_state", + "platform": "input_boolean", + "name": null, + "icon": null, + "disabled_by": null, + "capabilities": {}, + "supported_features": 0, + "device_class": null, + "unit_of_measurement": null, + "original_name": null, + "original_icon": null + }, + { + "entity_id": "input_boolean.irrigation_zone_6_state", + "config_entry_id": null, + "device_id": null, + "area_id": null, + "unique_id": "irrigation_zone_6_state", + "platform": "input_boolean", + "name": null, + "icon": null, + "disabled_by": null, + "capabilities": {}, + "supported_features": 0, + "device_class": null, + "unit_of_measurement": null, + "original_name": null, + "original_icon": null + }, + { + "entity_id": "input_boolean.irrigation_zone_7_state", + "config_entry_id": null, + "device_id": null, + "area_id": null, + "unique_id": "irrigation_zone_7_state", + "platform": "input_boolean", + "name": null, + "icon": null, + "disabled_by": null, + "capabilities": {}, + "supported_features": 0, + "device_class": null, + "unit_of_measurement": null, + "original_name": null, + "original_icon": null + }, + { + "entity_id": "device_tracker.sm_g935r4", + "config_entry_id": "b7c6f2ecbb0d48ce83a92d218b4e129c", + "device_id": "379851aebc71474585da2f6ce68a5745", + "area_id": null, + "unique_id": "ea51283736a711a9", + "platform": "mobile_app", + "name": null, + "icon": null, + "disabled_by": null, + "capabilities": {}, + "supported_features": 0, + "device_class": null, + "unit_of_measurement": null, + "original_name": "SM-G935R4", + "original_icon": null + }, + { + "entity_id": "sensor.sm_g935r4_battery_level", + "config_entry_id": "b7c6f2ecbb0d48ce83a92d218b4e129c", + "device_id": "379851aebc71474585da2f6ce68a5745", + "area_id": null, + "unique_id": "b0bd2f0affedb208d53f95e0f264ec0ba55e5c867ee3a864dd94e29c3f8a8db0_battery_level", + "platform": "mobile_app", + "name": null, + "icon": null, + "disabled_by": null, + "capabilities": {}, + "supported_features": 0, + "device_class": "battery", + "unit_of_measurement": "%", + "original_name": "SM-G935R4 Battery Level", + "original_icon": "mdi:battery-50" + }, + { + "entity_id": "sensor.sm_g935r4_battery_state", + "config_entry_id": "b7c6f2ecbb0d48ce83a92d218b4e129c", + "device_id": "379851aebc71474585da2f6ce68a5745", + "area_id": null, + "unique_id": "b0bd2f0affedb208d53f95e0f264ec0ba55e5c867ee3a864dd94e29c3f8a8db0_battery_state", + "platform": "mobile_app", + "name": null, + "icon": null, + "disabled_by": null, + "capabilities": {}, + "supported_features": 0, + "device_class": "battery", + "unit_of_measurement": null, + "original_name": "SM-G935R4 Battery State", + "original_icon": "mdi:battery-50" + }, + { + "entity_id": "sensor.sm_g935r4_wifi_connection", + "config_entry_id": "b7c6f2ecbb0d48ce83a92d218b4e129c", + "device_id": "379851aebc71474585da2f6ce68a5745", + "area_id": null, + "unique_id": "b0bd2f0affedb208d53f95e0f264ec0ba55e5c867ee3a864dd94e29c3f8a8db0_wifi_connection", + "platform": "mobile_app", + "name": null, + "icon": null, + "disabled_by": null, + "capabilities": {}, + "supported_features": 0, + "device_class": null, + "unit_of_measurement": null, + "original_name": "SM-G935R4 Wifi Connection", + "original_icon": "mdi:wifi-strength-2" + }, + { + "entity_id": "sensor.sm_g935r4_geocoded_location", + "config_entry_id": "b7c6f2ecbb0d48ce83a92d218b4e129c", + "device_id": "379851aebc71474585da2f6ce68a5745", + "area_id": null, + "unique_id": "b0bd2f0affedb208d53f95e0f264ec0ba55e5c867ee3a864dd94e29c3f8a8db0_geocoded_location", + "platform": "mobile_app", + "name": null, + "icon": null, + "disabled_by": null, + "capabilities": {}, + "supported_features": 0, + "device_class": null, + "unit_of_measurement": null, + "original_name": "SM-G935R4 Geocoded Location", + "original_icon": "mdi:map" + }, + { + "entity_id": "sensor.xt1064_battery_state", + "config_entry_id": "ea9548ce249e4e388ef42aa16cedfa29", + "device_id": "f16e11491ac04282ab179f5727d4f5dc", + "area_id": null, + "unique_id": "5f53d4c3439e791aab47663319361609868cb300c034204db0e25e40147d27be_battery_state", + "platform": "mobile_app", + "name": null, + "icon": null, + "disabled_by": null, + "capabilities": {}, + "supported_features": 0, + "device_class": "battery", + "unit_of_measurement": null, + "original_name": "XT1064 Battery State", + "original_icon": "mdi:battery-charging" + }, + { + "entity_id": "sensor.xt1064_wifi_connection_2", + "config_entry_id": "ea9548ce249e4e388ef42aa16cedfa29", + "device_id": "f16e11491ac04282ab179f5727d4f5dc", + "area_id": null, + "unique_id": "5f53d4c3439e791aab47663319361609868cb300c034204db0e25e40147d27be_wifi_connection", + "platform": "mobile_app", + "name": null, + "icon": null, + "disabled_by": null, + "capabilities": {}, + "supported_features": 0, + "device_class": null, + "unit_of_measurement": null, + "original_name": "XT1064 Wifi Connection", + "original_icon": "mdi:wifi-strength-off" + }, + { + "entity_id": "device_tracker.white_motog", + "config_entry_id": "ea9548ce249e4e388ef42aa16cedfa29", + "device_id": "f16e11491ac04282ab179f5727d4f5dc", + "area_id": null, + "unique_id": "764372054dcb3823", + "platform": "mobile_app", + "name": "White Moto G", + "icon": null, + "disabled_by": null, + "capabilities": {}, + "supported_features": 0, + "device_class": null, + "unit_of_measurement": null, + "original_name": "XT1064", + "original_icon": null + }, + { + "entity_id": "sensor.white_motog_battery_level_2", + "config_entry_id": "ea9548ce249e4e388ef42aa16cedfa29", + "device_id": "f16e11491ac04282ab179f5727d4f5dc", + "area_id": null, + "unique_id": "5f53d4c3439e791aab47663319361609868cb300c034204db0e25e40147d27be_battery_level", + "platform": "mobile_app", + "name": null, + "icon": null, + "disabled_by": null, + "capabilities": {}, + "supported_features": 0, + "device_class": "battery", + "unit_of_measurement": "%", + "original_name": "XT1064 Battery Level", + "original_icon": "mdi:battery" + }, + { + "entity_id": "device_tracker.lgus215", + "config_entry_id": "0f3b31063a5d4293a373c98d6c5d5c77", + "device_id": "ff1bf04ce1b2439b8a7ebc9237a010d9", + "area_id": null, + "unique_id": "315c682d3b2d88cc", + "platform": "mobile_app", + "name": null, + "icon": null, + "disabled_by": null, + "capabilities": {}, + "supported_features": 0, + "device_class": null, + "unit_of_measurement": null, + "original_name": "LGUS215", + "original_icon": null + }, + { + "entity_id": "sensor.lgus215_battery_level", + "config_entry_id": "0f3b31063a5d4293a373c98d6c5d5c77", + "device_id": "ff1bf04ce1b2439b8a7ebc9237a010d9", + "area_id": null, + "unique_id": "05937d05f5a6239334725f7b14a462ab0e409dbbe11c2cf4b2f20747f403edf2_battery_level", + "platform": "mobile_app", + "name": null, + "icon": null, + "disabled_by": null, + "capabilities": {}, + "supported_features": 0, + "device_class": "battery", + "unit_of_measurement": "%", + "original_name": "LGUS215 Battery Level", + "original_icon": "mdi:battery-90" + }, + { + "entity_id": "sensor.lgus215_battery_state", + "config_entry_id": "0f3b31063a5d4293a373c98d6c5d5c77", + "device_id": "ff1bf04ce1b2439b8a7ebc9237a010d9", + "area_id": null, + "unique_id": "05937d05f5a6239334725f7b14a462ab0e409dbbe11c2cf4b2f20747f403edf2_battery_state", + "platform": "mobile_app", + "name": null, + "icon": null, + "disabled_by": null, + "capabilities": {}, + "supported_features": 0, + "device_class": "battery", + "unit_of_measurement": null, + "original_name": "LGUS215 Battery State", + "original_icon": "mdi:battery-90" + }, + { + "entity_id": "sensor.lgus215_wifi_connection", + "config_entry_id": "0f3b31063a5d4293a373c98d6c5d5c77", + "device_id": "ff1bf04ce1b2439b8a7ebc9237a010d9", + "area_id": null, + "unique_id": "05937d05f5a6239334725f7b14a462ab0e409dbbe11c2cf4b2f20747f403edf2_wifi_connection", + "platform": "mobile_app", + "name": null, + "icon": null, + "disabled_by": null, + "capabilities": {}, + "supported_features": 0, + "device_class": null, + "unit_of_measurement": null, + "original_name": "LGUS215 Wifi Connection", + "original_icon": "mdi:wifi-strength-off" + }, + { + "entity_id": "sensor.700t1c_bluetooth_connection", + "config_entry_id": "2872c3dbbf1246c2b7aab8c616984183", + "device_id": "515c6f88684b452e86231c4411e3001e", + "area_id": null, + "unique_id": "972369ddfba8b95dfc4b3ab68f74f19e19227919102c4720d1fc38d9fd45a0b4_bluetooth_connection", + "platform": "mobile_app", + "name": null, + "icon": null, + "disabled_by": null, + "capabilities": {}, + "supported_features": 0, + "device_class": null, + "unit_of_measurement": "connection(s)", + "original_name": "700T1C Bluetooth Connection", + "original_icon": "mdi:bluetooth" + }, + { + "entity_id": "sensor.700t1c_light_sensor", + "config_entry_id": "2872c3dbbf1246c2b7aab8c616984183", + "device_id": "515c6f88684b452e86231c4411e3001e", + "area_id": null, + "unique_id": "972369ddfba8b95dfc4b3ab68f74f19e19227919102c4720d1fc38d9fd45a0b4_light_sensor", + "platform": "mobile_app", + "name": null, + "icon": null, + "disabled_by": null, + "capabilities": {}, + "supported_features": 0, + "device_class": "illuminance", + "unit_of_measurement": "lx", + "original_name": "700T1C Light Sensor", + "original_icon": "mdi:brightness-5" + }, + { + "entity_id": "sensor.700t1c_next_alarm", + "config_entry_id": "2872c3dbbf1246c2b7aab8c616984183", + "device_id": "515c6f88684b452e86231c4411e3001e", + "area_id": null, + "unique_id": "972369ddfba8b95dfc4b3ab68f74f19e19227919102c4720d1fc38d9fd45a0b4_next_alarm", + "platform": "mobile_app", + "name": null, + "icon": null, + "disabled_by": null, + "capabilities": {}, + "supported_features": 0, + "device_class": "timestamp", + "unit_of_measurement": null, + "original_name": "700T1C Next Alarm", + "original_icon": "mdi:alarm" + }, + { + "entity_id": "sensor.700t1c_storage_sensor", + "config_entry_id": "2872c3dbbf1246c2b7aab8c616984183", + "device_id": "515c6f88684b452e86231c4411e3001e", + "area_id": null, + "unique_id": "972369ddfba8b95dfc4b3ab68f74f19e19227919102c4720d1fc38d9fd45a0b4_storage_sensor", + "platform": "mobile_app", + "name": null, + "icon": null, + "disabled_by": null, + "capabilities": {}, + "supported_features": 0, + "device_class": null, + "unit_of_measurement": "%", + "original_name": "700T1C Storage Sensor", + "original_icon": "mdi:harddisk" + }, + { + "entity_id": "sensor.sm_g935r4_bluetooth_connection", + "config_entry_id": "b7c6f2ecbb0d48ce83a92d218b4e129c", + "device_id": "379851aebc71474585da2f6ce68a5745", + "area_id": null, + "unique_id": "b0bd2f0affedb208d53f95e0f264ec0ba55e5c867ee3a864dd94e29c3f8a8db0_bluetooth_connection", + "platform": "mobile_app", + "name": null, + "icon": null, + "disabled_by": null, + "capabilities": {}, + "supported_features": 0, + "device_class": null, + "unit_of_measurement": "connection(s)", + "original_name": "SM-G935R4 Bluetooth Connection", + "original_icon": "mdi:bluetooth" + }, + { + "entity_id": "sensor.sm_g935r4_light_sensor", + "config_entry_id": "b7c6f2ecbb0d48ce83a92d218b4e129c", + "device_id": "379851aebc71474585da2f6ce68a5745", + "area_id": null, + "unique_id": "b0bd2f0affedb208d53f95e0f264ec0ba55e5c867ee3a864dd94e29c3f8a8db0_light_sensor", + "platform": "mobile_app", + "name": null, + "icon": null, + "disabled_by": null, + "capabilities": {}, + "supported_features": 0, + "device_class": "illuminance", + "unit_of_measurement": "lx", + "original_name": "SM-G935R4 Light Sensor", + "original_icon": "mdi:brightness-5" + }, + { + "entity_id": "sensor.sm_g935r4_next_alarm", + "config_entry_id": "b7c6f2ecbb0d48ce83a92d218b4e129c", + "device_id": "379851aebc71474585da2f6ce68a5745", + "area_id": null, + "unique_id": "b0bd2f0affedb208d53f95e0f264ec0ba55e5c867ee3a864dd94e29c3f8a8db0_next_alarm", + "platform": "mobile_app", + "name": null, + "icon": null, + "disabled_by": null, + "capabilities": {}, + "supported_features": 0, + "device_class": "timestamp", + "unit_of_measurement": null, + "original_name": "SM-G935R4 Next Alarm", + "original_icon": "mdi:alarm" + }, + { + "entity_id": "sensor.sm_g935r4_storage_sensor", + "config_entry_id": "b7c6f2ecbb0d48ce83a92d218b4e129c", + "device_id": "379851aebc71474585da2f6ce68a5745", + "area_id": null, + "unique_id": "b0bd2f0affedb208d53f95e0f264ec0ba55e5c867ee3a864dd94e29c3f8a8db0_storage_sensor", + "platform": "mobile_app", + "name": null, + "icon": null, + "disabled_by": null, + "capabilities": {}, + "supported_features": 0, + "device_class": null, + "unit_of_measurement": "%", + "original_name": "SM-G935R4 Storage Sensor", + "original_icon": "mdi:harddisk" + }, + { + "entity_id": "sensor.700t1c_audio_sensor", + "config_entry_id": "2872c3dbbf1246c2b7aab8c616984183", + "device_id": "515c6f88684b452e86231c4411e3001e", + "area_id": null, + "unique_id": "972369ddfba8b95dfc4b3ab68f74f19e19227919102c4720d1fc38d9fd45a0b4_audio_sensor", + "platform": "mobile_app", + "name": null, + "icon": null, + "disabled_by": null, + "capabilities": {}, + "supported_features": 0, + "device_class": null, + "unit_of_measurement": null, + "original_name": "700T1C Audio Sensor", + "original_icon": "mdi:volume-high" + }, + { + "entity_id": "sensor.700t1c_do_not_disturb_sensor", + "config_entry_id": "2872c3dbbf1246c2b7aab8c616984183", + "device_id": "515c6f88684b452e86231c4411e3001e", + "area_id": null, + "unique_id": "972369ddfba8b95dfc4b3ab68f74f19e19227919102c4720d1fc38d9fd45a0b4_dnd_sensor", + "platform": "mobile_app", + "name": null, + "icon": null, + "disabled_by": null, + "capabilities": {}, + "supported_features": 0, + "device_class": null, + "unit_of_measurement": null, + "original_name": "700T1C Do Not Disturb Sensor", + "original_icon": "mdi:do-not-disturb" + }, + { + "entity_id": "sensor.700t1c_last_reboot", + "config_entry_id": "2872c3dbbf1246c2b7aab8c616984183", + "device_id": "515c6f88684b452e86231c4411e3001e", + "area_id": null, + "unique_id": "972369ddfba8b95dfc4b3ab68f74f19e19227919102c4720d1fc38d9fd45a0b4_last_reboot", + "platform": "mobile_app", + "name": null, + "icon": null, + "disabled_by": null, + "capabilities": {}, + "supported_features": 0, + "device_class": "timestamp", + "unit_of_measurement": null, + "original_name": "700T1C Last Reboot", + "original_icon": "mdi:restart" + }, + { + "entity_id": "sensor.700t1c_detected_activity", + "config_entry_id": "2872c3dbbf1246c2b7aab8c616984183", + "device_id": "515c6f88684b452e86231c4411e3001e", + "area_id": null, + "unique_id": "972369ddfba8b95dfc4b3ab68f74f19e19227919102c4720d1fc38d9fd45a0b4_detected_activity", + "platform": "mobile_app", + "name": null, + "icon": null, + "disabled_by": null, + "capabilities": {}, + "supported_features": 0, + "device_class": null, + "unit_of_measurement": null, + "original_name": "700T1C Detected Activity", + "original_icon": "mdi:progress-question" + }, + { + "entity_id": "binary_sensor.700t1c_is_charging", + "config_entry_id": "2872c3dbbf1246c2b7aab8c616984183", + "device_id": "515c6f88684b452e86231c4411e3001e", + "area_id": null, + "unique_id": "972369ddfba8b95dfc4b3ab68f74f19e19227919102c4720d1fc38d9fd45a0b4_is_charging", + "platform": "mobile_app", + "name": null, + "icon": null, + "disabled_by": null, + "capabilities": {}, + "supported_features": 0, + "device_class": "plug", + "unit_of_measurement": null, + "original_name": "700T1C Is Charging", + "original_icon": "mdi:power-plug" + }, + { + "entity_id": "sensor.700t1c_charger_type", + "config_entry_id": "2872c3dbbf1246c2b7aab8c616984183", + "device_id": "515c6f88684b452e86231c4411e3001e", + "area_id": null, + "unique_id": "972369ddfba8b95dfc4b3ab68f74f19e19227919102c4720d1fc38d9fd45a0b4_charger_type", + "platform": "mobile_app", + "name": null, + "icon": null, + "disabled_by": null, + "capabilities": {}, + "supported_features": 0, + "device_class": null, + "unit_of_measurement": null, + "original_name": "700T1C Charger Type", + "original_icon": "mdi:power-plug" + }, + { + "entity_id": "sensor.700t1c_battery_health", + "config_entry_id": "2872c3dbbf1246c2b7aab8c616984183", + "device_id": "515c6f88684b452e86231c4411e3001e", + "area_id": null, + "unique_id": "972369ddfba8b95dfc4b3ab68f74f19e19227919102c4720d1fc38d9fd45a0b4_battery_health", + "platform": "mobile_app", + "name": null, + "icon": null, + "disabled_by": null, + "capabilities": {}, + "supported_features": 0, + "device_class": null, + "unit_of_measurement": null, + "original_name": "700T1C Battery Health", + "original_icon": "mdi:battery-alert" + }, + { + "entity_id": "automation.system_set_custom_theme_at_startup", + "config_entry_id": null, + "device_id": null, + "area_id": null, + "unique_id": "themestartup", + "platform": "automation", + "name": null, + "icon": null, + "disabled_by": null, + "capabilities": {}, + "supported_features": 0, + "device_class": null, + "unit_of_measurement": null, + "original_name": "System - Set Custom Theme at Startup", + "original_icon": null + }, + { + "entity_id": "sensor.hacs", + "config_entry_id": "d00c44239e11a6b5cc8a1f44eb4c23e2", + "device_id": "da36236384f2c8816377b036cf723ece", + "area_id": null, + "unique_id": "0717a0cd-745c-48fd-9b16-c8534c9704f9-bc944b0f-fd42-4a58-a072-ade38d1444cd", + "platform": "hacs", + "name": null, + "icon": null, + "disabled_by": null, + "capabilities": null, + "supported_features": 0, + "device_class": null, + "unit_of_measurement": "pending update(s)", + "original_name": "hacs", + "original_icon": "hacs:hacs" + } + ] + } +} \ No newline at end of file diff --git a/.storage/core.restore_state b/.storage/core.restore_state new file mode 100644 index 0000000..72f3a9c --- /dev/null +++ b/.storage/core.restore_state @@ -0,0 +1,1805 @@ +{ + "version": 1, + "key": "core.restore_state", + "data": [ + { + "state": { + "entity_id": "person.sysadmin", + "state": "home", + "attributes": { + "editable": true, + "id": "sysadmin", + "latitude": 44.461052, + "longitude": -118.7120106, + "gps_accuracy": 3, + "source": "device_tracker.sm_g935r4", + "user_id": "99685dc45d9f40dc8183e11ce5128038", + "friendly_name": "sysadmin" + }, + "last_changed": "2020-12-08T05:25:00.344021+00:00", + "last_updated": "2020-12-08T05:25:02.997639+00:00", + "context": { + "id": "4255f575145f5b45cd7400e14766b5f0", + "parent_id": null, + "user_id": null + } + }, + "last_seen": "2020-12-09T03:25:05.267622+00:00" + }, + { + "state": { + "entity_id": "variable.node_test_sum", + "state": "0", + "attributes": {}, + "last_changed": "2020-12-08T05:25:00.748365+00:00", + "last_updated": "2020-12-08T05:25:00.748365+00:00", + "context": { + "id": "ecd48655f8280e92da236c91e1b80758", + "parent_id": null, + "user_id": null + } + }, + "last_seen": "2020-12-09T03:25:05.267622+00:00" + }, + { + "state": { + "entity_id": "variable.node_test_switch", + "state": "OFF", + "attributes": { + "icon": "mdi:flash" + }, + "last_changed": "2020-12-08T05:25:00.748425+00:00", + "last_updated": "2020-12-08T05:25:00.748425+00:00", + "context": { + "id": "69cb64e5d3dd7b63f1938640d328c79b", + "parent_id": null, + "user_id": null + } + }, + "last_seen": "2020-12-09T03:25:05.267622+00:00" + }, + { + "state": { + "entity_id": "variable.irrigation_zone_1_schedule_countdown", + "state": "disabled", + "attributes": { + "icon": "mdi:timer-sand", + "friendly_name": "Countdown to Next Scheduled Run" + }, + "last_changed": "2020-12-08T05:25:00.748465+00:00", + "last_updated": "2020-12-08T05:25:00.748465+00:00", + "context": { + "id": "2e819b9a0d598d3c93287390faa1186e", + "parent_id": null, + "user_id": null + } + }, + "last_seen": "2020-12-09T03:25:05.267622+00:00" + }, + { + "state": { + "entity_id": "variable.irrigation_zone_1_schedule_next_dt", + "state": "disabled", + "attributes": { + "friendly_name": "Time of Next Scheduled Run", + "icon": "mdi:clock" + }, + "last_changed": "2020-12-08T05:25:00.748503+00:00", + "last_updated": "2020-12-08T05:25:00.748503+00:00", + "context": { + "id": "f94d0de80a7b2bc8f638b0af8b8f04bf", + "parent_id": null, + "user_id": null + } + }, + "last_seen": "2020-12-09T03:25:05.267622+00:00" + }, + { + "state": { + "entity_id": "variable.irrigation_next_trigger", + "state": "00:00:00", + "attributes": { + "friendly_name": "Countdown to Next Scheduled Run", + "icon": "mdi:alarm" + }, + "last_changed": "2020-12-08T05:25:00.748544+00:00", + "last_updated": "2020-12-08T05:25:00.748544+00:00", + "context": { + "id": "c2e9e762cebededd2819fc6d906ec9df", + "parent_id": null, + "user_id": null + } + }, + "last_seen": "2020-12-09T03:25:05.267622+00:00" + }, + { + "state": { + "entity_id": "variable.irrigation_next_trigger_dt", + "state": "", + "attributes": { + "friendly_name": "Next Scheduled Run at", + "icon": "mdi:alarm" + }, + "last_changed": "2020-12-08T05:25:00.748577+00:00", + "last_updated": "2020-12-08T05:25:00.748577+00:00", + "context": { + "id": "37d19e65ad25c33e4e6847bf13061749", + "parent_id": null, + "user_id": null + } + }, + "last_seen": "2020-12-09T03:25:05.267622+00:00" + }, + { + "state": { + "entity_id": "variable.irrigation_next_schedule_name", + "state": "", + "attributes": { + "friendly_name": "Next Schedule to Run", + "icon": "mdi:calendar" + }, + "last_changed": "2020-12-08T05:25:00.748609+00:00", + "last_updated": "2020-12-08T05:25:00.748609+00:00", + "context": { + "id": "f713d899e96f1ed98962fe6388d17c6f", + "parent_id": null, + "user_id": null + } + }, + "last_seen": "2020-12-09T03:25:05.267622+00:00" + }, + { + "state": { + "entity_id": "variable.irrigation_running_names", + "state": "", + "attributes": { + "friendly_name": "Zones Running Now", + "icon": "mdi:view-list" + }, + "last_changed": "2020-12-08T05:25:00.748639+00:00", + "last_updated": "2020-12-08T05:25:00.748639+00:00", + "context": { + "id": "7a0b33cc2d2bc7dfa0ca426d82f2bf4f", + "parent_id": null, + "user_id": null + } + }, + "last_seen": "2020-12-09T03:25:05.267622+00:00" + }, + { + "state": { + "entity_id": "variable.irrigation_queue_names", + "state": "", + "attributes": { + "friendly_name": "Zones Queued to Run", + "icon": "mdi:view-list" + }, + "last_changed": "2020-12-08T05:25:00.748670+00:00", + "last_updated": "2020-12-08T05:25:00.748670+00:00", + "context": { + "id": "5e92cceb251ddb4a794e4c4159129559", + "parent_id": null, + "user_id": null + } + }, + "last_seen": "2020-12-09T03:25:05.267622+00:00" + }, + { + "state": { + "entity_id": "variable.irrigation_zone_2_schedule_countdown", + "state": "disabled", + "attributes": { + "icon": "mdi:timer-sand", + "friendly_name": "Countdown to Next Scheduled Run" + }, + "last_changed": "2020-12-08T05:25:00.748702+00:00", + "last_updated": "2020-12-08T05:25:00.748702+00:00", + "context": { + "id": "a674065748110184cc3552dfe8e39258", + "parent_id": null, + "user_id": null + } + }, + "last_seen": "2020-12-09T03:25:05.267622+00:00" + }, + { + "state": { + "entity_id": "variable.irrigation_zone_2_schedule_next_dt", + "state": "disabled", + "attributes": { + "friendly_name": "Time of Next Scheduled Run", + "icon": "mdi:clock" + }, + "last_changed": "2020-12-08T05:25:00.748736+00:00", + "last_updated": "2020-12-08T05:25:00.748736+00:00", + "context": { + "id": "751275476d35233beca9f0b30711655e", + "parent_id": null, + "user_id": null + } + }, + "last_seen": "2020-12-09T03:25:05.267622+00:00" + }, + { + "state": { + "entity_id": "variable.irrigation_zone_3_schedule_countdown", + "state": "disabled", + "attributes": { + "icon": "mdi:timer-sand", + "friendly_name": "Countdown to Next Scheduled Run" + }, + "last_changed": "2020-12-08T05:25:00.748771+00:00", + "last_updated": "2020-12-08T05:25:00.748771+00:00", + "context": { + "id": "62238a8a16560f7568fc643150612ed4", + "parent_id": null, + "user_id": null + } + }, + "last_seen": "2020-12-09T03:25:05.267622+00:00" + }, + { + "state": { + "entity_id": "variable.irrigation_zone_3_schedule_next_dt", + "state": "disabled", + "attributes": { + "friendly_name": "Time of Next Scheduled Run", + "icon": "mdi:clock" + }, + "last_changed": "2020-12-08T05:25:00.748803+00:00", + "last_updated": "2020-12-08T05:25:00.748803+00:00", + "context": { + "id": "6217301a3d9e1e60a08f7673e95f807a", + "parent_id": null, + "user_id": null + } + }, + "last_seen": "2020-12-09T03:25:05.267622+00:00" + }, + { + "state": { + "entity_id": "variable.irrigation_zone_4_schedule_countdown", + "state": "disabled", + "attributes": { + "icon": "mdi:timer-sand", + "friendly_name": "Countdown to Next Scheduled Run" + }, + "last_changed": "2020-12-08T05:25:00.748835+00:00", + "last_updated": "2020-12-08T05:25:00.748835+00:00", + "context": { + "id": "4a9dac0b2e2e02987d24cf49a24794b1", + "parent_id": null, + "user_id": null + } + }, + "last_seen": "2020-12-09T03:25:05.267622+00:00" + }, + { + "state": { + "entity_id": "variable.irrigation_zone_4_schedule_next_dt", + "state": "disabled", + "attributes": { + "friendly_name": "Time of Next Scheduled Run", + "icon": "mdi:clock" + }, + "last_changed": "2020-12-08T05:25:00.748865+00:00", + "last_updated": "2020-12-08T05:25:00.748865+00:00", + "context": { + "id": "c1b5807b07a53d8885cc4406dc330c0c", + "parent_id": null, + "user_id": null + } + }, + "last_seen": "2020-12-09T03:25:05.267622+00:00" + }, + { + "state": { + "entity_id": "variable.irrigation_zone_5_schedule_countdown", + "state": "disabled", + "attributes": { + "icon": "mdi:timer-sand", + "friendly_name": "Countdown to Next Scheduled Run" + }, + "last_changed": "2020-12-08T05:25:00.748896+00:00", + "last_updated": "2020-12-08T05:25:00.748896+00:00", + "context": { + "id": "db736a852be84abb240e5721bbc3337a", + "parent_id": null, + "user_id": null + } + }, + "last_seen": "2020-12-09T03:25:05.267622+00:00" + }, + { + "state": { + "entity_id": "variable.irrigation_zone_5_schedule_next_dt", + "state": "disabled", + "attributes": { + "friendly_name": "Time of Next Scheduled Run", + "icon": "mdi:clock" + }, + "last_changed": "2020-12-08T05:25:00.748927+00:00", + "last_updated": "2020-12-08T05:25:00.748927+00:00", + "context": { + "id": "14188bfad2c0e4c34816f033c51edf5a", + "parent_id": null, + "user_id": null + } + }, + "last_seen": "2020-12-09T03:25:05.267622+00:00" + }, + { + "state": { + "entity_id": "variable.irrigation_zone_6_schedule_countdown", + "state": "disabled", + "attributes": { + "icon": "mdi:timer-sand", + "friendly_name": "Countdown to Next Scheduled Run" + }, + "last_changed": "2020-12-08T05:25:00.748960+00:00", + "last_updated": "2020-12-08T05:25:00.748960+00:00", + "context": { + "id": "598c4bcc240c9354fb37794607258329", + "parent_id": null, + "user_id": null + } + }, + "last_seen": "2020-12-09T03:25:05.267622+00:00" + }, + { + "state": { + "entity_id": "variable.irrigation_zone_6_schedule_next_dt", + "state": "disabled", + "attributes": { + "friendly_name": "Time of Next Scheduled Run", + "icon": "mdi:clock" + }, + "last_changed": "2020-12-08T05:25:00.748992+00:00", + "last_updated": "2020-12-08T05:25:00.748992+00:00", + "context": { + "id": "085d3c06359e71d6128d57952c7dd387", + "parent_id": null, + "user_id": null + } + }, + "last_seen": "2020-12-09T03:25:05.267622+00:00" + }, + { + "state": { + "entity_id": "variable.irrigation_zone_7_schedule_countdown", + "state": "disabled", + "attributes": { + "icon": "mdi:timer-sand", + "friendly_name": "Countdown to Next Scheduled Run" + }, + "last_changed": "2020-12-08T05:25:00.749024+00:00", + "last_updated": "2020-12-08T05:25:00.749024+00:00", + "context": { + "id": "e1c369cd1716b30c17ba671475ebf52b", + "parent_id": null, + "user_id": null + } + }, + "last_seen": "2020-12-09T03:25:05.267622+00:00" + }, + { + "state": { + "entity_id": "variable.irrigation_zone_7_schedule_next_dt", + "state": "disabled", + "attributes": { + "friendly_name": "Time of Next Scheduled Run", + "icon": "mdi:clock" + }, + "last_changed": "2020-12-08T05:25:00.749060+00:00", + "last_updated": "2020-12-08T05:25:00.749060+00:00", + "context": { + "id": "8d88ec9f27dc83d6216b5d3974e3c27d", + "parent_id": null, + "user_id": null + } + }, + "last_seen": "2020-12-09T03:25:05.267622+00:00" + }, + { + "state": { + "entity_id": "input_select.irrigation_zone_1_schedule_delta", + "state": "12 Hours", + "attributes": { + "options": [ + "12 Hours" + ], + "values": null, + "friendly_name": "Repeat Every", + "icon": "mdi:repeat" + }, + "last_changed": "2020-12-08T05:25:00.750229+00:00", + "last_updated": "2020-12-08T05:25:00.750229+00:00", + "context": { + "id": "e3e45a3129daaa40a608e21a341e8907", + "parent_id": null, + "user_id": null + } + }, + "last_seen": "2020-12-09T03:25:05.267622+00:00" + }, + { + "state": { + "entity_id": "input_select.irrigation_zone_2_schedule_delta", + "state": "12 Hours", + "attributes": { + "options": [ + "12 Hours" + ], + "values": null, + "friendly_name": "Repeat Every", + "icon": "mdi:repeat" + }, + "last_changed": "2020-12-08T05:25:00.750296+00:00", + "last_updated": "2020-12-08T05:25:00.750296+00:00", + "context": { + "id": "293a69646f52ae7683790b70c4e74d0f", + "parent_id": null, + "user_id": null + } + }, + "last_seen": "2020-12-09T03:25:05.267622+00:00" + }, + { + "state": { + "entity_id": "input_select.irrigation_zone_3_schedule_delta", + "state": "12 Hours", + "attributes": { + "options": [ + "12 Hours" + ], + "values": null, + "friendly_name": "Repeat Every", + "icon": "mdi:repeat" + }, + "last_changed": "2020-12-08T05:25:00.750350+00:00", + "last_updated": "2020-12-08T05:25:00.750350+00:00", + "context": { + "id": "2f77e0955c20015c685930176ce86980", + "parent_id": null, + "user_id": null + } + }, + "last_seen": "2020-12-09T03:25:05.267622+00:00" + }, + { + "state": { + "entity_id": "input_select.irrigation_zone_4_schedule_delta", + "state": "12 Hours", + "attributes": { + "options": [ + "12 Hours" + ], + "values": null, + "friendly_name": "Repeat Every", + "icon": "mdi:repeat" + }, + "last_changed": "2020-12-08T05:25:00.750401+00:00", + "last_updated": "2020-12-08T05:25:00.750401+00:00", + "context": { + "id": "5b2f6604fe5e9a12576577aee0d5f271", + "parent_id": null, + "user_id": null + } + }, + "last_seen": "2020-12-09T03:25:05.267622+00:00" + }, + { + "state": { + "entity_id": "input_select.irrigation_zone_5_schedule_delta", + "state": "12 Hours", + "attributes": { + "options": [ + "12 Hours" + ], + "values": null, + "friendly_name": "Repeat Every", + "icon": "mdi:repeat" + }, + "last_changed": "2020-12-08T05:25:00.750449+00:00", + "last_updated": "2020-12-08T05:25:00.750449+00:00", + "context": { + "id": "21dae0d2d34c882ba47e220f6b98fe5e", + "parent_id": null, + "user_id": null + } + }, + "last_seen": "2020-12-09T03:25:05.267622+00:00" + }, + { + "state": { + "entity_id": "input_select.irrigation_zone_6_schedule_delta", + "state": "12 Hours", + "attributes": { + "options": [ + "12 Hours" + ], + "values": null, + "friendly_name": "Repeat Every", + "icon": "mdi:repeat" + }, + "last_changed": "2020-12-08T05:25:00.750497+00:00", + "last_updated": "2020-12-08T05:25:00.750497+00:00", + "context": { + "id": "4d7253bc8638860bdcbd4cf37111bc05", + "parent_id": null, + "user_id": null + } + }, + "last_seen": "2020-12-09T03:25:05.267622+00:00" + }, + { + "state": { + "entity_id": "input_select.irrigation_zone_7_schedule_delta", + "state": "12 Hours", + "attributes": { + "options": [ + "12 Hours" + ], + "values": null, + "friendly_name": "Repeat Every", + "icon": "mdi:repeat" + }, + "last_changed": "2020-12-08T05:25:00.750544+00:00", + "last_updated": "2020-12-08T05:25:00.750544+00:00", + "context": { + "id": "17a4332be00403a613e70a2d4481d1a7", + "parent_id": null, + "user_id": null + } + }, + "last_seen": "2020-12-09T03:25:05.267622+00:00" + }, + { + "state": { + "entity_id": "input_boolean.irrigation_zone_1_schedule_enabled", + "state": "off", + "attributes": { + "editable": false, + "friendly_name": "Schedule (enabled/disabled)" + }, + "last_changed": "2020-12-08T05:25:00.752408+00:00", + "last_updated": "2020-12-08T05:25:00.752408+00:00", + "context": { + "id": "f20ccf4a600ae730c0b5467a9aebaf81", + "parent_id": null, + "user_id": null + } + }, + "last_seen": "2020-12-09T03:25:05.267622+00:00" + }, + { + "state": { + "entity_id": "input_boolean.irrigation_zone_1_state", + "state": "off", + "attributes": { + "editable": false, + "friendly_name": "Zone State (manual override)" + }, + "last_changed": "2020-12-08T05:25:00.752474+00:00", + "last_updated": "2020-12-08T05:25:00.752474+00:00", + "context": { + "id": "2055fd4c26728938c112a63f92d0842d", + "parent_id": null, + "user_id": null + } + }, + "last_seen": "2020-12-09T03:25:05.267622+00:00" + }, + { + "state": { + "entity_id": "input_boolean.irrigation_pump", + "state": "off", + "attributes": { + "editable": false, + "friendly_name": "Well Pump", + "icon": "mdi:water-pump" + }, + "last_changed": "2020-12-08T05:25:00.752533+00:00", + "last_updated": "2020-12-08T05:25:00.752533+00:00", + "context": { + "id": "99ed4cdeeeb31f123f1e48510408389f", + "parent_id": null, + "user_id": null + } + }, + "last_seen": "2020-12-09T03:25:05.267622+00:00" + }, + { + "state": { + "entity_id": "input_boolean.irrigation_enabled", + "state": "off", + "attributes": { + "editable": false, + "friendly_name": "Irrigation System Enabled", + "icon": "mdi:water" + }, + "last_changed": "2020-12-08T05:25:00.752594+00:00", + "last_updated": "2020-12-08T05:25:00.752594+00:00", + "context": { + "id": "62f52d16d2eed84271d763ed6f557ed3", + "parent_id": null, + "user_id": null + } + }, + "last_seen": "2020-12-09T03:25:05.267622+00:00" + }, + { + "state": { + "entity_id": "input_boolean.irrigation_zone_2_schedule_enabled", + "state": "off", + "attributes": { + "editable": false, + "friendly_name": "Schedule (enabled/disabled)" + }, + "last_changed": "2020-12-08T05:25:00.752648+00:00", + "last_updated": "2020-12-08T05:25:00.752648+00:00", + "context": { + "id": "bb1065a7f4663f62f68445dd2b03e460", + "parent_id": null, + "user_id": null + } + }, + "last_seen": "2020-12-09T03:25:05.267622+00:00" + }, + { + "state": { + "entity_id": "input_boolean.irrigation_zone_2_state", + "state": "off", + "attributes": { + "editable": false, + "friendly_name": "Zone State (manual override)" + }, + "last_changed": "2020-12-08T05:25:00.752700+00:00", + "last_updated": "2020-12-08T05:25:00.752700+00:00", + "context": { + "id": "cc3d81b0a87239acb4abd78710cf188b", + "parent_id": null, + "user_id": null + } + }, + "last_seen": "2020-12-09T03:25:05.267622+00:00" + }, + { + "state": { + "entity_id": "input_boolean.irrigation_zone_3_schedule_enabled", + "state": "off", + "attributes": { + "editable": false, + "friendly_name": "Schedule (enabled/disabled)" + }, + "last_changed": "2020-12-08T05:25:00.752750+00:00", + "last_updated": "2020-12-08T05:25:00.752750+00:00", + "context": { + "id": "3c5e3b86305d13fd0a0ddd84ff67fa5b", + "parent_id": null, + "user_id": null + } + }, + "last_seen": "2020-12-09T03:25:05.267622+00:00" + }, + { + "state": { + "entity_id": "input_boolean.irrigation_zone_3_state", + "state": "off", + "attributes": { + "editable": false, + "friendly_name": "Zone State (manual override)" + }, + "last_changed": "2020-12-08T05:25:00.752800+00:00", + "last_updated": "2020-12-08T05:25:00.752800+00:00", + "context": { + "id": "38eb980329d5b79f0d2e26bb5872cadb", + "parent_id": null, + "user_id": null + } + }, + "last_seen": "2020-12-09T03:25:05.267622+00:00" + }, + { + "state": { + "entity_id": "input_boolean.irrigation_zone_4_schedule_enabled", + "state": "off", + "attributes": { + "editable": false, + "friendly_name": "Schedule (enabled/disabled)" + }, + "last_changed": "2020-12-08T05:25:00.752849+00:00", + "last_updated": "2020-12-08T05:25:00.752849+00:00", + "context": { + "id": "29951bdcd1214ee82554d0d0915b9d9f", + "parent_id": null, + "user_id": null + } + }, + "last_seen": "2020-12-09T03:25:05.267622+00:00" + }, + { + "state": { + "entity_id": "input_boolean.irrigation_zone_4_state", + "state": "off", + "attributes": { + "editable": false, + "friendly_name": "Zone State (manual override)" + }, + "last_changed": "2020-12-08T05:25:00.752898+00:00", + "last_updated": "2020-12-08T05:25:00.752898+00:00", + "context": { + "id": "95aeba03dbd19f8b417fc25fcf593f28", + "parent_id": null, + "user_id": null + } + }, + "last_seen": "2020-12-09T03:25:05.267622+00:00" + }, + { + "state": { + "entity_id": "input_boolean.irrigation_zone_5_schedule_enabled", + "state": "on", + "attributes": { + "editable": false, + "friendly_name": "Schedule (enabled/disabled)" + }, + "last_changed": "2020-12-08T05:25:00.752947+00:00", + "last_updated": "2020-12-08T05:25:00.752947+00:00", + "context": { + "id": "a0beff858f1b21c48fd9be638c8a625d", + "parent_id": null, + "user_id": null + } + }, + "last_seen": "2020-12-09T03:25:05.267622+00:00" + }, + { + "state": { + "entity_id": "input_boolean.irrigation_zone_5_state", + "state": "off", + "attributes": { + "editable": false, + "friendly_name": "Zone State (manual override)" + }, + "last_changed": "2020-12-08T05:25:00.752996+00:00", + "last_updated": "2020-12-08T05:25:00.752996+00:00", + "context": { + "id": "c7b5a3b65d7c0383f444fdb2fe460a9b", + "parent_id": null, + "user_id": null + } + }, + "last_seen": "2020-12-09T03:25:05.267622+00:00" + }, + { + "state": { + "entity_id": "input_boolean.irrigation_zone_6_schedule_enabled", + "state": "on", + "attributes": { + "editable": false, + "friendly_name": "Schedule (enabled/disabled)" + }, + "last_changed": "2020-12-08T05:25:00.753047+00:00", + "last_updated": "2020-12-08T05:25:00.753047+00:00", + "context": { + "id": "4f88548e6c06ad8eccf1e89cb1d800b5", + "parent_id": null, + "user_id": null + } + }, + "last_seen": "2020-12-09T03:25:05.267622+00:00" + }, + { + "state": { + "entity_id": "input_boolean.irrigation_zone_6_state", + "state": "off", + "attributes": { + "editable": false, + "friendly_name": "Zone State (manual override)" + }, + "last_changed": "2020-12-08T05:25:00.753105+00:00", + "last_updated": "2020-12-08T05:25:00.753105+00:00", + "context": { + "id": "fe21c425423f99be8bac2d7d5ccc14ca", + "parent_id": null, + "user_id": null + } + }, + "last_seen": "2020-12-09T03:25:05.267622+00:00" + }, + { + "state": { + "entity_id": "input_boolean.irrigation_zone_7_schedule_enabled", + "state": "off", + "attributes": { + "editable": false, + "friendly_name": "Schedule (enabled/disabled)" + }, + "last_changed": "2020-12-08T05:25:00.753154+00:00", + "last_updated": "2020-12-08T05:25:00.753154+00:00", + "context": { + "id": "3592f97e375fafbf67c3aa821f9696d9", + "parent_id": null, + "user_id": null + } + }, + "last_seen": "2020-12-09T03:25:05.267622+00:00" + }, + { + "state": { + "entity_id": "input_boolean.irrigation_zone_7_state", + "state": "off", + "attributes": { + "editable": false, + "friendly_name": "Zone State (manual override)" + }, + "last_changed": "2020-12-08T05:25:00.753204+00:00", + "last_updated": "2020-12-08T05:25:00.753204+00:00", + "context": { + "id": "f100bd58ceb516494efa69580b7a4458", + "parent_id": null, + "user_id": null + } + }, + "last_seen": "2020-12-09T03:25:05.267622+00:00" + }, + { + "state": { + "entity_id": "input_datetime.security_lights_interior_time_off", + "state": "22:30:00", + "attributes": { + "editable": false, + "has_date": false, + "has_time": true, + "hour": 22, + "minute": 30, + "second": 0, + "timestamp": 81000 + }, + "last_changed": "2020-12-08T05:25:00.754630+00:00", + "last_updated": "2020-12-08T05:25:00.754630+00:00", + "context": { + "id": "32da5ad7fa766eca40a0a4784ced040d", + "parent_id": null, + "user_id": null + } + }, + "last_seen": "2020-12-09T03:25:05.267622+00:00" + }, + { + "state": { + "entity_id": "input_number.fan_on_temp", + "state": "25.0", + "attributes": { + "initial": 25.0, + "editable": false, + "min": 20.0, + "max": 30.0, + "step": 0.5, + "mode": "box", + "unit_of_measurement": "Celcuis", + "friendly_name": "Fan On Set Temperature (C)", + "icon": "mdi:target" + }, + "last_changed": "2020-12-08T05:25:00.755171+00:00", + "last_updated": "2020-12-08T05:25:00.755171+00:00", + "context": { + "id": "e87632e5ed4c67fde31794228ee65d22", + "parent_id": null, + "user_id": null + } + }, + "last_seen": "2020-12-09T03:25:05.267622+00:00" + }, + { + "state": { + "entity_id": "input_number.node_test_first_number", + "state": "25.0", + "attributes": { + "initial": 25.0, + "editable": false, + "min": 0.0, + "max": 100.0, + "step": 1.0, + "mode": "slider", + "friendly_name": "First Number" + }, + "last_changed": "2020-12-08T05:25:00.755223+00:00", + "last_updated": "2020-12-08T05:25:00.755223+00:00", + "context": { + "id": "e69e7f897026333dd263b59a66343ccc", + "parent_id": null, + "user_id": null + } + }, + "last_seen": "2020-12-09T03:25:05.267622+00:00" + }, + { + "state": { + "entity_id": "input_number.node_test_second_number", + "state": "75.0", + "attributes": { + "initial": 75.0, + "editable": false, + "min": 0.0, + "max": 100.0, + "step": 1.0, + "mode": "slider", + "friendly_name": "Second Number" + }, + "last_changed": "2020-12-08T05:25:00.755264+00:00", + "last_updated": "2020-12-08T05:25:00.755264+00:00", + "context": { + "id": "8c7f2bc11904fd59885ee09cb1ebda43", + "parent_id": null, + "user_id": null + } + }, + "last_seen": "2020-12-09T03:25:05.267622+00:00" + }, + { + "state": { + "entity_id": "input_number.irrigation_zone_1_duration", + "state": "15.0", + "attributes": { + "initial": null, + "editable": false, + "min": 1.0, + "max": 30.0, + "step": 1.0, + "mode": "slider", + "friendly_name": "Duration in minutes", + "icon": "mdi:timer-sand" + }, + "last_changed": "2020-12-08T05:25:00.755337+00:00", + "last_updated": "2020-12-08T05:25:00.755337+00:00", + "context": { + "id": "6dff36871bbb629e6a90b635a5a0bc86", + "parent_id": null, + "user_id": null + } + }, + "last_seen": "2020-12-09T03:25:05.267622+00:00" + }, + { + "state": { + "entity_id": "input_number.irrigation_zone_1_schedule_base_hour", + "state": "6.0", + "attributes": { + "initial": 6.0, + "editable": false, + "min": 0.0, + "max": 24.0, + "step": 1.0, + "mode": "slider", + "friendly_name": "Hour of Day", + "icon": "mdi:clock" + }, + "last_changed": "2020-12-08T05:25:00.755379+00:00", + "last_updated": "2020-12-08T05:25:00.755379+00:00", + "context": { + "id": "88aafb3a2af12ca01818c0dae8f76a05", + "parent_id": null, + "user_id": null + } + }, + "last_seen": "2020-12-09T03:25:05.267622+00:00" + }, + { + "state": { + "entity_id": "input_number.irrigation_zone_1_schedule_base_minute", + "state": "0.0", + "attributes": { + "initial": 0.0, + "editable": false, + "min": 0.0, + "max": 45.0, + "step": 15.0, + "mode": "slider", + "friendly_name": "Minute of Day", + "icon": "mdi:clock" + }, + "last_changed": "2020-12-08T05:25:00.755416+00:00", + "last_updated": "2020-12-08T05:25:00.755416+00:00", + "context": { + "id": "f1d5dc1ac45010dc5b5ddb562c0ef3fa", + "parent_id": null, + "user_id": null + } + }, + "last_seen": "2020-12-09T03:25:05.267622+00:00" + }, + { + "state": { + "entity_id": "input_number.irrigation_zone_2_duration", + "state": "15.0", + "attributes": { + "initial": null, + "editable": false, + "min": 1.0, + "max": 30.0, + "step": 1.0, + "mode": "slider", + "friendly_name": "Duration in minutes", + "icon": "mdi:timer-sand" + }, + "last_changed": "2020-12-08T05:25:00.755476+00:00", + "last_updated": "2020-12-08T05:25:00.755476+00:00", + "context": { + "id": "bd4d9d97cad4d81f2116339561ff5969", + "parent_id": null, + "user_id": null + } + }, + "last_seen": "2020-12-09T03:25:05.267622+00:00" + }, + { + "state": { + "entity_id": "input_number.irrigation_zone_2_schedule_base_hour", + "state": "6.0", + "attributes": { + "initial": 6.0, + "editable": false, + "min": 0.0, + "max": 24.0, + "step": 1.0, + "mode": "slider", + "friendly_name": "Hour of Day", + "icon": "mdi:clock" + }, + "last_changed": "2020-12-08T05:25:00.755517+00:00", + "last_updated": "2020-12-08T05:25:00.755517+00:00", + "context": { + "id": "ca0e8e74d944bb5468fd8c000438f6df", + "parent_id": null, + "user_id": null + } + }, + "last_seen": "2020-12-09T03:25:05.267622+00:00" + }, + { + "state": { + "entity_id": "input_number.irrigation_zone_2_schedule_base_minute", + "state": "0.0", + "attributes": { + "initial": 0.0, + "editable": false, + "min": 0.0, + "max": 45.0, + "step": 15.0, + "mode": "slider", + "friendly_name": "Minute of Day", + "icon": "mdi:clock" + }, + "last_changed": "2020-12-08T05:25:00.755553+00:00", + "last_updated": "2020-12-08T05:25:00.755553+00:00", + "context": { + "id": "16d2223edd72f746de304127d2889d5d", + "parent_id": null, + "user_id": null + } + }, + "last_seen": "2020-12-09T03:25:05.267622+00:00" + }, + { + "state": { + "entity_id": "input_number.irrigation_zone_3_duration", + "state": "15.0", + "attributes": { + "initial": null, + "editable": false, + "min": 1.0, + "max": 30.0, + "step": 1.0, + "mode": "slider", + "friendly_name": "Duration in minutes", + "icon": "mdi:timer-sand" + }, + "last_changed": "2020-12-08T05:25:00.755610+00:00", + "last_updated": "2020-12-08T05:25:00.755610+00:00", + "context": { + "id": "9b5be34cbd81399103b468094a48a880", + "parent_id": null, + "user_id": null + } + }, + "last_seen": "2020-12-09T03:25:05.267622+00:00" + }, + { + "state": { + "entity_id": "input_number.irrigation_zone_3_schedule_base_hour", + "state": "6.0", + "attributes": { + "initial": 6.0, + "editable": false, + "min": 0.0, + "max": 24.0, + "step": 1.0, + "mode": "slider", + "friendly_name": "Hour of Day", + "icon": "mdi:clock" + }, + "last_changed": "2020-12-08T05:25:00.755651+00:00", + "last_updated": "2020-12-08T05:25:00.755651+00:00", + "context": { + "id": "522cc3dc28ac8bf704cee4f9880d7051", + "parent_id": null, + "user_id": null + } + }, + "last_seen": "2020-12-09T03:25:05.267622+00:00" + }, + { + "state": { + "entity_id": "input_number.irrigation_zone_3_schedule_base_minute", + "state": "0.0", + "attributes": { + "initial": 0.0, + "editable": false, + "min": 0.0, + "max": 59.0, + "step": 1.0, + "mode": "slider", + "friendly_name": "Minute of Day", + "icon": "mdi:clock" + }, + "last_changed": "2020-12-08T05:25:00.755687+00:00", + "last_updated": "2020-12-08T05:25:00.755687+00:00", + "context": { + "id": "51ba8efb111c9ca2bfb1a58bb6c95728", + "parent_id": null, + "user_id": null + } + }, + "last_seen": "2020-12-09T03:25:05.267622+00:00" + }, + { + "state": { + "entity_id": "input_number.irrigation_zone_4_duration", + "state": "10.0", + "attributes": { + "initial": null, + "editable": false, + "min": 1.0, + "max": 30.0, + "step": 1.0, + "mode": "slider", + "friendly_name": "Duration in minutes", + "icon": "mdi:timer-sand" + }, + "last_changed": "2020-12-08T05:25:00.755739+00:00", + "last_updated": "2020-12-08T05:25:00.755739+00:00", + "context": { + "id": "3ec87caff395b3f59af3e7624e30a852", + "parent_id": null, + "user_id": null + } + }, + "last_seen": "2020-12-09T03:25:05.267622+00:00" + }, + { + "state": { + "entity_id": "input_number.irrigation_zone_4_schedule_base_hour", + "state": "6.0", + "attributes": { + "initial": 6.0, + "editable": false, + "min": 0.0, + "max": 24.0, + "step": 1.0, + "mode": "slider", + "friendly_name": "Hour of Day", + "icon": "mdi:clock" + }, + "last_changed": "2020-12-08T05:25:00.755776+00:00", + "last_updated": "2020-12-08T05:25:00.755776+00:00", + "context": { + "id": "d478969b942c21079be796b521c02bed", + "parent_id": null, + "user_id": null + } + }, + "last_seen": "2020-12-09T03:25:05.267622+00:00" + }, + { + "state": { + "entity_id": "input_number.irrigation_zone_4_schedule_base_minute", + "state": "0.0", + "attributes": { + "initial": 0.0, + "editable": false, + "min": 0.0, + "max": 59.0, + "step": 1.0, + "mode": "slider", + "friendly_name": "Minute of Day", + "icon": "mdi:clock" + }, + "last_changed": "2020-12-08T05:25:00.755811+00:00", + "last_updated": "2020-12-08T05:25:00.755811+00:00", + "context": { + "id": "3ead7bd0c1891bfa7e18e4e9664840a0", + "parent_id": null, + "user_id": null + } + }, + "last_seen": "2020-12-09T03:25:05.267622+00:00" + }, + { + "state": { + "entity_id": "input_number.irrigation_zone_5_duration", + "state": "15.0", + "attributes": { + "initial": null, + "editable": false, + "min": 1.0, + "max": 30.0, + "step": 1.0, + "mode": "slider", + "friendly_name": "Duration in minutes", + "icon": "mdi:timer-sand" + }, + "last_changed": "2020-12-08T05:25:00.755863+00:00", + "last_updated": "2020-12-08T05:25:00.755863+00:00", + "context": { + "id": "722617726165891f651575777539d430", + "parent_id": null, + "user_id": null + } + }, + "last_seen": "2020-12-09T03:25:05.267622+00:00" + }, + { + "state": { + "entity_id": "input_number.irrigation_zone_5_schedule_base_hour", + "state": "6.0", + "attributes": { + "initial": 6.0, + "editable": false, + "min": 0.0, + "max": 24.0, + "step": 1.0, + "mode": "slider", + "friendly_name": "Hour of Day", + "icon": "mdi:clock" + }, + "last_changed": "2020-12-08T05:25:00.755899+00:00", + "last_updated": "2020-12-08T05:25:00.755899+00:00", + "context": { + "id": "3b04eee81ea61483eaec22951136d566", + "parent_id": null, + "user_id": null + } + }, + "last_seen": "2020-12-09T03:25:05.267622+00:00" + }, + { + "state": { + "entity_id": "input_number.irrigation_zone_5_schedule_base_minute", + "state": "0.0", + "attributes": { + "initial": 0.0, + "editable": false, + "min": 0.0, + "max": 45.0, + "step": 15.0, + "mode": "slider", + "friendly_name": "Minute of Day", + "icon": "mdi:clock" + }, + "last_changed": "2020-12-08T05:25:00.755933+00:00", + "last_updated": "2020-12-08T05:25:00.755933+00:00", + "context": { + "id": "e70936c27fad7725a8a35471342e0d2c", + "parent_id": null, + "user_id": null + } + }, + "last_seen": "2020-12-09T03:25:05.267622+00:00" + }, + { + "state": { + "entity_id": "input_number.irrigation_zone_6_duration", + "state": "15.0", + "attributes": { + "initial": null, + "editable": false, + "min": 1.0, + "max": 30.0, + "step": 1.0, + "mode": "slider", + "friendly_name": "Duration in minutes", + "icon": "mdi:timer-sand" + }, + "last_changed": "2020-12-08T05:25:00.755982+00:00", + "last_updated": "2020-12-08T05:25:00.755982+00:00", + "context": { + "id": "23a3422eee3bd73e4c9301fbe7756406", + "parent_id": null, + "user_id": null + } + }, + "last_seen": "2020-12-09T03:25:05.267622+00:00" + }, + { + "state": { + "entity_id": "input_number.irrigation_zone_6_schedule_base_hour", + "state": "6.0", + "attributes": { + "initial": 6.0, + "editable": false, + "min": 0.0, + "max": 24.0, + "step": 1.0, + "mode": "slider", + "friendly_name": "Hour of Day", + "icon": "mdi:clock" + }, + "last_changed": "2020-12-08T05:25:00.756020+00:00", + "last_updated": "2020-12-08T05:25:00.756020+00:00", + "context": { + "id": "b44b8a0b2ae20d4fc086c668736f4f47", + "parent_id": null, + "user_id": null + } + }, + "last_seen": "2020-12-09T03:25:05.267622+00:00" + }, + { + "state": { + "entity_id": "input_number.irrigation_zone_6_schedule_base_minute", + "state": "0.0", + "attributes": { + "initial": 0.0, + "editable": false, + "min": 0.0, + "max": 45.0, + "step": 15.0, + "mode": "slider", + "friendly_name": "Minute of Day", + "icon": "mdi:clock" + }, + "last_changed": "2020-12-08T05:25:00.756055+00:00", + "last_updated": "2020-12-08T05:25:00.756055+00:00", + "context": { + "id": "ddb18c213d8c0970f2f8a4884d9be481", + "parent_id": null, + "user_id": null + } + }, + "last_seen": "2020-12-09T03:25:05.267622+00:00" + }, + { + "state": { + "entity_id": "input_number.irrigation_zone_7_duration", + "state": "10.0", + "attributes": { + "initial": null, + "editable": false, + "min": 1.0, + "max": 30.0, + "step": 1.0, + "mode": "slider", + "friendly_name": "Duration in minutes", + "icon": "mdi:timer-sand" + }, + "last_changed": "2020-12-08T05:25:00.756104+00:00", + "last_updated": "2020-12-08T05:25:00.756104+00:00", + "context": { + "id": "7cbe9116c9197977c164b440947190d5", + "parent_id": null, + "user_id": null + } + }, + "last_seen": "2020-12-09T03:25:05.267622+00:00" + }, + { + "state": { + "entity_id": "input_number.irrigation_zone_7_schedule_base_hour", + "state": "6.0", + "attributes": { + "initial": 6.0, + "editable": false, + "min": 0.0, + "max": 24.0, + "step": 1.0, + "mode": "slider", + "friendly_name": "Hour of Day", + "icon": "mdi:clock" + }, + "last_changed": "2020-12-08T05:25:00.756141+00:00", + "last_updated": "2020-12-08T05:25:00.756141+00:00", + "context": { + "id": "e9a00b873c3de16bb9d1a997b907b2e5", + "parent_id": null, + "user_id": null + } + }, + "last_seen": "2020-12-09T03:25:05.267622+00:00" + }, + { + "state": { + "entity_id": "input_number.irrigation_zone_7_schedule_base_minute", + "state": "0.0", + "attributes": { + "initial": 0.0, + "editable": false, + "min": 0.0, + "max": 45.0, + "step": 15.0, + "mode": "slider", + "friendly_name": "Minute of Day", + "icon": "mdi:clock" + }, + "last_changed": "2020-12-08T05:25:00.756177+00:00", + "last_updated": "2020-12-08T05:25:00.756177+00:00", + "context": { + "id": "0279640a47052c11b16485ede482cf17", + "parent_id": null, + "user_id": null + } + }, + "last_seen": "2020-12-09T03:25:05.267622+00:00" + }, + { + "state": { + "entity_id": "automation.system_set_custom_theme_at_startup", + "state": "on", + "attributes": { + "last_triggered": "2020-12-08T05:25:03.059367+00:00", + "mode": "single", + "current": 0, + "id": "themestartup", + "friendly_name": "System - Set Custom Theme at Startup" + }, + "last_changed": "2020-12-08T05:25:00.983260+00:00", + "last_updated": "2020-12-08T05:25:03.077445+00:00", + "context": { + "id": "76d5b39b7c4ca8de78ee92b61076832d", + "parent_id": null, + "user_id": null + } + }, + "last_seen": "2020-12-09T03:25:05.267622+00:00" + }, + { + "state": { + "entity_id": "automation.restart_notification", + "state": "on", + "attributes": { + "last_triggered": "2020-12-08T05:25:03.059516+00:00", + "mode": "single", + "current": 0, + "friendly_name": "Restart Notification" + }, + "last_changed": "2020-12-08T05:25:00.983382+00:00", + "last_updated": "2020-12-08T05:25:05.190029+00:00", + "context": { + "id": "ff750280c2a575e2f51f73c06bff7264", + "parent_id": null, + "user_id": null + } + }, + "last_seen": "2020-12-09T03:25:05.267622+00:00" + }, + { + "state": { + "entity_id": "automation.closet_fan_on_set_temperature_slider", + "state": "on", + "attributes": { + "last_triggered": "2020-12-09T03:25:04.075368+00:00", + "mode": "single", + "current": 0, + "friendly_name": "Closet Fan On Set Temperature slider" + }, + "last_changed": "2020-12-08T05:25:00.983482+00:00", + "last_updated": "2020-12-09T03:25:04.080231+00:00", + "context": { + "id": "45fa9166d84e926558a00b4c56201ebe", + "parent_id": null, + "user_id": null + } + }, + "last_seen": "2020-12-09T03:25:05.267622+00:00" + }, + { + "state": { + "entity_id": "automation.closet_fan_temp_slider_moved", + "state": "on", + "attributes": { + "last_triggered": "2020-04-08T04:53:57.636051+00:00", + "mode": "single", + "current": 0, + "friendly_name": "Closet Fan Temp Slider Moved" + }, + "last_changed": "2020-12-08T05:25:00.983576+00:00", + "last_updated": "2020-12-08T05:25:00.983576+00:00", + "context": { + "id": "af809fac85084fd4740deaa24db02ad9", + "parent_id": null, + "user_id": null + } + }, + "last_seen": "2020-12-09T03:25:05.267622+00:00" + }, + { + "state": { + "entity_id": "automation.security_lights_interior_time_off", + "state": "on", + "attributes": { + "last_triggered": "2020-12-06T06:30:00.999141+00:00", + "mode": "single", + "current": 0, + "friendly_name": "security_lights_interior_time_off" + }, + "last_changed": "2020-12-08T05:25:00.983712+00:00", + "last_updated": "2020-12-08T05:25:00.983712+00:00", + "context": { + "id": "7e277322f7fbac5c4e7c67f4eef42871", + "parent_id": null, + "user_id": null + } + }, + "last_seen": "2020-12-09T03:25:05.267622+00:00" + }, + { + "state": { + "entity_id": "automation.security_lights_interior_time_on", + "state": "on", + "attributes": { + "last_triggered": "2020-12-08T23:33:27.001284+00:00", + "mode": "single", + "current": 0, + "friendly_name": "security_lights_interior_time_on" + }, + "last_changed": "2020-12-08T05:25:00.983814+00:00", + "last_updated": "2020-12-08T23:33:27.009497+00:00", + "context": { + "id": "2ee234181f8664e15891164be03b0063", + "parent_id": null, + "user_id": null + } + }, + "last_seen": "2020-12-09T03:25:05.267622+00:00" + }, + { + "state": { + "entity_id": "automation.power_state_on_ha_start_up", + "state": "on", + "attributes": { + "last_triggered": "2020-12-08T05:25:03.059634+00:00", + "mode": "single", + "current": 0, + "friendly_name": "\u201cPower state on HA start-up\u201d" + }, + "last_changed": "2020-12-08T05:25:00.983911+00:00", + "last_updated": "2020-12-08T05:25:03.080166+00:00", + "context": { + "id": "4b062a8a61b8355d337ffc2a181999bb", + "parent_id": null, + "user_id": null + } + }, + "last_seen": "2020-12-09T03:25:05.267622+00:00" + }, + { + "state": { + "entity_id": "switch.all_sonoffs", + "state": "on", + "attributes": { + "friendly_name": "All Sonoffs on Network" + }, + "last_changed": "2020-12-08T23:33:27.121145+00:00", + "last_updated": "2020-12-08T23:33:27.121145+00:00", + "context": { + "id": "2ee234181f8664e15891164be03b0063", + "parent_id": null, + "user_id": null + } + }, + "last_seen": "2020-12-09T03:25:05.267622+00:00" + }, + { + "state": { + "entity_id": "device_tracker.700t1c", + "state": "unknown", + "attributes": { + "source_type": "gps", + "friendly_name": "700T1C" + }, + "last_changed": "2020-12-08T05:25:01.267061+00:00", + "last_updated": "2020-12-08T05:25:01.267061+00:00", + "context": { + "id": "4ce6f00d7984c133e3b19fc63b18d63f", + "parent_id": null, + "user_id": null + } + }, + "last_seen": "2020-12-09T03:25:05.267622+00:00" + }, + { + "state": { + "entity_id": "device_tracker.sm_g935r4", + "state": "home", + "attributes": { + "source_type": "gps", + "latitude": 44.461052, + "longitude": -118.7120106, + "gps_accuracy": 3, + "altitude": 1057.0, + "course": 0, + "speed": 0, + "vertical_accuracy": 6, + "friendly_name": "SM-G935R4" + }, + "last_changed": "2020-12-08T05:25:01.267258+00:00", + "last_updated": "2020-12-08T05:25:01.267258+00:00", + "context": { + "id": "684ad1241578e464c230310c7f821247", + "parent_id": null, + "user_id": null + } + }, + "last_seen": "2020-12-09T03:25:05.267622+00:00" + }, + { + "state": { + "entity_id": "device_tracker.white_motog", + "state": "unknown", + "attributes": { + "source_type": "gps", + "friendly_name": "White Moto G" + }, + "last_changed": "2020-12-08T05:25:01.267368+00:00", + "last_updated": "2020-12-08T05:25:01.267368+00:00", + "context": { + "id": "66647dcc08c2dc675c3ea2675f8ead78", + "parent_id": null, + "user_id": null + } + }, + "last_seen": "2020-12-09T03:25:05.267622+00:00" + }, + { + "state": { + "entity_id": "device_tracker.lgus215", + "state": "unknown", + "attributes": { + "source_type": "gps", + "friendly_name": "LGUS215" + }, + "last_changed": "2020-12-08T05:25:01.267462+00:00", + "last_updated": "2020-12-08T05:25:01.267462+00:00", + "context": { + "id": "12781f71d6eb0534f39dc2a359a38804", + "parent_id": null, + "user_id": null + } + }, + "last_seen": "2020-12-09T03:25:05.267622+00:00" + }, + { + "state": { + "entity_id": "light.panic", + "state": "off", + "attributes": { + "friendly_name": "Panic", + "assumed_state": true, + "supported_features": 0 + }, + "last_changed": "2020-12-08T05:25:01.416827+00:00", + "last_updated": "2020-12-08T05:25:01.416827+00:00", + "context": { + "id": "ce98612457458c2d4fbf0cfa96c1b78d", + "parent_id": null, + "user_id": null + } + }, + "last_seen": "2020-12-09T03:25:05.267622+00:00" + }, + { + "state": { + "entity_id": "switch.fan_automation_enabled", + "state": "on", + "attributes": { + "friendly_name": "Fan Automation Enabled", + "icon": "mdi:fan" + }, + "last_changed": "2020-12-08T05:25:02.977553+00:00", + "last_updated": "2020-12-08T05:25:02.977553+00:00", + "context": { + "id": "0220b0de6de9f945ed644872bb78cd86", + "parent_id": null, + "user_id": null + } + }, + "last_seen": "2020-12-09T03:25:05.267622+00:00" + }, + { + "state": { + "entity_id": "switch.outside_north_circuit_1", + "state": "off", + "attributes": { + "friendly_name": "Outside North Circuit 1" + }, + "last_changed": "2020-12-09T03:05:30.493999+00:00", + "last_updated": "2020-12-09T03:05:30.493999+00:00", + "context": { + "id": "63e955a64b49d2226172c5d3ab9cebfc", + "parent_id": null, + "user_id": null + } + }, + "last_seen": "2020-12-09T03:25:05.267622+00:00" + }, + { + "state": { + "entity_id": "switch.outside_north_circuit_2", + "state": "off", + "attributes": { + "friendly_name": "Outside North Circuit 2" + }, + "last_changed": "2020-12-09T03:05:30.494193+00:00", + "last_updated": "2020-12-09T03:05:30.494193+00:00", + "context": { + "id": "cefb26be9817194b081564a86132e249", + "parent_id": null, + "user_id": null + } + }, + "last_seen": "2020-12-09T03:25:05.267622+00:00" + }, + { + "state": { + "entity_id": "switch.sonoff_white", + "state": "on", + "attributes": { + "friendly_name": "Sonoff White" + }, + "last_changed": "2020-12-08T23:33:27.124669+00:00", + "last_updated": "2020-12-08T23:33:27.124669+00:00", + "context": { + "id": "2ee234181f8664e15891164be03b0063", + "parent_id": null, + "user_id": null + } + }, + "last_seen": "2020-12-09T03:25:05.267622+00:00" + }, + { + "state": { + "entity_id": "switch.sonoff_brown", + "state": "on", + "attributes": { + "friendly_name": "Sonoff Brown" + }, + "last_changed": "2020-12-08T23:33:27.117563+00:00", + "last_updated": "2020-12-08T23:33:27.117563+00:00", + "context": { + "id": "2ee234181f8664e15891164be03b0063", + "parent_id": null, + "user_id": null + } + }, + "last_seen": "2020-12-09T03:25:05.267622+00:00" + }, + { + "state": { + "entity_id": "switch.circuit_1", + "state": "off", + "attributes": { + "friendly_name": "Circuit 1" + }, + "last_changed": "2020-12-08T05:25:01.430019+00:00", + "last_updated": "2020-12-08T05:25:01.430019+00:00", + "context": { + "id": "efc99edad00e0378b97418a8061eb822", + "parent_id": null, + "user_id": null + } + }, + "last_seen": "2020-12-09T03:25:05.267622+00:00" + } + ] +} \ No newline at end of file diff --git a/.storage/core.uuid b/.storage/core.uuid new file mode 100644 index 0000000..ccf08c4 --- /dev/null +++ b/.storage/core.uuid @@ -0,0 +1,7 @@ +{ + "version": 1, + "key": "core.uuid", + "data": { + "uuid": "730705095bdc49d696bac6c69892ff3c" + } +} \ No newline at end of file diff --git a/.storage/esphome.28ebec0e77234c51bb89a4a1b26e47b3 b/.storage/esphome.28ebec0e77234c51bb89a4a1b26e47b3 new file mode 100644 index 0000000..a67a860 --- /dev/null +++ b/.storage/esphome.28ebec0e77234c51bb89a4a1b26e47b3 @@ -0,0 +1,39 @@ +{ + "data": { + "device_info": { + "compilation_time": "Apr 24 2020, 17:23:25", + "esphome_version": "1.14.3", + "has_deep_sleep": false, + "mac_address": "84:F3:EB:3B:84:92", + "model": "PLATFORMIO_D1_MINI", + "name": "outside_temp_humd", + "uses_password": false + }, + "sensor": [ + { + "accuracy_decimals": 1, + "force_update": false, + "icon": "mdi:thermometer", + "key": 2259435129, + "name": "Outside Temperature", + "object_id": "outside_temperature", + "unique_id": "outside_temp_humdsensoroutside_temperature", + "unit_of_measurement": "\u00b0C" + }, + { + "accuracy_decimals": 1, + "force_update": false, + "icon": "mdi:water-percent", + "key": 1320590942, + "name": "Outside Humidity", + "object_id": "outside_humidity", + "unique_id": "outside_temp_humdsensoroutside_humidity", + "unit_of_measurement": "%" + } + ], + "services": [], + "text_sensor": [] + }, + "key": "esphome.28ebec0e77234c51bb89a4a1b26e47b3", + "version": 1 +} \ No newline at end of file diff --git a/.storage/esphome.9436e71647e64581a19a2cc8172f9a35 b/.storage/esphome.9436e71647e64581a19a2cc8172f9a35 new file mode 100644 index 0000000..8bd9644 --- /dev/null +++ b/.storage/esphome.9436e71647e64581a19a2cc8172f9a35 @@ -0,0 +1,39 @@ +{ + "version": 1, + "key": "esphome.9436e71647e64581a19a2cc8172f9a35", + "data": { + "device_info": { + "uses_password": false, + "name": "inside_temp_humd", + "mac_address": "84:F3:EB:3B:77:30", + "compilation_time": "Dec 1 2020, 11:20:56", + "model": "PLATFORMIO_D1_MINI", + "has_deep_sleep": false, + "esphome_version": "1.15.3" + }, + "services": [], + "sensor": [ + { + "object_id": "inside_temperature", + "key": 920924750, + "name": "Inside Temperature", + "unique_id": "inside_temp_humdsensorinside_temperature", + "icon": "mdi:thermometer", + "unit_of_measurement": "\u00b0C", + "accuracy_decimals": 1, + "force_update": false + }, + { + "object_id": "inside_humidity", + "key": 847076999, + "name": "Inside Humidity", + "unique_id": "inside_temp_humdsensorinside_humidity", + "icon": "mdi:water-percent", + "unit_of_measurement": "%", + "accuracy_decimals": 1, + "force_update": false + } + ], + "text_sensor": [] + } +} \ No newline at end of file diff --git a/.storage/esphome.cf5266d3fa4a4824850935818193ac48 b/.storage/esphome.cf5266d3fa4a4824850935818193ac48 new file mode 100644 index 0000000..96b17ee --- /dev/null +++ b/.storage/esphome.cf5266d3fa4a4824850935818193ac48 @@ -0,0 +1,39 @@ +{ + "version": 1, + "key": "esphome.cf5266d3fa4a4824850935818193ac48", + "data": { + "device_info": { + "uses_password": false, + "name": "outside_temp_humd", + "mac_address": "84:F3:EB:3B:84:92", + "compilation_time": "Dec 1 2020, 10:59:50", + "model": "PLATFORMIO_D1_MINI", + "has_deep_sleep": false, + "esphome_version": "1.15.3" + }, + "services": [], + "sensor": [ + { + "object_id": "outside_temperature", + "key": 2259435129, + "name": "Outside Temperature", + "unique_id": "outside_temp_humdsensoroutside_temperature", + "icon": "mdi:thermometer", + "unit_of_measurement": "\u00b0C", + "accuracy_decimals": 1, + "force_update": false + }, + { + "object_id": "outside_humidity", + "key": 1320590942, + "name": "Outside Humidity", + "unique_id": "outside_temp_humdsensoroutside_humidity", + "icon": "mdi:water-percent", + "unit_of_measurement": "%", + "accuracy_decimals": 1, + "force_update": false + } + ], + "text_sensor": [] + } +} \ No newline at end of file diff --git a/.storage/esphome.eeb6ca4d62944fa4b32711e291ba2f63 b/.storage/esphome.eeb6ca4d62944fa4b32711e291ba2f63 new file mode 100644 index 0000000..1e721d0 --- /dev/null +++ b/.storage/esphome.eeb6ca4d62944fa4b32711e291ba2f63 @@ -0,0 +1,39 @@ +{ + "data": { + "device_info": { + "compilation_time": "Feb 28 2020, 20:07:32", + "esphome_version": "1.14.3", + "has_deep_sleep": false, + "mac_address": "84:F3:EB:3B:84:92", + "model": "PLATFORMIO_D1_MINI", + "name": "outside_temp_humd", + "uses_password": false + }, + "sensor": [ + { + "accuracy_decimals": 1, + "force_update": false, + "icon": "mdi:thermometer", + "key": 2259435129, + "name": "Outside Temperature", + "object_id": "outside_temperature", + "unique_id": "outside_temp_humdsensoroutside_temperature", + "unit_of_measurement": "\u00b0C" + }, + { + "accuracy_decimals": 1, + "force_update": false, + "icon": "mdi:water-percent", + "key": 1320590942, + "name": "Outside Humidity", + "object_id": "outside_humidity", + "unique_id": "outside_temp_humdsensoroutside_humidity", + "unit_of_measurement": "%" + } + ], + "services": [], + "text_sensor": [] + }, + "key": "esphome.eeb6ca4d62944fa4b32711e291ba2f63", + "version": 1 +} \ No newline at end of file diff --git a/.storage/frontend.user_data_99685dc45d9f40dc8183e11ce5128038 b/.storage/frontend.user_data_99685dc45d9f40dc8183e11ce5128038 new file mode 100644 index 0000000..33c126a --- /dev/null +++ b/.storage/frontend.user_data_99685dc45d9f40dc8183e11ce5128038 @@ -0,0 +1,9 @@ +{ + "data": { + "core": { + "showAdvanced": true + } + }, + "key": "frontend.user_data_99685dc45d9f40dc8183e11ce5128038", + "version": 1 +} \ No newline at end of file diff --git a/.storage/frontend_theme b/.storage/frontend_theme new file mode 100644 index 0000000..9f21cfe --- /dev/null +++ b/.storage/frontend_theme @@ -0,0 +1,8 @@ +{ + "version": 1, + "key": "frontend_theme", + "data": { + "frontend_default_theme": "Dark Turqoise", + "frontend_default_dark_theme": null + } +} \ No newline at end of file diff --git a/.storage/hacs.critical b/.storage/hacs.critical new file mode 100644 index 0000000..ef3400e --- /dev/null +++ b/.storage/hacs.critical @@ -0,0 +1,12 @@ +{ + "version": "6", + "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 + } + ] +} \ No newline at end of file diff --git a/.storage/hacs.hacs b/.storage/hacs.hacs new file mode 100644 index 0000000..12dd712 --- /dev/null +++ b/.storage/hacs.hacs @@ -0,0 +1,9 @@ +{ + "version": "6", + "key": "hacs.hacs", + "data": { + "view": "Grid", + "compact": false, + "onboarding_done": false + } +} \ No newline at end of file diff --git a/.storage/hacs.repositories b/.storage/hacs.repositories new file mode 100644 index 0000000..358be11 --- /dev/null +++ b/.storage/hacs.repositories @@ -0,0 +1,21929 @@ +{ + "version": "6", + "key": "hacs.repositories", + "data": { + "200081161": { + "authors": [], + "category": "plugin", + "description": "Custom Lovelace card for Budapest Public Transportation custom component", + "domain": "", + "downloads": 0, + "full_name": "amaximus/bkk-stop-card", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "477238a", + "last_release_tag": null, + "last_updated": "2020-11-23T15:09:08Z", + "name": "bkk-stop-card", + "new": false, + "repository_manifest": {}, + "selected_tag": null, + "show_beta": false, + "stars": 6, + "topics": [ + "bkk", + "budapest", + "homeassistant", + "lovelace-custom-card", + "lovelace-ui", + "transportation" + ], + "version_installed": null + }, + "202546107": { + "authors": [], + "category": "plugin", + "description": "Extras for the synthwave inspired theme for Home Assistant", + "domain": "", + "downloads": 0, + "full_name": "bbbenji/synthwave-hass-extras", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "2a2542d", + "last_release_tag": "0.2.4", + "last_updated": "2020-10-30T00:24:02Z", + "name": "synthwave-hass-extras", + "new": false, + "repository_manifest": {}, + "selected_tag": null, + "show_beta": false, + "stars": 13, + "topics": [], + "version_installed": null + }, + "169525033": { + "authors": [], + "category": "plugin", + "description": "Custom calendar card for Home Assistant with Lovelace", + "domain": "", + "downloads": 5641, + "full_name": "atomic7777/atomic_calendar", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "875b940", + "last_release_tag": "v0.8.9", + "last_updated": "2020-12-04T12:12:41Z", + "name": "atomic_calendar", + "new": false, + "repository_manifest": {}, + "selected_tag": null, + "show_beta": false, + "stars": 95, + "topics": [], + "version_installed": null + }, + "257102434": { + "authors": [], + "category": "plugin", + "description": "FKF Budapest Garbage Collection Card for Home Assistant/Lovelace", + "domain": "", + "downloads": 82, + "full_name": "amaximus/fkf-garbage-collection-card", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "cc65d3b", + "last_release_tag": "0.7.0", + "last_updated": "2020-11-23T15:10:23Z", + "name": "fkf-garbage-collection-card", + "new": false, + "repository_manifest": { + "name": "FKF Budapest Garbage Collection Card", + "filename": "fkf-garbage-collection-card.js", + "render_readme": "true" + }, + "selected_tag": null, + "show_beta": false, + "stars": 3, + "topics": [ + "budapest", + "homeassistant", + "lovelace", + "lovelace-custom-card", + "lovelace-ui" + ], + "version_installed": null + }, + "287840715": { + "authors": [], + "category": "plugin", + "description": "Lovelace custom card for visualizing the ZWave network with the OpenZWave (beta) integration.", + "domain": "", + "downloads": 0, + "full_name": "abmantis/ozw-network-visualization-card", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "fb85517", + "last_release_tag": null, + "last_updated": "2020-09-12T18:12:50Z", + "name": "ozw-network-visualization-card", + "new": false, + "repository_manifest": { + "name": "OZW Network Visualization Card", + "content_in_root": true, + "filename": "ozw-network-visualization-card.js", + "homeassistant": "0.114.0", + "render_readme": true + }, + "selected_tag": null, + "show_beta": false, + "stars": 21, + "topics": [ + "home-automation", + "homeassistant", + "zwave" + ], + "version_installed": null + }, + "303857065": { + "authors": [], + "category": "plugin", + "description": "Show battery states or attributes with dynamic icon on entity rows in Home Assistant's Lovelace UI", + "domain": "", + "downloads": 2171, + "full_name": "benct/lovelace-battery-entity-row", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "094a0fa", + "last_release_tag": "v1.2.0", + "last_updated": "2020-11-05T01:31:49Z", + "name": "lovelace-battery-entity-row", + "new": false, + "repository_manifest": { + "name": "Battery Entity Row", + "filename": "battery-entity-row.js", + "render_readme": true + }, + "selected_tag": null, + "show_beta": false, + "stars": 9, + "topics": [ + "attribute", + "battery", + "card", + "entity", + "entity-rows", + "hacs", + "home-assistant", + "home-assistant-frontend", + "lovelace", + "lovelace-ui", + "state" + ], + "version_installed": null + }, + "268163975": { + "authors": [], + "category": "plugin", + "description": "Minimalistic climate card for Home Assistant Lovelace UI", + "domain": "", + "downloads": 2923, + "full_name": "artem-sedykh/mini-climate-card", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "e3a78a1", + "last_release_tag": "v1.2.7", + "last_updated": "2020-08-02T09:29:18Z", + "name": "mini-climate-card", + "new": false, + "repository_manifest": { + "name": "mini climate card", + "render_readme": false, + "filename": "mini-climate-card-bundle.js", + "domains": [ + "climate" + ] + }, + "selected_tag": null, + "show_beta": false, + "stars": 41, + "topics": [ + "automation", + "climate", + "climate-entity", + "custom", + "hassio", + "home-assistant", + "lovelace", + "lovelace-ui" + ], + "version_installed": null + }, + "207292725": { + "authors": [], + "category": "plugin", + "description": "Flexible Horseshoe card for Home Assistant Lovelace UI. A card with a flexible layout, a horseshoe-like donut graph, multiple entities or attributes, graphics and animations!", + "domain": "", + "downloads": 0, + "full_name": "AmoebeLabs/flex-horseshoe-card", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "e09bb7b", + "last_release_tag": "0.9.0", + "last_updated": "2020-11-19T03:04:13Z", + "name": "flex-horseshoe-card", + "new": false, + "repository_manifest": { + "name": "Flexible Horseshoe Card for Lovelace", + "content_in_root": true, + "filename": "flex-horseshoe-card.js" + }, + "selected_tag": null, + "show_beta": false, + "stars": 68, + "topics": [ + "home-assistant", + "home-assistant-custom", + "home-assistant-frontend", + "lovelace-card", + "lovelace-custom-card", + "lovelace-ui" + ], + "version_installed": null + }, + "269474857": { + "authors": [], + "category": "plugin", + "description": "Custom lovelace card to use in\u00a0Home assistant allowing you to redirect a user to certain view based on entity states.", + "domain": "", + "downloads": 0, + "full_name": "ben8p/lovelace-tab-redirect-card", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "25d35e4", + "last_release_tag": null, + "last_updated": "2020-11-23T21:30:41Z", + "name": "lovelace-tab-redirect-card", + "new": false, + "repository_manifest": { + "name": "Tab Redirect Card", + "render_readme": true + }, + "selected_tag": null, + "show_beta": false, + "stars": 2, + "topics": [ + "hacs", + "homeassistant", + "lovelace-custom-card", + "lovelace-ui" + ], + "version_installed": null + }, + "253019926": { + "authors": [], + "category": "plugin", + "description": "Minimalistic humidifier card for Home Assistant Lovelace UI", + "domain": "", + "downloads": 546, + "full_name": "artem-sedykh/mini-humidifier", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "df6395e", + "last_release_tag": "v2.2.7", + "last_updated": "2020-11-18T09:41:39Z", + "name": "mini-humidifier", + "new": false, + "repository_manifest": { + "name": "mini humidifier", + "render_readme": false, + "filename": "mini-humidifier-bundle.js", + "domains": [ + "fan" + ] + }, + "selected_tag": null, + "show_beta": false, + "stars": 38, + "topics": [ + "automation", + "custom", + "hassio", + "home-assistant", + "humidifier", + "lovelace", + "lovelace-ui" + ], + "version_installed": null + }, + "271886611": { + "authors": [], + "category": "plugin", + "description": "Like a picture glance card, but for plant data", + "domain": "", + "downloads": 0, + "full_name": "badguy99/PlantPictureCard", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "eb06132", + "last_release_tag": "v0.1.4", + "last_updated": "2020-09-13T17:45:01Z", + "name": "PlantPictureCard", + "new": false, + "repository_manifest": { + "name": "Plant Picture Card", + "render_readme": true, + "filename": "PlantPictureCard.js" + }, + "selected_tag": null, + "show_beta": false, + "stars": 4, + "topics": [ + "image", + "lovelace-card", + "plants" + ], + "version_installed": null + }, + "201292040": { + "authors": [], + "category": "plugin", + "description": "Home Assistant Custom Card to show Zigbee2mqtt network map", + "domain": "", + "downloads": 8421, + "full_name": "azuwis/zigbee2mqtt-networkmap", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "d5bcc69", + "last_release_tag": "v0.6.0", + "last_updated": "2020-09-18T01:34:08Z", + "name": "zigbee2mqtt-networkmap", + "new": false, + "repository_manifest": { + "name": "Zigbee2mqtt Networkmap Card", + "render_readme": true + }, + "selected_tag": null, + "show_beta": false, + "stars": 92, + "topics": [ + "home-assistant", + "zigbee2mqtt" + ], + "version_installed": null + }, + "214365813": { + "authors": [], + "category": "plugin", + "description": "StarLine lovelace card for Home Assistant", + "domain": "", + "downloads": 0, + "full_name": "Anonym-tsk/lovelace-starline-card", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "8a15c42", + "last_release_tag": "v1.1.2", + "last_updated": "2020-08-06T09:16:49Z", + "name": "lovelace-starline-card", + "new": false, + "repository_manifest": { + "name": "StarLine Card", + "homeassistant": "0.103.0" + }, + "selected_tag": null, + "show_beta": false, + "stars": 14, + "topics": [], + "version_installed": null + }, + "207018200": { + "authors": [], + "category": "plugin", + "description": "Custom Lovelace card for Garbage Collection custom component", + "domain": "", + "downloads": 1191, + "full_name": "amaximus/garbage-collection-card", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "7523c53", + "last_release_tag": "1.16.2", + "last_updated": "2020-11-28T16:17:05Z", + "name": "garbage-collection-card", + "new": false, + "repository_manifest": { + "name": "Garbage Collection Card", + "filename": "garbage-collection-card.js", + "render_readme": "true" + }, + "selected_tag": null, + "show_beta": false, + "stars": 22, + "topics": [ + "garbage-collection", + "homeassistant", + "lovelace", + "lovelace-custom-card", + "ui-lovelace" + ], + "version_installed": null + }, + "315044466": { + "authors": [], + "category": "plugin", + "description": "Custom Transmission card for Home Assistant/Lovelace", + "domain": "", + "downloads": 188, + "full_name": "amaximus/transmission-card", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "fe3805d", + "last_release_tag": "0.4.1", + "last_updated": "2020-12-06T18:49:52Z", + "name": "transmission-card", + "new": false, + "repository_manifest": { + "name": "Transmission Card", + "filename": "transmission-card.js", + "render_readme": "true" + }, + "selected_tag": null, + "show_beta": false, + "stars": 4, + "topics": [ + "home-assistant", + "homeassistant", + "lovelace", + "lovelace-card", + "lovelace-custom-card", + "transmission" + ], + "version_installed": null + }, + "192732636": { + "authors": [], + "category": "plugin", + "description": "Weather Card with animated icons for Home Assistant Lovelace", + "domain": "", + "downloads": 0, + "full_name": "bramkragten/weather-card", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "c185504", + "last_release_tag": "v1.5.0", + "last_updated": "2020-10-30T14:11:40Z", + "name": "weather-card", + "new": false, + "repository_manifest": {}, + "selected_tag": null, + "show_beta": false, + "stars": 169, + "topics": [], + "version_installed": null + }, + "184658908": { + "authors": [], + "category": "plugin", + "description": "GitHub repository sensor data on entity rows in Home Assistant's Lovelace UI", + "domain": "", + "downloads": 359, + "full_name": "benct/lovelace-github-entity-row", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "e273046", + "last_release_tag": "v1.0.0", + "last_updated": "2020-10-19T20:51:09Z", + "name": "lovelace-github-entity-row", + "new": false, + "repository_manifest": { + "name": "GitHub Entity Row", + "filename": "github-entity-row.js", + "render_readme": true + }, + "selected_tag": null, + "show_beta": false, + "stars": 13, + "topics": [ + "card", + "entity", + "entity-rows", + "github", + "hacs", + "home-assistant", + "home-assistant-frontend", + "lovelace", + "lovelace-ui", + "sensor" + ], + "version_installed": null + }, + "194037195": { + "authors": [], + "category": "plugin", + "description": "A custom card for displaying information provided by Beerbolaget (https://github.com/Ceerbeerus/beerbolaget).", + "domain": "", + "downloads": 0, + "full_name": "Ceerbeerus/beerbolaget-card", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "ac8da42", + "last_release_tag": "0.1.55", + "last_updated": "2020-08-07T06:24:51Z", + "name": "beerbolaget-card", + "new": false, + "repository_manifest": {}, + "selected_tag": null, + "show_beta": false, + "stars": 2, + "topics": [], + "version_installed": null + }, + "180229356": { + "authors": [], + "category": "plugin", + "description": "Simple card for various robot vacuums in Home Assistant's Lovelace UI", + "domain": "", + "downloads": 3680, + "full_name": "benct/lovelace-xiaomi-vacuum-card", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "f88567b", + "last_release_tag": "v4.1.0", + "last_updated": "2020-10-26T13:07:12Z", + "name": "lovelace-xiaomi-vacuum-card", + "new": false, + "repository_manifest": { + "name": "(Simple) Vacuum Card", + "filename": "xiaomi-vacuum-card.js", + "render_readme": true + }, + "selected_tag": null, + "show_beta": false, + "stars": 106, + "topics": [ + "card", + "hacs", + "home-assistant", + "home-assistant-frontend", + "lovelace", + "lovelace-ui", + "roborock", + "robot-vacuums", + "vacuum", + "xiaomi", + "xiaomi-vacuum" + ], + "version_installed": null + }, + "187245418": { + "authors": [], + "category": "plugin", + "description": null, + "domain": "", + "downloads": 0, + "full_name": "custom-cards/bignumber-card", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "a5df3da", + "last_release_tag": "0.0.2", + "last_updated": "2020-12-04T12:03:09Z", + "name": "bignumber-card", + "new": false, + "repository_manifest": {}, + "selected_tag": null, + "show_beta": false, + "stars": 33, + "topics": [], + "version_installed": null + }, + "143850865": { + "authors": [], + "category": "plugin", + "description": "This card give you a list of your wishlist items.", + "domain": "", + "downloads": 0, + "full_name": "custom-cards/beer-card", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "395380e", + "last_release_tag": "0.2.4", + "last_updated": "2020-12-04T12:14:36Z", + "name": "beer-card", + "new": false, + "repository_manifest": {}, + "selected_tag": null, + "show_beta": false, + "stars": 1, + "topics": [], + "version_installed": null + }, + "178921037": { + "authors": [], + "category": "plugin", + "description": "Show multiple entity states and attributes on entity rows in Home Assistant's Lovelace UI", + "domain": "", + "downloads": 4175, + "full_name": "benct/lovelace-multiple-entity-row", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "5dd17b1", + "last_release_tag": "v4.1.1", + "last_updated": "2020-11-16T00:07:07Z", + "name": "lovelace-multiple-entity-row", + "new": false, + "repository_manifest": { + "name": "Multiple Entity Row", + "filename": "multiple-entity-row.js", + "render_readme": true + }, + "selected_tag": null, + "show_beta": false, + "stars": 211, + "topics": [ + "attribute", + "card", + "entity", + "entity-attribute", + "entity-rows", + "format", + "hacs", + "home-assistant", + "home-assistant-frontend", + "lovelace", + "lovelace-ui", + "multiple", + "state" + ], + "version_installed": null + }, + "139634406": { + "authors": [], + "category": "plugin", + "description": "\ud83c\udf21 Thermostat card with a round and black feel to it", + "domain": "", + "downloads": 1256, + "full_name": "ciotlosm/lovelace-thermostat-dark-card", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "2362a1e", + "last_release_tag": "0.0.4", + "last_updated": "2020-11-22T14:33:21Z", + "name": "lovelace-thermostat-dark-card", + "new": false, + "repository_manifest": { + "name": "Dark Thermostat", + "render_readme": true, + "filename": "thermostat-dark-card.js" + }, + "selected_tag": null, + "show_beta": false, + "stars": 570, + "topics": [ + "custom-cards", + "hacs", + "home-assistant", + "lovelace", + "thermostat" + ], + "version_installed": null + }, + "192732887": { + "authors": [], + "category": "plugin", + "description": "Card that allows you to swipe throught multiple cards for Home Assistant Lovelace", + "domain": "", + "downloads": 0, + "full_name": "bramkragten/swipe-card", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "d2959d3", + "last_release_tag": "v4.0.0", + "last_updated": "2020-07-17T11:18:54Z", + "name": "swipe-card", + "new": false, + "repository_manifest": { + "homeassistant": "0.110.0" + }, + "selected_tag": null, + "show_beta": false, + "stars": 25, + "topics": [], + "version_installed": null + }, + "235449701": { + "authors": [], + "category": "plugin", + "description": "\u23f0 Lovelace Card to Control Light Alarm Properties", + "domain": "", + "downloads": 0, + "full_name": "chaptergy/lightalarm-card", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "4df9223", + "last_release_tag": "v3.0.2", + "last_updated": "2020-10-02T09:12:59Z", + "name": "lightalarm-card", + "new": false, + "repository_manifest": { + "name": "Lightalarm Card", + "content_in_root": false, + "filename": "lightalarm-card.js" + }, + "selected_tag": null, + "show_beta": false, + "stars": 24, + "topics": [], + "version_installed": null + }, + "215327195": { + "authors": [], + "category": "plugin", + "description": "\ud83d\udca1 A Lovelace custom card for RGB lights", + "domain": "", + "downloads": 3785, + "full_name": "bokub/rgb-light-card", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "5c2fcef", + "last_release_tag": "1.7.1", + "last_updated": "2020-10-11T16:29:16Z", + "name": "rgb-light-card", + "new": false, + "repository_manifest": { + "name": "RGB Light Card", + "content_in_root": true, + "filename": "card.js", + "render_readme": true, + "domains": [ + "light" + ] + }, + "selected_tag": null, + "show_beta": false, + "stars": 110, + "topics": [ + "home-assistant", + "lovelace", + "lovelace-custom-card", + "rgb-lights" + ], + "version_installed": null + }, + "163363577": { + "authors": [], + "category": "plugin", + "description": "Customizable Animated Bar card for Home Assistant Lovelace", + "domain": "", + "downloads": 7782, + "full_name": "custom-cards/bar-card", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "24b4162", + "last_release_tag": "3.2.0", + "last_updated": "2020-08-12T23:34:42Z", + "name": "bar-card", + "new": false, + "repository_manifest": { + "name": "Bar Card", + "render_readme": true, + "filename": "bar-card.js" + }, + "selected_tag": null, + "show_beta": false, + "stars": 150, + "topics": [], + "version_installed": null + }, + "142545838": { + "authors": [], + "category": "plugin", + "description": "The card makes it possible to use gauges from https://canvas-gauges.com/", + "domain": "", + "downloads": 2019, + "full_name": "custom-cards/canvas-gauge-card", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "8e769e3", + "last_release_tag": "0.4.1", + "last_updated": "2020-09-25T19:45:39Z", + "name": "canvas-gauge-card", + "new": false, + "repository_manifest": { + "name": "Canvas Gauge Card", + "render_readme": true, + "filename": "canvas-gauge-card.js" + }, + "selected_tag": null, + "show_beta": false, + "stars": 60, + "topics": [], + "version_installed": null + }, + "141952963": { + "authors": [], + "category": "plugin", + "description": "A custom component for displaying sensor values as cards or elements", + "domain": "", + "downloads": 0, + "full_name": "custom-cards/circle-sensor-card", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "9b3b78c", + "last_release_tag": "1.2.0", + "last_updated": "2020-09-29T11:57:40Z", + "name": "circle-sensor-card", + "new": false, + "repository_manifest": {}, + "selected_tag": null, + "show_beta": false, + "stars": 67, + "topics": [], + "version_installed": null + }, + "180000010": { + "authors": [], + "category": "plugin", + "description": null, + "domain": "", + "downloads": 1599, + "full_name": "custom-cards/cover-element", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "495f600", + "last_release_tag": "0.3.0", + "last_updated": "2019-06-03T04:34:16Z", + "name": "cover-element", + "new": false, + "repository_manifest": {}, + "selected_tag": null, + "show_beta": false, + "stars": 12, + "topics": [], + "version_installed": null + }, + "187245461": { + "authors": [], + "category": "plugin", + "description": "Entity Attributes", + "domain": "", + "downloads": 0, + "full_name": "custom-cards/entity-attributes-card", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "d810913", + "last_release_tag": "0.1.2", + "last_updated": "2020-12-04T12:11:31Z", + "name": "entity-attributes-card", + "new": false, + "repository_manifest": {}, + "selected_tag": null, + "show_beta": false, + "stars": 29, + "topics": [], + "version_installed": null + }, + "164022050": { + "authors": [], + "category": "plugin", + "description": "Check Button Card is a button that tracks when it is last pressed, for the Home Assistant Lovelace front-end using MQTT auto discovery.", + "domain": "", + "downloads": 1107, + "full_name": "custom-cards/check-button-card", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "2591726", + "last_release_tag": "1.2.0", + "last_updated": "2020-08-24T22:34:29Z", + "name": "check-button-card", + "new": false, + "repository_manifest": {}, + "selected_tag": null, + "show_beta": false, + "stars": 46, + "topics": [], + "version_installed": null + }, + "144902010": { + "authors": [], + "category": "plugin", + "description": "Adds ext weblink with icon to picture-elements or entity cards", + "domain": "", + "downloads": 0, + "full_name": "custom-cards/ext-weblink", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "f04ad8f", + "last_release_tag": "0.3.2", + "last_updated": "2020-12-04T12:08:54Z", + "name": "ext-weblink", + "new": false, + "repository_manifest": {}, + "selected_tag": null, + "show_beta": false, + "stars": 6, + "topics": [], + "version_installed": null + }, + "187245495": { + "authors": [], + "category": "plugin", + "description": null, + "domain": "", + "downloads": 0, + "full_name": "custom-cards/gauge-card", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "c4b8c9f", + "last_release_tag": "0.2.2", + "last_updated": "2020-12-04T11:57:43Z", + "name": "gauge-card", + "new": false, + "repository_manifest": {}, + "selected_tag": null, + "show_beta": false, + "stars": 9, + "topics": [], + "version_installed": null + }, + "143762825": { + "authors": [], + "category": "plugin", + "description": "Dual gauge custom card for Lovelace in Home Assistant", + "domain": "", + "downloads": 0, + "full_name": "custom-cards/dual-gauge-card", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "5b8c57d", + "last_release_tag": "0.5.2", + "last_updated": "2020-07-22T21:54:49Z", + "name": "dual-gauge-card", + "new": false, + "repository_manifest": { + "name": "Dual gauge card", + "filename": "dual-gauge-card.js", + "content_in_root": true + }, + "selected_tag": null, + "show_beta": false, + "stars": 63, + "topics": [], + "version_installed": null + }, + "188686483": { + "authors": [], + "category": "plugin", + "description": "\ud83e\uddf9 Declutter your lovelace configuration with the help of this card", + "domain": "", + "downloads": 3086, + "full_name": "custom-cards/decluttering-card", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "81b8fb7", + "last_release_tag": "0.6.3", + "last_updated": "2020-07-17T02:56:33Z", + "name": "decluttering-card", + "new": false, + "repository_manifest": { + "name": "Decluttering Card", + "render_readme": true, + "filename": "decluttering-card.js" + }, + "selected_tag": null, + "show_beta": false, + "stars": 85, + "topics": [ + "custom-cards", + "home-assistant", + "homeassistant", + "lovelace" + ], + "version_installed": null + }, + "151318225": { + "authors": [], + "category": "plugin", + "description": "Entity row for lights with sliders for adjusting different values based on features", + "domain": "", + "downloads": 0, + "full_name": "custom-cards/light-entity-row", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "d08721a", + "last_release_tag": "v0.2.2", + "last_updated": "2020-12-04T11:56:04Z", + "name": "light-entity-row", + "new": false, + "repository_manifest": {}, + "selected_tag": null, + "show_beta": false, + "stars": 21, + "topics": [], + "version_installed": null + }, + "179491130": { + "authors": [], + "category": "plugin", + "description": "A group element for picture-elements with dynamic toggle capability", + "domain": "", + "downloads": 523, + "full_name": "custom-cards/group-element", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "04ad4c4", + "last_release_tag": "0.6.0", + "last_updated": "2020-08-22T03:29:44Z", + "name": "group-element", + "new": false, + "repository_manifest": {}, + "selected_tag": null, + "show_beta": false, + "stars": 35, + "topics": [], + "version_installed": null + }, + "187245511": { + "authors": [], + "category": "plugin", + "description": null, + "domain": "", + "downloads": 0, + "full_name": "custom-cards/group-card", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "18d5aed", + "last_release_tag": "0.0.4", + "last_updated": "2020-12-04T12:15:48Z", + "name": "group-card", + "new": false, + "repository_manifest": {}, + "selected_tag": null, + "show_beta": false, + "stars": 12, + "topics": [], + "version_installed": null + }, + "196132939": { + "authors": [], + "category": "plugin", + "description": "Displays a card showing Nintendo Switch games that are on sale from your wish list.", + "domain": "", + "downloads": 0, + "full_name": "custom-cards/nintendo-wishlist-card", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "38bc197", + "last_release_tag": "v2.1.0", + "last_updated": "2020-07-31T01:18:03Z", + "name": "nintendo-wishlist-card", + "new": false, + "repository_manifest": {}, + "selected_tag": null, + "show_beta": false, + "stars": 1, + "topics": [ + "home-assistant", + "lovelace-ui" + ], + "version_installed": null + }, + "196250835": { + "authors": [], + "category": "plugin", + "description": "Show a notification count badge.", + "domain": "", + "downloads": 450, + "full_name": "custom-cards/favicon-counter", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "1611db6", + "last_release_tag": "1.0.0", + "last_updated": "2019-12-25T19:05:15Z", + "name": "favicon-counter", + "new": false, + "repository_manifest": { + "name": "Favicon counter", + "filename": "favicon-counter.js" + }, + "selected_tag": null, + "show_beta": false, + "stars": 9, + "topics": [], + "version_installed": null + }, + "156292058": { + "authors": [], + "category": "plugin", + "description": "Highly Flexible Lovelace Card - arbitrary contents/columns/rows, regex matched, perfect to show appdaemon created content and anything breaking out of the entity_id + attributes concept", + "domain": "", + "downloads": 0, + "full_name": "custom-cards/flex-table-card", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "dd0f174", + "last_release_tag": null, + "last_updated": "2020-07-17T20:48:02Z", + "name": "flex-table-card", + "new": false, + "repository_manifest": { + "name": "Flex Table - Highly customizable, Data visualization", + "content_in_root": true, + "filename": "flex-table-card.js" + }, + "selected_tag": null, + "show_beta": false, + "stars": 53, + "topics": [ + "data-table", + "data-visualization", + "flexible-table", + "high-configurability", + "home-automation", + "homeassistant", + "javascript", + "lovelace", + "single-file", + "table-visualization" + ], + "version_installed": null + }, + "165156754": { + "authors": [], + "category": "plugin", + "description": "\ud83d\udcb5 Personal Capital Card", + "domain": "", + "downloads": 0, + "full_name": "custom-cards/pc-card", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "033af45", + "last_release_tag": "v0.0.3", + "last_updated": "2019-10-21T03:36:31Z", + "name": "pc-card", + "new": false, + "repository_manifest": {}, + "selected_tag": null, + "show_beta": false, + "stars": 4, + "topics": [], + "version_installed": null + }, + "187247927": { + "authors": [], + "category": "plugin", + "description": null, + "domain": "", + "downloads": 0, + "full_name": "custom-cards/plan-coordinates", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "83fb42b", + "last_release_tag": "0.1.1", + "last_updated": "2020-12-04T11:54:24Z", + "name": "plan-coordinates", + "new": false, + "repository_manifest": {}, + "selected_tag": null, + "show_beta": false, + "stars": 20, + "topics": [], + "version_installed": null + }, + "159711605": { + "authors": [], + "category": "plugin", + "description": "Custom entity row for HomeAssistant, providing additional types of data to be displayed in the secondary info area of the Lovelace Entities card", + "domain": "", + "downloads": 0, + "full_name": "custom-cards/secondaryinfo-entity-row", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "a4d521b", + "last_release_tag": null, + "last_updated": "2020-12-04T11:51:35Z", + "name": "secondaryinfo-entity-row", + "new": false, + "repository_manifest": {}, + "selected_tag": null, + "show_beta": false, + "stars": 85, + "topics": [], + "version_installed": null + }, + "146335411": { + "authors": [], + "category": "plugin", + "description": "Custom card for the RMV component.", + "domain": "", + "downloads": 0, + "full_name": "custom-cards/rmv-card", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "fcfae8e", + "last_release_tag": null, + "last_updated": "2020-07-08T15:41:50Z", + "name": "rmv-card", + "new": false, + "repository_manifest": { + "name": "RMV Card", + "content_in_root": true, + "filename": "rmv-card.js" + }, + "selected_tag": null, + "show_beta": false, + "stars": 5, + "topics": [], + "version_installed": null + }, + "142038085": { + "authors": [], + "category": "plugin", + "description": "Give you the specified attribute of an entity", + "domain": "", + "downloads": 0, + "full_name": "custom-cards/state-attribute-element", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "6e4da57", + "last_release_tag": "0.4.0", + "last_updated": "2020-02-29T16:14:12Z", + "name": "state-attribute-element", + "new": false, + "repository_manifest": {}, + "selected_tag": null, + "show_beta": false, + "stars": 19, + "topics": [], + "version_installed": null + }, + "173955605": { + "authors": [], + "category": "plugin", + "description": "Spotify playlist card for Home Assistant card", + "domain": "", + "downloads": 0, + "full_name": "custom-cards/spotify-card", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "5249309", + "last_release_tag": "v2.3.4", + "last_updated": "2020-12-06T11:53:55Z", + "name": "spotify-card", + "new": false, + "repository_manifest": { + "name": "Spotify Lovelace Card", + "render_readme": true, + "filename": "spotify-card.js" + }, + "selected_tag": null, + "show_beta": false, + "stars": 137, + "topics": [], + "version_installed": null + }, + "142344403": { + "authors": [], + "category": "plugin", + "description": "Give you the option to prefix the state-label with a formated string.", + "domain": "", + "downloads": 0, + "full_name": "custom-cards/state-element", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "f5f596a", + "last_release_tag": "0.0.1", + "last_updated": "2020-08-11T08:32:17Z", + "name": "state-element", + "new": false, + "repository_manifest": {}, + "selected_tag": null, + "show_beta": false, + "stars": 9, + "topics": [], + "version_installed": null + }, + "185304888": { + "authors": [], + "category": "plugin", + "description": null, + "domain": "", + "downloads": 395, + "full_name": "custom-cards/text-action-element", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "24dffd9", + "last_release_tag": "0.1.0", + "last_updated": "2019-06-03T04:44:08Z", + "name": "text-action-element", + "new": false, + "repository_manifest": {}, + "selected_tag": null, + "show_beta": false, + "stars": 2, + "topics": [], + "version_installed": null + }, + "141897999": { + "authors": [], + "category": "plugin", + "description": "An element that can be used to show static text on the `picture-elements` card", + "domain": "", + "downloads": 0, + "full_name": "custom-cards/text-element", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "be4ea0f", + "last_release_tag": "0.0.2", + "last_updated": "2018-07-22T19:11:28Z", + "name": "text-element", + "new": false, + "repository_manifest": {}, + "selected_tag": null, + "show_beta": false, + "stars": 6, + "topics": [], + "version_installed": null + }, + "248954055": { + "authors": [], + "category": "plugin", + "description": "\ud83d\udee0 group multiple cards into one card without the borders", + "domain": "", + "downloads": 2453, + "full_name": "custom-cards/stack-in-card", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "6d8401d", + "last_release_tag": "0.2.0", + "last_updated": "2020-11-09T16:18:14Z", + "name": "stack-in-card", + "new": false, + "repository_manifest": { + "name": "Stack In Card", + "render_readme": true, + "filename": "stack-in-card.js" + }, + "selected_tag": null, + "show_beta": false, + "stars": 38, + "topics": [], + "version_installed": null + }, + "146783593": { + "authors": [], + "category": "plugin", + "description": "\ud83d\udcfa A card to display upcoming episodes and movies from services like: Plex, Kodi, Radarr, Sonarr, and Trakt.", + "domain": "", + "downloads": 0, + "full_name": "custom-cards/upcoming-media-card", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "9d91802", + "last_release_tag": "0.4.3", + "last_updated": "2020-11-15T00:51:09Z", + "name": "upcoming-media-card", + "new": false, + "repository_manifest": {}, + "selected_tag": null, + "show_beta": false, + "stars": 119, + "topics": [], + "version_installed": null + }, + "145143666": { + "authors": [], + "category": "plugin", + "description": "Show the current logged in user.", + "domain": "", + "downloads": 0, + "full_name": "custom-cards/username-element", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "f8ff808", + "last_release_tag": "0.0.1", + "last_updated": "2020-12-04T11:48:02Z", + "name": "username-element", + "new": false, + "repository_manifest": {}, + "selected_tag": null, + "show_beta": false, + "stars": 3, + "topics": [], + "version_installed": null + }, + "147764937": { + "authors": [], + "category": "plugin", + "description": "A custom component for displaying camera feeds in the style of a surveillance system.", + "domain": "", + "downloads": 0, + "full_name": "custom-cards/surveillance-card", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "ae917f7", + "last_release_tag": "0.0.4", + "last_updated": "2020-10-23T13:47:24Z", + "name": "surveillance-card", + "new": false, + "repository_manifest": { + "name": "surveillance-card", + "content_in_root": true, + "render_readme": true + }, + "selected_tag": null, + "show_beta": false, + "stars": 89, + "topics": [], + "version_installed": null + }, + "203294272": { + "authors": [], + "category": "plugin", + "description": "All your unused entities in a list", + "domain": "", + "downloads": 0, + "full_name": "custom-cards/unused-card", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "32a053e", + "last_release_tag": "1.1", + "last_updated": "2020-07-18T01:29:23Z", + "name": "unused-card", + "new": false, + "repository_manifest": {}, + "selected_tag": null, + "show_beta": false, + "stars": 17, + "topics": [], + "version_installed": null + }, + "192835334": { + "authors": [], + "category": "plugin", + "description": "Home Assistant Lovelace card to lock entire cards behind passwords or prompts.", + "domain": "", + "downloads": 0, + "full_name": "CyrisXD/love-lock-card", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "3afbb80", + "last_release_tag": null, + "last_updated": "2020-12-03T19:12:33Z", + "name": "love-lock-card", + "new": false, + "repository_manifest": { + "name": "Lovelace Lock Card", + "render_readme": true + }, + "selected_tag": null, + "show_beta": false, + "stars": 65, + "topics": [], + "version_installed": null + }, + "254206234": { + "authors": [], + "category": "plugin", + "description": "Home Assistant Lovelace custom card to use with Spain electricity hourly pricing (PVPC) integration", + "domain": "", + "downloads": 410, + "full_name": "danimart1991/pvpc-hourly-pricing-card", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "bc908ba", + "last_release_tag": "1.2.4", + "last_updated": "2020-10-08T13:39:09Z", + "name": "pvpc-hourly-pricing-card", + "new": false, + "repository_manifest": { + "name": "PVPC Hourly Pricing Card", + "render_readme": true + }, + "selected_tag": null, + "show_beta": false, + "stars": 19, + "topics": [ + "esios", + "graphics", + "hacs", + "home-assistant", + "home-assistant-custom", + "home-assistant-hacs", + "home-automation", + "lovelace", + "lovelace-card", + "lovelace-custom-card", + "lovelace-ui", + "pvpc", + "ree" + ], + "version_installed": null + }, + "195497310": { + "authors": [], + "category": "plugin", + "description": "Custom BOM Australia Animated Weather Card", + "domain": "", + "downloads": 0, + "full_name": "DavidFW1960/bom-weather-card", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "e2502f7", + "last_release_tag": "0.85c", + "last_updated": "2020-12-04T22:58:21Z", + "name": "bom-weather-card", + "new": false, + "repository_manifest": {}, + "selected_tag": null, + "show_beta": false, + "stars": 33, + "topics": [ + "bom", + "home-assistant", + "home-automation", + "homeassistant", + "weather-forecast" + ], + "version_installed": null + }, + "296396632": { + "authors": [], + "category": "plugin", + "description": "Lovelace card for listing departures from Rejseplanen sensors, in the style of S-Tog departure boards.", + "domain": "", + "downloads": 0, + "full_name": "DarkFox/rejseplanen-stog-card", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "568114a", + "last_release_tag": "1.5.0", + "last_updated": "2020-11-10T15:39:37Z", + "name": "rejseplanen-stog-card", + "new": false, + "repository_manifest": { + "name": "Rejseplanen S-Tog Card", + "country": "DK" + }, + "selected_tag": null, + "show_beta": false, + "stars": 1, + "topics": [ + "denmark", + "hacs", + "home-assistant", + "lovelace-card", + "lovelace-ui", + "rejseplanen", + "rejseplanen-sensors" + ], + "version_installed": null + }, + "193262086": { + "authors": [], + "category": "plugin", + "description": "Home assistant remote control", + "domain": "", + "downloads": 0, + "full_name": "dimagoltsman/content-card-remote-control", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "f4acaf4", + "last_release_tag": null, + "last_updated": "2020-09-27T22:35:55Z", + "name": "content-card-remote-control", + "new": false, + "repository_manifest": {}, + "selected_tag": null, + "show_beta": false, + "stars": 2, + "topics": [], + "version_installed": null + }, + "231145540": { + "authors": [], + "category": "plugin", + "description": "A card giving richer public transit display using NextBus sensors.", + "domain": "", + "downloads": 116, + "full_name": "dcramer/lovelace-nextbus-card", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "4f69bcb", + "last_release_tag": "0.2.0", + "last_updated": "2020-10-01T07:59:05Z", + "name": "lovelace-nextbus-card", + "new": false, + "repository_manifest": { + "name": "NextBus Card", + "render_readme": true, + "filename": "nextbus-card.js" + }, + "selected_tag": null, + "show_beta": false, + "stars": 5, + "topics": [ + "hacs", + "homeassistant", + "lovelace-custom-card", + "nextbus", + "public-transit" + ], + "version_installed": null + }, + "273007955": { + "authors": [], + "category": "plugin", + "description": "Lovelace pie chart card that displays current energy usage", + "domain": "", + "downloads": 0, + "full_name": "DBa2016/power-usage-card-regex", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "cf6d4cd", + "last_release_tag": null, + "last_updated": "2020-07-04T10:57:39Z", + "name": "power-usage-card-regex", + "new": false, + "repository_manifest": { + "name": "Power Usage Card with Regular Expressions", + "content_in_root": true, + "filename": "power-usage-card-regex.js", + "render_readme": true + }, + "selected_tag": null, + "show_beta": false, + "stars": 4, + "topics": [ + "homeassistant", + "lovelace-custom-card", + "power-usage" + ], + "version_installed": null + }, + "198066338": { + "authors": [], + "category": "plugin", + "description": "Lovelace card for listing departures from Rejseplanen sensors", + "domain": "", + "downloads": 0, + "full_name": "DarkFox/rejseplanen-card", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "5b6eed3", + "last_release_tag": "1.5.4", + "last_updated": "2020-11-09T15:48:22Z", + "name": "rejseplanen-card", + "new": false, + "repository_manifest": { + "name": "Rejseplanen Card", + "country": "DK" + }, + "selected_tag": null, + "show_beta": false, + "stars": 4, + "topics": [ + "denmark", + "hacs", + "home-assistant", + "lovelace-card", + "lovelace-ui", + "rejseplanen", + "rejseplanen-card", + "rejseplanen-sensors" + ], + "version_installed": null + }, + "197929015": { + "authors": [], + "category": "plugin", + "description": "Custom Lovelace card that displays ZHA network and device information", + "domain": "", + "downloads": 0, + "full_name": "dmulcahey/zha-network-card", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "b6b05fa", + "last_release_tag": null, + "last_updated": "2020-11-25T23:16:49Z", + "name": "zha-network-card", + "new": false, + "repository_manifest": {}, + "selected_tag": null, + "show_beta": false, + "stars": 34, + "topics": [], + "version_installed": null + }, + "269011342": { + "authors": [], + "category": "plugin", + "description": "Shutter card for Home Assistant Lovelace UI", + "domain": "", + "downloads": 3040, + "full_name": "Deejayfool/hass-shutter-card", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "d1d26f6", + "last_release_tag": "v1.2.0", + "last_updated": "2020-10-18T08:02:53Z", + "name": "hass-shutter-card", + "new": false, + "repository_manifest": { + "name": "Shutter Card", + "filename": "hass-shutter-card.js", + "render_readme": true + }, + "selected_tag": null, + "show_beta": false, + "stars": 60, + "topics": [ + "custom-cards", + "hacs", + "hassio", + "home-assistant", + "lovelace" + ], + "version_installed": null + }, + "274738925": { + "authors": [], + "category": "plugin", + "description": "Air Purifier card for Home Assistant Lovelace UI", + "domain": "", + "downloads": 708, + "full_name": "denysdovhan/purifier-card", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "e89ce5e", + "last_release_tag": "v0.5.0", + "last_updated": "2020-12-08T09:57:40Z", + "name": "purifier-card", + "new": false, + "repository_manifest": { + "name": "Purifier Card", + "render_readme": true, + "filename": "purifier-card.js" + }, + "selected_tag": null, + "show_beta": false, + "stars": 28, + "topics": [ + "air-purifier", + "custom-cards", + "hacs", + "hass", + "home-assistant", + "homeassistant", + "lovelace", + "purifier", + "smart-home" + ], + "version_installed": null + }, + "265313034": { + "authors": [], + "category": "plugin", + "description": "a picture that can be loaded from url or entity attribute and refreshed every N seconds", + "domain": "", + "downloads": 0, + "full_name": "dimagoltsman/refreshable-picture-card", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "7b1f6c9", + "last_release_tag": "0.1.5", + "last_updated": "2020-10-20T18:10:04Z", + "name": "refreshable-picture-card", + "new": false, + "repository_manifest": { + "name": "Refreshable picture card", + "description": "a picture that can be loaded from url or entity attribute and refreshed every N seconds", + "content_in_root": false, + "filename": "refreshable-picture-card.js", + "render_readme": true + }, + "selected_tag": null, + "show_beta": false, + "stars": 11, + "topics": [], + "version_installed": null + }, + "263901624": { + "authors": [], + "category": "plugin", + "description": "a remote control card that can be used with any HA service", + "domain": "", + "downloads": 0, + "full_name": "dimagoltsman/generic-remote-control-card", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "f0091a7", + "last_release_tag": "0.2.2", + "last_updated": "2020-09-20T11:09:09Z", + "name": "generic-remote-control-card", + "new": false, + "repository_manifest": { + "name": "Generic Remote Control Card", + "description": "a remote control card that can be used with any HA service", + "content_in_root": false, + "filename": "generic-remote-control-card.js", + "render_readme": true + }, + "selected_tag": null, + "show_beta": false, + "stars": 19, + "topics": [], + "version_installed": null + }, + "157674859": { + "authors": [], + "category": "plugin", + "description": "A Lovelace card showing air quality data from airvisual.com. Requires the AirVisual component.", + "domain": "", + "downloads": 0, + "full_name": "dnguyen800/air-visual-card", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "45d7a8b", + "last_release_tag": "0.0.13", + "last_updated": "2020-11-14T22:20:08Z", + "name": "air-visual-card", + "new": false, + "repository_manifest": { + "name": "Air Visual Card" + }, + "selected_tag": null, + "show_beta": false, + "stars": 34, + "topics": [ + "air-quality", + "air-visual", + "home-assistant", + "lovelace" + ], + "version_installed": null + }, + "261291295": { + "authors": [], + "category": "plugin", + "description": "Vacuum cleaner card for Home Assistant Lovelace UI", + "domain": "", + "downloads": 5693, + "full_name": "denysdovhan/vacuum-card", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "4abb373", + "last_release_tag": "v1.12.0", + "last_updated": "2020-12-08T10:24:10Z", + "name": "vacuum-card", + "new": false, + "repository_manifest": { + "name": "Vacuum Card", + "render_readme": true, + "filename": "vacuum-card.js" + }, + "selected_tag": null, + "show_beta": false, + "stars": 217, + "topics": [ + "custom-cards", + "hacs", + "hass", + "home-assistant", + "homeassistant", + "lovelace", + "robot-vacuum", + "vacuum" + ], + "version_installed": null + }, + "286408741": { + "authors": [], + "category": "plugin", + "description": "A Lovelace card to display Norwegian mail delivery days", + "domain": "", + "downloads": 353, + "full_name": "ezand/lovelace-posten-card", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "3a00f67", + "last_release_tag": "1.0.1", + "last_updated": "2020-08-20T11:22:51Z", + "name": "lovelace-posten-card", + "new": false, + "repository_manifest": { + "name": "Posten Card", + "render_readme": true, + "filename": "posten-card.js", + "country": "NO" + }, + "selected_tag": null, + "show_beta": false, + "stars": 4, + "topics": [ + "home-assistant", + "lovelace-card", + "mail-delivery" + ], + "version_installed": null + }, + "236664033": { + "authors": [], + "category": "plugin", + "description": ":point_up_2: Swipe Glance Card", + "domain": "", + "downloads": 641, + "full_name": "dooz127/swipe-glance-card", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "9b4f41a", + "last_release_tag": "0.3", + "last_updated": "2020-07-19T09:52:10Z", + "name": "swipe-glance-card", + "new": false, + "repository_manifest": { + "name": "Swipe Glance Card", + "render_readme": true, + "filename": "swipe-glance-card.js" + }, + "selected_tag": null, + "show_beta": false, + "stars": 4, + "topics": [ + "automation", + "custom-cards", + "hass", + "home-assistant", + "homeassistant", + "lovelace" + ], + "version_installed": null + }, + "260597137": { + "authors": [], + "category": "plugin", + "description": "\u7528\u4e8eLovelace\u7684\u5c0f\u7c73\u7a7a\u6c14\u51c0\u5316\u5668\u5361\u7247", + "domain": "", + "downloads": 0, + "full_name": "fineemb/lovelace-air-filter-card", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "a20f6b2", + "last_release_tag": "v1.1.3", + "last_updated": "2020-07-20T11:31:05Z", + "name": "lovelace-air-filter-card", + "new": false, + "repository_manifest": { + "name": "Air Purifier Card", + "render_readme": true + }, + "selected_tag": null, + "show_beta": false, + "stars": 9, + "topics": [], + "version_installed": null + }, + "247134044": { + "authors": [], + "category": "plugin", + "description": "A simple lovelace multiline text input card", + "domain": "", + "downloads": 228, + "full_name": "faeibson/lovelace-multiline-text-input-card", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "7b5040b", + "last_release_tag": "1.0.4", + "last_updated": "2020-10-15T00:16:17Z", + "name": "lovelace-multiline-text-input-card", + "new": false, + "repository_manifest": { + "name": "Multiline Text Input Card", + "render_readme": false + }, + "selected_tag": null, + "show_beta": false, + "stars": 3, + "topics": [ + "home-assistant", + "home-assistant-frontend", + "lovelace", + "lovelace-card", + "lovelace-ui", + "multiline", + "text-input" + ], + "version_installed": null + }, + "307058107": { + "authors": [], + "category": "plugin", + "description": "\u8f66\u8f86\u4eea\u8868\u76d8", + "domain": "", + "downloads": 0, + "full_name": "fineemb/lovelace-car-card", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "a28bca5", + "last_release_tag": null, + "last_updated": "2020-11-01T23:45:40Z", + "name": "lovelace-car-card", + "new": false, + "repository_manifest": { + "name": "Car card", + "render_readme": true, + "country": [ + "CN" + ] + }, + "selected_tag": null, + "show_beta": false, + "stars": 1, + "topics": [ + "car", + "hacs", + "hass", + "lovelace", + "lovelace-custom-card", + "lynkco" + ], + "version_installed": null + }, + "259784620": { + "authors": [], + "category": "plugin", + "description": "\u590d\u523b\u5b98\u65b9Lovelace\u5730\u56fe\u5361\u7247,\u57fa\u4e8e\u9ad8\u5fb7\u5730\u56fe", + "domain": "", + "downloads": 0, + "full_name": "fineemb/lovelace-cn-map-card", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "fce3dbf", + "last_release_tag": "v1.2.7", + "last_updated": "2020-10-26T06:35:55Z", + "name": "lovelace-cn-map-card", + "new": false, + "repository_manifest": { + "name": "Gaode Map card", + "render_readme": true, + "country": [ + "CN" + ] + }, + "selected_tag": null, + "show_beta": false, + "stars": 16, + "topics": [], + "version_installed": null + }, + "291480917": { + "authors": [], + "category": "plugin", + "description": "\u8fd9\u662f\u4e00\u4e2a\u9002\u7528\u4e8e\u5f69\u4e91\u5929\u6c14\u96c6\u6210\u7684Lovelace\u5361\u7247", + "domain": "", + "downloads": 0, + "full_name": "fineemb/lovelace-colorfulclouds-weather-card", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "5a327bb", + "last_release_tag": "v1.3.1", + "last_updated": "2020-11-14T13:50:37Z", + "name": "lovelace-colorfulclouds-weather-card", + "new": false, + "repository_manifest": { + "name": "Colorfulclouds Weather Card", + "render_readme": true, + "filename": "colorfulclouds-weather-card.js", + "country": [ + "CN" + ] + }, + "selected_tag": null, + "show_beta": false, + "stars": 11, + "topics": [ + "lovelace-custom-card", + "weather" + ], + "version_installed": null + }, + "240906060": { + "authors": [], + "category": "plugin", + "description": "\u6590\u8bafDC1\u6392\u63d2\u7684Lovelace\u5361\u7247", + "domain": "", + "downloads": 0, + "full_name": "fineemb/lovelace-dc1-card", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "4c2c109", + "last_release_tag": "v1.2.0", + "last_updated": "2020-08-26T08:19:40Z", + "name": "lovelace-dc1-card", + "new": false, + "repository_manifest": { + "name": "PHICOMM DC1 card", + "render_readme": true, + "country": [ + "CN" + ] + }, + "selected_tag": null, + "show_beta": false, + "stars": 16, + "topics": [], + "version_installed": null + }, + "214792276": { + "authors": [], + "category": "plugin", + "description": "Xiaomi Smartmi Fan Lovelace card for HASS/Home Assistant.", + "domain": "", + "downloads": 0, + "full_name": "fineemb/lovelace-fan-xiaomi", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "dd573b2", + "last_release_tag": "v1.3.3", + "last_updated": "2020-10-03T09:29:40Z", + "name": "lovelace-fan-xiaomi", + "new": false, + "repository_manifest": { + "name": "Xiaomi Fan Lovelace Card", + "render_readme": true + }, + "selected_tag": null, + "show_beta": false, + "stars": 23, + "topics": [], + "version_installed": null + }, + "237887092": { + "authors": [], + "category": "plugin", + "description": "Thermostat Lovelace card", + "domain": "", + "downloads": 0, + "full_name": "fineemb/lovelace-thermostat-card", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "f95cd32", + "last_release_tag": "v1.3.0", + "last_updated": "2020-11-23T15:27:28Z", + "name": "lovelace-thermostat-card", + "new": false, + "repository_manifest": { + "name": "Climate thermostat card", + "render_readme": true, + "filename": "main.js" + }, + "selected_tag": null, + "show_beta": false, + "stars": 35, + "topics": [], + "version_installed": null + }, + "191663150": { + "authors": [], + "category": "plugin", + "description": "Provides a means to show a compact graphical control row for 2 or 3 speed fans in Home Assistant", + "domain": "", + "downloads": 0, + "full_name": "finity69x2/fan-control-entity-row", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "5cd518d", + "last_release_tag": "2.0", + "last_updated": "2020-12-01T18:05:09Z", + "name": "fan-control-entity-row", + "new": false, + "repository_manifest": {}, + "selected_tag": null, + "show_beta": false, + "stars": 30, + "topics": [], + "version_installed": null + }, + "250552447": { + "authors": [], + "category": "plugin", + "description": "Provides a customizable button row for binary entities in Home Assistant", + "domain": "", + "downloads": 0, + "full_name": "finity69x2/binary-control-button-row", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "1fed835", + "last_release_tag": "1.5", + "last_updated": "2020-09-08T23:01:39Z", + "name": "binary-control-button-row", + "new": false, + "repository_manifest": { + "name": "Binary Control Button Row", + "filename": "binary-control-button-row.js" + }, + "selected_tag": null, + "show_beta": false, + "stars": 11, + "topics": [], + "version_installed": null + }, + "287409957": { + "authors": [], + "category": "plugin", + "description": "button row for controlling open/close covers in Home Assistant", + "domain": "", + "downloads": 0, + "full_name": "finity69x2/cover-control-button-row", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "63e1c52", + "last_release_tag": "1.1", + "last_updated": "2020-12-01T00:21:53Z", + "name": "cover-control-button-row", + "new": false, + "repository_manifest": { + "name": "Cover Control Button Row", + "filename": "cover-control-button-row.js" + }, + "selected_tag": null, + "show_beta": false, + "stars": 4, + "topics": [ + "cover", + "lovelace-ui", + "plugin" + ], + "version_installed": null + }, + "286860710": { + "authors": [], + "category": "plugin", + "description": "pluig-in for Home Assistant that provides an easy means set 3 fixed positions for a programmable cover entity.", + "domain": "", + "downloads": 0, + "full_name": "finity69x2/cover-position-preset-row", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "8559bcc", + "last_release_tag": null, + "last_updated": "2020-08-14T13:01:49Z", + "name": "cover-position-preset-row", + "new": false, + "repository_manifest": { + "name": "Cover Position Preset Row", + "filename": "cover-position-preset-row.js" + }, + "selected_tag": null, + "show_beta": false, + "stars": 5, + "topics": [ + "cover", + "lovelace-custom-card", + "plugin" + ], + "version_installed": null + }, + "226862969": { + "authors": [], + "category": "plugin", + "description": "Provides a means to program 3 preset brightness settings for dimmable lights in Home Assistant", + "domain": "", + "downloads": 0, + "full_name": "finity69x2/light-brightness-preset-row", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "7557854", + "last_release_tag": "1.8", + "last_updated": "2020-08-14T12:54:14Z", + "name": "light-brightness-preset-row", + "new": false, + "repository_manifest": { + "name": "Light Brightness Preset Row", + "filename": "light-brightness-preset-row.js" + }, + "selected_tag": null, + "show_beta": false, + "stars": 14, + "topics": [], + "version_installed": null + }, + "245239101": { + "authors": [], + "category": "plugin", + "description": "Allows preloading of Lovelace cards as a work around for changes in Home Assistant 0.107", + "domain": "", + "downloads": 0, + "full_name": "gadgetchnnel/lovelace-card-preloader", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "09d7b18", + "last_release_tag": "0.0.5", + "last_updated": "2020-09-12T12:52:34Z", + "name": "lovelace-card-preloader", + "new": false, + "repository_manifest": { + "name": "Lovelace Card Preloader", + "render_readme": true + }, + "selected_tag": null, + "show_beta": false, + "stars": 14, + "topics": [], + "version_installed": null + }, + "184333163": { + "authors": [], + "category": "plugin", + "description": "Custom Lovelace card which allows Jinja2 templates to be applied to other cards", + "domain": "", + "downloads": 0, + "full_name": "gadgetchnnel/lovelace-card-templater", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "c4554ec", + "last_release_tag": "0.0.16", + "last_updated": "2020-11-27T15:50:08Z", + "name": "lovelace-card-templater", + "new": false, + "repository_manifest": { + "name": "Lovelace Card Templater", + "render_readme": true + }, + "selected_tag": null, + "show_beta": false, + "stars": 36, + "topics": [], + "version_installed": null + }, + "182113743": { + "authors": [], + "category": "plugin", + "description": "A custom Lovelace text input row for use in entities cards", + "domain": "", + "downloads": 0, + "full_name": "gadgetchnnel/lovelace-text-input-row", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "2b1e1ff", + "last_release_tag": "v0.0.8", + "last_updated": "2020-08-18T05:04:31Z", + "name": "lovelace-text-input-row", + "new": false, + "repository_manifest": {}, + "selected_tag": null, + "show_beta": false, + "stars": 12, + "topics": [], + "version_installed": null + }, + "174016256": { + "authors": [], + "category": "plugin", + "description": "A custom Lovelace card for displaying a combination of persistent notifications, calendar events, and entities in the style of a feed.", + "domain": "", + "downloads": 0, + "full_name": "gadgetchnnel/lovelace-home-feed-card", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "5e38139", + "last_release_tag": "0.5.0", + "last_updated": "2020-09-30T15:02:08Z", + "name": "lovelace-home-feed-card", + "new": false, + "repository_manifest": { + "name": "Lovelace Home Feed Card", + "render_readme": true + }, + "selected_tag": null, + "show_beta": false, + "stars": 91, + "topics": [], + "version_installed": null + }, + "250865851": { + "authors": [], + "category": "plugin", + "description": "\ud83d\udcc2 Folder Card for Home Assistant's Lovelace UI", + "domain": "", + "downloads": 404, + "full_name": "GeorgeSG/lovelace-folder-card", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "cc7ffaa", + "last_release_tag": "0.4.0", + "last_updated": "2020-07-20T06:40:15Z", + "name": "lovelace-folder-card", + "new": false, + "repository_manifest": { + "name": "Folder Card", + "render_readme": true, + "filename": "folder-card.js" + }, + "selected_tag": null, + "show_beta": false, + "stars": 3, + "topics": [ + "home-assistant", + "homeassistant", + "lovelace", + "lovelace-card", + "lovelace-custom-card" + ], + "version_installed": null + }, + "160042309": { + "authors": [], + "category": "plugin", + "description": "An intuitive way to represent the power and energy that your home is consuming or producing. (A custom card for the Lovelace UI of Home Assistant.)", + "domain": "", + "downloads": 0, + "full_name": "gurbyz/power-wheel-card", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "d136d6d", + "last_release_tag": "v0.1.5", + "last_updated": "2020-11-05T15:05:31Z", + "name": "power-wheel-card", + "new": false, + "repository_manifest": { + "name": "Power wheel card" + }, + "selected_tag": null, + "show_beta": false, + "stars": 79, + "topics": [ + "custom-card", + "energy", + "home-assistant", + "lovelace-ui", + "solar-panels" + ], + "version_installed": null + }, + "220679143": { + "authors": [], + "category": "plugin", + "description": "Lovelace Departure Card for the HASL Platform", + "domain": "", + "downloads": 0, + "full_name": "hasl-sensor/lovelace-hasl-departure-card", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "fe5d2bc", + "last_release_tag": "v2.5.0", + "last_updated": "2020-03-04T12:49:27Z", + "name": "lovelace-hasl-departure-card", + "new": false, + "repository_manifest": { + "name": "HASL Departure Card", + "content_in_root": false, + "render_readme": false, + "filename": "hasl-departure-card.js", + "homeassistant": "0.92", + "zip_release": false + }, + "selected_tag": null, + "show_beta": false, + "stars": 6, + "topics": [ + "departures", + "hasl", + "sl", + "stockholms-lokaltrafik" + ], + "version_installed": null + }, + "304967918": { + "authors": [], + "category": "plugin", + "description": "Replace input_number sliders with plus and minus buttons", + "domain": "", + "downloads": 0, + "full_name": "htmltiger/numberbox-card", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "3b23009", + "last_release_tag": null, + "last_updated": "2020-12-07T12:37:41Z", + "name": "numberbox-card", + "new": false, + "repository_manifest": { + "name": "Number Box", + "filename": "numberbox-card.js", + "render_readme": true + }, + "selected_tag": null, + "show_beta": false, + "stars": 4, + "topics": [ + "hacs", + "input", + "lovelace", + "number", + "numberbox-card", + "slider" + ], + "version_installed": null + }, + "220679530": { + "authors": [], + "category": "plugin", + "description": "Lovelace Traffic Status Card for the HASL Platform", + "domain": "", + "downloads": 0, + "full_name": "hasl-sensor/lovelace-hasl-traffic-status-card", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "fdf9915", + "last_release_tag": "v2.3.1", + "last_updated": "2020-03-04T12:20:16Z", + "name": "lovelace-hasl-traffic-status-card", + "new": false, + "repository_manifest": { + "name": "HASL Traffic Status Card", + "content_in_root": false, + "render_readme": false, + "filename": "hasl-traffic-status-card.js", + "homeassistant": "0.92", + "zip_release": false + }, + "selected_tag": null, + "show_beta": false, + "stars": 4, + "topics": [ + "hasl", + "sl", + "stockholms-lokaltrafik", + "traffic-status" + ], + "version_installed": null + }, + "145142810": { + "authors": [], + "category": "plugin", + "description": "\ud83d\udce6 Aftership Card for package tracking", + "domain": "", + "downloads": 931, + "full_name": "iantrich/aftership-card", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "3d0f815", + "last_release_tag": "1.2.1", + "last_updated": "2020-08-06T19:54:54Z", + "name": "aftership-card", + "new": false, + "repository_manifest": { + "name": "Aftership Card", + "render_readme": true, + "filename": "aftership-card.js" + }, + "selected_tag": null, + "show_beta": false, + "stars": 28, + "topics": [], + "version_installed": null + }, + "172177543": { + "authors": [], + "category": "plugin", + "description": "\ud83d\udcdd Templatable Lovelace Configurations", + "domain": "", + "downloads": 1957, + "full_name": "iantrich/config-template-card", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "604227d", + "last_release_tag": "1.3.0", + "last_updated": "2020-11-08T14:11:58Z", + "name": "config-template-card", + "new": false, + "repository_manifest": { + "name": "Config Template Card", + "render_readme": true, + "homeassistant": "0.110.0" + }, + "selected_tag": null, + "show_beta": false, + "stars": 116, + "topics": [], + "version_installed": null + }, + "175927964": { + "authors": [], + "category": "plugin", + "description": "\ud83c\udfa7 Podcast Player Card", + "domain": "", + "downloads": 415, + "full_name": "iantrich/podcast-card", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "1dd0645", + "last_release_tag": "1.0.9", + "last_updated": "2020-07-18T17:55:56Z", + "name": "podcast-card", + "new": false, + "repository_manifest": { + "name": "Podcast Card", + "render_readme": true + }, + "selected_tag": null, + "show_beta": false, + "stars": 18, + "topics": [], + "version_installed": null + }, + "215633404": { + "authors": [], + "category": "plugin", + "description": "\ud83d\udd12 Apply restrictions to Lovelace cards", + "domain": "", + "downloads": 1717, + "full_name": "iantrich/restriction-card", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "1f56db9", + "last_release_tag": "1.2.6", + "last_updated": "2020-10-20T21:59:15Z", + "name": "restriction-card", + "new": false, + "repository_manifest": { + "name": "Restriction Card", + "render_readme": true, + "homeassistant": "0.110.0" + }, + "selected_tag": null, + "show_beta": false, + "stars": 58, + "topics": [ + "custom-card", + "home-assistant", + "lovelace", + "security" + ], + "version_installed": null + }, + "164367214": { + "authors": [], + "category": "plugin", + "description": "\ud83d\udcfa Roku Remote Card", + "domain": "", + "downloads": 783, + "full_name": "iantrich/roku-card", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "ae15fdf", + "last_release_tag": "1.2.2", + "last_updated": "2020-11-26T05:59:46Z", + "name": "roku-card", + "new": false, + "repository_manifest": { + "name": "Roku Card", + "render_readme": true, + "homeassistant": "0.110.0" + }, + "selected_tag": null, + "show_beta": false, + "stars": 58, + "topics": [ + "custom-card", + "home-assistant", + "lovelace", + "roku" + ], + "version_installed": null + }, + "181124811": { + "authors": [], + "category": "plugin", + "description": "\u2b55 Radial Menu Element", + "domain": "", + "downloads": 550, + "full_name": "iantrich/radial-menu", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "688e6c9", + "last_release_tag": "1.6.0", + "last_updated": "2020-06-30T03:42:08Z", + "name": "radial-menu", + "new": false, + "repository_manifest": { + "name": "Radial Menu Element", + "render_readme": true, + "homeassistant": "0.110.0" + }, + "selected_tag": null, + "show_beta": false, + "stars": 41, + "topics": [], + "version_installed": null + }, + "179788256": { + "authors": [], + "category": "plugin", + "description": "\ud83d\uddc2 Text Divider Row", + "domain": "", + "downloads": 2474, + "full_name": "iantrich/text-divider-row", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "beb7a25", + "last_release_tag": "1.4.0", + "last_updated": "2020-10-20T05:13:08Z", + "name": "text-divider-row", + "new": false, + "repository_manifest": { + "name": "Text Divider Row", + "render_readme": true + }, + "selected_tag": null, + "show_beta": false, + "stars": 30, + "topics": [ + "homeassistant", + "lovelace" + ], + "version_installed": null + }, + "231015759": { + "authors": [], + "category": "plugin", + "description": "Xiaomi Smartmi Fan Lovelace card with CSS fan animation", + "domain": "", + "downloads": 0, + "full_name": "ikaruswill/lovelace-fan-xiaomi", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "de659b3", + "last_release_tag": "1.5.2", + "last_updated": "2020-10-28T22:33:09Z", + "name": "lovelace-fan-xiaomi", + "new": false, + "repository_manifest": { + "name": "Xiaomi Smartmi Fan Card", + "render_readme": true + }, + "selected_tag": null, + "show_beta": false, + "stars": 22, + "topics": [], + "version_installed": null + }, + "194824532": { + "authors": [], + "category": "plugin", + "description": "Weather Card with animated icons for Home Assistant Lovelace adapted to display all informations from M\u00e9t\u00e9o France integration", + "domain": "", + "downloads": 0, + "full_name": "Imbuzi/meteo-france-weather-card", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "b22c7f0", + "last_release_tag": "v1.12", + "last_updated": "2020-10-17T21:38:11Z", + "name": "meteo-france-weather-card", + "new": false, + "repository_manifest": { + "name": "M\u00e9t\u00e9o France Weather Card", + "country": "FR", + "render_readme": true + }, + "selected_tag": null, + "show_beta": false, + "stars": 15, + "topics": [ + "animated-icons", + "home-assistant", + "lovelace-card", + "meteo-france", + "weather" + ], + "version_installed": null + }, + "164887047": { + "authors": [], + "category": "plugin", + "description": "A Lovelace custom card for custom component Krisinformation is Home Assistant", + "domain": "", + "downloads": 0, + "full_name": "isabellaalstrom/krisinfo-card", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "962f549", + "last_release_tag": "v1.3.0", + "last_updated": "2020-09-18T17:45:35Z", + "name": "krisinfo-card", + "new": false, + "repository_manifest": {}, + "selected_tag": null, + "show_beta": false, + "stars": 6, + "topics": [ + "hacktoberfest" + ], + "version_installed": null + }, + "195671060": { + "authors": [], + "category": "plugin", + "description": "A card to track chores in Grocy. This card requires card tools: https://github.com/thomasloven/lovelace-card-tools.", + "domain": "", + "downloads": 0, + "full_name": "isabellaalstrom/lovelace-grocy-chores-card", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "d898685", + "last_release_tag": "v2.0.0", + "last_updated": "2020-10-01T08:38:05Z", + "name": "lovelace-grocy-chores-card", + "new": false, + "repository_manifest": {}, + "selected_tag": null, + "show_beta": false, + "stars": 31, + "topics": [ + "hacktoberfest" + ], + "version_installed": null + }, + "273405252": { + "authors": [], + "category": "plugin", + "description": "A Lightning Detection Display Card for Home Assistant Lovelace", + "domain": "", + "downloads": 680, + "full_name": "ironsheep/lovelace-lightning-detector-card", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "1579a15", + "last_release_tag": "v1.0.3", + "last_updated": "2020-10-22T04:25:54Z", + "name": "lovelace-lightning-detector-card", + "new": false, + "repository_manifest": { + "name": "Lightning Detector Card", + "render_readme": false, + "filename": "lightning-detector-card.js" + }, + "selected_tag": null, + "show_beta": false, + "stars": 5, + "topics": [ + "as3935", + "hassio", + "home-assistant", + "home-assistant-custom", + "homeassistant", + "lovelace-card", + "lovelace-custom-card" + ], + "version_installed": null + }, + "281214271": { + "authors": [], + "category": "plugin", + "description": "A Raspberry Pi status display Card for Home Assistant Lovelace", + "domain": "", + "downloads": 2952, + "full_name": "ironsheep/lovelace-rpi-monitor-card", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "485fa57", + "last_release_tag": "v1.2.5", + "last_updated": "2020-11-25T00:01:19Z", + "name": "lovelace-rpi-monitor-card", + "new": false, + "repository_manifest": { + "name": "RPi Monitor Card", + "render_readme": false, + "filename": "rpi-monitor-card.js" + }, + "selected_tag": null, + "show_beta": false, + "stars": 28, + "topics": [ + "hassio", + "home-assistant", + "home-assistant-custom", + "homeassistant", + "lovelace-card", + "lovelace-custom-card", + "raspberry-pi" + ], + "version_installed": null + }, + "237812136": { + "authors": [], + "category": "plugin", + "description": "Yandex devices icons for Home Assistant", + "domain": "", + "downloads": 0, + "full_name": "iswitch/ha-yandex-icons", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "406a309", + "last_release_tag": null, + "last_updated": "2020-06-20T22:03:59Z", + "name": "ha-yandex-icons", + "new": false, + "repository_manifest": { + "name": "Yandex Icons", + "filename": "yandex-icons.js", + "render_readme": true, + "country": [ + "RU" + ], + "homeassistant": "0.110.0" + }, + "selected_tag": null, + "show_beta": false, + "stars": 16, + "topics": [ + "home-assistant", + "homeassistant", + "icon-pack", + "icons", + "media-player", + "yandex" + ], + "version_installed": null + }, + "244872232": { + "authors": [], + "category": "plugin", + "description": "Adds highly configurable buttons that use actions and per-state styling.", + "domain": "", + "downloads": 2639, + "full_name": "jcwillox/lovelace-paper-buttons-row", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "4711ad0", + "last_release_tag": "0.3.0", + "last_updated": "2020-07-31T14:36:33Z", + "name": "lovelace-paper-buttons-row", + "new": false, + "repository_manifest": { + "name": "Paper Buttons Row" + }, + "selected_tag": null, + "show_beta": false, + "stars": 41, + "topics": [ + "actions", + "buttons", + "haptic", + "home-assistant", + "paper" + ], + "version_installed": null + }, + "245159052": { + "authors": [], + "category": "plugin", + "description": "\ud83d\udc24 Adds many useful extensions to lovelace, such as templating secondary info, stacking within a card and more!", + "domain": "", + "downloads": 1374, + "full_name": "jcwillox/lovelace-canary", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "efd4615", + "last_release_tag": "0.3.1", + "last_updated": "2020-07-06T05:28:35Z", + "name": "lovelace-canary", + "new": false, + "repository_manifest": { + "name": "Canary" + }, + "selected_tag": null, + "show_beta": false, + "stars": 21, + "topics": [ + "canary-card", + "extensions", + "home-assistant", + "lovelace" + ], + "version_installed": null + }, + "238802974": { + "authors": [], + "category": "plugin", + "description": "HA Lovelace Card for iRobot Roomba Vacuum Cleaner leveraging the rest980 Docker Image", + "domain": "", + "downloads": 0, + "full_name": "jeremywillans/lovelace-roomba-vacuum-card", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "6005f43", + "last_release_tag": "0.9", + "last_updated": "2020-10-10T09:04:50Z", + "name": "lovelace-roomba-vacuum-card", + "new": false, + "repository_manifest": { + "name": "Roomba Vacuum Card", + "render_readme": true + }, + "selected_tag": null, + "show_beta": false, + "stars": 14, + "topics": [ + "home-assistant", + "home-assistant-frontend", + "irobot", + "irobot-roomba", + "lovelace", + "lovelace-custom-card", + "lovelace-ui", + "vacuum" + ], + "version_installed": null + }, + "151280062": { + "authors": [], + "category": "plugin", + "description": "Minimalistic graph card for Home Assistant Lovelace UI", + "domain": "", + "downloads": 37557, + "full_name": "kalkih/mini-graph-card", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "33f3a19", + "last_release_tag": "v0.9.4", + "last_updated": "2020-12-08T15:11:26Z", + "name": "mini-graph-card", + "new": false, + "repository_manifest": {}, + "selected_tag": null, + "show_beta": false, + "stars": 1179, + "topics": [ + "automation", + "custom", + "graph", + "hassio", + "home-assistant", + "lovelace", + "lovelace-ui", + "sensor" + ], + "version_installed": null + }, + "148520838": { + "authors": [], + "category": "plugin", + "description": "Minimalistic media card for Home Assistant Lovelace UI", + "domain": "", + "downloads": 23598, + "full_name": "kalkih/mini-media-player", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "2cb0845", + "last_release_tag": "v1.11.0", + "last_updated": "2020-11-21T16:21:19Z", + "name": "mini-media-player", + "new": false, + "repository_manifest": {}, + "selected_tag": null, + "show_beta": false, + "stars": 760, + "topics": [ + "automation", + "custom", + "hassio", + "home-assistant", + "lovelace", + "lovelace-ui", + "media-player", + "sonos" + ], + "version_installed": null + }, + "163446489": { + "authors": [], + "category": "plugin", + "description": "This card is made to work with the Entur public transport component.", + "domain": "", + "downloads": 0, + "full_name": "jonkristian/entur-card", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "57aa6ad", + "last_release_tag": null, + "last_updated": "2020-08-06T10:54:21Z", + "name": "entur-card", + "new": false, + "repository_manifest": { + "name": "Entur Card", + "render_readme": true, + "filename": "entur-card.js" + }, + "selected_tag": null, + "show_beta": false, + "stars": 32, + "topics": [], + "version_installed": null + }, + "172998062": { + "authors": [], + "category": "plugin", + "description": "Minimalistic weather card for Home Assistant", + "domain": "", + "downloads": 3343, + "full_name": "kalkih/simple-weather-card", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "b51750a", + "last_release_tag": "v0.8.1", + "last_updated": "2020-10-07T19:19:29Z", + "name": "simple-weather-card", + "new": false, + "repository_manifest": {}, + "selected_tag": null, + "show_beta": false, + "stars": 122, + "topics": [ + "home-assistant", + "home-automation", + "homeassistant", + "lovelace", + "weather" + ], + "version_installed": null + }, + "283578257": { + "authors": [], + "category": "plugin", + "description": "A Lovelace Card for visualizing power distributions.", + "domain": "", + "downloads": 588, + "full_name": "JonahKr/power-distribution-card", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "d8e04cf", + "last_release_tag": "v1.7", + "last_updated": "2020-11-30T00:08:51Z", + "name": "power-distribution-card", + "new": false, + "repository_manifest": { + "name": "power-distribution-card", + "render_readme": true, + "content_in_root": false, + "filename": "power-distribution-card.js" + }, + "selected_tag": null, + "show_beta": false, + "stars": 17, + "topics": [ + "e3dc", + "hacs", + "homeassistant", + "lovelace-card" + ], + "version_installed": null + }, + "313269367": { + "authors": [], + "category": "plugin", + "description": "Replace the history graph colors with a material design color palette.", + "domain": "", + "downloads": 0, + "full_name": "Kibibit/kb-better-graph-colors", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "410067e", + "last_release_tag": "v1.1.0", + "last_updated": "2020-11-25T22:04:52Z", + "name": "kb-better-graph-colors", + "new": false, + "repository_manifest": { + "name": "kibibit Better Graph Colors", + "render_readme": true, + "filename": "kb-better-graph-colors.js" + }, + "selected_tag": null, + "show_beta": false, + "stars": 4, + "topics": [ + "color-scheme", + "graphs", + "hacs", + "home-assistant", + "palette" + ], + "version_installed": null + }, + "168570875": { + "authors": [], + "category": "plugin", + "description": "fitbit-card for lovelace", + "domain": "", + "downloads": 1128, + "full_name": "ljmerza/fitbit-card", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "9ead350", + "last_release_tag": "1.1.1", + "last_updated": "2020-07-10T20:55:32Z", + "name": "fitbit-card", + "new": false, + "repository_manifest": {}, + "selected_tag": null, + "show_beta": false, + "stars": 19, + "topics": [], + "version_installed": null + }, + "313270182": { + "authors": [], + "category": "plugin", + "description": "Make Cards and Popups blur everything behind them.", + "domain": "", + "downloads": 0, + "full_name": "Kibibit/kb-frosted-cards", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "5e1586e", + "last_release_tag": "v1.3.1", + "last_updated": "2020-11-22T22:02:11Z", + "name": "kb-frosted-cards", + "new": false, + "repository_manifest": { + "name": "kibibit Frosted Cards", + "render_readme": true, + "filename": "kb-frosted-cards.js" + }, + "selected_tag": null, + "show_beta": false, + "stars": 2, + "topics": [ + "effect", + "frosted-glass", + "hacs", + "home-assistant" + ], + "version_installed": null + }, + "169783299": { + "authors": [], + "category": "plugin", + "description": "Track your repo issues, starts, forks, and pull requests", + "domain": "", + "downloads": 488, + "full_name": "ljmerza/github-card", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "7e4c628", + "last_release_tag": "1.4.1", + "last_updated": "2020-01-13T23:55:00Z", + "name": "github-card", + "new": false, + "repository_manifest": {}, + "selected_tag": null, + "show_beta": false, + "stars": 8, + "topics": [], + "version_installed": null + }, + "292008305": { + "authors": [], + "category": "plugin", + "description": "A Home Assistant card for Steam integrations", + "domain": "", + "downloads": 0, + "full_name": "Kibibit/kb-steam-card", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "3f5b816", + "last_release_tag": "v1.0.3", + "last_updated": "2020-11-16T18:02:58Z", + "name": "kb-steam-card", + "new": false, + "repository_manifest": { + "name": "Steam Card", + "render_readme": true, + "filename": "kb-steam-card.js" + }, + "selected_tag": null, + "show_beta": false, + "stars": 11, + "topics": [ + "card", + "hacs", + "home-assistant", + "steam" + ], + "version_installed": null + }, + "276636213": { + "authors": [], + "category": "plugin", + "description": "Cover card with homekit style vertical position slider (best with panel-mode but normal-mode works also)", + "domain": "", + "downloads": 1011, + "full_name": "konnectedvn/lovelace-vertical-slider-cover-card", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "1b119b8", + "last_release_tag": "v0.1.2", + "last_updated": "2020-10-31T02:55:57Z", + "name": "lovelace-vertical-slider-cover-card", + "new": false, + "repository_manifest": { + "name": "Vertical Slider Cover Card", + "render_readme": true, + "filename": "vertical-slider-cover-card.js" + }, + "selected_tag": null, + "show_beta": false, + "stars": 15, + "topics": [ + "card", + "home-assistant", + "lovelace" + ], + "version_installed": null + }, + "197960232": { + "authors": [], + "category": "plugin", + "description": "our groceries lovelace card", + "domain": "", + "downloads": 521, + "full_name": "ljmerza/our-groceries-card", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "dcacb8c", + "last_release_tag": "1.4.0", + "last_updated": "2020-05-28T13:12:41Z", + "name": "our-groceries-card", + "new": false, + "repository_manifest": {}, + "selected_tag": null, + "show_beta": false, + "stars": 18, + "topics": [], + "version_installed": null + }, + "290281267": { + "authors": [], + "category": "plugin", + "description": "Make your Home Assistant browser fullscreen with one tap.", + "domain": "", + "downloads": 690, + "full_name": "KTibow/fullscreen-card", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "2063b63", + "last_release_tag": "v0.6", + "last_updated": "2020-11-10T01:47:50Z", + "name": "fullscreen-card", + "new": false, + "repository_manifest": { + "name": "Fullscreen Card", + "render_readme": "true" + }, + "selected_tag": null, + "show_beta": false, + "stars": 10, + "topics": [ + "card", + "custom-card", + "fullscreen", + "hacktoberfest", + "hacktoberfest2020", + "hacs", + "home-assistant", + "homeassistant" + ], + "version_installed": null + }, + "175727366": { + "authors": [], + "category": "plugin", + "description": "Reddit Card for Home Assistant", + "domain": "", + "downloads": 404, + "full_name": "ljmerza/reddit-card", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "11b0e51", + "last_release_tag": "1.0.0", + "last_updated": "2020-01-13T23:59:31Z", + "name": "reddit-card", + "new": false, + "repository_manifest": {}, + "selected_tag": null, + "show_beta": false, + "stars": 7, + "topics": [], + "version_installed": null + }, + "168744428": { + "authors": [], + "category": "plugin", + "description": "Control any light or switch entity", + "domain": "", + "downloads": 5977, + "full_name": "ljmerza/light-entity-card", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "004a1c3", + "last_release_tag": "4.3.0", + "last_updated": "2020-11-03T20:29:11Z", + "name": "light-entity-card", + "new": false, + "repository_manifest": { + "name": "Light Entity Card", + "render_readme": true, + "filename": "dist/light-entity-card.js" + }, + "selected_tag": null, + "show_beta": false, + "stars": 93, + "topics": [], + "version_installed": null + }, + "236945951": { + "authors": [], + "category": "plugin", + "description": "Graph of Buienradars rain forecast ", + "domain": "", + "downloads": 1285, + "full_name": "lukevink/lovelace-buien-rain-card", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "b806ecb", + "last_release_tag": "0.0.4", + "last_updated": "2020-05-01T14:26:54Z", + "name": "lovelace-buien-rain-card", + "new": false, + "repository_manifest": {}, + "selected_tag": null, + "show_beta": false, + "stars": 22, + "topics": [ + "buienradar", + "chartjs", + "custom-card", + "forecast", + "graph", + "home-assistant" + ], + "version_installed": null + }, + "180464361": { + "authors": [], + "category": "plugin", + "description": "show travel times for you travel time sensors", + "domain": "", + "downloads": 1489, + "full_name": "ljmerza/travel-time-card", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "ef15306", + "last_release_tag": "1.1.3", + "last_updated": "2020-09-11T01:50:59Z", + "name": "travel-time-card", + "new": false, + "repository_manifest": { + "name": "Travel Time Card", + "render_readme": true, + "filename": "dist/travel-time-card.js" + }, + "selected_tag": null, + "show_beta": false, + "stars": 12, + "topics": [], + "version_installed": null + }, + "183499944": { + "authors": [], + "category": "plugin", + "description": "Show Tracking Numbers from the Email Sensor for Home Assistant", + "domain": "", + "downloads": 451, + "full_name": "ljmerza/tracking-number-card", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "b299cb5", + "last_release_tag": "1.2.6", + "last_updated": "2020-02-14T03:04:43Z", + "name": "tracking-number-card", + "new": false, + "repository_manifest": { + "name": "Tracking Number Card", + "render_readme": true, + "filename": "dist/tracking-number-card.js" + }, + "selected_tag": null, + "show_beta": false, + "stars": 9, + "topics": [], + "version_installed": null + }, + "257123327": { + "authors": [], + "category": "plugin", + "description": "channel pad for LG TV Remote control", + "domain": "", + "downloads": 0, + "full_name": "madmicio/channel-pad", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "abdbcc9", + "last_release_tag": null, + "last_updated": "2020-05-28T19:17:53Z", + "name": "channel-pad", + "new": false, + "repository_manifest": { + "name": "LG WebOS channel pad", + "content_in_root": false, + "filename": "card-channel-pad.js", + "render_readme": true + }, + "selected_tag": null, + "show_beta": false, + "stars": 3, + "topics": [ + "channel-pad", + "lg", + "tv-remote" + ], + "version_installed": null + }, + "257005990": { + "authors": [], + "category": "plugin", + "description": "Remote Control for LG TV WebOS", + "domain": "", + "downloads": 0, + "full_name": "madmicio/LG-WebOS-Remote-Control", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "e3c6f49", + "last_release_tag": null, + "last_updated": "2020-05-15T19:45:42Z", + "name": "LG-WebOS-Remote-Control", + "new": false, + "repository_manifest": { + "name": "LG WebOS Remote Control", + "content_in_root": true, + "filename": "lg-remote-control.js", + "render_readme": true + }, + "selected_tag": null, + "show_beta": false, + "stars": 79, + "topics": [], + "version_installed": null + }, + "308883876": { + "authors": [], + "category": "plugin", + "description": "Custom card for bluetooth bulb (BLE light) control for Homeassistant", + "domain": "", + "downloads": 0, + "full_name": "marcomow/ble-bulb-card", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "b7355f3", + "last_release_tag": "v0.0.32", + "last_updated": "2020-12-01T15:44:30Z", + "name": "ble-bulb-card", + "new": false, + "repository_manifest": { + "name": "BLE bulb card", + "content_in_root": true, + "filename": "ble-bulb-card.js", + "render_readme": true + }, + "selected_tag": null, + "show_beta": false, + "stars": 2, + "topics": [ + "ble", + "ble-bulb", + "ble-bulb-card", + "custom-card", + "hacs", + "hass", + "home-assistant", + "home-assistant-card", + "home-assistant-custom", + "magic-blue-bulbs", + "magicblue", + "triones", + "web-bluetooth" + ], + "version_installed": null + }, + "187501032": { + "authors": [], + "category": "plugin", + "description": "\ud83d\udcfa FireTV Remote Card", + "domain": "", + "downloads": 0, + "full_name": "marrobHD/firetv-card", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "d0a6d14", + "last_release_tag": "0.1.3", + "last_updated": "2020-06-01T16:50:31Z", + "name": "firetv-card", + "new": false, + "repository_manifest": { + "name": "FireTV Remote Card", + "content_in_root": true + }, + "selected_tag": null, + "show_beta": false, + "stars": 7, + "topics": [ + "custom-cards", + "firetv-card", + "hassio", + "home-assistant", + "home-assistant-custom", + "home-assistant-frontend", + "lovelace", + "lovelace-card", + "lovelace-firetv", + "lovelace-ui" + ], + "version_installed": null + }, + "246549747": { + "authors": [], + "category": "plugin", + "description": "Custom calendar card for Home Assistant with Lovelace", + "domain": "", + "downloads": 0, + "full_name": "marksie1988/atomic-calendar-revive", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "b9c4f15", + "last_release_tag": "v2.5.5", + "last_updated": "2020-12-07T07:56:13Z", + "name": "atomic-calendar-revive", + "new": false, + "repository_manifest": { + "name": "Atomic Calendar Revive", + "filename": "atomic-calendar-revive.js", + "country": [ + "GB" + ], + "render_readme": true + }, + "selected_tag": null, + "show_beta": false, + "stars": 40, + "topics": [ + "calendar", + "card", + "hacs", + "homeassistant", + "javascript", + "lovelace", + "module" + ], + "version_installed": null + }, + "188572845": { + "authors": [], + "category": "plugin", + "description": "\ud83d\udd0a Rotel Remote Card", + "domain": "", + "downloads": 0, + "full_name": "marrobHD/rotel-card", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "f942636", + "last_release_tag": "0.1.2", + "last_updated": "2020-07-08T14:18:44Z", + "name": "rotel-card", + "new": false, + "repository_manifest": { + "name": "Rotel Remote Card", + "content_in_root": true + }, + "selected_tag": null, + "show_beta": false, + "stars": 2, + "topics": [ + "custom-cards", + "hassio", + "home-assistant", + "home-assistant-custom", + "home-assistant-frontend", + "home-assistant-rotel-card", + "lovelace", + "lovelace-card", + "lovelace-ui" + ], + "version_installed": null + }, + "187339794": { + "authors": [], + "category": "plugin", + "description": "\ud83d\udcfa TV Remote Card", + "domain": "", + "downloads": 0, + "full_name": "marrobHD/tv-card", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "0a28112", + "last_release_tag": "0.1.3", + "last_updated": "2020-06-27T17:57:49Z", + "name": "tv-card", + "new": false, + "repository_manifest": { + "name": "TV Remote Card", + "content_in_root": true + }, + "selected_tag": null, + "show_beta": false, + "stars": 20, + "topics": [ + "custom-cards", + "hassio", + "home-assistant", + "home-assistant-custom", + "home-assistant-frontend", + "homeassistant-tv-card", + "lovelace", + "lovelace-card", + "lovelace-ui", + "tv-card" + ], + "version_installed": null + }, + "275672933": { + "authors": [], + "category": "plugin", + "description": "Select List Card displays an input_select entity as a list in lovelace", + "domain": "", + "downloads": 1117, + "full_name": "mattieha/select-list-card", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "9446704", + "last_release_tag": "v1.2.0", + "last_updated": "2020-11-20T20:35:48Z", + "name": "select-list-card", + "new": false, + "repository_manifest": { + "name": "Select list Card", + "render_readme": true, + "domains": [ + "input_select" + ], + "filename": "select-list-card.js" + }, + "selected_tag": null, + "show_beta": false, + "stars": 15, + "topics": [ + "homeassistant", + "lovelace", + "lovelace-custom-card" + ], + "version_installed": null + }, + "183995552": { + "authors": [], + "category": "plugin", + "description": "\u2194\ufe0f Swipe through Lovelace views on mobile.", + "domain": "", + "downloads": 3040, + "full_name": "maykar/lovelace-swipe-navigation", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "1bdc9ed", + "last_release_tag": "1.3.0", + "last_updated": "2020-11-30T20:06:05Z", + "name": "lovelace-swipe-navigation", + "new": false, + "repository_manifest": {}, + "selected_tag": null, + "show_beta": false, + "stars": 89, + "topics": [], + "version_installed": null + }, + "302895020": { + "authors": [], + "category": "plugin", + "description": "Github stats card for Home Assistant", + "domain": "", + "downloads": 245, + "full_name": "maxwroc/github-flexi-card", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "110771b", + "last_release_tag": "v1.0.0", + "last_updated": "2020-11-15T22:57:09Z", + "name": "github-flexi-card", + "new": false, + "repository_manifest": { + "name": "Github Flexi Card / Entity Row", + "content_in_root": false, + "filename": "github-flexi-card.js", + "render_readme": true + }, + "selected_tag": null, + "show_beta": false, + "stars": 6, + "topics": [ + "card", + "flexi", + "github", + "github-flexi-card", + "home-assistant" + ], + "version_installed": null + }, + "256292682": { + "authors": [], + "category": "plugin", + "description": "Battery state card for Home Assistant", + "domain": "", + "downloads": 1555, + "full_name": "maxwroc/battery-state-card", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "9baf80f", + "last_release_tag": "v1.6.2", + "last_updated": "2020-12-07T08:15:32Z", + "name": "battery-state-card", + "new": false, + "repository_manifest": { + "name": "Battery State Card / Entity Row", + "content_in_root": false, + "filename": "battery-state-card.js", + "render_readme": true + }, + "selected_tag": null, + "show_beta": false, + "stars": 134, + "topics": [ + "battery", + "hassio", + "home-assistant", + "lovelace-custom-card", + "sensor" + ], + "version_installed": null + }, + "303101606": { + "authors": [], + "category": "plugin", + "description": "\ud83d\ude48 Hides the Home Assistant header and/or sidebar", + "domain": "", + "downloads": 2699, + "full_name": "maykar/kiosk-mode", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "78b95f3", + "last_release_tag": "1.4.7", + "last_updated": "2020-11-19T12:46:12Z", + "name": "kiosk-mode", + "new": false, + "repository_manifest": { + "name": "Kiosk Mode", + "render_readme": true + }, + "selected_tag": null, + "show_beta": false, + "stars": 47, + "topics": [ + "home-assistant", + "kiosk", + "kiosk-mode", + "lovelace" + ], + "version_installed": null + }, + "204049047": { + "authors": [], + "category": "plugin", + "description": "A Home-Assistant Lovelace card which displays information from the openmensa-sensor.", + "domain": "", + "downloads": 0, + "full_name": "Mofeywalker/openmensa-lovelace-card", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "924327a", + "last_release_tag": null, + "last_updated": "2019-08-23T19:16:54Z", + "name": "openmensa-lovelace-card", + "new": false, + "repository_manifest": { + "name": "OpenMensa Lovelace Card", + "content_in_root": true + }, + "selected_tag": null, + "show_beta": false, + "stars": 2, + "topics": [], + "version_installed": null + }, + "186765704": { + "authors": [], + "category": "plugin", + "description": "A fluffy banner card for Home Assistant \ud83e\udd70", + "domain": "", + "downloads": 3967, + "full_name": "nervetattoo/banner-card", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "c1e6daf", + "last_release_tag": "0.12.0", + "last_updated": "2020-11-15T20:59:26Z", + "name": "banner-card", + "new": false, + "repository_manifest": { + "homeassistant": "0.100.0", + "render_readme": true + }, + "selected_tag": null, + "show_beta": false, + "stars": 227, + "topics": [ + "custom-cards", + "hassio", + "home-assistant", + "home-assistant-custom", + "home-assistant-frontend", + "lovelace", + "lovelace-ui" + ], + "version_installed": null + }, + "158654878": { + "authors": [], + "category": "plugin", + "description": "A different take on the thermostat card for Home Assistant \u2668\ufe0f", + "domain": "", + "downloads": 2816, + "full_name": "nervetattoo/simple-thermostat", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "0654f88", + "last_release_tag": "0.40.0", + "last_updated": "2020-12-07T21:02:42Z", + "name": "simple-thermostat", + "new": false, + "repository_manifest": { + "homeassistant": "0.84.0", + "render_readme": true + }, + "selected_tag": null, + "show_beta": false, + "stars": 314, + "topics": [ + "hassio", + "home-assistant", + "home-assistant-custom", + "lovelace", + "lovelace-ui", + "polymer-3" + ], + "version_installed": null + }, + "286270157": { + "authors": [], + "category": "plugin", + "description": "HA Lovelace card for control of scheduler entities", + "domain": "", + "downloads": 2581, + "full_name": "nielsfaber/scheduler-card", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "83238ed", + "last_release_tag": "v1.9.7", + "last_updated": "2020-11-28T20:39:55Z", + "name": "scheduler-card", + "new": false, + "repository_manifest": { + "name": "Scheduler Card", + "render_readme": "true" + }, + "selected_tag": null, + "show_beta": false, + "stars": 87, + "topics": [ + "assistant", + "automation", + "card", + "custom-card", + "home", + "home-assistant", + "homeassistant", + "lovelace", + "schedule", + "scheduler", + "sunrise", + "sunset", + "week", + "weekly" + ], + "version_installed": null + }, + "142051833": { + "authors": [], + "category": "plugin", + "description": "\ud83d\udcd0 Home Assistant Card: Similar to vertical/horizontal-stack, but removes card borders", + "domain": "", + "downloads": 15721, + "full_name": "ofekashery/vertical-stack-in-card", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "ef46a0d", + "last_release_tag": "v0.3.3", + "last_updated": "2020-06-04T18:14:58Z", + "name": "vertical-stack-in-card", + "new": false, + "repository_manifest": { + "name": "Vertical Stack In Card", + "render_readme": true, + "filename": "vertical-stack-in-card.js" + }, + "selected_tag": null, + "show_beta": false, + "stars": 465, + "topics": [ + "hacktoberfest", + "hacktoberfest2020", + "homeassistant", + "lovelace", + "lovelace-ui" + ], + "version_installed": null + }, + "199546187": { + "authors": [], + "category": "plugin", + "description": "This is a companion card for Google Keep sensor. It displays notes downloaded by integration in a friendly way, similar to Google Keep app.", + "domain": "", + "downloads": 0, + "full_name": "PiotrMachowski/lovelace-google-keep-card", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "d979a43", + "last_release_tag": "v1.1.0", + "last_updated": "2020-10-21T21:26:29Z", + "name": "lovelace-google-keep-card", + "new": false, + "repository_manifest": {}, + "selected_tag": null, + "show_beta": false, + "stars": 16, + "topics": [ + "home-assistant", + "lovelace-card" + ], + "version_installed": null + }, + "193408399": { + "authors": [], + "category": "plugin", + "description": "This card displays provided data as an HTML content of a card.", + "domain": "", + "downloads": 0, + "full_name": "PiotrMachowski/lovelace-html-card", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "6911759", + "last_release_tag": "v1.0.0", + "last_updated": "2020-10-15T15:54:14Z", + "name": "lovelace-html-card", + "new": false, + "repository_manifest": {}, + "selected_tag": null, + "show_beta": false, + "stars": 7, + "topics": [ + "home-assistant", + "lovelace-card" + ], + "version_installed": null + }, + "193372044": { + "authors": [], + "category": "plugin", + "description": "This card enables you to specify a target or start a zoned cleanup using live or static map, just like in Xiaomi Home app. Additionally you can define a list of zones and choose the ones to be cleaned.", + "domain": "", + "downloads": 0, + "full_name": "PiotrMachowski/lovelace-xiaomi-vacuum-map-card", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "738bd88", + "last_release_tag": "v1.2.1", + "last_updated": "2020-10-29T17:07:33Z", + "name": "lovelace-xiaomi-vacuum-map-card", + "new": false, + "repository_manifest": {}, + "selected_tag": null, + "show_beta": false, + "stars": 349, + "topics": [ + "home-assistant", + "lovelace-card", + "roborock", + "vacuum", + "xiaomi", + "xiaomi-vacuum" + ], + "version_installed": null + }, + "238414582": { + "authors": [], + "category": "plugin", + "description": "Custom Lovelace card for Warsaw public transport", + "domain": "", + "downloads": 0, + "full_name": "peetereczek/ztm-stop-card", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "8f140cc", + "last_release_tag": null, + "last_updated": "2020-10-23T11:13:51Z", + "name": "ztm-stop-card", + "new": false, + "repository_manifest": { + "name": "Custom Card for Warsaw ZTM Information", + "country": "PL", + "homeassistant": "0.100.0" + }, + "selected_tag": null, + "show_beta": false, + "stars": 2, + "topics": [], + "version_installed": null + }, + "197759180": { + "authors": [], + "category": "plugin", + "description": "Quickly search for entities from a Lovelace card.", + "domain": "", + "downloads": 0, + "full_name": "postlund/search-card", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "df53527", + "last_release_tag": null, + "last_updated": "2020-07-01T05:25:43Z", + "name": "search-card", + "new": false, + "repository_manifest": {}, + "selected_tag": null, + "show_beta": false, + "stars": 47, + "topics": [], + "version_installed": null + }, + "205261230": { + "authors": [], + "category": "plugin", + "description": "This card displays provided Jinja2 template as an HTML content of a card. It uses exactly the same engine as Home Assistant in Developer tools.", + "domain": "", + "downloads": 0, + "full_name": "PiotrMachowski/Home-Assistant-Lovelace-HTML-Jinja2-Template-card", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "d1cb083", + "last_release_tag": "v1.0.2", + "last_updated": "2020-10-15T15:53:49Z", + "name": "Home-Assistant-Lovelace-HTML-Jinja2-Template-card", + "new": false, + "repository_manifest": { + "name": "HTML Jinja2 Template card", + "filename": "html-template-card.js" + }, + "selected_tag": null, + "show_beta": false, + "stars": 9, + "topics": [ + "home-assistant", + "jinja2", + "lovelace-card", + "template" + ], + "version_installed": null + }, + "236127727": { + "authors": [], + "category": "plugin", + "description": "A custom card which exposes Camect video streams via the Home Assistant Lovelace interface. To use this card, you MUST have already installed the Camect HACS integration.", + "domain": "", + "downloads": 0, + "full_name": "pfunkmallone/HACS-camect-custom_card", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "321c001", + "last_release_tag": "2020051001", + "last_updated": "2020-05-15T18:42:57Z", + "name": "HACS-camect-custom_card", + "new": false, + "repository_manifest": { + "name": "Camect Camera Card", + "filename": "HACS-camect-custom_card.js" + }, + "selected_tag": null, + "show_beta": false, + "stars": 3, + "topics": [ + "camect", + "custom-card", + "hacs", + "home-assistant" + ], + "version_installed": null + }, + "218178802": { + "authors": [], + "category": "plugin", + "description": "This card can show and hide a specific card on current device while not affecting other windows. It does not require any integration to run.", + "domain": "", + "downloads": 0, + "full_name": "PiotrMachowski/Home-Assistant-Lovelace-Local-Conditional-card", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "8ebc051", + "last_release_tag": "v1.1.1", + "last_updated": "2020-10-15T15:53:23Z", + "name": "Home-Assistant-Lovelace-Local-Conditional-card", + "new": false, + "repository_manifest": { + "name": "Local Conditional card", + "filename": "local-conditional-card.js" + }, + "selected_tag": null, + "show_beta": false, + "stars": 8, + "topics": [ + "home-assistant", + "lovelace-card" + ], + "version_installed": null + }, + "215037975": { + "authors": [], + "category": "plugin", + "description": "Home Assistant Lovelace card for Waze Travel Time Sensor", + "domain": "", + "downloads": 0, + "full_name": "r-renato/ha-card-waze-travel-time", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "038f2a8", + "last_release_tag": null, + "last_updated": "2020-05-15T12:43:51Z", + "name": "ha-card-waze-travel-time", + "new": false, + "repository_manifest": { + "name": "HA (Lovelace) Card Waze Travel Time", + "domains": [ + "plugin" + ] + }, + "selected_tag": null, + "show_beta": false, + "stars": 18, + "topics": [ + "hacs", + "home-assistant", + "home-assistant-hacs", + "lovelace-card" + ], + "version_installed": null + }, + "260940136": { + "authors": [], + "category": "plugin", + "description": "Weather condition card (Lovelace) for Home Assistant.", + "domain": "", + "downloads": 0, + "full_name": "r-renato/ha-card-weather-conditions", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "f5774d1", + "last_release_tag": null, + "last_updated": "2020-12-06T17:47:13Z", + "name": "ha-card-weather-conditions", + "new": false, + "repository_manifest": { + "name": "HA (Lovelace) Card Weather Conditions", + "domains": [ + "weather" + ], + "render_readme": false, + "filename": "ha-card-weather-conditions.js" + }, + "selected_tag": null, + "show_beta": false, + "stars": 33, + "topics": [ + "card", + "home-assistant", + "home-assistant-hacs", + "lovelace", + "weather-conditions" + ], + "version_installed": null + }, + "197715418": { + "authors": [], + "category": "plugin", + "description": "A Home Assistant Lovelace card to report MiFlora plant sensors based on the HA Plant Card.", + "domain": "", + "downloads": 0, + "full_name": "RodBr/miflora-card", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "889ee81", + "last_release_tag": "0.1.0", + "last_updated": "2020-06-06T04:43:54Z", + "name": "miflora-card", + "new": false, + "repository_manifest": {}, + "selected_tag": null, + "show_beta": false, + "stars": 5, + "topics": [], + "version_installed": null + }, + "289188530": { + "authors": [], + "category": "plugin", + "description": "Home assistant power card mimicking the one tesla provides for the powerwall app.", + "domain": "", + "downloads": 0, + "full_name": "reptilex/tesla-style-solar-power-card", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "90f9908", + "last_release_tag": "v9.1", + "last_updated": "2020-12-08T08:53:26Z", + "name": "tesla-style-solar-power-card", + "new": false, + "repository_manifest": { + "name": "Tesla style solar power card", + "content_in_root": true, + "filename": "tesla-style-solar-power-card.js", + "render_readme": true + }, + "selected_tag": null, + "show_beta": false, + "stars": 21, + "topics": [ + "battery", + "card", + "eletric-car", + "homeassistant", + "lovelace", + "power", + "solar-energy" + ], + "version_installed": null + }, + "279157206": { + "authors": [], + "category": "plugin", + "description": "Water Heater card for Home Assistant's Lovelace UI", + "domain": "", + "downloads": 0, + "full_name": "rsnodgrass/water-heater-card", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "37268fd", + "last_release_tag": "0.0.2", + "last_updated": "2020-09-13T02:10:31Z", + "name": "water-heater-card", + "new": false, + "repository_manifest": { + "name": "Water Heater Card", + "render_readme": true, + "content_in_root": false, + "homeassistant": "0.81.0", + "filename": "water-heater-card.js" + }, + "selected_tag": null, + "show_beta": false, + "stars": 2, + "topics": [ + "hacs", + "home-assistant", + "lovelace" + ], + "version_installed": null + }, + "216008446": { + "authors": [], + "category": "plugin", + "description": "Logbook card for Home Assistant UI Lovelace", + "domain": "", + "downloads": 1450, + "full_name": "royto/logbook-card", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "6f673ce", + "last_release_tag": "1.2.0", + "last_updated": "2020-05-29T20:25:28Z", + "name": "logbook-card", + "new": false, + "repository_manifest": { + "name": "Logbook Card", + "filename": "logbook-card.js" + }, + "selected_tag": null, + "show_beta": false, + "stars": 36, + "topics": [ + "custom-card", + "hacs", + "home-assistant", + "lovelace" + ], + "version_installed": null + }, + "241706284": { + "authors": [], + "category": "plugin", + "description": "Custom, \"neumorphism\" Lovelace card", + "domain": "", + "downloads": 0, + "full_name": "Savjee/button-text-card", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "2ab6d69", + "last_release_tag": null, + "last_updated": "2020-06-22T09:38:33Z", + "name": "button-text-card", + "new": false, + "repository_manifest": { + "name": "Button Text Card", + "render_readme": true, + "filename": "button-text-card.js" + }, + "selected_tag": null, + "show_beta": false, + "stars": 39, + "topics": [ + "home-assistant", + "lovelace-card", + "templating", + "typescript" + ], + "version_installed": null + }, + "202874270": { + "authors": [], + "category": "plugin", + "description": "Sets the background of your Home Assistant to match the entity picture of a media player", + "domain": "", + "downloads": 0, + "full_name": "TheLastProject/lovelace-media-art-background", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "f7752ae", + "last_release_tag": null, + "last_updated": "2020-10-25T21:19:04Z", + "name": "lovelace-media-art-background", + "new": false, + "repository_manifest": {}, + "selected_tag": null, + "show_beta": false, + "stars": 8, + "topics": [], + "version_installed": null + }, + "259126760": { + "authors": [], + "category": "plugin", + "description": "Honeycomb menu is a Home Assistant module (not a card) that can be applied to any lovelace card. When activated by the defined action on said card, the module will display a 'rounded' list of honeycomb buttons with an optional XY pad to make interfacing with lovelace more fluent", + "domain": "", + "downloads": 595, + "full_name": "Sian-Lee-SA/honeycomb-menu", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "c2ba1b5", + "last_release_tag": "0.8.9", + "last_updated": "2020-10-13T01:23:31Z", + "name": "honeycomb-menu", + "new": false, + "repository_manifest": { + "name": "Honeycomb Menu", + "render_readme": true, + "filename": "honeycomb-menu.js" + }, + "selected_tag": null, + "show_beta": false, + "stars": 13, + "topics": [ + "home-assistant", + "home-assistant-frontend", + "lovelace-module", + "lovelace-ui", + "menu", + "module" + ], + "version_installed": null + }, + "284283867": { + "authors": [], + "category": "plugin", + "description": "A custom card for Home Assistant that will display images and/or videos from a folder in the style of a gallery. Requires files component.", + "domain": "", + "downloads": 0, + "full_name": "TarheelGrad1998/gallery-card", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "2cf75ad", + "last_release_tag": "2.2", + "last_updated": "2020-11-22T00:17:54Z", + "name": "gallery-card", + "new": false, + "repository_manifest": { + "name": "Gallery Card", + "filename": "gallery-card.js", + "render_readme": true + }, + "selected_tag": null, + "show_beta": false, + "stars": 10, + "topics": [ + "gallery", + "gallery-card", + "home-assistant", + "images", + "videos" + ], + "version_installed": null + }, + "237532750": { + "authors": [], + "category": "plugin", + "description": "A Home Assistant Lovelace Care for Harmony Integration", + "domain": "", + "downloads": 1629, + "full_name": "sbryfcz/harmony-card", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "65c7729", + "last_release_tag": "0.13.1", + "last_updated": "2020-11-30T19:44:17Z", + "name": "harmony-card", + "new": false, + "repository_manifest": { + "name": "Harmony Card", + "render_readme": true, + "filename": "harmony-card.js" + }, + "selected_tag": null, + "show_beta": false, + "stars": 46, + "topics": [], + "version_installed": null + }, + "236317072": { + "authors": [], + "category": "plugin", + "description": "Generalized Lovelace pie chart card", + "domain": "", + "downloads": 0, + "full_name": "sdelliot/pie-chart-card", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "03a951f", + "last_release_tag": null, + "last_updated": "2020-03-07T21:19:19Z", + "name": "pie-chart-card", + "new": false, + "repository_manifest": { + "name": "Pie Chart Card", + "content_in_root": true, + "filename": "pie-chart-card.js", + "render_readme": true + }, + "selected_tag": null, + "show_beta": false, + "stars": 7, + "topics": [], + "version_installed": null + }, + "191580766": { + "authors": [], + "category": "plugin", + "description": "Turn on lights based on light_profiles.csv", + "domain": "", + "downloads": 0, + "full_name": "tcarlsen/lovelace-light-with-profiles", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "0110446", + "last_release_tag": "2.3.0", + "last_updated": "2020-04-02T14:22:23Z", + "name": "lovelace-light-with-profiles", + "new": false, + "repository_manifest": { + "name": "Light with profiles", + "content_in_root": true, + "filename": "light-with-profiles.js", + "homeassistant": "0.100.0" + }, + "selected_tag": null, + "show_beta": false, + "stars": 45, + "topics": [ + "custom-card", + "home-assistant", + "home-assistant-frontend", + "light", + "light-profiles", + "lovelace", + "lovelace-card", + "profiles" + ], + "version_installed": null + }, + "203036108": { + "authors": [], + "category": "plugin", + "description": "Draws the map available from a Xiaomi Vacuum cleaner flashed with Valetudo in a Home Assistant Lovelace card", + "domain": "", + "downloads": 0, + "full_name": "TheLastProject/lovelace-valetudo-map-card", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "a82e1e1", + "last_release_tag": null, + "last_updated": "2020-12-05T13:56:25Z", + "name": "lovelace-valetudo-map-card", + "new": false, + "repository_manifest": { + "name": "Valetudo Map Card", + "content_in_root": true, + "filename": "valetudo-map-card.js", + "render_readme": true + }, + "selected_tag": null, + "show_beta": false, + "stars": 72, + "topics": [ + "hacktoberfest" + ], + "version_installed": null + }, + "167744584": { + "authors": [], + "category": "plugin", + "description": "\ud83d\udd39Automatically populate the entities-list of lovelace cards", + "domain": "", + "downloads": 0, + "full_name": "thomasloven/lovelace-auto-entities", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "f17fcc1", + "last_release_tag": "1.7.0", + "last_updated": "2020-11-21T19:52:51Z", + "name": "lovelace-auto-entities", + "new": false, + "repository_manifest": { + "name": "auto-entities", + "render_readme": true + }, + "selected_tag": null, + "show_beta": false, + "stars": 327, + "topics": [], + "version_installed": null + }, + "281453608": { + "authors": [], + "category": "plugin", + "description": "\ud83d\udd39 Place badges anywhere in the lovelace layout", + "domain": "", + "downloads": 0, + "full_name": "thomasloven/lovelace-badge-card", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "817bdb3", + "last_release_tag": null, + "last_updated": "2020-07-27T09:50:35Z", + "name": "lovelace-badge-card", + "new": false, + "repository_manifest": { + "name": "badge-card", + "render_readme": true + }, + "selected_tag": null, + "show_beta": false, + "stars": 11, + "topics": [ + "home-assistant" + ], + "version_installed": null + }, + "190927524": { + "authors": [], + "category": "plugin", + "description": "\ud83d\udd39 Add CSS styles to (almost) any lovelace card", + "domain": "", + "downloads": 0, + "full_name": "thomasloven/lovelace-card-mod", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "457cf40", + "last_release_tag": "2.0.3", + "last_updated": "2020-11-18T09:01:24Z", + "name": "lovelace-card-mod", + "new": false, + "repository_manifest": { + "name": "card-mod", + "homeassistant": "0.98.0" + }, + "selected_tag": null, + "show_beta": false, + "stars": 215, + "topics": [], + "version_installed": null + }, + "161403328": { + "authors": [], + "category": "plugin", + "description": "\ud83d\udd39A collection of tools for other lovelace plugins to use", + "domain": "", + "downloads": 0, + "full_name": "thomasloven/lovelace-card-tools", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "477f3d4", + "last_release_tag": "11", + "last_updated": "2020-10-28T12:01:32Z", + "name": "lovelace-card-tools", + "new": false, + "repository_manifest": { + "name": "card-tools", + "homeassistant": "0.100.0" + }, + "selected_tag": null, + "show_beta": false, + "stars": 135, + "topics": [], + "version_installed": null + }, + "150781994": { + "authors": [], + "category": "plugin", + "description": "\ud83d\udd39 A foldable row for entities card, containing other rows", + "domain": "", + "downloads": 0, + "full_name": "thomasloven/lovelace-fold-entity-row", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "01b4108", + "last_release_tag": "19", + "last_updated": "2020-08-01T03:12:50Z", + "name": "lovelace-fold-entity-row", + "new": false, + "repository_manifest": { + "name": "fold-entity-row", + "render_readme": true + }, + "selected_tag": null, + "show_beta": false, + "stars": 190, + "topics": [], + "version_installed": null + }, + "180197912": { + "authors": [], + "category": "plugin", + "description": "\ud83d\udd39 An entity row with only icon and name", + "domain": "", + "downloads": 0, + "full_name": "thomasloven/lovelace-dummy-entity-row", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "d92055d", + "last_release_tag": null, + "last_updated": "2019-11-13T22:52:16Z", + "name": "lovelace-dummy-entity-row", + "new": false, + "repository_manifest": { + "name": "dummy-entity-row", + "render_readme": true + }, + "selected_tag": null, + "show_beta": false, + "stars": 7, + "topics": [], + "version_installed": null + }, + "173108147": { + "authors": [], + "category": "plugin", + "description": "\ud83d\udd39 A lovelace card that does nothing and looks like nothing. Incredibly useful! No, really.", + "domain": "", + "downloads": 0, + "full_name": "thomasloven/lovelace-gap-card", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "cbb8a85", + "last_release_tag": null, + "last_updated": "2019-11-13T22:53:16Z", + "name": "lovelace-gap-card", + "new": false, + "repository_manifest": { + "name": "gap-card", + "render_readme": true + }, + "selected_tag": null, + "show_beta": false, + "stars": 21, + "topics": [], + "version_installed": null + }, + "214154110": { + "authors": [], + "category": "plugin", + "description": "\ud83d\udd39 Lets you play around with the GUI editors even if you're using YAML mode", + "domain": "", + "downloads": 0, + "full_name": "thomasloven/lovelace-gui-sandbox", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "146b2bd", + "last_release_tag": null, + "last_updated": "2019-10-10T13:14:00Z", + "name": "lovelace-gui-sandbox", + "new": false, + "repository_manifest": { + "name": "gui-sandbox", + "render_readme": true + }, + "selected_tag": null, + "show_beta": false, + "stars": 12, + "topics": [], + "version_installed": null + }, + "180528950": { + "authors": [], + "category": "plugin", + "description": "\ud83d\udd39 Display the more-info dialog of any entity as a lovelace card", + "domain": "", + "downloads": 0, + "full_name": "thomasloven/lovelace-more-info-card", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "96ce4d2", + "last_release_tag": null, + "last_updated": "2020-09-11T22:40:51Z", + "name": "lovelace-more-info-card", + "new": false, + "repository_manifest": { + "name": "more-info-card", + "render_readme": true, + "homeassistant": "0.113" + }, + "selected_tag": null, + "show_beta": false, + "stars": 30, + "topics": [], + "version_installed": null + }, + "231674882": { + "authors": [], + "category": "plugin", + "description": "\ud83d\udd39 Display whatever you want in an entities card row.", + "domain": "", + "downloads": 0, + "full_name": "thomasloven/lovelace-template-entity-row", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "a8dce27", + "last_release_tag": "1.1.1", + "last_updated": "2020-10-28T12:48:44Z", + "name": "lovelace-template-entity-row", + "new": false, + "repository_manifest": { + "name": "template-entity-row", + "render_readme": true, + "homeassistant": "0.107.0b1" + }, + "selected_tag": null, + "show_beta": false, + "stars": 43, + "topics": [], + "version_installed": null + }, + "144899700": { + "authors": [], + "category": "plugin", + "description": "\ud83d\udd39 Add sliders to entity cards", + "domain": "", + "downloads": 0, + "full_name": "thomasloven/lovelace-slider-entity-row", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "a0748e1", + "last_release_tag": "16", + "last_updated": "2020-10-09T11:06:18Z", + "name": "lovelace-slider-entity-row", + "new": false, + "repository_manifest": { + "name": "slider-entity-row", + "render_readme": true + }, + "selected_tag": null, + "show_beta": false, + "stars": 382, + "topics": [], + "version_installed": null + }, + "158756598": { + "authors": [], + "category": "plugin", + "description": "\ud83d\udd39Dynamically replace lovelace cards depending on occasion", + "domain": "", + "downloads": 0, + "full_name": "thomasloven/lovelace-state-switch", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "35bd9c8", + "last_release_tag": "8", + "last_updated": "2020-08-01T07:34:10Z", + "name": "lovelace-state-switch", + "new": false, + "repository_manifest": { + "name": "state-switch", + "render_readme": true + }, + "selected_tag": null, + "show_beta": false, + "stars": 99, + "topics": [], + "version_installed": null + }, + "156434866": { + "authors": [], + "category": "plugin", + "description": "\ud83d\udd39 Get more control over the placement of lovelace cards.", + "domain": "", + "downloads": 0, + "full_name": "thomasloven/lovelace-layout-card", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "e8f0ac8", + "last_release_tag": "16", + "last_updated": "2020-11-18T07:35:50Z", + "name": "lovelace-layout-card", + "new": false, + "repository_manifest": { + "name": "layout-card", + "render_readme": true + }, + "selected_tag": null, + "show_beta": false, + "stars": 249, + "topics": [], + "version_installed": null + }, + "249942054": { + "authors": [], + "category": "plugin", + "description": "\ud83d\udd39 Use built-in elements in the wrong place", + "domain": "", + "downloads": 0, + "full_name": "thomasloven/lovelace-hui-element", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "dd3f31f", + "last_release_tag": null, + "last_updated": "2020-08-01T18:13:13Z", + "name": "lovelace-hui-element", + "new": false, + "repository_manifest": { + "name": "hui-element", + "render_readme": true + }, + "selected_tag": null, + "show_beta": false, + "stars": 16, + "topics": [], + "version_installed": null + }, + "197245179": { + "authors": [], + "category": "plugin", + "description": "Lovelace card for hass-aarlo integration.", + "domain": "", + "downloads": 0, + "full_name": "twrecked/lovelace-hass-aarlo", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "e3f4831", + "last_release_tag": "v0.1.5", + "last_updated": "2020-10-11T14:02:34Z", + "name": "lovelace-hass-aarlo", + "new": false, + "repository_manifest": {}, + "selected_tag": null, + "show_beta": false, + "stars": 19, + "topics": [ + "arlo", + "camera", + "homeassistant", + "lovelace-card", + "streaming" + ], + "version_installed": null + }, + "264796130": { + "authors": [], + "category": "plugin", + "description": "Pandora lovelace card for Home Assistant", + "domain": "", + "downloads": 0, + "full_name": "turbulator/pandora-cas-card", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "32ad046", + "last_release_tag": "0.2", + "last_updated": "2020-10-03T15:41:00Z", + "name": "pandora-cas-card", + "new": false, + "repository_manifest": { + "name": "Pandora CAS card", + "render_readme": true + }, + "selected_tag": null, + "show_beta": false, + "stars": 2, + "topics": [ + "home-assistant", + "lovelace-custom-card", + "pandora" + ], + "version_installed": null + }, + "286038496": { + "authors": [], + "category": "plugin", + "description": "A Lovelace card that shows a directional indicator on a compass for Home Assistant", + "domain": "", + "downloads": 463, + "full_name": "tomvanswam/compass-card", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "6f08fb3", + "last_release_tag": "v1.0.0", + "last_updated": "2020-12-08T18:39:23Z", + "name": "compass-card", + "new": false, + "repository_manifest": { + "name": "Compass Card", + "render_readme": true, + "filename": "compass-card.js" + }, + "selected_tag": null, + "show_beta": false, + "stars": 22, + "topics": [ + "compass", + "hacs", + "home-assistant", + "lovelace-card" + ], + "version_installed": null + }, + "202743061": { + "authors": [], + "category": "plugin", + "description": "Animated backgrounds for lovelace ", + "domain": "", + "downloads": 0, + "full_name": "Villhellm/lovelace-animated-background", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "cbacf95", + "last_release_tag": "v0.6.3", + "last_updated": "2020-11-26T03:49:25Z", + "name": "lovelace-animated-background", + "new": false, + "repository_manifest": { + "name": "Lovelace Animated Background", + "render_readme": true + }, + "selected_tag": null, + "show_beta": false, + "stars": 73, + "topics": [ + "animated", + "background", + "hacktoberfest" + ], + "version_installed": null + }, + "223008910": { + "authors": [], + "category": "plugin", + "description": "Custom Sidebar for Home Assistant", + "domain": "", + "downloads": 0, + "full_name": "Villhellm/custom-sidebar", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "3ed0a8f", + "last_release_tag": "v0.2.2", + "last_updated": "2020-10-31T13:26:16Z", + "name": "custom-sidebar", + "new": false, + "repository_manifest": { + "name": "Custom Sidebar", + "render_readme": true + }, + "selected_tag": null, + "show_beta": false, + "stars": 52, + "topics": [ + "custom", + "hacktoberfest", + "sidebar" + ], + "version_installed": null + }, + "259904390": { + "authors": [], + "category": "plugin", + "description": "A custom lovelace card for the custom Jumbo component.", + "domain": "", + "downloads": 248, + "full_name": "Voxxie/lovelace-jumbo-card", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "7d5b2e7", + "last_release_tag": "v0.2.1", + "last_updated": "2020-05-04T10:11:59Z", + "name": "lovelace-jumbo-card", + "new": false, + "repository_manifest": { + "name": "Jumbo Card", + "filename": "jumbo-card.js" + }, + "selected_tag": null, + "show_beta": false, + "stars": 1, + "topics": [ + "home-assistant", + "jumbo", + "lovelace-card", + "lovelace-custom-card" + ], + "version_installed": null + }, + "283542587": { + "authors": [], + "category": "plugin", + "description": "Basic analog clock for Lovelace", + "domain": "", + "downloads": 0, + "full_name": "Villhellm/lovelace-clock-card", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "7a89550", + "last_release_tag": "v0.4.1", + "last_updated": "2020-11-24T17:31:42Z", + "name": "lovelace-clock-card", + "new": false, + "repository_manifest": { + "name": "Lovelace Clock Card", + "render_readme": true + }, + "selected_tag": null, + "show_beta": false, + "stars": 17, + "topics": [ + "analog", + "clock", + "hacktoberfest", + "lovelace" + ], + "version_installed": null + }, + "194319685": { + "authors": [], + "category": "python_script", + "description": "Script that adds MQTT discovery support for Shellies devices", + "domain": "", + "downloads": 170, + "full_name": "bieniu/ha-shellies-discovery", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "7b8cf4d", + "last_release_tag": "0.35.1", + "last_updated": "2020-12-07T12:00:04Z", + "name": "ha-shellies-discovery", + "new": false, + "repository_manifest": { + "name": "Shellies Discovery", + "homeassistant": "0.115.0", + "zip_release": true, + "filename": "shellies-discovery.zip" + }, + "selected_tag": null, + "show_beta": false, + "stars": 81, + "topics": [ + "discovery", + "home-assistant", + "mqtt", + "python", + "python-script", + "shelly" + ], + "version_installed": null + }, + "194381263": { + "authors": [], + "category": "python_script", + "description": "This script updates Z-Wave thermostat entity state and current temperature from external sensor", + "domain": "", + "downloads": 0, + "full_name": "bieniu/ha-thermostat-update", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "08d0fc6", + "last_release_tag": "0.3.5", + "last_updated": "2020-04-28T06:54:52Z", + "name": "ha-thermostat-update", + "new": false, + "repository_manifest": { + "name": "Thermostat Update" + }, + "selected_tag": null, + "show_beta": false, + "stars": 7, + "topics": [ + "home-assistant", + "python", + "python-script", + "thermostat", + "z-wave" + ], + "version_installed": null + }, + "233093604": { + "authors": [], + "category": "python_script", + "description": "merge master/slave device trackers", + "domain": "", + "downloads": 0, + "full_name": "kodi1/tracker_merge", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "034275b", + "last_release_tag": null, + "last_updated": "2020-01-15T22:42:17Z", + "name": "tracker_merge", + "new": false, + "repository_manifest": { + "name": "kodi1/tracker_merge", + "render_readme": "true", + "country": "NO" + }, + "selected_tag": null, + "show_beta": false, + "stars": 3, + "topics": [ + "hacs", + "home-assistant", + "python", + "python-script", + "tracking" + ], + "version_installed": null + }, + "246406566": { + "authors": [], + "category": "python_script", + "description": "Python script to update climate devices", + "domain": "", + "downloads": 0, + "full_name": "Santobert/HA-UpdateClimate", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "c4afcc6", + "last_release_tag": "v1.0.3", + "last_updated": "2020-04-10T12:58:11Z", + "name": "HA-UpdateClimate", + "new": false, + "repository_manifest": { + "name": "UpdateClimate", + "render_readme": true, + "domains": "climate", + "homeassistant": "0.47.0" + }, + "selected_tag": null, + "show_beta": false, + "stars": 3, + "topics": [ + "climate", + "hacs", + "home-assistant", + "hvac", + "preset", + "python-script", + "scheduler" + ], + "version_installed": null + }, + "240900380": { + "authors": [], + "category": "python_script", + "description": "Python script to handle state and attributes of existing sensors and entities", + "domain": "", + "downloads": 0, + "full_name": "pmazz/ps_hassio_entities", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "9412cbd", + "last_release_tag": "v1.1.0", + "last_updated": "2020-05-05T16:07:37Z", + "name": "ps_hassio_entities", + "new": false, + "repository_manifest": { + "name": "Entities Script", + "render_readme": "true" + }, + "selected_tag": null, + "show_beta": false, + "stars": 9, + "topics": [ + "hacs", + "home-assistant", + "python", + "python-script" + ], + "version_installed": null + }, + "258712314": { + "authors": [], + "category": "python_script", + "description": "A python script for Home Assistant that control fan speed with Fan Template and Broadlink.", + "domain": "", + "downloads": 0, + "full_name": "iml885203/HA-FanSpeedControl", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "1432970", + "last_release_tag": "v1.3", + "last_updated": "2020-09-21T13:14:57Z", + "name": "HA-FanSpeedControl", + "new": false, + "repository_manifest": { + "name": "Fan Speed Control", + "render_readme": "true" + }, + "selected_tag": null, + "show_beta": false, + "stars": 2, + "topics": [ + "hacs", + "home-assistant", + "homeassistant", + "python", + "python-script" + ], + "version_installed": null + }, + "312649007": { + "authors": [], + "category": "python_script", + "description": "A python script for Home Assistant that counts down the days to reminder", + "domain": "", + "downloads": 0, + "full_name": "eyalcha/ha-reminder", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "f495aa5", + "last_release_tag": "1.0.3", + "last_updated": "2020-12-04T12:35:57Z", + "name": "ha-reminder", + "new": false, + "repository_manifest": { + "name": "Reminder" + }, + "selected_tag": null, + "show_beta": false, + "stars": 5, + "topics": [ + "hacs", + "home-assistant", + "python-scripts" + ], + "version_installed": null + }, + "198460710": { + "authors": [], + "category": "python_script", + "description": "A python script for Homeassistant that counts down the days to birthdays, anniversaries etc", + "domain": "", + "downloads": 0, + "full_name": "mf-social/ps-date-countdown", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "cf51683", + "last_release_tag": "v6.0", + "last_updated": "2020-12-02T10:54:08Z", + "name": "ps-date-countdown", + "new": false, + "repository_manifest": { + "name": "Date Countdown" + }, + "selected_tag": null, + "show_beta": false, + "stars": 17, + "topics": [ + "hacs", + "home-assistant", + "homeassistant", + "python", + "python-script" + ], + "version_installed": null + }, + "231083679": { + "authors": [], + "category": "theme", + "description": "\ud83d\udc35 Dark Theme based on clear-theme-dark by @naofireblade", + "domain": "", + "downloads": 0, + "full_name": "aFFekopp/dark_teal", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "d3f96b2", + "last_release_tag": "1.3", + "last_updated": "2020-08-10T17:15:43Z", + "name": "dark_teal", + "new": false, + "repository_manifest": { + "name": "Dark Teal" + }, + "selected_tag": null, + "show_beta": false, + "stars": 8, + "topics": [ + "dark-theme", + "hacs", + "home-assistant", + "home-assistant-theme" + ], + "version_installed": null + }, + "253311340": { + "authors": [], + "category": "theme", + "description": "\ud83d\ude0e My Theme 'Blue' - with semi-transparent Cards", + "domain": "", + "downloads": 0, + "full_name": "3ative/3ative-blue-theme", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "6bd6b63", + "last_release_tag": "v1.5.6c", + "last_updated": "2020-11-29T05:26:51Z", + "name": "3ative-blue-theme", + "new": false, + "repository_manifest": { + "name": "3Ative Blue Theme" + }, + "selected_tag": null, + "show_beta": false, + "stars": 2, + "topics": [ + "3ative", + "blue", + "theme", + "theme-ui", + "themes" + ], + "version_installed": null + }, + "233715171": { + "authors": [], + "category": "theme", + "description": "Darkish-Theme for Home Assistant", + "domain": "", + "downloads": 0, + "full_name": "78wesley/Darkish-Theme", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "e55c8ec", + "last_release_tag": null, + "last_updated": "2020-01-19T20:43:15Z", + "name": "Darkish-Theme", + "new": false, + "repository_manifest": { + "name": "Darkish Theme" + }, + "selected_tag": null, + "show_beta": false, + "stars": 4, + "topics": [], + "version_installed": null + }, + "233445397": { + "authors": [], + "category": "theme", + "description": "Custom theme for home assistant", + "domain": "", + "downloads": 0, + "full_name": "am80l/sundown", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "bdfa827", + "last_release_tag": "1.0.6", + "last_updated": "2020-07-29T01:28:19Z", + "name": "sundown", + "new": false, + "repository_manifest": { + "name": "Sundown Theme" + }, + "selected_tag": null, + "show_beta": false, + "stars": 3, + "topics": [], + "version_installed": null + }, + "231829137": { + "authors": [], + "category": "theme", + "description": "\ud83d\udc35 Dark Blue Theme for Home Assistant", + "domain": "", + "downloads": 0, + "full_name": "aFFekopp/noctis", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "779738f", + "last_release_tag": "2.4", + "last_updated": "2020-07-30T18:56:14Z", + "name": "noctis", + "new": false, + "repository_manifest": { + "name": "Noctis" + }, + "selected_tag": null, + "show_beta": false, + "stars": 66, + "topics": [ + "dark-theme", + "hacs", + "home-assistant", + "home-assistant-theme" + ], + "version_installed": null + }, + "230974064": { + "authors": [], + "category": "theme", + "description": "Oxford blue theme for Home Assistant", + "domain": "", + "downloads": 0, + "full_name": "arsaboo/oxford_blue_theme", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "ce33c91", + "last_release_tag": null, + "last_updated": "2020-02-27T00:08:56Z", + "name": "oxford_blue_theme", + "new": false, + "repository_manifest": { + "name": "Oxford Blue", + "render_readme": true + }, + "selected_tag": null, + "show_beta": false, + "stars": 3, + "topics": [], + "version_installed": null + }, + "222422187": { + "authors": [], + "category": "theme", + "description": "\ud83c\udfe0\ud83e\udd16 Theme by @basnijholt based on iOS Dark Mode for Lovelace Home Assistant ", + "domain": "", + "downloads": 0, + "full_name": "basnijholt/lovelace-ios-dark-mode-theme", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "66c93f4", + "last_release_tag": null, + "last_updated": "2020-09-24T08:45:32Z", + "name": "lovelace-ios-dark-mode-theme", + "new": false, + "repository_manifest": { + "name": "iOS Dark Mode Theme", + "render_readme": true + }, + "selected_tag": null, + "show_beta": false, + "stars": 284, + "topics": [ + "dark-mode", + "darkmode", + "hacs", + "home-assistant", + "ios", + "lovelace", + "theme" + ], + "version_installed": null + }, + "255366214": { + "authors": [], + "category": "theme", + "description": "\ud83c\udf99\ufe0f Vintage theme original colours & style designed by @surendrananup HACS adapted by @Banditen01", + "domain": "", + "downloads": 0, + "full_name": "Banditen01/vintage_theme", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "d3f50b1", + "last_release_tag": "1.3.7.5", + "last_updated": "2020-09-25T08:29:06Z", + "name": "vintage_theme", + "new": false, + "repository_manifest": { + "name": "Vintage", + "render_readme": true, + "filename": "themes/vintage.yaml" + }, + "selected_tag": null, + "show_beta": false, + "stars": 2, + "topics": [ + "hacs", + "homeassistant", + "theme", + "unofficial" + ], + "version_installed": null + }, + "202203063": { + "authors": [], + "category": "theme", + "description": "Synthwave inspired theme for Home Assistant", + "domain": "", + "downloads": 0, + "full_name": "bbbenji/synthwave-hass", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "0b7c81b", + "last_release_tag": "0.3.3.2", + "last_updated": "2020-10-15T12:48:38Z", + "name": "synthwave-hass", + "new": false, + "repository_manifest": {}, + "selected_tag": null, + "show_beta": false, + "stars": 98, + "topics": [ + "css", + "home-assistant", + "home-assistant-theme", + "javascript", + "synthwave" + ], + "version_installed": null + }, + "236318024": { + "authors": [], + "category": "theme", + "description": "\u2764\ufe0f\ud83d\udcf1\ud83c\udfe0\ud83e\udd16 Themes inspired by iOS Dark \u2b1b\ufe0f and Light \u25fb\ufe0f Mode for Lovelace Home Assistant with different backgrounds by @basnijholt", + "domain": "", + "downloads": 0, + "full_name": "basnijholt/lovelace-ios-themes", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "a133c62", + "last_release_tag": "v2.5.0", + "last_updated": "2020-10-30T11:47:08Z", + "name": "lovelace-ios-themes", + "new": false, + "repository_manifest": { + "name": "iOS Themes - Dark Mode and Light Mode", + "hacs": "0.21.2", + "filename": "ios-themes.yaml", + "render_readme": true + }, + "selected_tag": null, + "show_beta": false, + "stars": 155, + "topics": [], + "version_installed": null + }, + "234750356": { + "authors": [], + "category": "theme", + "description": "\ud83c\udfe0\ud83e\udd16 Theme based on iOS Light Mode for Lovelace Home Assistant ", + "domain": "", + "downloads": 0, + "full_name": "basnijholt/lovelace-ios-light-mode-theme", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "d3d404b", + "last_release_tag": null, + "last_updated": "2020-01-20T19:48:02Z", + "name": "lovelace-ios-light-mode-theme", + "new": false, + "repository_manifest": { + "name": "iOS Light Mode Theme", + "render_readme": true + }, + "selected_tag": null, + "show_beta": false, + "stars": 7, + "topics": [ + "hacs", + "home-assistant", + "ios", + "light-mode", + "lightmode", + "lovelace", + "lovelace-ui", + "theme" + ], + "version_installed": null + }, + "261924981": { + "authors": [], + "category": "theme", + "description": "\ud83c\udfa8 Green, dark mode theme for Home Assistant, Enjoy.\ud83e\udd18\ud83c\udffb", + "domain": "", + "downloads": 0, + "full_name": "DickSwart/swart_ninja_dark_theme", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "e5b62fd", + "last_release_tag": "v1.0.2", + "last_updated": "2020-10-15T23:56:45Z", + "name": "swart_ninja_dark_theme", + "new": false, + "repository_manifest": { + "name": "Swart Ninja Dark Theme" + }, + "selected_tag": null, + "show_beta": false, + "stars": 3, + "topics": [ + "dark-theme", + "home-assistant", + "lovelace", + "theme" + ], + "version_installed": null + }, + "235436539": { + "authors": [], + "category": "theme", + "description": "Dark Grey Theme for Home Assistant", + "domain": "", + "downloads": 0, + "full_name": "chaptergy/noctis-grey", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "1621272", + "last_release_tag": "v1.3.1", + "last_updated": "2020-08-26T19:49:25Z", + "name": "noctis-grey", + "new": false, + "repository_manifest": { + "name": "Noctis Grey", + "render_readme": true + }, + "selected_tag": null, + "show_beta": false, + "stars": 6, + "topics": [], + "version_installed": null + }, + "309056232": { + "authors": [], + "category": "theme", + "description": "A Home Assistant theme inspired on Github.", + "domain": "", + "downloads": 0, + "full_name": "einschmidt/github_dark_theme", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "08390fd", + "last_release_tag": "v1.2", + "last_updated": "2020-11-15T11:04:20Z", + "name": "github_dark_theme", + "new": false, + "repository_manifest": { + "name": "GitHub Dark Theme", + "render_readme": true + }, + "selected_tag": null, + "show_beta": false, + "stars": 1, + "topics": [ + "assistant-theme", + "hacs", + "themes" + ], + "version_installed": null + }, + "227988032": { + "authors": [], + "category": "theme", + "description": "Theme for home assistant that makes use of pinks and purples and maybe some teal", + "domain": "", + "downloads": 0, + "full_name": "estiens/sweet_pink_hass_theme", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "0b51f9a", + "last_release_tag": null, + "last_updated": "2020-05-18T22:33:48Z", + "name": "sweet_pink_hass_theme", + "new": false, + "repository_manifest": { + "name": "sweet pink", + "render_readme": true + }, + "selected_tag": null, + "show_beta": false, + "stars": 2, + "topics": [ + "home-assistant", + "lovelace", + "lovelace-theme", + "lovelace-ui" + ], + "version_installed": null + }, + "234581410": { + "authors": [], + "category": "theme", + "description": "\ud83c\udfa8 Theme for Home Assistant inspired by iOS Dark Mode \ud83c\udf16", + "domain": "", + "downloads": 0, + "full_name": "fi-sch/ux_goodie_theme", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "39cd0b5", + "last_release_tag": "1.8", + "last_updated": "2020-11-25T11:28:39Z", + "name": "ux_goodie_theme", + "new": false, + "repository_manifest": { + "name": "UX Goodie Theme", + "render_readme": true + }, + "selected_tag": null, + "show_beta": false, + "stars": 7, + "topics": [ + "dark", + "hass", + "hassio", + "ios", + "lovelace", + "lovelace-theme", + "lovelace-ui", + "mode", + "theme", + "ux" + ], + "version_installed": null + }, + "236277163": { + "authors": [], + "category": "theme", + "description": "Reeder Dark Theme for Home Assistant", + "domain": "", + "downloads": 0, + "full_name": "hekm77/reeder_dark_theme", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "dbd319f", + "last_release_tag": null, + "last_updated": "2020-09-18T07:41:54Z", + "name": "reeder_dark_theme", + "new": false, + "repository_manifest": { + "name": "Reeder Dark Theme", + "render_readme": true + }, + "selected_tag": null, + "show_beta": false, + "stars": 4, + "topics": [], + "version_installed": null + }, + "209891408": { + "authors": [], + "category": "theme", + "description": "Amoled theme for Home Assistant", + "domain": "", + "downloads": 0, + "full_name": "home-assistant-community-themes/amoled", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "6b0e27c", + "last_release_tag": null, + "last_updated": "2020-09-30T05:48:05Z", + "name": "amoled", + "new": false, + "repository_manifest": { + "name": "Amoled Theme" + }, + "selected_tag": null, + "show_beta": false, + "stars": 22, + "topics": [ + "hacs", + "home-assistant", + "theme" + ], + "version_installed": null + }, + "309053262": { + "authors": [], + "category": "theme", + "description": "A Home Assistant theme inspired on Github.", + "domain": "", + "downloads": 0, + "full_name": "einschmidt/github_light_theme", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "e98fd4a", + "last_release_tag": "v1.2", + "last_updated": "2020-11-15T11:05:45Z", + "name": "github_light_theme", + "new": false, + "repository_manifest": { + "name": "GitHub Light Theme", + "render_readme": true + }, + "selected_tag": null, + "show_beta": false, + "stars": 2, + "topics": [ + "assistant-theme", + "hacs", + "themes" + ], + "version_installed": null + }, + "215075805": { + "authors": [], + "category": "theme", + "description": "Blue Night theme for Home Assistant", + "domain": "", + "downloads": 0, + "full_name": "home-assistant-community-themes/blue-night", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "4c34f97", + "last_release_tag": null, + "last_updated": "2020-09-30T17:38:02Z", + "name": "blue-night", + "new": false, + "repository_manifest": { + "name": "Blue Night Theme" + }, + "selected_tag": null, + "show_beta": false, + "stars": 6, + "topics": [ + "hacs", + "home-assistant", + "theme" + ], + "version_installed": null + }, + "235984421": { + "authors": [], + "category": "theme", + "description": "Blackened theme for Home Assistant", + "domain": "", + "downloads": 0, + "full_name": "home-assistant-community-themes/blackened", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "1a176a3", + "last_release_tag": null, + "last_updated": "2020-09-30T06:04:54Z", + "name": "blackened", + "new": false, + "repository_manifest": { + "name": "Blackened Theme" + }, + "selected_tag": null, + "show_beta": false, + "stars": 4, + "topics": [ + "hacs", + "home-assistant", + "theme" + ], + "version_installed": null + }, + "216173358": { + "authors": [], + "category": "theme", + "description": "Christmas theme for Home Assistant", + "domain": "", + "downloads": 0, + "full_name": "home-assistant-community-themes/christmas", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "9872255", + "last_release_tag": null, + "last_updated": "2020-10-05T07:17:03Z", + "name": "christmas", + "new": false, + "repository_manifest": { + "name": "Christmas Theme" + }, + "selected_tag": null, + "show_beta": false, + "stars": 1, + "topics": [ + "hacs", + "home-assistant", + "theme" + ], + "version_installed": null + }, + "225969186": { + "authors": [], + "category": "theme", + "description": "Aqua Fiesta theme for Home Assistant", + "domain": "", + "downloads": 0, + "full_name": "home-assistant-community-themes/aqua-fiesta", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "0620e96", + "last_release_tag": "v1.0", + "last_updated": "2020-10-05T07:15:08Z", + "name": "aqua-fiesta", + "new": false, + "repository_manifest": { + "name": "Aqua Fiesta Theme" + }, + "selected_tag": null, + "show_beta": false, + "stars": 3, + "topics": [ + "hacs", + "home-assistant", + "theme" + ], + "version_installed": null + }, + "215075899": { + "authors": [], + "category": "theme", + "description": "Grey Night theme for Home Assistant", + "domain": "", + "downloads": 0, + "full_name": "home-assistant-community-themes/grey-night", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "d07d2e4", + "last_release_tag": null, + "last_updated": "2020-09-30T05:49:08Z", + "name": "grey-night", + "new": false, + "repository_manifest": { + "name": "Grey Night Theme" + }, + "selected_tag": null, + "show_beta": false, + "stars": 4, + "topics": [ + "hacs", + "home-assistant", + "theme" + ], + "version_installed": null + }, + "220641275": { + "authors": [], + "category": "theme", + "description": "Dark Orange theme for Home Assistant", + "domain": "", + "downloads": 0, + "full_name": "home-assistant-community-themes/dark-orange", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "f2e0351", + "last_release_tag": null, + "last_updated": "2020-09-30T05:50:44Z", + "name": "dark-orange", + "new": false, + "repository_manifest": { + "name": "Dark Orange Theme" + }, + "selected_tag": null, + "show_beta": false, + "stars": 7, + "topics": [ + "hacs", + "home-assistant", + "theme" + ], + "version_installed": null + }, + "217374413": { + "authors": [], + "category": "theme", + "description": "Halloween theme for Home Assistant", + "domain": "", + "downloads": 0, + "full_name": "home-assistant-community-themes/halloween", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "bf8f278", + "last_release_tag": null, + "last_updated": "2020-10-05T07:12:11Z", + "name": "halloween", + "new": false, + "repository_manifest": { + "name": "Halloween Theme" + }, + "selected_tag": null, + "show_beta": false, + "stars": 2, + "topics": [ + "hacs", + "home-assistant", + "theme" + ], + "version_installed": null + }, + "214979604": { + "authors": [], + "category": "theme", + "description": "Another Dark theme for Home Assistant", + "domain": "", + "downloads": 0, + "full_name": "home-assistant-community-themes/dark-mint", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "4e8930b", + "last_release_tag": null, + "last_updated": "2020-10-05T07:17:01Z", + "name": "dark-mint", + "new": false, + "repository_manifest": { + "name": "Dark Mint Theme" + }, + "selected_tag": null, + "show_beta": false, + "stars": 5, + "topics": [ + "hacs", + "home-assistant", + "theme" + ], + "version_installed": null + }, + "216178553": { + "authors": [], + "category": "theme", + "description": "Material Dark Green theme for Home Assistant", + "domain": "", + "downloads": 0, + "full_name": "home-assistant-community-themes/material-dark-green", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "4deeaaa", + "last_release_tag": null, + "last_updated": "2020-10-05T07:21:20Z", + "name": "material-dark-green", + "new": false, + "repository_manifest": { + "name": "Material Dark Green Theme" + }, + "selected_tag": null, + "show_beta": false, + "stars": 1, + "topics": [ + "hacs", + "home-assistant", + "theme" + ], + "version_installed": null + }, + "216183299": { + "authors": [], + "category": "theme", + "description": "Material Dark Pink theme for Home Assistant", + "domain": "", + "downloads": 0, + "full_name": "home-assistant-community-themes/material-dark-pink", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "cd3c578", + "last_release_tag": null, + "last_updated": "2020-10-05T07:13:03Z", + "name": "material-dark-pink", + "new": false, + "repository_manifest": { + "name": "Material Dark Pink Theme" + }, + "selected_tag": null, + "show_beta": false, + "stars": 3, + "topics": [ + "hacs", + "home-assistant", + "theme" + ], + "version_installed": null + }, + "214664317": { + "authors": [], + "category": "theme", + "description": "Midnight theme for Home Assistant", + "domain": "", + "downloads": 0, + "full_name": "home-assistant-community-themes/midnight", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "9d583e9", + "last_release_tag": null, + "last_updated": "2020-10-05T13:59:40Z", + "name": "midnight", + "new": false, + "repository_manifest": { + "name": "Midnight Theme" + }, + "selected_tag": null, + "show_beta": false, + "stars": 44, + "topics": [ + "hacs", + "home-assistant", + "theme" + ], + "version_installed": null + }, + "235057110": { + "authors": [], + "category": "theme", + "description": "Material Dark Red theme for Home Assistant", + "domain": "", + "downloads": 0, + "full_name": "home-assistant-community-themes/material-dark-red", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "496b017", + "last_release_tag": null, + "last_updated": "2020-09-30T06:04:46Z", + "name": "material-dark-red", + "new": false, + "repository_manifest": { + "name": "Material Dark Red Theme" + }, + "selected_tag": null, + "show_beta": false, + "stars": 4, + "topics": [ + "hacs", + "home-assistant", + "theme" + ], + "version_installed": null + }, + "223938651": { + "authors": [], + "category": "theme", + "description": "Midnight Blue theme for Home Assistant", + "domain": "", + "downloads": 0, + "full_name": "home-assistant-community-themes/midnight-blue", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "95c2ad8", + "last_release_tag": null, + "last_updated": "2020-10-05T07:14:40Z", + "name": "midnight-blue", + "new": false, + "repository_manifest": { + "name": "Midnight Blue Theme" + }, + "selected_tag": null, + "show_beta": false, + "stars": 6, + "topics": [ + "hacs", + "home-assistant", + "theme" + ], + "version_installed": null + }, + "270638476": { + "authors": [], + "category": "theme", + "description": "Nord theme for Home Assistant", + "domain": "", + "downloads": 0, + "full_name": "home-assistant-community-themes/nord", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "521d1e2", + "last_release_tag": null, + "last_updated": "2020-10-05T07:24:39Z", + "name": "nord", + "new": false, + "repository_manifest": { + "name": "Nord Theme" + }, + "selected_tag": null, + "show_beta": false, + "stars": 9, + "topics": [ + "hacs", + "home-assistant", + "theme" + ], + "version_installed": null + }, + "216165131": { + "authors": [], + "category": "theme", + "description": "Solarized Light theme for Home Assistant", + "domain": "", + "downloads": 0, + "full_name": "home-assistant-community-themes/solarized-light", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "54c1ab2", + "last_release_tag": null, + "last_updated": "2020-10-05T07:15:33Z", + "name": "solarized-light", + "new": false, + "repository_manifest": { + "name": "Solarized Light Theme" + }, + "selected_tag": null, + "show_beta": false, + "stars": 3, + "topics": [ + "hacs", + "home-assistant", + "theme" + ], + "version_installed": null + }, + "216181396": { + "authors": [], + "category": "theme", + "description": "Teal theme for Home Assistant", + "domain": "", + "downloads": 0, + "full_name": "home-assistant-community-themes/teal", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "fff03a5", + "last_release_tag": null, + "last_updated": "2020-10-05T07:17:23Z", + "name": "teal", + "new": false, + "repository_manifest": { + "name": "Teal Theme" + }, + "selected_tag": null, + "show_beta": false, + "stars": 1, + "topics": [ + "hacs", + "home-assistant", + "theme" + ], + "version_installed": null + }, + "255270395": { + "authors": [], + "category": "theme", + "description": "Stell Blue with Colors theme for Home Assistant", + "domain": "", + "downloads": 0, + "full_name": "home-assistant-community-themes/stell-blue-with-colors", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "9ae68be", + "last_release_tag": null, + "last_updated": "2020-10-05T07:19:25Z", + "name": "stell-blue-with-colors", + "new": false, + "repository_manifest": { + "name": "Stell Blue with Colors Theme" + }, + "selected_tag": null, + "show_beta": false, + "stars": 2, + "topics": [ + "hacs", + "home-assistant", + "theme" + ], + "version_installed": null + }, + "234375294": { + "authors": [], + "category": "theme", + "description": "Vaporwave Pink Theme for Home Assistant", + "domain": "", + "downloads": 0, + "full_name": "home-assistant-community-themes/vaporwave-pink", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "f61c8be", + "last_release_tag": "v1.1", + "last_updated": "2020-01-16T19:26:58Z", + "name": "vaporwave-pink", + "new": false, + "repository_manifest": { + "name": "Vaporwave Pink Theme", + "render_readme": true + }, + "selected_tag": null, + "show_beta": false, + "stars": 2, + "topics": [ + "80s", + "hacs", + "home-assistant", + "pink", + "theme", + "vaporwave" + ], + "version_installed": null + }, + "230672465": { + "authors": [], + "category": "theme", + "description": "Christmas theme for Home-Assistant", + "domain": "", + "downloads": 0, + "full_name": "houtknots/UglyChristmas-Theme", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "98746a8", + "last_release_tag": null, + "last_updated": "2020-01-19T20:22:53Z", + "name": "UglyChristmas-Theme", + "new": false, + "repository_manifest": { + "name": "Ugly Christmas Theme" + }, + "selected_tag": null, + "show_beta": false, + "stars": 0, + "topics": [], + "version_installed": null + }, + "306914292": { + "authors": [], + "category": "theme", + "description": "A transparent blue theme for Home Assistant", + "domain": "", + "downloads": 0, + "full_name": "JOHLC/transparentblue", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "2a37797", + "last_release_tag": "v0.9.5", + "last_updated": "2020-11-22T16:51:57Z", + "name": "transparentblue", + "new": false, + "repository_manifest": { + "name": "Transparent Blue", + "render_readme": "true" + }, + "selected_tag": null, + "show_beta": false, + "stars": 10, + "topics": [ + "hacs", + "home-assistant", + "home-automation", + "homeassistant", + "homeassistant-addons", + "theme", + "transparent-blue-theme", + "transparentblue", + "yaml" + ], + "version_installed": null + }, + "234491154": { + "authors": [], + "category": "theme", + "description": "\ud83c\udfa8 By JuanMTech -- A true black Home Assistant theme for devices with AMOLED displays", + "domain": "", + "downloads": 0, + "full_name": "JuanMTech/amoled_blue", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "cc8f8df", + "last_release_tag": "v1.6", + "last_updated": "2020-07-29T22:51:24Z", + "name": "amoled_blue", + "new": false, + "repository_manifest": { + "name": "AMOLED Blue", + "render_readme": true + }, + "selected_tag": null, + "show_beta": false, + "stars": 10, + "topics": [ + "hacs", + "home-assistant", + "themes" + ], + "version_installed": null + }, + "234022648": { + "authors": [], + "category": "theme", + "description": "\ud83c\udfa8 By JuanMTech -- A Home Assistant theme inspired on the Google app dark mode.", + "domain": "", + "downloads": 0, + "full_name": "JuanMTech/google_dark_theme", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "4c206df", + "last_release_tag": "v1.9", + "last_updated": "2020-08-03T08:18:06Z", + "name": "google_dark_theme", + "new": false, + "repository_manifest": { + "name": "Google Dark Theme", + "render_readme": true + }, + "selected_tag": null, + "show_beta": false, + "stars": 92, + "topics": [ + "hacs", + "home-assistant", + "theme" + ], + "version_installed": null + }, + "235862795": { + "authors": [], + "category": "theme", + "description": "\ud83c\udfa8 By JuanMTech -- A matte black theme with a green accent color", + "domain": "", + "downloads": 0, + "full_name": "JuanMTech/green_dark_mode", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "87fe94d", + "last_release_tag": "v1.7", + "last_updated": "2020-07-29T22:52:55Z", + "name": "green_dark_mode", + "new": false, + "repository_manifest": { + "name": "Green Dark mode", + "render_readme": true + }, + "selected_tag": null, + "show_beta": false, + "stars": 5, + "topics": [ + "hacs", + "home-assistant", + "themes" + ], + "version_installed": null + }, + "235865145": { + "authors": [], + "category": "theme", + "description": "\ud83c\udfa8 By JuanMTech -- A light mode theme with a green accent color", + "domain": "", + "downloads": 0, + "full_name": "JuanMTech/green_light_mode", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "b8bd5e8", + "last_release_tag": "v1.7", + "last_updated": "2020-07-29T22:54:27Z", + "name": "green_light_mode", + "new": false, + "repository_manifest": { + "name": "Green Light mode", + "render_readme": true + }, + "selected_tag": null, + "show_beta": false, + "stars": 1, + "topics": [ + "hacs", + "home-assistant", + "themes" + ], + "version_installed": null + }, + "234032927": { + "authors": [], + "category": "theme", + "description": "\ud83c\udfa8 By JuanMTech -- A Home Assistant theme inspired on the Google app light mode.", + "domain": "", + "downloads": 0, + "full_name": "JuanMTech/google_light_theme", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "61f6adc", + "last_release_tag": "v1.9", + "last_updated": "2020-11-24T15:18:14Z", + "name": "google_light_theme", + "new": false, + "repository_manifest": { + "name": "Google Light Theme", + "render_readme": true + }, + "selected_tag": null, + "show_beta": false, + "stars": 46, + "topics": [ + "assistant-theme", + "hacs", + "themes" + ], + "version_installed": null + }, + "235867730": { + "authors": [], + "category": "theme", + "description": "\ud83c\udfa8 By JuanMTech -- A matte black theme with an orange accent color", + "domain": "", + "downloads": 0, + "full_name": "JuanMTech/orange_dark", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "60213dc", + "last_release_tag": "v1.7", + "last_updated": "2020-07-29T22:55:59Z", + "name": "orange_dark", + "new": false, + "repository_manifest": { + "name": "Orange Dark", + "render_readme": true + }, + "selected_tag": null, + "show_beta": false, + "stars": 4, + "topics": [ + "hacs", + "home-assistant", + "themes" + ], + "version_installed": null + }, + "284294048": { + "authors": [], + "category": "theme", + "description": "\ud83c\udfa8 By JuanMTech -- A Home Assistant theme inspired on the iOS light mode interface.", + "domain": "", + "downloads": 0, + "full_name": "JuanMTech/ios_light_mode_theme", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "ed4802d", + "last_release_tag": "v1.0", + "last_updated": "2020-08-01T17:32:44Z", + "name": "ios_light_mode_theme", + "new": false, + "repository_manifest": { + "name": "iOS Light Mode", + "render_readme": true + }, + "selected_tag": null, + "show_beta": false, + "stars": 4, + "topics": [ + "hacs", + "home-assistant", + "theme" + ], + "version_installed": null + }, + "284293899": { + "authors": [], + "category": "theme", + "description": "\ud83c\udfa8 By JuanMTech -- A Home Assistant theme inspired on the iOS dark mode interface.", + "domain": "", + "downloads": 0, + "full_name": "JuanMTech/ios_dark_mode_theme", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "52c93b0", + "last_release_tag": "v1.0", + "last_updated": "2020-08-01T17:17:45Z", + "name": "ios_dark_mode_theme", + "new": false, + "repository_manifest": { + "name": "iOS Dark Mode", + "render_readme": true + }, + "selected_tag": null, + "show_beta": false, + "stars": 12, + "topics": [ + "hacs", + "home-assistant", + "theme" + ], + "version_installed": null + }, + "235869023": { + "authors": [], + "category": "theme", + "description": "\ud83c\udfa8 By JuanMTech -- A light mode theme with an orange accent color", + "domain": "", + "downloads": 0, + "full_name": "JuanMTech/orange_light", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "af3be93", + "last_release_tag": "v1.5", + "last_updated": "2020-07-29T22:57:13Z", + "name": "orange_light", + "new": false, + "repository_manifest": { + "name": "Orange Light", + "render_readme": true + }, + "selected_tag": null, + "show_beta": false, + "stars": 1, + "topics": [ + "hacs", + "home-assistant", + "themes" + ], + "version_installed": null + }, + "262748544": { + "authors": [], + "category": "theme", + "description": "A milky glass theme for Home Assistant", + "domain": "", + "downloads": 0, + "full_name": "Kibibit/hass-kibibit-theme", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "8a82fda", + "last_release_tag": "v1.1.1", + "last_updated": "2020-12-08T03:22:48Z", + "name": "hass-kibibit-theme", + "new": false, + "repository_manifest": { + "name": "kibibit Theme" + }, + "selected_tag": null, + "show_beta": false, + "stars": 23, + "topics": [], + "version_installed": null + }, + "282325367": { + "authors": [], + "category": "theme", + "description": "\ud83c\udfa8 Home Assistant soft UI dark theme, with help from @JuanMTech, @thomasloven, and @N-l1.", + "domain": "", + "downloads": 0, + "full_name": "KTibow/lovelace-dark-soft-ui-theme", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "196c5ca", + "last_release_tag": "v0.59", + "last_updated": "2020-10-30T01:51:14Z", + "name": "lovelace-dark-soft-ui-theme", + "new": false, + "repository_manifest": { + "name": "Dark Soft UI Theme", + "homeassistant": "0.98.0", + "country": "US" + }, + "selected_tag": null, + "show_beta": false, + "stars": 19, + "topics": [ + "dark-theme", + "hacktoberfest", + "hacktoberfest2020", + "hacs", + "home-assistant", + "homeassistant", + "skin", + "soft-ui", + "theme", + "theme-ui", + "themes" + ], + "version_installed": null + }, + "282315875": { + "authors": [], + "category": "theme", + "description": "\ud83c\udfa8 Home Assistant soft UI light theme, with help from @JuanMTech, @thomasloven, and @N-L1.", + "domain": "", + "downloads": 0, + "full_name": "KTibow/lovelace-light-soft-ui-theme", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "713ba9a", + "last_release_tag": "v0.59", + "last_updated": "2020-10-30T14:17:02Z", + "name": "lovelace-light-soft-ui-theme", + "new": false, + "repository_manifest": { + "name": "Light Soft UI Theme", + "homeassistant": "0.98.0", + "country": "US" + }, + "selected_tag": null, + "show_beta": false, + "stars": 26, + "topics": [ + "hacktoberfest", + "hacktoberfest2020", + "hacs", + "home-assistant", + "homeassistant", + "light-theme", + "skin", + "soft-ui", + "theme", + "theme-ui", + "themes" + ], + "version_installed": null + }, + "292621909": { + "authors": [], + "category": "theme", + "description": "Home Assistant Windows 10 inspired themes", + "domain": "", + "downloads": 0, + "full_name": "mikosoft83/hass-windows10-themes", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "828af6c", + "last_release_tag": "1.2", + "last_updated": "2020-10-25T19:22:14Z", + "name": "hass-windows10-themes", + "new": false, + "repository_manifest": { + "name": "Windows 10 themes", + "render_readme": true + }, + "selected_tag": null, + "show_beta": false, + "stars": 2, + "topics": [ + "accent-color", + "hacs", + "homeassistant", + "theme", + "themes", + "windows", + "windows-10" + ], + "version_installed": null + }, + "221287384": { + "authors": [], + "category": "theme", + "description": "Clear Theme for Home Assistant", + "domain": "", + "downloads": 0, + "full_name": "naofireblade/clear-theme", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "a9f68e1", + "last_release_tag": "v1.1", + "last_updated": "2020-10-08T10:10:49Z", + "name": "clear-theme", + "new": false, + "repository_manifest": { + "name": "Clear Theme", + "homeassistant": "0.102.0" + }, + "selected_tag": null, + "show_beta": false, + "stars": 16, + "topics": [], + "version_installed": null + }, + "221288367": { + "authors": [], + "category": "theme", + "description": "Dark variant of Clear Theme for Home Assistant", + "domain": "", + "downloads": 0, + "full_name": "naofireblade/clear-theme-dark", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "75af86a", + "last_release_tag": "v1.3", + "last_updated": "2020-10-08T10:10:57Z", + "name": "clear-theme-dark", + "new": false, + "repository_manifest": { + "name": "Clear Theme Dark", + "homeassistant": "0.108.9" + }, + "selected_tag": null, + "show_beta": false, + "stars": 11, + "topics": [], + "version_installed": null + }, + "277068969": { + "authors": [], + "category": "theme", + "description": "10 modern colors | 4 categories of styles (Black Glass, Black, Dark, Light) | 40 themes in total | Animated icons for the weather forecast card | And a bonus automatic theme selector for your interface.", + "domain": "", + "downloads": 0, + "full_name": "orickcorreia/caule-themes-pack-1", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "32c7971", + "last_release_tag": "v.1.3", + "last_updated": "2020-08-13T04:45:25Z", + "name": "caule-themes-pack-1", + "new": false, + "repository_manifest": { + "name": "Caule Themes Pack 1 - by caulecriativo.com", + "filename": "caule-themes-pack-1.yaml", + "render_readme": false + }, + "selected_tag": null, + "show_beta": false, + "stars": 98, + "topics": [ + "caule", + "hassio", + "homeassistant", + "lovelace", + "pack", + "themes" + ], + "version_installed": null + }, + "223028160": { + "authors": [], + "category": "theme", + "description": "Green adaptation of this Home-Assistant theme: https://github.com/seangreen2/slate_theme", + "domain": "", + "downloads": 0, + "full_name": "pbeckcom/green_slate_theme", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "c67b3f1", + "last_release_tag": null, + "last_updated": "2019-11-20T22:22:55Z", + "name": "green_slate_theme", + "new": false, + "repository_manifest": { + "name": "Green Slate Theme" + }, + "selected_tag": null, + "show_beta": false, + "stars": 1, + "topics": [], + "version_installed": null + }, + "249722008": { + "authors": [], + "category": "theme", + "description": "Home Assistant theme - A dark, electric blue theme that reminds the movie Your Name. ", + "domain": "", + "downloads": 0, + "full_name": "Nihvel/your_name", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "ed699b5", + "last_release_tag": null, + "last_updated": "2020-10-30T15:28:27Z", + "name": "your_name", + "new": false, + "repository_manifest": { + "name": "Your Name.", + "render_readme": true + }, + "selected_tag": null, + "show_beta": false, + "stars": 14, + "topics": [], + "version_installed": null + }, + "197006509": { + "authors": [], + "category": "theme", + "description": "A Dark Theme for Home Assistant", + "domain": "", + "downloads": 0, + "full_name": "seangreen2/slate_theme", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "c18ae17", + "last_release_tag": null, + "last_updated": "2020-11-21T01:36:26Z", + "name": "slate_theme", + "new": false, + "repository_manifest": {}, + "selected_tag": null, + "show_beta": false, + "stars": 77, + "topics": [ + "hacs", + "home-assistant", + "theme" + ], + "version_installed": null + }, + "226567922": { + "authors": [], + "category": "theme", + "description": "My red\"isch\" home assistant theme.", + "domain": "", + "downloads": 0, + "full_name": "Poeschl/slate_red", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "e3b62d6", + "last_release_tag": "1.2.1", + "last_updated": "2020-05-25T17:04:49Z", + "name": "slate_red", + "new": false, + "repository_manifest": { + "name": "Red slate theme", + "render_readme": "True" + }, + "selected_tag": null, + "show_beta": false, + "stars": 1, + "topics": [ + "hacs", + "home-assistant", + "material-design", + "red", + "theme" + ], + "version_installed": null + }, + "274111031": { + "authors": [], + "category": "theme", + "description": "Animated icons for default Home Assistant weather card", + "domain": "", + "downloads": 0, + "full_name": "wowgamr/animated-weather-card", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "6ed3229", + "last_release_tag": "1.2", + "last_updated": "2020-07-04T13:45:21Z", + "name": "animated-weather-card", + "new": false, + "repository_manifest": { + "name": "Animated Weather Card", + "render_readme": true, + "homeassistant": "0.109.0" + }, + "selected_tag": null, + "show_beta": false, + "stars": 5, + "topics": [ + "hacs", + "home-assistant", + "lovelace", + "theme", + "weather-card" + ], + "version_installed": null + }, + "224374747": { + "authors": [], + "category": "integration", + "description": "\u6590\u8bafDC1\u63d2\u6392\u63a5\u5165Home Assistant\u63d2\u4ef6\uff0c\u672c\u63d2\u4ef6\u539f\u4f5c\u8005NETYJ\uff0c\u6b64\u5904\u4ec5\u4e3aHACS\u5b89\u88c5\u65b9\u4fbf\u4e4b\u7528\u3002", + "domain": "phicomm_dc1", + "downloads": 0, + "full_name": "5high/phicomm-dc1-homeassistant", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "6258236", + "last_release_tag": null, + "last_updated": "2020-11-03T04:15:30Z", + "name": "phicomm_dc1", + "new": false, + "repository_manifest": { + "name": "Phicomm DC1", + "country": "CN", + "render_readme": false, + "domains": [ + "sensor", + "swtich" + ] + }, + "selected_tag": null, + "show_beta": false, + "stars": 5, + "topics": [], + "version_installed": null + }, + "232269564": { + "authors": [ + "@jedmeng" + ], + "category": "integration", + "description": "\u63a7\u5ba2\u5c0fK \u63a5\u5165Home Assistant\uff0c\u652f\u6301\u6700\u65b0\u7248\u672cHA \u76ee\u524d\u6700\u65b0\u7248\u672c\uff080.103\uff09\uff0c\u76f8\u4fe1\u672a\u6765\u7684\u7248\u672c\u4e5f\u53ef\u4ee5\u652f\u6301\u3002", + "domain": "konke", + "downloads": 0, + "full_name": "5high/konke", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "9bfc453", + "last_release_tag": null, + "last_updated": "2020-11-03T04:13:03Z", + "name": "konke", + "new": false, + "repository_manifest": { + "name": "Konke", + "country": "CN", + "render_readme": false, + "domains": [ + "sensor", + "swtich" + ] + }, + "selected_tag": null, + "show_beta": false, + "stars": 8, + "topics": [], + "version_installed": null + }, + "299476136": { + "authors": [ + "@9rpp" + ], + "category": "integration", + "description": "This is a partial implementation of the Securifi RESTful API for Home Assistant", + "domain": "securifi", + "downloads": 0, + "full_name": "9rpp/securifi", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "3b6c2eb", + "last_release_tag": "v1.1", + "last_updated": "2020-10-29T15:54:45Z", + "name": "securifi", + "new": false, + "repository_manifest": { + "name": "Securifi RESTful API", + "render_readme": true + }, + "selected_tag": null, + "show_beta": false, + "stars": 1, + "topics": [ + "automation", + "homeassistant-integration", + "iot", + "securifi" + ], + "version_installed": null + }, + "234514524": { + "authors": [], + "category": "integration", + "description": "HomeAssistant custom component to control your SONOS Alarm", + "domain": "sonos_alarm", + "downloads": 0, + "full_name": "AaronDavidSchneider/SonosAlarm", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "3667c54", + "last_release_tag": "v1.0.8", + "last_updated": "2020-10-14T13:05:49Z", + "name": "sonos_alarm", + "new": false, + "repository_manifest": { + "name": "Sonos Alarm", + "domains": [ + "switch" + ] + }, + "selected_tag": null, + "show_beta": false, + "stars": 9, + "topics": [], + "version_installed": null + }, + "258012483": { + "authors": [ + "@AkA57" + ], + "category": "integration", + "description": "Livebox TV UHD custom component for Home Assistant", + "domain": "liveboxtvuhd", + "downloads": 0, + "full_name": "AkA57/liveboxtvuhd", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "e8a39ed", + "last_release_tag": "0.0.7", + "last_updated": "2020-05-24T19:08:31Z", + "name": "liveboxtvuhd", + "new": false, + "repository_manifest": { + "name": "Livebox TV UHD", + "hacs": "0.24.0", + "domains": [ + "media_player" + ], + "iot_class": "Local Polling", + "homeassistant": "0.108.0" + }, + "selected_tag": null, + "show_beta": false, + "stars": 5, + "topics": [ + "homeassistant", + "livebox" + ], + "version_installed": null + }, + "233575809": { + "authors": [ + "@adamnaj" + ], + "category": "integration", + "description": "The linksys_velop platform allows for presence detection by listing devices connected to your Linksys Velop router.", + "domain": "linksys_velop", + "downloads": 0, + "full_name": "AdamNaj/linksys_velop", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "8cb5d9b", + "last_release_tag": "0.1.1", + "last_updated": "2020-02-11T16:37:00Z", + "name": "linksys_velop", + "new": false, + "repository_manifest": { + "name": "Linksys Velop Device Tracker", + "content_in_root": false, + "domains": [ + "device_tracker" + ], + "homeassistant": "0.100.0" + }, + "selected_tag": null, + "show_beta": false, + "stars": 5, + "topics": [], + "version_installed": null + }, + "169460975": { + "authors": [ + "@akasma74" + ], + "category": "integration", + "description": "It is a fork of \"Yet another take on a home assistant custom alarm\" that will exist until its author is back to our Earth", + "domain": "bwalarm", + "downloads": 0, + "full_name": "akasma74/Hass-Custom-Alarm", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "8bc8091", + "last_release_tag": "v1.12.7", + "last_updated": "2020-09-25T00:08:45Z", + "name": "bwalarm", + "new": false, + "repository_manifest": {}, + "selected_tag": null, + "show_beta": false, + "stars": 64, + "topics": [], + "version_installed": null + }, + "252195707": { + "authors": [ + "@abacao" + ], + "category": "integration", + "description": "Home Assistant Component: Mirubee or Wibeee ", + "domain": "wibeee", + "downloads": 0, + "full_name": "abacao/hass_wibeee", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "77f16da", + "last_release_tag": null, + "last_updated": "2020-09-22T12:14:55Z", + "name": "wibeee", + "new": false, + "repository_manifest": { + "name": "Wibeee - abacao", + "render_readme": true, + "content_in_root": true + }, + "selected_tag": null, + "show_beta": false, + "stars": 1, + "topics": [ + "hacs", + "home-assistant", + "wibeee" + ], + "version_installed": null + }, + "191831638": { + "authors": [ + "@albertogeniola" + ], + "category": "integration", + "description": "Custom component that leverages the Meross IoT library to integrate with Homeassistant", + "domain": "meross_cloud", + "downloads": 0, + "full_name": "albertogeniola/meross-homeassistant", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "fe8f677", + "last_release_tag": "v1.0.5", + "last_updated": "2020-12-06T10:15:10Z", + "name": "meross_cloud", + "new": false, + "repository_manifest": {}, + "selected_tag": null, + "show_beta": false, + "stars": 189, + "topics": [ + "homeassistant", + "meross", + "meross-homeassistant" + ], + "version_installed": null + }, + "222118751": { + "authors": [ + "AlexxIT" + ], + "category": "integration", + "description": "Control Sonoff Devices with eWeLink (original) firmware over LAN and/or Cloud from Home Assistant", + "domain": "sonoff", + "downloads": 0, + "full_name": "AlexxIT/SonoffLAN", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "328cb6e", + "last_release_tag": "v2.3.2", + "last_updated": "2020-12-07T20:43:44Z", + "name": "sonoff", + "new": false, + "repository_manifest": { + "name": "Sonoff LAN", + "render_readme": true + }, + "selected_tag": null, + "show_beta": false, + "stars": 749, + "topics": [ + "ewelink", + "home-assistant", + "sonoff" + ], + "version_installed": null + }, + "272432260": { + "authors": [ + "@algirdasc" + ], + "category": "integration", + "description": "Floureon (Broadlink based) thermostat integration for Home Assistant", + "domain": "floureon", + "downloads": 0, + "full_name": "algirdasc/hass-floureon", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "dfa6bc7", + "last_release_tag": null, + "last_updated": "2020-11-28T11:04:23Z", + "name": "floureon", + "new": false, + "repository_manifest": { + "name": "Floureon Thermostat", + "content_in_root": false, + "domains": [ + "switch", + "climate" + ], + "homeassistant": "0.110.0", + "iot_class": "Local Polling", + "render_readme": true + }, + "selected_tag": null, + "show_beta": false, + "stars": 12, + "topics": [ + "broadlink", + "floureon", + "homeassistant", + "thermostat" + ], + "version_installed": null + }, + "215825339": { + "authors": [ + "@alryaz" + ], + "category": "integration", + "description": "Hekr integration using python-hekr", + "domain": "hekr", + "downloads": 0, + "full_name": "alryaz/hass-hekr-component", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "84af38d", + "last_release_tag": "v0.2.6", + "last_updated": "2020-09-12T10:57:05Z", + "name": "hekr", + "new": false, + "repository_manifest": { + "name": "Hekr Component", + "content_in_root": false, + "filename": false, + "render_readme": false, + "domains": [ + "sensor", + "switch" + ], + "country": false, + "homeassistant": "0.109.0", + "persistent_directory": false, + "iot_class": "Local Polling" + }, + "selected_tag": null, + "show_beta": false, + "stars": 14, + "topics": [ + "consumption", + "hekr", + "homeassistant-components", + "python", + "wisen-application" + ], + "version_installed": null + }, + "125756318": { + "authors": [ + "@amaximus" + ], + "category": "integration", + "description": "HomeAssistant custom component for Budapest public transportation", + "domain": "bkk_stop", + "downloads": 0, + "full_name": "amaximus/bkk_stop", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "ac855b8", + "last_release_tag": "1.4.1", + "last_updated": "2020-11-23T15:07:37Z", + "name": "bkk_stop", + "new": false, + "repository_manifest": {}, + "selected_tag": null, + "show_beta": false, + "stars": 13, + "topics": [ + "bkk", + "budapest", + "homeassistant", + "homeassistant-components", + "transportation" + ], + "version_installed": null + }, + "202483335": { + "authors": [ + "@alryaz" + ], + "category": "integration", + "description": "Home Assistant Mosenergosbyt personal cabinet data and statistics sensor", + "domain": "mosenergosbyt", + "downloads": 0, + "full_name": "alryaz/hass-mosenergosbyt", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "26b6870", + "last_release_tag": "v0.2.8", + "last_updated": "2020-10-21T10:53:39Z", + "name": "mosenergosbyt", + "new": false, + "repository_manifest": { + "name": "Mosenergosbyt (\u041c\u043e\u0441\u044d\u043d\u0435\u0440\u0433\u043e\u0441\u0431\u044b\u0442)", + "content_in_root": false, + "zip_release": false, + "render_readme": false, + "domains": [ + "sensor" + ], + "country": "ru", + "homeassistant": "0.109.0", + "iot_class": "Cloud Polling" + }, + "selected_tag": null, + "show_beta": false, + "stars": 12, + "topics": [ + "electricity-meter", + "homeassistant", + "mosenergosbyt", + "power-consumption" + ], + "version_installed": null + }, + "292720530": { + "authors": [ + "@alryaz", + "@turbo-lab" + ], + "category": "integration", + "description": "Home Assistant custom component for Pandora Car Alarm System", + "domain": "pandora_cas", + "downloads": 0, + "full_name": "alryaz/hass-pandora-cas", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "e1ef276", + "last_release_tag": "v0.0.4", + "last_updated": "2020-09-18T23:27:30Z", + "name": "pandora_cas", + "new": false, + "repository_manifest": { + "name": "Pandora Car Alarm System", + "content_in_root": false, + "zip_release": false, + "render_readme": true, + "domains": [ + "device_tracker", + "binary_sensor", + "sensor", + "lock", + "switch" + ], + "homeassistant": "0.109.0", + "country": [ + "RU", + "BY", + "KZ" + ], + "iot_class": "Cloud Polling" + }, + "selected_tag": null, + "show_beta": false, + "stars": 3, + "topics": [ + "car-system", + "custom-components", + "home-assistant", + "pandora-alarm", + "vehicle-tracking" + ], + "version_installed": null + }, + "257104502": { + "authors": [ + "@amaximus" + ], + "category": "integration", + "description": "FKF Budapest Garbage Collection custom component for Home Assistant", + "domain": "fkf_garbage_collection", + "downloads": 0, + "full_name": "amaximus/fkf-garbage-collection", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "1bed735", + "last_release_tag": null, + "last_updated": "2020-11-23T15:09:48Z", + "name": "fkf_garbage_collection", + "new": false, + "repository_manifest": { + "name": "FKF Budapest Garbage Collection", + "country": [ + "HU" + ], + "domains": [ + "sensor" + ], + "render_readme": "true" + }, + "selected_tag": null, + "show_beta": false, + "stars": 7, + "topics": [ + "budapest", + "home-assistant", + "homeassistant-components" + ], + "version_installed": null + }, + "197920457": { + "authors": [ + "@andersonshatch" + ], + "category": "integration", + "description": "This is a library to allow communicating to a Midea appliance via the Midea cloud.", + "domain": "midea", + "downloads": 0, + "full_name": "andersonshatch/midea-ac-py", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "27c44ad", + "last_release_tag": null, + "last_updated": "2020-08-08T09:23:05Z", + "name": "midea", + "new": false, + "repository_manifest": {}, + "selected_tag": null, + "show_beta": false, + "stars": 42, + "topics": [ + "hacs", + "home-assistant-custom", + "homeassistant", + "midea", + "python" + ], + "version_installed": null + }, + "190378093": { + "authors": [ + "@And3rsL" + ], + "category": "integration", + "description": "Visonic/Bentel/Tyco Alarm System integrtation for Home Assistant", + "domain": "visonicalarm", + "downloads": 0, + "full_name": "And3rsL/VisonicAlarm-for-Hassio", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "5b56159", + "last_release_tag": "3.0.1", + "last_updated": "2020-10-16T09:43:57Z", + "name": "visonicalarm", + "new": false, + "repository_manifest": { + "name": "Visonic/Bentel/Tyco Alarm System" + }, + "selected_tag": null, + "show_beta": false, + "stars": 6, + "topics": [ + "alarm", + "alarm-control-panel", + "bentel", + "home-assistant", + "python", + "sensor", + "tycomonitor", + "visonic" + ], + "version_installed": null + }, + "234290263": { + "authors": [ + "@And3rsL" + ], + "category": "integration", + "description": "Vacuum component for Deebot ozmo 960/950/920/T8", + "domain": "deebot", + "downloads": 0, + "full_name": "And3rsL/Deebot-for-hassio", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "1a731e0", + "last_release_tag": "1.9.7", + "last_updated": "2020-11-06T10:29:53Z", + "name": "deebot", + "new": false, + "repository_manifest": { + "name": "Deebot 960/950/920 vacuum", + "homeassistant": "0.110.0", + "iot_class": "Cloud Polling" + }, + "selected_tag": null, + "show_beta": false, + "stars": 49, + "topics": [ + "deebot", + "deebot920", + "deebot950", + "deebot960", + "deebotozmot8", + "ecovacs", + "hasso", + "homeassistant", + "ozmot8" + ], + "version_installed": null + }, + "160728801": { + "authors": [ + "@asantaga" + ], + "category": "integration", + "description": "Home Assistant Sensor for the LightwaveRF energy monitor", + "domain": "lightwaverf_energy", + "downloads": 0, + "full_name": "asantaga/lightwaverf_HA_EnergySensor", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "1c8098c", + "last_release_tag": "R1.4.2", + "last_updated": "2020-03-29T22:11:07Z", + "name": "lightwaverf_energy", + "new": false, + "repository_manifest": {}, + "selected_tag": null, + "show_beta": false, + "stars": 2, + "topics": [ + "electricity", + "energysensor", + "lightwaverf" + ], + "version_installed": null + }, + "201497401": { + "authors": [ + "@anonym-tsk" + ], + "category": "integration", + "description": "Xiaomi IR Climate Component", + "domain": "xiaomi_remote", + "downloads": 0, + "full_name": "Anonym-tsk/homeassistant-climate-xiaomi-remote", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "82db6c2", + "last_release_tag": null, + "last_updated": "2020-11-10T02:06:19Z", + "name": "xiaomi_remote", + "new": false, + "repository_manifest": { + "name": "Xiaomi IR Climate", + "domains": "climate", + "iot_class": "Local Polling" + }, + "selected_tag": null, + "show_beta": false, + "stars": 21, + "topics": [ + "climate", + "custom-component", + "hacs", + "hassio", + "home-assistant", + "xiaomi" + ], + "version_installed": null + }, + "186347733": { + "authors": [ + "@arjenvrh" + ], + "category": "integration", + "description": "Adds an audi connect integration to home assistant", + "domain": "audiconnect", + "downloads": 0, + "full_name": "arjenvrh/audi_connect_ha", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "42c11ef", + "last_release_tag": null, + "last_updated": "2020-10-30T20:30:21Z", + "name": "audiconnect", + "new": false, + "repository_manifest": { + "name": "Audi connect", + "domains": [ + "sensor", + "binary_sensor", + "switch", + "device_tracker", + "lock" + ], + "homeassistant": "0.110.0" + }, + "selected_tag": null, + "show_beta": false, + "stars": 54, + "topics": [ + "audi", + "audi-connect", + "homeassistant", + "integration", + "sensors" + ], + "version_installed": null + }, + "159080189": { + "authors": [ + "@asantaga", + "@msp1974" + ], + "category": "integration", + "description": "Platform and related climate/sensors to support the Drayton Wiser Home Heating System", + "domain": "wiser", + "downloads": 0, + "full_name": "asantaga/wiserHomeAssistantPlatform", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "3ea9620", + "last_release_tag": "2.4beta1", + "last_updated": "2020-12-04T21:30:57Z", + "name": "wiser", + "new": false, + "repository_manifest": {}, + "selected_tag": null, + "show_beta": false, + "stars": 49, + "topics": [ + "drayton", + "heating", + "homeassistant", + "wiser" + ], + "version_installed": null + }, + "296028613": { + "authors": [ + "@ayavilevich" + ], + "category": "integration", + "description": "A D-Link AP/router device tracker for Home Assistant", + "domain": "dlink_presence", + "downloads": 0, + "full_name": "ayavilevich/homeassistant-dlink-presence", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "d5cc4aa", + "last_release_tag": null, + "last_updated": "2020-10-29T13:41:38Z", + "name": "dlink_presence", + "new": false, + "repository_manifest": { + "name": "D-Link Presence / device_Tracker", + "render_readme": true + }, + "selected_tag": null, + "show_beta": false, + "stars": 1, + "topics": [ + "d-link", + "dlink", + "homeassistant", + "presence-detection" + ], + "version_installed": null + }, + "252926906": { + "authors": [ + "@asev" + ], + "category": "integration", + "description": "Uponor Smatrix Pulse X-265 heating system integration for Home Assistant.", + "domain": "uponor", + "downloads": 0, + "full_name": "asev/homeassistant-uponor", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "74b1f2b", + "last_release_tag": "v0.6.1", + "last_updated": "2020-11-23T10:01:55Z", + "name": "uponor", + "new": false, + "repository_manifest": { + "name": "Uponor Smatrix Pulse X-265 heating", + "iot_class": "Local Polling", + "render_readme": "True" + }, + "selected_tag": null, + "show_beta": false, + "stars": 3, + "topics": [ + "heating-control", + "homeassistant", + "homeassistant-integration", + "smatrix", + "uponor" + ], + "version_installed": null + }, + "240283330": { + "authors": [], + "category": "integration", + "description": "Generic Thermostat climate platform for Home Assistant", + "domain": "generic_hygrostat", + "downloads": 0, + "full_name": "avdeevsv91/ha_generic_hygrostat", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "e5fc89e", + "last_release_tag": null, + "last_updated": "2020-07-01T19:22:17Z", + "name": "generic_hygrostat", + "new": false, + "repository_manifest": { + "name": "Generic Hygrostat", + "render_readme": true, + "domains": [ + "climate" + ], + "homeassistant": "0.91.0" + }, + "selected_tag": null, + "show_beta": false, + "stars": 4, + "topics": [], + "version_installed": null + }, + "223739645": { + "authors": [ + "@SebuZet" + ], + "category": "integration", + "description": "Home Assistant Climate Device for controlling (not only) Samsung AC", + "domain": "climate_ip", + "downloads": 0, + "full_name": "atxbyea/samsungrac", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "0e4ffd9", + "last_release_tag": "3.4", + "last_updated": "2020-11-23T18:40:08Z", + "name": "climate_ip", + "new": false, + "repository_manifest": { + "name": "Climate IP", + "country": "NO", + "domains": [ + "climate", + "switch" + ], + "homeassistant": "0.110.2" + }, + "selected_tag": null, + "show_beta": false, + "stars": 13, + "topics": [ + "airconditioning", + "homeassistant", + "samsung" + ], + "version_installed": null + }, + "250866164": { + "authors": [ + "@azogue" + ], + "category": "integration", + "description": "HomeAssistant custom sensor to track specific events", + "domain": "eventsensor", + "downloads": 0, + "full_name": "azogue/eventsensor", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "7a050eb", + "last_release_tag": "v2.2.0", + "last_updated": "2020-08-23T10:42:11Z", + "name": "eventsensor", + "new": false, + "repository_manifest": { + "name": "Event sensor", + "content_in_root": false, + "domains": [ + "sensor" + ], + "homeassistant": "0.109.0", + "iot_class": "Local Push", + "render_readme": true + }, + "selected_tag": null, + "show_beta": false, + "stars": 32, + "topics": [], + "version_installed": null + }, + "255999506": { + "authors": [ + "@azogue" + ], + "category": "integration", + "description": "HomeAssistant integration to customize Hue polling for sensors and remotes", + "domain": "fasthue", + "downloads": 0, + "full_name": "azogue/fasthue", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "cf550bc", + "last_release_tag": "v1.1.1", + "last_updated": "2020-11-15T14:49:42Z", + "name": "fasthue", + "new": false, + "repository_manifest": { + "name": "Fast-Hue polling", + "content_in_root": false, + "domains": [ + "sensor" + ], + "homeassistant": "0.109.0", + "iot_class": "Local Polling", + "render_readme": true + }, + "selected_tag": null, + "show_beta": false, + "stars": 16, + "topics": [], + "version_installed": null + }, + "256899380": { + "authors": [ + "@atymic" + ], + "category": "integration", + "description": "Project Three Zero Home Assistant Integration", + "domain": "project_zero_three", + "downloads": 0, + "full_name": "atymic/project_three_zero_ha", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "d00cfa6", + "last_release_tag": null, + "last_updated": "2020-04-25T04:04:54Z", + "name": "project_zero_three", + "new": false, + "repository_manifest": { + "name": "Project Three Zero (7-11 Fuel Lock Monitor)", + "render_readme": true + }, + "selected_tag": null, + "show_beta": false, + "stars": 3, + "topics": [ + "fuel", + "home-automation" + ], + "version_installed": null + }, + "237695750": { + "authors": [ + "@bacco007" + ], + "category": "integration", + "description": "OpenNEM Sensor for Home Assistant", + "domain": "opennem", + "downloads": 0, + "full_name": "bacco007/sensor.opennem", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "eaefeaf", + "last_release_tag": "v0.0.5", + "last_updated": "2020-06-28T02:05:39Z", + "name": "opennem", + "new": false, + "repository_manifest": { + "name": "OpenNEM (AU) Data", + "domains": "sensor", + "country": "AU" + }, + "selected_tag": null, + "show_beta": false, + "stars": 0, + "topics": [], + "version_installed": null + }, + "181480967": { + "authors": [ + "@basschipper" + ], + "category": "integration", + "description": "Generic Hygrostat for Home Assistant", + "domain": "generic_hygrostat", + "downloads": 0, + "full_name": "basschipper/homeassistant-generic-hygrostat", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "15999b7", + "last_release_tag": "v0.2.0", + "last_updated": "2020-11-05T11:08:40Z", + "name": "generic_hygrostat", + "new": false, + "repository_manifest": {}, + "selected_tag": null, + "show_beta": false, + "stars": 21, + "topics": [], + "version_installed": null + }, + "229519365": { + "authors": [ + "@bacco007" + ], + "category": "integration", + "description": "Home Assistant Sensor for WaterNSW Real Time Data", + "domain": "waternsw", + "downloads": 0, + "full_name": "bacco007/sensor.waternsw", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "a72e71f", + "last_release_tag": "v0.5", + "last_updated": "2020-06-28T02:03:39Z", + "name": "waternsw", + "new": false, + "repository_manifest": { + "name": "WaterNSW Real Time Data", + "domains": "sensor", + "country": "AU" + }, + "selected_tag": null, + "show_beta": false, + "stars": 1, + "topics": [], + "version_installed": null + }, + "282688934": { + "authors": [ + "@barban-dev" + ], + "category": "integration", + "description": "Home Assistant Custom Integration for EVA II PRO WiFi Smart Dehumidifier appliance by Midea/Inventor.", + "domain": "midea_dehumidifier", + "downloads": 0, + "full_name": "barban-dev/homeassistant-midea-dehumidifier", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "a09f948", + "last_release_tag": null, + "last_updated": "2020-11-27T13:39:00Z", + "name": "midea_dehumidifier", + "new": false, + "repository_manifest": { + "name": "EVA II PRO WiFi Midea Inventor Dehumidifier custom integration", + "domains": [ + "humidifier", + "sensor" + ], + "codeowners": [ + "barban-dev" + ], + "documentation": "https://github.com/barban-dev/homeassistant-midea-dehumidifier/blob/master/README.md", + "issue_tracker": "https://github.com/barban-dev/homeassistant-midea-dehumidifier/issues", + "homeassistant": "0.96.0", + "iot_class": "Cloud Polling" + }, + "selected_tag": null, + "show_beta": false, + "stars": 5, + "topics": [ + "dehumidifier", + "eva-ii-pro-wifi", + "home-automation", + "homeassistant", + "homeassistant-integration", + "internet-of-things", + "inventor", + "iot", + "midea", + "python" + ], + "version_installed": null + }, + "137655647": { + "authors": [ + "@bertbert72" + ], + "category": "integration", + "description": "HomeAssistant component for control of Virgin Media Tivo boxes", + "domain": "virgintivo", + "downloads": 0, + "full_name": "bertbert72/HomeAssistant_VirginTivo", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "24df976", + "last_release_tag": "0.1.14", + "last_updated": "2020-07-16T20:13:02Z", + "name": "virgintivo", + "new": false, + "repository_manifest": {}, + "selected_tag": null, + "show_beta": false, + "stars": 18, + "topics": [], + "version_installed": null + }, + "254203764": { + "authors": [ + "@BSantalucia" + ], + "category": "integration", + "description": "Home assistant custom component to provide monetary account balance sensors for Bunq", + "domain": "bunq", + "downloads": 0, + "full_name": "ben8p/home-assistant-bunq-balance-sensors", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "58be982", + "last_release_tag": null, + "last_updated": "2020-05-20T07:59:20Z", + "name": "bunq", + "new": false, + "repository_manifest": { + "name": "Bunq balance sensor", + "content_in_root": false, + "render_readme": true + }, + "selected_tag": null, + "show_beta": false, + "stars": 1, + "topics": [ + "bunq", + "bunq-api", + "hacs", + "hassio", + "home-assistant", + "home-assistant-custom", + "home-assistant-sensor" + ], + "version_installed": null + }, + "194941760": { + "authors": [ + "@bieniu" + ], + "category": "integration", + "description": "Airly air quality custom integration", + "domain": "airly", + "downloads": 120, + "full_name": "bieniu/ha-airly", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "32cb429", + "last_release_tag": "0.11.6", + "last_updated": "2020-11-14T12:11:40Z", + "name": "airly", + "new": false, + "repository_manifest": { + "name": "Airly", + "homeassistant": "0.118.0", + "iot_class": "cloud_poll", + "domains": [ + "sensor" + ], + "zip_release": true, + "filename": "airly.zip" + }, + "selected_tag": null, + "show_beta": false, + "stars": 13, + "topics": [ + "air-quality", + "airly", + "home-assistant", + "python" + ], + "version_installed": null + }, + "197139082": { + "authors": [ + "@bieniu" + ], + "category": "integration", + "description": "GIO\u015a (Polish G\u0142\u00f3wny Inspektorat Ochrony \u015arodowiska) air quality data integration for Home Assistant", + "domain": "gios", + "downloads": 114, + "full_name": "bieniu/ha-gios", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "082b5d6", + "last_release_tag": "0.8.5", + "last_updated": "2020-09-09T11:21:47Z", + "name": "gios", + "new": false, + "repository_manifest": { + "name": "GIO\u015a", + "homeassistant": "0.115.0", + "iot_class": "cloud_poll", + "domains": [ + "sensor" + ], + "country": "PL", + "zip_release": true, + "filename": "gios.zip" + }, + "selected_tag": null, + "show_beta": false, + "stars": 7, + "topics": [ + "air-quality", + "gios", + "home-assistant", + "python" + ], + "version_installed": null + }, + "203592862": { + "authors": [ + "@blindlight" + ], + "category": "integration", + "description": "USR-R16 integration for Home Assistant", + "domain": "usr_r16", + "downloads": 0, + "full_name": "blindlight86/HA_USR-R16", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "7c81295", + "last_release_tag": null, + "last_updated": "2020-10-20T13:45:02Z", + "name": "usr_r16", + "new": false, + "repository_manifest": { + "name": "USR-R16 16\u8def\u7f51\u7edc\u7ee7\u7535\u5668", + "domains": [ + "switch" + ], + "render_readme": true, + "homeassistant": "0.106.0", + "country": [ + "CN" + ], + "iot_class": [ + "Assumed State", + "Local Push" + ] + }, + "selected_tag": null, + "show_beta": false, + "stars": 4, + "topics": [ + "hacs", + "home-assistant", + "relays" + ], + "version_installed": null + }, + "162808336": { + "authors": [ + "@bigbadblunt" + ], + "category": "integration", + "description": "Lightwave RF custom component for Home Assistant. Requires generation 2 (\"Link Plus\") hub, but will control both generation 1 (\"Connect Series\") and generation 2 (\"Smart Series\") devices.", + "domain": "lightwave2", + "downloads": 0, + "full_name": "bigbadblunt/homeassistant-lightwave2", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "67b9d85", + "last_release_tag": "v2.3.2", + "last_updated": "2020-09-26T09:57:19Z", + "name": "lightwave2", + "new": false, + "repository_manifest": { + "name": "Lightwave RF" + }, + "selected_tag": null, + "show_beta": false, + "stars": 14, + "topics": [ + "home-assistant", + "home-automation", + "homeassistant", + "lightwave", + "lightwaverf" + ], + "version_installed": null + }, + "296946072": { + "authors": [ + "@boralyl" + ], + "category": "integration", + "description": "Custom component to feed recently added tv shows and movies to the custom card \"Upcoming Media Card\" for Home Assistant. ", + "domain": "kodi_recently_added", + "downloads": 0, + "full_name": "boralyl/kodi-recently-added", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "ef3f7bb", + "last_release_tag": "v2.0.0", + "last_updated": "2020-10-23T17:55:21Z", + "name": "kodi_recently_added", + "new": false, + "repository_manifest": { + "config_flow": true, + "documentation": "https://github.com/boralyl/kodi-recently-added", + "domains": [ + "sensor" + ], + "homeassistant": "0.115.0", + "iot_class": "Local Polling", + "name": "Kodi Recently Added Media", + "render_readme": true + }, + "selected_tag": null, + "show_beta": false, + "stars": 0, + "topics": [ + "hacs", + "home-assistant", + "homeassistant", + "homeassistant-integration", + "kodi", + "python" + ], + "version_installed": null + }, + "236146080": { + "authors": [ + "@binsentsu" + ], + "category": "integration", + "description": "Home assistant Component for reading data locally from Solaredge inverter through modbus TCP", + "domain": "solaredge_modbus", + "downloads": 0, + "full_name": "binsentsu/home-assistant-solaredge-modbus", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "e52d368", + "last_release_tag": "V1.2.1", + "last_updated": "2020-10-24T19:45:46Z", + "name": "solaredge_modbus", + "new": false, + "repository_manifest": { + "name": "Solaredge Modbus", + "content_in_root": false, + "domains": [ + "sensor" + ], + "homeassistant": "0.109.0", + "iot_class": "local_poll" + }, + "selected_tag": null, + "show_beta": false, + "stars": 10, + "topics": [ + "hassio", + "home-assistant", + "modbus", + "modbus-tcp", + "solaredge", + "solaredge-inverter" + ], + "version_installed": null + }, + "259865897": { + "authors": [ + "Bouni" + ], + "category": "integration", + "description": "DRK Blutspende component for Home Assistant ", + "domain": "drkblutspende", + "downloads": 0, + "full_name": "Bouni/drkblutspende", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "9c7fcb7", + "last_release_tag": "0.1.3", + "last_updated": "2020-07-21T07:14:11Z", + "name": "drkblutspende", + "new": false, + "repository_manifest": { + "name": "DRK Blutspende", + "country": "DE", + "render_readme": true + }, + "selected_tag": null, + "show_beta": false, + "stars": 3, + "topics": [], + "version_installed": null + }, + "250688607": { + "authors": [ + "@boralyl" + ], + "category": "integration", + "description": "A home assistant integration that monitors games on sale on your Steam wishlist.", + "domain": "steam_wishlist", + "downloads": 0, + "full_name": "boralyl/steam-wishlist", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "1181fd6", + "last_release_tag": "v1.3.1", + "last_updated": "2020-11-17T05:04:06Z", + "name": "steam_wishlist", + "new": false, + "repository_manifest": { + "codeowners": [ + "@boralyl" + ], + "config_flow": true, + "documentation": "https://github.com/boralyl/steam-wishlist", + "domains": [ + "binary_sensor", + "sensor" + ], + "homeassistant": "0.108.0", + "iot_class": "Cloud Polling", + "name": "Steam Wishlist", + "render_readme": true + }, + "selected_tag": null, + "show_beta": false, + "stars": 6, + "topics": [ + "hacs", + "home-assistant", + "homeassistant", + "python", + "steam" + ], + "version_installed": null + }, + "190260910": { + "authors": [ + "@bramkragten" + ], + "category": "integration", + "description": "Add support for Honeywell Lyric thermostats in Home Assistant", + "domain": "lyric", + "downloads": 0, + "full_name": "bramkragten/lyric", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "383e45e", + "last_release_tag": "v1.0.4", + "last_updated": "2020-04-20T19:37:23Z", + "name": "lyric", + "new": false, + "repository_manifest": {}, + "selected_tag": null, + "show_beta": false, + "stars": 11, + "topics": [], + "version_installed": null + }, + "190260955": { + "authors": [ + "@bramkragten" + ], + "category": "integration", + "description": "Add support for Mind Mobility vehicles in Home Assistant", + "domain": "mind", + "downloads": 0, + "full_name": "bramkragten/mind", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "b74eb0b", + "last_release_tag": "v1.2.0", + "last_updated": "2020-09-01T14:51:23Z", + "name": "mind", + "new": false, + "repository_manifest": {}, + "selected_tag": null, + "show_beta": false, + "stars": 3, + "topics": [], + "version_installed": null + }, + "192086849": { + "authors": [ + "bouwew" + ], + "category": "integration", + "description": "GoodWe SEMS MQTT-componenent for Home Assistant", + "domain": "sems2mqtt", + "downloads": 0, + "full_name": "bouwew/sems2mqtt", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "77a827f", + "last_release_tag": "v0.1.10", + "last_updated": "2020-05-04T10:59:55Z", + "name": "sems2mqtt", + "new": false, + "repository_manifest": {}, + "selected_tag": null, + "show_beta": false, + "stars": 4, + "topics": [], + "version_installed": null + }, + "260169906": { + "authors": [ + "@bouni" + ], + "category": "integration", + "description": "Luxtronik integration for Home Assistant", + "domain": "luxtronik", + "downloads": 0, + "full_name": "Bouni/luxtronik", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "c6d5903", + "last_release_tag": "0.1.12", + "last_updated": "2020-11-23T06:18:55Z", + "name": "luxtronik", + "new": false, + "repository_manifest": { + "name": "Luxtronik", + "render_readme": true + }, + "selected_tag": null, + "show_beta": false, + "stars": 11, + "topics": [ + "hacktoberfest" + ], + "version_installed": null + }, + "284910970": { + "authors": [ + "@Bre77" + ], + "category": "integration", + "description": "Home Assistant Climate Integration for MyAir, a ducted air conditioning controller that runs on a wall mounted Android tablet.", + "domain": "myair", + "downloads": 0, + "full_name": "Bre77/myair", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "0e805ee", + "last_release_tag": null, + "last_updated": "2020-10-08T07:49:59Z", + "name": "myair", + "new": false, + "repository_manifest": { + "name": "MyAir", + "content_in_root": false, + "render_readme": true, + "domains": [ + "climate", + "binary_sensor", + "sensor", + "cover" + ], + "country": "AU", + "iot_class": "Local Polling" + }, + "selected_tag": null, + "show_beta": false, + "stars": 7, + "topics": [ + "climate", + "homeassistant-integration", + "myair" + ], + "version_installed": null + }, + "284006518": { + "authors": [ + "@bremor" + ], + "category": "integration", + "description": "Reverse engineered implementation of the Bonaire MyClimate app.", + "domain": "bonaire_myclimate", + "downloads": 0, + "full_name": "bremor/bonaire_myclimate", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "aa535f9", + "last_release_tag": "0.6.1", + "last_updated": "2020-10-25T22:46:05Z", + "name": "bonaire_myclimate", + "new": false, + "repository_manifest": { + "name": "Bonaire MyClimate", + "render_readme": "true", + "iot_class": "Local Push" + }, + "selected_tag": null, + "show_beta": false, + "stars": 3, + "topics": [ + "bonaire", + "bonaire-myclimate", + "climate", + "hacktoberfest", + "hacs", + "myclimate" + ], + "version_installed": null + }, + "283508779": { + "authors": [ + "@brefra" + ], + "category": "integration", + "description": "Plugwise USB-stick integration to support Circle+, Circle and Stealth devices", + "domain": "plugwise_stick", + "downloads": 0, + "full_name": "brefra/home-assistant-plugwise-stick", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "7887e28", + "last_release_tag": "v2.0.0", + "last_updated": "2020-10-07T18:56:15Z", + "name": "plugwise_stick", + "new": false, + "repository_manifest": { + "name": "Plugwise USB-Stick", + "domains": [ + "switch", + "sensor" + ], + "homeassistant": "0.113.2", + "iot_class": [ + "Local Push" + ] + }, + "selected_tag": null, + "show_beta": false, + "stars": 1, + "topics": [ + "circle", + "plug", + "plugwise", + "stealth", + "usb-stick" + ], + "version_installed": null + }, + "307586942": { + "authors": [ + "@bremor" + ], + "category": "integration", + "description": "Custom component for retrieving weather information from the Bureau of Meteorology.", + "domain": "bureau_of_meteorology", + "downloads": 0, + "full_name": "bremor/bureau_of_meteorology", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "3ed3d9f", + "last_release_tag": "0.9.1", + "last_updated": "2020-12-06T22:11:29Z", + "name": "bureau_of_meteorology", + "new": false, + "repository_manifest": { + "name": "Bureau of Meteorology", + "render_readme": "true", + "iot_class": "Cloud Polling" + }, + "selected_tag": null, + "show_beta": false, + "stars": 29, + "topics": [ + "bom", + "bureau", + "forecast", + "hacs", + "meteorology", + "observations", + "weather", + "weather-information" + ], + "version_installed": null + }, + "282427417": { + "authors": [ + "@bremor" + ], + "category": "integration", + "description": "Custom component for retrieving departure times for Public Transport Victoria.", + "domain": "public_transport_victoria", + "downloads": 0, + "full_name": "bremor/public_transport_victoria", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "d9ee1fc", + "last_release_tag": "0.3", + "last_updated": "2020-11-29T03:23:55Z", + "name": "public_transport_victoria", + "new": false, + "repository_manifest": { + "name": "Public Transport Victoria", + "render_readme": "true", + "iot_class": "Cloud Polling" + }, + "selected_tag": null, + "show_beta": false, + "stars": 1, + "topics": [ + "australia", + "bus", + "hacktoberfest", + "hacs", + "ptv", + "public", + "train", + "tram", + "transport", + "victoria" + ], + "version_installed": null + }, + "175156605": { + "authors": [ + "@briis" + ], + "category": "integration", + "description": "WeatherFlow Smart Weather Component for Home Assistant", + "domain": "smartweather", + "downloads": 0, + "full_name": "briis/smartweather", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "8f7f1ae", + "last_release_tag": "v2.0.5", + "last_updated": "2020-11-21T13:29:51Z", + "name": "smartweather", + "new": false, + "repository_manifest": { + "name": "WeatherFlow Smart Weather", + "render_readme": true, + "domains": [ + "binary_sensor", + "sensor", + "weather" + ], + "iot_class": [ + "Cloud Polling" + ] + }, + "selected_tag": null, + "show_beta": false, + "stars": 61, + "topics": [ + "darksky", + "home-assistant", + "python", + "weatherflow" + ], + "version_installed": null + }, + "262854926": { + "authors": [ + "@briis" + ], + "category": "integration", + "description": "The Meteobridge Integration adds support for retrieving current weather data from a Meteobridge datalogger connected to a local Weather Station.", + "domain": "meteobridge", + "downloads": 0, + "full_name": "briis/meteobridge", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "99af59f", + "last_release_tag": "v2.6", + "last_updated": "2020-11-02T09:03:29Z", + "name": "meteobridge", + "new": false, + "repository_manifest": { + "name": "Meteobridge Datalogger Integration", + "content_in_root": false, + "render_readme": true, + "domains": [ + "binary_sensor", + "sensor" + ], + "iot_class": "Local Polling", + "homeassistant": "0.109.0" + }, + "selected_tag": null, + "show_beta": false, + "stars": 3, + "topics": [ + "home-assistant", + "meteobridge" + ], + "version_installed": null + }, + "230199787": { + "authors": [ + "@briis" + ], + "category": "integration", + "description": "Control and monitor your Unifi Protect Cameras from Home Assistant", + "domain": "unifiprotect", + "downloads": 0, + "full_name": "briis/unifiprotect", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "4cd2eb5", + "last_release_tag": "v0.6.6", + "last_updated": "2020-12-02T20:14:40Z", + "name": "unifiprotect", + "new": false, + "repository_manifest": { + "name": "Unifi Protect Integration", + "domains": [ + "binary_sensor", + "sensor", + "camera", + "switch" + ], + "iot_class": [ + "Local Polling", + "Local Push" + ] + }, + "selected_tag": null, + "show_beta": false, + "stars": 222, + "topics": [ + "cloudkey", + "home-assistant", + "homeassistant", + "protect", + "python", + "ubiquiti", + "unifi", + "unifi-protect", + "unifi-protect-server" + ], + "version_installed": null + }, + "202990294": { + "authors": [ + "@bruxy70" + ], + "category": "integration", + "description": "\ud83d\ude8d Home Assistant custom sensor for finding Czech Public Transportation Connections", + "domain": "cz_pub_tran", + "downloads": 51, + "full_name": "bruxy70/CZ-Public-Transport", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "118a11c", + "last_release_tag": "2.9", + "last_updated": "2020-10-05T20:28:23Z", + "name": "cz_pub_tran", + "new": false, + "repository_manifest": { + "name": "CZ Public Transport", + "domains": [ + "cz_pub_tran", + "sensor" + ], + "homeassistant": "0.109.0", + "zip_release": true, + "filename": "cz_pub_tran.zip", + "country": "CZ", + "iot_class": "Cloud Polling" + }, + "selected_tag": null, + "show_beta": false, + "stars": 7, + "topics": [ + "chaps", + "crws", + "departure-times", + "idos", + "public-transportation" + ], + "version_installed": null + }, + "261970408": { + "authors": [ + "@briis" + ], + "category": "integration", + "description": "The weatherbit integration adds support for the weatherbit.io web service as a source for meteorological data for your location.", + "domain": "weatherbit", + "downloads": 0, + "full_name": "briis/weatherbit", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "b9aed34", + "last_release_tag": "v0.34.1", + "last_updated": "2020-11-22T11:49:52Z", + "name": "weatherbit", + "new": false, + "repository_manifest": { + "name": "Weatherbit Weather Forecast for Home Assistant", + "content_in_root": false, + "render_readme": true, + "domains": [ + "weather" + ], + "iot_class": "Cloud Polling", + "homeassistant": "0.106.0" + }, + "selected_tag": null, + "show_beta": false, + "stars": 17, + "topics": [ + "home-assistant", + "meteorological-data", + "weather-forecast", + "weatherbit" + ], + "version_installed": null + }, + "204200635": { + "authors": [ + "@bruxy70" + ], + "category": "integration", + "description": "\ud83d\uddd1 Custom Home Assistant sensor for scheduling garbage collection (or other regularly re-occurring events - weekly on given days, semi-weekly or monthly)", + "domain": "garbage_collection", + "downloads": 2537, + "full_name": "bruxy70/Garbage-Collection", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "38ee001", + "last_release_tag": "3.10", + "last_updated": "2020-11-25T07:44:35Z", + "name": "garbage_collection", + "new": false, + "repository_manifest": { + "name": "Garbage Collection", + "zip_release": true, + "filename": "garbage_collection.zip", + "domains": [ + "garbage_collection", + "sensor" + ], + "homeassistant": "0.110.0", + "iot_class": "Local Push" + }, + "selected_tag": null, + "show_beta": false, + "stars": 113, + "topics": [ + "garbage-collection", + "schedule", + "waste", + "waste-management" + ], + "version_installed": null + }, + "189680764": { + "authors": [], + "category": "integration", + "description": "Screenly media player custom component for Home Assistant.", + "domain": "screenly", + "downloads": 0, + "full_name": "burnnat/media_player.screenly", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "078fee5", + "last_release_tag": "v0.0.5", + "last_updated": "2019-09-27T05:19:26Z", + "name": "screenly", + "new": false, + "repository_manifest": {}, + "selected_tag": null, + "show_beta": false, + "stars": 3, + "topics": [], + "version_installed": null + }, + "229872760": { + "authors": [], + "category": "integration", + "description": "Home Assistant integration to push fitness data to remote services.", + "domain": "fitness_push", + "downloads": 0, + "full_name": "burnnat/ha-fitness-push", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "8d80568", + "last_release_tag": "v0.0.1", + "last_updated": "2020-01-24T00:27:12Z", + "name": "fitness_push", + "new": false, + "repository_manifest": { + "name": "Fitness Push", + "content_in_root": false, + "domains": [ + "fitness_push" + ], + "iot_class": "Cloud Push" + }, + "selected_tag": null, + "show_beta": false, + "stars": 0, + "topics": [], + "version_installed": null + }, + "210269734": { + "authors": [], + "category": "integration", + "description": "HDHomeRun integration for Home Assistant.", + "domain": "hdhomerun", + "downloads": 0, + "full_name": "burnnat/ha-hdhomerun", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "773d0ab", + "last_release_tag": "v0.0.7", + "last_updated": "2020-05-22T11:53:36Z", + "name": "hdhomerun", + "new": false, + "repository_manifest": { + "name": "HDHomeRun", + "content_in_root": false, + "domains": [ + "sensor" + ], + "iot_class": "Local Polling" + }, + "selected_tag": null, + "show_beta": false, + "stars": 7, + "topics": [], + "version_installed": null + }, + "211397713": { + "authors": [], + "category": "integration", + "description": "Polar fitness tracking integration for Home Assistant.", + "domain": "polar", + "downloads": 0, + "full_name": "burnnat/ha-polar", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "342cd0c", + "last_release_tag": "v0.0.2", + "last_updated": "2020-10-01T13:16:14Z", + "name": "polar", + "new": false, + "repository_manifest": { + "name": "Polar", + "content_in_root": false, + "domains": [ + "sensor" + ], + "iot_class": "Local Polling" + }, + "selected_tag": null, + "show_beta": false, + "stars": 2, + "topics": [], + "version_installed": null + }, + "201445202": { + "authors": [ + "@Vloris", + "@Cadsters", + "@aritmeester" + ], + "category": "integration", + "description": "\ud83d\uddd1\ufe0f Component for bin/waste collection by acv-groep", + "domain": "acv", + "downloads": 0, + "full_name": "Cadsters/acv-hass-component", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "00442e3", + "last_release_tag": "0.22", + "last_updated": "2020-04-25T17:52:55Z", + "name": "acv", + "new": false, + "repository_manifest": {}, + "selected_tag": null, + "show_beta": false, + "stars": 3, + "topics": [ + "acv-groep", + "component", + "home-assistant", + "homeassistant", + "python3", + "trash", + "waste" + ], + "version_installed": null + }, + "262017793": { + "authors": [ + "@cagnulein" + ], + "category": "integration", + "description": "This is a simple project that manage the Switchbot ( https://amzn.to/3dnliBD ) that has only the \"press\" ability in Home Assistant.", + "domain": "switchbot_press", + "downloads": 0, + "full_name": "cagnulein/switchbot_press", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "a7aedf5", + "last_release_tag": null, + "last_updated": "2020-05-07T10:35:37Z", + "name": "switchbot_press", + "new": false, + "repository_manifest": { + "name": "Switchbot_press", + "render_readme": true, + "domains": [ + "switch" + ] + }, + "selected_tag": null, + "show_beta": false, + "stars": 0, + "topics": [ + "home-assistant", + "homeassistant", + "integration", + "python3", + "switchbot" + ], + "version_installed": null + }, + "207620142": { + "authors": [ + "@caiosweet" + ], + "category": "integration", + "description": "Italy METEO-HYDRO ALERT (Protezione Civile)", + "domain": "dpc", + "downloads": 0, + "full_name": "caiosweet/Home-Assistant-custom-components-DPC-Alert", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "456cb01", + "last_release_tag": "v1.2.7", + "last_updated": "2020-11-22T23:06:27Z", + "name": "dpc", + "new": false, + "repository_manifest": { + "name": "DPC binary sensor", + "domains": [ + "binary_sensor" + ], + "country": [ + "IT" + ], + "render_readme": true + }, + "selected_tag": null, + "show_beta": false, + "stars": 18, + "topics": [ + "dpc", + "hacs", + "homeassistant", + "protezionecivile", + "sensor" + ], + "version_installed": null + }, + "191563578": { + "authors": [ + "@Ceerbeerus" + ], + "category": "integration", + "description": "Gives you information about the latest beer available at Systembolaget in Sweden, also known as \"Tillf\u00e4lligt sortiment\".", + "domain": "beerbolaget", + "downloads": 0, + "full_name": "Ceerbeerus/beerbolaget", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "379f4a7", + "last_release_tag": "0.4.11", + "last_updated": "2020-05-20T15:25:17Z", + "name": "beerbolaget", + "new": false, + "repository_manifest": {}, + "selected_tag": null, + "show_beta": false, + "stars": 3, + "topics": [], + "version_installed": null + }, + "307503425": { + "authors": [ + "@exxamalte", + "@caiosweet" + ], + "category": "integration", + "description": "INGV - National Institute of Geophysics and Volcanology [Istituto Nazionale di Geofisica e Vulcanologia] Terremoti Italia.", + "domain": "ingv_centro_nazionale_terremoti", + "downloads": 0, + "full_name": "caiosweet/Home-Assistant-custom-components-INGV", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "7e4f8f8", + "last_release_tag": "v1.0.2", + "last_updated": "2020-11-23T18:47:10Z", + "name": "ingv_centro_nazionale_terremoti", + "new": false, + "repository_manifest": { + "name": "INGV Istituto Nazionale di Geofisica e Vulcanologia", + "domains": [ + "geo_location" + ], + "country": [ + "IT" + ], + "render_readme": true, + "iot_class": [ + "Cloud Polling" + ] + }, + "selected_tag": null, + "show_beta": false, + "stars": 2, + "topics": [ + "assistant", + "geofisica", + "home", + "homeassistant", + "ingv", + "terremoti", + "vulcanologia" + ], + "version_installed": null + }, + "179347477": { + "authors": [ + "@claytonjn" + ], + "category": "integration", + "description": "Circadian Lighting custom component for Home Assistant", + "domain": "circadian_lighting", + "downloads": 0, + "full_name": "claytonjn/hass-circadian_lighting", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "6a919ae", + "last_release_tag": "1.0.13", + "last_updated": "2020-10-07T16:31:14Z", + "name": "circadian_lighting", + "new": false, + "repository_manifest": {}, + "selected_tag": null, + "show_beta": false, + "stars": 274, + "topics": [ + "circadian", + "circadian-rhythms", + "hacs", + "home-assistant", + "lighting", + "sleep", + "wellness" + ], + "version_installed": null + }, + "224560492": { + "authors": [ + "@claudegel" + ], + "category": "integration", + "description": "Neviweb custom component for Home Assistant to manage devices connected via a GT130 ", + "domain": "neviweb130", + "downloads": 0, + "full_name": "claudegel/sinope-130", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "2e2954d", + "last_release_tag": "v0.1.7", + "last_updated": "2020-10-29T00:06:40Z", + "name": "neviweb130", + "new": false, + "repository_manifest": { + "name": "Sinope Neviweb130", + "content_in_root": false, + "zip_release": false, + "filename": false, + "render_readme": true, + "domains": [ + "sensor", + "switch", + "climate", + "light" + ], + "country": [ + "CA" + ], + "homeassistant": "0.110.0", + "iot_class": "Cloud Polling" + }, + "selected_tag": null, + "show_beta": false, + "stars": 4, + "topics": [], + "version_installed": null + }, + "195594888": { + "authors": [ + "@claudegel" + ], + "category": "integration", + "description": "Sinope custom component for Home Assistant to manage Sinop\u00e9 devices directly via the GT125 gateway", + "domain": "sinope", + "downloads": 0, + "full_name": "claudegel/sinope-gt125", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "962daf5", + "last_release_tag": "v1.1.6", + "last_updated": "2020-12-01T01:43:58Z", + "name": "sinope", + "new": false, + "repository_manifest": { + "name": "Sinope GT125", + "content_in_root": false, + "zip_release": false, + "filename": false, + "render_readme": true, + "domains": [ + "switch", + "climate", + "light" + ], + "country": [ + "CA" + ], + "homeassistant": "0.110.0", + "iot_class": "Local Polling" + }, + "selected_tag": null, + "show_beta": false, + "stars": 8, + "topics": [], + "version_installed": null + }, + "180440465": { + "authors": [ + "@ludeeus" + ], + "category": "integration", + "description": "Component to show potential breaking_changes in the current published version based on your loaded components", + "domain": "breaking_changes", + "downloads": 0, + "full_name": "custom-components/breaking_changes", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "4dcf67c", + "last_release_tag": "0.4.1", + "last_updated": "2020-09-26T00:49:55Z", + "name": "breaking_changes", + "new": false, + "repository_manifest": {}, + "selected_tag": null, + "show_beta": false, + "stars": 53, + "topics": [ + "breaking-changes", + "custom-component", + "home-assistant", + "homeassistant", + "python" + ], + "version_installed": null + }, + "304573324": { + "authors": [ + "@dgomes", + "@crowbarz" + ], + "category": "integration", + "description": "Updated SQL integration for Home Assistant that supports JSON attributes", + "domain": "sql_json", + "downloads": 0, + "full_name": "crowbarz/ha-sql_json", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "4d00ea4", + "last_release_tag": null, + "last_updated": "2020-10-20T13:37:59Z", + "name": "sql_json", + "new": false, + "repository_manifest": { + "name": "SQL (with JSON detection)", + "render_readme": true, + "domains": [ + "sensor" + ], + "homeassistant": "0.99.9", + "persistent_directory": "userfiles", + "iot_class": [ + "Local Polling", + "Cloud Polling" + ] + }, + "selected_tag": null, + "show_beta": false, + "stars": 1, + "topics": [ + "hacs", + "home-assistant", + "integration", + "json", + "sql" + ], + "version_installed": null + }, + "127689312": { + "authors": [ + "@claudegel" + ], + "category": "integration", + "description": "Neviweb Custom Component for Home Assistant to manage devices connected via GT125", + "domain": "neviweb", + "downloads": 0, + "full_name": "claudegel/sinope-1", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "1666fc0", + "last_release_tag": "1.2.5", + "last_updated": "2020-11-24T01:54:14Z", + "name": "neviweb", + "new": false, + "repository_manifest": { + "name": "Sinope Neviweb", + "content_in_root": false, + "zip_release": false, + "filename": false, + "render_readme": true, + "domains": [ + "climate", + "light", + "switch" + ], + "country": [ + "CA" + ], + "homeassistant": "0.110.0", + "iot_class": "Cloud Polling" + }, + "selected_tag": null, + "show_beta": false, + "stars": 8, + "topics": [], + "version_installed": null + }, + "139892990": { + "authors": [ + "@ludeeus" + ], + "category": "integration", + "description": "\ud83c\udf7b Display information about random beers from Brewdog as a sensor in Home Assistant, you can use this in a push notification next time you visit a bar.", + "domain": "brewdog", + "downloads": 0, + "full_name": "custom-components/brewdog", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "0b938d4", + "last_release_tag": null, + "last_updated": "2019-07-23T07:41:23Z", + "name": "brewdog", + "new": false, + "repository_manifest": {}, + "selected_tag": null, + "show_beta": false, + "stars": 3, + "topics": [ + "api", + "brewdog", + "custom-components", + "home-assistant", + "homeassistant", + "integration", + "punkapi" + ], + "version_installed": null + }, + "140169152": { + "authors": [ + "@ludeeus" + ], + "category": "integration", + "description": "A platform which allows you to get information about sucessfull logins to Home Assistant.", + "domain": "authenticated", + "downloads": 0, + "full_name": "custom-components/authenticated", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "6a265b4", + "last_release_tag": "1.2.2", + "last_updated": "2020-11-25T19:39:16Z", + "name": "authenticated", + "new": false, + "repository_manifest": {}, + "selected_tag": null, + "show_beta": false, + "stars": 56, + "topics": [ + "custom-components", + "home-assistant", + "homeassistant", + "security" + ], + "version_installed": null + }, + "223993584": { + "authors": [ + "@Magalex2x14", + "@Ernst79" + ], + "category": "integration", + "description": "Xiaomi Mijia BLE MiBeacon monitor", + "domain": "ble_monitor", + "downloads": 0, + "full_name": "custom-components/ble_monitor", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "5ce9c11", + "last_release_tag": "0.8.5", + "last_updated": "2020-12-08T16:44:32Z", + "name": "ble_monitor", + "new": false, + "repository_manifest": { + "name": "Passive BLE monitor integration (Xiaomi Mijia BLE MiBeacon monitor)", + "domains": "ble_monitor", + "iot_class": "local push" + }, + "selected_tag": null, + "show_beta": false, + "stars": 422, + "topics": [ + "custom-component", + "hacs", + "hassio", + "home-assistant", + "homeassistant", + "mibeacon", + "mijia", + "mitemp-bt", + "xiaomi", + "xiaomi-sensors" + ], + "version_installed": null + }, + "139664351": { + "authors": [ + "@keatontaylor", + "@alandtse" + ], + "category": "integration", + "description": "This is a custom component to allow control of Amazon Alexa devices in Home Assistant using the unofficial Alexa API.", + "domain": "alexa_media", + "downloads": 2661, + "full_name": "custom-components/alexa_media_player", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "262ee5d", + "last_release_tag": "v3.4.0", + "last_updated": "2020-12-05T22:03:13Z", + "name": "alexa_media", + "new": false, + "repository_manifest": { + "name": "Alexa Media Player", + "content_in_root": false, + "domains": [ + "media_player", + "switch", + "alarm_control_panel", + "sensor" + ], + "iot_class": "cloud_poll", + "zip_release": true, + "filename": "alexa_media.zip", + "homeassistant": "0.106.0" + }, + "selected_tag": null, + "show_beta": false, + "stars": 564, + "topics": [ + "alexa", + "hacktoberfest", + "home-assistant" + ], + "version_installed": null + }, + "146510412": { + "authors": [ + "@gerard33" + ], + "category": "integration", + "description": "E-Thermostaat (ICY) component for Home Assistant", + "domain": "e_thermostaat", + "downloads": 0, + "full_name": "custom-components/climate.e_thermostaat", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "18bb146", + "last_release_tag": "0.3.5", + "last_updated": "2020-05-20T20:29:54Z", + "name": "e_thermostaat", + "new": false, + "repository_manifest": { + "name": "ICY E-thermostaat", + "domains": [ + "climate" + ], + "iot_class": "Cloud Polling", + "homeassistant": "0.96.0" + }, + "selected_tag": null, + "show_beta": false, + "stars": 2, + "topics": [ + "e-thermostaat", + "home-assistant", + "icy" + ], + "version_installed": null + }, + "143741845": { + "authors": [ + "@ludeeus" + ], + "category": "integration", + "description": "A camera platform that give you a combined feed of your defined camera entities.", + "domain": "combined", + "downloads": 0, + "full_name": "custom-components/combined", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "c5a00d0", + "last_release_tag": "0.1.0", + "last_updated": "2019-07-20T07:52:03Z", + "name": "combined", + "new": false, + "repository_manifest": {}, + "selected_tag": null, + "show_beta": false, + "stars": 15, + "topics": [], + "version_installed": null + }, + "173563704": { + "authors": [], + "category": "integration", + "description": "Programmable thermostat that let you have a smart thermostat on budget.", + "domain": "programmable_thermostat", + "downloads": 0, + "full_name": "custom-components/climate.programmable_thermostat", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "3098c95", + "last_release_tag": "7.2", + "last_updated": "2020-11-26T20:21:01Z", + "name": "programmable_thermostat", + "new": false, + "repository_manifest": {}, + "selected_tag": null, + "show_beta": false, + "stars": 44, + "topics": [], + "version_installed": null + }, + "172800356": { + "authors": [ + "@ludeeus" + ], + "category": "integration", + "description": "Run the CLI config_check from a service call.", + "domain": "config_check", + "downloads": 0, + "full_name": "custom-components/config_check", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "bcaeacc", + "last_release_tag": null, + "last_updated": "2019-10-22T08:35:09Z", + "name": "config_check", + "new": false, + "repository_manifest": {}, + "selected_tag": null, + "show_beta": false, + "stars": 5, + "topics": [ + "check", + "config", + "custom-components", + "customcomponents", + "home-assistant", + "homeassistant" + ], + "version_installed": null + }, + "209854868": { + "authors": [], + "category": "integration", + "description": "The fedex platform allows one to track deliveries by FedEx", + "domain": "fedex", + "downloads": 0, + "full_name": "custom-components/fedex", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "4b1ae50", + "last_release_tag": null, + "last_updated": "2020-01-18T17:31:45Z", + "name": "fedex", + "new": false, + "repository_manifest": { + "name": "FedEx", + "country": "US" + }, + "selected_tag": null, + "show_beta": false, + "stars": 3, + "topics": [], + "version_installed": null + }, + "145180996": { + "authors": [ + "@iantrich" + ], + "category": "integration", + "description": "\ud83d\udcf0 RSS Feed Integration", + "domain": "feedparser", + "downloads": 0, + "full_name": "custom-components/feedparser", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "d008ecd", + "last_release_tag": "0.1.1", + "last_updated": "2020-07-01T03:35:06Z", + "name": "feedparser", + "new": false, + "repository_manifest": {}, + "selected_tag": null, + "show_beta": false, + "stars": 38, + "topics": [], + "version_installed": null + }, + "201963665": { + "authors": [ + "@ludeeus" + ], + "category": "integration", + "description": "Update and display the status of your healthchecks.io checks.", + "domain": "healthchecksio", + "downloads": 0, + "full_name": "custom-components/healthchecksio", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "66fde67", + "last_release_tag": "0.1.3", + "last_updated": "2020-11-01T14:09:46Z", + "name": "healthchecksio", + "new": false, + "repository_manifest": {}, + "selected_tag": null, + "show_beta": false, + "stars": 10, + "topics": [ + "api-client", + "healthchecksio", + "monitor" + ], + "version_installed": null + }, + "201805130": { + "authors": [ + "@hellowlol" + ], + "category": "integration", + "description": "nordpool sensor for ha.", + "domain": "nordpool", + "downloads": 0, + "full_name": "custom-components/nordpool", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "7825575", + "last_release_tag": null, + "last_updated": "2020-11-25T22:19:19Z", + "name": "nordpool", + "new": false, + "repository_manifest": {}, + "selected_tag": null, + "show_beta": false, + "stars": 22, + "topics": [], + "version_installed": null + }, + "131915802": { + "authors": [ + "@tenly2000", + "@iantrich" + ], + "category": "integration", + "description": "Component to integrate with OpenStreetMap Reverse Geocode (PLACE)", + "domain": "places", + "downloads": 0, + "full_name": "custom-components/places", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "2325f23", + "last_release_tag": "1.6", + "last_updated": "2020-09-18T18:08:39Z", + "name": "places", + "new": false, + "repository_manifest": {}, + "selected_tag": null, + "show_beta": false, + "stars": 28, + "topics": [], + "version_installed": null + }, + "176018567": { + "authors": [ + "@iantrich" + ], + "category": "integration", + "description": "\ud83c\udfa7 gPodder Integration for Podcast Feed Monitoring", + "domain": "gpodder", + "downloads": 219, + "full_name": "custom-components/gpodder", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "6b3af21", + "last_release_tag": "2.0.0", + "last_updated": "2020-07-07T19:46:06Z", + "name": "gpodder", + "new": false, + "repository_manifest": { + "name": "gPodder", + "render_readme": true, + "zip_release": true, + "hide_default_branch": true, + "hacs": "0.19.0", + "filename": "gpodder.zip" + }, + "selected_tag": null, + "show_beta": false, + "stars": 10, + "topics": [], + "version_installed": null + }, + "209855108": { + "authors": [], + "category": "integration", + "description": "The linksys_ap platform offers presence detection by looking at connected devices to a Linksys based access point.", + "domain": "linksys_ap", + "downloads": 0, + "full_name": "custom-components/linksys_ap", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "e0b4431", + "last_release_tag": null, + "last_updated": "2019-09-20T18:28:36Z", + "name": "linksys_ap", + "new": false, + "repository_manifest": { + "name": "linksys_ap" + }, + "selected_tag": null, + "show_beta": false, + "stars": 0, + "topics": [], + "version_installed": null + }, + "184404372": { + "authors": [ + "@SebRut" + ], + "category": "integration", + "description": null, + "domain": "qbo", + "downloads": 0, + "full_name": "custom-components/qbo", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "d808297", + "last_release_tag": null, + "last_updated": "2019-05-26T13:38:57Z", + "name": "qbo", + "new": false, + "repository_manifest": {}, + "selected_tag": null, + "show_beta": false, + "stars": 0, + "topics": [], + "version_installed": null + }, + "146660369": { + "authors": [ + "@gerard33" + ], + "category": "integration", + "description": "Sony Bravia TV (Pre-Shared Key) component for Home Assistant", + "domain": "braviatv_psk", + "downloads": 0, + "full_name": "custom-components/media_player.braviatv_psk", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "9a8cc3a", + "last_release_tag": "v0.3.5", + "last_updated": "2020-07-31T10:30:21Z", + "name": "braviatv_psk", + "new": false, + "repository_manifest": { + "name": "Bravia TV PSK", + "domains": [ + "media_player" + ], + "iot_class": "Local Polling" + }, + "selected_tag": null, + "show_beta": false, + "stars": 58, + "topics": [ + "bravia", + "home-assistant", + "psk", + "sony" + ], + "version_installed": null + }, + "201740996": { + "authors": [ + "@ludeeus" + ], + "category": "integration", + "description": "Use Jinja and data from Home Assistant to generate your README.md file", + "domain": "readme", + "downloads": 0, + "full_name": "custom-components/readme", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "ccb3a68", + "last_release_tag": "0.2.1", + "last_updated": "2020-07-15T17:55:32Z", + "name": "readme", + "new": false, + "repository_manifest": {}, + "selected_tag": null, + "show_beta": false, + "stars": 11, + "topics": [ + "automation", + "home-assistant", + "jinja", + "readme" + ], + "version_installed": null + }, + "182915754": { + "authors": [ + "@SebRut", + "@isabellaalstrom" + ], + "category": "integration", + "description": "Custom Grocy integration for Home Assistant", + "domain": "grocy", + "downloads": 0, + "full_name": "custom-components/grocy", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "e4971b0", + "last_release_tag": "v2.0.2", + "last_updated": "2020-11-18T12:38:34Z", + "name": "grocy", + "new": false, + "repository_manifest": { + "name": "Grocy custom component", + "domains": [ + "sensor", + "binary_sensor" + ], + "render_readme": true, + "iot_class": "Cloud Polling", + "homeassistant": "0.109.0" + }, + "selected_tag": null, + "show_beta": false, + "stars": 29, + "topics": [ + "grocy", + "hacktoberfest" + ], + "version_installed": null + }, + "283847957": { + "authors": [ + "@craigbarratt" + ], + "category": "integration", + "description": "Pyscript adds rich Python scripting to HASS", + "domain": "pyscript", + "downloads": 1106, + "full_name": "custom-components/pyscript", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "9dfa312", + "last_release_tag": "1.0.0", + "last_updated": "2020-12-08T04:36:06Z", + "name": "pyscript", + "new": false, + "repository_manifest": { + "name": "pyscript", + "content_in_root": false, + "domains": [ + "automation", + "script", + "timer" + ], + "zip_release": true, + "filename": "hass-custom-pyscript.zip" + }, + "selected_tag": null, + "show_beta": false, + "stars": 168, + "topics": [ + "custom-component", + "custom-components", + "hacs", + "home-assistant", + "homeassistant", + "integration", + "jupyter" + ], + "version_installed": null + }, + "178101579": { + "authors": [ + "@MartyTremblay" + ], + "category": "integration", + "description": "hassio support for Airthings Wave BLE environmental radon sensor.", + "domain": "airthings_wave", + "downloads": 0, + "full_name": "custom-components/sensor.airthings_wave", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "1e96d9b", + "last_release_tag": "v2.6", + "last_updated": "2020-11-26T17:42:27Z", + "name": "airthings_wave", + "new": false, + "repository_manifest": {}, + "selected_tag": null, + "show_beta": false, + "stars": 34, + "topics": [ + "airthings-wave", + "bluetooth-low-energy", + "btle", + "environmental", + "home-assistant-custom", + "home-assistant-sensor", + "radon" + ], + "version_installed": null + }, + "207110572": { + "authors": [ + "@hellowlol" + ], + "category": "integration", + "description": "Simple sensor for avfallsor", + "domain": "avfallsor", + "downloads": 0, + "full_name": "custom-components/sensor.avfallsor", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "7be0679", + "last_release_tag": null, + "last_updated": "2020-12-07T22:41:43Z", + "name": "avfallsor", + "new": false, + "repository_manifest": { + "name": "Avfallsor", + "content_in_root": false, + "country": "NOR", + "domains": [ + "sensors" + ], + "iot_class": "cloud_poll", + "homeassistant": "0.96.0", + "render_readme": true + }, + "selected_tag": null, + "show_beta": false, + "stars": 6, + "topics": [], + "version_installed": null + }, + "174809046": { + "authors": [ + "@claha" + ], + "category": "integration", + "description": "Custom component to get stock data from Avanza for Home Assistant", + "domain": "avanza_stock", + "downloads": 0, + "full_name": "custom-components/sensor.avanza_stock", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "4a23298", + "last_release_tag": "v1.0.7", + "last_updated": "2020-10-22T18:12:27Z", + "name": "avanza_stock", + "new": false, + "repository_manifest": { + "name": "Avanza Stock", + "domains": [ + "sensor" + ], + "iot_class": "cloud_poll", + "render_readme": true + }, + "selected_tag": null, + "show_beta": false, + "stars": 17, + "topics": [], + "version_installed": null + }, + "173564471": { + "authors": [], + "category": "integration", + "description": "Improved file sensor component that let you read the whole last line content.", + "domain": "file_restore", + "downloads": 0, + "full_name": "custom-components/sensor.file_restore", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "828b1c4", + "last_release_tag": "3.2", + "last_updated": "2020-06-12T17:16:55Z", + "name": "file_restore", + "new": false, + "repository_manifest": {}, + "selected_tag": null, + "show_beta": false, + "stars": 6, + "topics": [], + "version_installed": null + }, + "151550084": { + "authors": [ + "@glpatcern" + ], + "category": "integration", + "description": "A set of sensors to integrate the OWL Intuition devices network", + "domain": "owlintuition", + "downloads": 0, + "full_name": "custom-components/sensor.owlintuition", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "b529d1b", + "last_release_tag": null, + "last_updated": "2019-05-21T14:08:00Z", + "name": "owlintuition", + "new": false, + "repository_manifest": {}, + "selected_tag": null, + "show_beta": false, + "stars": 3, + "topics": [], + "version_installed": null + }, + "145777833": { + "authors": [ + "@iantrich" + ], + "category": "integration", + "description": "\ud83d\udcb5 Personal Capital Integration for Bank Account Monitoring", + "domain": "personalcapital", + "downloads": 0, + "full_name": "custom-components/sensor.personalcapital", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "72785ef", + "last_release_tag": "0.1.1", + "last_updated": "2019-05-06T13:15:06Z", + "name": "personalcapital", + "new": false, + "repository_manifest": {}, + "selected_tag": null, + "show_beta": false, + "stars": 9, + "topics": [], + "version_installed": null + }, + "155499113": { + "authors": [ + "@maykar" + ], + "category": "integration", + "description": "\u25b6\ufe0f Plex component to feed Upcoming Media Card.", + "domain": "plex_recently_added", + "downloads": 0, + "full_name": "custom-components/sensor.plex_recently_added", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "5ad747f", + "last_release_tag": "0.3.6", + "last_updated": "2020-11-10T12:22:20Z", + "name": "plex_recently_added", + "new": false, + "repository_manifest": {}, + "selected_tag": null, + "show_beta": false, + "stars": 39, + "topics": [], + "version_installed": null + }, + "195438291": { + "authors": [ + "@boralyl" + ], + "category": "integration", + "description": "A sensor that monitors a Nintendo Switch wish list for when games are on sale.", + "domain": "nintendo_wishlist", + "downloads": 0, + "full_name": "custom-components/sensor.nintendo_wishlist", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "d25e7a2", + "last_release_tag": "v3.1.3", + "last_updated": "2020-11-09T17:54:13Z", + "name": "nintendo_wishlist", + "new": false, + "repository_manifest": { + "domains": [ + "binary_sensor", + "sensor" + ], + "iot_class": "Cloud Polling", + "name": "Nintendo Wishlist", + "render_readme": true, + "homeassistant": "0.106.0" + }, + "selected_tag": null, + "show_beta": false, + "stars": 7, + "topics": [ + "homeassistant", + "nintendo-switch" + ], + "version_installed": null + }, + "146929882": { + "authors": [ + "@maykar" + ], + "category": "integration", + "description": "\ud83c\udfac Radarr component to feed Upcoming Media Card.", + "domain": "radarr_upcoming_media", + "downloads": 0, + "full_name": "custom-components/sensor.radarr_upcoming_media", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "322bc08", + "last_release_tag": "0.3.3", + "last_updated": "2020-10-04T16:14:12Z", + "name": "radarr_upcoming_media", + "new": false, + "repository_manifest": {}, + "selected_tag": null, + "show_beta": false, + "stars": 25, + "topics": [], + "version_installed": null + }, + "140146811": { + "authors": [], + "category": "integration", + "description": "A Custom component for Home-Assistant that checks if your Raspberry Pi power supply is giving enough voltage from the kernel.", + "domain": "rpi_power", + "downloads": 0, + "full_name": "custom-components/sensor.rpi_power", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "a0ac3c9", + "last_release_tag": "0.2.0", + "last_updated": "2020-08-26T09:25:40Z", + "name": "rpi_power", + "new": false, + "repository_manifest": {}, + "selected_tag": null, + "show_beta": false, + "stars": 102, + "topics": [ + "community-driven", + "component", + "homeassistant", + "power", + "raspberry-pi", + "sensor" + ], + "version_installed": null + }, + "154845921": { + "authors": [ + "@jchasey" + ], + "category": "integration", + "description": "SSH Generic Sensor", + "domain": "ssh", + "downloads": 0, + "full_name": "custom-components/sensor.ssh", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "df82126", + "last_release_tag": null, + "last_updated": "2019-11-15T11:36:10Z", + "name": "ssh", + "new": false, + "repository_manifest": {}, + "selected_tag": null, + "show_beta": false, + "stars": 16, + "topics": [], + "version_installed": null + }, + "146792954": { + "authors": [ + "@maykar" + ], + "category": "integration", + "description": "\ud83d\udcfa Sonarr component to feed Upcoming Media Card.", + "domain": "sonarr_upcoming_media", + "downloads": 0, + "full_name": "custom-components/sensor.sonarr_upcoming_media", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "a100ec8", + "last_release_tag": "0.2.5", + "last_updated": "2020-08-31T03:48:23Z", + "name": "sonarr_upcoming_media", + "new": false, + "repository_manifest": {}, + "selected_tag": null, + "show_beta": false, + "stars": 24, + "topics": [], + "version_installed": null + }, + "140146868": { + "authors": [ + "@iantrich", + "@swetoast" + ], + "category": "integration", + "description": "\ud83c\udf7b Untappd Integration", + "domain": "untappd", + "downloads": 0, + "full_name": "custom-components/sensor.untappd", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "cfb0b74", + "last_release_tag": "0.1.5", + "last_updated": "2020-10-13T18:50:01Z", + "name": "untappd", + "new": false, + "repository_manifest": {}, + "selected_tag": null, + "show_beta": false, + "stars": 22, + "topics": [ + "automations", + "badges", + "beer", + "homeassistant", + "homeassistant-components", + "untappd", + "untappd-api" + ], + "version_installed": null + }, + "151580533": { + "authors": [ + "@jchasey" + ], + "category": "integration", + "description": "High level health status of UniFi Security Gateway devices via UniFi Controller", + "domain": "unifigateway", + "downloads": 0, + "full_name": "custom-components/sensor.unifigateway", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "93f8f13", + "last_release_tag": "v0.2.3", + "last_updated": "2019-11-20T21:43:10Z", + "name": "unifigateway", + "new": false, + "repository_manifest": {}, + "selected_tag": null, + "show_beta": false, + "stars": 50, + "topics": [], + "version_installed": null + }, + "146379582": { + "authors": [ + "@iantrich", + "@engrbm" + ], + "category": "integration", + "description": "\ud83d\udcfa Trakt Integration for Upcoming Media Card", + "domain": "trakt", + "downloads": 0, + "full_name": "custom-components/sensor.trakt", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "9318502", + "last_release_tag": "2.0.1", + "last_updated": "2020-07-13T08:52:03Z", + "name": "trakt", + "new": false, + "repository_manifest": { + "name": "Trakt", + "domains": [ + "sensor" + ], + "homeassistant": "0.99.0", + "render_readme": true + }, + "selected_tag": null, + "show_beta": false, + "stars": 38, + "topics": [], + "version_installed": null + }, + "199888538": { + "authors": [ + "@vigonotion" + ], + "category": "integration", + "description": "Stadtreinigung Hamburg - get garbage collection dates in Hamburg - custom component for Home Assistant", + "domain": "stadtreinigung_hamburg", + "downloads": 0, + "full_name": "custom-components/sensor.stadtreinigung_hamburg", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "79b83a3", + "last_release_tag": "v0.1.6", + "last_updated": "2020-04-30T19:55:17Z", + "name": "stadtreinigung_hamburg", + "new": false, + "repository_manifest": { + "name": "Stadtreinigung Hamburg", + "domains": [ + "sensor" + ], + "country": "DE", + "homeassistant": "0.109.0" + }, + "selected_tag": null, + "show_beta": false, + "stars": 8, + "topics": [], + "version_installed": null + }, + "173173413": { + "authors": [], + "category": "integration", + "description": "A platform which give you the time it will take to drive.", + "domain": "yandex_maps", + "downloads": 0, + "full_name": "custom-components/sensor.yandex_maps", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "3796c62", + "last_release_tag": null, + "last_updated": "2020-12-03T19:28:52Z", + "name": "yandex_maps", + "new": false, + "repository_manifest": {}, + "selected_tag": null, + "show_beta": false, + "stars": 16, + "topics": [], + "version_installed": null + }, + "209855274": { + "authors": [], + "category": "integration", + "description": "The srp_energy integration shows information from Srp hourly energy usage report for their customers", + "domain": "srp_energy", + "downloads": 0, + "full_name": "custom-components/srp_energy", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "34afa6b", + "last_release_tag": null, + "last_updated": "2019-09-20T18:28:33Z", + "name": "srp_energy", + "new": false, + "repository_manifest": { + "name": "SRP Energy Sensor", + "country": "US" + }, + "selected_tag": null, + "show_beta": false, + "stars": 2, + "topics": [], + "version_installed": null + }, + "209855510": { + "authors": [], + "category": "integration", + "description": "The sytadin sensor platform allows you to monitor traffic details from Sytadin", + "domain": "sytadin", + "downloads": 0, + "full_name": "custom-components/sytadin", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "8fe6108", + "last_release_tag": null, + "last_updated": "2019-09-20T18:28:24Z", + "name": "sytadin", + "new": false, + "repository_manifest": { + "name": "Sytadin", + "country": "FR" + }, + "selected_tag": null, + "show_beta": false, + "stars": 1, + "topics": [], + "version_installed": null + }, + "143647651": { + "authors": [ + "@ludeeus" + ], + "category": "integration", + "description": "A camera platform that give you random images from Unsplash presented as a camera feed.", + "domain": "unsplash", + "downloads": 0, + "full_name": "custom-components/unsplash", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "9a7f438", + "last_release_tag": "0.6.1", + "last_updated": "2020-08-01T10:29:06Z", + "name": "unsplash", + "new": false, + "repository_manifest": {}, + "selected_tag": null, + "show_beta": false, + "stars": 7, + "topics": [ + "camera", + "home-assistant", + "integration", + "unsplash", + "unsplash-api" + ], + "version_installed": null + }, + "171854441": { + "authors": [ + "@ludeeus" + ], + "category": "integration", + "description": "A platform which give you info about the newest video on a channel", + "domain": "youtube", + "downloads": 0, + "full_name": "custom-components/youtube", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "332cb46", + "last_release_tag": "0.3.3", + "last_updated": "2020-10-19T22:52:08Z", + "name": "youtube", + "new": false, + "repository_manifest": {}, + "selected_tag": null, + "show_beta": false, + "stars": 10, + "topics": [ + "custom-components", + "customcomponents", + "home-assistant", + "homeassistant", + "python", + "youtube" + ], + "version_installed": null + }, + "180651910": { + "authors": [ + "hellowlol" + ], + "category": "integration", + "description": "zaptec charger custom component for home assistant", + "domain": "zaptec", + "downloads": 0, + "full_name": "custom-components/zaptec", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "32e7770", + "last_release_tag": null, + "last_updated": "2020-07-06T10:25:34Z", + "name": "zaptec", + "new": false, + "repository_manifest": {}, + "selected_tag": null, + "show_beta": false, + "stars": 5, + "topics": [ + "api", + "custom-component", + "homeassistant", + "zaptec" + ], + "version_installed": null + }, + "199736646": { + "authors": [ + "@ludeeus" + ], + "category": "integration", + "description": "Add template sensors from the UI.", + "domain": "templatesensor", + "downloads": 0, + "full_name": "custom-components/templatesensor", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "23a18e7", + "last_release_tag": "0.1.2", + "last_updated": "2020-07-08T05:41:17Z", + "name": "templatesensor", + "new": false, + "repository_manifest": { + "name": "UI Template sensor configuration", + "domains": [ + "sensor" + ], + "homeassistant": "0.109.0" + }, + "selected_tag": null, + "show_beta": false, + "stars": 12, + "topics": [ + "custom", + "homeassistant", + "integration", + "sensor", + "template" + ], + "version_installed": null + }, + "237789042": { + "authors": [ + "@ludeeus" + ], + "category": "integration", + "description": "Custom panel that show colorful logs for Home Assistant (core), and the supervisor (if you have it).", + "domain": "uilogs", + "downloads": 0, + "full_name": "custom-components/uilogs", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "7e8f883", + "last_release_tag": null, + "last_updated": "2020-05-11T13:37:38Z", + "name": "uilogs", + "new": false, + "repository_manifest": { + "name": "UI Logs", + "render_readme": true + }, + "selected_tag": null, + "show_beta": false, + "stars": 20, + "topics": [ + "colorlog", + "home-assistant", + "litelement", + "log", + "python" + ], + "version_installed": null + }, + "139894340": { + "authors": [ + "@ludeeus", + "@jlverhagen" + ], + "category": "integration", + "description": "A sensor that gives you weather alerts from alerts.weather.gov.", + "domain": "weatheralerts", + "downloads": 0, + "full_name": "custom-components/weatheralerts", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "f29c827", + "last_release_tag": "v0.1.3", + "last_updated": "2020-09-24T05:14:03Z", + "name": "weatheralerts", + "new": false, + "repository_manifest": { + "name": "Weatheralerts", + "render_readme": true, + "country": "US" + }, + "selected_tag": null, + "show_beta": false, + "stars": 32, + "topics": [ + "custom-components", + "home-assistant", + "homeassistant", + "weatheralerts" + ], + "version_installed": null + }, + "228627470": { + "authors": [ + "@cyberjunky" + ], + "category": "integration", + "description": ":recycle: :wastebasket: This component fetches garbage pickup dates for parts of The Netherlands using HVC Groep's REST API.", + "domain": "hvcgroep", + "downloads": 0, + "full_name": "cyberjunky/home-assistant-hvcgroep", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "19b7c2e", + "last_release_tag": "1.0.5", + "last_updated": "2020-05-28T14:40:41Z", + "name": "hvcgroep", + "new": false, + "repository_manifest": { + "name": "HVCGroep", + "country": "NL", + "render_readme": false, + "domains": [ + "sensor" + ] + }, + "selected_tag": null, + "show_beta": false, + "stars": 2, + "topics": [ + "custom-component", + "hacs", + "home-assistant", + "homeassistant", + "python" + ], + "version_installed": null + }, + "228604799": { + "authors": [ + "@cyberjunky" + ], + "category": "integration", + "description": "This component tracks devices using the arp-scan liinux command, it's very fast, and reasonably accurate.", + "domain": "arpscan_tracker", + "downloads": 0, + "full_name": "cyberjunky/home-assistant-arpscan_tracker", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "eaf5e6f", + "last_release_tag": "1.0.3", + "last_updated": "2020-05-31T17:52:14Z", + "name": "arpscan_tracker", + "new": false, + "repository_manifest": { + "name": "Arpscan Device Tracker", + "country": "NL", + "render_readme": false, + "domains": [ + "device_tracker" + ] + }, + "selected_tag": null, + "show_beta": false, + "stars": 7, + "topics": [ + "custom-component", + "hacs", + "home-assistant", + "homeassistant", + "python" + ], + "version_installed": null + }, + "228649088": { + "authors": [ + "@cyberjunky" + ], + "category": "integration", + "description": ":fire_engine: This component tracks P2000 emergency events in The Netherlands.", + "domain": "p2000", + "downloads": 0, + "full_name": "cyberjunky/home-assistant-p2000", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "545813d", + "last_release_tag": "1.0.16", + "last_updated": "2020-06-09T17:08:40Z", + "name": "p2000", + "new": false, + "repository_manifest": { + "name": "P2000 Sensor", + "country": "NL", + "render_readme": false, + "domains": [ + "sensor" + ] + }, + "selected_tag": null, + "show_beta": false, + "stars": 21, + "topics": [ + "custom-component", + "emergency", + "hacs", + "home-assistant", + "homeassistant", + "p2000", + "python" + ], + "version_installed": null + }, + "139894509": { + "authors": [ + "@ludeeus" + ], + "category": "integration", + "description": "A sensor that give you information about next departure from spesified stop.", + "domain": "wienerlinien", + "downloads": 0, + "full_name": "custom-components/wienerlinien", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "c21b901", + "last_release_tag": null, + "last_updated": "2020-07-05T14:54:41Z", + "name": "wienerlinien", + "new": false, + "repository_manifest": { + "name": "wienerlinien", + "render_readme": true, + "hacs": "0.19.0" + }, + "selected_tag": null, + "show_beta": false, + "stars": 14, + "topics": [ + "custom-components", + "home-assistant", + "homeassistant", + "wiener-linien" + ], + "version_installed": null + }, + "209855666": { + "authors": [], + "category": "integration", + "description": "The ups platform allows one to track deliveries by the UPS", + "domain": "ups", + "downloads": 0, + "full_name": "custom-components/ups", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "4e6c7db", + "last_release_tag": null, + "last_updated": "2019-09-20T18:28:15Z", + "name": "ups", + "new": false, + "repository_manifest": { + "name": "UPS", + "country": "US" + }, + "selected_tag": null, + "show_beta": false, + "stars": 3, + "topics": [], + "version_installed": null + }, + "228618998": { + "authors": [ + "@cyberjunky" + ], + "category": "integration", + "description": ":electric_plug: This component can read values from and control Plugwise circles and plugs.", + "domain": "plugwise", + "downloads": 0, + "full_name": "cyberjunky/home-assistant-plugwise", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "8b55d62", + "last_release_tag": "1.0.4", + "last_updated": "2020-07-08T06:34:47Z", + "name": "plugwise", + "new": false, + "repository_manifest": { + "name": "Plugwise", + "country": "NL", + "render_readme": false, + "domains": [ + "switch", + "sensor" + ] + }, + "selected_tag": null, + "show_beta": false, + "stars": 0, + "topics": [ + "custom-component", + "hacs", + "home-assistant", + "homeassistant", + "plugwise", + "power", + "python" + ], + "version_installed": null + }, + "228685436": { + "authors": [ + "@cyberjunky" + ], + "category": "integration", + "description": "This component reads and displays the boiler status values from a rooted Toon thermostat.", + "domain": "toon_boilerstatus", + "downloads": 0, + "full_name": "cyberjunky/home-assistant-toon_boilerstatus", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "9c15a2c", + "last_release_tag": "1.0.5", + "last_updated": "2020-05-30T12:04:14Z", + "name": "toon_boilerstatus", + "new": false, + "repository_manifest": { + "name": "Toon Boiler Status", + "country": [ + "NL", + "BE" + ], + "render_readme": false, + "domains": [ + "sensor" + ] + }, + "selected_tag": null, + "show_beta": false, + "stars": 4, + "topics": [ + "custom-component", + "cv", + "hacs", + "home-assistant", + "homeassistant", + "opentherm", + "python", + "toon" + ], + "version_installed": null + }, + "228662926": { + "authors": [ + "@cyberjunky" + ], + "category": "integration", + "description": "This component provides a climate device for rooted Toon thermostats.", + "domain": "toon_climate", + "downloads": 0, + "full_name": "cyberjunky/home-assistant-toon_climate", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "366d2aa", + "last_release_tag": "1.0.8", + "last_updated": "2020-10-11T07:52:01Z", + "name": "toon_climate", + "new": false, + "repository_manifest": { + "name": "Toon Climate", + "country": "NL", + "render_readme": false, + "domains": [ + "climate" + ] + }, + "selected_tag": null, + "show_beta": false, + "stars": 10, + "topics": [], + "version_installed": null + }, + "228678807": { + "authors": [ + "@cyberjunky" + ], + "category": "integration", + "description": "This component reads and displays sensor values from the meteradapter connected to a rooted Toon thermostat.", + "domain": "toon_smartmeter", + "downloads": 0, + "full_name": "cyberjunky/home-assistant-toon_smartmeter", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "360663b", + "last_release_tag": "1.0.8", + "last_updated": "2020-11-11T09:24:33Z", + "name": "toon_smartmeter", + "new": false, + "repository_manifest": { + "name": "Toon Smart Meter", + "country": "NL", + "render_readme": false, + "domains": [ + "sensor" + ] + }, + "selected_tag": null, + "show_beta": false, + "stars": 6, + "topics": [ + "custom-component", + "hacs", + "home-assistant", + "homeassistant", + "python" + ], + "version_installed": null + }, + "220661494": { + "authors": [ + "@cyr-ius" + ], + "category": "integration", + "description": "Livebox Component for Home assistant", + "domain": "livebox", + "downloads": 0, + "full_name": "Cyr-ius/hass-livebox-component", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "133cd9a", + "last_release_tag": "1.5.3", + "last_updated": "2020-10-26T16:13:55Z", + "name": "livebox", + "new": false, + "repository_manifest": { + "name": "Orange Livebox routeur", + "country": "FR", + "domains": [ + "device_tracker", + "sensor", + "binary_sensor", + "switch" + ], + "homeassistant": "0.109" + }, + "selected_tag": null, + "show_beta": false, + "stars": 9, + "topics": [ + "hacs", + "home-assistant", + "homeassistant", + "homeassistant-components" + ], + "version_installed": null + }, + "258796304": { + "authors": [ + "@cyr-ius" + ], + "category": "integration", + "description": "Service to set motion's sensors and change scan intervall for remotes and motions", + "domain": "hueserviceadvanced", + "downloads": 0, + "full_name": "Cyr-ius/hass-hue-service-advanced", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "c60d1d8", + "last_release_tag": "1.3.5", + "last_updated": "2020-10-24T11:55:23Z", + "name": "hueserviceadvanced", + "new": false, + "repository_manifest": { + "name": "Hue Service Advanced", + "country": "FR", + "domains": [ + "sensor" + ], + "homeassistant": "0.109" + }, + "selected_tag": null, + "show_beta": false, + "stars": 3, + "topics": [], + "version_installed": null + }, + "293843053": { + "authors": [ + "@danielhiversen" + ], + "category": "integration", + "description": "Integration for Adax heaters", + "domain": "adax", + "downloads": 0, + "full_name": "Danielhiversen/home_assistant_adax", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "6ee2e24", + "last_release_tag": "0.4.2", + "last_updated": "2020-11-14T06:17:42Z", + "name": "adax", + "new": false, + "repository_manifest": { + "name": "Adax heaters", + "domains": [ + "climate" + ], + "iot_class": "Cloud Polling" + }, + "selected_tag": null, + "show_beta": false, + "stars": 13, + "topics": [ + "adax", + "adax-heaters", + "hacs", + "homeassistant" + ], + "version_installed": null + }, + "228690854": { + "authors": [ + "@cyberjunky" + ], + "category": "integration", + "description": "This components reads statistics from a The Things Network Gateway.", + "domain": "ttn_gateway", + "downloads": 0, + "full_name": "cyberjunky/home-assistant-ttn_gateway", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "3a1871c", + "last_release_tag": "1.0.5", + "last_updated": "2020-09-24T19:45:28Z", + "name": "ttn_gateway", + "new": false, + "repository_manifest": { + "name": "TTN Gateway Sensor", + "country": "NL", + "render_readme": false, + "domains": [ + "sensor" + ] + }, + "selected_tag": null, + "show_beta": false, + "stars": 0, + "topics": [], + "version_installed": null + }, + "224001103": { + "authors": [ + "@biker91620", + "@cyr-ius" + ], + "category": "integration", + "description": "Cozytouch support for Home Assistant", + "domain": "cozytouch", + "downloads": 0, + "full_name": "Cyr-ius/hass-cozytouch", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "fdd53fd", + "last_release_tag": "1.5.8", + "last_updated": "2020-10-06T14:40:37Z", + "name": "cozytouch", + "new": false, + "repository_manifest": { + "name": "Cozytouch", + "country": "FR", + "domains": [ + "binary_sensor", + "sensor", + "climate", + "water_heater", + "switch" + ], + "homeassistant": "0.109" + }, + "selected_tag": null, + "show_beta": false, + "stars": 10, + "topics": [], + "version_installed": null + }, + "195459345": { + "authors": [ + "@daenny" + ], + "category": "integration", + "description": "Home Assistant Climate Groupe", + "domain": "climate_group", + "downloads": 0, + "full_name": "daenny/climate_group", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "2a27463", + "last_release_tag": "0.4.1", + "last_updated": "2020-06-13T11:20:34Z", + "name": "climate_group", + "new": false, + "repository_manifest": { + "domains": [ + "climate" + ], + "name": "Climate Group", + "render_readme": true, + "homeassistant": "0.96.0" + }, + "selected_tag": null, + "show_beta": false, + "stars": 17, + "topics": [], + "version_installed": null + }, + "195492813": { + "authors": [ + "@davidfw1960" + ], + "category": "integration", + "description": "BOM Australian Weather Forecast via FTP", + "domain": "bom_forecast", + "downloads": 0, + "full_name": "DavidFW1960/bom_forecast", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "af3aec3", + "last_release_tag": "0.35", + "last_updated": "2020-10-28T08:40:24Z", + "name": "bom_forecast", + "new": false, + "repository_manifest": {}, + "selected_tag": null, + "show_beta": false, + "stars": 15, + "topics": [ + "bom", + "home-assistant", + "home-automation", + "homeassistant", + "weather-forecast" + ], + "version_installed": null + }, + "158194879": { + "authors": [ + "@danobot" + ], + "category": "integration", + "description": "Entity and lighting controller for managing devices via timers, scripts, and sun-based time restrictions.", + "domain": "entity_controller", + "downloads": 0, + "full_name": "danobot/entity-controller", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "fedecd4", + "last_release_tag": "v9.2.0", + "last_updated": "2020-12-04T11:20:08Z", + "name": "entity_controller", + "new": false, + "repository_manifest": { + "name": "Entity Controller", + "homeassistant": "0.90.1" + }, + "selected_tag": null, + "show_beta": false, + "stars": 143, + "topics": [ + "finite-state-machine", + "hass", + "home-assistant", + "home-automation", + "internet-of-things", + "iot", + "lighting-controller", + "motion-light", + "motion-sensor", + "python", + "smart-home" + ], + "version_installed": null + }, + "201457186": { + "authors": [ + "@darksir23" + ], + "category": "integration", + "description": "HomeAssistant Sensor for Mylar (Compatible with Upcoming Meda Card)", + "domain": "mylar", + "downloads": 0, + "full_name": "DarkSir23/sensor.mylar", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "b25f87d", + "last_release_tag": null, + "last_updated": "2019-11-23T19:10:34Z", + "name": "mylar", + "new": false, + "repository_manifest": { + "name": "Mylar Sensor Card", + "render_readme": true + }, + "selected_tag": null, + "show_beta": false, + "stars": 2, + "topics": [ + "media-card", + "mylar", + "sensor" + ], + "version_installed": null + }, + "203736221": { + "authors": [ + "@almirdelkic", + "@dave-code-ruiz", + "@LordMike" + ], + "category": "integration", + "description": "Custom Component to connect Home Assistant with Uhome Uponor Smatrix App", + "domain": "uhomeuponor", + "downloads": 0, + "full_name": "dave-code-ruiz/uhomeuponor", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "3ad1cb2", + "last_release_tag": null, + "last_updated": "2020-10-19T13:27:05Z", + "name": "uhomeuponor", + "new": false, + "repository_manifest": { + "name": "Uponor Uhome integration", + "render_readme": true, + "domains": [ + "sensor", + "climate" + ] + }, + "selected_tag": null, + "show_beta": false, + "stars": 5, + "topics": [ + "customcomponents", + "gateway", + "homeassistant", + "python", + "rest-api", + "setpoint", + "smatrix", + "smatrixwaveplus", + "thermostat", + "uponor" + ], + "version_installed": null + }, + "160218749": { + "authors": [ + "@DavidMStraub" + ], + "category": "integration", + "description": "Custom component for Home Assistant to connect appliances supporting the Home Connect standard", + "domain": "home_connect_beta", + "downloads": 0, + "full_name": "DavidMStraub/homeassistant-homeconnect", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "fbfde3d", + "last_release_tag": "v1.0.1", + "last_updated": "2020-10-17T17:50:41Z", + "name": "home_connect_beta", + "new": false, + "repository_manifest": { + "name": "Home Connect", + "domains": [ + "sensor", + "binary_sensor", + "switch" + ], + "homeassistant": "0.102", + "render_readme": true + }, + "selected_tag": null, + "show_beta": false, + "stars": 107, + "topics": [ + "hacs", + "homeassistant", + "homeconnect", + "python" + ], + "version_installed": null + }, + "297379398": { + "authors": [ + "@danielhiversen" + ], + "category": "integration", + "description": "Custom component for Tractive", + "domain": "tractive", + "downloads": 0, + "full_name": "Danielhiversen/home_assistant_tractive", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "333d4f9", + "last_release_tag": "0.2.1", + "last_updated": "2020-11-07T07:29:11Z", + "name": "tractive", + "new": false, + "repository_manifest": { + "name": "Tractive", + "domains": [ + "device_tracker" + ], + "iot_class": "Cloud Polling" + }, + "selected_tag": null, + "show_beta": false, + "stars": 10, + "topics": [ + "hacs", + "home-assistant", + "home-assistant-custom", + "tractive" + ], + "version_installed": null + }, + "266557774": { + "authors": [ + "deblockt" + ], + "category": "integration", + "description": "proscenic 790T intergration for home assistant", + "domain": "proscenic", + "downloads": 43, + "full_name": "deblockt/hass-proscenic-790T-vacuum", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "143a0dc", + "last_release_tag": "0.0.5", + "last_updated": "2020-07-28T06:28:58Z", + "name": "proscenic", + "new": false, + "repository_manifest": { + "name": "proscenic 790T vacuum", + "domains": [ + "vacuum" + ], + "homeassistant": "0.109.6", + "iot_class": "" + }, + "selected_tag": null, + "show_beta": false, + "stars": 4, + "topics": [ + "790t", + "home-assistant", + "proscenic", + "vacuum", + "vacuum-cleaner" + ], + "version_installed": null + }, + "283518438": { + "authors": [ + "@definitio" + ], + "category": "integration", + "description": "Home Assistant - RHVoice component", + "domain": "rhvoice", + "downloads": 0, + "full_name": "definitio/ha-rhvoice", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "1d0bf66", + "last_release_tag": "1.1.1", + "last_updated": "2020-11-22T12:16:23Z", + "name": "rhvoice", + "new": false, + "repository_manifest": { + "name": "RHVoice", + "render_readme": true, + "domains": [ + "tts" + ] + }, + "selected_tag": null, + "show_beta": false, + "stars": 8, + "topics": [ + "home-assistant", + "integration", + "tts" + ], + "version_installed": null + }, + "283243425": { + "authors": [ + "@definitio" + ], + "category": "integration", + "description": "Home Assistant - SoX component", + "domain": "sox", + "downloads": 0, + "full_name": "definitio/ha-sox", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "1fa164d", + "last_release_tag": "1.0.2", + "last_updated": "2020-11-22T12:57:28Z", + "name": "sox", + "new": false, + "repository_manifest": { + "name": "SoX", + "render_readme": true, + "domains": [ + "media_player" + ] + }, + "selected_tag": null, + "show_beta": false, + "stars": 6, + "topics": [ + "home-assistant", + "integration", + "mediaplayer" + ], + "version_installed": null + }, + "308690707": { + "authors": [ + "@djtimca" + ], + "category": "integration", + "description": "Home Assistant integration for Google Wifi systems.", + "domain": "googlewifi", + "downloads": 0, + "full_name": "djtimca/hagooglewifi", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "3a823f0", + "last_release_tag": "0.1.19", + "last_updated": "2020-12-07T22:17:45Z", + "name": "googlewifi", + "new": false, + "repository_manifest": { + "name": "Google WiFi", + "country": "CA", + "domains": [ + "binary_sensor" + ], + "homeassistant": "0.115.0", + "iot_class": [ + "Cloud Polling" + ] + }, + "selected_tag": null, + "show_beta": false, + "stars": 14, + "topics": [ + "custom-component", + "google-wifi", + "hacs", + "home-assistant", + "home-automation" + ], + "version_installed": null + }, + "200073618": { + "authors": [ + "@dlashua" + ], + "category": "integration", + "description": "Add template binary_sensors from the UI.", + "domain": "templatebinarysensor", + "downloads": 0, + "full_name": "dlashua/templatebinarysensor", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "91e79e8", + "last_release_tag": null, + "last_updated": "2019-08-05T21:32:37Z", + "name": "templatebinarysensor", + "new": false, + "repository_manifest": {}, + "selected_tag": null, + "show_beta": false, + "stars": 0, + "topics": [], + "version_installed": null + }, + "255139072": { + "authors": [ + "@dgomes" + ], + "category": "integration", + "description": "Home Assistant Custom Component for ERSE", + "domain": "erse", + "downloads": 0, + "full_name": "dgomes/ha_erse", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "3cddc32", + "last_release_tag": null, + "last_updated": "2020-11-22T21:40:28Z", + "name": "erse", + "new": false, + "repository_manifest": { + "name": "Entidade Reguladora dos Servi\u00e7os Energ\u00e9ticos", + "country": "PT", + "homeassistant": "0.87" + }, + "selected_tag": null, + "show_beta": false, + "stars": 5, + "topics": [], + "version_installed": null + }, + "177469955": { + "authors": [ + "@dlarrick" + ], + "category": "integration", + "description": "Home Assistant module interfacing with Mitsubishi mini-split units", + "domain": "kumo", + "downloads": 0, + "full_name": "dlarrick/hass-kumo", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "6b811ea", + "last_release_tag": "release-0.2.2", + "last_updated": "2020-11-23T02:39:55Z", + "name": "kumo", + "new": false, + "repository_manifest": { + "name": "Mitsubishi Kumo Cloud", + "render_readme": true, + "domains": [ + "climate" + ], + "homeassistant": "0.96.0", + "iot_class": "local_poll" + }, + "selected_tag": null, + "show_beta": false, + "stars": 16, + "topics": [ + "climate", + "custom-component", + "home-assistant", + "homeassistant", + "kumo", + "kumocloud", + "mini-split", + "mitsubishi", + "python" + ], + "version_installed": null + }, + "188698828": { + "authors": [], + "category": "integration", + "description": "Adds support for Yandex Smart Home (Alice voice assistant) into Home Assistant", + "domain": "yandex_smart_home", + "downloads": 0, + "full_name": "dmitry-k/yandex_smart_home", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "bf9e810", + "last_release_tag": null, + "last_updated": "2020-12-05T12:06:20Z", + "name": "yandex_smart_home", + "new": false, + "repository_manifest": { + "name": "Yandex Smart Home", + "country": [ + "RU", + "BY" + ], + "render_readme": true, + "homeassistant": "0.112.0" + }, + "selected_tag": null, + "show_beta": false, + "stars": 245, + "topics": [ + "alice", + "home-assistant-component", + "voice-assistant", + "yandex" + ], + "version_installed": null + }, + "163322610": { + "authors": [ + "Djbulsink", + "SeraphimSerapis" + ], + "category": "integration", + "description": "Panasonic Comfort Cloud HA component", + "domain": "panasonic_ac", + "downloads": 0, + "full_name": "djbulsink/panasonic_ac", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "c74b8bf", + "last_release_tag": null, + "last_updated": "2020-11-11T07:09:01Z", + "name": "panasonic_ac", + "new": false, + "repository_manifest": { + "name": "Panasonic Comfort Cloud HA component", + "domains": [ + "climate" + ], + "homeassistant": "0.110.0" + }, + "selected_tag": null, + "show_beta": false, + "stars": 14, + "topics": [], + "version_installed": null + }, + "291751884": { + "authors": [ + "@djtimca" + ], + "category": "integration", + "description": "Home Assistant integration for SpaceX Next Launch and Starman data.", + "domain": "spacex", + "downloads": 0, + "full_name": "djtimca/HASpaceX", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "28de20c", + "last_release_tag": "025", + "last_updated": "2020-12-07T14:08:37Z", + "name": "spacex", + "new": false, + "repository_manifest": { + "name": "SpaceX Next Launch and Starman", + "country": "CA", + "domains": [ + "binary_sensor", + "sensor" + ], + "homeassistant": "0.115.0", + "iot_class": [ + "Cloud Polling" + ] + }, + "selected_tag": null, + "show_beta": false, + "stars": 13, + "topics": [ + "home-assistant", + "home-assistant-component", + "home-assistant-custom", + "home-assistant-sensor", + "spacex", + "spacex-launches" + ], + "version_installed": null + }, + "229014136": { + "authors": [ + "doudz" + ], + "category": "integration", + "description": "myjdownloader integration for home assistant", + "domain": "myjdownloader", + "downloads": 0, + "full_name": "doudz/homeassistant-myjdownloader", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "44e5a45", + "last_release_tag": null, + "last_updated": "2020-08-16T11:04:53Z", + "name": "myjdownloader", + "new": false, + "repository_manifest": { + "name": "MyJDownloader", + "domains": [ + "sensor" + ], + "render_readme": true + }, + "selected_tag": null, + "show_beta": false, + "stars": 5, + "topics": [], + "version_installed": null + }, + "138378307": { + "authors": [ + "doudz" + ], + "category": "integration", + "description": "zigate component for Home Assistant", + "domain": "zigate", + "downloads": 0, + "full_name": "doudz/homeassistant-zigate", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "327c3e0", + "last_release_tag": null, + "last_updated": "2020-11-28T19:11:17Z", + "name": "zigate", + "new": false, + "repository_manifest": { + "name": "ZiGate", + "domains": [ + "binary_sensor", + "climate", + "cover", + "light", + "sensor", + "switch", + "lock" + ], + "iot_class": [ + "Local Push" + ] + }, + "selected_tag": null, + "show_beta": false, + "stars": 52, + "topics": [], + "version_installed": null + }, + "230841240": { + "authors": [ + "@dr1rrb" + ], + "category": "integration", + "description": "Twinkly integration for Home-Assistant", + "domain": "twinkly", + "downloads": 0, + "full_name": "dr1rrb/ha-twinkly", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "5e66794", + "last_release_tag": null, + "last_updated": "2020-11-22T03:12:22Z", + "name": "twinkly", + "new": false, + "repository_manifest": { + "name": "Twinkly", + "domains": [ + "light" + ], + "iot_class": "local_polling", + "render_readme": false + }, + "selected_tag": null, + "show_beta": false, + "stars": 12, + "topics": [ + "custom-component", + "hacs", + "home-assistant", + "homeassistant", + "light", + "twinkly" + ], + "version_installed": null + }, + "157782680": { + "authors": [ + "@DSorlov" + ], + "category": "integration", + "description": "HomeAssistant SL Sensor (HASL)", + "domain": "hasl", + "downloads": 0, + "full_name": "DSorlov/hasl-platform", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "9a800d4", + "last_release_tag": "2.2.4", + "last_updated": "2020-05-23T14:03:20Z", + "name": "hasl", + "new": false, + "repository_manifest": { + "name": "Home Assistant SL Sensor (HASL)", + "content_in_root": false, + "zip_release": false, + "render_readme": false, + "domains": [ + "sensor" + ], + "country": "SE", + "homeassistant": "0.92", + "iot_class": "cloud_poll" + }, + "selected_tag": null, + "show_beta": false, + "stars": 18, + "topics": [ + "ha-sensor-sl", + "homeassistant", + "homeassistant-sensor", + "sl-sensor", + "stockholms-lokaltrafik" + ], + "version_installed": null + }, + "267076188": { + "authors": [ + "@dynasticorpheus" + ], + "category": "integration", + "description": "Gigaset Smart Home integration for Home Assistant", + "domain": "gigasetelements", + "downloads": 0, + "full_name": "dynasticorpheus/gigasetelements-ha", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "5ffe731", + "last_release_tag": "0.3.0", + "last_updated": "2020-11-30T10:20:16Z", + "name": "gigasetelements", + "new": false, + "repository_manifest": { + "name": "Gigaset Elements", + "render_readme": "true", + "homeassistant": "0.110.0" + }, + "selected_tag": null, + "show_beta": false, + "stars": 7, + "topics": [ + "custom-component", + "custom-components", + "gigaset", + "gigasetelements", + "homeassistant", + "python3", + "smarthome" + ], + "version_installed": null + }, + "206574936": { + "authors": [ + "@eavanvalkenburg" + ], + "category": "integration", + "description": "SIA alarm systems integration into Home Assistant", + "domain": "sia", + "downloads": 0, + "full_name": "eavanvalkenburg/sia", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "70d2a17", + "last_release_tag": "v0.3.10", + "last_updated": "2020-10-30T12:42:38Z", + "name": "sia", + "new": false, + "repository_manifest": { + "name": "SIA", + "domains": [ + "binary_sensor" + ] + }, + "selected_tag": null, + "show_beta": false, + "stars": 27, + "topics": [], + "version_installed": null + }, + "237640198": { + "authors": [ + "@edenhaus" + ], + "category": "integration", + "description": "Prosenic Vacuum integration for Home Assitant", + "domain": "prosenic", + "downloads": 42, + "full_name": "edenhaus/ha-prosenic", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "cdefdf9", + "last_release_tag": "v0.4.1", + "last_updated": "2020-11-04T19:19:06Z", + "name": "prosenic", + "new": false, + "repository_manifest": { + "name": "Prosenic vacuum cleaner", + "domains": [ + "vacuum" + ], + "country": [ + "IT", + "AT" + ], + "iot_class": "Local Polling", + "zip_release": true, + "filename": "prosenic.zip", + "hide_default_branch": true, + "homeassistant": "0.109.0" + }, + "selected_tag": null, + "show_beta": false, + "stars": 1, + "topics": [ + "home-assistant", + "home-assistant-component", + "home-assistant-vacuum", + "home-automation", + "homeassistant", + "prosenic", + "vacuum", + "vacuum-cleaner" + ], + "version_installed": null + }, + "250345421": { + "authors": [ + "@eifinger" + ], + "category": "integration", + "description": "Homeassistant integration for FoldingAtHomeControl", + "domain": "foldingathomecontrol", + "downloads": 0, + "full_name": "eifinger/hass-foldingathomecontrol", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "ccc184e", + "last_release_tag": "v2.0.4", + "last_updated": "2020-11-23T08:10:03Z", + "name": "foldingathomecontrol", + "new": false, + "repository_manifest": { + "name": "Folding@HomeControl", + "domains": [ + "sensor" + ], + "homeassistant": "0.109", + "iot_class": "Local Push" + }, + "selected_tag": null, + "show_beta": false, + "stars": 10, + "topics": [], + "version_installed": null + }, + "202322117": { + "authors": [ + "@eifinger" + ], + "category": "integration", + "description": "Custom Component for Homeassistant Providing Travel Time Information using openrouteservice.org", + "domain": "open_route_service", + "downloads": 0, + "full_name": "eifinger/open_route_service", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "ae6197d", + "last_release_tag": "v1.1.0", + "last_updated": "2020-11-23T08:10:57Z", + "name": "open_route_service", + "new": false, + "repository_manifest": {}, + "selected_tag": null, + "show_beta": false, + "stars": 4, + "topics": [], + "version_installed": null + }, + "169641362": { + "authors": [ + "@elad-bar" + ], + "category": "integration", + "description": "Integration with Blue Iris Video Security Software", + "domain": "blueiris", + "downloads": 0, + "full_name": "elad-bar/ha-blueiris", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "9ca84b8", + "last_release_tag": "v1.0.0", + "last_updated": "2020-12-03T19:03:31Z", + "name": "blueiris", + "new": false, + "repository_manifest": { + "name": "Integration with Blue Iris NVR", + "iot_class": "Local Polling" + }, + "selected_tag": null, + "show_beta": false, + "stars": 66, + "topics": [], + "version_installed": null + }, + "213959778": { + "authors": [ + "@elad-bar" + ], + "category": "integration", + "description": "Dahua VTO Integration", + "domain": "dahuavto", + "downloads": 0, + "full_name": "elad-bar/ha-dahuavto", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "895da2c", + "last_release_tag": null, + "last_updated": "2020-10-01T10:34:46Z", + "name": "dahuavto", + "new": false, + "repository_manifest": { + "name": "Dahua VTO Integration", + "iot_class": "Local Polling" + }, + "selected_tag": null, + "show_beta": false, + "stars": 2, + "topics": [], + "version_installed": null + }, + "169467285": { + "authors": [ + "@elad-bar" + ], + "category": "integration", + "description": "Integration with EdgeOS (Ubiquiti)", + "domain": "edgeos", + "downloads": 0, + "full_name": "elad-bar/ha-edgeos", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "b957408", + "last_release_tag": "v1.1.0", + "last_updated": "2020-10-24T14:26:35Z", + "name": "edgeos", + "new": false, + "repository_manifest": { + "name": "Integration with EdgeOS (Ubiquiti)", + "iot_class": "Local Polling" + }, + "selected_tag": null, + "show_beta": false, + "stars": 67, + "topics": [], + "version_installed": null + }, + "220482107": { + "authors": [ + "@elad-bar" + ], + "category": "integration", + "description": "HP Printer Integration", + "domain": "hpprinter", + "downloads": 0, + "full_name": "elad-bar/ha-hpprinter", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "e234160", + "last_release_tag": "v1.0.0", + "last_updated": "2020-10-01T20:46:25Z", + "name": "hpprinter", + "new": false, + "repository_manifest": { + "name": "HP Printers Integration", + "iot_class": "Local Polling" + }, + "selected_tag": null, + "show_beta": false, + "stars": 38, + "topics": [], + "version_installed": null + }, + "255073429": { + "authors": [ + "@ericpignet" + ], + "category": "integration", + "description": "LG Hombot/Roboking Component for Home Assistant.", + "domain": "lg_hombot", + "downloads": 0, + "full_name": "ericpignet/home-assistant-lg_hombot", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "13d21d3", + "last_release_tag": "v0.0.3", + "last_updated": "2020-05-20T14:17:07Z", + "name": "lg_hombot", + "new": false, + "repository_manifest": { + "name": "LG Hombot Vacuum Cleaner", + "domains": [ + "vacuum" + ], + "homeassistant": "0.108.0" + }, + "selected_tag": null, + "show_beta": false, + "stars": 2, + "topics": [ + "custom-components", + "hacs", + "hombot", + "home-assistant", + "home-assistant-component", + "homeassistant", + "roboking" + ], + "version_installed": null + }, + "257586609": { + "authors": [ + "@ericpignet" + ], + "category": "integration", + "description": "TPLink router device tracker for Home Assistant", + "domain": "tplink_router", + "downloads": 0, + "full_name": "ericpignet/home-assistant-tplink_router", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "eaf3e98", + "last_release_tag": "v0.0.6", + "last_updated": "2020-08-31T01:37:00Z", + "name": "tplink_router", + "new": false, + "repository_manifest": { + "name": "TPLink Router", + "domains": [ + "device_tracker" + ], + "homeassistant": "0.108.0" + }, + "selected_tag": null, + "show_beta": false, + "stars": 7, + "topics": [ + "custom-components", + "hacs", + "home-assistant", + "home-assistant-component", + "homeassistant", + "tplink" + ], + "version_installed": null + }, + "262140617": { + "authors": [ + "@eyalcha" + ], + "category": "integration", + "description": "Home Assistant sensor to read water meter", + "domain": "read_your_meter", + "downloads": 0, + "full_name": "eyalcha/read_your_meter", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "23063bf", + "last_release_tag": "1.0.11", + "last_updated": "2020-12-03T21:52:35Z", + "name": "read_your_meter", + "new": false, + "repository_manifest": { + "name": "Read Your Meter", + "domains": [ + "sensor" + ], + "country": "IL", + "homeassistant": "0.106.0" + }, + "selected_tag": null, + "show_beta": false, + "stars": 18, + "topics": [ + "custom-component", + "hacs", + "home-assistant", + "sensor" + ], + "version_installed": null + }, + "177169766": { + "authors": [ + "@eseglem" + ], + "category": "integration", + "description": "Home Assistant WattBox Component", + "domain": "wattbox", + "downloads": 0, + "full_name": "eseglem/hass-wattbox", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "2b2b43e", + "last_release_tag": null, + "last_updated": "2020-10-17T21:09:05Z", + "name": "wattbox", + "new": false, + "repository_manifest": { + "name": "Wattbox", + "domains": [ + "binary_sensor", + "sensor", + "switch" + ], + "iot_class": "Local Polling", + "homeassistant": "0.115.0" + }, + "selected_tag": null, + "show_beta": false, + "stars": 7, + "topics": [ + "battery", + "hacs", + "home-assistant", + "ups", + "wattbox" + ], + "version_installed": null + }, + "213950645": { + "authors": [], + "category": "integration", + "description": "Support for interface with an ElkoEP Lara devices", + "domain": "elkoep_lara", + "downloads": 0, + "full_name": "exKAjFASH/media_player.elkoep_lara", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "c8db18b", + "last_release_tag": "v0.1.0", + "last_updated": "2019-10-09T17:12:48Z", + "name": "elkoep_lara", + "new": false, + "repository_manifest": { + "name": "ElkoEP Lara", + "render_readme": true, + "domains": [ + "media_player" + ] + }, + "selected_tag": null, + "show_beta": false, + "stars": 0, + "topics": [ + "custom-component", + "home-assistant", + "homeassistant", + "python" + ], + "version_installed": null + }, + "261291147": { + "authors": [ + "@eyalcha" + ], + "category": "integration", + "description": "Thermal camera for Home Assistant", + "domain": "thermal", + "downloads": 0, + "full_name": "eyalcha/thermal", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "bf43aa3", + "last_release_tag": "1.0.0", + "last_updated": "2020-05-05T21:52:07Z", + "name": "thermal", + "new": false, + "repository_manifest": { + "name": "Thermal", + "domains": [ + "camera", + "sensor" + ], + "homeassistant": "0.106.0" + }, + "selected_tag": null, + "show_beta": false, + "stars": 11, + "topics": [ + "camera", + "custom-component", + "home-assistant", + "sensor" + ], + "version_installed": null + }, + "178838527": { + "authors": [ + "@filipvh" + ], + "category": "integration", + "description": "Niko Home Control II Home Assistant Integration", + "domain": "nhc2", + "downloads": 0, + "full_name": "filipvh/hass-nhc2", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "2a62bb0", + "last_release_tag": "1.4.0", + "last_updated": "2020-12-06T21:22:03Z", + "name": "nhc2", + "new": false, + "repository_manifest": { + "name": "Niko Home Control II", + "domains": [ + "light", + "switch" + ], + "iot_class": [ + "Local Push" + ], + "render_readme": true, + "homeassistant": "0.114.1" + }, + "selected_tag": null, + "show_beta": false, + "stars": 10, + "topics": [ + "coco", + "domotica", + "nhc", + "nhc2", + "niko", + "niko-home-control" + ], + "version_installed": null + }, + "237880993": { + "authors": [ + "@fineemb" + ], + "category": "integration", + "description": "\u667a\u7c73\u667a\u80fd\u7535\u6696\u5668", + "domain": "miheater", + "downloads": 0, + "full_name": "fineemb/Smartmi-smart-heater", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "5d71061", + "last_release_tag": "v1.22", + "last_updated": "2020-06-09T15:24:34Z", + "name": "miheater", + "new": false, + "repository_manifest": { + "name": "Smartmi smart heater", + "domains": [ + "climate" + ], + "render_readme": true, + "homeassistant": "0.99.9", + "country": [ + "CN" + ] + }, + "selected_tag": null, + "show_beta": false, + "stars": 11, + "topics": [], + "version_installed": null + }, + "290436986": { + "authors": [ + "@fineemb" + ], + "category": "integration", + "description": "\u7528\u4e8eHASS\u7684\u5f69\u4e91\u5929\u6c14\u7ec4\u4ef6", + "domain": "colorfulclouds", + "downloads": 0, + "full_name": "fineemb/Colorfulclouds-weather", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "76e142a", + "last_release_tag": "v1.2.3", + "last_updated": "2020-11-21T04:22:45Z", + "name": "colorfulclouds", + "new": false, + "repository_manifest": { + "name": "\u5f69\u4e91\u5929\u6c14", + "domains": [ + "weather", + "sensor" + ], + "render_readme": true, + "homeassistant": "0.99.9", + "country": [ + "CN" + ] + }, + "selected_tag": null, + "show_beta": false, + "stars": 10, + "topics": [ + "hass", + "weather" + ], + "version_installed": null + }, + "296320952": { + "authors": [ + "@fineemb" + ], + "category": "integration", + "description": "HASS\u7684\u5c0f\u7c73\u4e91\u670d\u52a1\u96c6\u6210", + "domain": "xiaomi_cloud", + "downloads": 0, + "full_name": "fineemb/xiaomi-cloud", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "1b74027", + "last_release_tag": "v1.2.3", + "last_updated": "2020-10-29T07:57:39Z", + "name": "xiaomi_cloud", + "new": false, + "repository_manifest": { + "name": "\u5c0f\u7c73\u4e91\u670d\u52a1", + "domains": [ + "device_tracker", + "sensor" + ], + "render_readme": true, + "homeassistant": "0.99.9", + "country": [ + "CN" + ] + }, + "selected_tag": null, + "show_beta": false, + "stars": 15, + "topics": [ + "cloud", + "hacs", + "hass", + "xiaomi" + ], + "version_installed": null + }, + "183212377": { + "authors": [ + "@fondberg" + ], + "category": "integration", + "description": "Home assistant custom component to start Spotify playback on an idle chromecast device", + "domain": "spotcast", + "downloads": 0, + "full_name": "fondberg/spotcast", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "c3c60b3", + "last_release_tag": "v3.4.6", + "last_updated": "2020-12-02T14:40:30Z", + "name": "spotcast", + "new": false, + "repository_manifest": {}, + "selected_tag": null, + "show_beta": false, + "stars": 163, + "topics": [], + "version_installed": null + }, + "229060565": { + "authors": [ + "@fineemb" + ], + "category": "integration", + "description": "\u5c0f\u7c73\u517b\u751f\u58f6", + "domain": "health_pot", + "downloads": 0, + "full_name": "fineemb/Xiaomi-Smart-Multipurpose-Kettle", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "39384c5", + "last_release_tag": "v1.0", + "last_updated": "2020-02-05T03:56:32Z", + "name": "health_pot", + "new": false, + "repository_manifest": { + "name": "Xiaomi Smart Multipurpose Kettle", + "domains": [ + "health_pot" + ], + "render_readme": true, + "homeassistant": "0.99.9" + }, + "selected_tag": null, + "show_beta": false, + "stars": 3, + "topics": [], + "version_installed": null + }, + "217507414": { + "authors": [ + "@fineemb" + ], + "category": "integration", + "description": "\u63a5\u5165Hass\u7684\u51c9\u9738\u7ec4\u4ef6", + "domain": "yeelink", + "downloads": 0, + "full_name": "fineemb/Yeelink-ven-fan", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "27cbd60", + "last_release_tag": "v1.0", + "last_updated": "2020-01-31T15:57:47Z", + "name": "yeelink", + "new": false, + "repository_manifest": { + "name": "Yeelight ven fan", + "domains": [ + "fan" + ], + "render_readme": true, + "homeassistant": "0.99.9" + }, + "selected_tag": null, + "show_beta": false, + "stars": 6, + "topics": [], + "version_installed": null + }, + "285560672": { + "authors": [ + "@FL550" + ], + "category": "integration", + "description": "Deutscher Wetterdienst integration for Home-Assistant", + "domain": "dwd_weather", + "downloads": 0, + "full_name": "FL550/dwd_weather", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "c6d3bfb", + "last_release_tag": "v1.1.9", + "last_updated": "2020-12-05T13:30:34Z", + "name": "dwd_weather", + "new": false, + "repository_manifest": { + "name": "Deutscher Wetterdienst", + "domains": [ + "weather", + "sensor" + ], + "iot_class": "Cloud Polling", + "render_readme": false + }, + "selected_tag": null, + "show_beta": false, + "stars": 35, + "topics": [ + "deutscher-wetterdienst", + "dwd", + "dwd-weather", + "hacs", + "home-assistant", + "weather", + "weather-forecast" + ], + "version_installed": null + }, + "226707533": { + "authors": [ + "@freol35241" + ], + "category": "integration", + "description": "Long time state storage (LTSS) custom component for Home Assistant using Timescale DB", + "domain": "ltss", + "downloads": 0, + "full_name": "freol35241/ltss", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "3a60413", + "last_release_tag": null, + "last_updated": "2020-12-08T12:52:55Z", + "name": "ltss", + "new": false, + "repository_manifest": { + "name": "ltss", + "iot_class": "Local Polling" + }, + "selected_tag": null, + "show_beta": false, + "stars": 18, + "topics": [ + "database", + "hacs", + "homeassistant", + "ltss", + "state-storage", + "storage", + "timescaledb" + ], + "version_installed": null + }, + "229755760": { + "authors": [ + "@garbled1", + "@natekspencer" + ], + "category": "integration", + "description": "Balboa spa integration for home-assistant", + "domain": "balboa", + "downloads": 0, + "full_name": "garbled1/balboa_homeassistan", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "f403dd4", + "last_release_tag": "0.6", + "last_updated": "2020-12-05T15:54:42Z", + "name": "balboa", + "new": false, + "repository_manifest": { + "name": "Balboa Spa Client", + "render_readme": true, + "domains": [ + "binary_sensor", + "climate", + "fan", + "switch" + ], + "iot_class": "Local Push" + }, + "selected_tag": null, + "show_beta": false, + "stars": 5, + "topics": [ + "balboa", + "hacs", + "home-assistant", + "home-assistant-custom", + "homeassistant-integration" + ], + "version_installed": null + }, + "286094901": { + "authors": [ + "@FutureTense", + "@firstof9" + ], + "category": "integration", + "description": "Home Assistant Lock Manager", + "domain": "lock-manager", + "downloads": 0, + "full_name": "FutureTense/lock-manager", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "3b5b5c0", + "last_release_tag": "v0.0.45", + "last_updated": "2020-12-06T20:59:58Z", + "name": "lock-manager", + "new": false, + "repository_manifest": { + "name": "lock-manager", + "domains": [], + "homeassistant": "0.110.0", + "iot_class": "Local Polling" + }, + "selected_tag": null, + "show_beta": false, + "stars": 30, + "topics": [ + "home-assistant", + "home-automation", + "zwave-lock" + ], + "version_installed": null + }, + "264655935": { + "authors": [ + "@gadgetchnnel" + ], + "category": "integration", + "description": "A custom component for Home Assistant to allow regular entities to be used as a calendar", + "domain": "entities_calendar", + "downloads": 0, + "full_name": "gadgetchnnel/entities_calendar", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "a1af054", + "last_release_tag": "0.0.6", + "last_updated": "2020-08-07T18:31:54Z", + "name": "entities_calendar", + "new": false, + "repository_manifest": { + "name": "Entities Calendar", + "content_in_root": false, + "domains": [ + "calendar" + ] + }, + "selected_tag": null, + "show_beta": false, + "stars": 5, + "topics": [ + "calendar", + "entities-calendar" + ], + "version_installed": null + }, + "201599575": { + "authors": [ + "@gcorgnet" + ], + "category": "integration", + "description": "Home Assistant component to feed Upcoming Media Card with the latest releases on an Emby instance.", + "domain": "emby_upcoming_media", + "downloads": 0, + "full_name": "gcorgnet/sensor.emby_upcoming_media", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "aca2451", + "last_release_tag": "0.3.4", + "last_updated": "2020-05-10T17:34:20Z", + "name": "emby_upcoming_media", + "new": false, + "repository_manifest": { + "name": "Emby Latest Media" + }, + "selected_tag": null, + "show_beta": false, + "stars": 3, + "topics": [], + "version_installed": null + }, + "220685552": { + "authors": [ + "@freakshock88" + ], + "category": "integration", + "description": "Custom component for Home Assistant which generates a sensor to show popularity for a google maps place.", + "domain": "populartimes", + "downloads": 0, + "full_name": "freakshock88/hass-populartimes", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "f193661", + "last_release_tag": "0.22", + "last_updated": "2020-07-30T21:02:26Z", + "name": "populartimes", + "new": false, + "repository_manifest": { + "name": "Popular Times", + "render_readme": true, + "domains": [ + "sensor" + ] + }, + "selected_tag": null, + "show_beta": false, + "stars": 5, + "topics": [ + "google-maps", + "google-places-api", + "homeassistant" + ], + "version_installed": null + }, + "264490983": { + "authors": [ + "@GeorgeSG" + ], + "category": "integration", + "description": "Slack User sensor for Home Assistant", + "domain": "slack_user", + "downloads": 0, + "full_name": "GeorgeSG/ha-slack-user", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "ff12b58", + "last_release_tag": "0.2.1", + "last_updated": "2020-10-05T08:40:33Z", + "name": "slack_user", + "new": false, + "repository_manifest": { + "name": "Slack User", + "domains": [ + "sensor" + ], + "render_readme": true, + "iot_class": "Cloud Polling" + }, + "selected_tag": null, + "show_beta": false, + "stars": 6, + "topics": [ + "home-assistant", + "home-assistant-component", + "home-assistant-custom", + "homeassistant", + "homeassistant-integration" + ], + "version_installed": null + }, + "199306003": { + "authors": [ + "@gieljnssns" + ], + "category": "integration", + "description": "Buienalarm custom_component for Home-Assistant", + "domain": "buienalarm", + "downloads": 0, + "full_name": "gieljnssns/buienalarm-sensor-homeassistant", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "2590418", + "last_release_tag": "v1.3", + "last_updated": "2020-10-25T11:00:13Z", + "name": "buienalarm", + "new": false, + "repository_manifest": { + "name": "Buienalarm", + "render_readme": true, + "domains": [ + "sensor" + ] + }, + "selected_tag": null, + "show_beta": false, + "stars": 7, + "topics": [], + "version_installed": null + }, + "261311061": { + "authors": [ + "@garbled1" + ], + "category": "integration", + "description": "Ecowitt Weather Station integration for homeassistant", + "domain": "ecowitt", + "downloads": 0, + "full_name": "garbled1/homeassistant_ecowitt", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "649876b", + "last_release_tag": "0.5", + "last_updated": "2020-11-28T12:15:31Z", + "name": "ecowitt", + "new": false, + "repository_manifest": { + "name": "Ecowitt Weather Station", + "render_readme": true, + "domains": [ + "sensor", + "binary_sensor" + ], + "iot_class": "Local Push" + }, + "selected_tag": null, + "show_beta": false, + "stars": 17, + "topics": [ + "ecowitt", + "hacs", + "home-assistant", + "home-assistant-custom", + "homeassistant-integration" + ], + "version_installed": null + }, + "222292912": { + "authors": [ + "@gcobb321" + ], + "category": "integration", + "description": "iCloud3 is a device_tracker custom_component for iPhones, iPads & iWatches that monitors zone & location events triggered by the HA iOS Companion App", + "domain": "icloud3", + "downloads": 899, + "full_name": "gcobb321/icloud3", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "6f3893f", + "last_release_tag": "v2.2.1", + "last_updated": "2020-11-26T22:34:20Z", + "name": "icloud3", + "new": false, + "repository_manifest": { + "name": "iCloud3 Device Tracker", + "zip_release": true, + "render_readme": true, + "domains": [ + "device_tracker" + ], + "filename": "icloud3.zip" + }, + "selected_tag": null, + "show_beta": false, + "stars": 93, + "topics": [ + "device-tracker", + "icloud", + "icloud-account", + "tracking", + "zones" + ], + "version_installed": null + }, + "263757123": { + "authors": [ + "@finity69x2" + ], + "category": "integration", + "description": "An updated version of the nws_alerts custom integration for Home Assistant", + "domain": "nws_alerts", + "downloads": 0, + "full_name": "finity69x2/nws_alerts", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "e0b4a39", + "last_release_tag": "1.4", + "last_updated": "2020-08-08T16:42:18Z", + "name": "nws_alerts", + "new": false, + "repository_manifest": { + "name": "NWS Alerts", + "domains": [ + "sensor" + ], + "homeassistant": "0.95.4", + "iot_class": "Cloud Polling" + }, + "selected_tag": null, + "show_beta": false, + "stars": 3, + "topics": [ + "alerts", + "assistant", + "home", + "sensor", + "weather" + ], + "version_installed": null + }, + "199306511": { + "authors": [ + "@gieljnssns" + ], + "category": "integration", + "description": "A custom component to get the readings of a Kostal Piko inverter", + "domain": "kostal", + "downloads": 0, + "full_name": "gieljnssns/kostalpiko-sensor-homeassistant", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "4b79281", + "last_release_tag": "v2.7", + "last_updated": "2020-10-30T13:39:35Z", + "name": "kostal", + "new": false, + "repository_manifest": { + "name": "Kostal Piko", + "render_readme": true, + "domains": [ + "sensor" + ] + }, + "selected_tag": null, + "show_beta": false, + "stars": 4, + "topics": [], + "version_installed": null + }, + "265716369": { + "authors": [ + "@gilsonmandalogo" + ], + "category": "integration", + "description": "Minerstat mining hashrate.", + "domain": "hacs-minerstat", + "downloads": 0, + "full_name": "gilsonmandalogo/hacs-minerstat", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "81d1e40", + "last_release_tag": "1.1.0", + "last_updated": "2020-10-03T21:50:32Z", + "name": "hacs-minerstat", + "new": false, + "repository_manifest": { + "name": "Minerstat", + "render_readme": "true", + "domains": [ + "sensor" + ] + }, + "selected_tag": null, + "show_beta": false, + "stars": 0, + "topics": [ + "hacktoberfest", + "hacs", + "homeassistant", + "minerstat", + "mining" + ], + "version_installed": null + }, + "261873234": { + "authors": [ + "@gjohansson-ST" + ], + "category": "integration", + "description": "Integration to Sector Alarm for Home Assistant", + "domain": "sector", + "downloads": 0, + "full_name": "gjohansson-ST/sector", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "26ff519", + "last_release_tag": "v0.2.0", + "last_updated": "2020-11-17T10:13:44Z", + "name": "sector", + "new": false, + "repository_manifest": { + "name": "Sector Alarm", + "render_readme": true, + "domains": [ + "sensor", + "lock", + "alarm_control_panel" + ], + "iot_class": "Cloud Polling" + }, + "selected_tag": null, + "show_beta": false, + "stars": 6, + "topics": [ + "alarm", + "alarm-control", + "alarm-control-panel", + "lock", + "sector", + "sector-alarm", + "temperature-sensor" + ], + "version_installed": null + }, + "207794683": { + "authors": [], + "category": "integration", + "description": "Google Location for HASS using the Google Geocode API", + "domain": "google_geocode", + "downloads": 0, + "full_name": "gregoryduckworth/GoogleGeocode-HASS", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "0d1ce34", + "last_release_tag": "0.1.3", + "last_updated": "2019-12-20T20:06:45Z", + "name": "google_geocode", + "new": false, + "repository_manifest": { + "name": "GoogleGeocode-HASS", + "render_readme": true + }, + "selected_tag": null, + "show_beta": false, + "stars": 4, + "topics": [], + "version_installed": null + }, + "263075818": { + "authors": [ + "@guysie" + ], + "category": "integration", + "description": "Integration for Meural Canvas digital art frame in Home Assistant ", + "domain": "meural", + "downloads": 0, + "full_name": "GuySie/ha-meural", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "cbce0d9", + "last_release_tag": "v0.2.0", + "last_updated": "2020-11-22T12:48:51Z", + "name": "meural", + "new": false, + "repository_manifest": { + "name": "HA-meural", + "domains": [ + "media_player" + ], + "iot_class": "Cloud Polling" + }, + "selected_tag": null, + "show_beta": false, + "stars": 14, + "topics": [ + "home-assistant", + "homeassistant", + "homeassistant-components", + "homeassistant-integration", + "meural", + "netgear" + ], + "version_installed": null + }, + "164419416": { + "authors": [ + "@epenet" + ], + "category": "integration", + "description": "\ud83d\ude97 Renault ZE sensor for home assistant", + "domain": "renault", + "downloads": 0, + "full_name": "hacf-fr/hassRenaultZE", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "1c024b0", + "last_release_tag": "v3.0.2", + "last_updated": "2020-12-04T14:18:49Z", + "name": "renault", + "new": false, + "repository_manifest": { + "name": "Renault", + "hacs": "0.24.0", + "domains": [ + "binary_sensor", + "climate", + "device_tracker", + "sensor" + ], + "iot_class": "Cloud Polling", + "homeassistant": "0.115.0" + }, + "selected_tag": null, + "show_beta": false, + "stars": 21, + "topics": [ + "custom-components", + "home-assistant", + "renault", + "renault-ze", + "renault-zoe" + ], + "version_installed": null + }, + "120370503": { + "authors": [], + "category": "integration", + "description": "Home Assistant Omnik Solar sensor component", + "domain": "omnik", + "downloads": 0, + "full_name": "heinoldenhuis/home_assistant_omnik_solar", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "79002d3", + "last_release_tag": "0.0.4", + "last_updated": "2019-12-27T12:01:43Z", + "name": "omnik", + "new": false, + "repository_manifest": {}, + "selected_tag": null, + "show_beta": false, + "stars": 8, + "topics": [ + "home-assistant", + "home-assistant-component", + "homeassistant", + "integration", + "omnik", + "solar" + ], + "version_installed": null + }, + "292197182": { + "authors": [ + "@hcoohb" + ], + "category": "integration", + "description": "Home assistant custom component for Yeelight bluetooth", + "domain": "yeelight_bt", + "downloads": 0, + "full_name": "hcoohb/hass-yeelightbt", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "584b010", + "last_release_tag": "v0.11.2", + "last_updated": "2020-10-03T03:20:43Z", + "name": "yeelight_bt", + "new": false, + "repository_manifest": { + "name": "Yeelight bluetooth", + "render_readme": true, + "domains": [ + "yeelight_bt", + "light" + ], + "homeassistant": "0.109.0", + "iot_class": "Local Polling" + }, + "selected_tag": null, + "show_beta": false, + "stars": 7, + "topics": [ + "bluetooth", + "bluetooth-low-energy", + "custom-component", + "home-assistant", + "yeelight-lamp" + ], + "version_installed": null + }, + "243122556": { + "authors": [ + "@hellowlol" + ], + "category": "integration", + "description": "Tide a sensor for HASS.", + "domain": "tide", + "downloads": 0, + "full_name": "Hellowlol/ha-tide", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "de0fb07", + "last_release_tag": null, + "last_updated": "2020-04-12T11:22:39Z", + "name": "tide", + "new": false, + "repository_manifest": { + "name": "tide", + "content_in_root": false, + "country": "NOR", + "domains": [ + "sensors" + ], + "iot_class": "cloud_poll", + "homeassistant": "0.96.0", + "render_readme": true + }, + "selected_tag": null, + "show_beta": false, + "stars": 4, + "topics": [ + "hass", + "norway", + "tide" + ], + "version_installed": null + }, + "143340728": { + "authors": [ + "@herikw" + ], + "category": "integration", + "description": "Atag One Custom components for Home-Assistant", + "domain": "atagone", + "downloads": 0, + "full_name": "herikw/home-assistant-custom-components", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "309db37", + "last_release_tag": "v1.0.7", + "last_updated": "2020-08-30T23:34:19Z", + "name": "atagone", + "new": false, + "repository_manifest": { + "name": "ATAG One", + "country": "NL", + "render_readme": false, + "domains": [ + "sensor", + "climate" + ] + }, + "selected_tag": null, + "show_beta": false, + "stars": 9, + "topics": [ + "atag", + "custom-components", + "hacs", + "home-assistant", + "integration", + "thermostat" + ], + "version_installed": null + }, + "234118477": { + "authors": [ + "@heyajohnny" + ], + "category": "integration", + "description": "Provides Home Assistant sensors for multiple Dutch waste collectors. The idea is to add more cities and features in the future.", + "domain": "afvalinfo", + "downloads": 0, + "full_name": "heyajohnny/afvalinfo", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "1291a28", + "last_release_tag": "v0.5.35", + "last_updated": "2020-11-22T20:01:05Z", + "name": "afvalinfo", + "new": false, + "repository_manifest": { + "name": "Afvalinfo", + "content_in_root": false, + "render_readme": true, + "domains": [ + "sensor" + ], + "country": [ + "NL" + ], + "homeassistant": "0.100.0" + }, + "selected_tag": null, + "show_beta": false, + "stars": 34, + "topics": [], + "version_installed": null + }, + "264499592": { + "authors": [ + "@heyajohnny" + ], + "category": "integration", + "description": "Provides Home Assistant sensors for all cryptocurrencies supported by CoinGecko", + "domain": "cryptoinfo", + "downloads": 0, + "full_name": "heyajohnny/cryptoinfo", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "c42b1b6", + "last_release_tag": "v0.0.6", + "last_updated": "2020-10-16T09:27:43Z", + "name": "cryptoinfo", + "new": false, + "repository_manifest": { + "name": "Cryptoinfo", + "content_in_root": false, + "render_readme": true, + "domains": [ + "sensor" + ], + "homeassistant": "0.100.0" + }, + "selected_tag": null, + "show_beta": false, + "stars": 6, + "topics": [], + "version_installed": null + }, + "234983286": { + "authors": [ + "@thrilleratplay" + ], + "category": "integration", + "description": "Govee Temperature/Humidity BLE Home Assistant Component", + "domain": "govee_ble_hci", + "downloads": 0, + "full_name": "Home-Is-Where-You-Hang-Your-Hack/sensor.goveetemp_bt_hci", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "d4af7e9", + "last_release_tag": "v0.7.1", + "last_updated": "2020-11-29T05:27:44Z", + "name": "govee_ble_hci", + "new": false, + "repository_manifest": { + "name": "Govee BLE HCI monitor sensor integration", + "domains": "sensor" + }, + "selected_tag": null, + "show_beta": false, + "stars": 47, + "topics": [ + "ble", + "govee", + "h5051", + "h5072", + "h5074", + "h5075", + "h5101", + "h5102", + "hacs", + "home-assistant", + "home-assistant-component" + ], + "version_installed": null + }, + "269316095": { + "authors": [ + "@philklei", + "@imicknl", + "@vlebourl", + "@tetienne" + ], + "category": "integration", + "description": "Custom component for Home Assistant to interact with Somfy devices via TahomaLink", + "domain": "tahoma", + "downloads": 0, + "full_name": "iMicknl/ha-tahoma", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "70b339c", + "last_release_tag": "v2.3.7", + "last_updated": "2020-12-07T13:33:27Z", + "name": "tahoma", + "new": false, + "repository_manifest": { + "name": "Somfy TaHoma", + "domains": [ + "cover", + "light", + "lock", + "sensor", + "switch", + "climate" + ], + "homeassistant": "0.99.9", + "render_readme": "true", + "iot_class": "Cloud Polling" + }, + "selected_tag": null, + "show_beta": false, + "stars": 41, + "topics": [ + "hacktoberfest", + "home-assistant", + "somfy", + "tahoma" + ], + "version_installed": null + }, + "266779715": { + "authors": [ + "@iprak" + ], + "category": "integration", + "description": "Home Assistant component which allows you to get stock updates from Yahoo finance.", + "domain": "yahoofinance", + "downloads": 0, + "full_name": "iprak/yahoofinance", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "31b5051", + "last_release_tag": "v0.3", + "last_updated": "2020-12-05T13:30:16Z", + "name": "yahoofinance", + "new": false, + "repository_manifest": { + "name": "Yahoo Finance", + "domains": [ + "sensor" + ], + "iot_class": "cloud_poll", + "render_readme": true + }, + "selected_tag": null, + "show_beta": false, + "stars": 13, + "topics": [ + "hacs", + "home-assistant", + "homeassistant", + "homeassistant-components", + "stock-updates", + "yahoo-finance" + ], + "version_installed": null + }, + "164841067": { + "authors": [ + "@isabellaalstrom" + ], + "category": "integration", + "description": "A custom component for Home Assistant to get messages from krisinformation.se", + "domain": "krisinformation", + "downloads": 0, + "full_name": "isabellaalstrom/sensor.krisinformation", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "68e092f", + "last_release_tag": "1.0.1", + "last_updated": "2020-08-13T09:25:46Z", + "name": "krisinformation", + "new": false, + "repository_manifest": {}, + "selected_tag": null, + "show_beta": false, + "stars": 22, + "topics": [ + "hacktoberfest" + ], + "version_installed": null + }, + "309018094": { + "authors": [ + "@itchannel" + ], + "category": "integration", + "description": "Fordpass integration for Home Assistant", + "domain": "fordpass", + "downloads": 0, + "full_name": "itchannel/fordpass-ha", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "cf8c3fd", + "last_release_tag": "1.0.9", + "last_updated": "2020-12-04T11:36:03Z", + "name": "fordpass", + "new": false, + "repository_manifest": { + "name": "fordpass", + "content_in_root": false, + "iot_class": "Cloud Polling" + }, + "selected_tag": null, + "show_beta": false, + "stars": 11, + "topics": [ + "assistant", + "car", + "fordpass", + "home" + ], + "version_installed": null + }, + "231989179": { + "authors": [ + "al-fontes-jr", + "JAAlperin" + ], + "category": "integration", + "description": "HASS custom component to load and run Bardolph (simple scripting utility for LIFX light bulbs by Al Fontes, Jr.)", + "domain": "bardolph", + "downloads": 0, + "full_name": "JAAlperin/hass-bardolph", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "e855c80", + "last_release_tag": null, + "last_updated": "2020-08-08T11:53:58Z", + "name": "bardolph", + "new": false, + "repository_manifest": { + "name": "HASS Bardolph" + }, + "selected_tag": null, + "show_beta": false, + "stars": 1, + "topics": [ + "bardolph", + "color-bulb", + "home-assistant", + "lifx", + "lifx-lan-protocol", + "scripts", + "services" + ], + "version_installed": null + }, + "242700009": { + "authors": [ + "@ITTV-Tools" + ], + "category": "integration", + "description": "Home Assistant Component for Kostal Plenticore ", + "domain": "kostal_plenticore", + "downloads": 0, + "full_name": "ITTV-tools/homeassistant-kostalplenticore", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "fc1a370", + "last_release_tag": "0.9.2", + "last_updated": "2020-11-09T12:10:26Z", + "name": "kostal_plenticore", + "new": false, + "repository_manifest": { + "name": "Kostal Plenticore", + "domains": [ + "sensor" + ], + "iot_class": "Local Polling" + }, + "selected_tag": null, + "show_beta": false, + "stars": 10, + "topics": [ + "component", + "home-assistant", + "kostal", + "plenticore" + ], + "version_installed": null + }, + "223541049": { + "authors": [ + "@jaruba" + ], + "category": "integration", + "description": "\ud83d\udcfa HomeAssistant - For Samsung TVs 2016+, Includes SmartThings API and Channel List Support", + "domain": "samsungtv_tizen", + "downloads": 0, + "full_name": "jaruba/ha-samsungtv-tizen", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "9965c12", + "last_release_tag": "v1.5.6", + "last_updated": "2020-11-22T17:04:28Z", + "name": "samsungtv_tizen", + "new": false, + "repository_manifest": { + "name": "SamsungTV Tizen", + "domains": [ + "media-player" + ] + }, + "selected_tag": null, + "show_beta": false, + "stars": 108, + "topics": [], + "version_installed": null + }, + "183989659": { + "authors": [ + "@jayblackedout" + ], + "category": "integration", + "description": "NHL Stats API Integration Into Home Assistant", + "domain": "nhl_api", + "downloads": 0, + "full_name": "JayBlackedOut/hass-nhlapi", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "8b24a4f", + "last_release_tag": "v0.5.4", + "last_updated": "2020-08-29T16:27:58Z", + "name": "nhl_api", + "new": false, + "repository_manifest": { + "name": "NHL API" + }, + "selected_tag": null, + "show_beta": false, + "stars": 14, + "topics": [], + "version_installed": null + }, + "235943258": { + "authors": [ + "@jason0x43" + ], + "category": "integration", + "description": "A Hubitat integration for Home Assistant", + "domain": "hubitat", + "downloads": 0, + "full_name": "jason0x43/hacs-hubitat", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "ee64c7d", + "last_release_tag": "v0.5.16", + "last_updated": "2020-12-08T01:41:10Z", + "name": "hubitat", + "new": false, + "repository_manifest": { + "name": "Hubitat", + "domains": [ + "binary_sensor", + "climate", + "light", + "sensor", + "switch" + ], + "country": "US", + "iot_class": "local_push" + }, + "selected_tag": null, + "show_beta": false, + "stars": 44, + "topics": [ + "hacs", + "home-assistant", + "hubitat", + "maker-api" + ], + "version_installed": null + }, + "212552703": { + "authors": [ + "@jensweimann" + ], + "category": "integration", + "description": "Home Assistant sensor for german AWB waste collection schedule", + "domain": "awb", + "downloads": 0, + "full_name": "jensweimann/awb", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "ae65016", + "last_release_tag": "1.1", + "last_updated": "2020-06-04T08:09:39Z", + "name": "awb", + "new": false, + "repository_manifest": { + "name": "AWB - Home Assistant sensor for german AWB waste collection schedule", + "country": "DE", + "homeassistant": "0.98.0" + }, + "selected_tag": null, + "show_beta": false, + "stars": 2, + "topics": [ + "hacs", + "homeassistant", + "homeassistant-sensor" + ], + "version_installed": null + }, + "221855213": { + "authors": [ + "@jcwillox" + ], + "category": "integration", + "description": "\ud83d\uddc3\ufe0f Improved Backup Service for Hass.io that can Automatically Remove Snapshots and Supports Generational Backup Schemes.", + "domain": "auto_backup", + "downloads": 3348, + "full_name": "jcwillox/hass-auto-backup", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "9a0d1df", + "last_release_tag": "0.6.0", + "last_updated": "2020-08-31T12:41:42Z", + "name": "auto_backup", + "new": false, + "repository_manifest": { + "name": "Auto Backup", + "zip_release": true, + "hide_default_branch": true, + "filename": "auto_backup.zip" + }, + "selected_tag": null, + "show_beta": false, + "stars": 60, + "topics": [ + "auto-purge", + "generational-backups", + "hassio", + "homeassistant", + "snapshots" + ], + "version_installed": null + }, + "273333188": { + "authors": [ + "@jeroenterheerdt" + ], + "category": "integration", + "description": "Sensor for Home Assistant that gets reset at midnight", + "domain": "daily", + "downloads": 0, + "full_name": "jeroenterheerdt/HADailySensor", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "ca52841", + "last_release_tag": "v0.3", + "last_updated": "2020-11-05T21:51:19Z", + "name": "daily", + "new": false, + "repository_manifest": { + "name": "Daily Sensor", + "render_readme": true, + "domains": [ + "sensor" + ] + }, + "selected_tag": null, + "show_beta": false, + "stars": 9, + "topics": [ + "aggregation", + "average", + "home-assistant", + "home-automation", + "homeassistant", + "max", + "maximum", + "mean", + "median", + "min", + "minimum", + "sensor", + "standard-deviation", + "statistics", + "stdev", + "sum", + "var", + "variance" + ], + "version_installed": null + }, + "263179176": { + "authors": [ + "@jeroenterheerdt" + ], + "category": "integration", + "description": "Smart Irrigation custom component for Home Assistant", + "domain": "smart_irrigation", + "downloads": 0, + "full_name": "jeroenterheerdt/HAsmartirrigation", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "bcba87d", + "last_release_tag": "v0.0.64", + "last_updated": "2020-10-02T16:46:22Z", + "name": "smart_irrigation", + "new": false, + "repository_manifest": { + "name": "Smart Irrigation", + "render_readme": true, + "domains": [ + "sensor" + ] + }, + "selected_tag": null, + "show_beta": false, + "stars": 42, + "topics": [ + "crop", + "evaporation", + "evapotranspiration", + "flow", + "grass", + "home-automation", + "homeassistant", + "irrigation", + "lawn", + "openweathermap", + "rain", + "snow", + "sprinkler", + "sprinklers", + "water", + "watering" + ], + "version_installed": null + }, + "278596510": { + "authors": [ + "@jesserockz" + ], + "category": "integration", + "description": "A Home Assistant integration to receive live data sent from the LeafSpy app", + "domain": "leafspy", + "downloads": 0, + "full_name": "jesserockz/ha-leafspy", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "7ceeb9b", + "last_release_tag": "v0.1.1", + "last_updated": "2020-09-21T00:35:10Z", + "name": "leafspy", + "new": false, + "repository_manifest": { + "name": "Leaf Spy", + "hacs": "0.24.0", + "domains": [ + "device_tracker" + ], + "iot_class": "Local Push", + "homeassistant": "0.110.0" + }, + "selected_tag": null, + "show_beta": false, + "stars": 2, + "topics": [ + "electric-vehicles", + "ev", + "hacs", + "home-assistant", + "leaf", + "leafspy", + "nissan" + ], + "version_installed": null + }, + "190982718": { + "authors": [ + "@jihao" + ], + "category": "integration", + "description": "A hass component to integrate with colorfulclouds (\u5f69\u4e91\u5929\u6c14)", + "domain": "colorfulclouds", + "downloads": 0, + "full_name": "jihao/colorfulclouds-hass", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "2b01bb0", + "last_release_tag": "v0.1.2", + "last_updated": "2019-06-20T12:38:40Z", + "name": "colorfulclouds", + "new": false, + "repository_manifest": {}, + "selected_tag": null, + "show_beta": false, + "stars": 17, + "topics": [], + "version_installed": null + }, + "187201747": { + "authors": [], + "category": "integration", + "description": "rokid webhook component for Home Assistant (\u82e5\u742aHA\u7ec4\u4ef6)", + "domain": "rokid_webhook", + "downloads": 0, + "full_name": "jihao/rokid-webhook-hass", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "3c97e80", + "last_release_tag": "0.1.3", + "last_updated": "2019-07-19T10:51:41Z", + "name": "rokid_webhook", + "new": false, + "repository_manifest": {}, + "selected_tag": null, + "show_beta": false, + "stars": 11, + "topics": [], + "version_installed": null + }, + "198758494": { + "authors": [ + "@ludeeus", + "adapt by @jihao" + ], + "category": "integration", + "description": "A hass component to integrate with traccar_cn which adapts Chinese map coordinates (\u4e2d\u6587\u5730\u56fe traccar.cn)", + "domain": "traccar_cn", + "downloads": 0, + "full_name": "jihao/traccar-cn-hass", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "b426dfe", + "last_release_tag": "0.1.0", + "last_updated": "2020-08-28T09:51:37Z", + "name": "traccar_cn", + "new": false, + "repository_manifest": {}, + "selected_tag": null, + "show_beta": false, + "stars": 15, + "topics": [], + "version_installed": null + }, + "192664631": { + "authors": [ + "@jm-73", + "@eavanvalkenburg" + ], + "category": "integration", + "description": "Home Assistant Custom Component for Bosch Indego Lawn Mower", + "domain": "indego", + "downloads": 0, + "full_name": "jm-73/Indego", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "ebf7d1a", + "last_release_tag": "3.7", + "last_updated": "2020-08-14T12:30:06Z", + "name": "indego", + "new": false, + "repository_manifest": { + "name": "Bosch Indego Mower", + "country": "SE", + "domains": [ + "sensor", + "binary_sensor" + ], + "homeassistant": "0.110.0" + }, + "selected_tag": null, + "show_beta": false, + "stars": 16, + "topics": [ + "bosch-mower", + "hacs", + "home-assistant", + "homeassistant-components", + "indego", + "iot", + "sensor" + ], + "version_installed": null + }, + "193732581": { + "authors": [ + "@jomwells" + ], + "category": "integration", + "description": "ON/OFF Abilight+Hue (Switch) component for Philips Ambilight TV's", + "domain": "philips_ambilight+hue", + "downloads": 0, + "full_name": "jomwells/ambihue", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "42ae8b8", + "last_release_tag": null, + "last_updated": "2020-05-30T14:17:18Z", + "name": "philips_ambilight+hue", + "new": false, + "repository_manifest": {}, + "selected_tag": null, + "show_beta": false, + "stars": 7, + "topics": [], + "version_installed": null + }, + "297106424": { + "authors": [ + "@joggs" + ], + "category": "integration", + "description": "Integration for Ebeco thermostats", + "domain": "ebeco", + "downloads": 0, + "full_name": "joggs/home_assistant_ebeco", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "e8b7ec9", + "last_release_tag": "0.2", + "last_updated": "2020-09-21T19:39:35Z", + "name": "ebeco", + "new": false, + "repository_manifest": { + "name": "Ebeco thermostats", + "domains": [ + "climate" + ], + "iot_class": "Cloud Polling" + }, + "selected_tag": null, + "show_beta": false, + "stars": 4, + "topics": [ + "ebeco", + "hacs", + "homeassistant" + ], + "version_installed": null + }, + "239339530": { + "authors": [ + "@johnwulp" + ], + "category": "integration", + "description": "Home Assisant sensor component for RAD Hoekschewaard Afval Kalender", + "domain": "rad-afval", + "downloads": 0, + "full_name": "Johnwulp/rad-afval", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "a95a54f", + "last_release_tag": null, + "last_updated": "2020-02-10T20:54:07Z", + "name": "rad-afval", + "new": false, + "repository_manifest": { + "name": "RAD Hoekschewaard Afval Kalender", + "content_in_root": false, + "render_readme": true, + "domains": [ + "sensor" + ], + "country": [ + "NL" + ], + "homeassistant": "0.100.0" + }, + "selected_tag": null, + "show_beta": false, + "stars": 3, + "topics": [], + "version_installed": null + }, + "193746664": { + "authors": [ + "@jomwells" + ], + "category": "integration", + "description": "A switch component which mimics the functionality of Ambilight+Hue for all Yeelight lights/bulbs", + "domain": "philips_ambilight+yeelight", + "downloads": 0, + "full_name": "jomwells/ambilight-yeelight", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "5c4502e", + "last_release_tag": null, + "last_updated": "2020-05-29T18:20:34Z", + "name": "philips_ambilight+yeelight", + "new": false, + "repository_manifest": {}, + "selected_tag": null, + "show_beta": false, + "stars": 15, + "topics": [], + "version_installed": null + }, + "147953507": { + "authors": [ + "@jomwells", + "@hutchinsane" + ], + "category": "integration", + "description": "Custom Home Assistant (Light) Component for Ambilight LED's on Philips Android TV's", + "domain": "philips_ambilight", + "downloads": 0, + "full_name": "jomwells/ambilights", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "1389c5e", + "last_release_tag": null, + "last_updated": "2020-10-12T16:24:06Z", + "name": "philips_ambilight", + "new": false, + "repository_manifest": {}, + "selected_tag": null, + "show_beta": false, + "stars": 33, + "topics": [], + "version_installed": null + }, + "235659413": { + "authors": [ + "@JonasPed" + ], + "category": "integration", + "description": "Home Assistant Custom Component showing data from eloverblik.dk", + "domain": "eloverblik", + "downloads": 0, + "full_name": "JonasPed/homeassistant-eloverblik", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "5d76abc", + "last_release_tag": null, + "last_updated": "2020-10-03T17:58:51Z", + "name": "eloverblik", + "new": false, + "repository_manifest": { + "name": "Eloverblik", + "render_readme": true + }, + "selected_tag": null, + "show_beta": false, + "stars": 34, + "topics": [], + "version_installed": null + }, + "246939713": { + "authors": [ + "@jonkristian" + ], + "category": "integration", + "description": "Home Assistant component for Trondheim renholdsverk bin pickups.", + "domain": "wasteplan_trv", + "downloads": 0, + "full_name": "jonkristian/wasteplan_trv", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "dbc7799", + "last_release_tag": null, + "last_updated": "2020-03-16T22:30:50Z", + "name": "wasteplan_trv", + "new": false, + "repository_manifest": { + "name": "Wasteplan TRV", + "domains": "sensor", + "country": "NO", + "render_readme": true + }, + "selected_tag": null, + "show_beta": false, + "stars": 3, + "topics": [], + "version_installed": null + }, + "262803775": { + "authors": [ + "@jscruz" + ], + "category": "integration", + "description": "Carbon Intensity UK Sensor for Home Assistant", + "domain": "carbon_intensity_uk", + "downloads": 0, + "full_name": "jscruz/sensor.carbon_intensity_uk", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "881d9ed", + "last_release_tag": "0.0.2", + "last_updated": "2020-05-28T23:57:07Z", + "name": "carbon_intensity_uk", + "new": false, + "repository_manifest": { + "name": "Carbon Intensity UK", + "hacs": "0.24.0", + "domains": [ + "sensor" + ], + "iot_class": "Cloud Polling", + "homeassistant": "0.108.0" + }, + "selected_tag": null, + "show_beta": false, + "stars": 0, + "topics": [ + "carbon", + "custom-component", + "custom-integration", + "energy", + "hacs", + "home-assistant-custom", + "sensor-platform" + ], + "version_installed": null + }, + "299123388": { + "authors": [ + "@jseidl" + ], + "category": "integration", + "description": "Magic Areas custom_component for Home Assistant", + "domain": "magic_areas", + "downloads": 0, + "full_name": "jseidl/hass-magic_areas", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "ffa60b5", + "last_release_tag": "v.1.1.2", + "last_updated": "2020-12-08T08:00:45Z", + "name": "magic_areas", + "new": false, + "repository_manifest": { + "name": "Magic Areas", + "homeassistant": "0.115.0" + }, + "selected_tag": null, + "show_beta": false, + "stars": 53, + "topics": [ + "automation", + "homeassistant", + "python" + ], + "version_installed": null + }, + "170309600": { + "authors": [ + "@JurajNyiri" + ], + "category": "integration", + "description": "Custom component allowing control of Atrea ventilation units", + "domain": "atrea", + "downloads": 0, + "full_name": "JurajNyiri/HomeAssistant-Atrea", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "3afc12b", + "last_release_tag": "4.3.1", + "last_updated": "2020-08-16T15:18:58Z", + "name": "atrea", + "new": false, + "repository_manifest": {}, + "selected_tag": null, + "show_beta": false, + "stars": 6, + "topics": [], + "version_installed": null + }, + "199291345": { + "authors": [ + "@JurajNyiri" + ], + "category": "integration", + "description": "Adds ability to switch alternative speed in qBittorrent through Home Assistant.", + "domain": "qbittorrent_alternative_speed", + "downloads": 0, + "full_name": "JurajNyiri/HomeAssistant-qBitTorrentAlternativeSpeed", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "232f4e5", + "last_release_tag": "1.3.1", + "last_updated": "2020-05-21T10:57:00Z", + "name": "qbittorrent_alternative_speed", + "new": false, + "repository_manifest": {}, + "selected_tag": null, + "show_beta": false, + "stars": 3, + "topics": [], + "version_installed": null + }, + "300358676": { + "authors": [ + "@JurajNyiri" + ], + "category": "integration", + "description": "Control for Tapo cameras as a Home Assistant component", + "domain": "tapo_control", + "downloads": 0, + "full_name": "JurajNyiri/HomeAssistant-Tapo-Control", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "d8b500a", + "last_release_tag": "2.3.2", + "last_updated": "2020-12-05T23:18:14Z", + "name": "tapo_control", + "new": false, + "repository_manifest": { + "name": "Tapo: Cameras Control" + }, + "selected_tag": null, + "show_beta": false, + "stars": 45, + "topics": [ + "camera", + "cameras", + "hacktoberfest", + "ptz", + "tapo" + ], + "version_installed": null + }, + "175020245": { + "authors": [ + "@JurajNyiri" + ], + "category": "integration", + "description": "Sensor which gathers water outage information from Tavos (Slovakia) website", + "domain": "tavos_water_outage", + "downloads": 0, + "full_name": "JurajNyiri/HomeAssistant-Tavos", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "d277d14", + "last_release_tag": "0.3.7", + "last_updated": "2019-12-12T12:28:39Z", + "name": "tavos_water_outage", + "new": false, + "repository_manifest": {}, + "selected_tag": null, + "show_beta": false, + "stars": 0, + "topics": [], + "version_installed": null + }, + "197058358": { + "authors": [ + "@jxlarrea" + ], + "category": "integration", + "description": "Emfit QS Sleep Tracker Component for Home Assistant", + "domain": "emfitqs", + "downloads": 0, + "full_name": "jxlarrea/ha-emfitqs", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "fb4db3b", + "last_release_tag": "2.2", + "last_updated": "2019-08-18T01:44:17Z", + "name": "emfitqs", + "new": false, + "repository_manifest": {}, + "selected_tag": null, + "show_beta": false, + "stars": 7, + "topics": [ + "emfit", + "emfitqs", + "home-assistant", + "home-automation", + "homeassistant", + "homeassistant-components", + "presence", + "presence-detection", + "python", + "sleep-tracker" + ], + "version_installed": null + }, + "159025199": { + "authors": [ + "@kalanda" + ], + "category": "integration", + "description": "AEMET integration for Home Assistant", + "domain": "aemet", + "downloads": 0, + "full_name": "kalanda/homeassistant-aemet-sensor", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "19b328b", + "last_release_tag": "v1.3.0", + "last_updated": "2020-08-25T14:29:45Z", + "name": "aemet", + "new": false, + "repository_manifest": {}, + "selected_tag": null, + "show_beta": false, + "stars": 16, + "topics": [ + "aemet", + "hacs", + "hassio", + "homeassistant", + "homeassistant-components" + ], + "version_installed": null + }, + "233079250": { + "authors": [ + "@kodi1" + ], + "category": "integration", + "description": "darksky - clouds cover and alerts", + "domain": "darksky_m", + "downloads": 0, + "full_name": "kodi1/darksky_m", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "580385d", + "last_release_tag": null, + "last_updated": "2020-01-15T23:22:01Z", + "name": "darksky_m", + "new": false, + "repository_manifest": { + "name": "darksky_m", + "render_readme": "true", + "country": "NO" + }, + "selected_tag": null, + "show_beta": false, + "stars": 0, + "topics": [ + "darksky", + "hacs", + "home-assistant", + "python" + ], + "version_installed": null + }, + "232077394": { + "authors": [ + "@kirei" + ], + "category": "integration", + "description": "Home Assistant Component for Chargeamps", + "domain": "chargeamps", + "downloads": 0, + "full_name": "kirei/hass-chargeamps", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "282d52b", + "last_release_tag": "v1.3.0", + "last_updated": "2020-11-26T21:36:18Z", + "name": "chargeamps", + "new": false, + "repository_manifest": { + "name": "Chargeamps", + "domains": [ + "sensor", + "light", + "switch" + ], + "iot_class": "cloud_polling", + "render_readme": true + }, + "selected_tag": null, + "show_beta": false, + "stars": 1, + "topics": [ + "chargeamps", + "homeassistant-integration" + ], + "version_installed": null + }, + "233090507": { + "authors": [ + "@kodi1" + ], + "category": "integration", + "description": "meteoalarm sensor", + "domain": "meteoalarm_m", + "downloads": 0, + "full_name": "kodi1/meteoalarm", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "063e671", + "last_release_tag": null, + "last_updated": "2020-03-23T20:48:18Z", + "name": "meteoalarm_m", + "new": false, + "repository_manifest": { + "name": "meteoalarm", + "render_readme": "true", + "country": "NO" + }, + "selected_tag": null, + "show_beta": false, + "stars": 4, + "topics": [ + "hacs", + "home-assistant", + "meteoalarm", + "python" + ], + "version_installed": null + }, + "233089370": { + "authors": [ + "@kodi1" + ], + "category": "integration", + "description": "easyesp status sensor", + "domain": "esp_wd", + "downloads": 0, + "full_name": "kodi1/esp_wd", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "d99eeff", + "last_release_tag": null, + "last_updated": "2020-07-06T11:12:38Z", + "name": "esp_wd", + "new": false, + "repository_manifest": { + "name": "esp_wd", + "render_readme": "true", + "country": "NO" + }, + "selected_tag": null, + "show_beta": false, + "stars": 2, + "topics": [ + "esp-easy", + "hacs", + "home-assistant", + "python" + ], + "version_installed": null + }, + "233092112": { + "authors": [ + "@kodi1" + ], + "category": "integration", + "description": "songpal - volume down workaround", + "domain": "songpal_m", + "downloads": 0, + "full_name": "kodi1/songpal_m", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "989ec2f", + "last_release_tag": null, + "last_updated": "2020-02-09T11:17:54Z", + "name": "songpal_m", + "new": false, + "repository_manifest": { + "name": "songpal_m", + "render_readme": "true", + "country": "NO" + }, + "selected_tag": null, + "show_beta": false, + "stars": 1, + "topics": [ + "hacs", + "home-assistant", + "python", + "songpal" + ], + "version_installed": null + }, + "233092629": { + "authors": [ + "@kodi1" + ], + "category": "integration", + "description": "tvheadend recorder sensor - lovelace upcoming media card", + "domain": "tvh_rec", + "downloads": 0, + "full_name": "kodi1/tvh_rec", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "26b3fbc", + "last_release_tag": null, + "last_updated": "2020-12-01T15:22:54Z", + "name": "tvh_rec", + "new": false, + "repository_manifest": { + "name": "tvh_rec", + "country": "NO" + }, + "selected_tag": null, + "show_beta": false, + "stars": 1, + "topics": [ + "hacs", + "home-assistant", + "python", + "recordings", + "tvheadend" + ], + "version_installed": null + }, + "246410785": { + "authors": [ + "@KoljaWindeler" + ], + "category": "integration", + "description": "Integration that displays the next event of an ics link (support reoccuring events)", + "domain": "ics", + "downloads": 0, + "full_name": "KoljaWindeler/ics", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "a7c2824", + "last_release_tag": "v0.5", + "last_updated": "2020-10-05T11:55:30Z", + "name": "ics", + "new": false, + "repository_manifest": { + "name": "ICS", + "content_in_root": false, + "render_readme": true, + "iot_class": "Cloud Polling" + }, + "selected_tag": null, + "show_beta": false, + "stars": 14, + "topics": [ + "appointments", + "filtering", + "homeassistant", + "ics", + "reoccuring-events" + ], + "version_installed": null + }, + "286554328": { + "authors": [ + "@KoljaWindeler" + ], + "category": "integration", + "description": "custom integration for kaco solar inverter", + "domain": "kaco", + "downloads": 0, + "full_name": "KoljaWindeler/kaco", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "93152f4", + "last_release_tag": null, + "last_updated": "2020-09-07T17:10:05Z", + "name": "kaco", + "new": false, + "repository_manifest": { + "name": "Kaco", + "content_in_root": false, + "render_readme": true, + "iot_class": "Local Pull" + }, + "selected_tag": null, + "show_beta": false, + "stars": 0, + "topics": [ + "homeassistant-integration", + "inverter", + "solar-energy" + ], + "version_installed": null + }, + "254253124": { + "authors": [ + "@heythisisnate", + "@snicker" + ], + "category": "integration", + "description": "HomeAssistant integration for Noonlight", + "domain": "noonlight", + "downloads": 0, + "full_name": "konnected-io/noonlight-hass", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "e5870b4", + "last_release_tag": "v1.0.1", + "last_updated": "2020-08-02T03:29:03Z", + "name": "noonlight", + "new": false, + "repository_manifest": { + "name": "Noonlight - Alarm Monitoring", + "render_readme": true, + "country": "US", + "domains": [ + "switch" + ], + "homeassistant": "0.96" + }, + "selected_tag": null, + "show_beta": false, + "stars": 7, + "topics": [ + "alarm", + "homeassistant", + "homeassistant-components", + "monitoring", + "noonlight", + "security" + ], + "version_installed": null + }, + "242635439": { + "authors": [ + "@kuchel77" + ], + "category": "integration", + "description": "Disk space for a path. For use with Home Assistant", + "domain": "diskspace", + "downloads": 0, + "full_name": "kuchel77/diskspace", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "fef5e61", + "last_release_tag": "v0.7", + "last_updated": "2020-03-06T04:52:39Z", + "name": "diskspace", + "new": false, + "repository_manifest": { + "name": "Disk Space", + "content_in_root": false, + "domains": [ + "sensor" + ], + "render_readme": false + }, + "selected_tag": null, + "show_beta": false, + "stars": 7, + "topics": [ + "assistant", + "disk", + "hass", + "home", + "space" + ], + "version_installed": null + }, + "279680951": { + "authors": [ + "@kukulich" + ], + "category": "integration", + "description": "Home Assistant custom component for JABLOTRON 100+ alarm system", + "domain": "jablotron100", + "downloads": 0, + "full_name": "kukulich/home-assistant-jablotron100", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "4418a58", + "last_release_tag": "2.3.1", + "last_updated": "2020-12-07T19:53:11Z", + "name": "jablotron100", + "new": false, + "repository_manifest": { + "name": "Jablotron 100", + "content_in_root": false, + "domains": [ + "alarm_control_panel", + "binary_sensor" + ], + "country": [ + "CS", + "DA", + "EN", + "NB", + "SK" + ], + "render_readme": true + }, + "selected_tag": null, + "show_beta": false, + "stars": 9, + "topics": [ + "alarm", + "custom-component", + "hacs", + "home-assistant", + "integration", + "jablotron" + ], + "version_installed": null + }, + "230151505": { + "authors": [ + "@laszlojakab" + ], + "category": "integration", + "description": "Dijnet integration for Home Assistant", + "domain": "dijnet", + "downloads": 0, + "full_name": "laszlojakab/homeassistant-dijnet", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "278a653", + "last_release_tag": null, + "last_updated": "2020-06-10T16:38:15Z", + "name": "dijnet", + "new": false, + "repository_manifest": { + "name": "Dijnet integration", + "country": "HU", + "render_readme": "True", + "iot_class": "Cloud Polling" + }, + "selected_tag": null, + "show_beta": false, + "stars": 5, + "topics": [ + "dijnet", + "home-assistant", + "home-assistant-custom", + "home-assistant-sensor" + ], + "version_installed": null + }, + "258852884": { + "authors": [ + "@laszlojakab" + ], + "category": "integration", + "description": "Helios EasyControls Modbus TCP/IP integration for Home Assistant", + "domain": "easycontrols", + "downloads": 0, + "full_name": "laszlojakab/homeassistant-easycontrols", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "29c0f48", + "last_release_tag": null, + "last_updated": "2020-07-21T10:54:40Z", + "name": "easycontrols", + "new": false, + "repository_manifest": { + "name": "Helios EasyControls Modbus TCP/IP integration", + "iot_class": "Local Polling", + "render_readme": "True" + }, + "selected_tag": null, + "show_beta": false, + "stars": 4, + "topics": [ + "easycontrols", + "eazyctrl", + "home-assistant-custom", + "homeassistant", + "modbus" + ], + "version_installed": null + }, + "132661981": { + "authors": [ + "@legrego" + ], + "category": "integration", + "description": "Publish Home-Assistant events to Elasticsearch", + "domain": "elastic", + "downloads": 0, + "full_name": "legrego/homeassistant-elasticsearch", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "9c7b3f1", + "last_release_tag": "v0.3.0", + "last_updated": "2020-10-18T16:50:57Z", + "name": "elastic", + "new": false, + "repository_manifest": {}, + "selected_tag": null, + "show_beta": false, + "stars": 56, + "topics": [ + "elasticsearch", + "hacktoberfest", + "hass", + "home-assistant", + "home-automation", + "python" + ], + "version_installed": null + }, + "183222061": { + "authors": [ + "@lichtteil" + ], + "category": "integration", + "description": "Custom component for Home Assistant that integrates your (own) local Luftdaten sensor (air quality/particle sensor) without using the cloud.", + "domain": "local_luftdaten", + "downloads": 0, + "full_name": "lichtteil/local_luftdaten", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "49fcc4f", + "last_release_tag": "1.7", + "last_updated": "2020-10-29T15:01:24Z", + "name": "local_luftdaten", + "new": false, + "repository_manifest": { + "name": "Local Luftdaten Sensor", + "render_readme": true, + "domains": "sensor" + }, + "selected_tag": null, + "show_beta": false, + "stars": 10, + "topics": [ + "air-quality", + "homeassistant", + "homeassistant-sensor", + "sensor" + ], + "version_installed": null + }, + "199313405": { + "authors": [ + "@Limych" + ], + "category": "integration", + "description": "Home Assistant custom component for Beward security Cameras and Doorbells", + "domain": "beward", + "downloads": 1, + "full_name": "Limych/ha-beward", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "a663fe8", + "last_release_tag": "1.1.16", + "last_updated": "2020-11-18T09:53:34Z", + "name": "beward", + "new": false, + "repository_manifest": { + "name": "Beward Cameras & Doorbells", + "domains": [ + "beward", + "camera", + "sensor", + "binary_sensor" + ], + "iot_class": "Local Push" + }, + "selected_tag": null, + "show_beta": false, + "stars": 10, + "topics": [ + "beward", + "camera", + "custom-component", + "doorbell", + "dvr", + "home-assistant", + "security", + "surveillance" + ], + "version_installed": null + }, + "204192861": { + "authors": [ + "@Limych" + ], + "category": "integration", + "description": "Average Sensor for Home Assistant", + "domain": "average", + "downloads": 0, + "full_name": "Limych/ha-average", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "9e42155", + "last_release_tag": "1.6.6", + "last_updated": "2020-12-07T00:05:05Z", + "name": "average", + "new": false, + "repository_manifest": { + "name": "Average sensor", + "domains": [ + "sensor" + ] + }, + "selected_tag": null, + "show_beta": false, + "stars": 58, + "topics": [ + "average", + "home-assistant", + "sensor" + ], + "version_installed": null + }, + "206868881": { + "authors": [ + "@limych" + ], + "category": "integration", + "description": "Gismeteo Weather Provider for Home Assistant", + "domain": "gismeteo", + "downloads": 12, + "full_name": "Limych/ha-gismeteo", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "6dc95b4", + "last_release_tag": "2.0.22", + "last_updated": "2020-11-18T09:53:58Z", + "name": "gismeteo", + "new": false, + "repository_manifest": { + "name": "Gismeteo Weather Provider", + "domains": [ + "weather", + "sensor" + ] + }, + "selected_tag": null, + "show_beta": false, + "stars": 31, + "topics": [ + "forecast", + "gismeteo", + "gismeteo-weather", + "hacs", + "home-assistant", + "sensor", + "sensors", + "weather-provider" + ], + "version_installed": null + }, + "210194956": { + "authors": [ + "@limych" + ], + "category": "integration", + "description": "Car Wash Binary Sensor for Home Assistant", + "domain": "car_wash", + "downloads": 1, + "full_name": "Limych/ha-car_wash", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "372b7d9", + "last_release_tag": "1.2.16", + "last_updated": "2020-11-18T09:54:03Z", + "name": "car_wash", + "new": false, + "repository_manifest": { + "name": "Car Wash sensor", + "domains": [ + "sensor" + ] + }, + "selected_tag": null, + "show_beta": false, + "stars": 28, + "topics": [ + "binary-sensor", + "car", + "car-wash", + "home-assistant", + "wash", + "weather-forecast" + ], + "version_installed": null + }, + "228063780": { + "authors": [ + "@Limych" + ], + "category": "integration", + "description": "Indoor Air Quality Sensor Component for Home Assistant", + "domain": "iaquk", + "downloads": 5, + "full_name": "Limych/ha-iaquk", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "4cc08b7", + "last_release_tag": "1.3.4", + "last_updated": "2020-11-18T09:54:40Z", + "name": "iaquk", + "new": false, + "repository_manifest": { + "name": "Indoor Air Quality UK Index", + "domains": [ + "iaquk", + "sensor" + ], + "iot_class": "Local Push" + }, + "selected_tag": null, + "show_beta": false, + "stars": 27, + "topics": [ + "air-quality", + "hacs", + "home-assistant", + "indoor", + "sensor" + ], + "version_installed": null + }, + "256709811": { + "authors": [ + "@Limych" + ], + "category": "integration", + "description": "JQ-300 Indoor Air Quality Meter Home Assistant Integration", + "domain": "jq300", + "downloads": 0, + "full_name": "Limych/ha-jq300", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "6c8a6de", + "last_release_tag": "0.7.17", + "last_updated": "2020-12-06T18:30:48Z", + "name": "jq300", + "new": false, + "repository_manifest": { + "name": "JQ-300/200/100 Indoor Air Quality Meter", + "domains": [ + "jq300", + "sensor" + ], + "iot_class": "Local Push" + }, + "selected_tag": null, + "show_beta": false, + "stars": 17, + "topics": [ + "air-quality", + "air-quality-measurements", + "air-quality-sensor", + "custom-component", + "home-assistant", + "home-assistant-component" + ], + "version_installed": null + }, + "242335771": { + "authors": [], + "category": "integration", + "description": "Play SVT Play videos and channels via home assistant", + "domain": "svt_play", + "downloads": 0, + "full_name": "lindell/home-assistant-svt-play", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "1d506c4", + "last_release_tag": "1.0.1", + "last_updated": "2020-09-23T05:52:00Z", + "name": "svt_play", + "new": false, + "repository_manifest": { + "name": "SVT Play", + "country": [ + "SE" + ] + }, + "selected_tag": null, + "show_beta": false, + "stars": 10, + "topics": [ + "home-assistant", + "homeassistant", + "svt", + "svtplay", + "sweden", + "tv", + "video" + ], + "version_installed": null + }, + "219363790": { + "authors": [], + "category": "integration", + "description": "Play videos from the Swedish channel 4", + "domain": "tv4_play", + "downloads": 0, + "full_name": "lindell/home-assistant-tv4-play", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "9d4d44e", + "last_release_tag": "1.0.0", + "last_updated": "2020-08-30T11:19:18Z", + "name": "tv4_play", + "new": false, + "repository_manifest": { + "name": "TV4 Play", + "country": [ + "SE" + ] + }, + "selected_tag": null, + "show_beta": false, + "stars": 9, + "topics": [ + "homeassistant", + "tv4", + "tv4play" + ], + "version_installed": null + }, + "197950768": { + "authors": [], + "category": "integration", + "description": "Our Groceries Integration for Home Assistant", + "domain": "ourgroceries", + "downloads": 0, + "full_name": "ljmerza/ha-our-groceries", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "8f8630f", + "last_release_tag": "1.3.5", + "last_updated": "2020-05-05T01:48:23Z", + "name": "ourgroceries", + "new": false, + "repository_manifest": {}, + "selected_tag": null, + "show_beta": false, + "stars": 14, + "topics": [], + "version_installed": null + }, + "183064800": { + "authors": [ + "@ljmerza" + ], + "category": "integration", + "description": "Email Sensor for collecting tracking numbers from FedEx, UPS, USPS, Rockauto, Best Buy, Dollar Shave Club, Newegg, B&H Photo, Paypal, eBay, DHL, Philips Hue, Google Express, Monoprice, NuLeaf, Timeless, DSW, Wyze, Reolink, and Ali Express.", + "domain": "email", + "downloads": 0, + "full_name": "ljmerza/ha-email-sensor", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "3e17e79", + "last_release_tag": "2.7.0", + "last_updated": "2020-12-03T02:56:48Z", + "name": "email", + "new": false, + "repository_manifest": { + "name": "Email Sensor", + "render_readme": true + }, + "selected_tag": null, + "show_beta": false, + "stars": 29, + "topics": [], + "version_installed": null + }, + "243841075": { + "authors": [ + "@lociii" + ], + "category": "integration", + "description": "CS:GO gamestate reporting to Home Assistant", + "domain": "csgo_gamestate", + "downloads": 0, + "full_name": "lociii/homeassistant-csgo", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "1d860db", + "last_release_tag": "1.1.0", + "last_updated": "2020-10-01T12:44:06Z", + "name": "csgo_gamestate", + "new": false, + "repository_manifest": { + "name": "CS:GO game state", + "homeassistant": "0.100", + "iot_class": "local_push" + }, + "selected_tag": null, + "show_beta": false, + "stars": 7, + "topics": [], + "version_installed": null + }, + "200035037": { + "authors": [], + "category": "integration", + "description": "Home Assistant custom component to get online and game status of Discord users", + "domain": "discord_game", + "downloads": 0, + "full_name": "LordBoos/discord_game", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "d2413a4", + "last_release_tag": "3.3.1", + "last_updated": "2020-10-30T15:19:21Z", + "name": "discord_game", + "new": false, + "repository_manifest": {}, + "selected_tag": null, + "show_beta": false, + "stars": 22, + "topics": [], + "version_installed": null + }, + "246657442": { + "authors": [ + "@lolouk44" + ], + "category": "integration", + "description": "CurrentCost Meter Reading Custom Component for Home Assistant ", + "domain": "currentcost", + "downloads": 0, + "full_name": "lolouk44/CurrentCost_HA_CC", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "545837e", + "last_release_tag": "0.1.4", + "last_updated": "2020-09-11T19:19:57Z", + "name": "currentcost", + "new": false, + "repository_manifest": { + "name": "Current Cost", + "domains": [ + "sensor" + ], + "iot_class": "local push" + }, + "selected_tag": null, + "show_beta": false, + "stars": 4, + "topics": [ + "cc128", + "current-cost", + "currentcost", + "envi", + "envir" + ], + "version_installed": null + }, + "152294445": { + "authors": [ + "@lukas-hetzenecker" + ], + "category": "integration", + "description": "Links multiple home-assistant instances together", + "domain": "remote_homeassistant", + "downloads": 0, + "full_name": "lukas-hetzenecker/home-assistant-remote", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "225814a", + "last_release_tag": "3.0", + "last_updated": "2020-12-08T12:23:48Z", + "name": "remote_homeassistant", + "new": false, + "repository_manifest": { + "name": "Remote Home-Assistant", + "render_readme": true, + "iot_class": "Local Polling" + }, + "selected_tag": null, + "show_beta": false, + "stars": 206, + "topics": [], + "version_installed": null + }, + "257912976": { + "authors": [], + "category": "integration", + "description": "mqtt template switch for homeassistant", + "domain": "mqtt_template", + "downloads": 0, + "full_name": "lukich48/hass_mqtt_template_switch", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "6a83bdf", + "last_release_tag": "2.0", + "last_updated": "2020-09-24T20:04:33Z", + "name": "mqtt_template", + "new": false, + "repository_manifest": { + "name": "Mqtt template switch", + "render_readme": true, + "domains": [ + "switch" + ] + }, + "selected_tag": null, + "show_beta": false, + "stars": 1, + "topics": [], + "version_installed": null + }, + "195883127": { + "authors": [ + "@Martinvdm", + "@vloris" + ], + "category": "integration", + "description": "Garbage collection Nissewaard for Home Assistant", + "domain": "nissewaard", + "downloads": 0, + "full_name": "Martinvdm/garbage-nissewaard-homeassistant", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "e601984", + "last_release_tag": null, + "last_updated": "2019-12-05T06:54:30Z", + "name": "nissewaard", + "new": false, + "repository_manifest": {}, + "selected_tag": null, + "show_beta": false, + "stars": 0, + "topics": [], + "version_installed": null + }, + "254347436": { + "authors": [ + "@mampfes" + ], + "category": "integration", + "description": "Home Assistant integration framework for (garbage collection) schedules", + "domain": "waste_collection_schedule", + "downloads": 0, + "full_name": "mampfes/hacs_waste_collection_schedule", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "7d03ce8", + "last_release_tag": null, + "last_updated": "2020-12-06T09:10:11Z", + "name": "waste_collection_schedule", + "new": false, + "repository_manifest": { + "name": "Waste Collection Schedule", + "domains": [ + "sensor" + ], + "iot_class": "cloud_polling" + }, + "selected_tag": null, + "show_beta": false, + "stars": 37, + "topics": [ + "abfall", + "abfallnavi", + "abfallplus", + "berlin", + "garbage", + "garbage-collection", + "hamburg", + "home-assistant", + "homeassistant", + "homeassistant-integration", + "jumomind", + "koeln", + "muell", + "muellabfuhr", + "muellsammlung", + "regioit", + "waste", + "waste-collection" + ], + "version_installed": null + }, + "262203345": { + "authors": [ + "@mac-zhou" + ], + "category": "integration", + "description": "This is a custom component for Home Assistant to integrate the Midea Air Conditioners via the Local area network.", + "domain": "midea_ac", + "downloads": 0, + "full_name": "mac-zhou/midea-ac-py", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "c1bd84c", + "last_release_tag": null, + "last_updated": "2020-11-23T01:09:42Z", + "name": "midea_ac", + "new": false, + "repository_manifest": { + "name": "Midea Smart Aircon", + "domains": [ + "climate" + ], + "render_readme": false, + "homeassistant": "0.108.0" + }, + "selected_tag": null, + "show_beta": false, + "stars": 48, + "topics": [], + "version_installed": null + }, + "208569075": { + "authors": [ + "@mammuth" + ], + "category": "integration", + "description": "Control guest wifi, port forwardings, device profiles, ... all from within Home Assistant! \ud83e\udd16\u2728", + "domain": "fritzbox_tools", + "downloads": 0, + "full_name": "mammuth/ha-fritzbox-tools", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "8ada2c3", + "last_release_tag": "1.13.2", + "last_updated": "2020-10-31T22:15:38Z", + "name": "fritzbox_tools", + "new": false, + "repository_manifest": { + "name": "FRITZ!Box Tools", + "domains": [ + "switch" + ] + }, + "selected_tag": null, + "show_beta": false, + "stars": 60, + "topics": [ + "home-assistant", + "home-automation", + "python", + "smart-home" + ], + "version_installed": null + }, + "252929074": { + "authors": [ + "@mampfes" + ], + "category": "integration", + "description": "Support for Wiffi devices (e.g. Weatherman, Rainyman) from stall.biz", + "domain": "wiffi", + "downloads": 0, + "full_name": "mampfes/hacs_wiffi", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "fc124f8", + "last_release_tag": null, + "last_updated": "2020-10-03T08:13:58Z", + "name": "wiffi", + "new": false, + "repository_manifest": { + "name": "WIFFI from stall.biz", + "render_readme": true, + "domains": [ + "binary_sensor", + "sensor" + ], + "iot_class": "local_push" + }, + "selected_tag": null, + "show_beta": false, + "stars": 0, + "topics": [ + "home-assistant", + "homeassistant", + "homeassistant-integration", + "homeassistant-sensor", + "rainyman", + "stallbiz", + "weather", + "weatherman", + "wetter", + "wiffi", + "wiffi-wz" + ], + "version_installed": null + }, + "186600448": { + "authors": [ + "@MatthewFlamm" + ], + "category": "integration", + "description": "Home assistant custom camera platform for NWS radar loops", + "domain": "nwsradar", + "downloads": 1042, + "full_name": "MatthewFlamm/nwsradar", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "740691a", + "last_release_tag": "0.5.1", + "last_updated": "2020-10-08T23:52:58Z", + "name": "nwsradar", + "new": false, + "repository_manifest": { + "name": "National Weather Service Radar", + "country": "US", + "domains": [ + "weather" + ], + "iot_class": "Cloud Polling", + "zip_release": true, + "filename": "nwsradar.zip" + }, + "selected_tag": null, + "show_beta": false, + "stars": 17, + "topics": [ + "custom-component", + "home-assistant", + "nws", + "weather-radar" + ], + "version_installed": null + }, + "238446853": { + "authors": [ + "@maykar" + ], + "category": "integration", + "description": "\u2771 Plex Assistant is a Home Assistant component to cast Plex media to Google devices or Plex clients with Google Assistant, HA's voice assistant, and more.", + "domain": "plex_assistant", + "downloads": 0, + "full_name": "maykar/plex_assistant", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "6768b7c", + "last_release_tag": "0.3.1", + "last_updated": "2020-11-28T12:57:25Z", + "name": "plex_assistant", + "new": false, + "repository_manifest": { + "name": "Plex Assistant" + }, + "selected_tag": null, + "show_beta": false, + "stars": 104, + "topics": [], + "version_installed": null + }, + "295627573": { + "authors": [ + "@michaellunzer", + "@clyra" + ], + "category": "integration", + "description": "This is a Home-Assistant custom component that pulls Fortnite stats using the python API library from the site fortnitetracker.com", + "domain": "fortnite", + "downloads": 2, + "full_name": "michaellunzer/Home-Assistant-Custom-Component-Fortnite", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "ec3b938", + "last_release_tag": "v0.1.0", + "last_updated": "2020-10-27T04:34:24Z", + "name": "fortnite", + "new": false, + "repository_manifest": { + "name": "Fortnite Stats", + "hacs": "0.24.0", + "domains": [ + "sensor" + ], + "iot_class": "Cloud Polling", + "homeassistant": "0.110.0", + "render_readme": true + }, + "selected_tag": null, + "show_beta": false, + "stars": 0, + "topics": [ + "custom-component", + "fortnite", + "fortnite-api", + "fortnite-stats", + "hacs", + "home-assistant", + "home-assistant-custom", + "python" + ], + "version_installed": null + }, + "239366330": { + "authors": [ + "@mikelawrence" + ], + "category": "integration", + "description": "Haiku with SenseME fan integration for Home Assistant", + "domain": "senseme", + "downloads": 0, + "full_name": "mikelawrence/senseme-hacs", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "2c30cc6", + "last_release_tag": "v2.1.3", + "last_updated": "2020-10-02T16:29:56Z", + "name": "senseme", + "new": false, + "repository_manifest": { + "name": "SenseME", + "domains": [ + "fan", + "light", + "binary_sensor" + ], + "homeassistant": "0.110.0" + }, + "selected_tag": null, + "show_beta": false, + "stars": 10, + "topics": [ + "bigassfans", + "fan", + "hacs", + "haiku", + "home-assistant", + "homeassistant", + "senseme" + ], + "version_installed": null + }, + "197578489": { + "authors": [ + "@mlowijs", + "@robhofmann" + ], + "category": "integration", + "description": null, + "domain": "tesla_cc", + "downloads": 0, + "full_name": "mlowijs/HomeAssistant-TeslaCustomComponent", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "6948798", + "last_release_tag": null, + "last_updated": "2020-06-13T08:59:32Z", + "name": "tesla_cc", + "new": false, + "repository_manifest": {}, + "selected_tag": null, + "show_beta": false, + "stars": 6, + "topics": [], + "version_installed": null + }, + "289251122": { + "authors": [ + "@mletenay" + ], + "category": "integration", + "description": "Read the voltage/current/power/battery values from your GoodWe Inverter via local network (UDP - no cloud) ", + "domain": "goodwe", + "downloads": 0, + "full_name": "mletenay/home-assistant-goodwe-inverter", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "9437972", + "last_release_tag": null, + "last_updated": "2020-11-25T23:05:37Z", + "name": "goodwe", + "new": false, + "repository_manifest": { + "name": "GoodWe Inverter Solar Sensor (UDP - no cloud)", + "country": "SK", + "domains": [ + "sensor" + ], + "homeassistant": "0.115.0" + }, + "selected_tag": null, + "show_beta": false, + "stars": 8, + "topics": [ + "goodwe", + "hacs", + "home-assistant", + "home-assistant-sensor", + "pv-systems" + ], + "version_installed": null + }, + "291317330": { + "authors": [ + "@mletenay" + ], + "category": "integration", + "description": "Home Assistant custom component for Electric Vehicle Charge Control devices by Phoenix Contact ", + "domain": "phoenix_contact", + "downloads": 0, + "full_name": "mletenay/home-assistant-ev-charge-control", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "679a2ff", + "last_release_tag": null, + "last_updated": "2020-09-19T21:49:40Z", + "name": "phoenix_contact", + "new": false, + "repository_manifest": { + "name": "Electric Vehicle Charge Control", + "country": "SK", + "domains": [ + "switch" + ], + "homeassistant": "0.110.0" + }, + "selected_tag": null, + "show_beta": false, + "stars": 0, + "topics": [ + "charging-stations", + "electric-vehicles", + "evse", + "home-assistant", + "home-assistant-hacs" + ], + "version_installed": null + }, + "203244705": { + "authors": [], + "category": "integration", + "description": "A platform sensor which tells you which meals are served in your canteen.", + "domain": "openmensa", + "downloads": 0, + "full_name": "Mofeywalker/openmensa-hass-component", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "c7690ea", + "last_release_tag": null, + "last_updated": "2019-08-23T18:13:49Z", + "name": "openmensa", + "new": false, + "repository_manifest": { + "name": "OpenMensa Sensor", + "domains": [ + "sensor" + ] + }, + "selected_tag": null, + "show_beta": false, + "stars": 1, + "topics": [], + "version_installed": null + }, + "188106531": { + "authors": [ + "@moralmunky", + "@firstof9" + ], + "category": "integration", + "description": "Home Assistant integration providing day of package counts and USPS informed delivery images.", + "domain": "mail_and_packages", + "downloads": 0, + "full_name": "moralmunky/Home-Assistant-Mail-And-Packages", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "2ec50a8", + "last_release_tag": "0.2.2", + "last_updated": "2020-12-08T18:07:39Z", + "name": "mail_and_packages", + "new": false, + "repository_manifest": { + "name": "Mail and Packages", + "domains": [ + "sensor" + ], + "homeassistant": "0.109.0", + "iot_class": "Cloud Polling" + }, + "selected_tag": null, + "show_beta": false, + "stars": 120, + "topics": [ + "home-assistant", + "home-assistant-config", + "home-automation", + "homeassistant" + ], + "version_installed": null + }, + "272094506": { + "authors": [ + "@mrk-its" + ], + "category": "integration", + "description": "Custom Component for fetching lightning data from blitzortung.org", + "domain": "blitzortung", + "downloads": 0, + "full_name": "mrk-its/homeassistant-blitzortung", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "9ef18f7", + "last_release_tag": null, + "last_updated": "2020-10-29T10:42:13Z", + "name": "blitzortung", + "new": false, + "repository_manifest": { + "name": "Blitzortung.org Lightning Detector", + "country": "PL", + "render_readme": true, + "domains": [ + "sensor" + ] + }, + "selected_tag": null, + "show_beta": false, + "stars": 33, + "topics": [ + "blitzortung", + "home-assistant", + "home-automation", + "lightning-network" + ], + "version_installed": null + }, + "253842395": { + "authors": [ + "@msp1974" + ], + "category": "integration", + "description": "An integration for JLR InControl to Home Assistant", + "domain": "jlrincontrol", + "downloads": 0, + "full_name": "msp1974/homeassistant-jlrincontrol", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "d11d931", + "last_release_tag": "V2.1.2", + "last_updated": "2020-09-21T17:59:40Z", + "name": "jlrincontrol", + "new": false, + "repository_manifest": { + "name": "Jaguar Landrover InControl", + "render_readme": false, + "iot_class": "Cloud Polling", + "homeassistant": "0.110.0" + }, + "selected_tag": null, + "show_beta": false, + "stars": 15, + "topics": [ + "hacs", + "homeassistant", + "i-pace", + "jaguar", + "jlr", + "landrover", + "rrs", + "vehicle", + "wirelesscar" + ], + "version_installed": null + }, + "255662264": { + "authors": [ + "@MTrab" + ], + "category": "integration", + "description": "Landroid Cloud component for Home Assistant", + "domain": "landroid_cloud", + "downloads": 0, + "full_name": "MTrab/landroid_cloud", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "3cc2a8e", + "last_release_tag": "v1.6.4", + "last_updated": "2020-08-06T20:08:29Z", + "name": "landroid_cloud", + "new": false, + "repository_manifest": { + "name": "Landroid Cloud", + "render_readme": true + }, + "selected_tag": null, + "show_beta": false, + "stars": 28, + "topics": [], + "version_installed": null + }, + "269588712": { + "authors": [ + "@mvdwetering" + ], + "category": "integration", + "description": "Home Assistant integration for the Philips Hue Play HDMI Sync Box", + "domain": "huesyncbox", + "downloads": 0, + "full_name": "mvdwetering/huesyncbox", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "af2fee0", + "last_release_tag": "v1.9", + "last_updated": "2020-11-20T18:38:38Z", + "name": "huesyncbox", + "new": false, + "repository_manifest": { + "name": "Philips Hue Play HDMI Sync Box", + "render_readme": true, + "domains": [ + "media_player" + ], + "homeassistant": "0.114.0", + "iot_class": "Local Polling" + }, + "selected_tag": null, + "show_beta": false, + "stars": 13, + "topics": [ + "hacs", + "homeassistant-integration", + "hue-entertainment", + "huesync", + "philips-hue" + ], + "version_installed": null + }, + "192604318": { + "authors": [ + "@mudape" + ], + "category": "integration", + "description": "A custom component for Home Assistant to detect iPhones connected to local LAN, even if the phone is in deep sleep.", + "domain": "iphonedetect", + "downloads": 3330, + "full_name": "mudape/iphonedetect", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "0447806", + "last_release_tag": "1.2.5", + "last_updated": "2020-11-14T19:57:39Z", + "name": "iphonedetect", + "new": false, + "repository_manifest": { + "name": "iPhone Device Tracker", + "homeassistant": "0.94.0", + "iot_class": "Local Polling", + "domains": [ + "device_tracker" + ], + "zip_release": true, + "filename": "iphonedetect.zip" + }, + "selected_tag": null, + "show_beta": false, + "stars": 73, + "topics": [ + "custom-component", + "hacs", + "home-assistant", + "iphonedetect" + ], + "version_installed": null + }, + "256733675": { + "authors": [ + "@davidramosweb", + "@nagyrobi", + "@Alfiegerner" + ], + "category": "integration", + "description": "Time-based cover with customisable scripts to trigger opening, stopping and closing. Position is calculated based on the fraction of time spent by the cover travelling up or down. State can be updated with information based on external sensors.", + "domain": "cover_rf_time_based", + "downloads": 0, + "full_name": "nagyrobi/home-assistant-custom-components-cover-rf-time-based", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "8f2f19e", + "last_release_tag": "v1.4a", + "last_updated": "2020-10-23T09:50:48Z", + "name": "cover_rf_time_based", + "new": false, + "repository_manifest": { + "name": "Cover Time Based RF (trigger script)", + "content_in_root": false, + "render_readme": false, + "domains": [ + "cover" + ] + }, + "selected_tag": null, + "show_beta": false, + "stars": 10, + "topics": [ + "433", + "433mhz", + "cover", + "home-assistant", + "rf", + "roller-shutters", + "script", + "service", + "shutter", + "trigger" + ], + "version_installed": null + }, + "278930028": { + "authors": [ + "@nicjo814", + "@limych", + "@nagyrobi" + ], + "category": "integration", + "description": "LinkPlay based media devices integration for Home Assistant. Fully compatible with Mini Media Player card including speaker group management. Supports snapshot and restore functionality for TTS.", + "domain": "linkplay", + "downloads": 0, + "full_name": "nagyrobi/home-assistant-custom-components-linkplay", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "be51ccc", + "last_release_tag": "v2.0.27", + "last_updated": "2020-12-02T07:42:50Z", + "name": "linkplay", + "new": false, + "repository_manifest": { + "name": "Linkplay-based speakers and devices", + "content_in_root": false, + "render_readme": false, + "domains": [ + "media_player" + ], + "homeassistant": "0.117.0" + }, + "selected_tag": null, + "show_beta": false, + "stars": 17, + "topics": [ + "arylic", + "cvte", + "harman-kardon", + "home-assistant", + "linkplay", + "media-player", + "speaker", + "tts" + ], + "version_installed": null + }, + "306078097": { + "authors": [ + "@nagyrobi" + ], + "category": "integration", + "description": "Monitor and react on your pfSense gateway's status with Home Assistant.", + "domain": "pfsense_gateways", + "downloads": 0, + "full_name": "nagyrobi/home-assistant-custom-components-pfsense-gateways", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "c3cae33", + "last_release_tag": null, + "last_updated": "2020-11-22T18:55:59Z", + "name": "pfsense_gateways", + "new": false, + "repository_manifest": { + "name": "pfSense gateways monitoring", + "content_in_root": false, + "render_readme": true, + "domains": [ + "pfsense_gateways" + ], + "homeassistant": "0.115.0" + }, + "selected_tag": null, + "show_beta": false, + "stars": 3, + "topics": [ + "gateways", + "isp", + "modem", + "pfsense", + "reboot", + "router" + ], + "version_installed": null + }, + "153870340": { + "authors": [], + "category": "integration", + "description": "\u2744 Use a Harmony Hub to control an IR controlled climate device", + "domain": "harmony_ac", + "downloads": 0, + "full_name": "nickneos/HA_harmony_climate_component", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "e6e44b8", + "last_release_tag": "v0.2.0", + "last_updated": "2020-08-01T13:43:26Z", + "name": "harmony_ac", + "new": false, + "repository_manifest": { + "name": "Harmony Hub Climate Controller", + "domains": [ + "climate" + ], + "homeassistant": "0.96.0", + "render_readme": true + }, + "selected_tag": null, + "show_beta": false, + "stars": 18, + "topics": [ + "air-conditioner", + "climate", + "hacktoberfest", + "hacs", + "harmony", + "hass", + "home-assistant", + "homeassistant", + "hvac" + ], + "version_installed": null + }, + "307098646": { + "authors": [ + "@nielsfaber" + ], + "category": "integration", + "description": "Easy to use alarm system integration for Home Assistant", + "domain": "alarmo", + "downloads": 578, + "full_name": "nielsfaber/alarmo", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "d32aa0d", + "last_release_tag": "v1.3.2", + "last_updated": "2020-12-03T15:11:05Z", + "name": "alarmo", + "new": false, + "repository_manifest": { + "name": "Alarmo", + "render_readme": "true", + "iot_class": [ + "Local Push" + ], + "domains": [ + "alarm_control_panel" + ], + "zip_release": true, + "filename": "alarmo.zip", + "hide_default_branch": true + }, + "selected_tag": null, + "show_beta": false, + "stars": 44, + "topics": [ + "alarm", + "assistant", + "hass", + "home", + "integration", + "lovelace", + "security" + ], + "version_installed": null + }, + "154417419": { + "authors": [], + "category": "integration", + "description": "Home Assistant custom component for the newer (2016+) Philips Android TVs", + "domain": "philips_android_tv", + "downloads": 0, + "full_name": "nstrelow/ha_philips_android_tv", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "d9d847c", + "last_release_tag": null, + "last_updated": "2020-11-30T08:41:31Z", + "name": "philips_android_tv", + "new": false, + "repository_manifest": {}, + "selected_tag": null, + "show_beta": false, + "stars": 85, + "topics": [ + "home-automation", + "homeassistant", + "philips-tv", + "tv" + ], + "version_installed": null + }, + "286186485": { + "authors": [ + "@nielsfaber" + ], + "category": "integration", + "description": "Custom component for HA that enables the creation of scheduler entities", + "domain": "scheduler", + "downloads": 0, + "full_name": "nielsfaber/scheduler-component", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "d231268", + "last_release_tag": "v2.1.1", + "last_updated": "2020-12-08T15:56:08Z", + "name": "scheduler", + "new": false, + "repository_manifest": { + "name": "Scheduler component", + "render_readme": "true", + "iot_class": [ + "Local Push" + ], + "domains": [ + "switch" + ] + }, + "selected_tag": null, + "show_beta": false, + "stars": 54, + "topics": [ + "custom-component", + "hacs", + "homeassistant", + "integration", + "python", + "scheduler" + ], + "version_installed": null + }, + "140907992": { + "authors": [ + "@nikrolls" + ], + "category": "integration", + "description": "Home Assistant integration for Goldair WiFi heaters, dehumidifiers and fans", + "domain": "goldair_climate", + "downloads": 70, + "full_name": "nikrolls/homeassistant-goldair-climate", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "d539e58", + "last_release_tag": "0.1.2", + "last_updated": "2020-07-12T23:47:58Z", + "name": "goldair_climate", + "new": false, + "repository_manifest": { + "name": "Goldair WiFi climate devices", + "render_readme": true, + "hide_default_branch": true, + "domains": [ + "climate", + "light", + "lock" + ], + "country": [ + "NZ", + "AU" + ], + "homeassistant": "0.96.0", + "zip_release": true, + "filename": "homeassistant-goldair-climate.zip" + }, + "selected_tag": null, + "show_beta": false, + "stars": 11, + "topics": [ + "dehumidifier", + "fan", + "goldair", + "hacs", + "heater", + "homeassistant", + "wifi" + ], + "version_installed": null + }, + "238774420": { + "authors": [ + "@ntilley905" + ], + "category": "integration", + "description": "A Home Assistant custom sensor that retrieves data from the FAA API.", + "domain": "faastatus", + "downloads": 0, + "full_name": "ntilley905/faastatus", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "220ef8d", + "last_release_tag": "v0.3.0", + "last_updated": "2020-11-28T18:04:19Z", + "name": "faastatus", + "new": false, + "repository_manifest": { + "name": "FAA Airport Status", + "domains": "sensor", + "country": "US", + "iot_class": "Cloud Polling" + }, + "selected_tag": null, + "show_beta": false, + "stars": 8, + "topics": [ + "custom-components", + "faastatus", + "home-assistant", + "homeassistant" + ], + "version_installed": null + }, + "247070270": { + "authors": [ + "@ollo69" + ], + "category": "integration", + "description": "\ud83d\udcfa Home Assistant SamsungTV Smart Component with simplified SmartThings API Support configurable from User Interface.", + "domain": "samsungtv_smart", + "downloads": 0, + "full_name": "ollo69/ha-samsungtv-smart", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "244dcdd", + "last_release_tag": "v0.3.15", + "last_updated": "2020-11-24T22:00:48Z", + "name": "samsungtv_smart", + "new": false, + "repository_manifest": { + "name": "SamsungTV Smart", + "domains": [ + "media-player" + ], + "iot_class": "Cloud Polling" + }, + "selected_tag": null, + "show_beta": false, + "stars": 51, + "topics": [ + "home-assistant", + "homeassistant", + "samsung", + "samsung-smart-tv", + "samsung-tv", + "smartthings" + ], + "version_installed": null + }, + "250022973": { + "authors": [ + "@ollo69" + ], + "category": "integration", + "description": "Home Assistant custom integration for SmartThinQ LG devices configurable with Lovelace User Interface.", + "domain": "smartthinq_sensors", + "downloads": 0, + "full_name": "ollo69/ha-smartthinq-sensors", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "dc66795", + "last_release_tag": "v0.5.10", + "last_updated": "2020-11-07T02:09:03Z", + "name": "smartthinq_sensors", + "new": false, + "repository_manifest": { + "name": "SmartThinQ LGE Sensors", + "content_in_root": false, + "domains": [ + "sensor" + ], + "iot_class": "cloud_poll" + }, + "selected_tag": null, + "show_beta": false, + "stars": 121, + "topics": [ + "dishwasher", + "dryer", + "homeassistant", + "lg", + "lge", + "refrigerator", + "smartthinq", + "thinq", + "washer" + ], + "version_installed": null + }, + "144888844": { + "authors": [], + "category": "integration", + "description": null, + "domain": "ovapi", + "downloads": 0, + "full_name": "Paul-dH/Home-Assisant-Sensor-OvApi", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "cd9a78c", + "last_release_tag": "v1.4.2", + "last_updated": "2020-06-25T08:37:17Z", + "name": "ovapi", + "new": false, + "repository_manifest": {}, + "selected_tag": null, + "show_beta": false, + "stars": 12, + "topics": [], + "version_installed": null + }, + "255110178": { + "authors": [ + "@opravdin" + ], + "category": "integration", + "description": "Weback integration with Home Assistant", + "domain": "weback", + "downloads": 0, + "full_name": "opravdin/weback-hass", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "205dc80", + "last_release_tag": "v1.0.2", + "last_updated": "2020-10-30T15:24:24Z", + "name": "weback", + "new": false, + "repository_manifest": { + "name": "Weback cloud integration", + "content_in_root": false, + "render_readme": true, + "domains": [ + "weback" + ], + "hide_default_branch": true + }, + "selected_tag": null, + "show_beta": false, + "stars": 15, + "topics": [ + "home-assistant", + "home-assistant-component", + "home-assistant-hacs", + "weback" + ], + "version_installed": null + }, + "259270792": { + "authors": [ + "@peternijssen" + ], + "category": "integration", + "description": ":convenience_store: Integrate Jumbo.com in Home Assistant", + "domain": "jumbo", + "downloads": 0, + "full_name": "peternijssen/home-assistant-jumbo", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "4e66be4", + "last_release_tag": "v0.6.1", + "last_updated": "2020-08-22T11:37:35Z", + "name": "jumbo", + "new": false, + "repository_manifest": {}, + "selected_tag": null, + "show_beta": false, + "stars": 5, + "topics": [ + "hacs", + "home-assistant", + "jumbo", + "supermarket" + ], + "version_installed": null + }, + "237102126": { + "authors": [ + "@kabturek", + "@peetereczek" + ], + "category": "integration", + "description": "Home Assistant (hass.io) custom component for Warsaw public transport", + "domain": "ztm", + "downloads": 0, + "full_name": "peetereczek/ztm", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "114ae1e", + "last_release_tag": "0.2.0", + "last_updated": "2020-02-15T18:00:34Z", + "name": "ztm", + "new": false, + "repository_manifest": { + "name": "Warsaw ZTM Information", + "content_in_root": false, + "domains": [ + "sensor" + ], + "country": "PL", + "homeassistant": "0.100.0", + "iot_class": "Cloud Polling" + }, + "selected_tag": null, + "show_beta": false, + "stars": 3, + "topics": [], + "version_installed": null + }, + "236123258": { + "authors": [ + "@camect" + ], + "category": "integration", + "description": "A HACS integration for the Camect smart home surveillance system", + "domain": "camect", + "downloads": 0, + "full_name": "pfunkmallone/HACS-camect-integration", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "d32a4fd", + "last_release_tag": "2020071901", + "last_updated": "2020-07-19T15:39:40Z", + "name": "camect", + "new": false, + "repository_manifest": { + "name": "Camect Integration", + "country": "US", + "domains": "camera", + "homeassistant": "0.99.9", + "render_readme": true + }, + "selected_tag": null, + "show_beta": false, + "stars": 0, + "topics": [ + "camect", + "hacs", + "home-assistant" + ], + "version_installed": null + }, + "196057008": { + "authors": [ + "@pilotak" + ], + "category": "integration", + "description": "Breaks out specified attribute from other entities to a sensor", + "domain": "attributes", + "downloads": 0, + "full_name": "pilotak/homeassistant-attributes", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "5b264c1", + "last_release_tag": "v1.1.0", + "last_updated": "2020-10-04T16:28:34Z", + "name": "attributes", + "new": false, + "repository_manifest": {}, + "selected_tag": null, + "show_beta": false, + "stars": 40, + "topics": [ + "attributes", + "breakout", + "hacs", + "hass", + "homeassistant", + "sensor" + ], + "version_installed": null + }, + "196055705": { + "authors": [ + "@pilotak" + ], + "category": "integration", + "description": "Clientraw weather parser (clientraw.txt) for HomeAssistant", + "domain": "clientraw", + "downloads": 0, + "full_name": "pilotak/homeassistant-clientraw", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "85e72fc", + "last_release_tag": "v2.0.0", + "last_updated": "2020-08-16T11:22:54Z", + "name": "clientraw", + "new": false, + "repository_manifest": {}, + "selected_tag": null, + "show_beta": false, + "stars": 4, + "topics": [ + "clientraw", + "davis", + "hacs", + "hass", + "homeassistant", + "weather" + ], + "version_installed": null + }, + "196059277": { + "authors": [ + "@pilotak" + ], + "category": "integration", + "description": "Enables you to execute scripts and perform API requests in MikroTik router", + "domain": "mikrotik", + "downloads": 0, + "full_name": "pilotak/homeassistant-mikrotik", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "a6390e6", + "last_release_tag": "1.2.0", + "last_updated": "2020-08-16T10:42:29Z", + "name": "mikrotik", + "new": false, + "repository_manifest": {}, + "selected_tag": null, + "show_beta": false, + "stars": 2, + "topics": [ + "api", + "hacs", + "homeassistant", + "homeassistant-components", + "mikrotik", + "script" + ], + "version_installed": null + }, + "224073673": { + "authors": [ + "@pinkywafer" + ], + "category": "integration", + "description": "Calendarific holiday sensor for Home Assistant ", + "domain": "calendarific", + "downloads": 303, + "full_name": "pinkywafer/Calendarific", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "369f069", + "last_release_tag": "0.12.0", + "last_updated": "2020-10-02T12:46:31Z", + "name": "calendarific", + "new": false, + "repository_manifest": { + "name": "Calendarific", + "zip_release": true, + "filename": "calendarific.zip", + "domains": [ + "calendarific", + "sensor" + ], + "homeassistant": "0.109.0", + "iot_class": "Cloud Polling" + }, + "selected_tag": null, + "show_beta": false, + "stars": 7, + "topics": [ + "api-client", + "calendarific", + "holidays", + "home-assistant", + "sensor" + ], + "version_installed": null + }, + "207881337": { + "authors": [ + "@pinkywafer" + ], + "category": "integration", + "description": "Anniversary Countdown Sensor for Home Assistant", + "domain": "anniversaries", + "downloads": 1791, + "full_name": "pinkywafer/Anniversaries", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "417ff4f", + "last_release_tag": "4.0.0", + "last_updated": "2020-11-23T02:25:44Z", + "name": "anniversaries", + "new": false, + "repository_manifest": { + "name": "Anniversaries", + "zip_release": true, + "filename": "anniversaries.zip", + "domains": [ + "anniversaries", + "sensor" + ], + "iot_class": "Local Push", + "homeassistant": "0.109.0" + }, + "selected_tag": null, + "show_beta": false, + "stars": 49, + "topics": [ + "anniversaries", + "hacktoberfest", + "hacs", + "home-assistant", + "sensor" + ], + "version_installed": null + }, + "199549304": { + "authors": [ + "@PiotrMachowski" + ], + "category": "integration", + "description": "This sensor uses gkeepapi library to download a list of notes from https://keep.google.com/.", + "domain": "google_keep", + "downloads": 0, + "full_name": "PiotrMachowski/Home-Assistant-custom-components-Google-Keep", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "c8d133c", + "last_release_tag": "v1.1.0", + "last_updated": "2020-10-21T21:25:44Z", + "name": "google_keep", + "new": false, + "repository_manifest": {}, + "selected_tag": null, + "show_beta": false, + "stars": 16, + "topics": [ + "custom-component", + "home-assistant", + "integration", + "notes" + ], + "version_installed": null + }, + "193371566": { + "authors": [ + "PiotrMachowski" + ], + "category": "integration", + "description": "This sensor uses official API to get weather warnings for Poland and storm warnings for Europe from https://burze.dzis.net.", + "domain": "burze_dzis_net", + "downloads": 0, + "full_name": "PiotrMachowski/Home-Assistant-custom-components-Burze.dzis.net", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "dc927f8", + "last_release_tag": "v1.0.2", + "last_updated": "2020-10-15T15:53:19Z", + "name": "burze_dzis_net", + "new": false, + "repository_manifest": { + "name": "Burze.dzis.net sensor", + "domains": [ + "binary_sensor" + ], + "country": [ + "EU", + "PL" + ] + }, + "selected_tag": null, + "show_beta": false, + "stars": 16, + "topics": [ + "custom-component", + "home-assistant", + "integration", + "weather" + ], + "version_installed": null + }, + "193371469": { + "authors": [ + "PiotrMachowski" + ], + "category": "integration", + "description": "This sensor uses official API to get storm warnings from https://antistorm.eu.", + "domain": "antistorm", + "downloads": 0, + "full_name": "PiotrMachowski/Home-Assistant-custom-components-Antistorm", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "8429f25", + "last_release_tag": "v1.0.2", + "last_updated": "2020-10-15T15:53:44Z", + "name": "antistorm", + "new": false, + "repository_manifest": { + "name": "Antistorm sensor", + "domains": [ + "binary_sensor", + "sensor" + ], + "country": [ + "PL" + ] + }, + "selected_tag": null, + "show_beta": false, + "stars": 4, + "topics": [ + "custom-component", + "home-assistant", + "integration", + "weather" + ], + "version_installed": null + }, + "193588612": { + "authors": [ + "PiotrMachowski" + ], + "category": "integration", + "description": "This sensor uses unofficial API retrieved by decompilation of iMPK application to provide a list of MPK Wroc\u0142aw news available in original app.", + "domain": "impk", + "downloads": 0, + "full_name": "PiotrMachowski/Home-Assistant-custom-components-iMPK", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "1056128", + "last_release_tag": "v1.1.2", + "last_updated": "2020-10-15T15:54:06Z", + "name": "impk", + "new": false, + "repository_manifest": { + "name": "iMPK sensor", + "domains": [ + "binary_sensor" + ], + "country": "PL" + }, + "selected_tag": null, + "show_beta": false, + "stars": 4, + "topics": [ + "custom-component", + "home-assistant", + "integration", + "public-transport", + "wroclaw" + ], + "version_installed": null + }, + "193371652": { + "authors": [ + "PiotrMachowski" + ], + "category": "integration", + "description": "This sensor uses official API to get air quality data from https://looko2.com.", + "domain": "looko2", + "downloads": 0, + "full_name": "PiotrMachowski/Home-Assistant-custom-components-Looko2", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "07e91f7", + "last_release_tag": "v1.0.1", + "last_updated": "2020-10-15T15:53:53Z", + "name": "looko2", + "new": false, + "repository_manifest": { + "name": "Looko2 sensor", + "domains": [ + "sensor" + ], + "country": "PL" + }, + "selected_tag": null, + "show_beta": false, + "stars": 3, + "topics": [ + "air-quality", + "custom-component", + "home-assistant", + "weather" + ], + "version_installed": null + }, + "193588464": { + "authors": [ + "PiotrMachowski" + ], + "category": "integration", + "description": "This sensor uses unofficial API to get data from https://www.rozkladzik.pl and provide information about departures for chosen stop.", + "domain": "rozkladzik", + "downloads": 0, + "full_name": "PiotrMachowski/Home-Assistant-custom-components-Rozkladzik", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "a949302", + "last_release_tag": "v1.1.3", + "last_updated": "2020-10-15T15:53:40Z", + "name": "rozkladzik", + "new": false, + "repository_manifest": { + "name": "Rozk\u0142adzik sensor", + "domains": [ + "sensor" + ], + "country": "PL" + }, + "selected_tag": null, + "show_beta": false, + "stars": 5, + "topics": [ + "custom-component", + "home-assistant", + "integration", + "public-transport" + ], + "version_installed": null + }, + "271398374": { + "authors": [ + "@PiotrMachowski" + ], + "category": "integration", + "description": "This custom component allows you to save current state of any entity and use its data later to restore it.", + "domain": "saver", + "downloads": 0, + "full_name": "PiotrMachowski/Home-Assistant-custom-components-Saver", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "41f15ce", + "last_release_tag": "v1.1.1", + "last_updated": "2020-10-15T15:54:02Z", + "name": "saver", + "new": false, + "repository_manifest": { + "name": "Saver", + "render_readme": true + }, + "selected_tag": null, + "show_beta": false, + "stars": 18, + "topics": [ + "automation", + "helper", + "home-assistant", + "save", + "script", + "variable" + ], + "version_installed": null + }, + "166045890": { + "authors": [ + "@pippyn" + ], + "category": "integration", + "description": "Provides Home Assistant sensors for multiple Dutch and Belgium waste collectors", + "domain": "afvalbeheer", + "downloads": 0, + "full_name": "pippyn/Home-Assistant-Sensor-Afvalbeheer", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "0b17acb", + "last_release_tag": "v4.7.4", + "last_updated": "2020-12-07T16:39:50Z", + "name": "afvalbeheer", + "new": false, + "repository_manifest": {}, + "selected_tag": null, + "show_beta": false, + "stars": 67, + "topics": [ + "belgium", + "dutch", + "hacs", + "hassio", + "hassio-integration", + "home-assistant", + "waste-collectors" + ], + "version_installed": null + }, + "193371922": { + "authors": [ + "PiotrMachowski" + ], + "category": "integration", + "description": "This sensor uses unofficial API to get energy usage and generation data from https://elicznik.tauron-dystrybucja.pl.", + "domain": "tauron_amiplus", + "downloads": 0, + "full_name": "PiotrMachowski/Home-Assistant-custom-components-Tauron-AMIplus", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "de4d8b4", + "last_release_tag": "v1.1.1", + "last_updated": "2020-10-15T15:53:14Z", + "name": "tauron_amiplus", + "new": false, + "repository_manifest": { + "name": "Tauron AMIplus sensor", + "domains": [ + "sensor" + ], + "country": "PL" + }, + "selected_tag": null, + "show_beta": false, + "stars": 15, + "topics": [ + "custom-component", + "energy-monitor", + "home-assistant", + "integration" + ], + "version_installed": null + }, + "165637690": { + "authors": [ + "@pippyn" + ], + "category": "integration", + "description": "Provides sensors for the Dutch waste collector Groningen Afvalwijzer.", + "domain": "groningen_afvalwijzer", + "downloads": 0, + "full_name": "pippyn/Home-Assistant-Sensor-Groningen-Afvalwijzer", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "645d9f7", + "last_release_tag": "v1.1.3", + "last_updated": "2019-06-11T12:41:23Z", + "name": "groningen_afvalwijzer", + "new": false, + "repository_manifest": {}, + "selected_tag": null, + "show_beta": false, + "stars": 3, + "topics": [], + "version_installed": null + }, + "209996125": { + "authors": [], + "category": "integration", + "description": "A custom component for Home Assistant which integrates my picoTTS Addon on HASS.io,", + "domain": "picotts_remote", + "downloads": 0, + "full_name": "Poeschl/Remote-PicoTTS", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "110bcfb", + "last_release_tag": "1.0.0", + "last_updated": "2020-02-15T20:30:13Z", + "name": "picotts_remote", + "new": false, + "repository_manifest": { + "name": "Remote PicoTTS", + "render_readme": true, + "domains": "tts", + "homeassistant": "0.36", + "iot_class": "Local Polling" + }, + "selected_tag": null, + "show_beta": false, + "stars": 7, + "topics": [ + "component", + "hacs", + "hassio", + "home-assistant", + "picotts-addon", + "remote-picotts" + ], + "version_installed": null + }, + "281956859": { + "authors": [ + "@postlund" + ], + "category": "integration", + "description": "Experimental integration to Home Assistant supporting D-Link devices", + "domain": "dlink_hnap", + "downloads": 0, + "full_name": "postlund/dlink_hnap", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "5fc683f", + "last_release_tag": null, + "last_updated": "2020-11-01T18:33:37Z", + "name": "dlink_hnap", + "new": false, + "repository_manifest": { + "name": "D-Link HNAP", + "content_in_root": false, + "domains": [ + "binary_sensor" + ], + "homeassistant": "0.109.0", + "iot_class": "Local Pull", + "render_readme": true + }, + "selected_tag": null, + "show_beta": false, + "stars": 9, + "topics": [ + "custom-integration", + "dlink", + "home-assistant" + ], + "version_installed": null + }, + "238568340": { + "authors": [ + "@Pouzor" + ], + "category": "integration", + "description": "Custom Component for Home Assistant, enable to remote Freebox Player", + "domain": "freebox_player", + "downloads": 0, + "full_name": "Pouzor/freebox_player", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "3039117", + "last_release_tag": "1.0.4", + "last_updated": "2020-11-13T16:54:56Z", + "name": "freebox_player", + "new": false, + "repository_manifest": { + "name": "Freebox Player" + }, + "selected_tag": null, + "show_beta": false, + "stars": 4, + "topics": [ + "custom-component", + "freebox", + "home-assistant", + "homeassistant", + "python" + ], + "version_installed": null + }, + "274711981": { + "authors": [ + "@ppanagiotis" + ], + "category": "integration", + "description": "Group MusicCast Speakers with Home Assistant", + "domain": "musiccast_yamaha", + "downloads": 0, + "full_name": "ppanagiotis/pymusiccast", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "9311dab", + "last_release_tag": "v0.3", + "last_updated": "2020-07-13T01:40:56Z", + "name": "musiccast_yamaha", + "new": false, + "repository_manifest": { + "name": "Musiccast Yamaha", + "domains": [ + "media_player" + ], + "render_readme": true + }, + "selected_tag": null, + "show_beta": false, + "stars": 15, + "topics": [ + "automation", + "hass", + "hassio", + "home-assistant", + "homeassistant", + "lovelace", + "media-player", + "musiccast", + "yamaha" + ], + "version_installed": null + }, + "219035415": { + "authors": [ + "ptimatth" + ], + "category": "integration", + "description": "GeoRide integration for Home Assistant", + "domain": "georide", + "downloads": 0, + "full_name": "ptimatth/GeorideHA", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "02e1656", + "last_release_tag": "0.5.1", + "last_updated": "2020-12-01T18:28:45Z", + "name": "georide", + "new": false, + "repository_manifest": { + "name": "GeoRide integration", + "content_in_root": false, + "render_readme": true, + "domains": [ + "devices_tracker", + "sensor" + ], + "country": [ + "FR" + ], + "homeassistant": "0.110.0" + }, + "selected_tag": null, + "show_beta": false, + "stars": 5, + "topics": [], + "version_installed": null + }, + "232573716": { + "authors": [ + "@PTST" + ], + "category": "integration", + "description": "Office 365 integration for Home Assistant", + "domain": "o365", + "downloads": 0, + "full_name": "PTST/O365-HomeAssistant", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "d61a26d", + "last_release_tag": "v2.1.8", + "last_updated": "2020-12-03T04:39:57Z", + "name": "o365", + "new": false, + "repository_manifest": { + "name": "Office 365 Integration", + "homeassistant": "0.103", + "persistent_directory": ".O365-token-cache", + "render_readme": true + }, + "selected_tag": null, + "show_beta": false, + "stars": 24, + "topics": [], + "version_installed": null + }, + "268118148": { + "authors": [ + "@py-smart-gardena" + ], + "category": "integration", + "description": "Home Assistant custom component integration for Gardena Smart System", + "domain": "gardena_smart_system", + "downloads": 0, + "full_name": "py-smart-gardena/hass-gardena-smart-system", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "0e663f8", + "last_release_tag": "0.2.2", + "last_updated": "2020-10-03T12:37:45Z", + "name": "gardena_smart_system", + "new": false, + "repository_manifest": { + "name": "Gardena Smart System", + "domains": [ + "sensor", + "switch", + "vacuum", + "binary_sensor" + ], + "render_readme": true, + "homeassistant": "0.115.0" + }, + "selected_tag": null, + "show_beta": false, + "stars": 31, + "topics": [ + "gardena", + "gardena-api", + "gardena-smart-system", + "home-assistant", + "homeassistant" + ], + "version_installed": null + }, + "264415552": { + "authors": [ + "@Rendili", + "@KJonline" + ], + "category": "integration", + "description": "A custom version of the home assistant hive component", + "domain": "hive", + "downloads": 0, + "full_name": "Pyhive/HA-Hive-Custom-Component", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "fde6dee", + "last_release_tag": "2020.0", + "last_updated": "2020-12-06T16:04:10Z", + "name": "hive", + "new": false, + "repository_manifest": { + "name": "Hive Custom Component", + "render_readme": true, + "domains": [ + "binary_sensor", + "climate", + "light", + "sensor", + "switch", + "water_heater" + ] + }, + "selected_tag": null, + "show_beta": false, + "stars": 12, + "topics": [ + "hive" + ], + "version_installed": null + }, + "260264517": { + "authors": [ + "@r-renato" + ], + "category": "integration", + "description": "Climacell weather provider integration is a custom component for Home Assistant. The climacell platform uses the Climacell API as a source for meteorological data for your location.", + "domain": "climacell", + "downloads": 0, + "full_name": "r-renato/ha-climacell-weather", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "33245e3", + "last_release_tag": null, + "last_updated": "2020-07-31T19:35:29Z", + "name": "climacell", + "new": false, + "repository_manifest": { + "name": "Climacell weather provider", + "domains": [ + "sensor" + ] + }, + "selected_tag": null, + "show_beta": false, + "stars": 21, + "topics": [ + "climacell", + "hacs", + "home-assistant", + "weather" + ], + "version_installed": null + }, + "197983504": { + "authors": [ + "@rdehuyss" + ], + "category": "integration", + "description": "Support for Denkovi IOT Relay modules in HomeAssistant", + "domain": "denkovi", + "downloads": 0, + "full_name": "rdehuyss/homeassistant-custom_components-denkovi", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "4dae6d3", + "last_release_tag": "v1.0", + "last_updated": "2019-07-20T21:57:00Z", + "name": "denkovi", + "new": false, + "repository_manifest": {}, + "selected_tag": null, + "show_beta": false, + "stars": 2, + "topics": [ + "denkovi", + "homeassistant", + "homeassistant-components" + ], + "version_installed": null + }, + "289550686": { + "authors": [ + "@raman325" + ], + "category": "integration", + "description": "Custom Home Assistant component for Zoom. Tracks when you are connected to a Zoom call by default but may allow you to track more.", + "domain": "zoom", + "downloads": 0, + "full_name": "raman325/ha-zoom-automation", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "1e9e1ce", + "last_release_tag": "v0.3.0", + "last_updated": "2020-11-03T17:21:52Z", + "name": "zoom", + "new": false, + "repository_manifest": { + "name": "Zoom", + "hacs": "0.24.0", + "domains": [ + "binary_sensor", + "sensor" + ], + "iot_class": "Cloud Push", + "render_readme": true, + "homeassistant": "0.110.0" + }, + "selected_tag": null, + "show_beta": false, + "stars": 20, + "topics": [ + "automation", + "custom-component", + "ha", + "hacs", + "hassio", + "home-assistant", + "homeassistant", + "integration", + "webhook-event", + "zoom" + ], + "version_installed": null + }, + "225305915": { + "authors": [ + "@Martinvdm", + "@vloris", + "@remco770" + ], + "category": "integration", + "description": "Garbage collection BAR for Home Assistant", + "domain": "bar_afvalbeheer", + "downloads": 0, + "full_name": "remco770/garbage-bar-homeassistant", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "1cee6c3", + "last_release_tag": "1.3", + "last_updated": "2020-05-01T10:02:46Z", + "name": "bar_afvalbeheer", + "new": false, + "repository_manifest": { + "name": "BAR garbage collection", + "domains": [ + "sensor" + ] + }, + "selected_tag": null, + "show_beta": false, + "stars": 2, + "topics": [], + "version_installed": null + }, + "164161024": { + "authors": [ + "@rgruebel" + ], + "category": "integration", + "description": "Custom Component for Homeassistant to show zigbee2mqtt Networkmap", + "domain": "zigbee2mqtt_networkmap", + "downloads": 0, + "full_name": "rgruebel/ha_zigbee2mqtt_networkmap", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "7a8eb0b", + "last_release_tag": null, + "last_updated": "2020-07-20T04:53:00Z", + "name": "zigbee2mqtt_networkmap", + "new": false, + "repository_manifest": {}, + "selected_tag": null, + "show_beta": false, + "stars": 161, + "topics": [], + "version_installed": null + }, + "158878235": { + "authors": [ + "@reharmsen", + "@pdwonline", + "@jongsoftdev" + ], + "category": "integration", + "description": "Custom Youless component for Home-Assistant ", + "domain": "youless", + "downloads": 0, + "full_name": "reharmsen/hass-youless-component", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "997937a", + "last_release_tag": "2.0.1", + "last_updated": "2019-10-06T14:11:19Z", + "name": "youless", + "new": false, + "repository_manifest": { + "name": "YouLess", + "country": "NL", + "render_readme": true, + "domains": [ + "sensor" + ] + }, + "selected_tag": null, + "show_beta": false, + "stars": 4, + "topics": [], + "version_installed": null + }, + "255380953": { + "authors": [ + "@rkoebrugge", + "@reharmsen", + "@pdwonline", + "@jongsoftdev" + ], + "category": "integration", + "description": "Custom Youless LS110 component for Home-Assistant ", + "domain": "youless", + "downloads": 0, + "full_name": "rkoebrugge/hacs-youless-component", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "3cd8d1a", + "last_release_tag": "2.0.5", + "last_updated": "2020-06-19T08:52:21Z", + "name": "youless", + "new": false, + "repository_manifest": { + "name": "YouLess LS110", + "country": "NL", + "render_readme": true, + "domains": [ + "sensor" + ] + }, + "selected_tag": null, + "show_beta": false, + "stars": 0, + "topics": [], + "version_installed": null + }, + "257634153": { + "authors": [ + "@rob196" + ], + "category": "integration", + "description": "This is a custom component to integrate into FXMarketAPI (https://fxmarketapi.com) to get the live mid-rates in Home Assistant.", + "domain": "fxmarketapi", + "downloads": 0, + "full_name": "rob196/home-assistant-fxmarketapi", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "c178ade", + "last_release_tag": "v1.0.2", + "last_updated": "2020-05-15T21:44:45Z", + "name": "fxmarketapi", + "new": false, + "repository_manifest": { + "name": "FXMarketAPI Integration", + "content_in_root": false, + "render_readme": false, + "domains": [ + "sensor" + ] + }, + "selected_tag": null, + "show_beta": false, + "stars": 2, + "topics": [ + "custom-component", + "hacs", + "home-assistant", + "homeassistant", + "python" + ], + "version_installed": null + }, + "241427839": { + "authors": [ + "@robbinjanssen" + ], + "category": "integration", + "description": "Read the current, daily and total power from your Omnik Inverter via local network (no cloud!)", + "domain": "omnik_inverter", + "downloads": 0, + "full_name": "robbinjanssen/home-assistant-omnik-inverter", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "8bd6876", + "last_release_tag": "v1.5.0", + "last_updated": "2020-08-04T10:52:39Z", + "name": "omnik_inverter", + "new": false, + "repository_manifest": { + "name": "Omnik Inverter Solar Sensor (No Cloud)", + "country": "NL", + "domains": [ + "sensor" + ], + "homeassistant": "0.109.0" + }, + "selected_tag": null, + "show_beta": false, + "stars": 8, + "topics": [ + "home-assistant", + "home-assistant-sensor" + ], + "version_installed": null + }, + "213551635": { + "authors": [ + "@roberodin" + ], + "category": "integration", + "description": "\ud83d\udcfa HomeAssistant - SamsungTV Custom Component", + "domain": "samsungtv_custom", + "downloads": 0, + "full_name": "roberodin/ha-samsungtv-custom", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "da6bf93", + "last_release_tag": "3.0.5", + "last_updated": "2020-10-25T20:41:48Z", + "name": "samsungtv_custom", + "new": false, + "repository_manifest": {}, + "selected_tag": null, + "show_beta": false, + "stars": 67, + "topics": [], + "version_installed": null + }, + "140618233": { + "authors": [ + "@robhofmann" + ], + "category": "integration", + "description": "Custom Gree climate component written in Python3 for Home Assistant. Controls AC's supporting the Gree protocol.", + "domain": "gree", + "downloads": 0, + "full_name": "RobHofmann/HomeAssistant-GreeClimateComponent", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "e53e70f", + "last_release_tag": "2.2.0", + "last_updated": "2020-11-02T09:09:16Z", + "name": "gree", + "new": false, + "repository_manifest": {}, + "selected_tag": null, + "show_beta": false, + "stars": 72, + "topics": [], + "version_installed": null + }, + "196605143": { + "authors": [ + "@SirGilbot", + "@robhofmann" + ], + "category": "integration", + "description": "Custom component for Philips TV's running Android which are built between 2014 and 2016. Written in Python3 for Home Assistant.", + "domain": "philips_2014", + "downloads": 0, + "full_name": "RobHofmann/HomeAssistant-PhilipsAndroid2014", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "2bee15c", + "last_release_tag": null, + "last_updated": "2019-07-16T07:03:49Z", + "name": "philips_2014", + "new": false, + "repository_manifest": {}, + "selected_tag": null, + "show_beta": false, + "stars": 1, + "topics": [], + "version_installed": null + }, + "197116235": { + "authors": [ + "@robmarkcole" + ], + "category": "integration", + "description": "Home Assistant custom component for using Deepstack face recognition", + "domain": "deepstack_face", + "downloads": 0, + "full_name": "robmarkcole/HASS-Deepstack-face", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "7e56965", + "last_release_tag": "v0.6", + "last_updated": "2020-06-13T03:29:32Z", + "name": "deepstack_face", + "new": false, + "repository_manifest": {}, + "selected_tag": null, + "show_beta": false, + "stars": 72, + "topics": [ + "computer-vision", + "deep-learning", + "home-assistant" + ], + "version_installed": null + }, + "117426840": { + "authors": [ + "@robinostlund" + ], + "category": "integration", + "description": "Volkswagen Carnet Component for home assistant", + "domain": "volkswagencarnet", + "downloads": 864, + "full_name": "robinostlund/homeassistant-volkswagencarnet", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "c36caa3", + "last_release_tag": "v4.4.27", + "last_updated": "2020-11-30T17:49:40Z", + "name": "volkswagencarnet", + "new": false, + "repository_manifest": { + "name": "Volkswagen We Connect", + "iot_class": "Cloud Polling", + "homeassistant": "0.110.0", + "hide_default_branch": true, + "zip_release": true, + "filename": "volkswagencarnet.zip" + }, + "selected_tag": null, + "show_beta": false, + "stars": 105, + "topics": [ + "custom-component", + "hacs", + "homeassistant", + "volkswagen-carnet" + ], + "version_installed": null + }, + "160022220": { + "authors": [ + "@robmarkcole" + ], + "category": "integration", + "description": "Home Assistant Object detection with Amazon Rekognition", + "domain": "amazon_rekognition", + "downloads": 0, + "full_name": "robmarkcole/HASS-amazon-rekognition", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "bcfcb57", + "last_release_tag": "v2.7", + "last_updated": "2020-11-04T04:37:16Z", + "name": "amazon_rekognition", + "new": false, + "repository_manifest": { + "name": "Amazon Rekognition", + "domains": [ + "image_processing" + ] + }, + "selected_tag": null, + "show_beta": false, + "stars": 58, + "topics": [ + "home-assistant", + "rekognition" + ], + "version_installed": null + }, + "165791238": { + "authors": [ + "@robmarkcole" + ], + "category": "integration", + "description": "Home Assistant custom component for using Deepstack object detection", + "domain": "deepstack_object", + "downloads": 0, + "full_name": "robmarkcole/HASS-Deepstack-object", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "658d03c", + "last_release_tag": "v3.4", + "last_updated": "2020-11-04T05:40:17Z", + "name": "deepstack_object", + "new": false, + "repository_manifest": {}, + "selected_tag": null, + "show_beta": false, + "stars": 180, + "topics": [ + "facial-recognition", + "home-assistant" + ], + "version_installed": null + }, + "135166048": { + "authors": [ + "@robmarkcole" + ], + "category": "integration", + "description": "Home-Assistant image classification using Machinebox.io", + "domain": "classificationbox", + "downloads": 0, + "full_name": "robmarkcole/HASS-Machinebox-Classificationbox", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "5d11474", + "last_release_tag": "v0.5", + "last_updated": "2019-04-28T06:44:42Z", + "name": "classificationbox", + "new": false, + "repository_manifest": {}, + "selected_tag": null, + "show_beta": false, + "stars": 18, + "topics": [ + "computer-vision", + "deep-neural-networks", + "home-assistant", + "home-automation", + "machinebox" + ], + "version_installed": null + }, + "98039328": { + "authors": [ + "@robmarkcole" + ], + "category": "integration", + "description": "Support for Hue motion sensors and device tracker", + "domain": "huesensor", + "downloads": 0, + "full_name": "robmarkcole/Hue-sensors-HASS", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "1f409b0", + "last_release_tag": "V3.0", + "last_updated": "2020-11-19T17:17:07Z", + "name": "huesensor", + "new": false, + "repository_manifest": { + "name": "Hue sensor advanced", + "country": "UK", + "domains": [ + "device_tracker", + "binary_sensor", + "remote" + ], + "homeassistant": "0.106.0" + }, + "selected_tag": null, + "show_beta": false, + "stars": 350, + "topics": [ + "home-assistant", + "hue", + "hue-lights", + "hue-sensors" + ], + "version_installed": null + }, + "200989730": { + "authors": [ + "@rsnodgrass", + "@snicker", + "@DubhAd" + ], + "category": "integration", + "description": "Flo Water Control for Home Assistant", + "domain": "flo", + "downloads": 0, + "full_name": "rsnodgrass/hass-flo-water", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "a3c0af1", + "last_release_tag": "3.0.5", + "last_updated": "2020-10-01T15:29:25Z", + "name": "flo", + "new": false, + "repository_manifest": { + "name": "Flo by Moen Smart Water Monitor", + "domains": [ + "sensor", + "switch" + ], + "render_readme": true + }, + "selected_tag": null, + "show_beta": false, + "stars": 8, + "topics": [], + "version_installed": null + }, + "149443194": { + "authors": [ + "@rsnodgrass" + ], + "category": "integration", + "description": "ADT Pulse sensor for Home Assistant", + "domain": "adtpulse", + "downloads": 0, + "full_name": "rsnodgrass/hass-adtpulse", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "58c7a2d", + "last_release_tag": "0.1.5", + "last_updated": "2020-11-27T09:43:34Z", + "name": "adtpulse", + "new": false, + "repository_manifest": { + "name": "ADT Pulse", + "domains": [ + "alarm_control_panel", + "binary_sensor" + ], + "render_readme": true + }, + "selected_tag": null, + "show_beta": false, + "stars": 7, + "topics": [ + "adt-pulse", + "hacktoberfest", + "homeassistant" + ], + "version_installed": null + }, + "249381778": { + "authors": [ + "@rospogrigio", + "@postlund" + ], + "category": "integration", + "description": "local handling for Tuya devices", + "domain": "localtuya", + "downloads": 0, + "full_name": "rospogrigio/localtuya", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "b50ee8b", + "last_release_tag": "v3.1.0", + "last_updated": "2020-12-08T14:00:09Z", + "name": "localtuya", + "new": false, + "repository_manifest": { + "name": "Local Tuya", + "domains": [ + "climate", + "cover", + "fan", + "light", + "sensor", + "switch" + ], + "homeassistant": "0.116.0", + "iot_class": [ + "Local Push" + ] + }, + "selected_tag": null, + "show_beta": false, + "stars": 155, + "topics": [ + "localtuya", + "tuya", + "tuya-api" + ], + "version_installed": null + }, + "179931720": { + "authors": [ + "@rsnodgrass" + ], + "category": "integration", + "description": "Home Assistant smart home platform integrations including sensors and custom tools", + "domain": "groupme", + "downloads": 0, + "full_name": "rsnodgrass/hass-integrations", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "4d4bde8", + "last_release_tag": null, + "last_updated": "2019-12-17T02:59:22Z", + "name": "groupme", + "new": false, + "repository_manifest": {}, + "selected_tag": null, + "show_beta": false, + "stars": 6, + "topics": [ + "home-assistant", + "water-sensor" + ], + "version_installed": null + }, + "200927325": { + "authors": [ + "@rsnodgrass" + ], + "category": "integration", + "description": "Pool Math for Home Assistant", + "domain": "poolmath", + "downloads": 0, + "full_name": "rsnodgrass/hass-poolmath", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "7889abf", + "last_release_tag": "0.1.2", + "last_updated": "2020-12-08T09:49:48Z", + "name": "poolmath", + "new": false, + "repository_manifest": { + "name": "Pool Math (Trouble Free Pool)", + "domains": [ + "sensor" + ], + "render_readme": true + }, + "selected_tag": null, + "show_beta": false, + "stars": 4, + "topics": [ + "hacktoberfest", + "homeassistant" + ], + "version_installed": null + }, + "235385658": { + "authors": [ + "@rsnodgrass" + ], + "category": "integration", + "description": "Xantech Multi-Zone Matrix Audio for Home Assistant", + "domain": "xantech", + "downloads": 0, + "full_name": "rsnodgrass/hass-xantech", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "9dc97a6", + "last_release_tag": "0.0.3", + "last_updated": "2020-08-23T15:24:04Z", + "name": "xantech", + "new": false, + "repository_manifest": { + "name": "Xantech Multi-Zone Audio Amplifier", + "domains": [ + "media_player" + ], + "render_readme": true + }, + "selected_tag": null, + "show_beta": false, + "stars": 2, + "topics": [ + "audiophile", + "hacktoberfest", + "home-assistant", + "home-automation", + "homeassistant" + ], + "version_installed": null + }, + "205416078": { + "authors": [ + "@rsnodgrass" + ], + "category": "integration", + "description": "SensorPush integration for Home Assistant", + "domain": "sensorpush", + "downloads": 0, + "full_name": "rsnodgrass/hass-sensorpush", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "9e6df38", + "last_release_tag": "0.0.5", + "last_updated": "2020-10-11T05:04:12Z", + "name": "sensorpush", + "new": false, + "repository_manifest": { + "name": "SensorPush", + "domains": [ + "sensor" + ], + "render_readme": true + }, + "selected_tag": null, + "show_beta": false, + "stars": 8, + "topics": [ + "hacktoberfest", + "homeassistant" + ], + "version_installed": null + }, + "153006394": { + "authors": [ + "@yuval_mejahez" + ], + "category": "integration", + "description": null, + "domain": "school_holidays", + "downloads": 0, + "full_name": "rt400/School-Vacation", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "e783e07", + "last_release_tag": null, + "last_updated": "2020-10-12T20:47:04Z", + "name": "school_holidays", + "new": false, + "repository_manifest": {}, + "selected_tag": null, + "show_beta": false, + "stars": 3, + "topics": [], + "version_installed": null + }, + "279861920": { + "authors": [ + "@yuval_mejahez" + ], + "category": "integration", + "description": "ReversoTTS component for HomeAssistant", + "domain": "reversotts", + "downloads": 0, + "full_name": "rt400/ReversoTTS-HA", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "5519688", + "last_release_tag": null, + "last_updated": "2020-10-09T05:25:54Z", + "name": "reversotts", + "new": false, + "repository_manifest": { + "name": "Reverso TTS / tts", + "render_readme": true + }, + "selected_tag": null, + "show_beta": false, + "stars": 11, + "topics": [ + "reversotts", + "tts" + ], + "version_installed": null + }, + "235915302": { + "authors": [], + "category": "integration", + "description": "Custom Home Assistant sensor for the Marta/Breeze Card.", + "domain": "marta", + "downloads": 0, + "full_name": "ryanmac8/Home-Assistant-Marta", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "468fb30", + "last_release_tag": "v1.1", + "last_updated": "2020-02-07T17:44:05Z", + "name": "marta", + "new": false, + "repository_manifest": { + "name": "Marta / Breeze Card", + "content_in_root": false, + "domains": [ + "sensor" + ], + "render_readme": true + }, + "selected_tag": null, + "show_beta": false, + "stars": 0, + "topics": [ + "breeze-card", + "homeassistant", + "marta", + "sensor" + ], + "version_installed": null + }, + "222845480": { + "authors": [ + "ryannazaretian" + ], + "category": "integration", + "description": "Nexia climate integration for Trane and American Standard thermostats", + "domain": "nexia", + "downloads": 0, + "full_name": "ryannazaretian/hacs-nexia-climate-integration", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "1bfa7bf", + "last_release_tag": "v1.0", + "last_updated": "2020-04-17T15:57:11Z", + "name": "nexia", + "new": false, + "repository_manifest": { + "name": "Nexia Climate Integration", + "render_readme": true, + "domains": [ + "sensor", + "binary_sensor", + "climate" + ], + "iot_class": "Local Polling" + }, + "selected_tag": null, + "show_beta": false, + "stars": 10, + "topics": [ + "american-standard", + "hacs", + "home-assistant", + "nexia", + "nexia-thermostat", + "trane" + ], + "version_installed": null + }, + "224743334": { + "authors": [ + "@safepay" + ], + "category": "integration", + "description": "Control Hunter Douglas / Luxaflex PowerView Window Shades in Home Assistant", + "domain": "hd_powerview", + "downloads": 0, + "full_name": "safepay/cover.hd_powerview", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "a9c4ba9", + "last_release_tag": "v0.9.0", + "last_updated": "2020-03-21T04:54:26Z", + "name": "hd_powerview", + "new": false, + "repository_manifest": { + "name": "Hunter Douglas and Luxaflex PowerView Cover", + "domains": [ + "cover" + ] + }, + "selected_tag": null, + "show_beta": false, + "stars": 5, + "topics": [], + "version_installed": null + }, + "195308808": { + "authors": [ + "@safepay" + ], + "category": "integration", + "description": "A Fronius Sensor for Home Assistant", + "domain": "fronius_inverter", + "downloads": 0, + "full_name": "safepay/sensor.fronius", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "cecd6a6", + "last_release_tag": "v0.9.3", + "last_updated": "2020-05-26T21:59:49Z", + "name": "fronius_inverter", + "new": false, + "repository_manifest": {}, + "selected_tag": null, + "show_beta": false, + "stars": 21, + "topics": [], + "version_installed": null + }, + "194971711": { + "authors": [ + "@safepay" + ], + "category": "integration", + "description": "A WillyWeather Australian Bureau of Meteorology (BoM) integration for Home Assistant", + "domain": "willyweather", + "downloads": 0, + "full_name": "safepay/sensor.willyweather", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "d9fb6d1", + "last_release_tag": "v1.5", + "last_updated": "2020-09-05T04:40:00Z", + "name": "willyweather", + "new": false, + "repository_manifest": {}, + "selected_tag": null, + "show_beta": false, + "stars": 3, + "topics": [], + "version_installed": null + }, + "303827752": { + "authors": [ + "@sbabcock23" + ], + "category": "integration", + "description": "Home Assistant integration for TryFi Dog Collar GPS monitoring.", + "domain": "tryfi", + "downloads": 0, + "full_name": "sbabcock23/hass-tryfi", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "46a47e3", + "last_release_tag": "0.0.2", + "last_updated": "2020-11-05T14:55:05Z", + "name": "tryfi", + "new": false, + "repository_manifest": { + "config_flow": true, + "documentation": "https://github.com/sbabcock23/hass-pytryfi", + "domains": [ + "device_tracker", + "light", + "sensor", + "lock" + ], + "homeassistant": "0.117.0", + "iot_class": "Local Polling", + "name": "TryFi Dog Monitor", + "render_readme": true + }, + "selected_tag": null, + "show_beta": false, + "stars": 1, + "topics": [ + "dog", + "dog-collar", + "gps", + "hacs", + "home-assistant", + "homeassistant-integration", + "iot", + "python", + "tryfi" + ], + "version_installed": null + }, + "213346369": { + "authors": [], + "category": "integration", + "description": "A Home Assistant custom component for use with ECHONET enabled Mitsubishi HVAC systems. ", + "domain": "mitsubishi", + "downloads": 0, + "full_name": "scottyphillips/mitsubishi_hass", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "380b837", + "last_release_tag": "v2.0", + "last_updated": "2020-09-08T23:53:32Z", + "name": "mitsubishi", + "new": false, + "repository_manifest": { + "name": "Mitsubishi Echonet Climate and Sensor Component", + "domains": [ + "climate", + "sensor" + ], + "homeassistant": "0.110.4", + "iot_class": "Local Polling" + }, + "selected_tag": null, + "show_beta": false, + "stars": 13, + "topics": [], + "version_installed": null + }, + "228579545": { + "authors": [ + "@sebr" + ], + "category": "integration", + "description": "Orbit BHyve custom component for Home Assistant", + "domain": "bhyve", + "downloads": 0, + "full_name": "sebr/bhyve-home-assistant", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "e80964a", + "last_release_tag": "2.0.5", + "last_updated": "2020-11-09T06:38:58Z", + "name": "bhyve", + "new": false, + "repository_manifest": { + "name": "Orbit BHyve", + "domains": [ + "sensor", + "switch" + ], + "iot_class": "Cloud Push" + }, + "selected_tag": null, + "show_beta": false, + "stars": 50, + "topics": [ + "bhyve", + "home-assistant", + "home-assistant-component", + "irrigation", + "orbit", + "orbit-bhyve" + ], + "version_installed": null + }, + "172395276": { + "authors": [ + "@SebuZet" + ], + "category": "integration", + "description": "Home Assistant Climate Device for controlling (not only) Samsung AC", + "domain": "climate_ip", + "downloads": 0, + "full_name": "SebuZet/samsungrac", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "40dad45", + "last_release_tag": "2.2.2", + "last_updated": "2020-11-23T12:55:04Z", + "name": "climate_ip", + "new": false, + "repository_manifest": { + "name": "Climate IP", + "country": "NO", + "domains": [ + "climate", + "switch" + ], + "homeassistant": "0.96.0" + }, + "selected_tag": null, + "show_beta": false, + "stars": 35, + "topics": [], + "version_installed": null + }, + "268722568": { + "authors": [ + "@Sennevds" + ], + "category": "integration", + "description": "Template media_player for Home Assistant", + "domain": "media_player_template", + "downloads": 0, + "full_name": "Sennevds/media_player.template", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "045a16d", + "last_release_tag": "0.0.6", + "last_updated": "2020-12-04T15:29:16Z", + "name": "media_player_template", + "new": false, + "repository_manifest": { + "name": "Media player template", + "domains": [ + "media_player", + "template" + ], + "homeassistant": "0.116", + "render_readme": true, + "iot_class": [ + "Local Polling", + "Local Push" + ] + }, + "selected_tag": null, + "show_beta": false, + "stars": 19, + "topics": [ + "custom-component", + "home-assistant", + "homeassistant", + "media-player" + ], + "version_installed": null + }, + "248046910": { + "authors": [ + "@sermayoral" + ], + "category": "integration", + "description": "Samsung TV Encrypted Models (H & J Series) custom component for Home Assistant", + "domain": "samsungtv_encrypted", + "downloads": 0, + "full_name": "sermayoral/ha-samsungtv-encrypted", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "f0bb1b5", + "last_release_tag": "v3.1", + "last_updated": "2020-12-03T12:41:25Z", + "name": "samsungtv_encrypted", + "new": false, + "repository_manifest": { + "name": "SamsungTV Encrypted", + "render_readme": false, + "homeassistant": "0.110.0" + }, + "selected_tag": null, + "show_beta": false, + "stars": 20, + "topics": [ + "home-assistant-custom", + "iot", + "python", + "samsungtv" + ], + "version_installed": null + }, + "202226247": { + "authors": [ + "@bdraco", + "@shbatm" + ], + "category": "integration", + "description": "Custom Integration for ISY994 with Home Assistant (for use with HACS) ", + "domain": "isy994", + "downloads": 0, + "full_name": "shbatm/hacs-isy994", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "b3ffcfc", + "last_release_tag": "3.0.0dev15-1", + "last_updated": "2020-11-21T14:53:20Z", + "name": "isy994", + "new": false, + "repository_manifest": { + "name": "Universal Devices ISY994", + "homeassistant": "0.117.4", + "render_readme": true + }, + "selected_tag": null, + "show_beta": false, + "stars": 10, + "topics": [], + "version_installed": null + }, + "220678749": { + "authors": [ + "@shogunxam" + ], + "category": "integration", + "description": "HA Integration for Centro Funzionale Regione Toscana", + "domain": "cfr", + "downloads": 0, + "full_name": "shogunxam/Home-Assistant-custom-components-cfr-toscana", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "b336f63", + "last_release_tag": "v0.7", + "last_updated": "2020-08-30T09:46:43Z", + "name": "cfr", + "new": false, + "repository_manifest": { + "name": "cfr sensor", + "domains": [ + "sensor" + ], + "country": [ + "IT" + ] + }, + "selected_tag": null, + "show_beta": false, + "stars": 0, + "topics": [], + "version_installed": null + }, + "204164206": { + "authors": [ + "@Sholofly" + ], + "category": "integration", + "description": "Custom component to integrate Ziggo Mediabox Next into Home Assistant", + "domain": "ziggonext", + "downloads": 0, + "full_name": "Sholofly/ZiggoNext", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "e3e6b11", + "last_release_tag": "v0.7.5", + "last_updated": "2020-12-08T11:02:44Z", + "name": "ziggonext", + "new": false, + "repository_manifest": { + "name": "ZiggoNext", + "country": [ + "NL" + ], + "iot_class": "Local Push", + "domains": [ + "media_player" + ], + "render_readme": true + }, + "selected_tag": null, + "show_beta": false, + "stars": 29, + "topics": [ + "assistant", + "home", + "media-players", + "ziggo-next" + ], + "version_installed": null + }, + "231840220": { + "authors": [], + "category": "integration", + "description": "MoneyDashboard Net Balance sensor for HomeAssistant", + "domain": "moneydashboard", + "downloads": 0, + "full_name": "shutupflanders/sensor.moneydashboard", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "6298052", + "last_release_tag": "1.0.3", + "last_updated": "2020-02-20T10:04:03Z", + "name": "moneydashboard", + "new": false, + "repository_manifest": { + "domains": [ + "sensor" + ], + "name": "MoneyDashboard", + "render_readme": true, + "country": "GB", + "homeassistant": "0.99.0" + }, + "selected_tag": null, + "show_beta": false, + "stars": 1, + "topics": [], + "version_installed": null + }, + "267433712": { + "authors": [ + "@slesinger" + ], + "category": "integration", + "description": "Home Assistant integration to display info about energy plan", + "domain": "predistribuce", + "downloads": 0, + "full_name": "slesinger/HomeAssistant-PREdistribuce", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "7c9cbaf", + "last_release_tag": null, + "last_updated": "2020-06-04T20:34:09Z", + "name": "predistribuce", + "new": false, + "repository_manifest": { + "name": "PRE Distribuce CZ", + "render_readme": false, + "domains": [ + "binary_sensor" + ], + "country": "CZ", + "homeassistant": "0.110.0" + }, + "selected_tag": null, + "show_beta": false, + "stars": 1, + "topics": [ + "energy", + "home-assistant", + "home-automation", + "power", + "sensor" + ], + "version_installed": null + }, + "209955487": { + "authors": [ + "@slesinger" + ], + "category": "integration", + "description": "Control BMR heating regulation system from Home Assistant", + "domain": "bmr_hc64", + "downloads": 0, + "full_name": "slesinger/HomeAssistant-BMR", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "02a4b9b", + "last_release_tag": null, + "last_updated": "2020-11-19T18:01:39Z", + "name": "bmr_hc64", + "new": false, + "repository_manifest": { + "name": "BMR", + "render_readme": true, + "domains": [ + "climate", + "sensor", + "binary_sensor", + "switch" + ], + "country": "CZ", + "homeassistant": "0.110.0" + }, + "selected_tag": null, + "show_beta": false, + "stars": 0, + "topics": [], + "version_installed": null + }, + "246417951": { + "authors": [ + "@SLG" + ], + "category": "integration", + "description": "This component retrieves the statistics from Whatpulse", + "domain": "whatpulse", + "downloads": 0, + "full_name": "SLG/home-assistant-whatpulse", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "6a26678", + "last_release_tag": null, + "last_updated": "2020-03-11T18:18:41Z", + "name": "whatpulse", + "new": false, + "repository_manifest": { + "name": "Whatpulse Sensor", + "render_readme": true, + "domains": [ + "sensor" + ] + }, + "selected_tag": null, + "show_beta": false, + "stars": 0, + "topics": [ + "custom-components", + "hacs", + "home-assistant", + "homeassistant", + "python", + "whatpulse" + ], + "version_installed": null + }, + "199332790": { + "authors": [ + "@snarky-snark" + ], + "category": "integration", + "description": "A custom Home Assistant component for declaring and setting generic variable entities dynamically.", + "domain": "var", + "downloads": 0, + "full_name": "snarky-snark/home-assistant-variables", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "e4cd147", + "last_release_tag": "v0.9.3", + "last_updated": "2020-04-08T14:50:52Z", + "name": "var", + "new": false, + "repository_manifest": { + "name": "Variable", + "render_readme": true, + "homeassistant": "0.103.0b1" + }, + "selected_tag": null, + "show_beta": false, + "stars": 64, + "topics": [], + "version_installed": null + }, + "210966517": { + "authors": [ + "snicker" + ], + "category": "integration", + "description": "Zwift Sensor Integration for HomeAssistant", + "domain": "zwift", + "downloads": 0, + "full_name": "snicker/zwift_hass", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "e3be36b", + "last_release_tag": "v3.2", + "last_updated": "2020-07-09T21:49:45Z", + "name": "zwift", + "new": false, + "repository_manifest": { + "name": "Zwift Sensors", + "domains": [ + "sensor" + ] + }, + "selected_tag": null, + "show_beta": false, + "stars": 12, + "topics": [], + "version_installed": null + }, + "260410453": { + "authors": [ + "sockless-coding" + ], + "category": "integration", + "description": "Panasonic Comfort Cloud - Home Assistant Component", + "domain": "panasonic_cc", + "downloads": 0, + "full_name": "sockless-coding/panasonic_cc", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "7e322ef", + "last_release_tag": "v1.0.20", + "last_updated": "2020-11-04T19:20:04Z", + "name": "panasonic_cc", + "new": false, + "repository_manifest": { + "name": "Panasonic Comfort Cloud" + }, + "selected_tag": null, + "show_beta": false, + "stars": 8, + "topics": [], + "version_installed": null + }, + "261849832": { + "authors": [ + "sockless-coding" + ], + "category": "integration", + "description": "Garo wallbox - Home Assistant Component ", + "domain": "garo_wallbox", + "downloads": 0, + "full_name": "sockless-coding/garo_wallbox", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "6a89947", + "last_release_tag": "v1.0.3", + "last_updated": "2020-10-11T11:15:58Z", + "name": "garo_wallbox", + "new": false, + "repository_manifest": { + "name": "Garo Wallbox" + }, + "selected_tag": null, + "show_beta": false, + "stars": 2, + "topics": [], + "version_installed": null + }, + "289579468": { + "authors": [ + "@swartjean" + ], + "category": "integration", + "description": "Fetches loadshedding data from Eskom", + "domain": "eskom_loadshedding", + "downloads": 0, + "full_name": "swartjean/ha-eskom-loadshedding", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "67c7989", + "last_release_tag": "v1.0.2", + "last_updated": "2020-09-14T17:08:17Z", + "name": "eskom_loadshedding", + "new": false, + "repository_manifest": { + "name": "Eskom Loadshedding Interface", + "domains": [ + "sensor" + ], + "iot_class": "Cloud Polling" + }, + "selected_tag": null, + "show_beta": false, + "stars": 8, + "topics": [ + "custom-component", + "eskom", + "hacs", + "home-assistant", + "home-automation", + "homeassistant", + "integration", + "loadshedding", + "south-africa" + ], + "version_installed": null + }, + "292616002": { + "authors": [ + "@swartjean" + ], + "category": "integration", + "description": "Home Assistant - Seedboxes.cc Integration", + "domain": "seedboxes_cc", + "downloads": 0, + "full_name": "swartjean/ha-seedboxes-cc", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "63cef48", + "last_release_tag": "v1.0.1", + "last_updated": "2020-09-29T07:27:03Z", + "name": "seedboxes_cc", + "new": false, + "repository_manifest": { + "name": "Seedboxes.cc", + "domains": [ + "sensor" + ], + "iot_class": "Cloud Polling" + }, + "selected_tag": null, + "show_beta": false, + "stars": 1, + "topics": [ + "custom-component", + "hacs", + "home-assistant", + "home-automation", + "homeassistant", + "integration", + "monitoring", + "seedbox", + "torrents" + ], + "version_installed": null + }, + "177978011": { + "authors": [ + "@hakana", + "@StyraHem" + ], + "category": "integration", + "description": "Shelly smart home platform for Home Assistant", + "domain": "shelly", + "downloads": 3125, + "full_name": "StyraHem/ShellyForHASS", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "cd6b4b5", + "last_release_tag": "0.2.1", + "last_updated": "2020-12-07T08:03:41Z", + "name": "shelly", + "new": false, + "repository_manifest": { + "name": "ShellyForHass (Shelly integration)", + "hide_default_branch": true, + "zip_release": true, + "filename": "shelly4hass.zip", + "homeassistant": "0.104.0" + }, + "selected_tag": null, + "show_beta": false, + "stars": 407, + "topics": [], + "version_installed": null + }, + "164489685": { + "authors": [ + "@syssi" + ], + "category": "integration", + "description": "Nextbike integration for Home Assistant", + "domain": "nextbike", + "downloads": 0, + "full_name": "syssi/nextbike", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "a9f1e04", + "last_release_tag": "1.0.0", + "last_updated": "2020-11-25T19:40:01Z", + "name": "nextbike", + "new": false, + "repository_manifest": { + "name": "Nextbike Integration", + "content_in_root": false, + "render_readme": true, + "iot_class": "local_polling" + }, + "selected_tag": null, + "show_beta": false, + "stars": 1, + "topics": [ + "custom-component", + "free-floating", + "home-assistant", + "nextbike" + ], + "version_installed": null + }, + "121934877": { + "authors": [ + "@syssi" + ], + "category": "integration", + "description": "Xiaomi Mi and Aqara Air Conditioning Companion integration for Home Assistant", + "domain": "xiaomi_miio_airconditioningcompanion", + "downloads": 0, + "full_name": "syssi/xiaomi_airconditioningcompanion", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "7a61fae", + "last_release_tag": "0.2.0", + "last_updated": "2020-11-01T10:23:37Z", + "name": "xiaomi_miio_airconditioningcompanion", + "new": false, + "repository_manifest": { + "name": "Xiaomi Mi and Aqara Air Conditioning Companion Integration", + "content_in_root": false, + "render_readme": true, + "iot_class": "local_polling" + }, + "selected_tag": null, + "show_beta": false, + "stars": 285, + "topics": [ + "acpartner", + "airconditioning", + "aqara", + "custom-component", + "home-assistant", + "infrared", + "xiaomi" + ], + "version_installed": null + }, + "101482973": { + "authors": [ + "@syssi" + ], + "category": "integration", + "description": "Xiaomi Mi Air Purifier and Xiaomi Mi Air Humidifier integration for Home Assistant", + "domain": "xiaomi_miio_airpurifier", + "downloads": 0, + "full_name": "syssi/xiaomi_airpurifier", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "48380ab", + "last_release_tag": "0.6.2", + "last_updated": "2020-11-27T06:38:47Z", + "name": "xiaomi_miio_airpurifier", + "new": false, + "repository_manifest": { + "name": "Xiaomi Mi Air Purifier, Air Humidifier, Air Fresh and Pedestal Fan Integration", + "content_in_root": false, + "render_readme": true, + "iot_class": "local_polling" + }, + "selected_tag": null, + "show_beta": false, + "stars": 165, + "topics": [ + "airfresh", + "airhumidifier", + "airpurifier", + "custom-component", + "fan", + "home-assistant", + "miio", + "miio-protocol", + "miot", + "xiaomi" + ], + "version_installed": null + }, + "129353521": { + "authors": [ + "@syssi" + ], + "category": "integration", + "description": "Custom component for Home Assistant to faciliate the reverse engeneering of Xiaomi MiIO devices", + "domain": "xiaomi_miio_raw", + "downloads": 0, + "full_name": "syssi/xiaomi_raw", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "9ba82ba", + "last_release_tag": "1.3.0", + "last_updated": "2020-11-30T08:11:15Z", + "name": "xiaomi_miio_raw", + "new": false, + "repository_manifest": { + "name": "Xiaomi MiIO Raw", + "content_in_root": false, + "render_readme": true, + "iot_class": "local_polling" + }, + "selected_tag": null, + "show_beta": false, + "stars": 33, + "topics": [ + "custom-component", + "home-assistant", + "miio", + "miio-device", + "miio-protocol", + "monitoring", + "sensor" + ], + "version_installed": null + }, + "97201395": { + "authors": [ + "@syssi" + ], + "category": "integration", + "description": "Xiaomi Mi Smart WiFi Socket integration for Home Assistant", + "domain": "xiaomi_miio_plug", + "downloads": 0, + "full_name": "syssi/xiaomiplug", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "f77641e", + "last_release_tag": "1.4.0", + "last_updated": "2020-10-28T09:43:02Z", + "name": "xiaomi_miio_plug", + "new": false, + "repository_manifest": { + "name": "Xiaomi Mi Smart WiFi Socket Integration", + "content_in_root": false, + "render_readme": true, + "iot_class": "local_polling" + }, + "selected_tag": null, + "show_beta": false, + "stars": 70, + "topics": [ + "custom-component", + "home-assistant", + "miio", + "miio-device", + "miio-protocol", + "switch", + "xiaomi" + ], + "version_installed": null + }, + "181743867": { + "authors": [ + "@tefinger" + ], + "category": "integration", + "description": "Custom component for Home Assistant to support Brematic devices", + "domain": "brematic", + "downloads": 0, + "full_name": "tefinger/hass-brematic", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "4ddd7f3", + "last_release_tag": "v0.2.1", + "last_updated": "2020-10-30T20:39:45Z", + "name": "brematic", + "new": false, + "repository_manifest": { + "name": "Brematic", + "homeassistant": "0.110", + "render_readme": true, + "domains": [ + "switch" + ] + }, + "selected_tag": null, + "show_beta": false, + "stars": 4, + "topics": [ + "433mhz", + "brematic", + "brennenstuhl", + "custom-component", + "gateway", + "hacktoberfest", + "hass", + "home-assistant", + "intertechno" + ], + "version_installed": null + }, + "259591506": { + "authors": [ + "@tellerbop" + ], + "category": "integration", + "description": "Custom Vista Pool Integration for Home Assistant", + "domain": "vistapool", + "downloads": 0, + "full_name": "tellerbop/havistapool", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "c1b91df", + "last_release_tag": "v1.2", + "last_updated": "2020-05-27T18:37:00Z", + "name": "vistapool", + "new": false, + "repository_manifest": { + "name": "Vista Pool", + "domains": [ + "sensor", + "binary_sensor", + "switch" + ], + "homeassistant": "0.110.0" + }, + "selected_tag": null, + "show_beta": false, + "stars": 3, + "topics": [ + "hacs", + "hayward", + "homeassistant", + "integration", + "sensors", + "vista-pool", + "vistapool" + ], + "version_installed": null + }, + "307974458": { + "authors": [ + "@iesus" + ], + "category": "integration", + "description": "Get departure times for swedish public transportation", + "domain": "resrobot", + "downloads": 0, + "full_name": "TekniskSupport/home-assistant-resrobot", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "dabc35d", + "last_release_tag": "0.0.4", + "last_updated": "2020-11-22T08:18:02Z", + "name": "resrobot", + "new": false, + "repository_manifest": { + "name": "ResRobot", + "country": "SE", + "domains": [ + "sensor" + ], + "homeassistant": "0.117.0" + }, + "selected_tag": null, + "show_beta": false, + "stars": 5, + "topics": [ + "bus", + "ferry", + "hacs", + "homeassistant", + "iesus", + "integration", + "public", + "sweden", + "train", + "tram", + "transportation" + ], + "version_installed": null + }, + "265059207": { + "authors": [ + "@ThermIQ" + ], + "category": "integration", + "description": "Home Assistant integration of ThermIQ-MQTT, providing control and logging of Thermia heatpumps ", + "domain": "thermiq_mqtt", + "downloads": 0, + "full_name": "ThermIQ/thermiq_mqtt-ha", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "344ab4d", + "last_release_tag": null, + "last_updated": "2020-11-19T17:16:12Z", + "name": "thermiq_mqtt", + "new": false, + "repository_manifest": { + "name": "ThermIQ MQTT", + "hacs": "0.24.0", + "domains": [ + "binary_sensor", + "sensor", + "switch" + ], + "iot_class": "Local Push", + "homeassistant": "0.100.0", + "render_readme": true + }, + "selected_tag": null, + "show_beta": false, + "stars": 3, + "topics": [ + "bergvarme", + "danfoss", + "dhp", + "diplomat", + "g2", + "g3", + "ha", + "hacs", + "hass", + "heatpump", + "home-assistant", + "home-automation", + "homeassistant", + "optimum", + "thermal-pump", + "thermia", + "thermiq", + "thermiq-mqtt", + "varmepump" + ], + "version_installed": null + }, + "121891488": { + "authors": [ + "@thevoltagesource" + ], + "category": "integration", + "description": "Python code for interacting with the Lennox iComfort thermostat", + "domain": "myicomfort", + "downloads": 0, + "full_name": "thevoltagesource/LennoxiComfort", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "9f3195f", + "last_release_tag": "v3.0", + "last_updated": "2020-06-30T14:36:14Z", + "name": "myicomfort", + "new": false, + "repository_manifest": { + "name": "Lennox iComfort WiFi Thermostat Integration", + "domains": [ + "climate" + ], + "homeassistant": "0.96.0" + }, + "selected_tag": null, + "show_beta": false, + "stars": 19, + "topics": [], + "version_installed": null + }, + "202220932": { + "authors": [], + "category": "integration", + "description": "\ud83d\udd39 Change the favicon of your Home Assistant instance", + "domain": "favicon", + "downloads": 0, + "full_name": "thomasloven/hass-favicon", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "70b281f", + "last_release_tag": "10.2", + "last_updated": "2020-11-20T11:16:23Z", + "name": "favicon", + "new": false, + "repository_manifest": {}, + "selected_tag": null, + "show_beta": false, + "stars": 35, + "topics": [], + "version_installed": null + }, + "200897141": { + "authors": [], + "category": "integration", + "description": "\ud83d\udd39 Improve the lovelace yaml parser for Home Assistant", + "domain": "lovelace_gen", + "downloads": 0, + "full_name": "thomasloven/hass-lovelace_gen", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "14fd723", + "last_release_tag": "6", + "last_updated": "2020-02-26T13:18:57Z", + "name": "lovelace_gen", + "new": false, + "repository_manifest": {}, + "selected_tag": null, + "show_beta": false, + "stars": 71, + "topics": [], + "version_installed": null + }, + "157618389": { + "authors": [], + "category": "integration", + "description": "\ud83d\udd39 Use icons from fontawesome in home-assistant", + "domain": "fontawesome", + "downloads": 0, + "full_name": "thomasloven/hass-fontawesome", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "73e51df", + "last_release_tag": "1.2.0", + "last_updated": "2020-08-01T04:02:38Z", + "name": "fontawesome", + "new": false, + "repository_manifest": { + "name": "fontawesome", + "render_readme": true + }, + "selected_tag": null, + "show_beta": false, + "stars": 105, + "topics": [], + "version_installed": null + }, + "194140521": { + "authors": [], + "category": "integration", + "description": "\ud83d\udd39 A Home Assistant integration to turn your browser into a controllable entity - and also an audio player", + "domain": "browser_mod", + "downloads": 0, + "full_name": "thomasloven/hass-browser_mod", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "37e731b", + "last_release_tag": "1.2.3", + "last_updated": "2020-11-21T15:24:31Z", + "name": "browser_mod", + "new": false, + "repository_manifest": { + "name": "browser_mod", + "homeassistant": "0.113.0" + }, + "selected_tag": null, + "show_beta": false, + "stars": 308, + "topics": [], + "version_installed": null + }, + "287318591": { + "authors": [ + "@Thomas55555" + ], + "category": "integration", + "description": "Custom component to monitor and control your Husqvrana Automower", + "domain": "husqvarna_automower", + "downloads": 0, + "full_name": "Thomas55555/husqvarna_automower", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "52549f8", + "last_release_tag": "v0.6.1", + "last_updated": "2020-11-22T19:45:00Z", + "name": "husqvarna_automower", + "new": false, + "repository_manifest": { + "name": "Husqvarna Automower", + "domains": [ + "vacuum" + ], + "homeassistant": "0.113.0", + "iot_class": [ + "Cloud Polling" + ], + "render_readme": true + }, + "selected_tag": null, + "show_beta": false, + "stars": 3, + "topics": [ + "hass", + "homeassistant", + "husqvarna-automower" + ], + "version_installed": null + }, + "251020820": { + "authors": [ + "@Tikismoke" + ], + "category": "integration", + "description": "NESPRESSO ble Home Assistant custom componenets and also a 2MQTT script", + "domain": "nespresso", + "downloads": 0, + "full_name": "tikismoke/home-assistant-nespressoble", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "19b068e", + "last_release_tag": "v0.0.1", + "last_updated": "2020-07-14T12:56:27Z", + "name": "nespresso", + "new": false, + "repository_manifest": { + "name": "Nespresso Ble coffee machine" + }, + "selected_tag": null, + "show_beta": false, + "stars": 18, + "topics": [ + "homeassistant", + "homeassistant-components", + "nespresso", + "nespresso-ble" + ], + "version_installed": null + }, + "164155243": { + "authors": [ + "@TimSoethout" + ], + "category": "integration", + "description": "Sensor for Home Assistant pulling data from the Goodwe SEMS API for solar panel production metrics.", + "domain": "sems", + "downloads": 0, + "full_name": "TimSoethout/goodwe-sems-home-assistant", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "5f9a2d3", + "last_release_tag": "1.0.0", + "last_updated": "2020-09-12T20:09:41Z", + "name": "sems", + "new": false, + "repository_manifest": {}, + "selected_tag": null, + "show_beta": false, + "stars": 20, + "topics": [], + "version_installed": null + }, + "261496794": { + "authors": [ + "@Tikismoke" + ], + "category": "integration", + "description": "a plcbus custom somponents for HomeAssistant", + "domain": "plcbus", + "downloads": 0, + "full_name": "tikismoke/home-assistant-plcbus", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "b6909bd", + "last_release_tag": "0.0.2", + "last_updated": "2020-07-14T13:41:50Z", + "name": "plcbus", + "new": false, + "repository_manifest": { + "name": "Plcbus integration" + }, + "selected_tag": null, + "show_beta": false, + "stars": 0, + "topics": [ + "customcomponents", + "hass", + "homeassistant", + "plcbus" + ], + "version_installed": null + }, + "195620540": { + "authors": [ + "@timvancann" + ], + "category": "integration", + "description": null, + "domain": "growatt", + "downloads": 0, + "full_name": "timvancann/homeassistant-growatt", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "b22c18f", + "last_release_tag": "0.0.3", + "last_updated": "2019-09-08T13:09:10Z", + "name": "growatt", + "new": false, + "repository_manifest": { + "name": "Growatt solar panels" + }, + "selected_tag": null, + "show_beta": false, + "stars": 1, + "topics": [], + "version_installed": null + }, + "200399989": { + "authors": [], + "category": "integration", + "description": "Service to clean up your home assistant snapshots, so you don't manually have to.", + "domain": "clean_up_snapshots_service", + "downloads": 0, + "full_name": "tmonck/clean_up_snapshots", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "be59630", + "last_release_tag": "0.6", + "last_updated": "2020-12-08T13:15:09Z", + "name": "clean_up_snapshots_service", + "new": false, + "repository_manifest": { + "name": "Clean up snapshots service", + "domains": [ + "clean_up_snapshots_service" + ], + "render_readme": true + }, + "selected_tag": null, + "show_beta": false, + "stars": 7, + "topics": [], + "version_installed": null + }, + "207794499": { + "authors": [], + "category": "integration", + "description": "A BER Status Sensor", + "domain": "ber_status", + "downloads": 0, + "full_name": "tmechen/ber_status", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "d237174", + "last_release_tag": null, + "last_updated": "2020-10-31T13:57:32Z", + "name": "ber_status", + "new": false, + "repository_manifest": { + "name": "BER Status Sensor", + "country": "DE", + "domains": [ + "sensor" + ] + }, + "selected_tag": null, + "show_beta": false, + "stars": 6, + "topics": [], + "version_installed": null + }, + "225248441": { + "authors": [ + "@tomaae" + ], + "category": "integration", + "description": "Mikrotik router integration for Home Assistant", + "domain": "mikrotik_router", + "downloads": 1644, + "full_name": "tomaae/homeassistant-mikrotik_router", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "5e7ae94", + "last_release_tag": "v1.5.10", + "last_updated": "2020-12-03T15:41:29Z", + "name": "mikrotik_router", + "new": false, + "repository_manifest": { + "name": "Mikrotik Router", + "homeassistant": "0.110.0", + "iot_class": "local_poll", + "domains": [ + "device_tracker", + "switch", + "sensor", + "binary_sensor" + ], + "render_readme": false, + "zip_release": true, + "filename": "mikrotik_router.zip" + }, + "selected_tag": null, + "show_beta": false, + "stars": 39, + "topics": [ + "hacs", + "homeassistant", + "homeassistant-components", + "homeassistant-integration", + "mikrotik" + ], + "version_installed": null + }, + "257275420": { + "authors": [ + "@tomaae" + ], + "category": "integration", + "description": "OpenMediaVault integration for Home Assistant", + "domain": "openmediavault", + "downloads": 1393, + "full_name": "tomaae/homeassistant-openmediavault", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "2c2226c", + "last_release_tag": "v1.1.5", + "last_updated": "2020-06-21T13:25:35Z", + "name": "openmediavault", + "new": false, + "repository_manifest": { + "name": "OpenMediaVault", + "homeassistant": "0.110.0", + "iot_class": "local_poll", + "domains": [ + "sensor", + "switch" + ], + "render_readme": false, + "zip_release": true, + "filename": "openmediavault.zip" + }, + "selected_tag": null, + "show_beta": false, + "stars": 20, + "topics": [ + "hacs", + "homeassistant", + "homeassistant-components", + "homeassistant-integration", + "omv", + "openmediavault" + ], + "version_installed": null + }, + "277201070": { + "authors": [ + "@toreamun" + ], + "category": "integration", + "description": "Integrate HAN-port attached Aidon, Kaifa and Kamstrum meters used in Norway into Home Assistant", + "domain": "amshan", + "downloads": 0, + "full_name": "toreamun/amshan-homeassistant", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "5ab4d7e", + "last_release_tag": "0.2", + "last_updated": "2020-10-22T19:33:46Z", + "name": "amshan", + "new": false, + "repository_manifest": { + "name": "AmsHan", + "domains": [ + "sensor" + ], + "iot_class": "Local Push", + "homeassistant": "0.110.0", + "country": "NO", + "render_readme": true + }, + "selected_tag": null, + "show_beta": false, + "stars": 4, + "topics": [ + "aidon", + "ams", + "han", + "kaifa", + "kamstrup", + "smart-meter" + ], + "version_installed": null + }, + "299875200": { + "authors": [ + "@toreamun" + ], + "category": "integration", + "description": "Home Assistant integration for Victor Smart-Kill WI-FI electronic mouse and rat traps from VictorPest.com.", + "domain": "victorsmartkill", + "downloads": 0, + "full_name": "toreamun/victorsmartkill-homeassistant", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "b71bb67", + "last_release_tag": "0.3.2", + "last_updated": "2020-11-08T22:16:43Z", + "name": "victorsmartkill", + "new": false, + "repository_manifest": { + "name": "Victor Smart-Kill", + "hacs": "0.24.0", + "domains": [ + "binary_sensor", + "sensor" + ], + "iot_class": "Cloud Polling", + "homeassistant": "0.115.0", + "render_readme": true + }, + "selected_tag": null, + "show_beta": false, + "stars": 3, + "topics": [ + "hacs", + "home-assistant", + "homeassistant-integration", + "mouse", + "python-library", + "rat", + "trap", + "victor" + ], + "version_installed": null + }, + "298816063": { + "authors": [ + "@troykelly" + ], + "category": "integration", + "description": "Trackimo Integration for HACS Home Assistant", + "domain": "trackimo", + "downloads": 0, + "full_name": "troykelly/hacs-trackimo", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "945c367", + "last_release_tag": "v0.0.20", + "last_updated": "2020-10-08T02:51:55Z", + "name": "trackimo", + "new": false, + "repository_manifest": { + "name": "Trackimo Device Tracker", + "country": "AU", + "domains": [ + "device_tracker" + ], + "homeassistant": "0.115.3", + "iot_class": [ + "Cloud Polling" + ] + }, + "selected_tag": null, + "show_beta": false, + "stars": 0, + "topics": [ + "geolocation", + "hacktoberfest", + "homeassistant", + "homeassistant-integration", + "trackimo" + ], + "version_installed": null + }, + "301916553": { + "authors": [ + "@troykelly" + ], + "category": "integration", + "description": "Unofficial Amber Electric integration for Home Assistant", + "domain": "amber_electric", + "downloads": 0, + "full_name": "troykelly/hacs-amberelectric", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "8543e9e", + "last_release_tag": "v0.0.12", + "last_updated": "2020-10-20T07:28:06Z", + "name": "amber_electric", + "new": false, + "repository_manifest": { + "name": "Amber Electric", + "country": "AU", + "domains": [ + "sensor" + ], + "homeassistant": "0.116.0", + "iot_class": [ + "Cloud Polling" + ] + }, + "selected_tag": null, + "show_beta": false, + "stars": 1, + "topics": [ + "amber-electric", + "electricity-market", + "electricity-pricing", + "hacktoberfest", + "home-assistant", + "home-automation" + ], + "version_installed": null + }, + "272337216": { + "authors": [ + "@turbulator" + ], + "category": "integration", + "description": "Home Assistant custom component for Pandora Car Alarm System", + "domain": "pandora_cas", + "downloads": 0, + "full_name": "turbulator/pandora-cas", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "11c7bd7", + "last_release_tag": "1.2", + "last_updated": "2020-11-02T16:21:32Z", + "name": "pandora_cas", + "new": false, + "repository_manifest": { + "name": "Pandora Car Alarm System", + "domains": [ + "device_tracker", + "binary_sensor", + "sensor" + ], + "country": [ + "RU", + "BY" + ], + "iot_class": "local push", + "render_readme": true + }, + "selected_tag": null, + "show_beta": false, + "stars": 12, + "topics": [ + "hacs", + "home-assistant", + "integration", + "pandora" + ], + "version_installed": null + }, + "240459262": { + "authors": [ + "@tuxuser" + ], + "category": "integration", + "description": "Abfall API (Jumomind) custom component for home assistant - Get an alert when garbage collection is due", + "domain": "abfallapi_jumomind", + "downloads": 0, + "full_name": "tuxuser/abfallapi_jumomind_ha", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "5e4c792", + "last_release_tag": null, + "last_updated": "2020-02-24T16:35:36Z", + "name": "abfallapi_jumomind", + "new": false, + "repository_manifest": { + "name": "Abfall API (Jumomind)", + "country": "DE", + "homeassistant": "0.104.3", + "render_readme": true + }, + "selected_tag": null, + "show_beta": false, + "stars": 0, + "topics": [ + "abfall", + "collection", + "deutschland", + "garbage", + "germany", + "hacs", + "home-assistant", + "homeassistant-sensor", + "jumomind", + "muell", + "sensor", + "waste" + ], + "version_installed": null + }, + "220313935": { + "authors": [ + "@turbokongen" + ], + "category": "integration", + "description": "Custom component reading AMS through MBus adapter into HomeAssistant", + "domain": "ams", + "downloads": 0, + "full_name": "turbokongen/hass-AMS", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "ee35a7a", + "last_release_tag": "v1.13", + "last_updated": "2020-10-04T17:18:10Z", + "name": "ams", + "new": false, + "repository_manifest": { + "name": "hass-AMS", + "country": "NO", + "domains": [ + "sensor" + ], + "render_readme": true + }, + "selected_tag": null, + "show_beta": false, + "stars": 15, + "topics": [ + "mbus-adapter", + "meter", + "sensors" + ], + "version_installed": null + }, + "167885769": { + "authors": [ + "@twrecked" + ], + "category": "integration", + "description": "Asynchronous Arlo Component for Home Assistant", + "domain": "aarlo", + "downloads": 0, + "full_name": "twrecked/hass-aarlo", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "7db3244", + "last_release_tag": "v0.7.0.3", + "last_updated": "2020-12-08T14:19:28Z", + "name": "aarlo", + "new": false, + "repository_manifest": { + "name": "Arlo Camera Support", + "homeassistant": "0.110.0" + }, + "selected_tag": null, + "show_beta": false, + "stars": 142, + "topics": [ + "arlo", + "homeassistant", + "homeassistant-components", + "netgear" + ], + "version_installed": null + }, + "237628853": { + "authors": [ + "@tuxuser" + ], + "category": "integration", + "description": "Abfall API (RegioIT) custom component for home assistant - Get an alert when garbage collection is due", + "domain": "abfallapi_regioit", + "downloads": 0, + "full_name": "tuxuser/abfallapi_regioit_ha", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "8675207", + "last_release_tag": null, + "last_updated": "2020-04-13T23:35:37Z", + "name": "abfallapi_regioit", + "new": false, + "repository_manifest": { + "name": "Abfall API (RegioIT)", + "country": "DE", + "homeassistant": "0.104.3", + "render_readme": true + }, + "selected_tag": null, + "show_beta": false, + "stars": 7, + "topics": [ + "collection", + "component", + "garbage", + "homeassistant", + "homeassistant-sensor", + "muell", + "muellabfuhr", + "regioit", + "sensor", + "smart-home", + "waste" + ], + "version_installed": null + }, + "204700563": { + "authors": [ + "@sherrell" + ], + "category": "integration", + "description": "Momentary Switch Component for Home Assistant", + "domain": "momentary", + "downloads": 0, + "full_name": "twrecked/hass-momentary", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "0ab21ab", + "last_release_tag": "v0.5", + "last_updated": "2020-05-26T01:50:28Z", + "name": "momentary", + "new": false, + "repository_manifest": { + "name": "Momentary Switch Component" + }, + "selected_tag": null, + "show_beta": false, + "stars": 9, + "topics": [], + "version_installed": null + }, + "245267534": { + "authors": [ + "@twrecked" + ], + "category": "integration", + "description": "Virtual Components for Home Assistant", + "domain": "virtual", + "downloads": 0, + "full_name": "twrecked/hass-virtual", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "8492f20", + "last_release_tag": "v0.5", + "last_updated": "2020-05-26T01:49:36Z", + "name": "virtual", + "new": false, + "repository_manifest": { + "name": "Virtual Components" + }, + "selected_tag": null, + "show_beta": false, + "stars": 16, + "topics": [], + "version_installed": null + }, + "76125161": { + "authors": [ + "@tybritten" + ], + "category": "integration", + "description": null, + "domain": "ical", + "downloads": 0, + "full_name": "tybritten/ical-sensor-homeassistant", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "ff5ec88", + "last_release_tag": "0.6", + "last_updated": "2019-09-20T12:39:50Z", + "name": "ical", + "new": false, + "repository_manifest": {}, + "selected_tag": null, + "show_beta": false, + "stars": 20, + "topics": [], + "version_installed": null + }, + "272140589": { + "authors": [ + "@ualex73" + ], + "category": "integration", + "description": "Monitor Docker containers from Home Assistant", + "domain": "monitor_docker", + "downloads": 0, + "full_name": "ualex73/monitor_docker", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "0b4c5d1", + "last_release_tag": "1.5", + "last_updated": "2020-10-26T11:02:48Z", + "name": "monitor_docker", + "new": false, + "repository_manifest": { + "name": "Monitor Docker", + "render_readme": false, + "domains": [ + "sensor", + "switch" + ] + }, + "selected_tag": null, + "show_beta": false, + "stars": 53, + "topics": [ + "custom-component", + "docker", + "hacs", + "home-assistant", + "homeassistant", + "python" + ], + "version_installed": null + }, + "306666927": { + "authors": [ + "@unsigus" + ], + "category": "integration", + "description": "Custom Home Assistant integration for HomeWizard Energy", + "domain": "homewizard_energy", + "downloads": 0, + "full_name": "Unsigus/hass-homewizard-energy", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "a96ba92", + "last_release_tag": "0.4.1", + "last_updated": "2020-11-26T10:38:28Z", + "name": "homewizard_energy", + "new": false, + "repository_manifest": { + "name": "HomeWizard Energy", + "render_readme": true, + "domains": [ + "sensor" + ], + "homeassistant": "0.116.0", + "iot_class": "Local Polling" + }, + "selected_tag": null, + "show_beta": false, + "stars": 3, + "topics": [ + "custom-integration", + "dsmr", + "home-assistant", + "homewizard", + "homewizard-energy", + "p1" + ], + "version_installed": null + }, + "265916869": { + "authors": [ + "@vanstinator" + ], + "category": "integration", + "description": "Melnor Raincloud Home Assistant Integration", + "domain": "raincloud", + "downloads": 49, + "full_name": "vanstinator/hass-raincloud", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "87859c4", + "last_release_tag": "1.2.1", + "last_updated": "2020-09-17T17:32:43Z", + "name": "raincloud", + "new": false, + "repository_manifest": { + "name": "Melnor Raincloud", + "render_readme": true, + "domains": [ + "binary_sensor", + "sensor", + "switch" + ], + "homeassistant": "0.115.0", + "zip_release": true, + "filename": "raincloud.zip", + "iot_class": "cloud_polling" + }, + "selected_tag": null, + "show_beta": false, + "stars": 5, + "topics": [ + "assistant", + "home", + "home-assistant", + "home-automation", + "irrigation", + "melnor", + "raincloud", + "sprinkler" + ], + "version_installed": null + }, + "247566230": { + "authors": [ + "@Verbalinsurection" + ], + "category": "integration", + "description": "The Next Rocket Launch sensor platform allows you to monitor the next rocket launch from Teamup.", + "domain": "next_rocket_launch", + "downloads": 0, + "full_name": "Verbalinsurection/next_rocket_launch", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "6ce68bd", + "last_release_tag": "V1.0.2", + "last_updated": "2020-05-03T00:45:46Z", + "name": "next_rocket_launch", + "new": false, + "repository_manifest": { + "name": "Next Rocket Launch", + "country": "FR", + "render_readme": true + }, + "selected_tag": null, + "show_beta": false, + "stars": 6, + "topics": [ + "hassio", + "home-assistant", + "homeassistant", + "homeassistant-components", + "rocket" + ], + "version_installed": null + }, + "258012818": { + "authors": [], + "category": "integration", + "description": "Use Simple Icons in Home Assistant", + "domain": "simpleicons", + "downloads": 0, + "full_name": "vigonotion/hass-simpleicons", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "70b9a15", + "last_release_tag": "v1.8.0", + "last_updated": "2020-12-07T05:22:07Z", + "name": "simpleicons", + "new": false, + "repository_manifest": { + "name": "simpleicons", + "render_readme": true, + "homeassistant": "0.110.0b0" + }, + "selected_tag": null, + "show_beta": false, + "stars": 33, + "topics": [ + "hacktoberfest", + "home-assistant", + "simple-icons" + ], + "version_installed": null + }, + "190418598": { + "authors": [ + "@walthowd" + ], + "category": "integration", + "description": "Automower Custom Component for Home Assistant", + "domain": "automower", + "downloads": 0, + "full_name": "walthowd/ha-automower", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "05ded8e", + "last_release_tag": "v0.5", + "last_updated": "2020-07-27T16:01:01Z", + "name": "automower", + "new": false, + "repository_manifest": {}, + "selected_tag": null, + "show_beta": false, + "stars": 22, + "topics": [], + "version_installed": null + }, + "120696364": { + "authors": [ + "@vinteo" + ], + "category": "integration", + "description": "OpenSprinkler Integration for Home Assistant", + "domain": "opensprinkler", + "downloads": 0, + "full_name": "vinteo/hass-opensprinkler", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "abdbd8e", + "last_release_tag": "v1.1.3", + "last_updated": "2020-10-16T23:29:26Z", + "name": "opensprinkler", + "new": false, + "repository_manifest": { + "name": "OpenSprinkler integration for Home Assistant", + "domains": [ + "binary_sensor", + "sensor", + "switch" + ], + "render_readme": true, + "iot_class": "Local Polling" + }, + "selected_tag": null, + "show_beta": false, + "stars": 68, + "topics": [ + "hacktoberfest", + "hacs", + "home-assistant", + "home-automation", + "opensprinkler" + ], + "version_installed": null + }, + "234875951": { + "authors": [ + "@nwiborg", + "@vlumikero" + ], + "category": "integration", + "description": "A Home Assistant custom component for Securitas Home Alarm, for alarms bought in Sweden before 2018-12-01", + "domain": "securitas", + "downloads": 0, + "full_name": "vlumikero/home-assistant-securitas", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "98f09ba", + "last_release_tag": null, + "last_updated": "2020-05-20T20:32:12Z", + "name": "securitas", + "new": false, + "repository_manifest": { + "name": "Securitas Home", + "render_readme": "true", + "country": "SE", + "homeassistant": "0.110.1" + }, + "selected_tag": null, + "show_beta": false, + "stars": 0, + "topics": [], + "version_installed": null + }, + "235316264": { + "authors": [ + "websylv" + ], + "category": "integration", + "description": ":sun_behind_rain_cloud: :switzerland: Meteo Swiss Integration for Home Assisant", + "domain": "meteo-swiss", + "downloads": 0, + "full_name": "websylv/homeassistant-meteoswiss", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "1dd8ae6", + "last_release_tag": "1.2", + "last_updated": "2020-11-10T19:10:28Z", + "name": "meteo-swiss", + "new": false, + "repository_manifest": { + "name": "Meteo Swiss", + "render_readme": true, + "domains": "weather", + "iot_class": "weather", + "country": "CH" + }, + "selected_tag": null, + "show_beta": false, + "stars": 28, + "topics": [], + "version_installed": null + }, + "299967654": { + "authors": [ + "@willholdoway" + ], + "category": "integration", + "description": "This is a custom component to allow control of HifiberryOS devices in Home Assistant using the audiocontrol2 REST API.", + "domain": "hifiberry", + "downloads": 0, + "full_name": "willholdoway/hifiberry", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "c685c84", + "last_release_tag": null, + "last_updated": "2020-10-09T10:30:05Z", + "name": "hifiberry", + "new": false, + "repository_manifest": { + "name": "Hifiberry", + "domains": [ + "media_player" + ], + "iot_class": "Local Polling", + "render_readme": "true" + }, + "selected_tag": null, + "show_beta": false, + "stars": 6, + "topics": [ + "hacs", + "hassio", + "hifiberry", + "home-assistant", + "home-automation", + "internet-of-things", + "iot", + "media-player", + "python" + ], + "version_installed": null + }, + "127251446": { + "authors": [ + "@xirixiz" + ], + "category": "integration", + "description": "Provides sensors for the Dutch waste collector mijnafvalwijzer.nl and/or afvalstoffendienstkalender.nl", + "domain": "afvalwijzer", + "downloads": 0, + "full_name": "xirixiz/homeassistant-afvalwijzer", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "3c334aa", + "last_release_tag": "5.2.0", + "last_updated": "2020-12-02T00:20:11Z", + "name": "afvalwijzer", + "new": false, + "repository_manifest": { + "name": "Afvalwijzer", + "content_in_root": false, + "render_readme": true, + "domains": [ + "sensor" + ], + "country": [ + "NL" + ], + "homeassistant": "0.100.0" + }, + "selected_tag": null, + "show_beta": false, + "stars": 51, + "topics": [ + "afvalwijzer", + "hacs", + "home-assistant", + "homeassistant", + "python", + "sensor", + "trash" + ], + "version_installed": null + }, + "212657669": { + "authors": [ + "@xMrVizzy" + ], + "category": "integration", + "description": "\ud83c\udf3f Minecraft Version Checker for Home Assistant.", + "domain": "minecraft_version", + "downloads": 0, + "full_name": "xMrVizzy/minecraft-version", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "9c0ec53", + "last_release_tag": "0.1.3", + "last_updated": "2020-01-16T12:36:24Z", + "name": "minecraft_version", + "new": false, + "repository_manifest": { + "name": "\u26cf\ufe0f Minecraft Version", + "render_readme": "true" + }, + "selected_tag": null, + "show_beta": false, + "stars": 2, + "topics": [], + "version_installed": null + }, + "194107337": { + "authors": [ + "@xMrVizzy" + ], + "category": "integration", + "description": "\ud83d\udca8 Philips AirPurifier custom component for Home Assistant.", + "domain": "philips_airpurifier", + "downloads": 0, + "full_name": "xMrVizzy/philips-airpurifier", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "b6e7ebe", + "last_release_tag": "1.0.1", + "last_updated": "2020-11-14T08:56:20Z", + "name": "philips_airpurifier", + "new": false, + "repository_manifest": { + "name": "Philips AirPurifier", + "render_readme": "true" + }, + "selected_tag": null, + "show_beta": false, + "stars": 25, + "topics": [], + "version_installed": null + }, + "261031401": { + "authors": [ + "@youdroid" + ], + "category": "integration", + "description": "\ud83c\udfa5 CouchPotato component to feed Upcoming Media Card.", + "domain": "couchpotato", + "downloads": 0, + "full_name": "youdroid/home-assistant-couchpotato", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "95b7ae8", + "last_release_tag": "V1.2.0", + "last_updated": "2020-06-11T22:37:59Z", + "name": "couchpotato", + "new": false, + "repository_manifest": { + "name": "couchpotato", + "domains": [ + "sensor" + ], + "render_readme": true, + "country": "FR" + }, + "selected_tag": null, + "show_beta": false, + "stars": 5, + "topics": [ + "couchpotato", + "customcomponents", + "homeassistant", + "homeassistant-components", + "homeassistant-sensor", + "python" + ], + "version_installed": null + }, + "262645913": { + "authors": [ + "@youdroid" + ], + "category": "integration", + "description": "\ud83c\udf75 Gitea component to follow your repositories", + "domain": "gitea", + "downloads": 0, + "full_name": "youdroid/home-assistant-gitea", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "e813b2f", + "last_release_tag": null, + "last_updated": "2020-05-16T10:32:32Z", + "name": "gitea", + "new": false, + "repository_manifest": { + "name": "gitea", + "domains": [ + "sensor" + ], + "render_readme": true, + "country": "FR" + }, + "selected_tag": null, + "show_beta": false, + "stars": 1, + "topics": [], + "version_installed": null + }, + "261614146": { + "authors": [ + "@youdroid" + ], + "category": "integration", + "description": "\ud83c\udfa5 SickChill component to feed Upcoming Media Card.", + "domain": "sickchill", + "downloads": 0, + "full_name": "youdroid/home-assistant-sickchill", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "302ebe1", + "last_release_tag": "V1.3", + "last_updated": "2020-10-08T20:53:31Z", + "name": "sickchill", + "new": false, + "repository_manifest": { + "name": "SickChill", + "domains": [ + "sensor" + ], + "render_readme": true, + "country": "FR" + }, + "selected_tag": null, + "show_beta": false, + "stars": 3, + "topics": [ + "customcomponents", + "homeassistant", + "homeassistant-components", + "homeassistant-sensor", + "python", + "sickchill" + ], + "version_installed": null + }, + "202987887": { + "authors": [ + "@zachowj" + ], + "category": "integration", + "description": "Companion Component for node-red-contrib-home-assistant-websocket to help integrate Node-RED with Home Assistant Core", + "domain": "nodered", + "downloads": 0, + "full_name": "zachowj/hass-node-red", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "739619e", + "last_release_tag": "v0.4.4", + "last_updated": "2020-10-15T23:29:00Z", + "name": "nodered", + "new": false, + "repository_manifest": { + "name": "Node-RED", + "domains": [ + "binary_sensor", + "sensor", + "switch" + ], + "homeassistant": "0.96.0", + "iot_class": "local_push" + }, + "selected_tag": null, + "show_beta": false, + "stars": 93, + "topics": [ + "home-assistant", + "node-red" + ], + "version_installed": null + }, + "199761906": { + "authors": [ + "@adminiuga" + ], + "category": "integration", + "description": "Build ZHA network topology map.", + "domain": "zha_map", + "downloads": 0, + "full_name": "zha-ng/zha-map", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "505fb92", + "last_release_tag": "0.4.0", + "last_updated": "2020-10-07T23:22:28Z", + "name": "zha_map", + "new": false, + "repository_manifest": { + "name": "ZHA-MAP" + }, + "selected_tag": null, + "show_beta": false, + "stars": 44, + "topics": [ + "zha", + "zha-map" + ], + "version_installed": null + }, + "305147191": { + "authors": [ + "@zigul" + ], + "category": "integration", + "description": "CEZ Distribuce - Home Assistant Sensor", + "domain": "cezdistribuce", + "downloads": 0, + "full_name": "zigul/HomeAssistant-CEZdistribuce", + "first_install": true, + "installed_commit": null, + "installed": false, + "last_commit": "40ec477", + "last_release_tag": "0.2", + "last_updated": "2020-11-20T21:39:05Z", + "name": "cezdistribuce", + "new": false, + "repository_manifest": { + "name": "CEZ Distribuce CZ", + "render_readme": false, + "domains": [ + "binary_sensor" + ], + "country": "CZ", + "homeassistant": "0.110.0" + }, + "selected_tag": null, + "show_beta": false, + "stars": 1, + "topics": [ + "cez", + "hacs", + "home-assistant", + "home-automation" + ], + "version_installed": null + }, + "260526528": { + "authors": [], + "category": "plugin", + "description": "Custom Dark Sky Weather plugin for HACS. This creates a rich weather card using the Dark Sky weather plugin.", + "domain": "", + "downloads": 0, + "full_name": "clayauld/lovelace-darksky-card", + "first_install": false, + "installed_commit": "03e6eac", + "installed": true, + "last_commit": "03e6eac", + "last_release_tag": null, + "last_updated": "2020-11-05T01:35:52Z", + "name": "lovelace-darksky-card", + "new": false, + "repository_manifest": { + "name": "Dark Sky Rich Weather Card", + "content_in_root": true, + "filename": "lovelace-darksky-card.js", + "render_readme": "true" + }, + "selected_tag": null, + "show_beta": false, + "stars": 3, + "topics": [], + "version_installed": null + }, + "261262884": { + "authors": [], + "category": "plugin", + "description": "\ud83d\udd70\ufe0f Time Picker Card for Home Assistant's Lovelace UI", + "domain": "", + "downloads": 958, + "full_name": "GeorgeSG/lovelace-time-picker-card", + "first_install": false, + "installed_commit": "c3fa2ce", + "installed": true, + "last_commit": "c3fa2ce", + "last_release_tag": "1.1.1", + "last_updated": "2020-11-22T14:38:10Z", + "name": "lovelace-time-picker-card", + "new": false, + "repository_manifest": { + "name": "Time Picker Card", + "render_readme": true, + "filename": "time-picker-card.js" + }, + "selected_tag": null, + "show_beta": false, + "stars": 43, + "topics": [ + "home-assistant", + "homeassistant", + "lovelace", + "lovelace-card", + "lovelace-custom-card" + ], + "version_installed": "1.1.1" + }, + "172733314": { + "authors": [ + "@ludeeus" + ], + "category": "integration", + "description": "HACS gives you a powerful UI to handle downloads of all your custom needs.", + "domain": "hacs", + "downloads": 9120, + "full_name": "hacs/integration", + "first_install": false, + "installed_commit": null, + "installed": true, + "last_commit": "d41c3f7", + "last_release_tag": "1.8.0", + "last_updated": "2020-12-08T07:18:56Z", + "name": "hacs", + "new": false, + "repository_manifest": { + "name": "HACS", + "zip_release": true, + "hide_default_branch": true, + "homeassistant": "0.110.0", + "hacs": "0.19.0", + "filename": "hacs.zip" + }, + "selected_tag": null, + "show_beta": false, + "stars": 1422, + "topics": [ + "community", + "hacktoberfest", + "hacs", + "home-assistant", + "integration", + "package-manager", + "python" + ], + "version_installed": "1.8.0" + }, + "162468030": { + "authors": [], + "category": "theme", + "description": "A collection of dark themes for Home Assistant. ", + "domain": "", + "downloads": 0, + "full_name": "awolkers/home-assistant-themes", + "first_install": false, + "installed_commit": "648aef9", + "installed": true, + "last_commit": "648aef9", + "last_release_tag": null, + "last_updated": "2020-09-16T14:47:00Z", + "name": "home-assistant-themes", + "new": false, + "repository_manifest": { + "name": "Dark theme pack for Home Assistant", + "render_readme": true, + "filename": "dark_themes.yaml" + }, + "selected_tag": null, + "show_beta": false, + "stars": 7, + "topics": [ + "dark-mode", + "dark-theme", + "hacs", + "hassio", + "home-assistant", + "theme" + ], + "version_installed": null + }, + "146194325": { + "authors": [], + "category": "plugin", + "description": "\u2747\ufe0f Lovelace button-card for home assistant", + "domain": "", + "downloads": 5070, + "full_name": "custom-cards/button-card", + "first_install": false, + "installed_commit": "b5de991", + "installed": true, + "last_commit": "b5de991", + "last_release_tag": "3.4.0", + "last_updated": "2020-12-02T14:06:47Z", + "name": "button-card", + "new": false, + "repository_manifest": { + "name": "button-card", + "content_in_root": false, + "render_readme": true, + "filename": "button-card.js" + }, + "selected_tag": null, + "show_beta": false, + "stars": 580, + "topics": [ + "home-assistant", + "home-assistant-custom", + "home-automation", + "lovelace", + "lovelace-ui" + ], + "version_installed": "3.4.0" + } + } +} \ No newline at end of file diff --git a/.storage/hacs/146194325.hacs b/.storage/hacs/146194325.hacs new file mode 100644 index 0000000..a787cbd --- /dev/null +++ b/.storage/hacs/146194325.hacs @@ -0,0 +1,59 @@ +{ + "version": "6", + "key": "hacs/146194325.hacs", + "data": { + "archived": false, + "authors": [], + "category": "plugin", + "content_in_root": false, + "country": [], + "config_flow": false, + "default_branch": "master", + "description": "\u2747\ufe0f Lovelace button-card for home assistant", + "domain": "", + "domains": [], + "downloads": 5070, + "file_name": "button-card.js", + "filename": "button-card.js", + "first_install": false, + "fork": false, + "full_name": "custom-cards/button-card", + "hacs": null, + "hide": false, + "hide_default_branch": false, + "homeassistant": null, + "id": "146194325", + "iot_class": null, + "installed": true, + "installed_commit": "b5de991", + "installed_version": "3.4.0", + "open_issues": 28, + "last_commit": "b5de991", + "last_version": "3.4.0", + "last_updated": "2020-12-02T14:06:47Z", + "manifest_name": null, + "new": false, + "persistent_directory": null, + "pushed_at": "2020-12-02T14:06:47", + "releases": true, + "render_readme": true, + "published_tags": [ + "3.4.0", + "3.3.6", + "3.3.5", + "3.3.4", + "3.3.3" + ], + "selected_tag": null, + "show_beta": false, + "stargazers_count": 580, + "topics": [ + "home-assistant", + "home-assistant-custom", + "home-automation", + "lovelace", + "lovelace-ui" + ], + "zip_release": false + } +} \ No newline at end of file diff --git a/.storage/hacs/162468030.hacs b/.storage/hacs/162468030.hacs new file mode 100644 index 0000000..86c4b03 --- /dev/null +++ b/.storage/hacs/162468030.hacs @@ -0,0 +1,54 @@ +{ + "version": "6", + "key": "hacs/162468030.hacs", + "data": { + "archived": false, + "authors": [], + "category": "theme", + "content_in_root": false, + "country": [], + "config_flow": false, + "default_branch": "master", + "description": "A collection of dark themes for Home Assistant. ", + "domain": "", + "domains": [], + "downloads": 0, + "file_name": "dark_themes.yaml", + "filename": "dark_themes.yaml", + "first_install": false, + "fork": false, + "full_name": "awolkers/home-assistant-themes", + "hacs": null, + "hide": false, + "hide_default_branch": false, + "homeassistant": null, + "id": "162468030", + "iot_class": null, + "installed": true, + "installed_commit": "648aef9", + "installed_version": null, + "open_issues": 0, + "last_commit": "648aef9", + "last_version": null, + "last_updated": "2020-09-16T14:47:00Z", + "manifest_name": null, + "new": false, + "persistent_directory": null, + "pushed_at": "2020-09-16T14:47:00", + "releases": false, + "render_readme": true, + "published_tags": [], + "selected_tag": null, + "show_beta": false, + "stargazers_count": 7, + "topics": [ + "dark-mode", + "dark-theme", + "hacs", + "hassio", + "home-assistant", + "theme" + ], + "zip_release": false + } +} \ No newline at end of file diff --git a/.storage/hacs/172733314.hacs b/.storage/hacs/172733314.hacs new file mode 100644 index 0000000..5f76e61 --- /dev/null +++ b/.storage/hacs/172733314.hacs @@ -0,0 +1,63 @@ +{ + "version": "6", + "key": "hacs/172733314.hacs", + "data": { + "archived": false, + "authors": [ + "@ludeeus" + ], + "category": "integration", + "content_in_root": false, + "country": [], + "config_flow": true, + "default_branch": "main", + "description": "HACS gives you a powerful UI to handle downloads of all your custom needs.", + "domain": "hacs", + "domains": [], + "downloads": 9120, + "file_name": "", + "filename": "hacs.zip", + "first_install": false, + "fork": false, + "full_name": "hacs/integration", + "hacs": "0.19.0", + "hide": false, + "hide_default_branch": true, + "homeassistant": "0.110.0", + "id": "172733314", + "iot_class": null, + "installed": true, + "installed_commit": null, + "installed_version": "1.8.0", + "open_issues": 30, + "last_commit": "d41c3f7", + "last_version": "1.8.0", + "last_updated": "2020-12-08T07:18:56Z", + "manifest_name": "HACS", + "new": false, + "persistent_directory": null, + "pushed_at": "2020-12-08T07:18:56", + "releases": true, + "render_readme": false, + "published_tags": [ + "1.8.0", + "1.6.2", + "1.6.1", + "1.6.0", + "1.5.2" + ], + "selected_tag": null, + "show_beta": false, + "stargazers_count": 1422, + "topics": [ + "community", + "hacktoberfest", + "hacs", + "home-assistant", + "integration", + "package-manager", + "python" + ], + "zip_release": true + } +} \ No newline at end of file diff --git a/.storage/hacs/260526528.hacs b/.storage/hacs/260526528.hacs new file mode 100644 index 0000000..cc6fedf --- /dev/null +++ b/.storage/hacs/260526528.hacs @@ -0,0 +1,47 @@ +{ + "version": "6", + "key": "hacs/260526528.hacs", + "data": { + "archived": false, + "authors": [], + "category": "plugin", + "content_in_root": true, + "country": [], + "config_flow": false, + "default_branch": "master", + "description": "Custom Dark Sky Weather plugin for HACS. This creates a rich weather card using the Dark Sky weather plugin.", + "domain": "", + "domains": [], + "downloads": 0, + "file_name": "lovelace-darksky-card.js", + "filename": "lovelace-darksky-card.js", + "first_install": false, + "fork": true, + "full_name": "clayauld/lovelace-darksky-card", + "hacs": null, + "hide": false, + "hide_default_branch": false, + "homeassistant": null, + "id": "260526528", + "iot_class": null, + "installed": true, + "installed_commit": "03e6eac", + "installed_version": null, + "open_issues": 1, + "last_commit": "03e6eac", + "last_version": null, + "last_updated": "2020-11-05T01:35:52Z", + "manifest_name": null, + "new": false, + "persistent_directory": null, + "pushed_at": "2020-11-05T01:35:52", + "releases": false, + "render_readme": "true", + "published_tags": [], + "selected_tag": null, + "show_beta": false, + "stargazers_count": 3, + "topics": [], + "zip_release": false + } +} \ No newline at end of file diff --git a/.storage/hacs/261262884.hacs b/.storage/hacs/261262884.hacs new file mode 100644 index 0000000..5c52b41 --- /dev/null +++ b/.storage/hacs/261262884.hacs @@ -0,0 +1,59 @@ +{ + "version": "6", + "key": "hacs/261262884.hacs", + "data": { + "archived": false, + "authors": [], + "category": "plugin", + "content_in_root": false, + "country": [], + "config_flow": false, + "default_branch": "master", + "description": "\ud83d\udd70\ufe0f Time Picker Card for Home Assistant's Lovelace UI", + "domain": "", + "domains": [], + "downloads": 958, + "file_name": "time-picker-card.js", + "filename": "time-picker-card.js", + "first_install": false, + "fork": false, + "full_name": "GeorgeSG/lovelace-time-picker-card", + "hacs": null, + "hide": false, + "hide_default_branch": false, + "homeassistant": null, + "id": "261262884", + "iot_class": null, + "installed": true, + "installed_commit": "c3fa2ce", + "installed_version": "1.1.1", + "open_issues": 1, + "last_commit": "c3fa2ce", + "last_version": "1.1.1", + "last_updated": "2020-11-22T14:38:10Z", + "manifest_name": null, + "new": false, + "persistent_directory": null, + "pushed_at": "2020-11-22T14:38:10", + "releases": true, + "render_readme": true, + "published_tags": [ + "1.1.1", + "1.1.0", + "1.0.1", + "0.1.1", + "0.1.0" + ], + "selected_tag": null, + "show_beta": false, + "stargazers_count": 43, + "topics": [ + "home-assistant", + "homeassistant", + "lovelace", + "lovelace-card", + "lovelace-custom-card" + ], + "zip_release": false + } +} \ No newline at end of file diff --git a/.storage/hassio b/.storage/hassio new file mode 100644 index 0000000..f2d6d90 --- /dev/null +++ b/.storage/hassio @@ -0,0 +1,7 @@ +{ + "data": { + "hassio_user": "5dc5beb72dbc49d59080804cea6f026c" + }, + "key": "hassio", + "version": 1 +} \ No newline at end of file diff --git a/.storage/http b/.storage/http new file mode 100644 index 0000000..76a1513 --- /dev/null +++ b/.storage/http @@ -0,0 +1,13 @@ +{ + "version": 1, + "key": "http", + "data": { + "server_port": 8123, + "ssl_profile": "modern", + "cors_allowed_origins": [ + "https://cast.home-assistant.io" + ], + "login_attempts_threshold": -1, + "ip_ban_enabled": true + } +} \ No newline at end of file diff --git a/.storage/lovelace.lovelace_learning b/.storage/lovelace.lovelace_learning new file mode 100644 index 0000000..be9bda1 --- /dev/null +++ b/.storage/lovelace.lovelace_learning @@ -0,0 +1,209 @@ +{ + "version": 1, + "key": "lovelace.lovelace_learning", + "data": { + "config": { + "title": "238 McHaley", + "views": [ + { + "title": "Meterology", + "path": "meterology", + "icon": "mdi:thermometer-lines", + "badges": [], + "cards": [ + { + "type": "entities", + "show_header_toggle": false, + "title": "Outside", + "entities": [ + "sensor.outside_temperature", + "sensor.outside_humidity" + ] + }, + { + "type": "entities", + "show_header_toggle": false, + "title": "Inside", + "entities": [ + "sensor.inside_temperature", + "sensor.inside_humidity" + ] + }, + { + "type": "weather-forecast", + "entity": "weather.praire_city_weather" + }, + { + "type": "entities", + "entities": [ + { + "entity": "sensor.bme_outside_temperature", + "icon": "mdi:temperature-fahrenheit", + "name": "Outside Temperature", + "secondary_info": "last-changed" + }, + { + "entity": "sensor.bme_outside_humidity", + "name": "Humidity", + "icon": "mdi:water-percent" + }, + { + "entity": "sensor.bme_outside_barometric_pressure", + "name": "Barometric Pressue", + "icon": "mdi:gauge" + }, + { + "entity": "sensor.bme_outside_dew_point_temperature", + "name": "Dew Point Temperature", + "icon": "mdi:thermometer-lines" + } + ], + "title": "Outside Conditions", + "theme": "Dark Blue", + "show_header_toggle": false + }, + { + "type": "iframe", + "url": "https://embed.windy.com/embed2.html?lat=44.422&lon=-118.718&detailLat=44.452&detailLon=-118.529&width=650&height=450&zoom=9&level=surface&overlay=radar&product=radar&menu=&message=&marker=&calendar=now&pressure=&type=map&location=coordinates&detail=&metricWind=mph&metricTemp=%C2%B0F&radarRange=-1", + "aspect_ratio": "100%", + "title": "Live Weather Map" + }, + { + "type": "iframe", + "title": "Local Radar", + "aspect_ratio": "100%", + "url": "https://www.rainviewer.com/map.html?loc=44.4823,-118.7828,10&oFa=0&oC=1&oU=0&oCS=1&oF=0&oAP=0&rmt=0&c=0&o=83&lm=1&th=1&sm=1&sn=1" + }, + { + "type": "weather-forecast", + "entity": "weather.praire_city_weather" + }, + { + "type": "custom:lovelace-darksky-card", + "entity_current_conditions": "sensor.dark_sky_icon", + "entity_temperature": "sensor.dark_sky_temperature", + "entity_forecast_high_temp_1": "sensor.dark_sky_daytime_high_temperature_1d", + "entity_forecast_high_temp_2": "sensor.dark_sky_daytime_high_temperature_2d", + "entity_forecast_high_temp_3": "sensor.dark_sky_daytime_high_temperature_3d", + "entity_forecast_high_temp_4": "sensor.dark_sky_daytime_high_temperature_4d", + "entity_forecast_high_temp_5": "sensor.dark_sky_daytime_high_temperature_5d", + "entity_forecast_icon_1": "sensor.dark_sky_icon_1d", + "entity_forecast_icon_2": "sensor.dark_sky_icon_2d", + "entity_forecast_icon_3": "sensor.dark_sky_icon_3d", + "entity_forecast_icon_4": "sensor.dark_sky_icon_4d", + "entity_forecast_icon_5": "sensor.dark_sky_icon_5d", + "entity_forecast_low_temp_1": "sensor.dark_sky_overnight_low_temperature_0d", + "entity_forecast_low_temp_2": "sensor.dark_sky_overnight_low_temperature_1d", + "entity_forecast_low_temp_3": "sensor.dark_sky_overnight_low_temperature_2d", + "entity_forecast_low_temp_4": "sensor.dark_sky_overnight_low_temperature_3d", + "entity_forecast_low_temp_5": "sensor.dark_sky_overnight_low_temperature_4d", + "entity_summary_1": "sensor.dark_sky_summary_1d", + "entity_summary_2": "sensor.dark_sky_summary_2d", + "entity_summary_3": "sensor.dark_sky_summary_3d", + "entity_summary_4": "sensor.dark_sky_summary_4d", + "entity_summary_5": "sensor.dark_sky_summary_5d", + "entity_sun": "sun.sun", + "entity_visibility": "sensor.dark_sky_visibility", + "entity_daytime_high": "sensor.dark_sky_daytime_high_temperature_0d", + "entity_wind_bearing": "sensor.dark_sky_wind_bearing", + "entity_wind_speed": "sensor.dark_sky_wind_speed", + "entity_humidity": "sensor.dark_sky_humidity", + "entity_pressure": "sensor.dark_sky_pressure", + "entity_apparent_temp": "sensor.dark_sky_apparent_temperature", + "entity_daily_summary": "sensor.dark_sky_daily_summary", + "entity_pop": "sensor.dark_sky_precip_probability", + "entity_pop_intensity": "sensor.dark_sky_precip_intensity", + "entity_pop_1": "sensor.dark_sky_precip_probability_1d", + "entity_pop_2": "sensor.dark_sky_precip_probability_2d", + "entity_pop_3": "sensor.dark_sky_precip_probability_3d", + "entity_pop_4": "sensor.dark_sky_precip_probability_4d", + "entity_pop_5": "sensor.dark_sky_precip_probability_5d" + } + ] + }, + { + "title": "Lights", + "path": "lights", + "icon": "mdi:lightbulb", + "badges": [], + "cards": [ + { + "type": "entities", + "show_header_toggle": false, + "title": "Indoor Security Lights", + "entities": [ + { + "entity": "switch.all_sonoffs", + "name": "Lights State (manual override)" + }, + { + "entity": "input_datetime.security_lights_interior_time_off", + "name": "Time Of Day To Turn Off (24HR)" + }, + { + "entity": "automation.security_lights_interior_time_on", + "name": "Lights On At Sunset Automation Enabled" + }, + { + "entity": "automation.security_lights_interior_time_off", + "name": "Lights Off Automation Enabled" + } + ] + }, + { + "type": "entities", + "show_header_toggle": false, + "title": "Raw Sonoff Control", + "entities": [ + "switch.sonoff_white", + "switch.sonoff_brown", + "switch.all_sonoffs" + ] + }, + { + "type": "custom:time-picker-card", + "entity": "input_datetime.security_lights_interior_time_off", + "hour_mode": 24, + "hour_step": 1, + "minute_step": 5, + "second_step": 5, + "name": "lights off at", + "layout": { + "embedded": true, + "name": "header", + "align_controls": "center" + }, + "link_values": false + }, + { + "type": "button", + "tap_action": { + "action": "toggle" + }, + "entity": "switch.outside_north_circuit_1" + } + ] + }, + { + "title": "Electronics Closet", + "path": "electronics-closet", + "icon": "mdi:door", + "badges": [], + "cards": [ + { + "type": "entities", + "show_header_toggle": false, + "title": "Temperature Control", + "entities": [ + "sensor.closet_temperature", + "input_number.fan_on_temp", + "switch.fan_automation_enabled", + "fan.fan_state" + ] + } + ] + } + ] + } + } +} \ No newline at end of file diff --git a/.storage/lovelace.system_setup b/.storage/lovelace.system_setup new file mode 100644 index 0000000..0f6c635 --- /dev/null +++ b/.storage/lovelace.system_setup @@ -0,0 +1,91 @@ +{ + "version": 1, + "key": "lovelace.system_setup", + "data": { + "config": { + "views": [ + { + "title": "Main", + "path": "main", + "badges": [], + "cards": [ + { + "type": "entities", + "show_header_toggle": false, + "entities": [ + { + "type": "weblink", + "name": "ESPHome", + "url": "/15ef4d2f_esphome", + "icon": "mdi:alpha-e-box" + }, + { + "type": "weblink", + "name": "HACS", + "url": "/hacs/entry", + "icon": "mdi:alpha-h-box" + }, + { + "type": "weblink", + "name": "Dashboards", + "url": "/config/lovelace/dashboards", + "icon": "mdi:view-dashboard-variant" + }, + { + "type": "weblink", + "name": "Edit Configuration Files", + "url": "/a0d7b954_vscode", + "icon": "mdi:playlist-edit" + } + ] + }, + { + "type": "entities", + "title": "HomeAssistant", + "show_header_toggle": false, + "entities": [ + { + "type": "weblink", + "name": "Home Assistant", + "url": "https://home-assistant.io", + "icon": "mdi:home-assistant" + }, + { + "type": "weblink", + "name": "Configuring Home Assistant", + "url": "https://home-assistant.io/getting-started/configuration/", + "icon": "mdi:assistant" + }, + { + "type": "weblink", + "name": "Available Components", + "url": "https://home-assistant.io/components/", + "icon": "mdi:briefcase-check" + }, + { + "type": "weblink", + "name": "Troubleshooting your configuration", + "url": "https://home-assistant.io/docs/configuration/troubleshooting/", + "icon": "mdi:wrench" + }, + { + "type": "weblink", + "name": "Getting Help", + "url": "https://home-assistant.io/help/", + "icon": "mdi:help" + }, + { + "type": "weblink", + "name": "Home Assistant Forum", + "url": "https://community.home-assistant.io", + "icon": "mdi:comment-multiple-outline" + } + ] + } + ] + } + ], + "title": "System Setup Navigator" + } + } +} \ No newline at end of file diff --git a/.storage/lovelace_dashboards b/.storage/lovelace_dashboards new file mode 100644 index 0000000..2bcd6d0 --- /dev/null +++ b/.storage/lovelace_dashboards @@ -0,0 +1,24 @@ +{ + "version": 1, + "key": "lovelace_dashboards", + "data": { + "items": [ + { + "require_admin": false, + "show_in_sidebar": true, + "title": "Learning", + "url_path": "lovelace-learning", + "mode": "storage", + "id": "lovelace_learning" + }, + { + "require_admin": true, + "show_in_sidebar": true, + "title": "Setup", + "url_path": "system-setup", + "mode": "storage", + "id": "system_setup" + } + ] + } +} \ No newline at end of file diff --git a/.storage/lovelace_resources b/.storage/lovelace_resources new file mode 100644 index 0000000..02de00a --- /dev/null +++ b/.storage/lovelace_resources @@ -0,0 +1,23 @@ +{ + "version": 1, + "key": "lovelace_resources", + "data": { + "items": [ + { + "url": "/hacsfiles/button-card/button-card.js", + "type": "module", + "id": "2e6bc398cf194600a54c288f0f39df30" + }, + { + "url": "/hacsfiles/lovelace-time-picker-card/time-picker-card.js", + "type": "module", + "id": "320b29e23fa841389e7f288518351f20" + }, + { + "url": "/hacsfiles/lovelace-darksky-card/lovelace-darksky-card.js", + "type": "module", + "id": "1b05f5d79aba4d86876333158b04a3c9" + } + ] + } +} \ No newline at end of file diff --git a/.storage/mobile_app b/.storage/mobile_app new file mode 100644 index 0000000..34d48cf --- /dev/null +++ b/.storage/mobile_app @@ -0,0 +1,530 @@ +{ + "version": 1, + "key": "mobile_app", + "data": { + "binary_sensor": { + "972369ddfba8b95dfc4b3ab68f74f19e19227919102c4720d1fc38d9fd45a0b4_is_charging": { + "added": true, + "attributes": {}, + "device_class": "plug", + "icon": "mdi:power-plug", + "name": "Is Charging", + "state": true, + "type": "binary_sensor", + "unique_id": "is_charging", + "webhook_id": "972369ddfba8b95dfc4b3ab68f74f19e19227919102c4720d1fc38d9fd45a0b4" + } + }, + "deleted_ids": [ + "e05272f132b2ece8de22ec23b279ff8775479cfe54e60bb4d935111c007480a1", + "e689e9489fb188efebd2131925467e20069b7725bbd125ef8f8765986e124d0f", + "589b63fea7e219d0ebc4a4804c59f7cfacef9c25c4621300f84eb7614446152c" + ], + "sensor": { + "05937d05f5a6239334725f7b14a462ab0e409dbbe11c2cf4b2f20747f403edf2_battery_level": { + "added": true, + "attributes": {}, + "device_class": "battery", + "icon": "mdi:battery-90", + "name": "Battery Level", + "state": 93, + "type": "sensor", + "unique_id": "battery_level", + "unit_of_measurement": "%", + "webhook_id": "05937d05f5a6239334725f7b14a462ab0e409dbbe11c2cf4b2f20747f403edf2" + }, + "05937d05f5a6239334725f7b14a462ab0e409dbbe11c2cf4b2f20747f403edf2_battery_state": { + "added": true, + "attributes": { + "charger_type": "unknown", + "is_charging": false + }, + "device_class": "battery", + "icon": "mdi:battery-90", + "name": "Battery State", + "state": "discharging", + "type": "sensor", + "unique_id": "battery_state", + "webhook_id": "05937d05f5a6239334725f7b14a462ab0e409dbbe11c2cf4b2f20747f403edf2" + }, + "05937d05f5a6239334725f7b14a462ab0e409dbbe11c2cf4b2f20747f403edf2_wifi_connection": { + "added": true, + "attributes": { + "bssid": "0c:80:63:06:fc:b0", + "frequency": 2462, + "ip_address": "10.0.0.50", + "is_hidden": false, + "link_speed": 72, + "signal_level": -1 + }, + "icon": "mdi:wifi-strength-off", + "name": "Wifi Connection", + "state": "zoesplace", + "type": "sensor", + "unique_id": "wifi_connection", + "webhook_id": "05937d05f5a6239334725f7b14a462ab0e409dbbe11c2cf4b2f20747f403edf2" + }, + "589b63fea7e219d0ebc4a4804c59f7cfacef9c25c4621300f84eb7614446152c_battery_level": { + "added": true, + "attributes": { + "charger_type": "N/A", + "is_charging": false + }, + "device_class": "battery", + "icon": "mdi:battery-60", + "name": "Battery Level", + "state": 67, + "type": "sensor", + "unique_id": "battery_level", + "unit_of_measurement": "%", + "webhook_id": "589b63fea7e219d0ebc4a4804c59f7cfacef9c25c4621300f84eb7614446152c" + }, + "589b63fea7e219d0ebc4a4804c59f7cfacef9c25c4621300f84eb7614446152c_geocoded_location": { + "added": true, + "attributes": { + "Administrative Area": "Oregon", + "Country": "United States", + "ISO Country Code": "US", + "Locality": "Prairie City", + "Location": [ + 44.4611229, + -118.71172790000001 + ], + "Postal Code": "97869", + "Sub Administrative Area": "Grant County", + "Sub Locality": null, + "Sub Thoroughfare": "238", + "Thoroughfare": "North McHaley Street" + }, + "icon": "mdi:map", + "name": "Geocoded Location", + "state": "238 N McHaley St, Prairie City, OR 97869, USA", + "type": "sensor", + "unique_id": "geocoded_location", + "webhook_id": "589b63fea7e219d0ebc4a4804c59f7cfacef9c25c4621300f84eb7614446152c" + }, + "589b63fea7e219d0ebc4a4804c59f7cfacef9c25c4621300f84eb7614446152c_wifi_connection": { + "added": true, + "attributes": { + "bssid": "0c:80:63:06:fc:b0", + "frequency": 2437, + "ip_address": "10.0.0.203", + "is_hidden": false, + "link_speed": 28, + "signal_level": -74 + }, + "icon": "mdi:wifi-strength-1", + "name": "Wifi Connection", + "state": "zoesplace", + "type": "sensor", + "unique_id": "wifi_connection", + "webhook_id": "589b63fea7e219d0ebc4a4804c59f7cfacef9c25c4621300f84eb7614446152c" + }, + "5f53d4c3439e791aab47663319361609868cb300c034204db0e25e40147d27be_battery_level": { + "added": true, + "attributes": {}, + "device_class": "battery", + "icon": "mdi:battery", + "name": "Battery Level", + "state": 100, + "type": "sensor", + "unique_id": "battery_level", + "unit_of_measurement": "%", + "webhook_id": "5f53d4c3439e791aab47663319361609868cb300c034204db0e25e40147d27be" + }, + "5f53d4c3439e791aab47663319361609868cb300c034204db0e25e40147d27be_battery_state": { + "added": true, + "attributes": { + "is_charging": true, + "charger_type": "ac" + }, + "device_class": "battery", + "icon": "mdi:battery-charging", + "name": "Battery State", + "state": "full", + "type": "sensor", + "unique_id": "battery_state", + "webhook_id": "5f53d4c3439e791aab47663319361609868cb300c034204db0e25e40147d27be" + }, + "5f53d4c3439e791aab47663319361609868cb300c034204db0e25e40147d27be_wifi_connection": { + "added": true, + "attributes": { + "bssid": "0c:80:63:91:4c:90", + "ip_address": "10.0.0.203", + "link_speed": 72, + "is_hidden": false, + "frequency": 2437, + "signal_level": -1 + }, + "icon": "mdi:wifi-strength-off", + "name": "Wifi Connection", + "state": "zoesplace-outside", + "type": "sensor", + "unique_id": "wifi_connection", + "webhook_id": "5f53d4c3439e791aab47663319361609868cb300c034204db0e25e40147d27be" + }, + "972369ddfba8b95dfc4b3ab68f74f19e19227919102c4720d1fc38d9fd45a0b4_audio_sensor": { + "added": true, + "attributes": { + "audio_mode": "normal", + "is_headphones": false, + "is_mic_muted": false, + "is_music_active": false, + "is_speakerphone_on": false, + "volume_level_alarm": 6, + "volume_level_call": 4, + "volume_level_music": 0, + "volume_level_ring": 5 + }, + "icon": "mdi:volume-high", + "name": "Audio Sensor", + "state": "normal", + "type": "sensor", + "unique_id": "audio_sensor", + "webhook_id": "972369ddfba8b95dfc4b3ab68f74f19e19227919102c4720d1fc38d9fd45a0b4" + }, + "972369ddfba8b95dfc4b3ab68f74f19e19227919102c4720d1fc38d9fd45a0b4_battery_health": { + "added": true, + "attributes": {}, + "icon": "mdi:battery-alert", + "name": "Battery Health", + "state": "unknown", + "type": "sensor", + "unique_id": "battery_health", + "webhook_id": "972369ddfba8b95dfc4b3ab68f74f19e19227919102c4720d1fc38d9fd45a0b4" + }, + "972369ddfba8b95dfc4b3ab68f74f19e19227919102c4720d1fc38d9fd45a0b4_battery_level": { + "added": true, + "attributes": {}, + "device_class": "battery", + "icon": "mdi:battery", + "name": "Battery Level", + "state": 100, + "type": "sensor", + "unique_id": "battery_level", + "unit_of_measurement": "%", + "webhook_id": "972369ddfba8b95dfc4b3ab68f74f19e19227919102c4720d1fc38d9fd45a0b4" + }, + "972369ddfba8b95dfc4b3ab68f74f19e19227919102c4720d1fc38d9fd45a0b4_battery_state": { + "added": true, + "attributes": {}, + "device_class": "battery", + "icon": "mdi:battery-charging", + "name": "Battery State", + "state": "full", + "type": "sensor", + "unique_id": "battery_state", + "webhook_id": "972369ddfba8b95dfc4b3ab68f74f19e19227919102c4720d1fc38d9fd45a0b4" + }, + "972369ddfba8b95dfc4b3ab68f74f19e19227919102c4720d1fc38d9fd45a0b4_bluetooth_connection": { + "added": true, + "attributes": { + "connected_not_paired_devices": "[]", + "connected_paired_devices": "[]", + "is_bt_on": false, + "paired_devices": "" + }, + "icon": "mdi:bluetooth", + "name": "Bluetooth Connection", + "state": 0, + "type": "sensor", + "unique_id": "bluetooth_connection", + "unit_of_measurement": "connection(s)", + "webhook_id": "972369ddfba8b95dfc4b3ab68f74f19e19227919102c4720d1fc38d9fd45a0b4" + }, + "972369ddfba8b95dfc4b3ab68f74f19e19227919102c4720d1fc38d9fd45a0b4_charger_type": { + "added": true, + "attributes": {}, + "icon": "mdi:power-plug", + "name": "Charger Type", + "state": "ac", + "type": "sensor", + "unique_id": "charger_type", + "webhook_id": "972369ddfba8b95dfc4b3ab68f74f19e19227919102c4720d1fc38d9fd45a0b4" + }, + "972369ddfba8b95dfc4b3ab68f74f19e19227919102c4720d1fc38d9fd45a0b4_detected_activity": { + "added": true, + "attributes": { + "in_vehicle": 10, + "on_bicycle": 10, + "on_foot": 10, + "running": 10, + "still": 10, + "unknown": 40, + "walking": 10 + }, + "icon": "mdi:progress-question", + "name": "Detected Activity", + "state": "unknown", + "type": "sensor", + "unique_id": "detected_activity", + "webhook_id": "972369ddfba8b95dfc4b3ab68f74f19e19227919102c4720d1fc38d9fd45a0b4" + }, + "972369ddfba8b95dfc4b3ab68f74f19e19227919102c4720d1fc38d9fd45a0b4_dnd_sensor": { + "added": true, + "attributes": {}, + "icon": "mdi:do-not-disturb", + "name": "Do Not Disturb Sensor", + "state": "off", + "type": "sensor", + "unique_id": "dnd_sensor", + "webhook_id": "972369ddfba8b95dfc4b3ab68f74f19e19227919102c4720d1fc38d9fd45a0b4" + }, + "972369ddfba8b95dfc4b3ab68f74f19e19227919102c4720d1fc38d9fd45a0b4_last_reboot": { + "added": true, + "attributes": { + "Local Time": "Thu Dec 03 12:36:33 PST 2020", + "Time in Milliseconds": 1607027793942 + }, + "device_class": "timestamp", + "icon": "mdi:restart", + "name": "Last Reboot", + "state": "2020-12-03T20:36:33Z", + "type": "sensor", + "unique_id": "last_reboot", + "webhook_id": "972369ddfba8b95dfc4b3ab68f74f19e19227919102c4720d1fc38d9fd45a0b4" + }, + "972369ddfba8b95dfc4b3ab68f74f19e19227919102c4720d1fc38d9fd45a0b4_light_sensor": { + "added": true, + "attributes": {}, + "device_class": "illuminance", + "icon": "mdi:brightness-5", + "name": "Light Sensor", + "state": "116", + "type": "sensor", + "unique_id": "light_sensor", + "unit_of_measurement": "lx", + "webhook_id": "972369ddfba8b95dfc4b3ab68f74f19e19227919102c4720d1fc38d9fd45a0b4" + }, + "972369ddfba8b95dfc4b3ab68f74f19e19227919102c4720d1fc38d9fd45a0b4_next_alarm": { + "added": true, + "attributes": { + "Local Time": "", + "Package": "", + "Time in Milliseconds": 0.0 + }, + "device_class": "timestamp", + "icon": "mdi:alarm", + "name": "Next Alarm", + "state": "unavailable", + "type": "sensor", + "unique_id": "next_alarm", + "webhook_id": "972369ddfba8b95dfc4b3ab68f74f19e19227919102c4720d1fc38d9fd45a0b4" + }, + "972369ddfba8b95dfc4b3ab68f74f19e19227919102c4720d1fc38d9fd45a0b4_storage_sensor": { + "added": true, + "attributes": { + "Total internal storage": "116GB" + }, + "icon": "mdi:harddisk", + "name": "Storage Sensor", + "state": 88, + "type": "sensor", + "unique_id": "storage_sensor", + "unit_of_measurement": "%", + "webhook_id": "972369ddfba8b95dfc4b3ab68f74f19e19227919102c4720d1fc38d9fd45a0b4" + }, + "972369ddfba8b95dfc4b3ab68f74f19e19227919102c4720d1fc38d9fd45a0b4_wifi_connection": { + "added": true, + "attributes": { + "bssid": "02:00:00:00:00:00", + "frequency": 5180, + "ip_address": "10.0.0.250", + "is_hidden": true, + "link_speed": 300, + "signal_level": -1 + }, + "icon": "mdi:wifi-strength-off", + "name": "Wifi Connection", + "state": "", + "type": "sensor", + "unique_id": "wifi_connection", + "webhook_id": "972369ddfba8b95dfc4b3ab68f74f19e19227919102c4720d1fc38d9fd45a0b4" + }, + "b0bd2f0affedb208d53f95e0f264ec0ba55e5c867ee3a864dd94e29c3f8a8db0_battery_level": { + "added": true, + "attributes": {}, + "device_class": "battery", + "icon": "mdi:battery-50", + "name": "Battery Level", + "state": 58, + "type": "sensor", + "unique_id": "battery_level", + "unit_of_measurement": "%", + "webhook_id": "b0bd2f0affedb208d53f95e0f264ec0ba55e5c867ee3a864dd94e29c3f8a8db0" + }, + "b0bd2f0affedb208d53f95e0f264ec0ba55e5c867ee3a864dd94e29c3f8a8db0_battery_state": { + "added": true, + "attributes": { + "battery_health": "good", + "charger_type": "unknown", + "is_charging": false + }, + "device_class": "battery", + "icon": "mdi:battery-50", + "name": "Battery State", + "state": "discharging", + "type": "sensor", + "unique_id": "battery_state", + "webhook_id": "b0bd2f0affedb208d53f95e0f264ec0ba55e5c867ee3a864dd94e29c3f8a8db0" + }, + "b0bd2f0affedb208d53f95e0f264ec0ba55e5c867ee3a864dd94e29c3f8a8db0_bluetooth_connection": { + "added": true, + "attributes": { + "connected_not_paired_devices": [], + "connected_paired_devices": [], + "is_bt_on": false, + "paired_devices": "" + }, + "icon": "mdi:bluetooth", + "name": "Bluetooth Connection", + "state": 0, + "type": "sensor", + "unique_id": "bluetooth_connection", + "unit_of_measurement": "connection(s)", + "webhook_id": "b0bd2f0affedb208d53f95e0f264ec0ba55e5c867ee3a864dd94e29c3f8a8db0" + }, + "b0bd2f0affedb208d53f95e0f264ec0ba55e5c867ee3a864dd94e29c3f8a8db0_geocoded_location": { + "added": true, + "attributes": { + "Administrative Area": "Oregon", + "Country": "United States", + "ISO Country Code": "US", + "Locality": "Prairie City", + "Location": [ + 44.4609349, + -118.71165899999998 + ], + "Postal Code": "97869", + "Sub Administrative Area": "Grant County", + "Sub Locality": null, + "Sub Thoroughfare": "258", + "Thoroughfare": "South McHaley Avenue" + }, + "icon": "mdi:map", + "name": "Geocoded Location", + "state": "258 S McHaley Ave, Prairie City, OR 97869, USA", + "type": "sensor", + "unique_id": "geocoded_location", + "webhook_id": "b0bd2f0affedb208d53f95e0f264ec0ba55e5c867ee3a864dd94e29c3f8a8db0" + }, + "b0bd2f0affedb208d53f95e0f264ec0ba55e5c867ee3a864dd94e29c3f8a8db0_light_sensor": { + "added": true, + "attributes": {}, + "device_class": "illuminance", + "icon": "mdi:brightness-5", + "name": "Light Sensor", + "state": "unavailable", + "type": "sensor", + "unique_id": "light_sensor", + "unit_of_measurement": "lx", + "webhook_id": "b0bd2f0affedb208d53f95e0f264ec0ba55e5c867ee3a864dd94e29c3f8a8db0" + }, + "b0bd2f0affedb208d53f95e0f264ec0ba55e5c867ee3a864dd94e29c3f8a8db0_next_alarm": { + "added": true, + "attributes": { + "Local Time": "", + "Package": "", + "Time in Milliseconds": 0 + }, + "device_class": "timestamp", + "icon": "mdi:alarm", + "name": "Next Alarm", + "state": "unavailable", + "type": "sensor", + "unique_id": "next_alarm", + "webhook_id": "b0bd2f0affedb208d53f95e0f264ec0ba55e5c867ee3a864dd94e29c3f8a8db0" + }, + "b0bd2f0affedb208d53f95e0f264ec0ba55e5c867ee3a864dd94e29c3f8a8db0_storage_sensor": { + "added": true, + "attributes": { + "Free external storage": "17GB", + "Free internal storage": "0GB", + "Total external storage": "29GB", + "Total internal storage": "24GB" + }, + "icon": "mdi:harddisk", + "name": "Storage Sensor", + "state": 3, + "type": "sensor", + "unique_id": "storage_sensor", + "unit_of_measurement": "%", + "webhook_id": "b0bd2f0affedb208d53f95e0f264ec0ba55e5c867ee3a864dd94e29c3f8a8db0" + }, + "b0bd2f0affedb208d53f95e0f264ec0ba55e5c867ee3a864dd94e29c3f8a8db0_wifi_connection": { + "added": true, + "attributes": { + "bssid": "0c:80:63:91:4c:91", + "frequency": 5180, + "ip_address": "10.0.0.81", + "is_hidden": false, + "is_wifi_on": true, + "link_speed": 390, + "signal_level": -63 + }, + "icon": "mdi:wifi-strength-2", + "name": "Wifi Connection", + "state": "zoesplace-outside", + "type": "sensor", + "unique_id": "wifi_connection", + "webhook_id": "b0bd2f0affedb208d53f95e0f264ec0ba55e5c867ee3a864dd94e29c3f8a8db0" + }, + "e689e9489fb188efebd2131925467e20069b7725bbd125ef8f8765986e124d0f_battery_level": { + "added": true, + "attributes": { + "charger_type": "AC", + "is_charging": true + }, + "device_class": "battery", + "icon": "mdi:battery-charging", + "name": "Battery Level", + "state": 100, + "type": "sensor", + "unique_id": "battery_level", + "unit_of_measurement": "%", + "webhook_id": "e689e9489fb188efebd2131925467e20069b7725bbd125ef8f8765986e124d0f" + }, + "e689e9489fb188efebd2131925467e20069b7725bbd125ef8f8765986e124d0f_geocoded_location": { + "added": true, + "attributes": { + "Administrative Area": "Oregon", + "Country": "United States", + "ISO Country Code": "US", + "Locality": "Prairie City", + "Location": [ + 44.4611229, + -118.71172790000001 + ], + "Postal Code": "97869", + "Sub Administrative Area": "Grant County", + "Sub Locality": null, + "Sub Thoroughfare": "238", + "Thoroughfare": "North McHaley Street" + }, + "icon": "mdi:map", + "name": "Geocoded Location", + "state": "238 N McHaley St, Prairie City, OR 97869, USA", + "type": "sensor", + "unique_id": "geocoded_location", + "webhook_id": "e689e9489fb188efebd2131925467e20069b7725bbd125ef8f8765986e124d0f" + }, + "e689e9489fb188efebd2131925467e20069b7725bbd125ef8f8765986e124d0f_wifi_connection": { + "added": true, + "attributes": { + "bssid": "0c:80:63:91:4c:90", + "frequency": 2437, + "ip_address": "10.0.0.182", + "is_hidden": false, + "link_speed": 72, + "signal_level": -39 + }, + "icon": "mdi:wifi-strength-3", + "name": "Wifi Connection", + "state": "zoesplace-outside", + "type": "sensor", + "unique_id": "wifi_connection", + "webhook_id": "e689e9489fb188efebd2131925467e20069b7725bbd125ef8f8765986e124d0f" + } + } + } +} \ No newline at end of file diff --git a/.storage/onboarding b/.storage/onboarding new file mode 100644 index 0000000..691451b --- /dev/null +++ b/.storage/onboarding @@ -0,0 +1,11 @@ +{ + "data": { + "done": [ + "user", + "core_config", + "integration" + ] + }, + "key": "onboarding", + "version": 3 +} \ No newline at end of file diff --git a/.storage/person b/.storage/person new file mode 100644 index 0000000..6bdb79a --- /dev/null +++ b/.storage/person @@ -0,0 +1,19 @@ +{ + "data": { + "items": [ + { + "device_trackers": [ + "device_tracker.700t1c", + "device_tracker.sm_g935r4", + "device_tracker.xt1064_2", + "device_tracker.lgus215" + ], + "id": "sysadmin", + "name": "sysadmin", + "user_id": "99685dc45d9f40dc8183e11ce5128038" + } + ] + }, + "key": "person", + "version": 2 +} \ No newline at end of file diff --git a/.storage/zone b/.storage/zone new file mode 100644 index 0000000..de7b529 --- /dev/null +++ b/.storage/zone @@ -0,0 +1,7 @@ +{ + "data": { + "items": [] + }, + "key": "zone", + "version": 1 +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..a04b218 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "files.associations": { + "*.yaml": "home-assistant" + } +} \ No newline at end of file diff --git a/configs/http.yaml b/archive/configs/http.yaml similarity index 100% rename from configs/http.yaml rename to archive/configs/http.yaml diff --git a/configs/mqtt.yaml b/archive/configs/mqtt.yaml similarity index 96% rename from configs/mqtt.yaml rename to archive/configs/mqtt.yaml index 8708d3c..7c134c1 100644 --- a/configs/mqtt.yaml +++ b/archive/configs/mqtt.yaml @@ -6,6 +6,7 @@ #port: 1883 #username: !secret MQTT_username #password: !secret MQTT_password +#discovery: true birth_message: topic: "status/ha" payload: "online" diff --git a/configs/panel.yaml b/archive/configs/panel.yaml similarity index 100% rename from configs/panel.yaml rename to archive/configs/panel.yaml diff --git a/configs/scripts.yaml b/archive/configs/scripts.yaml similarity index 100% rename from configs/scripts.yaml rename to archive/configs/scripts.yaml diff --git a/archive/lovelace/bak.irrigation.yaml. b/archive/lovelace/bak.irrigation.yaml. new file mode 100644 index 0000000..e4c2cf2 --- /dev/null +++ b/archive/lovelace/bak.irrigation.yaml. @@ -0,0 +1,18 @@ +# lovelace_gen + +# {% zones = [ '1':'Front North', '2':'Front South', '3':'Front Beds'} %} +icon: mdi:water +cards: + - type: vertical-stack + cards: + - type: markdown + content: > + # Irrigation + # {% for zone in zones %} + # - type: entity-button + # entity: switch.irrigation_zone_{{ zone }} + # icon: mdi:water + # name: zones[zone] + # tap_action: + # action: toggle + # {% endfor %} diff --git a/archive/lovelace/button-card.yaml.j2 b/archive/lovelace/button-card.yaml.j2 new file mode 100644 index 0000000..a9f4d8c --- /dev/null +++ b/archive/lovelace/button-card.yaml.j2 @@ -0,0 +1,8 @@ +# lovelace_gen +{% if entity.startswith("light") %} +type: light +{% else %} +type: entity-button +{% endif %} +entity: {{ entity }} +name: {{ name }} diff --git a/lovelace/cameras.yaml b/archive/lovelace/cameras.yaml similarity index 100% rename from lovelace/cameras.yaml rename to archive/lovelace/cameras.yaml diff --git a/lovelace/fio.yaml b/archive/lovelace/fio.yaml similarity index 100% rename from lovelace/fio.yaml rename to archive/lovelace/fio.yaml diff --git a/lovelace/gios.yaml b/archive/lovelace/gpios.yaml similarity index 97% rename from lovelace/gios.yaml rename to archive/lovelace/gpios.yaml index 9e4ec53..0bb6e19 100644 --- a/lovelace/gios.yaml +++ b/archive/lovelace/gpios.yaml @@ -4,7 +4,7 @@ cards: cards: - type: markdown content: > - # Control GPIO Relays on Pine64 + # Control UCI GPIO Relays - type: horizontal-stack cards: - type: entity-button diff --git a/archive/lovelace/irrigation-buttons-old.yaml.j2 b/archive/lovelace/irrigation-buttons-old.yaml.j2 new file mode 100644 index 0000000..75896c4 --- /dev/null +++ b/archive/lovelace/irrigation-buttons-old.yaml.j2 @@ -0,0 +1,34 @@ +# lovelace_gen +title: MQTT Irrigation Buttons +{# icon: mdi:water #} +panel: true +cards: + - type: 'custom:layout-card' + min_columns: 2 + max_columns: 4 + column_width: 200px + cards: + - type: entity-button + entity: switch.irrigation_pump + icon: mdi:water-pump + name: well pump + tap_action: + action: toggle + {% set zones = { + '1':'Front Yard North', + '2':'Front Yard South', + '3':'Fr. Beds/Back E. Spigot', + '4':'Back Garage Spigot', + '5':'Garden North/West', + '6':'Garden South', + '7':'Back West Spigot' + } + %} + {% for zone,name in zones.items() %} + - type: entity-button + entity: switch.irrigation_zone_{{ zone }} + icon: mdi:water + name: {{ name }} + tap_action: + action: toggle + {% endfor %} diff --git a/archive/lovelace/irrigation-buttons.yaml.j2 b/archive/lovelace/irrigation-buttons.yaml.j2 new file mode 100644 index 0000000..72c477b --- /dev/null +++ b/archive/lovelace/irrigation-buttons.yaml.j2 @@ -0,0 +1,34 @@ +# lovelace_gen +title: Irrigation +icon: mdi:water +panel: true +cards: + - type: 'custom:layout-card' + min_columns: 2 + max_columns: 4 + column_width: 200px + cards: + - type: entity-button + entity: input_boolean.irrigation_pump + icon: mdi:water-pump + name: well pump + tap_action: + action: toggle + {% set zones = { + '1':'Front Yard North', + '2':'Front Yard South', + '3':'Fr. Beds/Back E. Spigot', + '4':'Back Garage Spigot', + '5':'Garden North/West', + '6':'Garden South', + '7':'Back West Spigot' + } + %} + {% for zone,name in zones.items() %} + - type: entity-button + entity: input_boolean.irrigation_zone_{{ zone }}_state + icon: mdi:water + name: {{ name }} + tap_action: + action: toggle + {% endfor %} diff --git a/archive/lovelace/irrigation.yaml.j2 b/archive/lovelace/irrigation.yaml.j2 new file mode 100644 index 0000000..b6d4d69 --- /dev/null +++ b/archive/lovelace/irrigation.yaml.j2 @@ -0,0 +1,70 @@ +# lovelace_gen +title: Irrigation +cards: +{% set zones = { + '1':'Front Yard North', + '2':'Front Yard South', + '3':'Fr. Beds/Back E. Spigot', + '4':'Back Garage Spigot', + '5':'Garden North/West', + '6':'Garden South', + '7':'Back West Spigot' + } + %} + - type: vertical-stack + cards: + - type: markdown + content: > + # IRRIGATION SYSTEM STATE + theme: solarized_light + - type: entities + show_header_toggle: false + entities: + - entity: input_boolean.irrigation_enabled + - type: divider + - entity: variable.irrigation_next_trigger + - entity: variable.irrigation_next_trigger_dt + - entity: variable.irrigation_next_schedule_name + - entity: variable.irrigation_running_names + - entity: variable.irrigation_queue_names + - type: divider + {% for zone,name in zones.items() %} + - entity: input_boolean.irrigation_zone_{{ zone }}_schedule_enabled + name: {{ name }} + {% endfor %} + {% for zone,name in zones.items() %} + - type: conditional + conditions: + - entity: input_boolean.irrigation_zone_{{ zone }}_schedule_enabled + state: "on" + card: + type: vertical-stack + cards: + - type: markdown + content: > + ## {{ name }} : Zone {{ zone }} + theme: slate + - type: entities + entities: + - entity: input_boolean.irrigation_zone_{{ zone }}_state + - entity: input_number.irrigation_zone_{{ zone }}_duration + - type: divider + - entity: variable.irrigation_zone_{{ zone }}_schedule_next_dt + - entity: variable.irrigation_zone_{{ zone }}_schedule_countdown + - type: entities + entities: + - type: custom:fold-entity-row + head: input_boolean.irrigation_zone_{{ zone }}_schedule_enabled + entities: + - type: section + label: 'Schedule Parameters for {{name}}' + - type: divider + - type: section + label: 'Base Time of Day' + - entity: input_number.irrigation_zone_{{ zone }}_schedule_base_hour + - entity: input_number.irrigation_zone_{{ zone }}_schedule_base_minute + - type: divider + - entity: input_select.irrigation_zone_{{ zone }}_schedule_delta + + + {% endfor %} diff --git a/archive/lovelace/irrigation.yaml.j2.old b/archive/lovelace/irrigation.yaml.j2.old new file mode 100644 index 0000000..700af14 --- /dev/null +++ b/archive/lovelace/irrigation.yaml.j2.old @@ -0,0 +1,19 @@ +# lovelace_gen +{% set zones = ['1','2','3','4','5','6','7','8'] %} +icon: mdi:water +cards: + - type: vertical-stack + cards: + - type: markdown + content: > + # Irrigation + - type: horizontal-stack + cards: + {% for zone in zones %} + - type: entity-button + entity: switch.irrigation_zone_{{ zone }} + icon: mdi:water + name: {{ zone }} + tap_action: + action: toggle + {% endfor %} diff --git a/archive/lovelace/irrigation0.yaml.j2 b/archive/lovelace/irrigation0.yaml.j2 new file mode 100644 index 0000000..570d360 --- /dev/null +++ b/archive/lovelace/irrigation0.yaml.j2 @@ -0,0 +1,30 @@ +# lovelace_gen +{% set zones = { +'1':'Front Yard North', +'2':'Front Yard South', +'3':'Front Yard Beds', +'4':'Back Yard East', +'5':'Back Yard Garage', +'6':'Back Yard West', +'7':'Garden East', +'8':'Garden West' +} +%} +icon: mdi:water +panel: true +cards: + - type: 'custom:layout-card' + {# layout: #} + {# min_height: #} + min_columns: 2 + max_columns: 4 + column_width: 200px + cards: + {% for zone,name in zones.items() %} + - type: entity-button + entity: switch.irrigation_zone_{{ zone }} + icon: mdi:water + name: {{ name }} + tap_action: + action: toggle + {% endfor %} diff --git a/archive/lovelace/layout-testing.yaml b/archive/lovelace/layout-testing.yaml new file mode 100644 index 0000000..13accc6 --- /dev/null +++ b/archive/lovelace/layout-testing.yaml @@ -0,0 +1,29 @@ +path: tiles +title: Tile Test +panel: true +cards: + - type: 'custom:layout-card' + layout: auto + # min_height: + # min_columns: 2 + # max_columns: 20 + # column_width: 50px + # max_width: + # min_width: 50px + flex_grow: 5 + # gridcols: + # gridrows: + justify_content: flex-start + cards: + - type: 'custom:button-card' # + entity: sensor.outside_temperature # + name: Outside # + label: 238 McHaley + template: large_value # + - type: 'custom:button-card' # + entity: sensor.inside_temperature # + name: Inside # + label: 238 McHaley + template: large_value # + - type: weather-forecast + entity: weather.praire_city_weather diff --git a/archive/lovelace/layout.yaml b/archive/lovelace/layout.yaml new file mode 100644 index 0000000..e6c9531 --- /dev/null +++ b/archive/lovelace/layout.yaml @@ -0,0 +1,58 @@ +path: tiles +title: Tile Test +theme: Backend-selected +badges: [] +panel: true +cards: + - type: 'custom:layout-card' + column_width: 100% + layout: vertical + cards: + - type: 'custom:layout-card' + layout: grid + gridcols: 50px 50px 50px 50px 50px 50px 50px 50px 50px 50px 50px 50px 50px 50px 50px 50px + gridrows: 50px 50px 50px 50px 50px 50px 50px 50px 50px 50px 50px 50px + cards: + - type: 'custom:button-card' # + entity: sensor.outside_temperature # + gridcol: 1/3 # Your first + gridrow: 1/3 # card + name: Outside # + label: 238 McHaley + template: large_value # + - type: custom:canvas-gauge-card + entity: sensor.outside_humidity + name: Humidity (%) + card_height: 135 + gridcol: 4/7 + gridrow: 1/3 + gauge: + type: "radial-gauge" + width: 120 + height: 120 + # borderShadowWidth: 0 + # borderOuterWidth: 0 + # borderMiddleWidth: 0 + # borderInnerWidth: 0 + # minValue: 0 + # maxValue: 100 + # startAngle: 90 + # ticksAngle: 180 + # valueBox: true + # majorTicks: + # ["0", "10", "20", "30", "40", "50", "60", "70", "80", "90", "100"] + # minorTicks: 2 + # strokeTicks: true + # highlights: [{ "from": 80, "to": 100, "color": "rgba(200, 50, 50, .75)" }] + # borders: true + - type: 'custom:button-card' # + entity: sensor.inside_temperature # + gridcol: 1/3 # Your first + gridrow: 4/7 # card + name: Inside # + label: 238 McHaley + template: large_value # + # - type: weather-forecast + # entity: weather.praire_city_weather + # gridcol: 4/14 # Your first + # gridrow: 1/5 diff --git a/archive/lovelace/node-red-scheduler.yaml b/archive/lovelace/node-red-scheduler.yaml new file mode 100644 index 0000000..953603b --- /dev/null +++ b/archive/lovelace/node-red-scheduler.yaml @@ -0,0 +1,6 @@ +# Exampe Scheduler +title: Node Red Scheduler +cards: + - type: iframe + url: 'https://giskard.kebler.net:1880/endpoint/ui' + aspect_ratio: 100% \ No newline at end of file diff --git a/lovelace/node_testing.yaml b/archive/lovelace/node_testing.yaml similarity index 100% rename from lovelace/node_testing.yaml rename to archive/lovelace/node_testing.yaml diff --git a/lovelace/scheduler.yaml b/archive/lovelace/scheduler.yaml similarity index 95% rename from lovelace/scheduler.yaml rename to archive/lovelace/scheduler.yaml index 1dccc9b..ed78df5 100644 --- a/lovelace/scheduler.yaml +++ b/archive/lovelace/scheduler.yaml @@ -6,6 +6,9 @@ cards: ## Scheduler Example - type: vertical-stack cards: + - type: markdown + content: > + ## Scheduler 2 - type: entities entities: # - entity: input_datetime.test_schedule_base diff --git a/lovelace/timer.yaml b/archive/lovelace/timer.yaml similarity index 100% rename from lovelace/timer.yaml rename to archive/lovelace/timer.yaml diff --git a/lovelace/uci-lighting.yaml b/archive/lovelace/uci-lighting.yaml similarity index 100% rename from lovelace/uci-lighting.yaml rename to archive/lovelace/uci-lighting.yaml diff --git a/archive/lovelace/zone_1.yaml b/archive/lovelace/zone_1.yaml new file mode 100644 index 0000000..d0d652a --- /dev/null +++ b/archive/lovelace/zone_1.yaml @@ -0,0 +1,43 @@ +# Exampe Scheduler +title: Zone1 +cards: + - type: markdown + content: > + ## Scheduler Example + - type: vertical-stack + cards: + - type: entities + entities: + # - entity: input_datetime.irrigation_zone_1_schedule_base + - type: section + label: 'Base Time of Day' + - entity: input_number.irrigation_zone_1_schedule_base_hour + - entity: input_number.irrigation_zone_1_schedule_base_minute + - type: divider + - entity: input_select.irrigation_zone_1_schedule_repeatin + # - entity: variable.irrigation_zone_1_schedule_countdown + # - entity: variable.irrigation_zone_1_schedule_next_timestamp + - type: divider + - entity: variable.irrigation_zone_1_schedule_next + - entity: sensor.irrigation_zone_1_schedule_next + - entity: variable.irrigation_zone_1_schedule_countdown + - type: horizontal-stack + cards: + - type: entity-button + name: Enable Scheduler + icon: mdi:timer + tap_action: + action: call-service + service: script.turn_on + service_data: + entity_id: script.enable_irrigation_zone_1_schedule + entity: script.enable_irrigation_zone_1_schedule + - type: entity-button + name: Disable Scheduler + icon: mdi:timer + tap_action: + action: call-service + service: script.turn_on + service_data: + entity_id: script.disable_irrigation_zone_1_schedule + entity: script.disable_irrigation_zone_1_schedule diff --git a/archive/packages/alarm.yaml.off b/archive/packages/alarm.yaml.off new file mode 100644 index 0000000..7ea26d2 --- /dev/null +++ b/archive/packages/alarm.yaml.off @@ -0,0 +1,76 @@ +sensor: + - platform: template + sensors: + alarm_time: + friendly_name: "Time" + value_template: "{{ '%0.02d:%0.02d' | format(states('input_number.alarmhour') | int, states('input_number.alarmminutes') | int) }}" + - platform: time_date + display_options: + - 'time' + - 'date' + - 'date_time' + - 'time_date' + - 'time_utc' + +automation: + - alias: 'Wake Me Up' + trigger: + platform: template + value_template: "{{ states.sensor.time.state == states.sensor.alarm_time.state }}" + condition: + condition: or + conditions: + - condition: and + conditions: + - condition: state + entity_id: input_boolean.alarmweekday + state: 'on' + - condition: time + weekday: + - mon + - tue + - wed + - thu + - fri + - condition: state + entity_id: input_boolean.alarmweekday + state: 'off' + action: + service: notify.notify + data_template: + message: 'Good morning. Time to Wake Up!' + title: '' + +group: + default_view: + view: yes + entities: + - group.alarmclock + + alarmclock: + name: Wake Me Up + entities: + - automation.wake_me_up + - sensor.alarm_time + - input_number.alarmhour + - input_number.alarmminutes + - input_boolean.alarmweekday + +input_boolean: + alarmweekday: + name: Weekdays Only + icon: mdi:calendar + +input_number: + alarmhour: + name: Hour + icon: mdi:timer + min: 0 + max: 23 + step: 1 + alarmminutes: + name: Minutes + icon: mdi:timer + min: 0 + max: 59 + step: 5 diff --git a/packages/cameras.yaml.off b/archive/packages/cameras.yaml.off similarity index 100% rename from packages/cameras.yaml.off rename to archive/packages/cameras.yaml.off diff --git a/archive/packages/irrigation.yaml b/archive/packages/irrigation.yaml new file mode 100644 index 0000000..917b82f --- /dev/null +++ b/archive/packages/irrigation.yaml @@ -0,0 +1,78 @@ +# package of switches to test gpio pins/relays for @uci/gpio example +# gpio pins on pine64 in order for relays 1-8 const PINS = [80,73,69,230,229,75,74,70] +switch: + - platform: mqtt + name: "uci Switch 1" + state_topic: "relay/status/80" + command_topic: "relay/set/80" + state_on: "on" + state_off: "off" + payload_on: "on" + payload_off: "off" + icon: mdi:lightbulb + - platform: mqtt + name: "pine64 Switch 2" + state_topic: "relay/status/73" + command_topic: "relay/set/73" + state_on: "on" + state_off: "off" + payload_on: "on" + payload_off: "off" + icon: mdi:lightbulb + - platform: mqtt + name: "pine64 Switch 3" + state_topic: "relay/status/69" + command_topic: "relay/set/69" + state_on: "on" + state_off: "off" + payload_on: "on" + payload_off: "off" + icon: mdi:lightbulb + - platform: mqtt + name: "pine64 Switch 4" + state_topic: "relay/status/230" + command_topic: "relay/set/230" + state_on: "on" + state_off: "off" + payload_on: "on" + payload_off: "off" + icon: mdi:lightbulb + - platform: mqtt + name: "pine64 Switch 5" + state_topic: "relay/status/229" + command_topic: "relay/set/229" + state_on: "on" + state_off: "off" + payload_on: "on" + payload_off: "off" + icon: mdi:lightbulb + - platform: mqtt + name: "pine64 Switch 6" + state_topic: "relay/status/75" + command_topic: "relay/set/75" + state_on: "on" + state_off: "off" + payload_on: "on" + payload_off: "off" + icon: mdi:lightbulb + - platform: mqtt + name: "pine64 Switch 7" + state_topic: "relay/status/74" + command_topic: "relay/set/74" + state_on: "on" + state_off: "off" + payload_on: "on" + payload_off: "off" + icon: mdi:lightbulb + - platform: mqtt + name: "pine64 Switch 8" + state_topic: "relay/status/70" + command_topic: "relay/set/70" + state_on: "on" + state_off: "off" + payload_on: "on" + payload_off: "off" + icon: mdi:lightbulb + + + # copy and paste and uncomment below as a view under views: in ui-lovelace.yaml diff --git a/archive/packages/irritation-old-extras.yaml.off b/archive/packages/irritation-old-extras.yaml.off new file mode 100644 index 0000000..6009444 --- /dev/null +++ b/archive/packages/irritation-old-extras.yaml.off @@ -0,0 +1,165 @@ +# - Day +# - Every Other Day +# - Every Third Day +# - Every Week +# values: +# - 3 +# - 6 +# - 12 +# - 24 +# - 48 +# - 72 +# - 168 + +# sensor: +# - platform: template # Derived values +# sensors: +# irrigation_zone_1_schedule_delta: # computes delta based on choice +# entity_id: input_select.irrigation_zone_1_schedule_repeatin +# unit_of_measurement: 'hours' +# value_template: > +# {% for option in state_attr("input_select.irrigation_zone_1_schedule_repeatin", "options") -%} +# {% if is_state("input_select.irrigation_zone_1_schedule_repeatin", option) -%} +# {{ state_attr("input_select.irrigation_zone_1_schedule_repeatin", 'values')[loop.index - 1] }} +# {%- endif %} +# {%- endfor %} + + + + +# format the next timestamp for humans +# irrigation_zone_1_schedule_next: +# friendly_name: Next run to start at +# entity_id: variable.irrigation_zone_1_schedule_next_timestamp +# value_template: '{{ states.variable.irrigation_zone_1_schedule_next.state | int | timestamp_custom("%A, %d %h %H:%M") }}' +# irrigation_zone_1_schedule_countdown: +# friendly_name: Countdown to next run +# entity_id: variable.irrigation_zone_1_schedule_countdown +# # value_template: '{{ states.variable.irrigation_zone_1_schedule_countdown.state | int | timestamp_custom("%A, %d %h %H:%M") }}' +# value_template: >- +# {% set time = states.variable.irrigation_zone_1_schedule_countdown.state | int %} +# {% set minutes = ((time % 3600) / 60) | int %} +# {% set hours = ((time % 86400) / 3600) | int %} +# {% set days = (time / 86400) | int %} +# {{time}} seconds is {{ days }}:{{ hours }}:{{minutes}} (D:H:M) + + + +# switch: +# - platform: mqtt +# name: "Scheduler Test Solenoid" +# state_topic: "status/irrigation/zone_1" +# command_topic: "set/irrigation/zone_1" +# payload_on: "ON" +# payload_off: "OFF" +# qos: 0 +# retain: true + + +# script: +# enable_irrigation_zone_1_schedule: +# sequence: +# - event: ENABLE_SCHEDULE +# - service: variable.set_variable +# data: +# variable: irrigation_zone_1_schedule_countdown +# value_template: '{{ states.variable.irrigation_zone_1_schedule_next_timestamp.state | int - as_timestamp(now()) }}' + # value_template: "{{ as_timestamp(now()) }}" +# - service: variable.set_variable + # data: + # variable: irrigation_zone_1_schedule_dummy_device + # value: 'ON' +# - service: automation.turn_on +# entity_id: automation.irrigation_zone_1_schedule_countdown +# - service: automation.turn_on +# data: +# entity_id: automation.irrigation_zone_1_timer_zero_trigger +# disable_irrigation_zone_1_schedule: +# sequence: +# - service: automation.turn_off +# data: +# entity_id: automation.irrigation_zone_1_schedule_countdown +# - service: variable.set_variable +# data: +# variable: irrigation_zone_1_schedule_countdown +# value: 0 +# automation: +# - alias: irrigation_zone_1_schedule_countdown +# initial_state: false +# trigger: +# platform: time_pattern +# seconds: '/1' +# action: +# - service: variable.set_variable +# data: +# variable: irrigation_zone_1_schedule_countdown + # value_template: '{{ [((variable.state | int) - 1), 0] | max }}' +# - alias: irrigation_zone_1_schedule_changed +# # initial_state: false +# trigger: +# platform: state +# action: +# - service: variable.set_variable +# data: +# variable: irrigation_zone_1_schedule_countdown +# value_template: '{{ states.variable.irrigation_zone_1_schedule_next_timestamp.state | int - as_timestamp(now()) }}' + + +# - service: automation.turn_off +# data: +# entity_id: automation.irrigation_zone_1_timer_zero_trigger +# - service: variable.set_variable +# data: +# variable: irrigation_zone_1_timer_device +# value: 'OFF' + +# +# + +# - alias: irrigation_zone_1_timer_zero_trigger +# initial_state: false +# trigger: +# platform: numeric_state +# entity_id: variable.irrigation_zone_1_timer_countdown +# below: 1 +# action: +# - service: automation.turn_off +# entity_id: automation.irrigation_zone_1_timer_countdown +# - service: variable.set_variable +# data: +# variable: irrigation_zone_1_timer_device +# value: 'OFF' +# # + + + + + +# return corresponding value from input_select option of same name +# irrigation_zone_1_schedule_delta: +# entity_id: input_select.irrigation_zone_1_schedule_repeatin +# value_template: > +# {% for option in state_attr("input_select.irrigation_zone_1_schedule_repeatin", "options") -%} +# {% if is_state("input_select.irrigation_zone_1_schedule_repeatin", option) -%} +# {{ state_attr("input_select.irrigation_zone_1_schedule_repeatin", 'values')[loop.index - 1] }} +# {%- endif %} +# {%- endfor %} +# irrigation_zone_1_schedule_base_timestamp: +# entity_id: input_datetime.irrigation_zone_1_schedule_base +# value_template: > +# {{ +# as_timestamp(now()) +# - ( now().second + now().minute | int * 60 + now().hour | int * 3600 ) +# + state_attr('input_datetime.irrigation_zone_1_schedule_base','hour')|int * 3600 +# + state_attr('input_datetime.irrigation_zone_1_schedule_base','minute') * 60 +# }} + # value_template: "{{ (state_attr('input_datetime.irrigation_zone_1_schedule_base','hour')|int * 3600 + state_attr('input_datetime.irrigation_zone_1_schedule_base','minute')|int * 60) | timestamp_custom('%A, %d %h %H:%M') }}" + # value_template: > + # "{{ (state_attr('input_datetime.irrigation_zone_1_schedule_base','hour')|int * 3600 + + # state_attr('input_datetime.irrigation_zone_1_schedule_base','minute')|int * 60 + + # as_timestamp(now())) + # | timestamp_custom("%A, %d %h %H:%M") }}" + + # value_template: "{{ states(input_datetime.irrigation_zone_1_schedule_base) }}" +# {{ as_timestamp(now()) + as_timestamp(states.input_datetime.irrigation_zone_1_schedule_base) }} +# {{ as_timestamp(now()) + as_timestamp(states.input_datetime.irrigation_zone_1_schedule_base) }} diff --git a/packages/node_test.yaml b/archive/packages/node_test.yaml similarity index 100% rename from packages/node_test.yaml rename to archive/packages/node_test.yaml diff --git a/archive/packages/sprinklers.yaml.off b/archive/packages/sprinklers.yaml.off new file mode 100644 index 0000000..5c5bc5d --- /dev/null +++ b/archive/packages/sprinklers.yaml.off @@ -0,0 +1,47 @@ +homeassistant: + # Customize for Sprinklers - map relay to sprinkler name/location + customize: + switch.relay1: + friendly_name: Front Yard North + # sensor.closet_temperature: + # friendly_name: Closet Temperature (C) + # input_number.fan_on_temp: + # friendly_name: Fan On Set Temperature (C) + +# 8 Relay Board, Topic number is gpio pin +switch: + - platform: mqtt + name: "relay1" # Pine64 gpio 80 is pin 40 + state_topic: "relays/status/80" + command_topic: "relays/set/80" + state_on: "on" + state_off: "off" + payload_on: "on" + payload_off: "off" + icon: mdi:water + +# This automation script runs when a value is received via MQTT on retained topic: setTemperature +# It sets the value slider on the GUI. This slides also had its own automation when the value is changed. +automation: + # - alias: Closet Fan On Set Temperature slider + # trigger: + # platform: mqtt + # topic: 'closet/status/fan/automation/temp' + # action: + # service: input_number.set_value + # data_template: + # entity_id: input_number.fan_on_temp + # value: "{{ trigger.payload }}" + +# This second automation script runs when the target temperature slider is moved. +# It publishes its value to the same MQTT topic it is also subscribed to. + # - alias: Closet Fan Temp Slider Moved + # trigger: + # platform: state + # entity_id: input_number.fan_on_temp + # action: + # service: mqtt.publish + # data_template: + # topic: 'closet/fan/automation/temp' + # retain: true + # payload: "{{ states('input_number.fan_on_temp') }}" diff --git a/archive/packages/temp.yaml.off b/archive/packages/temp.yaml.off new file mode 100644 index 0000000..7140777 --- /dev/null +++ b/archive/packages/temp.yaml.off @@ -0,0 +1,131 @@ +- id: lounge_aircon_manual + alias: 'Lounge Room Aircon' + hide_entity: True + trigger: + platform: state + entity_id: input_select.lounge_ac_mode + action: + - service_template: > + {% if is_state('input_select.lounge_ac_mode', 'Powerful Heat') %} shell_command.lounge_ac_powerful_heat + {% elif is_state('input_select.lounge_ac_mode', 'Normal Heat') %} shell_command.lounge_ac_normal_heat + {% elif is_state('input_select.lounge_ac_mode', 'Silent Heat') %} shell_command.lounge_ac_silent_heat + {% elif is_state('input_select.lounge_ac_mode', 'Powerful Cool') %} shell_command.lounge_ac_powerful_cool + {% elif is_state('input_select.lounge_ac_mode', 'Normal Cool') %} shell_command.lounge_ac_normal_cool + {% elif is_state('input_select.lounge_ac_mode', 'Silent Cool') %} shell_command.lounge_ac_silent_cool + {% elif is_state('input_select.lounge_ac_mode', 'Dry') %} shell_command.lounge_ac_dry + {% elif is_state('input_select.lounge_ac_mode', 'Off') %} shell_command.lounge_ac_off + {% endif %} + +- id: lounge_aircon_auto_am_on + alias: 'Lounge Room Aircon Scheduled AM On' + trigger: + platform: template + value_template: "{{ states('sensor.time') == (states.input_datetime.lounge_ac_am_on_time.state[0:5]) }}" + condition: + condition: and + conditions: + - condition: state + entity_id: input_boolean.lounge_ac_am_automation # If automation is required and ... + state: 'on' + - condition: or + conditions: + - condition: state + entity_id: input_boolean.lounge_ac_workday # If workday is not tested or is a workday + state: 'off' + - condition: state + entity_id: binary_sensor.workday_sensor + state: 'on' + action: + - service: input_select.select_option + data_template: + entity_id: input_select.lounge_ac_mode + option: > + {% if states.sensor.stats_roomt_mean.state < states.input_number.lounge_ac_heat_temp_set.state %} + Normal Heat + {% elif states.sensor.stats_roomt_mean.state > states.input_number.lounge_ac_cool_temp_set.state %} + Normal Cool + {% else %} + 'Off' + {% endif %} + +- id: lounge_aircon_auto_pm_on + alias: 'Lounge Room Aircon Scheduled PM On' + trigger: + platform: template + value_template: "{{ states('sensor.time') == (states.input_datetime.lounge_ac_pm_on_time.state[0:5]) }}" + condition: + condition: and + conditions: + - condition: state + entity_id: input_boolean.lounge_ac_pm_automation # If automation is required and ... + state: 'on' + - condition: or + conditions: + - condition: state + entity_id: input_boolean.lounge_ac_workday # If workday is not tested or is a workday + state: 'off' + - condition: state + entity_id: binary_sensor.workday_sensor + state: 'on' + action: + - service: input_select.select_option + data_template: + entity_id: input_select.lounge_ac_mode + option: > + {% if states.sensor.stats_roomt_mean.state < states.input_number.lounge_ac_heat_temp_set.state %} + Normal Heat + {% elif states.sensor.stats_roomt_mean.state > states.input_number.lounge_ac_cool_temp_set.state %} + Normal Cool + {% else %} + 'Off' + {% endif %} + +- id: lounge_aircon_auto_am_off + alias: 'Lounge Room Aircon Scheduled AM Off' + trigger: + platform: template + value_template: "{{ states('sensor.time') == (states.input_datetime.lounge_ac_am_off_time.state[0:5]) }}" + condition: + condition: and + conditions: + - condition: state + entity_id: input_boolean.lounge_ac_am_automation # If automation is required and ... + state: 'on' + - condition: or + conditions: + - condition: state + entity_id: input_boolean.lounge_ac_workday # If workday is not tested or is a workday + state: 'off' + - condition: state + entity_id: binary_sensor.workday_sensor + state: 'on' + action: + - service: input_select.select_option + data_template: + entity_id: input_select.lounge_ac_mode + option: 'Off' + +- id: lounge_aircon_auto_pm_off + alias: 'Lounge Room Aircon Scheduled PM Off' + trigger: + platform: template + value_template: "{{ states('sensor.time') == (states.input_datetime.lounge_ac_pm_off_time.state[0:5]) }}" + condition: + condition: and + conditions: + - condition: state + entity_id: input_boolean.lounge_ac_pm_automation # If automation is required and ... + state: 'on' + - condition: or + conditions: + - condition: state + entity_id: input_boolean.lounge_ac_workday # If workday not tested or if workday + state: 'off' + - condition: state + entity_id: binary_sensor.workday_sensor + state: 'on' + action: + - service: input_select.select_option + data_template: + entity_id: input_select.lounge_ac_mode + option: 'Off' diff --git a/packages/scheduler.yaml b/archive/packages/test.yaml similarity index 100% rename from packages/scheduler.yaml rename to archive/packages/test.yaml diff --git a/packages/timer.yaml b/archive/packages/timer.yaml similarity index 100% rename from packages/timer.yaml rename to archive/packages/timer.yaml diff --git a/packages/uci_base_fio.yaml b/archive/packages/uci_base_fio.yaml similarity index 100% rename from packages/uci_base_fio.yaml rename to archive/packages/uci_base_fio.yaml diff --git a/packages/uci_gpio_example.yaml b/archive/packages/uci_gpio_example.yaml similarity index 100% rename from packages/uci_gpio_example.yaml rename to archive/packages/uci_gpio_example.yaml diff --git a/packages/uci_lights.yaml b/archive/packages/uci_lights.yaml similarity index 73% rename from packages/uci_lights.yaml rename to archive/packages/uci_lights.yaml index eacf3d1..64f74d0 100644 --- a/packages/uci_lights.yaml +++ b/archive/packages/uci_lights.yaml @@ -1,11 +1,11 @@ -homeassistant: - customize: - fan.fan_state: - friendly_name: 'Fan State (manual override)' - sensor.closet_temperature: - friendly_name: 'Closet Temperature (C)' - input_number.fan_on_temp: - friendly_name: 'Fan On Set Temperature (C)' +# homeassistant: +# customize: +# fan.fan_state: +# friendly_name: 'Fan State (manual override)' +# sensor.closet_temperature: +# friendly_name: 'Closet Temperature (C)' +# input_number.fan_on_temp: +# friendly_name: 'Fan On Set Temperature (C)' switch: - platform: mqtt diff --git a/archive/packages/weatherunderground.yaml b/archive/packages/weatherunderground.yaml new file mode 100644 index 0000000..2a19b40 --- /dev/null +++ b/archive/packages/weatherunderground.yaml @@ -0,0 +1,20 @@ +sensor: + - platform: wundergroundpws + api_key: d54bc75310ae4d378bc75310aead372e + pws_id: KORPRAIR9 +# lang: de + monitored_conditions: + - stationID + - solarRadiation + - obsTimeLocal + - uv + - winddir + - humidity + - dewpt + - heatIndex + - windChill + - precipTotal + - precipRate + - temp + - windGust + - windSpeed diff --git a/packages/zone1.yaml b/archive/packages/zone1.yaml similarity index 100% rename from packages/zone1.yaml rename to archive/packages/zone1.yaml diff --git a/archive/packages/zone1.yaml.org b/archive/packages/zone1.yaml.org new file mode 100644 index 0000000..1b7dc29 --- /dev/null +++ b/archive/packages/zone1.yaml.org @@ -0,0 +1,262 @@ +sensor: + - platform: template + sensors: + zone_1_timer: + value_template: '{{ "{:02d}".format(states.input_number.timer_hours_1.state|int) }}:{{ "{:02d}".format(states.input_number.timer_minutes_1.state|int) }}' + friendly_name: "Time" + last_run_zone_1: + friendly_name: "Last Run" + value_template: '{{ (as_timestamp(states.switch.zone_1.last_changed)) | timestamp_custom("%A, %d %h %H:%M") }}' + next_run_zone_1: + friendly_name: "Next Run" + value_template: '{{states.sensor.zone_1_timer_reset_sensor.state}}' + time_delta: + friendly_name: "Zone 1 Timedelta" + value_template: '{{states.input_number.repeat_1.state|int * 3600}}' + duration_1: + value_template: '{{states.input_number.duration_1.state | int}}mins' + friendly_name: "Duration" + repeat_1: + value_template: '{{ "{:02d}".format(states.input_number.repeat_1.state|int)}}hrs' + friendly_name: "Repeat in" + rain_sensor: + friendly_name: "Rainfall Threshold Sensitivity" + value_template: >- + {% if states.sensor.pws_precip_1d.state <= states.input_number.pws_precip_1d_sensitivity.state and states.sensor.pws_precip_today_metric.state <= states.input_number.pws_precip_today_metric_sensitivity.state %} + dry + {% else %} + too wet + {% endif %} + +switch mqtt: + - platform: mqtt + name: "Zone 1" + state_topic: "status/irrigation/80" + command_topic: "cmnd/irrigation/80" + payload_on: "ON" + payload_off: "OFF" + qos: 0 + retain: true + +binary_sensor mqtt: + - platform: mqtt + name: "ZONE 1" + device_class: moisture + state_topic: "cmnd/irrigation/POWER1" + payload_on: "ON" + +sensor mqtt: + - platform: mqtt + name: "Zone 1 Timer Reset Sensor" + state_topic: "cmnd/zone_1_control/TIMER" + +input_boolean: + reset_zone_1: + name: Reset Next Run Timer + initial: off + icon: mdi:lock-reset + +input_number: + timer_minutes_1: + name: "Minutes" + initial: 0 + min: 0 + max: 55 + step: 1 + icon: mdi:timer + timer_hours_1: + name: "Hour" + initial: 6 + min: 0 + max: 23 + step: 1 + icon: mdi:timer + duration_1: + name: "Set Duration" + initial: 3 + min: 0 + max: 15 + step: 1 + icon: mdi:camera-timer + repeat_1: + name: "Set Repeat" + initial: 24 + min: 0 + max: 48 + icon: mdi:repeat + pws_precip_today_metric_sensitivity: + name: "Rainfall mm Sensitivity" + initial: 0.1 + min: 0 + max: 2 + step: 0.1 + icon: mdi:contrast + pws_precip_1d_sensitivity: + name: "Rainfall Probability Sensitivity" + initial: 40 + min: 0 + max: 100 + step: 10 + icon: mdi:contrast + +group: + garden: + view: yes + control: hidden + name: "Garden" + entities: + - group.solenoids + - group.irrigation_timer_1 + - group.rain_sensor + - sensor.pws_precip_today_metric + - sensor.pws_precip_1d + - binary_sensor.zone_1 + + solenoids: + view: no + name: "Back Garden Irrigation" + icon: 'mdi:flower' + entities: + - switch.zone_1 + + + rain_sensor: + view: no + name: "Rainfall Sensitivity" + icon: mdi:contrast + entities: + - sensor.rain_sensor + - input_number.pws_precip_today_metric_sensitivity + - input_number.pws_precip_1d_sensitivity + + irrigation_timer_1: + view: no + name: "Sprinklers Zone 1" + icon: mdi:clock + entities: + - sensor.last_run_zone_1 + - sensor.next_run_zone_1 + - sensor.duration_1 + - sensor.repeat_1 + - group.setting_zone_1 + - automation.activate_zone_1_timer + + setting_zone_1: + view: no + control: hidden + name: "Settings" + icon: mdi:settings + entities: + - sensor.zone_1_timer + - input_number.timer_hours_1 + - input_number.timer_minutes_1 + - input_number.duration_1 + - input_number.repeat_1 + - input_boolean.reset_zone_1 + +automation zone_1_1: + alias: "Activate Zone 1 Timer" + trigger: + - platform: time_pattern + minutes: '/1' + condition: + condition: and + conditions: + - condition: template + value_template: '{{(as_timestamp(now()) | timestamp_custom("%A, %d %h %H:%M")) == states.sensor.next_run_zone_1.state}}' + - condition: state + entity_id: sensor.rain_sensor + state: 'dry' + action: + - service: script.turn_on + entity_id: script.activate_irrigation_zone_1 + +automation zone_1_2: + alias: "Zone 1 Active Notification" + hide_entity: False + trigger: + - platform: state + entity_id: switch.zone_1 + from: 'off' + to: 'on' + action: + - service: notify.pushbullet + data: + title: "Irrigation Zone 1" + message: "Watering has started" + +automation zone_1_3: + alias: "Zone 1 Completed Notification" + hide_entity: False + trigger: + - platform: state + entity_id: switch.zone_1 + from: 'on' + to: 'off' + action: + - service: notify.pushbullet + data: + title: "Irrigation Zone 1" + message: "Watering has completed" + +automation zone_1_4: + alias: "Zone 1 Timer Reset" + trigger: + - platform: state + entity_id: input_boolean.reset_zone_1 + from: 'off' + to: 'on' + action: + - service: mqtt.publish + data: + topic: "cmnd/zone_1_control/TIMER" + retain: 1 + payload_template: >- + {%if now().strftime("%H:%M") > states.sensor.zone_1_timer.state %} + {{(as_timestamp(now() )+24*3600 ) | timestamp_custom("%A, %d %h ")}}{{states.sensor.zone_1_timer.state}} + {%else%} + {{(as_timestamp(now() ) ) | timestamp_custom("%A, %d %h ")}}{{states.sensor.zone_1_timer.state}} + {%endif%} + - delay: + seconds: 1 + - service: input_boolean.turn_off + data: + entity_id: input_boolean.reset_zone_1 + +automation zone_1_5: + alias: "Extend Zone 1 Timer When Wet" + trigger: + - platform: time_pattern + minutes: '/1' + condition: + condition: and + conditions: + - condition: template + value_template: '{{(as_timestamp(now()) | timestamp_custom("%A, %d %h %H:%M")) == states.sensor.next_run_zone_1.state}}' + - condition: state + entity_id: sensor.rain_sensor + state: 'too wet' + action: + - service: mqtt.publish + data: + topic: "cmnd/zone_1_control/TIMER" + retain: 1 + payload_template: '{{(as_timestamp(now() )+ states.sensor.time_delta.state | int) | timestamp_custom("%A, %d %h %H:%M") }}' + +script: + activate_irrigation_zone_1: + alias: "Activate Irrigation Zone 1" + sequence: + - alias: "Switch on Zone 1" + service: switch.turn_on + entity_id: switch.zone_1 + - delay: '00:{{ states.input_number.duration_1.state | int }}:00' + - alias: "Switch off Zone 1" + service: switch.turn_off + entity_id: switch.zone_1 + - alias: "Update Next Run Time" + service: mqtt.publish + data: + topic: "cmnd/zone_1_control/TIMER" + retain: 1 + payload_template: '{{ (as_timestamp(states.switch.zone_1.last_changed)+ states.sensor.time_delta.state | int) | timestamp_custom("%A, %d %h %H:%M") }}' diff --git a/configs/auth.yaml b/configs/auth.yaml index 54a490d..180e002 100644 --- a/configs/auth.yaml +++ b/configs/auth.yaml @@ -1,4 +1,5 @@ -- type: homeassistant +# authorization settings - none needed +# - type: homeassistant # - type: trusted_networks # trusted_networks: # - 10.0.0.3 diff --git a/configs/automations.yaml b/configs/automations.yaml index adf9ab4..93d91ca 100644 --- a/configs/automations.yaml +++ b/configs/automations.yaml @@ -1,8 +1,32 @@ -- alias: Update Available Notification +# global and startup automations here +- id: 'themestartup' + alias: 'System - Set Custom Theme at Startup' trigger: - platform: state - entity_id: updater.updater + platform: homeassistant + event: start action: - service: notify.martin + service: frontend.set_theme data: - message: "Update for Home Assistant is available." + name: 'Dark Turqoise' +- alias: Restart Notification + trigger: + platform: homeassistant + event: start + action: + service: notify.pushsafer + data: + title: "Home Assistant Has Restarted" + message: "Attention [i]italic[/i] Text[br][url=https://ha.238.kebler.net/]Home Assitant 238[/url]" + target: ["26583"] + data: + icon: "20" + iconcolor: "#FF00FF" + sound: "33" + vibration: "0" + url: "https://ha.238.kebler.net/" + urltitle: "Open Home Assistant" + time2live: "10" + priority: "2" + retry: "60" + expire: "600" + answer: "1" diff --git a/configs/customize.yaml b/configs/customize.yaml index 52e266d..05487d7 100644 --- a/configs/customize.yaml +++ b/configs/customize.yaml @@ -1,2 +1,4 @@ -fan.fan_state: - friendly_name: This is a dummy entry to be overwrrten +#global friendly names and other customizations can go here +# ex: +#fan.fan_state: +# friendly_name: This is a dummy entry to be overwrrten diff --git a/configs/notify.yaml b/configs/notify.yaml new file mode 100644 index 0000000..20b8ea6 --- /dev/null +++ b/configs/notify.yaml @@ -0,0 +1,4 @@ +# push notifications services here. +- name: pushsafer + platform: pushsafer + private_key: !secret pushsafer_api_key diff --git a/configs/secrets.yaml b/configs/secrets.yaml deleted file mode 100644 index d0e011b..0000000 --- a/configs/secrets.yaml +++ /dev/null @@ -1,4 +0,0 @@ - -# Use this file to store secrets like usernames and passwords. -# Learn more at https://home-assistant.io/docs/configuration/secrets/ -some_password: welcome diff --git a/configuration.yaml b/configuration.yaml index 07825cd..8caa168 100644 --- a/configuration.yaml +++ b/configuration.yaml @@ -2,26 +2,54 @@ homeassistant: # see packages/aa_system.yaml for basic settings customize: !include configs/customize.yaml auth_providers: !include configs/auth.yaml + # mqtt: !include configs/mqtt.yaml # set by integration packages: !include_dir_named packages # discovery: + default_config: -# CONIG FILES -# group: !include configs/groups.yaml + automation: !include configs/automations.yaml +notify: !include configs/notify.yaml +# group: !include configs/groups.yaml # script: !include configs/scripts.yaml # http: !include configs/http.yaml # mqtt: !include configs/mqtt.yaml # panel_iframe: !include configs/panel.yaml +python_script: + +# see https://github.com/thomasloven/hass-browser_mod +# browser_mod: + +frontend: + themes: !include_dir_merge_named themes + # not needed browser_mod handles it + extra_module_url: + # see https://github.com/Villhellm/custom-sidebar + - /hacsfiles/custom-sidebar/custom-sidebar.js + +# lovelace_gen: + lovelace: - mode: yaml + mode: storage + dashboards: !include lovelace/dashboards.yaml + # needed for any time of day related triggers -sensor: - - platform: time_date - display_options: - - 'time' - - 'date' - - 'date_time' - - 'date_time_iso' - - 'time_date' - - 'time_utc' - - 'beat' +# sensor: +# - platform: time_date +# display_options: +# - 'time' +# - 'date' +# - 'date_time' +# - 'date_time_iso' +# - 'time_date' +# - 'time_utc' +# - 'beat' +# recorder: +# exclude: +# entities: +# - sensor.time +# - sensor.time_date +# - sensor.date +# - sensor.date_time_iso +# - sensor.date_time_iso +# - sensor.date_time_iso diff --git a/custom_components/browser_mod/__init__.py b/custom_components/browser_mod/__init__.py new file mode 100644 index 0000000..a3440d6 --- /dev/null +++ b/custom_components/browser_mod/__init__.py @@ -0,0 +1,52 @@ +import logging + +from .mod_view import setup_view +from .connection import setup_connection +from .service import setup_service +from .const import ( + DOMAIN, + DATA_DEVICES, + DATA_ALIASES, + DATA_ADDERS, + CONFIG_DEVICES, + DATA_CONFIG, + DATA_SETUP_COMPLETE, +) + +_LOGGER = logging.getLogger(__name__) + + +async def async_setup(hass, config): + + aliases = {} + for d in config[DOMAIN].get(CONFIG_DEVICES, {}): + name = config[DOMAIN][CONFIG_DEVICES][d].get("name", None) + if name: + aliases[name] = d.replace('_', '-') + + hass.data[DOMAIN] = { + DATA_DEVICES: {}, + DATA_ALIASES: aliases, + DATA_ADDERS: {}, + DATA_CONFIG: config[DOMAIN], + DATA_SETUP_COMPLETE: False, + } + + await setup_connection(hass, config) + setup_view(hass) + + async_load_platform = hass.helpers.discovery.async_load_platform + await async_load_platform("media_player", DOMAIN, {}, config) + await async_load_platform("sensor", DOMAIN, {}, config) + await async_load_platform("binary_sensor", DOMAIN, {}, config) + await async_load_platform("light", DOMAIN, {}, config) + await async_load_platform("camera", DOMAIN, {}, config) + + await setup_service(hass) + + hass.data[DOMAIN][DATA_SETUP_COMPLETE] = True + + for device in hass.data[DOMAIN][DATA_DEVICES].values(): + device.trigger_update() + + return True diff --git a/custom_components/browser_mod/__pycache__/__init__.cpython-38.pyc b/custom_components/browser_mod/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..352fd6a Binary files /dev/null and b/custom_components/browser_mod/__pycache__/__init__.cpython-38.pyc differ diff --git a/custom_components/browser_mod/__pycache__/binary_sensor.cpython-38.pyc b/custom_components/browser_mod/__pycache__/binary_sensor.cpython-38.pyc new file mode 100644 index 0000000..9132b9d Binary files /dev/null and b/custom_components/browser_mod/__pycache__/binary_sensor.cpython-38.pyc differ diff --git a/custom_components/browser_mod/__pycache__/camera.cpython-38.pyc b/custom_components/browser_mod/__pycache__/camera.cpython-38.pyc new file mode 100644 index 0000000..0c5bb9b Binary files /dev/null and b/custom_components/browser_mod/__pycache__/camera.cpython-38.pyc differ diff --git a/custom_components/browser_mod/__pycache__/connection.cpython-38.pyc b/custom_components/browser_mod/__pycache__/connection.cpython-38.pyc new file mode 100644 index 0000000..3e650c4 Binary files /dev/null and b/custom_components/browser_mod/__pycache__/connection.cpython-38.pyc differ diff --git a/custom_components/browser_mod/__pycache__/const.cpython-38.pyc b/custom_components/browser_mod/__pycache__/const.cpython-38.pyc new file mode 100644 index 0000000..09a6ba9 Binary files /dev/null and b/custom_components/browser_mod/__pycache__/const.cpython-38.pyc differ diff --git a/custom_components/browser_mod/__pycache__/helpers.cpython-38.pyc b/custom_components/browser_mod/__pycache__/helpers.cpython-38.pyc new file mode 100644 index 0000000..5c06567 Binary files /dev/null and b/custom_components/browser_mod/__pycache__/helpers.cpython-38.pyc differ diff --git a/custom_components/browser_mod/__pycache__/light.cpython-38.pyc b/custom_components/browser_mod/__pycache__/light.cpython-38.pyc new file mode 100644 index 0000000..3605dbd Binary files /dev/null and b/custom_components/browser_mod/__pycache__/light.cpython-38.pyc differ diff --git a/custom_components/browser_mod/__pycache__/media_player.cpython-38.pyc b/custom_components/browser_mod/__pycache__/media_player.cpython-38.pyc new file mode 100644 index 0000000..8a95e08 Binary files /dev/null and b/custom_components/browser_mod/__pycache__/media_player.cpython-38.pyc differ diff --git a/custom_components/browser_mod/__pycache__/mod_view.cpython-38.pyc b/custom_components/browser_mod/__pycache__/mod_view.cpython-38.pyc new file mode 100644 index 0000000..54d96ef Binary files /dev/null and b/custom_components/browser_mod/__pycache__/mod_view.cpython-38.pyc differ diff --git a/custom_components/browser_mod/__pycache__/sensor.cpython-38.pyc b/custom_components/browser_mod/__pycache__/sensor.cpython-38.pyc new file mode 100644 index 0000000..39937b1 Binary files /dev/null and b/custom_components/browser_mod/__pycache__/sensor.cpython-38.pyc differ diff --git a/custom_components/browser_mod/__pycache__/service.cpython-38.pyc b/custom_components/browser_mod/__pycache__/service.cpython-38.pyc new file mode 100644 index 0000000..5cf73bf Binary files /dev/null and b/custom_components/browser_mod/__pycache__/service.cpython-38.pyc differ diff --git a/custom_components/browser_mod/binary_sensor.py b/custom_components/browser_mod/binary_sensor.py new file mode 100644 index 0000000..5240356 --- /dev/null +++ b/custom_components/browser_mod/binary_sensor.py @@ -0,0 +1,50 @@ +import logging +from datetime import datetime + +from homeassistant.const import STATE_UNAVAILABLE, ATTR_BATTERY_CHARGING, ATTR_BATTERY_LEVEL, STATE_ON, STATE_OFF +from homeassistant.components.binary_sensor import DEVICE_CLASS_MOTION + +from .helpers import setup_platform, BrowserModEntity + +PLATFORM = 'binary_sensor' + +async def async_setup_platform(hass, config, async_add_devices, discovery_info=None): + return setup_platform(hass, config, async_add_devices, PLATFORM, BrowserModSensor) + +class BrowserModSensor(BrowserModEntity): + domain = PLATFORM + + def __init__(self, hass, connection, deviceID, alias=None): + super().__init__(hass, connection, deviceID, alias) + self.last_seen = None + + def updated(self): + self.last_seen = datetime.now() + self.schedule_update_ha_state() + + @property + def state(self): + if not self.connection.connection: + return STATE_UNAVAILABLE + if self.data.get('motion', False): + return STATE_ON + return STATE_OFF + + @property + def is_on(self): + return not self.data.get('motion', False) + + + @property + def device_class(self): + return DEVICE_CLASS_MOTION + + @property + def device_state_attributes(self): + return { + "type": "browser_mod", + "last_seen": self.last_seen, + ATTR_BATTERY_LEVEL: self.data.get('battery', None), + ATTR_BATTERY_CHARGING: self.data.get('charging', None), + **self.data + } diff --git a/custom_components/browser_mod/browser_mod.js b/custom_components/browser_mod/browser_mod.js new file mode 100644 index 0000000..6e46b9a --- /dev/null +++ b/custom_components/browser_mod/browser_mod.js @@ -0,0 +1,149 @@ +!function(e){var t={};function o(s){if(t[s])return t[s].exports;var i=t[s]={i:s,l:!1,exports:{}};return e[s].call(i.exports,i,i.exports,o),i.l=!0,i.exports}o.m=e,o.c=t,o.d=function(e,t,s){o.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:s})},o.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},o.t=function(e,t){if(1&t&&(e=o(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var s=Object.create(null);if(o.r(s),Object.defineProperty(s,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var i in e)o.d(s,i,function(t){return e[t]}.bind(null,i));return s},o.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return o.d(t,"a",t),t},o.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},o.p="",o(o.s=1)}([function(e){e.exports=JSON.parse('{"name":"browser_mod","private":true,"version":"1.2.3","description":"","scripts":{"build":"webpack","watch":"webpack --watch --mode=development","update-card-tools":"npm uninstall card-tools && npm install thomasloven/lovelace-card-tools"},"keywords":[],"author":"Thomas Lovén","license":"MIT","devDependencies":{"webpack":"^4.44.2","webpack-cli":"^3.3.12"},"dependencies":{"card-tools":"github:thomasloven/lovelace-card-tools"}}')},function(e,t,o){"use strict";o.r(t);const s="lovelace-player-device-id";function i(){if(!localStorage[s]){const e=()=>Math.floor(1e5*(1+Math.random())).toString(16).substring(1);window.fully&&"function"==typeof fully.getDeviceId?localStorage[s]=fully.getDeviceId():localStorage[s]=`${e()}${e()}-${e()}${e()}`}return localStorage[s]}let a=i();const n=e=>{null!==e&&("clear"===e?localStorage.removeItem(s):localStorage[s]=e,a=i())},r=new URLSearchParams(window.location.search);function l(){return document.querySelector("hc-main")?document.querySelector("hc-main").hass:document.querySelector("home-assistant")?document.querySelector("home-assistant").hass:void 0}function c(e){return document.querySelector("hc-main")?document.querySelector("hc-main").provideHass(e):document.querySelector("home-assistant")?document.querySelector("home-assistant").provideHass(e):void 0}function d(){var e,t=document.querySelector("hc-main");return t?((e=t._lovelaceConfig).current_view=t._lovelacePath,e):(t=(t=(t=(t=(t=(t=(t=(t=(t=document.querySelector("home-assistant"))&&t.shadowRoot)&&t.querySelector("home-assistant-main"))&&t.shadowRoot)&&t.querySelector("app-drawer-layout partial-panel-resolver"))&&t.shadowRoot||t)&&t.querySelector("ha-panel-lovelace"))&&t.shadowRoot)&&t.querySelector("hui-root"))?((e=t.lovelace).current_view=t.___curView,e):null}function u(){var e=document.querySelector("hc-main");return e=e?(e=(e=(e=e&&e.shadowRoot)&&e.querySelector("hc-lovelace"))&&e.shadowRoot)&&e.querySelector("hui-view")||e.querySelector("hui-panel-view"):(e=(e=(e=(e=(e=(e=(e=(e=(e=(e=(e=(e=document.querySelector("home-assistant"))&&e.shadowRoot)&&e.querySelector("home-assistant-main"))&&e.shadowRoot)&&e.querySelector("app-drawer-layout partial-panel-resolver"))&&e.shadowRoot||e)&&e.querySelector("ha-panel-lovelace"))&&e.shadowRoot)&&e.querySelector("hui-root"))&&e.shadowRoot)&&e.querySelector("ha-app-layout"))&&e.querySelector("#view"))&&e.firstElementChild}async function h(){if(customElements.get("hui-view"))return!0;await customElements.whenDefined("partial-panel-resolver");const e=document.createElement("partial-panel-resolver");if(e.hass={panels:[{url_path:"tmp",component_name:"lovelace"}]},e._updateRoutes(),await e.routerOptions.routes.tmp.load(),!customElements.get("ha-panel-lovelace"))return!1;const t=document.createElement("ha-panel-lovelace");return t.hass=l(),void 0===t.hass&&(await new Promise(e=>{window.addEventListener("connection-status",t=>{console.log(t),e()},{once:!0})}),t.hass=l()),t.panel={config:{mode:null}},t._fetchConfig(),!0}async function p(e,t,o=!1){let s=e;"string"==typeof t&&(t=t.split(/(\$| )/)),""===t[t.length-1]&&t.pop();for(const[e,i]of t.entries())if(i.trim().length){if(!s)return null;s.localName&&s.localName.includes("-")&&await customElements.whenDefined(s.localName),s.updateComplete&&await s.updateComplete,s="$"===i?o&&e==t.length-1?[s.shadowRoot]:s.shadowRoot:o&&e==t.length-1?s.querySelectorAll(i):s.querySelector(i)}return s}async function m(e,t,o=!1,s=1e4){return Promise.race([p(e,t,o),new Promise((e,t)=>setTimeout(()=>t(new Error("timeout")),s))]).catch(e=>{if(!e.message||"timeout"!==e.message)throw e;return null})}function w(e,t,o=null){if((e=new Event(e,{bubbles:!0,cancelable:!1,composed:!0})).detail=t||{},o)o.dispatchEvent(e);else{var s=u();s&&s.dispatchEvent(e)}}r.get("deviceID")&&n(r.get("deviceID"));let y=window.cardHelpers;new Promise(async(e,t)=>{y&&e();const o=async()=>{y=await window.loadCardHelpers(),window.cardHelpers=y,e()};window.loadCardHelpers?o():window.addEventListener("load",async()=>{h(),window.loadCardHelpers&&o()})});async function _(){const e=document.querySelector("home-assistant")||document.querySelector("hc-root");w("hass-more-info",{entityId:"."},e);const t=await m(e,"$ card-tools-popup");t&&t.closeDialog()}async function v(e,t,o=!1,s={},i=!1){if(!customElements.get("card-tools-popup")){const e=customElements.get("home-assistant-main")?Object.getPrototypeOf(customElements.get("home-assistant-main")):Object.getPrototypeOf(customElements.get("hui-view")),t=e.prototype.html,o=e.prototype.css;class s extends e{static get properties(){return{open:{},large:{reflect:!0,type:Boolean},hass:{}}}updated(e){e.has("hass")&&this.card&&(this.card.hass=this.hass)}closeDialog(){this.open=!1}async _makeCard(){const e=await window.loadCardHelpers();this.card=await e.createCardElement(this._card),this.card.hass=this.hass,this.requestUpdate()}async _applyStyles(){let e=await m(this,"$ ha-dialog");customElements.whenDefined("card-mod").then(async()=>{if(!e)return;customElements.get("card-mod").applyToElement(e,"more-info",this._style,{config:this._card},[],!1)})}async showDialog(e,t,o=!1,s={},i=!1){this.title=e,this._card=t,this.large=o,this._style=s,this.fullscreen=!!i,this._makeCard(),await this.updateComplete,this.open=!0,await this._applyStyles()}_enlarge(){this.large=!this.large}render(){return this.open?t` + + ${this.fullscreen?t`
`:t` + + + + +
+ ${this.title} +
+
+ `} +
+ ${this.card} +
+
+ `:t``}static get styles(){return o` + ha-dialog { + --mdc-dialog-min-width: 400px; + --mdc-dialog-max-width: 600px; + --mdc-dialog-heading-ink-color: var(--primary-text-color); + --mdc-dialog-content-ink-color: var(--primary-text-color); + --justify-action-buttons: space-between; + } + @media all and (max-width: 450px), all and (max-height: 500px) { + ha-dialog { + --mdc-dialog-min-width: 100vw; + --mdc-dialog-max-width: 100vw; + --mdc-dialog-min-height: 100%; + --mdc-dialog-max-height: 100%; + --mdc-shape-medium: 0px; + --vertial-align-dialog: flex-end; + } + } + + app-toolbar { + flex-shrink: 0; + color: var(--primary-text-color); + background-color: var(--secondary-background-color); + } + + .main-title { + margin-left: 16px; + line-height: 1.3em; + max-height: 2.6em; + overflow: hidden; + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + text-overflow: ellipsis; + } + .content { + margin: -20px -24px; + } + + @media all and (max-width: 450px), all and (max-height: 500px) { + app-toolbar { + background-color: var(--app-header-background-color); + color: var(--app-header-text-color, white); + } + } + + @media all and (min-width: 451px) and (min-height: 501px) { + ha-dialog { + --mdc-dialog-max-width: 90vw; + } + + .content { + width: 400px; + } + :host([large]) .content { + width: calc(90vw - 48px); + } + + :host([large]) app-toolbar { + max-width: calc(90vw - 32px); + } + } + `}}customElements.define("card-tools-popup",s)}const a=document.querySelector("home-assistant")||document.querySelector("hc-root");if(!a)return;let n=await m(a,"$ card-tools-popup");if(!n){n=document.createElement("card-tools-popup");const e=a.shadowRoot.querySelector("ha-more-info-dialog");e?a.shadowRoot.insertBefore(n,e):a.shadowRoot.appendChild(n),c(n)}if(!window._moreInfoDialogListener){const e=async e=>{if(e.state&&"cardToolsPopup"in e.state)if(e.state.cardToolsPopup){const{title:t,card:o,large:s,style:i,fullscreen:a}=e.state.params;v(t,o,s,i,a)}else n.closeDialog()};window.addEventListener("popstate",e),window._moreInfoDialogListener=!0}history.replaceState({cardToolsPopup:!1},""),history.pushState({cardToolsPopup:!0,params:{title:e,card:t,large:o,style:s,fullscreen:i}},""),n.showDialog(e,t,o,s,i)}async function g(e,t=!1){const o=document.querySelector("hc-main")||document.querySelector("home-assistant");w("hass-more-info",{entityId:e},o);const s=await m(o,"$ ha-more-info-dialog");return s&&(s.large=t),s}const f=[customElements.whenDefined("home-assistant-main"),customElements.whenDefined("hui-view")];Promise.race(f).then(()=>{const e=customElements.get("home-assistant-main")?Object.getPrototypeOf(customElements.get("home-assistant-main")):Object.getPrototypeOf(customElements.get("hui-view")),t=e.prototype.html;e.prototype.css;class o extends e{setConfig(e){}render(){return t` +
+ Nothing to configure. +
+ `}}customElements.get("browser-player-editor")||(customElements.define("browser-player-editor",o),window.customCards=window.customCards||[],window.customCards.push({type:"browser-player",name:"Browser Player",preview:!0}))});const b=[customElements.whenDefined("home-assistant-main"),customElements.whenDefined("hui-view")];Promise.race(b).then(()=>{const e=customElements.get("home-assistant-main")?Object.getPrototypeOf(customElements.get("home-assistant-main")):Object.getPrototypeOf(customElements.get("hui-view")),t=e.prototype.html,o=e.prototype.css;customElements.get("browser-player")||customElements.define("browser-player",class extends e{static get properties(){return{hass:{}}}static getConfigElement(){return document.createElement("browser-player-editor")}static getStubConfig(){return{}}setConfig(e){this._config=e;for(const e of["play","pause","ended","volumechange","canplay","loadeddata"])window.browser_mod.player.addEventListener(e,()=>this.requestUpdate())}handleMute(e){window.browser_mod.player_mute()}handleVolumeChange(e){const t=parseFloat(e.target.value);window.browser_mod.player_set_volume(t)}handleMoreInfo(e){g("media_player."+window.browser_mod.entity_id)}handlePlayPause(e){window.browser_mod.player.paused?window.browser_mod.player_play():window.browser_mod.player_pause()}setDeviceID(){const e=prompt("Set deviceID",a);e!==a&&(n(e),this.requestUpdate())}render(){if(!window.browser_mod)return window.setTimeout(()=>this.requestUpdate(),100),t``;const e=window.browser_mod.player;return t` + +
+ + + + ${"stopped"===window.browser_mod.player_state?t`
`:t` + + `} + +
+ +
+ ${a} +
+ +
+ `}static get styles(){return o` + paper-icon-button[highlight] { + color: var(--accent-color); + } + .card-content { + display: flex; + justify-content: center; + } + .placeholder { + width: 24px; + padding: 8px; + } + .device-id { + opacity: 0.7; + font-size: xx-small; + margin-top: -10px; + user-select: all; + -webkit-user-select: all; + -moz-user-select: all; + -ms-user-select: all; + } + `}})});class S{async connect(){if(null!==document.querySelector("hc-main"))this._connection=l().connection;else{if(!window.hassConnection)return void window.setTimeout(()=>this._do_connect(),100);this._connection=(await window.hassConnection).conn}this._connection.subscribeMessage(e=>this.msg_callback(e),{type:"browser_mod/connect",deviceID:a}),this._hass_patched=!1,c(this)}set hass(e){if(this._hass=e,!e||this._hass_patched)return;this._hass_patched=!0;const t=e.callService;e.callService=(e,o,s)=>{if(s&&s.deviceID){s=JSON.parse(JSON.stringify(s));const e=JSON.stringify(s.deviceID).replace('"this"',`"${a}"`);s.deviceID=JSON.parse(e)}return t(e,o,s)},document.querySelector("hc-main")?document.querySelector("hc-main").hassChanged(e,e):document.querySelector("home-assistant").hassChanged(e,e)}get connected(){return void 0!==this._connection}msg_callback(e){console.log(e)}sendUpdate(e){this.connected&&this._connection.sendMessage({type:"browser_mod/update",deviceID:a,data:e})}}const k=e=>class extends e{constructor(){super(),this.player=new Audio;for(const e of["play","pause","ended","volumechange"])this.player.addEventListener(e,()=>this.player_update());window.addEventListener("click",()=>this.player.play(),{once:!0})}player_update(e){this.sendUpdate({player:{volume:this.player.volume,muted:this.player.muted,src:this.player.src,state:this.player_state}})}get player_state(){return this.player.src?this.player.ended?"stopped":this.player.paused?"paused":"playing":"stopped"}player_play(e){e&&(this.player.src=e),this.player.play()}player_pause(){this.player.pause()}player_stop(){this.player.pause(),this.player.src=null}player_set_volume(e){void 0!==e&&(this.player.volume=e)}player_mute(e){void 0===e&&(e=!this.player.muted),this.player.muted=Boolean(e)}},x=e=>class extends e{get isFully(){return void 0!==window.fully}constructor(){if(super(),this.isFully){this._fullyMotion=!1,this._motionTimeout=void 0;for(const e of["screenOn","screenOff","pluggedAC","pluggedUSB","onBatteryLevelChanged","unplugged","networkReconnect","onMotion"])window.fully.bind(e,`window.browser_mod.fully_update("${e}");`);this._keepingAlive=!1}}fully_update(e){this.isFully&&("screenOn"===e?(window.clearTimeout(this._keepAliveTimer),this._keepingAlive||this.screen_update()):"screenOff"===e?(this.screen_update(),this._keepingAlive=!1,this.config.force_stay_awake&&(this._keepAliveTimer=window.setTimeout(()=>{this._keepingAlive=!0,window.fully.turnScreenOn(),window.fully.turnScreenOff()},27e4))):"onMotion"===e&&this.fullyMotionTriggered(),this.sendUpdate({fully:{battery:window.fully.getBatteryLevel(),charging:window.fully.isPlugged(),motion:this._fullyMotion,ip:window.fully.getIp4Address()}}))}fullyMotionTriggered(){this._keepingAlive||(this._fullyMotion=!0,clearTimeout(this._motionTimeout),this._motionTimeout=setTimeout(()=>{this._fullyMotion=!1,this.fully_update()},5e3),this.fully_update())}},E=e=>class extends e{setup_camera(){console.log("Starting camera"),this._video||(this._video=document.createElement("video"),this._video.autoplay=!0,this._video.playsInline=!0,this._video.style.display="none",this._canvas=document.createElement("canvas"),this._canvas.style.display="none",document.body.appendChild(this._video),document.body.appendChild(this._canvas),navigator.mediaDevices&&(console.log("Starting devices"),navigator.mediaDevices.getUserMedia({video:!0,audio:!1}).then(e=>{this._video.srcObject=e,this._video.play(),this.update_camera()}),this._camera_framerate=2,window.addEventListener("click",()=>this._video.play(),{once:!0})))}update_camera(){this._canvas.width=this._video.videoWidth,this._canvas.height=this._video.videoHeight;this._canvas.getContext("2d").drawImage(this._video,0,0,this._video.videoWidth,this._video.videoHeight),this.sendUpdate({camera:this._canvas.toDataURL("image/jpeg")}),setTimeout(()=>this.update_camera(),Math.round(1e3/this._camera_framerate))}},q=e=>class extends e{constructor(){super(),this._blackout_panel=document.createElement("div"),this._screenSaver=void 0,this._screenSaverTimer=void 0,this._screenSaverTimeOut=0,this._screenSaver={fn:void 0,clearfn:void 0,timer:void 0,timeout:void 0,listeners:{},active:!1},this._blackout_panel.style.cssText="\n position: fixed;\n left: 0;\n top: 0;\n padding: 0;\n margin: 0;\n width: 100%;\n height: 100%;\n background: black;\n display: none;\n ",document.body.appendChild(this._blackout_panel)}screensaver_set(e,t,o){this._ss_clear(),this._screenSaver={fn:e,clearfn:t,timer:void 0,timeout:o,listeners:{},active:!1};const s=()=>this.screensaver_update();for(const e of["mousemove","mousedown","keydown","touchstart"])window.addEventListener(e,s),this._screenSaver.listeners[e]=s;this._screenSaver.timer=window.setTimeout(()=>this._ss_run(),1e3*o)}screensaver_update(){this._screenSaver.active?this.screensaver_stop():(window.clearTimeout(this._screenSaver.timer),this._screenSaver.timer=window.setTimeout(()=>this._ss_run(),1e3*this._screenSaver.timeout))}screensaver_stop(){this._ss_clear(),this._screenSaver.active=!1,this._screenSaver.clearfn&&this._screenSaver.clearfn(),this._screenSaver.timeout&&this.screensaver_set(this._screenSaver.fn,this._screenSaver.clearfn,this._screenSaver.timeout)}_ss_clear(){window.clearTimeout(this._screenSaverTimer);for(const[e,t]of Object.entries(this._screenSaver.listeners))window.removeEventListener(e,t)}_ss_run(){this._screenSaver.active=!0,this._screenSaver.fn()}do_blackout(e){this.screensaver_set(()=>{this.isFully?window.fully.turnScreenOff(!0):this._blackout_panel.style.display="block",this.screen_update()},()=>{(this._blackout_panel.style.display="block")&&(this._blackout_panel.style.display="none"),this.isFully&&!window.fully.getScreenOn()&&window.fully.turnScreenOn(),this.screen_update()},e||0)}no_blackout(){this.isFully&&window.fully.turnScreenOn(),this.screensaver_stop()}screen_update(){this.sendUpdate({screen:{blackout:this.isFully?!window.fully.getScreenOn():Boolean("block"===this._blackout_panel.style.display),brightness:this.isFully?window.fully.getScreenBrightness():void 0}})}},D=e=>class extends e{constructor(){super(),document.querySelector("home-assistant")&&document.querySelector("home-assistant").addEventListener("hass-more-info",e=>this._popup_card(e));null!==document.querySelector("hc-main")||h()}_popup_card(e){if(!d())return;if(!e.detail||!e.detail.entityId)return;const t={...d().config.popup_cards,...d().config.views[d().current_view].popup_cards}[e.detail.entityId];t&&(v(t.title,t.card,t.large||!1,t.style),window.setTimeout(()=>{w("hass-more-info",{entityID:"."},document.querySelector("home-assistant"))},50))}do_popup(e){if(!(e.title||e.auto_close||e.hide_header))return;if(!e.card)return;const t=()=>{v(e.title,e.card,e.large,e.style,e.auto_close||e.hide_header)};e.auto_close?this.screensaver_set(t,_,e.time):t()}do_close_popup(){this.screensaver_stop(),_()}do_more_info(e,t){e&&g(e,t)}do_toast(e,t){e&&w("hass-notification",{message:e,duration:parseInt(t)},document.querySelector("home-assistant"))}},O=e=>class extends e{constructor(){super(),document.addEventListener("visibilitychange",()=>this.sensor_update()),window.addEventListener("location-changed",()=>this.sensor_update()),window.setInterval(()=>this.sensor_update(),1e4)}sensor_update(){window.queueMicrotask(async()=>{const e=navigator.getBattery?await navigator.getBattery():void 0;this.sendUpdate({browser:{path:window.location.pathname,visibility:document.visibilityState,userAgent:navigator.userAgent,currentUser:this._hass&&this._hass.user&&this._hass.user.name,fullyKiosk:this.isFully,width:window.innerWidth,height:window.innerHeight,battery_level:this.isFully?window.fully.getBatteryLevel():e?100*e.level:void 0,charging:this.isFully?window.fully.isPlugged():e?e.charging:void 0}})})}do_navigate(e){e&&(history.pushState(null,"",e),w("location-changed",{},document.querySelector("home-assistant")))}};class C extends(((e,t)=>t.reduceRight((e,t)=>t(e),e))(S,[O,D,q,E,x,k])){constructor(){super(),this.entity_id=a.replace("-","_"),this.cast=null!==document.querySelector("hc-main"),this.connect();const e=o(0);console.info(`%cBROWSER_MOD ${e.version} IS INSTALLED\n %cDeviceID: ${a}`,"color: green; font-weight: bold","")}msg_callback(e){({update:e=>this.update(e),debug:e=>this.debug(e),play:e=>this.player_play(e.media_content_id),pause:e=>this.player_pause(),stop:e=>this.player_stop(),set_volume:e=>this.player_set_volume(e.volume_level),mute:e=>this.player_mute(e.mute),toast:e=>this.do_toast(e.message,e.duration),popup:e=>this.do_popup(e),"close-popup":e=>this.do_close_popup(),"more-info":e=>this.do_more_info(e.entity_id,e.large),navigate:e=>this.do_navigate(e.navigation_path),"set-theme":e=>this.set_theme(e),"lovelace-reload":e=>this.lovelace_reload(e),"window-reload":()=>window.location.reload(!1),blackout:e=>this.do_blackout(e.time?parseInt(e.time):void 0),"no-blackout":e=>{e.brightness&&this.isFully&&window.fully.setScreenBrightness(e.brightness),this.no_blackout()}})[e.command](e)}debug(e){v("deviceID",{type:"markdown",content:"# "+a}),alert(a)}set_theme(e){e.theme||(e.theme="default"),w("settheme",{theme:e.theme},document.querySelector("home-assistant"))}lovelace_reload(e){const t=u();t&&w("config-refresh",{},t)}update(e=null){e&&(e.name&&(this.entity_id=e.name.toLowerCase()),e.camera&&this.setup_camera(),this.config={...this.config,...e}),this.player_update(),this.fully_update(),this.screen_update(),this.sensor_update()}}const $=[customElements.whenDefined("home-assistant"),customElements.whenDefined("hc-main")];Promise.race($).then(()=>{window.browser_mod=window.browser_mod||new C})}]); \ No newline at end of file diff --git a/custom_components/browser_mod/camera.py b/custom_components/browser_mod/camera.py new file mode 100644 index 0000000..59baad4 --- /dev/null +++ b/custom_components/browser_mod/camera.py @@ -0,0 +1,37 @@ +import logging +from datetime import datetime +import base64 + +from homeassistant.const import STATE_UNAVAILABLE, STATE_ON, STATE_OFF, STATE_IDLE +from homeassistant.components.camera import Camera + +from .helpers import setup_platform, BrowserModEntity + +PLATFORM = 'camera' + +async def async_setup_platform(hass, config, async_add_devices, discovery_info=None): + return setup_platform(hass, config, async_add_devices, PLATFORM, BrowserModCamera) + +class BrowserModCamera(Camera, BrowserModEntity): + domain = PLATFORM + + def __init__(self, hass, connection, deviceID, alias=None): + Camera.__init__(self) + BrowserModEntity.__init__(self, hass, connection, deviceID, alias) + self.last_seen = None + + def updated(self): + if self.last_seen is None or (datetime.now() - self.last_seen).seconds > 15: + self.last_seen = datetime.now() + self.schedule_update_ha_state() + + def camera_image(self): + return base64.b64decode(self.data.split(',')[1]) + + @property + def device_state_attributes(self): + return { + "type": "browser_mod", + "deviceID": self.deviceID, + "last_seen": self.last_seen, + } diff --git a/custom_components/browser_mod/connection.py b/custom_components/browser_mod/connection.py new file mode 100644 index 0000000..964381b --- /dev/null +++ b/custom_components/browser_mod/connection.py @@ -0,0 +1,125 @@ +import logging +import voluptuous as vol + +from homeassistant.components.websocket_api import ( + websocket_command, + result_message, + event_message, + async_register_command +) + +from .const import WS_CONNECT, WS_UPDATE +from .helpers import get_devices, create_entity, get_config, is_setup_complete + +_LOGGER = logging.getLogger(__name__) + + +async def setup_connection(hass, config): + + @websocket_command({ + vol.Required("type"): WS_CONNECT, + vol.Required("deviceID"): str, + }) + def handle_connect(hass, connection, msg): + deviceID = msg["deviceID"] + + device = get_devices(hass).get(deviceID, + BrowserModConnection(hass, deviceID)) + device.connect(connection, msg["id"]) + get_devices(hass)[deviceID] = device + + connection.send_message(result_message(msg["id"])) + + @websocket_command({ + vol.Required("type"): WS_UPDATE, + vol.Required("deviceID"): str, + vol.Optional("data"): dict, + }) + def handle_update(hass, connection, msg): + devices = get_devices(hass) + deviceID = msg["deviceID"] + if deviceID in devices: + devices[deviceID].update(msg.get("data", None)) + + async_register_command(hass, handle_connect) + async_register_command(hass, handle_update) + + +class BrowserModConnection: + def __init__(self, hass, deviceID): + self.hass = hass + self.deviceID = deviceID + self.connection = [] + + self.media_player = None + self.screen = None + self.sensor = None + self.fully = None + self.camera = None + + def connect(self, connection, cid): + self.connection.append((connection, cid)) + self.trigger_update() + + def disconnect(): + self.connection.remove((connection, cid)) + + connection.subscriptions[cid] = disconnect + + def send(self, command, **kwargs): + if self.connection: + connection, cid = self.connection[-1] + connection.send_message(event_message(cid, { + "command": command, + **kwargs, + })) + + def trigger_update(self): + if is_setup_complete(self.hass): + self.send("update", **get_config(self.hass, self.deviceID)) + + def update(self, data): + if data.get('browser'): + self.sensor = self.sensor or create_entity( + self.hass, + 'sensor', + self.deviceID, + self) + if self.sensor: + self.sensor.data = data.get('browser') + + if data.get('player'): + self.media_player = self.media_player or create_entity( + self.hass, + 'media_player', + self.deviceID, + self) + if self.media_player: + self.media_player.data = data.get('player') + + if data.get('screen'): + self.screen = self.screen or create_entity( + self.hass, + 'light', + self.deviceID, + self) + if self.screen: + self.screen.data = data.get('screen') + + if data.get('fully'): + self.fully = self.fully or create_entity( + self.hass, + 'binary_sensor', + self.deviceID, + self) + if self.fully: + self.fully.data = data.get('fully') + + if data.get('camera'): + self.camera = self.camera or create_entity( + self.hass, + 'camera', + self.deviceID, + self) + if self.camera: + self.camera.data = data.get('camera') diff --git a/custom_components/browser_mod/const.py b/custom_components/browser_mod/const.py new file mode 100644 index 0000000..95a54d6 --- /dev/null +++ b/custom_components/browser_mod/const.py @@ -0,0 +1,35 @@ +DOMAIN = "browser_mod" + +FRONTEND_SCRIPT_URL = "/browser_mod.js" + +DATA_EXTRA_MODULE_URL = 'frontend_extra_module_url' + +DATA_DEVICES = "devices" +DATA_ALIASES = "aliases" +DATA_ADDERS = "adders" +DATA_CONFIG = "config" +DATA_SETUP_COMPLETE = "setup_complete" + +CONFIG_DEVICES = "devices" +CONFIG_PREFIX = "prefix" +CONFIG_DISABLE = "disable" +CONFIG_DISABLE_ALL = "all" + +WS_ROOT = DOMAIN +WS_CONNECT = "{}/connect".format(WS_ROOT) +WS_UPDATE = "{}/update".format(WS_ROOT) +WS_CAMERA = "{}/camera".format(WS_ROOT) + +USER_COMMANDS = [ + "debug", + "popup", + "close-popup", + "navigate", + "more-info", + "set-theme", + "lovelace-reload", + "window-reload", + "blackout", + "no-blackout", + "toast", + ] diff --git a/custom_components/browser_mod/helpers.py b/custom_components/browser_mod/helpers.py new file mode 100644 index 0000000..b65b5fb --- /dev/null +++ b/custom_components/browser_mod/helpers.py @@ -0,0 +1,96 @@ +import logging + +from homeassistant.helpers.entity import Entity, async_generate_entity_id + +from .const import ( + DOMAIN, + DATA_DEVICES, + DATA_ALIASES, + DATA_ADDERS, + CONFIG_DEVICES, + DATA_CONFIG, + CONFIG_PREFIX, + CONFIG_DISABLE, + CONFIG_DISABLE_ALL, + DATA_SETUP_COMPLETE, +) + +_LOGGER = logging.getLogger(__name__) + + +def get_devices(hass): + return hass.data[DOMAIN][DATA_DEVICES] + + +def get_alias(hass, deviceID): + for k, v in hass.data[DOMAIN][DATA_ALIASES].items(): + if v == deviceID: + return k + return None + + +def get_config(hass, deviceID): + config = hass.data[DOMAIN][DATA_CONFIG].get(CONFIG_DEVICES, {}) + return config.get(deviceID, config.get(deviceID.replace('-', '_'), {})) + + +def create_entity(hass, platform, deviceID, connection): + conf = get_config(hass, deviceID) + if conf and (platform in conf.get(CONFIG_DISABLE, []) + or CONFIG_DISABLE_ALL in conf.get(CONFIG_DISABLE, [])): + return None + if not conf and \ + (platform in hass.data[DOMAIN][DATA_CONFIG].get(CONFIG_DISABLE, []) + or CONFIG_DISABLE_ALL in + hass.data[DOMAIN][DATA_CONFIG].get(CONFIG_DISABLE, [])): + return None + adder = hass.data[DOMAIN][DATA_ADDERS][platform] + entity = adder(hass, deviceID, connection, get_alias(hass, deviceID)) + return entity + + +def setup_platform(hass, config, async_add_devices, platform, cls): + def adder(hass, deviceID, connection, alias=None): + entity = cls(hass, connection, deviceID, alias) + async_add_devices([entity]) + return entity + hass.data[DOMAIN][DATA_ADDERS][platform] = adder + return True + + +def is_setup_complete(hass): + return hass.data[DOMAIN][DATA_SETUP_COMPLETE] + + +class BrowserModEntity(Entity): + + def __init__(self, hass, connection, deviceID, alias=None): + self.hass = hass + self.connection = connection + self.deviceID = deviceID + self._data = {} + prefix = hass.data[DOMAIN][DATA_CONFIG].get(CONFIG_PREFIX, '') + self.entity_id = async_generate_entity_id( + self.domain+".{}", + alias or f"{prefix}{deviceID}", + hass=hass + ) + + def updated(self): + pass + + @property + def data(self): + return self._data + + @data.setter + def data(self, data): + self._data = data + self.updated() + + @property + def device_id(self): + return self.deviceID + + def send(self, command, **kwargs): + self.connection.send(command, **kwargs) diff --git a/custom_components/browser_mod/light.py b/custom_components/browser_mod/light.py new file mode 100644 index 0000000..ffd1fb8 --- /dev/null +++ b/custom_components/browser_mod/light.py @@ -0,0 +1,59 @@ +import logging +from datetime import datetime + +from homeassistant.const import STATE_UNAVAILABLE, STATE_ON, STATE_OFF +from homeassistant.components.light import LightEntity, SUPPORT_BRIGHTNESS + +from .helpers import setup_platform, BrowserModEntity + +PLATFORM = 'light' + +async def async_setup_platform(hass, config, async_add_devices, discovery_info=None): + return setup_platform(hass, config, async_add_devices, PLATFORM, BrowserModLight) + +class BrowserModLight(LightEntity, BrowserModEntity): + domain = PLATFORM + + def __init__(self, hass, connection, deviceID, alias=None): + super().__init__(hass, connection, deviceID, alias) + self.last_seen = None + + def updated(self): + self.last_seen = datetime.now() + self.schedule_update_ha_state() + + @property + def state(self): + if not self.connection.connection: + return STATE_UNAVAILABLE + if self.data.get('blackout', False): + return STATE_OFF + return STATE_ON + + @property + def is_on(self): + return not self.data.get('blackout', False) + + @property + def device_state_attributes(self): + return { + "type": "browser_mod", + "deviceID": self.deviceID, + "last_seen": self.last_seen, + } + + @property + def supported_features(self): + if self.data.get('brightness', False): + return SUPPORT_BRIGHTNESS + return 0 + + @property + def brightness(self): + return self.data.get('brightness', None) + + def turn_on(self, **kwargs): + self.connection.send("no-blackout", **kwargs) + + def turn_off(self, **kwargs): + self.connection.send("blackout") diff --git a/custom_components/browser_mod/manifest.json b/custom_components/browser_mod/manifest.json new file mode 100644 index 0000000..a559ef5 --- /dev/null +++ b/custom_components/browser_mod/manifest.json @@ -0,0 +1,8 @@ +{ + "domain": "browser_mod", + "name": "Browser mod", + "documentation": "", + "dependencies": ["websocket_api", "http"], + "codeowners": [], + "requirements": [] +} diff --git a/custom_components/browser_mod/media_player.py b/custom_components/browser_mod/media_player.py new file mode 100644 index 0000000..b9b8d6c --- /dev/null +++ b/custom_components/browser_mod/media_player.py @@ -0,0 +1,82 @@ +import logging +from homeassistant.components.media_player import ( + SUPPORT_PLAY, SUPPORT_PLAY_MEDIA, + SUPPORT_PAUSE, SUPPORT_STOP, + SUPPORT_VOLUME_SET, SUPPORT_VOLUME_MUTE, + MediaPlayerEntity, + ) +from homeassistant.const import ( + STATE_UNAVAILABLE, + STATE_PAUSED, + STATE_PLAYING, + STATE_IDLE, + STATE_UNKNOWN, + ) + +from .helpers import setup_platform, BrowserModEntity + +_LOGGER = logging.getLogger(__name__) + +PLATFORM = 'media_player' + +async def async_setup_platform(hass, config, async_add_devices, discovery_info=None): + return setup_platform(hass, config, async_add_devices, PLATFORM, BrowserModPlayer) + + +class BrowserModPlayer(MediaPlayerEntity, BrowserModEntity): + domain = PLATFORM + + def __init__(self, hass, connection, deviceID, alias=None): + super().__init__(hass, connection, deviceID, alias) + self.last_seen = None + + def updated(self): + self.schedule_update_ha_state() + + @property + def device_state_attributes(self): + return { + "type": "browser_mod", + "deviceID": self.deviceID, + } + + @property + def state(self): + if not self.connection.connection: + return STATE_UNAVAILABLE + state = self.data.get("state", "unknown") + return { + "playing": STATE_PLAYING, + "paused": STATE_PAUSED, + "stopped": STATE_IDLE, + }.get(state, STATE_UNKNOWN) + @property + def supported_features(self): + return ( + SUPPORT_PLAY | SUPPORT_PLAY_MEDIA | + SUPPORT_PAUSE | SUPPORT_STOP | + SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE + ) + @property + def volume_level(self): + return self.data.get("volume", 0) + @property + def is_volume_muted(self): + return self.data.get("muted", False) + @property + def media_content_id(self): + return self.data.get("src", "") + + def set_volume_level(self, volume): + self.connection.send("set_volume", volume_level=volume) + def mute_volume(self, mute): + self.connection.send("mute", mute=mute) + + def play_media(self, media_type, media_id, **kwargs): + self.connection.send("play", media_content_id=media_id) + def media_play(self): + self.connection.send("play") + def media_pause(self): + self.connection.send("pause") + def media_stop(self): + self.connection.send("stop") diff --git a/custom_components/browser_mod/mod_view.py b/custom_components/browser_mod/mod_view.py new file mode 100644 index 0000000..ec84dfb --- /dev/null +++ b/custom_components/browser_mod/mod_view.py @@ -0,0 +1,36 @@ +from aiohttp import web +from homeassistant.components.http import HomeAssistantView + +from .const import FRONTEND_SCRIPT_URL, DATA_EXTRA_MODULE_URL + + +def setup_view(hass): + if DATA_EXTRA_MODULE_URL not in hass.data: + hass.data[DATA_EXTRA_MODULE_URL] = set() + url_set = hass.data[DATA_EXTRA_MODULE_URL] + url_set.add(FRONTEND_SCRIPT_URL) + + hass.http.register_view(ModView(hass, FRONTEND_SCRIPT_URL)) + +class ModView(HomeAssistantView): + + name = "browser_mod_script" + requires_auth = False + + def __init__(self, hass, url): + self.url = url + self.config_dir = hass.config.path() + + async def get(self, request): + path = "{}/custom_components/browser_mod/browser_mod.js".format(self.config_dir) + + filecontent = "" + + try: + with open(path, mode="r", encoding="utf-8", errors="ignore") as localfile: + filecontent = localfile.read() + localfile.close() + except Exception as exception: + pass + + return web.Response(body=filecontent, content_type="text/javascript", charset="utf-8") diff --git a/custom_components/browser_mod/sensor.py b/custom_components/browser_mod/sensor.py new file mode 100644 index 0000000..71c9a07 --- /dev/null +++ b/custom_components/browser_mod/sensor.py @@ -0,0 +1,37 @@ +import logging +from datetime import datetime + +from homeassistant.const import STATE_UNAVAILABLE + +from .helpers import setup_platform, BrowserModEntity + +PLATFORM = 'sensor' + +async def async_setup_platform(hass, config, async_add_devices, discovery_info=None): + return setup_platform(hass, config, async_add_devices, PLATFORM, BrowserModSensor) + +class BrowserModSensor(BrowserModEntity): + domain = PLATFORM + + def __init__(self, hass, connection, deviceID, alias=None): + super().__init__(hass, connection, deviceID, alias) + self.last_seen = None + + def updated(self): + self.last_seen = datetime.now() + self.schedule_update_ha_state() + + @property + def state(self): + if not self.connection.connection: + return STATE_UNAVAILABLE + return len(self.connection.connection) + + @property + def device_state_attributes(self): + return { + "type": "browser_mod", + "last_seen": self.last_seen, + "deviceID": self.deviceID, + **self.data + } diff --git a/custom_components/browser_mod/service.py b/custom_components/browser_mod/service.py new file mode 100644 index 0000000..9ed092e --- /dev/null +++ b/custom_components/browser_mod/service.py @@ -0,0 +1,37 @@ +import logging +from .const import DOMAIN, DATA_DEVICES, DATA_ALIASES, USER_COMMANDS + +_LOGGER = logging.getLogger(__name__) + +async def setup_service(hass): + + def handle_command(call): + command = call.data.get("command", None) + if not command: + return + + targets = call.data.get("deviceID", None) + if isinstance(targets, str): + targets = [targets] + devices = hass.data[DOMAIN][DATA_DEVICES] + aliases = hass.data[DOMAIN][DATA_ALIASES] + if not targets: + targets = devices.keys() + targets = [aliases.get(t, t) for t in targets] + + data = dict(call.data) + del data["command"] + + for t in targets: + if t in devices: + devices[t].send(command, **data) + + def command_wrapper(call): + command = call.service.replace('_','-') + call.data = dict(call.data) + call.data['command'] = command + handle_command(call) + + hass.services.async_register(DOMAIN, 'command', handle_command) + for cmd in USER_COMMANDS: + hass.services.async_register(DOMAIN, cmd.replace('-','_'), command_wrapper) diff --git a/custom_components/browser_mod/services.yaml b/custom_components/browser_mod/services.yaml new file mode 100644 index 0000000..f9225fe --- /dev/null +++ b/custom_components/browser_mod/services.yaml @@ -0,0 +1,85 @@ +command: + description: 'Send a command to a browser.' + fields: + command: + description: 'Command to send' + example: 'navigate' + deviceID: + description: 'List of receiving browsers' + example: '["99980b13-dabc9563", "office_computer"]' +debug: + description: 'On all browsers, show a popup, and a javascript alert with the current device ID.' +set_theme: + description: 'On all browsers, change the theme.' + fields: + theme: + description: 'Theme to change to' + example: '{theme: "clear_light"}' +navigate: + description: 'Navigate to a path on a browser.' + fields: + navigation_path: + description: 'Path to navigate to' + example: '/lovelace/1' + deviceID: + description: 'List of receiving browsers' + example: '["99980b13-dabc9563", "office_computer"]' +more_info: + description: 'Open the more info dialog of an entity on a browser.' + fields: + entity_id: + description: 'Entity to show more info for' + example: 'camera.front_door' + deviceID: + description: 'List of receiving browsers' + example: '["99980b13-dabc9563", "office_computer"]' + large: + description: '(optional) Set to true to make wider' + example: 'true' +toast: + description: 'Show a toast message in the bottom left on all browsers.' + fields: + message: + description: 'Message to show' + example: 'Short message' + duration: + description: '(optional) Time in milliseconds to show message for. Set to 0 for persistent display.' + example: '10000' +popup: + description: 'Pop up a card on a browser.' + fields: + title: + description: 'Name to show in popup bar' + example: 'Popup example' + card: + description: 'YAML config for card to show' + deviceID: + description: 'List of receiving browsers' + example: '["99980b13-dabc9563", "office_computer"]' + large: + description: '(optional) Set to true to make wider' + example: 'true' + hide_header: + description: '(optional) Hide header title and close button' + example: 'true' + auto_close: + description: '(optional) Close popup when mouse is moved or key is pressed. Also hides header' + example: 'true' + time: + description: "(optional) When mouse isn't moved or keys aren't pressed for this amount of seconds, reopen. Only usable with auto_close. See blackout" + example: '20' +close_popup: + description: 'Close all popups on all browsers.' +blackout: + description: 'Cover screen in black until the mouse is moved or a key is pressed.' + fields: + time: + description: '(optional) The blackout will turn on automatically after the specified number of seconds. It works kind of like a screensaver and will keep turning on until blackout is called again with time: -1.' + example: '20' +no_blackout: + description: 'Remove a blackout from a browser.' + fields: + brightness: + description: '(optional) On a Fully Kiosk Browser Plus set the screen brightness from 0 - 255.' +lovelace_reload: + description: 'Refresh the lovelace configuration.' diff --git a/custom_components/hacs/.translations/da.json b/custom_components/hacs/.translations/da.json deleted file mode 100644 index 8e45229..0000000 --- a/custom_components/hacs/.translations/da.json +++ /dev/null @@ -1,191 +0,0 @@ -{ - "common": { - "about": "Om", - "appdaemon_apps": "AppDaemon-apps", - "background_task": "Baggrundsopgave kører. Denne side vil genindlæses automatisk.", - "check_log_file": "Tjek din logfil for flere detaljer.", - "continue": "Fortsæt", - "disabled": "Deaktiveret", - "documentation": "Dokumentation", - "hacs_is_disabled": "HACS er deaktiveret", - "installed": "installeret", - "integration": "Integration", - "integrations": "Integrationer", - "manage": "administrer", - "plugin": "Plugin", - "plugins": "Plugins", - "python_script": "Python-script", - "python_scripts": "Python-scripts", - "repositories": "Repositories", - "settings": "indstillinger", - "theme": "Tema", - "themes": "Temaer", - "version": "Version" - }, - "config": { - "abort": { - "single_instance_allowed": "Kun en enkelt konfiguration af HACS er tilladt." - }, - "error": { - "auth": "Personlig adgangstoken er ikke korrekt." - }, - "step": { - "user": { - "data": { - "appdaemon": "Aktiver opdagelse & sporing af AppDaemon-apps", - "python_script": "Aktivér opdagelse og sporing af python_scripts", - "sidepanel_icon": "Sidepanelikon", - "sidepanel_title": "Sidepanelets titel", - "theme": "Aktivér opdagelse og sporing af temaer", - "token": "GitHub personlig adgangstoken" - }, - "description": "Hvis du har brug for hjælp til konfigurationen, så kig her: https:\/\/hacs.xyz\/docs\/configuration\/start" - } - } - }, - "confirm": { - "add_to_lovelace": "Er du sikker på, at du vil tilføje dette til dine Lovelace-ressourcer?", - "bg_task": "Handlingen er deaktiveret, mens baggrundsopgaver kører.", - "cancel": "Annuller", - "continue": "Er du sikker på, at du vil fortsætte?", - "delete": "Er du sikker på, at du vil slette '{Item}'?", - "delete_installed": "'{item}' er installeret. Du skal afinstallere det, før du kan slette det.", - "exist": "{item} findes allerede", - "generic": "Er du sikker?", - "home_assistant_is_restarting": "Vent venligst - Home Assistant genstarter nu.", - "home_assistant_version_not_correct": "Du kører Home Assistant version '{haversion}', men dette repository kræver som minimum version '{minversion}'.", - "no": "Nej", - "no_upgrades": "Der er ingen opdateringer tilgængelig", - "ok": "OK", - "overwrite": "Dette vil overskrive den.", - "reload_data": "Dette genindlæser data fra alle repositories, som HACS kender til. Dette vil tage nogen tid at fuldføre.", - "restart_home_assistant": "Er du sikker på, at du vil genstarte Home Assistant?", - "uninstall": "Er du sikker på, at du vil afinstallere '{Item}'?", - "upgrade_all": "Dette vil opdatere alle repositories. Sørg for at du har læst udgivelsesnoterne for dem alle, inden du fortsætter.", - "yes": "Ja" - }, - "options": { - "step": { - "user": { - "data": { - "appdaemon": "Aktiver opdagelse og sporing af AppDaemon-apps", - "country": "Filtrer med landekode.", - "debug": "Aktiver debug.", - "experimental": "Aktivér eksperimentelle funktioner", - "not_in_use": "Ikke i brug med YAML", - "release_limit": "Antal udgivelser, der skal vises.", - "sidepanel_icon": "Sidepanelikon", - "sidepanel_title": "Sidepanelets titel" - } - } - } - }, - "repository_banner": { - "config_flow": "Denne integration understøtter config_flow. Det betyder, at du nu kan gå til integrationssektionen i din brugerflade for at konfigurere den.", - "config_flow_title": "Brugerfladekonfiguration understøttet", - "integration_not_loaded": "Denne integration er ikke indlæst i Home Assistant.", - "no_restart_required": "Ingen genstart påkrævet", - "not_loaded": "Ikke indlæst", - "plugin_not_loaded": "Dette plugin er ikke føjet til dine Lovelace-ressourcer.", - "restart": "Du skal genstarte Home Assistant.", - "restart_pending": "Afventer genstart" - }, - "repository": { - "add_to_lovelace": "Tilføj til Lovelace", - "authors": "Forfattere", - "available": "Tilgængelig", - "back_to": "Tilbage til", - "changelog": "Udgivelsesnoter", - "downloads": "Downloads", - "flag_this": "Marker denne", - "frontend_version": "Frontend-version", - "github_stars": "GitHub-stjerner", - "goto_integrations": "Gå til integrationer", - "hide": "Skjul", - "hide_beta": "Skjul beta", - "install": "Installer", - "installed": "Installeret", - "lovelace_copy_example": "Kopiér eksemplet til din Udklipsholder", - "lovelace_instruction": "Tilføj dette til din lovelace-konfiguration", - "lovelace_no_js_type": "Kunne ikke afgøre typen af dette plugin, tjek venligst repository.", - "newest": "nyeste", - "note_appdaemon": "Du skal stadig føje den til filen 'apps.yaml'", - "note_installed": "Når det er installeret, vil dette være placeret i", - "note_integration": "du skal stadig føje den til filen 'configuration.yaml'", - "note_plugin": "du skal stadig tilføje det til din lovelace-konfiguration ('ui-lovelace.yaml' eller Tekstbaseret redigering)", - "open_issue": "Opret issue", - "open_plugin": "Åbn plugin", - "reinstall": "Geninstaller", - "repository": "Repository", - "restart_home_assistant": "Genstart Home Assistant", - "show_beta": "Vis beta", - "uninstall": "Afinstaller", - "update_information": "Opdater oplysninger", - "upgrade": "Opdater" - }, - "settings": { - "add_custom_repository": "TILFØJ ET BRUGERDEFINERET REPOSITORY", - "adding_new_repo": "Tilføjer nyt repository '{repo}'", - "adding_new_repo_category": "Med kategorien '{category}'.", - "bg_task_custom": "Brugerdefinerede repositories er skjult, mens opgaver i baggrunden kører.", - "category": "Kategori", - "compact_mode": "Kompakt tilstand", - "custom_repositories": "BRUGERDEFINEREDE REPOSITORIES", - "delete": "Slet", - "display": "Visning", - "grid": "Gitter", - "hacs_repo": "HACS-repo", - "hidden_repositories": "Skjulte repositories", - "missing_category": "Du skal vælge en kategori", - "open_repository": "Åbn repository", - "reload_data": "Genindlæs data", - "reload_window": "Genindlæs vindue", - "repository_configuration": "Konfiguration af repository", - "save": "Gem", - "table": "Tabel", - "table_view": "Tabelvisning", - "unhide": "Vis", - "upgrade_all": "Opdater alle" - }, - "store": { - "ascending": "stigende", - "clear_new": "Marker alle som set", - "descending": "faldende", - "last_updated": "Sidst opdateret", - "name": "Navn", - "new_repositories": "Nye repositories", - "pending_upgrades": "Afventende opdateringer", - "placeholder_search": "Indtast en søgeterm...", - "sort": "sorter", - "stars": "Stjerner", - "status": "Status" - }, - "time": { - "ago": "siden", - "day": "dag", - "days": "dage", - "hour": "time", - "hours": "timer", - "minute": "minut", - "minutes": "minutter", - "month": "måned", - "months": "måneder", - "one": "Et", - "one_day_ago": "en dag siden", - "one_hour_ago": "en time siden", - "one_minute_ago": "et minut siden", - "one_month_ago": "en måned siden", - "one_second_ago": "et sekund siden", - "one_year_ago": "et år siden", - "second": "sekund", - "seconds": "sekunder", - "x_days_ago": "{x} dage siden", - "x_hours_ago": "{x} timer siden", - "x_minutes_ago": "{x} minutter siden", - "x_months_ago": "{x} måneder siden", - "x_seconds_ago": "{x} sekunder siden", - "x_years_ago": "{x} år siden", - "year": "år", - "years": "år" - } -} \ No newline at end of file diff --git a/custom_components/hacs/.translations/de.json b/custom_components/hacs/.translations/de.json deleted file mode 100644 index f96ac00..0000000 --- a/custom_components/hacs/.translations/de.json +++ /dev/null @@ -1,199 +0,0 @@ -{ - "common": { - "about": "Über", - "appdaemon": "AppDaemon", - "appdaemon_apps": "AppDaemon Apps", - "background_task": "Hintergrundprozess läuft. Diese Seite lädt neu, sobald dieser fertig ist.", - "check_log_file": "Überprüfe die Logdatei für weitere Informationen.", - "continue": "Fortfahren", - "disabled": "Deaktiviert", - "documentation": "Dokumentation", - "hacs_is_disabled": "HACS ist deaktiviert", - "installed": "Installiert", - "integration": "Integration", - "integrations": "Integrationen", - "manage": "verwalten", - "netdaemon": "NetDaemon", - "netdaemon_apps": "NetDaemon Apps", - "plugin": "Plugin", - "plugins": "Plugins", - "python_script": "Python Skript", - "python_scripts": "Python Skripte", - "repositories": "Repositories", - "settings": "Einstellungen", - "theme": "Theme", - "themes": "Themes", - "version": "Version" - }, - "config": { - "abort": { - "single_instance_allowed": "Es ist nur eine einzelne HACS-Instanz erlaubt." - }, - "error": { - "auth": "Persönlicher Zugriffstoken ist falsch." - }, - "step": { - "user": { - "data": { - "appdaemon": "AppDaemon App-Entdeckung & Nachverfolgung aktivieren", - "netdaemon": "NetDaemon App-Entdeckung & Nachverfolgung aktivieren", - "python_script": "Python Script-Entdeckung & Nachverfolgung aktivieren", - "sidepanel_icon": "Sidepanel Symbol", - "sidepanel_title": "Sidepanel Titel", - "theme": "Theme-Entdeckung & Nachverfolgung aktivieren", - "token": "Persönlicher GitHub Zugriffstoken" - }, - "description": "Wenn du Hilfe mit den Einstellungen brauchst, kannst du hier nachsehen: https:\/\/hacs.xyz\/docs\/configuration\/start", - "title": "HACS (Home Assistant Community Store)" - } - }, - "title": "HACS (Home Assistant Community Store)" - }, - "confirm": { - "add_to_lovelace": "Möchtest du dies wirklich zu deinen Lovelace-Ressourcen hinzufügen?", - "bg_task": "Die Aktion ist deaktiviert, während Hintergrundprozesse ausgeführt werden.", - "cancel": "Abbrechen", - "continue": "Bist du dir sicher, dass du fortfahren möchtest?", - "delete": "Möchtest du '{item}' wirklich löschen?", - "delete_installed": "'{item}' ist installiert. Du musst es deinstallieren, bevor du es löschen kannst.", - "exist": "{item} existiert bereits", - "generic": "Bist du dir sicher?", - "home_assistant_is_restarting": "Bitte warte, Home Assistant wird jetzt neu gestartet.", - "home_assistant_version_not_correct": "Du benutzt die Home Assistant-Version '{haversion}', für dieses Repository muss jedoch die Mindestversion '{minversion}' installiert sein.", - "no": "Nein", - "no_upgrades": "Keine Upgrades ausstehend", - "ok": "OK", - "overwrite": "Dadurch wird die Datei überschrieben.", - "reload_data": "Hierdurch werden die Daten aller Repositories die HACS kennt neu geladen. Dies wird einige Zeit in Anspruch nehmen.", - "restart_home_assistant": "Bist du dir sicher, dass du Home Assistant neu starten möchtest?", - "uninstall": "Möchtest du '{item}' wirklich deinstallieren?", - "upgrade_all": "Hierdurch werden all diese Repositories aktualisiert. Stelle sicher, dass du die Versionshinweise vorher gelesen hast.", - "yes": "Ja" - }, - "options": { - "step": { - "user": { - "data": { - "appdaemon": "AppDaemon App-Entdeckung & Nachverfolgung aktivieren", - "country": "Nach Ländercode filtern.", - "debug": "Debug aktivieren.", - "experimental": "Experimentelle Funktionen aktivieren", - "netdaemon": "NetDaemon App-Entdeckung & Nachverfolgung aktivieren", - "not_in_use": "Nicht in Verwendung mit YAML", - "release_limit": "Anzahl anzuzeigender Releases.", - "sidepanel_icon": "Sidepanel Symbol", - "sidepanel_title": "Sidepanel Titel" - } - } - } - }, - "repository_banner": { - "config_flow": "Diese Integration unterstützt config_flow. Das bedeutet, dass du jetzt in den Integrationsbereich deiner Benutzeroberfläche wechseln kannst, um es zu konfigurieren.", - "config_flow_title": "UI-Konfiguration unterstützt", - "integration_not_loaded": "Diese Integration ist in Home Assistant nicht geladen.", - "no_restart_required": "Kein Neustart erforderlich", - "not_loaded": "Nicht geladen", - "plugin_not_loaded": "Dieses Plugin wurde deinen Lovelace-Ressourcen nicht hinzugefügt.", - "restart": "Du musst Home Assistant neu starten.", - "restart_pending": "Neustart ausstehend" - }, - "repository": { - "add_to_lovelace": "Zu Lovelace hinzufügen", - "authors": "Autoren", - "available": "Verfügbar", - "back_to": "Zurück zu", - "changelog": "Änderungsprotokoll", - "downloads": "Downloads", - "flag_this": "Melden", - "frontend_version": "Frontend Version", - "github_stars": "GitHub Sterne", - "goto_integrations": "Gehe zu Integrationen", - "hide": "Verstecken", - "hide_beta": "Beta verstecken", - "install": "Installieren", - "installed": "Installiert", - "lovelace_copy_example": "Beispiel in die Zwischenablage kopieren", - "lovelace_instruction": "Zum Hinzufügen zu deinen Lovelace-Einstellungen, benutze Folgendes", - "lovelace_no_js_type": "Konnte die Art dieses Plugins nicht erkennen. Prüfe das Repository.", - "newest": "neueste", - "note_appdaemon": "du musst es dann noch in die Datei 'apps.yaml' hinzufügen", - "note_installed": "Wird installiert nach", - "note_integration": "du musst es dann noch in die Datei 'configuration.yaml' hinzufügen", - "note_plugin": "du musst es dann noch in deine Lovelace-Einstellungen ('ui-lovelace.yaml' oder im Raw-Konfigurationseditor) hinzufügen", - "note_plugin_post_107": "Du musst es noch zu deiner Lovelace-Konfiguration hinzufügen ('configuration.yaml' oder der Ressourceneditor '\/config\/lovelace\/resources')", - "open_issue": "Problem melden", - "open_plugin": "Plugin öffnen", - "reinstall": "Neu installieren", - "repository": "Repository", - "restart_home_assistant": "Home Assistant neu starten", - "show_beta": "Beta anzeigen", - "uninstall": "Deinstallieren", - "update_information": "Aktualisierungsinformationen", - "upgrade": "Aktualisieren" - }, - "settings": { - "add_custom_repository": "BENUTZERDEFINIERTES REPOSITORY HINZUFÜGEN", - "adding_new_repo": "Hinzufügen eines neuen Repository '{repo}'", - "adding_new_repo_category": "Mit der Kategorie '{category}'.", - "bg_task_custom": "Custom Repositorys werden ausgeblendet, während Hintergrundaufgaben ausgeführt werden.", - "category": "Kategorie", - "compact_mode": "Kompakter Modus", - "custom_repositories": "BENUTZERDEFINIERTE REPOSITORIES", - "delete": "Löschen", - "display": "Anzeige", - "grid": "Gitter", - "hacs_repo": "HACS repo", - "hidden_repositories": "versteckte Repositories", - "missing_category": "Du musst eine Kategorie auswählen.", - "open_repository": "Repository öffnen", - "reload_data": "Daten neu laden", - "reload_window": "Fenster neu laden", - "repository_configuration": "Repository Konfiguration", - "save": "Speichern", - "table": "Tabelle", - "table_view": "Tabellenansicht", - "unhide": "einblenden", - "upgrade_all": "Alles aktualisieren" - }, - "store": { - "ascending": "Aufsteigend", - "clear_new": "Alle neuen Repositories als gesehen markieren", - "descending": "Absteigend", - "last_updated": "Zuletzt aktualisiert", - "name": "Name", - "new_repositories": "Neue Repositories", - "pending_upgrades": "Ausstehende Upgrades", - "placeholder_search": "Suchbegriff eingeben…", - "sort": "Sortieren", - "stars": "Sterne", - "status": "Status" - }, - "time": { - "ago": "vor", - "day": "Tag", - "days": "Tage", - "hour": "Stunde", - "hours": "Stunden", - "minute": "Minute", - "minutes": "Minuten", - "month": "Monat", - "months": "Monate", - "one": "Eins", - "one_day_ago": "vor einem Tag", - "one_hour_ago": "vor einer Stunde", - "one_minute_ago": "vor einer Minute", - "one_month_ago": "vor einem Monat", - "one_second_ago": "vor einer Sekunde", - "one_year_ago": "vor einem Jahr", - "second": "Sekunde", - "seconds": "Sekunden", - "x_days_ago": "vor {x} Tagen", - "x_hours_ago": "vor {x} Stunden", - "x_minutes_ago": "vor {x} Minuten", - "x_months_ago": "vor {x} Monaten", - "x_seconds_ago": "vor {x} Sekunden", - "x_years_ago": "vor {x} Jahren", - "year": "Jahr", - "years": "Jahre" - } -} \ No newline at end of file diff --git a/custom_components/hacs/.translations/en.json b/custom_components/hacs/.translations/en.json deleted file mode 100644 index 387c89f..0000000 --- a/custom_components/hacs/.translations/en.json +++ /dev/null @@ -1,199 +0,0 @@ -{ - "common": { - "about": "About", - "appdaemon": "AppDaemon", - "appdaemon_apps": "AppDaemon Apps", - "background_task": "Background task running, this page will reload when it's done.", - "check_log_file": "Check your log file for more details.", - "continue": "Continue", - "disabled": "Disabled", - "documentation": "Documentation", - "hacs_is_disabled": "HACS is disabled", - "installed": "installed", - "integration": "Integration", - "integrations": "Integrations", - "manage": "manage", - "netdaemon": "NetDaemon", - "netdaemon_apps": "NetDaemon Apps", - "plugin": "Plugin", - "plugins": "Plugins", - "python_script": "Python Script", - "python_scripts": "Python Scripts", - "repositories": "Repositories", - "settings": "settings", - "theme": "Theme", - "themes": "Themes", - "version": "Version" - }, - "config": { - "abort": { - "single_instance_allowed": "Only a single configuration of HACS is allowed." - }, - "error": { - "auth": "Personal Access Token is not correct." - }, - "step": { - "user": { - "data": { - "appdaemon": "Enable AppDaemon apps discovery & tracking", - "netdaemon": "Enable NetDaemon apps discovery & tracking", - "python_script": "Enable python_scripts discovery & tracking", - "sidepanel_icon": "Side panel icon", - "sidepanel_title": "Side panel title", - "theme": "Enable Themes discovery & tracking", - "token": "GitHub Personal Access Token" - }, - "description": "If you need help with the configuration have a look here: https:\/\/hacs.xyz\/docs\/configuration\/start\/", - "title": "HACS (Home Assistant Community Store)" - } - }, - "title": "HACS (Home Assistant Community Store)" - }, - "confirm": { - "add_to_lovelace": "Are you sure you want to add this to your Lovelace resources?", - "bg_task": "Action is disabled while background tasks is running.", - "cancel": "Cancel", - "continue": "Are you sure you want to continue?", - "delete": "Are you sure you want to delete \"{item}\"?", - "delete_installed": "'{item}' is installed, you need to uninstall it before you can delete it.", - "exist": "{item} already exists", - "generic": "Are you sure?", - "home_assistant_is_restarting": "Hold on, Home Assistant is now restarting.", - "home_assistant_version_not_correct": "You are running Home Assistant version '{haversion}', but this repository requires minimum version '{minversion}' to be installed.", - "no": "No", - "no_upgrades": "No upgrades pending", - "ok": "OK", - "overwrite": "Doing this will overwrite it.", - "reload_data": "This reloads the data of all repositories HACS knows about, this will take some time to finish.", - "restart_home_assistant": "Are you sure you want to restart Home Assistant?", - "uninstall": "Are you sure you want to uninstall \"{item}\"?", - "upgrade_all": "This will upgrade all of these repositories, make sure that you have read the release notes for all of them before proceeding.", - "yes": "Yes" - }, - "options": { - "step": { - "user": { - "data": { - "appdaemon": "Enable AppDaemon apps discovery & tracking", - "country": "Filter with country code.", - "debug": "Enable debug.", - "experimental": "Enable experimental features", - "netdaemon": "Enable NetDaemon apps discovery & tracking", - "not_in_use": "Not in use with YAML", - "release_limit": "Number of releases to show.", - "sidepanel_icon": "Side panel icon", - "sidepanel_title": "Side panel title" - } - } - } - }, - "repository_banner": { - "config_flow": "This integration supports config_flow, that means that you now can go to the integration section of your UI to configure it.", - "config_flow_title": "UI Configuration supported", - "integration_not_loaded": "This integration is not loaded in Home Assistant.", - "no_restart_required": "No restart required", - "not_loaded": "Not loaded", - "plugin_not_loaded": "This plugin is not added to your Lovelace resources.", - "restart": "You need to restart Home Assistant.", - "restart_pending": "Restart pending" - }, - "repository": { - "add_to_lovelace": "Add to Lovelace", - "authors": "Authors", - "available": "Available", - "back_to": "Back to", - "changelog": "Change log", - "downloads": "Downloads", - "flag_this": "Flag this", - "frontend_version": "Frontend version", - "github_stars": "GitHub stars", - "goto_integrations": "Go to integrations", - "hide": "Hide", - "hide_beta": "Hide beta", - "install": "Install", - "installed": "Installed", - "lovelace_copy_example": "Copy the example to your clipboard", - "lovelace_instruction": "When you add this to your lovelace configuration use this", - "lovelace_no_js_type": "Could not determine the type of this plugin, check the repository.", - "newest": "newest", - "note_appdaemon": "you still need to add it to your 'apps.yaml' file", - "note_installed": "When installed, this will be located in", - "note_integration": "you still need to add it to your 'configuration.yaml' file", - "note_plugin": "you still need to add it to your lovelace configuration ('ui-lovelace.yaml' or the raw UI config editor)", - "note_plugin_post_107": "you still need to add it to your lovelace configuration ('configuration.yaml' or the resource editor '\/config\/lovelace\/resources')", - "open_issue": "Open issue", - "open_plugin": "Open plugin", - "reinstall": "Reinstall", - "repository": "Repository", - "restart_home_assistant": "Restart Home Assistant", - "show_beta": "Show beta", - "uninstall": "Uninstall", - "update_information": "Update information", - "upgrade": "Update" - }, - "settings": { - "add_custom_repository": "ADD CUSTOM REPOSITORY", - "adding_new_repo": "Adding new repository '{repo}'", - "adding_new_repo_category": "With category '{category}'.", - "bg_task_custom": "Custom repositories are hidden while background tasks is running.", - "category": "Category", - "compact_mode": "Compact mode", - "custom_repositories": "CUSTOM REPOSITORIES", - "delete": "Delete", - "display": "Display", - "grid": "Grid", - "hacs_repo": "HACS repo", - "hidden_repositories": "hidden repositories", - "missing_category": "You need to select a category", - "open_repository": "Open repository", - "reload_data": "Reload data", - "reload_window": "Reload window", - "repository_configuration": "Repository configuration", - "save": "Save", - "table": "Table", - "table_view": "Table view", - "unhide": "unhide", - "upgrade_all": "Upgrade all" - }, - "store": { - "ascending": "ascending", - "clear_new": "Clear all new repositories", - "descending": "descending", - "last_updated": "Last updated", - "name": "Name", - "new_repositories": "New Repositories", - "pending_upgrades": "Pending upgrades", - "placeholder_search": "Please enter a search term...", - "sort": "sort", - "stars": "Stars", - "status": "Status" - }, - "time": { - "ago": "ago", - "day": "day", - "days": "days", - "hour": "hour", - "hours": "hours", - "minute": "minute", - "minutes": "minutes", - "month": "month", - "months": "months", - "one": "One", - "one_day_ago": "one day ago", - "one_hour_ago": "one hour ago", - "one_minute_ago": "one minute ago", - "one_month_ago": "one month ago", - "one_second_ago": "one second ago", - "one_year_ago": "one year ago", - "second": "second", - "seconds": "seconds", - "x_days_ago": "{x} days ago", - "x_hours_ago": "{x} hours ago", - "x_minutes_ago": "{x} minutes ago", - "x_months_ago": "{x} months ago", - "x_seconds_ago": "{x} seconds ago", - "x_years_ago": "{x} years ago", - "year": "year", - "years": "years" - } -} \ No newline at end of file diff --git a/custom_components/hacs/.translations/es.json b/custom_components/hacs/.translations/es.json deleted file mode 100644 index ccc56f4..0000000 --- a/custom_components/hacs/.translations/es.json +++ /dev/null @@ -1,198 +0,0 @@ -{ - "common": { - "about": "Acerca de", - "appdaemon": "AppDaemon", - "appdaemon_apps": "AppDaemon Apps", - "background_task": "Ejecutando tareas en segundo plano. Se refrescará automaticamente esta página al finalizar.", - "check_log_file": "Compruebe el archivo de registro para obtener más detalles.", - "continue": "Continuar", - "disabled": "Deshabilitado", - "documentation": "Documentación", - "hacs_is_disabled": "HACS está deshabilitado", - "installed": "instalado", - "integration": "Integración", - "integrations": "Integraciones", - "manage": "Administrar", - "netdaemon": "NetDaemon", - "netdaemon_apps": "NetDaemon Apps", - "plugin": "Plugin", - "plugins": "Plugins", - "python_script": "Python Script", - "python_scripts": "Python Scripts", - "repositories": "Repositorios", - "settings": "configuraciones", - "theme": "Tema", - "themes": "Temas", - "version": "Versión" - }, - "config": { - "abort": { - "single_instance_allowed": "Sólo se permite una única configuración de HACS." - }, - "error": { - "auth": "El token de acceso personal no es correcto." - }, - "step": { - "user": { - "data": { - "appdaemon": "Habilitar el descubrimiento y seguimiento de las aplicaciones de AppDaemon", - "netdaemon": "Habilitar el descubrimiento y seguimiento de las aplicaciones de NetDaemon", - "python_script": "Habilitar el descubrimiento y seguimiento en python_scripts", - "sidepanel_icon": "Ícono del panel lateral", - "sidepanel_title": "Título del panel lateral", - "theme": "Habilitar el descubrimiento y seguimiento de temas", - "token": "Token de acceso personal de GitHub" - }, - "description": "Si necesitas ayuda con la configuración, visita la siguiente pagina: https:\/\/hacs.xyz\/docs\/configuration\/start", - "title": "HACS (Home Assistant Community Store)" - } - }, - "title": "HACS (Home Assistant Community Store)" - }, - "confirm": { - "add_to_lovelace": "¿Está seguro de que desea agregar esto a sus recursos de Lovelace?", - "bg_task": "La acción está deshabilitada mientras se ejecutan tareas en segundo plano.", - "cancel": "Cancelar", - "continue": "Estás seguro de que quieres continuar?", - "delete": "¿Seguro que quieres eliminar '{item}'?", - "delete_installed": "'{item}' está instalado, debe desinstalarlo antes de poder eliminarlo.", - "exist": "{item} ya existe", - "generic": "¿Estás seguro?", - "home_assistant_is_restarting": "Espera, Home Assistant se está reiniciando.", - "home_assistant_version_not_correct": "Está ejecutando la versión '{haversion}' de Home Assistant, pero este repositorio requiere la instalación de la versión '{minversion}' mínima.", - "no": "No", - "no_upgrades": "No hay actualizaciones pendientes", - "ok": "OK", - "overwrite": "Si lo hace, se sobrescribirá.", - "reload_data": "Esto recarga los datos de todos los repositorios que HACS conoce, esto tardará algún tiempo en finalizar.", - "restart_home_assistant": "¿Está seguro de que desea reiniciar Home Assistant?", - "uninstall": "¿Está seguro de que desea desinstalar '{item}'?", - "upgrade_all": "Esto actualizará todos estos repositorios, asegúrese de que ha leído las notas de la versión de todos ellos antes de continuar.", - "yes": "Si" - }, - "options": { - "step": { - "user": { - "data": { - "appdaemon": "Habilitar el descubrimiento y seguimiento de las aplicaciones de AppDaemon", - "country": "Filtrar por el código de país.", - "debug": "Habilitar depuración.", - "experimental": "Habilitar funciones experimentales", - "netdaemon": "Habilitar el descubrimiento y seguimiento de las aplicaciones de NetDaemon", - "not_in_use": "No usarse con YAML", - "release_limit": "Número de versiones a mostrar.", - "sidepanel_icon": "Icono del panel lateral", - "sidepanel_title": "Título del panel lateral" - } - } - } - }, - "repository_banner": { - "config_flow": "Esta integración soporta config_flow, lo que significa que ahora puede ir a la sección de integración de su UI para configurarlo.", - "config_flow_title": "Configuración de UI soportada", - "integration_not_loaded": "Esta integración no se carga en Home Assistant.", - "no_restart_required": "No es necesario reiniciar", - "not_loaded": "No está cargado", - "plugin_not_loaded": "Este plugin no se añade a sus recursos de Lovelace.", - "restart": "Es necesario reiniciar Home Assistant.", - "restart_pending": "Reinicio pendiente" - }, - "repository": { - "add_to_lovelace": "Añadir a Lovelace", - "authors": "Autores", - "available": "Disponible", - "back_to": "Volver a", - "changelog": "Registro de cambios", - "downloads": "Descargas", - "flag_this": "Marcar esto", - "frontend_version": "Versión del frontend", - "github_stars": "Estrellas de GitHub", - "goto_integrations": "Ir a integraciones", - "hide": "Ocultar", - "hide_beta": "Ocultar beta", - "install": "Instalar", - "installed": "Instalado", - "lovelace_copy_example": "Copiar el ejemplo al clipboard", - "lovelace_instruction": "Agregue lo siguiente en su configuración de lovelace", - "lovelace_no_js_type": "No se pudo determinar el tipo de plugin, revise el repositorio.", - "newest": "más nuevo", - "note_appdaemon": "deberá agregar esto a su archivo 'apps.yaml'", - "note_installed": "Al instalarse se encontrará en", - "note_integration": "deberá agregar esto a su archivo 'configuration.yaml'", - "note_plugin": "deberá agregar esto a su configuración de lovelace ('ui-lovelace.yaml' o en el editor UI de lovelace)", - "open_issue": "Abrir issue", - "open_plugin": "Abrir plugin", - "reinstall": "Reinstalar", - "repository": "Repositorio", - "restart_home_assistant": "Reiniciar Home Assistant", - "show_beta": "Mostrar beta", - "uninstall": "Desinstalar", - "update_information": "Actualizar información", - "upgrade": "Actualizar" - }, - "settings": { - "add_custom_repository": "AGREGAR REPOSITORIO PERSONALIZADO", - "adding_new_repo": "Añadiendo un nuevo repositorio '{repo}'.", - "adding_new_repo_category": "Con la categoría '{category}'.", - "bg_task_custom": "Los repositorios personalizados están ocultos mientras se ejecutan las tareas en segundo plano.", - "category": "Categoría", - "compact_mode": "Modo compacto", - "custom_repositories": "REPOSITORIOS PERSONALIZADOS", - "delete": "Eliminar", - "display": "Mostrar", - "grid": "Cuadrícula", - "hacs_repo": "HACS repo", - "hidden_repositories": "repositorios ocultos", - "missing_category": "Es necesario seleccionar una categoría", - "open_repository": "Abrir repositorio", - "reload_data": "Recargar datos", - "reload_window": "Recargar ventana", - "repository_configuration": "Configuración del repositorio", - "save": "Grabar", - "table": "Tabla", - "table_view": "Vista de la tabla", - "unhide": "mostrar", - "upgrade_all": "Actualizar todo" - }, - "store": { - "ascending": "ascendente", - "clear_new": "Borrar todos los nuevos repositorios", - "descending": "descendente", - "last_updated": "Última actualización", - "name": "Nombre", - "new_repositories": "Nuevos Repositorios", - "pending_upgrades": "Actualizaciones pendientes", - "placeholder_search": "Por favor escriba una palabra clave de búsqueda...", - "sort": "ordenar", - "stars": "Estrellas", - "status": "Estado" - }, - "time": { - "ago": "hace", - "day": "dia", - "days": "dias", - "hour": "hora", - "hours": "horas", - "minute": "minuto", - "minutes": "minutos", - "month": "mes", - "months": "meses", - "one": "Uno", - "one_day_ago": "hace un día", - "one_hour_ago": "hace una hora", - "one_minute_ago": "hace un minuto", - "one_month_ago": "hace un mes", - "one_second_ago": "hace un segundo", - "one_year_ago": "hace un año", - "second": "segundo", - "seconds": "segundos", - "x_days_ago": "hace {x} dias", - "x_hours_ago": "hace {x} horas", - "x_minutes_ago": "hace {x} minutos", - "x_months_ago": "hace {x} meses", - "x_seconds_ago": "hace {x} segundos", - "x_years_ago": "hace {x} años", - "year": "año", - "years": "años" - } -} \ No newline at end of file diff --git a/custom_components/hacs/.translations/fr.json b/custom_components/hacs/.translations/fr.json deleted file mode 100644 index 2d2f487..0000000 --- a/custom_components/hacs/.translations/fr.json +++ /dev/null @@ -1,198 +0,0 @@ -{ - "common": { - "about": "À propos de", - "appdaemon": "AppDaemon", - "appdaemon_apps": "Applications AppDaemon", - "background_task": "Tache de fond en cours, cette page se rechargera une fois terminée", - "check_log_file": "Consultez votre fichier journal pour plus de détails.", - "continue": "Continuer", - "disabled": "Désactivé", - "documentation": "Documentation", - "hacs_is_disabled": "HACS est désactivé", - "installed": "installés", - "integration": "Intégration", - "integrations": "Intégrations", - "manage": "gérer", - "netdaemon": "NetDaemon", - "netdaemon_apps": "Applications NetDaemon", - "plugin": "Plugin", - "plugins": "Plugins", - "python_script": "Script Python", - "python_scripts": "Scripts Python", - "repositories": "Dépôts", - "settings": "paramètres", - "theme": "Thème", - "themes": "Thèmes", - "version": "Version" - }, - "config": { - "abort": { - "single_instance_allowed": "Une seule configuration de HACS est autorisée." - }, - "error": { - "auth": "Le jeton personnel d'accès est invalide." - }, - "step": { - "user": { - "data": { - "appdaemon": "Activer la découverte et le suivi des applications AppDaemon", - "netdaemon": "Activer la découverte et le suivi des applications NetDaemon", - "python_script": "Activer la découverte et le suivi des scripts python", - "sidepanel_icon": "Icône de la barre latérale", - "sidepanel_title": "Titre de la barre latérale", - "theme": "Activer la découverte et le suivi des thèmes", - "token": "Jeton d'accès personnel GitHub" - }, - "description": "Si vous avez besoin d'aide pour la configuration, jetez un œil ici: https:\/\/hacs.xyz\/docs\/configuration\/start", - "title": "HACS (Home Assistant Community Store)" - } - }, - "title": "HACS (Home Assistant Community Store)" - }, - "confirm": { - "add_to_lovelace": "Êtes-vous sûr de vouloir l'ajouter à vos ressources Lovelace ?", - "bg_task": "L'action est désactivée pendant l'exécution de tâches en arrière-plan.", - "cancel": "Annuler", - "continue": "Es-tu sur de vouloir continuer?", - "delete": "Êtes-vous sûr de vouloir supprimer '{item}'?", - "delete_installed": "'{item}' est installé, vous devez le désinstaller avant de pouvoir le supprimer.", - "exist": "{item} existe déjà", - "generic": "Êtes-vous sûr?", - "home_assistant_is_restarting": "Attendez, Home Assistant redémarre maintenant.", - "home_assistant_version_not_correct": "Vous utilisez la version '{haversion}' de Home Assistant, mais ce référentiel nécessite l'installation de la version minimale '{minversion}'.", - "no": "Non", - "no_upgrades": "Aucune mise à niveau en attente", - "ok": "OK", - "overwrite": "En faisant cela, cela l'écrasera.", - "reload_data": "Cela recharge les données de tous les dépôts dont HACS a connaissance, cela prendra un certain temps à terminer.", - "restart_home_assistant": "Voulez-vous vraiment redémarrer Home Assistant ?", - "uninstall": "Êtes-vous sûr de vouloir désinstaller '{item}'?", - "upgrade_all": "Cela mettra à niveau tous ces dépôts, assurez-vous d'avoir lu les notes de publication pour chacun d'entre eux avant de continuer.", - "yes": "Oui" - }, - "options": { - "step": { - "user": { - "data": { - "appdaemon": "Activer la découverte et le suivi des applications AppDaemon", - "country": "Filtrer par code pays.", - "debug": "Activez le débogage.", - "experimental": "Activer les fonctionnalités expérimentales", - "netdaemon": "Activer la découverte et le suivi des applications NetDaemon", - "not_in_use": "Non utilisé avec YAML", - "release_limit": "Nombre de recensés à afficher.", - "sidepanel_icon": "Icône de la barre latérale", - "sidepanel_title": "Titre de la barre latérale" - } - } - } - }, - "repository_banner": { - "config_flow": "Cette intégration prend en charge config_flow, ce qui signifie que vous pouvez maintenant accéder à la section d'intégration de votre interface utilisateur pour le configurer.", - "config_flow_title": "Configuration de l'interface utilisateur prise en charge", - "integration_not_loaded": "Cette intégration n'est pas chargée dans Home Assistant.", - "no_restart_required": "Aucun redémarrage requis", - "not_loaded": "Non chargé", - "plugin_not_loaded": "Ce plugin n'est pas ajouté à vos ressources Lovelace.", - "restart": "Vous devez redémarrer Home Assistant.", - "restart_pending": "Redémarrage en attente" - }, - "repository": { - "add_to_lovelace": "Ajouter à Lovelace", - "authors": "Auteurs", - "available": "Disponible", - "back_to": "Retour", - "changelog": "Change log", - "downloads": "Téléchargements", - "flag_this": "Marquer", - "frontend_version": "Version de l'interface", - "github_stars": "Étoiles GitHub", - "goto_integrations": "Aller aux intégrations", - "hide": "Masquer", - "hide_beta": "Masquer les bêta", - "install": "Installer", - "installed": "Installé", - "lovelace_copy_example": "Copier cet exemple dans le presse-papier", - "lovelace_instruction": "Quand vous l'ajoutez à votre configuration lovelace, utilisez", - "lovelace_no_js_type": "Impossible de déterminer le type de plugin, veuillez vérifier le dépôt", - "newest": "nouveau", - "note_appdaemon": "vous devez toujours l'ajouter à votre fichier 'apps.yaml'", - "note_installed": "Une fois installé, il se trouvera dans", - "note_integration": "Vous devez toujours l'ajouter à votre fichier 'configuration.yaml'", - "note_plugin": "Vous devez toujours l'ajouter à votre configuration lovelace ('ui-lovelace.yaml' ou l'éditeur de configuration de l'interface)", - "open_issue": "Ouvrir un ticket", - "open_plugin": "Ouvrir le plugin", - "reinstall": "Réinstaller", - "repository": "Dépôt", - "restart_home_assistant": "Redémarrer Home Assistant", - "show_beta": "Afficher les bêta", - "uninstall": "Désinstaller", - "update_information": "Mettre à jour les informations", - "upgrade": "Mettre à jour" - }, - "settings": { - "add_custom_repository": "AJOUTER UN DÉPÔT PERSONNALISÉ", - "adding_new_repo": "Ajout d'un nouveau dépôt '{repo}'", - "adding_new_repo_category": "Avec la catégorie '{category}'.", - "bg_task_custom": "Les dépôts personnalisés sont masqués pendant l'exécution de tâches en arrière-plan.", - "category": "Catégorie", - "compact_mode": "Mode compact", - "custom_repositories": "DÉPÔTS PERSONNALISÉS", - "delete": "Supprimer", - "display": "Affichage", - "grid": "Grille", - "hacs_repo": "Dépôt HACS", - "hidden_repositories": "dépôts cachés", - "missing_category": "Vous devez sélectionner une catégorie", - "open_repository": "Ouvrir dépôt", - "reload_data": "Recharger les données", - "reload_window": "Recharger la fenêtre", - "repository_configuration": "Configuration de dépôt", - "save": "Enregistrer", - "table": "Tableau", - "table_view": "Vue table", - "unhide": "Afficher", - "upgrade_all": "Tout mettre à jour" - }, - "store": { - "ascending": "ascendant", - "clear_new": "Effacer tous les nouveaux dépôts", - "descending": "descendant", - "last_updated": "Dernière mise à jour", - "name": "Nom", - "new_repositories": "Nouveaux dépôts", - "pending_upgrades": "Mises à niveau en attente", - "placeholder_search": "Veuillez entrer un terme de recherche...", - "sort": "Trier", - "stars": "Étoiles", - "status": "Statut" - }, - "time": { - "ago": "il y a", - "day": "jour", - "days": "jours", - "hour": "heure", - "hours": "heures", - "minute": "minute", - "minutes": "minutes", - "month": "mois", - "months": "mois", - "one": "Un", - "one_day_ago": "il y a 1 jour", - "one_hour_ago": "il y a 1 heure", - "one_minute_ago": "il y a 1 minute", - "one_month_ago": "il y a 1 mois", - "one_second_ago": "il y a 1 seconde", - "one_year_ago": "il y a 1 an", - "second": "seconde", - "seconds": "secondes", - "x_days_ago": "il y a {x} jours", - "x_hours_ago": "il y a {x} heures", - "x_minutes_ago": "il y a {x} minutes", - "x_months_ago": "il y a {x} mois", - "x_seconds_ago": "il y a {x} secondes", - "x_years_ago": "il y a {x} ans", - "year": "an", - "years": "ans" - } -} \ No newline at end of file diff --git a/custom_components/hacs/.translations/hu.json b/custom_components/hacs/.translations/hu.json deleted file mode 100644 index 657e261..0000000 --- a/custom_components/hacs/.translations/hu.json +++ /dev/null @@ -1,194 +0,0 @@ -{ - "common": { - "about": "Névjegy", - "appdaemon": "AppDaemon", - "appdaemon_apps": "AppDaemon Appok", - "background_task": "Éppen háttérfeladat fut, ez az oldal frissülni fog, ha kész.", - "check_log_file": "További részletekért ellenőrizd a naplófájlt.", - "continue": "Folytatás", - "disabled": "Tiltva", - "documentation": "Dokumentáció", - "hacs_is_disabled": "A HACS le van tiltva", - "installed": "Telepített", - "integration": "Integráció", - "integrations": "Integrációk", - "manage": "kezelés", - "plugin": "Bővítmény", - "plugins": "Bővítmények", - "python_script": "Python Szkript", - "python_scripts": "Python Szkriptek", - "repositories": "Tárolók", - "settings": "beállítások", - "theme": "Téma", - "themes": "Témák", - "version": "Verzió" - }, - "config": { - "abort": { - "single_instance_allowed": "Csak egyetlen HACS-konfiguráció megengedett." - }, - "error": { - "auth": "A Személyes Hozzáférési Token nem megfelelő." - }, - "step": { - "user": { - "data": { - "appdaemon": "AppDaemon appok felfedezésének és nyomon követésének engedélyezése", - "python_script": "Python szkriptek felfedezésének és nyomon követésének engedélyezése", - "sidepanel_icon": "Oldalsó panel ikon", - "sidepanel_title": "Oldalsó panel cím", - "theme": "Témák felfedezésének és nyomon követésének engedélyezése", - "token": "GitHub Személyes Hozzáférési Token" - }, - "description": "Ha segítségre van szükséged a konfigurációval kapcsolatban, akkor tekintsd meg ezt az oldalt: https:\/\/hacs.xyz\/docs\/configuration\/start", - "title": "HACS (Home Assistant Community Store)" - } - }, - "title": "HACS (Home Assistant Community Store)" - }, - "confirm": { - "add_to_lovelace": "Biztosan hozzá szeretnéd ezt adni a Lovelace erőforrásokhoz?", - "bg_task": "A művelet le van tiltva, amíg háttérfeladat fut.", - "cancel": "Mégse", - "continue": "Biztosan folytatni szeretnéd?", - "delete": "Biztosan törölni szeretnéd a(z) '{item}'-t?", - "delete_installed": "A(z) '{item}' telepítve van, törlés előtt el kell távolítanod.", - "exist": "{item} már létezik", - "generic": "Biztos vagy benne?", - "home_assistant_is_restarting": "Várj, a Home Assistant éppen újraindul.", - "home_assistant_version_not_correct": "A Home Assistant '{haversion}' verzióját használod, de ehhez a tárolóhoz legalább a(z) '{minversion}' verzióra van szükség.", - "no": "Nem", - "no_upgrades": "Nincs függőben lévő frissítés", - "ok": "OK", - "overwrite": "Ezzel felül fogod írni.", - "reload_data": "Ez újratölti a HACS által ismert összes tároló adatát, ami némi időbe telhet.", - "restart_home_assistant": "Biztosan újraindítod a Home Assistant programot?", - "uninstall": "Biztosan el szeretnéd távolítani a(z) '{item}'-t?", - "upgrade_all": "Ez frissíteni fogja az összes tárolót. Győzödj meg róla, hogy elolvastad az összes kiadási megjegyzést, mielőtt továbblépnél.", - "yes": "Igen" - }, - "options": { - "step": { - "user": { - "data": { - "appdaemon": "AppDaemon appok felfedezésének és nyomon követésének engedélyezése", - "country": "Szűrés országkóddal.", - "debug": "Hibakeresés engedélyezése.", - "experimental": "Kísérleti funkciók engedélyezése", - "not_in_use": "YAML-lel nem használható", - "release_limit": "Megjelenítendő kiadások száma.", - "sidepanel_icon": "Oldalsó panel ikon", - "sidepanel_title": "Oldalsó panel cím" - } - } - } - }, - "repository_banner": { - "config_flow": "Ez az integráció támogatja a config_flow-t, vagyis a felhasználói felületen az integrációk menüben lehet konfigurálni.", - "config_flow_title": "A felhasználói felület konfigurációja támogatott", - "integration_not_loaded": "Ez az integráció nincs betöltve a Home Assistantban.", - "no_restart_required": "Nincs szükség újraindításra", - "not_loaded": "Nincs betöltve", - "plugin_not_loaded": "Ez a bővítmény nincs hozzáadva a Lovelace erőforrásokhoz.", - "restart": "Indítsd újra a Home Assistant programot.", - "restart_pending": "Újraindítás függőben" - }, - "repository": { - "add_to_lovelace": "Hozzáadás a Lovelace-hez", - "authors": "Szerzők", - "available": "Elérhető", - "back_to": "Vissza -", - "changelog": "Változási napló", - "downloads": "Letöltések", - "flag_this": "Megjelölés", - "frontend_version": "Frontend verzió", - "github_stars": "GitHub csillagok", - "goto_integrations": "Ugrás az integrációkhoz", - "hide": "Elrejtés", - "hide_beta": "Béta elrejtése", - "install": "Telepítés", - "installed": "Telepített", - "lovelace_copy_example": "Példa másolása a vágólapra", - "lovelace_instruction": "Amikor hozzáadod ezt a lovelace konfigurációdhoz, használd ezt", - "lovelace_no_js_type": "Nem sikerült meghatározni a beépülő modul típusát, ellenőrizd a tárolót.", - "newest": "legújabb", - "note_appdaemon": "de még hozzá kell adnod az 'apps.yaml' fájlhoz", - "note_installed": "Telepítéskor a következő helyre kerül:", - "note_integration": "de még hozzá kell adnod a 'configuration.yaml' fájlhoz", - "note_plugin": "de még hozzá kell adnod a lovelace konfigurációhoz (az 'ui-lovelace.yaml' fájlban vagy a Lovelace felületen a konfiguráció szerkesztőben)", - "open_issue": "Probléma jelentése", - "open_plugin": "Bővítmény megnyitása", - "reinstall": "Újratelepítés", - "repository": "Tároló", - "restart_home_assistant": "Home Assistant újraindítása", - "show_beta": "Béta megjelenítése", - "uninstall": "Eltávolítás", - "update_information": "Frissítési információk", - "upgrade": "Frissítés" - }, - "settings": { - "add_custom_repository": "EGYÉNI TÁROLÓ HOZZÁADÁSA", - "adding_new_repo": "Új tároló hozzáadása '{repo}'", - "adding_new_repo_category": "A '{category}' kategóriával.", - "bg_task_custom": "Az egyéni tárolók rejtve vannak, amíg háttérfeladat fut.", - "category": "Kategória", - "compact_mode": "Kompakt mód", - "custom_repositories": "EGYÉNI TÁROLÓK", - "delete": "Törlés", - "display": "Megjelenítés", - "grid": "Rács", - "hacs_repo": "HACS tároló", - "hidden_repositories": "rejtett tárolók", - "missing_category": "Ki kell választanod egy kategóriát", - "open_repository": "Tároló megnyitása", - "reload_data": "Adatok újratöltése", - "reload_window": "Ablak újratöltése", - "repository_configuration": "Tároló konfiguráció", - "save": "Mentés", - "table": "Táblázat", - "table_view": "Táblázat nézet", - "unhide": "felfedés", - "upgrade_all": "Minden frissítése" - }, - "store": { - "ascending": "növekvő", - "clear_new": "Új tárolók megjelölése látottként", - "descending": "csökkenő", - "last_updated": "Utolsó frissítés", - "name": "Név", - "new_repositories": "Új tárolók", - "pending_upgrades": "Függőben lévő frissítések", - "placeholder_search": "Kérlek adj meg egy keresési kifejezést...", - "sort": "rendezés", - "stars": "Csillag", - "status": "Állapot" - }, - "time": { - "ago": "ezelőtt", - "day": "nap", - "days": "nap", - "hour": "óra", - "hours": "óra", - "minute": "perc", - "minutes": "perc", - "month": "hónap", - "months": "hónap", - "one": "Egy", - "one_day_ago": "egy nappal ezelőtt", - "one_hour_ago": "egy órával ezelőtt", - "one_minute_ago": "egy perccel ezelőtt", - "one_month_ago": "egy hónappal ezelőtt", - "one_second_ago": "egy másodperccel ezelőtt", - "one_year_ago": "egy évvel ezelőtt", - "second": "másodperc", - "seconds": "másodperc", - "x_days_ago": "{x} nappal ezelőtt", - "x_hours_ago": "{x} órával ezelőtt", - "x_minutes_ago": "{x} perccel ezelőtt", - "x_months_ago": "{x} hónappal ezelőtt", - "x_seconds_ago": "{x} másodperccel ezelőtt", - "x_years_ago": "{x} évvel ezelőtt", - "year": "év", - "years": "év" - } -} \ No newline at end of file diff --git a/custom_components/hacs/.translations/it.json b/custom_components/hacs/.translations/it.json deleted file mode 100644 index fc42529..0000000 --- a/custom_components/hacs/.translations/it.json +++ /dev/null @@ -1,198 +0,0 @@ -{ - "common": { - "about": "Informazioni su", - "appdaemon": "AppDaemon", - "appdaemon_apps": "Applicazioni AppDaemon", - "background_task": "Attività in esecuzione, questa pagina sarà ricaricata al termine.", - "check_log_file": "Controlla il tuo file di registro per maggiori dettagli.", - "continue": "Continua", - "disabled": "Disabilitato", - "documentation": "Documentazione", - "hacs_is_disabled": "HACS è disabilitato", - "installed": "Installati", - "integration": "Integrazione", - "integrations": "Integrazioni", - "manage": "gestione", - "netdaemon": "NetDaemon", - "netdaemon_apps": "Applicazioni NetDaemon", - "plugin": "Plugin", - "plugins": "Plugin", - "python_script": "Script python", - "python_scripts": "Script python", - "repositories": "Repository", - "settings": "Impostazioni", - "theme": "Tema", - "themes": "Temi", - "version": "Versione" - }, - "config": { - "abort": { - "single_instance_allowed": "È consentita una sola configurazione di HACS." - }, - "error": { - "auth": "Il token di accesso personale non è corretto." - }, - "step": { - "user": { - "data": { - "appdaemon": "Abilita il rilevamento e il monitoraggio delle applicazioni AppDaemon", - "netdaemon": "Abilita il rilevamento e il monitoraggio delle applicazioni NetDaemon", - "python_script": "Abilita il rilevamento e il monitoraggio dei python_scripts", - "sidepanel_icon": "Icona nel pannello laterale", - "sidepanel_title": "Titolo nel pannello laterale", - "theme": "Abilita individuazione e tracciamento dei temi", - "token": "Token di accesso personale GitHub" - }, - "description": "Se hai bisogno di aiuto con la configurazione dai un'occhiata qui: https:\/\/hacs.xyz\/docs\/configuration\/start", - "title": "HACS (Home Assistant Community Store)" - } - }, - "title": "HACS (Home Assistant Community Store)" - }, - "confirm": { - "add_to_lovelace": "Sei sicuro di voler aggiungerlo alle tue risorse Lovelace?", - "bg_task": "L'azione è disabilitata mentre sono in esecuzione attività in background.", - "cancel": "Annulla", - "continue": "Sei sicuro di voler continuare?", - "delete": "Sei sicuro di voler disinstallare '{item}'?", - "delete_installed": "'{item}' è installato, è necessario disinstallarlo prima di poterlo eliminare.", - "exist": "{item} esiste già", - "generic": "Sei sicuro?", - "home_assistant_is_restarting": "Aspetta, Home Assistant si sta riavviando.", - "home_assistant_version_not_correct": "Stai eseguendo la versione Home Assistant '{haversion}', ma questo repository richiede l'installazione della versione minima '{minversion}'.", - "no": "No", - "no_upgrades": "Nessun aggiornamento in sospeso", - "ok": "OK", - "overwrite": "In questo modo lo sovrascriverà.", - "reload_data": "Questo ricarica i dati di tutte le repository di cui HACS è a conoscenza, ci vorrà del tempo per finire.", - "restart_home_assistant": "Sei sicuro di voler riavviare Home Assistant?", - "uninstall": "Sei sicuro di voler disinstallare '{item}'?", - "upgrade_all": "Questa azione aggiornerà tutti i repository, assicurati di aver letto le note di rilascio prima di procedere.", - "yes": "Sì" - }, - "options": { - "step": { - "user": { - "data": { - "appdaemon": "Abilita il rilevamento e il monitoraggio delle applicazioni AppDaemon", - "country": "Filtra con prefisso internazionale.", - "debug": "Abilita debug.", - "experimental": "Abilita funzionalità sperimentali", - "netdaemon": "Abilita il rilevamento e il monitoraggio delle applicazioni NetDaemon", - "not_in_use": "Non in uso con YAML", - "release_limit": "Numero di versioni da mostrare.", - "sidepanel_icon": "Icona nel pannello laterale", - "sidepanel_title": "Titolo nel pannello laterale" - } - } - } - }, - "repository_banner": { - "config_flow": "Questa integrazione supporta config_flow, questo significa che è ora possibile passare alla sezione \"IntegrazionI\" dell'interfaccia utente per la configurazione.", - "config_flow_title": "Configurazione dell'interfaccia utente supportata", - "integration_not_loaded": "Questa integrazione non è caricata in Home Assistant.", - "no_restart_required": "Non è necessario riavviare", - "not_loaded": "Non caricato", - "plugin_not_loaded": "Questo plugin non è stato aggiunto alle risorse di Lovelace.", - "restart": "È necessario riavviare Home Assistant.", - "restart_pending": "Riavvio in attesa" - }, - "repository": { - "add_to_lovelace": "Aggiungi a Lovelace", - "authors": "Autori", - "available": "Disponibile", - "back_to": "Torna a", - "changelog": "Change log", - "downloads": "Downloads", - "flag_this": "Spunta questo", - "frontend_version": "Frontend versione", - "github_stars": "GitHub stelle", - "goto_integrations": "Vai alle Integrazioni", - "hide": "Nascondi", - "hide_beta": "Nascondi beta", - "install": "Installa", - "installed": "Installato", - "lovelace_copy_example": "Copia l'esempio negli appunti", - "lovelace_instruction": "Quando lo aggiungi nella configurazione di lovelace, usa questo", - "lovelace_no_js_type": "Impossibile determinare il tipo di plugin, verificare il repository.", - "newest": "Più recente", - "note_appdaemon": "dovrai aggiungerlo nel file 'apps.yaml'", - "note_installed": "Una volta installato, si troverà in", - "note_integration": "dovrai aggiungerlo nel file 'configuration.yaml'", - "note_plugin": "devi aggiungere la configurazione nel file 'ui-lovelace.yaml' oppure via Editor RAW della UI.", - "open_issue": "Segnala anomalia", - "open_plugin": "Apri plugin", - "reinstall": "Reinstalla", - "repository": "Archivio Software (Repository)", - "restart_home_assistant": "Riavvia Home Assistant", - "show_beta": "Visualizza beta", - "uninstall": "Rimuovi", - "update_information": "Aggiorna informazioni", - "upgrade": "Aggiorna" - }, - "settings": { - "add_custom_repository": "AGGIUNGI REPOSITORY PERSONALIZZATA", - "adding_new_repo": "Aggiunta di un nuovo repository '{repo}'", - "adding_new_repo_category": "Con la categoria '{category}'.", - "bg_task_custom": "I repository personalizzati sono nascosti mentre sono in esecuzione attività in background.", - "category": "Categoria", - "compact_mode": "Modalità compatta", - "custom_repositories": "REPOSITORY PERSONALIZZATE", - "delete": "Cancella", - "display": "Visualizza", - "grid": "Griglia", - "hacs_repo": "HACS repo", - "hidden_repositories": "repository nascosti", - "missing_category": "Devi selezionare una categoria", - "open_repository": "Apri il repository", - "reload_data": "Ricarica i dati", - "reload_window": "Ricarica la finestra", - "repository_configuration": "Configurazione del repository", - "save": "Salva", - "table": "Tabella", - "table_view": "Vista tabella", - "unhide": "Mostra", - "upgrade_all": "Aggiorna tutto" - }, - "store": { - "ascending": "ascendente", - "clear_new": "Ripulisci i nuovi repository", - "descending": "discendente", - "last_updated": "Ultimo aggiornamento", - "name": "Nome", - "new_repositories": "Nuovi repository", - "pending_upgrades": "Aggiornamenti in sospeso", - "placeholder_search": "Inserire un termine di ricerca", - "sort": "Ordinare", - "stars": "Stelle", - "status": "Stato" - }, - "time": { - "ago": "fa", - "day": "giorno", - "days": "giorni", - "hour": "ora", - "hours": "ore", - "minute": "minuto", - "minutes": "minuti", - "month": "mese", - "months": "mesi", - "one": "Un", - "one_day_ago": "un giorno fa", - "one_hour_ago": "un'ora fa", - "one_minute_ago": "un minuto fa", - "one_month_ago": "un mese fa", - "one_second_ago": "un secondo fa", - "one_year_ago": "un anno fa", - "second": "secondo", - "seconds": "secondi", - "x_days_ago": "{x} giorni fa", - "x_hours_ago": "{x} ore fa", - "x_minutes_ago": "{x} minuti fa", - "x_months_ago": "{x} mesi fa", - "x_seconds_ago": "{x} secondi fa", - "x_years_ago": "{x} anni fa", - "year": "anno", - "years": "anni" - } -} \ No newline at end of file diff --git a/custom_components/hacs/.translations/nb.json b/custom_components/hacs/.translations/nb.json deleted file mode 100644 index 0a7ef22..0000000 --- a/custom_components/hacs/.translations/nb.json +++ /dev/null @@ -1,197 +0,0 @@ -{ - "common": { - "about": "Om", - "appdaemon": "AppDaemon", - "appdaemon_apps": "AppDaemon Apper", - "background_task": "Bakgrunnsoppgaven kjører. Denne siden lastes inn på nytt når den er ferdig.", - "check_log_file": "Sjekk loggfilen din for mer informasjon.", - "continue": "Fortsett", - "disabled": "Deaktivert", - "documentation": "dokumentasjon", - "hacs_is_disabled": "HACS er deaktivert", - "installed": "Installert", - "integration": "Integrasjon", - "integrations": "Integrasjoner", - "manage": "manage", - "netdaemon": "NetDaemon", - "netdaemon_apps": "NetDaemon Apper", - "plugin": "Plugin", - "plugins": "Plugins", - "python_script": "Python-skript", - "python_scripts": "Python-skript", - "repositories": "Repositories", - "settings": "Innstillinger", - "theme": "Tema", - "themes": "Temaer", - "version": "Versjon" - }, - "config": { - "abort": { - "single_instance_allowed": "Bare en konfigurasjon av HACS er tillatt." - }, - "error": { - "auth": "Personlig tilgangstoken er ikke korrekt." - }, - "step": { - "user": { - "data": { - "appdaemon": "Aktiver oppdagelse og sporing av AppDaemon-apper", - "netdaemon": "Aktiver oppdagelse og sporing av NetDaemon-apper", - "python_script": "Aktiver oppdagelse og sporing av python_scripts", - "sidepanel_icon": "Sidepanel ikon", - "sidepanel_title": "Sidepanel tittel", - "theme": "Aktiver oppdagelse og sporing av temaer", - "token": "GitHub Personal Access Token" - }, - "description": "Hvis du trenger hjelp med konfigurasjonen, ta en titt her: https:\/\/hacs.xyz\/docs\/configuration\/start", - "title": "HACS (Home Assistant Community Store)" - } - }, - "title": "HACS (Home Assistant Community Store)" - }, - "confirm": { - "add_to_lovelace": "Er du sikker på at du vil legge dette til i dine Lovelace resources?", - "bg_task": "Handlingen er deaktivert mens bakgrunnsoppgaver kjører.", - "cancel": "Avbryt", - "continue": "Er du sikker på at du vil fortsette?", - "delete": "Er du sikker på at du vil fjerne '{item}'?", - "delete_installed": "'{item}' er installert, du må avinstallere det før du kan slette det.", - "exist": "{item} eksisterer allerede", - "generic": "Er du sikker?", - "home_assistant_is_restarting": "Vent, Home Assistant starter nå på nytt.", - "home_assistant_version_not_correct": "Du kjører Home Assistant '{haversion}', men denne krever minimum versjon '{minversion}' for å bli installert.", - "no": "Nei", - "no_upgrades": "Ingen oppgraderinger tilgjengelig", - "ok": "OK", - "overwrite": "Å gjøre dette vil overskrive det.", - "reload_data": "Dette laster inn dataene til alle repositories HACS vet om, dette vil ta litt tid å fullføre.", - "restart_home_assistant": "Er du sikker på at du vil starte Home Assistant på nytt?", - "uninstall": "Er du sikker på at du vil avinstallere '{item}'?", - "upgrade_all": "Dette vil oppgradere alle disse repositorene, sørg for at du har lest utgivelses notatene for dem alle før du fortsetter.", - "yes": "Ja" - }, - "options": { - "step": { - "user": { - "data": { - "appdaemon": "Aktiver oppdagelse og sporing av AppDaemon-apper", - "country": "Filtrer med landskode.", - "debug": "Aktiver debug", - "experimental": "Aktiver eksperimentelle funksjoner", - "netdaemon": "Aktiver oppdagelse og sporing av NetDaemon-apper", - "not_in_use": "Ikke i bruk med YAML", - "release_limit": "Antall utgivelser som skal vises.", - "sidepanel_icon": "Sidepanel ikon", - "sidepanel_title": "Sidepanel tittel" - } - } - } - }, - "repository_banner": { - "config_flow": "Denne integrasjonen støtter config_flow, det betyr at du nå kan gå til integrasjoner i brukergrensesnittet for å konfigurere den.", - "config_flow_title": "UI konfigurasjon støttet", - "integration_not_loaded": "Integrasjonen er ikke lastet inn i Home Assistant.", - "no_restart_required": "Ingen omstart kreves", - "not_loaded": "Ikke lastet inn", - "plugin_not_loaded": "Denne pluginen er ikke lagt til i lovelace under \"resource\" delen av konfigurasjonen.", - "restart": "Du må restart Home Assistant", - "restart_pending": "Restart er nødvendig" - }, - "repository": { - "add_to_lovelace": "Legg til i Lovelace", - "authors": "Laget av", - "available": "Tilgjengelig", - "back_to": "Tilbake til", - "changelog": "Endringslogg", - "downloads": "Nedlastinger", - "flag_this": "Flag dette", - "frontend_version": "Frontend versjon", - "github_stars": "GitHub-stjerner", - "goto_integrations": "Gå til integrasjoner", - "hide": "Skjul", - "hide_beta": "Skjul beta", - "installed": "Installert", - "lovelace_copy_example": "Kopier eksemplet til utklippstavlen", - "lovelace_instruction": "Når du legger til dette i lovelace-konfigurasjonen din, bruk dette", - "lovelace_no_js_type": "Kunne ikke bestemme typen for denne plugin, sjekk repository.", - "newest": "Nyeste", - "note_appdaemon": "du må fortsatt legge den til i 'apps.yaml' filen", - "note_installed": "Når det er installert, vil dette ligge i", - "note_integration": "du må fortsatt legge den til 'configuration.yaml' filen", - "note_plugin": "du må fortsatt legge den til i lovelace-konfigurasjonen ('ui-lovelace.yaml' eller den rå UI-konfigurasjonsredigereren)", - "open_issue": "Meld et problem", - "open_plugin": "Åpne plugin", - "reinstall": "Installer på nytt", - "repository": "Repository", - "restart_home_assistant": "Start Home Assistant på nytt", - "show_beta": "Vis beta", - "uninstall": "Avinstaller", - "update_information": "Oppdater informasjon", - "upgrade": "Oppdater" - }, - "settings": { - "add_custom_repository": "LEGG TIL REPOSITORY", - "adding_new_repo": "Legger til ny repository '{repo}'", - "adding_new_repo_category": "Med kategori '{category}'.", - "bg_task_custom": "Custom repositories er skjult mens bakgrunnsoppgaver kjører.", - "category": "Kategori", - "compact_mode": "Kompakt modus", - "custom_repositories": "TILPASSEDE REPOSITORIER", - "delete": "Slett", - "display": "Vise", - "grid": "Nett", - "hacs_repo": "HACS repo", - "hidden_repositories": "Gjemte repositories", - "missing_category": "Du må velge en kategori", - "open_repository": "Åpne repository", - "reload_data": "Last inn data på nytt", - "reload_window": "Last inn vinduet på nytt", - "repository_configuration": "Repository konfigurasjon", - "save": "Lagre", - "table": "Tabell", - "table_view": "Tabellvisning", - "unhide": "Vis igjen", - "upgrade_all": "Oppgradere alle" - }, - "store": { - "ascending": "stigende", - "clear_new": "Tøm nye repositories", - "descending": "synkende", - "last_updated": "Sist oppdatert", - "name": "Navn", - "new_repositories": "Nye repositories", - "pending_upgrades": "Venter på oppgradering", - "placeholder_search": "Skriv inn et søkeord ...", - "sort": "sorter", - "stars": "Stjerner", - "status": "Status" - }, - "time": { - "ago": "siden", - "day": "dag", - "days": "dager", - "hour": "time", - "hours": "timer", - "minute": "minutt", - "minutes": "minutter", - "month": "måned", - "months": "måneder", - "one": "En", - "one_day_ago": "for en dag siden", - "one_hour_ago": "en time siden", - "one_minute_ago": "ett minutt siden", - "one_month_ago": "en måned siden", - "one_second_ago": "ett sekund siden", - "one_year_ago": "ett år siden", - "second": "sekund", - "seconds": "sekunder", - "x_days_ago": "{x} dager siden", - "x_hours_ago": "{x} timer siden", - "x_minutes_ago": "{x} minutter siden", - "x_months_ago": "{x} måneder siden", - "x_seconds_ago": "{x} sekunder siden", - "x_years_ago": "{x} år siden", - "year": "år", - "years": "år" - } -} \ No newline at end of file diff --git a/custom_components/hacs/.translations/nl.json b/custom_components/hacs/.translations/nl.json deleted file mode 100644 index 5a8de78..0000000 --- a/custom_components/hacs/.translations/nl.json +++ /dev/null @@ -1,197 +0,0 @@ -{ - "common": { - "about": "Over", - "appdaemon": "AppDaemon", - "appdaemon_apps": "AppDaemon Apps", - "background_task": "Achtergrond taak is draaiende, de pagina herhaalt zichzelf wanneer dit klaar is.", - "check_log_file": "Controleer het logbestand voor meer details.", - "continue": "Doorgaan", - "disabled": "Uitgeschakeld", - "documentation": "Documentatie", - "hacs_is_disabled": "HACS is uitgeschakeld", - "installed": "geinstalleerd", - "integration": "Integratie", - "integrations": "Integraties", - "manage": "beheer", - "netdaemon": "NetDaemon", - "netdaemon_apps": "NetDaemon Apps", - "plugin": "Plugin", - "plugins": "Plugins", - "python_script": "Python Script", - "python_scripts": "Python Scripts", - "repositories": "Repositories", - "settings": "instellingen", - "theme": "Thema", - "themes": "Themas", - "version": "Versie" - }, - "config": { - "abort": { - "single_instance_allowed": "Je kunt maar een enkele configuratie van HACS tegelijk hebben." - }, - "error": { - "auth": "Persoonlijke Toegang Token is niet correct." - }, - "step": { - "user": { - "data": { - "appdaemon": "Zet AppDaemon apps ontdekken & traceren aan", - "netdaemon": "Zet NetDaemon apps ontdekken & traceren aan", - "python_script": "Zet python_scripts ontdekken & traceren aan", - "sidepanel_icon": "Zijpaneel icoon", - "sidepanel_title": "Zijpaneel titel", - "theme": "Zet Themes ontdekken & traceren aan", - "token": "GitHub Persoonlijke Toegang Token" - }, - "description": "Als je hulp nodig hebt met de configuratie, kun je hier verder kijken: https:\/\/hacs.xyz\/docs\/configuration\/start", - "title": "HACS (Home Assistant Community Store)" - } - }, - "title": "HACS (Home Assistant Community Store)" - }, - "confirm": { - "add_to_lovelace": "Weet u zeker dat u dit wilt toevoegen aan uw Lovelace bronnen?", - "bg_task": "Actie is geblokkeerd terwijl achtergrondtaken actief zijn.", - "cancel": "Annuleer", - "continue": "Weet je zeker dat je wilt doorgaan?", - "delete": "Weet u zeker dat u '{item}' wilt verwijderen?", - "delete_installed": "'{item}' is geïnstalleerd, je dient het eerst te deïnstalleren voordat je het kan verwijderen.", - "exist": "{item} bestaat al.", - "generic": "Weet je het zeker?", - "home_assistant_is_restarting": "Een moment alstublieft, Home Assistant is aan het herstarten.", - "no": "Nee", - "no_upgrades": "Geen upgrades in afwachting.", - "ok": "Oké", - "overwrite": "Door dit te doen, wordt het overschreven.", - "reload_data": "Dit zal alle bekende data herladen van alle repositories van HACS. Dit kan even duren", - "restart_home_assistant": "Weet u zeker dat u Home Assistant opnieuw wilt starten?", - "uninstall": "Weet u zeker dat u '{item}' wilt verwijderen?", - "upgrade_all": "Hiermee worden al deze repositories geüpgraded. Zorg ervoor dat u de release-opmerkingen van allen heeft gelezen voordat u doorgaat.", - "yes": "Ja" - }, - "options": { - "step": { - "user": { - "data": { - "appdaemon": "Zet AppDaemon apps ontdekken & traceren aan", - "country": "Filter met land code.", - "debug": "Schakel debug in.", - "experimental": "Zet experimentele functies aan", - "netdaemon": "Zet NetDaemon apps ontdekken & traceren aan", - "not_in_use": "Niet in gebruik met YAML", - "release_limit": "Aantal releases om te laten zien.", - "sidepanel_icon": "Zijpaneel icoon", - "sidepanel_title": "Zijpaneel titel" - } - } - } - }, - "repository_banner": { - "config_flow": "Deze integratie ondersteunt config_flow, wat betekent dat u via uw \"Instellingen\" naar \"Integraties\" kunt gaan om het te configureren.", - "config_flow_title": "UI-configuratie ondersteund", - "integration_not_loaded": "Deze integratie wordt niet geladen in Home Assistant.", - "no_restart_required": "Geen herstart vereist", - "not_loaded": "Niet geladen", - "plugin_not_loaded": "Deze plugin wordt niet toegevoegd aan je Lovelace resources.", - "restart": "U moet Home Assistant opnieuw starten.", - "restart_pending": "Wachten op herstart" - }, - "repository": { - "add_to_lovelace": "Toevoegen aan Lovelace", - "authors": "Auteurs", - "available": "Beschikbaar", - "back_to": "Terug naar", - "changelog": "Changelog", - "downloads": "Downloads", - "flag_this": "Vlag dit", - "frontend_version": "Frontend versie", - "github_stars": "GitHub-sterren", - "goto_integrations": "Ga naar integraties", - "hide": "Verberg", - "hide_beta": "Verberg beta", - "install": "Installeer", - "installed": "Geinstalleerd", - "lovelace_copy_example": "Kopier het voorbeeld naar je klembord", - "lovelace_instruction": "Wanneer je dit gaat toevoegen aan je lovelace configuratie gebruik dit", - "lovelace_no_js_type": "Kon niet achterhalen welk type plugin dit is, controleer de repository van de plugin.", - "newest": "nieuwste", - "note_appdaemon": "je moet het nog steeds toevoegen aan je 'apps.yaml' bestand", - "note_installed": "Wanneer geïnstalleerd, staat het in", - "note_integration": "je moet het nog steeds toevoegen aan je 'configuration.yaml' bestand", - "note_plugin": "je moet het nog steeds toevoegen aan je lovelace configuratie ('ui-lovelace.yaml') of raw UI config editor.", - "open_issue": "Meld probleem", - "open_plugin": "Open plugin", - "reinstall": "Herinstalleer", - "repository": "Repository", - "restart_home_assistant": "Start Home Assistant opnieuw", - "show_beta": "Laat beta zien", - "uninstall": "Verwijder", - "update_information": "Update informatie", - "upgrade": "Update" - }, - "settings": { - "add_custom_repository": "VOEG EIGEN REPOSITORY TOE", - "adding_new_repo": "Nieuwe repository '{repo}' toevoegen", - "adding_new_repo_category": "Met categorie '{category}'.", - "bg_task_custom": "Aangepaste repositories zijn verborgen terwijl de achtergrondtaken actief zijn.", - "category": "Categorie", - "compact_mode": "Compacte modus", - "custom_repositories": "EIGEN REPOSITORIES", - "delete": "Verwijder", - "display": "Weergave", - "grid": "Rooster", - "hacs_repo": "HACS repo", - "hidden_repositories": "verborgen repositories", - "missing_category": "Je moet een categorie selecteren.", - "open_repository": "Open repository", - "reload_data": "Herlaad data", - "reload_window": "Herlaad venster", - "repository_configuration": "Repository configuratie", - "save": "Opslaan", - "table": "Tabel", - "table_view": "Tabelweergave", - "unhide": "zichtbaar maken", - "upgrade_all": "Upgrade alles" - }, - "store": { - "ascending": "oplopend", - "clear_new": "Wissen van alle nieuwe repositories", - "descending": "Aflopend", - "last_updated": "Laatste update", - "name": "Naam", - "new_repositories": "Nieuwe Repositories", - "pending_upgrades": "Upgrades in afwachting", - "placeholder_search": "Typ iets om te zoeken...", - "sort": "sorteer", - "stars": "Sterren", - "status": "Status" - }, - "time": { - "ago": "geleden", - "day": "dag", - "days": "dagen", - "hour": "uur", - "hours": "uren", - "minute": "minuut", - "minutes": "minuten", - "month": "maand", - "months": "maanden", - "one": "Eén", - "one_day_ago": "een dag geleden", - "one_hour_ago": "een uur geleden", - "one_minute_ago": "een minuut geleden", - "one_month_ago": "een maand geleden", - "one_second_ago": "een seconde geleden", - "one_year_ago": "een jaar geleden", - "second": "seconde", - "seconds": "seconden", - "x_days_ago": "{x} dagen geleden", - "x_hours_ago": "{x} uur geleden", - "x_minutes_ago": "{x} minuten geleden", - "x_months_ago": "{x} maanden geleden", - "x_seconds_ago": "{x} seconden geleden", - "x_years_ago": "{x} jaar geleden", - "year": "jaar", - "years": "jaren" - } -} \ No newline at end of file diff --git a/custom_components/hacs/.translations/pl.json b/custom_components/hacs/.translations/pl.json deleted file mode 100644 index b76329d..0000000 --- a/custom_components/hacs/.translations/pl.json +++ /dev/null @@ -1,198 +0,0 @@ -{ - "common": { - "about": "O", - "appdaemon": "AppDaemon", - "appdaemon_apps": "Aplikacje AppDaemon", - "background_task": "Wykonywanie zadania w tle, ta strona zostanie odświeżona, gdy zadanie zostanie ukończone.", - "check_log_file": "Sprawdź plik dziennika, aby uzyskać więcej informacji.", - "continue": "Kontynuuj", - "disabled": "Wyłączony", - "documentation": "Dokumentacja", - "hacs_is_disabled": "HACS jest wyłączony", - "installed": "zainstalowane", - "integration": "Integracja", - "integrations": "Integracje", - "manage": "zarządzaj", - "netdaemon": "NetDaemon", - "netdaemon_apps": "Aplikacje NetDaemon", - "plugin": "Wtyczka", - "plugins": "Wtyczki", - "python_script": "Skrypt Python", - "python_scripts": "Skrypty Python", - "repositories": "Repozytoria", - "settings": "ustawienia", - "theme": "Motyw", - "themes": "Motywy", - "version": "Wersja" - }, - "config": { - "abort": { - "single_instance_allowed": "Dozwolona jest tylko jedna konfiguracja HACS." - }, - "error": { - "auth": "Osobisty token dostępu jest nieprawidłowy." - }, - "step": { - "user": { - "data": { - "appdaemon": "Włącz wykrywanie i śledzenie aplikacji AppDaemon", - "netdaemon": "Włącz wykrywanie i śledzenie aplikacji NetDaemon", - "python_script": "Włącz wykrywanie i śledzenie skryptów Python", - "sidepanel_icon": "Ikona w panelu bocznym", - "sidepanel_title": "Tytuł w panelu bocznym", - "theme": "Włącz wykrywanie i śledzenie motywów", - "token": "Osobisty token dostępu GitHub" - }, - "description": "Jeśli potrzebujesz pomocy w konfiguracji, przejdź na stronę: https:\/\/hacs.xyz\/docs\/configuration\/start", - "title": "HACS (Home Assistant Community Store)" - } - }, - "title": "HACS (Home Assistant Community Store)" - }, - "confirm": { - "add_to_lovelace": "Na pewno chcesz dodać to do swoich zasobów Lovelace?", - "bg_task": "Akcja jest wyłączona podczas wykonywania zadań w tle.", - "cancel": "Anuluj", - "continue": "Na pewno chcesz kontynuować?", - "delete": "Na pewno chcesz usunąć '{item}'?", - "delete_installed": "'{item}' jest zainstalowany, musisz go odinstalować zanim będziesz mógł go usunąć.", - "exist": "{item} już istnieje", - "generic": "Jesteś pewny?", - "home_assistant_is_restarting": "Poczekaj, Home Assistant jest teraz ponownie uruchamiany.", - "home_assistant_version_not_correct": "Używasz Home Assistant'a w wersji '{haversion}', a to repozytorium wymaga wersji minimum '{minversion}'.", - "no": "Nie", - "no_upgrades": "Brak oczekujących aktualizacji", - "ok": "Ok", - "overwrite": "Spowoduje to zastąpienie istniejącej kopii.", - "reload_data": "To przeładowuje dane wszystkich repozytoriów, o których wie HACS, może to trochę potrwać.", - "restart_home_assistant": "Na pewno chcesz ponownie uruchomić Home Assistant'a?", - "uninstall": "Na pewno chcesz odinstalować '{item}'?", - "upgrade_all": "To uaktualni wszystkie te repozytoria, upewnij się, że przeczytałeś uwagi do wydania dla wszystkich z nich przed kontynuacją.", - "yes": "Tak" - }, - "options": { - "step": { - "user": { - "data": { - "appdaemon": "Włącz wykrywanie i śledzenie aplikacji AppDaemon", - "country": "Filtruj według kodu kraju", - "debug": "Włącz debugowanie.", - "experimental": "Włącz funkcje eksperymentalne", - "netdaemon": "Włącz wykrywanie i śledzenie aplikacji NetDaemon", - "not_in_use": "Nieużywany z YAML", - "release_limit": "Liczba wydań do wyświetlenia", - "sidepanel_icon": "Ikona w panelu bocznym", - "sidepanel_title": "Tytuł w panelu bocznym" - } - } - } - }, - "repository_banner": { - "config_flow": "Ta integracja obsługuje config_flow, co oznacza, że możesz teraz przejść do sekcji integracji w interfejsie użytkownika, aby ją skonfigurować.", - "config_flow_title": "Obsługiwana konfiguracja poprzez interfejs użytkownika", - "integration_not_loaded": "Ta integracja nie jest załadowana do Home Assistant'a.", - "no_restart_required": "Ponowne uruchomienie nie jest wymagane", - "not_loaded": "Nie załadowano", - "plugin_not_loaded": "Ta wtyczka nie jest dodana do zasobów Lovelace.", - "restart": "Musisz ponownie uruchomić Home Assistant'a.", - "restart_pending": "Oczekiwanie na ponowne uruchomienie" - }, - "repository": { - "add_to_lovelace": "Dodaj do Lovelace", - "authors": "Autorzy", - "available": "Dostępna", - "back_to": "Wróć do", - "changelog": "Lista zmian", - "downloads": "Ilość pobrań", - "flag_this": "Oflaguj", - "frontend_version": "Wersja frontendu", - "github_stars": "Gwiazdki GitHub", - "goto_integrations": "Przejdź do integracji", - "hide": "Ukryj", - "hide_beta": "Ukryj wydania beta", - "install": "Zainstaluj", - "installed": "Zainstalowano", - "lovelace_copy_example": "Skopiuj przykład do schowka", - "lovelace_instruction": "Interfejs użytkownika użyje tej wtyczki po dodaniu konfiguracji", - "lovelace_no_js_type": "Nie można określić typu tej wtyczki, sprawdź repozytorium.", - "newest": "najnowsza", - "note_appdaemon": "musisz jeszcze dodać aplikację do pliku 'apps.yaml'", - "note_installed": "Po zainstalowaniu dodatek będzie znajdować się w", - "note_integration": "musisz jeszcze dodać integrację do pliku 'configuration.yaml'", - "note_plugin": "musisz jeszcze dodać wtyczkę do konfiguracji interfejsu użytkownika (plik 'ui-lovelace.yaml' lub edytor interfejsu użytkownika)", - "open_issue": "Powiadom o problemie", - "open_plugin": "Otwórz dodatek", - "reinstall": "Przeinstaluj", - "repository": "Repozytorium", - "restart_home_assistant": "Uruchom ponownie Home Assistant'a", - "show_beta": "Wyświetl wydania beta", - "uninstall": "Odinstaluj", - "update_information": "Informacje o aktualizacji", - "upgrade": "Uaktualnij" - }, - "settings": { - "add_custom_repository": "DODAJ REPOZYTORIUM NIESTANDARDOWE", - "adding_new_repo": "Dodawanie nowego repozytorium '{repo}'", - "adding_new_repo_category": "Z kategorią '{category}'.", - "bg_task_custom": "Niestandardowe repozytoria są ukryte podczas wykonywania zadań w tle.", - "category": "Kategoria", - "compact_mode": "Tryb kompaktowy", - "custom_repositories": "REPOZYTORIA NIESTANDARDOWE", - "delete": "Usuń", - "display": "Sposób wyświetlania", - "grid": "Siatka", - "hacs_repo": "Repozytorium HACS", - "hidden_repositories": "ukryte repozytoria", - "missing_category": "Musisz wybrać kategorię", - "open_repository": "Otwórz repozytorium", - "reload_data": "Wczytaj ponownie dane", - "reload_window": "Załaduj ponownie okno", - "repository_configuration": "Konfiguracja repozytorium", - "save": "Zapisz", - "table": "tabela", - "table_view": "Widok tabeli", - "unhide": "pokaż", - "upgrade_all": "Uaktualnij wszystkie" - }, - "store": { - "ascending": "rosnąco", - "clear_new": "Wyczyść wszystkie nowe repozytoria", - "descending": "malejąco", - "last_updated": "Ostatnia aktualizacja", - "name": "Nazwa", - "new_repositories": "Nowe repozytoria", - "pending_upgrades": "Oczekujące aktualizacje", - "placeholder_search": "Wprowadź wyszukiwane hasło...", - "sort": "sortowanie", - "stars": "Gwiazdki", - "status": "Status" - }, - "time": { - "ago": "temu", - "day": "dzień", - "days": "dni", - "hour": "godzina", - "hours": "godziny", - "minute": "minuta", - "minutes": "minuty", - "month": "miesiąc", - "months": "miesięcy", - "one": "Jeden", - "one_day_ago": "jeden dzień temu", - "one_hour_ago": "jedna godzina temu", - "one_minute_ago": "jedna minuta temu", - "one_month_ago": "jeden miesiąc temu", - "one_second_ago": "jedna sekunda temu", - "one_year_ago": "jeden rok temu", - "second": "sekunda", - "seconds": "sekundy", - "x_days_ago": "{x} dni temu", - "x_hours_ago": "{x} godzin(-y) temu", - "x_minutes_ago": "{x} minut(-y) temu", - "x_months_ago": "{x} miesi(-ące\/-ęcy) temu", - "x_seconds_ago": "{x} sekund(-y) temu", - "x_years_ago": "{x} lat(-a) temu", - "year": "rok", - "years": "lata" - } -} \ No newline at end of file diff --git a/custom_components/hacs/.translations/pt-BR.json b/custom_components/hacs/.translations/pt-BR.json deleted file mode 100644 index e7089af..0000000 --- a/custom_components/hacs/.translations/pt-BR.json +++ /dev/null @@ -1,182 +0,0 @@ -{ - "common": { - "about": "Sobre", - "appdaemon": "AppDaemon", - "appdaemon_apps": "AppDaemon Apps", - "background_task": "Tarefa em segundo plano em execução, esta página será recarregada quando terminar.", - "continue": "Continuar", - "disabled": "Desativado", - "documentation": "Documentação", - "hacs_is_disabled": "HACS está desativado", - "installed": "instalado", - "integration": "Integração", - "integrations": "Integrações", - "manage": "gerenciar", - "netdaemon": "NetDaemon", - "netdaemon_apps": "NetDaemon Apps", - "plugin": "Plugin", - "plugins": "Plugins", - "python_script": "Python Script", - "python_scripts": "Python Scripts", - "repositories": "Repositórios", - "settings": "configurações", - "theme": "Tema", - "themes": "Temas", - "version": "Versão" - }, - "config": { - "abort": { - "single_instance_allowed": "Apenas uma configuração do HACS é permitida." - }, - "error": { - "auth": "Token de acesso pessoal incorreto." - }, - "step": { - "user": { - "data": { - "appdaemon": "Habilitar AppDaemon apps descoberta & rastreamento", - "netdaemon": "Habilitar NetDaemon apps descoberta & rastreamento", - "python_script": "Habilitar python_scripts descoberta & rastreamento", - "sidepanel_icon": "Icone do painel lateral", - "sidepanel_title": "Titulo do painel lateral", - "theme": "Habilitar temas descoberta & rastreamento", - "token": "GitHub Token de acesso pessoal" - }, - "description": "Se você preciar de ajuda com a configuração olhe aqui: https:\/\/hacs.xyz\/docs\/configuration\/start", - "title": "HACS (Home Assistant Community Store)" - } - }, - "title": "HACS (Home Assistant Community Store)" - }, - "confirm": { - "cancel": "Cancelar", - "continue": "Tem certeza que quer continuar?", - "delete": "Tem certeza de que deseja excluir '{item}'?", - "exist": "{item} já existe", - "home_assistant_is_restarting": "Espere, o Home Assistant está agora a reiniciar.", - "no": "Não", - "ok": "OK", - "restart_home_assistant": "Tem certeza de que deseja reiniciar o Home Assistant?", - "uninstall": "Tem certeza de que deseja desinstalar '{item}'?", - "yes": "Sim" - }, - "options": { - "step": { - "user": { - "data": { - "appdaemon": "Habilitar AppDaemon apps descoberta & rastreamento", - "country": "Filtro pelo código do país.", - "experimental": "Ativar recursos experimentais", - "netdaemon": "Habilitar NetDaemon apps descoberta & rastreamento", - "not_in_use": "Não está em uso com o YAML", - "release_limit": "Número de lançamentos a serem exibidos.", - "sidepanel_icon": "Icone do painel lateral", - "sidepanel_title": "Titulo do painel lateral" - } - } - } - }, - "repository_banner": { - "integration_not_loaded": "Esta integração não é carregada no Home Assistant.", - "no_restart_required": "Não é necessário reiniciar", - "not_loaded": "Não carregado", - "restart": "Você precisa reiniciar o Home Assistant.", - "restart_pending": "Reiniciar pendente" - }, - "repository": { - "add_to_lovelace": "Adicionar a Lovelace", - "authors": "Autores", - "available": "Disponível", - "back_to": "Voltar para", - "changelog": "Changelog", - "downloads": "Downloads", - "flag_this": "Sinalizar isso", - "frontend_version": "Versão Frontend", - "github_stars": "Estrelas de GitHub", - "goto_integrations": "Ir para integrações", - "hide": "Esconder", - "hide_beta": "Esconder beta", - "install": "Instalar", - "installed": "Instalado", - "lovelace_copy_example": "Copie este exemplo para seu clipboard", - "lovelace_instruction": "Quando você adicionar isso à sua configuração do lovelace, use este", - "lovelace_no_js_type": "Não foi possível determinar o tipo deste plug-in, verifique o repositório.", - "newest": "O mais novo", - "note_appdaemon": "Você ainda precisa adicioná-lo ao seu arquivo 'apps.yaml'", - "note_installed": "Quando instalado, ele estará localizado em", - "note_integration": "Você ainda precisa adicioná-lo ao seu arquivo 'configuration.yaml'", - "note_plugin": "você ainda precisará adicioná-lo à sua configuração do lovelace ('ui-lovelace.yaml' ou o editor de configuração da interface do usuário)", - "open_issue": "Open issue", - "open_plugin": "Plugin aberto", - "reinstall": "Reinstalar", - "repository": "Repositório", - "restart_home_assistant": "Reiniciar Home Assistant", - "show_beta": "Mostrar beta", - "uninstall": "Desinstalar", - "update_information": "Atualizar informação", - "upgrade": "Melhorar" - }, - "settings": { - "add_custom_repository": "ADICIONAR REPOSITÓRIO PERSONALIZADO", - "adding_new_repo": "Adicionando novo repositório '{repo}'", - "adding_new_repo_category": "Com a categoria '{category}'.", - "category": "Categoria", - "compact_mode": "Modo compacto", - "custom_repositories": "REPOSITÓRIOS PERSONALIZADOS", - "delete": "Deletar", - "display": "Display", - "grid": "Grade", - "hacs_repo": "HACS repo", - "hidden_repositories": "repositórios ocultos", - "missing_category": "Você precisa selecionar uma categoria", - "open_repository": "Repositório aberto", - "reload_data": "Recarregar dados", - "reload_window": "Recarregar janela", - "repository_configuration": "Configuração do Repositório", - "save": "Salvar", - "table": "Tabela", - "table_view": "Vista de mesa", - "unhide": "reexibir", - "upgrade_all": "Atualizar tudo" - }, - "store": { - "ascending": "ascendente", - "clear_new": "Limpar todos os novos repositórios", - "descending": "descendente", - "last_updated": "Última atualização", - "name": "Nome", - "new_repositories": "Novos Repositórios", - "pending_upgrades": "Atualizações pendentes", - "placeholder_search": "Por favor insira um termo de pesquisa...", - "stars": "Estrelas", - "status": "Status" - }, - "time": { - "ago": "atrás", - "day": "dia", - "days": "dias", - "hour": "hora", - "hours": "horas", - "minute": "minuto", - "minutes": "minutos", - "month": "mês", - "months": "meses", - "one": "Um", - "one_day_ago": "um dia atrás", - "one_hour_ago": "uma hora atrás", - "one_minute_ago": "um minuto atrás", - "one_month_ago": "um mês atrás", - "one_second_ago": "um segundo atrás", - "one_year_ago": "um ano atrás", - "second": "segundo", - "seconds": "segundos", - "x_days_ago": "{x} dias atrás", - "x_hours_ago": "{x} horas atrás", - "x_minutes_ago": "{x} minutos atrás", - "x_months_ago": "{x} meses atrás", - "x_seconds_ago": "{x} segundos atrás", - "x_years_ago": "{x} anos atrás", - "year": "ano", - "years": "anos" - } -} \ No newline at end of file diff --git a/custom_components/hacs/.translations/ru.json b/custom_components/hacs/.translations/ru.json deleted file mode 100644 index 7cde0bf..0000000 --- a/custom_components/hacs/.translations/ru.json +++ /dev/null @@ -1,193 +0,0 @@ -{ - "common": { - "about": "О проекте", - "appdaemon": "AppDaemon", - "appdaemon_apps": "Приложения AppDaemon", - "background_task": "Выполняется фоновая задача, страница перезагрузится по готовности.", - "check_log_file": "Проверьте логи для получения более подробной информации.", - "continue": "Продолжить", - "disabled": "Отключено", - "documentation": "Документация", - "hacs_is_disabled": "HACS отключен", - "installed": "установлено", - "integration": "Интеграция", - "integrations": "Интеграции", - "manage": "управлять", - "netdaemon": "NetDaemon", - "netdaemon_apps": "Приложения NetDaemon", - "plugin": "Плагин", - "plugins": "Плагины", - "python_script": "Скрипт Python", - "python_scripts": "Скрипты Python", - "repositories": "Репозитории", - "settings": "настройки", - "theme": "Тема", - "themes": "Темы", - "version": "Версия" - }, - "config": { - "abort": { - "single_instance_allowed": "Разрешена только одна настройка HACS." - }, - "error": { - "auth": "Неверный токен персонального доступа." - }, - "step": { - "user": { - "data": { - "appdaemon": "Включить поиск и отслеживание приложений AppDaemon", - "netdaemon": "Включить поиск и отслеживание приложений NetDaemon", - "python_script": "Включить поиск и отслеживание скриптов Python", - "sidepanel_icon": "Иконка в боковом меню", - "sidepanel_title": "Название в боковом меню", - "theme": "Включить поиск и отслеживание тем", - "token": "Персональный токен доступа GitHub" - }, - "description": "Если вам нужна помощь с настройкой, посмотрите: https:\/\/hacs.xyz\/docs\/configuration\/start", - "title": "HACS (Home Assistant Community Store)" - } - }, - "title": "HACS (Home Assistant Community Store)" - }, - "confirm": { - "add_to_lovelace": "Вы уверены, что хотите добавить это в ресурсы Lovelace?", - "bg_task": "Действие отключено во время выполнения фоновых задач.", - "cancel": "Отмена", - "continue": "Вы уверены, что хотите продолжить?", - "delete": "Вы уверены, что хотите удалить '{item}'?", - "delete_installed": "'{item}' установлен, вам нужно нажать 'Удалить', чтобы удалить его.", - "exist": "{item} уже существует.", - "generic": "Вы уверены?", - "no": "Нет", - "no_upgrades": "Нет обновлений", - "ok": "ОК", - "overwrite": "После подтверждения файлы будут перезаписаны.", - "reload_data": "Выполняется перезагрузка данных всех репозиториев в HACS, это займет некоторое время.", - "restart_home_assistant": "Вы уверены, что хотите перезапустить Home Assistant?", - "uninstall": "Вы уверены, что хотите удалить '{item}'?", - "yes": "Да" - }, - "options": { - "step": { - "user": { - "data": { - "appdaemon": "Включить поиск и отслеживание приложений AppDaemon", - "country": "Фильтр по стране.", - "debug": "Включить отладку.", - "experimental": "Вкл. экспериментальные функции", - "netdaemon": "Включить поиск и отслеживание приложений NetDaemon", - "not_in_use": "Не используется с YAML", - "release_limit": "Число доступных версий.", - "sidepanel_icon": "Иконка в боковом меню", - "sidepanel_title": "Название в боковом меню" - } - } - } - }, - "repository_banner": { - "integration_not_loaded": "Эта интеграция не загружена в Home Assistant.", - "no_restart_required": "Перезагрузка не требуется", - "not_loaded": "Не загружено", - "plugin_not_loaded": "Этот плагин не добавлен к ресурсам Lovelace.", - "restart": "Вам нужно перезапустить Home Assistant.", - "restart_pending": "Ожидается перезапуск" - }, - "repository": { - "add_to_lovelace": "Добавить в Lovelace", - "authors": "Авторы", - "available": "Доступно", - "back_to": "Назад к", - "changelog": "Изменения", - "downloads": "Загрузки", - "flag_this": "Пожаловаться", - "frontend_version": "Версия", - "github_stars": "Звезды GitHub", - "goto_integrations": "Перейти к интеграции", - "hide": "Скрыть", - "hide_beta": "Скрыть бета", - "install": "Установить", - "installed": "Установлено", - "lovelace_copy_example": "Скопируйте пример в буфер обмена", - "lovelace_instruction": "Для добавления в конфигурацию Lovelace, используйте", - "lovelace_no_js_type": "Не удалось определить тип этого плагина, проверьте репозиторий.", - "newest": "новейшая", - "note_appdaemon": "вам всё ещё нужно добавить код для настройки приложения в файл 'apps.yaml'", - "note_installed": "После установки, файлы будут расположены в", - "note_integration": "вам всё ещё нужно добавить код для настройки интеграции в файл 'configuration.yaml'", - "note_plugin": "вам всё ещё нужно добавить код для настройки плагина в конфигурацию Lovelace ('ui-lovelace.yaml')", - "open_issue": "Сообщить о проблеме", - "open_plugin": "Открыть плагин", - "reinstall": "Переустановить", - "repository": "Репозиторий", - "restart_home_assistant": "Перезагрузка Home Assistant", - "show_beta": "Показать бета", - "uninstall": "Удалить", - "update_information": "Обновить информацию", - "upgrade": "Обновить" - }, - "settings": { - "add_custom_repository": "ДОБАВИТЬ СВОЙ РЕПОЗИТОРИЙ", - "adding_new_repo": "Добавление нового репозитория '{repo}'", - "adding_new_repo_category": "С категорией '{category}'.", - "bg_task_custom": "Свои репозитории скрыты во время выполнения фоновых задач.", - "category": "Категория", - "compact_mode": "Компактный режим", - "custom_repositories": "СВОИ РЕПОЗИТОРИИ", - "delete": "Удалить", - "display": "Вид", - "grid": "Сетка", - "hacs_repo": "Репозиторий HACS", - "hidden_repositories": "Скрытые репозитории", - "missing_category": "Вы должны выбрать категорию", - "open_repository": "Открыть репозиторий", - "reload_data": "Перезагрузить", - "reload_window": "Перезагрузить окно", - "repository_configuration": "Конфигурация репозитория", - "save": "Сохранить", - "table": "Таблица", - "table_view": "Таблица", - "unhide": "Показать", - "upgrade_all": "Обновить всё" - }, - "store": { - "ascending": "по возрастанию", - "clear_new": "Очистить все новые репозитории", - "descending": "по убыванию", - "last_updated": "Последнее обновление", - "name": "Название", - "new_repositories": "Новые репозитории", - "pending_upgrades": "Ожидается обновление", - "placeholder_search": "Пожалуйста, введите условие для поиска...", - "sort": "Сортировка", - "stars": "Звезды", - "status": "Статус" - }, - "time": { - "ago": "назад", - "day": "день", - "days": "дней", - "hour": "час", - "hours": "часов", - "minute": "минута", - "minutes": "минут", - "month": "месяц", - "months": "месяца", - "one": "Одна", - "one_day_ago": "один день назад", - "one_hour_ago": "час назад", - "one_minute_ago": "минуту назад", - "one_month_ago": "месяц назад", - "one_second_ago": "одну секунду назад", - "one_year_ago": "один год назад", - "second": "секунда", - "seconds": "секунд", - "x_days_ago": "{x} дней назад", - "x_hours_ago": "{x} часов назад", - "x_minutes_ago": "{x} минут назад", - "x_months_ago": "{x} месяцев назад", - "x_seconds_ago": "{x} секунд назад", - "x_years_ago": "{x} лет назад", - "year": "год", - "years": "лет" - } -} \ No newline at end of file diff --git a/custom_components/hacs/.translations/zh-Hans.json b/custom_components/hacs/.translations/zh-Hans.json deleted file mode 100644 index 73ecd24..0000000 --- a/custom_components/hacs/.translations/zh-Hans.json +++ /dev/null @@ -1,198 +0,0 @@ -{ - "common": { - "about": "关于", - "appdaemon": "AppDaemon", - "appdaemon_apps": "AppDaemon应用", - "background_task": "后台任务正在运行,完成后将重新加载此页面。", - "check_log_file": "请查看日志文件以了解更多信息。", - "continue": "继续", - "disabled": "禁用", - "documentation": "文档", - "hacs_is_disabled": "HACS已禁用", - "installed": "已安装", - "integration": "自定义组件", - "integrations": "自定义组件", - "manage": "管理", - "netdaemon": "NetDaemon应用", - "netdaemon_apps": "NetDaemon应用", - "plugin": "Lovelace插件", - "plugins": "Lovelace插件", - "python_script": "Python脚本", - "python_scripts": "Python脚本", - "repositories": "仓库数量", - "settings": "设置", - "theme": "主题", - "themes": "主题", - "version": "版本" - }, - "config": { - "abort": { - "single_instance_allowed": "仅允许单个HACS配置。" - }, - "error": { - "auth": "个人访问令牌不正确。" - }, - "step": { - "user": { - "data": { - "appdaemon": "启用AppDaemon应用发现和跟踪", - "netdaemon": "启用NetDaemon应用发现和跟踪", - "python_script": "启用python_scripts发现和跟踪", - "sidepanel_icon": "侧面板图标", - "sidepanel_title": "侧面板标题", - "theme": "启用主题发现和跟踪", - "token": "GitHub个人访问令牌" - }, - "description": "如果您需要有关配置的帮助,请在此处查看:https:\/\/hacs.xyz\/docs\/configuration\/start", - "title": "HACS (Home Assistant Community Store)" - } - }, - "title": "HACS (Home Assistant Community Store)" - }, - "confirm": { - "add_to_lovelace": "您确定要将此添加到Lovelace资源中吗?", - "bg_task": "后台任务正在运行时,操作被禁用。", - "cancel": "取消", - "continue": "你确定你要继续吗?", - "delete": "是否确实要删除\"{item}\"?", - "delete_installed": "已安装 '{item}',需要先将其卸载,然后才能将其删除。", - "exist": "{item}已经存在", - "generic": "你确定吗?", - "home_assistant_is_restarting": "请等待,Home Assistant现在正在重新启动。", - "home_assistant_version_not_correct": "您正在运行Home Assistant的版本为'{haversion}',但是这个需要安装最低版本是'{minversion}'。", - "no": "取消", - "no_upgrades": "暂无升级", - "ok": "确定", - "overwrite": "这样做会覆盖它。", - "reload_data": "这将重新加载HACS知道的所有仓库的数据,这需要一些时间才能完成。", - "restart_home_assistant": "您确定要重新启动Home Assistant吗?", - "uninstall": "您确定要卸载“ {item} ”吗?", - "upgrade_all": "这将升级所有这些仓库,请确保在继续之前已阅读所有仓库的发行说明。", - "yes": "确定" - }, - "options": { - "step": { - "user": { - "data": { - "appdaemon": "启用AppDaemon应用发现和跟踪", - "country": "用国家代码过滤。", - "debug": "启用调试。", - "experimental": "启用实验功能", - "netdaemon": "启用NetDaemon应用发现和跟踪", - "not_in_use": "不适用于 YAML", - "release_limit": "要显示的发行数量。", - "sidepanel_icon": "侧面板图标", - "sidepanel_title": "侧面板标题" - } - } - } - }, - "repository_banner": { - "config_flow": "此组件支持config_flow,这意味着您现在可以转到UI的“集成”部分进行配置。", - "config_flow_title": "支持UI配置", - "integration_not_loaded": "此集成未加载到Home Assistant中。", - "no_restart_required": "无需重启", - "not_loaded": "未加载", - "plugin_not_loaded": "该插件未添加到您的Lovelace资源中。", - "restart": "您需要重新启动Home Assistant。", - "restart_pending": "重新启动待处理" - }, - "repository": { - "add_to_lovelace": "添加到Lovelace", - "authors": "作者", - "available": "版本号", - "back_to": "返回", - "changelog": "更新日志", - "downloads": "下载", - "flag_this": "标记", - "frontend_version": "前端版本", - "github_stars": "GitHub星级", - "goto_integrations": "转到组件", - "hide": "隐藏", - "hide_beta": "隐藏测试版", - "install": "安装", - "installed": "已安装版本", - "lovelace_copy_example": "复制样例代码到你的剪贴板", - "lovelace_instruction": "您仍然需要将下列代码添加到lovelace配置中(“ ui-lovelace.yaml”或原始配置编辑器):", - "lovelace_no_js_type": "无法确定此插件的类型,请检查仓库。", - "newest": "最新", - "note_appdaemon": "您仍然需要将其添加到“ apps.yaml”文件中", - "note_installed": "安装后,它将位于", - "note_integration": "您仍然需要将其添加到“ configuration.yaml”文件中", - "note_plugin": "您仍然需要将其添加到lovelace配置中(“ ui-lovelace.yaml”或原始配置编辑器)ui-lovelace.yaml", - "open_issue": "提交问题", - "open_plugin": "打开插件", - "reinstall": "重新安装", - "repository": "仓库", - "restart_home_assistant": "重新启动Home Assistant", - "show_beta": "显示测试版", - "uninstall": "卸载", - "update_information": "更新信息", - "upgrade": "升级" - }, - "settings": { - "add_custom_repository": "添加自定义仓库", - "adding_new_repo": "添加新的仓库 '{repo}'", - "adding_new_repo_category": "类别为 '{category}'。", - "bg_task_custom": "自定义仓库在后台任务运行时被隐藏。", - "category": "类别", - "compact_mode": "紧凑模式", - "custom_repositories": "自定义仓库", - "delete": "删除", - "display": "视图方式", - "grid": "网格", - "hacs_repo": "HACS仓库", - "hidden_repositories": "隐藏的仓库", - "missing_category": "您需要选择一个类别", - "open_repository": "打开仓库", - "reload_data": "重载数据", - "reload_window": "重新加载窗口", - "repository_configuration": "仓库数据配置", - "save": "保存", - "table": "列表", - "table_view": "表视图", - "unhide": "取消隐藏", - "upgrade_all": "升级全部" - }, - "store": { - "ascending": "升序", - "clear_new": "清除所有新仓库标记", - "descending": "降序", - "last_updated": "最近更新时间", - "name": "名称", - "new_repositories": "新仓库", - "pending_upgrades": "待升级", - "placeholder_search": "搜索项目...", - "sort": "排序", - "stars": "星级", - "status": "状态" - }, - "time": { - "ago": "过去", - "day": "天", - "days": "天", - "hour": "小时", - "hours": "小时", - "minute": "分", - "minutes": "分", - "month": "月", - "months": "月", - "one": "一", - "one_day_ago": "一天前", - "one_hour_ago": "一个小时之前", - "one_minute_ago": "一分钟前", - "one_month_ago": "一个月前", - "one_second_ago": "一秒钟前", - "one_year_ago": "一年前", - "second": "秒", - "seconds": "秒", - "x_days_ago": "{x}天前", - "x_hours_ago": "{x}小时前", - "x_minutes_ago": "{x}分钟前", - "x_months_ago": "{x}个月前", - "x_seconds_ago": "{x}秒前", - "x_years_ago": "{x}年前", - "year": "年", - "years": "年" - } -} \ No newline at end of file diff --git a/custom_components/hacs/__init__.py b/custom_components/hacs/__init__.py index 1b94fbc..5a738a4 100644 --- a/custom_components/hacs/__init__.py +++ b/custom_components/hacs/__init__.py @@ -1,224 +1,30 @@ -""" -Custom element manager for community created elements. - -For more details about this integration, please refer to the documentation at -https://hacs.xyz/ -""" - -import voluptuous as vol -from aiogithubapi import AIOGitHub -from homeassistant import config_entries -from homeassistant.const import EVENT_HOMEASSISTANT_START -from homeassistant.const import __version__ as HAVERSION -from homeassistant.components.lovelace import system_health_info -from homeassistant.exceptions import ConfigEntryNotReady, ServiceNotFound -from homeassistant.helpers.aiohttp_client import async_create_clientsession -from homeassistant.helpers.event import async_call_later - -from custom_components.hacs.configuration_schema import ( - hacs_base_config_schema, - hacs_config_option_schema, -) -from custom_components.hacs.const import DOMAIN, ELEMENT_TYPES, STARTUP, VERSION -from custom_components.hacs.constrains import check_constans, check_requirements -from custom_components.hacs.hacsbase.configuration import Configuration -from custom_components.hacs.hacsbase.data import HacsData -from custom_components.hacs.setup import ( - add_sensor, - load_hacs_repository, - setup_frontend, -) - -from custom_components.hacs.globals import get_hacs - -from custom_components.hacs.helpers.network import internet_connectivity_check - -SCHEMA = hacs_base_config_schema() -SCHEMA[vol.Optional("options")] = hacs_config_option_schema() -CONFIG_SCHEMA = vol.Schema({DOMAIN: SCHEMA}, extra=vol.ALLOW_EXTRA) - - -async def async_setup(hass, config): - """Set up this integration using yaml.""" - hacs = get_hacs() - if DOMAIN not in config: - return True - hass.data[DOMAIN] = config - hacs.hass = hass - hacs.session = async_create_clientsession(hass) - hacs.configuration = Configuration.from_dict( - config[DOMAIN], config[DOMAIN].get("options") - ) - hacs.configuration.config = config - hacs.configuration.config_type = "yaml" - await startup_wrapper_for_yaml() - hass.async_create_task( - hass.config_entries.flow.async_init( - DOMAIN, context={"source": config_entries.SOURCE_IMPORT}, data={} - ) - ) - return True - - -async def async_setup_entry(hass, config_entry): - """Set up this integration using UI.""" - hacs = get_hacs() - conf = hass.data.get(DOMAIN) - if config_entry.source == config_entries.SOURCE_IMPORT: - if conf is None: - hass.async_create_task( - hass.config_entries.async_remove(config_entry.entry_id) - ) - return False - hacs.hass = hass - hacs.session = async_create_clientsession(hass) - hacs.configuration = Configuration.from_dict( - config_entry.data, config_entry.options - ) - hacs.configuration.config_type = "flow" - hacs.configuration.config_entry = config_entry - config_entry.add_update_listener(reload_hacs) - startup_result = await hacs_startup() - if not startup_result: - hacs.system.disabled = True - raise ConfigEntryNotReady - hacs.system.disabled = False - return startup_result - - -async def startup_wrapper_for_yaml(): - """Startup wrapper for yaml config.""" - hacs = get_hacs() - startup_result = await hacs_startup() - if not startup_result: - hacs.system.disabled = True - hacs.hass.components.frontend.async_remove_panel( - hacs.configuration.sidepanel_title.lower() - .replace(" ", "_") - .replace("-", "_") - ) - hacs.logger.info("Could not setup HACS, trying again in 15 min") - async_call_later(hacs.hass, 900, startup_wrapper_for_yaml()) - return - hacs.system.disabled = False - - -async def hacs_startup(): - """HACS startup tasks.""" - hacs = get_hacs() - if not check_requirements(): - return False - if hacs.configuration.debug: - try: - await hacs.hass.services.async_call( - "logger", "set_level", {"hacs": "debug"} - ) - except ServiceNotFound: - hacs.logger.error( - "Could not set logging level to debug, logger is not enabled" - ) - - lovelace_info = await system_health_info(hacs.hass) - hacs.logger.debug(f"Configuration type: {hacs.configuration.config_type}") - hacs.version = VERSION - hacs.logger.info(STARTUP) - hacs.system.config_path = hacs.hass.config.path() - hacs.system.ha_version = HAVERSION - - hacs.system.lovelace_mode = lovelace_info.get("mode", "yaml") - hacs.system.disabled = False - hacs.github = AIOGitHub( - hacs.configuration.token, async_create_clientsession(hacs.hass) - ) - hacs.data = HacsData() - - # Check HACS Constrains - if not await hacs.hass.async_add_executor_job(check_constans): - if hacs.configuration.config_type == "flow": - if hacs.configuration.config_entry is not None: - await async_remove_entry(hacs.hass, hacs.configuration.config_entry) - return False - - # Set up frontend - await setup_frontend() - - if not await hacs.hass.async_add_executor_job(internet_connectivity_check): - hacs.logger.critical("No network connectivity") - return False - - # Load HACS - if not await load_hacs_repository(): - if hacs.configuration.config_type == "flow": - if hacs.configuration.config_entry is not None: - await async_remove_entry(hacs.hass, hacs.configuration.config_entry) - return False - - # Restore from storefiles - if not await hacs.data.restore(): - hacs_repo = hacs.get_by_name("hacs/integration") - hacs_repo.pending_restart = True - if hacs.configuration.config_type == "flow": - if hacs.configuration.config_entry is not None: - await async_remove_entry(hacs.hass, hacs.configuration.config_entry) - return False - - # Add aditional categories - hacs.common.categories = ELEMENT_TYPES - if hacs.configuration.appdaemon: - hacs.common.categories.append("appdaemon") - if hacs.configuration.netdaemon: - hacs.common.categories.append("netdaemon") - if hacs.configuration.python_script: - hacs.configuration.python_script = False - if hacs.configuration.config_type == "yaml": - hacs.logger.warning( - "Configuration option 'python_script' is deprecated and you should remove it from your configuration, HACS will know if you use 'python_script' in your Home Assistant configuration, this option will be removed in a future release." - ) - if hacs.configuration.theme: - hacs.configuration.theme = False - if hacs.configuration.config_type == "yaml": - hacs.logger.warning( - "Configuration option 'theme' is deprecated and you should remove it from your configuration, HACS will know if you use 'theme' in your Home Assistant configuration, this option will be removed in a future release." - ) - - # Setup startup tasks - if hacs.configuration.config_type == "yaml": - hacs.hass.bus.async_listen_once(EVENT_HOMEASSISTANT_START, hacs.startup_tasks()) - else: - async_call_later(hacs.hass, 5, hacs.startup_tasks()) - - # Show the configuration - hacs.configuration.print() - - # Set up sensor - await hacs.hass.async_add_executor_job(add_sensor) - - # Mischief managed! - return True - - -async def async_remove_entry(hass, config_entry): - """Handle removal of an entry.""" - hacs = get_hacs() - hacs.logger.info("Disabling HACS") - hacs.logger.info("Removing recuring tasks") - for task in hacs.recuring_tasks: - task() - hacs.logger.info("Removing sensor") - try: - await hass.config_entries.async_forward_entry_unload(config_entry, "sensor") - except ValueError: - pass - hacs.logger.info("Removing sidepanel") - try: - hass.components.frontend.async_remove_panel("hacs") - except AttributeError: - pass - hacs.system.disabled = True - hacs.logger.info("HACS is now disabled") - - -async def reload_hacs(hass, config_entry): - """Reload HACS.""" - await async_remove_entry(hass, config_entry) - await async_setup_entry(hass, config_entry) +""" +HACS gives you a powerful UI to handle downloads of all your custom needs. + +For more details about this integration, please refer to the documentation at +https://hacs.xyz/ +""" +import voluptuous as vol + +from .const import DOMAIN +from .helpers.functions.configuration_schema import ( + hacs_config_combined, +) +from .operational.setup import ( + async_setup as hacs_yaml_setup, + async_setup_entry as hacs_ui_setup, +) + +CONFIG_SCHEMA = vol.Schema({DOMAIN: hacs_config_combined()}, extra=vol.ALLOW_EXTRA) + + +async def async_setup(hass, config): + """Set up this integration using yaml.""" + + return await hacs_yaml_setup(hass, config) + + +async def async_setup_entry(hass, config_entry): + """Set up this integration using UI.""" + + return await hacs_ui_setup(hass, config_entry) diff --git a/custom_components/hacs/__pycache__/__init__.cpython-37.pyc b/custom_components/hacs/__pycache__/__init__.cpython-37.pyc deleted file mode 100644 index ac153ca..0000000 Binary files a/custom_components/hacs/__pycache__/__init__.cpython-37.pyc and /dev/null differ diff --git a/custom_components/hacs/__pycache__/__init__.cpython-38.pyc b/custom_components/hacs/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..adca759 Binary files /dev/null and b/custom_components/hacs/__pycache__/__init__.cpython-38.pyc differ diff --git a/custom_components/hacs/__pycache__/base.cpython-38.pyc b/custom_components/hacs/__pycache__/base.cpython-38.pyc new file mode 100644 index 0000000..d3ca0b8 Binary files /dev/null and b/custom_components/hacs/__pycache__/base.cpython-38.pyc differ diff --git a/custom_components/hacs/__pycache__/config_flow.cpython-37.pyc b/custom_components/hacs/__pycache__/config_flow.cpython-37.pyc deleted file mode 100644 index 56aa705..0000000 Binary files a/custom_components/hacs/__pycache__/config_flow.cpython-37.pyc and /dev/null differ diff --git a/custom_components/hacs/__pycache__/config_flow.cpython-38.pyc b/custom_components/hacs/__pycache__/config_flow.cpython-38.pyc new file mode 100644 index 0000000..608509e Binary files /dev/null and b/custom_components/hacs/__pycache__/config_flow.cpython-38.pyc differ diff --git a/custom_components/hacs/__pycache__/configuration_schema.cpython-37.pyc b/custom_components/hacs/__pycache__/configuration_schema.cpython-37.pyc deleted file mode 100644 index 5f62546..0000000 Binary files a/custom_components/hacs/__pycache__/configuration_schema.cpython-37.pyc and /dev/null differ diff --git a/custom_components/hacs/__pycache__/const.cpython-37.pyc b/custom_components/hacs/__pycache__/const.cpython-37.pyc deleted file mode 100644 index be94846..0000000 Binary files a/custom_components/hacs/__pycache__/const.cpython-37.pyc and /dev/null differ diff --git a/custom_components/hacs/__pycache__/const.cpython-38.pyc b/custom_components/hacs/__pycache__/const.cpython-38.pyc new file mode 100644 index 0000000..62b3011 Binary files /dev/null and b/custom_components/hacs/__pycache__/const.cpython-38.pyc differ diff --git a/custom_components/hacs/__pycache__/constrains.cpython-37.pyc b/custom_components/hacs/__pycache__/constrains.cpython-37.pyc deleted file mode 100644 index 860bdc0..0000000 Binary files a/custom_components/hacs/__pycache__/constrains.cpython-37.pyc and /dev/null differ diff --git a/custom_components/hacs/__pycache__/enums.cpython-38.pyc b/custom_components/hacs/__pycache__/enums.cpython-38.pyc new file mode 100644 index 0000000..6a40443 Binary files /dev/null and b/custom_components/hacs/__pycache__/enums.cpython-38.pyc differ diff --git a/custom_components/hacs/__pycache__/globals.cpython-37.pyc b/custom_components/hacs/__pycache__/globals.cpython-37.pyc deleted file mode 100644 index 6d8c9c9..0000000 Binary files a/custom_components/hacs/__pycache__/globals.cpython-37.pyc and /dev/null differ diff --git a/custom_components/hacs/__pycache__/http.cpython-37.pyc b/custom_components/hacs/__pycache__/http.cpython-37.pyc deleted file mode 100644 index 6a4a911..0000000 Binary files a/custom_components/hacs/__pycache__/http.cpython-37.pyc and /dev/null differ diff --git a/custom_components/hacs/__pycache__/sensor.cpython-37.pyc b/custom_components/hacs/__pycache__/sensor.cpython-37.pyc deleted file mode 100644 index 411e882..0000000 Binary files a/custom_components/hacs/__pycache__/sensor.cpython-37.pyc and /dev/null differ diff --git a/custom_components/hacs/__pycache__/sensor.cpython-38.pyc b/custom_components/hacs/__pycache__/sensor.cpython-38.pyc new file mode 100644 index 0000000..73d92a5 Binary files /dev/null and b/custom_components/hacs/__pycache__/sensor.cpython-38.pyc differ diff --git a/custom_components/hacs/__pycache__/setup.cpython-37.pyc b/custom_components/hacs/__pycache__/setup.cpython-37.pyc deleted file mode 100644 index 8815614..0000000 Binary files a/custom_components/hacs/__pycache__/setup.cpython-37.pyc and /dev/null differ diff --git a/custom_components/hacs/__pycache__/share.cpython-38.pyc b/custom_components/hacs/__pycache__/share.cpython-38.pyc new file mode 100644 index 0000000..0bb1fe6 Binary files /dev/null and b/custom_components/hacs/__pycache__/share.cpython-38.pyc differ diff --git a/custom_components/hacs/__pycache__/store.cpython-37.pyc b/custom_components/hacs/__pycache__/store.cpython-37.pyc deleted file mode 100644 index 10c8526..0000000 Binary files a/custom_components/hacs/__pycache__/store.cpython-37.pyc and /dev/null differ diff --git a/custom_components/hacs/__pycache__/ws_api_handlers.cpython-37.pyc b/custom_components/hacs/__pycache__/ws_api_handlers.cpython-37.pyc deleted file mode 100644 index d994e9a..0000000 Binary files a/custom_components/hacs/__pycache__/ws_api_handlers.cpython-37.pyc and /dev/null differ diff --git a/custom_components/hacs/api/__init__.py b/custom_components/hacs/api/__init__.py new file mode 100644 index 0000000..f8017b3 --- /dev/null +++ b/custom_components/hacs/api/__init__.py @@ -0,0 +1 @@ +"""Initialize HACS API""" diff --git a/custom_components/hacs/api/__pycache__/__init__.cpython-38.pyc b/custom_components/hacs/api/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..3841d57 Binary files /dev/null and b/custom_components/hacs/api/__pycache__/__init__.cpython-38.pyc differ diff --git a/custom_components/hacs/api/__pycache__/acknowledge_critical_repository.cpython-38.pyc b/custom_components/hacs/api/__pycache__/acknowledge_critical_repository.cpython-38.pyc new file mode 100644 index 0000000..93cc168 Binary files /dev/null and b/custom_components/hacs/api/__pycache__/acknowledge_critical_repository.cpython-38.pyc differ diff --git a/custom_components/hacs/api/__pycache__/check_local_path.cpython-38.pyc b/custom_components/hacs/api/__pycache__/check_local_path.cpython-38.pyc new file mode 100644 index 0000000..812583d Binary files /dev/null and b/custom_components/hacs/api/__pycache__/check_local_path.cpython-38.pyc differ diff --git a/custom_components/hacs/api/__pycache__/get_critical_repositories.cpython-38.pyc b/custom_components/hacs/api/__pycache__/get_critical_repositories.cpython-38.pyc new file mode 100644 index 0000000..8d31941 Binary files /dev/null and b/custom_components/hacs/api/__pycache__/get_critical_repositories.cpython-38.pyc differ diff --git a/custom_components/hacs/api/__pycache__/hacs_config.cpython-38.pyc b/custom_components/hacs/api/__pycache__/hacs_config.cpython-38.pyc new file mode 100644 index 0000000..ff8083d Binary files /dev/null and b/custom_components/hacs/api/__pycache__/hacs_config.cpython-38.pyc differ diff --git a/custom_components/hacs/api/__pycache__/hacs_removed.cpython-38.pyc b/custom_components/hacs/api/__pycache__/hacs_removed.cpython-38.pyc new file mode 100644 index 0000000..b73d233 Binary files /dev/null and b/custom_components/hacs/api/__pycache__/hacs_removed.cpython-38.pyc differ diff --git a/custom_components/hacs/api/__pycache__/hacs_repositories.cpython-38.pyc b/custom_components/hacs/api/__pycache__/hacs_repositories.cpython-38.pyc new file mode 100644 index 0000000..4a10629 Binary files /dev/null and b/custom_components/hacs/api/__pycache__/hacs_repositories.cpython-38.pyc differ diff --git a/custom_components/hacs/api/__pycache__/hacs_repository.cpython-38.pyc b/custom_components/hacs/api/__pycache__/hacs_repository.cpython-38.pyc new file mode 100644 index 0000000..84390f4 Binary files /dev/null and b/custom_components/hacs/api/__pycache__/hacs_repository.cpython-38.pyc differ diff --git a/custom_components/hacs/api/__pycache__/hacs_repository_data.cpython-38.pyc b/custom_components/hacs/api/__pycache__/hacs_repository_data.cpython-38.pyc new file mode 100644 index 0000000..1a45178 Binary files /dev/null and b/custom_components/hacs/api/__pycache__/hacs_repository_data.cpython-38.pyc differ diff --git a/custom_components/hacs/api/__pycache__/hacs_settings.cpython-38.pyc b/custom_components/hacs/api/__pycache__/hacs_settings.cpython-38.pyc new file mode 100644 index 0000000..a345469 Binary files /dev/null and b/custom_components/hacs/api/__pycache__/hacs_settings.cpython-38.pyc differ diff --git a/custom_components/hacs/api/__pycache__/hacs_status.cpython-38.pyc b/custom_components/hacs/api/__pycache__/hacs_status.cpython-38.pyc new file mode 100644 index 0000000..6fe6e8e Binary files /dev/null and b/custom_components/hacs/api/__pycache__/hacs_status.cpython-38.pyc differ diff --git a/custom_components/hacs/api/acknowledge_critical_repository.py b/custom_components/hacs/api/acknowledge_critical_repository.py new file mode 100644 index 0000000..02235b2 --- /dev/null +++ b/custom_components/hacs/api/acknowledge_critical_repository.py @@ -0,0 +1,25 @@ +"""API Handler for acknowledge_critical_repository""" +import homeassistant.helpers.config_validation as cv +import voluptuous as vol +from homeassistant.components import websocket_api + +from custom_components.hacs.helpers.functions.store import ( + async_load_from_store, + async_save_to_store, +) + + +@websocket_api.async_response +@websocket_api.websocket_command( + {vol.Required("type"): "hacs/critical", vol.Optional("repository"): cv.string} +) +async def acknowledge_critical_repository(hass, connection, msg): + """Handle get media player cover command.""" + repository = msg["repository"] + + critical = await async_load_from_store(hass, "critical") + for repo in critical: + if repository == repo["repository"]: + repo["acknowledged"] = True + await async_save_to_store(hass, "critical", critical) + connection.send_message(websocket_api.result_message(msg["id"], critical)) diff --git a/custom_components/hacs/api/check_local_path.py b/custom_components/hacs/api/check_local_path.py new file mode 100644 index 0000000..308b81b --- /dev/null +++ b/custom_components/hacs/api/check_local_path.py @@ -0,0 +1,24 @@ +"""API Handler for check_local_path""" +import homeassistant.helpers.config_validation as cv +import voluptuous as vol +from homeassistant.components import websocket_api + +from custom_components.hacs.helpers.functions.path_exsist import async_path_exsist + + +@websocket_api.async_response +@websocket_api.websocket_command( + {vol.Required("type"): "hacs/check_path", vol.Optional("path"): cv.string} +) +async def check_local_path(_hass, connection, msg): + """Handle get media player cover command.""" + path = msg.get("path") + exist = {"exist": False} + + if path is None: + return + + if await async_path_exsist(path): + exist["exist"] = True + + connection.send_message(websocket_api.result_message(msg["id"], exist)) diff --git a/custom_components/hacs/api/get_critical_repositories.py b/custom_components/hacs/api/get_critical_repositories.py new file mode 100644 index 0000000..35f2c94 --- /dev/null +++ b/custom_components/hacs/api/get_critical_repositories.py @@ -0,0 +1,15 @@ +"""API Handler for get_critical_repositories""" +import voluptuous as vol +from homeassistant.components import websocket_api + +from custom_components.hacs.helpers.functions.store import async_load_from_store + + +@websocket_api.async_response +@websocket_api.websocket_command({vol.Required("type"): "hacs/get_critical"}) +async def get_critical_repositories(hass, connection, msg): + """Handle get media player cover command.""" + critical = await async_load_from_store(hass, "critical") + if not critical: + critical = [] + connection.send_message(websocket_api.result_message(msg["id"], critical)) diff --git a/custom_components/hacs/api/hacs_config.py b/custom_components/hacs/api/hacs_config.py new file mode 100644 index 0000000..1760ee8 --- /dev/null +++ b/custom_components/hacs/api/hacs_config.py @@ -0,0 +1,28 @@ +"""API Handler for hacs_config""" +import voluptuous as vol +from homeassistant.components import websocket_api + +from custom_components.hacs.share import get_hacs + + +@websocket_api.async_response +@websocket_api.websocket_command({vol.Required("type"): "hacs/config"}) +async def hacs_config(_hass, connection, msg): + """Handle get media player cover command.""" + hacs = get_hacs() + config = hacs.configuration + + content = {} + content["frontend_mode"] = config.frontend_mode + content["frontend_compact"] = config.frontend_compact + content["onboarding_done"] = config.onboarding_done + content["version"] = hacs.version + content["frontend_expected"] = hacs.frontend.version_expected + content["frontend_running"] = hacs.frontend.version_running + content["dev"] = config.dev + content["debug"] = config.debug + content["country"] = config.country + content["experimental"] = config.experimental + content["categories"] = hacs.common.categories + + connection.send_message(websocket_api.result_message(msg["id"], content)) diff --git a/custom_components/hacs/api/hacs_removed.py b/custom_components/hacs/api/hacs_removed.py new file mode 100644 index 0000000..28628e8 --- /dev/null +++ b/custom_components/hacs/api/hacs_removed.py @@ -0,0 +1,15 @@ +"""API Handler for hacs_removed""" +import voluptuous as vol +from homeassistant.components import websocket_api + +from custom_components.hacs.share import list_removed_repositories + + +@websocket_api.async_response +@websocket_api.websocket_command({vol.Required("type"): "hacs/removed"}) +async def hacs_removed(_hass, connection, msg): + """Get information about removed repositories.""" + content = [] + for repo in list_removed_repositories(): + content.append(repo.to_json()) + connection.send_message(websocket_api.result_message(msg["id"], content)) diff --git a/custom_components/hacs/api/hacs_repositories.py b/custom_components/hacs/api/hacs_repositories.py new file mode 100644 index 0000000..e003342 --- /dev/null +++ b/custom_components/hacs/api/hacs_repositories.py @@ -0,0 +1,62 @@ +"""API Handler for hacs_repositories""" +import voluptuous as vol +from homeassistant.components import websocket_api + +from custom_components.hacs.share import get_hacs + + +@websocket_api.async_response +@websocket_api.websocket_command({vol.Required("type"): "hacs/repositories"}) +async def hacs_repositories(_hass, connection, msg): + """Handle get media player cover command.""" + hacs = get_hacs() + repositories = hacs.repositories + content = [] + for repo in repositories: + if repo.data.category in hacs.common.categories: + data = { + "additional_info": repo.information.additional_info, + "authors": repo.data.authors, + "available_version": repo.display_available_version, + "beta": repo.data.show_beta, + "can_install": repo.can_install, + "category": repo.data.category, + "country": repo.data.country, + "config_flow": repo.data.config_flow, + "custom": repo.custom, + "default_branch": repo.data.default_branch, + "description": repo.data.description, + "domain": repo.data.domain, + "downloads": repo.data.downloads, + "file_name": repo.data.file_name, + "first_install": repo.status.first_install, + "full_name": repo.data.full_name, + "hide": repo.data.hide, + "hide_default_branch": repo.data.hide_default_branch, + "homeassistant": repo.data.homeassistant, + "id": repo.data.id, + "info": repo.information.info, + "installed_version": repo.display_installed_version, + "installed": repo.data.installed, + "issues": repo.data.open_issues, + "javascript_type": repo.information.javascript_type, + "last_updated": repo.data.last_updated, + "local_path": repo.content.path.local, + "main_action": repo.main_action, + "name": repo.display_name, + "new": repo.data.new, + "pending_upgrade": repo.pending_upgrade, + "releases": repo.data.published_tags, + "selected_tag": repo.data.selected_tag, + "stars": repo.data.stargazers_count, + "state": repo.state, + "status_description": repo.display_status_description, + "status": repo.display_status, + "topics": repo.data.topics, + "updated_info": repo.status.updated_info, + "version_or_commit": repo.display_version_or_commit, + } + + content.append(data) + + connection.send_message(websocket_api.result_message(msg["id"], content)) diff --git a/custom_components/hacs/api/hacs_repository.py b/custom_components/hacs/api/hacs_repository.py new file mode 100644 index 0000000..80257ea --- /dev/null +++ b/custom_components/hacs/api/hacs_repository.py @@ -0,0 +1,113 @@ +"""API Handler for hacs_repository""" +import homeassistant.helpers.config_validation as cv +import voluptuous as vol +from aiogithubapi import AIOGitHubAPIException +from homeassistant.components import websocket_api + +from custom_components.hacs.helpers.functions.logger import getLogger +from custom_components.hacs.share import get_hacs + + +@websocket_api.async_response +@websocket_api.websocket_command( + { + vol.Required("type"): "hacs/repository", + vol.Optional("action"): cv.string, + vol.Optional("repository"): cv.string, + } +) +async def hacs_repository(hass, connection, msg): + """Handle get media player cover command.""" + hacs = get_hacs() + logger = getLogger() + data = {} + repository = None + + repo_id = msg.get("repository") + action = msg.get("action") + if repo_id is None or action is None: + return + + try: + repository = hacs.get_by_id(repo_id) + logger.debug(f"Running {action} for {repository.data.full_name}") + + if action == "update": + await repository.update_repository(True) + repository.status.updated_info = True + + elif action == "install": + repository.data.new = False + was_installed = repository.data.installed + await repository.async_install() + if not was_installed: + hass.bus.async_fire("hacs/reload", {"force": True}) + + elif action == "not_new": + repository.data.new = False + + elif action == "uninstall": + repository.data.new = False + await repository.update_repository(True) + await repository.uninstall() + + elif action == "hide": + repository.data.hide = True + + elif action == "unhide": + repository.data.hide = False + + elif action == "show_beta": + repository.data.show_beta = True + await repository.update_repository() + + elif action == "hide_beta": + repository.data.show_beta = False + await repository.update_repository() + + elif action == "toggle_beta": + repository.data.show_beta = not repository.data.show_beta + await repository.update_repository() + + elif action == "delete": + repository.data.show_beta = False + repository.remove() + + elif action == "release_notes": + data = [ + { + "name": x.attributes["name"], + "body": x.attributes["body"], + "tag": x.attributes["tag_name"], + } + for x in repository.releases.objects + ] + + elif action == "set_version": + if msg["version"] == repository.data.default_branch: + repository.data.selected_tag = None + else: + repository.data.selected_tag = msg["version"] + await repository.update_repository() + + hass.bus.async_fire("hacs/reload", {"force": True}) + + else: + logger.error(f"WS action '{action}' is not valid") + + await hacs.data.async_write() + message = None + except AIOGitHubAPIException as exception: + message = exception + except AttributeError as exception: + message = f"Could not use repository with ID {repo_id} ({exception})" + except (Exception, BaseException) as exception: # pylint: disable=broad-except + message = exception + + if message is not None: + logger.error(message) + hass.bus.async_fire("hacs/error", {"message": str(message)}) + + if repository: + repository.state = None + connection.send_message(websocket_api.result_message(msg["id"], data)) diff --git a/custom_components/hacs/api/hacs_repository_data.py b/custom_components/hacs/api/hacs_repository_data.py new file mode 100644 index 0000000..3052e72 --- /dev/null +++ b/custom_components/hacs/api/hacs_repository_data.py @@ -0,0 +1,121 @@ +"""API Handler for hacs_repository_data""" +import sys + +import homeassistant.helpers.config_validation as cv +import voluptuous as vol +from aiogithubapi import AIOGitHubAPIException +from homeassistant.components import websocket_api + +from custom_components.hacs.helpers.classes.exceptions import HacsException +from custom_components.hacs.helpers.functions.logger import getLogger +from custom_components.hacs.helpers.functions.misc import extract_repository_from_url +from custom_components.hacs.helpers.functions.register_repository import ( + register_repository, +) +from custom_components.hacs.share import get_hacs + +_LOGGER = getLogger() + + +@websocket_api.async_response +@websocket_api.websocket_command( + { + vol.Required("type"): "hacs/repository/data", + vol.Optional("action"): cv.string, + vol.Optional("repository"): cv.string, + vol.Optional("data"): cv.string, + } +) +async def hacs_repository_data(hass, connection, msg): + """Handle get media player cover command.""" + hacs = get_hacs() + repo_id = msg.get("repository") + action = msg.get("action") + data = msg.get("data") + + if repo_id is None: + return + + if action == "add": + repo_id = extract_repository_from_url(repo_id) + if repo_id is None: + return + + if repo_id in hacs.common.skip: + hacs.common.skip.remove(repo_id) + + if not hacs.get_by_name(repo_id): + try: + registration = await register_repository(repo_id, data.lower()) + if registration is not None: + raise HacsException(registration) + except ( + Exception, + BaseException, + ) as exception: # pylint: disable=broad-except + hass.bus.async_fire( + "hacs/error", + { + "action": "add_repository", + "exception": str(sys.exc_info()[0].__name__), + "message": str(exception), + }, + ) + else: + hass.bus.async_fire( + "hacs/error", + { + "action": "add_repository", + "message": f"Repository '{repo_id}' exists in the store.", + }, + ) + + repository = hacs.get_by_name(repo_id) + else: + repository = hacs.get_by_id(repo_id) + + if repository is None: + hass.bus.async_fire("hacs/repository", {}) + return + + _LOGGER.debug("Running %s for %s", action, repository.data.full_name) + try: + if action == "set_state": + repository.state = data + + elif action == "set_version": + repository.data.selected_tag = data + await repository.update_repository() + + repository.state = None + + elif action == "install": + was_installed = repository.data.installed + repository.data.selected_tag = data + await repository.update_repository() + await repository.async_install() + repository.state = None + if not was_installed: + hass.bus.async_fire("hacs/reload", {"force": True}) + + elif action == "add": + repository.state = None + + else: + repository.state = None + _LOGGER.error("WS action '%s' is not valid", action) + + message = None + except AIOGitHubAPIException as exception: + message = exception + except AttributeError as exception: + message = f"Could not use repository with ID {repo_id} ({exception})" + except (Exception, BaseException) as exception: # pylint: disable=broad-except + message = exception + + if message is not None: + _LOGGER.error(message) + hass.bus.async_fire("hacs/error", {"message": str(message)}) + + await hacs.data.async_write() + connection.send_message(websocket_api.result_message(msg["id"], {})) diff --git a/custom_components/hacs/api/hacs_settings.py b/custom_components/hacs/api/hacs_settings.py new file mode 100644 index 0000000..fe07634 --- /dev/null +++ b/custom_components/hacs/api/hacs_settings.py @@ -0,0 +1,54 @@ +"""API Handler for hacs_settings""" +import homeassistant.helpers.config_validation as cv +import voluptuous as vol +from homeassistant.components import websocket_api + +from custom_components.hacs.helpers.functions.logger import getLogger +from custom_components.hacs.share import get_hacs + +_LOGGER = getLogger() + + +@websocket_api.async_response +@websocket_api.websocket_command( + { + vol.Required("type"): "hacs/settings", + vol.Optional("action"): cv.string, + vol.Optional("categories"): cv.ensure_list, + } +) +async def hacs_settings(hass, connection, msg): + """Handle get media player cover command.""" + hacs = get_hacs() + + action = msg["action"] + _LOGGER.debug("WS action '%s'", action) + + if action == "set_fe_grid": + hacs.configuration.frontend_mode = "Grid" + + elif action == "onboarding_done": + hacs.configuration.onboarding_done = True + + elif action == "set_fe_table": + hacs.configuration.frontend_mode = "Table" + + elif action == "set_fe_compact_true": + hacs.configuration.frontend_compact = False + + elif action == "set_fe_compact_false": + hacs.configuration.frontend_compact = True + + elif action == "clear_new": + for repo in hacs.repositories: + if repo.data.new and repo.data.category in msg.get("categories", []): + _LOGGER.debug( + "Clearing new flag from '%s'", + repo.data.full_name, + ) + repo.data.new = False + else: + _LOGGER.error("WS action '%s' is not valid", action) + hass.bus.async_fire("hacs/config", {}) + await hacs.data.async_write() + connection.send_message(websocket_api.result_message(msg["id"], {})) diff --git a/custom_components/hacs/api/hacs_status.py b/custom_components/hacs/api/hacs_status.py new file mode 100644 index 0000000..058c201 --- /dev/null +++ b/custom_components/hacs/api/hacs_status.py @@ -0,0 +1,23 @@ +"""API Handler for hacs_status""" +import voluptuous as vol +from homeassistant.components import websocket_api + +from custom_components.hacs.share import get_hacs + + +@websocket_api.async_response +@websocket_api.websocket_command({vol.Required("type"): "hacs/status"}) +async def hacs_status(_hass, connection, msg): + """Handle get media player cover command.""" + hacs = get_hacs() + content = { + "startup": hacs.status.startup, + "background_task": hacs.status.background_task, + "lovelace_mode": hacs.system.lovelace_mode, + "reloading_data": hacs.status.reloading_data, + "upgrading_all": hacs.status.upgrading_all, + "disabled": hacs.system.disabled, + "has_pending_tasks": hacs.queue.has_pending_tasks, + "stage": hacs.stage, + } + connection.send_message(websocket_api.result_message(msg["id"], content)) diff --git a/custom_components/hacs/base.py b/custom_components/hacs/base.py new file mode 100644 index 0000000..027cc50 --- /dev/null +++ b/custom_components/hacs/base.py @@ -0,0 +1,108 @@ +"""Base HACS class.""" +import logging +from typing import List, Optional +import attr + +from aiogithubapi.github import AIOGitHubAPI +from aiogithubapi.objects.repository import AIOGitHubAPIRepository +from homeassistant.core import HomeAssistant + +from .enums import HacsStage +from .helpers.functions.logger import getLogger +from .models.core import HacsCore +from .models.frontend import HacsFrontend +from .models.system import HacsSystem + + +class HacsCommon: + """Common for HACS.""" + + categories: List = [] + default: List = [] + installed: List = [] + skip: List = [] + + +class HacsStatus: + """HacsStatus.""" + + startup: bool = True + new: bool = False + background_task: bool = False + reloading_data: bool = False + upgrading_all: bool = False + + +@attr.s +class HacsBaseAttributes: + """Base HACS class.""" + + _default: Optional[AIOGitHubAPIRepository] + _github: Optional[AIOGitHubAPI] + _hass: Optional[HomeAssistant] + _repository: Optional[AIOGitHubAPIRepository] + _stage: HacsStage = HacsStage.SETUP + _common: Optional[HacsCommon] + + core: HacsCore = attr.ib(HacsCore) + common: HacsCommon = attr.ib(HacsCommon) + status: HacsStatus = attr.ib(HacsStatus) + frontend: HacsFrontend = attr.ib(HacsFrontend) + log: logging.Logger = getLogger() + system: HacsSystem = attr.ib(HacsSystem) + repositories: List = [] + + +@attr.s +class HacsBase(HacsBaseAttributes): + """Base HACS class.""" + + @property + def stage(self) -> HacsStage: + """Returns a HacsStage object.""" + return self._stage + + @stage.setter + def stage(self, value: HacsStage) -> None: + """Set the value for the stage property.""" + self._stage = value + + @property + def github(self) -> Optional[AIOGitHubAPI]: + """Returns a AIOGitHubAPI object.""" + return self._github + + @github.setter + def github(self, value: AIOGitHubAPI) -> None: + """Set the value for the github property.""" + self._github = value + + @property + def repository(self) -> Optional[AIOGitHubAPIRepository]: + """Returns a AIOGitHubAPIRepository object representing hacs/integration.""" + return self._repository + + @repository.setter + def repository(self, value: AIOGitHubAPIRepository) -> None: + """Set the value for the repository property.""" + self._repository = value + + @property + def default(self) -> Optional[AIOGitHubAPIRepository]: + """Returns a AIOGitHubAPIRepository object representing hacs/default.""" + return self._default + + @default.setter + def default(self, value: AIOGitHubAPIRepository) -> None: + """Set the value for the default property.""" + self._default = value + + @property + def hass(self) -> Optional[HomeAssistant]: + """Returns a HomeAssistant object.""" + return self._hass + + @hass.setter + def hass(self, value: HomeAssistant) -> None: + """Set the value for the default property.""" + self._hass = value diff --git a/custom_components/hacs/config_flow.py b/custom_components/hacs/config_flow.py index 7bf329d..0a0c5ef 100644 --- a/custom_components/hacs/config_flow.py +++ b/custom_components/hacs/config_flow.py @@ -1,102 +1,144 @@ -"""Adds config flow for HACS.""" -# pylint: disable=dangerous-default-value -import logging -import voluptuous as vol -from aiogithubapi import AIOGitHubException, AIOGitHubAuthentication -from homeassistant import config_entries -from homeassistant.core import callback -from homeassistant.helpers import aiohttp_client - -from .const import DOMAIN -from .configuration_schema import hacs_base_config_schema, hacs_config_option_schema - -from custom_components.hacs.globals import get_hacs -from custom_components.hacs.helpers.information import get_repository - -_LOGGER = logging.getLogger(__name__) - - -class HacsFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): - """Config flow for HACS.""" - - VERSION = 1 - CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_POLL - - def __init__(self): - """Initialize.""" - self._errors = {} - - async def async_step_user(self, user_input={}): - """Handle a flow initialized by the user.""" - self._errors = {} - if self._async_current_entries(): - return self.async_abort(reason="single_instance_allowed") - if self.hass.data.get(DOMAIN): - return self.async_abort(reason="single_instance_allowed") - - if user_input is not None: - if await self._test_token(user_input["token"]): - return self.async_create_entry(title="", data=user_input) - - self._errors["base"] = "auth" - return await self._show_config_form(user_input) - - return await self._show_config_form(user_input) - - async def _show_config_form(self, user_input): - """Show the configuration form to edit location data.""" - return self.async_show_form( - step_id="user", - data_schema=vol.Schema(hacs_base_config_schema(user_input, True)), - errors=self._errors, - ) - - @staticmethod - @callback - def async_get_options_flow(config_entry): - return HacsOptionsFlowHandler(config_entry) - - async def async_step_import(self, user_input): - """Import a config entry. - Special type of import, we're not actually going to store any data. - Instead, we're going to rely on the values that are in config file. - """ - if self._async_current_entries(): - return self.async_abort(reason="single_instance_allowed") - - return self.async_create_entry(title="configuration.yaml", data={}) - - async def _test_token(self, token): - """Return true if token is valid.""" - try: - session = aiohttp_client.async_get_clientsession(self.hass) - await get_repository(session, token, "hacs/org") - return True - except (AIOGitHubException, AIOGitHubAuthentication) as exception: - _LOGGER.error(exception) - return False - - -class HacsOptionsFlowHandler(config_entries.OptionsFlow): - """HACS config flow options handler.""" - - def __init__(self, config_entry): - """Initialize HACS options flow.""" - self.config_entry = config_entry - - async def async_step_init(self, user_input=None): - """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.""" - hacs = get_hacs() - if user_input is not None: - return self.async_create_entry(title="", data=user_input) - - if hacs.configuration.config_type == "yaml": - schema = {vol.Optional("not_in_use", default=""): str} - else: - schema = hacs_config_option_schema(self.config_entry.options) - - return self.async_show_form(step_id="user", data_schema=vol.Schema(schema)) +"""Adds config flow for HACS.""" +import voluptuous as vol +from aiogithubapi import ( + AIOGitHubAPIAuthenticationException, + AIOGitHubAPIException, + GitHubDevice, +) +from aiogithubapi.common.const import OAUTH_USER_LOGIN +from homeassistant import config_entries +from homeassistant.core import callback +from homeassistant.helpers import aiohttp_client + +from custom_components.hacs.const import DOMAIN +from custom_components.hacs.helpers.functions.configuration_schema import ( + hacs_config_option_schema, +) +from custom_components.hacs.helpers.functions.logger import getLogger +from custom_components.hacs.share import get_hacs + +from .base import HacsBase + +_LOGGER = getLogger() + + +class HacsFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): + """Config flow for HACS.""" + + VERSION = 1 + CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_POLL + + def __init__(self): + """Initialize.""" + self._errors = {} + self.device = None + + async def async_step_device(self, user_input): + """Handle device steps""" + ## Vaiting for token + try: + activation = await self.device.async_device_activation() + return self.async_create_entry( + title="", data={"token": activation.access_token} + ) + except ( + AIOGitHubAPIException, + AIOGitHubAPIAuthenticationException, + ) as exception: + _LOGGER.error(exception) + self._errors["base"] = "auth" + return await self._show_config_form(user_input) + + async def async_step_user(self, user_input): + """Handle a flow initialized by the user.""" + self._errors = {} + if self._async_current_entries(): + return self.async_abort(reason="single_instance_allowed") + if self.hass.data.get(DOMAIN): + return self.async_abort(reason="single_instance_allowed") + + if user_input: + if [x for x in user_input if not user_input[x]]: + self._errors["base"] = "acc" + return await self._show_config_form(user_input) + + ## Get device key + if not self.device: + return await self._show_device_form() + + ## Initial form + return await self._show_config_form(user_input) + + async def _show_device_form(self): + """Device flow""" + self.device = GitHubDevice( + "395a8e669c5de9f7c6e8", + session=aiohttp_client.async_get_clientsession(self.hass), + ) + device_data = await self.device.async_register_device() + + return self.async_show_form( + step_id="device", + errors=self._errors, + description_placeholders={ + "url": OAUTH_USER_LOGIN, + "code": device_data.user_code, + }, + ) + + async def _show_config_form(self, user_input): + """Show the configuration form to edit location data.""" + if not user_input: + user_input = {} + return self.async_show_form( + step_id="user", + data_schema=vol.Schema( + { + vol.Required( + "acc_logs", default=user_input.get("acc_logs", False) + ): bool, + vol.Required( + "acc_addons", default=user_input.get("acc_addons", False) + ): bool, + vol.Required( + "acc_untested", default=user_input.get("acc_untested", False) + ): bool, + vol.Required( + "acc_disable", default=user_input.get("acc_disable", False) + ): bool, + } + ), + errors=self._errors, + ) + + @staticmethod + @callback + def async_get_options_flow(config_entry): + return HacsOptionsFlowHandler(config_entry) + + +class HacsOptionsFlowHandler(config_entries.OptionsFlow): + """HACS config flow options handler.""" + + def __init__(self, config_entry): + """Initialize HACS options flow.""" + self.config_entry = config_entry + + async def async_step_init(self, _user_input=None): + """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.""" + hacs: HacsBase = get_hacs() + if user_input is not None: + return self.async_create_entry(title="", data=user_input) + + if hacs.configuration.config_type == "yaml": + schema = {vol.Optional("not_in_use", default=""): str} + else: + schema = hacs_config_option_schema(self.config_entry.options) + del schema["frontend_repo"] + del schema["frontend_repo_url"] + + return self.async_show_form(step_id="user", data_schema=vol.Schema(schema)) diff --git a/custom_components/hacs/const.py b/custom_components/hacs/const.py index 47f44f2..c7411da 100644 --- a/custom_components/hacs/const.py +++ b/custom_components/hacs/const.py @@ -1,293 +1,299 @@ -"""Constants for HACS""" -NAME_LONG = "HACS (Home Assistant Community Store)" -NAME_SHORT = "HACS" -VERSION = "0.23.2" -DOMAIN = "hacs" -PROJECT_URL = "https://github.com/hacs/integration/" -CUSTOM_UPDATER_LOCATIONS = [ - "{}/custom_components/custom_updater.py", - "{}/custom_components/custom_updater/__init__.py", -] - -ISSUE_URL = f"{PROJECT_URL}issues" -DOMAIN_DATA = f"{NAME_SHORT.lower()}_data" - -ELEMENT_TYPES = ["integration", "plugin"] - -IFRAME = { - "title": "Community", - "icon": "mdi:alpha-c-box", - "url": "/community_overview", - "path": "community", - "require_admin": True, -} - - -# Messages -CUSTOM_UPDATER_WARNING = """ -This cannot be used with custom_updater. -To use this you need to remove custom_updater form {} -""" - -STARTUP = f""" -------------------------------------------------------------------- -HACS (Home Assistant Community Store) - -Version: {VERSION} -This is a custom integration -If you have any issues with this you need to open an issue here: -https://github.com/hacs/integration/issues -------------------------------------------------------------------- -""" - -LOCALE = [ - "ALL", - "AF", - "AL", - "DZ", - "AS", - "AD", - "AO", - "AI", - "AQ", - "AG", - "AR", - "AM", - "AW", - "AU", - "AT", - "AZ", - "BS", - "BH", - "BD", - "BB", - "BY", - "BE", - "BZ", - "BJ", - "BM", - "BT", - "BO", - "BQ", - "BA", - "BW", - "BV", - "BR", - "IO", - "BN", - "BG", - "BF", - "BI", - "KH", - "CM", - "CA", - "CV", - "KY", - "CF", - "TD", - "CL", - "CN", - "CX", - "CC", - "CO", - "KM", - "CG", - "CD", - "CK", - "CR", - "HR", - "CU", - "CW", - "CY", - "CZ", - "CI", - "DK", - "DJ", - "DM", - "DO", - "EC", - "EG", - "SV", - "GQ", - "ER", - "EE", - "ET", - "FK", - "FO", - "FJ", - "FI", - "FR", - "GF", - "PF", - "TF", - "GA", - "GM", - "GE", - "DE", - "GH", - "GI", - "GR", - "GL", - "GD", - "GP", - "GU", - "GT", - "GG", - "GN", - "GW", - "GY", - "HT", - "HM", - "VA", - "HN", - "HK", - "HU", - "IS", - "IN", - "ID", - "IR", - "IQ", - "IE", - "IM", - "IL", - "IT", - "JM", - "JP", - "JE", - "JO", - "KZ", - "KE", - "KI", - "KP", - "KR", - "KW", - "KG", - "LA", - "LV", - "LB", - "LS", - "LR", - "LY", - "LI", - "LT", - "LU", - "MO", - "MK", - "MG", - "MW", - "MY", - "MV", - "ML", - "MT", - "MH", - "MQ", - "MR", - "MU", - "YT", - "MX", - "FM", - "MD", - "MC", - "MN", - "ME", - "MS", - "MA", - "MZ", - "MM", - "NA", - "NR", - "NP", - "NL", - "NC", - "NZ", - "NI", - "NE", - "NG", - "NU", - "NF", - "MP", - "NO", - "OM", - "PK", - "PW", - "PS", - "PA", - "PG", - "PY", - "PE", - "PH", - "PN", - "PL", - "PT", - "PR", - "QA", - "RO", - "RU", - "RW", - "RE", - "BL", - "SH", - "KN", - "LC", - "MF", - "PM", - "VC", - "WS", - "SM", - "ST", - "SA", - "SN", - "RS", - "SC", - "SL", - "SG", - "SX", - "SK", - "SI", - "SB", - "SO", - "ZA", - "GS", - "SS", - "ES", - "LK", - "SD", - "SR", - "SJ", - "SZ", - "SE", - "CH", - "SY", - "TW", - "TJ", - "TZ", - "TH", - "TL", - "TG", - "TK", - "TO", - "TT", - "TN", - "TR", - "TM", - "TC", - "TV", - "UG", - "UA", - "AE", - "GB", - "US", - "UM", - "UY", - "UZ", - "VU", - "VE", - "VN", - "VG", - "VI", - "WF", - "EH", - "YE", - "ZM", - "ZW", -] +"""Constants for HACS""" +NAME_LONG = "HACS (Home Assistant Community Store)" +NAME_SHORT = "HACS" +VERSION = "1.8.0" +DOMAIN = "hacs" +PROJECT_URL = "https://github.com/hacs/integration/" +CUSTOM_UPDATER_LOCATIONS = [ + "{}/custom_components/custom_updater.py", + "{}/custom_components/custom_updater/__init__.py", +] + +ISSUE_URL = f"{PROJECT_URL}issues" +DOMAIN_DATA = f"{NAME_SHORT.lower()}_data" + +ELEMENT_TYPES = ["integration", "plugin"] + +PACKAGE_NAME = "custom_components.hacs" + +IFRAME = { + "title": "HACS", + "icon": "hacs:hacs", + "url": "/community_overview", + "path": "community", + "require_admin": True, +} + +VERSION_STORAGE = "6" +STORENAME = "hacs" + +# Messages +NO_ELEMENTS = "No elements to show, open the store to install some awesome stuff." + +CUSTOM_UPDATER_WARNING = """ +This cannot be used with custom_updater. +To use this you need to remove custom_updater form {} +""" + +STARTUP = f""" +------------------------------------------------------------------- +HACS (Home Assistant Community Store) + +Version: {VERSION} +This is a custom integration +If you have any issues with this you need to open an issue here: +https://github.com/hacs/integration/issues +------------------------------------------------------------------- +""" + +LOCALE = [ + "ALL", + "AF", + "AL", + "DZ", + "AS", + "AD", + "AO", + "AI", + "AQ", + "AG", + "AR", + "AM", + "AW", + "AU", + "AT", + "AZ", + "BS", + "BH", + "BD", + "BB", + "BY", + "BE", + "BZ", + "BJ", + "BM", + "BT", + "BO", + "BQ", + "BA", + "BW", + "BV", + "BR", + "IO", + "BN", + "BG", + "BF", + "BI", + "KH", + "CM", + "CA", + "CV", + "KY", + "CF", + "TD", + "CL", + "CN", + "CX", + "CC", + "CO", + "KM", + "CG", + "CD", + "CK", + "CR", + "HR", + "CU", + "CW", + "CY", + "CZ", + "CI", + "DK", + "DJ", + "DM", + "DO", + "EC", + "EG", + "SV", + "GQ", + "ER", + "EE", + "ET", + "FK", + "FO", + "FJ", + "FI", + "FR", + "GF", + "PF", + "TF", + "GA", + "GM", + "GE", + "DE", + "GH", + "GI", + "GR", + "GL", + "GD", + "GP", + "GU", + "GT", + "GG", + "GN", + "GW", + "GY", + "HT", + "HM", + "VA", + "HN", + "HK", + "HU", + "IS", + "IN", + "ID", + "IR", + "IQ", + "IE", + "IM", + "IL", + "IT", + "JM", + "JP", + "JE", + "JO", + "KZ", + "KE", + "KI", + "KP", + "KR", + "KW", + "KG", + "LA", + "LV", + "LB", + "LS", + "LR", + "LY", + "LI", + "LT", + "LU", + "MO", + "MK", + "MG", + "MW", + "MY", + "MV", + "ML", + "MT", + "MH", + "MQ", + "MR", + "MU", + "YT", + "MX", + "FM", + "MD", + "MC", + "MN", + "ME", + "MS", + "MA", + "MZ", + "MM", + "NA", + "NR", + "NP", + "NL", + "NC", + "NZ", + "NI", + "NE", + "NG", + "NU", + "NF", + "MP", + "NO", + "OM", + "PK", + "PW", + "PS", + "PA", + "PG", + "PY", + "PE", + "PH", + "PN", + "PL", + "PT", + "PR", + "QA", + "RO", + "RU", + "RW", + "RE", + "BL", + "SH", + "KN", + "LC", + "MF", + "PM", + "VC", + "WS", + "SM", + "ST", + "SA", + "SN", + "RS", + "SC", + "SL", + "SG", + "SX", + "SK", + "SI", + "SB", + "SO", + "ZA", + "GS", + "SS", + "ES", + "LK", + "SD", + "SR", + "SJ", + "SZ", + "SE", + "CH", + "SY", + "TW", + "TJ", + "TZ", + "TH", + "TL", + "TG", + "TK", + "TO", + "TT", + "TN", + "TR", + "TM", + "TC", + "TV", + "UG", + "UA", + "AE", + "GB", + "US", + "UM", + "UY", + "UZ", + "VU", + "VE", + "VN", + "VG", + "VI", + "WF", + "EH", + "YE", + "ZM", + "ZW", +] diff --git a/custom_components/hacs/constrains.py b/custom_components/hacs/constrains.py deleted file mode 100644 index 269cba8..0000000 --- a/custom_components/hacs/constrains.py +++ /dev/null @@ -1,99 +0,0 @@ -"""HACS Startup constrains.""" -# pylint: disable=bad-continuation -import os - -from .const import CUSTOM_UPDATER_LOCATIONS, CUSTOM_UPDATER_WARNING -from .helpers.misc import version_left_higher_then_right - -from custom_components.hacs.globals import get_hacs - -MINIMUM_HA_VERSION = "0.98.0" - - -def check_constans(): - """Check HACS constrains.""" - if not constrain_translations(): - return False - if not constrain_custom_updater(): - return False - if not constrain_version(): - return False - return True - - -def constrain_custom_updater(): - """Check if custom_updater exist.""" - hacs = get_hacs() - for location in CUSTOM_UPDATER_LOCATIONS: - if os.path.exists(location.format(hacs.system.config_path)): - msg = CUSTOM_UPDATER_WARNING.format( - location.format(hacs.system.config_path) - ) - hacs.logger.critical(msg) - return False - return True - - -def constrain_version(): - """Check if the version is valid.""" - hacs = get_hacs() - if not version_left_higher_then_right(hacs.system.ha_version, MINIMUM_HA_VERSION): - hacs.logger.critical( - f"You need HA version {MINIMUM_HA_VERSION} or newer to use this integration." - ) - return False - return True - - -def constrain_translations(): - """Check if traslations exist.""" - hacs = get_hacs() - if not os.path.exists( - f"{hacs.system.config_path}/custom_components/hacs/.translations" - ): - hacs.logger.critical("You are missing the translations directory.") - return False - return True - - -def check_requirements(): - """Check the requirements""" - missing = [] - try: - from aiogithubapi import AIOGitHubException # pylint: disable=unused-import - except ImportError: - missing.append("aiogithubapi") - - try: - from hacs_frontend import locate_gz # pylint: disable=unused-import - except ImportError: - missing.append("hacs_frontend") - - try: - import semantic_version # pylint: disable=unused-import - except ImportError: - missing.append("semantic_version") - - try: - from integrationhelper import Logger # pylint: disable=unused-import - except ImportError: - missing.append("integrationhelper") - - try: - import backoff # pylint: disable=unused-import - except ImportError: - missing.append("backoff") - - try: - import aiofiles # pylint: disable=unused-import - except ImportError: - missing.append("aiofiles") - - if missing: - hacs = get_hacs() - for requirement in missing: - hacs.logger.critical( - f"Required python requirement '{requirement}' is not installed" - ) - return False - return True diff --git a/custom_components/hacs/enums.py b/custom_components/hacs/enums.py new file mode 100644 index 0000000..887946a --- /dev/null +++ b/custom_components/hacs/enums.py @@ -0,0 +1,39 @@ +"""Helper constants.""" +# pylint: disable=missing-class-docstring +from enum import Enum + + +class HacsCategory(str, Enum): + APPDAEMON = "appdaemon" + INTEGRATION = "integration" + LOVELACE = "lovelace" + PLUGIN = "plugin" # Kept for legacy purposes + NETDAEMON = "netdaemon" + PYTHON_SCRIPT = "python_script" + THEME = "theme" + REMOVED = "removed" + + +class LovelaceMode(str, Enum): + """Lovelace Modes.""" + + STORAGE = "storage" + AUTO = "auto" + YAML = "yaml" + + +class HacsStage(str, Enum): + SETUP = "setup" + STARTUP = "startup" + WAITING = "waiting" + RUNNING = "running" + BACKGROUND = "background" + + +class HacsSetupTask(str, Enum): + WEBSOCKET = "WebSocket API" + FRONTEND = "Frontend" + SENSOR = "Sensor" + HACS_REPO = "Hacs Repository" + CATEGORIES = "Additional categories" + CLEAR_STORAGE = "Clear storage" diff --git a/custom_components/hacs/globals.py b/custom_components/hacs/globals.py deleted file mode 100644 index 91d8314..0000000 --- a/custom_components/hacs/globals.py +++ /dev/null @@ -1,29 +0,0 @@ -# pylint: disable=invalid-name, missing-docstring -hacs = [] -removed_repositories = [] - - -def get_hacs(): - if not hacs: - from custom_components.hacs.hacsbase import Hacs - - hacs.append(Hacs()) - - return hacs[0] - - -def is_removed(repository): - return repository in [x.repository for x in removed_repositories] - - -def get_removed(repository): - if not is_removed(repository): - from custom_components.hacs.repositories.removed import RemovedRepository - - removed_repo = RemovedRepository() - removed_repo.repository = repository - removed_repositories.append(removed_repo) - filter_repos = [ - x for x in removed_repositories if x.repository.lower() == repository.lower() - ] - return filter_repos[0] diff --git a/custom_components/hacs/hacsbase/__init__.py b/custom_components/hacs/hacsbase/__init__.py index 8795661..e69de29 100644 --- a/custom_components/hacs/hacsbase/__init__.py +++ b/custom_components/hacs/hacsbase/__init__.py @@ -1,355 +0,0 @@ -"""Initialize the HACS base.""" -# pylint: disable=unused-argument, bad-continuation -import json -import uuid -from datetime import timedelta - -from homeassistant.helpers.event import async_call_later, async_track_time_interval - -from aiogithubapi import AIOGitHubException, AIOGitHubRatelimit -from integrationhelper import Logger - -from custom_components.hacs.hacsbase.task_factory import HacsTaskFactory -from custom_components.hacs.hacsbase.exceptions import HacsException - -from custom_components.hacs.const import ELEMENT_TYPES -from custom_components.hacs.setup import setup_extra_stores -from custom_components.hacs.store import async_load_from_store, async_save_to_store -from custom_components.hacs.helpers.get_defaults import ( - get_default_repos_lists, - get_default_repos_orgs, -) - -from custom_components.hacs.helpers.register_repository import register_repository -from custom_components.hacs.globals import removed_repositories, get_removed, is_removed -from custom_components.hacs.repositories.removed import RemovedRepository - - -class HacsStatus: - """HacsStatus.""" - - startup = True - new = False - background_task = False - reloading_data = False - upgrading_all = False - - -class HacsFrontend: - """HacsFrontend.""" - - version_running = None - version_available = None - update_pending = False - - -class HacsCommon: - """Common for HACS.""" - - categories = [] - default = [] - installed = [] - skip = [] - - -class System: - """System info.""" - - status = HacsStatus() - config_path = None - ha_version = None - disabled = False - lovelace_mode = "storage" - - -class Developer: - """Developer settings/tools.""" - - template_id = "Repository ID" - template_content = "" - template_raw = "" - - @property - def devcontainer(self): - """Is it a devcontainer?""" - import os - - if "DEVCONTAINER" in os.environ: - return True - return False - - -class Hacs: - """The base class of HACS, nested thoughout the project.""" - - token = f"{str(uuid.uuid4())}-{str(uuid.uuid4())}" - hacsweb = f"/hacsweb/{token}" - hacsapi = f"/hacsapi/{token}" - repositories = [] - frontend = HacsFrontend() - repo = None - data_repo = None - developer = Developer() - data = None - configuration = None - logger = Logger("hacs") - github = None - hass = None - version = None - session = None - factory = HacsTaskFactory() - system = System() - recuring_tasks = [] - common = HacsCommon() - - @staticmethod - def init(hass, github_token): - """Return a initialized HACS object.""" - return Hacs() - - def get_by_id(self, repository_id): - """Get repository by ID.""" - try: - for repository in self.repositories: - if repository.information.uid == repository_id: - return repository - except Exception: # pylint: disable=broad-except - pass - return None - - def get_by_name(self, repository_full_name): - """Get repository by full_name.""" - try: - for repository in self.repositories: - if repository.data.full_name.lower() == repository_full_name.lower(): - return repository - except Exception: # pylint: disable=broad-except - pass - return None - - def is_known(self, repository_full_name): - """Return a bool if the repository is known.""" - return repository_full_name.lower() in [ - x.data.full_name.lower() for x in self.repositories - ] - - @property - def sorted_by_name(self): - """Return a sorted(by name) list of repository objects.""" - return sorted(self.repositories, key=lambda x: x.display_name) - - @property - def sorted_by_repository_name(self): - """Return a sorted(by repository_name) list of repository objects.""" - return sorted(self.repositories, key=lambda x: x.data.full_name) - - async def register_repository(self, full_name, category, check=True): - """Register a repository.""" - await register_repository(full_name, category, check=True) - - async def startup_tasks(self): - """Tasks tha are started after startup.""" - self.system.status.background_task = True - await self.hass.async_add_executor_job(setup_extra_stores) - self.hass.bus.async_fire("hacs/status", {}) - self.logger.debug(self.github.ratelimits.remaining) - self.logger.debug(self.github.ratelimits.reset_utc) - - await self.handle_critical_repositories_startup() - await self.handle_critical_repositories() - await self.load_known_repositories() - await self.clear_out_removed_repositories() - - self.recuring_tasks.append( - async_track_time_interval( - self.hass, self.recuring_tasks_installed, timedelta(minutes=30) - ) - ) - self.recuring_tasks.append( - async_track_time_interval( - self.hass, self.recuring_tasks_all, timedelta(minutes=800) - ) - ) - - self.hass.bus.async_fire("hacs/reload", {"force": True}) - await self.recuring_tasks_installed() - - self.system.status.startup = False - self.system.status.new = False - self.system.status.background_task = False - self.hass.bus.async_fire("hacs/status", {}) - await self.data.async_write() - - async def handle_critical_repositories_startup(self): - """Handled critical repositories during startup.""" - alert = False - critical = await async_load_from_store(self.hass, "critical") - if not critical: - return - for repo in critical: - if not repo["acknowledged"]: - alert = True - if alert: - self.logger.critical("URGENT!: Check the HACS panel!") - self.hass.components.persistent_notification.create( - title="URGENT!", message="**Check the HACS panel!**" - ) - - async def handle_critical_repositories(self): - """Handled critical repositories during runtime.""" - # Get critical repositories - instored = [] - critical = [] - was_installed = False - - try: - critical = await self.data_repo.get_contents("critical") - critical = json.loads(critical.content) - except AIOGitHubException: - pass - - if not critical: - self.logger.debug("No critical repositories") - return - - stored_critical = await async_load_from_store(self.hass, "critical") - - for stored in stored_critical or []: - instored.append(stored["repository"]) - - stored_critical = [] - - for repository in critical: - removed_repo = get_removed(repository["repository"]) - removed_repo.removal_type = "critical" - repo = self.get_by_name(repository["repository"]) - - stored = { - "repository": repository["repository"], - "reason": repository["reason"], - "link": repository["link"], - "acknowledged": True, - } - if repository["repository"] not in instored: - if repo is not None and repo.installed: - self.logger.critical( - f"Removing repository {repository['repository']}, it is marked as critical" - ) - was_installed = True - stored["acknowledged"] = False - # Uninstall from HACS - repo.remove() - await repo.uninstall() - stored_critical.append(stored) - removed_repo.update_data(stored) - - # Save to FS - await async_save_to_store(self.hass, "critical", stored_critical) - - # Resart HASS - if was_installed: - self.logger.critical("Resarting Home Assistant") - self.hass.async_create_task(self.hass.async_stop(100)) - - async def recuring_tasks_installed(self, notarealarg=None): - """Recuring tasks for installed repositories.""" - self.logger.debug( - "Starting recuring background task for installed repositories" - ) - self.system.status.background_task = True - self.hass.bus.async_fire("hacs/status", {}) - self.logger.debug(self.github.ratelimits.remaining) - self.logger.debug(self.github.ratelimits.reset_utc) - for repository in self.repositories: - if ( - repository.status.installed - and repository.data.category in self.common.categories - ): - self.factory.tasks.append(self.factory.safe_update(repository)) - - await self.factory.execute() - await self.handle_critical_repositories() - self.system.status.background_task = False - self.hass.bus.async_fire("hacs/status", {}) - await self.data.async_write() - self.logger.debug("Recuring background task for installed repositories done") - - async def recuring_tasks_all(self, notarealarg=None): - """Recuring tasks for all repositories.""" - self.logger.debug("Starting recuring background task for all repositories") - await self.hass.async_add_executor_job(setup_extra_stores) - self.system.status.background_task = True - self.hass.bus.async_fire("hacs/status", {}) - self.logger.debug(self.github.ratelimits.remaining) - self.logger.debug(self.github.ratelimits.reset_utc) - for repository in self.repositories: - if repository.data.category in self.common.categories: - self.factory.tasks.append(self.factory.safe_common_update(repository)) - - await self.factory.execute() - await self.load_known_repositories() - await self.clear_out_removed_repositories() - self.system.status.background_task = False - await self.data.async_write() - self.hass.bus.async_fire("hacs/status", {}) - self.hass.bus.async_fire("hacs/repository", {"action": "reload"}) - self.logger.debug("Recuring background task for all repositories done") - - async def clear_out_removed_repositories(self): - """Clear out blaclisted repositories.""" - need_to_save = False - for removed in removed_repositories: - if self.is_known(removed.repository): - repository = self.get_by_name(removed.repository) - if repository.status.installed and removed.removal_type != "critical": - self.logger.warning( - f"You have {repository.data.full_name} installed with HACS " - + f"this repository has been removed, please consider removing it. " - + f"Removal reason ({removed.removal_type})" - ) - else: - need_to_save = True - repository.remove() - - if need_to_save: - await self.data.async_write() - - async def get_repositories(self): - """Return a list of repositories.""" - repositories = {} - for category in self.common.categories: - repositories[category] = await get_default_repos_lists( - self.session, self.configuration.token, category - ) - org = await get_default_repos_orgs(self.github, category) - for repo in org: - repositories[category].append(repo) - - for category in repositories: - for repo in repositories[category]: - if repo not in self.common.default: - self.common.default.append(repo) - return repositories - - async def load_known_repositories(self): - """Load known repositories.""" - self.logger.info("Loading known repositories") - repositories = await self.get_repositories() - - for item in await get_default_repos_lists( - self.session, self.configuration.token, "removed" - ): - removed = get_removed(item["repository"]) - removed.reason = item.get("reason") - removed.link = item.get("link") - removed.removal_type = item.get("removal_type") - - for category in repositories: - for repo in repositories[category]: - if is_removed(repo): - continue - if self.is_known(repo): - continue - self.factory.tasks.append(self.factory.safe_register(repo, category)) - await self.factory.execute() - self.logger.info("Loading known repositories finished") diff --git a/custom_components/hacs/hacsbase/__pycache__/__init__.cpython-37.pyc b/custom_components/hacs/hacsbase/__pycache__/__init__.cpython-37.pyc deleted file mode 100644 index b9fec3b..0000000 Binary files a/custom_components/hacs/hacsbase/__pycache__/__init__.cpython-37.pyc and /dev/null differ diff --git a/custom_components/hacs/hacsbase/__pycache__/__init__.cpython-38.pyc b/custom_components/hacs/hacsbase/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..1809b80 Binary files /dev/null and b/custom_components/hacs/hacsbase/__pycache__/__init__.cpython-38.pyc differ diff --git a/custom_components/hacs/hacsbase/__pycache__/backup.cpython-37.pyc b/custom_components/hacs/hacsbase/__pycache__/backup.cpython-37.pyc deleted file mode 100644 index f1d211b..0000000 Binary files a/custom_components/hacs/hacsbase/__pycache__/backup.cpython-37.pyc and /dev/null differ diff --git a/custom_components/hacs/hacsbase/__pycache__/configuration.cpython-37.pyc b/custom_components/hacs/hacsbase/__pycache__/configuration.cpython-37.pyc deleted file mode 100644 index 7d4a15d..0000000 Binary files a/custom_components/hacs/hacsbase/__pycache__/configuration.cpython-37.pyc and /dev/null differ diff --git a/custom_components/hacs/hacsbase/__pycache__/configuration.cpython-38.pyc b/custom_components/hacs/hacsbase/__pycache__/configuration.cpython-38.pyc new file mode 100644 index 0000000..9513610 Binary files /dev/null and b/custom_components/hacs/hacsbase/__pycache__/configuration.cpython-38.pyc differ diff --git a/custom_components/hacs/hacsbase/__pycache__/const.cpython-37.pyc b/custom_components/hacs/hacsbase/__pycache__/const.cpython-37.pyc deleted file mode 100644 index 2ae3672..0000000 Binary files a/custom_components/hacs/hacsbase/__pycache__/const.cpython-37.pyc and /dev/null differ diff --git a/custom_components/hacs/hacsbase/__pycache__/data.cpython-37.pyc b/custom_components/hacs/hacsbase/__pycache__/data.cpython-37.pyc deleted file mode 100644 index 87fad74..0000000 Binary files a/custom_components/hacs/hacsbase/__pycache__/data.cpython-37.pyc and /dev/null differ diff --git a/custom_components/hacs/hacsbase/__pycache__/data.cpython-38.pyc b/custom_components/hacs/hacsbase/__pycache__/data.cpython-38.pyc new file mode 100644 index 0000000..11981f5 Binary files /dev/null and b/custom_components/hacs/hacsbase/__pycache__/data.cpython-38.pyc differ diff --git a/custom_components/hacs/hacsbase/__pycache__/exceptions.cpython-37.pyc b/custom_components/hacs/hacsbase/__pycache__/exceptions.cpython-37.pyc deleted file mode 100644 index b0789b2..0000000 Binary files a/custom_components/hacs/hacsbase/__pycache__/exceptions.cpython-37.pyc and /dev/null differ diff --git a/custom_components/hacs/hacsbase/__pycache__/hacs.cpython-38.pyc b/custom_components/hacs/hacsbase/__pycache__/hacs.cpython-38.pyc new file mode 100644 index 0000000..81b589a Binary files /dev/null and b/custom_components/hacs/hacsbase/__pycache__/hacs.cpython-38.pyc differ diff --git a/custom_components/hacs/hacsbase/__pycache__/task_factory.cpython-37.pyc b/custom_components/hacs/hacsbase/__pycache__/task_factory.cpython-37.pyc deleted file mode 100644 index dce0e78..0000000 Binary files a/custom_components/hacs/hacsbase/__pycache__/task_factory.cpython-37.pyc and /dev/null differ diff --git a/custom_components/hacs/hacsbase/configuration.py b/custom_components/hacs/hacsbase/configuration.py index c9bb80e..825582b 100644 --- a/custom_components/hacs/hacsbase/configuration.py +++ b/custom_components/hacs/hacsbase/configuration.py @@ -1,73 +1,77 @@ -"""HACS Configuration.""" -import attr -from integrationhelper import Logger -from custom_components.hacs.hacsbase.exceptions import HacsException - - -@attr.s(auto_attribs=True) -class Configuration: - """Configuration class.""" - - # Main configuration: - appdaemon_path: str = "appdaemon/apps/" - appdaemon: bool = False - netdaemon_path: str = "netdaemon/apps/" - netdaemon: bool = False - config: dict = {} - config_entry: dict = {} - config_type: str = None - debug: bool = False - dev: bool = False - frontend_mode: str = "Grid" - frontend_compact: bool = False - options: dict = {} - onboarding_done: bool = False - plugin_path: str = "www/community/" - python_script_path: str = "python_scripts/" - python_script: bool = False - sidepanel_icon: str = "mdi:alpha-c-box" - sidepanel_title: str = "Community" - theme_path: str = "themes/" - theme: bool = False - token: str = None - - # Config options: - country: str = "ALL" - experimental: bool = False - release_limit: int = 5 - - def to_json(self): - """Return a dict representation of the configuration.""" - return self.__dict__ - - def print(self): - """Print the current configuration to the log.""" - logger = Logger("hacs.configuration") - config = self.to_json() - for key in config: - if key in ["config", "config_entry", "options", "token"]: - continue - logger.debug(f"{key}: {config[key]}") - - @staticmethod - def from_dict(configuration: dict, options: dict): - """Set attributes from dicts.""" - if isinstance(options, bool) or isinstance(configuration.get("options"), bool): - raise HacsException("Configuration is not valid.") - - if options is None: - options = {} - - if not configuration: - raise HacsException("Configuration is not valid.") - - config = Configuration() - - config.config = configuration - config.options = options - - for conf_type in [configuration, options]: - for key in conf_type: - setattr(config, key, conf_type[key]) - - return config +"""HACS Configuration.""" +import attr + +from custom_components.hacs.helpers.classes.exceptions import HacsException +from custom_components.hacs.helpers.functions.logger import getLogger + +_LOGGER = getLogger() + + +@attr.s(auto_attribs=True) +class Configuration: + """Configuration class.""" + + # Main configuration: + appdaemon_path: str = "appdaemon/apps/" + appdaemon: bool = False + netdaemon_path: str = "netdaemon/apps/" + netdaemon: bool = False + config: dict = {} + config_entry: dict = {} + config_type: str = None + debug: bool = False + dev: bool = False + frontend_mode: str = "Grid" + frontend_compact: bool = False + frontend_repo: str = "" + frontend_repo_url: str = "" + options: dict = {} + onboarding_done: bool = False + plugin_path: str = "www/community/" + python_script_path: str = "python_scripts/" + python_script: bool = False + sidepanel_icon: str = "hacs:hacs" + sidepanel_title: str = "HACS" + theme_path: str = "themes/" + theme: bool = False + token: str = None + + # Config options: + country: str = "ALL" + experimental: bool = False + release_limit: int = 5 + + def to_json(self) -> dict: + """Return a dict representation of the configuration.""" + return self.__dict__ + + def print(self) -> None: + """Print the current configuration to the log.""" + config = self.to_json() + for key in config: + if key in ["config", "config_entry", "options", "token"]: + continue + _LOGGER.debug("%s: %s", key, config[key]) + + @staticmethod + def from_dict(configuration: dict, options: dict = None) -> None: + """Set attributes from dicts.""" + if isinstance(options, bool) or isinstance(configuration.get("options"), bool): + raise HacsException("Configuration is not valid.") + + if options is None: + options = {} + + if not configuration: + raise HacsException("Configuration is not valid.") + + config = Configuration() + + config.config = configuration + config.options = options + + for conf_type in [configuration, options]: + for key in conf_type: + setattr(config, key, conf_type[key]) + + return config diff --git a/custom_components/hacs/hacsbase/const.py b/custom_components/hacs/hacsbase/const.py deleted file mode 100644 index 1e72154..0000000 --- a/custom_components/hacs/hacsbase/const.py +++ /dev/null @@ -1,10 +0,0 @@ -"""Constants for HACS""" -# pylint: disable=unused-import -STORAGE_VERSION = "6" -STORENAME = "hacs" - -# Messages -NOT_SUPPORTED_HA_VERSION = "You have version '{}' of Home Assistant, but version '{}' of '{}' require version '{}' of Home Assistant, install and upgrades are disabled for this integration untill you upgrade Home Assistant." - - -NO_ELEMENTS = "No elements to show, open the store to install some awesome stuff." diff --git a/custom_components/hacs/hacsbase/data.py b/custom_components/hacs/hacsbase/data.py index 1464849..e1f62a1 100644 --- a/custom_components/hacs/hacsbase/data.py +++ b/custom_components/hacs/hacsbase/data.py @@ -1,152 +1,203 @@ -"""Data handler for HACS.""" -from integrationhelper import Logger -from ..const import VERSION -from ..repositories.repository import HacsRepository -from ..repositories.manifest import HacsManifest -from ..store import async_save_to_store, async_load_from_store - -from custom_components.hacs.globals import get_hacs, removed_repositories, get_removed -from custom_components.hacs.helpers.register_repository import register_repository - - -class HacsData: - """HacsData class.""" - - def __init__(self): - """Initialize.""" - self.logger = Logger("hacs.data") - self.hacs = get_hacs() - - async def async_write(self): - """Write content to the store files.""" - if self.hacs.system.status.background_task or self.hacs.system.disabled: - return - - self.logger.debug("Saving data") - - # Hacs - await async_save_to_store( - self.hacs.hass, - "hacs", - { - "view": self.hacs.configuration.frontend_mode, - "compact": self.hacs.configuration.frontend_compact, - "onboarding_done": self.hacs.configuration.onboarding_done, - }, - ) - - await async_save_to_store( - self.hacs.hass, "removed", [x.__dict__ for x in removed_repositories] - ) - - # Repositories - content = {} - for repository in self.hacs.repositories: - if repository.repository_manifest is not None: - repository_manifest = repository.repository_manifest.manifest - else: - repository_manifest = None - content[repository.information.uid] = { - "authors": repository.data.authors, - "category": repository.data.category, - "description": repository.data.description, - "downloads": repository.releases.downloads, - "full_name": repository.data.full_name, - "first_install": repository.status.first_install, - "hide": repository.status.hide, - "installed_commit": repository.versions.installed_commit, - "installed": repository.status.installed, - "last_commit": repository.versions.available_commit, - "last_release_tag": repository.versions.available, - "last_updated": repository.information.last_updated, - "name": repository.data.name, - "new": repository.status.new, - "repository_manifest": repository_manifest, - "selected_tag": repository.status.selected_tag, - "show_beta": repository.status.show_beta, - "stars": repository.data.stargazers_count, - "topics": repository.data.topics, - "version_installed": repository.versions.installed, - } - - await async_save_to_store(self.hacs.hass, "repositories", content) - self.hacs.hass.bus.async_fire("hacs/repository", {}) - self.hacs.hass.bus.fire("hacs/config", {}) - - async def restore(self): - """Restore saved data.""" - hacs = await async_load_from_store(self.hacs.hass, "hacs") - repositories = await async_load_from_store(self.hacs.hass, "repositories") - removed = await async_load_from_store(self.hacs.hass, "removed") - try: - if not hacs and not repositories: - # Assume new install - self.hacs.system.status.new = True - return True - self.logger.info("Restore started") - - # Hacs - self.hacs.configuration.frontend_mode = hacs.get("view", "Grid") - self.hacs.configuration.frontend_compact = hacs.get("compact", False) - self.hacs.configuration.onboarding_done = hacs.get("onboarding_done", False) - - for entry in removed: - removed_repo = get_removed(entry["repository"]) - removed_repo.update_data(entry) - - # Repositories - for entry in repositories: - repo = repositories[entry] - if not self.hacs.is_known(repo["full_name"]): - await register_repository( - repo["full_name"], repo["category"], False - ) - repository = self.hacs.get_by_name(repo["full_name"]) - if repository is None: - self.logger.error(f"Did not find {repo['full_name']}") - continue - - # Restore repository attributes - repository.information.uid = entry - await self.hacs.hass.async_add_executor_job( - restore_repository_data, repository, repo - ) - - self.logger.info("Restore done") - except Exception as exception: # pylint: disable=broad-except - self.logger.critical(f"[{exception}] Restore Failed!") - return False - return True - - -def restore_repository_data( - repository: type(HacsRepository), repository_data: dict -) -> None: - """Restore Repository Data""" - repository.data.authors = repository_data.get("authors", []) - repository.data.description = repository_data.get("description") - repository.releases.last_release_object_downloads = repository_data.get("downloads") - repository.information.last_updated = repository_data.get("last_updated") - repository.data.topics = repository_data.get("topics", []) - repository.data.stargazers_count = repository_data.get("stars", 0) - repository.releases.last_release = repository_data.get("last_release_tag") - repository.status.hide = repository_data.get("hide", False) - repository.status.installed = repository_data.get("installed", False) - repository.status.new = repository_data.get("new", True) - repository.status.selected_tag = repository_data.get("selected_tag") - repository.status.show_beta = repository_data.get("show_beta", False) - repository.versions.available = repository_data.get("last_release_tag") - repository.versions.available_commit = repository_data.get("last_commit") - repository.versions.installed = repository_data.get("version_installed") - repository.versions.installed_commit = repository_data.get("installed_commit") - - repository.repository_manifest = HacsManifest.from_dict( - repository_data.get("repository_manifest", {}) - ) - - if repository.status.installed: - repository.status.first_install = False - - if repository_data["full_name"] == "hacs/integration": - repository.versions.installed = VERSION - repository.status.installed = True +"""Data handler for HACS.""" +import os +from queueman import QueueManager + +from custom_components.hacs.const import VERSION +from custom_components.hacs.helpers.classes.manifest import HacsManifest +from custom_components.hacs.helpers.functions.logger import getLogger +from custom_components.hacs.helpers.functions.register_repository import ( + register_repository, +) +from custom_components.hacs.helpers.functions.store import ( + async_load_from_store, + async_save_to_store, + get_store_for_key, +) +from custom_components.hacs.share import get_hacs + + +class HacsData: + """HacsData class.""" + + def __init__(self): + """Initialize.""" + self.logger = getLogger() + self.hacs = get_hacs() + self.queue = QueueManager() + self.content = {} + + async def async_write(self): + """Write content to the store files.""" + if self.hacs.status.background_task or self.hacs.system.disabled: + return + + self.logger.debug("Saving data") + + # Hacs + await async_save_to_store( + self.hacs.hass, + "hacs", + { + "view": self.hacs.configuration.frontend_mode, + "compact": self.hacs.configuration.frontend_compact, + "onboarding_done": self.hacs.configuration.onboarding_done, + }, + ) + + # Repositories + self.content = {} + for repository in self.hacs.repositories or []: + self.queue.add(self.async_store_repository_data(repository)) + + if not self.queue.has_pending_tasks: + self.logger.debug("Nothing in the queue") + elif self.queue.running: + self.logger.debug("Queue is already running") + else: + await self.queue.execute() + await async_save_to_store(self.hacs.hass, "repositories", self.content) + self.hacs.hass.bus.async_fire("hacs/repository", {}) + self.hacs.hass.bus.fire("hacs/config", {}) + + async def async_store_repository_data(self, repository): + repository_manifest = repository.repository_manifest.manifest + data = { + "authors": repository.data.authors, + "category": repository.data.category, + "description": repository.data.description, + "domain": repository.data.domain, + "downloads": repository.data.downloads, + "full_name": repository.data.full_name, + "first_install": repository.status.first_install, + "installed_commit": repository.data.installed_commit, + "installed": repository.data.installed, + "last_commit": repository.data.last_commit, + "last_release_tag": repository.data.last_version, + "last_updated": repository.data.last_updated, + "name": repository.data.name, + "new": repository.data.new, + "repository_manifest": repository_manifest, + "selected_tag": repository.data.selected_tag, + "show_beta": repository.data.show_beta, + "stars": repository.data.stargazers_count, + "topics": repository.data.topics, + "version_installed": repository.data.installed_version, + } + if data: + if repository.data.installed and ( + repository.data.installed_commit or repository.data.installed_version + ): + await async_save_to_store( + self.hacs.hass, + f"hacs/{repository.data.id}.hacs", + repository.data.to_json(), + ) + self.content[str(repository.data.id)] = data + + async def restore(self): + """Restore saved data.""" + hacs = await async_load_from_store(self.hacs.hass, "hacs") + repositories = await async_load_from_store(self.hacs.hass, "repositories") + try: + if not hacs and not repositories: + # Assume new install + self.hacs.status.new = True + return True + self.logger.info("Restore started") + self.hacs.status.new = False + + # Hacs + self.hacs.configuration.frontend_mode = hacs.get("view", "Grid") + self.hacs.configuration.frontend_compact = hacs.get("compact", False) + self.hacs.configuration.onboarding_done = hacs.get("onboarding_done", False) + + # Repositories + stores = {} + for entry in repositories or []: + stores[entry] = get_store_for_key(self.hacs.hass, f"hacs/{entry}.hacs") + + stores_exist = {} + + def _populate_stores(): + for entry in repositories or []: + stores_exist[entry] = os.path.exists(stores[entry].path) + + await self.hacs.hass.async_add_executor_job(_populate_stores) + + # Repositories + for entry in repositories or []: + self.queue.add( + self.async_restore_repository( + entry, repositories[entry], stores[entry], stores_exist[entry] + ) + ) + + await self.queue.execute() + + self.logger.info("Restore done") + except (Exception, BaseException) as exception: # pylint: disable=broad-except + self.logger.critical(f"[{exception}] Restore Failed!") + return False + return True + + async def async_restore_repository( + self, entry, repository_data, store, store_exists + ): + if not self.hacs.is_known(entry): + await register_repository( + repository_data["full_name"], repository_data["category"], False + ) + repository = [ + x + for x in self.hacs.repositories + if str(x.data.id) == str(entry) + or x.data.full_name == repository_data["full_name"] + ] + if not repository: + self.logger.error(f"Did not find {repository_data['full_name']} ({entry})") + return + + repository = repository[0] + + # Restore repository attributes + repository.data.id = entry + repository.data.authors = repository_data.get("authors", []) + repository.data.description = repository_data.get("description") + repository.releases.last_release_object_downloads = repository_data.get( + "downloads" + ) + repository.data.last_updated = repository_data.get("last_updated") + repository.data.topics = repository_data.get("topics", []) + repository.data.domain = repository_data.get("domain", None) + repository.data.stargazers_count = repository_data.get("stars", 0) + repository.releases.last_release = repository_data.get("last_release_tag") + repository.data.hide = repository_data.get("hide", False) + repository.data.installed = repository_data.get("installed", False) + repository.data.new = repository_data.get("new", True) + repository.data.selected_tag = repository_data.get("selected_tag") + repository.data.show_beta = repository_data.get("show_beta", False) + repository.data.last_version = repository_data.get("last_release_tag") + repository.data.last_commit = repository_data.get("last_commit") + repository.data.installed_version = repository_data.get("version_installed") + repository.data.installed_commit = repository_data.get("installed_commit") + + repository.repository_manifest = HacsManifest.from_dict( + repository_data.get("repository_manifest", {}) + ) + + if repository.data.installed: + repository.status.first_install = False + + if repository_data["full_name"] == "hacs/integration": + repository.data.installed_version = VERSION + repository.data.installed = True + + restored = store_exists and await store.async_load() or {} + + if restored: + repository.data.update_data(restored) + if not repository.data.installed: + repository.logger.debug( + "Should be installed but is not... Fixing that!" + ) + repository.data.installed = True diff --git a/custom_components/hacs/hacsbase/hacs.py b/custom_components/hacs/hacsbase/hacs.py new file mode 100644 index 0000000..ba06d62 --- /dev/null +++ b/custom_components/hacs/hacsbase/hacs.py @@ -0,0 +1,361 @@ +"""Initialize the HACS base.""" +import json +from datetime import timedelta + +from aiogithubapi import AIOGitHubAPIException +from queueman import QueueManager +from queueman.exceptions import QueueManagerExecutionStillInProgress + +from custom_components.hacs.helpers import HacsHelpers + +from custom_components.hacs.helpers.functions.get_list_from_default import ( + async_get_list_from_default, +) +from custom_components.hacs.helpers.functions.register_repository import ( + register_repository, +) +from custom_components.hacs.helpers.functions.remaining_github_calls import ( + get_fetch_updates_for, +) +from custom_components.hacs.helpers.functions.store import ( + async_load_from_store, + async_save_to_store, +) +from custom_components.hacs.operational.setup_actions.categories import ( + async_setup_extra_stores, +) +from custom_components.hacs.share import ( + get_factory, + get_queue, + get_removed, + is_removed, + list_removed_repositories, +) + +from ..enums import HacsCategory, HacsStage +from ..base import HacsBase + + +class HacsStatus: + """HacsStatus.""" + + startup = True + new = False + background_task = False + reloading_data = False + upgrading_all = False + + +class HacsFrontend: + """HacsFrontend.""" + + version_running = None + version_available = None + version_expected = None + update_pending = False + + +class HacsCommon: + """Common for HACS.""" + + categories = [] + default = [] + installed = [] + skip = [] + + +class System: + """System info.""" + + status = HacsStatus() + config_path = None + ha_version = None + disabled = False + running = False + lovelace_mode = "storage" + + +class Hacs(HacsBase, HacsHelpers): + """The base class of HACS, nested throughout the project.""" + + repositories = [] + repo = None + data_repo = None + data = None + status = HacsStatus() + configuration = None + version = None + session = None + factory = get_factory() + queue = get_queue() + recuring_tasks = [] + common = HacsCommon() + + def get_by_id(self, repository_id): + """Get repository by ID.""" + try: + for repository in self.repositories: + if str(repository.data.id) == str(repository_id): + return repository + except (Exception, BaseException): # pylint: disable=broad-except + pass + return None + + def get_by_name(self, repository_full_name): + """Get repository by full_name.""" + try: + repository_full_name_lower = repository_full_name.lower() + for repository in self.repositories: + if repository.data.full_name_lower == repository_full_name_lower: + return repository + except (Exception, BaseException): # pylint: disable=broad-except + pass + return None + + def is_known(self, repository_id): + """Return a bool if the repository is known.""" + return str(repository_id) in [str(x.data.id) for x in self.repositories] + + @property + def sorted_by_name(self): + """Return a sorted(by name) list of repository objects.""" + return sorted(self.repositories, key=lambda x: x.display_name) + + @property + def sorted_by_repository_name(self): + """Return a sorted(by repository_name) list of repository objects.""" + return sorted(self.repositories, key=lambda x: x.data.full_name) + + async def register_repository(self, full_name, category, check=True): + """Register a repository.""" + await register_repository(full_name, category, check=check) + + async def startup_tasks(self, _event=None): + """Tasks that are started after startup.""" + await self.async_set_stage(HacsStage.STARTUP) + self.status.background_task = True + await async_setup_extra_stores() + self.hass.bus.async_fire("hacs/status", {}) + + await self.handle_critical_repositories_startup() + await self.handle_critical_repositories() + await self.async_load_default_repositories() + await self.clear_out_removed_repositories() + + self.recuring_tasks.append( + self.hass.helpers.event.async_track_time_interval( + self.recurring_tasks_installed, timedelta(minutes=30) + ) + ) + self.recuring_tasks.append( + self.hass.helpers.event.async_track_time_interval( + self.recurring_tasks_all, timedelta(minutes=800) + ) + ) + self.recuring_tasks.append( + self.hass.helpers.event.async_track_time_interval( + self.prosess_queue, timedelta(minutes=10) + ) + ) + + self.hass.bus.async_fire("hacs/reload", {"force": True}) + await self.recurring_tasks_installed() + + await self.prosess_queue() + + self.status.startup = False + self.status.background_task = False + self.hass.bus.async_fire("hacs/status", {}) + await self.async_set_stage(HacsStage.RUNNING) + + async def handle_critical_repositories_startup(self): + """Handled critical repositories during startup.""" + alert = False + critical = await async_load_from_store(self.hass, "critical") + if not critical: + return + for repo in critical: + if not repo["acknowledged"]: + alert = True + if alert: + self.log.critical("URGENT!: Check the HACS panel!") + self.hass.components.persistent_notification.create( + title="URGENT!", message="**Check the HACS panel!**" + ) + + async def handle_critical_repositories(self): + """Handled critical repositories during runtime.""" + # Get critical repositories + critical_queue = QueueManager() + instored = [] + critical = [] + was_installed = False + + try: + critical = await self.data_repo.get_contents("critical") + critical = json.loads(critical.content) + except AIOGitHubAPIException: + pass + + if not critical: + self.log.debug("No critical repositories") + return + + stored_critical = await async_load_from_store(self.hass, "critical") + + for stored in stored_critical or []: + instored.append(stored["repository"]) + + stored_critical = [] + + for repository in critical: + removed_repo = get_removed(repository["repository"]) + removed_repo.removal_type = "critical" + repo = self.get_by_name(repository["repository"]) + + stored = { + "repository": repository["repository"], + "reason": repository["reason"], + "link": repository["link"], + "acknowledged": True, + } + if repository["repository"] not in instored: + if repo is not None and repo.installed: + self.log.critical( + "Removing repository %s, it is marked as critical", + repository["repository"], + ) + was_installed = True + stored["acknowledged"] = False + # Remove from HACS + critical_queue.add(repository.uninstall()) + repo.remove() + + stored_critical.append(stored) + removed_repo.update_data(stored) + + # Uninstall + await critical_queue.execute() + + # Save to FS + await async_save_to_store(self.hass, "critical", stored_critical) + + # Restart HASS + if was_installed: + self.log.critical("Resarting Home Assistant") + self.hass.async_create_task(self.hass.async_stop(100)) + + async def prosess_queue(self, _notarealarg=None): + """Recurring tasks for installed repositories.""" + if not self.queue.has_pending_tasks: + self.log.debug("Nothing in the queue") + return + if self.queue.running: + self.log.debug("Queue is already running") + return + + can_update = await get_fetch_updates_for(self.github) + if can_update == 0: + self.log.info("HACS is ratelimited, repository updates will resume later.") + else: + self.status.background_task = True + self.hass.bus.async_fire("hacs/status", {}) + try: + await self.queue.execute(can_update) + except QueueManagerExecutionStillInProgress: + pass + self.status.background_task = False + self.hass.bus.async_fire("hacs/status", {}) + + async def recurring_tasks_installed(self, _notarealarg=None): + """Recurring tasks for installed repositories.""" + self.log.debug("Starting recurring background task for installed repositories") + self.status.background_task = True + self.hass.bus.async_fire("hacs/status", {}) + + for repository in self.repositories: + if ( + repository.data.installed + and repository.data.category in self.common.categories + ): + self.queue.add(self.factory.safe_update(repository)) + + await self.handle_critical_repositories() + self.status.background_task = False + self.hass.bus.async_fire("hacs/status", {}) + await self.data.async_write() + self.log.debug("Recurring background task for installed repositories done") + + async def recurring_tasks_all(self, _notarealarg=None): + """Recurring tasks for all repositories.""" + self.log.debug("Starting recurring background task for all repositories") + await async_setup_extra_stores() + self.status.background_task = True + self.hass.bus.async_fire("hacs/status", {}) + + for repository in self.repositories: + if repository.data.category in self.common.categories: + self.queue.add(self.factory.safe_common_update(repository)) + + await self.async_load_default_repositories() + await self.clear_out_removed_repositories() + self.status.background_task = False + await self.data.async_write() + self.hass.bus.async_fire("hacs/status", {}) + self.hass.bus.async_fire("hacs/repository", {"action": "reload"}) + self.log.debug("Recurring background task for all repositories done") + + async def clear_out_removed_repositories(self): + """Clear out blaclisted repositories.""" + need_to_save = False + for removed in list_removed_repositories(): + repository = self.get_by_name(removed.repository) + if repository is not None: + if repository.data.installed and removed.removal_type != "critical": + self.log.warning( + f"You have {repository.data.full_name} installed with HACS " + + "this repository has been removed, please consider removing it. " + + f"Removal reason ({removed.removal_type})" + ) + else: + need_to_save = True + repository.remove() + + if need_to_save: + await self.data.async_write() + + async def async_load_default_repositories(self): + """Load known repositories.""" + self.log.info("Loading known repositories") + + for item in await async_get_list_from_default(HacsCategory.REMOVED): + removed = get_removed(item["repository"]) + removed.reason = item.get("reason") + removed.link = item.get("link") + removed.removal_type = item.get("removal_type") + + for category in self.common.categories or []: + self.queue.add(self.async_get_category_repositories(HacsCategory(category))) + + await self.prosess_queue() + + async def async_get_category_repositories(self, category: HacsCategory): + """Get repositories from category.""" + repositories = await async_get_list_from_default(category) + for repo in repositories: + if is_removed(repo): + continue + repository = self.get_by_name(repo) + if repository is not None: + if str(repository.data.id) not in self.common.default: + self.common.default.append(str(repository.data.id)) + else: + continue + continue + self.queue.add(self.factory.safe_register(repo, category)) + + async def async_set_stage(self, stage: str) -> None: + """Set the stage of HACS.""" + self.stage = HacsStage(stage) + self.log.info("Stage changed: %s", self.stage) + self.hass.bus.async_fire("hacs/stage", {"stage": self.stage}) diff --git a/custom_components/hacs/hacsbase/task_factory.py b/custom_components/hacs/hacsbase/task_factory.py deleted file mode 100644 index 8b32aa9..0000000 --- a/custom_components/hacs/hacsbase/task_factory.py +++ /dev/null @@ -1,75 +0,0 @@ -# pylint: disable=missing-docstring,invalid-name -import logging -import time -from datetime import timedelta -import asyncio -from aiogithubapi import AIOGitHubException - -from custom_components.hacs.hacsbase.exceptions import HacsException -from custom_components.hacs.helpers.register_repository import register_repository - - -max_concurrent_tasks = asyncio.Semaphore(15) -sleeper = 5 - -logger = logging.getLogger("hacs.factory") - - -class HacsTaskFactory: - def __init__(self): - self.tasks = [] - self.running = False - - async def execute(self): - if not self.tasks: - logger.debug("No tasks to execute") - return - if self.running: - logger.debug("Allready executing tasks") - return - try: - self.running = True - logger.info("Processing %s tasks", len(self.tasks)) - start = time.time() - await asyncio.gather(*self.tasks) - logger.info( - "Task processing of %s tasks completed in %s seconds", - len(self.tasks), - timedelta(seconds=round(time.time() - start)).seconds, - ) - self.tasks = [] - self.running = False - except RuntimeError: - logger.warning("RuntimeError, Clearing current tasks") - self.tasks = [] - self.running = False - - async def safe_common_update(self, repository): - async with max_concurrent_tasks: - try: - await repository.common_update() - except (AIOGitHubException, HacsException) as exception: - logger.error("%s - %s", repository.data.full_name, exception) - - # Due to GitHub ratelimits we need to sleep a bit - await asyncio.sleep(sleeper) - - async def safe_update(self, repository): - async with max_concurrent_tasks: - try: - await repository.update_repository() - except (AIOGitHubException, HacsException) as exception: - logger.error("%s - %s", repository.data.full_name, exception) - - # Due to GitHub ratelimits we need to sleep a bit - await asyncio.sleep(sleeper) - - async def safe_register(self, repo, category): - async with max_concurrent_tasks: - try: - await register_repository(repo, category) - except (AIOGitHubException, HacsException) as exception: - logger.error("%s - %s", repo, exception) - - # Due to GitHub ratelimits we need to sleep a bit - await asyncio.sleep(sleeper) diff --git a/custom_components/hacs/handler/__init__.py b/custom_components/hacs/handler/__init__.py deleted file mode 100644 index ab064c7..0000000 --- a/custom_components/hacs/handler/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""Initialize handlers.""" diff --git a/custom_components/hacs/handler/__pycache__/__init__.cpython-37.pyc b/custom_components/hacs/handler/__pycache__/__init__.cpython-37.pyc deleted file mode 100644 index 50f4649..0000000 Binary files a/custom_components/hacs/handler/__pycache__/__init__.cpython-37.pyc and /dev/null differ diff --git a/custom_components/hacs/handler/__pycache__/download.cpython-37.pyc b/custom_components/hacs/handler/__pycache__/download.cpython-37.pyc deleted file mode 100644 index e2b77c7..0000000 Binary files a/custom_components/hacs/handler/__pycache__/download.cpython-37.pyc and /dev/null differ diff --git a/custom_components/hacs/handler/__pycache__/template.cpython-37.pyc b/custom_components/hacs/handler/__pycache__/template.cpython-37.pyc deleted file mode 100644 index eb3c0aa..0000000 Binary files a/custom_components/hacs/handler/__pycache__/template.cpython-37.pyc and /dev/null differ diff --git a/custom_components/hacs/handler/download.py b/custom_components/hacs/handler/download.py deleted file mode 100644 index 489c99d..0000000 --- a/custom_components/hacs/handler/download.py +++ /dev/null @@ -1,90 +0,0 @@ -"""Download.""" -import os -import gzip -import shutil - -import aiofiles -import async_timeout -from integrationhelper import Logger -import backoff -from ..hacsbase.exceptions import HacsException - -from custom_components.hacs.globals import get_hacs - - -@backoff.on_exception(backoff.expo, Exception, max_tries=5) -async def async_download_file(url): - """ - Download files, and return the content. - """ - hacs = get_hacs() - logger = Logger("hacs.download.downloader") - if url is None: - return - - # There is a bug somewhere... TODO: Find that bug.... - if "tags/" in url: - url = url.replace("tags/", "") - - logger.debug(f"Downloading {url}") - - result = None - - with async_timeout.timeout(60, loop=hacs.hass.loop): - request = await hacs.session.get(url) - - # Make sure that we got a valid result - if request.status == 200: - result = await request.read() - else: - raise HacsException( - "Got status code {} when trying to download {}".format( - request.status, url - ) - ) - - return result - - -async def async_save_file(location, content): - """Save files.""" - logger = Logger("hacs.download.save") - logger.debug(f"Saving {location}") - mode = "w" - encoding = "utf-8" - errors = "ignore" - - if not isinstance(content, str): - mode = "wb" - encoding = None - errors = None - - try: - async with aiofiles.open( - location, mode=mode, encoding=encoding, errors=errors - ) as outfile: - await outfile.write(content) - outfile.close() - - # Create gz for .js files - if os.path.isfile(location): - if location.endswith(".js") or location.endswith(".css"): - with open(location, "rb") as f_in: - with gzip.open(location + ".gz", "wb") as f_out: - shutil.copyfileobj(f_in, f_out) - - # Remove with 2.0 - if "themes" in location and location.endswith(".yaml"): - filename = location.split("/")[-1] - base = location.split("/themes/")[0] - combined = f"{base}/themes/{filename}" - if os.path.exists(combined): - logger.info(f"Removing old theme file {combined}") - os.remove(combined) - - except Exception as error: # pylint: disable=broad-except - msg = "Could not write data to {} - {}".format(location, error) - logger.error(msg) - return False - - return os.path.exists(location) diff --git a/custom_components/hacs/helpers/__init__.py b/custom_components/hacs/helpers/__init__.py new file mode 100644 index 0000000..8c5755c --- /dev/null +++ b/custom_components/hacs/helpers/__init__.py @@ -0,0 +1,17 @@ +# pylint: disable=missing-class-docstring,missing-module-docstring,missing-function-docstring,no-member +from custom_components.hacs.helpers.methods import ( + HacsHelperMethods, + RepositoryHelperMethods, +) +from custom_components.hacs.helpers.properties import RepositoryHelperProperties + + +class RepositoryHelpers( + RepositoryHelperMethods, + RepositoryHelperProperties, +): + """Helper class for repositories""" + + +class HacsHelpers(HacsHelperMethods): + """Helper class for HACS""" diff --git a/custom_components/hacs/helpers/__pycache__/__init__.cpython-38.pyc b/custom_components/hacs/helpers/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..ac59aa0 Binary files /dev/null and b/custom_components/hacs/helpers/__pycache__/__init__.cpython-38.pyc differ diff --git a/custom_components/hacs/helpers/__pycache__/download.cpython-37.pyc b/custom_components/hacs/helpers/__pycache__/download.cpython-37.pyc deleted file mode 100644 index d366712..0000000 Binary files a/custom_components/hacs/helpers/__pycache__/download.cpython-37.pyc and /dev/null differ diff --git a/custom_components/hacs/helpers/__pycache__/filters.cpython-37.pyc b/custom_components/hacs/helpers/__pycache__/filters.cpython-37.pyc deleted file mode 100644 index 3e47a69..0000000 Binary files a/custom_components/hacs/helpers/__pycache__/filters.cpython-37.pyc and /dev/null differ diff --git a/custom_components/hacs/helpers/__pycache__/get_defaults.cpython-37.pyc b/custom_components/hacs/helpers/__pycache__/get_defaults.cpython-37.pyc deleted file mode 100644 index 4f7ec21..0000000 Binary files a/custom_components/hacs/helpers/__pycache__/get_defaults.cpython-37.pyc and /dev/null differ diff --git a/custom_components/hacs/helpers/__pycache__/information.cpython-37.pyc b/custom_components/hacs/helpers/__pycache__/information.cpython-37.pyc deleted file mode 100644 index af7ad8b..0000000 Binary files a/custom_components/hacs/helpers/__pycache__/information.cpython-37.pyc and /dev/null differ diff --git a/custom_components/hacs/helpers/__pycache__/install.cpython-37.pyc b/custom_components/hacs/helpers/__pycache__/install.cpython-37.pyc deleted file mode 100644 index 98744e5..0000000 Binary files a/custom_components/hacs/helpers/__pycache__/install.cpython-37.pyc and /dev/null differ diff --git a/custom_components/hacs/helpers/__pycache__/misc.cpython-37.pyc b/custom_components/hacs/helpers/__pycache__/misc.cpython-37.pyc deleted file mode 100644 index 0c9b130..0000000 Binary files a/custom_components/hacs/helpers/__pycache__/misc.cpython-37.pyc and /dev/null differ diff --git a/custom_components/hacs/helpers/__pycache__/network.cpython-37.pyc b/custom_components/hacs/helpers/__pycache__/network.cpython-37.pyc deleted file mode 100644 index 79b69f2..0000000 Binary files a/custom_components/hacs/helpers/__pycache__/network.cpython-37.pyc and /dev/null differ diff --git a/custom_components/hacs/helpers/__pycache__/register_repository.cpython-37.pyc b/custom_components/hacs/helpers/__pycache__/register_repository.cpython-37.pyc deleted file mode 100644 index 98476d0..0000000 Binary files a/custom_components/hacs/helpers/__pycache__/register_repository.cpython-37.pyc and /dev/null differ diff --git a/custom_components/hacs/helpers/__pycache__/validate_repository.cpython-37.pyc b/custom_components/hacs/helpers/__pycache__/validate_repository.cpython-37.pyc deleted file mode 100644 index 816e3af..0000000 Binary files a/custom_components/hacs/helpers/__pycache__/validate_repository.cpython-37.pyc and /dev/null differ diff --git a/custom_components/hacs/helpers/classes/__init__.py b/custom_components/hacs/helpers/classes/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/custom_components/hacs/helpers/classes/__pycache__/__init__.cpython-38.pyc b/custom_components/hacs/helpers/classes/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..4bc010c Binary files /dev/null and b/custom_components/hacs/helpers/classes/__pycache__/__init__.cpython-38.pyc differ diff --git a/custom_components/hacs/helpers/classes/__pycache__/exceptions.cpython-38.pyc b/custom_components/hacs/helpers/classes/__pycache__/exceptions.cpython-38.pyc new file mode 100644 index 0000000..c7f8b11 Binary files /dev/null and b/custom_components/hacs/helpers/classes/__pycache__/exceptions.cpython-38.pyc differ diff --git a/custom_components/hacs/helpers/classes/__pycache__/frontend_view.cpython-38.pyc b/custom_components/hacs/helpers/classes/__pycache__/frontend_view.cpython-38.pyc new file mode 100644 index 0000000..4c36ce9 Binary files /dev/null and b/custom_components/hacs/helpers/classes/__pycache__/frontend_view.cpython-38.pyc differ diff --git a/custom_components/hacs/helpers/classes/__pycache__/manifest.cpython-38.pyc b/custom_components/hacs/helpers/classes/__pycache__/manifest.cpython-38.pyc new file mode 100644 index 0000000..8c9627b Binary files /dev/null and b/custom_components/hacs/helpers/classes/__pycache__/manifest.cpython-38.pyc differ diff --git a/custom_components/hacs/helpers/classes/__pycache__/removed.cpython-38.pyc b/custom_components/hacs/helpers/classes/__pycache__/removed.cpython-38.pyc new file mode 100644 index 0000000..101b97a Binary files /dev/null and b/custom_components/hacs/helpers/classes/__pycache__/removed.cpython-38.pyc differ diff --git a/custom_components/hacs/helpers/classes/__pycache__/repository.cpython-38.pyc b/custom_components/hacs/helpers/classes/__pycache__/repository.cpython-38.pyc new file mode 100644 index 0000000..d15ad33 Binary files /dev/null and b/custom_components/hacs/helpers/classes/__pycache__/repository.cpython-38.pyc differ diff --git a/custom_components/hacs/helpers/classes/__pycache__/repositorydata.cpython-38.pyc b/custom_components/hacs/helpers/classes/__pycache__/repositorydata.cpython-38.pyc new file mode 100644 index 0000000..4df1eb1 Binary files /dev/null and b/custom_components/hacs/helpers/classes/__pycache__/repositorydata.cpython-38.pyc differ diff --git a/custom_components/hacs/helpers/classes/__pycache__/validate.cpython-38.pyc b/custom_components/hacs/helpers/classes/__pycache__/validate.cpython-38.pyc new file mode 100644 index 0000000..0de8e06 Binary files /dev/null and b/custom_components/hacs/helpers/classes/__pycache__/validate.cpython-38.pyc differ diff --git a/custom_components/hacs/hacsbase/exceptions.py b/custom_components/hacs/helpers/classes/exceptions.py similarity index 94% rename from custom_components/hacs/hacsbase/exceptions.py rename to custom_components/hacs/helpers/classes/exceptions.py index 62e5389..ee6f832 100644 --- a/custom_components/hacs/hacsbase/exceptions.py +++ b/custom_components/hacs/helpers/classes/exceptions.py @@ -1,9 +1,9 @@ -"""Custom Exceptions.""" - - -class HacsException(Exception): - """Super basic.""" - - -class HacsExpectedException(HacsException): - """For stuff that are expected.""" +"""Custom Exceptions.""" + + +class HacsException(Exception): + """Super basic.""" + + +class HacsExpectedException(HacsException): + """For stuff that are expected.""" diff --git a/custom_components/hacs/helpers/classes/frontend_view.py b/custom_components/hacs/helpers/classes/frontend_view.py new file mode 100644 index 0000000..391e426 --- /dev/null +++ b/custom_components/hacs/helpers/classes/frontend_view.py @@ -0,0 +1,40 @@ +"""HACS http endpoints.""" +from aiohttp import web +from homeassistant.components.http import HomeAssistantView + +from custom_components.hacs.helpers.functions.logger import getLogger +from custom_components.hacs.webresponses.category import async_serve_category_file +from custom_components.hacs.webresponses.frontend import async_serve_frontend +from custom_components.hacs.webresponses.iconset import serve_iconset + +IGNORE = [] + +_LOGGER = getLogger() + + +class HacsFrontend(HomeAssistantView): + """Base View Class for HACS.""" + + requires_auth = False + name = "hacs_files" + url = r"/hacsfiles/{requested_file:.+}" + + async def get(self, request, requested_file): # pylint: disable=unused-argument + """Handle HACS Web requests.""" + return await get_file_response(request, requested_file) + + +async def get_file_response(request, requested_file): + """Get file.""" + + if requested_file in IGNORE: + _LOGGER.debug("Ignoring request for %s", requested_file) + return web.Response(status=200) + + elif requested_file.startswith("frontend/"): + return await async_serve_frontend(requested_file) + + elif requested_file == "iconset.js": + return serve_iconset() + + return await async_serve_category_file(request, requested_file) diff --git a/custom_components/hacs/repositories/manifest.py b/custom_components/hacs/helpers/classes/manifest.py similarity index 89% rename from custom_components/hacs/repositories/manifest.py rename to custom_components/hacs/helpers/classes/manifest.py index 4213d33..f303010 100644 --- a/custom_components/hacs/repositories/manifest.py +++ b/custom_components/hacs/helpers/classes/manifest.py @@ -1,42 +1,43 @@ -""" -Manifest handling of a repository. - -https://hacs.xyz/docs/publish/start#hacsjson -""" -from typing import List -import attr - -from custom_components.hacs.hacsbase.exceptions import HacsException - - -@attr.s(auto_attribs=True) -class HacsManifest: - """HacsManifest class.""" - - name: str = None - content_in_root: bool = False - zip_release: bool = False - filename: str = None - manifest: dict = {} - hacs: str = None - hide_default_branch: bool = False - domains: List[str] = [] - country: List[str] = [] - homeassistant: str = None - persistent_directory: str = None - iot_class: str = None - render_readme: bool = False - - @staticmethod - def from_dict(manifest: dict): - """Set attributes from dicts.""" - if manifest is None: - raise HacsException("Missing manifest data") - - manifest_data = HacsManifest() - - manifest_data.manifest = manifest - - for key in manifest: - setattr(manifest_data, key, manifest[key]) - return manifest_data +""" +Manifest handling of a repository. + +https://hacs.xyz/docs/publish/start#hacsjson +""" +from typing import List + +import attr + +from custom_components.hacs.helpers.classes.exceptions import HacsException + + +@attr.s(auto_attribs=True) +class HacsManifest: + """HacsManifest class.""" + + name: str = None + content_in_root: bool = False + zip_release: bool = False + filename: str = None + manifest: dict = {} + hacs: str = None + hide_default_branch: bool = False + domains: List[str] = [] + country: List[str] = [] + homeassistant: str = None + persistent_directory: str = None + iot_class: str = None + render_readme: bool = False + + @staticmethod + def from_dict(manifest: dict): + """Set attributes from dicts.""" + if manifest is None: + raise HacsException("Missing manifest data") + + manifest_data = HacsManifest() + + manifest_data.manifest = manifest + + for key in manifest: + setattr(manifest_data, key, manifest[key]) + return manifest_data diff --git a/custom_components/hacs/repositories/removed.py b/custom_components/hacs/helpers/classes/removed.py similarity index 80% rename from custom_components/hacs/repositories/removed.py rename to custom_components/hacs/helpers/classes/removed.py index 69955eb..fb5b9b9 100644 --- a/custom_components/hacs/repositories/removed.py +++ b/custom_components/hacs/helpers/classes/removed.py @@ -15,3 +15,7 @@ class RemovedRepository: for key in data: if key in self.__dict__: setattr(self, key, data[key]) + + def to_json(self): + """Return a JSON representation of the data.""" + return attr.asdict(self) diff --git a/custom_components/hacs/repositories/repository.py b/custom_components/hacs/helpers/classes/repository.py similarity index 53% rename from custom_components/hacs/repositories/repository.py rename to custom_components/hacs/helpers/classes/repository.py index b471683..b00c1f2 100644 --- a/custom_components/hacs/repositories/repository.py +++ b/custom_components/hacs/helpers/classes/repository.py @@ -1,424 +1,436 @@ -"""Repository.""" -# pylint: disable=broad-except, bad-continuation, no-member -import json -import os -import tempfile -import zipfile -from integrationhelper import Validate -from aiogithubapi import AIOGitHubException -from .manifest import HacsManifest -from ..helpers.misc import get_repository_name -from ..handler.download import async_download_file, async_save_file -from ..helpers.misc import version_left_higher_then_right -from ..helpers.install import install_repository, version_to_install - -from custom_components.hacs.globals import get_hacs -from custom_components.hacs.helpers.information import ( - get_info_md_content, - get_repository, -) -from custom_components.hacs.helpers.validate_repository import ( - common_validate, - common_update_data, -) -from custom_components.hacs.repositories.repositorydata import RepositoryData - - -class RepositoryVersions: - """Versions.""" - - available = None - available_commit = None - installed = None - installed_commit = None - - -class RepositoryStatus: - """Repository status.""" - - hide = False - installed = False - last_updated = None - new = True - selected_tag = None - show_beta = False - track = True - updated_info = False - first_install = True - - -class RepositoryInformation: - """RepositoryInformation.""" - - additional_info = None - authors = [] - category = None - default_branch = None - description = "" - state = None - full_name = None - file_name = None - javascript_type = None - homeassistant_version = None - last_updated = None - uid = None - stars = 0 - info = None - name = None - topics = [] - - -class RepositoryReleases: - """RepositoyReleases.""" - - last_release = None - last_release_object = None - last_release_object_downloads = None - published_tags = [] - objects = [] - releases = False - downloads = None - - -class RepositoryPath: - """RepositoryPath.""" - - local = None - remote = None - - -class RepositoryContent: - """RepositoryContent.""" - - path = None - files = [] - objects = [] - single = False - - -class HacsRepository: - """HacsRepository.""" - - def __init__(self): - """Set up HacsRepository.""" - self.hacs = get_hacs() - self.data = RepositoryData() - self.content = RepositoryContent() - self.content.path = RepositoryPath() - self.information = RepositoryInformation() - self.repository_object = None - self.status = RepositoryStatus() - self.state = None - self.integration_manifest = {} - self.repository_manifest = HacsManifest.from_dict({}) - self.validate = Validate() - self.releases = RepositoryReleases() - self.versions = RepositoryVersions() - self.pending_restart = False - self.tree = [] - self.treefiles = [] - self.ref = None - - @property - def pending_upgrade(self): - """Return pending upgrade.""" - if self.status.installed: - if self.status.selected_tag is not None: - if self.status.selected_tag == self.data.default_branch: - if self.versions.installed_commit != self.versions.available_commit: - return True - return False - if self.display_installed_version != self.display_available_version: - return True - return False - - @property - def config_flow(self): - """Return bool if integration has config_flow.""" - if self.integration_manifest: - if self.data.full_name == "hacs/integration": - return False - return self.integration_manifest.get("config_flow", False) - return False - - @property - def custom(self): - """Return flag if the repository is custom.""" - if self.data.full_name.split("/")[0] in ["custom-components", "custom-cards"]: - return False - if self.data.full_name.lower() in [x.lower() for x in self.hacs.common.default]: - return False - if self.data.full_name == "hacs/integration": - return False - return True - - @property - def can_install(self): - """Return bool if repository can be installed.""" - target = None - if self.information.homeassistant_version is not None: - target = self.information.homeassistant_version - if self.repository_manifest is not None: - if self.data.homeassistant is not None: - target = self.data.homeassistant - - if target is not None: - if self.releases.releases: - if not version_left_higher_then_right( - self.hacs.system.ha_version, target - ): - return False - return True - - @property - def display_name(self): - """Return display name.""" - return get_repository_name(self) - - @property - def display_status(self): - """Return display_status.""" - if self.status.new: - status = "new" - elif self.pending_restart: - status = "pending-restart" - elif self.pending_upgrade: - status = "pending-upgrade" - elif self.status.installed: - status = "installed" - else: - status = "default" - return status - - @property - def display_status_description(self): - """Return display_status_description.""" - description = { - "default": "Not installed.", - "pending-restart": "Restart pending.", - "pending-upgrade": "Upgrade pending.", - "installed": "No action required.", - "new": "This is a newly added repository.", - } - return description[self.display_status] - - @property - def display_installed_version(self): - """Return display_authors""" - if self.versions.installed is not None: - installed = self.versions.installed - else: - if self.versions.installed_commit is not None: - installed = self.versions.installed_commit - else: - installed = "" - return installed - - @property - def display_available_version(self): - """Return display_authors""" - if self.versions.available is not None: - available = self.versions.available - else: - if self.versions.available_commit is not None: - available = self.versions.available_commit - else: - available = "" - return available - - @property - def display_version_or_commit(self): - """Does the repositoriy use releases or commits?""" - if self.releases.releases: - version_or_commit = "version" - else: - version_or_commit = "commit" - return version_or_commit - - @property - def main_action(self): - """Return the main action.""" - actions = { - "new": "INSTALL", - "default": "INSTALL", - "installed": "REINSTALL", - "pending-restart": "REINSTALL", - "pending-upgrade": "UPGRADE", - } - return actions[self.display_status] - - async def common_validate(self): - """Common validation steps of the repository.""" - await common_validate(self) - - async def common_registration(self): - """Common registration steps of the repository.""" - # Attach repository - if self.repository_object is None: - self.repository_object = await get_repository( - self.hacs.session, self.hacs.configuration.token, self.data.full_name - ) - self.data.update_data(self.repository_object.attributes) - - # Set id - self.information.uid = str(self.data.id) - - # Set topics - self.data.topics = self.data.topics - - # Set stargazers_count - self.data.stargazers_count = self.data.stargazers_count - - # Set description - self.data.description = self.data.description - - async def common_update(self): - """Common information update steps of the repository.""" - self.logger.debug("Getting repository information") - - # Attach repository - await common_update_data(self) - - # Update last updaeted - self.information.last_updated = self.repository_object.attributes.get( - "pushed_at", 0 - ) - - # Update last available commit - await self.repository_object.set_last_commit() - self.versions.available_commit = self.repository_object.last_commit - - # Get the content of hacs.json - await self.get_repository_manifest_content() - - # Update "info.md" - self.information.additional_info = await get_info_md_content(self) - - async def install(self): - """Common installation steps of the repository.""" - await install_repository(self) - - async def download_zip(self, validate): - """Download ZIP archive from repository release.""" - try: - contents = False - - for release in self.releases.objects: - self.logger.info(f"ref: {self.ref} --- tag: {release.tag_name}") - if release.tag_name == self.ref.split("/")[1]: - contents = release.assets - - if not contents: - return validate - - for content in contents or []: - filecontent = await async_download_file(content.download_url) - - if filecontent is None: - validate.errors.append(f"[{content.name}] was not downloaded.") - continue - - result = await async_save_file( - f"{tempfile.gettempdir()}/{self.data.filename}", filecontent - ) - with zipfile.ZipFile( - f"{tempfile.gettempdir()}/{self.data.filename}", "r" - ) as zip_file: - zip_file.extractall(self.content.path.local) - - if result: - self.logger.info(f"download of {content.name} complete") - continue - validate.errors.append(f"[{content.name}] was not downloaded.") - except Exception: - validate.errors.append(f"Download was not complete.") - - return validate - - async def download_content(self, validate, directory_path, local_directory, ref): - """Download the content of a directory.""" - from custom_components.hacs.helpers.download import download_content - - validate = await download_content(self) - return validate - - async def get_repository_manifest_content(self): - """Get the content of the hacs.json file.""" - if not "hacs.json" in [x.filename for x in self.tree]: - return - if self.ref is None: - self.ref = version_to_install(self) - try: - manifest = await self.repository_object.get_contents("hacs.json", self.ref) - self.repository_manifest = HacsManifest.from_dict( - json.loads(manifest.content) - ) - self.data.update_data(json.loads(manifest.content)) - except (AIOGitHubException, Exception): # Gotta Catch 'Em All - pass - - def remove(self): - """Run remove tasks.""" - self.logger.info("Starting removal") - - if self.information.uid in self.hacs.common.installed: - self.hacs.common.installed.remove(self.information.uid) - for repository in self.hacs.repositories: - if repository.information.uid == self.information.uid: - self.hacs.repositories.remove(repository) - - async def uninstall(self): - """Run uninstall tasks.""" - self.logger.info("Uninstalling") - await self.remove_local_directory() - self.status.installed = False - if self.data.category == "integration": - if self.config_flow: - await self.reload_custom_components() - else: - self.pending_restart = True - elif self.data.category == "theme": - try: - await self.hacs.hass.services.async_call( - "frontend", "reload_themes", {} - ) - except Exception: # pylint: disable=broad-except - pass - if self.data.full_name in self.hacs.common.installed: - self.hacs.common.installed.remove(self.data.full_name) - self.versions.installed = None - self.versions.installed_commit = None - self.hacs.hass.bus.async_fire( - "hacs/repository", - {"id": 1337, "action": "uninstall", "repository": self.data.full_name}, - ) - - async def remove_local_directory(self): - """Check the local directory.""" - import shutil - from asyncio import sleep - - try: - if self.data.category == "python_script": - local_path = "{}/{}.py".format(self.content.path.local, self.data.name) - elif self.data.category == "theme": - local_path = "{}/{}.yaml".format( - self.content.path.local, self.data.name - ) - else: - local_path = self.content.path.local - - if os.path.exists(local_path): - self.logger.debug(f"Removing {local_path}") - - if self.data.category in ["python_script", "theme"]: - os.remove(local_path) - else: - shutil.rmtree(local_path) - - while os.path.exists(local_path): - await sleep(1) - - except Exception as exception: - self.logger.debug(f"Removing {local_path} failed with {exception}") - return +"""Repository.""" +# pylint: disable=broad-except, no-member +from custom_components.hacs.helpers.functions.logger import getLogger +import json +import os +import tempfile +import zipfile + +from aiogithubapi import AIOGitHubAPIException +from queueman import QueueManager + +from custom_components.hacs.helpers import RepositoryHelpers +from custom_components.hacs.helpers.classes.exceptions import HacsException +from custom_components.hacs.helpers.classes.manifest import HacsManifest +from custom_components.hacs.helpers.classes.repositorydata import RepositoryData +from custom_components.hacs.helpers.classes.validate import Validate +from custom_components.hacs.helpers.functions.is_safe_to_remove import is_safe_to_remove + +from custom_components.hacs.helpers.functions.download import async_download_file +from custom_components.hacs.helpers.functions.information import ( + get_info_md_content, + get_repository, +) +from custom_components.hacs.helpers.functions.misc import get_repository_name +from custom_components.hacs.helpers.functions.save import async_save_file +from custom_components.hacs.helpers.functions.store import async_remove_store +from custom_components.hacs.helpers.functions.validate_repository import ( + common_update_data, + common_validate, +) +from custom_components.hacs.helpers.functions.version_to_install import ( + version_to_install, +) +from custom_components.hacs.share import get_hacs + + +class RepositoryVersions: + """Versions.""" + + available = None + available_commit = None + installed = None + installed_commit = None + + +class RepositoryStatus: + """Repository status.""" + + hide = False + installed = False + last_updated = None + new = True + selected_tag = None + show_beta = False + track = True + updated_info = False + first_install = True + + +class RepositoryInformation: + """RepositoryInformation.""" + + additional_info = None + authors = [] + category = None + default_branch = None + description = "" + state = None + full_name = None + full_name_lower = None + file_name = None + javascript_type = None + homeassistant_version = None + last_updated = None + uid = None + stars = 0 + info = None + name = None + topics = [] + + +class RepositoryReleases: + """RepositoyReleases.""" + + last_release = None + last_release_object = None + last_release_object_downloads = None + published_tags = [] + objects = [] + releases = False + downloads = None + + +class RepositoryPath: + """RepositoryPath.""" + + local = None + remote = None + + +class RepositoryContent: + """RepositoryContent.""" + + path = None + files = [] + objects = [] + single = False + + +class HacsRepository(RepositoryHelpers): + """HacsRepository.""" + + def __init__(self): + """Set up HacsRepository.""" + self.hacs = get_hacs() + self.data = RepositoryData() + self.content = RepositoryContent() + self.content.path = RepositoryPath() + self.information = RepositoryInformation() + self.repository_object = None + self.status = RepositoryStatus() + self.state = None + self.force_branch = False + self.integration_manifest = {} + self.repository_manifest = HacsManifest.from_dict({}) + self.validate = Validate() + self.releases = RepositoryReleases() + self.versions = RepositoryVersions() + self.pending_restart = False + self.tree = [] + self.treefiles = [] + self.ref = None + self.logger = getLogger() + + def __str__(self) -> str: + """Return a string representation of the repository.""" + return f"<{self.data.category.title()} {self.data.full_name}>" + + @property + def display_name(self): + """Return display name.""" + return get_repository_name(self) + + @property + def display_status(self): + """Return display_status.""" + if self.data.new: + status = "new" + elif self.pending_restart: + status = "pending-restart" + elif self.pending_upgrade: + status = "pending-upgrade" + elif self.data.installed: + status = "installed" + else: + status = "default" + return status + + @property + def display_status_description(self): + """Return display_status_description.""" + description = { + "default": "Not installed.", + "pending-restart": "Restart pending.", + "pending-upgrade": "Upgrade pending.", + "installed": "No action required.", + "new": "This is a newly added repository.", + } + return description[self.display_status] + + @property + def display_installed_version(self): + """Return display_authors""" + if self.data.installed_version is not None: + installed = self.data.installed_version + else: + if self.data.installed_commit is not None: + installed = self.data.installed_commit + else: + installed = "" + return installed + + @property + def display_available_version(self): + """Return display_authors""" + if self.data.last_version is not None: + available = self.data.last_version + else: + if self.data.last_commit is not None: + available = self.data.last_commit + else: + available = "" + return available + + @property + def display_version_or_commit(self): + """Does the repositoriy use releases or commits?""" + if self.data.releases: + version_or_commit = "version" + else: + version_or_commit = "commit" + return version_or_commit + + @property + def main_action(self): + """Return the main action.""" + actions = { + "new": "INSTALL", + "default": "INSTALL", + "installed": "REINSTALL", + "pending-restart": "REINSTALL", + "pending-upgrade": "UPGRADE", + } + return actions[self.display_status] + + async def common_validate(self, ignore_issues=False): + """Common validation steps of the repository.""" + await common_validate(self, ignore_issues) + + async def common_registration(self): + """Common registration steps of the repository.""" + # Attach repository + if self.repository_object is None: + self.repository_object = await get_repository( + self.hacs.session, self.hacs.configuration.token, self.data.full_name + ) + self.data.update_data(self.repository_object.attributes) + + # Set topics + self.data.topics = self.data.topics + + # Set stargazers_count + self.data.stargazers_count = self.data.stargazers_count + + # Set description + self.data.description = self.data.description + + if self.hacs.system.action: + if self.data.description is None or len(self.data.description) == 0: + raise HacsException("::error:: Missing repository description") + + async def common_update(self, ignore_issues=False): + """Common information update steps of the repository.""" + self.logger.debug("%s Getting repository information", self) + + # Attach repository + await common_update_data(self, ignore_issues) + + # Update last updaeted + self.data.last_updated = self.repository_object.attributes.get("pushed_at", 0) + + # Update last available commit + await self.repository_object.set_last_commit() + self.data.last_commit = self.repository_object.last_commit + + # Get the content of hacs.json + await self.get_repository_manifest_content() + + # Update "info.md" + self.information.additional_info = await get_info_md_content(self) + + async def download_zip_files(self, validate): + """Download ZIP archive from repository release.""" + download_queue = QueueManager() + try: + contents = False + + for release in self.releases.objects: + self.logger.info( + "%s ref: %s --- tag: %s.", self, self.ref, release.tag_name + ) + if release.tag_name == self.ref.split("/")[1]: + contents = release.assets + + if not contents: + return validate + + for content in contents or []: + download_queue.add(self.async_download_zip_file(content, validate)) + + await download_queue.execute() + except (Exception, BaseException): + validate.errors.append("Download was not completed") + + return validate + + async def async_download_zip_file(self, content, validate): + """Download ZIP archive from repository release.""" + try: + filecontent = await async_download_file(content.download_url) + + if filecontent is None: + validate.errors.append(f"[{content.name}] was not downloaded") + return + + result = await async_save_file( + f"{tempfile.gettempdir()}/{self.data.filename}", filecontent + ) + with zipfile.ZipFile( + f"{tempfile.gettempdir()}/{self.data.filename}", "r" + ) as zip_file: + zip_file.extractall(self.content.path.local) + + if result: + self.logger.info("%s Download of %s completed", self, content.name) + return + validate.errors.append(f"[{content.name}] was not downloaded") + except (Exception, BaseException): + validate.errors.append("Download was not completed") + + return validate + + async def download_content(self, validate, _directory_path, _local_directory, _ref): + """Download the content of a directory.""" + from custom_components.hacs.helpers.functions.download import download_content + + validate = await download_content(self) + return validate + + async def get_repository_manifest_content(self): + """Get the content of the hacs.json file.""" + if not "hacs.json" in [x.filename for x in self.tree]: + if self.hacs.system.action: + raise HacsException( + "::error:: No hacs.json file in the root of the repository." + ) + return + if self.hacs.system.action: + self.logger.info("%s Found hacs.json", self) + + self.ref = version_to_install(self) + + try: + manifest = await self.repository_object.get_contents("hacs.json", self.ref) + self.repository_manifest = HacsManifest.from_dict( + json.loads(manifest.content) + ) + self.data.update_data(json.loads(manifest.content)) + except (AIOGitHubAPIException, Exception) as exception: # Gotta Catch 'Em All + if self.hacs.system.action: + raise HacsException( + f"::error:: hacs.json file is not valid ({exception})." + ) from None + if self.hacs.system.action: + self.logger.info("%s hacs.json is valid", self) + + def remove(self): + """Run remove tasks.""" + self.logger.info("%s Starting removal", self) + + if self.data.id in self.hacs.common.installed: + self.hacs.common.installed.remove(self.data.id) + for repository in self.hacs.repositories: + if repository.data.id == self.data.id: + self.hacs.repositories.remove(repository) + + async def uninstall(self): + """Run uninstall tasks.""" + self.logger.info("%s Uninstalling", self) + if not await self.remove_local_directory(): + raise HacsException("Could not uninstall") + self.data.installed = False + if self.data.category == "integration": + if self.data.config_flow: + await self.reload_custom_components() + else: + self.pending_restart = True + elif self.data.category == "theme": + try: + await self.hacs.hass.services.async_call( + "frontend", "reload_themes", {} + ) + except (Exception, BaseException): # pylint: disable=broad-except + pass + if self.data.full_name in self.hacs.common.installed: + self.hacs.common.installed.remove(self.data.full_name) + + await async_remove_store(self.hacs.hass, f"hacs/{self.data.id}.hacs") + + self.data.installed_version = None + self.data.installed_commit = None + self.hacs.hass.bus.async_fire( + "hacs/repository", + {"id": 1337, "action": "uninstall", "repository": self.data.full_name}, + ) + + async def remove_local_directory(self): + """Check the local directory.""" + import shutil + from asyncio import sleep + + try: + if self.data.category == "python_script": + local_path = f"{self.content.path.local}/{self.data.name}.py" + elif self.data.category == "theme": + if os.path.exists( + f"{self.hacs.core.config_path}/{self.hacs.configuration.theme_path}/{self.data.name}.yaml" + ): + os.remove( + f"{self.hacs.core.config_path}/{self.hacs.configuration.theme_path}/{self.data.name}.yaml" + ) + local_path = self.content.path.local + elif self.data.category == "integration": + if not self.data.domain: + self.logger.error("%s Missing domain", self) + return False + local_path = self.content.path.local + else: + local_path = self.content.path.local + + if os.path.exists(local_path): + if not is_safe_to_remove(local_path): + self.logger.error( + "%s Path %s is blocked from removal", self, local_path + ) + return False + self.logger.debug("%s Removing %s", self, local_path) + + if self.data.category in ["python_script"]: + os.remove(local_path) + else: + shutil.rmtree(local_path) + + while os.path.exists(local_path): + await sleep(1) + else: + self.logger.debug( + "%s Presumed local content path %s does not exist", self, local_path + ) + + except (Exception, BaseException) as exception: + self.logger.debug( + "%s Removing %s failed with %s", self, local_path, exception + ) + return False + return True diff --git a/custom_components/hacs/repositories/repositorydata.py b/custom_components/hacs/helpers/classes/repositorydata.py similarity index 52% rename from custom_components/hacs/repositories/repositorydata.py rename to custom_components/hacs/helpers/classes/repositorydata.py index 60b6775..6741bc6 100644 --- a/custom_components/hacs/repositories/repositorydata.py +++ b/custom_components/hacs/helpers/classes/repositorydata.py @@ -1,6 +1,7 @@ """Repository data.""" from datetime import datetime from typing import List + import attr @@ -8,32 +9,52 @@ import attr class RepositoryData: """RepositoryData class.""" - id: int = 0 - full_name: str = "" - pushed_at: str = "" - category: str = "" archived: bool = False - description: str = "" - manifest_name: str = None - topics: List[str] = [] - fork: bool = False - domain: str = "" - default_branch: str = None - stargazers_count: int = 0 - last_commit: str = "" - file_name: str = "" - content_in_root: bool = False - zip_release: bool = False - filename: str = "" - render_readme: bool = False - hide_default_branch: bool = False - domains: List[str] = [] - country: List[str] = [] authors: List[str] = [] - homeassistant: str = None # Minimum Home Assistant version + category: str = "" + content_in_root: bool = False + country: List[str] = [] + config_flow: bool = False + default_branch: str = None + description: str = "" + domain: str = "" + domains: List[str] = [] + downloads: int = 0 + file_name: str = "" + filename: str = "" + first_install: bool = False + fork: bool = False + full_name: str = "" hacs: str = None # Minimum HACS version - persistent_directory: str = None + hide: bool = False + hide_default_branch: bool = False + homeassistant: str = None # Minimum Home Assistant version + id: int = 0 iot_class: str = None + installed: bool = False + installed_commit: str = None + installed_version: str = None + open_issues: int = 0 + last_commit: str = None + last_version: str = None + last_updated: str = 0 + manifest_name: str = None + new: bool = True + persistent_directory: str = None + pushed_at: str = "" + releases: bool = False + render_readme: bool = False + published_tags: List[str] = [] + selected_tag: str = None + show_beta: bool = False + stargazers_count: int = 0 + topics: List[str] = [] + zip_release: bool = False + + @property + def stars(self): + """Return the stargazers count.""" + return self.stargazers_count or 0 @property def name(self): @@ -44,19 +65,33 @@ class RepositoryData: def to_json(self): """Export to json.""" - return self.__dict__ + return attr.asdict(self) @staticmethod def create_from_dict(source: dict): """Set attributes from dicts.""" data = RepositoryData() for key in source: + print(key) if key in data.__dict__: if key == "pushed_at": - setattr( - data, key, datetime.strptime(source[key], "%Y-%m-%dT%H:%M:%SZ") - ) - elif key == "county": + if source[key] == "": + continue + if "Z" in source[key]: + setattr( + data, + key, + datetime.strptime(source[key], "%Y-%m-%dT%H:%M:%SZ"), + ) + else: + setattr( + data, + key, + datetime.strptime(source[key], "%Y-%m-%dT%H:%M:%S"), + ) + elif key == "id": + setattr(data, key, str(source[key])) + elif key == "country": if isinstance(source[key], str): setattr(data, key, [source[key]]) else: @@ -70,10 +105,21 @@ class RepositoryData: for key in data: if key in self.__dict__: if key == "pushed_at": - setattr( - self, key, datetime.strptime(data[key], "%Y-%m-%dT%H:%M:%SZ") - ) - elif key == "county": + if data[key] == "": + continue + if "Z" in data[key]: + setattr( + self, + key, + datetime.strptime(data[key], "%Y-%m-%dT%H:%M:%SZ"), + ) + else: + setattr( + self, key, datetime.strptime(data[key], "%Y-%m-%dT%H:%M:%S") + ) + elif key == "id": + setattr(self, key, str(data[key])) + elif key == "country": if isinstance(data[key], str): setattr(self, key, [data[key]]) else: diff --git a/custom_components/hacs/helpers/classes/validate.py b/custom_components/hacs/helpers/classes/validate.py new file mode 100644 index 0000000..716da9c --- /dev/null +++ b/custom_components/hacs/helpers/classes/validate.py @@ -0,0 +1,11 @@ +class Validate: + """Validate.""" + + errors = [] + + @property + def success(self): + """Return bool if the validation was a success.""" + if self.errors: + return False + return True diff --git a/custom_components/hacs/helpers/functions/__init__.py b/custom_components/hacs/helpers/functions/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/custom_components/hacs/helpers/functions/__pycache__/__init__.cpython-38.pyc b/custom_components/hacs/helpers/functions/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..41b5882 Binary files /dev/null and b/custom_components/hacs/helpers/functions/__pycache__/__init__.cpython-38.pyc differ diff --git a/custom_components/hacs/helpers/functions/__pycache__/configuration_schema.cpython-38.pyc b/custom_components/hacs/helpers/functions/__pycache__/configuration_schema.cpython-38.pyc new file mode 100644 index 0000000..83944b4 Binary files /dev/null and b/custom_components/hacs/helpers/functions/__pycache__/configuration_schema.cpython-38.pyc differ diff --git a/custom_components/hacs/helpers/functions/__pycache__/constrains.cpython-38.pyc b/custom_components/hacs/helpers/functions/__pycache__/constrains.cpython-38.pyc new file mode 100644 index 0000000..5addd8d Binary files /dev/null and b/custom_components/hacs/helpers/functions/__pycache__/constrains.cpython-38.pyc differ diff --git a/custom_components/hacs/helpers/functions/__pycache__/download.cpython-38.pyc b/custom_components/hacs/helpers/functions/__pycache__/download.cpython-38.pyc new file mode 100644 index 0000000..0cce5aa Binary files /dev/null and b/custom_components/hacs/helpers/functions/__pycache__/download.cpython-38.pyc differ diff --git a/custom_components/hacs/helpers/functions/__pycache__/filters.cpython-38.pyc b/custom_components/hacs/helpers/functions/__pycache__/filters.cpython-38.pyc new file mode 100644 index 0000000..7b2190e Binary files /dev/null and b/custom_components/hacs/helpers/functions/__pycache__/filters.cpython-38.pyc differ diff --git a/custom_components/hacs/helpers/functions/__pycache__/get_list_from_default.cpython-38.pyc b/custom_components/hacs/helpers/functions/__pycache__/get_list_from_default.cpython-38.pyc new file mode 100644 index 0000000..c1539ff Binary files /dev/null and b/custom_components/hacs/helpers/functions/__pycache__/get_list_from_default.cpython-38.pyc differ diff --git a/custom_components/hacs/helpers/functions/__pycache__/information.cpython-38.pyc b/custom_components/hacs/helpers/functions/__pycache__/information.cpython-38.pyc new file mode 100644 index 0000000..8759d61 Binary files /dev/null and b/custom_components/hacs/helpers/functions/__pycache__/information.cpython-38.pyc differ diff --git a/custom_components/hacs/helpers/functions/__pycache__/is_safe_to_remove.cpython-38.pyc b/custom_components/hacs/helpers/functions/__pycache__/is_safe_to_remove.cpython-38.pyc new file mode 100644 index 0000000..cdbb709 Binary files /dev/null and b/custom_components/hacs/helpers/functions/__pycache__/is_safe_to_remove.cpython-38.pyc differ diff --git a/custom_components/hacs/helpers/functions/__pycache__/logger.cpython-38.pyc b/custom_components/hacs/helpers/functions/__pycache__/logger.cpython-38.pyc new file mode 100644 index 0000000..f63be79 Binary files /dev/null and b/custom_components/hacs/helpers/functions/__pycache__/logger.cpython-38.pyc differ diff --git a/custom_components/hacs/helpers/functions/__pycache__/misc.cpython-38.pyc b/custom_components/hacs/helpers/functions/__pycache__/misc.cpython-38.pyc new file mode 100644 index 0000000..de4b450 Binary files /dev/null and b/custom_components/hacs/helpers/functions/__pycache__/misc.cpython-38.pyc differ diff --git a/custom_components/hacs/helpers/functions/__pycache__/path_exsist.cpython-38.pyc b/custom_components/hacs/helpers/functions/__pycache__/path_exsist.cpython-38.pyc new file mode 100644 index 0000000..d59bfad Binary files /dev/null and b/custom_components/hacs/helpers/functions/__pycache__/path_exsist.cpython-38.pyc differ diff --git a/custom_components/hacs/helpers/functions/__pycache__/register_repository.cpython-38.pyc b/custom_components/hacs/helpers/functions/__pycache__/register_repository.cpython-38.pyc new file mode 100644 index 0000000..24948dd Binary files /dev/null and b/custom_components/hacs/helpers/functions/__pycache__/register_repository.cpython-38.pyc differ diff --git a/custom_components/hacs/helpers/functions/__pycache__/remaining_github_calls.cpython-38.pyc b/custom_components/hacs/helpers/functions/__pycache__/remaining_github_calls.cpython-38.pyc new file mode 100644 index 0000000..c16380c Binary files /dev/null and b/custom_components/hacs/helpers/functions/__pycache__/remaining_github_calls.cpython-38.pyc differ diff --git a/custom_components/hacs/helpers/functions/__pycache__/save.cpython-38.pyc b/custom_components/hacs/helpers/functions/__pycache__/save.cpython-38.pyc new file mode 100644 index 0000000..8ceffd7 Binary files /dev/null and b/custom_components/hacs/helpers/functions/__pycache__/save.cpython-38.pyc differ diff --git a/custom_components/hacs/helpers/functions/__pycache__/store.cpython-38.pyc b/custom_components/hacs/helpers/functions/__pycache__/store.cpython-38.pyc new file mode 100644 index 0000000..1894d3a Binary files /dev/null and b/custom_components/hacs/helpers/functions/__pycache__/store.cpython-38.pyc differ diff --git a/custom_components/hacs/helpers/functions/__pycache__/template.cpython-38.pyc b/custom_components/hacs/helpers/functions/__pycache__/template.cpython-38.pyc new file mode 100644 index 0000000..d6c6662 Binary files /dev/null and b/custom_components/hacs/helpers/functions/__pycache__/template.cpython-38.pyc differ diff --git a/custom_components/hacs/helpers/functions/__pycache__/validate_repository.cpython-38.pyc b/custom_components/hacs/helpers/functions/__pycache__/validate_repository.cpython-38.pyc new file mode 100644 index 0000000..c4dbdb1 Binary files /dev/null and b/custom_components/hacs/helpers/functions/__pycache__/validate_repository.cpython-38.pyc differ diff --git a/custom_components/hacs/helpers/functions/__pycache__/version_to_install.cpython-38.pyc b/custom_components/hacs/helpers/functions/__pycache__/version_to_install.cpython-38.pyc new file mode 100644 index 0000000..bf50c8b Binary files /dev/null and b/custom_components/hacs/helpers/functions/__pycache__/version_to_install.cpython-38.pyc differ diff --git a/custom_components/hacs/configuration_schema.py b/custom_components/hacs/helpers/functions/configuration_schema.py similarity index 53% rename from custom_components/hacs/configuration_schema.py rename to custom_components/hacs/helpers/functions/configuration_schema.py index 704ea4f..dfe632b 100644 --- a/custom_components/hacs/configuration_schema.py +++ b/custom_components/hacs/helpers/functions/configuration_schema.py @@ -1,16 +1,17 @@ """HACS Configuration Schemas.""" # pylint: disable=dangerous-default-value import voluptuous as vol -from .const import LOCALE + +from custom_components.hacs.const import LOCALE # Configuration: TOKEN = "token" SIDEPANEL_TITLE = "sidepanel_title" SIDEPANEL_ICON = "sidepanel_icon" +FRONTEND_REPO = "frontend_repo" +FRONTEND_REPO_URL = "frontend_repo_url" APPDAEMON = "appdaemon" NETDAEMON = "netdaemon" -PYTHON_SCRIPT = "python_script" -THEME = "theme" # Options: COUNTRY = "country" @@ -18,47 +19,47 @@ DEBUG = "debug" RELEASE_LIMIT = "release_limit" EXPERIMENTAL = "experimental" +# Config group +PATH_OR_URL = "frontend_repo_path_or_url" -def hacs_base_config_schema(config: dict = {}, config_flow: bool = False) -> dict: + +def hacs_base_config_schema(config: dict = {}) -> dict: """Return a shcema configuration dict for HACS.""" if not config: config = { TOKEN: "xxxxxxxxxxxxxxxxxxxxxxxxxxx", - SIDEPANEL_ICON: "mdi:alpha-c-box", - SIDEPANEL_TITLE: "HACS", - APPDAEMON: False, - NETDAEMON: False, - PYTHON_SCRIPT: False, - THEME: False, - } - if config_flow: - return { - vol.Required(TOKEN, default=config.get(TOKEN)): str, - vol.Optional(SIDEPANEL_TITLE, default=config.get(SIDEPANEL_TITLE)): str, - vol.Optional(SIDEPANEL_ICON, default=config.get(SIDEPANEL_ICON)): str, - vol.Optional(APPDAEMON, default=config.get(APPDAEMON)): bool, - vol.Optional(NETDAEMON, default=config.get(NETDAEMON)): bool, } return { vol.Required(TOKEN, default=config.get(TOKEN)): str, - vol.Optional(SIDEPANEL_TITLE, default=config.get(SIDEPANEL_TITLE)): str, - vol.Optional(SIDEPANEL_ICON, default=config.get(SIDEPANEL_ICON)): str, - vol.Optional(APPDAEMON, default=config.get(APPDAEMON)): bool, - vol.Optional(NETDAEMON, default=config.get(NETDAEMON)): bool, - vol.Optional(PYTHON_SCRIPT, default=config.get(PYTHON_SCRIPT)): bool, - vol.Optional(THEME, default=config.get(THEME)): bool, } def hacs_config_option_schema(options: dict = {}) -> dict: """Return a shcema for HACS configuration options.""" if not options: - options = {COUNTRY: "ALL", DEBUG: False, RELEASE_LIMIT: 5, EXPERIMENTAL: False} + options = { + APPDAEMON: False, + COUNTRY: "ALL", + DEBUG: False, + EXPERIMENTAL: False, + NETDAEMON: False, + RELEASE_LIMIT: 5, + SIDEPANEL_ICON: "hacs:hacs", + SIDEPANEL_TITLE: "HACS", + FRONTEND_REPO: "", + FRONTEND_REPO_URL: "", + } return { - vol.Optional(COUNTRY, default=options.get(COUNTRY)): vol.In(LOCALE), + vol.Optional(SIDEPANEL_TITLE, default=options.get(SIDEPANEL_TITLE)): str, + vol.Optional(SIDEPANEL_ICON, default=options.get(SIDEPANEL_ICON)): str, vol.Optional(RELEASE_LIMIT, default=options.get(RELEASE_LIMIT)): int, - vol.Optional(EXPERIMENTAL, default=options.get(EXPERIMENTAL)): bool, + vol.Optional(COUNTRY, default=options.get(COUNTRY)): vol.In(LOCALE), + vol.Optional(APPDAEMON, default=options.get(APPDAEMON)): bool, + vol.Optional(NETDAEMON, default=options.get(NETDAEMON)): bool, vol.Optional(DEBUG, default=options.get(DEBUG)): bool, + vol.Optional(EXPERIMENTAL, default=options.get(EXPERIMENTAL)): bool, + vol.Exclusive(FRONTEND_REPO, PATH_OR_URL): str, + vol.Exclusive(FRONTEND_REPO_URL, PATH_OR_URL): str, } diff --git a/custom_components/hacs/helpers/functions/constrains.py b/custom_components/hacs/helpers/functions/constrains.py new file mode 100644 index 0000000..ebef8ab --- /dev/null +++ b/custom_components/hacs/helpers/functions/constrains.py @@ -0,0 +1,43 @@ +"""HACS Startup constrains.""" +# pylint: disable=bad-continuation +import os + +from custom_components.hacs.const import ( + CUSTOM_UPDATER_LOCATIONS, + CUSTOM_UPDATER_WARNING, +) +from custom_components.hacs.helpers.functions.misc import version_left_higher_then_right +from custom_components.hacs.share import get_hacs + +MINIMUM_HA_VERSION = "0.110.0" + + +def check_constrains(): + """Check HACS constrains.""" + if not constrain_custom_updater(): + return False + if not constrain_version(): + return False + return True + + +def constrain_custom_updater(): + """Check if custom_updater exist.""" + hacs = get_hacs() + for location in CUSTOM_UPDATER_LOCATIONS: + if os.path.exists(location.format(hacs.core.config_path)): + msg = CUSTOM_UPDATER_WARNING.format(location.format(hacs.core.config_path)) + hacs.log.critical(msg) + return False + return True + + +def constrain_version(): + """Check if the version is valid.""" + hacs = get_hacs() + if not version_left_higher_then_right(hacs.system.ha_version, MINIMUM_HA_VERSION): + hacs.log.critical( + f"You need HA version {MINIMUM_HA_VERSION} or newer to use this integration." + ) + return False + return True diff --git a/custom_components/hacs/helpers/download.py b/custom_components/hacs/helpers/functions/download.py similarity index 51% rename from custom_components/hacs/helpers/download.py rename to custom_components/hacs/helpers/functions/download.py index 20414db..18851bf 100644 --- a/custom_components/hacs/helpers/download.py +++ b/custom_components/hacs/helpers/functions/download.py @@ -1,10 +1,22 @@ """Helpers to download repository content.""" +import os import pathlib import tempfile import zipfile -from custom_components.hacs.hacsbase.exceptions import HacsException -from custom_components.hacs.handler.download import async_download_file, async_save_file -from custom_components.hacs.helpers.filters import filter_content_return_one_of_type + +import async_timeout +import backoff +from queueman import QueueManager, concurrent + +from custom_components.hacs.helpers.classes.exceptions import HacsException +from custom_components.hacs.helpers.functions.filters import ( + filter_content_return_one_of_type, +) +from custom_components.hacs.helpers.functions.logger import getLogger +from custom_components.hacs.helpers.functions.save import async_save_file +from custom_components.hacs.share import get_hacs + +_LOGGER = getLogger() class FileInformation: @@ -14,6 +26,36 @@ class FileInformation: self.name = name +@backoff.on_exception(backoff.expo, Exception, max_tries=5) +async def async_download_file(url): + """Download files, and return the content.""" + hacs = get_hacs() + if url is None: + return + + if "tags/" in url: + url = url.replace("tags/", "") + + _LOGGER.debug("Downloading %s", url) + + result = None + + with async_timeout.timeout(60, loop=hacs.hass.loop): + request = await hacs.session.get(url) + + # Make sure that we got a valid result + if request.status == 200: + result = await request.read() + else: + raise HacsException( + "Got status code {} when trying to download {}".format( + request.status, url + ) + ) + + return result + + def should_try_releases(repository): """Return a boolean indicating whether to download releases or not.""" if repository.data.zip_release: @@ -24,7 +66,7 @@ def should_try_releases(repository): return False if repository.data.category not in ["plugin", "theme"]: return False - if not repository.releases.releases: + if not repository.data.releases: return False return True @@ -94,9 +136,10 @@ def gather_files_to_download(repository): return files -async def download_zip(repository, validate): +async def download_zip_files(repository, validate): """Download ZIP archive from repository release.""" contents = [] + queue = QueueManager() try: for release in repository.releases.objects: repository.logger.info( @@ -108,33 +151,48 @@ async def download_zip(repository, validate): if not contents: return validate - for content in contents: - filecontent = await async_download_file(content.download_url) + for content in contents or []: + queue.add(async_download_zip_file(repository, content, validate)) - if filecontent is None: - validate.errors.append(f"[{content.name}] was not downloaded.") - continue + await queue.execute() + except (Exception, BaseException) as exception: # pylint: disable=broad-except + validate.errors.append(f"Download was not completed [{exception}]") - result = await async_save_file( - f"{tempfile.gettempdir()}/{repository.data.filename}", filecontent - ) - with zipfile.ZipFile( - f"{tempfile.gettempdir()}/{repository.data.filename}", "r" - ) as zip_file: - zip_file.extractall(repository.content.path.local) + return validate - if result: - repository.logger.info(f"download of {content.name} complete") - continue + +async def async_download_zip_file(repository, content, validate): + """Download ZIP archive from repository release.""" + try: + filecontent = await async_download_file(content.download_url) + + if filecontent is None: validate.errors.append(f"[{content.name}] was not downloaded.") - except Exception as exception: # pylint: disable=broad-except - validate.errors.append(f"Download was not complete [{exception}]") + return + + result = await async_save_file( + f"{tempfile.gettempdir()}/{repository.data.filename}", filecontent + ) + with zipfile.ZipFile( + f"{tempfile.gettempdir()}/{repository.data.filename}", "r" + ) as zip_file: + zip_file.extractall(repository.content.path.local) + + os.remove(f"{tempfile.gettempdir()}/{repository.data.filename}") + + if result: + repository.logger.info(f"Download of {content.name} completed") + return + validate.errors.append(f"[{content.name}] was not downloaded.") + except (Exception, BaseException) as exception: # pylint: disable=broad-except + validate.errors.append(f"Download was not completed [{exception}]") return validate async def download_content(repository): """Download the content of a directory.""" + queue = QueueManager() contents = gather_files_to_download(repository) repository.logger.debug(repository.data.filename) if not contents: @@ -144,38 +202,45 @@ async def download_content(repository): if repository.data.content_in_root and repository.data.filename: if content.name != repository.data.filename: continue - repository.logger.debug(f"Downloading {content.name}") + queue.add(dowload_repository_content(repository, content)) + await queue.execute() + return repository.validate - filecontent = await async_download_file(content.download_url) - if filecontent is None: - repository.validate.errors.append(f"[{content.name}] was not downloaded.") - continue +@concurrent(10) +async def dowload_repository_content(repository, content): + """Download content.""" + repository.logger.debug(f"Downloading {content.name}") - # Save the content of the file. - if repository.content.single or content.path is None: - local_directory = repository.content.path.local + filecontent = await async_download_file(content.download_url) - else: - _content_path = content.path - if not repository.data.content_in_root: - _content_path = _content_path.replace( - f"{repository.content.path.remote}", "" - ) - - local_directory = f"{repository.content.path.local}/{_content_path}" - local_directory = local_directory.split("/") - del local_directory[-1] - local_directory = "/".join(local_directory) - - # Check local directory - pathlib.Path(local_directory).mkdir(parents=True, exist_ok=True) - - local_file_path = (f"{local_directory}/{content.name}").replace("//", "/") - - result = await async_save_file(local_file_path, filecontent) - if result: - repository.logger.info(f"download of {content.name} complete") - continue + if filecontent is None: repository.validate.errors.append(f"[{content.name}] was not downloaded.") + return + # Save the content of the file. + if repository.content.single or content.path is None: + local_directory = repository.content.path.local + + else: + _content_path = content.path + if not repository.data.content_in_root: + _content_path = _content_path.replace( + f"{repository.content.path.remote}", "" + ) + + local_directory = f"{repository.content.path.local}/{_content_path}" + local_directory = local_directory.split("/") + del local_directory[-1] + local_directory = "/".join(local_directory) + + # Check local directory + pathlib.Path(local_directory).mkdir(parents=True, exist_ok=True) + + local_file_path = (f"{local_directory}/{content.name}").replace("//", "/") + + result = await async_save_file(local_file_path, filecontent) + if result: + repository.logger.info(f"Download of {content.name} completed") + return + repository.validate.errors.append(f"[{content.name}] was not downloaded.") diff --git a/custom_components/hacs/helpers/filters.py b/custom_components/hacs/helpers/functions/filters.py similarity index 100% rename from custom_components/hacs/helpers/filters.py rename to custom_components/hacs/helpers/functions/filters.py diff --git a/custom_components/hacs/helpers/functions/get_list_from_default.py b/custom_components/hacs/helpers/functions/get_list_from_default.py new file mode 100644 index 0000000..b2ed428 --- /dev/null +++ b/custom_components/hacs/helpers/functions/get_list_from_default.py @@ -0,0 +1,35 @@ +"""Helper to get default repositories.""" +import json +from typing import List + +from aiogithubapi import AIOGitHubAPIException + +from custom_components.hacs.enums import HacsCategory +from custom_components.hacs.helpers.classes.exceptions import HacsException +from custom_components.hacs.helpers.functions.information import get_repository +from custom_components.hacs.share import get_hacs + + +async def async_get_list_from_default(default: HacsCategory) -> List: + """Get repositories from default list.""" + hacs = get_hacs() + repositories = [] + + try: + repo = await get_repository( + hacs.session, + hacs.configuration.token, + "hacs/default", + ) + content = await repo.get_contents(default, repo.default_branch) + repositories = json.loads(content.content) + + except (AIOGitHubAPIException, HacsException) as exception: + hacs.log.error(exception) + + except (Exception, BaseException) as exception: + hacs.log.error(exception) + + hacs.log.debug("Got %s elements for %s", len(repositories), default) + + return repositories diff --git a/custom_components/hacs/helpers/information.py b/custom_components/hacs/helpers/functions/information.py similarity index 71% rename from custom_components/hacs/helpers/information.py rename to custom_components/hacs/helpers/functions/information.py index 2fde31f..7fb21d3 100644 --- a/custom_components/hacs/helpers/information.py +++ b/custom_components/hacs/helpers/functions/information.py @@ -1,8 +1,11 @@ """Return repository information if any.""" import json -from aiogithubapi import AIOGitHubException, AIOGitHub -from custom_components.hacs.handler.template import render_template -from custom_components.hacs.hacsbase.exceptions import HacsException + +from aiogithubapi import AIOGitHubAPIException, GitHub + +from custom_components.hacs.helpers.classes.exceptions import HacsException +from custom_components.hacs.helpers.functions.template import render_template +from custom_components.hacs.share import get_hacs def info_file(repository): @@ -29,17 +32,19 @@ async def get_info_md_content(repository): return "" info = info.content.replace(" bool: + """Helper to check if path is safe to remove.""" + hacs = get_hacs() + paths = [ + Path(f"{hacs.core.config_path}/{hacs.configuration.appdaemon_path}"), + Path(f"{hacs.core.config_path}/{hacs.configuration.netdaemon_path}"), + Path(f"{hacs.core.config_path}/{hacs.configuration.plugin_path}"), + Path(f"{hacs.core.config_path}/{hacs.configuration.python_script_path}"), + Path(f"{hacs.core.config_path}/{hacs.configuration.theme_path}"), + Path(f"{hacs.core.config_path}/custom_components/"), + ] + if Path(path) in paths: + return False + return True diff --git a/custom_components/hacs/helpers/functions/logger.py b/custom_components/hacs/helpers/functions/logger.py new file mode 100644 index 0000000..900bc91 --- /dev/null +++ b/custom_components/hacs/helpers/functions/logger.py @@ -0,0 +1,19 @@ +"""Custom logger for HACS.""" +# pylint: disable=invalid-name +import logging +import os + +from ...const import PACKAGE_NAME + +_HACSLogger: logging.Logger = logging.getLogger(PACKAGE_NAME) + +if "GITHUB_ACTION" in os.environ: + logging.basicConfig( + format="::%(levelname)s:: %(message)s", + level="DEBUG", + ) + + +def getLogger(_name: str = None) -> logging.Logger: + """Return a Logger instance.""" + return _HACSLogger diff --git a/custom_components/hacs/helpers/misc.py b/custom_components/hacs/helpers/functions/misc.py similarity index 70% rename from custom_components/hacs/helpers/misc.py rename to custom_components/hacs/helpers/functions/misc.py index aeb19ed..83bd56c 100644 --- a/custom_components/hacs/helpers/misc.py +++ b/custom_components/hacs/helpers/functions/misc.py @@ -1,5 +1,11 @@ """Helper functions: misc""" +import re import semantic_version +from functools import lru_cache + +RE_REPOSITORY = re.compile( + r"(?:(?:.*github.com.)|^)([A-Za-z0-9-]+\/[\w.-]+?)(?:(?:\.git)?|(?:[^\w.-].*)?)$" +) def get_repository_name(repository) -> str: @@ -21,6 +27,7 @@ def get_repository_name(repository) -> str: ) +@lru_cache(maxsize=1024) def version_left_higher_then_right(new: str, old: str) -> bool: """Return a bool if source is newer than target, will also be true if identical.""" if not isinstance(new, str) or not isinstance(old, str): @@ -28,3 +35,11 @@ def version_left_higher_then_right(new: str, old: str) -> bool: if new == old: return True return semantic_version.Version.coerce(new) > semantic_version.Version.coerce(old) + + +def extract_repository_from_url(url: str) -> str or None: + """Extract the owner/repo part form a URL.""" + match = re.match(RE_REPOSITORY, url) + if not match: + return None + return match.group(1).lower() diff --git a/custom_components/hacs/helpers/functions/path_exsist.py b/custom_components/hacs/helpers/functions/path_exsist.py new file mode 100644 index 0000000..5e941d6 --- /dev/null +++ b/custom_components/hacs/helpers/functions/path_exsist.py @@ -0,0 +1,13 @@ +# pylint: disable=missing-class-docstring,missing-module-docstring,missing-function-docstring,no-member +import os + +from custom_components.hacs.share import get_hacs + + +def path_exsist(path) -> bool: + return os.path.exists(path) + + +async def async_path_exsist(path) -> bool: + hass = get_hacs().hass + return await hass.async_add_executor_job(path_exsist, path) diff --git a/custom_components/hacs/helpers/functions/register_repository.py b/custom_components/hacs/helpers/functions/register_repository.py new file mode 100644 index 0000000..2fad499 --- /dev/null +++ b/custom_components/hacs/helpers/functions/register_repository.py @@ -0,0 +1,70 @@ +"""Register a repository.""" +from aiogithubapi import AIOGitHubAPIException + +from custom_components.hacs.helpers.classes.exceptions import ( + HacsException, + HacsExpectedException, +) +from custom_components.hacs.share import get_hacs + +from ...repositories import RERPOSITORY_CLASSES + + +# @concurrent(15, 5) +async def register_repository(full_name, category, check=True, ref=None): + """Register a repository.""" + hacs = get_hacs() + + if full_name in hacs.common.skip: + if full_name != "hacs/integration": + raise HacsExpectedException(f"Skipping {full_name}") + + if category not in RERPOSITORY_CLASSES: + raise HacsException(f"{category} is not a valid repository category.") + + repository = RERPOSITORY_CLASSES[category](full_name) + if check: + try: + await repository.async_registration(ref) + if hacs.status.new: + repository.data.new = False + if repository.validate.errors: + hacs.common.skip.append(repository.data.full_name) + if not hacs.status.startup: + hacs.log.error("Validation for %s failed.", full_name) + if hacs.system.action: + raise HacsException(f"::error:: Validation for {full_name} failed.") + return repository.validate.errors + if hacs.system.action: + repository.logger.info("%s Validation completed", repository) + else: + repository.logger.info("%s Registration completed", repository) + except AIOGitHubAPIException as exception: + hacs.common.skip.append(repository.data.full_name) + raise HacsException( + f"Validation for {full_name} failed with {exception}." + ) from None + + exists = ( + False + if str(repository.data.id) == "0" + else [x for x in hacs.repositories if str(x.data.id) == str(repository.data.id)] + ) + + if exists: + if exists[0] in hacs.repositories: + hacs.repositories.remove(exists[0]) + + else: + if hacs.hass is not None and ( + (check and repository.data.new) or hacs.status.new + ): + hacs.hass.bus.async_fire( + "hacs/repository", + { + "action": "registration", + "repository": repository.data.full_name, + "repository_id": repository.data.id, + }, + ) + hacs.repositories.append(repository) diff --git a/custom_components/hacs/helpers/functions/remaining_github_calls.py b/custom_components/hacs/helpers/functions/remaining_github_calls.py new file mode 100644 index 0000000..27a0afe --- /dev/null +++ b/custom_components/hacs/helpers/functions/remaining_github_calls.py @@ -0,0 +1,32 @@ +"""Helper to calculate the remaining calls to github.""" +import math + +from custom_components.hacs.helpers.functions.logger import getLogger + +_LOGGER = getLogger() + + +async def remaining(github): + """Helper to calculate the remaining calls to github.""" + try: + ratelimits = await github.get_rate_limit() + except (BaseException, Exception) as exception: # pylint: disable=broad-except + _LOGGER.error(exception) + return None + if ratelimits.get("remaining") is not None: + return int(ratelimits["remaining"]) + return 0 + + +async def get_fetch_updates_for(github): + """Helper to calculate the number of repositories we can fetch data for.""" + margin = 1000 + limit = await remaining(github) + pr_repo = 15 + + if limit is None: + return None + + if limit - margin <= pr_repo: + return 0 + return math.floor((limit - margin) / pr_repo) diff --git a/custom_components/hacs/helpers/functions/save.py b/custom_components/hacs/helpers/functions/save.py new file mode 100644 index 0000000..05a6e03 --- /dev/null +++ b/custom_components/hacs/helpers/functions/save.py @@ -0,0 +1,52 @@ +"""Download.""" +import gzip +import os +import shutil + +import aiofiles + +from custom_components.hacs.helpers.functions.logger import getLogger + +_LOGGER = getLogger() + + +async def async_save_file(location, content): + """Save files.""" + _LOGGER.debug("Saving %s", location) + mode = "w" + encoding = "utf-8" + errors = "ignore" + + if not isinstance(content, str): + mode = "wb" + encoding = None + errors = None + + try: + async with aiofiles.open( + location, mode=mode, encoding=encoding, errors=errors + ) as outfile: + await outfile.write(content) + outfile.close() + + # Create gz for .js files + if os.path.isfile(location): + if location.endswith(".js") or location.endswith(".css"): + with open(location, "rb") as f_in: + with gzip.open(location + ".gz", "wb") as f_out: + shutil.copyfileobj(f_in, f_out) + + # Remove with 2.0 + if "themes" in location and location.endswith(".yaml"): + filename = location.split("/")[-1] + base = location.split("/themes/")[0] + combined = f"{base}/themes/{filename}" + if os.path.exists(combined): + _LOGGER.info("Removing old theme file %s", combined) + os.remove(combined) + + except (Exception, BaseException) as error: # pylint: disable=broad-except + _LOGGER.error("Could not write data to %s - %s", location, error) + return False + + return os.path.exists(location) diff --git a/custom_components/hacs/helpers/functions/store.py b/custom_components/hacs/helpers/functions/store.py new file mode 100644 index 0000000..6ecdfa2 --- /dev/null +++ b/custom_components/hacs/helpers/functions/store.py @@ -0,0 +1,34 @@ +"""Storage handers.""" +# pylint: disable=import-outside-toplevel +from homeassistant.helpers.json import JSONEncoder + +from custom_components.hacs.const import VERSION_STORAGE + + +def get_store_for_key(hass, key): + """Create a Store object for the key.""" + key = key if "/" in key else f"hacs.{key}" + from homeassistant.helpers.storage import Store + + return Store(hass, VERSION_STORAGE, key, encoder=JSONEncoder) + + +async def async_load_from_store(hass, key): + """Load the retained data from store and return de-serialized data.""" + store = get_store_for_key(hass, key) + restored = await store.async_load() + if restored is None: + return {} + return restored + + +async def async_save_to_store(hass, key, data): + """Generate dynamic data to store and save it to the filesystem.""" + await get_store_for_key(hass, key).async_save(data) + + +async def async_remove_store(hass, key): + """Remove a store element that should no longer be used""" + if "/" not in key: + return + await get_store_for_key(hass, key).async_remove() diff --git a/custom_components/hacs/handler/template.py b/custom_components/hacs/helpers/functions/template.py similarity index 72% rename from custom_components/hacs/handler/template.py rename to custom_components/hacs/helpers/functions/template.py index ca77e7a..4f59840 100644 --- a/custom_components/hacs/handler/template.py +++ b/custom_components/hacs/helpers/functions/template.py @@ -1,29 +1,32 @@ -"""Custom template support.""" -# pylint: disable=broad-except -from jinja2 import Template -from integrationhelper import Logger - - -def render_template(content, context): - """Render templates in content.""" - # Fix None issues - if context.releases.last_release_object is not None: - prerelease = context.releases.last_release_object.prerelease - else: - prerelease = False - - # Render the template - try: - render = Template(content) - render = render.render( - installed=context.status.installed, - pending_update=context.pending_upgrade, - prerelease=prerelease, - selected_tag=context.status.selected_tag, - version_available=context.releases.last_release, - version_installed=context.display_installed_version, - ) - return render - except Exception as exception: - Logger("hacs.template").debug(exception) - return content +"""Custom template support.""" +# pylint: disable=broad-except +from jinja2 import Template + +from custom_components.hacs.helpers.functions.logger import getLogger + +_LOGGER = getLogger() + + +def render_template(content, context): + """Render templates in content.""" + # Fix None issues + if context.releases.last_release_object is not None: + prerelease = context.releases.last_release_object.prerelease + else: + prerelease = False + + # Render the template + try: + render = Template(content) + render = render.render( + installed=context.data.installed, + pending_update=context.pending_upgrade, + prerelease=prerelease, + selected_tag=context.data.selected_tag, + version_available=context.releases.last_release, + version_installed=context.display_installed_version, + ) + return render + except (Exception, BaseException) as exception: + _LOGGER.debug(exception) + return content diff --git a/custom_components/hacs/helpers/functions/validate_repository.py b/custom_components/hacs/helpers/functions/validate_repository.py new file mode 100644 index 0000000..57e4381 --- /dev/null +++ b/custom_components/hacs/helpers/functions/validate_repository.py @@ -0,0 +1,98 @@ +"""Helper to do common validation for repositories.""" +from aiogithubapi import AIOGitHubAPIException + +from custom_components.hacs.helpers.classes.exceptions import HacsException +from custom_components.hacs.helpers.functions.information import ( + get_releases, + get_repository, + get_tree, +) +from custom_components.hacs.helpers.functions.version_to_install import ( + version_to_install, +) +from custom_components.hacs.share import get_hacs, is_removed + + +async def common_validate(repository, ignore_issues=False): + """Common validation steps of the repository.""" + repository.validate.errors = [] + + # Make sure the repository exist. + repository.logger.debug("%s Checking repository.", repository) + await common_update_data(repository, ignore_issues) + + # Step 6: Get the content of hacs.json + await repository.get_repository_manifest_content() + + +async def common_update_data(repository, ignore_issues=False): + """Common update data.""" + hacs = get_hacs() + releases = [] + try: + repository_object = await get_repository( + hacs.session, hacs.configuration.token, repository.data.full_name + ) + repository.repository_object = repository_object + repository.data.update_data(repository_object.attributes) + except (AIOGitHubAPIException, HacsException) as exception: + if not hacs.status.startup: + repository.logger.error("%s %s", repository, exception) + if not ignore_issues: + repository.validate.errors.append("Repository does not exist.") + raise HacsException(exception) from None + + # Make sure the repository is not archived. + if repository.data.archived and not ignore_issues: + repository.validate.errors.append("Repository is archived.") + raise HacsException("Repository is archived.") + + # Make sure the repository is not in the blacklist. + if is_removed(repository.data.full_name) and not ignore_issues: + repository.validate.errors.append("Repository is in the blacklist.") + raise HacsException("Repository is in the blacklist.") + + # Get releases. + try: + releases = await get_releases( + repository.repository_object, + repository.data.show_beta, + hacs.configuration.release_limit, + ) + if releases: + repository.data.releases = True + repository.releases.objects = [x for x in releases if not x.draft] + repository.data.published_tags = [ + x.tag_name for x in repository.releases.objects + ] + repository.data.last_version = next(iter(repository.data.published_tags)) + + except (AIOGitHubAPIException, HacsException): + repository.data.releases = False + + if not repository.force_branch: + repository.ref = version_to_install(repository) + if repository.data.releases: + for release in repository.releases.objects or []: + if release.tag_name == repository.ref: + assets = release.assets + if assets: + downloads = next(iter(assets)).attributes.get("download_count") + repository.data.downloads = downloads + + repository.logger.debug( + "%s Running checks against %s", repository, repository.ref.replace("tags/", "") + ) + + try: + repository.tree = await get_tree(repository.repository_object, repository.ref) + if not repository.tree: + raise HacsException("No files in tree") + repository.treefiles = [] + for treefile in repository.tree: + repository.treefiles.append(treefile.full_path) + except (AIOGitHubAPIException, HacsException) as exception: + if not hacs.status.startup: + repository.logger.error("%s %s", repository, exception) + if not ignore_issues: + raise HacsException(exception) from None diff --git a/custom_components/hacs/helpers/functions/version_to_install.py b/custom_components/hacs/helpers/functions/version_to_install.py new file mode 100644 index 0000000..75dd4ba --- /dev/null +++ b/custom_components/hacs/helpers/functions/version_to_install.py @@ -0,0 +1,20 @@ +"""Install helper for repositories.""" + + +def version_to_install(repository): + """Determine which version to isntall.""" + if repository.data.last_version is not None: + if repository.data.selected_tag is not None: + if repository.data.selected_tag == repository.data.last_version: + repository.data.selected_tag = None + return repository.data.last_version + return repository.data.selected_tag + return repository.data.last_version + if repository.data.selected_tag is not None: + if repository.data.selected_tag == repository.data.default_branch: + return repository.data.default_branch + if repository.data.selected_tag in repository.data.published_tags: + return repository.data.selected_tag + if repository.data.default_branch is None: + return "main" + return repository.data.default_branch diff --git a/custom_components/hacs/helpers/get_defaults.py b/custom_components/hacs/helpers/get_defaults.py deleted file mode 100644 index 744540d..0000000 --- a/custom_components/hacs/helpers/get_defaults.py +++ /dev/null @@ -1,44 +0,0 @@ -"""Helpers to get default repositories.""" -import json -from aiogithubapi import AIOGitHub, AIOGitHubException -from integrationhelper import Logger -from custom_components.hacs.helpers.information import get_repository - - -async def get_default_repos_orgs(github: type(AIOGitHub), category: str) -> dict: - """Gets default org repositories.""" - repositories = [] - logger = Logger("hacs") - orgs = { - "plugin": "custom-cards", - "integration": "custom-components", - "theme": "home-assistant-community-themes", - } - if category not in orgs: - return repositories - - try: - repos = await github.get_org_repos(orgs[category]) - for repo in repos: - repositories.append(repo.full_name) - - except AIOGitHubException as exception: - logger.error(exception) - - return repositories - - -async def get_default_repos_lists(session, token, default: str) -> dict: - """Gets repositories from default list.""" - repositories = [] - logger = Logger("hacs") - - try: - repo = await get_repository(session, token, "hacs/default") - content = await repo.get_contents(default) - repositories = json.loads(content.content) - - except AIOGitHubException as exception: - logger.error(exception) - - return repositories diff --git a/custom_components/hacs/helpers/install.py b/custom_components/hacs/helpers/install.py deleted file mode 100644 index 7eab265..0000000 --- a/custom_components/hacs/helpers/install.py +++ /dev/null @@ -1,128 +0,0 @@ -"""Install helper for repositories.""" -import os -import tempfile -from custom_components.hacs.globals import get_hacs -from custom_components.hacs.hacsbase.exceptions import HacsException -from custom_components.hacs.hacsbase.backup import Backup, BackupNetDaemon -from custom_components.hacs.helpers.download import download_content - - -async def install_repository(repository): - """Common installation steps of the repository.""" - persistent_directory = None - await repository.update_repository() - repository.validate.errors = [] - - if not repository.can_install: - raise HacsException( - "The version of Home Assistant is not compatible with this version" - ) - - version = version_to_install(repository) - if version == repository.data.default_branch: - repository.ref = version - else: - repository.ref = f"tags/{version}" - - if repository.status.installed and repository.data.category == "netdaemon": - persistent_directory = BackupNetDaemon(repository) - persistent_directory.create() - - elif repository.data.persistent_directory: - if os.path.exists( - f"{repository.content.path.local}/{repository.data.persistent_directory}" - ): - persistent_directory = Backup( - f"{repository.content.path.local}/{repository.data.persistent_directory}", - tempfile.gettempdir() + "/hacs_persistent_directory/", - ) - persistent_directory.create() - - if repository.status.installed and not repository.content.single: - backup = Backup(repository.content.path.local) - backup.create() - - if repository.data.zip_release and version != repository.data.default_branch: - await repository.download_zip(repository) - else: - await download_content(repository) - - if repository.validate.errors: - for error in repository.validate.errors: - repository.logger.error(error) - if repository.status.installed and not repository.content.single: - backup.restore() - - if repository.status.installed and not repository.content.single: - backup.cleanup() - - if persistent_directory is not None: - persistent_directory.restore() - persistent_directory.cleanup() - - if repository.validate.success: - if repository.data.full_name not in repository.hacs.common.installed: - if repository.data.full_name == "hacs/integration": - repository.hacs.common.installed.append(repository.data.full_name) - repository.status.installed = True - repository.versions.installed_commit = repository.versions.available_commit - - if version == repository.data.default_branch: - repository.versions.installed = None - else: - repository.versions.installed = version - - await reload_after_install(repository) - installation_complete(repository) - - -async def reload_after_install(repository): - """Reload action after installation success.""" - if repository.data.category == "integration": - if repository.config_flow: - if repository.data.full_name != "hacs/integration": - await repository.reload_custom_components() - repository.pending_restart = True - - elif repository.data.category == "theme": - try: - await repository.hacs.hass.services.async_call( - "frontend", "reload_themes", {} - ) - except Exception: # pylint: disable=broad-except - pass - elif repository.data.category == "netdaemon": - try: - await repository.hacs.hass.services.async_call( - "hassio", "addon_restart", {"addon": "e466aeb3_netdaemon"} - ) - except Exception: # pylint: disable=broad-except - pass - - -def installation_complete(repository): - """Action to run when the installation is complete.""" - hacs = get_hacs() - hacs.hass.bus.async_fire( - "hacs/repository", - {"id": 1337, "action": "install", "repository": repository.data.full_name}, - ) - - -def version_to_install(repository): - """Determine which version to isntall.""" - if repository.versions.available is not None: - if repository.status.selected_tag is not None: - if repository.status.selected_tag == repository.versions.available: - repository.status.selected_tag = None - return repository.versions.available - return repository.status.selected_tag - return repository.versions.available - if repository.status.selected_tag is not None: - if repository.status.selected_tag == repository.data.default_branch: - return repository.data.default_branch - if repository.status.selected_tag in repository.releases.published_tags: - return repository.status.selected_tag - if repository.data.default_branch is None: - return "master" - return repository.data.default_branch diff --git a/custom_components/hacs/helpers/methods/__init__.py b/custom_components/hacs/helpers/methods/__init__.py new file mode 100644 index 0000000..26d9422 --- /dev/null +++ b/custom_components/hacs/helpers/methods/__init__.py @@ -0,0 +1,30 @@ +# pylint: disable=missing-class-docstring,missing-module-docstring,missing-function-docstring,no-member +from custom_components.hacs.helpers.methods.installation import ( + RepositoryMethodInstall, + RepositoryMethodPostInstall, + RepositoryMethodPreInstall, +) +from custom_components.hacs.helpers.methods.registration import ( + RepositoryMethodPostRegistration, + RepositoryMethodPreRegistration, + RepositoryMethodRegistration, +) +from custom_components.hacs.helpers.methods.reinstall_if_needed import ( + RepositoryMethodReinstallIfNeeded, +) + + +class RepositoryHelperMethods( + RepositoryMethodReinstallIfNeeded, + RepositoryMethodInstall, + RepositoryMethodPostInstall, + RepositoryMethodPreInstall, + RepositoryMethodPreRegistration, + RepositoryMethodRegistration, + RepositoryMethodPostRegistration, +): + """Collection of repository methods that are nested to all repositories.""" + + +class HacsHelperMethods: + """Helper class for HACS methods""" diff --git a/custom_components/hacs/helpers/methods/__pycache__/__init__.cpython-38.pyc b/custom_components/hacs/helpers/methods/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..586b3b7 Binary files /dev/null and b/custom_components/hacs/helpers/methods/__pycache__/__init__.cpython-38.pyc differ diff --git a/custom_components/hacs/helpers/methods/__pycache__/installation.cpython-38.pyc b/custom_components/hacs/helpers/methods/__pycache__/installation.cpython-38.pyc new file mode 100644 index 0000000..0845fdd Binary files /dev/null and b/custom_components/hacs/helpers/methods/__pycache__/installation.cpython-38.pyc differ diff --git a/custom_components/hacs/helpers/methods/__pycache__/registration.cpython-38.pyc b/custom_components/hacs/helpers/methods/__pycache__/registration.cpython-38.pyc new file mode 100644 index 0000000..4ab53be Binary files /dev/null and b/custom_components/hacs/helpers/methods/__pycache__/registration.cpython-38.pyc differ diff --git a/custom_components/hacs/helpers/methods/__pycache__/reinstall_if_needed.cpython-38.pyc b/custom_components/hacs/helpers/methods/__pycache__/reinstall_if_needed.cpython-38.pyc new file mode 100644 index 0000000..54952f9 Binary files /dev/null and b/custom_components/hacs/helpers/methods/__pycache__/reinstall_if_needed.cpython-38.pyc differ diff --git a/custom_components/hacs/helpers/methods/installation.py b/custom_components/hacs/helpers/methods/installation.py new file mode 100644 index 0000000..81354dd --- /dev/null +++ b/custom_components/hacs/helpers/methods/installation.py @@ -0,0 +1,113 @@ +# pylint: disable=missing-class-docstring,missing-module-docstring,missing-function-docstring,no-member +import os +import tempfile +from abc import ABC + +from custom_components.hacs.helpers.classes.exceptions import HacsException +from custom_components.hacs.helpers.functions.download import download_content +from custom_components.hacs.helpers.functions.version_to_install import ( + version_to_install, +) +from custom_components.hacs.operational.backup import Backup, BackupNetDaemon + + +class RepositoryMethodPreInstall(ABC): + async def async_pre_install(self) -> None: + pass + + async def _async_pre_install(self) -> None: + self.logger.info("Running pre installation steps") + await self.async_pre_install() + self.logger.info("Pre installation steps completed") + + +class RepositoryMethodInstall(ABC): + async def async_install(self) -> None: + await self._async_pre_install() + self.logger.info("Running installation steps") + await async_install_repository(self) + self.logger.info("Installation steps completed") + await self._async_post_install() + + +class RepositoryMethodPostInstall(ABC): + async def async_post_installation(self) -> None: + pass + + async def _async_post_install(self) -> None: + self.logger.info("Running post installation steps") + await self.async_post_installation() + self.data.new = False + self.hacs.hass.bus.async_fire( + "hacs/repository", + {"id": 1337, "action": "install", "repository": self.data.full_name}, + ) + self.logger.info("Post installation steps completed") + + +async def async_install_repository(repository): + """Common installation steps of the repository.""" + persistent_directory = None + await repository.update_repository() + if repository.content.path.local is None: + raise HacsException("repository.content.path.local is None") + repository.validate.errors = [] + + if not repository.can_install: + raise HacsException( + "The version of Home Assistant is not compatible with this version" + ) + + version = version_to_install(repository) + if version == repository.data.default_branch: + repository.ref = version + else: + repository.ref = f"tags/{version}" + + if repository.data.installed and repository.data.category == "netdaemon": + persistent_directory = BackupNetDaemon(repository) + persistent_directory.create() + + elif repository.data.persistent_directory: + if os.path.exists( + f"{repository.content.path.local}/{repository.data.persistent_directory}" + ): + persistent_directory = Backup( + f"{repository.content.path.local}/{repository.data.persistent_directory}", + tempfile.gettempdir() + "/hacs_persistent_directory/", + ) + persistent_directory.create() + + if repository.data.installed and not repository.content.single: + backup = Backup(repository.content.path.local) + backup.create() + + if repository.data.zip_release and version != repository.data.default_branch: + await repository.download_zip_files(repository) + else: + await download_content(repository) + + if repository.validate.errors: + for error in repository.validate.errors: + repository.logger.error(error) + if repository.data.installed and not repository.content.single: + backup.restore() + + if repository.data.installed and not repository.content.single: + backup.cleanup() + + if persistent_directory is not None: + persistent_directory.restore() + persistent_directory.cleanup() + + if repository.validate.success: + if repository.data.full_name not in repository.hacs.common.installed: + if repository.data.full_name == "hacs/integration": + repository.hacs.common.installed.append(repository.data.full_name) + repository.data.installed = True + repository.data.installed_commit = repository.data.last_commit + + if version == repository.data.default_branch: + repository.data.installed_version = None + else: + repository.data.installed_version = version diff --git a/custom_components/hacs/helpers/methods/registration.py b/custom_components/hacs/helpers/methods/registration.py new file mode 100644 index 0000000..d6f2288 --- /dev/null +++ b/custom_components/hacs/helpers/methods/registration.py @@ -0,0 +1,43 @@ +# pylint: disable=missing-class-docstring,missing-module-docstring,missing-function-docstring,no-member, attribute-defined-outside-init +from abc import ABC + +from custom_components.hacs.validate import async_run_repository_checks + + +class RepositoryMethodPreRegistration(ABC): + async def async_pre_registration(self): + pass + + +class RepositoryMethodRegistration(ABC): + async def registration(self, ref=None) -> None: + self.logger.warning( + "'registration' is deprecated, use 'async_registration' instead" + ) + await self.async_registration(ref) + + async def async_registration(self, ref=None) -> None: + # Run local pre registration steps. + await self.async_pre_registration() + + if ref is not None: + self.data.selected_tag = ref + self.ref = ref + self.force_branch = True + + if not await self.validate_repository(): + return False + + # Run common registration steps. + await self.common_registration() + + # Set correct local path + self.content.path.local = self.localpath + + # Run local post registration steps. + await self.async_post_registration() + + +class RepositoryMethodPostRegistration(ABC): + async def async_post_registration(self): + await async_run_repository_checks(self) diff --git a/custom_components/hacs/helpers/methods/reinstall_if_needed.py b/custom_components/hacs/helpers/methods/reinstall_if_needed.py new file mode 100644 index 0000000..a83353b --- /dev/null +++ b/custom_components/hacs/helpers/methods/reinstall_if_needed.py @@ -0,0 +1,12 @@ +# pylint: disable=missing-class-docstring,missing-module-docstring,missing-function-docstring,no-member +from abc import ABC + +from custom_components.hacs.helpers.functions.path_exsist import async_path_exsist + + +class RepositoryMethodReinstallIfNeeded(ABC): + async def async_reinstall_if_needed(self) -> None: + if self.data.installed: + if not await async_path_exsist(self.content.path.local): + self.logger.error("Missing from local FS, should be reinstalled.") + # await self.async_install() diff --git a/custom_components/hacs/helpers/network.py b/custom_components/hacs/helpers/network.py deleted file mode 100644 index acc0a1f..0000000 --- a/custom_components/hacs/helpers/network.py +++ /dev/null @@ -1,8 +0,0 @@ -"""Verify network.""" -from socket import gaierror -from integrationhelper import Logger - - -def internet_connectivity_check(host="api.github.com"): - """Verify network connectivity.""" - return True diff --git a/custom_components/hacs/helpers/properties/__init__.py b/custom_components/hacs/helpers/properties/__init__.py new file mode 100644 index 0000000..4267caf --- /dev/null +++ b/custom_components/hacs/helpers/properties/__init__.py @@ -0,0 +1,16 @@ +# pylint: disable=missing-class-docstring,missing-module-docstring,missing-function-docstring,no-member +from custom_components.hacs.helpers.properties.can_be_installed import ( + RepositoryPropertyCanBeInstalled, +) +from custom_components.hacs.helpers.properties.custom import RepositoryPropertyCustom +from custom_components.hacs.helpers.properties.pending_update import ( + RepositoryPropertyPendingUpdate, +) + + +class RepositoryHelperProperties( + RepositoryPropertyPendingUpdate, + RepositoryPropertyCustom, + RepositoryPropertyCanBeInstalled, +): + pass diff --git a/custom_components/hacs/helpers/properties/__pycache__/__init__.cpython-38.pyc b/custom_components/hacs/helpers/properties/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..85d0ba2 Binary files /dev/null and b/custom_components/hacs/helpers/properties/__pycache__/__init__.cpython-38.pyc differ diff --git a/custom_components/hacs/helpers/properties/__pycache__/can_be_installed.cpython-38.pyc b/custom_components/hacs/helpers/properties/__pycache__/can_be_installed.cpython-38.pyc new file mode 100644 index 0000000..91812af Binary files /dev/null and b/custom_components/hacs/helpers/properties/__pycache__/can_be_installed.cpython-38.pyc differ diff --git a/custom_components/hacs/helpers/properties/__pycache__/custom.cpython-38.pyc b/custom_components/hacs/helpers/properties/__pycache__/custom.cpython-38.pyc new file mode 100644 index 0000000..8db3846 Binary files /dev/null and b/custom_components/hacs/helpers/properties/__pycache__/custom.cpython-38.pyc differ diff --git a/custom_components/hacs/helpers/properties/__pycache__/pending_update.cpython-38.pyc b/custom_components/hacs/helpers/properties/__pycache__/pending_update.cpython-38.pyc new file mode 100644 index 0000000..c601c75 Binary files /dev/null and b/custom_components/hacs/helpers/properties/__pycache__/pending_update.cpython-38.pyc differ diff --git a/custom_components/hacs/helpers/properties/can_be_installed.py b/custom_components/hacs/helpers/properties/can_be_installed.py new file mode 100644 index 0000000..df6b4d6 --- /dev/null +++ b/custom_components/hacs/helpers/properties/can_be_installed.py @@ -0,0 +1,21 @@ +# pylint: disable=missing-class-docstring,missing-module-docstring,missing-function-docstring,no-member +from abc import ABC + +from custom_components.hacs.helpers.functions.misc import version_left_higher_then_right + + +class RepositoryPropertyCanBeInstalled(ABC): + @property + def can_be_installed(self) -> bool: + if self.data.homeassistant is not None: + if self.data.releases: + if not version_left_higher_then_right( + self.hacs.system.ha_version, self.data.homeassistant + ): + return False + return True + + @property + def can_install(self): + """kept for legacy compatibility""" + return self.can_be_installed diff --git a/custom_components/hacs/helpers/properties/custom.py b/custom_components/hacs/helpers/properties/custom.py new file mode 100644 index 0000000..3cb0486 --- /dev/null +++ b/custom_components/hacs/helpers/properties/custom.py @@ -0,0 +1,13 @@ +# pylint: disable=missing-class-docstring,missing-module-docstring,missing-function-docstring,no-member +from abc import ABC + + +class RepositoryPropertyCustom(ABC): + @property + def custom(self): + """Return flag if the repository is custom.""" + if str(self.data.id) in self.hacs.common.default: + return False + if self.data.full_name == "hacs/integration": + return False + return True diff --git a/custom_components/hacs/helpers/properties/pending_update.py b/custom_components/hacs/helpers/properties/pending_update.py new file mode 100644 index 0000000..083930e --- /dev/null +++ b/custom_components/hacs/helpers/properties/pending_update.py @@ -0,0 +1,23 @@ +# pylint: disable=missing-class-docstring,missing-module-docstring,missing-function-docstring,no-member +from abc import ABC + + +class RepositoryPropertyPendingUpdate(ABC): + @property + def pending_update(self) -> bool: + if not self.can_install: + return False + if self.data.installed: + if self.data.selected_tag is not None: + if self.data.selected_tag == self.data.default_branch: + if self.data.installed_commit != self.data.last_commit: + return True + return False + if self.display_installed_version != self.display_available_version: + return True + return False + + @property + def pending_upgrade(self) -> bool: + """kept for legacy compatibility""" + return self.pending_update diff --git a/custom_components/hacs/helpers/register_repository.py b/custom_components/hacs/helpers/register_repository.py deleted file mode 100644 index d7c556f..0000000 --- a/custom_components/hacs/helpers/register_repository.py +++ /dev/null @@ -1,49 +0,0 @@ -"""Register a repository.""" -from aiogithubapi import AIOGitHubException -from custom_components.hacs.globals import get_hacs -from custom_components.hacs.hacsbase.exceptions import ( - HacsException, - HacsExpectedException, -) - - -async def register_repository(full_name, category, check=True): - """Register a repository.""" - hacs = get_hacs() - from custom_components.hacs.repositories import ( - RERPOSITORY_CLASSES, - ) # To hanle import error - - if full_name in hacs.common.skip: - if full_name != "hacs/integration": - raise HacsExpectedException(f"Skipping {full_name}") - - if category not in RERPOSITORY_CLASSES: - raise HacsException(f"{category} is not a valid repository category.") - - repository = RERPOSITORY_CLASSES[category](full_name) - if check: - try: - await repository.registration() - if hacs.system.status.new: - repository.status.new = False - if repository.validate.errors: - hacs.common.skip.append(repository.data.full_name) - if not hacs.system.status.startup: - hacs.logger.error(f"Validation for {full_name} failed.") - return repository.validate.errors - repository.logger.info("Registration complete") - except AIOGitHubException as exception: - hacs.common.skip.append(repository.data.full_name) - raise HacsException(f"Validation for {full_name} failed with {exception}.") - - hacs.hass.bus.async_fire( - "hacs/repository", - { - "id": 1337, - "action": "registration", - "repository": repository.data.full_name, - "repository_id": repository.information.uid, - }, - ) - hacs.repositories.append(repository) diff --git a/custom_components/hacs/helpers/validate_repository.py b/custom_components/hacs/helpers/validate_repository.py deleted file mode 100644 index f20adbf..0000000 --- a/custom_components/hacs/helpers/validate_repository.py +++ /dev/null @@ -1,90 +0,0 @@ -"""Helper to do common validation for repositories.""" -from aiogithubapi import AIOGitHubException -from custom_components.hacs.globals import get_hacs, is_removed -from custom_components.hacs.hacsbase.exceptions import HacsException -from custom_components.hacs.helpers.install import version_to_install -from custom_components.hacs.helpers.information import ( - get_repository, - get_tree, - get_releases, -) - - -async def common_validate(repository): - """Common validation steps of the repository.""" - repository.validate.errors = [] - - # Make sure the repository exist. - repository.logger.debug("Checking repository.") - await common_update_data(repository) - - # Step 6: Get the content of hacs.json - await repository.get_repository_manifest_content() - - -async def common_update_data(repository): - """Common update data.""" - hacs = get_hacs() - try: - repository_object = await get_repository( - hacs.session, hacs.configuration.token, repository.data.full_name - ) - repository.repository_object = repository_object - repository.data.update_data(repository_object.attributes) - except (AIOGitHubException, HacsException) as exception: - if not hacs.system.status.startup: - repository.logger.error(exception) - repository.validate.errors.append("Repository does not exist.") - raise HacsException(exception) - - # Make sure the repository is not archived. - if repository.data.archived: - repository.validate.errors.append("Repository is archived.") - raise HacsException("Repository is archived.") - - # Make sure the repository is not in the blacklist. - if is_removed(repository.data.full_name): - repository.validate.errors.append("Repository is in the blacklist.") - raise HacsException("Repository is in the blacklist.") - - # Get releases. - try: - releases = await get_releases( - repository.repository_object, - repository.status.show_beta, - hacs.configuration.release_limit, - ) - if releases: - repository.releases.releases = True - repository.releases.objects = releases - repository.releases.published_tags = [ - x.tag_name for x in releases if not x.draft - ] - repository.versions.available = next(iter(releases)).tag_name - for release in releases: - if release.tag_name == repository.ref: - assets = release.assets - if assets: - downloads = next(iter(assets)).attributes.get("download_count") - repository.releases.downloads = downloads - - except (AIOGitHubException, HacsException): - repository.releases.releases = False - - repository.ref = version_to_install(repository) - - repository.logger.debug( - f"Running checks against {repository.ref.replace('tags/', '')}" - ) - - try: - repository.tree = await get_tree(repository.repository_object, repository.ref) - if not repository.tree: - raise HacsException("No files in tree") - repository.treefiles = [] - for treefile in repository.tree: - repository.treefiles.append(treefile.full_path) - except (AIOGitHubException, HacsException) as exception: - if not hacs.system.status.startup: - repository.logger.error(exception) - raise HacsException(exception) diff --git a/custom_components/hacs/http.py b/custom_components/hacs/http.py deleted file mode 100644 index cab3fb1..0000000 --- a/custom_components/hacs/http.py +++ /dev/null @@ -1,83 +0,0 @@ -"""HACS http endpoints.""" -import os -from integrationhelper import Logger -from homeassistant.components.http import HomeAssistantView -from aiohttp import web -from hacs_frontend import locate_gz, locate_debug_gz - -from custom_components.hacs.globals import get_hacs - - -class HacsFrontend(HomeAssistantView): - """Base View Class for HACS.""" - - requires_auth = False - name = "hacs_files" - url = r"/hacsfiles/{requested_file:.+}" - - async def get(self, request, requested_file): # pylint: disable=unused-argument - """Handle HACS Web requests.""" - return await get_file_response(requested_file) - - -class HacsPluginViewLegacy(HacsFrontend): - """Alias for legacy, remove with 1.0""" - - name = "community_plugin" - url = r"/community_plugin/{requested_file:.+}" - - async def get(self, request, requested_file): # pylint: disable=unused-argument - """DEPRECATED.""" - hacs = get_hacs() - if hacs.system.ha_version.split(".")[1] >= "107": - logger = Logger("hacs.deprecated") - logger.warning( - "The '/community_plugin/*' is deprecated and will be removed in an upcomming version of HACS, it has been replaced by '/hacsfiles/*', if you use the UI to manage your lovelace configuration, you can update this by going to the settings tab in HACS, if you use YAML to manage your lovelace configuration, you manually need to replace the URL in your resources." - ) - - return await get_file_response(requested_file) - - -async def get_file_response(requested_file): - """Get file.""" - hacs = get_hacs() - - if requested_file.startswith("frontend-"): - if hacs.configuration.debug: - servefile = await hacs.hass.async_add_executor_job(locate_debug_gz) - hacs.logger.debug("Serving DEBUG frontend") - else: - servefile = await hacs.hass.async_add_executor_job(locate_gz) - - if os.path.exists(servefile): - return web.FileResponse(servefile) - elif requested_file == "iconset.js": - return web.FileResponse( - f"{hacs.system.config_path}/custom_components/hacs/iconset.js" - ) - - try: - if requested_file.startswith("themes"): - file = f"{hacs.system.config_path}/{requested_file}" - else: - file = f"{hacs.system.config_path}/www/community/{requested_file}" - - # Serve .gz if it exist - if os.path.exists(file + ".gz"): - file += ".gz" - - if os.path.exists(file): - hacs.logger.debug("Serving {} from {}".format(requested_file, file)) - response = web.FileResponse(file) - response.headers["Cache-Control"] = "no-store, max-age=0" - response.headers["Pragma"] = "no-store" - return response - else: - hacs.logger.error(f"Tried to serve up '{file}' but it does not exist") - - except Exception as error: # pylint: disable=broad-except - hacs.logger.debug( - "there was an issue trying to serve {} - {}".format(requested_file, error) - ) - - return web.Response(status=404) diff --git a/custom_components/hacs/iconset.js b/custom_components/hacs/iconset.js deleted file mode 100644 index ab225c7..0000000 --- a/custom_components/hacs/iconset.js +++ /dev/null @@ -1,34 +0,0 @@ -const iconset = document.createElement("ha-iconset-svg"); -iconset.name = "hacs"; -iconset.size = "1024"; -iconset.innerHTML = ` - - - - - - - - - - - - - - - -` -document.body.appendChild(iconset); \ No newline at end of file diff --git a/custom_components/hacs/manifest.json b/custom_components/hacs/manifest.json index 95c49e0..700ed1f 100644 --- a/custom_components/hacs/manifest.json +++ b/custom_components/hacs/manifest.json @@ -1,24 +1,25 @@ { - "codeowners": [ - "@ludeeus" - ], - "config_flow": true, - "dependencies": [ - "websocket_api", - "frontend", - "persistent_notification", - "lovelace" - ], - "documentation": "https://hacs.xyz/docs/configuration/start", - "domain": "hacs", - "issues": "https://hacs.xyz/docs/issues", - "name": "HACS (Home Assistant Community Store)", - "requirements": [ - "aiofiles==0.4.0", - "aiogithubapi==0.5.0", - "backoff==1.10.0", - "hacs_frontend==20200309184730", - "integrationhelper==0.2.2", - "semantic_version==2.8.4" - ] + "codeowners": [ + "@ludeeus" + ], + "config_flow": true, + "dependencies": [ + "http", + "websocket_api", + "frontend", + "persistent_notification", + "lovelace" + ], + "documentation": "https://hacs.xyz/docs/configuration/start", + "issue_tracker": "https://github.com/hacs/integration/issues", + "domain": "hacs", + "name": "HACS", + "requirements": [ + "aiofiles>=0.5.0", + "aiogithubapi>=2.0.0<3.0.0", + "backoff>=1.10.0", + "hacs_frontend==202012051441", + "semantic_version>=2.8.5", + "queueman==0.5" + ] } \ No newline at end of file diff --git a/custom_components/hacs/models/__init__.py b/custom_components/hacs/models/__init__.py new file mode 100644 index 0000000..3fb384a --- /dev/null +++ b/custom_components/hacs/models/__init__.py @@ -0,0 +1 @@ +"""Hacs models.""" diff --git a/custom_components/hacs/models/__pycache__/__init__.cpython-38.pyc b/custom_components/hacs/models/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..f31a36a Binary files /dev/null and b/custom_components/hacs/models/__pycache__/__init__.cpython-38.pyc differ diff --git a/custom_components/hacs/models/__pycache__/core.cpython-38.pyc b/custom_components/hacs/models/__pycache__/core.cpython-38.pyc new file mode 100644 index 0000000..a184e32 Binary files /dev/null and b/custom_components/hacs/models/__pycache__/core.cpython-38.pyc differ diff --git a/custom_components/hacs/models/__pycache__/frontend.cpython-38.pyc b/custom_components/hacs/models/__pycache__/frontend.cpython-38.pyc new file mode 100644 index 0000000..e9488f2 Binary files /dev/null and b/custom_components/hacs/models/__pycache__/frontend.cpython-38.pyc differ diff --git a/custom_components/hacs/models/__pycache__/system.cpython-38.pyc b/custom_components/hacs/models/__pycache__/system.cpython-38.pyc new file mode 100644 index 0000000..deaccfe Binary files /dev/null and b/custom_components/hacs/models/__pycache__/system.cpython-38.pyc differ diff --git a/custom_components/hacs/models/core.py b/custom_components/hacs/models/core.py new file mode 100644 index 0000000..5b72b9f --- /dev/null +++ b/custom_components/hacs/models/core.py @@ -0,0 +1,14 @@ +"""HACS Core info.""" +from pathlib import Path +import attr + +from ..enums import LovelaceMode + + +@attr.s +class HacsCore: + """HACS Core info.""" + + config_path = attr.ib(Path) + ha_version = attr.ib(str) + lovelace_mode = LovelaceMode("storage") diff --git a/custom_components/hacs/models/frontend.py b/custom_components/hacs/models/frontend.py new file mode 100644 index 0000000..1b9d875 --- /dev/null +++ b/custom_components/hacs/models/frontend.py @@ -0,0 +1,10 @@ +"""HacsFrontend.""" + + +class HacsFrontend: + """HacsFrontend.""" + + version_running: bool = None + version_available: bool = None + version_expected: bool = None + update_pending: bool = False diff --git a/custom_components/hacs/models/system.py b/custom_components/hacs/models/system.py new file mode 100644 index 0000000..c92023d --- /dev/null +++ b/custom_components/hacs/models/system.py @@ -0,0 +1,15 @@ +"""HACS System info.""" +import attr +from ..enums import HacsStage +from ..const import VERSION + + +@attr.s +class HacsSystem: + """HACS System info.""" + + disabled: bool = False + running: bool = False + version: str = VERSION + stage: HacsStage = attr.ib(HacsStage) + action: bool = False diff --git a/custom_components/hacs/operational/__init__.py b/custom_components/hacs/operational/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/custom_components/hacs/operational/__pycache__/__init__.cpython-38.pyc b/custom_components/hacs/operational/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..0009e49 Binary files /dev/null and b/custom_components/hacs/operational/__pycache__/__init__.cpython-38.pyc differ diff --git a/custom_components/hacs/operational/__pycache__/backup.cpython-38.pyc b/custom_components/hacs/operational/__pycache__/backup.cpython-38.pyc new file mode 100644 index 0000000..ec2ae01 Binary files /dev/null and b/custom_components/hacs/operational/__pycache__/backup.cpython-38.pyc differ diff --git a/custom_components/hacs/operational/__pycache__/factory.cpython-38.pyc b/custom_components/hacs/operational/__pycache__/factory.cpython-38.pyc new file mode 100644 index 0000000..b96d98e Binary files /dev/null and b/custom_components/hacs/operational/__pycache__/factory.cpython-38.pyc differ diff --git a/custom_components/hacs/operational/__pycache__/reload.cpython-38.pyc b/custom_components/hacs/operational/__pycache__/reload.cpython-38.pyc new file mode 100644 index 0000000..bd7b979 Binary files /dev/null and b/custom_components/hacs/operational/__pycache__/reload.cpython-38.pyc differ diff --git a/custom_components/hacs/operational/__pycache__/remove.cpython-38.pyc b/custom_components/hacs/operational/__pycache__/remove.cpython-38.pyc new file mode 100644 index 0000000..033ef3c Binary files /dev/null and b/custom_components/hacs/operational/__pycache__/remove.cpython-38.pyc differ diff --git a/custom_components/hacs/operational/__pycache__/setup.cpython-38.pyc b/custom_components/hacs/operational/__pycache__/setup.cpython-38.pyc new file mode 100644 index 0000000..55c8a10 Binary files /dev/null and b/custom_components/hacs/operational/__pycache__/setup.cpython-38.pyc differ diff --git a/custom_components/hacs/hacsbase/backup.py b/custom_components/hacs/operational/backup.py similarity index 81% rename from custom_components/hacs/hacsbase/backup.py rename to custom_components/hacs/operational/backup.py index acd20c1..f0e8bb9 100644 --- a/custom_components/hacs/hacsbase/backup.py +++ b/custom_components/hacs/operational/backup.py @@ -1,117 +1,124 @@ -"""Backup.""" -import os -import shutil -import tempfile -from time import sleep - -from integrationhelper import Logger - -BACKUP_PATH = tempfile.gettempdir() + "/hacs_backup/" - - -class Backup: - """Backup.""" - - def __init__(self, local_path, backup_path=BACKUP_PATH): - """initialize.""" - self.logger = Logger("hacs.backup") - self.local_path = local_path - self.backup_path = backup_path - self.backup_path_full = f"{self.backup_path}{self.local_path.split('/')[-1]}" - - def create(self): - """Create a backup in /tmp""" - if not os.path.exists(self.local_path): - return - if os.path.exists(self.backup_path): - shutil.rmtree(self.backup_path) - while os.path.exists(self.backup_path): - sleep(0.1) - os.makedirs(self.backup_path, exist_ok=True) - - try: - if os.path.isfile(self.local_path): - shutil.copyfile(self.local_path, self.backup_path_full) - os.remove(self.local_path) - else: - shutil.copytree(self.local_path, self.backup_path_full) - shutil.rmtree(self.local_path) - while os.path.exists(self.local_path): - sleep(0.1) - self.logger.debug( - f"Backup for {self.local_path}, created in {self.backup_path_full}" - ) - except Exception: # pylint: disable=broad-except - pass - - def restore(self): - """Restore from backup.""" - if not os.path.exists(self.backup_path_full): - return - - if os.path.isfile(self.backup_path_full): - if os.path.exists(self.local_path): - os.remove(self.local_path) - shutil.copyfile(self.backup_path_full, self.local_path) - else: - if os.path.exists(self.local_path): - shutil.rmtree(self.local_path) - while os.path.exists(self.local_path): - sleep(0.1) - shutil.copytree(self.backup_path_full, self.local_path) - self.logger.debug( - f"Restored {self.local_path}, from backup {self.backup_path_full}" - ) - - def cleanup(self): - """Cleanup backup files.""" - if os.path.exists(self.backup_path): - shutil.rmtree(self.backup_path) - while os.path.exists(self.backup_path): - sleep(0.1) - self.logger.debug(f"Backup dir {self.backup_path} cleared") - - -class BackupNetDaemon: - """BackupNetDaemon.""" - - def __init__(self, repository): - """Initialize.""" - self.repository = repository - self.logger = Logger("hacs.backup") - self.backup_path = ( - tempfile.gettempdir() + "/hacs_persistent_netdaemon/" + repository.data.name - ) - - def create(self): - """Create a backup in /tmp""" - if os.path.exists(self.backup_path): - shutil.rmtree(self.backup_path) - while os.path.exists(self.backup_path): - sleep(0.1) - os.makedirs(self.backup_path, exist_ok=True) - - for filename in os.listdir(self.repository.content.path.local): - if filename.endswith(".yaml"): - source_file_name = f"{self.repository.content.path.local}/{filename}" - target_file_name = f"{self.backup_path}/{filename}" - shutil.copyfile(source_file_name, target_file_name) - - def restore(self): - """Create a backup in /tmp""" - if os.path.exists(self.backup_path): - for filename in os.listdir(self.backup_path): - if filename.endswith(".yaml"): - source_file_name = f"{self.backup_path}/{filename}" - target_file_name = ( - f"{self.repository.content.path.local}/{filename}" - ) - shutil.copyfile(source_file_name, target_file_name) - - def cleanup(self): - """Create a backup in /tmp""" - if os.path.exists(self.backup_path): - shutil.rmtree(self.backup_path) - while os.path.exists(self.backup_path): - sleep(0.1) - self.logger.debug(f"Backup dir {self.backup_path} cleared") +"""Backup.""" +from custom_components.hacs.helpers.functions.is_safe_to_remove import is_safe_to_remove +import os +import shutil +import tempfile +from time import sleep + +from custom_components.hacs.helpers.functions.logger import getLogger + +BACKUP_PATH = tempfile.gettempdir() + "/hacs_backup/" + +_LOGGER = getLogger() + + +class Backup: + """Backup.""" + + def __init__(self, local_path, backup_path=BACKUP_PATH): + """initialize.""" + self.local_path = local_path + self.backup_path = backup_path + self.backup_path_full = f"{self.backup_path}{self.local_path.split('/')[-1]}" + + def create(self): + """Create a backup in /tmp""" + if not os.path.exists(self.local_path): + return + if not is_safe_to_remove(self.local_path): + return + if os.path.exists(self.backup_path): + shutil.rmtree(self.backup_path) + while os.path.exists(self.backup_path): + sleep(0.1) + os.makedirs(self.backup_path, exist_ok=True) + + try: + if os.path.isfile(self.local_path): + shutil.copyfile(self.local_path, self.backup_path_full) + os.remove(self.local_path) + else: + shutil.copytree(self.local_path, self.backup_path_full) + shutil.rmtree(self.local_path) + while os.path.exists(self.local_path): + sleep(0.1) + _LOGGER.debug( + "Backup for %s, created in %s", + self.local_path, + self.backup_path_full, + ) + except (Exception, BaseException): # pylint: disable=broad-except + pass + + def restore(self): + """Restore from backup.""" + if not os.path.exists(self.backup_path_full): + return + + if os.path.isfile(self.backup_path_full): + if os.path.exists(self.local_path): + os.remove(self.local_path) + shutil.copyfile(self.backup_path_full, self.local_path) + else: + if os.path.exists(self.local_path): + shutil.rmtree(self.local_path) + while os.path.exists(self.local_path): + sleep(0.1) + shutil.copytree(self.backup_path_full, self.local_path) + _LOGGER.debug( + "Restored %s, from backup %s", self.local_path, self.backup_path_full + ) + + def cleanup(self): + """Cleanup backup files.""" + if os.path.exists(self.backup_path): + shutil.rmtree(self.backup_path) + while os.path.exists(self.backup_path): + sleep(0.1) + _LOGGER.debug("Backup dir %s cleared", self.backup_path) + + +class BackupNetDaemon: + """BackupNetDaemon.""" + + def __init__(self, repository): + """Initialize.""" + self.repository = repository + self.backup_path = ( + tempfile.gettempdir() + "/hacs_persistent_netdaemon/" + repository.data.name + ) + + def create(self): + """Create a backup in /tmp""" + if not is_safe_to_remove(self.repository.content.path.local): + return + if os.path.exists(self.backup_path): + shutil.rmtree(self.backup_path) + while os.path.exists(self.backup_path): + sleep(0.1) + os.makedirs(self.backup_path, exist_ok=True) + + for filename in os.listdir(self.repository.content.path.local): + if filename.endswith(".yaml"): + source_file_name = f"{self.repository.content.path.local}/{filename}" + target_file_name = f"{self.backup_path}/{filename}" + shutil.copyfile(source_file_name, target_file_name) + + def restore(self): + """Create a backup in /tmp""" + if os.path.exists(self.backup_path): + for filename in os.listdir(self.backup_path): + if filename.endswith(".yaml"): + source_file_name = f"{self.backup_path}/{filename}" + target_file_name = ( + f"{self.repository.content.path.local}/{filename}" + ) + shutil.copyfile(source_file_name, target_file_name) + + def cleanup(self): + """Create a backup in /tmp""" + if os.path.exists(self.backup_path): + shutil.rmtree(self.backup_path) + while os.path.exists(self.backup_path): + sleep(0.1) + _LOGGER.debug("Backup dir %s cleared", self.backup_path) diff --git a/custom_components/hacs/operational/factory.py b/custom_components/hacs/operational/factory.py new file mode 100644 index 0000000..06b6231 --- /dev/null +++ b/custom_components/hacs/operational/factory.py @@ -0,0 +1,50 @@ +# pylint: disable=missing-docstring,invalid-name +import asyncio +from aiogithubapi import AIOGitHubAPIException + +from custom_components.hacs.helpers.classes.exceptions import HacsException +from custom_components.hacs.helpers.functions.logger import getLogger +from custom_components.hacs.helpers.functions.register_repository import ( + register_repository, +) + +max_concurrent_tasks = asyncio.Semaphore(15) +sleeper = 5 + +_LOGGER = getLogger() + + +class HacsTaskFactory: + def __init__(self): + self.tasks = [] + self.running = False + + async def safe_common_update(self, repository): + async with max_concurrent_tasks: + try: + await repository.common_update() + except (AIOGitHubAPIException, HacsException) as exception: + _LOGGER.error("%s - %s", repository.data.full_name, exception) + + # Due to GitHub ratelimits we need to sleep a bit + await asyncio.sleep(sleeper) + + async def safe_update(self, repository): + async with max_concurrent_tasks: + try: + await repository.update_repository() + except (AIOGitHubAPIException, HacsException) as exception: + _LOGGER.error("%s - %s", repository.data.full_name, exception) + + # Due to GitHub ratelimits we need to sleep a bit + await asyncio.sleep(sleeper) + + async def safe_register(self, repo, category): + async with max_concurrent_tasks: + try: + await register_repository(repo, category) + except (AIOGitHubAPIException, HacsException) as exception: + _LOGGER.error("%s - %s", repo, exception) + + # Due to GitHub ratelimits we need to sleep a bit + await asyncio.sleep(sleeper) diff --git a/custom_components/hacs/operational/reload.py b/custom_components/hacs/operational/reload.py new file mode 100644 index 0000000..037887e --- /dev/null +++ b/custom_components/hacs/operational/reload.py @@ -0,0 +1,10 @@ +"""Reload HACS""" + + +async def async_reload_entry(hass, config_entry): + """Reload HACS.""" + from custom_components.hacs.operational.remove import async_remove_entry + from custom_components.hacs.operational.setup import async_setup_entry + + await async_remove_entry(hass, config_entry) + await async_setup_entry(hass, config_entry) diff --git a/custom_components/hacs/operational/remove.py b/custom_components/hacs/operational/remove.py new file mode 100644 index 0000000..84d8232 --- /dev/null +++ b/custom_components/hacs/operational/remove.py @@ -0,0 +1,24 @@ +"""Remove HACS.""" +from custom_components.hacs.share import get_hacs + + +async def async_remove_entry(hass, config_entry): + """Handle removal of an entry.""" + hacs = get_hacs() + hacs.log.info("Disabling HACS") + hacs.log.info("Removing recurring tasks") + for task in hacs.recuring_tasks: + task() + if config_entry.state == "loaded": + hacs.log.info("Removing sensor") + try: + await hass.config_entries.async_forward_entry_unload(config_entry, "sensor") + except ValueError: + pass + hacs.log.info("Removing sidepanel") + try: + hass.components.frontend.async_remove_panel("hacs") + except AttributeError: + pass + hacs.system.disabled = True + hacs.log.info("HACS is now disabled") diff --git a/custom_components/hacs/operational/runtime.py b/custom_components/hacs/operational/runtime.py new file mode 100644 index 0000000..95f5985 --- /dev/null +++ b/custom_components/hacs/operational/runtime.py @@ -0,0 +1 @@ +"""Runtime...""" diff --git a/custom_components/hacs/operational/setup.py b/custom_components/hacs/operational/setup.py new file mode 100644 index 0000000..e89eaa2 --- /dev/null +++ b/custom_components/hacs/operational/setup.py @@ -0,0 +1,209 @@ +"""Setup HACS.""" +from custom_components.hacs.enums import HacsStage +from aiogithubapi import AIOGitHubAPIException, GitHub +from homeassistant.const import EVENT_HOMEASSISTANT_STARTED +from homeassistant.const import __version__ as HAVERSION +from homeassistant.exceptions import ConfigEntryNotReady +from homeassistant.helpers.aiohttp_client import async_create_clientsession +from homeassistant.helpers.event import async_call_later + +from custom_components.hacs.const import DOMAIN, STARTUP, VERSION +from custom_components.hacs.hacsbase.configuration import Configuration +from custom_components.hacs.hacsbase.data import HacsData +from custom_components.hacs.helpers.functions.constrains import check_constrains +from custom_components.hacs.helpers.functions.remaining_github_calls import ( + get_fetch_updates_for, +) +from custom_components.hacs.operational.reload import async_reload_entry +from custom_components.hacs.operational.remove import async_remove_entry +from custom_components.hacs.operational.setup_actions.clear_storage import ( + async_clear_storage, +) +from custom_components.hacs.operational.setup_actions.frontend import ( + async_setup_frontend, +) +from custom_components.hacs.operational.setup_actions.load_hacs_repository import ( + async_load_hacs_repository, +) +from custom_components.hacs.operational.setup_actions.sensor import async_add_sensor +from custom_components.hacs.operational.setup_actions.websocket_api import ( + async_setup_hacs_websockt_api, +) +from custom_components.hacs.share import get_hacs + +try: + from homeassistant.components.lovelace import system_health_info +except ImportError: + from homeassistant.components.lovelace.system_health import system_health_info + + +async def _async_common_setup(hass): + """Common setup stages.""" + hacs = get_hacs() + hacs.hass = hass + hacs.system.running = True + hacs.session = async_create_clientsession(hass) + + +async def async_setup_entry(hass, config_entry): + """Set up this integration using UI.""" + from homeassistant import config_entries + + hacs = get_hacs() + if hass.data.get(DOMAIN) is not None: + return False + if config_entry.source == config_entries.SOURCE_IMPORT: + hass.async_create_task(hass.config_entries.async_remove(config_entry.entry_id)) + return False + + await _async_common_setup(hass) + + hacs.configuration = Configuration.from_dict( + config_entry.data, config_entry.options + ) + hacs.configuration.config_type = "flow" + hacs.configuration.config_entry = config_entry + + return await async_startup_wrapper_for_config_entry() + + +async def async_setup(hass, config): + """Set up this integration using yaml.""" + hacs = get_hacs() + if DOMAIN not in config: + return True + if hacs.configuration and hacs.configuration.config_type == "flow": + return True + + await _async_common_setup(hass) + + hass.data[DOMAIN] = config[DOMAIN] + hacs.configuration = Configuration.from_dict(config[DOMAIN]) + hacs.configuration.config_type = "yaml" + await async_startup_wrapper_for_yaml() + return True + + +async def async_startup_wrapper_for_config_entry(): + """Startup wrapper for ui config.""" + hacs = get_hacs() + hacs.configuration.config_entry.add_update_listener(async_reload_entry) + try: + startup_result = await async_hacs_startup() + except AIOGitHubAPIException: + startup_result = False + if not startup_result: + hacs.system.disabled = True + raise ConfigEntryNotReady + hacs.system.disabled = False + return startup_result + + +async def async_startup_wrapper_for_yaml(): + """Startup wrapper for yaml config.""" + hacs = get_hacs() + try: + startup_result = await async_hacs_startup() + except AIOGitHubAPIException: + startup_result = False + if not startup_result: + hacs.system.disabled = True + hacs.hass.components.frontend.async_remove_panel( + hacs.configuration.sidepanel_title.lower() + .replace(" ", "_") + .replace("-", "_") + ) + hacs.log.info("Could not setup HACS, trying again in 15 min") + if int(hacs.system.ha_version.split(".")[1]) >= 117: + async_call_later(hacs.hass, 900, async_startup_wrapper_for_yaml) + else: + async_call_later(hacs.hass, 900, async_startup_wrapper_for_yaml()) + return + hacs.system.disabled = False + + +async def async_hacs_startup(): + """HACS startup tasks.""" + hacs = get_hacs() + + try: + lovelace_info = await system_health_info(hacs.hass) + except TypeError: + # If this happens, the users YAML is not valid, we assume YAML mode + lovelace_info = {"mode": "yaml"} + hacs.log.debug(f"Configuration type: {hacs.configuration.config_type}") + hacs.version = VERSION + hacs.log.info(STARTUP) + hacs.core.config_path = hacs.hass.config.path() + hacs.system.ha_version = HAVERSION + + # Setup websocket API + await async_setup_hacs_websockt_api() + + # Set up frontend + await async_setup_frontend() + + # Clear old storage files + await async_clear_storage() + + hacs.system.lovelace_mode = lovelace_info.get("mode", "yaml") + hacs.system.disabled = False + hacs.github = GitHub( + hacs.configuration.token, async_create_clientsession(hacs.hass) + ) + hacs.data = HacsData() + + can_update = await get_fetch_updates_for(hacs.github) + if can_update is None: + hacs.log.critical("Your GitHub token is not valid") + return False + + if can_update != 0: + hacs.log.debug(f"Can update {can_update} repositories") + else: + hacs.log.info( + "HACS is ratelimited, repository updates will resume when the limit is cleared, this can take up to 1 hour" + ) + return False + + # Check HACS Constrains + if not await hacs.hass.async_add_executor_job(check_constrains): + if hacs.configuration.config_type == "flow": + if hacs.configuration.config_entry is not None: + await async_remove_entry(hacs.hass, hacs.configuration.config_entry) + return False + + # Load HACS + if not await async_load_hacs_repository(): + if hacs.configuration.config_type == "flow": + if hacs.configuration.config_entry is not None: + await async_remove_entry(hacs.hass, hacs.configuration.config_entry) + return False + + # Restore from storefiles + if not await hacs.data.restore(): + hacs_repo = hacs.get_by_name("hacs/integration") + hacs_repo.pending_restart = True + if hacs.configuration.config_type == "flow": + if hacs.configuration.config_entry is not None: + await async_remove_entry(hacs.hass, hacs.configuration.config_entry) + return False + + # Setup startup tasks + if hacs.status.new: + if int(hacs.system.ha_version.split(".")[1]) >= 117: + async_call_later(hacs.hass, 5, hacs.startup_tasks) + else: + async_call_later(hacs.hass, 5, hacs.startup_tasks()) + else: + hacs.hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STARTED, hacs.startup_tasks) + + # Set up sensor + await async_add_sensor() + + # Mischief managed! + await hacs.async_set_stage(HacsStage.WAITING) + hacs.log.info( + "Setup complete, waiting for Home Assistant before startup tasks starts" + ) + return True diff --git a/custom_components/hacs/operational/setup_actions/__init__.py b/custom_components/hacs/operational/setup_actions/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/custom_components/hacs/operational/setup_actions/__pycache__/__init__.cpython-38.pyc b/custom_components/hacs/operational/setup_actions/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..ba681d2 Binary files /dev/null and b/custom_components/hacs/operational/setup_actions/__pycache__/__init__.cpython-38.pyc differ diff --git a/custom_components/hacs/operational/setup_actions/__pycache__/categories.cpython-38.pyc b/custom_components/hacs/operational/setup_actions/__pycache__/categories.cpython-38.pyc new file mode 100644 index 0000000..bf70adf Binary files /dev/null and b/custom_components/hacs/operational/setup_actions/__pycache__/categories.cpython-38.pyc differ diff --git a/custom_components/hacs/operational/setup_actions/__pycache__/clear_storage.cpython-38.pyc b/custom_components/hacs/operational/setup_actions/__pycache__/clear_storage.cpython-38.pyc new file mode 100644 index 0000000..53857ab Binary files /dev/null and b/custom_components/hacs/operational/setup_actions/__pycache__/clear_storage.cpython-38.pyc differ diff --git a/custom_components/hacs/operational/setup_actions/__pycache__/frontend.cpython-38.pyc b/custom_components/hacs/operational/setup_actions/__pycache__/frontend.cpython-38.pyc new file mode 100644 index 0000000..42dceb6 Binary files /dev/null and b/custom_components/hacs/operational/setup_actions/__pycache__/frontend.cpython-38.pyc differ diff --git a/custom_components/hacs/operational/setup_actions/__pycache__/load_hacs_repository.cpython-38.pyc b/custom_components/hacs/operational/setup_actions/__pycache__/load_hacs_repository.cpython-38.pyc new file mode 100644 index 0000000..330778c Binary files /dev/null and b/custom_components/hacs/operational/setup_actions/__pycache__/load_hacs_repository.cpython-38.pyc differ diff --git a/custom_components/hacs/operational/setup_actions/__pycache__/sensor.cpython-38.pyc b/custom_components/hacs/operational/setup_actions/__pycache__/sensor.cpython-38.pyc new file mode 100644 index 0000000..fb3ada7 Binary files /dev/null and b/custom_components/hacs/operational/setup_actions/__pycache__/sensor.cpython-38.pyc differ diff --git a/custom_components/hacs/operational/setup_actions/__pycache__/websocket_api.cpython-38.pyc b/custom_components/hacs/operational/setup_actions/__pycache__/websocket_api.cpython-38.pyc new file mode 100644 index 0000000..87b1f34 Binary files /dev/null and b/custom_components/hacs/operational/setup_actions/__pycache__/websocket_api.cpython-38.pyc differ diff --git a/custom_components/hacs/operational/setup_actions/categories.py b/custom_components/hacs/operational/setup_actions/categories.py new file mode 100644 index 0000000..f3c5688 --- /dev/null +++ b/custom_components/hacs/operational/setup_actions/categories.py @@ -0,0 +1,42 @@ +"""Starting setup task: extra stores.""" +from custom_components.hacs.const import ELEMENT_TYPES +from ...share import get_hacs +from ...enums import HacsCategory, HacsSetupTask + + +def _setup_extra_stores(): + """Set up extra stores in HACS if enabled in Home Assistant.""" + hacs = get_hacs() + hacs.log.debug("Starting setup task: Extra stores") + hacs.common.categories = set() + for category in ELEMENT_TYPES: + enable_category(hacs, HacsCategory(category)) + + if HacsCategory.PYTHON_SCRIPT in hacs.hass.config.components: + if HacsCategory.PYTHON_SCRIPT not in hacs.common.categories: + enable_category(hacs, HacsCategory.PYTHON_SCRIPT) + + if ( + hacs.hass.services._services.get("frontend", {}).get("reload_themes") + is not None + ): + if HacsCategory.THEME not in hacs.common.categories: + enable_category(hacs, HacsCategory.THEME) + + if hacs.configuration.appdaemon: + enable_category(hacs, HacsCategory.APPDAEMON) + if hacs.configuration.netdaemon: + enable_category(hacs, HacsCategory.NETDAEMON) + + +async def async_setup_extra_stores(): + """Async wrapper for setup_extra_stores""" + hacs = get_hacs() + hacs.log.info("setup task %s", HacsSetupTask.CATEGORIES) + await hacs.hass.async_add_executor_job(_setup_extra_stores) + + +def enable_category(hacs, category: HacsCategory): + """Add category.""" + hacs.log.debug("Enable category: %s", category) + hacs.common.categories.add(category) diff --git a/custom_components/hacs/operational/setup_actions/clear_storage.py b/custom_components/hacs/operational/setup_actions/clear_storage.py new file mode 100644 index 0000000..3e55873 --- /dev/null +++ b/custom_components/hacs/operational/setup_actions/clear_storage.py @@ -0,0 +1,23 @@ +"""Starting setup task: clear storage.""" +import os + +from custom_components.hacs.share import get_hacs +from ...enums import HacsSetupTask + + +async def async_clear_storage(): + """Async wrapper for clear_storage""" + hacs = get_hacs() + hacs.log.info("Setup task %s", HacsSetupTask.CATEGORIES) + await hacs.hass.async_add_executor_job(_clear_storage) + + +def _clear_storage(): + """Clear old files from storage.""" + hacs = get_hacs() + storagefiles = ["hacs"] + for s_f in storagefiles: + path = f"{hacs.core.config_path}/.storage/{s_f}" + if os.path.isfile(path): + hacs.log.info(f"Cleaning up old storage file {path}") + os.remove(path) diff --git a/custom_components/hacs/operational/setup_actions/frontend.py b/custom_components/hacs/operational/setup_actions/frontend.py new file mode 100644 index 0000000..f5950ed --- /dev/null +++ b/custom_components/hacs/operational/setup_actions/frontend.py @@ -0,0 +1,46 @@ +from hacs_frontend.version import VERSION as FE_VERSION + +from custom_components.hacs.helpers.classes.frontend_view import HacsFrontend +from custom_components.hacs.helpers.functions.information import get_frontend_version +from custom_components.hacs.share import get_hacs +from ...enums import HacsSetupTask + + +async def async_setup_frontend(): + """Configure the HACS frontend elements.""" + hacs = get_hacs() + hacs.log.info("Setup task %s", HacsSetupTask.FRONTEND) + + # Custom view + hacs.hass.http.register_view(HacsFrontend()) + + # Custom iconset + if "frontend_extra_module_url" not in hacs.hass.data: + hacs.hass.data["frontend_extra_module_url"] = set() + hacs.hass.data["frontend_extra_module_url"].add("/hacsfiles/iconset.js") + + hacs.frontend.version_running = FE_VERSION + hacs.frontend.version_expected = await hacs.hass.async_add_executor_job( + get_frontend_version + ) + + # Add to sidepanel + if "hacs" not in hacs.hass.data.get("frontend_panels", {}): + custom_panel_config = { + "name": "hacs-frontend", + "embed_iframe": True, + "trust_external": False, + "js_url": "/hacsfiles/frontend/entrypoint.js", + } + + config = {} + config["_panel_custom"] = custom_panel_config + + hacs.hass.components.frontend.async_register_built_in_panel( + component_name="custom", + sidebar_title=hacs.configuration.sidepanel_title, + sidebar_icon=hacs.configuration.sidepanel_icon, + frontend_url_path="hacs", + config=config, + require_admin=True, + ) diff --git a/custom_components/hacs/operational/setup_actions/load_hacs_repository.py b/custom_components/hacs/operational/setup_actions/load_hacs_repository.py new file mode 100644 index 0000000..9ea9c8c --- /dev/null +++ b/custom_components/hacs/operational/setup_actions/load_hacs_repository.py @@ -0,0 +1,37 @@ +"""Starting setup task: load HACS repository.""" +from custom_components.hacs.const import VERSION +from custom_components.hacs.helpers.classes.exceptions import HacsException +from custom_components.hacs.helpers.functions.information import get_repository +from custom_components.hacs.helpers.functions.register_repository import ( + register_repository, +) +from custom_components.hacs.share import get_hacs +from ...enums import HacsSetupTask + + +async def async_load_hacs_repository(): + """Load HACS repositroy.""" + hacs = get_hacs() + hacs.log.info("Setup task %s", HacsSetupTask.HACS_REPO) + + try: + repository = hacs.get_by_name("hacs/integration") + if repository is None: + await register_repository("hacs/integration", "integration") + repository = hacs.get_by_name("hacs/integration") + if repository is None: + raise HacsException("Unknown error") + repository.data.installed = True + repository.data.installed_version = VERSION + repository.data.new = False + hacs.repo = repository.repository_object + hacs.data_repo = await get_repository( + hacs.session, hacs.configuration.token, "hacs/default" + ) + except HacsException as exception: + if "403" in f"{exception}": + hacs.log.critical("GitHub API is ratelimited, or the token is wrong.") + else: + hacs.log.critical(f"[{exception}] - Could not load HACS!") + return False + return True diff --git a/custom_components/hacs/operational/setup_actions/sensor.py b/custom_components/hacs/operational/setup_actions/sensor.py new file mode 100644 index 0000000..d7c8ec3 --- /dev/null +++ b/custom_components/hacs/operational/setup_actions/sensor.py @@ -0,0 +1,24 @@ +""""Starting setup task: Sensor".""" +from homeassistant.helpers import discovery + +from custom_components.hacs.const import DOMAIN +from custom_components.hacs.share import get_hacs +from ...enums import HacsSetupTask + + +async def async_add_sensor(): + """Async wrapper for add sensor""" + hacs = get_hacs() + hacs.log.info("Setup task %s", HacsSetupTask.SENSOR) + if hacs.configuration.config_type == "yaml": + hacs.hass.async_create_task( + discovery.async_load_platform( + hacs.hass, "sensor", DOMAIN, {}, hacs.configuration.config + ) + ) + else: + hacs.hass.async_add_job( + hacs.hass.config_entries.async_forward_entry_setup( + hacs.configuration.config_entry, "sensor" + ) + ) diff --git a/custom_components/hacs/operational/setup_actions/websocket_api.py b/custom_components/hacs/operational/setup_actions/websocket_api.py new file mode 100644 index 0000000..f77d33e --- /dev/null +++ b/custom_components/hacs/operational/setup_actions/websocket_api.py @@ -0,0 +1,35 @@ +"""Register WS API endpoints for HACS.""" +from homeassistant.components import websocket_api + +from custom_components.hacs.api.acknowledge_critical_repository import ( + acknowledge_critical_repository, +) +from custom_components.hacs.api.check_local_path import check_local_path +from custom_components.hacs.api.get_critical_repositories import ( + get_critical_repositories, +) +from custom_components.hacs.api.hacs_config import hacs_config +from custom_components.hacs.api.hacs_removed import hacs_removed +from custom_components.hacs.api.hacs_repositories import hacs_repositories +from custom_components.hacs.api.hacs_repository import hacs_repository +from custom_components.hacs.api.hacs_repository_data import hacs_repository_data +from custom_components.hacs.api.hacs_settings import hacs_settings +from custom_components.hacs.api.hacs_status import hacs_status +from custom_components.hacs.share import get_hacs +from ...enums import HacsSetupTask + + +async def async_setup_hacs_websockt_api(): + """Set up WS API handlers.""" + hacs = get_hacs() + hacs.log.info("Setup task %s", HacsSetupTask.WEBSOCKET) + websocket_api.async_register_command(hacs.hass, hacs_settings) + websocket_api.async_register_command(hacs.hass, hacs_config) + websocket_api.async_register_command(hacs.hass, hacs_repositories) + websocket_api.async_register_command(hacs.hass, hacs_repository) + websocket_api.async_register_command(hacs.hass, hacs_repository_data) + websocket_api.async_register_command(hacs.hass, check_local_path) + websocket_api.async_register_command(hacs.hass, hacs_status) + websocket_api.async_register_command(hacs.hass, hacs_removed) + websocket_api.async_register_command(hacs.hass, acknowledge_critical_repository) + websocket_api.async_register_command(hacs.hass, get_critical_repositories) diff --git a/custom_components/hacs/repositories/__init__.py b/custom_components/hacs/repositories/__init__.py index ddd6003..91d89c4 100644 --- a/custom_components/hacs/repositories/__init__.py +++ b/custom_components/hacs/repositories/__init__.py @@ -1,16 +1,16 @@ -"""Initialize repositories.""" -from custom_components.hacs.repositories.theme import HacsTheme -from custom_components.hacs.repositories.integration import HacsIntegration -from custom_components.hacs.repositories.python_script import HacsPythonScript -from custom_components.hacs.repositories.appdaemon import HacsAppdaemon -from custom_components.hacs.repositories.netdaemon import HacsNetdaemon -from custom_components.hacs.repositories.plugin import HacsPlugin - -RERPOSITORY_CLASSES = { - "theme": HacsTheme, - "integration": HacsIntegration, - "python_script": HacsPythonScript, - "appdaemon": HacsAppdaemon, - "netdaemon": HacsNetdaemon, - "plugin": HacsPlugin, -} +"""Initialize repositories.""" +from custom_components.hacs.repositories.appdaemon import HacsAppdaemon +from custom_components.hacs.repositories.integration import HacsIntegration +from custom_components.hacs.repositories.netdaemon import HacsNetdaemon +from custom_components.hacs.repositories.plugin import HacsPlugin +from custom_components.hacs.repositories.python_script import HacsPythonScript +from custom_components.hacs.repositories.theme import HacsTheme + +RERPOSITORY_CLASSES = { + "theme": HacsTheme, + "integration": HacsIntegration, + "python_script": HacsPythonScript, + "appdaemon": HacsAppdaemon, + "netdaemon": HacsNetdaemon, + "plugin": HacsPlugin, +} diff --git a/custom_components/hacs/repositories/__pycache__/__init__.cpython-37.pyc b/custom_components/hacs/repositories/__pycache__/__init__.cpython-37.pyc deleted file mode 100644 index 4aad74a..0000000 Binary files a/custom_components/hacs/repositories/__pycache__/__init__.cpython-37.pyc and /dev/null differ diff --git a/custom_components/hacs/repositories/__pycache__/__init__.cpython-38.pyc b/custom_components/hacs/repositories/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..5b45997 Binary files /dev/null and b/custom_components/hacs/repositories/__pycache__/__init__.cpython-38.pyc differ diff --git a/custom_components/hacs/repositories/__pycache__/appdaemon.cpython-37.pyc b/custom_components/hacs/repositories/__pycache__/appdaemon.cpython-37.pyc deleted file mode 100644 index b3e8e25..0000000 Binary files a/custom_components/hacs/repositories/__pycache__/appdaemon.cpython-37.pyc and /dev/null differ diff --git a/custom_components/hacs/repositories/__pycache__/appdaemon.cpython-38.pyc b/custom_components/hacs/repositories/__pycache__/appdaemon.cpython-38.pyc new file mode 100644 index 0000000..bfad5ee Binary files /dev/null and b/custom_components/hacs/repositories/__pycache__/appdaemon.cpython-38.pyc differ diff --git a/custom_components/hacs/repositories/__pycache__/integration.cpython-37.pyc b/custom_components/hacs/repositories/__pycache__/integration.cpython-37.pyc deleted file mode 100644 index f773ff4..0000000 Binary files a/custom_components/hacs/repositories/__pycache__/integration.cpython-37.pyc and /dev/null differ diff --git a/custom_components/hacs/repositories/__pycache__/integration.cpython-38.pyc b/custom_components/hacs/repositories/__pycache__/integration.cpython-38.pyc new file mode 100644 index 0000000..2d60350 Binary files /dev/null and b/custom_components/hacs/repositories/__pycache__/integration.cpython-38.pyc differ diff --git a/custom_components/hacs/repositories/__pycache__/manifest.cpython-37.pyc b/custom_components/hacs/repositories/__pycache__/manifest.cpython-37.pyc deleted file mode 100644 index d2b8872..0000000 Binary files a/custom_components/hacs/repositories/__pycache__/manifest.cpython-37.pyc and /dev/null differ diff --git a/custom_components/hacs/repositories/__pycache__/netdaemon.cpython-37.pyc b/custom_components/hacs/repositories/__pycache__/netdaemon.cpython-37.pyc deleted file mode 100644 index 6d91056..0000000 Binary files a/custom_components/hacs/repositories/__pycache__/netdaemon.cpython-37.pyc and /dev/null differ diff --git a/custom_components/hacs/repositories/__pycache__/netdaemon.cpython-38.pyc b/custom_components/hacs/repositories/__pycache__/netdaemon.cpython-38.pyc new file mode 100644 index 0000000..9c28b17 Binary files /dev/null and b/custom_components/hacs/repositories/__pycache__/netdaemon.cpython-38.pyc differ diff --git a/custom_components/hacs/repositories/__pycache__/plugin.cpython-37.pyc b/custom_components/hacs/repositories/__pycache__/plugin.cpython-37.pyc deleted file mode 100644 index 3482de4..0000000 Binary files a/custom_components/hacs/repositories/__pycache__/plugin.cpython-37.pyc and /dev/null differ diff --git a/custom_components/hacs/repositories/__pycache__/plugin.cpython-38.pyc b/custom_components/hacs/repositories/__pycache__/plugin.cpython-38.pyc new file mode 100644 index 0000000..635cbc0 Binary files /dev/null and b/custom_components/hacs/repositories/__pycache__/plugin.cpython-38.pyc differ diff --git a/custom_components/hacs/repositories/__pycache__/python_script.cpython-37.pyc b/custom_components/hacs/repositories/__pycache__/python_script.cpython-37.pyc deleted file mode 100644 index 91990ee..0000000 Binary files a/custom_components/hacs/repositories/__pycache__/python_script.cpython-37.pyc and /dev/null differ diff --git a/custom_components/hacs/repositories/__pycache__/python_script.cpython-38.pyc b/custom_components/hacs/repositories/__pycache__/python_script.cpython-38.pyc new file mode 100644 index 0000000..1a366aa Binary files /dev/null and b/custom_components/hacs/repositories/__pycache__/python_script.cpython-38.pyc differ diff --git a/custom_components/hacs/repositories/__pycache__/removed.cpython-37.pyc b/custom_components/hacs/repositories/__pycache__/removed.cpython-37.pyc deleted file mode 100644 index f7df1dd..0000000 Binary files a/custom_components/hacs/repositories/__pycache__/removed.cpython-37.pyc and /dev/null differ diff --git a/custom_components/hacs/repositories/__pycache__/repository.cpython-37.pyc b/custom_components/hacs/repositories/__pycache__/repository.cpython-37.pyc deleted file mode 100644 index 94f2ced..0000000 Binary files a/custom_components/hacs/repositories/__pycache__/repository.cpython-37.pyc and /dev/null differ diff --git a/custom_components/hacs/repositories/__pycache__/repositorydata.cpython-37.pyc b/custom_components/hacs/repositories/__pycache__/repositorydata.cpython-37.pyc deleted file mode 100644 index ac43283..0000000 Binary files a/custom_components/hacs/repositories/__pycache__/repositorydata.cpython-37.pyc and /dev/null differ diff --git a/custom_components/hacs/repositories/__pycache__/theme.cpython-37.pyc b/custom_components/hacs/repositories/__pycache__/theme.cpython-37.pyc deleted file mode 100644 index 8cf0e6e..0000000 Binary files a/custom_components/hacs/repositories/__pycache__/theme.cpython-37.pyc and /dev/null differ diff --git a/custom_components/hacs/repositories/__pycache__/theme.cpython-38.pyc b/custom_components/hacs/repositories/__pycache__/theme.cpython-38.pyc new file mode 100644 index 0000000..69a60f1 Binary files /dev/null and b/custom_components/hacs/repositories/__pycache__/theme.cpython-38.pyc differ diff --git a/custom_components/hacs/repositories/appdaemon.py b/custom_components/hacs/repositories/appdaemon.py index 92d19e4..15639f7 100644 --- a/custom_components/hacs/repositories/appdaemon.py +++ b/custom_components/hacs/repositories/appdaemon.py @@ -1,85 +1,72 @@ -"""Class for appdaemon apps in HACS.""" -from aiogithubapi import AIOGitHubException -from integrationhelper import Logger - -from .repository import HacsRepository -from ..hacsbase.exceptions import HacsException - - -class HacsAppdaemon(HacsRepository): - """Appdaemon apps in HACS.""" - - def __init__(self, full_name): - """Initialize.""" - super().__init__() - self.data.full_name = full_name - self.data.category = "appdaemon" - self.content.path.local = self.localpath - self.content.path.remote = "apps" - self.logger = Logger(f"hacs.repository.{self.data.category}.{full_name}") - - @property - def localpath(self): - """Return localpath.""" - return f"{self.hacs.system.config_path}/appdaemon/apps/{self.data.name}" - - async def validate_repository(self): - """Validate.""" - await self.common_validate() - - # Custom step 1: Validate content. - try: - addir = await self.repository_object.get_contents("apps", self.ref) - except AIOGitHubException: - raise HacsException( - f"Repostitory structure for {self.ref.replace('tags/','')} is not compliant" - ) - - if not isinstance(addir, list): - self.validate.errors.append("Repostitory structure not compliant") - - self.content.path.remote = addir[0].path - self.content.objects = await self.repository_object.get_contents( - self.content.path.remote, self.ref - ) - - # Handle potential errors - if self.validate.errors: - for error in self.validate.errors: - if not self.hacs.system.status.startup: - self.logger.error(error) - return self.validate.success - - async def registration(self): - """Registration.""" - if not await self.validate_repository(): - return False - - # Run common registration steps. - await self.common_registration() - - # Set local path - self.content.path.local = self.localpath - - async def update_repository(self): - """Update.""" - if self.hacs.github.ratelimits.remaining == 0: - return - await self.common_update() - - # Get appdaemon objects. - if self.repository_manifest: - if self.data.content_in_root: - self.content.path.remote = "" - - if self.content.path.remote == "apps": - addir = await self.repository_object.get_contents( - self.content.path.remote, self.ref - ) - self.content.path.remote = addir[0].path - self.content.objects = await self.repository_object.get_contents( - self.content.path.remote, self.ref - ) - - # Set local path - self.content.path.local = self.localpath +"""Class for appdaemon apps in HACS.""" +from aiogithubapi import AIOGitHubAPIException + +from custom_components.hacs.helpers.classes.exceptions import HacsException +from custom_components.hacs.helpers.classes.repository import HacsRepository +from custom_components.hacs.enums import HacsCategory + + +class HacsAppdaemon(HacsRepository): + """Appdaemon apps in HACS.""" + + def __init__(self, full_name): + """Initialize.""" + super().__init__() + self.data.full_name = full_name + self.data.full_name_lower = full_name.lower() + self.data.category = HacsCategory.APPDAEMON + self.content.path.local = self.localpath + self.content.path.remote = "apps" + + @property + def localpath(self): + """Return localpath.""" + return f"{self.hacs.core.config_path}/appdaemon/apps/{self.data.name}" + + async def validate_repository(self): + """Validate.""" + await self.common_validate() + + # Custom step 1: Validate content. + try: + addir = await self.repository_object.get_contents("apps", self.ref) + except AIOGitHubAPIException: + raise HacsException( + f"Repostitory structure for {self.ref.replace('tags/','')} is not compliant" + ) from None + + if not isinstance(addir, list): + self.validate.errors.append("Repostitory structure not compliant") + + self.content.path.remote = addir[0].path + self.content.objects = await self.repository_object.get_contents( + self.content.path.remote, self.ref + ) + + # Handle potential errors + if self.validate.errors: + for error in self.validate.errors: + if not self.hacs.status.startup: + self.logger.error("%s %s", self, error) + return self.validate.success + + async def update_repository(self, ignore_issues=False): + """Update.""" + await self.common_update(ignore_issues) + + # Get appdaemon objects. + if self.repository_manifest: + if self.data.content_in_root: + self.content.path.remote = "" + + if self.content.path.remote == "apps": + addir = await self.repository_object.get_contents( + self.content.path.remote, self.ref + ) + self.content.path.remote = addir[0].path + self.content.objects = await self.repository_object.get_contents( + self.content.path.remote, self.ref + ) + + # Set local path + self.content.path.local = self.localpath diff --git a/custom_components/hacs/repositories/integration.py b/custom_components/hacs/repositories/integration.py index 7b9f874..d8625ff 100644 --- a/custom_components/hacs/repositories/integration.py +++ b/custom_components/hacs/repositories/integration.py @@ -1,93 +1,97 @@ -"""Class for integrations in HACS.""" -from integrationhelper import Logger - -from homeassistant.loader import async_get_custom_components - -from custom_components.hacs.hacsbase.exceptions import HacsException -from custom_components.hacs.helpers.filters import get_first_directory_in_directory -from custom_components.hacs.helpers.information import get_integration_manifest -from custom_components.hacs.repositories.repository import HacsRepository - - -class HacsIntegration(HacsRepository): - """Integrations in HACS.""" - - def __init__(self, full_name): - """Initialize.""" - super().__init__() - self.data.full_name = full_name - self.data.category = "integration" - self.content.path.remote = "custom_components" - self.content.path.local = self.localpath - self.logger = Logger(f"hacs.repository.{self.data.category}.{full_name}") - - @property - def localpath(self): - """Return localpath.""" - return f"{self.hacs.system.config_path}/custom_components/{self.data.domain}" - - async def validate_repository(self): - """Validate.""" - await self.common_validate() - - # Custom step 1: Validate content. - if self.data.content_in_root: - self.content.path.remote = "" - - if self.content.path.remote == "custom_components": - name = get_first_directory_in_directory(self.tree, "custom_components") - if name is None: - raise HacsException( - f"Repostitory structure for {self.ref.replace('tags/','')} is not compliant" - ) - self.content.path.remote = f"custom_components/{name}" - - try: - await get_integration_manifest(self) - except HacsException as exception: - self.logger.error(exception) - - # Handle potential errors - if self.validate.errors: - for error in self.validate.errors: - if not self.hacs.system.status.startup: - self.logger.error(error) - return self.validate.success - - async def registration(self): - """Registration.""" - if not await self.validate_repository(): - return False - - # Run common registration steps. - await self.common_registration() - - # Set local path - self.content.path.local = self.localpath - - async def update_repository(self): - """Update.""" - if self.hacs.github.ratelimits.remaining == 0: - return - await self.common_update() - - if self.data.content_in_root: - self.content.path.remote = "" - - if self.content.path.remote == "custom_components": - name = get_first_directory_in_directory(self.tree, "custom_components") - self.content.path.remote = f"custom_components/{name}" - - try: - await get_integration_manifest(self) - except HacsException as exception: - self.logger.error(exception) - - # Set local path - self.content.path.local = self.localpath - - async def reload_custom_components(self): - """Reload custom_components (and config flows)in HA.""" - self.logger.info("Reloading custom_component cache") - del self.hacs.hass.data["custom_components"] - await async_get_custom_components(self.hacs.hass) +"""Class for integrations in HACS.""" +from homeassistant.loader import async_get_custom_components + +from custom_components.hacs.helpers.classes.exceptions import HacsException +from custom_components.hacs.helpers.classes.repository import HacsRepository +from custom_components.hacs.enums import HacsCategory +from custom_components.hacs.helpers.functions.filters import ( + get_first_directory_in_directory, +) +from custom_components.hacs.helpers.functions.information import ( + get_integration_manifest, +) +from custom_components.hacs.helpers.functions.logger import getLogger + + +class HacsIntegration(HacsRepository): + """Integrations in HACS.""" + + def __init__(self, full_name): + """Initialize.""" + super().__init__() + self.data.full_name = full_name + self.data.full_name_lower = full_name.lower() + self.data.category = HacsCategory.INTEGRATION + self.content.path.remote = "custom_components" + self.content.path.local = self.localpath + + @property + def localpath(self): + """Return localpath.""" + return f"{self.hacs.core.config_path}/custom_components/{self.data.domain}" + + async def async_post_installation(self): + """Run post installation steps.""" + if self.data.config_flow: + if self.data.full_name != "hacs/integration": + await self.reload_custom_components() + if self.data.first_install: + self.pending_restart = False + return + self.pending_restart = True + + async def validate_repository(self): + """Validate.""" + await self.common_validate() + + # Custom step 1: Validate content. + if self.data.content_in_root: + self.content.path.remote = "" + + if self.content.path.remote == "custom_components": + name = get_first_directory_in_directory(self.tree, "custom_components") + if name is None: + raise HacsException( + f"Repostitory structure for {self.ref.replace('tags/','')} is not compliant" + ) + self.content.path.remote = f"custom_components/{name}" + + try: + await get_integration_manifest(self) + except HacsException as exception: + if self.hacs.system.action: + raise HacsException(f"::error:: {exception}") from exception + self.logger.error("%s %s", self, exception) + + # Handle potential errors + if self.validate.errors: + for error in self.validate.errors: + if not self.hacs.status.startup: + self.logger.error("%s %s", self, error) + return self.validate.success + + async def update_repository(self, ignore_issues=False): + """Update.""" + await self.common_update(ignore_issues) + + if self.data.content_in_root: + self.content.path.remote = "" + + if self.content.path.remote == "custom_components": + name = get_first_directory_in_directory(self.tree, "custom_components") + self.content.path.remote = f"custom_components/{name}" + + try: + await get_integration_manifest(self) + except HacsException as exception: + self.logger.error("%s %s", self, exception) + + # Set local path + self.content.path.local = self.localpath + + async def reload_custom_components(self): + """Reload custom_components (and config flows)in HA.""" + self.logger.info("Reloading custom_component cache") + del self.hacs.hass.data["custom_components"] + await async_get_custom_components(self.hacs.hass) + self.logger.info("Custom_component cache reloaded") diff --git a/custom_components/hacs/repositories/netdaemon.py b/custom_components/hacs/repositories/netdaemon.py index 5c3ed1e..770add1 100644 --- a/custom_components/hacs/repositories/netdaemon.py +++ b/custom_components/hacs/repositories/netdaemon.py @@ -1,90 +1,87 @@ -"""Class for netdaemon apps in HACS.""" -from integrationhelper import Logger - -from .repository import HacsRepository -from ..hacsbase.exceptions import HacsException - -from custom_components.hacs.helpers.filters import get_first_directory_in_directory - - -class HacsNetdaemon(HacsRepository): - """Netdaemon apps in HACS.""" - - def __init__(self, full_name): - """Initialize.""" - super().__init__() - self.data.full_name = full_name - self.data.category = "netdaemon" - self.content.path.local = self.localpath - self.content.path.remote = "apps" - self.logger = Logger(f"hacs.repository.{self.data.category}.{full_name}") - - @property - def localpath(self): - """Return localpath.""" - return f"{self.hacs.system.config_path}/netdaemon/apps/{self.data.name}" - - async def validate_repository(self): - """Validate.""" - await self.common_validate() - - # Custom step 1: Validate content. - if self.repository_manifest: - if self.data.content_in_root: - self.content.path.remote = "" - - if self.content.path.remote == "apps": - self.data.domain = get_first_directory_in_directory( - self.tree, self.content.path.remote - ) - self.content.path.remote = f"apps/{self.data.name}" - - compliant = False - for treefile in self.treefiles: - if treefile.startswith(f"{self.content.path.remote}") and treefile.endswith( - ".cs" - ): - compliant = True - break - if not compliant: - raise HacsException( - f"Repostitory structure for {self.ref.replace('tags/','')} is not compliant" - ) - - # Handle potential errors - if self.validate.errors: - for error in self.validate.errors: - if not self.hacs.system.status.startup: - self.logger.error(error) - return self.validate.success - - async def registration(self): - """Registration.""" - if not await self.validate_repository(): - return False - - # Run common registration steps. - await self.common_registration() - - # Set local path - self.content.path.local = self.localpath - - async def update_repository(self): - """Update.""" - if self.hacs.github.ratelimits.remaining == 0: - return - await self.common_update() - - # Get appdaemon objects. - if self.repository_manifest: - if self.data.content_in_root: - self.content.path.remote = "" - - if self.content.path.remote == "apps": - self.data.domain = get_first_directory_in_directory( - self.tree, self.content.path.remote - ) - self.content.path.remote = f"apps/{self.data.name}" - - # Set local path - self.content.path.local = self.localpath +"""Class for netdaemon apps in HACS.""" +from custom_components.hacs.helpers.classes.exceptions import HacsException +from custom_components.hacs.helpers.classes.repository import HacsRepository +from custom_components.hacs.enums import HacsCategory +from custom_components.hacs.helpers.functions.filters import ( + get_first_directory_in_directory, +) +from custom_components.hacs.helpers.functions.logger import getLogger + + +class HacsNetdaemon(HacsRepository): + """Netdaemon apps in HACS.""" + + def __init__(self, full_name): + """Initialize.""" + super().__init__() + self.data.full_name = full_name + self.data.full_name_lower = full_name.lower() + self.data.category = HacsCategory.NETDAEMON + self.content.path.local = self.localpath + self.content.path.remote = "apps" + + @property + def localpath(self): + """Return localpath.""" + return f"{self.hacs.core.config_path}/netdaemon/apps/{self.data.name}" + + async def validate_repository(self): + """Validate.""" + await self.common_validate() + + # Custom step 1: Validate content. + if self.repository_manifest: + if self.data.content_in_root: + self.content.path.remote = "" + + if self.content.path.remote == "apps": + self.data.domain = get_first_directory_in_directory( + self.tree, self.content.path.remote + ) + self.content.path.remote = f"apps/{self.data.name}" + + compliant = False + for treefile in self.treefiles: + if treefile.startswith(f"{self.content.path.remote}") and treefile.endswith( + ".cs" + ): + compliant = True + break + if not compliant: + raise HacsException( + f"Repostitory structure for {self.ref.replace('tags/','')} is not compliant" + ) + + # Handle potential errors + if self.validate.errors: + for error in self.validate.errors: + if not self.hacs.status.startup: + self.logger.error("%s %s", self, error) + return self.validate.success + + async def update_repository(self, ignore_issues=False): + """Update.""" + await self.common_update(ignore_issues) + + # Get appdaemon objects. + if self.repository_manifest: + if self.data.content_in_root: + self.content.path.remote = "" + + if self.content.path.remote == "apps": + self.data.domain = get_first_directory_in_directory( + self.tree, self.content.path.remote + ) + self.content.path.remote = f"apps/{self.data.name}" + + # Set local path + self.content.path.local = self.localpath + + async def async_post_installation(self): + """Run post installation steps.""" + try: + await self.hacs.hass.services.async_call( + "hassio", "addon_restart", {"addon": "c6a2317c_netdaemon"} + ) + except (Exception, BaseException): # pylint: disable=broad-except + pass diff --git a/custom_components/hacs/repositories/plugin.py b/custom_components/hacs/repositories/plugin.py index 277e604..8b961d6 100644 --- a/custom_components/hacs/repositories/plugin.py +++ b/custom_components/hacs/repositories/plugin.py @@ -1,107 +1,77 @@ -"""Class for plugins in HACS.""" -import json -from integrationhelper import Logger - -from .repository import HacsRepository -from ..hacsbase.exceptions import HacsException - -from custom_components.hacs.helpers.information import find_file_name - - -class HacsPlugin(HacsRepository): - """Plugins in HACS.""" - - def __init__(self, full_name): - """Initialize.""" - super().__init__() - self.data.full_name = full_name - self.data.file_name = None - self.data.category = "plugin" - self.information.javascript_type = None - self.content.path.local = ( - f"{self.hacs.system.config_path}/www/community/{full_name.split('/')[-1]}" - ) - self.logger = Logger(f"hacs.repository.{self.data.category}.{full_name}") - - async def validate_repository(self): - """Validate.""" - # Run common validation steps. - await self.common_validate() - - # Custom step 1: Validate content. - find_file_name(self) - - if self.content.path.remote is None: - raise HacsException( - f"Repostitory structure for {self.ref.replace('tags/','')} is not compliant" - ) - - if self.content.path.remote == "release": - self.content.single = True - - # Handle potential errors - if self.validate.errors: - for error in self.validate.errors: - if not self.hacs.system.status.startup: - self.logger.error(error) - return self.validate.success - - async def registration(self): - """Registration.""" - if not await self.validate_repository(): - return False - - # Run common registration steps. - await self.common_registration() - - async def update_repository(self): - """Update.""" - if self.hacs.github.ratelimits.remaining == 0: - return - # Run common update steps. - await self.common_update() - - # Get plugin objects. - find_file_name(self) - - # Get JS type - await self.parse_readme_for_jstype() - - if self.content.path.remote is None: - self.validate.errors.append("Repostitory structure not compliant") - - if self.content.path.remote == "release": - self.content.single = True - - async def get_package_content(self): - """Get package content.""" - try: - package = await self.repository_object.get_contents("package.json") - package = json.loads(package.content) - - if package: - self.data.authors = package["author"] - except Exception: # pylint: disable=broad-except - pass - - async def parse_readme_for_jstype(self): - """Parse the readme looking for js type.""" - readme = None - readme_files = ["readme", "readme.md"] - root = await self.repository_object.get_contents("") - for file in root: - if file.name.lower() in readme_files: - readme = await self.repository_object.get_contents(file.name) - break - - if readme is None: - return - - readme = readme.content - for line in readme.splitlines(): - if "type: module" in line: - self.information.javascript_type = "module" - break - elif "type: js" in line: - self.information.javascript_type = "js" - break +"""Class for plugins in HACS.""" +import json + +from custom_components.hacs.helpers.classes.exceptions import HacsException +from custom_components.hacs.helpers.classes.repository import HacsRepository +from custom_components.hacs.helpers.functions.information import find_file_name +from custom_components.hacs.helpers.functions.logger import getLogger + + +class HacsPlugin(HacsRepository): + """Plugins in HACS.""" + + def __init__(self, full_name): + """Initialize.""" + super().__init__() + self.data.full_name = full_name + self.data.full_name_lower = full_name.lower() + self.data.file_name = None + self.data.category = "plugin" + self.information.javascript_type = None + self.content.path.local = self.localpath + + @property + def localpath(self): + """Return localpath.""" + return f"{self.hacs.core.config_path}/www/community/{self.data.full_name.split('/')[-1]}" + + async def validate_repository(self): + """Validate.""" + # Run common validation steps. + await self.common_validate() + + # Custom step 1: Validate content. + find_file_name(self) + + if self.content.path.remote is None: + raise HacsException( + f"Repostitory structure for {self.ref.replace('tags/','')} is not compliant" + ) + + if self.content.path.remote == "release": + self.content.single = True + + # Handle potential errors + if self.validate.errors: + for error in self.validate.errors: + if not self.hacs.status.startup: + self.logger.error("%s %s", self, error) + return self.validate.success + + async def update_repository(self, ignore_issues=False): + """Update.""" + await self.common_update(ignore_issues) + + # Get plugin objects. + find_file_name(self) + + if self.content.path.remote is None: + self.validate.errors.append( + f"Repostitory structure for {self.ref.replace('tags/','')} is not compliant" + ) + + if self.content.path.remote == "release": + self.content.single = True + + async def get_package_content(self): + """Get package content.""" + try: + package = await self.repository_object.get_contents( + "package.json", self.ref + ) + package = json.loads(package.content) + + if package: + self.data.authors = package["author"] + except (Exception, BaseException): # pylint: disable=broad-except + pass diff --git a/custom_components/hacs/repositories/python_script.py b/custom_components/hacs/repositories/python_script.py index c9f2db7..e848eb0 100644 --- a/custom_components/hacs/repositories/python_script.py +++ b/custom_components/hacs/repositories/python_script.py @@ -1,87 +1,83 @@ -"""Class for python_scripts in HACS.""" -from integrationhelper import Logger - -from .repository import HacsRepository -from ..hacsbase.exceptions import HacsException -from ..helpers.information import find_file_name - - -class HacsPythonScript(HacsRepository): - """python_scripts in HACS.""" - - category = "python_script" - - def __init__(self, full_name): - """Initialize.""" - super().__init__() - self.data.full_name = full_name - self.data.category = "python_script" - self.content.path.remote = "python_scripts" - self.content.path.local = f"{self.hacs.system.config_path}/python_scripts" - self.content.single = True - self.logger = Logger(f"hacs.repository.{self.data.category}.{full_name}") - - async def validate_repository(self): - """Validate.""" - # Run common validation steps. - await self.common_validate() - - # Custom step 1: Validate content. - if self.data.content_in_root: - self.content.path.remote = "" - - compliant = False - for treefile in self.treefiles: - if treefile.startswith(f"{self.content.path.remote}") and treefile.endswith( - ".py" - ): - compliant = True - break - if not compliant: - raise HacsException( - f"Repostitory structure for {self.ref.replace('tags/','')} is not compliant" - ) - - # Handle potential errors - if self.validate.errors: - for error in self.validate.errors: - if not self.hacs.system.status.startup: - self.logger.error(error) - return self.validate.success - - async def registration(self): - """Registration.""" - if not await self.validate_repository(): - return False - - # Run common registration steps. - await self.common_registration() - - # Set name - find_file_name(self) - - async def update_repository(self): # lgtm[py/similar-function] - """Update.""" - if self.hacs.github.ratelimits.remaining == 0: - return - # Run common update steps. - await self.common_update() - - # Get python_script objects. - if self.data.content_in_root: - self.content.path.remote = "" - - compliant = False - for treefile in self.treefiles: - if treefile.startswith(f"{self.content.path.remote}") and treefile.endswith( - ".py" - ): - compliant = True - break - if not compliant: - raise HacsException( - f"Repostitory structure for {self.ref.replace('tags/','')} is not compliant" - ) - - # Update name - find_file_name(self) +"""Class for python_scripts in HACS.""" +from custom_components.hacs.helpers.classes.exceptions import HacsException +from custom_components.hacs.helpers.classes.repository import HacsRepository +from custom_components.hacs.enums import HacsCategory +from custom_components.hacs.helpers.functions.information import find_file_name +from custom_components.hacs.helpers.functions.logger import getLogger + + +class HacsPythonScript(HacsRepository): + """python_scripts in HACS.""" + + category = "python_script" + + def __init__(self, full_name): + """Initialize.""" + super().__init__() + self.data.full_name = full_name + self.data.full_name_lower = full_name.lower() + self.data.category = HacsCategory.PYTHON_SCRIPT + self.content.path.remote = "python_scripts" + self.content.path.local = self.localpath + self.content.single = True + + @property + def localpath(self): + """Return localpath.""" + return f"{self.hacs.core.config_path}/python_scripts" + + async def validate_repository(self): + """Validate.""" + # Run common validation steps. + await self.common_validate() + + # Custom step 1: Validate content. + if self.data.content_in_root: + self.content.path.remote = "" + + compliant = False + for treefile in self.treefiles: + if treefile.startswith(f"{self.content.path.remote}") and treefile.endswith( + ".py" + ): + compliant = True + break + if not compliant: + raise HacsException( + f"Repository structure for {self.ref.replace('tags/','')} is not compliant" + ) + + # Handle potential errors + if self.validate.errors: + for error in self.validate.errors: + if not self.hacs.status.startup: + self.logger.error("%s %s", self, error) + return self.validate.success + + async def async_post_registration(self): + """Registration.""" + # Set name + find_file_name(self) + + async def update_repository(self, ignore_issues=False): + """Update.""" + await self.common_update(ignore_issues) + + # Get python_script objects. + if self.data.content_in_root: + self.content.path.remote = "" + + compliant = False + for treefile in self.treefiles: + if treefile.startswith(f"{self.content.path.remote}") and treefile.endswith( + ".py" + ): + compliant = True + break + if not compliant: + raise HacsException( + f"Repository structure for {self.ref.replace('tags/','')} is not compliant" + ) + + # Update name + find_file_name(self) diff --git a/custom_components/hacs/repositories/theme.py b/custom_components/hacs/repositories/theme.py index 6e09657..57094ee 100644 --- a/custom_components/hacs/repositories/theme.py +++ b/custom_components/hacs/repositories/theme.py @@ -1,72 +1,76 @@ -"""Class for themes in HACS.""" -from integrationhelper import Logger -from .repository import HacsRepository -from ..hacsbase.exceptions import HacsException -from ..helpers.information import find_file_name - - -class HacsTheme(HacsRepository): - """Themes in HACS.""" - - def __init__(self, full_name): - """Initialize.""" - super().__init__() - self.data.full_name = full_name - self.data.category = "theme" - self.content.path.remote = "themes" - self.content.path.local = f"{self.hacs.system.config_path}/themes/" - self.content.single = False - self.logger = Logger(f"hacs.repository.{self.data.category}.{full_name}") - - async def validate_repository(self): - """Validate.""" - # Run common validation steps. - await self.common_validate() - - # Custom step 1: Validate content. - compliant = False - for treefile in self.treefiles: - if treefile.startswith("themes/") and treefile.endswith(".yaml"): - compliant = True - break - if not compliant: - raise HacsException( - f"Repostitory structure for {self.ref.replace('tags/','')} is not compliant" - ) - - if self.data.content_in_root: - self.content.path.remote = "" - - # Handle potential errors - if self.validate.errors: - for error in self.validate.errors: - if not self.hacs.system.status.startup: - self.logger.error(error) - return self.validate.success - - async def registration(self): - """Registration.""" - if not await self.validate_repository(): - return False - - # Run common registration steps. - await self.common_registration() - - # Set name - find_file_name(self) - self.content.path.local = f"{self.hacs.system.config_path}/themes/{self.data.file_name.replace('.yaml', '')}" - - async def update_repository(self): # lgtm[py/similar-function] - """Update.""" - if self.hacs.github.ratelimits.remaining == 0: - return - # Run common update steps. - await self.common_update() - - # Get theme objects. - if self.data.content_in_root: - self.content.path.remote = "" - - # Update name - find_file_name(self) - self.content.path.local = f"{self.hacs.system.config_path}/themes/{self.data.file_name.replace('.yaml', '')}" +"""Class for themes in HACS.""" +from custom_components.hacs.helpers.classes.exceptions import HacsException +from custom_components.hacs.helpers.classes.repository import HacsRepository +from custom_components.hacs.enums import HacsCategory +from custom_components.hacs.helpers.functions.information import find_file_name +from custom_components.hacs.helpers.functions.logger import getLogger + + +class HacsTheme(HacsRepository): + """Themes in HACS.""" + + def __init__(self, full_name): + """Initialize.""" + super().__init__() + self.data.full_name = full_name + self.data.full_name_lower = full_name.lower() + self.data.category = HacsCategory.THEME + self.content.path.remote = "themes" + self.content.path.local = self.localpath + self.content.single = False + + @property + def localpath(self): + """Return localpath.""" + return f"{self.hacs.core.config_path}/themes/{self.data.file_name.replace('.yaml', '')}" + + async def async_post_installation(self): + """Run post installation steps.""" + try: + await self.hacs.hass.services.async_call("frontend", "reload_themes", {}) + except (Exception, BaseException): # pylint: disable=broad-except + pass + + async def validate_repository(self): + """Validate.""" + # Run common validation steps. + await self.common_validate() + + # Custom step 1: Validate content. + compliant = False + for treefile in self.treefiles: + if treefile.startswith("themes/") and treefile.endswith(".yaml"): + compliant = True + break + if not compliant: + raise HacsException( + f"Repostitory structure for {self.ref.replace('tags/','')} is not compliant" + ) + + if self.data.content_in_root: + self.content.path.remote = "" + + # Handle potential errors + if self.validate.errors: + for error in self.validate.errors: + if not self.hacs.status.startup: + self.logger.error("%s %s", self, error) + return self.validate.success + + async def async_post_registration(self): + """Registration.""" + # Set name + find_file_name(self) + self.content.path.local = self.localpath + + async def update_repository(self, ignore_issues=False): + """Update.""" + await self.common_update(ignore_issues) + + # Get theme objects. + if self.data.content_in_root: + self.content.path.remote = "" + + # Update name + find_file_name(self) + self.content.path.local = self.localpath diff --git a/custom_components/hacs/sensor.py b/custom_components/hacs/sensor.py index cda52de..482baa4 100644 --- a/custom_components/hacs/sensor.py +++ b/custom_components/hacs/sensor.py @@ -1,96 +1,122 @@ -"""Sensor platform for HACS.""" -# pylint: disable=unused-argument -from homeassistant.helpers.entity import Entity -from .hacsbase import Hacs as hacs -from .const import DOMAIN, VERSION, NAME_SHORT - - -async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): - """Setup sensor platform.""" - async_add_entities([HACSSensor()]) - - -async def async_setup_entry(hass, config_entry, async_add_devices): - """Setup sensor platform.""" - async_add_devices([HACSSensor()]) - - -class HACSDevice(Entity): - """HACS Device class.""" - - @property - def device_info(self): - """Return device information about HACS.""" - return { - "identifiers": {(DOMAIN, self.unique_id)}, - "name": NAME_SHORT, - "manufacturer": "hacs.xyz", - "model": "", - "sw_version": VERSION, - } - - -class HACSSensor(HACSDevice): - """HACS Sensor class.""" - - def __init__(self): - """Initialize.""" - self._state = None - self.repositories = [] - - async def async_update(self): - """Update the sensor.""" - if hacs.system.status.background_task: - return - - self.repositories = [] - - for repository in hacs.repositories: - if ( - repository.pending_upgrade - and repository.data.category in hacs.common.categories - ): - self.repositories.append(repository) - self._state = len(self.repositories) - - @property - def unique_id(self): - """Return a unique ID to use for this sensor.""" - return ( - "0717a0cd-745c-48fd-9b16-c8534c9704f9-bc944b0f-fd42-4a58-a072-ade38d1444cd" - ) - - @property - def name(self): - """Return the name of the sensor.""" - return "hacs" - - @property - def state(self): - """Return the state of the sensor.""" - return self._state - - @property - def icon(self): - """Return the icon of the sensor.""" - return "hacs:hacs" - - @property - def unit_of_measurement(self): - """Return the unit of measurement.""" - return "pending update(s)" - - @property - def device_state_attributes(self): - """Return attributes for the sensor.""" - data = [] - for repository in self.repositories: - data.append( - { - "name": repository.data.full_name, - "display_name": repository.display_name, - "installed version": repository.display_installed_version, - "available version": repository.display_available_version, - } - ) - return {"repositories": data} +"""Sensor platform for HACS.""" +from homeassistant.helpers.entity import Entity +from custom_components.hacs.const import DOMAIN, NAME_SHORT, VERSION +from custom_components.hacs.share import get_hacs +from homeassistant.core import callback + + +async def async_setup_platform( + _hass, _config, async_add_entities, _discovery_info=None +): + """Setup sensor platform.""" + async_add_entities([HACSSensor()]) + + +async def async_setup_entry(_hass, _config_entry, async_add_devices): + """Setup sensor platform.""" + async_add_devices([HACSSensor()]) + + +class HACSDevice(Entity): + """HACS Device class.""" + + @property + def device_info(self): + """Return device information about HACS.""" + return { + "identifiers": {(DOMAIN, self.unique_id)}, + "name": NAME_SHORT, + "manufacturer": "hacs.xyz", + "model": "", + "sw_version": VERSION, + "entry_type": "service", + } + + +class HACSSensor(HACSDevice): + """HACS Sensor class.""" + + def __init__(self): + """Initialize.""" + self._state = None + self.repositories = [] + + @property + def should_poll(self): + """No polling needed.""" + return False + + async def async_update(self): + """Manual updates of the sensor.""" + self._update() + + @callback + def _update_and_write_state(self, *_): + """Update the sensor and write state.""" + self._update() + self.async_write_ha_state() + + @callback + def _update(self): + """Update the sensor.""" + hacs = get_hacs() + if hacs.status.background_task: + return + + self.repositories = [] + + for repository in hacs.repositories: + if ( + repository.pending_upgrade + and repository.data.category in hacs.common.categories + ): + self.repositories.append(repository) + self._state = len(self.repositories) + + @property + def unique_id(self): + """Return a unique ID to use for this sensor.""" + return ( + "0717a0cd-745c-48fd-9b16-c8534c9704f9-bc944b0f-fd42-4a58-a072-ade38d1444cd" + ) + + @property + def name(self): + """Return the name of the sensor.""" + return "hacs" + + @property + def state(self): + """Return the state of the sensor.""" + return self._state + + @property + def icon(self): + """Return the icon of the sensor.""" + return "hacs:hacs" + + @property + def unit_of_measurement(self): + """Return the unit of measurement.""" + return "pending update(s)" + + @property + def device_state_attributes(self): + """Return attributes for the sensor.""" + repositories = [] + for repository in self.repositories: + repositories.append( + { + "name": repository.data.full_name, + "display_name": repository.display_name, + "installed_version": repository.display_installed_version, + "available_version": repository.display_available_version, + } + ) + return {"repositories": repositories} + + async def async_added_to_hass(self) -> None: + """Register for status events.""" + self.async_on_remove( + self.hass.bus.async_listen("hacs/status", self._update_and_write_state) + ) diff --git a/custom_components/hacs/services.yaml b/custom_components/hacs/services.yaml deleted file mode 100644 index 982a956..0000000 --- a/custom_components/hacs/services.yaml +++ /dev/null @@ -1,15 +0,0 @@ -install: - description: This is NOT intended to be used here, this is intended for developers! - fields: - repository: - description: The repository ID - example: '"123456789"' -register: - description: This is NOT intended to be used here, this is intended for developers! - fields: - repository: - description: The full name of the repository - example: 'developer/repo' - repository_type: - description: The repository type - example: 'plugin' \ No newline at end of file diff --git a/custom_components/hacs/setup.py b/custom_components/hacs/setup.py deleted file mode 100644 index af7f9a0..0000000 --- a/custom_components/hacs/setup.py +++ /dev/null @@ -1,110 +0,0 @@ -"""Setup functions for HACS.""" -# pylint: disable=bad-continuation -from hacs_frontend.version import VERSION as FE_VERSION -from homeassistant.helpers import discovery - -from custom_components.hacs.hacsbase.exceptions import HacsException -from custom_components.hacs.const import VERSION, DOMAIN -from custom_components.hacs.globals import get_hacs -from custom_components.hacs.helpers.information import get_repository -from custom_components.hacs.helpers.register_repository import register_repository - - -async def load_hacs_repository(): - """Load HACS repositroy.""" - hacs = get_hacs() - - try: - repository = hacs.get_by_name("hacs/integration") - if repository is None: - await register_repository("hacs/integration", "integration") - repository = hacs.get_by_name("hacs/integration") - if repository is None: - raise HacsException("Unknown error") - repository.status.installed = True - repository.versions.installed = VERSION - repository.status.new = False - hacs.repo = repository.repository_object - hacs.data_repo = await get_repository( - hacs.session, hacs.configuration.token, "hacs/default" - ) - except HacsException as exception: - if "403" in f"{exception}": - hacs.logger.critical("GitHub API is ratelimited, or the token is wrong.") - else: - hacs.logger.critical(f"[{exception}] - Could not load HACS!") - return False - return True - - -def setup_extra_stores(): - """Set up extra stores in HACS if enabled in Home Assistant.""" - hacs = get_hacs() - if "python_script" in hacs.hass.config.components: - if "python_script" not in hacs.common.categories: - hacs.common.categories.append("python_script") - - if hacs.hass.services.services.get("frontend", {}).get("reload_themes") is not None: - if "theme" not in hacs.common.categories: - hacs.common.categories.append("theme") - - -def add_sensor(): - """Add sensor.""" - hacs = get_hacs() - - try: - if hacs.configuration.config_type == "yaml": - hacs.hass.async_create_task( - discovery.async_load_platform( - hacs.hass, "sensor", DOMAIN, {}, hacs.configuration.config - ) - ) - else: - hacs.hass.async_add_job( - hacs.hass.config_entries.async_forward_entry_setup( - hacs.configuration.config_entry, "sensor" - ) - ) - except ValueError: - pass - - -async def setup_frontend(): - """Configure the HACS frontend elements.""" - from .http import HacsFrontend, HacsPluginViewLegacy - from .ws_api_handlers import setup_ws_api - - hacs = get_hacs() - - hacs.hass.http.register_view(HacsFrontend()) - hacs.frontend.version_running = FE_VERSION - - # Legacy views, remove with 2.0 - hacs.hass.http.register_view(HacsPluginViewLegacy()) - - # Add to sidepanel - custom_panel_config = { - "name": "hacs-frontend", - "embed_iframe": False, - "trust_external": False, - "js_url": f"/hacsfiles/frontend-{hacs.frontend.version_running}.js", - } - - config = {} - config["_panel_custom"] = custom_panel_config - - hacs.hass.components.frontend.async_register_built_in_panel( - component_name="custom", - sidebar_title=hacs.configuration.sidepanel_title, - sidebar_icon=hacs.configuration.sidepanel_icon, - frontend_url_path="hacs", - config=config, - require_admin=True, - ) - - if "frontend_extra_module_url" not in hacs.hass.data: - hacs.hass.data["frontend_extra_module_url"] = set() - hacs.hass.data["frontend_extra_module_url"].add("/hacsfiles/iconset.js") - - await setup_ws_api(hacs.hass) diff --git a/custom_components/hacs/share.py b/custom_components/hacs/share.py new file mode 100644 index 0000000..a3665c2 --- /dev/null +++ b/custom_components/hacs/share.py @@ -0,0 +1,68 @@ +"""Shared HACS elements.""" +import os + +from .base import HacsBase + +SHARE = { + "hacs": None, + "factory": None, + "queue": None, + "removed_repositories": [], + "rules": {}, +} + + +def get_hacs() -> HacsBase: + if SHARE["hacs"] is None: + from custom_components.hacs.hacsbase.hacs import Hacs as Legacy + + _hacs = Legacy() + + if not "PYTEST" in os.environ and "GITHUB_ACTION" in os.environ: + _hacs.system.action = True + + SHARE["hacs"] = _hacs + + return SHARE["hacs"] + + +def get_factory(): + if SHARE["factory"] is None: + from custom_components.hacs.operational.factory import HacsTaskFactory + + SHARE["factory"] = HacsTaskFactory() + + return SHARE["factory"] + + +def get_queue(): + if SHARE["queue"] is None: + from queueman import QueueManager + + SHARE["queue"] = QueueManager() + + return SHARE["queue"] + + +def is_removed(repository): + return repository in [x.repository for x in SHARE["removed_repositories"]] + + +def get_removed(repository): + if not is_removed(repository): + from custom_components.hacs.helpers.classes.removed import RemovedRepository + + removed_repo = RemovedRepository() + removed_repo.repository = repository + SHARE["removed_repositories"].append(removed_repo) + filter_repos = [ + x + for x in SHARE["removed_repositories"] + if x.repository.lower() == repository.lower() + ] + + return filter_repos.pop() or None + + +def list_removed_repositories(): + return SHARE["removed_repositories"] diff --git a/custom_components/hacs/store.py b/custom_components/hacs/store.py deleted file mode 100644 index 848d4af..0000000 --- a/custom_components/hacs/store.py +++ /dev/null @@ -1,19 +0,0 @@ -"""Storage handers.""" -from homeassistant.helpers.json import JSONEncoder -from homeassistant.helpers.storage import Store -from .hacsbase.const import STORAGE_VERSION - - -async def async_load_from_store(hass, key): - """Load the retained data from store and return de-serialized data.""" - store = Store(hass, STORAGE_VERSION, f"hacs.{key}", encoder=JSONEncoder) - restored = await store.async_load() - if restored is None: - return {} - return restored - - -async def async_save_to_store(hass, key, data): - """Generate dynamic data to store and save it to the filesystem.""" - store = Store(hass, STORAGE_VERSION, f"hacs.{key}", encoder=JSONEncoder) - await store.async_save(data) diff --git a/custom_components/hacs/translations/cs.json b/custom_components/hacs/translations/cs.json new file mode 100644 index 0000000..a7f4bb2 --- /dev/null +++ b/custom_components/hacs/translations/cs.json @@ -0,0 +1,358 @@ +{ + "common": { + "about": "O", + "add": "přidat", + "appdaemon_apps": "Aplikace AppDaemon", + "appdaemon_plural": "Aplikace AppDaemon", + "background_task": "Probíhá úloha na pozadí, tato stránka se znovu načte, až bude hotová.", + "cancel": "Zrušit", + "check_log_file": "Zkontrolujte protokol pro další podrobnosti", + "continue": "Pokračovat", + "disabled": "Zakázáno", + "documentation": "Dokumentace", + "element": "prvek", + "hacs_is_disabled": "HACS je zakázán", + "ignore": "Ignorovat", + "install": "Nainstalovat", + "installed": "nainstalováno", + "integration": "Integrace", + "integration_plural": "Integrace", + "integrations": "Integrace", + "lovelace": "Lovelace", + "lovelace_element": "Prvek Lovelace", + "lovelace_elements": "Prvky Lovelace", + "manage": "spravovat", + "netdaemon": "NetDaemon", + "netdaemon_apps": "Aplikace NetDaemon", + "netdaemon_plural": "Aplikace NetDaemon", + "plugin": "Lovelace", + "plugin_plural": "Prvky Lovelace", + "plugins": "Prvky Lovelace", + "python_script": "Skript v Pythonu", + "python_script_plural": "Skripty v Pythonu", + "python_scripts": "Skripty v Pythonu", + "reload": "Znovu načíst", + "repositories": "Repozitáře", + "repository": "Repozitář", + "settings": "nastavení", + "theme": "Motiv", + "theme_plural": "Motivy", + "themes": "Motivy", + "uninstall": "Odinstalovat", + "update": "Aktualizovat", + "version": "Verze" + }, + "config": { + "abort": { + "single_instance_allowed": "Je povolena pouze jediná konfigurace HACS." + }, + "error": { + "acc": "Než budete pokračovat, musíte potvrdit všechna prohlášení", + "auth": "Osobní přístupový token není správný." + } + }, + "confirm": { + "add_to_lovelace": "Opravdu to chcete přidat ke svým zdrojům Lovelace?", + "bg_task": "Akce je zakázána, když jsou spuštěny úlohy na pozadí.", + "cancel": "Zrušit", + "continue": "Jste si jistý, že chcete pokračovat?", + "delete": "Opravdu chcete smazat \"{item}\"?", + "delete_installed": "\"{item}\" je nainstalována, je třeba ji před smazáním odinstalovat.", + "exist": "{item} již existuje", + "generic": "Jste si jist?", + "home_assistant_is_restarting": "Počkejte, Home Assistant se nyní restartuje.", + "home_assistant_version_not_correct": "Používáte Home Assistant ve verzi \"{haversion}\", ale tento repozitář vyžaduje instalaci minimálně verzi \"{minversion}\".", + "no": "Ne", + "no_upgrades": "Žádné aktualizace čekající na vyřízení", + "ok": "OK", + "overwrite": "Pokud toto uděláte, tak to přepíšete.", + "reload_data": "Tím se znovu načtou data všech repozitářů, o kterých HACS ví. Bude to nějakou dobu trvat.", + "restart_home_assistant": "Opravdu chcete restartovat Home Assistant?", + "uninstall": "Opravdu chcete odinstalovat \"{item}\"?", + "upgrade_all": "Tím se aktualizují všechny tyto repozitáře. Před pokračováním se ujistěte, že jste si přečetli poznámky k vydání všech z nich.", + "yes": "Ano" + }, + "dialog_about": { + "frontend_version": "Verze rozhraní", + "installed_repositories": "Nainstalované repozitáře", + "integration_version": "Verze integrace", + "useful_links": "Užitečné odkazy" + }, + "dialog_add_repo": { + "limit": "Zobrazeno je pouze prvních 100 repozitářů, pomocí vyhledávání můžete filtrovat, co potřebujete", + "no_match": "Vašemu filtru neodpovídají žádné repozitáře", + "sort_by": "Řadit dle", + "title": "Přidat repozitář" + }, + "dialog_custom_repositories": { + "category": "Kategorie", + "no_category": "Chybí kategorie", + "no_repository": "Chybí repozitář", + "title": "Vlastní repozitáře", + "url_placeholder": "Přidat URL adresu vlastního repozitáře" + }, + "dialog_info": { + "author": "Autor", + "downloads": "Staženo", + "install": "Instalovat tento repozitář v HACS", + "loading": "Načítání informací...", + "no_info": "Vývojář neposkytl pro tento repozitář žádné další informace", + "open_issues": "Nahlásit problémy", + "open_repo": "Otevřít repozitář", + "stars": "Hvězdičky", + "version_installed": "Nainstalovaná verze" + }, + "dialog_install": { + "restart": "Nezapomeňte, že změny integrace (custom_components) se projeví až po restartu Home Assistant.", + "select_version": "Vyberte verzi", + "show_beta": "Zobrazit beta verze", + "type": "Typ", + "url": "URL adresa" + }, + "dialog_removed": { + "link": "Externí odkaz pro další informace", + "name": "Název repozitáře", + "reason": "Důvod odstranění", + "type": "Typ odstranění" + }, + "dialog_update": { + "available_version": "Dostupná verze", + "changelog": "Seznam změn", + "installed_version": "Nainstalovaná verze", + "releasenotes": "Poznámky k vydáno pro {release}", + "title": "Aktualizace čeká na vyřízení" + }, + "dialog": { + "reload": { + "confirm": "Chcete to udělat hned teď?", + "description": "Při změně zdrojů Lovelace musíte vymazat mezipaměť prohlížeče." + } + }, + "entry": { + "information": "Informace", + "intro": "Zde budou zobrazeny aktualizace a důležité zprávy", + "messages": { + "disabled": { + "content": "Zkontrolujte protokol pro další podrobnosti", + "title": "HACS je deaktivován" + }, + "has_pending_tasks": { + "content": "Některé repozitáře se nemusí zobrazit, dokud toto není dokončeno.", + "title": "Úkoly na pozadí čekají na vyřízení" + }, + "removed": "Odebrán repozitář \"{repository}\"", + "resources": { + "content": "Máte {number} prvků Lovelace, které nejsou v Lovelace správně načteny.", + "title": "Nenačteno v Lovelace" + }, + "restart": { + "content": "Máte {number} integrací, které vyžadují restart Home Assistant, můžete to udělat v sekci \"Ovládání serveru\" v nastavení Home Assistant.", + "title": "Čeká se na restart" + }, + "setup": { + "content": "HACS se nastavuje, během této doby mohou některé informace chybět nebo být nesprávné", + "title": "HACS se nastavuje" + }, + "startup": { + "content": "HACS se spouští, během této doby mohou některé informace chybět nebo být nesprávné", + "title": "HACS se spouští" + }, + "waiting": { + "content": "HACS čeká na dokončení spuštění Home Assistant, než bude moci spustit své úlohy", + "title": "HACS čeká" + }, + "wrong_frontend_installed": { + "content": "Používáte verzi {running} rozhraní HACS, ale je očekávána verze {expected}. Pokud vidíte tuto zprávu, Home Assistant nebyl schopen nainstalovat novou verzi. Zkuste restartovat Home Assistant.", + "title": "Neočekávaná verze rozhraní" + }, + "wrong_frontend_loaded": { + "content": "Používáte verzi {running} rozhraní HACS, ale je očekávána verze {expected}, měli byste vymazat mezipaměť prohlížeče.", + "title": "Neočekávaná verze rozhraní" + } + }, + "pending_updates": "Čekající aktualizace" + }, + "menu": { + "about": "O HACS", + "clear": "Vymazat vše nové", + "custom_repositories": "Vlastní repozitáře", + "dismiss": "Odmítnout všechny nové repozitáře", + "documentation": "Dokumentace", + "open_issue": "Nahlásit problém", + "reload": "Znovu načíst okno" + }, + "options": { + "step": { + "user": { + "data": { + "appdaemon": "Povolit zjišťování a sledování aplikací AppDaemon", + "country": "Filtrovat pomocí kódu země.", + "debug": "Povolit ladění.", + "experimental": "Povolit experimentální funkce", + "netdaemon": "Povolit zjišťování a sledování aplikací NetDaemon", + "not_in_use": "Nepoužívá se s YAML", + "release_limit": "Počet vydání, která se mají zobrazit.", + "sidepanel_icon": "Ikona postranního panelu", + "sidepanel_title": "Název postranního panelu" + } + } + } + }, + "repository_banner": { + "config_flow": "Tato integrace podporuje nastavení v uživatelském rozhraní, což znamená, že nyní můžete přejít do sekce Integrace ve vašem uživatelském rozhraní a nastavit ji.", + "config_flow_title": "Podporováno nastavení z uživatelského rozhraní", + "integration_not_loaded": "Tato integrace není načtena v Home Assistant.", + "no_restart_required": "Není vyžadován restart", + "not_loaded": "Nenačteno", + "plugin_not_loaded": "Tento prvek není přidán do vašich zdrojů Lovelace.", + "restart": "Musíte restartovat Home Assistant.", + "restart_pending": "Restart čeká na vyřízení" + }, + "repository_card": { + "dismiss": "zamítnout", + "hide": "Skrýt", + "information": "Informace", + "new_repository": "Nový repozitář", + "not_loaded": "Nenačteno", + "open_issue": "Nahlásit problém", + "open_source": "Otevřít zdrojový kód", + "pending_restart": "Čeká se na restart", + "pending_update": "Čeká na aktualizaci", + "reinstall": "Přeinstalovat", + "report": "Zpráva o odstranění", + "update_information": "Informace o aktualizaci" + }, + "repository": { + "add_to_lovelace": "Přidat do Lovelace", + "authors": "Autoři", + "available": "K dispozici", + "back_to": "Zpět k", + "changelog": "Seznam změn", + "downloads": "Staženo", + "flag_this": "Označit", + "frontend_version": "Verze rozhraní", + "github_stars": "Hvězdičky na GitHubu", + "goto_integrations": "Přejít na integrace", + "hide": "Skrýt", + "hide_beta": "Skrýt beta verze", + "install": "Nainstalovat", + "installed": "Nainstalováno", + "lovelace_copy_example": "Zkopírovat příklad do schránky", + "lovelace_instruction": "Při přidávávání do vaší Lovelace konfigurace použijte toto", + "lovelace_no_js_type": "Nelze určit typ tohoto prvku, zkontrolujte repozitář.", + "newest": "nejnovější", + "note_appdaemon": "stále je třeba to přidat do souboru \"apps.yaml\"", + "note_installed": "Po instalaci bude umístěno v", + "note_integration": "stále je třeba to přidat do souboru \"configuration.yaml\"", + "note_plugin": "stále je třeba to přidat do konfigurace Lovelace (\"ui-lovelace.yaml\" nebo v editoru kódu konfigurace uživatelského rozhraní)", + "note_plugin_post_107": "stále je třeba to přidat do konfigurace Lovelace (\"configuration.yaml\" nebo v editoru \"\/config\/lovelace\/resources\")", + "open_issue": "Nahlásit problém", + "open_plugin": "Otevřít prvek", + "reinstall": "Přeinstalovat", + "repository": "Repozitář", + "restart_home_assistant": "Restartovat Home Assistant", + "show_beta": "Zobrazit beta verze", + "uninstall": "Odinstalovat", + "update_information": "Informace o aktualizaci", + "upgrade": "Aktualizovat" + }, + "search": { + "installed": "Hledejte nainstalované repozitáře", + "installed_new": "Hledejte nainstalované nebo nové repozitáře", + "placeholder": "Hledat repozitář" + }, + "sections": { + "about": { + "description": "Zobrazit informace o HACS", + "title": "O HACS" + }, + "addon": { + "description": "V HACS nejsou žádné doplňky, ale kliknutím sem se dostanete k Supervisoru", + "title": "Doplňky" + }, + "automation": { + "description": "Zde najdete skripty v Pythonu, aplikace AppDaemon a aplikace NetDaemon", + "title": "Automatizace" + }, + "frontend": { + "description": "Zde najdete motivy, vlastní karty a další prvky pro Lovelace", + "title": "Rozhraní" + }, + "integrations": { + "description": "Zde najdete vlastní integrace (custom_components)", + "title": "Integrace" + }, + "pending_repository_upgrade": "Používáte verzi {installed}, verze {available} je k dispozici" + }, + "settings": { + "add_custom_repository": "PŘIDAT VLASTNÍ REPOZITÁŘ", + "adding_new_repo": "Přidání nového repozitáře \"{repo}\"", + "adding_new_repo_category": "S kategorií \"{category}\".", + "bg_task_custom": "Vlastní repozitáře jsou skryty, když jsou spuštěný úlohy na pozadí.", + "category": "Kategorie", + "compact_mode": "Kompaktní režim", + "custom_repositories": "VLASTNÍ REPOZITÁŘE", + "delete": "Smazat", + "display": "Zobrazení", + "grid": "Mřížka", + "hacs_repo": "Repozitář HACS", + "hidden_repositories": "skryté repozitáře", + "missing_category": "Je třeba vybrat kategorii", + "open_repository": "Otevřít repozitář", + "reload_data": "Znovu načíst data", + "reload_window": "Znovu načíst okno", + "repository_configuration": "Nastavení repozitáře", + "save": "Uložit", + "table": "Tabulka", + "table_view": "V tabulce", + "unhide": "odkrýt", + "upgrade_all": "Aktualizovat vše" + }, + "store": { + "ascending": "vzestupně", + "clear_new": "Vymazat všechny nové repozitáře", + "descending": "sestupně", + "last_updated": "Naposledy aktualizováno", + "name": "Název", + "new_repositories": "Nové repozitáře", + "new_repositories_note": "Zobrazuje se zde více než 10 nových repozitářů. Chcete-li je všechny vymazat, klikněte na 3 tečky v pravém horním rohu a všechny odmítněte.", + "no_repositories": "Žádné repozitáře", + "no_repositories_desc1": "Vypadá to, že v této sekci ještě nemáte nainstalované žádné repozitáře.", + "no_repositories_desc2": "Kliknutím na + ve spodním rohu přidáte svůj první!", + "no_repositories_found_desc1": "V této části nebyly nalezeny žádné nainstalované repozitáře odpovídající \"{searchInput}\".", + "no_repositories_found_desc2": "Zkuste hledat něco jiného!", + "pending_upgrades": "Čekající aktualizace", + "placeholder_search": "Zadejte výraz k hledání...", + "sort": "řadit", + "stars": "Hvězdičky", + "status": "Stav" + }, + "time": { + "ago": "před", + "day": "den", + "days": "dní", + "hour": "hodina", + "hours": "hodin", + "minute": "minuta", + "minutes": "minut", + "month": "měsíc", + "months": "měsíce", + "one": "Jeden", + "one_day_ago": "před jedním dnem", + "one_hour_ago": "před hodinou", + "one_minute_ago": "před minutou", + "one_month_ago": "před měsícem", + "one_second_ago": "před sekundou", + "one_year_ago": "před rokem", + "second": "sekunda", + "seconds": "sekundy", + "x_days_ago": "před {x} dny", + "x_hours_ago": "před {x} hodinami", + "x_minutes_ago": "před {x} minutami", + "x_months_ago": "před {x} měsíci", + "x_seconds_ago": "před {x} sekundami", + "x_years_ago": "před {x} lety", + "year": "rok", + "years": "let" + } +} \ No newline at end of file diff --git a/custom_components/hacs/translations/da.json b/custom_components/hacs/translations/da.json new file mode 100644 index 0000000..2ba2c4d --- /dev/null +++ b/custom_components/hacs/translations/da.json @@ -0,0 +1,345 @@ +{ + "common": { + "about": "Om", + "add": "tilføj", + "appdaemon_apps": "AppDaemon-apps", + "appdaemon_plural": "AppDaemon-apps", + "background_task": "Baggrundsopgave kører. Denne side vil genindlæses automatisk.", + "check_log_file": "Tjek din logfil for flere detaljer.", + "continue": "Fortsæt", + "disabled": "Deaktiveret", + "documentation": "Dokumentation", + "element": "element", + "hacs_is_disabled": "HACS er deaktiveret", + "install": "Installer", + "installed": "installeret", + "integration": "Integration", + "integration_plural": "Integrationer", + "integrations": "Integrationer", + "lovelace": "Lovelace", + "lovelace_element": "Lovelace-element", + "lovelace_elements": "Lovelace-elementer", + "manage": "administrer", + "netdaemon": "NetDaemon", + "netdaemon_apps": "NetDaemon-apps", + "netdaemon_plural": "NetDaemon-apps", + "plugin": "Lovelace", + "plugin_plural": "Lovelace-elementer", + "plugins": "Lovelace-elementer", + "python_script": "Python-script", + "python_script_plural": "Python-scripts", + "python_scripts": "Python-scripts", + "repositories": "Repositories", + "repository": "Repository", + "settings": "indstillinger", + "theme": "Tema", + "theme_plural": "Temaer", + "themes": "Temaer", + "uninstall": "Afinstaller", + "update": "Opdater", + "version": "Version" + }, + "config": { + "step": { + "device": { + "description": "Open {url} and paste this key `{code}` to authorize HACS, when you have done that click 'submit'", + "title": "HACS" + }, + "user": { + "data": { + "acc_addons": "I know that there are no add-ons in HACS", + "acc_disable": "I know that if I get issues with Home Assistant I should disable all my custom_components", + "acc_logs": "I know how to access Home Assistant logs", + "acc_untested": "I know that everything inside HACS is custom and untested by Home Assistant", + "appdaemon": "Enable AppDaemon apps discovery & tracking", + "netdaemon": "Enable NetDaemon apps discovery & tracking", + "python_script": "Enable python_scripts discovery & tracking", + "sidepanel_icon": "Side panel icon", + "sidepanel_title": "Side panel title", + "theme": "Enable Themes discovery & tracking", + "token": "GitHub Personal Access Token" + } + } + }, + "title": "HACS (Home Assistant Community Store)" + }, + "confirm": { + "add_to_lovelace": "Er du sikker på, at du vil tilføje dette til dine Lovelace-ressourcer?", + "bg_task": "Handlingen er deaktiveret, mens baggrundsopgaver kører.", + "cancel": "Annuller", + "continue": "Er du sikker på, at du vil fortsætte?", + "delete": "Er du sikker på, at du vil slette '{Item}'?", + "delete_installed": "'{item}' er installeret, du skal afinstallere det, før du kan slette det.", + "exist": "{item} findes allerede", + "generic": "Er du sikker?", + "home_assistant_is_restarting": "Vent venligst - Home Assistant genstarter nu.", + "home_assistant_version_not_correct": "Du kører Home Assistant version '{haversion}', men dette repository kræver som minimum version '{minversion}'.", + "no": "Nej", + "no_upgrades": "Der er ingen opdateringer tilgængelig", + "ok": "OK", + "overwrite": "Dette vil overskrive den.", + "reload_data": "Dette genindlæser data fra alle repositories, som HACS kender til. Dette vil tage nogen tid at fuldføre.", + "restart_home_assistant": "Er du sikker på, at du vil genstarte Home Assistant?", + "uninstall": "Er du sikker på, at du vil afinstallere '{Item}'?", + "upgrade_all": "Dette vil opdatere alle repositories. Sørg for at du har læst udgivelsesnoterne for dem alle, inden du fortsætter.", + "yes": "Ja" + }, + "dialog_about": { + "frontend_version": "Frontend-version", + "installed_repositories": "Installerede repositories", + "integration_version": "Integrationsversion", + "useful_links": "Nyttige links" + }, + "dialog_add_repo": { + "limit": "Kun de første 100 repositories vises. Brug søgningen til at filtrere, hvad du har brug for", + "no_match": "Der blev ikke fundet nogen repositories, der matcher dit filter", + "sort_by": "Sorter efter", + "title": "Tilføj repository" + }, + "dialog_custom_repositories": { + "category": "Kategori", + "no_category": "Manglende kategori", + "no_repository": "Manglende repository", + "title": "Brugerdefinerede repositories", + "url_placeholder": "Tilføj brugerdefineret repository-webadresse" + }, + "dialog_info": { + "author": "Udvikler", + "downloads": "Downloads", + "install": "Installer dette repository i HACS", + "loading": "Indlæser oplysninger...", + "no_info": "Udvikleren har ikke givet flere oplysninger om dette repository", + "open_issues": "Åbn issues", + "open_repo": "Åbn repository", + "stars": "Stjerner", + "version_installed": "Installeret version" + }, + "dialog_install": { + "restart": "Husk, at du skal genstarte Home Assistant, før ændringer af integrationer (custom_components) træder i kræft.", + "select_version": "Vælg version", + "show_beta": "Vis betaversioner", + "type": "Type", + "url": "Webadresse" + }, + "dialog_update": { + "available_version": "Tilgængelig version", + "changelog": "Udgivelsesnoter", + "installed_version": "Installeret version", + "releasenotes": "Udgivelsesnoter for {release}", + "title": "Ventende opdatering" + }, + "entry": { + "information": "Oplysninger", + "intro": "Opdateringer og vigtige meddelelser vises her, hvis der er nogen", + "messages": { + "disabled": { + "content": "Tjek din logfil for flere detaljer", + "title": "HACS er deaktiveret" + }, + "has_pending_tasks": { + "content": "Nogle repositories vises muligvis ikke, før dette er fuldført", + "title": "Baggrundsopgaver venter" + }, + "resources": { + "content": "Du har {number} Lovelace-elementer, der ikke er indlæst korrekt i Lovelace.", + "title": "Ikke indlæst i Lovelace" + }, + "restart": { + "content": "Du har {number} integrationer, der kræver en genstart af Home Assistant. Du kan genstarte fra 'Serveradministration'-sektionen under Indstillinger i Home Assistant-brugerfladen.", + "title": "Afventer genstart" + }, + "startup": { + "content": "HACS starter op. Der kan i dette tidsrum mangle nogle oplysninger, eller de kan være ukorekte.", + "title": "HACS starter op" + }, + "wrong_frontend_installed": { + "content": "Du har version {running} af HACS-frontend installeret, men version {expected} var forventet, hvis dette ser du denne besked. Home Assistant kunne ikke installere den nye version. Prøv at genstarte Home Assistant.", + "title": "Uventet frontend-version" + }, + "wrong_frontend_loaded": { + "content": "Du kører version {running} af HACS-frontend, men version {expected} var forventet. Du bør rydde din browser-cache.", + "title": "Uventet frontend-version" + } + }, + "pending_updates": "Ventende opdateringer" + }, + "menu": { + "about": "Om HACS", + "clear": "Ryd alle nye", + "custom_repositories": "Brugerdefinerede repositories", + "dismiss": "Afvis alle nye repositories", + "documentation": "Dokumentation", + "open_issue": "Opret issue", + "reload": "Genindlæs vindue" + }, + "options": { + "step": { + "user": { + "data": { + "appdaemon": "Aktiver opdagelse og sporing af AppDaemon-apps", + "country": "Filtrer med landekode.", + "debug": "Aktiver debug.", + "experimental": "Aktivér eksperimentelle funktioner", + "netdaemon": "Aktiver opdagelse og sporing af NetDaemon-apps", + "not_in_use": "Ikke i brug med YAML", + "release_limit": "Antal udgivelser, der skal vises.", + "sidepanel_icon": "Sidepanelikon", + "sidepanel_title": "Sidepanelets titel" + } + } + } + }, + "repository_banner": { + "config_flow": "Denne integration understøtter config_flow. Det betyder, at du nu kan gå til integrationssektionen i din brugerflade for at konfigurere den.", + "config_flow_title": "Brugerfladekonfiguration understøttet", + "integration_not_loaded": "Denne integration er ikke indlæst i Home Assistant.", + "no_restart_required": "Ingen genstart påkrævet", + "not_loaded": "Ikke indlæst", + "plugin_not_loaded": "Dette element er ikke føjet til dine Lovelace-ressourcer.", + "restart": "Du skal genstarte Home Assistant.", + "restart_pending": "Afventer genstart" + }, + "repository_card": { + "dismiss": "Afvis", + "hide": "Skjul", + "information": "Oplysninger", + "new_repository": "Nyt repository", + "not_loaded": "Ikke indlæst", + "open_issue": "Opret issue", + "open_source": "Åbn kilde", + "pending_restart": "Afventer genstart", + "pending_update": "Ventende opdatering", + "reinstall": "Geninstaller", + "report": "Rapporter til fjernelse", + "update_information": "Opdater oplysninger" + }, + "repository": { + "add_to_lovelace": "Tilføj til Lovelace", + "authors": "Forfattere", + "available": "Tilgængelig", + "back_to": "Tilbage til", + "changelog": "Udgivelsesnoter", + "downloads": "Downloads", + "flag_this": "Marker denne", + "frontend_version": "Frontend-version", + "github_stars": "GitHub-stjerner", + "goto_integrations": "Gå til integrationer", + "hide": "Skjul", + "hide_beta": "Skjul beta", + "install": "Installer", + "installed": "Installeret", + "lovelace_copy_example": "Kopiér eksemplet til din Udklipsholder", + "lovelace_instruction": "Tilføj dette til din lovelace-konfiguration", + "lovelace_no_js_type": "Kunne ikke afgøre typen af dette element, tjek venligst repository'et.", + "newest": "nyeste", + "note_appdaemon": "Du skal stadig føje den til filen 'apps.yaml'", + "note_installed": "Når det er installeret, vil dette være placeret i", + "note_integration": "du skal stadig føje den til filen 'configuration.yaml'", + "note_plugin": "du skal stadig tilføje det til din lovelace-konfiguration ('ui-lovelace.yaml' eller Tekstbaseret redigering)", + "note_plugin_post_107": "du skal stadig tilføje det til din lovelace-konfiguration ('configuration.yaml' eller ressourceeditoren '\/config\/lovelace\/resources')", + "open_issue": "Opret issue", + "open_plugin": "Åbn element", + "reinstall": "Geninstaller", + "repository": "Repository", + "restart_home_assistant": "Genstart Home Assistant", + "show_beta": "Vis beta", + "uninstall": "Afinstaller", + "update_information": "Opdater oplysninger", + "upgrade": "Opdater" + }, + "search": { + "installed": "Søg efter installerede repositories", + "installed_new": "Søg efter installerede eller nye repositories", + "placeholder": "Søg efter repository" + }, + "sections": { + "about": { + "description": "Vis information om HACS", + "title": "Om" + }, + "automation": { + "description": "Det er her, du finder python_scripts, AppDaemon-apps og NetDaemon-apps", + "title": "Automatisering" + }, + "frontend": { + "description": "Det er her, du finder temaer, brugerdefinerede kort og andre elementer til lovelace", + "title": "Frontend" + }, + "integrations": { + "description": "Det er her, du finder brugerdefinerede integrationer (custom_components)", + "title": "Integrationer" + }, + "pending_repository_upgrade": "Du kører version {installed}, version {available} er tilgængelig" + }, + "settings": { + "add_custom_repository": "TILFØJ ET BRUGERDEFINERET REPOSITORY", + "adding_new_repo": "Tilføjer nyt repository '{repo}'", + "adding_new_repo_category": "Med kategorien '{category}'.", + "bg_task_custom": "Brugerdefinerede repositories er skjult, mens opgaver i baggrunden kører.", + "category": "Kategori", + "compact_mode": "Kompakt tilstand", + "custom_repositories": "BRUGERDEFINEREDE REPOSITORIES", + "delete": "Slet", + "display": "Visning", + "grid": "Gitter", + "hacs_repo": "HACS-repo", + "hidden_repositories": "Skjulte repositories", + "missing_category": "Du skal vælge en kategori", + "open_repository": "Åbn repository", + "reload_data": "Genindlæs data", + "reload_window": "Genindlæs vindue", + "repository_configuration": "Konfiguration af repository", + "save": "Gem", + "table": "Tabel", + "table_view": "Tabelvisning", + "unhide": "Vis", + "upgrade_all": "Opdater alle" + }, + "store": { + "ascending": "stigende", + "clear_new": "Marker alle som set", + "descending": "faldende", + "last_updated": "Sidst opdateret", + "name": "Navn", + "new_repositories": "Nye repositories", + "new_repositories_note": "Du har over 10 nye repositories, der vises her. Hvis du vil rydde dem alle, skal du klikke på de 3 prikker i øverste højre hjørne og afvise dem alle.", + "no_repositories": "Ingen repositories", + "no_repositories_desc1": "Det ser ud til, at du ikke har nogen repositories installeret i denne sektion endnu.", + "no_repositories_desc2": "Klik på + i nederste hjørne for at tilføje dit første!", + "no_repositories_found_desc1": "Der blev ikke fundet installerede repositories, der matcher \"{searchInput}\" i denne sektion.", + "no_repositories_found_desc2": "Prøv at søge efter noget andet!", + "pending_upgrades": "Ventende opdateringer", + "placeholder_search": "Indtast en søgeterm...", + "sort": "sorter", + "stars": "Stjerner", + "status": "Status" + }, + "time": { + "ago": "siden", + "day": "dag", + "days": "dage", + "hour": "time", + "hours": "timer", + "minute": "minut", + "minutes": "minutter", + "month": "måned", + "months": "måneder", + "one": "Et", + "one_day_ago": "en dag siden", + "one_hour_ago": "en time siden", + "one_minute_ago": "et minut siden", + "one_month_ago": "en måned siden", + "one_second_ago": "et sekund siden", + "one_year_ago": "et år siden", + "second": "sekund", + "seconds": "sekunder", + "x_days_ago": "{x} dage siden", + "x_hours_ago": "{x} timer siden", + "x_minutes_ago": "{x} minutter siden", + "x_months_ago": "{x} måneder siden", + "x_seconds_ago": "{x} sekunder siden", + "x_years_ago": "{x} år siden", + "year": "år", + "years": "år" + } +} \ No newline at end of file diff --git a/custom_components/hacs/translations/de.json b/custom_components/hacs/translations/de.json new file mode 100644 index 0000000..902b61d --- /dev/null +++ b/custom_components/hacs/translations/de.json @@ -0,0 +1,383 @@ +{ + "common": { + "about": "Über", + "add": "hinzufügen", + "appdaemon": "AppDaemon", + "appdaemon_apps": "AppDaemon Apps", + "appdaemon_plural": "AppDaemon Apps", + "background_task": "Hintergrundprozess läuft. Diese Seite lädt neu, sobald dieser fertig ist.", + "cancel": "Abbrechen", + "check_log_file": "Überprüfe die Logdatei für weitere Informationen.", + "continue": "Fortfahren", + "disabled": "Deaktiviert", + "documentation": "Dokumentation", + "element": "Element", + "hacs_is_disabled": "HACS ist deaktiviert", + "ignore": "Ignorieren", + "install": "Installieren", + "installed": "Installiert", + "integration": "Integration", + "integration_plural": "Integrationen", + "integrations": "Integrationen", + "lovelace": "Lovelace", + "lovelace_element": "Lovelace-Element", + "lovelace_elements": "Lovelace-Elemente", + "manage": "verwalten", + "netdaemon": "NetDaemon", + "netdaemon_apps": "NetDaemon Apps", + "netdaemon_plural": "NetDaemon Apps", + "plugin": "Lovelace", + "plugin_plural": "Lovelace-Elemente", + "plugins": "Lovelace-Elemente", + "python_script": "Python Skript", + "python_script_plural": "Python Skripte", + "python_scripts": "Python Skripte", + "reload": "Neu laden", + "repositories": "Repositories", + "repository": "Repository", + "settings": "Einstellungen", + "theme": "Theme", + "theme_plural": "Themes", + "themes": "Themes", + "uninstall": "Deinstallieren", + "update": "Aktualisieren", + "version": "Version" + }, + "config": { + "abort": { + "single_instance_allowed": "Es ist nur eine einzelne HACS-Instanz erlaubt." + }, + "error": { + "acc": "Du musst alle Aussagen bestätigen, bevor du fortfahren kannst", + "auth": "Persönlicher Zugriffstoken ist falsch." + }, + "step": { + "device": { + "description": "Open {url} and paste this key `{code}` to authorize HACS, when you have done that click 'submit'", + "title": "HACS" + }, + "user": { + "data": { + "acc_addons": "I know that there are no add-ons in HACS", + "acc_disable": "I know that if I get issues with Home Assistant I should disable all my custom_components", + "acc_logs": "I know how to access Home Assistant logs", + "acc_untested": "I know that everything inside HACS is custom and untested by Home Assistant", + "appdaemon": "Enable AppDaemon apps discovery & tracking", + "netdaemon": "Enable NetDaemon apps discovery & tracking", + "python_script": "Enable python_scripts discovery & tracking", + "sidepanel_icon": "Side panel icon", + "sidepanel_title": "Side panel title", + "theme": "Enable Themes discovery & tracking", + "token": "GitHub Personal Access Token" + }, + "description": "Before you can setup HACS you need to acknowledge the following", + "title": "HACS" + } + }, + "title": "HACS (Home Assistant Community Store)" + }, + "confirm": { + "add_to_lovelace": "Möchtest du dies wirklich zu deinen Lovelace-Ressourcen hinzufügen?", + "bg_task": "Die Aktion ist deaktiviert, während Hintergrundprozesse ausgeführt werden.", + "cancel": "Abbrechen", + "continue": "Bist du dir sicher, dass du fortfahren möchtest?", + "delete": "Möchtest du '{item}' wirklich löschen?", + "delete_installed": "'{item}' ist installiert. Du musst es deinstallieren, bevor du es löschen kannst.", + "exist": "{item} existiert bereits", + "generic": "Bist du dir sicher?", + "home_assistant_is_restarting": "Bitte warte, Home Assistant wird jetzt neu gestartet.", + "home_assistant_version_not_correct": "Du benutzt die Home Assistant-Version '{haversion}', für dieses Repository muss jedoch die Mindestversion '{minversion}' installiert sein.", + "no": "Nein", + "no_upgrades": "Keine Upgrades ausstehend", + "ok": "OK", + "overwrite": "Dadurch wird die Datei überschrieben.", + "reload_data": "Hierdurch werden die Daten aller Repositories die HACS kennt neu geladen. Dies wird einige Zeit in Anspruch nehmen.", + "restart_home_assistant": "Bist du dir sicher, dass du Home Assistant neu starten möchtest?", + "uninstall": "Möchtest du '{item}' wirklich deinstallieren?", + "upgrade_all": "Hierdurch werden all diese Repositories aktualisiert. Stelle sicher, dass du die Versionshinweise vorher gelesen hast.", + "yes": "Ja" + }, + "dialog_about": { + "frontend_version": "Frontend Version", + "installed_repositories": "Installierte Repositories", + "integration_version": "Integrations Version", + "useful_links": "Nützliche Links" + }, + "dialog_add_repo": { + "limit": "Es werden nur die ersten 100 Repositories angezeigt. Verwende die Suche, um zu filtern, was du benötigst", + "no_match": "Es wurden keine Repositories gefunden, die deinen Filter entsprechen", + "sort_by": "Sortiere nach", + "title": "Repository hinzufügen" + }, + "dialog_custom_repositories": { + "category": "Kategorie", + "no_category": "Fehlende Kategorie", + "no_repository": "Fehlendes Repository", + "title": "Benutzerdefinierte Repositories", + "url_placeholder": "Füge eine benutzerdefinierte Repository-URL hinzu" + }, + "dialog_info": { + "author": "Autor", + "downloads": "Downloads", + "install": "Installiere dieses Repository in HACS", + "loading": "Informationen laden...", + "no_info": "Der Entwickler hat keine weiteren Informationen für dieses Repository bereitgestellt", + "open_issues": "Probleme melden", + "open_repo": "Repository öffnen", + "stars": "Sterne", + "version_installed": "Version installiert" + }, + "dialog_install": { + "restart": "Denke daran, dass du Home Assistant neu starten musst, bevor Änderungen an Integrationen (custom_components) angewendet werden.", + "select_version": "Version auswählen", + "show_beta": "Beta-Versionen anzeigen", + "type": "Typ", + "url": "URL" + }, + "dialog_removed": { + "link": "Externer Link zu weiteren Informationen", + "name": "Repository-Name", + "reason": "Grund für die Entfernung", + "type": "Art der Entfernung" + }, + "dialog_update": { + "available_version": "Verfügbare Version", + "changelog": "Änderungsprotokoll", + "installed_version": "Installierte Version", + "releasenotes": "Releasenotes für {release}", + "title": "Update ausstehend" + }, + "dialog": { + "reload": { + "confirm": "Willst du das jetzt machen?", + "description": "Du musst deinen Browser-Cache leeren, wenn du Lovelace-Ressourcen änderst." + } + }, + "entry": { + "information": "Information", + "intro": "Aktualisierungen und wichtige Meldungen werden hier angezeigt, falls vorhanden", + "messages": { + "disabled": { + "content": "Überprüfe die Logdatei für weitere Informationen", + "title": "HACS ist deaktiviert" + }, + "has_pending_tasks": { + "content": "Einige Repositorys werden möglicherweise erst angezeigt, wenn dies abgeschlossen ist", + "title": "Hintergrundaufgaben stehen noch aus" + }, + "removed": "Repository '{repository}' gelöscht", + "resources": { + "content": "Du hast {number} Lovelace-Elemente, die in Lovelace nicht richtig geladen sind.", + "title": "Nicht in Lovelace geladen" + }, + "restart": { + "content": "Du hast {number} Integrationen, die einen Neustart von Home Assistant erfordern. Dies kannst du im Abschnitt 'Server Controls' im Konfigurationsteil der Home Assistant-Benutzeroberfläche tun.", + "title": "Ausstehender Neustart" + }, + "setup": { + "content": "HACS wird gerade eingerichtet, während dieser Zeit könnten einige Informationen fehlen oder falsch sein", + "title": "HACS wird eingerichtet" + }, + "startup": { + "content": "HACS wird gestartet, während dieser Zeit könnten einige Informationen fehlen oder falsch sein", + "title": "HACS startet" + }, + "waiting": { + "content": "HACS wartet darauf, dass Home Assistant den Start beendet, bevor mit den Startaufgaben gestartet wird", + "title": "HACS wartet" + }, + "wrong_frontend_installed": { + "content": "Du hast {running} des HACS-Frontends installiert, aber Version {expected} wurde erwartet. Wenn diese Meldung angezeigt wird, dass Home Assistant die neue Version nicht installieren konnte, starte Home Assistant neu.", + "title": "Unerwartete Frontend-Version" + }, + "wrong_frontend_loaded": { + "content": "Du führst die Version {running} des HACS-Frontends aus, aber es wurde die Version {expected} erwartet. Du solltest deinen Browser-Cache leeren.", + "title": "Unerwartete Frontend-Version" + } + }, + "pending_updates": "Ausstehende Updates" + }, + "menu": { + "about": "Über HACS", + "clear": "Alles neue als gesehen markieren", + "custom_repositories": "Benutzerdefinierte Repositories", + "dismiss": "Alle neuen Repositories ausblenden", + "documentation": "Dokumentation", + "open_issue": "Problem melden", + "reload": "Fenster neu laden" + }, + "options": { + "step": { + "user": { + "data": { + "appdaemon": "AppDaemon App-Entdeckung & Nachverfolgung aktivieren", + "country": "Nach Ländercode filtern.", + "debug": "Debug aktivieren.", + "experimental": "Experimentelle Funktionen aktivieren", + "netdaemon": "NetDaemon App-Entdeckung & Nachverfolgung aktivieren", + "not_in_use": "Nicht in Verwendung mit YAML", + "release_limit": "Anzahl anzuzeigender Releases.", + "sidepanel_icon": "Sidepanel Symbol", + "sidepanel_title": "Sidepanel Titel" + } + } + } + }, + "repository_banner": { + "config_flow": "Diese Integration unterstützt config_flow. Das bedeutet, dass du jetzt in den Integrationsbereich deiner Benutzeroberfläche wechseln kannst, um es zu konfigurieren.", + "config_flow_title": "UI-Konfiguration unterstützt", + "integration_not_loaded": "Diese Integration ist in Home Assistant nicht geladen.", + "no_restart_required": "Kein Neustart erforderlich", + "not_loaded": "Nicht geladen", + "plugin_not_loaded": "Dieses Element wird nicht zu deinen Lovelace-Ressourcen hinzugefügt.", + "restart": "Du musst Home Assistant neu starten.", + "restart_pending": "Neustart ausstehend" + }, + "repository_card": { + "dismiss": "Ausblenden", + "hide": "Verstecken", + "information": "Information", + "new_repository": "Neues Repository", + "not_loaded": "Nicht geladen", + "open_issue": "Problem melden", + "open_source": "Quelldateien öffnen", + "pending_restart": "Ausstehender Neustart", + "pending_update": "Ausstehende Aktualisierung", + "reinstall": "Neu installieren", + "report": "Melden zur Entfernung des Repositorys", + "update_information": "Aktualisierungsinformationen" + }, + "repository": { + "add_to_lovelace": "Zu Lovelace hinzufügen", + "authors": "Autoren", + "available": "Verfügbar", + "back_to": "Zurück zu", + "changelog": "Änderungsprotokoll", + "downloads": "Downloads", + "flag_this": "Melden", + "frontend_version": "Frontend Version", + "github_stars": "GitHub Sterne", + "goto_integrations": "Gehe zu Integrationen", + "hide": "Verstecken", + "hide_beta": "Beta verstecken", + "install": "Installieren", + "installed": "Installiert", + "lovelace_copy_example": "Beispiel in die Zwischenablage kopieren", + "lovelace_instruction": "Zum Hinzufügen zu deinen Lovelace-Einstellungen, benutze Folgendes", + "lovelace_no_js_type": "Der Typ dieses Elements konnte nicht ermittelt werden. Überprüfe das Repository.", + "newest": "neueste", + "note_appdaemon": "du musst es dann noch in die Datei 'apps.yaml' hinzufügen", + "note_installed": "Wird installiert nach", + "note_integration": "du musst es dann noch in die Datei 'configuration.yaml' hinzufügen", + "note_plugin": "du musst es dann noch in deine Lovelace-Einstellungen ('ui-lovelace.yaml' oder im Raw-Konfigurationseditor) hinzufügen", + "note_plugin_post_107": "Du musst es noch zu deiner Lovelace-Konfiguration hinzufügen ('configuration.yaml' oder der Ressourceneditor '\/config\/lovelace\/resources')", + "open_issue": "Problem melden", + "open_plugin": "Element öffnen", + "reinstall": "Neu installieren", + "repository": "Repository", + "restart_home_assistant": "Home Assistant neu starten", + "show_beta": "Beta anzeigen", + "uninstall": "Deinstallieren", + "update_information": "Aktualisierungsinformationen", + "upgrade": "Aktualisieren" + }, + "search": { + "installed": "Suche nach installierten Repositories", + "installed_new": "Suche nach installierten oder neuen Repositories", + "placeholder": "Suche nach Repository" + }, + "sections": { + "about": { + "description": "Informationen zu HACS anzeigen", + "title": "Über" + }, + "addon": { + "description": "Es gibt in HACS selbst keine Add-ons, aber du kannst hier klicken, um zum Supervisor zu gelangen", + "title": "Add-ons" + }, + "automation": { + "description": "Hier findest du python_scripts, AppDaemon-Apps und NetDaemon-Apps", + "title": "Automatisierung" + }, + "frontend": { + "description": "Hier findest du Themen, individuelle Karten und andere Elemente für Lovelace", + "title": "Frontend" + }, + "integrations": { + "description": "Hier findest du benutzerdefinierte Integrationen (custom_components)", + "title": "Integrationen" + }, + "pending_repository_upgrade": "Du verwendest Version {installed}, Version {available} ist verfügbar" + }, + "settings": { + "add_custom_repository": "BENUTZERDEFINIERTES REPOSITORY HINZUFÜGEN", + "adding_new_repo": "Hinzufügen eines neuen Repository '{repo}'", + "adding_new_repo_category": "Mit der Kategorie '{category}'.", + "bg_task_custom": "Custom Repositorys werden ausgeblendet, während Hintergrundaufgaben ausgeführt werden.", + "category": "Kategorie", + "compact_mode": "Kompakter Modus", + "custom_repositories": "BENUTZERDEFINIERTE REPOSITORIES", + "delete": "Löschen", + "display": "Anzeige", + "grid": "Gitter", + "hacs_repo": "HACS repo", + "hidden_repositories": "versteckte Repositories", + "missing_category": "Du musst eine Kategorie auswählen.", + "open_repository": "Repository öffnen", + "reload_data": "Daten neu laden", + "reload_window": "Fenster neu laden", + "repository_configuration": "Repository Konfiguration", + "save": "Speichern", + "table": "Tabelle", + "table_view": "Tabellenansicht", + "unhide": "einblenden", + "upgrade_all": "Alles aktualisieren" + }, + "store": { + "ascending": "Aufsteigend", + "clear_new": "Alle neuen Repositories als gesehen markieren", + "descending": "Absteigend", + "last_updated": "Zuletzt aktualisiert", + "name": "Name", + "new_repositories": "Neue Repositories", + "new_repositories_note": "Hier werden über 10 neue Repositorys angezeigt. Wenn du alle als gelesen markieren möchten, klicke auf die 3 Punkte in der oberen rechten Ecke und blende alle aus.", + "no_repositories": "Keine Repositories", + "no_repositories_desc1": "Anscheinend sind in diesem Abschnitt noch keine Repositories installiert.", + "no_repositories_desc2": "Klicken auf das + in der unteren Ecke, um dein erstes hinzuzufügen!", + "no_repositories_found_desc1": "In diesem Abschnitt wurden keine installierten Repositorys gefunden, die mit \"{searchInput}\" übereinstimmen.", + "no_repositories_found_desc2": "Versuche, nach etwas anderem zu suchen!", + "pending_upgrades": "Ausstehende Upgrades", + "placeholder_search": "Suchbegriff eingeben…", + "sort": "Sortieren", + "stars": "Sterne", + "status": "Status" + }, + "time": { + "ago": "vor", + "day": "Tag", + "days": "Tage", + "hour": "Stunde", + "hours": "Stunden", + "minute": "Minute", + "minutes": "Minuten", + "month": "Monat", + "months": "Monate", + "one": "Eins", + "one_day_ago": "vor einem Tag", + "one_hour_ago": "vor einer Stunde", + "one_minute_ago": "vor einer Minute", + "one_month_ago": "vor einem Monat", + "one_second_ago": "vor einer Sekunde", + "one_year_ago": "vor einem Jahr", + "second": "Sekunde", + "seconds": "Sekunden", + "x_days_ago": "vor {x} Tagen", + "x_hours_ago": "vor {x} Stunden", + "x_minutes_ago": "vor {x} Minuten", + "x_months_ago": "vor {x} Monaten", + "x_seconds_ago": "vor {x} Sekunden", + "x_years_ago": "vor {x} Jahren", + "year": "Jahr", + "years": "Jahre" + } +} \ No newline at end of file diff --git a/custom_components/hacs/.translations/el.json b/custom_components/hacs/translations/el.json similarity index 85% rename from custom_components/hacs/.translations/el.json rename to custom_components/hacs/translations/el.json index 0d446ad..e783433 100644 --- a/custom_components/hacs/.translations/el.json +++ b/custom_components/hacs/translations/el.json @@ -12,9 +12,10 @@ "installed": "εγκατεστημένο", "integration": "Ενσωμάτωση", "integrations": "Ενσωματωμένα", + "manage": "διαχειρίζονται", "netdaemon": "NetDaemon", "netdaemon_apps": "NetDaemon Apps", - "plugin": "Πρόσθετο", + "plugin": "Lovelace", "plugins": "Πρόσθετα", "python_script": "Πρόγραμμα Python", "python_scripts": "Προγράμματα Python", @@ -25,25 +26,27 @@ "version": "Έκδοση" }, "config": { - "abort": { - "single_instance_allowed": "Μονάχα μία ρύθμιση των παραμέτρων του HACS επιτρέπεται." - }, - "error": { - "auth": "Το διακριτικό πρόσβασης δεν είναι σωστό." - }, "step": { + "device": { + "description": "Open {url} and paste this key `{code}` to authorize HACS, when you have done that click 'submit'", + "title": "HACS" + }, "user": { "data": { - "appdaemon": "Ενεργοποίηση εύρεσης & παρακολούθησης για το AppDaemon", - "netdaemon": "Ενεργοποίηση εύρεσης & παρακολούθησης για το NetDaemon", - "python_script": "Ενεργοποίηση εύρεσης & παρακολούθησης για τα python_scripts", - "sidepanel_icon": "Εικονίδιο πλαϊνού πάνελ", - "sidepanel_title": "Τίτλος πλαϊνού πάνελ", - "theme": "Ενεργοποίηση εύρεσης & παρακολούθησης για τα θεμάτων", - "token": "Διακριτικό πρόσβασης του GitHub" + "acc_addons": "I know that there are no add-ons in HACS", + "acc_disable": "I know that if I get issues with Home Assistant I should disable all my custom_components", + "acc_logs": "I know how to access Home Assistant logs", + "acc_untested": "I know that everything inside HACS is custom and untested by Home Assistant", + "appdaemon": "Enable AppDaemon apps discovery & tracking", + "netdaemon": "Enable NetDaemon apps discovery & tracking", + "python_script": "Enable python_scripts discovery & tracking", + "sidepanel_icon": "Side panel icon", + "sidepanel_title": "Side panel title", + "theme": "Enable Themes discovery & tracking", + "token": "GitHub Personal Access Token" }, - "description": "Εάν χρειαστείτε βοήθεια με τις ρυθμίσεις ανατρέξτε εδώ: https:\/\/hacs.xyz\/docs\/configuration\/start", - "title": "HACS (Home Assistant Community Store)" + "description": "Before you can setup HACS you need to acknowledge the following", + "title": "HACS" } }, "title": "HACS (Home Assistant Community Store)" @@ -76,6 +79,7 @@ "debug": "Ενεργοποίηση εντοπισμού σφαλμάτων.", "experimental": "Ενεργοποίση πειραματικών λειτουργιών", "netdaemon": "Ενεργοποίηση εύρεσης & παρακολούθησης για το NetDaemon", + "not_in_use": "Δεν χρησιμοποιείται με το YAML", "release_limit": "Αριθμός εκδόσεων που να παραθέτονται.", "sidepanel_icon": "Εικονίδιο πλαϊνού πάνελ", "sidepanel_title": "Τίτλος πλαϊνού πάνελ" @@ -100,6 +104,8 @@ "downloads": "Λήψεις", "flag_this": "Σημείωσε αυτό", "frontend_version": "Έκδοση Frontend", + "github_stars": "GitHub αστέρια", + "goto_integrations": "Μετάβαση στις ενσωματώσεις", "hide": "Απόκρυψη", "hide_beta": "Απόκριση του beta", "install": "Εγκατάσταση", @@ -124,6 +130,7 @@ }, "settings": { "add_custom_repository": "ΠΡΟΣΘΕΣΤΕ ΕΝΑ ΕΙΔΙΚΟ ΑΠΟΘΕΤΗΡΙΟ", + "adding_new_repo": "Προσθήκη νέου αποθετηρίου '{repo}'", "adding_new_repo_category": "Με κατηγορία '{category}'.", "category": "Κατηγορία", "compact_mode": "Συμπαγής λειτουργία", @@ -137,9 +144,11 @@ "open_repository": "Ανοίξτε το αποθετήριο", "reload_data": "Επαναφόρτωση δεδομένων", "reload_window": "Επαναφόρτωση του παραθύρου", + "repository_configuration": "Διαμόρφωση αποθετηρίου", "save": "Αποθήκευση", "table": "Πίνακας", "table_view": "Προβολή πίνακα", + "unhide": "αποκρύψω", "upgrade_all": "Αναβάθμιση όλων" }, "store": { diff --git a/custom_components/hacs/translations/en.json b/custom_components/hacs/translations/en.json new file mode 100644 index 0000000..da9080c --- /dev/null +++ b/custom_components/hacs/translations/en.json @@ -0,0 +1,384 @@ +{ + "common": { + "about": "About", + "add": "add", + "appdaemon": "AppDaemon", + "appdaemon_apps": "AppDaemon Apps", + "appdaemon_plural": "AppDaemon Apps", + "background_task": "Background task running, this page will reload when it's done.", + "cancel": "Cancel", + "check_log_file": "Check your log file for more details.", + "continue": "Continue", + "disabled": "Disabled", + "documentation": "Documentation", + "element": "element", + "hacs_is_disabled": "HACS is disabled", + "ignore": "Ignore", + "install": "Install", + "installed": "installed", + "integration": "Integration", + "integration_plural": "Integrations", + "integrations": "Integrations", + "lovelace": "Lovelace", + "lovelace_element": "Lovelace element", + "lovelace_elements": "Lovelace elements", + "manage": "manage", + "netdaemon": "NetDaemon", + "netdaemon_apps": "NetDaemon Apps", + "netdaemon_plural": "NetDaemon Apps", + "plugin": "Lovelace", + "plugin_plural": "Lovelace elements", + "plugins": "Lovelace elements", + "python_script": "Python Script", + "python_script_plural": "Python Scripts", + "python_scripts": "Python Scripts", + "reload": "Reload", + "repositories": "Repositories", + "repository": "Repository", + "settings": "settings", + "theme": "Theme", + "theme_plural": "Themes", + "themes": "Themes", + "uninstall": "Uninstall", + "update": "Update", + "version": "Version" + }, + "config": { + "abort": { + "single_instance_allowed": "Only a single configuration of HACS is allowed." + }, + "error": { + "acc": "You need to acknowledge all the statements before continuing", + "auth": "Personal Access Token is not correct" + }, + "step": { + "device": { + "description": "Open {url} and paste this key to authorize HACS: \\n```\\n{code}\\n```\\n When you have done that click 'submit'", + "title": "HACS" + }, + "user": { + "data": { + "acc_addons": "I know that there are no add-ons in HACS", + "acc_disable": "I know that if I get issues with Home Assistant I should disable all my custom_components", + "acc_logs": "I know how to access Home Assistant logs", + "acc_untested": "I know that everything inside HACS is custom and untested by Home Assistant", + "appdaemon": "Enable AppDaemon apps discovery & tracking", + "netdaemon": "Enable NetDaemon apps discovery & tracking", + "python_script": "Enable python_scripts discovery & tracking", + "sidepanel_icon": "Side panel icon", + "sidepanel_title": "Side panel title", + "theme": "Enable Themes discovery & tracking", + "token": "GitHub Personal Access Token" + }, + "description": "Before you can setup HACS you need to acknowledge the following", + "title": "HACS" + } + }, + "title": "HACS (Home Assistant Community Store)" + }, + "confirm": { + "add_to_lovelace": "Are you sure you want to add this to your Lovelace resources?", + "bg_task": "Action is disabled while background tasks is running.", + "cancel": "Cancel", + "continue": "Are you sure you want to continue?", + "delete": "Are you sure you want to delete '{item}'?", + "delete_installed": "'{item}' is installed, you need to uninstall it before you can delete it.", + "exist": "{item} already exists", + "generic": "Are you sure?", + "home_assistant_is_restarting": "Hold on, Home Assistant is now restarting.", + "home_assistant_version_not_correct": "You are running Home Assistant version '{haversion}', but this repository requires minimum version '{minversion}' to be installed.", + "no": "No", + "no_upgrades": "No upgrades pending", + "ok": "OK", + "overwrite": "Doing this will overwrite it.", + "reload_data": "This reloads the data of all repositories HACS knows about, this will take some time to finish.", + "restart_home_assistant": "Are you sure you want to restart Home Assistant?", + "uninstall": "Are you sure you want to uninstall '{item}'?", + "upgrade_all": "This will upgrade all of these repositories, make sure that you have read the release notes for all of them before proceeding.", + "yes": "Yes" + }, + "dialog_about": { + "frontend_version": "Frontend version", + "installed_repositories": "Installed repositories", + "integration_version": "Integration version", + "useful_links": "Useful links" + }, + "dialog_add_repo": { + "limit": "Only the first 100 repositories are shown, use the search to filter what you need", + "no_match": "No repositories found matching your filter", + "sort_by": "Sort by", + "title": "Add repository" + }, + "dialog_custom_repositories": { + "category": "Category", + "no_category": "Missing category", + "no_repository": "Missing repository", + "title": "Custom repositories", + "url_placeholder": "Add custom repository URL" + }, + "dialog_info": { + "author": "Author", + "downloads": "Downloads", + "install": "Install this repository in HACS", + "loading": "Loading information...", + "no_info": "The developer has not provided any more information for this repository", + "open_issues": "Open issues", + "open_repo": "Open repository", + "stars": "Stars", + "version_installed": "Version installed" + }, + "dialog_install": { + "restart": "Remember that you need to restart Home Assistant before changes to integrations (custom_components) are applied.", + "select_version": "Select version", + "show_beta": "Show beta versions", + "type": "Type", + "url": "URL" + }, + "dialog_removed": { + "link": "External link to more information", + "name": "Repository name", + "reason": "Removal reason", + "type": "Removal type" + }, + "dialog_update": { + "available_version": "Available version", + "changelog": "Changelog", + "installed_version": "Installed version", + "releasenotes": "Release notes for {release}", + "title": "Update pending" + }, + "dialog": { + "reload": { + "confirm": "Do you want to do that now?", + "description": "You need to clear your browser cache when changing Lovelace resources." + } + }, + "entry": { + "information": "Information", + "intro": "Updates and important messages will show here if there are any", + "messages": { + "disabled": { + "content": "Check your log file for more details", + "title": "HACS is disabled" + }, + "has_pending_tasks": { + "content": "Some repositories might not show until this is completed", + "title": "Background tasks pending" + }, + "removed": "Removed repository '{repository}'", + "resources": { + "content": "You have {number} Lovelace elements that are not loaded properly in Lovelace.", + "title": "Not loaded in Lovelace" + }, + "restart": { + "content": "You have {number} integrations that requires a restart of Home Assistant, you can do that from the 'Server Controls' section under the configuration part of Home Assistant UI.", + "title": "Pending restart" + }, + "setup": { + "content": "HACS is setting up, during this time some information might be missing or incorrect", + "title": "HACS is setting up" + }, + "startup": { + "content": "HACS is starting up, during this time some information might be missing or incorrect", + "title": "HACS is starting up" + }, + "waiting": { + "content": "HACS is waiting for Home Assistant to finish startup before starting startup tasks", + "title": "HACS is waiting" + }, + "wrong_frontend_installed": { + "content": "You have {running} of the HACS frontend installed, but version {expected} was expected, if this you see this message Home Assistant was not able to install the new version, try restarting Home Assistant.", + "title": "Unexpected frontend version" + }, + "wrong_frontend_loaded": { + "content": "You are running version {running} of the HACS frontend, but version {expected} was expected, you should clear your browser cache.", + "title": "Unexpected frontend version" + } + }, + "pending_updates": "Pending updates" + }, + "menu": { + "about": "About HACS", + "clear": "Clear all new", + "custom_repositories": "Custom repositories", + "dismiss": "Dismiss all new repositories", + "documentation": "Documentation", + "open_issue": "Open issue", + "reload": "Reload window" + }, + "options": { + "step": { + "user": { + "data": { + "appdaemon": "Enable AppDaemon apps discovery & tracking", + "country": "Filter with country code.", + "debug": "Enable debug.", + "experimental": "Enable experimental features", + "netdaemon": "Enable NetDaemon apps discovery & tracking", + "not_in_use": "Not in use with YAML", + "release_limit": "Number of releases to show.", + "sidepanel_icon": "Side panel icon", + "sidepanel_title": "Side panel title" + } + } + } + }, + "repository_banner": { + "config_flow": "This integration supports config_flow, that means that you now can go to the integration section of your UI to configure it.", + "config_flow_title": "UI Configuration supported", + "integration_not_loaded": "This integration is not loaded in Home Assistant.", + "no_restart_required": "No restart required", + "not_loaded": "Not loaded", + "plugin_not_loaded": "This element is not added to your Lovelace resources.", + "restart": "You need to restart Home Assistant.", + "restart_pending": "Restart pending" + }, + "repository_card": { + "dismiss": "dismiss", + "hide": "Hide", + "information": "Information", + "new_repository": "New repository", + "not_loaded": "Not loaded", + "open_issue": "Open issue", + "open_source": "Open source", + "pending_restart": "Pending restart", + "pending_update": "Pending update", + "reinstall": "Reinstall", + "report": "Report for removal", + "update_information": "Update information" + }, + "repository": { + "add_to_lovelace": "Add to Lovelace", + "authors": "Authors", + "available": "Available", + "back_to": "Back to", + "changelog": "Change log", + "downloads": "Downloads", + "flag_this": "Flag this", + "frontend_version": "Frontend version", + "github_stars": "GitHub stars", + "goto_integrations": "Go to integrations", + "hide": "Hide", + "hide_beta": "Hide beta", + "install": "Install", + "installed": "Installed", + "lovelace_copy_example": "Copy the example to your clipboard", + "lovelace_instruction": "When you add this to your lovelace configuration use this", + "lovelace_no_js_type": "Could not determine the type of this element, check the repository.", + "newest": "newest", + "note_appdaemon": "you still need to add it to your 'apps.yaml' file", + "note_installed": "When installed, this will be located in", + "note_integration": "you still need to add it to your 'configuration.yaml' file", + "note_plugin": "you still need to add it to your lovelace configuration ('ui-lovelace.yaml' or the raw UI config editor)", + "note_plugin_post_107": "you still need to add it to your lovelace configuration ('configuration.yaml' or the resource editor '\/config\/lovelace\/resources')", + "open_issue": "Open issue", + "open_plugin": "Open element", + "reinstall": "Reinstall", + "repository": "Repository", + "restart_home_assistant": "Restart Home Assistant", + "show_beta": "Show beta", + "uninstall": "Uninstall", + "update_information": "Update information", + "upgrade": "Update" + }, + "search": { + "installed": "Search for installed repositories", + "installed_new": "Search for installed or new repositories", + "placeholder": "Search for repository" + }, + "sections": { + "about": { + "description": "Show information about HACS", + "title": "About" + }, + "addon": { + "description": "There are no addons in HACS, but you can click here to get to the supervisor", + "title": "Add-ons" + }, + "automation": { + "description": "This is where you find python_scripts, AppDaemon apps and NetDaemon apps", + "title": "Automation" + }, + "frontend": { + "description": "This is where you find themes, custom cards and other elements for lovelace", + "title": "Frontend" + }, + "integrations": { + "description": "This is where you find custom integrations (custom_components)", + "title": "Integrations" + }, + "pending_repository_upgrade": "You are running version {installed}, version {available} is available" + }, + "settings": { + "add_custom_repository": "ADD CUSTOM REPOSITORY", + "adding_new_repo": "Adding new repository '{repo}'", + "adding_new_repo_category": "With category '{category}'.", + "bg_task_custom": "Custom repositories are hidden while background tasks is running.", + "category": "Category", + "compact_mode": "Compact mode", + "custom_repositories": "CUSTOM REPOSITORIES", + "delete": "Delete", + "display": "Display", + "grid": "Grid", + "hacs_repo": "HACS repo", + "hidden_repositories": "hidden repositories", + "missing_category": "You need to select a category", + "open_repository": "Open repository", + "reload_data": "Reload data", + "reload_window": "Reload window", + "repository_configuration": "Repository configuration", + "save": "Save", + "table": "Table", + "table_view": "Table view", + "unhide": "unhide", + "upgrade_all": "Upgrade all" + }, + "store": { + "add": "Explore & add repositories", + "ascending": "ascending", + "clear_new": "Clear all new repositories", + "descending": "descending", + "last_updated": "Last updated", + "name": "Name", + "new_repositories": "New Repositories", + "new_repositories_note": "You have over 10 new repositories showing here, if you want to clear them all click the 3 dots in the top right corner and dismiss all of them.", + "no_repositories": "No repositories", + "no_repositories_desc1": "It seems like you don't have any repositories installed in this section yet.", + "no_repositories_desc2": "Click on the + in the bottom corner to add your first!", + "no_repositories_found_desc1": "No installed repositories matching \"{searchInput}\" found in this section.", + "no_repositories_found_desc2": "Try searching for something else!", + "pending_upgrades": "Pending upgrades", + "placeholder_search": "Please enter a search term...", + "sort": "sort", + "stars": "Stars", + "status": "Status" + }, + "time": { + "ago": "ago", + "day": "day", + "days": "days", + "hour": "hour", + "hours": "hours", + "minute": "minute", + "minutes": "minutes", + "month": "month", + "months": "months", + "one": "One", + "one_day_ago": "one day ago", + "one_hour_ago": "one hour ago", + "one_minute_ago": "one minute ago", + "one_month_ago": "one month ago", + "one_second_ago": "one second ago", + "one_year_ago": "one year ago", + "second": "second", + "seconds": "seconds", + "x_days_ago": "{x} days ago", + "x_hours_ago": "{x} hours ago", + "x_minutes_ago": "{x} minutes ago", + "x_months_ago": "{x} months ago", + "x_seconds_ago": "{x} seconds ago", + "x_years_ago": "{x} years ago", + "year": "year", + "years": "years" + } +} \ No newline at end of file diff --git a/custom_components/hacs/translations/es.json b/custom_components/hacs/translations/es.json new file mode 100644 index 0000000..363db32 --- /dev/null +++ b/custom_components/hacs/translations/es.json @@ -0,0 +1,376 @@ +{ + "common": { + "about": "Acerca de", + "add": "añadir", + "appdaemon": "AppDaemon", + "appdaemon_apps": "AppDaemon Apps", + "appdaemon_plural": "AppDaemon Apps", + "background_task": "Ejecutando tareas en segundo plano. Se refrescará automaticamente esta página al finalizar.", + "cancel": "Cancelar", + "check_log_file": "Compruebe el archivo de registro para obtener más detalles.", + "continue": "Continuar", + "disabled": "Deshabilitado", + "documentation": "Documentación", + "element": "elemento", + "hacs_is_disabled": "HACS está deshabilitado", + "ignore": "Ignorar", + "install": "Instalar", + "installed": "instalado", + "integration": "Integración", + "integration_plural": "Integraciones", + "integrations": "Integraciones", + "lovelace": "Lovelace", + "lovelace_element": "Elemento de Lovelace", + "lovelace_elements": "Elementos de Lovelace", + "manage": "Administrar", + "netdaemon": "NetDaemon", + "netdaemon_apps": "NetDaemon Apps", + "netdaemon_plural": "Aplicaciones NetDaemon", + "plugin": "Lovelace", + "plugin_plural": "Elementos de Lovelace", + "plugins": "Elementos de Lovelace", + "python_script": "Python Script", + "python_script_plural": "Python Scripts", + "python_scripts": "Python Scripts", + "reload": "Recargar", + "repositories": "Repositorios", + "repository": "Repositorio", + "settings": "configuraciones", + "theme": "Tema", + "theme_plural": "Temas", + "themes": "Temas", + "uninstall": "Desinstalar", + "update": "Actualizar", + "version": "Versión" + }, + "config": { + "step": { + "device": { + "description": "Open {url} and paste this key `{code}` to authorize HACS, when you have done that click 'submit'", + "title": "HACS" + }, + "user": { + "data": { + "acc_addons": "I know that there are no add-ons in HACS", + "acc_disable": "I know that if I get issues with Home Assistant I should disable all my custom_components", + "acc_logs": "I know how to access Home Assistant logs", + "acc_untested": "I know that everything inside HACS is custom and untested by Home Assistant", + "appdaemon": "Enable AppDaemon apps discovery & tracking", + "netdaemon": "Enable NetDaemon apps discovery & tracking", + "python_script": "Enable python_scripts discovery & tracking", + "sidepanel_icon": "Side panel icon", + "sidepanel_title": "Side panel title", + "theme": "Enable Themes discovery & tracking", + "token": "GitHub Personal Access Token" + }, + "description": "Before you can setup HACS you need to acknowledge the following", + "title": "HACS" + } + }, + "title": "HACS (Home Assistant Community Store)" + }, + "confirm": { + "add_to_lovelace": "¿Está seguro de que desea agregar esto a sus recursos de Lovelace?", + "bg_task": "La acción está deshabilitada mientras se ejecutan tareas en segundo plano.", + "cancel": "Cancelar", + "continue": "Estás seguro de que quieres continuar?", + "delete": "¿Seguro que quieres eliminar '{item}'?", + "delete_installed": "'{item}' está instalado, debe desinstalarlo antes de poder eliminarlo.", + "exist": "{item} ya existe", + "generic": "¿Estás seguro?", + "home_assistant_is_restarting": "Espera, Home Assistant se está reiniciando.", + "home_assistant_version_not_correct": "Está ejecutando la versión '{haversion}' de Home Assistant, pero este repositorio requiere la instalación de la versión '{minversion}' mínima.", + "no": "No", + "no_upgrades": "No hay actualizaciones pendientes", + "ok": "OK", + "overwrite": "Si haces esto, se sobrescribirá.", + "reload_data": "Esto recarga los datos de todos los repositorios que HACS conoce, esto tardará algún tiempo en finalizar.", + "restart_home_assistant": "¿Está seguro de que desea reiniciar Home Assistant?", + "uninstall": "¿Está seguro de que deseas desinstalar '{item}'?", + "upgrade_all": "Esto actualizará todos estos repositorios, asegúrese de que ha leído las notas de la versión de todos ellos antes de continuar.", + "yes": "Si" + }, + "dialog_about": { + "frontend_version": "Versión del front-end", + "installed_repositories": "Repositorios instalados", + "integration_version": "Versión de la integración", + "useful_links": "Enlaces útiles" + }, + "dialog_add_repo": { + "limit": "Sólo se muestran los primeros 100 repositorios, utilice la búsqueda para filtrar lo que necesita", + "no_match": "No se han encontrado repositorios que coincidan con el filtro", + "sort_by": "Ordenar por", + "title": "Añadir repositorio" + }, + "dialog_custom_repositories": { + "category": "Categoría", + "no_category": "Categoría que falta", + "no_repository": "Falta el repositorio", + "title": "Repositorios personalizados", + "url_placeholder": "Agrega la URL del repositorio personalizado que deseas añadir" + }, + "dialog_info": { + "author": "Autor", + "downloads": "Descargas", + "install": "Instalar este repositorio en HACS", + "loading": "Cargando información ...", + "no_info": "El desarrollador no ha proporcionado más información para este repositorio", + "open_issues": "Abrir incidencias", + "open_repo": "Abrir repositorio", + "stars": "Estrellas", + "version_installed": "Versión instalada" + }, + "dialog_install": { + "restart": "Recuerde que debe reiniciar Home Assistant para que se apliquen los cambios en las integraciones (custom_components).", + "select_version": "Seleccione la versión", + "show_beta": "Mostrar versiones beta", + "type": "Tipo", + "url": "URL" + }, + "dialog_removed": { + "link": "Enlace externo para más información", + "name": "Nombre del repositorio", + "reason": "Motivo de la eliminación", + "type": "Tipo de eliminación" + }, + "dialog_update": { + "available_version": "Versión disponible", + "changelog": "Registro de cambios", + "installed_version": "Versión instalada", + "releasenotes": "Notas de lanzamiento para {release}", + "title": "Actualización pendiente" + }, + "dialog": { + "reload": { + "confirm": "¿Quieres hacer eso ahora?", + "description": "Necesitas limpiar el caché de tu navegador cuando cambies los recursos de Lovelace." + } + }, + "entry": { + "information": "Información", + "intro": "Las actualizaciones y los mensajes importantes se mostrarán aquí si hay alguno que mostrar", + "messages": { + "disabled": { + "content": "Compruebe el archivo de registro para obtener más detalles", + "title": "HACS está deshabilitado" + }, + "has_pending_tasks": { + "content": "Es posible que algunos repositorios no se muestren hasta que esto se complete", + "title": "Tareas en segundo plano pendientes" + }, + "removed": "Repositorio '{repository}' eliminado", + "resources": { + "content": "Tienes {number} elementos de Lovelace que no se cargan correctamente en Lovelace.", + "title": "No está cargada en Lovelace" + }, + "restart": { + "content": "Tienes {number} integraciones que requieren un reinicio de Home Assistant, puedes hacerlo desde la sección 'Controles del Servidor' en la parte de configuración de la UI de Home Assistant.", + "title": "Pendiente de reinicio" + }, + "setup": { + "content": "HACS se está configurando, durante este tiempo alguna información puede estar perdida o ser incorrecta", + "title": "HACS se está configurando" + }, + "startup": { + "content": "HACS se está iniciando, durante este tiempo alguna información podría faltar o ser incorrecta", + "title": "HACS se está iniciando" + }, + "waiting": { + "content": "HACS está esperando a que Home Assistant finalice el inicio antes de iniciar las tareas de inicio", + "title": "HACS está esperando" + }, + "wrong_frontend_installed": { + "content": "Tienes instalada la versión {running} de la interfaz de HACS, pero se esperaba la versión {expected}, si ves este mensaje, Home Assistant no pudo instalar la nueva versión, intenta reiniciar Home Assistant.", + "title": "Versión inesperada de la interfaz" + }, + "wrong_frontend_loaded": { + "content": "Estás ejecutando la versión {running} de la interfaz HACS, pero se esperaba la versión {expected} , deberías de limpiar la memoria caché del navegador.", + "title": "Versión inesperada de la interfaz" + } + }, + "pending_updates": "Actualizaciones pendientes" + }, + "menu": { + "about": "Acerca de HACS", + "clear": "Borrar todo lo nuevo", + "custom_repositories": "Repositorios personalizados", + "dismiss": "Descartar todos los repositorios nuevos", + "documentation": "Documentación", + "open_issue": "Abrir incidencias", + "reload": "Recargar la ventana" + }, + "options": { + "step": { + "user": { + "data": { + "appdaemon": "Habilitar el descubrimiento y seguimiento de las aplicaciones de AppDaemon", + "country": "Filtrar por el código de país.", + "debug": "Habilitar depuración.", + "experimental": "Habilitar funciones experimentales", + "netdaemon": "Habilitar el descubrimiento y seguimiento de las aplicaciones de NetDaemon", + "not_in_use": "No usarse con YAML", + "release_limit": "Número de versiones a mostrar.", + "sidepanel_icon": "Icono del panel lateral", + "sidepanel_title": "Título del panel lateral" + } + } + } + }, + "repository_banner": { + "config_flow": "Esta integración soporta config_flow, lo que significa que ahora puede ir a la sección de integración de su UI para configurarlo.", + "config_flow_title": "Configuración de UI soportada", + "integration_not_loaded": "Esta integración no se carga en Home Assistant.", + "no_restart_required": "No es necesario reiniciar", + "not_loaded": "No está cargado", + "plugin_not_loaded": "Este plugin aun no se ha añadido a sus recursos de Lovelace.", + "restart": "Es necesario reiniciar Home Assistant.", + "restart_pending": "Reinicio pendiente" + }, + "repository_card": { + "dismiss": "descartar", + "hide": "Ocultar", + "information": "Información", + "new_repository": "Nuevo repositorio", + "not_loaded": "Sin cargar", + "open_issue": "Abrir incidencias", + "open_source": "Código abierto", + "pending_restart": "Pendiente de reinicio", + "pending_update": "Actualización pendiente", + "reinstall": "Reinstalar", + "report": "Informe para la eliminación", + "update_information": "Actualizar información" + }, + "repository": { + "add_to_lovelace": "Añadir a Lovelace", + "authors": "Autores", + "available": "Disponible", + "back_to": "Volver a", + "changelog": "Registro de cambios", + "downloads": "Descargas", + "flag_this": "Marcar esto", + "frontend_version": "Versión del frontend", + "github_stars": "Estrellas de GitHub", + "goto_integrations": "Ir a integraciones", + "hide": "Ocultar", + "hide_beta": "Ocultar beta", + "install": "Instalar", + "installed": "Instalado", + "lovelace_copy_example": "Copiar el ejemplo al clipboard", + "lovelace_instruction": "Agregue lo siguiente en su configuración de lovelace", + "lovelace_no_js_type": "No se pudo determinar el tipo de elemento, revise el repositorio.", + "newest": "más nuevo", + "note_appdaemon": "deberá agregar esto a su archivo 'apps.yaml'", + "note_installed": "Cuando esté instalado, se ubicará en", + "note_integration": "deberá agregar esto a su archivo 'configuration.yaml'", + "note_plugin": "deberá agregar esto a su configuración de lovelace ('ui-lovelace.yaml' o en el editor UI de lovelace)", + "note_plugin_post_107": "todavía necesita agregarlo a su configuración de lovelace ('configuration.yaml' o al editor de recursos '\/config\/lovelace\/resources')", + "open_issue": "Abrir incidencias", + "open_plugin": "Abrir elemento", + "reinstall": "Reinstalar", + "repository": "Repositorio", + "restart_home_assistant": "Reiniciar Home Assistant", + "show_beta": "Mostrar beta", + "uninstall": "Desinstalar", + "update_information": "Actualizar información", + "upgrade": "Actualizar" + }, + "search": { + "installed": "Buscar repositorios instalados", + "installed_new": "Buscar repositorios instalados o nuevos", + "placeholder": "Buscar repositorio" + }, + "sections": { + "about": { + "description": "Mostrar información sobre HACS", + "title": "Acerca de" + }, + "addon": { + "description": "No hay complementos (addons) en HACS, pero puede hacer clic aquí para ir a la pestaña Supervisor", + "title": "Complementos (addons)" + }, + "automation": { + "description": "Aquí es donde se encuentran python_scripts, aplicaciones AppDaemon y aplicaciones NetDaemon", + "title": "Automatización" + }, + "frontend": { + "description": "Aquí es donde encontrarás temas, tarjetas personalizadas y otros elementos para lovelace", + "title": "Interfaz" + }, + "integrations": { + "description": "Aquí es donde se encuentran las integraciones personalizadas (custom_components)", + "title": "Integraciones" + }, + "pending_repository_upgrade": "Está ejecutando la versión {installed}, la versión {available} está disponible" + }, + "settings": { + "add_custom_repository": "AGREGAR REPOSITORIO PERSONALIZADO", + "adding_new_repo": "Añadiendo un nuevo repositorio '{repo}'.", + "adding_new_repo_category": "Con la categoría '{categoría}'.", + "bg_task_custom": "Los repositorios personalizados están ocultos mientras se ejecutan las tareas en segundo plano.", + "category": "Categoría", + "compact_mode": "Modo compacto", + "custom_repositories": "REPOSITORIOS PERSONALIZADOS", + "delete": "Eliminar", + "display": "Mostrar", + "grid": "Cuadrícula", + "hacs_repo": "HACS repo", + "hidden_repositories": "repositorios ocultos", + "missing_category": "Es necesario seleccionar una categoría", + "open_repository": "Abrir repositorio", + "reload_data": "Recargar datos", + "reload_window": "Recargar ventana", + "repository_configuration": "Configuración del repositorio", + "save": "Guardar", + "table": "Tabla", + "table_view": "Vista de la tabla", + "unhide": "mostrar", + "upgrade_all": "Actualizar todo" + }, + "store": { + "ascending": "ascendente", + "clear_new": "Eliminar la lista los nuevos repositorios", + "descending": "descendente", + "last_updated": "Última actualización", + "name": "Nombre", + "new_repositories": "Nuevos Repositorios", + "new_repositories_note": "Tienes más de 10 nuevos repositorios mostrados aquí, si quieres borrarlos todos haz clic en los 3 puntos de la esquina superior derecha y deséchalos todos.", + "no_repositories": "Sin repositorios", + "no_repositories_desc1": "Parece que todavía no tiene ningún repositorio instalado en esta sección.", + "no_repositories_desc2": "Haga clic en el + de la esquina inferior derecha para agregar su primer repositorio!", + "no_repositories_found_desc1": "No se ha encontrado ningún repositorio instalado que coincida con el valor de \"{searchInput}\" en esta sección.", + "no_repositories_found_desc2": "¡Intenta buscar otra cosa!", + "pending_upgrades": "Actualizaciones pendientes", + "placeholder_search": "Por favor escriba una palabra clave de búsqueda...", + "sort": "ordenar", + "stars": "Estrellas", + "status": "Estado" + }, + "time": { + "ago": "hace", + "day": "dia", + "days": "dias", + "hour": "hora", + "hours": "horas", + "minute": "minuto", + "minutes": "minutos", + "month": "mes", + "months": "meses", + "one": "Uno", + "one_day_ago": "hace un día", + "one_hour_ago": "hace una hora", + "one_minute_ago": "hace un minuto", + "one_month_ago": "hace un mes", + "one_second_ago": "hace un segundo", + "one_year_ago": "hace un año", + "second": "segundo", + "seconds": "segundos", + "x_days_ago": "hace {x} dias", + "x_hours_ago": "hace {x} horas", + "x_minutes_ago": "hace {x} minutos", + "x_months_ago": "hace {x} meses", + "x_seconds_ago": "hace {x} segundos", + "x_years_ago": "hace {x} años", + "year": "año", + "years": "años" + } +} \ No newline at end of file diff --git a/custom_components/hacs/translations/et_EE.json b/custom_components/hacs/translations/et_EE.json new file mode 100644 index 0000000..4cf2789 --- /dev/null +++ b/custom_components/hacs/translations/et_EE.json @@ -0,0 +1,358 @@ +{ + "common": { + "about": "Üldteave", + "add": "lisa", + "appdaemon_apps": "AppDaemoni rakendused", + "appdaemon_plural": "AppDaemoni rakendused", + "background_task": "Taustatoiming on töös. See leht taaslaetakse kui toiming on lõpetatud.", + "cancel": "Loobu", + "check_log_file": "Lisateavet leiad oma logifailist", + "continue": "Jätka", + "disabled": "Keelatud", + "documentation": "Dokumentatsioon", + "element": "element", + "hacs_is_disabled": "HACS on keelatud", + "ignore": "Eira", + "install": "Paigalda", + "installed": "paigaldatud", + "integration": "Sidumine", + "integration_plural": "Sidumised", + "integrations": "Sidumised", + "lovelace": "Lovelace", + "lovelace_element": "Kasutajaliidese element", + "lovelace_elements": "Kasutajaliidese elemendid", + "manage": "halda", + "netdaemon": "NetDaemon", + "netdaemon_apps": "NetDaemoni rakendused", + "netdaemon_plural": "NetDaemoni rakendused", + "plugin": "Kasutajaliides", + "plugin_plural": "Kasutajaliidese elemendid", + "plugins": "Kasutajaliidese elemendid", + "python_script": "Pythoni skript", + "python_script_plural": "Pythoni skriptid", + "python_scripts": "Pythoni skriptid", + "reload": "Taaslae", + "repositories": "Teegid", + "repository": "Hoidla", + "settings": "seaded", + "theme": "Kuva teema", + "theme_plural": "Kuvateemad", + "themes": "Kuvateemad", + "uninstall": "Desinstalli", + "update": "Uuendus", + "version": "Versioon" + }, + "config": { + "abort": { + "single_instance_allowed": "Lubatud on ainult üks HACS-i paigaldus." + }, + "error": { + "acc": "Enne jätkamist pead nõustuma kõiki tingimustega", + "auth": "Isiklik juurdepääsutõend pole õige" + } + }, + "confirm": { + "add_to_lovelace": "Kas oled kindel, et soovid lisada selle oma Lovelace'i ressurssidele?", + "bg_task": "Taustaülesannete töötamise ajal on toiming keelatud.", + "cancel": "Loobu", + "continue": "Kas soovite kindlasti jätkata?", + "delete": "Kas soovid kindlasti üksuse '{item}' kustutada?", + "delete_installed": "'{item}' on ipaigaldatud, peate selle enne kustutamist eemaldama.", + "exist": "{item} on juba olemas", + "generic": "Oled sa kindel?", + "home_assistant_is_restarting": "Oota, Home Assistant taaskäivitub.", + "home_assistant_version_not_correct": "Kasutad Home Assistanti versiooni '{haversion}' kuid see hoidla nõuab vähemalt versiooni '{minversion}' installimist.", + "no": "Ei", + "no_upgrades": "Värskendused puuduvad", + "ok": "Sobib", + "overwrite": "See toiming kirjutab selle üle.", + "reload_data": "See taaslaeb kõigi HACS-i teada olevate hoidlate andme. Selle lõpuleviimine võtab palju aega.", + "restart_home_assistant": "Kas soovid kindlasti Home Assistanti taaskäivitada?", + "uninstall": "Kas soovid kindlasti üksuse '{item}' desinstallida?", + "upgrade_all": "See uuendab kõiki valitud hoidlaid. Veenduge, et olete enne jätkamist lugenud kõigi nende väljalaskemärkmeid.", + "yes": "Jah" + }, + "dialog_about": { + "frontend_version": "Kasutajaliidese versioon", + "installed_repositories": "Paigaldatud hoidlad", + "integration_version": "Sidumise versioon", + "useful_links": "Kasulikud veebiviited" + }, + "dialog_add_repo": { + "limit": "Kuvatakse ainult esimesed 100 hoidlat. Vajaliku filtreerimiseks kasutage otsingut", + "no_match": "Filtrile vastavaid hoidlaid ei leitud", + "sort_by": "Sortimisalus", + "title": "Lisa hoidla" + }, + "dialog_custom_repositories": { + "category": "Kategooria", + "no_category": "Puuduv kategooria", + "no_repository": "Puuduv hoidla", + "title": "Kohandatud hoidlad", + "url_placeholder": "Lisa kohandatud hoidla URL" + }, + "dialog_info": { + "author": "Autor", + "downloads": "Allalaadimised", + "install": "Paigalda see hoidla HACS-i", + "loading": "Teabe laadimine ...", + "no_info": "Arendaja ei ole selle hoidla kohta rohkem teavet avaldanud", + "open_issues": "Teadaolevad tõrketeatised", + "open_repo": "Ava hoidla", + "stars": "Hinnang", + "version_installed": "Paigaldatud versioon" + }, + "dialog_install": { + "restart": "Pea meeles, et sidumiste (custom_components) muudatuste rakendamiseks pead Home Assistanti taaskäivitama.", + "select_version": "Vali versioon", + "show_beta": "Kuva beetaversioonid", + "type": "Liik", + "url": "URL" + }, + "dialog_removed": { + "link": "Väline link lisateabe saamiseks", + "name": "Hoidla nimi", + "reason": "Eemaldamise põhjus", + "type": "Eemaldamise tüüp" + }, + "dialog_update": { + "available_version": "Saadaolev versioon", + "changelog": "Muudatused", + "installed_version": "Paigaldatud versioon", + "releasenotes": "Väljalaske märkused väljaandele {release}", + "title": "Uuendus on ootel" + }, + "dialog": { + "reload": { + "confirm": "Kas teen seda kohe?", + "description": "Lovelace ressursside muutmisel pead brauseri vahemälu tühjendama." + } + }, + "entry": { + "information": "Teave", + "intro": "Siin kuvatakse saadaval värskendused ja olulised sõnumid kui neid juhtub olema", + "messages": { + "disabled": { + "content": "Lisateavet leiad oma logifailist", + "title": "HACS on keelatud" + }, + "has_pending_tasks": { + "content": "Mõnda hoidlat ei kuvata enne kui tegevus on lõpule viidud", + "title": "Taustal on ootel toiminguid" + }, + "removed": "Eemaldasin hoidla '{repository}'", + "resources": { + "content": "Teil on {number} Lovelace'i elementi mis pole Lovelace'is õigesti laaditud.", + "title": "Ei laaditud Lovelace'i" + }, + "restart": { + "content": "Teil on {number} sidumist mis nõuavad Home Assistanti taaskäivitamist. Saate seda teha Home Assistanti kasutajaliidese seadete alamjaotisest \"Serveri juhtimine\".", + "title": "Taaskäivitamise ootel" + }, + "setup": { + "content": "HACS on seadistub. Selle aja jooksul võib osa teavet puududa või olla vale", + "title": "HACS seadistub" + }, + "startup": { + "content": "HACS käivitub. Selle aja jooksul võib osa teavet puududa või olla vale", + "title": "HACS käivitub" + }, + "waiting": { + "content": "HACS ootab Home Assistanti käivitumist", + "title": "HACS on ootel" + }, + "wrong_frontend_installed": { + "content": "Teil on installitud HACS'i kasutajaliides {running} kuid eeldatakse versiooni {expected}. Kui näete seda teadet ei suutnud Home Assistant uut versiooni installida. Proovige Home Assistant taaskäivitada.", + "title": "Ootamatu kasutajaliidese versioon" + }, + "wrong_frontend_loaded": { + "content": "Paigaldatud on HACS'i kasutajaliides {running} kuid eeldatakse versiooni {expected}. Tühjenda oma veebilehitseja vahemälu.", + "title": "Ootamatu kasutajaliidese versioon" + } + }, + "pending_updates": "Ootel värskendused" + }, + "menu": { + "about": "HACS-i info", + "clear": "Tühjenda kõik uued", + "custom_repositories": "Kohandatud hoidlad", + "dismiss": "Peida kõik uued hoidlad", + "documentation": "Dokumentatsioon", + "open_issue": "Esita tõrketeade", + "reload": "Lae aken uuesti" + }, + "options": { + "step": { + "user": { + "data": { + "appdaemon": "Luba AppDaemoni rakenduste otsimine ja jälgimine", + "country": "Filtreeri riigi koodi abil.", + "debug": "Luba silumine.", + "experimental": "Luba katselised funktsioonid", + "netdaemon": "Luba NetDaemoni rakenduste otsimine ja jälgimine", + "not_in_use": "YAML režiimi ei toetata", + "release_limit": "Mitu väljalaset kuvada.", + "sidepanel_icon": "Külgpaneeli ikoon", + "sidepanel_title": "Külgpaneeli pealkiri" + } + } + } + }, + "repository_banner": { + "config_flow": "See sidumine toetab config_flow'd. See tähendab, et selle seadistamiseks pead minema oma kasutajaliidese sidumiste sektsiooni.", + "config_flow_title": "Seadistamine kasutajaliidese abil", + "integration_not_loaded": "Seda sidumist ei laeta Home Assistanti.", + "no_restart_required": "Taaskäivitamine pole vajalik", + "not_loaded": "Pole laaditud", + "plugin_not_loaded": "Seda elementi ei lisata teie Lovelace'i ressurssidesse", + "restart": "Pead Home Assistanti taaskäivitama.", + "restart_pending": "Taaskäivitamine on ootel" + }, + "repository_card": { + "dismiss": "Peida", + "hide": "Peida", + "information": "Teave", + "new_repository": "Uus hoidla", + "not_loaded": "Pole laaditud", + "open_issue": "Esita tõrketeade", + "open_source": "Avatud lähtekoodiga", + "pending_restart": "Taaskäivitamise ootel", + "pending_update": "Värskendamise ootel", + "reinstall": "Paigalda uuesti", + "report": "Teavita eemaldamiseks", + "update_information": "Värskenda teavet" + }, + "repository": { + "add_to_lovelace": "Lisa kasutajaliidesesse", + "authors": "Autorid", + "available": "Saadaval", + "back_to": "Tagasi", + "changelog": "Muudatuste teave", + "downloads": "Allalaadimised", + "flag_this": "Märgi see ära", + "frontend_version": "Kasutajaliidese versioon", + "github_stars": "GitHubi hinnangud", + "goto_integrations": "Mine sidumiste juurde", + "hide": "Peida", + "hide_beta": "Peida eelversioon", + "install": "Paigalda", + "installed": "Paigaldatud", + "lovelace_copy_example": "Kopeeri näide lõikelauale", + "lovelace_instruction": "Kui lisad selle oma kasutajaliidese seadetesse kasuta", + "lovelace_no_js_type": "Selle elemendi tüüpi ei õnnestunud määratleda, kontrolli hoidlat.", + "newest": "uusim", + "note_appdaemon": "pead selle ikkagi lisama oma faili 'apps.yaml'", + "note_installed": "Kui see on paigaldatus siis asub see", + "note_integration": "pead selle ikkagi lisama oma faili 'configuration.yaml'", + "note_plugin": "lisaks pead selle lisama oma kasutajaliidese seadetesse ('ui-lovelace.yaml' või raw UI seadete redaktoris')", + "note_plugin_post_107": "pead selle lisama oma kasutajaliidese seadetesse ('configuration.yaml' või ressursiredaktoris '\/ config \/ lovelace \/ resources')", + "open_issue": "Esita tõrketeade", + "open_plugin": "Ava element", + "reinstall": "Paigalda uuesti", + "repository": "Hoidla", + "restart_home_assistant": "Taaskäivita Home Assistant", + "show_beta": "Kuva eelversioon", + "uninstall": "Desinstalli", + "update_information": "Värskenduse teave", + "upgrade": "Uuenda" + }, + "search": { + "installed": "Otsi paigaldatud hoidlaid", + "installed_new": "Otsi paigaldatud või uusi hoidlaid", + "placeholder": "Otsi hoidlat" + }, + "sections": { + "about": { + "description": "Kuva HACS-i teave", + "title": "Üldteave" + }, + "addon": { + "description": "HACS-is pole lisandmooduleid. Supervisorisse pääsemiseks klõpsa siin", + "title": "Lisandmoodulid" + }, + "automation": { + "description": "Siit leiad python_scripts, AppDaemoni ja NetDaemoni rakendused", + "title": "Automatiseeringud" + }, + "frontend": { + "description": "Siit leiad kasutajaliidese teemad, kohandatud kaardid ja muud elemendid", + "title": "Kasutajaliides" + }, + "integrations": { + "description": "Siit leiad kohandatud sidumised (custom_components)", + "title": "Sidumised" + }, + "pending_repository_upgrade": "Kasutad versiooni {installed}, saadaval on versioon {available}" + }, + "settings": { + "add_custom_repository": "LISA KOHANDATUD HOIDLA", + "adding_new_repo": "Lisan uue hoidla '{repo}'", + "adding_new_repo_category": "Kategoorias '{category}'.", + "bg_task_custom": "Kohandatud hoidlad on taustatoimingute töötamise ajal peidetud.", + "category": "Kategooria", + "compact_mode": "Kompaktne režiim", + "custom_repositories": "KOHANDATUD HOIDLAD", + "delete": "Kustuta", + "display": "Kuva", + "grid": "Võrgustik", + "hacs_repo": "HACSi teek", + "hidden_repositories": "peidetud hoidlad", + "missing_category": "Pead valima kategooria", + "open_repository": "Ava hoidla", + "reload_data": "Taaslae andmed", + "reload_window": "Lae aken uuesti", + "repository_configuration": "Hoidla seaded", + "save": "Salvesta", + "table": "Tabel", + "table_view": "Tabeli vaade", + "unhide": "too peidust välja", + "upgrade_all": "Uuenda kõik" + }, + "store": { + "ascending": "kasvav", + "clear_new": "Peida kõik uued hoidlad", + "descending": "kahanev", + "last_updated": "Viimati uuendatud", + "name": "Nimi", + "new_repositories": "Uued hoidlad", + "new_repositories_note": "Siin on kuvatud üle 10 uue hoidla. Kui soovid need kõik peita klõpsa paremas ülanurgas 3 punkti ja jätaneed kõik kõrvale.", + "no_repositories": "Hoidlaid pole", + "no_repositories_desc1": "Tundub, et sul pole veel ühtegi hoidlat mis on siia jaotisse paigaldatud.", + "no_repositories_desc2": "Esimese hoidla lisamiseks klõpsake alumises nurgas + märki!", + "no_repositories_found_desc1": "Selles jaotises ei leitud ühtegi paigaldatud hoidlat mis vastaks otsingule \" {searchInput} \".", + "no_repositories_found_desc2": "Proovi otsida midagi muud!", + "pending_upgrades": "Ootel värskendused", + "placeholder_search": "Palun sisesta otsingutermin...", + "sort": "sorteeri", + "stars": "Hinnang", + "status": "Olek" + }, + "time": { + "ago": "eest", + "day": "päev", + "days": "päeva", + "hour": "tund", + "hours": "tundi", + "minute": "minut", + "minutes": "minutit", + "month": "kuu", + "months": "kuud", + "one": "Üks", + "one_day_ago": "ühe aasta eest", + "one_hour_ago": "ühe tunni eest", + "one_minute_ago": "ühe minuti eest", + "one_month_ago": "ühe kuu esst", + "one_second_ago": "ühe sekundi eest", + "one_year_ago": "ühe aasta eest", + "second": "sekund", + "seconds": "sekundit", + "x_days_ago": "{x} päeva eest", + "x_hours_ago": "{x} tunni eest", + "x_minutes_ago": "{x} minuti eest", + "x_months_ago": "{x} kuu eest", + "x_seconds_ago": "{x} sekundi eest", + "x_years_ago": "{x} aasta eest", + "year": "aasta", + "years": "aastat" + } +} \ No newline at end of file diff --git a/custom_components/hacs/translations/fi.json b/custom_components/hacs/translations/fi.json new file mode 100644 index 0000000..5529fe5 --- /dev/null +++ b/custom_components/hacs/translations/fi.json @@ -0,0 +1,242 @@ +{ + "common": { + "about": "Tietoja", + "add": "Lisää", + "appdaemon_plural": "AppDaemon-sovellukset", + "check_log_file": "Tarkista lokitiedostosi saadaksesi lisätietoja.", + "disabled": "Poistettu käytöstä", + "documentation": "Dokumentointi", + "element": "elementti", + "hacs_is_disabled": "HACS on poistettu käytöstä", + "ignore": "Ohita", + "install": "Asenna", + "installed": "asennettu", + "integration_plural": "Integraatiot", + "lovelace": "Lovelace", + "lovelace_element": "Lovelace-elementti", + "lovelace_elements": "Lovelace-elementit", + "manage": "hallinnoi", + "netdaemon": "NetDaemon", + "netdaemon_apps": "NetDaemon-sovellukset", + "netdaemon_plural": "NetDaemon-sovellukset", + "plugin": "Lovelace", + "plugin_plural": "Lovelace-elementit", + "plugins": "Lovelace-elementit", + "python_script_plural": "Python-skriptit", + "repository": "Repo", + "theme": "Teema", + "theme_plural": "Teemat", + "themes": "Teemat", + "uninstall": "Poista", + "update": "Päivitä", + "version": "Versio" + }, + "config": { + "step": { + "device": { + "description": "Open {url} and paste this key `{code}` to authorize HACS, when you have done that click 'submit'", + "title": "HACS" + }, + "user": { + "data": { + "acc_addons": "I know that there are no add-ons in HACS", + "acc_disable": "I know that if I get issues with Home Assistant I should disable all my custom_components", + "acc_logs": "I know how to access Home Assistant logs", + "acc_untested": "I know that everything inside HACS is custom and untested by Home Assistant", + "appdaemon": "Enable AppDaemon apps discovery & tracking", + "netdaemon": "Enable NetDaemon apps discovery & tracking", + "python_script": "Enable python_scripts discovery & tracking", + "sidepanel_icon": "Side panel icon", + "sidepanel_title": "Side panel title", + "theme": "Enable Themes discovery & tracking", + "token": "GitHub Personal Access Token" + }, + "description": "Before you can setup HACS you need to acknowledge the following", + "title": "HACS" + } + }, + "title": "HACS (Home Assistant Community Store)" + }, + "confirm": { + "cancel": "Peruuta", + "generic": "Oletko varma?", + "no": "Ei", + "ok": "Okei", + "yes": "Kyllä" + }, + "dialog_about": { + "frontend_version": "Frontend-versio", + "installed_repositories": "Asennetut repot", + "useful_links": "Hyödyllisiä linkkejä" + }, + "dialog_add_repo": { + "sort_by": "Järjestä", + "title": "Lisää repo" + }, + "dialog_custom_repositories": { + "category": "Kategoria", + "no_category": "Puuttuva kategoria", + "no_repository": "Puuttuva repo" + }, + "dialog_info": { + "author": "Luoja", + "downloads": "Lataukset", + "install": "Asenna tämä HACS:iin", + "loading": "Tietoja ladataan...", + "open_issues": "Avoimet ongelmat", + "open_repo": "Avaa repo", + "stars": "Tähdet", + "version_installed": "Asennettu versio" + }, + "dialog_install": { + "select_version": "Valitse versio", + "show_beta": "Näytä beetaversiot", + "type": "Tyyppi", + "url": "URL" + }, + "dialog_removed": { + "link": "Linkki lisätietoihin", + "name": "Repon nimi", + "reason": "Poiston syy", + "type": "Poiston tyyppi" + }, + "dialog_update": { + "available_version": "Saatavilla oleva versio", + "changelog": "Muutosloki", + "installed_version": "Asennettu versio", + "title": "Päivitys odottaa" + }, + "entry": { + "information": "Tiedot", + "messages": { + "disabled": { + "content": "Tarkista lokitiedostosi saadaksesi lisätietoja", + "title": "HACS on poistettu käytöstä" + }, + "has_pending_tasks": { + "title": "Taustatehtävät vireillä" + }, + "removed": "Poistettu repo '{arkisto}'", + "resources": { + "title": "Ei ladattu Lovelaceen" + }, + "restart": { + "title": "Odottaa uudelleenkäynnistystä" + }, + "startup": { + "title": "HACS käynnistyy" + }, + "wrong_frontend_installed": { + "title": "Odottamaton käyttöliittymäversio" + }, + "wrong_frontend_loaded": { + "title": "Odottamaton käyttöliittymäversio" + } + }, + "pending_updates": "Päivityksiä saatavilla" + }, + "menu": { + "about": "Tietoja HACS:stä", + "clear": "Tyhjennä kaikki uudet", + "custom_repositories": "Mukautetut repot", + "dismiss": "Hylkää kaikki uudet repot", + "documentation": "Dokumentointi", + "open_issue": "Avoin ongelma", + "reload": "Lataa ikkuna uudelleen" + }, + "options": { + "step": { + "user": { + "data": { + "netdaemon": "Ota NetDaemon-sovellusten etsintä ja seuranta käyttöön", + "not_in_use": "Ei käytössä YAML:n kanssa", + "sidepanel_icon": "Sivupaneelin kuvake", + "sidepanel_title": "Sivupaneelin otsikko" + } + } + } + }, + "repository_banner": { + "config_flow_title": "Käyttöliittymän määrityksiä tuetaan" + }, + "repository_card": { + "dismiss": "Hylkää", + "hide": "Piilota", + "information": "Tiedot", + "new_repository": "Uusi repo", + "not_loaded": "Ei ladattu", + "open_issue": "Avoin ongelma", + "open_source": "Avoin lähdekoodi", + "pending_restart": "Odottaa uudelleenkäynnistystä", + "pending_update": "Odottaa päivittämistä", + "reinstall": "Asenna uudelleen", + "report": "Raportoi poistettavaksi", + "update_information": "Päivitä tiedot" + }, + "repository": { + "frontend_version": "Frontend-versio", + "github_stars": "GitHub-tähdet", + "goto_integrations": "Siirry integraatioihin" + }, + "search": { + "installed": "Etsi asennettuja repoja", + "installed_new": "Etsi asennettuja tai uusia repoja", + "placeholder": "Etsi repoa" + }, + "sections": { + "about": { + "title": "Tietoja" + }, + "automation": { + "title": "Automaatio" + }, + "frontend": { + "title": "Frontend" + }, + "integrations": { + "description": "Täältä löydät mukautetut integraatiot (custom_components)", + "title": "integraatiot" + } + }, + "settings": { + "delete": "Poista", + "reload_window": "Lataa ikkuna uudelleen", + "save": "Tallenna" + }, + "store": { + "name": "Nimi", + "no_repositories": "Ei repoja", + "no_repositories_found_desc2": "Kokeile etsiä jotain muuta!", + "pending_upgrades": "Odottaa päivityksiä", + "sort": "järjestä", + "stars": "Tähdet", + "status": "Tila" + }, + "time": { + "day": "päivä", + "days": "päivää", + "hour": "tunti", + "hours": "tuntia", + "minute": "minuutti", + "minutes": "minuuttia", + "month": "kuukausi", + "months": "kuukautta", + "one": "Yksi", + "one_day_ago": "yksi päivä sitten", + "one_hour_ago": "tunti sitten", + "one_minute_ago": "minuutti sitten", + "one_month_ago": "kuukausi sitten", + "one_second_ago": "sekunti sitten", + "one_year_ago": "vuosi sitten", + "second": "sekunti", + "seconds": "sekuntia", + "x_days_ago": "{x} päivää sitten", + "x_hours_ago": "{x} tuntia sitten", + "x_minutes_ago": "{x} minuuttia sitten", + "x_months_ago": "{x} kuukautta sitten", + "x_seconds_ago": "{x} sekuntia sitten", + "x_years_ago": "{x} vuotta sitten", + "year": "vuosi", + "years": "vuotta" + } +} \ No newline at end of file diff --git a/custom_components/hacs/translations/fr.json b/custom_components/hacs/translations/fr.json new file mode 100644 index 0000000..1dc0197 --- /dev/null +++ b/custom_components/hacs/translations/fr.json @@ -0,0 +1,383 @@ +{ + "common": { + "about": "À propos de", + "add": "ajouter", + "appdaemon": "AppDaemon", + "appdaemon_apps": "Applications AppDaemon", + "appdaemon_plural": "AppDaemon Apps", + "background_task": "Tache de fond en cours, cette page se rechargera une fois terminée", + "cancel": "Annuler", + "check_log_file": "Consultez votre fichier journal pour plus de détails.", + "continue": "Continuer", + "disabled": "Désactivé", + "documentation": "Documentation", + "element": "élément", + "hacs_is_disabled": "HACS est désactivé", + "ignore": "Ignorer", + "install": "Installer", + "installed": "installés", + "integration": "Intégration", + "integration_plural": "Intégrations", + "integrations": "Intégrations", + "lovelace": "Lovelace", + "lovelace_element": "Élément Lovelace", + "lovelace_elements": "Éléments Lovelace", + "manage": "gérer", + "netdaemon": "NetDaemon", + "netdaemon_apps": "Applications NetDaemon", + "netdaemon_plural": "NetDaemon Apps", + "plugin": "Lovelace", + "plugin_plural": "Éléments Lovelace", + "plugins": "Éléments Lovelace", + "python_script": "Script Python", + "python_script_plural": "Scripts Python", + "python_scripts": "Scripts Python", + "reload": "Recharger", + "repositories": "Dépôts", + "repository": "Dépôt", + "settings": "paramètres", + "theme": "Thème", + "theme_plural": "Thèmes", + "themes": "Thèmes", + "uninstall": "Désinstaller", + "update": "Mettre à jour", + "version": "Version" + }, + "config": { + "abort": { + "single_instance_allowed": "Une seule configuration de HACS est autorisée." + }, + "error": { + "acc": "Vous devez accepter toutes les déclarations avant de continuer", + "auth": "Le jeton personnel d'accès est invalide." + }, + "step": { + "device": { + "description": "Open {url} and paste this key `{code}` to authorize HACS, when you have done that click 'submit'", + "title": "HACS" + }, + "user": { + "data": { + "acc_addons": "I know that there are no add-ons in HACS", + "acc_disable": "I know that if I get issues with Home Assistant I should disable all my custom_components", + "acc_logs": "I know how to access Home Assistant logs", + "acc_untested": "I know that everything inside HACS is custom and untested by Home Assistant", + "appdaemon": "Enable AppDaemon apps discovery & tracking", + "netdaemon": "Enable NetDaemon apps discovery & tracking", + "python_script": "Enable python_scripts discovery & tracking", + "sidepanel_icon": "Side panel icon", + "sidepanel_title": "Side panel title", + "theme": "Enable Themes discovery & tracking", + "token": "GitHub Personal Access Token" + }, + "description": "Before you can setup HACS you need to acknowledge the following", + "title": "HACS" + } + }, + "title": "HACS (Home Assistant Community Store)" + }, + "confirm": { + "add_to_lovelace": "Êtes-vous sûr de vouloir l'ajouter à vos ressources Lovelace ?", + "bg_task": "L'action est désactivée pendant l'exécution de tâches en arrière-plan.", + "cancel": "Annuler", + "continue": "Es-tu sur de vouloir continuer?", + "delete": "Êtes-vous sûr de vouloir supprimer '{item}'?", + "delete_installed": "'{item}' est installé, vous devez le désinstaller avant de pouvoir le supprimer.", + "exist": "{item} existe déjà", + "generic": "Êtes-vous sûr?", + "home_assistant_is_restarting": "Attendez, Home Assistant redémarre maintenant.", + "home_assistant_version_not_correct": "Vous utilisez la version '{haversion}' de Home Assistant, mais ce référentiel nécessite l'installation de la version minimale '{minversion}'.", + "no": "Non", + "no_upgrades": "Aucune mise à niveau en attente", + "ok": "OK", + "overwrite": "En faisant cela, cela l'écrasera.", + "reload_data": "Cela recharge les données de tous les dépôts dont HACS a connaissance, cela prendra un certain temps à terminer.", + "restart_home_assistant": "Voulez-vous vraiment redémarrer Home Assistant ?", + "uninstall": "Êtes-vous sûr de vouloir désinstaller '{item}'?", + "upgrade_all": "Cela mettra à niveau tous ces dépôts, assurez-vous d'avoir lu les notes de publication pour chacun d'entre eux avant de continuer.", + "yes": "Oui" + }, + "dialog_about": { + "frontend_version": "Version frontend", + "installed_repositories": "Dépôts installés", + "integration_version": "Version d'intégration", + "useful_links": "Liens utiles" + }, + "dialog_add_repo": { + "limit": "Seuls les 100 premiers dépôts sont affichés, utilisez la recherche pour filtrer ce dont vous avez besoin", + "no_match": "Aucun dépôt trouvé correspondant à votre filtre", + "sort_by": "Trier par", + "title": "Ajouter un dépôt" + }, + "dialog_custom_repositories": { + "category": "Catégorie", + "no_category": "Catégorie manquante", + "no_repository": "Dépôt manquant", + "title": "Dépôts personnalisés", + "url_placeholder": "Ajouter une URL de dépôt personnalisée" + }, + "dialog_info": { + "author": "Auteur", + "downloads": "Téléchargements", + "install": "Installer ce dépôt dans HACS", + "loading": "Chargement des informations ...", + "no_info": "Le développeur n'a pas fourni plus d'informations pour ce dépôt", + "open_issues": "Open issues", + "open_repo": "Ouvrir dépôt", + "stars": "Étoiles", + "version_installed": "Version installée" + }, + "dialog_install": { + "restart": "N'oubliez pas que vous devez redémarrer Home Assistant avant que les modifications aux intégrations (custom_components) soient appliquées.", + "select_version": "Sélectionnez une version", + "show_beta": "Afficher les versions bêta", + "type": "Type", + "url": "URL" + }, + "dialog_removed": { + "link": "Lien externe vers plus d'informations", + "name": "Nom du dépôt", + "reason": "Raison de la suppression", + "type": "Type de suppression" + }, + "dialog_update": { + "available_version": "Version disponible", + "changelog": "Changelog", + "installed_version": "Version installée", + "releasenotes": "Notes de version pour {release}", + "title": "Mise à jour en attente" + }, + "dialog": { + "reload": { + "confirm": "Voulez-vous faire cela maintenant ?", + "description": "Vous devez vider le cache de votre navigateur lors de la modification des ressources Lovelace." + } + }, + "entry": { + "information": "Information", + "intro": "Les mises à jour et les messages importants s'afficheront ici s'il y en a", + "messages": { + "disabled": { + "content": "Vérifiez votre fichier journal pour plus de détails", + "title": "HACS est désactivé" + }, + "has_pending_tasks": { + "content": "Certains dépôts peuvent ne pas apparaître tant que cette opération n'est pas terminée", + "title": "Tâches d’arrière-plan en attente" + }, + "removed": "Dépôt '{repository}' supprimé", + "resources": { + "content": "Vous avez {number} éléments Lovelace qui ne sont pas chargés correctement dans Lovelace.", + "title": "Non chargé dans Lovelace" + }, + "restart": { + "content": "Vous disposez de {number} intégrations qui nécessitent un redémarrage de Home Assistant, vous pouvez le faire à partir de la section \"Contrôle du serveur\" dans la partie configuration de l'interface utilisateur de Home Assistant.", + "title": "En attente de redémarrage" + }, + "setup": { + "content": "HACS est en cours de configuration, pendant ce temps, certaines informations peuvent être manquantes ou incorrectes", + "title": "HACS se met en place" + }, + "startup": { + "content": "HACS démarre, pendant ce temps, certaines informations peuvent être manquantes ou incorrectes", + "title": "HACS est en train de démarrer" + }, + "waiting": { + "content": "HACS attend que Home Assistant termine le démarrage avant de démarrer les tâches de démarrage", + "title": "HACS attend" + }, + "wrong_frontend_installed": { + "content": "Vous avez {running} du frontend HACS installé, mais la version {expected} était attendue. Si ce message s'affiche, Home Assistant n'a pas pu installer la nouvelle version, essayez de redémarrer Home Assistant.", + "title": "Version frontend inattendue" + }, + "wrong_frontend_loaded": { + "content": "Vous exécutez la version {running} de l'interface HACS, mais la version {expected} était attendue, vous devez vider le cache de votre navigateur.", + "title": "Version frontend inattendue" + } + }, + "pending_updates": "Mises à jour en attente" + }, + "menu": { + "about": "À propos de HACS", + "clear": "Effacer tous les nouveaux", + "custom_repositories": "Dépôts personnalisés", + "dismiss": "Rejeter tous les nouveaux dépôts", + "documentation": "Documentation", + "open_issue": "Open issue", + "reload": "Recharger la fenêtre" + }, + "options": { + "step": { + "user": { + "data": { + "appdaemon": "Activer la découverte et le suivi des applications AppDaemon", + "country": "Filtrer par code pays.", + "debug": "Activer le débogage.", + "experimental": "Activer les fonctionnalités expérimentales", + "netdaemon": "Activer la découverte et le suivi des applications NetDaemon", + "not_in_use": "Non utilisé avec YAML", + "release_limit": "Nombre de recensés à afficher.", + "sidepanel_icon": "Icône de la barre latérale", + "sidepanel_title": "Titre de la barre latérale" + } + } + } + }, + "repository_banner": { + "config_flow": "Cette intégration prend en charge config_flow, ce qui signifie que vous pouvez maintenant accéder à la section d'intégration de votre interface utilisateur pour le configurer.", + "config_flow_title": "Configuration de l'interface utilisateur prise en charge", + "integration_not_loaded": "Cette intégration n'est pas chargée dans Home Assistant.", + "no_restart_required": "Aucun redémarrage requis", + "not_loaded": "Non chargé", + "plugin_not_loaded": "Ce plugin n'est pas ajouté à vos ressources Lovelace.", + "restart": "Vous devez redémarrer Home Assistant.", + "restart_pending": "Redémarrage en attente" + }, + "repository_card": { + "dismiss": "rejeter", + "hide": "Cacher", + "information": "Information", + "new_repository": "Nouveau dépôt", + "not_loaded": "Non chargé", + "open_issue": "Ouvrir issue", + "open_source": "Open source", + "pending_restart": "Redémarrage en attente", + "pending_update": "Mise à jour en attente", + "reinstall": "Réinstaller", + "report": "Rapport de suppression", + "update_information": "Mettre à jour les informations" + }, + "repository": { + "add_to_lovelace": "Ajouter à Lovelace", + "authors": "Auteurs", + "available": "Disponible", + "back_to": "Retour", + "changelog": "Change log", + "downloads": "Téléchargements", + "flag_this": "Marquer", + "frontend_version": "Version de l'interface", + "github_stars": "Étoiles GitHub", + "goto_integrations": "Aller aux intégrations", + "hide": "Masquer", + "hide_beta": "Masquer les bêta", + "install": "Installer", + "installed": "Installés", + "lovelace_copy_example": "Copier cet exemple dans le presse-papier", + "lovelace_instruction": "Quand vous l'ajoutez à votre configuration lovelace, utilisez", + "lovelace_no_js_type": "Impossible de déterminer le type de plugin, veuillez vérifier le dépôt", + "newest": "nouveau", + "note_appdaemon": "vous devez toujours l'ajouter à votre fichier 'apps.yaml'", + "note_installed": "Une fois installé, il se trouvera dans", + "note_integration": "Vous devez toujours l'ajouter à votre fichier 'configuration.yaml'", + "note_plugin": "Vous devez toujours l'ajouter à votre configuration lovelace ('ui-lovelace.yaml' ou l'éditeur de configuration de l'interface)", + "note_plugin_post_107": "Vous devez toujours l'ajouter à votre configuration lovelace ('configuration.yaml' ou l'éditeur de configuration de l'interface '\/config\/lovelace\/resources')", + "open_issue": "Ouvrir un ticket", + "open_plugin": "Ouvrir l'élément", + "reinstall": "Réinstaller", + "repository": "Dépôt", + "restart_home_assistant": "Redémarrer Home Assistant", + "show_beta": "Afficher les bêta", + "uninstall": "Désinstaller", + "update_information": "Mettre à jour les informations", + "upgrade": "Mettre à jour" + }, + "search": { + "installed": "Rechercher des dépôts installés", + "installed_new": "Rechercher des dépôts installés ou nouveaux", + "placeholder": "Rechercher un dépôt" + }, + "sections": { + "about": { + "description": "Afficher des informations sur le système HACS", + "title": "À propos de" + }, + "addon": { + "description": "Il n'y a pas de modules complémentaires dans HACS, mais vous pouvez cliquer ici pour aller au superviseur", + "title": "Modules complémentaires" + }, + "automation": { + "description": "C'est ici que vous trouverez les scripts python, les applications AppDaemon et NetDaemon", + "title": "Automatisation" + }, + "frontend": { + "description": "C'est ici que vous trouverez des thèmes, des cartes personnalisées et d'autres éléments pour lovelace", + "title": "Frontend" + }, + "integrations": { + "description": "C'est ici que vous trouverez les intégrations personnalisées (custom_components)", + "title": "Intégrations" + }, + "pending_repository_upgrade": "Vous utilisez la version {installed} , la version {available} est disponible" + }, + "settings": { + "add_custom_repository": "AJOUTER UN DÉPÔT PERSONNALISÉ", + "adding_new_repo": "Ajout d'un nouveau dépôt '{repo}'", + "adding_new_repo_category": "Avec la catégorie '{category}'.", + "bg_task_custom": "Les dépôts personnalisés sont masqués pendant l'exécution de tâches en arrière-plan.", + "category": "Catégorie", + "compact_mode": "Mode compact", + "custom_repositories": "DÉPÔTS PERSONNALISÉS", + "delete": "Supprimer", + "display": "Affichage", + "grid": "Grille", + "hacs_repo": "Dépôt HACS", + "hidden_repositories": "dépôts cachés", + "missing_category": "Vous devez sélectionner une catégorie", + "open_repository": "Ouvrir dépôt", + "reload_data": "Recharger les données", + "reload_window": "Recharger la fenêtre", + "repository_configuration": "Configuration de dépôt", + "save": "Enregistrer", + "table": "Tableau", + "table_view": "Vue table", + "unhide": "Afficher", + "upgrade_all": "Tout mettre à jour" + }, + "store": { + "ascending": "ascendant", + "clear_new": "Effacer tous les nouveaux dépôts", + "descending": "descendant", + "last_updated": "Dernière mise à jour", + "name": "Nom", + "new_repositories": "Nouveaux dépôts", + "new_repositories_note": "Vous avez plus de 10 nouveaux dépôts qui apparaissent ici. Si vous voulez les effacer tous, cliquez sur les 3 points en haut à droite de l'écran et ignorez-les tous.", + "no_repositories": "Aucun dépôt", + "no_repositories_desc1": "Il semble que vous n’avez pas de dépôts installés dans cette section encore.", + "no_repositories_desc2": "Cliquez sur le + dans le coin inférieur pour ajouter votre premier !", + "no_repositories_found_desc1": "Aucun référentiel installé correspondant à \" {searchInput} \" n'a été trouvé dans cette section.", + "no_repositories_found_desc2": "Essayez de chercher autre chose !", + "pending_upgrades": "Mises à niveau en attente", + "placeholder_search": "Veuillez entrer un terme de recherche...", + "sort": "Trier", + "stars": "Étoiles", + "status": "Statut" + }, + "time": { + "ago": "il y a", + "day": "jour", + "days": "jours", + "hour": "heure", + "hours": "heures", + "minute": "minute", + "minutes": "minutes", + "month": "mois", + "months": "mois", + "one": "Un", + "one_day_ago": "il y a 1 jour", + "one_hour_ago": "il y a 1 heure", + "one_minute_ago": "il y a 1 minute", + "one_month_ago": "il y a 1 mois", + "one_second_ago": "il y a 1 seconde", + "one_year_ago": "il y a 1 an", + "second": "seconde", + "seconds": "secondes", + "x_days_ago": "il y a {x} jours", + "x_hours_ago": "il y a {x} heures", + "x_minutes_ago": "il y a {x} minutes", + "x_months_ago": "il y a {x} mois", + "x_seconds_ago": "il y a {x} secondes", + "x_years_ago": "il y a {x} ans", + "year": "an", + "years": "ans" + } +} \ No newline at end of file diff --git a/custom_components/hacs/translations/hu.json b/custom_components/hacs/translations/hu.json new file mode 100644 index 0000000..013f0dd --- /dev/null +++ b/custom_components/hacs/translations/hu.json @@ -0,0 +1,378 @@ +{ + "common": { + "about": "Névjegy", + "add": "hozzáadás", + "appdaemon": "AppDaemon", + "appdaemon_apps": "AppDaemon Appok", + "appdaemon_plural": "AppDaemon appok", + "background_task": "Éppen háttérfeladat fut, ez az oldal frissülni fog, ha kész.", + "cancel": "Mégse", + "check_log_file": "További részletekért ellenőrizd a naplófájlt.", + "continue": "Folytatás", + "disabled": "Tiltva", + "documentation": "Dokumentáció", + "element": "bővítmény", + "hacs_is_disabled": "A HACS le van tiltva", + "ignore": "Mellőzés", + "install": "Telepítés", + "installed": "Telepített", + "integration": "Integráció", + "integration_plural": "Integrációk", + "integrations": "Integrációk", + "lovelace": "Lovelace", + "lovelace_element": "Lovelace bővítmény", + "lovelace_elements": "Lovelace bővítmények", + "manage": "kezelés", + "netdaemon": "NetDaemon", + "netdaemon_apps": "NetDaemon Appok", + "netdaemon_plural": "NetDaemon appok", + "plugin": "Lovelace", + "plugin_plural": "Lovelace bővítmények", + "plugins": "Lovelace bővítmények", + "python_script": "Python Szkript", + "python_script_plural": "Python szkriptek", + "python_scripts": "Python Szkriptek", + "reload": "Újratöltés", + "repositories": "Tárolók", + "repository": "Tároló", + "settings": "beállítások", + "theme": "Téma", + "theme_plural": "Témák", + "themes": "Témák", + "uninstall": "Eltávolítás", + "update": "Frissítés", + "version": "Verzió" + }, + "config": { + "abort": { + "single_instance_allowed": "Csak egyetlen HACS konfiguráció megengedett." + }, + "error": { + "acc": "El kell fogadnod minden nyilatkozatot a folytatás előtt", + "auth": "A személyes hozzáférési token nem megfelelő" + }, + "step": { + "device": { + "description": "Open {url} and paste this key `{code}` to authorize HACS, when you have done that click 'submit'", + "title": "HACS" + }, + "user": { + "data": { + "acc_addons": "I know that there are no add-ons in HACS", + "acc_disable": "I know that if I get issues with Home Assistant I should disable all my custom_components", + "acc_logs": "I know how to access Home Assistant logs", + "acc_untested": "I know that everything inside HACS is custom and untested by Home Assistant", + "appdaemon": "Enable AppDaemon apps discovery & tracking", + "netdaemon": "Enable NetDaemon apps discovery & tracking", + "python_script": "Enable python_scripts discovery & tracking", + "sidepanel_icon": "Side panel icon", + "sidepanel_title": "Side panel title", + "theme": "Enable Themes discovery & tracking", + "token": "GitHub Personal Access Token" + }, + "description": "Before you can setup HACS you need to acknowledge the following", + "title": "HACS" + } + }, + "title": "HACS (Home Assistant Community Store)" + }, + "confirm": { + "add_to_lovelace": "Biztosan hozzá szeretnéd ezt adni a Lovelace erőforrásokhoz?", + "bg_task": "A művelet le van tiltva, amíg háttérfeladat fut.", + "cancel": "Mégse", + "continue": "Biztosan folytatni szeretnéd?", + "delete": "Biztosan törölni szeretnéd a(z) '{item}'-t?", + "delete_installed": "A(z) '{item}' telepítve van, törlés előtt el kell távolítanod.", + "exist": "{item} már létezik", + "generic": "Biztos vagy benne?", + "home_assistant_is_restarting": "Várj, a Home Assistant éppen újraindul.", + "home_assistant_version_not_correct": "A Home Assistant '{haversion}' verzióját használod, de ehhez a tárolóhoz legalább a(z) '{minversion}' verzióra van szükség.", + "no": "Nem", + "no_upgrades": "Nincsenek elérhető frissítések", + "ok": "OK", + "overwrite": "Ezzel felül fogod írni.", + "reload_data": "Ez újratölti a HACS által ismert összes tároló adatát, ami némi időbe telhet.", + "restart_home_assistant": "Biztosan újraindítod a Home Assistant programot?", + "uninstall": "Biztosan el szeretnéd távolítani a(z) '{item}'-t?", + "upgrade_all": "Ez frissíteni fogja az összes tárolót. Győzödj meg róla, hogy elolvastad az összes kiadási megjegyzést, mielőtt továbblépnél.", + "yes": "Igen" + }, + "dialog_about": { + "frontend_version": "Frontend verzió", + "installed_repositories": "Telepített tárolók", + "integration_version": "Integráció verzió", + "useful_links": "Hasznos linkek" + }, + "dialog_add_repo": { + "limit": "Csak az első 100 tároló jelenik meg, használd a keresőt a találatok szűkítéséhez", + "no_match": "Nincs a szűrésnek megfelelő tároló", + "sort_by": "Rendezés", + "title": "Tároló hozzáadása" + }, + "dialog_custom_repositories": { + "category": "Kategória", + "no_category": "Hiányzó kategória", + "no_repository": "Hiányzó tároló", + "title": "Egyéni tárolók", + "url_placeholder": "Egyéni tároló URL címének hozzáadása" + }, + "dialog_info": { + "author": "Szerző", + "downloads": "Letöltések", + "install": "Tároló telepítése HACS-ben", + "loading": "Információ betöltése...", + "no_info": "A fejlesztő nem adott meg több információt ehhez a tárolóhoz", + "open_issues": "Jelentett problémák", + "open_repo": "Tároló megnyitása", + "stars": "Csillagok", + "version_installed": "Telepített verzió" + }, + "dialog_install": { + "restart": "Ne feledd, hogy az egyéni integrációk (custom_components) módosításainak alkalmazásához újra kell indítanod a Home Assistant alkalmazást.", + "select_version": "Verzió kiválasztása", + "show_beta": "Béta verziók megjelenítése", + "type": "Típus", + "url": "URL" + }, + "dialog_removed": { + "link": "Külső link további információkhoz", + "name": "Tároló neve", + "reason": "Eltávolítás oka", + "type": "Eltávolítás típusa" + }, + "dialog_update": { + "available_version": "Elérhető verzió", + "changelog": "Változási napló", + "installed_version": "Telepített verzió", + "releasenotes": "{release} kiadási megjegyzései", + "title": "Frissítés érhető el" + }, + "dialog": { + "reload": { + "confirm": "Meg szeretnéd most ezt tenni?", + "description": "Törölnöd kell a böngésződ gyorsítótárát a Lovelace erőforrások módosításakor." + } + }, + "entry": { + "information": "Információ", + "intro": "A frissítések és a fontos üzenetek itt jelennek meg, ha vannak", + "messages": { + "disabled": { + "content": "További részletek a naplófájlban", + "title": "A HACS le van tiltva" + }, + "has_pending_tasks": { + "content": "Előfordulhat, hogy egyes tárolók nem jelennek meg, amíg ez be nem fejeződik", + "title": "Függőben lévő háttérfeladatok" + }, + "removed": "Eltávolított tároló '{repository}'", + "resources": { + "title": "Nincs betöltve a Lovelace-ben" + }, + "restart": { + "title": "Újraindításra vár" + }, + "setup": { + "content": "A HACS beállítása folyamatban van, ez idő alatt bizonyos információk hiányozhatnak vagy helytelenek lehetnek", + "title": "A HACS beállítása folyamatban van" + }, + "startup": { + "content": "A HACS éppen indul, ez idő alatt bizonyos információk hiányozhatnak vagy helytelenek lehetnek", + "title": "A HACS éppen indul" + }, + "waiting": { + "content": "A HACS az indítási feladatok megkezdése előtt arra vár, hogy a Home Assistant befejezze az indulást", + "title": "A HACS vár" + }, + "wrong_frontend_installed": { + "title": "Nem várt frontend verzió" + }, + "wrong_frontend_loaded": { + "title": "Nem várt frontend verzió" + } + }, + "pending_updates": "Frissítések érhetők el" + }, + "menu": { + "about": "HACS névjegye", + "clear": "Új jelölések törlése", + "custom_repositories": "Egyéni tárolók", + "dismiss": "Minden új tároló elvetése", + "documentation": "Dokumentáció", + "open_issue": "Probléma jelentése", + "reload": "Ablak újratöltése" + }, + "options": { + "step": { + "user": { + "data": { + "appdaemon": "AppDaemon appok felfedezésének és nyomon követésének engedélyezése", + "country": "Szűrés országkóddal.", + "debug": "Hibakeresés engedélyezése.", + "experimental": "Kísérleti funkciók engedélyezése", + "netdaemon": "NetDaemon appok felfedezésének és nyomon követésének engedélyezése", + "not_in_use": "YAML-lel nem használható", + "release_limit": "Megjelenítendő kiadások száma.", + "sidepanel_icon": "Oldalsó panel ikon", + "sidepanel_title": "Oldalsó panel cím" + } + } + } + }, + "repository_banner": { + "config_flow": "Ez az integráció támogatja a config_flow-t, vagyis a felhasználói felületen az integrációk menüben lehet konfigurálni.", + "config_flow_title": "A felhasználói felület konfigurációja támogatott", + "integration_not_loaded": "Ez az integráció nincs betöltve a Home Assistantban.", + "no_restart_required": "Nincs szükség újraindításra", + "not_loaded": "Nincs betöltve", + "plugin_not_loaded": "Ez a bővítmény nincs hozzáadva a Lovelace erőforrásaidhoz.", + "restart": "Indítsd újra a Home Assistant programot.", + "restart_pending": "Újraindítás függőben" + }, + "repository_card": { + "dismiss": "elvetés", + "hide": "Elrejtés", + "information": "Információ", + "new_repository": "Új tároló", + "not_loaded": "Nincs betöltve", + "open_issue": "Probléma jelentése", + "open_source": "Forrás megnyitása", + "pending_restart": "Újraindításra vár", + "pending_update": "Frissítés érhető el", + "reinstall": "Újratelepítés", + "report": "Jelentés eltávolításra", + "update_information": "Frissítési információ" + }, + "repository": { + "add_to_lovelace": "Hozzáadás a Lovelace-hez", + "authors": "Szerzők", + "available": "Elérhető", + "back_to": "Vissza -", + "changelog": "Változási napló", + "downloads": "Letöltések", + "flag_this": "Megjelölés", + "frontend_version": "Frontend verzió", + "github_stars": "GitHub csillagok", + "goto_integrations": "Ugrás az integrációkhoz", + "hide": "Elrejtés", + "hide_beta": "Béta elrejtése", + "install": "Telepítés", + "installed": "Telepített", + "lovelace_copy_example": "Példa másolása a vágólapra", + "lovelace_instruction": "Amikor hozzáadod ezt a lovelace konfigurációdhoz, használd ezt", + "lovelace_no_js_type": "Nem sikerült meghatározni a bővítmény típusát, ellenőrizd a tárolót.", + "newest": "legújabb", + "note_appdaemon": "de még hozzá kell adnod az 'apps.yaml' fájlhoz", + "note_installed": "Telepítéskor a következő helyre kerül:", + "note_integration": "de még hozzá kell adnod a 'configuration.yaml' fájlhoz", + "note_plugin": "de még hozzá kell adnod a lovelace konfigurációhoz (az 'ui-lovelace.yaml' fájlban vagy a Lovelace felületen a konfiguráció szerkesztőben)", + "note_plugin_post_107": "de még hozzá kell adnod a lovelace konfigurációhoz ('configuration.yaml' vagy az erőforrás szerkesztőben '\/config\/lovelace\/resources')", + "open_issue": "Probléma jelentése", + "open_plugin": "Bővítmény megnyitása", + "reinstall": "Újratelepítés", + "repository": "Tároló", + "restart_home_assistant": "Home Assistant újraindítása", + "show_beta": "Béta megjelenítése", + "uninstall": "Eltávolítás", + "update_information": "Frissítési információk", + "upgrade": "Frissítés" + }, + "search": { + "installed": "Telepített tárolók keresése", + "installed_new": "Telepített vagy új tárolók keresése", + "placeholder": "Tároló keresése" + }, + "sections": { + "about": { + "description": "Információk megjelenítése a HACS-ről", + "title": "Névjegy" + }, + "addon": { + "description": "A HACS-ban nincsenek kiegészítők, de ide kattintva eljuthatsz a supervisor-hoz", + "title": "Kiegészítők" + }, + "automation": { + "description": "Itt Python szkripteket, AppDaemon és NetDaemon appokat találsz", + "title": "Automatizálás" + }, + "frontend": { + "description": "Itt témákat, egyéni kártyákat és más bővítményeket találsz a Lovelace-hez", + "title": "Frontend" + }, + "integrations": { + "description": "Itt találod az egyéni integrációkat (custom_components)", + "title": "Integrációk" + }, + "pending_repository_upgrade": "A(z) {installed} verziót futtatod, a(z) {available} verzió már elérhető" + }, + "settings": { + "add_custom_repository": "EGYÉNI TÁROLÓ HOZZÁADÁSA", + "adding_new_repo": "Új tároló hozzáadása '{repo}'", + "adding_new_repo_category": "A '{category}' kategóriával.", + "bg_task_custom": "Az egyéni tárolók rejtve vannak, amíg háttérfeladat fut.", + "category": "Kategória", + "compact_mode": "Kompakt mód", + "custom_repositories": "EGYÉNI TÁROLÓK", + "delete": "Törlés", + "display": "Megjelenítés", + "grid": "Rács", + "hacs_repo": "HACS tároló", + "hidden_repositories": "rejtett tárolók", + "missing_category": "Ki kell választanod egy kategóriát", + "open_repository": "Tároló megnyitása", + "reload_data": "Adatok újratöltése", + "reload_window": "Ablak újratöltése", + "repository_configuration": "Tároló konfiguráció", + "save": "Mentés", + "table": "Táblázat", + "table_view": "Táblázat nézet", + "unhide": "felfedés", + "upgrade_all": "Minden frissítése" + }, + "store": { + "ascending": "növekvő", + "clear_new": "Új tárolók megjelölése látottként", + "descending": "csökkenő", + "last_updated": "Utolsó frissítés", + "name": "Név", + "new_repositories": "Új tárolók", + "new_repositories_note": "Több mint 10 új tároló látható. Ha törölni szeretnéd őket, akkor kattints a jobb felső sarokban lévő 3 pontra, és válaszd ki a 'Minden új tároló elvetése' menüpontot.", + "no_repositories": "Nincsenek tárolók", + "no_repositories_desc1": "Úgy tűnik, még nincsenek telepítve tárolók ebben a szekcióban.", + "no_repositories_desc2": "Kattints az alsó sarokban található + jelre az első hozzáadásához!", + "no_repositories_found_desc2": "Próbálj valami mást keresni!", + "pending_upgrades": "Frissítések érhetők el", + "placeholder_search": "Kérlek adj meg egy keresési kifejezést...", + "sort": "rendezés", + "stars": "Csillag", + "status": "Állapot" + }, + "time": { + "ago": "ezelőtt", + "day": "nap", + "days": "nap", + "hour": "óra", + "hours": "óra", + "minute": "perc", + "minutes": "perc", + "month": "hónap", + "months": "hónap", + "one": "Egy", + "one_day_ago": "egy nappal ezelőtt", + "one_hour_ago": "egy órával ezelőtt", + "one_minute_ago": "egy perccel ezelőtt", + "one_month_ago": "egy hónappal ezelőtt", + "one_second_ago": "egy másodperccel ezelőtt", + "one_year_ago": "egy évvel ezelőtt", + "second": "másodperc", + "seconds": "másodperc", + "x_days_ago": "{x} nappal ezelőtt", + "x_hours_ago": "{x} órával ezelőtt", + "x_minutes_ago": "{x} perccel ezelőtt", + "x_months_ago": "{x} hónappal ezelőtt", + "x_seconds_ago": "{x} másodperccel ezelőtt", + "x_years_ago": "{x} évvel ezelőtt", + "year": "év", + "years": "év" + } +} \ No newline at end of file diff --git a/custom_components/hacs/translations/it.json b/custom_components/hacs/translations/it.json new file mode 100644 index 0000000..dc376fc --- /dev/null +++ b/custom_components/hacs/translations/it.json @@ -0,0 +1,366 @@ +{ + "common": { + "about": "Informazioni su", + "add": "aggiungi", + "appdaemon": "AppDaemon", + "appdaemon_apps": "Applicazioni AppDaemon", + "appdaemon_plural": "Applicazioni AppDaemon", + "background_task": "Attività in esecuzione, questa pagina sarà ricaricata al termine.", + "cancel": "Annulla", + "check_log_file": "Controlla il tuo file di registro per maggiori dettagli.", + "continue": "Continua", + "disabled": "Disabilitato", + "documentation": "Documentazione", + "element": "elemento", + "hacs_is_disabled": "HACS è disabilitato", + "install": "Installa", + "installed": "Installati", + "integration": "Integrazione", + "integration_plural": "Integrazioni", + "integrations": "Integrazioni", + "lovelace": "Lovelace", + "lovelace_element": "Elemento Lovelace", + "lovelace_elements": "Elementi di Lovelace", + "manage": "gestione", + "netdaemon": "NetDaemon", + "netdaemon_apps": "Applicazioni NetDaemon", + "netdaemon_plural": "Applicazioni NetDaemon", + "plugin": "Lovelace", + "plugin_plural": "Elementi di Lovelace", + "plugins": "Elementi di Lovelace", + "python_script": "Script python", + "python_script_plural": "Script in Python", + "python_scripts": "Script python", + "reload": "Ricarica", + "repositories": "Repository", + "repository": "Repository", + "settings": "Impostazioni", + "theme": "Tema", + "theme_plural": "Temi", + "themes": "Temi", + "uninstall": "Disinstallare", + "update": "Aggiorna", + "version": "Versione" + }, + "config": { + "step": { + "device": { + "description": "Open {url} and paste this key `{code}` to authorize HACS, when you have done that click 'submit'", + "title": "HACS" + }, + "user": { + "data": { + "acc_addons": "I know that there are no add-ons in HACS", + "acc_disable": "I know that if I get issues with Home Assistant I should disable all my custom_components", + "acc_logs": "I know how to access Home Assistant logs", + "acc_untested": "I know that everything inside HACS is custom and untested by Home Assistant", + "appdaemon": "Enable AppDaemon apps discovery & tracking", + "netdaemon": "Enable NetDaemon apps discovery & tracking", + "python_script": "Enable python_scripts discovery & tracking", + "sidepanel_icon": "Side panel icon", + "sidepanel_title": "Side panel title", + "theme": "Enable Themes discovery & tracking", + "token": "GitHub Personal Access Token" + } + } + }, + "title": "HACS (Home Assistant Community Store)" + }, + "confirm": { + "add_to_lovelace": "Sei sicuro di voler aggiungerlo alle tue risorse Lovelace?", + "bg_task": "L'azione è disabilitata mentre sono in esecuzione attività in background.", + "cancel": "Annulla", + "continue": "Sei sicuro di voler continuare?", + "delete": "Sei sicuro di voler disinstallare '{item}'?", + "delete_installed": "'{item}' è installato, è necessario disinstallarlo prima di poterlo eliminare.", + "exist": "{item} esiste già", + "generic": "Sei sicuro?", + "home_assistant_is_restarting": "Aspetta, Home Assistant si sta riavviando.", + "home_assistant_version_not_correct": "Stai eseguendo la versione Home Assistant '{haversion}', ma questo repository richiede l'installazione della versione minima '{minversion}'.", + "no": "No", + "no_upgrades": "Nessun aggiornamento in sospeso", + "ok": "OK", + "overwrite": "In questo modo lo sovrascriverà.", + "reload_data": "Questo ricarica i dati di tutte le repository di cui HACS è a conoscenza, ci vorrà del tempo per finire.", + "restart_home_assistant": "Sei sicuro di voler riavviare Home Assistant?", + "uninstall": "Sei sicuro di voler disinstallare '{item}'?", + "upgrade_all": "Questa azione aggiornerà tutti i repository, assicurati di aver letto le note di rilascio prima di procedere.", + "yes": "Sì" + }, + "dialog_about": { + "frontend_version": "Versione frontend", + "installed_repositories": "Repository installati", + "integration_version": "Versione di integrazione", + "useful_links": "Link utili" + }, + "dialog_add_repo": { + "limit": "Vengono visualizzati solo i primi 100 repository, utilizza la ricerca per filtrare ciò di cui hai bisogno", + "no_match": "Nessun repository trovato corrispondente al filtro", + "sort_by": "Ordina per", + "title": "Aggiungi repository" + }, + "dialog_custom_repositories": { + "category": "Categoria", + "no_category": "Categoria mancante", + "no_repository": "Repository mancante", + "title": "Repository personalizzati", + "url_placeholder": "Aggiungi l'URL del repository personalizzato" + }, + "dialog_info": { + "author": "Autore", + "downloads": "Download", + "install": "Installa questo repository in HACS", + "loading": "Caricamento informazioni in corso ...", + "no_info": "Lo sviluppatore non ha fornito ulteriori informazioni per questo repository", + "open_issues": "Problemi aperti", + "open_repo": "Apri il repository", + "stars": "Stelle", + "version_installed": "Versione installata" + }, + "dialog_install": { + "restart": "Tenere presente che è necessario riavviare Home Assistant prima di applicare le modifiche alle integrazioni (custom_components).", + "select_version": "Seleziona la versione", + "show_beta": "Mostra le versioni beta", + "type": "Tipo", + "url": "URL" + }, + "dialog_update": { + "available_version": "Versione disponibile", + "changelog": "Registro dei cambiamenti", + "installed_version": "Versione installata", + "releasenotes": "Note di rilascio per {release}", + "title": "Aggiornamento in sospeso" + }, + "dialog": { + "reload": { + "confirm": "Vuoi farlo adesso?", + "description": "È necessario cancellare la cache del browser quando si modificano le risorse di Lovelace." + } + }, + "entry": { + "information": "Informazioni", + "intro": "Gli aggiornamenti e i messaggi importanti verranno visualizzati qui se presenti", + "messages": { + "disabled": { + "content": "Controlla il tuo file di registro per maggiori dettagli", + "title": "HACS è disabilitato" + }, + "has_pending_tasks": { + "content": "Alcuni repository potrebbero non essere visualizzati fino al completamento", + "title": "Attività in background in sospeso" + }, + "resources": { + "content": "Hai {number} elementi di Lovelace che non sono stati caricati correttamente in Lovelace.", + "title": "Non caricato in Lovelace" + }, + "restart": { + "content": "Hai {number} integrazioni che richiedono il riavvio di Home Assistant, puoi farlo dalla sezione \"Controlli del server\" nella sezione Impostazioni dell'interfaccia utente di Home Assistant.", + "title": "In attesa di riavvio" + }, + "setup": { + "content": "HACS è in fase di configurazione, durante questo periodo alcune informazioni potrebbero essere mancanti o errate", + "title": "HACS si sta installando" + }, + "startup": { + "content": "HACS si sta avviando, durante questo periodo alcune informazioni potrebbero essere mancanti o errate", + "title": "HACS si sta avviando" + }, + "waiting": { + "content": "HACS è in attesa che Home Assistant finisca l'avvio prima di iniziare le attività di avvio", + "title": "HACS è in attesa" + }, + "wrong_frontend_installed": { + "content": "Hai installato {running} del frontend HACS, ma era attesa la versione {expected}, se viene visualizzato questo messaggio Home Assistant non è stato in grado di installare la nuova versione, prova a riavviare Home Assistant.", + "title": "Versione frontend inaspettata" + }, + "wrong_frontend_loaded": { + "content": "Stai eseguendo la versione {running} del frontend HACS, ma la versione {expected} era prevista, è necessario svuotare la cache del browser.", + "title": "Versione frontend inaspettata" + } + }, + "pending_updates": "Aggiornamenti in sospeso" + }, + "menu": { + "about": "Informazioni su HACS", + "clear": "Cancella tutte le novità", + "custom_repositories": "Repository personalizzati", + "dismiss": "Elimina tutti i nuovi repository", + "documentation": "Documentazione", + "open_issue": "Segnala anomalia", + "reload": "Ricarica la finestra" + }, + "options": { + "step": { + "user": { + "data": { + "appdaemon": "Abilita il rilevamento e il monitoraggio delle applicazioni AppDaemon", + "country": "Filtra con prefisso internazionale.", + "debug": "Abilita debug.", + "experimental": "Abilita funzionalità sperimentali", + "netdaemon": "Abilita il rilevamento e il monitoraggio delle applicazioni NetDaemon", + "not_in_use": "Non in uso con YAML", + "release_limit": "Numero di versioni da mostrare.", + "sidepanel_icon": "Icona nel pannello laterale", + "sidepanel_title": "Titolo nel pannello laterale" + } + } + } + }, + "repository_banner": { + "config_flow": "Questa integrazione supporta config_flow, questo significa che è ora possibile passare alla sezione \"IntegrazionI\" dell'interfaccia utente per la configurazione.", + "config_flow_title": "Configurazione dell'interfaccia utente supportata", + "integration_not_loaded": "Questa integrazione non è caricata in Home Assistant.", + "no_restart_required": "Non è necessario riavviare", + "not_loaded": "Non caricato", + "plugin_not_loaded": "Questo elemento non è aggiunto alle tue risorse Lovelace.", + "restart": "È necessario riavviare Home Assistant.", + "restart_pending": "Riavvio in attesa" + }, + "repository_card": { + "dismiss": "elimina", + "hide": "Nascondi", + "information": "Informazioni", + "new_repository": "Nuovo repository", + "not_loaded": "Non caricato", + "open_issue": "Segnala anomalia", + "open_source": "Open source", + "pending_restart": "In attesa di riavvio", + "pending_update": "Aggiornamento in sospeso", + "reinstall": "Reinstallare", + "report": "Segnala per la rimozione", + "update_information": "Aggiorna informazioni" + }, + "repository": { + "add_to_lovelace": "Aggiungi a Lovelace", + "authors": "Autori", + "available": "Disponibile", + "back_to": "Torna a", + "changelog": "Change log", + "downloads": "Downloads", + "flag_this": "Spunta questo", + "frontend_version": "Frontend versione", + "github_stars": "GitHub stelle", + "goto_integrations": "Vai alle Integrazioni", + "hide": "Nascondi", + "hide_beta": "Nascondi beta", + "install": "Installa", + "installed": "Installato", + "lovelace_copy_example": "Copia l'esempio negli appunti", + "lovelace_instruction": "Quando lo aggiungi nella configurazione di Lovelace, usa questo", + "lovelace_no_js_type": "Impossibile determinare il tipo di questo elemento, controllare il repository.", + "newest": "Più recente", + "note_appdaemon": "dovrai aggiungerlo nel file 'apps.yaml'", + "note_installed": "Una volta installato, si troverà in", + "note_integration": "dovrai aggiungerlo nel file 'configuration.yaml'", + "note_plugin": "è comunque necessario aggiungerlo alla configurazione Lovelace ('ui-lovelace.yaml' o nell'editor grezzo di configurazione dell'Interfaccia Utente)", + "note_plugin_post_107": "è comunque necessario aggiungerlo alla configurazione di Lovelace ('configuration.yaml' o nell'editor di risorse '\/config\/lovelace\/resources')", + "open_issue": "Segnala anomalia", + "open_plugin": "Apri elemento", + "reinstall": "Reinstalla", + "repository": "Repository", + "restart_home_assistant": "Riavvia Home Assistant", + "show_beta": "Visualizza beta", + "uninstall": "Rimuovi", + "update_information": "Aggiorna informazioni", + "upgrade": "Aggiorna" + }, + "search": { + "installed": "Cerca i repository installati", + "installed_new": "Cercare repository installati o nuovi", + "placeholder": "Cerca repository" + }, + "sections": { + "about": { + "description": "Mostra informazioni su HACS", + "title": "Informazioni su" + }, + "addon": { + "description": "Non ci sono componenti aggiuntivi in HACS, ma puoi fare clic qui per accedere al supervisore", + "title": "Componenti aggiuntivi" + }, + "automation": { + "description": "Qui trovi python_scripts, app AppDaemon e app NetDaemon", + "title": "Automazione" + }, + "frontend": { + "description": "Qui troverai temi, schede personalizzate e altri elementi per Lovelace", + "title": "Frontend" + }, + "integrations": { + "description": "Qui si trovano le integrazioni personalizzate (custom_components)", + "title": "Integrazioni" + }, + "pending_repository_upgrade": "Stai eseguendo la versione {installed}, la versione {available}è disponibile" + }, + "settings": { + "add_custom_repository": "AGGIUNGI REPOSITORY PERSONALIZZATA", + "adding_new_repo": "Aggiunta di un nuovo repository '{repo}'", + "adding_new_repo_category": "Con la categoria '{category}'.", + "bg_task_custom": "I repository personalizzati sono nascosti mentre sono in esecuzione attività in background.", + "category": "Categoria", + "compact_mode": "Modalità compatta", + "custom_repositories": "REPOSITORY PERSONALIZZATE", + "delete": "Cancella", + "display": "Visualizza", + "grid": "Griglia", + "hacs_repo": "HACS repo", + "hidden_repositories": "repository nascosti", + "missing_category": "Devi selezionare una categoria", + "open_repository": "Apri il repository", + "reload_data": "Ricarica i dati", + "reload_window": "Ricarica la finestra", + "repository_configuration": "Configurazione del repository", + "save": "Salva", + "table": "Tabella", + "table_view": "Vista tabella", + "unhide": "Mostra", + "upgrade_all": "Aggiorna tutto" + }, + "store": { + "ascending": "ascendente", + "clear_new": "Ripulisci i nuovi repository", + "descending": "discendente", + "last_updated": "Ultimo aggiornamento", + "name": "Nome", + "new_repositories": "Nuovi repository", + "new_repositories_note": "Hai più di 10 nuovi repository visualizzati qui, se vuoi cancellarli tutti fai clic sui 3 punti nell'angolo in alto a destra e li eliminerai tutti.", + "no_repositories": "Nessun repository", + "no_repositories_desc1": "Sembra che non ci siano ancora repository installati in questa sezione.", + "no_repositories_desc2": "Fai clic sul + nell'angolo in basso per aggiungere il tuo primo!", + "no_repositories_found_desc1": "Nessun repository installato corrispondente a \"{searchInput}\" trovato in questa sezione.", + "no_repositories_found_desc2": "Prova a cercare qualcos'altro!", + "pending_upgrades": "Aggiornamenti in sospeso", + "placeholder_search": "Inserire un termine di ricerca", + "sort": "Ordinare", + "stars": "Stelle", + "status": "Stato" + }, + "time": { + "ago": "fa", + "day": "giorno", + "days": "giorni", + "hour": "ora", + "hours": "ore", + "minute": "minuto", + "minutes": "minuti", + "month": "mese", + "months": "mesi", + "one": "Un", + "one_day_ago": "un giorno fa", + "one_hour_ago": "un'ora fa", + "one_minute_ago": "un minuto fa", + "one_month_ago": "un mese fa", + "one_second_ago": "un secondo fa", + "one_year_ago": "un anno fa", + "second": "secondo", + "seconds": "secondi", + "x_days_ago": "{x} giorni fa", + "x_hours_ago": "{x} ore fa", + "x_minutes_ago": "{x} minuti fa", + "x_months_ago": "{x} mesi fa", + "x_seconds_ago": "{x} secondi fa", + "x_years_ago": "{x} anni fa", + "year": "anno", + "years": "anni" + } +} \ No newline at end of file diff --git a/custom_components/hacs/translations/nb.json b/custom_components/hacs/translations/nb.json new file mode 100644 index 0000000..5701d05 --- /dev/null +++ b/custom_components/hacs/translations/nb.json @@ -0,0 +1,383 @@ +{ + "common": { + "about": "Om", + "add": "legg til", + "appdaemon": "AppDaemon", + "appdaemon_apps": "AppDaemon Apper", + "appdaemon_plural": "AppDaemon-apper", + "background_task": "Bakgrunnsoppgaven kjører. Denne siden lastes inn på nytt når den er ferdig.", + "cancel": "Avbryt", + "check_log_file": "Sjekk loggfilen din for mer informasjon.", + "continue": "Fortsett", + "disabled": "Deaktivert", + "documentation": "dokumentasjon", + "element": "element", + "hacs_is_disabled": "HACS er deaktivert", + "ignore": "Ignorere", + "install": "Installer", + "installed": "Installert", + "integration": "Integrasjon", + "integration_plural": "Integrasjoner", + "integrations": "Integrasjoner", + "lovelace": "Lovelace", + "lovelace_element": "Lovelace-element", + "lovelace_elements": "Lovelace-elementer", + "manage": "manage", + "netdaemon": "NetDaemon", + "netdaemon_apps": "NetDaemon Apper", + "netdaemon_plural": "NetDaemon-apper", + "plugin": "Lovelace", + "plugin_plural": "Lovelace-elementer", + "plugins": "Lovelace-elementer", + "python_script": "Python-skript", + "python_script_plural": "Python-skript", + "python_scripts": "Python-skript", + "reload": "Last inn på nytt", + "repositories": "Repositories", + "repository": "Repository", + "settings": "Innstillinger", + "theme": "Tema", + "theme_plural": "Temaer", + "themes": "Temaer", + "uninstall": "Avinstaller", + "update": "Oppdater", + "version": "Versjon" + }, + "config": { + "abort": { + "single_instance_allowed": "Bare en konfigurasjon av HACS er tillatt." + }, + "error": { + "acc": "Du må bekrefte alle uttalelsene før du fortsetter", + "auth": "Personlig tilgangstoken er ikke korrekt" + }, + "step": { + "device": { + "description": "Open {url} and paste this key `{code}` to authorize HACS, when you have done that click 'submit'", + "title": "HACS" + }, + "user": { + "data": { + "acc_addons": "I know that there are no add-ons in HACS", + "acc_disable": "I know that if I get issues with Home Assistant I should disable all my custom_components", + "acc_logs": "I know how to access Home Assistant logs", + "acc_untested": "I know that everything inside HACS is custom and untested by Home Assistant", + "appdaemon": "Enable AppDaemon apps discovery & tracking", + "netdaemon": "Enable NetDaemon apps discovery & tracking", + "python_script": "Enable python_scripts discovery & tracking", + "sidepanel_icon": "Side panel icon", + "sidepanel_title": "Side panel title", + "theme": "Enable Themes discovery & tracking", + "token": "GitHub Personal Access Token" + }, + "description": "Before you can setup HACS you need to acknowledge the following", + "title": "HACS" + } + }, + "title": "HACS (Home Assistant Community Store)" + }, + "confirm": { + "add_to_lovelace": "Er du sikker på at du vil legge dette til i dine Lovelace resources?", + "bg_task": "Handlingen er deaktivert mens bakgrunnsoppgaver kjører.", + "cancel": "Avbryt", + "continue": "Er du sikker på at du vil fortsette?", + "delete": "Er du sikker på at du vil fjerne '{item}'?", + "delete_installed": "'{item}' er installert, du må avinstallere det før du kan slette det.", + "exist": "{item} eksisterer allerede", + "generic": "Er du sikker?", + "home_assistant_is_restarting": "Vent, Home Assistant starter nå på nytt.", + "home_assistant_version_not_correct": "Du kjører Home Assistant '{haversion}', men denne krever minimum versjon '{minversion}' for å bli installert.", + "no": "Nei", + "no_upgrades": "Ingen oppgraderinger tilgjengelig", + "ok": "OK", + "overwrite": "Å gjøre dette vil overskrive det.", + "reload_data": "Dette laster inn dataene til alle repositories HACS vet om, dette vil ta litt tid å fullføre.", + "restart_home_assistant": "Er du sikker på at du vil starte Home Assistant på nytt?", + "uninstall": "Er du sikker på at du vil avinstallere '{item}'?", + "upgrade_all": "Dette vil oppgradere alle disse repositorene, sørg for at du har lest utgivelses notatene for dem alle før du fortsetter.", + "yes": "Ja" + }, + "dialog_about": { + "frontend_version": "Frontend versjon", + "installed_repositories": "Installerte repositories", + "integration_version": "Integrasjonsversjon", + "useful_links": "Nyttige lenker" + }, + "dialog_add_repo": { + "limit": "Bare de første 100 elementene vises, bruk søket til å filtrere det du trenger", + "no_match": "Ingen elementer funnet som samsvarer med filteret ditt", + "sort_by": "Sorter etter", + "title": "Legg til repository" + }, + "dialog_custom_repositories": { + "category": "Kategori", + "no_category": "Mangler kategori", + "no_repository": "Mangler repository", + "title": "Custom repositories", + "url_placeholder": "Legg til custom repository" + }, + "dialog_info": { + "author": "Utgiver", + "downloads": "Nedlastinger", + "install": "Installer dette elementet i HACS", + "loading": "Laster inn informasjon ...", + "no_info": "Utvikleren har ikke gitt mer informasjon for dette elementet", + "open_issues": "Åpne problemer", + "open_repo": "Åpne repository", + "stars": "Stjerner", + "version_installed": "Versjon installert" + }, + "dialog_install": { + "restart": "Husk at du må starte Home Assistant på nytt før endringer i integrasjoner (custom_components) brukes.", + "select_version": "Velg versjon", + "show_beta": "Vis betaversjoner", + "type": "Type", + "url": "URL" + }, + "dialog_removed": { + "link": "Ekstern lenke til mer informasjon", + "name": "Navn på repositorium", + "reason": "Årsaken til fjerning", + "type": "Fjerningstype" + }, + "dialog_update": { + "available_version": "Tilgjengelig versjon", + "changelog": "Endringslogg", + "installed_version": "Installert versjon", + "releasenotes": "Utgivelsesnotater for {release}", + "title": "Oppdatering venter" + }, + "dialog": { + "reload": { + "confirm": "Vil du gjøre det nå?", + "description": "Du må tømme nettleserbufferen når du endrer ressurser for Lovelace." + } + }, + "entry": { + "information": "Informasjon", + "intro": "Oppdateringer og viktige meldinger vises her hvis det er noen", + "messages": { + "disabled": { + "content": "Sjekk loggfilen din for mer informasjon", + "title": "HACS er deaktivert" + }, + "has_pending_tasks": { + "content": "Noen elementer vises kanskje ikke før dette er fullført", + "title": "Venter på bakgrunnsoppgaver" + }, + "removed": "Fjernet repositoriet {repository}", + "resources": { + "content": "Du har {number} Lovelace-elementer som ikke er riktig lastet inn i Lovelace.", + "title": "Ikke lastet i Lovelace" + }, + "restart": { + "content": "Du har {number} integrasjoner som krever en omstart av Home Assistant, du kan gjøre det fra delen Serverkontroller under konfigurasjonsdelen av Home Assistant UI.", + "title": "Venter på omstart" + }, + "setup": { + "content": "HACS starter opp, i løpet av denne tiden kan det hende at noe informasjon mangler eller er feil", + "title": "HACS settes opp" + }, + "startup": { + "content": "HACS starter opp, i løpet av denne tiden kan det hende at noe informasjon mangler eller er feil", + "title": "HACS starter opp" + }, + "waiting": { + "content": "HACS venter på at Home Assistant skal fullføre oppstart før oppstart av oppgaver", + "title": "HACS venter" + }, + "wrong_frontend_installed": { + "content": "Du har {running} av HACS frontend installert, men versjon {expected} var forventet, hvis dette du ser denne meldingen Home Assistant ikke kunne installere den nye versjonen, kan du prøve å starte Home Assistant på nytt.", + "title": "Uventet frontend versjon" + }, + "wrong_frontend_loaded": { + "content": "Du kjører versjon {running} av HACS frontend, men versjon {expected} var forventet, du bør tømme nettleserens hurtigbuffer.", + "title": "Uventet frontend versjon" + } + }, + "pending_updates": "Oppdateringer er klare" + }, + "menu": { + "about": "Om HACS", + "clear": "Fjern alt nytt", + "custom_repositories": "Custom repositories", + "dismiss": "Avvis alle nye repositorier", + "documentation": "Dokumentasjon", + "open_issue": "Meld et problem", + "reload": "Last inn vinduet på nytt" + }, + "options": { + "step": { + "user": { + "data": { + "appdaemon": "Aktiver oppdagelse og sporing av AppDaemon-apper", + "country": "Filtrer med landskode.", + "debug": "Aktiver debug", + "experimental": "Aktiver eksperimentelle funksjoner", + "netdaemon": "Aktiver oppdagelse og sporing av NetDaemon-apper", + "not_in_use": "Ikke i bruk med YAML", + "release_limit": "Antall utgivelser som skal vises.", + "sidepanel_icon": "Sidepanel ikon", + "sidepanel_title": "Sidepanel tittel" + } + } + } + }, + "repository_banner": { + "config_flow": "Denne integrasjonen støtter config_flow, det betyr at du nå kan gå til integrasjoner i brukergrensesnittet for å konfigurere den.", + "config_flow_title": "UI konfigurasjon støttet", + "integration_not_loaded": "Integrasjonen er ikke lastet inn i Home Assistant.", + "no_restart_required": "Ingen omstart kreves", + "not_loaded": "Ikke lastet inn", + "plugin_not_loaded": "Dette elementet er ikke lagt til i lovelace under \"resource\" delen av konfigurasjonen.", + "restart": "Du må restart Home Assistant", + "restart_pending": "Restart er nødvendig" + }, + "repository_card": { + "dismiss": "Avvis", + "hide": "Skjul", + "information": "Informasjon", + "new_repository": "Ny", + "not_loaded": "Ikke lastet inn", + "open_issue": "Meld et problem", + "open_source": "Åpne kilde", + "pending_restart": "Venter på omstart", + "pending_update": "Oppdatering venter", + "reinstall": "Installer på nytt", + "report": "Rapporter for fjerning", + "update_information": "Oppdater informasjon" + }, + "repository": { + "add_to_lovelace": "Legg til i Lovelace", + "authors": "Laget av", + "available": "Tilgjengelig", + "back_to": "Tilbake til", + "changelog": "Endringslogg", + "downloads": "Nedlastinger", + "flag_this": "Flag dette", + "frontend_version": "Frontend versjon", + "github_stars": "GitHub-stjerner", + "goto_integrations": "Gå til integrasjoner", + "hide": "Skjul", + "hide_beta": "Skjul beta", + "install": "Installer", + "installed": "Installert", + "lovelace_copy_example": "Kopier eksemplet til utklippstavlen", + "lovelace_instruction": "Når du legger til dette i lovelace-konfigurasjonen din, bruk dette", + "lovelace_no_js_type": "Kunne ikke bestemme typen for dettte elementet, sjekk repository.", + "newest": "Nyeste", + "note_appdaemon": "du må fortsatt legge den til i 'apps.yaml' filen", + "note_installed": "Når det er installert, vil dette ligge i", + "note_integration": "du må fortsatt legge den til 'configuration.yaml' filen", + "note_plugin": "du må fortsatt legge den til i lovelace-konfigurasjonen ('ui-lovelace.yaml' eller den rå UI-konfigurasjonsredigereren)", + "note_plugin_post_107": "du må fortsatt legge den til i lovelace konfigurasjonen ('configuration.yaml' eller via resource behanleren i grensesnittet '\/config\/lovelace\/resources')", + "open_issue": "Meld et problem", + "open_plugin": "Åpne kilde", + "reinstall": "Installer på nytt", + "repository": "Repository", + "restart_home_assistant": "Start Home Assistant på nytt", + "show_beta": "Vis beta", + "uninstall": "Avinstaller", + "update_information": "Oppdater informasjon", + "upgrade": "Oppdater" + }, + "search": { + "installed": "Søke etter installerte repositorier", + "installed_new": "Søke etter installerte eller nye repositorier", + "placeholder": "Søk etter repository" + }, + "sections": { + "about": { + "description": "Vis informasjon om HACS", + "title": "Om" + }, + "addon": { + "description": "Det er ingen addons i HACS, men du kan klikke her for å komme til veilederen", + "title": "Add-ons" + }, + "automation": { + "description": "Det er her du finner python_scripts, AppDaemon-apper og NetDaemon-apper", + "title": "Automasjon" + }, + "frontend": { + "description": "Det er her du finner temaer, tilpassede kort og andre elementer for lovelace", + "title": "Frontend" + }, + "integrations": { + "description": "Det er her du finner tilpassede integrasjoner (custom_components)", + "title": "Integrasjoner" + }, + "pending_repository_upgrade": "Du kjører versjon {installed} , versjon {available} er tilgjengelig" + }, + "settings": { + "add_custom_repository": "LEGG TIL REPOSITORY", + "adding_new_repo": "Legger til ny repository '{repo}'", + "adding_new_repo_category": "Med kategori '{category}'.", + "bg_task_custom": "Custom repositories er skjult mens bakgrunnsoppgaver kjører.", + "category": "Kategori", + "compact_mode": "Kompakt modus", + "custom_repositories": "TILPASSEDE REPOSITORIER", + "delete": "Slett", + "display": "Vise", + "grid": "Nett", + "hacs_repo": "HACS repo", + "hidden_repositories": "Gjemte repositories", + "missing_category": "Du må velge en kategori", + "open_repository": "Åpne repository", + "reload_data": "Last inn data på nytt", + "reload_window": "Last inn vinduet på nytt", + "repository_configuration": "Repository konfigurasjon", + "save": "Lagre", + "table": "Tabell", + "table_view": "Tabellvisning", + "unhide": "Vis igjen", + "upgrade_all": "Oppgradere alle" + }, + "store": { + "ascending": "stigende", + "clear_new": "Tøm nye repositories", + "descending": "synkende", + "last_updated": "Sist oppdatert", + "name": "Navn", + "new_repositories": "Nye repositories", + "new_repositories_note": "Du har over 10 nye repositorier som viser her, hvis du vil fjerne dem alle, klikker du på de 3 prikkene øverst til høyre og avviser dem alle.", + "no_repositories": "Ingen repositories", + "no_repositories_desc1": "Det virker som om du ikke har noen elementer installert i denne delen ennå.", + "no_repositories_desc2": "Klikk på + i nederste hjørne for å legge til din første!", + "no_repositories_found_desc1": "Finner ingen installerte repositorier som samsvarer med {searchInput}i denne delen.", + "no_repositories_found_desc2": "Prøv å søke etter noe annet!", + "pending_upgrades": "Venter på oppgradering", + "placeholder_search": "Skriv inn et søkeord ...", + "sort": "sorter", + "stars": "Stjerner", + "status": "Status" + }, + "time": { + "ago": "siden", + "day": "dag", + "days": "dager", + "hour": "time", + "hours": "timer", + "minute": "minutt", + "minutes": "minutter", + "month": "måned", + "months": "måneder", + "one": "En", + "one_day_ago": "for en dag siden", + "one_hour_ago": "en time siden", + "one_minute_ago": "ett minutt siden", + "one_month_ago": "en måned siden", + "one_second_ago": "ett sekund siden", + "one_year_ago": "ett år siden", + "second": "sekund", + "seconds": "sekunder", + "x_days_ago": "{x} dager siden", + "x_hours_ago": "{x} timer siden", + "x_minutes_ago": "{x} minutter siden", + "x_months_ago": "{x} måneder siden", + "x_seconds_ago": "{x} sekunder siden", + "x_years_ago": "{x} år siden", + "year": "år", + "years": "år" + } +} \ No newline at end of file diff --git a/custom_components/hacs/translations/nl.json b/custom_components/hacs/translations/nl.json new file mode 100644 index 0000000..eac51c0 --- /dev/null +++ b/custom_components/hacs/translations/nl.json @@ -0,0 +1,346 @@ +{ + "common": { + "about": "Over", + "add": "toevoegen", + "appdaemon": "AppDaemon", + "appdaemon_apps": "AppDaemon Apps", + "appdaemon_plural": "AppDaemon Apps", + "background_task": "Achtergrond taak is draaiende, de pagina herhaalt zichzelf wanneer dit klaar is.", + "check_log_file": "Controleer het logbestand voor meer details.", + "continue": "Verder", + "disabled": "Uitgeschakeld", + "documentation": "Documentatie", + "element": "element", + "hacs_is_disabled": "HACS is uitgeschakeld", + "install": "Installeer", + "installed": "geinstalleerd", + "integration": "Integratie", + "integration_plural": "Integraties", + "integrations": "Integraties", + "lovelace": "Lovelace", + "lovelace_element": "Lovelace-element", + "lovelace_elements": "Lovelace-elementen", + "manage": "beheer", + "netdaemon": "NetDaemon", + "netdaemon_apps": "NetDaemon Apps", + "netdaemon_plural": "NetDaemon Apps", + "plugin": "Lovelace", + "plugin_plural": "Lovelace-elementen", + "plugins": "Plugins", + "python_script": "Python Script", + "python_script_plural": "Python Scripts", + "python_scripts": "Python Scripts", + "repositories": "Repositories", + "repository": "Repository", + "settings": "instellingen", + "theme": "Thema", + "theme_plural": "Themas", + "themes": "Themas", + "uninstall": "Verwijderen", + "update": "Update", + "version": "Versie" + }, + "config": { + "step": { + "device": { + "description": "Open {url} and paste this key `{code}` to authorize HACS, when you have done that click 'submit'", + "title": "HACS" + }, + "user": { + "data": { + "acc_addons": "I know that there are no add-ons in HACS", + "acc_disable": "I know that if I get issues with Home Assistant I should disable all my custom_components", + "acc_logs": "I know how to access Home Assistant logs", + "acc_untested": "I know that everything inside HACS is custom and untested by Home Assistant", + "appdaemon": "Enable AppDaemon apps discovery & tracking", + "netdaemon": "Enable NetDaemon apps discovery & tracking", + "python_script": "Enable python_scripts discovery & tracking", + "sidepanel_icon": "Side panel icon", + "sidepanel_title": "Side panel title", + "theme": "Enable Themes discovery & tracking", + "token": "GitHub Personal Access Token" + }, + "description": "Before you can setup HACS you need to acknowledge the following", + "title": "HACS" + } + }, + "title": "HACS (Home Assistant Community Store)" + }, + "confirm": { + "add_to_lovelace": "Weet u zeker dat u dit wilt toevoegen aan uw Lovelace bronnen?", + "bg_task": "Actie is geblokkeerd terwijl achtergrondtaken actief zijn.", + "cancel": "Annuleer", + "continue": "Weet je zeker dat je door wilt gaan?", + "delete": "Weet u zeker dat u '{item}' wilt verwijderen?", + "delete_installed": "'{item}' is geïnstalleerd, je dient het eerst te deïnstalleren voordat je het kan verwijderen.", + "exist": "{item} bestaat al.", + "generic": "Weet je het zeker?", + "home_assistant_is_restarting": "Een moment alstublieft, Home Assistant is aan het herstarten.", + "home_assistant_version_not_correct": "Je gebruikt Home Assistant versie '{haversion}', echter deze repository vereist dat minimaal versie '{minversion}' is geïnstalleerd.", + "no": "Nee", + "no_upgrades": "Geen upgrades in afwachting.", + "ok": "Oké", + "overwrite": "Door dit te doen, wordt het overschreven.", + "reload_data": "Dit zal alle bekende data herladen van alle repositories van HACS. Dit kan even duren", + "restart_home_assistant": "Weet u zeker dat u Home Assistant opnieuw wilt starten?", + "uninstall": "Weet u zeker dat u '{item}' wilt verwijderen?", + "upgrade_all": "Hiermee worden al deze repositories geüpgraded. Zorg ervoor dat u de release-opmerkingen van allen heeft gelezen voordat u doorgaat.", + "yes": "Ja" + }, + "dialog_about": { + "frontend_version": "Frontend versie", + "installed_repositories": "Geïnstalleerde repositories", + "integration_version": "Integratieversie", + "useful_links": "Nuttige links" + }, + "dialog_add_repo": { + "limit": "Alleen de eerste 100 repositories worden getoond, gebruik de zoekopdracht om te filteren wat je nodig hebt", + "no_match": "Er zijn geen repositories gevonden die overeenkomen met uw filter", + "sort_by": "Sorteren op", + "title": "Repository toevoegen" + }, + "dialog_custom_repositories": { + "category": "Categorie", + "no_category": "Ontbrekende categorie", + "no_repository": "Ontbrekende repository", + "title": "Aangepaste repositories", + "url_placeholder": "Voeg een aangepaste repository-URL toevoegen" + }, + "dialog_info": { + "author": "Auteur", + "downloads": "Downloads", + "install": "Installeer deze repository in HACS", + "loading": "Informatie laden ...", + "no_info": "De ontwikkelaar heeft geen verdere informatie verstrekt voor deze repository", + "open_issues": "Openstaande problemen", + "open_repo": "Open repository", + "stars": "Sterren", + "version_installed": "Versie geïnstalleerd" + }, + "dialog_install": { + "restart": "Onthoud dat u Home Assistant opnieuw moet opstarten voordat wijzigingen aan integraties (custom_components) worden toegepast.", + "select_version": "Selecteer versie", + "show_beta": "Bètaversies weergeven", + "type": "Type", + "url": "URL" + }, + "dialog_update": { + "available_version": "Beschikbare versie", + "changelog": "Changelog", + "installed_version": "Geïnstalleerde versie", + "releasenotes": "Releasenotes voor {release}", + "title": "Update in behandeling" + }, + "entry": { + "information": "Informatie", + "intro": "Updates en belangrijke berichten worden hier weergegeven als die er zijn", + "messages": { + "disabled": { + "content": "Controleer uw logbestand voor meer details", + "title": "HACS is uitgeschakeld" + }, + "has_pending_tasks": { + "content": "Sommige repositories worden mogelijk pas weergegeven als dit is voltooid", + "title": "Achtergrondtaken in behandeling" + }, + "resources": { + "content": "Je hebt {number} Lovelace-elementen die niet correct zijn geladen in Lovelace.", + "title": "Niet geladen in Lovelace" + }, + "restart": { + "content": "Je hebt {number} integraties waarvoor de Home Assistant opnieuw moet worden opgestart, je kunt dat doen via het gedeelte 'Serverbeheer' onder het configuratiegedeelte van de Home Assistant UI.", + "title": "In afwachting van herstart" + }, + "startup": { + "content": "HACS is aan het opstarten, gedurende deze tijd kan er informatie ontbreken of onjuist zijn", + "title": "HACS is aan het opstarten" + }, + "wrong_frontend_installed": { + "content": "Je hebt {running} van de HACS-frontend geïnstalleerd, maar versie {expected} werd verwacht. Als je dit bericht ziet, kon Home Assistant de nieuwe versie niet installeren, probeer dan Home Assistant opnieuw op te starten.", + "title": "Onverwachte frontend-versie" + }, + "wrong_frontend_loaded": { + "content": "U gebruikt versie {running} van de HACS-frontend, maar versie {expected} werd verwacht, u moet uw browsercache wissen.", + "title": "Onverwachte frontend-versie" + } + }, + "pending_updates": "In afwachting van updates" + }, + "menu": { + "about": "Over HACS", + "clear": "Wis alle nieuwe", + "custom_repositories": "Aangepaste repositories", + "dismiss": "Sluit alle nieuwe repositories af", + "documentation": "Documentatie", + "open_issue": "Meld probleem", + "reload": "Herlaad venster" + }, + "options": { + "step": { + "user": { + "data": { + "appdaemon": "Zet AppDaemon apps ontdekken & traceren aan", + "country": "Filter met land code.", + "debug": "Schakel debug in.", + "experimental": "Zet experimentele functies aan", + "netdaemon": "Zet NetDaemon apps ontdekken & traceren aan", + "not_in_use": "Niet in gebruik met YAML", + "release_limit": "Aantal releases om te laten zien.", + "sidepanel_icon": "Zijpaneel icoon", + "sidepanel_title": "Zijpaneel titel" + } + } + } + }, + "repository_banner": { + "config_flow": "Deze integratie ondersteunt config_flow, wat betekent dat u via uw \"Instellingen\" naar \"Integraties\" kunt gaan om het te configureren.", + "config_flow_title": "UI-configuratie ondersteund", + "integration_not_loaded": "Deze integratie wordt niet geladen in Home Assistant.", + "no_restart_required": "Geen herstart vereist", + "not_loaded": "Niet geladen", + "plugin_not_loaded": "Deze plugin wordt niet toegevoegd aan je Lovelace resources.", + "restart": "U moet Home Assistant opnieuw starten.", + "restart_pending": "Wachten op herstart" + }, + "repository_card": { + "dismiss": "afwijzen", + "hide": "Verbergen", + "information": "Informatie", + "new_repository": "Nieuwe repository", + "not_loaded": "Niet geladen", + "open_issue": "Meld probleem", + "open_source": "Open source", + "pending_restart": "In afwachting van herstart", + "pending_update": "In afwachting van update", + "reinstall": "Herinstalleer", + "report": "Rapport voor verwijdering", + "update_information": "Update informatie" + }, + "repository": { + "add_to_lovelace": "Toevoegen aan Lovelace", + "authors": "Auteurs", + "available": "Beschikbaar", + "back_to": "Terug naar", + "changelog": "Changelog", + "downloads": "Downloads", + "flag_this": "Vlag dit", + "frontend_version": "Frontend versie", + "github_stars": "GitHub-sterren", + "goto_integrations": "Ga naar integraties", + "hide": "Verberg", + "hide_beta": "Verberg beta", + "install": "Installeer", + "installed": "Geinstalleerd", + "lovelace_copy_example": "Kopier het voorbeeld naar je klembord", + "lovelace_instruction": "Wanneer je dit gaat toevoegen aan je lovelace configuratie gebruik dit", + "lovelace_no_js_type": "Kon niet achterhalen welk type plugin dit is, check de repository van de plugin.", + "newest": "nieuwste", + "note_appdaemon": "je moet het nog steeds toevoegen aan je 'apps.yaml' bestand", + "note_installed": "Wanneer geïnstalleerd, staat het in", + "note_integration": "je moet het nog steeds toevoegen aan je 'configuration.yaml' bestand", + "note_plugin": "je moet het nog steeds toevoegen aan je lovelace configuratie ('ui-lovelace.yaml') of raw UI config editor.", + "note_plugin_post_107": "je moet het nog steeds toevoegen aan je lovelace configuratie ('configuration.yaml' of de resource editor '\/config\/lovelace\/resources')", + "open_issue": "Meld probleem", + "open_plugin": "Open plugin", + "reinstall": "Herinstalleer", + "repository": "Repository", + "restart_home_assistant": "Start Home Assistant opnieuw", + "show_beta": "Laat beta zien", + "uninstall": "Verwijder", + "update_information": "Update informatie", + "upgrade": "Update" + }, + "search": { + "placeholder": "Zoek naar repository" + }, + "sections": { + "about": { + "description": "Toon informatie over HACS", + "title": "Over" + }, + "automation": { + "description": "Hier vind je python_scripts, AppDaemon-apps en NetDaemon-apps", + "title": "Automatisering" + }, + "frontend": { + "description": "Dit is waar je thema's, aangepaste kaarten en andere elementen voor lovelace", + "title": "Frontend" + }, + "integrations": { + "description": "Hier vindt u aangepaste integraties (custom_components)", + "title": "Integraties" + }, + "pending_repository_upgrade": "U gebruikt versie {installed} , versie {available} is beschikbaar" + }, + "settings": { + "add_custom_repository": "VOEG EIGEN REPOSITORY TOE", + "adding_new_repo": "Nieuwe repository '{repo}' toevoegen", + "adding_new_repo_category": "Met categorie '{category}'.", + "bg_task_custom": "Aangepaste repositories zijn verborgen terwijl de achtergrondtaken actief zijn.", + "category": "Categorie", + "compact_mode": "Compacte modus", + "custom_repositories": "EIGEN REPOSITORIES", + "delete": "Verwijder", + "display": "Weergave", + "grid": "Rooster", + "hacs_repo": "HACS repo", + "hidden_repositories": "verborgen repositories", + "missing_category": "Je moet een categorie selecteren.", + "open_repository": "Open repository", + "reload_data": "Herlaad data", + "reload_window": "Herlaad venster", + "repository_configuration": "Repository configuratie", + "save": "Opslaan", + "table": "Tabel", + "table_view": "Tabelweergave", + "unhide": "zichtbaar maken", + "upgrade_all": "Upgrade alles" + }, + "store": { + "ascending": "oplopend", + "clear_new": "Wissen van alle nieuwe repositories", + "descending": "Aflopend", + "last_updated": "Laatste update", + "name": "Naam", + "new_repositories": "Nieuwe Repositories", + "new_repositories_note": "Je hebt meer dan 10 nieuwe repositories die hier worden weergegeven, als je ze allemaal wilt wissen, klik dan op de 3 puntjes in de rechterbovenhoek en wijs ze af.", + "no_repositories": "Geen repositories", + "no_repositories_desc1": "Het lijkt erop dat je nog geen repositories hebt geïnstalleerd in deze sectie.", + "no_repositories_desc2": "Klik op de + in de benedenhoek om je eerste toe te voegen!", + "no_repositories_found_desc1": "Er zijn geen geïnstalleerde repositories die overeenkomen met {searchInput}\" in deze sectie.", + "no_repositories_found_desc2": "Probeer iets anders te zoeken!", + "pending_upgrades": "Upgrades in afwachting", + "placeholder_search": "Typ iets om te zoeken...", + "sort": "sorteer", + "stars": "Sterren", + "status": "Status" + }, + "time": { + "ago": "geleden", + "day": "dag", + "days": "dagen", + "hour": "uur", + "hours": "uren", + "minute": "minuut", + "minutes": "minuten", + "month": "maand", + "months": "maanden", + "one": "Eén", + "one_day_ago": "een dag geleden", + "one_hour_ago": "een uur geleden", + "one_minute_ago": "een minuut geleden", + "one_month_ago": "een maand geleden", + "one_second_ago": "een seconde geleden", + "one_year_ago": "een jaar geleden", + "second": "seconde", + "seconds": "seconden", + "x_days_ago": "{x} dagen geleden", + "x_hours_ago": "{x} uur geleden", + "x_minutes_ago": "{x} minuten geleden", + "x_months_ago": "{x} maanden geleden", + "x_seconds_ago": "{x} seconden geleden", + "x_years_ago": "{x} jaar geleden", + "year": "jaar", + "years": "jaren" + } +} \ No newline at end of file diff --git a/custom_components/hacs/.translations/nn.json b/custom_components/hacs/translations/nn.json similarity index 74% rename from custom_components/hacs/.translations/nn.json rename to custom_components/hacs/translations/nn.json index 5c1fc0c..0a1666d 100644 --- a/custom_components/hacs/.translations/nn.json +++ b/custom_components/hacs/translations/nn.json @@ -3,48 +3,60 @@ "about": "Om", "appdaemon": "AppDaemon", "appdaemon_apps": "AppDeamon-appar", + "appdaemon_plural": "AppDaemon-appar", "background_task": "Bakgrunnsoppgåve køyrer. Denne sida kjem til å laste seg omatt når ho er ferdig.", "check_log_file": "Sjå i loggfila di for meir detaljar.", "continue": "Hald fram", "disabled": "Deaktivert", "documentation": "Dokumentasjon", + "element": "element", "hacs_is_disabled": "HACS er deaktivert", "installed": "Installert", "integration": "Integrasjon", + "integration_plural": "Integrasjonar", "integrations": "Integrasjonar", + "lovelace": "Lovelace", + "lovelace_element": "Lovelace-element", + "lovelace_elements": "Lovelace-element", "manage": "Handtere", "netdaemon": "NetDaemon", "netdaemon_apps": "NetDeamon-appar", - "plugin": "Tillegg", - "plugins": "Tillegg", + "netdaemon_plural": "NetDaemon-appar", + "plugin": "Lovelace", + "plugin_plural": "Lovelace-element", + "plugins": "Lovelace-element", "python_script": "Pythonskript", + "python_script_plural": "Pythonskript", "python_scripts": "Pythonskript", "repositories": "Repositories", "settings": "innstillingar", "theme": "Tema", + "theme_plural": "Tema", "themes": "Tema", "version": "Versjon" }, "config": { - "abort": { - "single_instance_allowed": "Berre éin enkelt konfigurasjon av HACS er tillete." - }, - "error": { - "auth": "Personleg tilgangsnøkkel er ikkje korrekt." - }, "step": { + "device": { + "description": "Open {url} and paste this key `{code}` to authorize HACS, when you have done that click 'submit'", + "title": "HACS" + }, "user": { "data": { - "appdaemon": "Aktiver AppDeamon-appar-oppdaging og sporing", - "netdaemon": "Aktiver NetDeamon-appar-oppdaging og sporing", - "python_script": "Aktiver pythonscript-oppdaging og sporing", - "sidepanel_icon": "Sidepanelikon", - "sidepanel_title": "Sidepaneltittel", - "theme": "Aktiver tema-oppdaging og sporing", - "token": "Personleg GitHub tilgangsnøkkel" + "acc_addons": "I know that there are no add-ons in HACS", + "acc_disable": "I know that if I get issues with Home Assistant I should disable all my custom_components", + "acc_logs": "I know how to access Home Assistant logs", + "acc_untested": "I know that everything inside HACS is custom and untested by Home Assistant", + "appdaemon": "Enable AppDaemon apps discovery & tracking", + "netdaemon": "Enable NetDaemon apps discovery & tracking", + "python_script": "Enable python_scripts discovery & tracking", + "sidepanel_icon": "Side panel icon", + "sidepanel_title": "Side panel title", + "theme": "Enable Themes discovery & tracking", + "token": "GitHub Personal Access Token" }, - "description": "Dersom du treng hjelp med konfigurasjonen, ta ein kik her: https:\/\/hacs.xyz\/docs\/configuration\/start", - "title": "HACS (Home Assistant Community Store)" + "description": "Before you can setup HACS you need to acknowledge the following", + "title": "HACS" } }, "title": "HACS (Home Assistant Community Store)" @@ -59,7 +71,7 @@ "exist": "{item} eksisterer allereie", "generic": "Er du sikker?", "home_assistant_is_restarting": "Vent... Home Assistant starter på nytt no.", - "home_assistant_version_not_correct": "Du køyrer Home Assistant-versjonen '{haversion}', men dette kodedepoet krev minimum versjon '{minversion}' for å bli installert.", + "home_assistant_version_not_correct": "Du køyrer Home Assistant-versjonen '{haversion}', men dette kodedepoet krev minst versjon '{minversion}' for å bli installert.", "no": "Nei", "no_upgrades": "Ingen ventande oppgradringer", "ok": "OK", @@ -120,6 +132,7 @@ "note_installed": "Når dette er installert, kjem den til å vere plassert i", "note_integration": "du må framleis legge dette til i \"configuration.yaml\"-fila di", "note_plugin": "du må framleis dette til i Lovelace-konfigurasjonen (\"ui-lovelace.yaml\" eller i rå-brukargrensesnittredigeraren", + "note_plugin_post_107": "du må framleis legge dette til i lovelace-konfigurasjonen ('configuration.yaml' eller i kjelderedigeraren ''\/config\/lovelace\/resources')", "open_issue": "Opne problem", "open_plugin": "Opne tillegg", "reinstall": "Installer på nytt", @@ -130,6 +143,25 @@ "update_information": "Oppdater informasjonen", "upgrade": "Oppdater" }, + "sections": { + "about": { + "description": "Vis informasjon om HACS", + "title": "Om" + }, + "automation": { + "description": "Her finn du python_scripts, AppDaemon-appar og NetDaemon-appar", + "title": "Automasjon" + }, + "frontend": { + "description": "Her finn du tema, eigendefinerte kort og andre element for lovelace", + "title": "Frontend" + }, + "integrations": { + "description": "Her finn du eigendefinerte ingtegrasjonar (custom_components)", + "title": "Integrasjonar" + }, + "pending_repository_upgrade": "Du køyrer versjon {installed}, og versjon {available} er tilgjengeleg" + }, "settings": { "add_custom_repository": "LEGG TIL EIN ANNAN REPOSITORY", "adding_new_repo": "Legger til ny repository '{repo}'", diff --git a/custom_components/hacs/translations/pl.json b/custom_components/hacs/translations/pl.json new file mode 100644 index 0000000..7458e26 --- /dev/null +++ b/custom_components/hacs/translations/pl.json @@ -0,0 +1,383 @@ +{ + "common": { + "about": "O", + "add": "dodaj", + "appdaemon": "AppDaemon", + "appdaemon_apps": "Aplikacje AppDaemon", + "appdaemon_plural": "Aplikacje AppDaemon", + "background_task": "Wykonywanie zadania w tle, ta strona zostanie odświeżona, gdy zadanie zostanie ukończone.", + "cancel": "Anuluj", + "check_log_file": "Sprawdź plik dziennika, aby uzyskać więcej informacji.", + "continue": "Kontynuuj", + "disabled": "Wyłączony", + "documentation": "Dokumentacja", + "element": "element", + "hacs_is_disabled": "HACS jest wyłączony", + "ignore": "Ignoruj", + "install": "Zainstaluj", + "installed": "zainstalowane", + "integration": "Integracja", + "integration_plural": "Integracje", + "integrations": "Integracje", + "lovelace": "Lovelace", + "lovelace_element": "Element Lovelace", + "lovelace_elements": "Elementy Lovelace", + "manage": "zarządzaj", + "netdaemon": "NetDaemon", + "netdaemon_apps": "Aplikacje NetDaemon", + "netdaemon_plural": "Aplikacje NetDaemon", + "plugin": "Lovelace", + "plugin_plural": "Elementy Lovelace", + "plugins": "Elementy Lovelace", + "python_script": "Skrypt Python", + "python_script_plural": "Skrypty języka Python", + "python_scripts": "Skrypty Python", + "reload": "Wczytaj ponownie", + "repositories": "Repozytoria", + "repository": "Repozytorium", + "settings": "ustawienia", + "theme": "Motyw", + "theme_plural": "Motywy", + "themes": "Motywy", + "uninstall": "Odinstaluj", + "update": "Uaktualnij", + "version": "Wersja" + }, + "config": { + "abort": { + "single_instance_allowed": "Dozwolona jest tylko jedna konfiguracja HACS." + }, + "error": { + "acc": "Musisz potwierdzić wszystkie oświadczenia przed kontynuowaniem", + "auth": "Osobisty token dostępu jest nieprawidłowy" + }, + "step": { + "device": { + "description": "Open {url} and paste this key `{code}` to authorize HACS, when you have done that click 'submit'", + "title": "HACS" + }, + "user": { + "data": { + "acc_addons": "I know that there are no add-ons in HACS", + "acc_disable": "I know that if I get issues with Home Assistant I should disable all my custom_components", + "acc_logs": "I know how to access Home Assistant logs", + "acc_untested": "I know that everything inside HACS is custom and untested by Home Assistant", + "appdaemon": "Enable AppDaemon apps discovery & tracking", + "netdaemon": "Enable NetDaemon apps discovery & tracking", + "python_script": "Enable python_scripts discovery & tracking", + "sidepanel_icon": "Side panel icon", + "sidepanel_title": "Side panel title", + "theme": "Enable Themes discovery & tracking", + "token": "GitHub Personal Access Token" + }, + "description": "Before you can setup HACS you need to acknowledge the following", + "title": "HACS" + } + }, + "title": "HACS (Home Assistant Community Store)" + }, + "confirm": { + "add_to_lovelace": "Na pewno chcesz dodać to do swoich zasobów Lovelace?", + "bg_task": "Akcja jest wyłączona podczas wykonywania zadań w tle.", + "cancel": "Anuluj", + "continue": "Na pewno chcesz kontynuować?", + "delete": "Na pewno chcesz usunąć '{item}'?", + "delete_installed": "'{item}' jest zainstalowany, musisz go odinstalować zanim będziesz mógł go usunąć.", + "exist": "{item} już istnieje", + "generic": "Jesteś pewny?", + "home_assistant_is_restarting": "Poczekaj, Home Assistant jest teraz ponownie uruchamiany.", + "home_assistant_version_not_correct": "Używasz Home Assistant'a w wersji '{haversion}', a to repozytorium wymaga wersji minimum '{minversion}'.", + "no": "Nie", + "no_upgrades": "Brak oczekujących aktualizacji", + "ok": "Ok", + "overwrite": "Spowoduje to zastąpienie istniejącej kopii.", + "reload_data": "To przeładowuje dane wszystkich repozytoriów, o których wie HACS, może to trochę potrwać.", + "restart_home_assistant": "Na pewno chcesz ponownie uruchomić Home Assistant'a?", + "uninstall": "Na pewno chcesz odinstalować '{item}'?", + "upgrade_all": "To uaktualni wszystkie te repozytoria, upewnij się, że przeczytałeś uwagi do wydania dla wszystkich z nich przed kontynuacją.", + "yes": "Tak" + }, + "dialog_about": { + "frontend_version": "Wersja interfejsu użytkownika", + "installed_repositories": "Zainstalowane repozytoria", + "integration_version": "Wersja integracji", + "useful_links": "Przydatne linki" + }, + "dialog_add_repo": { + "limit": "Wyświetlanych jest tylko pierwszych 100 repozytoriów, użyj wyszukiwania, aby przefiltrować potrzebne informacje", + "no_match": "Nie znaleziono repozytoriów pasujących do filtra", + "sort_by": "Sortuj według", + "title": "Dodawanie repozytorium" + }, + "dialog_custom_repositories": { + "category": "Kategoria", + "no_category": "Brak kategorii", + "no_repository": "Brak repozytorium", + "title": "Niestandardowe repozytoria", + "url_placeholder": "Adres URL niestandardowego repozytorium" + }, + "dialog_info": { + "author": "Autor", + "downloads": "Ilość pobrań", + "install": "Zainstaluj to repozytorium w HACS", + "loading": "Pobieranie informacji...", + "no_info": "Deweloper nie dostarczył więcej informacji na temat tego repozytorium", + "open_issues": "Problemy", + "open_repo": "Otwórz repozytorium", + "stars": "Gwiazdki", + "version_installed": "Wersja zainstalowana" + }, + "dialog_install": { + "restart": "Pamiętaj, że musisz ponownie uruchomić Home Assistanta by zastosować zmiany w integracjach (custom_components).", + "select_version": "Wybierz wersję", + "show_beta": "Wyświetl wydania beta", + "type": "Typ", + "url": "URL" + }, + "dialog_removed": { + "link": "Link zewnętrzny do dodatkowych informacji", + "name": "Nazwa repozytorium", + "reason": "Powód usunięcia", + "type": "Rodzaj usunięcia" + }, + "dialog_update": { + "available_version": "Dostępna wersja", + "changelog": "Lista zmian", + "installed_version": "Zainstalowana wersja", + "releasenotes": "Informacje o {release}", + "title": "Dostępna aktualizacja" + }, + "dialog": { + "reload": { + "confirm": "Czy chcesz to zrobić teraz?", + "description": "Musisz wyczyścić pamięć podręczną przeglądarki po zmianie zasobów Lovelace." + } + }, + "entry": { + "information": "Informacje", + "intro": "Aktualizacje i ważne komunikaty będą wyświetlane w tym miejscu", + "messages": { + "disabled": { + "content": "Sprawdź log, aby uzyskać więcej informacji", + "title": "HACS jest wyłączony" + }, + "has_pending_tasks": { + "content": "Dopóki nie zostaną zakończone, niektóre repozytoria mogą nie być wyświetlane", + "title": "Wykonywane są zadania w tle" + }, + "removed": "Usunięto repozytorium '{repository}'", + "resources": { + "content": "Elementy Lovelace, które nie zostały poprawnie załadowane: {number}", + "title": "Nie załadowano w Lovelace" + }, + "restart": { + "content": "Integracje, które wymagają ponownego uruchomienia Home Assistanta: {number}\\nMożesz to zrobić w sekcji 'Kontrola serwera' konfiguracji Home Assistanta.", + "title": "Oczekiwanie na restart" + }, + "setup": { + "content": "HACS jest konfigurowany, w tym czasie może brakować niektórych informacji lub są one nieprawidłowe", + "title": "HACS jest konfigurowany" + }, + "startup": { + "content": "HACS uruchamia się, w tym czasie może brakować pewnych informacji lub mogą one być nieprawidłowe.", + "title": "HACS uruchamia się" + }, + "waiting": { + "content": "HACS czeka na zakończenie uruchamiania Home Assistanta przed rozpoczęciem własnych zadań", + "title": "HACS czeka" + }, + "wrong_frontend_installed": { + "content": "Masz zainstalowany interfejs HACS w wersji {running}, a wersja {expected} była oczekiwana. Komunikat ten oznacza, że Home Assistant nie mógł zainstalować nowej wersji interfejsu HACS, spróbuj ponownie uruchomić Home Assistanta.", + "title": "Nieoczekiwana wersja interfejsu" + }, + "wrong_frontend_loaded": { + "content": "Używasz wersji {running} interfejsu HACS, a wersja {expected} była oczekiwana, powinieneś wyczyścić pamięć podręczną przeglądarki.", + "title": "Nieoczekiwana wersja interfejsu" + } + }, + "pending_updates": "Oczekujące aktualizacje" + }, + "menu": { + "about": "O HACS", + "clear": "Wyczyść oznaczenia nowych", + "custom_repositories": "Niestandardowe repozytoria", + "dismiss": "Odrzuć wszystkie nowe repozytoria", + "documentation": "Dokumentacja", + "open_issue": "Powiadom o problemie", + "reload": "Załaduj ponownie okno" + }, + "options": { + "step": { + "user": { + "data": { + "appdaemon": "Włącz wykrywanie i śledzenie aplikacji AppDaemon", + "country": "Filtruj według kodu kraju", + "debug": "Włącz debugowanie.", + "experimental": "Włącz funkcje eksperymentalne", + "netdaemon": "Włącz wykrywanie i śledzenie aplikacji NetDaemon", + "not_in_use": "Nieużywany z YAML", + "release_limit": "Liczba wydań do wyświetlenia", + "sidepanel_icon": "Ikona w panelu bocznym", + "sidepanel_title": "Tytuł w panelu bocznym" + } + } + } + }, + "repository_banner": { + "config_flow": "Ta integracja obsługuje config_flow, co oznacza, że możesz teraz przejść do sekcji integracji w interfejsie użytkownika, aby ją skonfigurować.", + "config_flow_title": "Obsługiwana konfiguracja poprzez interfejs użytkownika", + "integration_not_loaded": "Ta integracja nie jest załadowana do Home Assistant'a.", + "no_restart_required": "Ponowne uruchomienie nie jest wymagane", + "not_loaded": "Nie załadowano", + "plugin_not_loaded": "Ta wtyczka nie jest dodana do zasobów Lovelace.", + "restart": "Musisz ponownie uruchomić Home Assistant'a.", + "restart_pending": "Oczekiwanie na ponowne uruchomienie" + }, + "repository_card": { + "dismiss": "odrzuć", + "hide": "Ukryj", + "information": "Informacje", + "new_repository": "Nowe repozytorium", + "not_loaded": "Nie załadowano", + "open_issue": "Powiadom o problemie", + "open_source": "Otwórz kod źródłowy", + "pending_restart": "Oczekiwanie na restart", + "pending_update": "Oczekująca aktualizacja", + "reinstall": "Przeinstaluj", + "report": "Zgłoś do usunięcia", + "update_information": "Uaktualnij dane" + }, + "repository": { + "add_to_lovelace": "Dodaj do Lovelace", + "authors": "Autorzy", + "available": "Dostępna", + "back_to": "Wróć do", + "changelog": "Lista zmian", + "downloads": "Ilość pobrań", + "flag_this": "Oflaguj", + "frontend_version": "Wersja frontendu", + "github_stars": "Gwiazdki GitHub", + "goto_integrations": "Przejdź do integracji", + "hide": "Ukryj", + "hide_beta": "Ukryj wydania beta", + "install": "Zainstaluj", + "installed": "Zainstalowano", + "lovelace_copy_example": "Skopiuj przykład do schowka", + "lovelace_instruction": "Interfejs użytkownika użyje tej wtyczki po dodaniu konfiguracji", + "lovelace_no_js_type": "Nie można określić typu tej wtyczki, sprawdź repozytorium.", + "newest": "najnowsza", + "note_appdaemon": "musisz jeszcze dodać aplikację do pliku 'apps.yaml'", + "note_installed": "Po zainstalowaniu dodatek będzie znajdować się w", + "note_integration": "musisz jeszcze dodać integrację do pliku 'configuration.yaml'", + "note_plugin": "musisz jeszcze dodać wtyczkę do konfiguracji interfejsu użytkownika (plik 'ui-lovelace.yaml' lub edytor interfejsu użytkownika)", + "note_plugin_post_107": "nadal musisz dodać go do konfiguracji Lovelace ('configuration.yaml' lub edytora zasobów '\/config\/lovelace\/resources')", + "open_issue": "Powiadom o problemie", + "open_plugin": "Otwórz element", + "reinstall": "Przeinstaluj", + "repository": "Repozytorium", + "restart_home_assistant": "Uruchom ponownie Home Assistant'a", + "show_beta": "Wyświetl wydania beta", + "uninstall": "Odinstaluj", + "update_information": "Uaktualnij dane", + "upgrade": "Uaktualnij" + }, + "search": { + "installed": "Wyszukaj zainstalowane repozytoria", + "installed_new": "Wyszukaj zainstalowane lub nowe repozytoria", + "placeholder": "Wyszukaj repozytorium" + }, + "sections": { + "about": { + "description": "Informacje o HACS", + "title": "O HACS" + }, + "addon": { + "description": "W HACS nie ma dodatków, ale możesz kliknąć tutaj, aby przejść do Supervisora", + "title": "Dodatki" + }, + "automation": { + "description": "Skrypty Pythona, aplikacje AppDaemon i NetDaemon", + "title": "Automatyzacje" + }, + "frontend": { + "description": "Motywy, niestandardowe karty i inne elementy interfejsu użytkownika", + "title": "Interfejs użytkownika" + }, + "integrations": { + "description": "Niestandardowe integracje (custom_components)", + "title": "Integracje" + }, + "pending_repository_upgrade": "Używasz wersji {installed}, wersja {available} jest dostępna" + }, + "settings": { + "add_custom_repository": "DODAJ REPOZYTORIUM NIESTANDARDOWE", + "adding_new_repo": "Dodawanie nowego repozytorium '{repo}'", + "adding_new_repo_category": "Z kategorią '{category}'.", + "bg_task_custom": "Niestandardowe repozytoria są ukryte podczas wykonywania zadań w tle.", + "category": "Kategoria", + "compact_mode": "Tryb kompaktowy", + "custom_repositories": "REPOZYTORIA NIESTANDARDOWE", + "delete": "Usuń", + "display": "Sposób wyświetlania", + "grid": "kratka", + "hacs_repo": "Repozytorium HACS", + "hidden_repositories": "ukryte repozytoria", + "missing_category": "Musisz wybrać kategorię", + "open_repository": "Otwórz repozytorium", + "reload_data": "Wczytaj ponownie dane", + "reload_window": "Załaduj ponownie okno", + "repository_configuration": "Konfiguracja repozytorium", + "save": "Zapisz", + "table": "tabela", + "table_view": "Widok tabeli", + "unhide": "pokaż", + "upgrade_all": "Uaktualnij wszystkie" + }, + "store": { + "ascending": "rosnąco", + "clear_new": "Wyczyść wszystkie nowe repozytoria", + "descending": "malejąco", + "last_updated": "Ostatnia aktualizacja", + "name": "Nazwa", + "new_repositories": "Nowe repozytoria", + "new_repositories_note": "Jest ponad 10 nowych repozytoriów, jeśli chcesz je wyczyścić, kliknij menu z trzema kropkami w prawym górnym rogu i odrzuć je wszystkie.", + "no_repositories": "Brak repozytoriów", + "no_repositories_desc1": "Wygląda na to, że nie masz jeszcze zainstalowanych repozytoriów w tej sekcji.", + "no_repositories_desc2": "Kliknij + w dolnym rogu, aby dodać pierwsze!", + "no_repositories_found_desc1": "W tej sekcji nie znaleziono zainstalowanych repozytoriów pasujących do \"{searchInput}\".", + "no_repositories_found_desc2": "Spróbuj wyszukać czegoś innego!", + "pending_upgrades": "Oczekujące aktualizacje", + "placeholder_search": "Wprowadź wyszukiwane hasło...", + "sort": "sortowanie", + "stars": "Gwiazdki", + "status": "Status" + }, + "time": { + "ago": "temu", + "day": "dzień", + "days": "dni", + "hour": "godzina", + "hours": "godziny", + "minute": "minuta", + "minutes": "minuty", + "month": "miesiąc", + "months": "miesięcy", + "one": "Jeden", + "one_day_ago": "jeden dzień temu", + "one_hour_ago": "jedna godzina temu", + "one_minute_ago": "jedna minuta temu", + "one_month_ago": "jeden miesiąc temu", + "one_second_ago": "jedna sekunda temu", + "one_year_ago": "jeden rok temu", + "second": "sekunda", + "seconds": "sekundy", + "x_days_ago": "{x} dni temu", + "x_hours_ago": "{x} godzin(y) temu", + "x_minutes_ago": "{x} minut(y) temu", + "x_months_ago": "{x} miesi(ące\/ęcy) temu", + "x_seconds_ago": "{x} sekund(y) temu", + "x_years_ago": "{x} lat(a) temu", + "year": "rok", + "years": "lata" + } +} \ No newline at end of file diff --git a/custom_components/hacs/translations/pt-BR.json b/custom_components/hacs/translations/pt-BR.json new file mode 100644 index 0000000..d97bec8 --- /dev/null +++ b/custom_components/hacs/translations/pt-BR.json @@ -0,0 +1,356 @@ +{ + "common": { + "about": "Sobre", + "add": "adicionar", + "appdaemon": "AppDaemon", + "appdaemon_apps": "Aplicativos AppDaemon", + "appdaemon_plural": "Aplicativos AppDaemon", + "background_task": "Tarefa em segundo plano em execução, esta página será recarregada quando terminar.", + "check_log_file": "Verifique seu arquivo de log para obter mais detalhes.", + "continue": "Continuar", + "disabled": "Desativado", + "documentation": "Documentação", + "element": "elemento", + "hacs_is_disabled": "HACS está desativado", + "ignore": "Ignorar", + "install": "Instalar", + "installed": "instalado", + "integration": "Integração", + "integration_plural": "Integrações", + "integrations": "Integrações", + "lovelace": "Lovelace", + "lovelace_element": "Elemento do Lovelace", + "lovelace_elements": "Elementos do lovelace", + "manage": "gerenciar", + "netdaemon": "NetDaemon", + "netdaemon_apps": "NetDaemon Apps", + "netdaemon_plural": "Aplicativos NetDaemon", + "plugin": "Lovelace", + "plugin_plural": "Elementos do Lovelace", + "plugins": "Elementos do Lovelace", + "python_script": "Script Python", + "python_script_plural": "Scripts python", + "python_scripts": "Scripts python", + "repositories": "Repositórios", + "repository": "Repositório", + "settings": "configurações", + "theme": "Tema", + "theme_plural": "Temas", + "themes": "Temas", + "uninstall": "Desinstalar", + "update": "Atualizar", + "version": "Versão" + }, + "config": { + "step": { + "device": { + "description": "Open {url} and paste this key `{code}` to authorize HACS, when you have done that click 'submit'", + "title": "HACS" + }, + "user": { + "data": { + "acc_addons": "I know that there are no add-ons in HACS", + "acc_disable": "I know that if I get issues with Home Assistant I should disable all my custom_components", + "acc_logs": "I know how to access Home Assistant logs", + "acc_untested": "I know that everything inside HACS is custom and untested by Home Assistant", + "appdaemon": "Enable AppDaemon apps discovery & tracking", + "netdaemon": "Enable NetDaemon apps discovery & tracking", + "python_script": "Enable python_scripts discovery & tracking", + "sidepanel_icon": "Side panel icon", + "sidepanel_title": "Side panel title", + "theme": "Enable Themes discovery & tracking", + "token": "GitHub Personal Access Token" + }, + "description": "Before you can setup HACS you need to acknowledge the following", + "title": "HACS" + } + }, + "title": "HACS (Home Assistant Community Store)" + }, + "confirm": { + "add_to_lovelace": "Tem certeza de que deseja adicionar isso aos seus recursos do Lovelace?", + "bg_task": "A ação é desativada enquanto as tarefas de fundo estão sendo executadas.", + "cancel": "Cancelar", + "continue": "Tem certeza que quer continuar?", + "delete": "Tem certeza de que deseja excluir '{item}'?", + "delete_installed": "'{item}' está instalado, é necessário desinstalá-lo para poder excluí-lo.", + "exist": "{item} já existe", + "generic": "Tem certeza?", + "home_assistant_is_restarting": "Espere, o Home Assistant está agora a reiniciar.", + "home_assistant_version_not_correct": "Você está executando a versão Home Assistant '{haversion}', mas este repositório requer que a versão mínima '{minversion}' esteja instalada.", + "no": "Não", + "no_upgrades": "Não há atualizações pendentes", + "ok": "OK", + "overwrite": "Fazer isso irá substituí-lo.", + "reload_data": "Isso recarrega os dados de todos os repositórios que o HACS conhece e levará algum tempo para concluir.", + "restart_home_assistant": "Tem certeza de que deseja reiniciar o Home Assistant?", + "uninstall": "Tem certeza de que deseja desinstalar '{item}'?", + "upgrade_all": "Isso atualizará todos esses repositórios, verifique se você leu as notas de versão de todos eles antes de continuar.", + "yes": "Sim" + }, + "dialog_about": { + "frontend_version": "Versão do frontend", + "installed_repositories": "Repositórios instalados", + "integration_version": "Versão da integração", + "useful_links": "Links úteis" + }, + "dialog_add_repo": { + "limit": "Apenas os 100 primeiros repositórios são mostrados, use a pesquisa para filtrar o que você precisa", + "no_match": "Nenhum repositório encontrado correspondente ao seu filtro", + "sort_by": "Ordenar por", + "title": "Novo repositório" + }, + "dialog_custom_repositories": { + "category": "Categoria", + "no_category": "Categoria ausente", + "no_repository": "Repositório ausente", + "title": "Repositórios personalizados", + "url_placeholder": "Adicionar URL de repositório personalizado" + }, + "dialog_info": { + "author": "Autor", + "downloads": "Downloads", + "install": "Instalar esse repositório no HACS", + "loading": "Carregando informações...", + "no_info": "O desenvolvedor não forneceu mais informações para este repositório", + "open_issues": "Problemas em aberto", + "open_repo": "Abrir repositório", + "stars": "Estrelas", + "version_installed": "Versão instalada" + }, + "dialog_install": { + "restart": "Lembre-se de que você precisa reiniciar o Home Assistant para que as alterações nas integrações (custom_components) sejam aplicadas.", + "select_version": "Selecionar versão", + "show_beta": "Mostrar versões beta", + "type": "Tipo", + "url": "URL" + }, + "dialog_removed": { + "link": "Link externo para mais informações", + "name": "Nome do Repositório", + "reason": "Motivo da remoção", + "type": "Tipo da Remoção" + }, + "dialog_update": { + "available_version": "Versão disponível", + "changelog": "Registro de mudanças", + "installed_version": "Versão instalada", + "releasenotes": "Notas de lançamento para {release}", + "title": "Atualização pendente" + }, + "entry": { + "information": "Informações", + "intro": "Atualizações e mensagens importantes serão mostradas aqui, quando necessário", + "messages": { + "disabled": { + "content": "Verifique seu arquivo de log para mais detalhes.", + "title": "O HACS está desativado" + }, + "has_pending_tasks": { + "content": "Alguns repositórios podem não aparecer até que isso seja concluído", + "title": "Tarefas em segundo plano pendentes" + }, + "removed": "Repositório {repository} removido", + "resources": { + "content": "Existem {number} elementos do Lovelace que não estão carregados corretamente no Lovelace.", + "title": "Não carregado no Lovelace" + }, + "restart": { + "content": "Existem {number} integrações que requerem um reinício do Home Assistant. Você pode fazer isso na seção 'Controles do Servidor' na parte de configuração do Home Assistant UI.", + "title": "Reinicialização pendente" + }, + "startup": { + "content": "O HACS está sendo iniciado, durante esse período algumas informações podem estar ausentes ou incorretas", + "title": "O HACS está iniciando" + }, + "wrong_frontend_installed": { + "content": "Você instalou a versão {running} do frontend HACS, mas a versão {expected} era esperada. Se você vir esta mensagem, o Home Assistant não conseguiu instalar a nova versão. Por favor tente reiniciar o Home Assistant.", + "title": "Versão frontend inesperada" + }, + "wrong_frontend_loaded": { + "content": "Você está executando a versão {running} do frontend HACS, mas a versão {expected} era esperada. Por favor limpe o cache do seu navegador.", + "title": "Versão frontend inesperada" + } + }, + "pending_updates": "Atualizações pendentes" + }, + "menu": { + "about": "Sobre o HACS", + "clear": "Limpar todos os novos", + "custom_repositories": "Repositórios personalizados", + "dismiss": "Limpar todos os novos repositórios", + "documentation": "Documentação", + "open_issue": "Relatar problema", + "reload": "Recarregar janela" + }, + "options": { + "step": { + "user": { + "data": { + "appdaemon": "Habilitar AppDaemon apps descoberta & rastreamento", + "country": "Filtrar pelo código do país.", + "debug": "Ative a depuração.", + "experimental": "Ativar recursos experimentais", + "netdaemon": "Habilitar NetDaemon apps descoberta & rastreamento", + "not_in_use": "Não está em uso com o YAML", + "release_limit": "Número de lançamentos a serem exibidos.", + "sidepanel_icon": "Icone do painel lateral", + "sidepanel_title": "Titulo do painel lateral" + } + } + } + }, + "repository_banner": { + "config_flow": "Essa integração oferece suporte ao config_flow, o que significa que agora você pode acessar a seção de integração da sua interface do usuário para configurá-lo.", + "config_flow_title": "Configuração de interface do usuário suportada", + "integration_not_loaded": "Esta integração não está carregada no Home Assistant.", + "no_restart_required": "Não é necessário reiniciar", + "not_loaded": "Não carregado", + "plugin_not_loaded": "Este elemento não é adicionado aos seus recursos do Lovelace.", + "restart": "Você precisa reiniciar o Home Assistant.", + "restart_pending": "Reinicialização pendente" + }, + "repository_card": { + "dismiss": "Dispensar", + "hide": "Esconder", + "information": "Informações", + "new_repository": "Novo repositório", + "not_loaded": "Não carregado", + "open_issue": "Relatar problema", + "open_source": "Código aberto", + "pending_restart": "Reinicialização pendente", + "pending_update": "Atualização pendente", + "reinstall": "Reinstalar", + "report": "Denunciar para remoção", + "update_information": "Atualizar informações" + }, + "repository": { + "add_to_lovelace": "Adicionar a Lovelace", + "authors": "Autores", + "available": "Disponível", + "back_to": "Voltar para", + "changelog": "Changelog", + "downloads": "Downloads", + "flag_this": "Sinalizar isso", + "frontend_version": "Versão Frontend", + "github_stars": "Estrelas de GitHub", + "goto_integrations": "Ir para integrações", + "hide": "Esconder", + "hide_beta": "Esconder beta", + "install": "Instalar", + "installed": "Instalado", + "lovelace_copy_example": "Copie este exemplo para seu clipboard", + "lovelace_instruction": "Quando você adicionar isso à sua configuração do lovelace, use este", + "lovelace_no_js_type": "Não foi possível determinar o tipo desse elemento, verifique o repositório.", + "newest": "O mais novo", + "note_appdaemon": "Você ainda precisa adicioná-lo ao seu arquivo 'apps.yaml'", + "note_installed": "Quando instalado, ele estará localizado em", + "note_integration": "Você ainda precisa adicioná-lo ao seu arquivo 'configuration.yaml'", + "note_plugin": "você ainda precisará adicioná-lo à sua configuração do lovelace ('ui-lovelace.yaml' ou o editor de configuração da interface do usuário)", + "note_plugin_post_107": "você ainda precisará adicioná-lo à sua configuração do lovelace ('configuration.yaml' ou o editor de recursos '\/config \/lovelace\/resources')", + "open_issue": "Open issue", + "open_plugin": "Elemento aberto", + "reinstall": "Reinstalar", + "repository": "Repositório", + "restart_home_assistant": "Reiniciar Home Assistant", + "show_beta": "Mostrar beta", + "uninstall": "Desinstalar", + "update_information": "Atualizar informações", + "upgrade": "Atualizar" + }, + "search": { + "installed": "Buscar repositórios instalados", + "installed_new": "Buscar repositórios instalados ou novos", + "placeholder": "Procurar repositório" + }, + "sections": { + "about": { + "description": "Exibir informações sobre o HACS", + "title": "Sobre" + }, + "automation": { + "description": "É aqui que você encontra python_scripts, aplicativos AppDaemon e aplicativos NetDaemon", + "title": "Automação" + }, + "frontend": { + "description": "É aqui que você encontra temas, cartões personalizados e outros elementos para o lovelace", + "title": "Frontend" + }, + "integrations": { + "description": "É aqui que você encontra integrações personalizadas (custom_components)", + "title": "Integrações" + }, + "pending_repository_upgrade": "Você está executando a versão {installed}, a versão {available} está disponível" + }, + "settings": { + "add_custom_repository": "ADICIONAR REPOSITÓRIO PERSONALIZADO", + "adding_new_repo": "Adicionando novo repositório '{repo}'", + "adding_new_repo_category": "Com a categoria '{category}'.", + "bg_task_custom": "Os repositórios personalizados ficam ocultos enquanto as tarefas de fundo estão em execução.", + "category": "Categoria", + "compact_mode": "Modo compacto", + "custom_repositories": "REPOSITÓRIOS PERSONALIZADOS", + "delete": "Deletar", + "display": "Display", + "grid": "Grade", + "hacs_repo": "HACS repo", + "hidden_repositories": "repositórios ocultos", + "missing_category": "Você precisa selecionar uma categoria", + "open_repository": "Repositório aberto", + "reload_data": "Recarregar dados", + "reload_window": "Recarregar janela", + "repository_configuration": "Configuração do Repositório", + "save": "Salvar", + "table": "Tabela", + "table_view": "Visualização em tabela", + "unhide": "reexibir", + "upgrade_all": "Atualizar tudo" + }, + "store": { + "ascending": "ascendente", + "clear_new": "Limpar todos os novos repositórios", + "descending": "descendente", + "last_updated": "Última atualização", + "name": "Nome", + "new_repositories": "Novos Repositórios", + "new_repositories_note": "Você tem mais de 10 novos repositórios sendo mostrados aqui, se quiser limpar todos eles clique nos 3 pontos no canto superior direito e dispense-os.", + "no_repositories": "Nenhum repositório", + "no_repositories_desc1": "Parece que você ainda não tem nenhum repositório instalado nesta seção.", + "no_repositories_desc2": "Clique no + no canto inferior para adicionar o seu primeiro repositório!", + "no_repositories_found_desc1": "Nenhum repositório instalado foi encontrado que corresponda a \"{searchInput}\" nesta seção.", + "no_repositories_found_desc2": "Tente procurar por outra coisa!", + "pending_upgrades": "Atualizações pendentes", + "placeholder_search": "Por favor insira um termo de pesquisa...", + "sort": "ordenar", + "stars": "Estrelas", + "status": "Status" + }, + "time": { + "ago": "atrás", + "day": "dia", + "days": "dias", + "hour": "hora", + "hours": "horas", + "minute": "minuto", + "minutes": "minutos", + "month": "mês", + "months": "meses", + "one": "Um", + "one_day_ago": "um dia atrás", + "one_hour_ago": "uma hora atrás", + "one_minute_ago": "um minuto atrás", + "one_month_ago": "um mês atrás", + "one_second_ago": "um segundo atrás", + "one_year_ago": "um ano atrás", + "second": "segundo", + "seconds": "segundos", + "x_days_ago": "{x} dias atrás", + "x_hours_ago": "{x} horas atrás", + "x_minutes_ago": "{x} minutos atrás", + "x_months_ago": "{x} meses atrás", + "x_seconds_ago": "{x} segundos atrás", + "x_years_ago": "{x} anos atrás", + "year": "ano", + "years": "anos" + } +} \ No newline at end of file diff --git a/custom_components/hacs/translations/pt.json b/custom_components/hacs/translations/pt.json new file mode 100644 index 0000000..d40cf50 --- /dev/null +++ b/custom_components/hacs/translations/pt.json @@ -0,0 +1,355 @@ +{ + "common": { + "about": "Sobre", + "add": "adicionar", + "appdaemon_apps": "Aplicações AppDaemon", + "appdaemon_plural": "Aplicações AppDaemon", + "background_task": "Está a ser executada uma tarefa em segundo plano, esta página será recarregada quando terminar.", + "check_log_file": "Verifique o seu ficheiro de log para obter mais detalhes.", + "continue": "Continuar", + "disabled": "Desativado", + "documentation": "Documentação", + "element": "elemento", + "hacs_is_disabled": "HACS está desativado", + "ignore": "Ignorar", + "install": "Instalar", + "installed": "instalado", + "integration": "Integração", + "integration_plural": "Integrações", + "integrations": "Integrações", + "lovelace": "Lovelace", + "lovelace_element": "Elemento Lovelace", + "lovelace_elements": "Elementos Lovelace", + "manage": "gerir", + "netdaemon": "NetDaemon", + "netdaemon_apps": "Aplicações NetDaemon", + "netdaemon_plural": "Aplicações NetDaemon", + "plugin": "Lovelace", + "plugin_plural": "Elementos Lovelace", + "plugins": "Elementos Lovelace", + "python_script": "Script Python", + "python_script_plural": "Scripts Python", + "python_scripts": "Scripts Python", + "repositories": "Repositórios", + "repository": "Repositório", + "settings": "configurações", + "theme": "Tema", + "theme_plural": "Temas", + "themes": "Temas", + "uninstall": "Desinstalar", + "update": "Atualizar", + "version": "Versão" + }, + "config": { + "step": { + "device": { + "description": "Open {url} and paste this key `{code}` to authorize HACS, when you have done that click 'submit'", + "title": "HACS" + }, + "user": { + "data": { + "acc_addons": "I know that there are no add-ons in HACS", + "acc_disable": "I know that if I get issues with Home Assistant I should disable all my custom_components", + "acc_logs": "I know how to access Home Assistant logs", + "acc_untested": "I know that everything inside HACS is custom and untested by Home Assistant", + "appdaemon": "Enable AppDaemon apps discovery & tracking", + "netdaemon": "Enable NetDaemon apps discovery & tracking", + "python_script": "Enable python_scripts discovery & tracking", + "sidepanel_icon": "Side panel icon", + "sidepanel_title": "Side panel title", + "theme": "Enable Themes discovery & tracking", + "token": "GitHub Personal Access Token" + }, + "description": "Before you can setup HACS you need to acknowledge the following", + "title": "HACS" + } + }, + "title": "HACS (Home Assistant Community Store)" + }, + "confirm": { + "add_to_lovelace": "Tem certeza que deseja adicionar aos recursos do Lovelace?", + "bg_task": "Esta ação é desativada enquanto as tarefas em segundo plano estão a ser executadas.", + "cancel": "Cancelar", + "continue": "Tem a certeza que deseja continuar?", + "delete": "Tem a certeza que deseja apagar '{item}'?", + "delete_installed": "'{item}' está instalado, é preciso desinstalá-lo antes de o poder apagar.", + "exist": "{item} já existe", + "generic": "Tem a certeza?", + "home_assistant_is_restarting": "Aguarde, o Home Assistant está a reiniciar.", + "home_assistant_version_not_correct": "Está a executar a versão '{haversion}' do Home Assistant, mas este repositório requer a versão mínima '{minversion}' para ser instalado.", + "no": "Não", + "no_upgrades": "Não há atualizações pendentes", + "ok": "OK", + "overwrite": "Ao fazer esta ação irá substituir o ficheiro atual.", + "reload_data": "Isto irá recarregar os dados de todos os repositórios que a HACS conhece, irá demorar algum tempo até terminar.", + "restart_home_assistant": "Tem a certeza que deseja reiniciar o Home Assistant?", + "uninstall": "Tem a certeza que deseja desinstalar '{item}'?", + "upgrade_all": "Isto irá actualizar todos estes repositórios, certifique-se de que leu as notas de lançamento de todos antes de continuar.", + "yes": "Sim" + }, + "dialog_about": { + "frontend_version": "Versão Frontend", + "installed_repositories": "Repositórios instalados", + "integration_version": "Versão de integração", + "useful_links": "Links úteis" + }, + "dialog_add_repo": { + "limit": "Apenas os 100 primeiros repositórios serão mostrados, use a pesquisa para filtrar o que precisa", + "no_match": "Não foram encontrados repositórios que correspondam ao filtro", + "sort_by": "Ordenar por", + "title": "Adicionar repositório" + }, + "dialog_custom_repositories": { + "category": "Categoria", + "no_category": "Categoria em falta", + "no_repository": "Repositório em falta", + "title": "Repositórios personalizados", + "url_placeholder": "Adicionar URL do repositório personalizado" + }, + "dialog_info": { + "author": "Autor", + "downloads": "Transferências", + "install": "Instalar este repositório no HACS", + "loading": "A carregar informações...", + "no_info": "O developer não forneceu mais informações sobre este repositório", + "open_issues": "Questões em aberto", + "open_repo": "Abrir Repositório", + "stars": "Estrelas", + "version_installed": "Versão instalada" + }, + "dialog_install": { + "restart": "Lembre-se de que é preciso reiniciar o Home Assistant para que as alterações das integrações (custom_components) sejam aplicadas.", + "select_version": "Selecionar versão", + "show_beta": "Mostrar versões beta", + "type": "Tipo", + "url": "URL" + }, + "dialog_removed": { + "link": "Link externo para mais informações", + "name": "Nome do repositório", + "reason": "Motivo de remoção", + "type": "Tipo de remoção" + }, + "dialog_update": { + "available_version": "Versão disponível", + "changelog": "Changelog", + "installed_version": "Versão instalada", + "releasenotes": "Notas de lançamento para {release}", + "title": "Atualização pendente" + }, + "entry": { + "information": "Informações", + "intro": "Atualizações e mensagens importantes serão mostradas aqui", + "messages": { + "disabled": { + "content": "Verifique o seu ficheiro de log para obter mais detalhes", + "title": "HACS está desativado" + }, + "has_pending_tasks": { + "content": "Alguns repositórios podem não aparecer até que isso seja concluído", + "title": "Tarefas em segundo plano pendentes" + }, + "removed": "Repositório '{repository}' removido", + "resources": { + "content": "Tem {number} elementos que não são carregados corretamente em Lovelace.", + "title": "Não carregado em Lovelace" + }, + "restart": { + "content": "Tem {number} integrações que exigem uma reinicialização do Home Assistant, pode fazer isso a partir da secção 'Controlo do Servidor' na parte de configuração do Home Assistant.", + "title": "Reinicialização pendente" + }, + "startup": { + "content": "O HACS está a iniciar. Durante este tempo, algumas informações podem estar ausentes ou incorretas", + "title": "O HACS está a iniciar" + }, + "wrong_frontend_installed": { + "content": "Tem a versão {running} do frontend HACS instalado, mas a versão {expected} é a mais atualizada, se está a ver esta mensagem e o Home Assistant não foi capaz de instalar a nova versão, tente reiniciar o Home Assistant.", + "title": "Versão do frontend inesperada" + }, + "wrong_frontend_loaded": { + "content": "Está a executar a versão {running} do frontend HACS, mas a versão {expected} é a mais atualizada, deve limpar a cache do seu browser.", + "title": "Versão do frontend inesperada" + } + }, + "pending_updates": "Atualizações pendentes" + }, + "menu": { + "about": "Sobre o HACS", + "clear": "Limpar todos os recentes", + "custom_repositories": "Repositórios personalizados", + "dismiss": "Dispensar todos os novos repositórios", + "documentation": "Documentação", + "open_issue": "Questão em aberto", + "reload": "Recarregar janela" + }, + "options": { + "step": { + "user": { + "data": { + "appdaemon": "Ativar a localização e o seguimento de aplicações AppDaemon", + "country": "Filtrar com o código do país.", + "debug": "Ativar depuração.", + "experimental": "Ativar recursos experimentais", + "netdaemon": "Ativar a localização e o seguimento de aplicações NetDaemon", + "not_in_use": "Não está a ser usado com YAML", + "release_limit": "Número de lançamentos a mostrar.", + "sidepanel_icon": "Ícone no painel lateral", + "sidepanel_title": "Título no painel lateral" + } + } + } + }, + "repository_banner": { + "config_flow": "Esta integração suporta o config_flow, o que significa que agora pode ir para a seção de integração do IU para a configurar.", + "config_flow_title": "Configuração UI suportada", + "integration_not_loaded": "Esta integração não foi carregada no Home Assistant.", + "no_restart_required": "Não é necessário reiniciar", + "not_loaded": "Não carregado", + "plugin_not_loaded": "Este elemento não é adicionado aos recursos Lovelace.", + "restart": "É necessário reiniciar o Home Assistant.", + "restart_pending": "Reinício pendente" + }, + "repository_card": { + "dismiss": "dispensar", + "hide": "Esconder", + "information": "Informações", + "new_repository": "Novo repositório", + "not_loaded": "Não carregado", + "open_issue": "Questão em aberto", + "open_source": "Código aberto", + "pending_restart": "Reinicialização pendente", + "pending_update": "Atualização pendente", + "reinstall": "Reinstalar", + "report": "Motivo para remover", + "update_information": "Atualizar informações" + }, + "repository": { + "add_to_lovelace": "Adicionar ao Lovelace", + "authors": "Autores", + "available": "Disponível", + "back_to": "Voltar", + "changelog": "Histórico de alterações", + "downloads": "Transferências", + "flag_this": "Marcar isto", + "frontend_version": "Versão Frontend", + "github_stars": "Estrelas do GitHub", + "goto_integrations": "Ir para as integrações", + "hide": "Ocultar", + "hide_beta": "Ocultar beta", + "install": "Instalar", + "installed": "Instalado", + "lovelace_copy_example": "Copiar o exemplo para a área de transferência", + "lovelace_instruction": "Quando for adicionar à sua configuração do lovelace, use este", + "lovelace_no_js_type": "Não foi possível determinar o tipo do elemento, verifique o repositório.", + "newest": "mais recente", + "note_appdaemon": "ainda é preciso adicioná-lo ao ficheiro 'apps.yaml'", + "note_installed": "Quando estiver instalado, a localização será", + "note_integration": "ainda é preciso adicioná-lo ao ficheiro 'configuration.yaml'", + "note_plugin": "ainda é preciso adicioná-lo à configuração do lovelace ('ui-lovelace.yaml' ou o editor de configuração de UI)", + "note_plugin_post_107": "ainda é preciso adicioná-lo à configuração do lovelace ('configuration.yaml' ou no editor '\/config\/lovelace\/resources')", + "open_issue": "Questão em aberto", + "open_plugin": "Abrir elemento", + "reinstall": "Reinstalar", + "repository": "Repositório", + "restart_home_assistant": "Reiniciar o Home Assistant", + "show_beta": "Mostrar beta", + "uninstall": "Desinstalar", + "update_information": "Atualizar informações", + "upgrade": "Atualizar" + }, + "search": { + "installed": "Pesquisar repositórios instalados", + "installed_new": "Pesquisar novos repositórios ou instalados", + "placeholder": "Procurar repositório" + }, + "sections": { + "about": { + "description": "Mostrar informações sobre o HACS", + "title": "Sobre" + }, + "automation": { + "description": "Aqui encontra os python_scripts, aplicações AppDaemon e NetDaemon", + "title": "Automação" + }, + "frontend": { + "description": "Aqui encontra os temas, cartões personalizados e outros elementos para o lovelace", + "title": "Frontend" + }, + "integrations": { + "description": "Aqui encontra as integrações personalizadas (custom_components)", + "title": "Integrações" + }, + "pending_repository_upgrade": "Está a executar a versão {installed} , mas a versão {available} já está disponível" + }, + "settings": { + "add_custom_repository": "ADICIONAR REPOSITÓRIO PERSONALIZADO", + "adding_new_repo": "Adicionando novo repositório '{repo}'", + "adding_new_repo_category": "Com a categoria '{category}'.", + "bg_task_custom": "Repositórios personalizados ficam ocultos, enquanto as tarefas em segundo plano estão a ser executadas.", + "category": "Categoria", + "compact_mode": "Modo compacto", + "custom_repositories": "REPOSITÓRIOS PERSONALIZADOS", + "delete": "Eliminar", + "display": "Mostrar", + "grid": "Grade", + "hacs_repo": "Repositório HACS", + "hidden_repositories": "repositórios ocultos", + "missing_category": "É preciso selecionar uma categoria", + "open_repository": "Abrir Repositório", + "reload_data": "Recarregar dados", + "reload_window": "Recarregar janela", + "repository_configuration": "Configuração do Repositório", + "save": "Guardar", + "table": "Tabela", + "table_view": "Vista de tabela", + "unhide": "Mostrar", + "upgrade_all": "Atualizar tudo" + }, + "store": { + "ascending": "ascendente", + "clear_new": "Limpar todos os novos repositórios", + "descending": "descendente", + "last_updated": "Última atualização", + "name": "Nome", + "new_repositories": "Novos Repositórios", + "new_repositories_note": "Tem mais de 10 novos repositórios visiveis aqui, se quiser limpar todos clique nos 3 pontos no canto superior direito e dispense todos.", + "no_repositories": "Sem repositórios", + "no_repositories_desc1": "Parece que ainda não possui nenhum repositório instalado nesta seção.", + "no_repositories_desc2": "Clique no \"+\", no canto inferior para adicionar o seu primeiro!", + "no_repositories_found_desc1": "Nenhum repositório instalado, correspondente a \"{searchInput}\" foi encontrado nesta secção.", + "no_repositories_found_desc2": "Tente procurar outra coisa!", + "pending_upgrades": "Atualizações pendentes", + "placeholder_search": "Por favor, introduza um termo de pesquisa...", + "sort": "ordenar", + "stars": "Estrelas", + "status": "Estado" + }, + "time": { + "ago": "atrás", + "day": "dia", + "days": "dias", + "hour": "hora", + "hours": "horas", + "minute": "minuto", + "minutes": "minutos", + "month": "mês", + "months": "meses", + "one": "Um", + "one_day_ago": "há um dia", + "one_hour_ago": "há uma hora", + "one_minute_ago": "há um minuto", + "one_month_ago": "há um mês", + "one_second_ago": "há um segundo", + "one_year_ago": "há um ano", + "second": "segundo", + "seconds": "segundos", + "x_days_ago": "{x} dias atrás", + "x_hours_ago": "{x} horas atrás", + "x_minutes_ago": "{x} minutos atrás", + "x_months_ago": "{x} meses atrás", + "x_seconds_ago": "{x} segundos atrás", + "x_years_ago": "{x} anos atrás", + "year": "ano", + "years": "anos" + } +} \ No newline at end of file diff --git a/custom_components/hacs/.translations/ro.json b/custom_components/hacs/translations/ro.json similarity index 74% rename from custom_components/hacs/.translations/ro.json rename to custom_components/hacs/translations/ro.json index d6884aa..d577abf 100644 --- a/custom_components/hacs/.translations/ro.json +++ b/custom_components/hacs/translations/ro.json @@ -4,6 +4,7 @@ "appdaemon": "AppDaemon", "appdaemon_apps": "Aplicații AppDaemon", "background_task": "Activitatea de fundal se execută, această pagină se va reîncărca atunci când este gata.", + "check_log_file": "Verificați log-ul pentru mai multe detalii.", "continue": "Continua", "disabled": "Dezactivat", "documentation": "Documentație", @@ -11,6 +12,7 @@ "installed": "instalat", "integration": "Integrare", "integrations": "Integrări", + "manage": "administra", "netdaemon": "NetDaemon", "netdaemon_apps": "Aplicații NetDaemon", "plugin": "Plugin", @@ -24,36 +26,45 @@ "version": "Versiune" }, "config": { - "abort": { - "single_instance_allowed": "Doar o singură configurație pentru HACS este permisă." - }, - "error": { - "auth": "Token-ul de acces personal nu este corect." - }, "step": { + "device": { + "description": "Open {url} and paste this key `{code}` to authorize HACS, when you have done that click 'submit'", + "title": "HACS" + }, "user": { "data": { - "appdaemon": "Activați descoperirea și urmărirea aplicațiilor AppDaemon", - "netdaemon": "Activați descoperirea și urmărirea aplicațiilor NetDaemon", - "python_script": "Activați descoperirea și urmărirea python_scripts", - "sidepanel_icon": "Pictogramă Panou lateral", - "sidepanel_title": "Titlu panou lateral", - "theme": "Activați descoperirea și urmărirea temelor", - "token": "Token de acces personal GitHub" + "acc_addons": "I know that there are no add-ons in HACS", + "acc_disable": "I know that if I get issues with Home Assistant I should disable all my custom_components", + "acc_logs": "I know how to access Home Assistant logs", + "acc_untested": "I know that everything inside HACS is custom and untested by Home Assistant", + "appdaemon": "Enable AppDaemon apps discovery & tracking", + "netdaemon": "Enable NetDaemon apps discovery & tracking", + "python_script": "Enable python_scripts discovery & tracking", + "sidepanel_icon": "Side panel icon", + "sidepanel_title": "Side panel title", + "theme": "Enable Themes discovery & tracking", + "token": "GitHub Personal Access Token" }, - "description": "Dacă aveți nevoie de ajutor pentru configurare, uitați-vă aici: https:\/\/hacs.xyz\/docs\/configuration\/start", - "title": "HACS (Home Assistant Community Store)" + "description": "Before you can setup HACS you need to acknowledge the following", + "title": "HACS" } }, "title": "HACS (Home Assistant Community Store)" }, "confirm": { + "add_to_lovelace": "Ești sigur că vrei să adaugi asta la resursele tale Lovelace?", "bg_task": "Acțiunea este dezactivată în timp ce activitățile de fundal se execută.", "cancel": "Anulare", + "continue": "Esti sigur ca vrei sa continui?", "delete": "Sigur doriți să ștergeți '{item}'?", "exist": "{item} există deja", + "generic": "Ești sigur?", + "home_assistant_is_restarting": "Asteptati, Home Assistant repornește.", "no": "Nu", + "no_upgrades": "Nu există actualizări în curs", "ok": "OK", + "overwrite": "Făcând acest lucru, îl va suprascrie.", + "restart_home_assistant": "Sigur doriți să reporniți Home Assistant?", "uninstall": "Sigur doriți să dezinstalați '{item}'?", "yes": "Da" }, @@ -63,6 +74,7 @@ "data": { "appdaemon": "Activați descoperirea și urmărirea aplicațiilor AppDaemon", "country": "Filtrează cu codul țării.", + "debug": "Activează depanarea.", "experimental": "Activați funcțiile experimentale", "netdaemon": "Activați descoperirea și urmărirea aplicațiilor NetDaemon", "not_in_use": "Nu este utilizat cu YAML", @@ -76,6 +88,8 @@ "repository_banner": { "integration_not_loaded": "Această integrare nu este încărcată în Home Assistant.", "no_restart_required": "Nu este necesară repornirea", + "not_loaded": "Neîncărcat", + "plugin_not_loaded": "Acest plugin nu este adăugat la resursele Lovelace.", "restart": "Trebuie să reporniți Home Assistant.", "restart_pending": "Reporniți în așteptare" }, @@ -89,6 +103,7 @@ "flag_this": "Semnalizează", "frontend_version": "Versiune frontend", "github_stars": "Stele GitHub", + "goto_integrations": "Mergi la integrări", "hide": "Ascunde", "hide_beta": "Ascundere beta", "install": "Instalează", @@ -136,10 +151,12 @@ }, "store": { "ascending": "ascendent", + "clear_new": "Ștergeți toate depozitele noi", "descending": "descendent", "last_updated": "Ultima actualizare", "name": "Nume", "new_repositories": "Noi depozite", + "pending_upgrades": "Actualizări în așteptare", "placeholder_search": "Vă rugăm să introduceți un termen de căutare ...", "sort": "fel", "stars": "Stele", diff --git a/custom_components/hacs/translations/ru.json b/custom_components/hacs/translations/ru.json new file mode 100644 index 0000000..c017ae5 --- /dev/null +++ b/custom_components/hacs/translations/ru.json @@ -0,0 +1,356 @@ +{ + "common": { + "about": "О проекте", + "add": "добавить", + "appdaemon": "AppDaemon", + "appdaemon_apps": "Приложения AppDaemon", + "appdaemon_plural": "Приложения AppDaemon", + "background_task": "Выполняется фоновая задача, страница перезагрузится по готовности.", + "check_log_file": "Проверьте логи для получения более подробной информации.", + "continue": "Продолжить", + "disabled": "Отключено", + "documentation": "Документация", + "element": "элемент", + "hacs_is_disabled": "HACS отключен", + "ignore": "Игнорировать", + "install": "Установить", + "installed": "установлено", + "integration": "Интеграция", + "integration_plural": "Интеграции", + "integrations": "Интеграции", + "lovelace": "Lovelace", + "lovelace_element": "Элемент Lovelace", + "lovelace_elements": "Элементы Lovelace", + "manage": "управлять", + "netdaemon": "NetDaemon", + "netdaemon_apps": "Приложения NetDaemon", + "netdaemon_plural": "Приложения NetDaemon", + "plugin": "Lovelace", + "plugin_plural": "Элементы Lovelace", + "plugins": "Плагины", + "python_script": "Скрипт Python", + "python_script_plural": "Скрипты Python", + "python_scripts": "Скрипты Python", + "repositories": "Репозитории", + "repository": "Репозиторий", + "settings": "настройки", + "theme": "Тема", + "theme_plural": "Темы", + "themes": "Темы", + "uninstall": "Удалить", + "update": "Обновить", + "version": "Версия" + }, + "config": { + "step": { + "device": { + "description": "Open {url} and paste this key `{code}` to authorize HACS, when you have done that click 'submit'", + "title": "HACS" + }, + "user": { + "data": { + "acc_addons": "I know that there are no add-ons in HACS", + "acc_disable": "I know that if I get issues with Home Assistant I should disable all my custom_components", + "acc_logs": "I know how to access Home Assistant logs", + "acc_untested": "I know that everything inside HACS is custom and untested by Home Assistant", + "appdaemon": "Enable AppDaemon apps discovery & tracking", + "netdaemon": "Enable NetDaemon apps discovery & tracking", + "python_script": "Enable python_scripts discovery & tracking", + "sidepanel_icon": "Side panel icon", + "sidepanel_title": "Side panel title", + "theme": "Enable Themes discovery & tracking", + "token": "GitHub Personal Access Token" + }, + "description": "Before you can setup HACS you need to acknowledge the following", + "title": "HACS" + } + }, + "title": "HACS (Home Assistant Community Store)" + }, + "confirm": { + "add_to_lovelace": "Вы уверены, что хотите добавить это в ресурсы Lovelace?", + "bg_task": "Действие отключено во время выполнения фоновых задач.", + "cancel": "Отмена", + "continue": "Вы уверены, что хотите продолжить?", + "delete": "Вы уверены, что хотите удалить '{item}'?", + "delete_installed": "'{item}' установлен, вам нужно нажать 'Удалить', чтобы удалить его.", + "exist": "{item} уже существует.", + "generic": "Вы уверены?", + "home_assistant_is_restarting": "Подожди, теперь Home Assistant перезагружается.", + "home_assistant_version_not_correct": "Вы используете Home Assistant версии '{haversion}', но данный репозиторий требует минимальную установленную версию '{minversion}'.", + "no": "Нет", + "no_upgrades": "Нет обновлений", + "ok": "ОК", + "overwrite": "После подтверждения файлы будут перезаписаны.", + "reload_data": "Выполняется перезагрузка данных всех репозиториев в HACS, это займет некоторое время.", + "restart_home_assistant": "Вы уверены, что хотите перезапустить Home Assistant?", + "uninstall": "Вы уверены, что хотите удалить '{item}'?", + "upgrade_all": "Это произведёт обновление всех этих репозиториев, убедитесь, что вы прочитали примечания к выпуску для каждого из них, прежде чем продолжить.", + "yes": "Да" + }, + "dialog_about": { + "frontend_version": "Версия интерфейса", + "installed_repositories": "Установленные репозитории", + "integration_version": "Версия интеграции", + "useful_links": "Полезные ссылки" + }, + "dialog_add_repo": { + "limit": "Показаны только первые 100 репозиториев, используйте поиск для фильтрации того, что вам нужно", + "no_match": "Не найдено репозиторий, соответствующих фильтру", + "sort_by": "Сортировать по", + "title": "Новый репозиторий" + }, + "dialog_custom_repositories": { + "category": "Категория", + "no_category": "Категория не указана", + "no_repository": "Репозиторий не указан", + "title": "Пользовательские репозитории", + "url_placeholder": "Добавить пользовательский URL-адрес репозитория" + }, + "dialog_info": { + "author": "Автор", + "downloads": "Загрузки", + "install": "Установить этот репозиторий в HACS", + "loading": "Загрузка информации...", + "no_info": "Разработчик не предоставил никакой дополнительной информации для этого репозитория", + "open_issues": "Открытые вопросы", + "open_repo": "Открыть репозиторий", + "stars": "Звёзды", + "version_installed": "Установлена версия" + }, + "dialog_install": { + "restart": "Помните, что вам нужно перезапустить Home Assistant, прежде чем будут применены изменения в интеграциях (custom_components).", + "select_version": "Выберите версию", + "show_beta": "Показывать бета-версии", + "type": "Тип", + "url": "Ссылка" + }, + "dialog_removed": { + "link": "Внешняя ссылка для получения дополнительной информации", + "name": "Имя репозитория", + "reason": "Причина удаления", + "type": "Тип удаления" + }, + "dialog_update": { + "available_version": "Доступная версия", + "changelog": "Изменения", + "installed_version": "Установленная версия", + "releasenotes": "Примечания к выпуску для {release}", + "title": "Обновление в ожидании" + }, + "entry": { + "information": "Информация", + "intro": "Обновления и важные сообщения будут отображаться здесь, если таковые имеются", + "messages": { + "disabled": { + "content": "Проверьте логи для получения более подробной информации.", + "title": "HACS отключен" + }, + "has_pending_tasks": { + "content": "Некоторые репозитории могут не отображаться до тех пор, пока это не будет завершено", + "title": "Выполняются фоновые задачи" + }, + "removed": "Репозиторий удален '{repository}'", + "resources": { + "content": "У вас есть {number} элементов Lovelace, которые не загружаются должным образом.", + "title": "Не загружено в Lovelace" + }, + "restart": { + "content": "У вас есть {number} интеграций, которые требуют перезагрузки Home Assistant, Вы можете сделать это из раздела 'Сервер' в разделе конфигурации пользовательского интерфейса Home Assistant.", + "title": "В ожидании перезапуска" + }, + "startup": { + "content": "HACS запускается, в течение этого времени некоторые сведения могут отсутствовать или быть неверными", + "title": "HACS запускается" + }, + "wrong_frontend_installed": { + "content": "Вы используете версию {running} интерфейса HACS, однако ожидаемая версия — {expected}; если Вы видите это сообщение, то Home Assistant не смог установить новую версию интерфейса; попробуйте перезапустить Home Assistant", + "title": "Неожиданная версия интерфейса" + }, + "wrong_frontend_loaded": { + "content": "Вы используете версию {running} интерфейса HACS, однако ожидаемая версия — {expected}, попробуйте очистить кеш браузера.", + "title": "Неожиданная версия интерфейса" + } + }, + "pending_updates": "Обновления в ожидании" + }, + "menu": { + "about": "О HACS", + "clear": "Очистить все новые репозитории", + "custom_repositories": "Пользовательские репозитории", + "dismiss": "Убрать все новые репозитории", + "documentation": "Документация", + "open_issue": "Сообщить о проблеме", + "reload": "Перезагрузить окно" + }, + "options": { + "step": { + "user": { + "data": { + "appdaemon": "Включить поиск и отслеживание приложений AppDaemon", + "country": "Фильтр по стране.", + "debug": "Включить отладку.", + "experimental": "Вкл. экспериментальные функции", + "netdaemon": "Включить поиск и отслеживание приложений NetDaemon", + "not_in_use": "Не используется с YAML", + "release_limit": "Число доступных версий.", + "sidepanel_icon": "Иконка в боковом меню", + "sidepanel_title": "Название в боковом меню" + } + } + } + }, + "repository_banner": { + "config_flow": "Эта интеграция поддерживает config_flow, это означает, что теперь вы можете перейти в раздел интеграций вашего пользовательского интерфейса, чтобы настроить её.", + "config_flow_title": "Поддерживается настройка из пользовательского интерфейса", + "integration_not_loaded": "Эта интеграция не загружена в Home Assistant.", + "no_restart_required": "Перезагрузка не требуется", + "not_loaded": "Не загружено", + "plugin_not_loaded": "Этот плагин не добавлен к ресурсам Lovelace.", + "restart": "Вам нужно перезапустить Home Assistant.", + "restart_pending": "Ожидается перезапуск" + }, + "repository_card": { + "dismiss": "убрать", + "hide": "Скрыть", + "information": "Информация", + "new_repository": "Новый репозиторий", + "not_loaded": "Не загружено", + "open_issue": "Сообщить о проблеме", + "open_source": "Открыть источник", + "pending_restart": "В ожидании перезапуска", + "pending_update": "Ожидается обновление", + "reinstall": "Переустановить", + "report": "Сообщить о нарушении", + "update_information": "Обновить информацию" + }, + "repository": { + "add_to_lovelace": "Добавить в Lovelace", + "authors": "Авторы", + "available": "Доступно", + "back_to": "Назад к", + "changelog": "Изменения", + "downloads": "Загрузки", + "flag_this": "Пожаловаться", + "frontend_version": "Версия", + "github_stars": "Звезды GitHub", + "goto_integrations": "Перейти к интеграции", + "hide": "Скрыть", + "hide_beta": "Скрыть бета", + "install": "Установить", + "installed": "Установлено", + "lovelace_copy_example": "Скопируйте пример в буфер обмена", + "lovelace_instruction": "Для добавления в конфигурацию Lovelace, используйте", + "lovelace_no_js_type": "Не удалось определить тип этого плагина, проверьте репозиторий.", + "newest": "новейшая", + "note_appdaemon": "вам всё ещё нужно добавить код для настройки приложения в файл 'apps.yaml'", + "note_installed": "После установки, файлы будут расположены в", + "note_integration": "вам всё ещё нужно добавить код для настройки интеграции в файл 'configuration.yaml'", + "note_plugin": "вам всё ещё нужно добавить код для настройки плагина в конфигурацию Lovelace ('ui-lovelace.yaml')", + "note_plugin_post_107": "вам всё равно нужно добавить его в конфигурацию Lovelace ('configuration.yaml' или редактор ресурсов по пути '\/config\/lovelace\/resources').", + "open_issue": "Сообщить о проблеме", + "open_plugin": "Открыть плагин", + "reinstall": "Переустановить", + "repository": "Репозиторий", + "restart_home_assistant": "Перезагрузка Home Assistant", + "show_beta": "Показать бета", + "uninstall": "Удалить", + "update_information": "Обновить информацию", + "upgrade": "Обновить" + }, + "search": { + "installed": "Поиск установленных репозиторий", + "installed_new": "Поиск установленных или новых репозиторий", + "placeholder": "Поиск репозитория" + }, + "sections": { + "about": { + "description": "Показать информацию о HACS", + "title": "О проекте" + }, + "automation": { + "description": "Здесь вы найдете python_scripts, приложения AppDaemon и NetDaemon.", + "title": "Автоматизация" + }, + "frontend": { + "description": "Здесь вы найдете темы, пользовательские карточки и другие элементы для Lovelace", + "title": "Пользовательский интерфейс" + }, + "integrations": { + "description": "Здесь вы найдете пользовательские интеграции (custom_components)", + "title": "Интеграции" + }, + "pending_repository_upgrade": "Вы используете версию {installed}, доступна версия {available}" + }, + "settings": { + "add_custom_repository": "ДОБАВИТЬ СВОЙ РЕПОЗИТОРИЙ", + "adding_new_repo": "Добавление нового репозитория '{repo}'", + "adding_new_repo_category": "С категорией '{category}'.", + "bg_task_custom": "Свои репозитории скрыты во время выполнения фоновых задач.", + "category": "Категория", + "compact_mode": "Компактный режим", + "custom_repositories": "СВОИ РЕПОЗИТОРИИ", + "delete": "Удалить", + "display": "Вид", + "grid": "Сетка", + "hacs_repo": "Репозиторий HACS", + "hidden_repositories": "Скрытые репозитории", + "missing_category": "Вы должны выбрать категорию", + "open_repository": "Открыть репозиторий", + "reload_data": "Перезагрузить", + "reload_window": "Перезагрузить окно", + "repository_configuration": "Конфигурация репозитория", + "save": "Сохранить", + "table": "Таблица", + "table_view": "Таблица", + "unhide": "Показать", + "upgrade_all": "Обновить всё" + }, + "store": { + "ascending": "по возрастанию", + "clear_new": "Очистить все новые репозитории", + "descending": "по убыванию", + "last_updated": "Последнее обновление", + "name": "Название", + "new_repositories": "Новые репозитории", + "new_repositories_note": "У Вас есть более 10 новых репозиториев, показанных здесь; если Вы хотите очистить их все, нажмите 3 точки в верхнем правом углу и уберите их разом.", + "no_repositories": "Нет репозиториев", + "no_repositories_desc1": "Похоже, у вас еще нет репозиториев, установленных в этом разделе.", + "no_repositories_desc2": "Нажмите на + в нижнем углу, чтобы добавить первый!", + "no_repositories_found_desc1": "В этом разделе не найдено установленных репозиторий, соответствующих \"{searchinput}\".", + "no_repositories_found_desc2": "Попробуйте искать что-нибудь другое!", + "pending_upgrades": "Ожидается обновление", + "placeholder_search": "Пожалуйста, введите условие для поиска...", + "sort": "Сортировка", + "stars": "Звезды", + "status": "Статус" + }, + "time": { + "ago": "назад", + "day": "день", + "days": "дней", + "hour": "час", + "hours": "часов", + "minute": "минута", + "minutes": "минут", + "month": "месяц", + "months": "месяца", + "one": "Одна", + "one_day_ago": "один день назад", + "one_hour_ago": "час назад", + "one_minute_ago": "минуту назад", + "one_month_ago": "месяц назад", + "one_second_ago": "одну секунду назад", + "one_year_ago": "один год назад", + "second": "секунда", + "seconds": "секунд", + "x_days_ago": "{x} дней назад", + "x_hours_ago": "{x} часов назад", + "x_minutes_ago": "{x} минут назад", + "x_months_ago": "{x} месяцев назад", + "x_seconds_ago": "{x} секунд назад", + "x_years_ago": "{x} лет назад", + "year": "год", + "years": "лет" + } +} \ No newline at end of file diff --git a/custom_components/hacs/.translations/sl.json b/custom_components/hacs/translations/sl.json similarity index 85% rename from custom_components/hacs/.translations/sl.json rename to custom_components/hacs/translations/sl.json index ada106e..4b1181b 100644 --- a/custom_components/hacs/.translations/sl.json +++ b/custom_components/hacs/translations/sl.json @@ -15,7 +15,7 @@ "manage": "upravljanje", "netdaemon": "NetDaemon", "netdaemon_apps": "NetDaemon Aplikacije", - "plugin": "vtičnik", + "plugin": "Lovelace", "plugins": "Vtičniki", "python_script": "Python skripta", "python_scripts": "Python skripte", @@ -26,25 +26,27 @@ "version": "Različica" }, "config": { - "abort": { - "single_instance_allowed": "Dovoljena je samo ena konfiguracija HACS." - }, - "error": { - "auth": "Osebni dostopni žeton ni pravilen." - }, "step": { + "device": { + "description": "Open {url} and paste this key `{code}` to authorize HACS, when you have done that click 'submit'", + "title": "HACS" + }, "user": { "data": { - "appdaemon": "Omogoči odkrivanje in sledenje aplikacij AppDaemon", - "netdaemon": "Omogoči odkrivanje in sledenje aplikacij NetDaemon", - "python_script": "Omogoči odkrivanje in sledenje python_scripts", - "sidepanel_icon": "Ikona stranske plošče", - "sidepanel_title": "Naslov stranske plošče", - "theme": "Omogoči odkrivanje in sledenje tem", - "token": "GitHub žeton za osebni dostop" + "acc_addons": "I know that there are no add-ons in HACS", + "acc_disable": "I know that if I get issues with Home Assistant I should disable all my custom_components", + "acc_logs": "I know how to access Home Assistant logs", + "acc_untested": "I know that everything inside HACS is custom and untested by Home Assistant", + "appdaemon": "Enable AppDaemon apps discovery & tracking", + "netdaemon": "Enable NetDaemon apps discovery & tracking", + "python_script": "Enable python_scripts discovery & tracking", + "sidepanel_icon": "Side panel icon", + "sidepanel_title": "Side panel title", + "theme": "Enable Themes discovery & tracking", + "token": "GitHub Personal Access Token" }, - "description": "Če potrebujete pomoč pri konfiguraciji, poglejte tukaj: https:\/\/hacs.xyz\/docs\/configuration\/start", - "title": "HACS (Home Assistant Community Store)" + "description": "Before you can setup HACS you need to acknowledge the following", + "title": "HACS" } }, "title": "HACS (Home Assistant Community Store)" @@ -120,6 +122,7 @@ "note_installed": "Ko bo nameščen, se bo nahajal v", "note_integration": "Še vedno ga morate dodati v svojo 'configuration.yaml' datoteko", "note_plugin": "vendar ga še vedno morate dodati v svojo lovelace konfiguracijo ('ui-lovelace.yaml' ali \"raw\" UI config urejevalnik)", + "note_plugin_post_107": "še vedno ga morate dodati v svojo konfiguracijo lovelace ('config.yaml' ali urejevalnik virov '\/config\/lovelace\/resources')", "open_issue": "Odprite težavo", "open_plugin": "Odprite vtičnik", "reinstall": "Znova namestite", diff --git a/custom_components/hacs/.translations/sv.json b/custom_components/hacs/translations/sv.json similarity index 75% rename from custom_components/hacs/.translations/sv.json rename to custom_components/hacs/translations/sv.json index e08e9a7..220c902 100644 --- a/custom_components/hacs/.translations/sv.json +++ b/custom_components/hacs/translations/sv.json @@ -4,6 +4,7 @@ "appdaemon": "AppDaemon", "appdaemon_apps": "Appdaemon Applikationer", "background_task": "Bakgrundsjobb körs, denna sida kommer att laddas igen när det är klart.", + "check_log_file": "Kontrollera din loggfil för mer information.", "continue": "Fortsätta", "disabled": "Inaktiverad", "documentation": "Dokumentation", @@ -11,9 +12,10 @@ "installed": "installerad", "integration": "Integration", "integrations": "Integrationer", + "manage": "hantera", "netdaemon": "NetDaemon", "netdaemon_apps": "NetDaemon Applikationer", - "plugin": "Plugin", + "plugin": "Lovelace", "plugins": "Plugins", "python_script": "Python skript", "python_scripts": "Python skript", @@ -24,25 +26,27 @@ "version": "Version" }, "config": { - "abort": { - "single_instance_allowed": "Endast en konfiguration kan användas i HACS." - }, - "error": { - "auth": "Personal Access Token är inte korrekt." - }, "step": { + "device": { + "description": "Open {url} and paste this key `{code}` to authorize HACS, when you have done that click 'submit'", + "title": "HACS" + }, "user": { "data": { - "appdaemon": "Upptäck och följ Appdaemon applikationer", - "netdaemon": "Upptäck och följ NetDaemon applikationer", - "python_script": "Upptäck och följ python_scripts", - "sidepanel_icon": "Ikon för sidpanel", - "sidepanel_title": "Rubrik för sidpanel", - "theme": "Upptäck och följ teman", + "acc_addons": "I know that there are no add-ons in HACS", + "acc_disable": "I know that if I get issues with Home Assistant I should disable all my custom_components", + "acc_logs": "I know how to access Home Assistant logs", + "acc_untested": "I know that everything inside HACS is custom and untested by Home Assistant", + "appdaemon": "Enable AppDaemon apps discovery & tracking", + "netdaemon": "Enable NetDaemon apps discovery & tracking", + "python_script": "Enable python_scripts discovery & tracking", + "sidepanel_icon": "Side panel icon", + "sidepanel_title": "Side panel title", + "theme": "Enable Themes discovery & tracking", "token": "GitHub Personal Access Token" }, - "description": "Om du behöver hjälp med konfigurationen, se här: https:\/\/hacs.xyz\/docs\/configuration\/start", - "title": "HACS (Home Assistant Community Store)" + "description": "Before you can setup HACS you need to acknowledge the following", + "title": "HACS" } }, "title": "HACS (Home Assistant Community Store)" @@ -56,10 +60,13 @@ "delete_installed": "'{item}' är installerat, du måste avinstallera det innan du kan ta bort det.", "exist": "{item} existerar redan", "generic": "Är du säker?", + "home_assistant_is_restarting": "Vänta, Home Assistant startar nu om.", + "home_assistant_version_not_correct": "Du kör Home Assistant-versionen '{haversion}', men detta repository kräver att lägsta versionen '{minversion}' måste installeras.", "no": "Nej", "no_upgrades": "Inga uppgraderingar väntar", "ok": "OK", "overwrite": "Detta kommer att skriva över den.", + "reload_data": "Detta laddar om data för alla repositories HACS vet om, kommer detta att ta lite tid att slutföra.", "restart_home_assistant": "Är du säker på att du vill starta om Home Assistant?", "uninstall": "Är du säker på att du vill avinstallera '{item}'?", "upgrade_all": "Detta kommer uppgradera alla dessa repositories, säkerhetsställ att du läst release anteckningarna för dem alla innan du fortsätter", @@ -74,6 +81,7 @@ "debug": "Aktivera felsökning", "experimental": "Använd experimentella funktioner", "netdaemon": "Upptäck och följ NetDaemon applikationer", + "not_in_use": "Används inte med YAML", "release_limit": "Antalet releaser som visas.", "sidepanel_icon": "Ikon för sidpanel", "sidepanel_title": "Rubrik för sidpanel" @@ -82,6 +90,8 @@ } }, "repository_banner": { + "config_flow": "Den här integreringen stöder config_flow, det innebär att du nu kan gå till integrationsdelen i användargränssnittet för att konfigurera det.", + "config_flow_title": "UI-konfiguration stöds", "integration_not_loaded": "Denna integration inte laddats i Hem Assistent.", "no_restart_required": "Ingen omstart krävs", "not_loaded": "Ej laddad", @@ -106,12 +116,13 @@ "installed": "Installerad", "lovelace_copy_example": "Kopiera exemplet till urklipp", "lovelace_instruction": "När du lägger till denna till din lovelace konfiguration, använd", - "lovelace_no_js_type": "Kan inte avgöra villken typ av plugin, kontrollera i GIT \nrepository", + "lovelace_no_js_type": "Kan inte avgöra villken typ av plugin, kontrollera i GIT \\nrepository", "newest": "nyaste", "note_appdaemon": "du behöver fortfarande lägga till den till filen 'apps.yaml'", "note_installed": "När den är installerad kommer den finnas i", "note_integration": "du behöver fortfarande lägga den till filen 'configuration.yaml'", "note_plugin": "du behöver fortfarande lägga till den till din lovelace konfiguration ('ui-lovelace.yaml' eller raw UI config redigerare)", + "note_plugin_post_107": "du behöver fortfarande lägga till den i din lovelace-konfiguration ('configuration.yaml' eller resursredigeraren \/config\/lovelace\/resources')", "open_issue": "Rapportera problem", "open_plugin": "Öppna plugin", "reinstall": "Ominstallera", @@ -122,6 +133,11 @@ "update_information": "Uppdatera information", "upgrade": "Uppdatera" }, + "sections": { + "addon": { + "title": "Tillägg" + } + }, "settings": { "add_custom_repository": "LÄGG TILL ETT REPOSITORY", "adding_new_repo": "Lägger till nytt repository '{repo}'", @@ -139,6 +155,7 @@ "open_repository": "Öppna Repository", "reload_data": "Ladda om data", "reload_window": "Ladda om fönstret", + "repository_configuration": "Konfiguration av repository", "save": "Spara", "table": "Tabell", "table_view": "Tabellvy", @@ -148,6 +165,7 @@ "store": { "ascending": "stigande", "clear_new": "Rensa alla nya förvar", + "descending": "fallande", "last_updated": "Senast uppdaterad", "name": "Namn", "new_repositories": "Nya förvar", diff --git a/custom_components/hacs/translations/vi.json b/custom_components/hacs/translations/vi.json new file mode 100644 index 0000000..db9f3cc --- /dev/null +++ b/custom_components/hacs/translations/vi.json @@ -0,0 +1,345 @@ +{ + "common": { + "about": "Thông tin", + "add": "thêm", + "appdaemon_apps": "Ứng dụng AppDaemon", + "appdaemon_plural": "Ứng dụng AppDaemon", + "background_task": "Tác vụ nền đang chạy, trang này sẽ được tải lại khi hoàn thành.", + "check_log_file": "Kiểm tra tệp nhật ký để biết thêm chi tiết.", + "continue": "Tiếp tục", + "disabled": "Bị vô hiệu hoá", + "documentation": "Tài liệu", + "element": "thành phần", + "hacs_is_disabled": "HACS đã bị vô hiệu hoá", + "install": "Cài đặt", + "installed": "đã cài đặt", + "integration": "Tích Hợp", + "integration_plural": "Tích Hợp", + "integrations": "Tích Hợp", + "lovelace": "Lovelace", + "lovelace_element": "Thành phần Lovelace", + "lovelace_elements": "Các thành phần Lovelace", + "manage": "quản lý", + "netdaemon": "NetDaemon", + "netdaemon_apps": "Ứng dụng NetDaemon", + "netdaemon_plural": "Ứng dụng NetDaemon", + "plugin": "Bổ Sung", + "plugin_plural": "Các thành phần Lovelace", + "plugins": "Bổ Sung", + "python_script": "Chương trình Python", + "python_script_plural": "Chương trình Python", + "python_scripts": "Chương trình Python", + "repositories": "Các kho ứng dụng", + "repository": "Kho lưu trữ", + "settings": "thiết lập", + "theme": "Chủ đề", + "theme_plural": "Chủ đề", + "themes": "Chủ đề", + "uninstall": "Gỡ cài đặt", + "update": "Cập nhật", + "version": "Phiên bản" + }, + "config": { + "step": { + "device": { + "description": "Open {url} and paste this key `{code}` to authorize HACS, when you have done that click 'submit'", + "title": "HACS" + }, + "user": { + "data": { + "acc_addons": "I know that there are no add-ons in HACS", + "acc_disable": "I know that if I get issues with Home Assistant I should disable all my custom_components", + "acc_logs": "I know how to access Home Assistant logs", + "acc_untested": "I know that everything inside HACS is custom and untested by Home Assistant", + "appdaemon": "Enable AppDaemon apps discovery & tracking", + "netdaemon": "Enable NetDaemon apps discovery & tracking", + "python_script": "Enable python_scripts discovery & tracking", + "sidepanel_icon": "Side panel icon", + "sidepanel_title": "Side panel title", + "theme": "Enable Themes discovery & tracking", + "token": "GitHub Personal Access Token" + }, + "description": "Before you can setup HACS you need to acknowledge the following", + "title": "HACS" + } + }, + "title": "HACS (Home Assistant Community Store)" + }, + "confirm": { + "add_to_lovelace": "Bạn có chắc muốn thêm vào tài nguyên Lovelace của mình không?", + "bg_task": "Hành động bị vô hiệu hoá trong khi các tác vụ nền đang chạy.", + "cancel": "Huỷ bỏ", + "continue": "Bạn có chắc chắn muốn tiếp tục?", + "delete": "Bạn có chắc muốn xoá '{item}'?", + "delete_installed": "'{item}' đã được cài đặt, bạn cần gỡ cài đặt nó trước khi có thể xoá.", + "exist": "{item} đang tồn tại", + "generic": "Bạn có chắc không?", + "home_assistant_is_restarting": "Khoan đã, Home Assistant đang khởi động lại.", + "home_assistant_version_not_correct": "Bạn đang chạy phiên bản Home Assistant '{haversion}' nhưng kho ứng dụng này yêu cầu phiên bản thấp nhất '{minversion}' để cài đặt.", + "no": "Không", + "no_upgrades": "Không có cập nhật nào đang xử lý", + "ok": "OK", + "overwrite": "Tiếp tục sẽ khiến card này bị ghi đè lên.", + "reload_data": "Điều này tải lại dữ liệu của tất cả các kho ứng dụng trong HACS, sẽ mất một lúc để hoàn tất.", + "restart_home_assistant": "Bạn có muốn khởi động lại Home Assistant?", + "uninstall": "Bạn có chắc muốn gỡ cài đặt '{item}'?", + "upgrade_all": "Điều này sẽ nâng cấp tất cả các kho ứng dụng này, đảm bảo rằng bạn đã đọc tất cả các lưu ý phát hành trước khi tiếp tục.", + "yes": "Có" + }, + "dialog_about": { + "frontend_version": "Phiên bản Frontend", + "installed_repositories": "Kho lưu trữ đã cài đặt", + "integration_version": "Phiên bản tích hợp", + "useful_links": "Liên kết hữu ích" + }, + "dialog_add_repo": { + "limit": "Chỉ 100 kho lưu trữ đầu tiên được hiển thị, sử dụng mục tìm kiếm để lọc những gì bạn cần", + "no_match": "Không tìm thấy kho lưu trữ phù hợp với bộ lọc của bạn", + "sort_by": "Sắp xếp theo", + "title": "Thêm kho lưu trữ" + }, + "dialog_custom_repositories": { + "category": "Danh mục", + "no_category": "Thiếu danh mục", + "no_repository": "Kho lưu trữ bị thiếu", + "title": "Các kho lưu trữ tuỳ chỉnh", + "url_placeholder": "Thêm URL của kho lưu trữ tùy chỉnh" + }, + "dialog_info": { + "author": "Tác giả", + "downloads": "Tải xuống", + "install": "Cài đặt kho lưu trữ này trong HACS", + "loading": "Đang tải thông tin...", + "no_info": "Nhà phát triển đã không cung cấp thêm thông tin nào cho kho lưu trữ này", + "open_issues": "Báo cáo vấn đề", + "open_repo": "Mở Kho ứng dụng", + "stars": "Số sao", + "version_installed": "Phiên bản đã cài đặt" + }, + "dialog_install": { + "restart": "Hãy nhớ rằng bạn cần khởi động lại Home Assistant trước khi các thay đổi với tích hợp (custom_components) được áp dụng.", + "select_version": "Chọn phiên bản", + "show_beta": "Hiển thị phiên bản beta", + "type": "Loại", + "url": "URL" + }, + "dialog_update": { + "available_version": "Phiên bản hiện có", + "changelog": "Thay đổi", + "installed_version": "Phiên bản đã cài đặt", + "releasenotes": "Thông tin phiên bản {Release}", + "title": "Cập nhật đang chờ" + }, + "entry": { + "information": "Thông tin", + "intro": "Các cập nhật và thông điệp quan trọng sẽ hiển thị ở đây nếu có", + "messages": { + "disabled": { + "content": "Kiểm tra tệp nhật ký của bạn để biết thêm chi tiết", + "title": "HACS đã bị vô hiệu hoá" + }, + "has_pending_tasks": { + "content": "Một số kho có thể không thấy cho đến khi điều này hoàn tất", + "title": "Tác vụ nền đang chờ" + }, + "resources": { + "content": "Bạn có {number} thành phần Lovelace không được tải chính xác.", + "title": "Không được tải trong Lovelace" + }, + "restart": { + "content": "Bạn có {number} tích hợp yêu cầu khởi động lại Home Assistant, bạn có thể làm điều này từ mục 'Điều khiển máy chủ' bên trong tab Cấu hình trên giao diện Home Assistant.", + "title": "Đang chờ khởi động lại" + }, + "startup": { + "content": "HACS đang khởi động, suốt quá trình này có thể một số thông tin sẽ bị thiếu hoặc không chính xác", + "title": "HACS đang khởi động" + }, + "wrong_frontend_installed": { + "content": "Bạn có HACS frontend phiên bản {running} đã được cài đặt, nhưng phiên bản yêu cầu là {version}, khi bạn nhìn thấy thông điệp này có nghĩa là Home Assistant không thể cài đặt phiên bản mới, thử khởi động lại Home Assistant.", + "title": "Phiên bản frontend không đúng" + }, + "wrong_frontend_loaded": { + "content": "Bạn đang chạy phiên bản {running} của HACS frontend, nhưng phiên bản được yêu cầu là {expected}, bạn nên xoá bộ đệm của trình duyệt web.", + "title": "Phiên bản frontend không đúng" + } + }, + "pending_updates": "Đang chờ cập nhật" + }, + "menu": { + "about": "Giới thiệu HACS", + "clear": "Ẩn thông báo mục mới", + "custom_repositories": "Các kho ứng dụng tuỳ chỉnh", + "dismiss": "Bỏ qua tất cả kho chứa mới", + "documentation": "Tài liệu", + "open_issue": "Báo cáo vấn đề", + "reload": "Tải lại cửa sổ" + }, + "options": { + "step": { + "user": { + "data": { + "appdaemon": "Kích hoạt phát hiện & theo dõi các ứng dụng AppDaemon", + "country": "Lọc với mã quốc gia.", + "debug": "Kích hoạt Trình gỡ lỗi", + "experimental": "Kích hoạt các tính năng thử nghiệm", + "netdaemon": "Kích hoạt phát hiện & theo dõi các ứng dụng NetDaemon", + "not_in_use": "Không sử dụng với YAML", + "release_limit": "Số lượng phiên bản hiển thị.", + "sidepanel_icon": "Biểu tượng bảng điều khiển cạnh bên", + "sidepanel_title": "Tiêu đề bảng điều khiển cạnh bên" + } + } + } + }, + "repository_banner": { + "config_flow": "Tích hợp này hỗ trợ config_flow, điều này có nghĩa là bây giờ bạn có thể chuyển đến khu vực Tích hợp trên giao diện để cấu hình nó.", + "config_flow_title": "Hỗ trợ cấu hình qua giao diện người dùng", + "integration_not_loaded": "Tích hợp này chưa được tải trong Home Assistant.", + "no_restart_required": "Không cần khởi động lại", + "not_loaded": "Chưa được tải", + "plugin_not_loaded": "Bổ sung này chưa được thêm vào tài nguyên Lovelace của bạn.", + "restart": "Bạn cần khởi động lại Home Assistant.", + "restart_pending": "Đang chờ khởi động lại" + }, + "repository_card": { + "dismiss": "bỏ qua", + "hide": "Ẩn", + "information": "Thông tin", + "new_repository": "Kho lưu trữ mới", + "not_loaded": "Không được tải", + "open_issue": "Báo cáo vấn đề", + "open_source": "Mã nguồn mở", + "pending_restart": "Đang chờ khởi động lại", + "pending_update": "Cập nhật đang chờ", + "reinstall": "Cài đặt lại", + "report": "Báo cáo để loại bỏ", + "update_information": "Cập nhật thông tin" + }, + "repository": { + "add_to_lovelace": "Thêm vào Lovelace", + "authors": "Tác giả", + "available": "Có sẵn", + "back_to": "Quay lại", + "changelog": "Nhật ký thay đổi", + "downloads": "Tải xuống", + "flag_this": "Đánh dấu mục này", + "frontend_version": "Phiên bản Frontend", + "github_stars": "Số Sao trên GitHub", + "goto_integrations": "Đi đến Tích hợp", + "hide": "Ẩn", + "hide_beta": "Ẩn phiên bản beta", + "install": "Cài đặt", + "installed": "Đã cài đặt", + "lovelace_copy_example": "Sao chép ví dụ vào bộ nhớ tạm", + "lovelace_instruction": "Khi bạn thêm mục này vào cấu hình lovelace, sử dụng", + "lovelace_no_js_type": "Không thể xác định loại Bổ sung này, kiểm tra lại kho ứng dụng.", + "newest": "mới nhất", + "note_appdaemon": "bạn vẫn cần thêm vào tập tin 'apps.yaml'", + "note_installed": "Một khi được cài đặt, mục này sẽ nằm ở", + "note_integration": "bạn vẫn cần thêm vào tập tin 'configuration.yaml'", + "note_plugin": "bạn vẫn cần thêm vào cấu hình lovelace ('ui-lovelace.yaml' hoặc trình soạn thảo giao diện)", + "note_plugin_post_107": "bạn vẫn cần phải thêm nó vào cấu hình Lovelace ('configuration.yaml' hoặc trình biên tập tài nguyên '\/config\/Lovelace\/Resources ')", + "open_issue": "Báo cáo vấn đề", + "open_plugin": "Mở Bổ sung", + "reinstall": "Cài đặt lại", + "repository": "Kho ứng dụng", + "restart_home_assistant": "Khởi động lại Home Assistant", + "show_beta": "Hiển thị phiên bản beta", + "uninstall": "Gỡ cài đặt", + "update_information": "Cập nhật thông tin", + "upgrade": "Cập nhật" + }, + "search": { + "placeholder": "Tìm kiếm kho lưu trữ" + }, + "sections": { + "about": { + "description": "Hiển thị thông tin về HACS", + "title": "Thông tin" + }, + "automation": { + "description": "Đây là nơi bạn tìm thấy python_scripts, ứng dụng AppDaemon và ứng dụng NetDaemon", + "title": "Tự động hóa" + }, + "frontend": { + "description": "Đây là nơi bạn tìm thấy chủ đề, thẻ tùy chỉnh và các thành phần khác cho lovelace", + "title": "Frontend" + }, + "integrations": { + "description": "Đây là nơi bạn tìm thấy tích hợp tùy chỉnh (custom_components)", + "title": "Tích Hợp" + }, + "pending_repository_upgrade": "Bạn đang chạy phiên bản {installed}, phiên bản {available} có sẵn" + }, + "settings": { + "add_custom_repository": "THÊM KHO ỨNG DỤNG TUỲ CHỈNH", + "adding_new_repo": "Thêm kho ứng dụng mới '{repo}'", + "adding_new_repo_category": "Với danh mục '{category}'", + "bg_task_custom": "Các kho ứng dụng tuỳ chỉnh bị ẩn khi tác vụ nền đang chạy.", + "category": "Danh mục", + "compact_mode": "Chế độ thu gọn", + "custom_repositories": "KHO ỨNG DỤNG TUỲ CHỈNH", + "delete": "Xoá", + "display": "Hiển thị", + "grid": "Lưới", + "hacs_repo": "Kho ứng dụng HACS", + "hidden_repositories": "các kho ứng dụng bị ẩn", + "missing_category": "Bạn cần chọn một danh mục", + "open_repository": "Mở Kho ứng dụng", + "reload_data": "Tải lại dữ liệu", + "reload_window": "Tải lại cửa sổ", + "repository_configuration": "Cấu hình Kho ứng dụng", + "save": "Lưu", + "table": "Bảng", + "table_view": "Xem dạng bảng", + "unhide": "thôi ẩn", + "upgrade_all": "Nâng cấp tất cả" + }, + "store": { + "ascending": "tăng dần", + "clear_new": "Xoá tất cả kho ứng dụng mới", + "descending": "giảm dần", + "last_updated": "Cập nhật lần cuối", + "name": "Tên", + "new_repositories": "Kho ứng dụng Mới", + "new_repositories_note": "Bạn có hơn 10 kho chứa mới được hiển thị, nếu bạn muốn xoá tất cả thông báo này, bấm vào dấu 3 chấm ở góc trên bên phải rồi chọn Bỏ qua tất cả kho chứa mới.", + "no_repositories": "Không có kho lưu trữ", + "no_repositories_desc1": "Có vẻ như bạn chưa có bất kỳ kho lưu trữ nào được cài đặt trong phần này.", + "no_repositories_desc2": "Nhấp vào biểu tượng + ở góc dưới cùng để thêm mục mới đầu tiên của bạn!", + "no_repositories_found_desc1": "Không tìm thấy kho lưu trữ được cài đặt phù hợp với \"{searchInput}\" trong mục này.", + "no_repositories_found_desc2": "Thử tìm kiếm với từ khoá khác!", + "pending_upgrades": "Nâng cấp chờ xử lý", + "placeholder_search": "Vui lòng nhập cụm từ cần tìm", + "sort": "sắp xếp", + "stars": "Sao", + "status": "Trạng thái" + }, + "time": { + "ago": "trước", + "day": "ngày", + "days": "ngày", + "hour": "giờ", + "hours": "giờ", + "minute": "phút", + "minutes": "phút", + "month": "tháng", + "months": "tháng", + "one": "Một", + "one_day_ago": "một ngày trước", + "one_hour_ago": "một giờ trước", + "one_minute_ago": "một phút trước", + "one_month_ago": "một tháng trước", + "one_second_ago": "một giây trước", + "one_year_ago": "một năm trước", + "second": "giây", + "seconds": "giây", + "x_days_ago": "{x} ngày trước", + "x_hours_ago": "{x} giờ trước", + "x_minutes_ago": "{x} phút trước", + "x_months_ago": "{x} tháng trước", + "x_seconds_ago": "{x} giây trước", + "x_years_ago": "{x} năm trước", + "year": "năm", + "years": "năm" + } +} \ No newline at end of file diff --git a/custom_components/hacs/translations/zh-Hans.json b/custom_components/hacs/translations/zh-Hans.json new file mode 100644 index 0000000..404ffa3 --- /dev/null +++ b/custom_components/hacs/translations/zh-Hans.json @@ -0,0 +1,383 @@ +{ + "common": { + "about": "关于", + "add": "添加", + "appdaemon": "AppDaemon", + "appdaemon_apps": "AppDaemon应用", + "appdaemon_plural": "AppDaemon应用", + "background_task": "后台任务正在运行,完成后将重新加载此页面。", + "cancel": "取消", + "check_log_file": "请查看日志文件以了解更多信息。", + "continue": "继续", + "disabled": "禁用", + "documentation": "文档", + "element": "元素", + "hacs_is_disabled": "HACS已禁用", + "ignore": "忽略", + "install": "安装", + "installed": "已安装", + "integration": "集成", + "integration_plural": "集成", + "integrations": "集成", + "lovelace": "Lovelace", + "lovelace_element": "Lovelace元素", + "lovelace_elements": "Lovelace元素", + "manage": "管理", + "netdaemon": "NetDaemon应用", + "netdaemon_apps": "NetDaemon应用", + "netdaemon_plural": "NetDaemon应用", + "plugin": "Lovelace", + "plugin_plural": "Lovelace元素", + "plugins": "Lovelace元素", + "python_script": "Python脚本", + "python_script_plural": "Python脚本", + "python_scripts": "Python脚本", + "reload": "重新加载", + "repositories": "储存库数量", + "repository": "存储库", + "settings": "设置", + "theme": "主题", + "theme_plural": "主题", + "themes": "主题", + "uninstall": "卸载", + "update": "升级", + "version": "版本" + }, + "config": { + "abort": { + "single_instance_allowed": "仅允许单个HACS配置。" + }, + "error": { + "acc": "您需要先确认所有声明,然后才能继续", + "auth": "个人访问令牌不正确。" + }, + "step": { + "device": { + "description": "Open {url} and paste this key `{code}` to authorize HACS, when you have done that click 'submit'", + "title": "HACS" + }, + "user": { + "data": { + "acc_addons": "I know that there are no add-ons in HACS", + "acc_disable": "I know that if I get issues with Home Assistant I should disable all my custom_components", + "acc_logs": "I know how to access Home Assistant logs", + "acc_untested": "I know that everything inside HACS is custom and untested by Home Assistant", + "appdaemon": "Enable AppDaemon apps discovery & tracking", + "netdaemon": "Enable NetDaemon apps discovery & tracking", + "python_script": "Enable python_scripts discovery & tracking", + "sidepanel_icon": "Side panel icon", + "sidepanel_title": "Side panel title", + "theme": "Enable Themes discovery & tracking", + "token": "GitHub Personal Access Token" + }, + "description": "Before you can setup HACS you need to acknowledge the following", + "title": "HACS" + } + }, + "title": "HACS (Home Assistant Community Store)" + }, + "confirm": { + "add_to_lovelace": "您确定要将此添加到Lovelace资源中吗?", + "bg_task": "后台任务正在运行时,操作被禁用。", + "cancel": "取消", + "continue": "你确定你要继续吗?", + "delete": "是否确实要删除\"{item}\"?", + "delete_installed": "已安装 '{item}',需要先将其卸载,然后才能将其删除。", + "exist": "{item}已经存在", + "generic": "你确定吗?", + "home_assistant_is_restarting": "请等待,Home Assistant现在正在重新启动。", + "home_assistant_version_not_correct": "您正在运行Home Assistant的版本为'{haversion}',但是这个需要安装最低版本是'{minversion}'。", + "no": "取消", + "no_upgrades": "暂无升级", + "ok": "确定", + "overwrite": "这样做会覆盖它。", + "reload_data": "这将重新加载HACS知道的所有存储库的数据,这需要一些时间才能完成。", + "restart_home_assistant": "您确定要重新启动Home Assistant吗?", + "uninstall": "您确定要卸载 '{item}' 吗?", + "upgrade_all": "这将升级所有这些存储库,请确保在继续之前已阅读所有存储库的发行说明。", + "yes": "确定" + }, + "dialog_about": { + "frontend_version": "前端版本", + "installed_repositories": "已安装的存储库", + "integration_version": "集成版本", + "useful_links": "有用的链接" + }, + "dialog_add_repo": { + "limit": "仅显示前100个存储库,使用搜索过滤所需的内容", + "no_match": "找不到与您的过滤器匹配的存储库", + "sort_by": "排序方式", + "title": "添加存储库" + }, + "dialog_custom_repositories": { + "category": "类别", + "no_category": "没有选择类别", + "no_repository": "存储库地址不能为空", + "title": "自定义存储库", + "url_placeholder": "添加自定义存储库URL" + }, + "dialog_info": { + "author": "作者", + "downloads": "下载", + "install": "在HACS中安装此存储库", + "loading": "正在加载详细信息...", + "no_info": "开发人员尚未为此存储库提供任何更多信息", + "open_issues": "提交问题", + "open_repo": "打开存储库", + "stars": "星级", + "version_installed": "已安装版本" + }, + "dialog_install": { + "restart": "请记住,在应用对集成(custom_components)所做的更改之前,您需要重新启动Home Assistant。", + "select_version": "选择版本", + "show_beta": "显示测试版", + "type": "Type", + "url": "URL" + }, + "dialog_removed": { + "link": "外部链接以获取更多信息", + "name": "存储库名称", + "reason": "删除原因", + "type": "删除类型" + }, + "dialog_update": { + "available_version": "可用版本", + "changelog": "更改日志", + "installed_version": "已安装版本", + "releasenotes": "更新日志{release}", + "title": "待更新" + }, + "dialog": { + "reload": { + "confirm": "您现在要这样做吗?", + "description": "更改Lovelace资源时,您需要清除浏览器缓存。" + } + }, + "entry": { + "information": "详情", + "intro": "如果有更新和重要消息,将在此处显示", + "messages": { + "disabled": { + "content": "请查看日志文件以了解更多信息", + "title": "HACS已禁用" + }, + "has_pending_tasks": { + "content": "在完成此操作之前,某些存储库可能不会显示", + "title": "待处理的后台任务" + }, + "removed": "删除了存储库“ {repository}”", + "resources": { + "content": "您有{number} 个Lovelace元素未正确加载到Lovelace中。", + "title": "未载入Lovelace" + }, + "restart": { + "content": "您有{number}集成,需要重新启动Home Assistant,您可以从Home Assistant 的配置下的“服务器控制”部分执行此操作。", + "title": "待重启" + }, + "setup": { + "content": "HACS正在配置,在此期间某些信息可能丢失或不正确", + "title": "HACS正在配置" + }, + "startup": { + "content": "HACS正在启动,在此期间某些信息可能丢失或不正确", + "title": "HACS正在启动" + }, + "waiting": { + "content": "HACS在开始启动任务之前正在等待Home Assistant完成启动", + "title": "HACS正在等待" + }, + "wrong_frontend_installed": { + "content": "您已经安装了{running}的HACS前端,但是{expected}版本是{expected} ,如果看到此消息,则Home Assistant无法安装新版本,请尝试重新启动Home Assistant。", + "title": "额外的前端版本" + }, + "wrong_frontend_loaded": { + "content": "您正在运行HACS前端的{running}版本,但是{expected}版本,您应该清除浏览器缓存。", + "title": "额外的前端版本" + } + }, + "pending_updates": "待升级" + }, + "menu": { + "about": "关于HACS", + "clear": "清除所有新标记", + "custom_repositories": "自定义存储库", + "dismiss": "忽略所有新的存储库", + "documentation": "文档", + "open_issue": "提交问题", + "reload": "重新加载窗口" + }, + "options": { + "step": { + "user": { + "data": { + "appdaemon": "启用AppDaemon应用发现和跟踪", + "country": "用国家代码过滤。", + "debug": "启用调试。", + "experimental": "启用实验功能", + "netdaemon": "启用NetDaemon应用发现和跟踪", + "not_in_use": "不适用于 YAML", + "release_limit": "要显示的发行数量。", + "sidepanel_icon": "侧面板图标", + "sidepanel_title": "侧面板标题" + } + } + } + }, + "repository_banner": { + "config_flow": "此组件支持config_flow,这意味着您现在可以转到UI的“集成”部分进行配置。", + "config_flow_title": "支持UI配置", + "integration_not_loaded": "此集成未加载到Home Assistant中。", + "no_restart_required": "无需重启", + "not_loaded": "未加载", + "plugin_not_loaded": "该元件未添加到您的Lovelace资源中。", + "restart": "您需要重新启动Home Assistant。", + "restart_pending": "重新启动待处理" + }, + "repository_card": { + "dismiss": "忽略", + "hide": "隐藏", + "information": "详情", + "new_repository": "新存储库", + "not_loaded": "未加载", + "open_issue": "提交问题", + "open_source": "开源", + "pending_restart": "待重启", + "pending_update": "待升级", + "reinstall": "重新安装", + "report": "报告删除", + "update_information": "更新信息" + }, + "repository": { + "add_to_lovelace": "添加到Lovelace", + "authors": "作者", + "available": "版本号", + "back_to": "返回", + "changelog": "更新日志", + "downloads": "下载", + "flag_this": "标记", + "frontend_version": "前端版本", + "github_stars": "GitHub星级", + "goto_integrations": "转到集成", + "hide": "隐藏", + "hide_beta": "隐藏测试版", + "install": "安装", + "installed": "已安装版本", + "lovelace_copy_example": "复制样例代码到你的剪贴板", + "lovelace_instruction": "您仍然需要将下列代码添加到lovelace配置中(“ ui-lovelace.yaml”或原始配置编辑器):", + "lovelace_no_js_type": "无法确定此元素的类型,请检查存储库。", + "newest": "最新", + "note_appdaemon": "您仍然需要将其添加到“ apps.yaml”文件中", + "note_installed": "安装后,它将位于", + "note_integration": "您仍然需要将其添加到“ configuration.yaml”文件中", + "note_plugin": "您仍然需要将其添加到lovelace配置中(“ ui-lovelace.yaml”或原始配置编辑器)ui-lovelace.yaml", + "note_plugin_post_107": "您仍然需要将其添加到lovelace配置中(“ configuration.yaml”或资源编辑器“\/config\/lovelace\/resources”)", + "open_issue": "提交问题", + "open_plugin": "打开元素", + "reinstall": "重新安装", + "repository": "存储库", + "restart_home_assistant": "重新启动Home Assistant", + "show_beta": "显示测试版", + "uninstall": "卸载", + "update_information": "更新信息", + "upgrade": "升级" + }, + "search": { + "installed": "搜索已安装的存储库", + "installed_new": "搜索已安装或新的存储库", + "placeholder": "搜索存储库" + }, + "sections": { + "about": { + "description": "显示有关HACS的信息", + "title": "关于" + }, + "addon": { + "description": "HACS 中没有附加组件,但您可以单击此处联系管理员", + "title": "附加组件" + }, + "automation": { + "description": "在这里可以找到python_scripts,AppDaemon应用程序和NetDaemon应用程序", + "title": "自动化" + }, + "frontend": { + "description": "在这里,您可以找到主题,自定义卡片和其他用于lovelace的元素", + "title": "前端" + }, + "integrations": { + "description": "在这里您可以找到自定义集成(custom_components)", + "title": "集成" + }, + "pending_repository_upgrade": "您正在运行版本{installed} ,有新版 ({available}) 可用" + }, + "settings": { + "add_custom_repository": "添加自定义存储库", + "adding_new_repo": "添加新的储存库 '{repo}'", + "adding_new_repo_category": "类别为 '{category}'。", + "bg_task_custom": "自定义存储库在后台任务运行时被隐藏。", + "category": "类别", + "compact_mode": "紧凑模式", + "custom_repositories": "自定义存储库", + "delete": "删除", + "display": "视图方式", + "grid": "网格", + "hacs_repo": "HACS存储库", + "hidden_repositories": "隐藏的存储库", + "missing_category": "您需要选择一个类别", + "open_repository": "打开存储库", + "reload_data": "重载数据", + "reload_window": "重新加载窗口", + "repository_configuration": "存储库配置", + "save": "保存", + "table": "列表", + "table_view": "表视图", + "unhide": "取消隐藏", + "upgrade_all": "升级全部" + }, + "store": { + "ascending": "升序", + "clear_new": "清除所有新存储库标记", + "descending": "降序", + "last_updated": "最近更新时间", + "name": "名称", + "new_repositories": "新存储库", + "new_repositories_note": "这里有多于10个新存储库,如果要清除它们,请单击右上角的3点按钮,然后将其全部忽略。", + "no_repositories": "没有储存库", + "no_repositories_desc1": "在本节中您似乎尚未安装任何存储库。", + "no_repositories_desc2": "单击右下角的+,添加第一个!", + "no_repositories_found_desc1": "在这里找不到与“ {searchInput} ”匹配的已安装存储库。", + "no_repositories_found_desc2": "尝试寻找其他东西!", + "pending_upgrades": "待升级", + "placeholder_search": "搜索项目...", + "sort": "排序", + "stars": "星级", + "status": "状态" + }, + "time": { + "ago": "过去", + "day": "天", + "days": "天", + "hour": "小时", + "hours": "小时", + "minute": "分", + "minutes": "分", + "month": "月", + "months": "月", + "one": "一", + "one_day_ago": "一天前", + "one_hour_ago": "一个小时之前", + "one_minute_ago": "一分钟前", + "one_month_ago": "一个月前", + "one_second_ago": "一秒钟前", + "one_year_ago": "一年前", + "second": "秒", + "seconds": "秒", + "x_days_ago": "{x}天前", + "x_hours_ago": "{x}小时前", + "x_minutes_ago": "{x}分钟前", + "x_months_ago": "{x}个月前", + "x_seconds_ago": "{x}秒前", + "x_years_ago": "{x}年前", + "year": "年", + "years": "年" + } +} \ No newline at end of file diff --git a/custom_components/hacs/validate/README.md b/custom_components/hacs/validate/README.md new file mode 100644 index 0000000..e0a7fd3 --- /dev/null +++ b/custom_components/hacs/validate/README.md @@ -0,0 +1,38 @@ +# Repository validation + +This is where the validation rules that run against the various repository categories live. + +## Structure + +- All validation rules are in the directory for their category. +- Validation rules that aplies to all categories are in the `common` directory. +- There is one file pr. rule. +- All rule needs tests to verify every possible outcome for the rule. +- It's better with multiple files than a big rule. +- All rules uses `ValidationBase` or `ActionValidationBase` as the base class. +- The `ActionValidationBase` are for checks that will breaks compatibility with with existing repositories (default), so these are only run in github actions. +- The class name should describe what the check does. +- Only use `validate` or `async_validate` methods to define validation rules. +- If a rule should fail, raise `ValidationException` with the failure message. + + +## Example + +```python +from custom_components.hacs.validate.base import ( + ActionValidationBase, + ValidationBase, + ValidationException, +) + + +class AwesomeRepository(ValidationBase): + def validate(self): + if self.repository != "awesome": + raise ValidationException("The repository is not awesome") + +class SuperAwesomeRepository(ActionValidationBase, category="integration"): + async def async_validate(self): + if self.repository != "super-awesome": + raise ValidationException("The repository is not super-awesome") +``` \ No newline at end of file diff --git a/custom_components/hacs/validate/__init__.py b/custom_components/hacs/validate/__init__.py new file mode 100644 index 0000000..42ec85f --- /dev/null +++ b/custom_components/hacs/validate/__init__.py @@ -0,0 +1,51 @@ +import asyncio +import glob +import importlib +from os.path import dirname, join, sep + +from custom_components.hacs.share import SHARE, get_hacs + + +def _initialize_rules(): + rules = glob.glob(join(dirname(__file__), "**/*.py")) + for rule in rules: + rule = rule.replace(sep, "/") + rule = rule.split("custom_components/hacs")[-1] + rule = f"custom_components/hacs{rule}".replace("/", ".")[:-3] + importlib.import_module(rule) + + +async def async_initialize_rules(): + hass = get_hacs().hass + await hass.async_add_executor_job(_initialize_rules) + + +async def async_run_repository_checks(repository): + hacs = get_hacs() + if not SHARE["rules"]: + await async_initialize_rules() + if not hacs.system.running: + return + checks = [] + for check in SHARE["rules"].get("common", []): + checks.append(check(repository)) + for check in SHARE["rules"].get(repository.data.category, []): + checks.append(check(repository)) + + await asyncio.gather( + *[ + check._async_run_check() + for check in checks or [] + if hacs.system.action or not check.action_only + ] + ) + + total = len([x for x in checks if hacs.system.action or not x.action_only]) + failed = len([x for x in checks if x.failed]) + + if failed != 0: + repository.logger.error("%s %s/%s checks failed", repository, failed, total) + if hacs.system.action: + exit(1) + else: + repository.logger.debug("%s All (%s) checks passed", repository, total) diff --git a/custom_components/hacs/validate/__pycache__/__init__.cpython-38.pyc b/custom_components/hacs/validate/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..f805c80 Binary files /dev/null and b/custom_components/hacs/validate/__pycache__/__init__.cpython-38.pyc differ diff --git a/custom_components/hacs/validate/__pycache__/base.cpython-38.pyc b/custom_components/hacs/validate/__pycache__/base.cpython-38.pyc new file mode 100644 index 0000000..73727d9 Binary files /dev/null and b/custom_components/hacs/validate/__pycache__/base.cpython-38.pyc differ diff --git a/custom_components/hacs/validate/base.py b/custom_components/hacs/validate/base.py new file mode 100644 index 0000000..481201c --- /dev/null +++ b/custom_components/hacs/validate/base.py @@ -0,0 +1,48 @@ +from custom_components.hacs.share import SHARE, get_hacs + + +class ValidationException(Exception): + pass + + +class ValidationBase: + def __init__(self, repository) -> None: + self.repository = repository + self.hacs = get_hacs() + self.failed = False + self.logger = repository.logger + + def __init_subclass__(cls, category="common", **kwargs) -> None: + """Initialize a subclass, register if possible.""" + super().__init_subclass__(**kwargs) + if SHARE["rules"].get(category) is None: + SHARE["rules"][category] = [] + if cls not in SHARE["rules"][category]: + SHARE["rules"][category].append(cls) + + @property + def action_only(self): + return False + + async def _async_run_check(self): + """DO NOT OVERRIDE THIS IN SUBCLASSES!""" + if self.hacs.system.action: + self.logger.info(f"Running check '{self.__class__.__name__}'") + try: + await self.hacs.hass.async_add_executor_job(self.check) + await self.async_check() + except ValidationException as exception: + self.failed = True + self.logger.error(exception) + + def check(self): + pass + + async def async_check(self): + pass + + +class ActionValidationBase(ValidationBase): + @property + def action_only(self): + return True diff --git a/custom_components/hacs/validate/common/__pycache__/hacs_manifest.cpython-38.pyc b/custom_components/hacs/validate/common/__pycache__/hacs_manifest.cpython-38.pyc new file mode 100644 index 0000000..6102be9 Binary files /dev/null and b/custom_components/hacs/validate/common/__pycache__/hacs_manifest.cpython-38.pyc differ diff --git a/custom_components/hacs/validate/common/__pycache__/repository_description.cpython-38.pyc b/custom_components/hacs/validate/common/__pycache__/repository_description.cpython-38.pyc new file mode 100644 index 0000000..ddc3925 Binary files /dev/null and b/custom_components/hacs/validate/common/__pycache__/repository_description.cpython-38.pyc differ diff --git a/custom_components/hacs/validate/common/__pycache__/repository_information_file.cpython-38.pyc b/custom_components/hacs/validate/common/__pycache__/repository_information_file.cpython-38.pyc new file mode 100644 index 0000000..4767363 Binary files /dev/null and b/custom_components/hacs/validate/common/__pycache__/repository_information_file.cpython-38.pyc differ diff --git a/custom_components/hacs/validate/common/__pycache__/repository_topics.cpython-38.pyc b/custom_components/hacs/validate/common/__pycache__/repository_topics.cpython-38.pyc new file mode 100644 index 0000000..e738d8c Binary files /dev/null and b/custom_components/hacs/validate/common/__pycache__/repository_topics.cpython-38.pyc differ diff --git a/custom_components/hacs/validate/common/hacs_manifest.py b/custom_components/hacs/validate/common/hacs_manifest.py new file mode 100644 index 0000000..6701c12 --- /dev/null +++ b/custom_components/hacs/validate/common/hacs_manifest.py @@ -0,0 +1,10 @@ +from custom_components.hacs.validate.base import ( + ActionValidationBase, + ValidationException, +) + + +class HacsManifest(ActionValidationBase): + def check(self): + if "hacs.json" not in [x.filename for x in self.repository.tree]: + raise ValidationException("The repository has no 'hacs.json' file") diff --git a/custom_components/hacs/validate/common/repository_description.py b/custom_components/hacs/validate/common/repository_description.py new file mode 100644 index 0000000..100de02 --- /dev/null +++ b/custom_components/hacs/validate/common/repository_description.py @@ -0,0 +1,10 @@ +from custom_components.hacs.validate.base import ( + ActionValidationBase, + ValidationException, +) + + +class RepositoryDescription(ActionValidationBase): + def check(self): + if not self.repository.data.description: + raise ValidationException("The repository has no description") diff --git a/custom_components/hacs/validate/common/repository_information_file.py b/custom_components/hacs/validate/common/repository_information_file.py new file mode 100644 index 0000000..0447988 --- /dev/null +++ b/custom_components/hacs/validate/common/repository_information_file.py @@ -0,0 +1,19 @@ +from custom_components.hacs.validate.base import ( + ActionValidationBase, + ValidationException, +) + + +class RepositoryInformationFile(ActionValidationBase): + async def async_check(self): + filenames = [x.filename.lower() for x in self.repository.tree] + if self.repository.data.render_readme and "readme" in filenames: + pass + elif self.repository.data.render_readme and "readme.md" in filenames: + pass + elif "info" in filenames: + pass + elif "info.md" in filenames: + pass + else: + raise ValidationException("The repository has no information file") diff --git a/custom_components/hacs/validate/common/repository_topics.py b/custom_components/hacs/validate/common/repository_topics.py new file mode 100644 index 0000000..194faad --- /dev/null +++ b/custom_components/hacs/validate/common/repository_topics.py @@ -0,0 +1,10 @@ +from custom_components.hacs.validate.base import ( + ActionValidationBase, + ValidationException, +) + + +class RepositoryTopics(ActionValidationBase): + def check(self): + if not self.repository.data.topics: + raise ValidationException("The repository has no topics") diff --git a/custom_components/hacs/validate/integration/__pycache__/integration_manifest.cpython-38.pyc b/custom_components/hacs/validate/integration/__pycache__/integration_manifest.cpython-38.pyc new file mode 100644 index 0000000..d78ae61 Binary files /dev/null and b/custom_components/hacs/validate/integration/__pycache__/integration_manifest.cpython-38.pyc differ diff --git a/custom_components/hacs/validate/integration/integration_manifest.py b/custom_components/hacs/validate/integration/integration_manifest.py new file mode 100644 index 0000000..41b1c97 --- /dev/null +++ b/custom_components/hacs/validate/integration/integration_manifest.py @@ -0,0 +1,10 @@ +from custom_components.hacs.validate.base import ( + ActionValidationBase, + ValidationException, +) + + +class IntegrationManifest(ActionValidationBase, category="integration"): + def check(self): + if "manifest.json" not in [x.filename for x in self.repository.tree]: + raise ValidationException("The repository has no 'hacs.json' file") diff --git a/custom_components/hacs/webresponses/__init__.py b/custom_components/hacs/webresponses/__init__.py new file mode 100644 index 0000000..4b9a8ba --- /dev/null +++ b/custom_components/hacs/webresponses/__init__.py @@ -0,0 +1 @@ +"""Initialize HACS Web responses""" diff --git a/custom_components/hacs/webresponses/__pycache__/__init__.cpython-38.pyc b/custom_components/hacs/webresponses/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..2eaec8f Binary files /dev/null and b/custom_components/hacs/webresponses/__pycache__/__init__.cpython-38.pyc differ diff --git a/custom_components/hacs/webresponses/__pycache__/category.cpython-38.pyc b/custom_components/hacs/webresponses/__pycache__/category.cpython-38.pyc new file mode 100644 index 0000000..1c9c57d Binary files /dev/null and b/custom_components/hacs/webresponses/__pycache__/category.cpython-38.pyc differ diff --git a/custom_components/hacs/webresponses/__pycache__/frontend.cpython-38.pyc b/custom_components/hacs/webresponses/__pycache__/frontend.cpython-38.pyc new file mode 100644 index 0000000..b01b5bb Binary files /dev/null and b/custom_components/hacs/webresponses/__pycache__/frontend.cpython-38.pyc differ diff --git a/custom_components/hacs/webresponses/__pycache__/iconset.cpython-38.pyc b/custom_components/hacs/webresponses/__pycache__/iconset.cpython-38.pyc new file mode 100644 index 0000000..5ba2dc6 Binary files /dev/null and b/custom_components/hacs/webresponses/__pycache__/iconset.cpython-38.pyc differ diff --git a/custom_components/hacs/webresponses/category.py b/custom_components/hacs/webresponses/category.py new file mode 100644 index 0000000..b8458c4 --- /dev/null +++ b/custom_components/hacs/webresponses/category.py @@ -0,0 +1,39 @@ +from aiohttp import web + +from custom_components.hacs.helpers.functions.logger import getLogger +from custom_components.hacs.helpers.functions.path_exsist import async_path_exsist +from custom_components.hacs.share import get_hacs + +_LOGGER = getLogger() + + +async def async_serve_category_file(request, requested_file): + hacs = get_hacs() + try: + if requested_file.startswith("themes/"): + servefile = f"{hacs.core.config_path}/{requested_file}" + else: + servefile = f"{hacs.core.config_path}/www/community/{requested_file}" + + if await async_path_exsist(servefile): + _LOGGER.debug("Serving %s from %s", requested_file, servefile) + response = web.FileResponse(servefile) + if requested_file.startswith("themes/"): + response.headers["Cache-Control"] = "public, max-age=2678400" + else: + response.headers["Cache-Control"] = "no-store, max-age=0" + response.headers["Pragma"] = "no-store" + return response + else: + _LOGGER.error( + "%s tried to request '%s' but the file does not exist", + request.remote, + servefile, + ) + + except (Exception, BaseException) as exception: + _LOGGER.debug( + "there was an issue trying to serve %s - %s", requested_file, exception + ) + + return web.Response(status=404) diff --git a/custom_components/hacs/webresponses/frontend.py b/custom_components/hacs/webresponses/frontend.py new file mode 100644 index 0000000..3dfa2bd --- /dev/null +++ b/custom_components/hacs/webresponses/frontend.py @@ -0,0 +1,50 @@ +from aiohttp import web +from hacs_frontend import locate_dir + +from custom_components.hacs.helpers.functions.logger import getLogger +from custom_components.hacs.helpers.functions.path_exsist import async_path_exsist +from custom_components.hacs.share import get_hacs + +_LOGGER = getLogger() + + +async def async_serve_frontend(requested_file): + hacs = get_hacs() + requested = requested_file.split("/")[-1] + servefile = None + dev = False + + if hacs.configuration.frontend_repo_url or hacs.configuration.frontend_repo: + dev = True + + if hacs.configuration.frontend_repo_url: + _LOGGER.debug("Serving REMOTE DEVELOPMENT frontend") + try: + request = await hacs.session.get( + f"{hacs.configuration.frontend_repo_url}/{requested}" + ) + if request.status == 200: + result = await request.read() + response = web.Response(body=result) + response.headers["Content-Type"] = "application/javascript" + + return response + except (Exception, BaseException) as exception: + _LOGGER.error(exception) + + elif hacs.configuration.frontend_repo: + _LOGGER.debug("Serving LOCAL DEVELOPMENT frontend") + servefile = f"{hacs.configuration.frontend_repo}/hacs_frontend/{requested}" + else: + servefile = f"{locate_dir()}/{requested}" + + if servefile is None or not await async_path_exsist(servefile): + return web.Response(status=404) + + response = web.FileResponse(servefile) + + if dev: + response.headers["Content-Type"] = "application/javascript" + response.headers["Cache-Control"] = "no-store, max-age=0" + response.headers["Pragma"] = "no-store" + return response diff --git a/custom_components/hacs/webresponses/iconset.py b/custom_components/hacs/webresponses/iconset.py new file mode 100644 index 0000000..4e6f37c --- /dev/null +++ b/custom_components/hacs/webresponses/iconset.py @@ -0,0 +1,15 @@ +from aiohttp import web + +ICON = """ +window.customIconsets = window.customIconsets || {}; +window.customIconsets["hacs"] = async () => { + return { + path: + "m 20.064849,22.306912 c -0.0319,0.369835 -0.280561,0.707789 -0.656773,0.918212 -0.280572,0.153036 -0.605773,0.229553 -0.950094,0.229553 -0.0765,0 -0.146661,-0.0064 -0.216801,-0.01275 -0.605774,-0.05739 -1.135016,-0.344329 -1.402827,-0.7588 l 0.784304,-0.516495 c 0.0893,0.146659 0.344331,0.312448 0.707793,0.34433 0.235931,0.02551 0.471852,-0.01913 0.637643,-0.108401 0.101998,-0.05101 0.172171,-0.127529 0.17854,-0.191295 0.0065,-0.08289 -0.0255,-0.369835 -0.733293,-0.439975 -1.013854,-0.09565 -1.645127,-0.688661 -1.568606,-1.460214 0.0319,-0.382589 0.280561,-0.714165 0.663153,-0.930965 0.331571,-0.172165 0.752423,-0.25506 1.166895,-0.210424 0.599382,0.05739 1.128635,0.344329 1.402816,0.7588 l -0.784304,0.510118 c -0.0893,-0.140282 -0.344331,-0.299694 -0.707782,-0.331576 -0.235932,-0.02551 -0.471863,0.01913 -0.637654,0.10202 -0.0956,0.05739 -0.165791,0.133906 -0.17216,0.191295 -0.0255,0.293317 0.465482,0.420847 0.726913,0.439976 v 0.0064 c 1.020234,0.09565 1.638757,0.66953 1.562237,1.460213 z m -7.466854,-0.988354 c 0,-1.192401 0.962855,-2.155249 2.15525,-2.155249 0.599393,0 1.179645,0.25506 1.594117,0.707789 l -0.695033,0.624895 c -0.235931,-0.25506 -0.561133,-0.401718 -0.899084,-0.401718 -0.675903,0 -1.217906,0.542 -1.217906,1.217906 0,0.66953 0.542003,1.217908 1.217906,1.217908 0.337951,0 0.663153,-0.140283 0.899084,-0.401718 l 0.695033,0.631271 c -0.414472,0.452729 -0.988355,0.707788 -1.594117,0.707788 -1.192395,0 -2.15525,-0.969224 -2.15525,-2.148872 z M 8.6573365,23.461054 10.353474,19.14418 h 0.624893 l 1.568618,4.316874 H 11.52037 L 11.265308,22.734136 H 9.964513 l -0.274192,0.726918 z m 1.6833885,-1.68339 h 0.580263 L 10.646796,21.012487 Z M 8.1089536,19.156932 v 4.297745 H 7.1461095 v -1.645131 h -1.606867 v 1.645131 H 4.5763876 v -4.297745 h 0.9628549 v 1.696143 h 1.606867 V 19.156932 Z M 20.115859,4.2997436 C 20.090359,4.159461 19.969198,4.0574375 19.822548,4.0574375 H 14.141102 10.506516 4.8250686 c -0.14665,0 -0.2678112,0.1020202 -0.2933108,0.2423061 L 3.690064,8.8461703 c -0.00651,0.01913 -0.00651,0.03826 -0.00651,0.057391 v 1.5239797 c 0,0.165789 0.133911,0.299694 0.2996911,0.299694 H 4.5762579 20.0711 20.664112 c 0.165781,0 0.299691,-0.133905 0.299691,-0.299694 V 8.8971848 c 0,-0.01913 0,-0.03826 -0.0065,-0.05739 z M 4.5763876,17.358767 c 0,0.184917 0.1466608,0.331577 0.3315819,0.331577 h 5.5985465 3.634586 0.924594 c 0.184911,0 0.331571,-0.14666 0.331571,-0.331577 v -4.744098 c 0,-0.184918 0.146661,-0.331577 0.331582,-0.331577 h 2.894913 c 0.184921,0 0.331582,0.146659 0.331582,0.331577 v 4.744098 c 0,0.184917 0.146661,0.331577 0.331571,0.331577 h 0.446363 c 0.18491,0 0.331571,-0.14666 0.331571,-0.331577 v -5.636804 c 0,-0.184918 -0.146661,-0.331577 -0.331571,-0.331577 H 4.9079695 c -0.1849211,0 -0.3315819,0.146659 -0.3315819,0.331577 z m 1.6578879,-4.852498 h 5.6495565 c 0.15303,0 0.280561,0.12753 0.280561,0.280564 v 3.513438 c 0,0.153036 -0.127531,0.280566 -0.280561,0.280566 H 6.2342755 c -0.1530412,0 -0.2805719,-0.12753 -0.2805719,-0.280566 v -3.513438 c 0,-0.159411 0.1275307,-0.280564 0.2805719,-0.280564 z M 19.790657,3.3879075 H 4.8569594 c -0.1530412,0 -0.2805718,-0.1275296 -0.2805718,-0.2805642 V 1.3665653 C 4.5763876,1.2135296 4.7039182,1.086 4.8569594,1.086 H 19.790657 c 0.153041,0 0.280572,0.1275296 0.280572,0.2805653 v 1.740778 c 0,0.1530346 -0.127531,0.2805642 -0.280572,0.2805642 z", + }; +}; +""" + + +def serve_iconset(): + return web.Response(body=ICON, content_type="application/javascript") diff --git a/custom_components/hacs/ws_api_handlers.py b/custom_components/hacs/ws_api_handlers.py deleted file mode 100644 index 6f52660..0000000 --- a/custom_components/hacs/ws_api_handlers.py +++ /dev/null @@ -1,379 +0,0 @@ -"""WebSocket API for HACS.""" -# pylint: disable=unused-argument -import sys -import os -import voluptuous as vol -from aiogithubapi import AIOGitHubException -from homeassistant.components import websocket_api -import homeassistant.helpers.config_validation as cv -from .hacsbase.exceptions import HacsException -from .store import async_load_from_store, async_save_to_store - -from custom_components.hacs.globals import get_hacs -from custom_components.hacs.helpers.register_repository import register_repository - - -async def setup_ws_api(hass): - """Set up WS API handlers.""" - websocket_api.async_register_command(hass, hacs_settings) - websocket_api.async_register_command(hass, hacs_config) - websocket_api.async_register_command(hass, hacs_repositories) - websocket_api.async_register_command(hass, hacs_repository) - websocket_api.async_register_command(hass, hacs_repository_data) - websocket_api.async_register_command(hass, check_local_path) - websocket_api.async_register_command(hass, hacs_status) - websocket_api.async_register_command(hass, acknowledge_critical_repository) - websocket_api.async_register_command(hass, get_critical_repositories) - - -@websocket_api.async_response -@websocket_api.websocket_command( - { - vol.Required("type"): "hacs/settings", - vol.Optional("action"): cv.string, - vol.Optional("category"): cv.string, - } -) -async def hacs_settings(hass, connection, msg): - """Handle get media player cover command.""" - hacs = get_hacs() - action = msg["action"] - hacs.logger.debug(f"WS action '{action}'") - - if action == "set_fe_grid": - hacs.configuration.frontend_mode = "Grid" - - elif action == "onboarding_done": - hacs.configuration.onboarding_done = True - - elif action == "set_fe_table": - hacs.configuration.frontend_mode = "Table" - - elif action == "set_fe_compact_true": - hacs.configuration.frontend_compact = False - - elif action == "set_fe_compact_false": - hacs.configuration.frontend_compact = True - - elif action == "reload_data": - hacs.system.status.reloading_data = True - hass.bus.async_fire("hacs/status", {}) - await hacs.recuring_tasks_all() - hacs.system.status.reloading_data = False - hass.bus.async_fire("hacs/status", {}) - - elif action == "upgrade_all": - hacs.system.status.upgrading_all = True - hacs.system.status.background_task = True - hass.bus.async_fire("hacs/status", {}) - for repository in hacs.repositories: - if repository.pending_upgrade: - repository.status.selected_tag = None - await repository.install() - hacs.system.status.upgrading_all = False - hacs.system.status.background_task = False - hass.bus.async_fire("hacs/status", {}) - hass.bus.async_fire("hacs/repository", {}) - - elif action == "clear_new": - for repo in hacs.repositories: - if msg.get("category") == repo.data.category: - if repo.status.new: - hacs.logger.debug( - f"Clearing new flag from '{repo.data.full_name}'" - ) - repo.status.new = False - else: - hacs.logger.error(f"WS action '{action}' is not valid") - hass.bus.async_fire("hacs/config", {}) - await hacs.data.async_write() - - -@websocket_api.async_response -@websocket_api.websocket_command({vol.Required("type"): "hacs/config"}) -async def hacs_config(hass, connection, msg): - """Handle get media player cover command.""" - hacs = get_hacs() - config = hacs.configuration - - content = {} - content["frontend_mode"] = config.frontend_mode - content["frontend_compact"] = config.frontend_compact - content["onboarding_done"] = config.onboarding_done - content["version"] = hacs.version - content["dev"] = config.dev - content["debug"] = config.debug - content["country"] = config.country - content["experimental"] = config.experimental - content["categories"] = hacs.common.categories - - connection.send_message(websocket_api.result_message(msg["id"], content)) - - -@websocket_api.async_response -@websocket_api.websocket_command({vol.Required("type"): "hacs/status"}) -async def hacs_status(hass, connection, msg): - """Handle get media player cover command.""" - hacs = get_hacs() - content = { - "startup": hacs.system.status.startup, - "background_task": hacs.system.status.background_task, - "lovelace_mode": hacs.system.lovelace_mode, - "reloading_data": hacs.system.status.reloading_data, - "upgrading_all": hacs.system.status.upgrading_all, - "disabled": hacs.system.disabled, - } - connection.send_message(websocket_api.result_message(msg["id"], content)) - - -@websocket_api.async_response -@websocket_api.websocket_command({vol.Required("type"): "hacs/repositories"}) -async def hacs_repositories(hass, connection, msg): - """Handle get media player cover command.""" - hacs = get_hacs() - repositories = hacs.repositories - content = [] - for repo in repositories: - if repo.data.category in hacs.common.categories: - data = { - "additional_info": repo.information.additional_info, - "authors": repo.data.authors, - "available_version": repo.display_available_version, - "beta": repo.status.show_beta, - "can_install": repo.can_install, - "category": repo.data.category, - "country": repo.data.country, - "config_flow": repo.config_flow, - "custom": repo.custom, - "default_branch": repo.data.default_branch, - "description": repo.data.description, - "domain": repo.integration_manifest.get("domain"), - "downloads": repo.releases.downloads, - "file_name": repo.data.file_name, - "first_install": repo.status.first_install, - "full_name": repo.data.full_name, - "hide": repo.status.hide, - "hide_default_branch": repo.data.hide_default_branch, - "homeassistant": repo.data.homeassistant, - "id": repo.information.uid, - "info": repo.information.info, - "installed_version": repo.display_installed_version, - "installed": repo.status.installed, - "javascript_type": repo.information.javascript_type, - "last_updated": repo.information.last_updated, - "local_path": repo.content.path.local, - "main_action": repo.main_action, - "name": repo.display_name, - "new": repo.status.new, - "pending_upgrade": repo.pending_upgrade, - "releases": repo.releases.published_tags, - "selected_tag": repo.status.selected_tag, - "stars": repo.data.stargazers_count, - "state": repo.state, - "status_description": repo.display_status_description, - "status": repo.display_status, - "topics": repo.data.topics, - "updated_info": repo.status.updated_info, - "version_or_commit": repo.display_version_or_commit, - } - - content.append(data) - - connection.send_message(websocket_api.result_message(msg["id"], content)) - - -@websocket_api.async_response -@websocket_api.websocket_command( - { - vol.Required("type"): "hacs/repository", - vol.Optional("action"): cv.string, - vol.Optional("repository"): cv.string, - } -) -async def hacs_repository(hass, connection, msg): - """Handle get media player cover command.""" - hacs = get_hacs() - try: - repo_id = msg.get("repository") - action = msg.get("action") - - if repo_id is None or action is None: - return - - repository = hacs.get_by_id(repo_id) - hacs.logger.debug(f"Running {action} for {repository.data.full_name}") - - if action == "update": - await repository.update_repository() - repository.status.updated_info = True - repository.status.new = False - - elif action == "install": - was_installed = repository.status.installed - await repository.install() - if not was_installed: - hass.bus.async_fire("hacs/reload", {"force": False}) - - elif action == "uninstall": - await repository.uninstall() - hass.bus.async_fire("hacs/reload", {"force": False}) - - elif action == "hide": - repository.status.hide = True - - elif action == "unhide": - repository.status.hide = False - - elif action == "show_beta": - repository.status.show_beta = True - await repository.update_repository() - - elif action == "hide_beta": - repository.status.show_beta = False - await repository.update_repository() - - elif action == "delete": - repository.status.show_beta = False - repository.remove() - - elif action == "set_version": - if msg["version"] == repository.data.default_branch: - repository.status.selected_tag = None - else: - repository.status.selected_tag = msg["version"] - await repository.update_repository() - - else: - hacs.logger.error(f"WS action '{action}' is not valid") - - repository.state = None - await hacs.data.async_write() - except AIOGitHubException as exception: - hass.bus.async_fire("hacs/error", {"message": str(exception)}) - except AttributeError as exception: - hass.bus.async_fire( - "hacs/error", {"message": f"Could not use repository with ID {repo_id}"} - ) - except Exception as exception: # pylint: disable=broad-except - hass.bus.async_fire("hacs/error", {"message": str(exception)}) - - -@websocket_api.async_response -@websocket_api.websocket_command( - { - vol.Required("type"): "hacs/repository/data", - vol.Optional("action"): cv.string, - vol.Optional("repository"): cv.string, - vol.Optional("data"): cv.string, - } -) -async def hacs_repository_data(hass, connection, msg): - """Handle get media player cover command.""" - hacs = get_hacs() - repo_id = msg.get("repository") - action = msg.get("action") - data = msg.get("data") - - if repo_id is None: - return - - if action == "add": - if "github." in repo_id: - repo_id = repo_id.split("github.com/")[1] - - if repo_id in hacs.common.skip: - hacs.common.skip.remove(repo_id) - - if not hacs.get_by_name(repo_id): - try: - registration = await register_repository(repo_id, data.lower()) - if registration is not None: - raise HacsException(registration) - except Exception as exception: # pylint: disable=broad-except - hass.bus.async_fire( - "hacs/error", - { - "action": "add_repository", - "exception": str(sys.exc_info()[0].__name__), - "message": str(exception), - }, - ) - else: - hass.bus.async_fire( - "hacs/error", - { - "action": "add_repository", - "message": f"Repository '{repo_id}' exists in the store.", - }, - ) - - repository = hacs.get_by_name(repo_id) - else: - repository = hacs.get_by_id(repo_id) - - if repository is None: - hass.bus.async_fire("hacs/repository", {}) - return - - hacs.logger.debug(f"Running {action} for {repository.data.full_name}") - - if action == "set_state": - repository.state = data - - elif action == "set_version": - repository.status.selected_tag = data - await repository.update_repository() - repository.state = None - - elif action == "add": - repository.state = None - - else: - repository.state = None - hacs.logger.error(f"WS action '{action}' is not valid") - - await hacs.data.async_write() - - -@websocket_api.async_response -@websocket_api.websocket_command( - {vol.Required("type"): "hacs/check_path", vol.Optional("path"): cv.string} -) -async def check_local_path(hass, connection, msg): - """Handle get media player cover command.""" - path = msg.get("path") - exist = {"exist": False} - - if path is None: - return - - if os.path.exists(path): - exist["exist"] = True - - connection.send_message(websocket_api.result_message(msg["id"], exist)) - - -@websocket_api.async_response -@websocket_api.websocket_command({vol.Required("type"): "hacs/get_critical"}) -async def get_critical_repositories(hass, connection, msg): - """Handle get media player cover command.""" - critical = await async_load_from_store(hass, "critical") - if not critical: - critical = [] - connection.send_message(websocket_api.result_message(msg["id"], critical)) - - -@websocket_api.async_response -@websocket_api.websocket_command( - {vol.Required("type"): "hacs/critical", vol.Optional("repository"): cv.string} -) -async def acknowledge_critical_repository(hass, connection, msg): - """Handle get media player cover command.""" - repository = msg["repository"] - - critical = await async_load_from_store(hass, "critical") - for repo in critical: - if repository == repo["repository"]: - repo["acknowledged"] = True - await async_save_to_store(hass, "critical", critical) - connection.send_message(websocket_api.result_message(msg["id"], critical)) diff --git a/custom_components/input_select/__pycache__/__init__.cpython-38.pyc b/custom_components/input_select/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..d169c4d Binary files /dev/null and b/custom_components/input_select/__pycache__/__init__.cpython-38.pyc differ diff --git a/custom_components/lovelace_gen/__init__.py b/custom_components/lovelace_gen/__init__.py new file mode 100644 index 0000000..c1e0974 --- /dev/null +++ b/custom_components/lovelace_gen/__init__.py @@ -0,0 +1,97 @@ +import os +import logging +import json +import io +import time +from collections import OrderedDict + +import jinja2 + +from homeassistant.util.yaml import loader +from homeassistant.exceptions import HomeAssistantError + +_LOGGER = logging.getLogger(__name__) + +def fromjson(value): + return json.loads(value) + +jinja = jinja2.Environment(loader=jinja2.FileSystemLoader("/")) + +jinja.filters['fromjson'] = fromjson + +llgen_config = {} + +def load_yaml(fname, args={}): + try: + ll_gen = False + with open(fname, encoding="utf-8") as f: + if f.readline().lower().startswith("# lovelace_gen"): + ll_gen = True + + if ll_gen: + stream = io.StringIO(jinja.get_template(fname).render({**args, "_global": llgen_config})) + stream.name = fname + return loader.yaml.load(stream, Loader=loader.SafeLineLoader) or OrderedDict() + else: + with open(fname, encoding="utf-8") as config_file: + return loader.yaml.load(config_file, Loader=loader.SafeLineLoader) or OrderedDict() + except loader.yaml.YAMLError as exc: + _LOGGER.error(str(exc)) + raise HomeAssistantError(exc) + except UnicodeDecodeError as exc: + _LOGGER.error("Unable to read file %s: %s", fname, exc) + raise HomeAssistantError(exc) + + +def _include_yaml(ldr, node): + args = {} + if isinstance(node.value, str): + fn = node.value + else: + fn, args, *_ = ldr.construct_sequence(node) + fname = os.path.abspath(os.path.join(os.path.dirname(ldr.name), fn)) + try: + return loader._add_reference(load_yaml(fname, args), ldr, node) + except FileNotFoundError as exc: + _LOGGER.error("Unable to include file %s: %s", fname, exc); + raise HomeAssistantError(exc) + +def _uncache_file(ldr, node): + path = node.value + timestamp = str(time.time()) + if '?' in path: + return f"{path}&{timestamp}" + return f"{path}?{timestamp}" + +loader.load_yaml = load_yaml +loader.yaml.SafeLoader.add_constructor("!include", _include_yaml) +loader.yaml.SafeLoader.add_constructor("!file", _uncache_file) + +async def async_setup(hass, config): + llgen_config.update(config.get("lovelace_gen")); + return True + +# Allow redefinition of node anchors +import yaml + +def compose_node(self, parent, index): + if self.check_event(yaml.events.AliasEvent): + event = self.get_event() + anchor = event.anchor + if anchor not in self.anchors: + raise yaml.composer.ComposerError(None, None, "found undefined alias %r" + % anchor, event.start_mark) + return self.anchors[anchor] + event = self.peek_event() + anchor = event.anchor + self.descend_resolver(parent, index) + if self.check_event(yaml.events.ScalarEvent): + node = self.compose_scalar_node(anchor) + elif self.check_event(yaml.events.SequenceStartEvent): + node = self.compose_sequence_node(anchor) + elif self.check_event(yaml.events.MappingStartEvent): + node = self.compose_mapping_node(anchor) + self.ascend_resolver() + return node + +yaml.composer.Composer.compose_node = compose_node diff --git a/custom_components/lovelace_gen/__pycache__/__init__.cpython-37.pyc b/custom_components/lovelace_gen/__pycache__/__init__.cpython-37.pyc new file mode 100644 index 0000000..532f30a Binary files /dev/null and b/custom_components/lovelace_gen/__pycache__/__init__.cpython-37.pyc differ diff --git a/custom_components/lovelace_gen/__pycache__/__init__.cpython-38.pyc b/custom_components/lovelace_gen/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..1638029 Binary files /dev/null and b/custom_components/lovelace_gen/__pycache__/__init__.cpython-38.pyc differ diff --git a/custom_components/lovelace_gen/manifest.json b/custom_components/lovelace_gen/manifest.json new file mode 100644 index 0000000..74a3829 --- /dev/null +++ b/custom_components/lovelace_gen/manifest.json @@ -0,0 +1,8 @@ +{ + "domain": "lovelace_gen", + "name": "Lovelace Gen", + "documentation": "", + "dependencies": ["lovelace"], + "codeowners": [], + "requirements": ["jinja2"] +} diff --git a/custom_components/nodered/__init__.py b/custom_components/nodered/__init__.py new file mode 100644 index 0000000..9db7199 --- /dev/null +++ b/custom_components/nodered/__init__.py @@ -0,0 +1,258 @@ +""" +Component to integrate with node-red. + +For more details about this component, please refer to +https://github.com/zachowj/hass-node-red +""" +import asyncio +import logging +import os +from typing import Any, Dict, Optional, Union + +from integrationhelper.const import CC_STARTUP_VERSION + +from homeassistant.const import ( + CONF_DEVICE_CLASS, + CONF_ICON, + CONF_STATE, + CONF_TYPE, + CONF_UNIT_OF_MEASUREMENT, +) +from homeassistant.core import callback +from homeassistant.helpers.dispatcher import ( + async_dispatcher_connect, + async_dispatcher_send, +) +from homeassistant.helpers.entity import Entity + +from .const import ( + CONF_COMPONENT, + CONF_CONFIG, + CONF_DEVICE_INFO, + CONF_NAME, + CONF_NODE_ID, + CONF_REMOVE, + CONF_SERVER_ID, + CONF_VERSION, + DEFAULT_NAME, + DOMAIN, + DOMAIN_DATA, + ISSUE_URL, + NODERED_DISCOVERY_UPDATED, + NODERED_ENTITY, + REQUIRED_FILES, + VERSION, +) +from .discovery import ( + ALREADY_DISCOVERED, + CHANGE_ENTITY_TYPE, + CONFIG_ENTRY_IS_SETUP, + NODERED_DISCOVERY, + start_discovery, + stop_discovery, +) +from .websocket import register_websocket_handlers + +_LOGGER = logging.getLogger(__name__) + + +async def async_setup(hass, config): + """Stub to allow setting up this component. + + Configuration through YAML is not supported. + """ + return True + + +async def async_setup_entry(hass, config_entry): + """Set up this integration using UI.""" + + # Print startup message + _LOGGER.info( + CC_STARTUP_VERSION.format(name=DOMAIN, version=VERSION, issue_link=ISSUE_URL) + ) + + # Check that all required files are present + file_check = await hass.async_add_executor_job(check_files, hass) + if not file_check: + return False + + # Create DATA dict + hass.data[DOMAIN_DATA] = {} + + register_websocket_handlers(hass) + await start_discovery(hass, hass.data[DOMAIN_DATA], config_entry) + hass.bus.async_fire(DOMAIN, {CONF_TYPE: "loaded", CONF_VERSION: VERSION}) + + return True + + +def check_files(hass): + """Return bool that indicates if all files are present.""" + # Verify that the user downloaded all files. + base = f"{hass.config.path()}/custom_components/{DOMAIN}/" + missing = [] + for file in REQUIRED_FILES: + fullpath = "{}{}".format(base, file) + if not os.path.exists(fullpath): + missing.append(file) + + if missing: + _LOGGER.critical(f"The following files are missing: {str(missing)}") + returnvalue = False + else: + returnvalue = True + + return returnvalue + + +async def async_remove_entry(hass, config_entry): + """Handle removal of an entry.""" + if hass.data[DOMAIN_DATA][CONFIG_ENTRY_IS_SETUP]: + await asyncio.wait( + [ + hass.config_entries.async_forward_entry_unload(config_entry, platform) + for platform in hass.data[DOMAIN_DATA][CONFIG_ENTRY_IS_SETUP] + ] + ) + + stop_discovery(hass) + del hass.data[DOMAIN_DATA] + hass.bus.async_fire(DOMAIN, {CONF_TYPE: "unloaded"}) + + +class NodeRedEntity(Entity): + """nodered Sensor class.""" + + def __init__(self, hass, config): + """Initialize the entity.""" + self.hass = hass + self.attr = {} + self._config = config[CONF_CONFIG] + self._component = None + self._device_info = config.get(CONF_DEVICE_INFO) + self._state = None + self._server_id = config[CONF_SERVER_ID] + self._node_id = config[CONF_NODE_ID] + self._remove_signal_discovery_update = None + self._remove_signal_entity_update = None + + @property + def should_poll(self) -> bool: + """Return True if entity has to be polled for state. + + False if entity pushes its state to HA. + """ + return False + + @property + def unique_id(self) -> Optional[str]: + """Return a unique ID to use for this sensor.""" + return f"{DOMAIN}-{self._server_id}-{self._node_id}" + + @property + def device_class(self) -> Optional[str]: + """Return the class of this binary_sensor.""" + return self._config.get(CONF_DEVICE_CLASS) + + @property + def name(self) -> Optional[str]: + """Return the name of the sensor.""" + return self._config.get(CONF_NAME, f"{DEFAULT_NAME} {self._node_id}") + + @property + def state(self) -> Union[None, str, int, float]: + """Return the state of the sensor.""" + return self._state + + @property + def icon(self) -> Optional[str]: + """Return the icon of the sensor.""" + return self._config.get(CONF_ICON) + + @property + def unit_of_measurement(self) -> Optional[str]: + """Return the unit this state is expressed in.""" + return self._config.get(CONF_UNIT_OF_MEASUREMENT) + + @property + def device_state_attributes(self) -> Optional[Dict[str, Any]]: + """Return the state attributes.""" + return self.attr + + @property + def device_info(self) -> Optional[Dict[str, Any]]: + """Return device specific attributes.""" + info = None + if self._device_info is not None and "id" in self._device_info: + # Use the id property to create the device identifier then delete it + info = {"identifiers": {(DOMAIN, self._device_info["id"])}} + del self._device_info["id"] + info.update(self._device_info) + + return info + + @callback + def handle_entity_update(self, msg): + """Update entity state.""" + _LOGGER.debug(f"Entity Update: {msg}") + self.attr = msg.get("attributes", {}) + self._state = msg[CONF_STATE] + self.async_write_ha_state() + + @callback + def handle_discovery_update(self, msg, connection): + """Update entity config.""" + if CONF_REMOVE not in msg: + self._config = msg[CONF_CONFIG] + self.async_write_ha_state() + return + + # Otherwise, remove entity + if msg[CONF_REMOVE] == CHANGE_ENTITY_TYPE: + # recreate entity if component type changed + @callback + def recreate_entity(): + """Create entity with new type.""" + del msg[CONF_REMOVE] + async_dispatcher_send( + self.hass, + NODERED_DISCOVERY.format(msg[CONF_COMPONENT]), + msg, + connection, + ) + + self.async_on_remove(recreate_entity) + + self.hass.async_create_task(self.async_remove()) + + async def async_added_to_hass(self) -> None: + """Run when entity about to be added to hass.""" + + self._remove_signal_entity_update = async_dispatcher_connect( + self.hass, + NODERED_ENTITY.format(self._server_id, self._node_id), + self.handle_entity_update, + ) + self._remove_signal_discovery_update = async_dispatcher_connect( + self.hass, + NODERED_DISCOVERY_UPDATED.format(self.unique_id), + self.handle_discovery_update, + ) + + async def async_will_remove_from_hass(self) -> None: + """Run when entity will be removed from hass.""" + if self._remove_signal_entity_update is not None: + self._remove_signal_entity_update() + if self._remove_signal_discovery_update is not None: + self._remove_signal_discovery_update() + + del self.hass.data[DOMAIN_DATA][ALREADY_DISCOVERED][self.unique_id] + + # Remove the entity_id from the entity registry + registry = await self.hass.helpers.entity_registry.async_get_registry() + entity_id = registry.async_get_entity_id( + self._component, DOMAIN, self.unique_id, + ) + if entity_id: + registry.async_remove(entity_id) diff --git a/custom_components/nodered/__pycache__/__init__.cpython-38.pyc b/custom_components/nodered/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..bc4119b Binary files /dev/null and b/custom_components/nodered/__pycache__/__init__.cpython-38.pyc differ diff --git a/custom_components/nodered/__pycache__/config_flow.cpython-38.pyc b/custom_components/nodered/__pycache__/config_flow.cpython-38.pyc new file mode 100644 index 0000000..37aa7fe Binary files /dev/null and b/custom_components/nodered/__pycache__/config_flow.cpython-38.pyc differ diff --git a/custom_components/nodered/__pycache__/const.cpython-38.pyc b/custom_components/nodered/__pycache__/const.cpython-38.pyc new file mode 100644 index 0000000..ce9bc83 Binary files /dev/null and b/custom_components/nodered/__pycache__/const.cpython-38.pyc differ diff --git a/custom_components/nodered/__pycache__/discovery.cpython-38.pyc b/custom_components/nodered/__pycache__/discovery.cpython-38.pyc new file mode 100644 index 0000000..cd5bd31 Binary files /dev/null and b/custom_components/nodered/__pycache__/discovery.cpython-38.pyc differ diff --git a/custom_components/nodered/__pycache__/websocket.cpython-38.pyc b/custom_components/nodered/__pycache__/websocket.cpython-38.pyc new file mode 100644 index 0000000..aab66c3 Binary files /dev/null and b/custom_components/nodered/__pycache__/websocket.cpython-38.pyc differ diff --git a/custom_components/nodered/binary_sensor.py b/custom_components/nodered/binary_sensor.py new file mode 100644 index 0000000..4d2502e --- /dev/null +++ b/custom_components/nodered/binary_sensor.py @@ -0,0 +1,74 @@ +"""Binary sensor platform for nodered.""" +from numbers import Number + +from homeassistant.const import ( + CONF_STATE, + STATE_HOME, + STATE_OFF, + STATE_ON, + STATE_OPEN, + STATE_UNLOCKED, +) +from homeassistant.helpers.dispatcher import async_dispatcher_connect + +from . import NodeRedEntity +from .const import CONF_ATTRIBUTES, CONF_BINARY_SENSOR, NODERED_DISCOVERY_NEW + + +async def async_setup_entry(hass, config_entry, async_add_devices): + """Set up sensor platform.""" + + async def async_discover(config, connection): + await _async_setup_entity(hass, config, async_add_devices) + + async_dispatcher_connect( + hass, NODERED_DISCOVERY_NEW.format(CONF_BINARY_SENSOR), async_discover, + ) + + +async def _async_setup_entity(hass, config, async_add_devices): + """Set up the Node-RED binary-sensor.""" + async_add_devices([NodeRedBinarySensor(hass, config)]) + + +class NodeRedBinarySensor(NodeRedEntity): + """Node-RED binary-sensor class.""" + + on_states = ( + "1", + "true", + "yes", + "enable", + STATE_ON, + STATE_OPEN, + STATE_HOME, + STATE_UNLOCKED, + ) + + def __init__(self, hass, config): + """Initialize the binary sensor.""" + super().__init__(hass, config) + self._component = CONF_BINARY_SENSOR + self._state = config.get(CONF_STATE) + self.attr = config.get(CONF_ATTRIBUTES, {}) + + @property + def is_on(self): + """Return true if the binary sensor is on.""" + value = self._state + + if isinstance(value, bool): + return value + if isinstance(value, str): + value = value.lower().strip() + if value in NodeRedBinarySensor.on_states: + return True + elif isinstance(value, Number): + return value != 0 + + return False + + @property + def state(self): + """Return the state of the binary sensor.""" + return STATE_ON if self.is_on else STATE_OFF diff --git a/custom_components/nodered/config_flow.py b/custom_components/nodered/config_flow.py new file mode 100644 index 0000000..f0b5ef8 --- /dev/null +++ b/custom_components/nodered/config_flow.py @@ -0,0 +1,31 @@ +"""Adds config flow for Node-RED.""" +import logging + +from homeassistant import config_entries + +from .const import DOMAIN + +_LOGGER = logging.getLogger(__name__) + + +@config_entries.HANDLERS.register(DOMAIN) +class NodeRedFlowHandler(config_entries.ConfigFlow): + """Config flow for Node-RED.""" + + VERSION = 1 + CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_PUSH + + def __init__(self): + """Initialize.""" + self._errors = {} + + async def async_step_user(self, user_input=None): + """Handle a user initiated set up flow to create a webhook.""" + if self._async_current_entries(): + return self.async_abort(reason="single_instance_allowed") + if self.hass.data.get(DOMAIN): + return self.async_abort(reason="single_instance_allowed") + + if user_input is None: + return self.async_show_form(step_id="user") + return self.async_create_entry(title="Node-RED", data={},) diff --git a/custom_components/nodered/const.py b/custom_components/nodered/const.py new file mode 100644 index 0000000..aab71c9 --- /dev/null +++ b/custom_components/nodered/const.py @@ -0,0 +1,51 @@ +"""Constants for Node-RED.""" +# Base component constants +DOMAIN = "nodered" +DOMAIN_DATA = f"{DOMAIN}_data" +VERSION = "0.4.4" +REQUIRED_FILES = [ + "translations/en.json", + "binary_sensor.py", + "config_flow.py", + "const.py", + "discovery.py", + "manifest.json", + "sensor.py", + "services.yaml", + "switch.py", + "websocket.py", +] +ISSUE_URL = "https://github.com/zachowj/hass-node-red/issues" +ATTRIBUTION = "Data from this is provided by Node-RED." + +# Configuration +CONF_ATTRIBUTES = "attributes" +CONF_BINARY_SENSOR = "binary_sensor" +CONF_COMPONENT = "component" +CONF_CONFIG = "config" +CONF_CONNECTION = "connection" +CONF_DATA = "data" +CONF_DEVICE_INFO = "device_info" +CONF_ENABLED = "enabled" +CONF_NAME = "name" +CONF_NODE_ID = "node_id" +CONF_OUTPUT_PATH = "output_path" +CONF_PAYLOAD = "payload" +CONF_REMOVE = "remove" +CONF_SENSOR = "sensor" +CONF_SERVER_ID = "server_id" +CONF_SKIP_CONDITION = "skip_condition" +CONF_SWITCH = "switch" +CONF_TRIGGER_ENTITY_ID = "trigger_entity_id" +CONF_VERSION = "version" + +NODERED_DISCOVERY = "nodered_discovery" +NODERED_DISCOVERY_NEW = "nodered_discovery_new_{}" +NODERED_DISCOVERY_UPDATED = "nodered_discovery_updated_{}" +NODERED_ENTITY = "nodered_entity_{}_{}" + +SERVICE_TRIGGER = "trigger" + +# Defaults +DEFAULT_NAME = DOMAIN +SWITCH_ICON = "mdi:electric-switch-closed" diff --git a/custom_components/nodered/discovery.py b/custom_components/nodered/discovery.py new file mode 100644 index 0000000..72b471c --- /dev/null +++ b/custom_components/nodered/discovery.py @@ -0,0 +1,107 @@ +"""Support for Node-RED discovery.""" +import asyncio +import logging + +from homeassistant.helpers.dispatcher import ( + async_dispatcher_connect, + async_dispatcher_send, +) +from homeassistant.helpers.typing import HomeAssistantType + +from .const import ( + CONF_BINARY_SENSOR, + CONF_COMPONENT, + CONF_NODE_ID, + CONF_REMOVE, + CONF_SENSOR, + CONF_SERVER_ID, + CONF_SWITCH, + DOMAIN, + DOMAIN_DATA, + NODERED_DISCOVERY, + NODERED_DISCOVERY_NEW, + NODERED_DISCOVERY_UPDATED, +) + +SUPPORTED_COMPONENTS = [ + CONF_SWITCH, + CONF_BINARY_SENSOR, + CONF_SENSOR, +] + +_LOGGER = logging.getLogger(__name__) + +ALREADY_DISCOVERED = "discovered_components" +CHANGE_ENTITY_TYPE = "change_entity_type" +CONFIG_ENTRY_LOCK = "config_entry_lock" +CONFIG_ENTRY_IS_SETUP = "config_entry_is_setup" +DISCOVERY_DISPATCHED = "discovery_dispatched" + + +async def start_discovery( + hass: HomeAssistantType, hass_config, config_entry=None +) -> bool: + """Initialize of Node-RED Discovery.""" + + async def async_device_message_received(msg, connection): + """Process the received message.""" + component = msg[CONF_COMPONENT] + server_id = msg[CONF_SERVER_ID] + node_id = msg[CONF_NODE_ID] + + if component not in SUPPORTED_COMPONENTS: + _LOGGER.warning(f"Integration {component} is not supported") + return + + discovery_hash = f"{DOMAIN}-{server_id}-{node_id}" + data = hass.data[DOMAIN_DATA] + + _LOGGER.debug(f"Discovery message: {msg}") + + if ALREADY_DISCOVERED not in data: + data[ALREADY_DISCOVERED] = {} + if discovery_hash in data[ALREADY_DISCOVERED]: + + if data[ALREADY_DISCOVERED][discovery_hash] != component: + # Remove old + log_text = f"Changing {data[ALREADY_DISCOVERED][discovery_hash]} to" + msg[CONF_REMOVE] = CHANGE_ENTITY_TYPE + elif CONF_REMOVE in msg: + log_text = "Removing" + else: + # Dispatch update + log_text = "Updating" + + _LOGGER.info(f"{log_text} {component} {server_id} {node_id}") + + data[ALREADY_DISCOVERED][discovery_hash] = component + async_dispatcher_send( + hass, NODERED_DISCOVERY_UPDATED.format(discovery_hash), msg, connection + ) + else: + # Add component + _LOGGER.info(f"Creating {component} {server_id} {node_id}") + data[ALREADY_DISCOVERED][discovery_hash] = component + + async with data[CONFIG_ENTRY_LOCK]: + if component not in data[CONFIG_ENTRY_IS_SETUP]: + await hass.config_entries.async_forward_entry_setup( + config_entry, component + ) + data[CONFIG_ENTRY_IS_SETUP].add(component) + + async_dispatcher_send( + hass, NODERED_DISCOVERY_NEW.format(component), msg, connection + ) + + hass.data[DOMAIN_DATA][CONFIG_ENTRY_LOCK] = asyncio.Lock() + hass.data[DOMAIN_DATA][CONFIG_ENTRY_IS_SETUP] = set() + + hass.data[DOMAIN_DATA][DISCOVERY_DISPATCHED] = async_dispatcher_connect( + hass, NODERED_DISCOVERY, async_device_message_received, + ) + + +def stop_discovery(hass: HomeAssistantType): + """Remove discovery dispatcher.""" + hass.data[DOMAIN_DATA][DISCOVERY_DISPATCHED]() diff --git a/custom_components/nodered/manifest.json b/custom_components/nodered/manifest.json new file mode 100644 index 0000000..2850639 --- /dev/null +++ b/custom_components/nodered/manifest.json @@ -0,0 +1,14 @@ +{ + "domain": "nodered", + "name": "Node-RED", + "documentation": "https://github.com/zachowj/node-red", + "dependencies": [], + "config_flow": true, + "codeowners": [ + "@zachowj" + ], + "requirements": [ + "integrationhelper" + ], + "homeassistant": "0.96.0" +} diff --git a/custom_components/nodered/sensor.py b/custom_components/nodered/sensor.py new file mode 100644 index 0000000..34f191d --- /dev/null +++ b/custom_components/nodered/sensor.py @@ -0,0 +1,37 @@ +"""Sensor platform for nodered.""" +import logging + +from homeassistant.const import CONF_STATE +from homeassistant.helpers.dispatcher import async_dispatcher_connect + +from . import NodeRedEntity +from .const import CONF_ATTRIBUTES, CONF_SENSOR, NODERED_DISCOVERY_NEW + +_LOGGER = logging.getLogger(__name__) + + +async def async_setup_entry(hass, config_entry, async_add_entities): + """Set up sensor platform.""" + + async def async_discover(config, connection): + await _async_setup_entity(hass, config, async_add_entities) + + async_dispatcher_connect( + hass, NODERED_DISCOVERY_NEW.format(CONF_SENSOR), async_discover, + ) + + +async def _async_setup_entity(hass, config, async_add_entities): + """Set up the Node-RED sensor.""" + async_add_entities([NodeRedSensor(hass, config)]) + + +class NodeRedSensor(NodeRedEntity): + """Node-RED Sensor class.""" + + def __init__(self, hass, config): + """Initialize the sensor.""" + super().__init__(hass, config) + self._component = CONF_SENSOR + self._state = config.get(CONF_STATE) + self.attr = config.get(CONF_ATTRIBUTES, {}) diff --git a/custom_components/nodered/services.yaml b/custom_components/nodered/services.yaml new file mode 100644 index 0000000..6bd5b36 --- /dev/null +++ b/custom_components/nodered/services.yaml @@ -0,0 +1,17 @@ +trigger: + description: Trigger a Node-RED Event Node + fields: + entity_id: + description: Entity Id of the Node-RED switch + example: switch.nodered_motion + trigger_entity_id: + description: Entity Id to trigger the event node with. Only needed if the node is not triggered by a single entity. + example: sun.sun + skip_condition: + description: Skip conditions of the node (defaults to false) + example: true + output_path: + description: Which output of the node to use (defaults to true, the top output). Only used when skip_condition is set to true. + example: true + payload: + description: The payload the node will output when triggered. Work only when triggering a entity node not an event node. diff --git a/custom_components/nodered/switch.py b/custom_components/nodered/switch.py new file mode 100644 index 0000000..4cc82cf --- /dev/null +++ b/custom_components/nodered/switch.py @@ -0,0 +1,153 @@ +"""Sensor platform for nodered.""" +import logging + +import voluptuous as vol + +from homeassistant.components.websocket_api import event_message +from homeassistant.const import ( + CONF_ENTITY_ID, + CONF_ICON, + CONF_ID, + CONF_STATE, + CONF_TYPE, + EVENT_STATE_CHANGED, +) +from homeassistant.core import callback +from homeassistant.helpers import entity_platform +import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.dispatcher import async_dispatcher_connect +from homeassistant.helpers.entity import ToggleEntity + +from . import NodeRedEntity +from .const import ( + CONF_CONFIG, + CONF_DATA, + CONF_OUTPUT_PATH, + CONF_PAYLOAD, + CONF_SKIP_CONDITION, + CONF_SWITCH, + CONF_TRIGGER_ENTITY_ID, + NODERED_DISCOVERY_NEW, + SERVICE_TRIGGER, + SWITCH_ICON, +) + +_LOGGER = logging.getLogger(__name__) + +SERVICE_TRIGGER_SCHEMA = vol.Schema( + { + vol.Required(CONF_ENTITY_ID): cv.entity_ids, + vol.Optional(CONF_TRIGGER_ENTITY_ID): cv.entity_id, + vol.Optional(CONF_SKIP_CONDITION): cv.boolean, + vol.Optional(CONF_OUTPUT_PATH): cv.boolean, + vol.Optional(CONF_PAYLOAD): vol.Extra, + } +) +EVENT_TRIGGER_NODE = "automation_triggered" + + +async def async_setup_entry(hass, config_entry, async_add_entities): + """Set up the Switch platform.""" + + async def async_discover(config, connection): + await _async_setup_entity(hass, config, async_add_entities, connection) + + async_dispatcher_connect( + hass, NODERED_DISCOVERY_NEW.format(CONF_SWITCH), async_discover, + ) + + platform = entity_platform.current_platform.get() + + platform.async_register_entity_service( + SERVICE_TRIGGER, SERVICE_TRIGGER_SCHEMA, "async_trigger_node" + ) + + +async def _async_setup_entity(hass, config, async_add_entities, connection): + """Set up the Node-RED Switch.""" + async_add_entities([NodeRedSwitch(hass, config, connection)]) + + +class NodeRedSwitch(ToggleEntity, NodeRedEntity): + """Node-RED Switch class.""" + + def __init__(self, hass, config, connection): + """Initialize the switch.""" + super().__init__(hass, config) + self._message_id = config[CONF_ID] + self._connection = connection + self._state = config.get(CONF_STATE, True) + self._component = CONF_SWITCH + self._available = True + + @property + def is_on(self) -> bool: + """Return the state of the switch.""" + return self._state + + @property + def icon(self): + """Return the icon of the sensor.""" + return self._config.get(CONF_ICON, SWITCH_ICON) + + @property + def available(self) -> bool: + """Return True if entity is available.""" + return self._available + + async def async_turn_off(self, **kwargs) -> None: + """Turn off the switch.""" + self._update_node_red(False) + + async def async_turn_on(self, **kwargs) -> None: + """Turn on the switch.""" + self._update_node_red(True) + + async def async_trigger_node(self, **kwargs) -> None: + """Trigger node in Node-RED.""" + data = {} + data[CONF_ENTITY_ID] = kwargs.get(CONF_TRIGGER_ENTITY_ID) + data[CONF_SKIP_CONDITION] = kwargs.get(CONF_SKIP_CONDITION, False) + data[CONF_OUTPUT_PATH] = kwargs.get(CONF_OUTPUT_PATH, True) + if kwargs.get(CONF_PAYLOAD) is not None: + data[CONF_PAYLOAD] = kwargs[CONF_PAYLOAD] + + self._connection.send_message( + event_message( + self._message_id, {CONF_TYPE: EVENT_TRIGGER_NODE, CONF_DATA: data}, + ) + ) + + def _update_node_red(self, state): + self._connection.send_message( + event_message( + self._message_id, {CONF_TYPE: EVENT_STATE_CHANGED, CONF_STATE: state} + ) + ) + + @callback + def handle_lost_connection(self): + """Set availability to False when disconnected.""" + self._available = False + self.async_write_ha_state() + + @callback + def handle_discovery_update(self, msg, connection): + """Update entity config.""" + if "remove" in msg: + # Remove entity + self.hass.async_create_task(self.async_remove()) + else: + self._available = True + self._state = msg[CONF_STATE] + self._config = msg[CONF_CONFIG] + self._message_id = msg[CONF_ID] + self._connection = connection + self._connection.subscriptions[msg[CONF_ID]] = self.handle_lost_connection + self.async_write_ha_state() + + async def async_added_to_hass(self) -> None: + """Run when entity about to be added to hass.""" + await super().async_added_to_hass() + + self._connection.subscriptions[self._message_id] = self.handle_lost_connection diff --git a/custom_components/nodered/translations/en.json b/custom_components/nodered/translations/en.json new file mode 100644 index 0000000..02f6ad0 --- /dev/null +++ b/custom_components/nodered/translations/en.json @@ -0,0 +1,14 @@ +{ + "config": { + "title": "Node-RED", + "step": { + "user": { + "title": "Node-RED", + "description": "Are you sure you want to set up Node-RED?" + } + }, + "abort": { + "single_instance_allowed": "Only a single configuration of Node-RED is allowed." + } + } +} diff --git a/custom_components/nodered/websocket.py b/custom_components/nodered/websocket.py new file mode 100644 index 0000000..17ffea7 --- /dev/null +++ b/custom_components/nodered/websocket.py @@ -0,0 +1,151 @@ +"""Websocket API for Node-RED.""" +import json +import logging + +import voluptuous as vol + +from homeassistant.components.websocket_api import ( + async_register_command, + async_response, + event_message, + require_admin, + result_message, + websocket_command, +) +from homeassistant.const import ( + CONF_ID, + CONF_NAME, + CONF_STATE, + CONF_TYPE, + CONF_WEBHOOK_ID, +) +from homeassistant.core import callback +import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.dispatcher import async_dispatcher_send +from homeassistant.helpers.typing import HomeAssistantType + +from .const import ( + CONF_ATTRIBUTES, + CONF_COMPONENT, + CONF_CONFIG, + CONF_DEVICE_INFO, + CONF_NODE_ID, + CONF_REMOVE, + CONF_SERVER_ID, + DOMAIN, + NODERED_DISCOVERY, + NODERED_ENTITY, + VERSION, +) + +_LOGGER = logging.getLogger(__name__) + + +def register_websocket_handlers(hass: HomeAssistantType): + """Register the websocket handlers.""" + + async_register_command(hass, websocket_version) + async_register_command(hass, websocket_webhook) + async_register_command(hass, websocket_discovery) + async_register_command(hass, websocket_entity) + + +@require_admin +@websocket_command( + { + vol.Required(CONF_TYPE): "nodered/discovery", + vol.Required(CONF_COMPONENT): cv.string, + vol.Required(CONF_SERVER_ID): cv.string, + vol.Required(CONF_NODE_ID): cv.string, + vol.Optional(CONF_CONFIG, default={}): dict, + vol.Optional(CONF_STATE): vol.Any(bool, str, int, float), + vol.Optional(CONF_ATTRIBUTES): dict, + vol.Optional(CONF_REMOVE): bool, + vol.Optional(CONF_DEVICE_INFO): dict, + } +) +def websocket_discovery(hass, connection, msg): + """Sensor command.""" + async_dispatcher_send( + hass, NODERED_DISCOVERY.format(msg[CONF_COMPONENT]), msg, connection + ) + connection.send_message(result_message(msg[CONF_ID], {"success": True})) + + +@require_admin +@websocket_command( + { + vol.Required(CONF_TYPE): "nodered/entity", + vol.Required(CONF_SERVER_ID): cv.string, + vol.Required(CONF_NODE_ID): cv.string, + vol.Required(CONF_STATE): vol.Any(bool, str, int, float), + vol.Optional(CONF_ATTRIBUTES, default={}): dict, + } +) +def websocket_entity(hass, connection, msg): + """Sensor command.""" + + async_dispatcher_send( + hass, NODERED_ENTITY.format(msg[CONF_SERVER_ID], msg[CONF_NODE_ID]), msg + ) + connection.send_message(result_message(msg[CONF_ID], {"success": True})) + + +@require_admin +@websocket_command({vol.Required(CONF_TYPE): "nodered/version"}) +def websocket_version(hass, connection, msg): + """Version command.""" + + connection.send_message(result_message(msg[CONF_ID], VERSION)) + + +@require_admin +@async_response +@websocket_command( + { + vol.Required(CONF_TYPE): "nodered/webhook", + vol.Required(CONF_WEBHOOK_ID): cv.string, + vol.Required(CONF_NAME): cv.string, + vol.Required(CONF_SERVER_ID): cv.string, + } +) +async def websocket_webhook(hass, connection, msg): + """Create webhook command.""" + webhook_id = msg[CONF_WEBHOOK_ID] + + @callback + async def handle_webhook(hass, id, request): + """Handle webhook callback.""" + body = await request.text() + try: + payload = json.loads(body) if body else {} + except ValueError: + payload = body + + data = {"payload": payload, "headers": dict(request.headers)} + + _LOGGER.debug(f"Webhook received {id[:15]}..: {data}") + connection.send_message(event_message(msg[CONF_ID], {"data": data})) + + def remove_webhook() -> None: + """Remove webhook command.""" + try: + hass.components.webhook.async_unregister(webhook_id) + + except ValueError: + pass + + _LOGGER.info(f"Webhook removed: {webhook_id[:15]}..") + connection.send_message(result_message(msg[CONF_ID], {"success": True})) + + try: + hass.components.webhook.async_register( + DOMAIN, msg[CONF_NAME], webhook_id, handle_webhook + ) + except ValueError: + connection.send_message(result_message(msg[CONF_ID], {"success": False})) + return + + _LOGGER.info(f"Webhook created: {webhook_id[:15]}..") + connection.subscriptions[msg[CONF_ID]] = remove_webhook + connection.send_message(result_message(msg[CONF_ID], {"success": True})) diff --git a/custom_components/variable/__init__.py b/custom_components/variable/__init__.py index dd0c48d..21b45b4 100644 --- a/custom_components/variable/__init__.py +++ b/custom_components/variable/__init__.py @@ -1,66 +1,89 @@ +"""variable implementation for Homme Assistant.""" import asyncio import logging import json import voluptuous as vol -from homeassistant.const import (CONF_NAME, ATTR_ICON) +from homeassistant.const import CONF_NAME, ATTR_ICON from homeassistant.helpers import config_validation as cv -from homeassistant.helpers import template from homeassistant.exceptions import TemplateError from homeassistant.loader import bind_hass -from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity_component import EntityComponent from homeassistant.helpers.restore_state import RestoreEntity _LOGGER = logging.getLogger(__name__) -DOMAIN = 'variable' -ENTITY_ID_FORMAT = DOMAIN + '.{}' +DOMAIN = "variable" +ENTITY_ID_FORMAT = DOMAIN + ".{}" CONF_ATTRIBUTES = "attributes" CONF_VALUE = "value" CONF_RESTORE = "restore" ATTR_VARIABLE = "variable" -ATTR_VALUE = 'value' -ATTR_VALUE_TEMPLATE = 'value_template' +ATTR_VALUE = "value" +ATTR_VALUE_TEMPLATE = "value_template" ATTR_ATTRIBUTES = "attributes" ATTR_ATTRIBUTES_TEMPLATE = "attributes_template" ATTR_REPLACE_ATTRIBUTES = "replace_attributes" SERVICE_SET_VARIABLE = "set_variable" -SERVICE_SET_VARIABLE_SCHEMA = vol.Schema({ - vol.Required(ATTR_VARIABLE): cv.string, - vol.Optional(ATTR_VALUE): cv.match_all, - vol.Optional(ATTR_VALUE_TEMPLATE): cv.template, - vol.Optional(ATTR_ATTRIBUTES): dict, - vol.Optional(ATTR_ATTRIBUTES_TEMPLATE): cv.template, - vol.Optional(ATTR_REPLACE_ATTRIBUTES): cv.boolean -}) +SERVICE_SET_VARIABLE_SCHEMA = vol.Schema( + { + vol.Required(ATTR_VARIABLE): cv.string, + vol.Optional(ATTR_VALUE): cv.match_all, + vol.Optional(ATTR_VALUE_TEMPLATE): cv.template, + vol.Optional(ATTR_ATTRIBUTES): dict, + vol.Optional(ATTR_ATTRIBUTES_TEMPLATE): cv.template, + vol.Optional(ATTR_REPLACE_ATTRIBUTES): cv.boolean, + } +) + +CONFIG_SCHEMA = vol.Schema( + { + DOMAIN: vol.Schema( + { + cv.slug: vol.Any( + { + vol.Optional(CONF_NAME): cv.string, + vol.Optional(CONF_VALUE): cv.match_all, + vol.Optional(CONF_ATTRIBUTES): dict, + vol.Optional(CONF_RESTORE): cv.boolean, + }, + None, + ) + } + ) + }, + extra=vol.ALLOW_EXTRA, +) -CONFIG_SCHEMA = vol.Schema({ - DOMAIN: vol.Schema({ - cv.slug: vol.Any({ - vol.Optional(CONF_NAME): cv.string, - vol.Optional(CONF_VALUE): cv.match_all, - vol.Optional(CONF_ATTRIBUTES): dict, - vol.Optional(CONF_RESTORE): cv.boolean, - }, None) - }) -}, extra=vol.ALLOW_EXTRA) @bind_hass -def set_variable(hass, variable, value, value_template, attributes, attributes_template, replace_attributes): +def set_variable( + hass, + variable, + value, + value_template, + attributes, + attributes_template, + replace_attributes, +): """Set input_boolean to True.""" - hass.services.call(DOMAIN, SERVICE_SET_VARIABLE, { - ATTR_VARIABLE: variable, - ATTR_VALUE: value, - ATTR_VALUE_TEMPLATE: value_template, - ATTR_ATTRIBUTES: attributes, - ATTR_ATTRIBUTES_TEMPLATE: attributes_template, - ATTR_REPLACE_ATTRIBUTES: replace_attributes, - }) + hass.services.call( + DOMAIN, + SERVICE_SET_VARIABLE, + { + ATTR_VARIABLE: variable, + ATTR_VALUE: value, + ATTR_VALUE_TEMPLATE: value_template, + ATTR_ATTRIBUTES: attributes, + ATTR_ATTRIBUTES_TEMPLATE: attributes_template, + ATTR_REPLACE_ATTRIBUTES: replace_attributes, + }, + ) + async def async_setup(hass, config): """Set up variables.""" @@ -77,37 +100,45 @@ async def async_setup(hass, config): attributes = variable_config.get(CONF_ATTRIBUTES) restore = variable_config.get(CONF_RESTORE, False) - entities.append(Variable(variable_id, name, value, attributes, restore)) + entities.append( + Variable(variable_id, name, value, attributes, restore) + ) @asyncio.coroutine def async_set_variable_service(call): """Handle calls to the set_variable service.""" - entity_id = ENTITY_ID_FORMAT.format(call.data.get(ATTR_VARIABLE)) entity = component.get_entity(entity_id) if entity: - target_variables = [ entity ] - tasks = [variable.async_set_variable( - call.data.get(ATTR_VALUE), - call.data.get(ATTR_VALUE_TEMPLATE), - call.data.get(ATTR_ATTRIBUTES), - call.data.get(ATTR_ATTRIBUTES_TEMPLATE), - call.data.get(ATTR_REPLACE_ATTRIBUTES, False)) - for variable in target_variables] + target_variables = [entity] + tasks = [ + variable.async_set_variable( + call.data.get(ATTR_VALUE), + call.data.get(ATTR_VALUE_TEMPLATE), + call.data.get(ATTR_ATTRIBUTES), + call.data.get(ATTR_ATTRIBUTES_TEMPLATE), + call.data.get(ATTR_REPLACE_ATTRIBUTES, False), + ) + for variable in target_variables + ] if tasks: yield from asyncio.wait(tasks, loop=hass.loop) else: - _LOGGER.warning('Failed to set unknown variable: %s', entity_id) + _LOGGER.warning("Failed to set unknown variable: %s", entity_id) hass.services.async_register( - DOMAIN, SERVICE_SET_VARIABLE, async_set_variable_service, - schema=SERVICE_SET_VARIABLE_SCHEMA) + DOMAIN, + SERVICE_SET_VARIABLE, + async_set_variable_service, + schema=SERVICE_SET_VARIABLE_SCHEMA, + ) await component.async_add_entities(entities) return True + class Variable(RestoreEntity): """Representation of a variable.""" @@ -122,10 +153,14 @@ class Variable(RestoreEntity): async def async_added_to_hass(self): """Run when entity about to be added.""" await super().async_added_to_hass() - if self._restore == True: + if self._restore is True: + # If variable state have been saved. state = await self.async_get_last_state() if state: + # restore state self._value = state.state + # restore value + self._attributes = state.attributes @property def should_poll(self): @@ -142,8 +177,7 @@ class Variable(RestoreEntity): """Return the icon to be used for this entity.""" if self._attributes is not None: return self._attributes.get(ATTR_ICON) - else: - return None + return None @property def state(self): @@ -156,9 +190,15 @@ class Variable(RestoreEntity): return self._attributes @asyncio.coroutine - def async_set_variable(self, value, value_template, attributes, attributes_template, replace_attributes): + def async_set_variable( + self, + value, + value_template, + attributes, + attributes_template, + replace_attributes, + ): """Update variable.""" - current_state = self.hass.states.get(self.entity_id) updated_attributes = None updated_value = None @@ -176,7 +216,11 @@ class Variable(RestoreEntity): attributes_template.hass = self.hass try: - attributes = json.loads(attributes_template.async_render({ 'variable': current_state })) + attributes = json.loads( + attributes_template.async_render( + {"variable": current_state} + ) + ) if isinstance(attributes, dict): if updated_attributes is not None: @@ -185,8 +229,11 @@ class Variable(RestoreEntity): updated_attributes = attributes except TemplateError as ex: - _LOGGER.error('Could not render attribute_template %s: %s', - self.entity_id, ex) + _LOGGER.error( + "Could not render attribute_template %s: %s", + self.entity_id, + ex, + ) if value is not None: updated_value = value @@ -194,14 +241,19 @@ class Variable(RestoreEntity): elif value_template is not None: try: value_template.hass = self.hass - updated_value = value_template.async_render({ 'variable': current_state }) + updated_value = value_template.async_render( + {"variable": current_state} + ) except TemplateError as ex: - _LOGGER.error('Could not render value_template %s: %s', - self.entity_id, ex) + _LOGGER.error( + "Could not render value_template %s: %s", + self.entity_id, + ex, + ) - self._attributes = updated_attributes; + self._attributes = updated_attributes if updated_value is not None: - self._value = updated_value; + self._value = updated_value - yield from self.async_update_ha_state() \ No newline at end of file + yield from self.async_update_ha_state() diff --git a/custom_components/variable/__pycache__/__init__.cpython-37.pyc b/custom_components/variable/__pycache__/__init__.cpython-37.pyc index 9970ea6..cb9dcdd 100644 Binary files a/custom_components/variable/__pycache__/__init__.cpython-37.pyc and b/custom_components/variable/__pycache__/__init__.cpython-37.pyc differ diff --git a/custom_components/variable/__pycache__/__init__.cpython-38.pyc b/custom_components/variable/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..d0bf3a5 Binary files /dev/null and b/custom_components/variable/__pycache__/__init__.cpython-38.pyc differ diff --git a/custom_components/wundergroundpws/__init__.py b/custom_components/wundergroundpws/__init__.py new file mode 100644 index 0000000..ebeaa10 --- /dev/null +++ b/custom_components/wundergroundpws/__init__.py @@ -0,0 +1 @@ +"""The wundergroundpws component.""" diff --git a/custom_components/wundergroundpws/__pycache__/__init__.cpython-38.pyc b/custom_components/wundergroundpws/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..d7f5536 Binary files /dev/null and b/custom_components/wundergroundpws/__pycache__/__init__.cpython-38.pyc differ diff --git a/custom_components/wundergroundpws/__pycache__/sensor.cpython-38.pyc b/custom_components/wundergroundpws/__pycache__/sensor.cpython-38.pyc new file mode 100644 index 0000000..b40e31f Binary files /dev/null and b/custom_components/wundergroundpws/__pycache__/sensor.cpython-38.pyc differ diff --git a/custom_components/wundergroundpws/manifest.json b/custom_components/wundergroundpws/manifest.json new file mode 100644 index 0000000..23be1a9 --- /dev/null +++ b/custom_components/wundergroundpws/manifest.json @@ -0,0 +1,8 @@ +{ + "domain": "wundergroundpws", + "name": "Wundergroundpws", + "documentation": "https://github.com/cytech/Home-Assistant-wundergroundpws", + "requirements": [], + "dependencies": [], + "codeowners": [] +} diff --git a/custom_components/wundergroundpws/sensor.py b/custom_components/wundergroundpws/sensor.py new file mode 100644 index 0000000..1becfd4 --- /dev/null +++ b/custom_components/wundergroundpws/sensor.py @@ -0,0 +1,575 @@ +""" +Support for WUndergroundPWS weather service. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/sensor.wundergroundpws/ +""" +import asyncio +from datetime import timedelta +import logging +import re + +import aiohttp +import async_timeout +import voluptuous as vol + +from homeassistant.helpers.typing import HomeAssistantType, ConfigType +from homeassistant.components import sensor +from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.const import ( + CONF_MONITORED_CONDITIONS, CONF_API_KEY, CONF_LATITUDE, CONF_LONGITUDE, + TEMP_FAHRENHEIT, TEMP_CELSIUS, LENGTH_INCHES, + LENGTH_FEET, ATTR_ATTRIBUTION) +from homeassistant.exceptions import PlatformNotReady +from homeassistant.helpers.entity import Entity +from homeassistant.helpers.aiohttp_client import async_get_clientsession +from homeassistant.util import Throttle +import homeassistant.helpers.config_validation as cv +import homeassistant.config as config + +_RESOURCECURRENT = 'https://api.weather.com/v2/pws/observations/current?stationId={}&format=json&units={}&apiKey={}' +_RESOURCEFORECAST = 'https://api.weather.com/v3/wx/forecast/daily/5day?geocode={},{}&units={}&{}&format=json&apiKey={}' +_LOGGER = logging.getLogger(__name__) + +CONF_ATTRIBUTION = "Data provided by the WUnderground weather service" +CONF_PWS_ID = 'pws_id' +CONF_LANG = 'lang' +LENGTH_MILLIMETERS = 'mm' +LENGTH_METERS = 'm' + +DEFAULT_LANG = 'en-US' + +MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=5) + +conf_file = config.get_default_config_dir() + '/configuration.yaml' +load_config = config.load_yaml_config_file('/config/configuration.yaml') + +try: + UNIT_SYSTEM = load_config['homeassistant']['unit_system'] +except KeyError as err: + UNIT_SYSTEM = "metric" + +if UNIT_SYSTEM == 'imperial': + TEMPUNIT = TEMP_FAHRENHEIT + LENGTHUNIT = LENGTH_INCHES + ALTITUDEUNIT = LENGTH_FEET + SPEEDUNIT = 'mph' + PRESSUREUNIT = 'inHg' +else: + TEMPUNIT = TEMP_CELSIUS + LENGTHUNIT = LENGTH_MILLIMETERS + ALTITUDEUNIT = LENGTH_METERS + SPEEDUNIT = 'kph' + PRESSUREUNIT = 'mBar' + + +# Helper classes for declaring sensor configurations + +class WUSensorConfig: + """WU Sensor Configuration. + + defines basic HA properties of the weather sensor and + stores callbacks that can parse sensor values out of + the json data received by WU API. + """ + + def __init__(self, friendly_name, feature, value, + unit_of_measurement=None, entity_picture=None, + icon="mdi:gauge", device_state_attributes=None, + device_class=None): + """Constructor. + + Args: + friendly_name (string|func): Friendly name + feature (string): WU feature. See: + https://docs.google.com/document/d/1eKCnKXI9xnoMGRRzOL1xPCBihNV2rOet08qpE_gArAY/edit + value (function(WUndergroundData)): callback that + extracts desired value from WUndergroundData object + unit_of_measurement (string): unit of measurement + entity_picture (string): value or callback returning + URL of entity picture + icon (string): icon name + device_state_attributes (dict): dictionary of attributes, + or callable that returns it + """ + self.friendly_name = friendly_name + self.unit_of_measurement = unit_of_measurement + self.feature = feature + self.value = value + self.entity_picture = entity_picture + self.icon = icon + self.device_state_attributes = device_state_attributes or {} + self.device_class = device_class + + +class WUCurrentConditionsSensorConfig(WUSensorConfig): + """Helper for defining sensor configurations for current conditions.""" + + def __init__(self, friendly_name, unit_system, field, icon="mdi:gauge", + unit_of_measurement=None, device_class=None): + """Constructor. + + Args: + friendly_name (string|func): Friendly name of sensor + field (string): Field name in the "observations[0][unit_system]" + dictionary. + icon (string): icon name , if None sensor + will use current weather symbol + unit_of_measurement (string): unit of measurement + """ + super().__init__( + friendly_name, + "conditions", + value=lambda wu: wu.data['observations'][0][unit_system][field], + icon=icon, + unit_of_measurement=unit_of_measurement, + device_state_attributes={ + 'date': lambda wu: wu.data['observations'][0][ + 'obsTimeLocal'] + }, + device_class=device_class + ) + + +class WUDailyTextForecastSensorConfig(WUSensorConfig): + """Helper for defining sensor configurations for daily text forecasts.""" + + def __init__(self, period, field=None, unit_of_measurement=None): + """Constructor. + + Args: + period (int): forecast period number + field (string): field name to use as value + unit_of_measurement(string): unit of measurement + """ + super().__init__( + friendly_name=lambda wu: wu.data['daypart'][0]['daypartName'][period], + feature='forecast', + value=lambda wu: wu.data['daypart'][0]['narrative'][period], + entity_picture=lambda wu: '/local/wupws_icons/' + str(wu.data['daypart'][0]['iconCode'][period]) + '.png', + unit_of_measurement=unit_of_measurement, + device_state_attributes={ + 'date': lambda wu: wu.data['observations'][0]['obsTimeLocal'] + } + ) + + +class WUDailySimpleForecastSensorConfig(WUSensorConfig): + """Helper for defining sensor configurations for daily simpleforecasts.""" + + def __init__(self, friendly_name, period, field, + ha_unit=None, icon=None, device_class=None): + """Constructor. + + Args: + period (int): forecast period number + field (string): field name to use as value + ha_unit (string): corresponding unit in home assistant + title (string): friendly_name of the sensor + """ + super().__init__( + friendly_name=friendly_name, + feature='forecast', + value=(lambda wu: wu.data['daypart'][0][field][period]), + unit_of_measurement=ha_unit, + entity_picture=lambda wu: wu.data['daypart'][0]['iconCode'][period] if not icon else None, + icon=icon, + device_state_attributes={ + 'date': lambda wu: wu.data['observations'][0]['obsTimeLocal'] + }, + device_class=device_class + ) + + +# Declaration of supported WU sensors +# (see above helper classes for argument explanation) + +SENSOR_TYPES = { + # current + 'neighborhood': WUSensorConfig( + 'Neighborhood', + 'observations', + value=lambda wu: wu.data['observations'][0][ + 'neighborhood'], + icon="mdi:map-marker"), + 'obsTimeLocal': WUSensorConfig( + 'Local Observation Time', + 'observations', + value=lambda wu: wu.data['observations'][0][ + 'obsTimeLocal'], + icon="mdi:clock"), + 'humidity': WUSensorConfig( + 'Relative Humidity', + 'observations', + value=lambda wu: int(wu.data['observations'][0][ + 'humidity']), + unit_of_measurement='%', + icon="mdi:water-percent", + device_class="humidity"), + 'stationID': WUSensorConfig( + 'Station ID', + 'observations', + value=lambda wu: wu.data['observations'][0][ + 'stationID'], + icon="mdi:home"), + 'solarRadiation': WUSensorConfig( + 'Solar Radiation', + 'observations', + value=lambda wu: str(wu.data['observations'][0][ + 'solarRadiation']), + unit_of_measurement='w/m2', + icon="mdi:weather-sunny"), + 'uv': WUSensorConfig( + 'UV', + 'observations', + value=lambda wu: str(wu.data['observations'][0][ + 'uv']), + unit_of_measurement='', + icon="mdi:sunglasses", ), + 'winddir': WUSensorConfig( + 'Wind Direction', + 'observations', + value=lambda wu: int(wu.data['observations'][0][ + 'winddir']), + unit_of_measurement='\u00b0', + icon="mdi:weather-windy"), + 'today_summary': WUSensorConfig( + 'Today Summary', + 'observations', + value=lambda wu: str(wu.data['narrative'][0]), + unit_of_measurement='\u00b0', + icon="mdi:gauge"), + 'elev': WUCurrentConditionsSensorConfig( + 'Elevation', UNIT_SYSTEM, 'elev', 'mdi:elevation-rise', ALTITUDEUNIT), + 'dewpt': WUCurrentConditionsSensorConfig( + 'Dewpoint', UNIT_SYSTEM, 'dewpt', 'mdi:water', TEMPUNIT), + 'heatIndex': WUCurrentConditionsSensorConfig( + 'Heat index', UNIT_SYSTEM, 'heatIndex', "mdi:thermometer", TEMPUNIT), + 'windChill': WUCurrentConditionsSensorConfig( + 'Wind chill', UNIT_SYSTEM, 'windChill', "mdi:thermometer", TEMPUNIT), + 'precipRate': WUCurrentConditionsSensorConfig( + 'Precipitation Rate', UNIT_SYSTEM, 'precipRate', "mdi:umbrella", LENGTHUNIT), + 'precipTotal': WUCurrentConditionsSensorConfig( + 'Precipitation Today', UNIT_SYSTEM, 'precipTotal', "mdi:umbrella", LENGTHUNIT), + 'pressure': WUCurrentConditionsSensorConfig( + 'Pressure', UNIT_SYSTEM, 'pressure', "mdi:gauge", PRESSUREUNIT, + device_class="pressure"), + 'temp': WUCurrentConditionsSensorConfig( + 'Temperature', UNIT_SYSTEM, 'temp', "mdi:thermometer", TEMPUNIT, + device_class="temperature"), + 'windGust': WUCurrentConditionsSensorConfig( + 'Wind Gust', UNIT_SYSTEM, 'windGust', "mdi:weather-windy", SPEEDUNIT), + 'windSpeed': WUCurrentConditionsSensorConfig( + 'Wind Speed', UNIT_SYSTEM, 'windSpeed', "mdi:weather-windy", SPEEDUNIT), + # forecast + 'weather_1d': WUDailyTextForecastSensorConfig(0), + 'weather_1n': WUDailyTextForecastSensorConfig(1), + 'weather_2d': WUDailyTextForecastSensorConfig(2), + 'weather_2n': WUDailyTextForecastSensorConfig(3), + 'weather_3d': WUDailyTextForecastSensorConfig(4), + 'weather_3n': WUDailyTextForecastSensorConfig(5), + 'weather_4d': WUDailyTextForecastSensorConfig(6), + 'weather_4n': WUDailyTextForecastSensorConfig(7), + 'weather_5d': WUDailyTextForecastSensorConfig(8), + 'weather_5n': WUDailyTextForecastSensorConfig(9), + 'temp_high_1d': WUDailySimpleForecastSensorConfig( + "High Temperature Today", 0, "temperature", TEMPUNIT, + "mdi:thermometer", device_class="temperature"), + 'temp_high_2d': WUDailySimpleForecastSensorConfig( + "High Temperature Tomorrow", 2, "temperature", TEMPUNIT, + "mdi:thermometer", device_class="temperature"), + 'temp_high_3d': WUDailySimpleForecastSensorConfig( + "High Temperature in 3 Days", 4, "temperature", TEMPUNIT, + "mdi:thermometer", device_class="temperature"), + 'temp_high_4d': WUDailySimpleForecastSensorConfig( + "High Temperature in 4 Days", 6, "temperature", TEMPUNIT, + "mdi:thermometer", device_class="temperature"), + 'temp_high_5d': WUDailySimpleForecastSensorConfig( + "High Temperature in 5 Days", 8, "temperature", TEMPUNIT, + "mdi:thermometer", device_class="temperature"), + 'temp_low_1d': WUDailySimpleForecastSensorConfig( + "Low Temperature Today", 1, "temperature", TEMPUNIT, + "mdi:thermometer", device_class="temperature"), + 'temp_low_2d': WUDailySimpleForecastSensorConfig( + "Low Temperature Tomorrow", 3, "temperature", TEMPUNIT, + "mdi:thermometer", device_class="temperature"), + 'temp_low_3d': WUDailySimpleForecastSensorConfig( + "Low Temperature in 3 Days", 5, "temperature", TEMPUNIT, + "mdi:thermometer", device_class="temperature"), + 'temp_low_4d': WUDailySimpleForecastSensorConfig( + "Low Temperature in 4 Days", 7, "temperature", TEMPUNIT, + "mdi:thermometer", device_class="temperature"), + 'temp_low_5d': WUDailySimpleForecastSensorConfig( + "Low Temperature in 5 Days", 9, "temperature", TEMPUNIT, + "mdi:thermometer", device_class="temperature"), + 'wind_1d': WUDailySimpleForecastSensorConfig( + "Avg. Wind Today", 0, "windSpeed", SPEEDUNIT, + "mdi:weather-windy"), + 'wind_2d': WUDailySimpleForecastSensorConfig( + "Avg. Wind Tomorrow", 2, "windSpeed", SPEEDUNIT, + "mdi:weather-windy"), + 'wind_3d': WUDailySimpleForecastSensorConfig( + "Avg. Wind in 3 Days", 4, "windSpeed", SPEEDUNIT, + "mdi:weather-windy"), + 'wind_4d': WUDailySimpleForecastSensorConfig( + "Avg. Wind in 4 Days", 6, "windSpeed", SPEEDUNIT, + "mdi:weather-windy"), + 'wind_5d': WUDailySimpleForecastSensorConfig( + "Avg. Wind in 5 Days", 8, "windSpeed", SPEEDUNIT, + "mdi:weather-windy"), + 'precip_1d': WUDailySimpleForecastSensorConfig( + "Precipitation Intensity Today", 0, 'qpf', LENGTHUNIT, + "mdi:umbrella"), + 'precip_2d': WUDailySimpleForecastSensorConfig( + "Precipitation Intensity Tomorrow", 2, 'qpf', LENGTHUNIT, + "mdi:umbrella"), + 'precip_3d': WUDailySimpleForecastSensorConfig( + "Precipitation Intensity in 3 Days", 4, 'qpf', LENGTHUNIT, + "mdi:umbrella"), + 'precip_4d': WUDailySimpleForecastSensorConfig( + "Precipitation Intensity in 4 Days", 6, 'qpf', LENGTHUNIT, + "mdi:umbrella"), + 'precip_5d': WUDailySimpleForecastSensorConfig( + "Precipitation Intensity in 5 Days", 8, 'qpf', LENGTHUNIT, + "mdi:umbrella"), + 'precip_chance_1d': WUDailySimpleForecastSensorConfig( + "Precipitation Probability Today", 0, "precipChance", "%", + "mdi:umbrella"), + 'precip_chance_2d': WUDailySimpleForecastSensorConfig( + "Precipitation Probability Tomorrow", 2, "precipChance", "%", + "mdi:umbrella"), + 'precip_chance_3d': WUDailySimpleForecastSensorConfig( + "Precipitation Probability in 3 Days", 4, "precipChance", "%", + "mdi:umbrella"), + 'precip_chance_4d': WUDailySimpleForecastSensorConfig( + "Precipitation Probability in 4 Days", 6, "precipChance", "%", + "mdi:umbrella"), + 'precip_chance_5d': WUDailySimpleForecastSensorConfig( + "Precipitation Probability in 5 Days", 8, "precipChance", "%", + "mdi:umbrella"), +} + +# Language Supported Codes +LANG_CODES = [ + 'ar-AE', 'az-AZ', 'bg-BG', 'bn-BD', 'bn-IN', 'bs-BA', 'ca-ES', 'cs-CZ', 'da-DK', 'de-DE', 'el-GR', 'en-GB', 'en-IN', + 'en-US', 'es-AR', 'es-ES', 'es-LA', 'es-MX', 'es-UN', 'es-US', 'et-EE', 'fa-IR', 'fi-FI', 'fr-CA', 'fr-FR', 'gu-IN', + 'he-IL', 'hi-IN', 'hr-HR', 'hu-HU', 'in-ID', 'is-IS', 'it-IT', 'iw-IL', 'ja-JP', 'jv-ID', 'ka-GE', 'kk-KZ', 'kn-IN', + 'ko-KR', 'lt-LT', 'lv-LV', 'mk-MK', 'mn-MN', 'ms-MY', 'nl-NL', 'no-NO', 'pl-PL', 'pt-BR', 'pt-PT', 'ro-RO', 'ru-RU', + 'si-LK', 'sk-SK', 'sl-SI', 'sq-AL', 'sr-BA', 'sr-ME', 'sr-RS', 'sv-SE', 'sw-KE', 'ta-IN', 'ta-LK', 'te-IN', 'tg-TJ', + 'th-TH', 'tk-TM', 'tl-PH', 'tr-TR', 'uk-UA', 'ur-PK', 'uz-UZ', 'vi-VN', 'zh-CN', 'zh-HK', 'zh-TW' +] + +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Required(CONF_API_KEY): cv.string, + vol.Required(CONF_PWS_ID): cv.string, + vol.Optional(CONF_LANG, default=DEFAULT_LANG): vol.All(vol.In(LANG_CODES)), + vol.Inclusive(CONF_LATITUDE, 'coordinates', + 'Latitude and longitude must exist together'): cv.latitude, + vol.Inclusive(CONF_LONGITUDE, 'coordinates', + 'Latitude and longitude must exist together'): cv.longitude, + vol.Required(CONF_MONITORED_CONDITIONS): + vol.All(cv.ensure_list, vol.Length(min=1), [vol.In(SENSOR_TYPES)]) +}) + + +async def async_setup_platform(hass: HomeAssistantType, config: ConfigType, + async_add_entities, discovery_info=None): + """Set up the WUnderground sensor.""" + latitude = config.get(CONF_LATITUDE, hass.config.latitude) + longitude = config.get(CONF_LONGITUDE, hass.config.longitude) + pws_id = config.get(CONF_PWS_ID) + + if hass.config.units.is_metric: + unit_system_api = 'm' + else: + unit_system_api = 'e' + + rest = WUndergroundData( + hass, config.get(CONF_API_KEY), pws_id, unit_system_api, + config.get(CONF_LANG), latitude, longitude) + + if pws_id is None: + unique_id_base = "@{:06f},{:06f}".format(longitude, latitude) + else: + # Manually specified weather station, use that for unique_id + unique_id_base = pws_id + sensors = [] + for variable in config[CONF_MONITORED_CONDITIONS]: + sensors.append(WUndergroundSensor(hass, rest, variable, + unique_id_base)) + + await rest.async_update() + if not rest.data: + raise PlatformNotReady + + async_add_entities(sensors, True) + + +class WUndergroundSensor(Entity): + """Implementing the WUnderground sensor.""" + + def __init__(self, hass: HomeAssistantType, rest, condition, + unique_id_base: str): + """Initialize the sensor.""" + self.rest = rest + self._condition = condition + self._state = None + self._attributes = { + ATTR_ATTRIBUTION: CONF_ATTRIBUTION, + } + self._icon = None + self._entity_picture = None + self._unit_of_measurement = self._cfg_expand("unit_of_measurement") + self.rest.request_feature(SENSOR_TYPES[condition].feature) + # This is only the suggested entity id, it might get changed by + # the entity registry later. + self.entity_id = sensor.ENTITY_ID_FORMAT.format('wupws_' + condition) + self._unique_id = "{},{}".format(unique_id_base, condition) + self._device_class = self._cfg_expand("device_class") + + def _cfg_expand(self, what, default=None): + """Parse and return sensor data.""" + cfg = SENSOR_TYPES[self._condition] + val = getattr(cfg, what) + if not callable(val): + return val + try: + val = val(self.rest) + except (KeyError, IndexError, TypeError, ValueError) as err: + _LOGGER.warning("Failed to expand cfg from WU API." + " Condition: %s Attr: %s Error: %s", + self._condition, what, repr(err)) + val = default + + return val + + def _update_attrs(self): + """Parse and update device state attributes.""" + attrs = self._cfg_expand("device_state_attributes", {}) + + for (attr, callback) in attrs.items(): + if callable(callback): + try: + self._attributes[attr] = callback(self.rest) + except (KeyError, IndexError, TypeError, ValueError) as err: + _LOGGER.warning("Failed to update attrs from WU API." + " Condition: %s Attr: %s Error: %s", + self._condition, attr, repr(err)) + else: + self._attributes[attr] = callback + + @property + def name(self): + """Return the name of the sensor.""" + return self._cfg_expand("friendly_name") + + @property + def state(self): + """Return the state of the sensor.""" + return self._state + + @property + def device_state_attributes(self): + """Return the state attributes.""" + return self._attributes + + @property + def icon(self): + """Return icon.""" + return self._icon + + @property + def entity_picture(self): + """Return the entity picture.""" + return self._entity_picture + + @property + def unit_of_measurement(self): + """Return the units of measurement.""" + return self._unit_of_measurement + + @property + def device_class(self): + """Return the units of measurement.""" + return self._device_class + + async def async_update(self): + """Update current conditions.""" + await self.rest.async_update() + + if not self.rest.data: + # no data, return + return + + self._state = self._cfg_expand("value") + self._update_attrs() + self._icon = self._cfg_expand("icon", super().icon) + url = self._cfg_expand("entity_picture") + if isinstance(url, str): + self._entity_picture = re.sub(r'^http://', 'https://', + url, flags=re.IGNORECASE) + + @property + def unique_id(self) -> str: + """Return a unique ID.""" + return self._unique_id + + +class WUndergroundData: + """Get data from WUnderground.""" + + def __init__(self, hass, api_key, pws_id, unit_system_api, lang, latitude, longitude): + """Initialize the data object.""" + self._hass = hass + self._api_key = api_key + self._pws_id = pws_id + self._unit_system_api = unit_system_api + self._lang = 'language={}'.format(lang) + self._latitude = latitude + self._longitude = longitude + self._features = set() + self.data = None + self._session = async_get_clientsession(self._hass) + + def request_feature(self, feature): + """Register feature to be fetched from WU API.""" + self._features.add(feature) + + def _build_url(self, baseurl): + if baseurl is _RESOURCECURRENT: + url = baseurl.format(self._pws_id, self._unit_system_api, self._api_key) + else: + url = baseurl.format(self._latitude, self._longitude, self._unit_system_api, self._lang, self._api_key) + + return url + + @Throttle(MIN_TIME_BETWEEN_UPDATES) + async def async_update(self): + """Get the latest data from WUnderground.""" + headers = {'Accept-Encoding': 'gzip'} + try: + with async_timeout.timeout(10, loop=self._hass.loop): + response = await self._session.get(self._build_url(_RESOURCECURRENT), headers=headers) + result_current = await response.json() + + # need to check specific new api errors + # if "error" in result['response']: + # raise ValueError(result['response']["error"]["description"]) + # _LOGGER.debug('result_current' + str(result_current)) + + if result_current is None: + raise ValueError('NO CURRENT RESULT') + with async_timeout.timeout(10, loop=self._hass.loop): + response = await self._session.get(self._build_url(_RESOURCEFORECAST), headers=headers) + result_forecast = await response.json() + + if result_forecast is None: + raise ValueError('NO FORECAST RESULT') + + result = {**result_current, **result_forecast} + + self.data = result + except ValueError as err: + _LOGGER.error("Check WUnderground API %s", err.args) + except (asyncio.TimeoutError, aiohttp.ClientError) as err: + _LOGGER.error("Error fetching WUnderground data: %s", repr(err)) diff --git a/custom_components/wundergroundpws/sensor.wundergroundpws.markdown b/custom_components/wundergroundpws/sensor.wundergroundpws.markdown new file mode 100644 index 0000000..9adcf36 --- /dev/null +++ b/custom_components/wundergroundpws/sensor.wundergroundpws.markdown @@ -0,0 +1,198 @@ +--- +layout: page +title: "Weather UndergroundPWS (WUndergroundPWS)" +description: "Instructions on how to integrate Weather Underground (WUnderground) Personal Weather Station within Home Assistant." +date: 2019-03-19 +sidebar: true +comments: false +sharing: true +footer: true +logo: wunderground.png +ha_category: Weather +ha_release: 0.89 +ha_iot_class: "Cloud Polling" +--- + +The `wundergroundpws` platform uses [Weather Underground](http://www.wunderground.com) as a source for current weather information. + +

+Free API keys are only issued to registered and active Weather Underground personal weather station users. + +To use this sensor, you need to enter your personal weather station API key and Station ID in configuration.yaml. + +To get a free API key: +1) You must have a personal weather station registered and uploading data to Weather Underground + + a) Join weather Underground + + b) Sign In + + c) My Profile -> My Weather Stations + + d) Add a New PWS +2) get API key at https://www.wunderground.com/member/api-keys + +Please consider this when using the following information. + +Current conditions are generated from the wundergroundpws configured pws_id. + +Forecast is generated from the HA configured latitude/longitude. +

+ +{% linkable_title Configuration %} + +To add Wunderground to your installation, add the following to your `configuration.yaml` file: + +```yaml +# Example configuration.yaml entry +sensor: + - platform: wundergroundpws + api_key: YOUR_API_KEY + pws_id: YOUR_STATION_ID + monitored_conditions: + - temp + - dewpt + - heatIndex +``` + +{% configuration %} +api_key: + description: The API key for Weather Underground. See above for details. + required: true + type: string +pws_id: + description: "You must enter a Personal Weather Station ID. The station id will be used to display current weather conditions." + required: true + type: string +lang: + description: Specify the language that the API returns. The current list of all Wunderground language codes is available [here](https://docs.google.com/document/d/13HTLgJDpsb39deFzk_YCQ5GoGoZCO_cRYzIxbwvgJLI/edit#). If not specified, it defaults to English (en-US). + required: false + type: string + default: en-US +latitude: + description: Latitude coordinate for weather forecast (required if **longitude** is specified). + required: false + type: string + default: Coordinates defined in your `configuration.yaml` +longitude: + description: Longitude coordinate for weather forecast (required if **latitude** is specified). + required: false + type: string + default: Coordinates defined in your `configuration.yaml` +monitored_conditions: + description: Conditions to display in the frontend. The following conditions can be monitored. + required: true + type: list + default: symbol + keys: + (generated from PWS) + stationID: + description: Your personal weather station (PWS) ID + solarRadiation: + description: Current levels of solar radiation + neighborhood: + description: WU PWS reference name + obsTimeLocal: + description: Text summary of local observation time + UV: + description: Current levels of UV radiation. See [here](https://www.wunderground.com/resources/health/uvindex.asp) for explanation. + winddir: + description: Wind degrees + humidity: + description: Relative humidity + dewpt: + description: Temperature below which water droplets begin to condense and dew can form + heatIndex: + description: Heat index (combined effects of the temperature and humidity of the air) + windChill: + description: Wind Chill (combined effects of the temperature and wind) + elev: + description: Elevation + precipTotal: + description: Today Total precipitation + precipRate: + description: Rain intensity + pressure: + description: Atmospheric air pressure + temp: + description: Current temperature + windGust: + description: Wind gusts speed + windSpeed: + description: Current wind speed + (generated from lat/lon forecast) + precip_1d: + description: "[[1d]](#1d): Forecasted precipitation intensity" + precip_chance_1d: + description: "[[1d]](#1d): Forecasted precipitation probability in %" + temp_high_1d: + description: "[[1d]](#1d): Forecasted high temperature" + temp_low_1d: + description: "[[1d]](#1d): Forecasted low temperature" + wind_1d: + description: "[[1d]](#1d): Forecasted wind speed" + weather_1d: + description: "[[12h]](#12h): A human-readable weather forecast of Day" + weather_1n: + description: "[[12h]](#12h): A human-readable weather forecast of Night" +{% endconfiguration %} + +All the conditions listed above will be updated every 5 minutes. + +## {% linkable_title Forecasts %} + +### {% linkable_title Daily forecasts %} + +Conditions above marked with [1d] are daily forecasts. To get forecast for different day, replace the number +in `_1d_` part of the sensor name. Valid values are from `1` to `5`. + +Conditions above marked with [1n] are nightly forecasts. To get forecast for different night, replace the number +in `_1n_` part of the sensor name. Valid values are from `1` to `5`. + +## {% linkable_title Additional examples %} + +### {% linkable_title Daily forecast %} + +```yaml +sensor: + - platform: wunderground + api_key: YOUR_API_KEY + pws_id: YOUR_STATION_ID + monitored_conditions: + - weather_1d + - weather_1n + - weather_2d + - weather_2n + - weather_3d + - weather_3n + - weather_4d + - weather_4n + +group: + daily_forecast: + name: Daily Forecast + entities: + - sensor.pws_weather_1d + - sensor.pws_weather_1n + - sensor.pws_weather_2d + - sensor.pws_weather_2n + - sensor.pws_weather_3d + - sensor.pws_weather_3n + - sensor.pws_weather_4d + - sensor.pws_weather_4n +``` + + +### {% linkable_title Weather overview %} + +

+Note: While the platform is called “wundergroundpws” the sensors will show up in Home Assistant as “WUPWS” (eg: sensor.wupws_weather_1d). +

+ +Note that the Weather Underground sensor is added to the entity_registry, so second and subsequent Personal Weather Station ID (pws_id) will have their monitored conditions suffixed with an index number e.g. + +```yaml +- sensor.wupws_weather_1d_metric_2 +``` + +Additional details about the API are available [here](https://docs.google.com/document/d/1eKCnKXI9xnoMGRRzOL1xPCBihNV2rOet08qpE_gArAY/edit). \ No newline at end of file diff --git a/esphome/inside_temp_humd.yaml b/esphome/inside_temp_humd.yaml new file mode 100644 index 0000000..1d0e867 --- /dev/null +++ b/esphome/inside_temp_humd.yaml @@ -0,0 +1,33 @@ +esphome: + name: inside_temp_humd + platform: ESP8266 + board: d1_mini + +wifi: + ssid: "zoesplace" + password: "PalDgk19591960" + + # Enable fallback hotspot (captive portal) in case wifi connection fails + ap: + ssid: "Inside Temp Humd" + password: "CP8pP6Tlg3cE" + +i2c: + +sensor: + - platform: dht12 + temperature: + name: "Inside Temperature" + humidity: + name: "Inside Humidity" + update_interval: 60s + +captive_portal: + +# Enable logging +logger: + +# Enable Home Assistant API +api: + +ota: diff --git a/esphome/outside-temp-humd.yaml b/esphome/outside_temp_humd.yaml similarity index 75% rename from esphome/outside-temp-humd.yaml rename to esphome/outside_temp_humd.yaml index 52e935e..ee61449 100644 --- a/esphome/outside-temp-humd.yaml +++ b/esphome/outside_temp_humd.yaml @@ -9,8 +9,19 @@ wifi: # Enable fallback hotspot (captive portal) in case wifi connection fails ap: - ssid: "Outside-Temp-Humd" - password: "1kmZsOcaCm44" + ssid: "Outside Temp Humid" + password: "jhazXSLEWNFr" + + +i2c: + +sensor: + - platform: dht12 + temperature: + name: "Outside Temperature" + humidity: + name: "Outside Humidity" + update_interval: 60s captive_portal: @@ -21,17 +32,3 @@ logger: api: ota: - -i2c: - sda: D2 - scl: D1 - scan: True - id: bus_a - -sensor: - - platform: dht12 - temperature: - name: "Outside Temperature" - humidity: - name: "Outside Humidity" - update_interval: 60s diff --git a/hass.code-workspace b/hass.code-workspace new file mode 100644 index 0000000..bccea69 --- /dev/null +++ b/hass.code-workspace @@ -0,0 +1,12 @@ +{ + "folders": [ + { + "path": "." + } + ], + "settings": { + "files.associations": { + "*.yaml": "home-assistant" + } + } +} \ No newline at end of file diff --git a/lovelace/closet.yaml b/lovelace/_manual/closet.yaml similarity index 100% rename from lovelace/closet.yaml rename to lovelace/_manual/closet.yaml diff --git a/lovelace/lights.yaml b/lovelace/_manual/lights.yaml similarity index 100% rename from lovelace/lights.yaml rename to lovelace/_manual/lights.yaml diff --git a/lovelace/_manual/temps.yaml b/lovelace/_manual/temps.yaml new file mode 100644 index 0000000..071b3d1 --- /dev/null +++ b/lovelace/_manual/temps.yaml @@ -0,0 +1,15 @@ +title: Temps - Humidity +icon: mdi:thermometer-lines +cards: + - type: entities + show_header_toggle: false + title: Outside + entities: + - sensor.outside_temperature + - sensor.outside_humidity + - type: entities + show_header_toggle: false + title: Inside + entities: + - sensor.inside_temperature + - sensor.inside_humidity diff --git a/lovelace/_manual/weather.yaml b/lovelace/_manual/weather.yaml new file mode 100644 index 0000000..4823160 --- /dev/null +++ b/lovelace/_manual/weather.yaml @@ -0,0 +1,45 @@ +title: Weather +cards: + - type: weather-forecast + name: NWS + entity: weather.praire_city_weather + - type: 'custom:lovelace-darksky-card' + name: Dark Sky + entity_current_conditions: sensor.dark_sky_icon + entity_temperature: sensor.dark_sky_temperature + entity_forecast_high_temp_1: sensor.dark_sky_daytime_high_temperature_1d + entity_forecast_high_temp_2: sensor.dark_sky_daytime_high_temperature_2d + entity_forecast_high_temp_3: sensor.dark_sky_daytime_high_temperature_3d + entity_forecast_high_temp_4: sensor.dark_sky_daytime_high_temperature_4d + entity_forecast_high_temp_5: sensor.dark_sky_daytime_high_temperature_5d + entity_forecast_icon_1: sensor.dark_sky_icon_1d + entity_forecast_icon_2: sensor.dark_sky_icon_2d + entity_forecast_icon_3: sensor.dark_sky_icon_3d + entity_forecast_icon_4: sensor.dark_sky_icon_4d + entity_forecast_icon_5: sensor.dark_sky_icon_5d + entity_forecast_low_temp_1: sensor.dark_sky_overnight_low_temperature_0d + entity_forecast_low_temp_2: sensor.dark_sky_overnight_low_temperature_1d + entity_forecast_low_temp_3: sensor.dark_sky_overnight_low_temperature_2d + entity_forecast_low_temp_4: sensor.dark_sky_overnight_low_temperature_3d + entity_forecast_low_temp_5: sensor.dark_sky_overnight_low_temperature_4d + entity_summary_1: sensor.dark_sky_summary_1d + entity_summary_2: sensor.dark_sky_summary_2d + entity_summary_3: sensor.dark_sky_summary_3d + entity_summary_4: sensor.dark_sky_summary_4d + entity_summary_5: sensor.dark_sky_summary_5d + entity_sun: sun.sun + entity_visibility: sensor.dark_sky_visibility + entity_daytime_high: sensor.dark_sky_daytime_high_temperature_0d + entity_wind_bearing: sensor.dark_sky_wind_bearing + entity_wind_speed: sensor.dark_sky_wind_speed + entity_humidity: sensor.dark_sky_humidity + entity_pressure: sensor.dark_sky_pressure + entity_apparent_temp: sensor.dark_sky_apparent_temperature + entity_daily_summary: sensor.dark_sky_daily_summary + entity_pop: sensor.dark_sky_precip_probability + entity_pop_intensity: sensor.dark_sky_precip_intensity + entity_pop_1: sensor.dark_sky_precip_probability_1d + entity_pop_2: sensor.dark_sky_precip_probability_2d + entity_pop_3: sensor.dark_sky_precip_probability_3d + entity_pop_4: sensor.dark_sky_precip_probability_4d + entity_pop_5: sensor.dark_sky_precip_probability_5d diff --git a/lovelace/dashboards.yaml b/lovelace/dashboards.yaml new file mode 100644 index 0000000..a614bba --- /dev/null +++ b/lovelace/dashboards.yaml @@ -0,0 +1,12 @@ +lovelace-panel: + mode: yaml + title: panel + icon: mdi:script + show_in_sidebar: true + filename: lovelace/panel.yaml +lovelace-manual: + mode: yaml + title: manual + icon: mdi:script + show_in_sidebar: true + filename: lovelace/manual.yaml diff --git a/lovelace/irrigation.yaml b/lovelace/irrigation.yaml deleted file mode 100644 index ef3a9eb..0000000 --- a/lovelace/irrigation.yaml +++ /dev/null @@ -1,43 +0,0 @@ -# --------------- IRRIGATION ------------- -title: Irrigation -icon: mdi:water -cards: - - type: vertical-stack - cards: - - type: markdown - content: > - # Irrigation - - type: vertical-stack - cards: - - type: vertical-stack - cards: - - type: markdown - content: > - ## Front Yard North - - type: entities - entities: - - entity: sensor.zone_1_last_run - icon: mdi:clock - - entity: sensor.zone_1_repeat - icon: mdi:timer - - entity: sensor.zone_1_next_run - icon: mdi:clock - - type: horizontal-stack - cards: - - type: entity-button - entity: switch.zone_1 - icon: mdi:water - name: on/off - tap_action: - action: toggle - - type: entity-button - entity: switch.zone_1_timer - icon: mdi:timer - name: timer - tap_action: - action: toggle - - type: entity-button - name: set - entity: group.zone_1_set - tap_action: - action: more-info diff --git a/lovelace/manual.yaml b/lovelace/manual.yaml new file mode 100644 index 0000000..90762a2 --- /dev/null +++ b/lovelace/manual.yaml @@ -0,0 +1,23 @@ +title: Manual Dashboard +# custom cards registration +resources: + - url: /haccsfiles/community/lovelace-darksky-card/lovelace-darksky-card.js + type: module +# - url: /hacsfiles/banner-card/banner-card.js +# type: module + - url: /hacsfiles/button-card/button-card.js + type: module +# - url: /hacsfiles/lovelace-auto-entities/auto-entities.js +# type: module +# - url: /hacsfiles/lovelace-fold-entity-row/fold-entity-row.js +# type: module +# - url: /hacsfiles/lovelace-card-mod/card-mod.js +# type: module +# - url: /hacsfiles/canvas-gauge-card/canvas-gauge-card.js +# type: module + +button_card_templates: + default: !include /config/templates/button-card-templates/default.yaml + large_value: !include /config/templates/button-card-templates/large_value.yaml + +views: !include_dir_list _manual diff --git a/lovelace/panel.yaml b/lovelace/panel.yaml new file mode 100644 index 0000000..5519283 --- /dev/null +++ b/lovelace/panel.yaml @@ -0,0 +1,13 @@ +title: Panel Dashboard +panel: true +# theme: slate +# custom cards registration +# resources: +# - url: /hacsfiles/banner-card/banner-card.js +# type: module + +# button_card_templates: +# default: !include /config/templates/button-card-templates/default.yaml +# large_value: !include /config/templates/button-card-templates/large_value.yaml + +# views: !include_dir_list _manual diff --git a/lovelace/temps.yaml b/lovelace/temps.yaml deleted file mode 100644 index 9828a06..0000000 --- a/lovelace/temps.yaml +++ /dev/null @@ -1,24 +0,0 @@ -title: Temps - Humidity -icon: mdi:thermometer-lines -cards: - - type: entities - show_header_toggle: false - title: Outside - entities: - - sensor.outside_temperature - - sensor.outside_humidity - - type: weather-forecast - entity: weather.praire_city_weather - # - type: custom:banner-card - # heading: - # - mdi:door - # - Closet - # background: "#B0C2ED" - # link: /lovelace/2 - # entities: - # - entity: sensor.closet_temperature - # name: Temperature - # - entity: input_number.fan_on_temp - # name: Set Temperature - # - entity: fan.fan_state - # name: Fan State diff --git a/packages/aa_system.yaml b/packages/aa_system.yaml index 6b91c36..0c7ab10 100644 --- a/packages/aa_system.yaml +++ b/packages/aa_system.yaml @@ -1,9 +1,9 @@ -homeassistant: - name: 238 McHaley +# homeassistant: + # name: 238 McHaley # latitude: !secret latitude_loc_home # longitude: !secret longitude_loc_home # elevation: !secret evelation_loc_home - time_zone: America/Los_Angeles + # time_zone: America/Los_Angeles # temperature_unit: F # unit_system: metric # customize_glob: !include config/customize_glob.yaml @@ -12,6 +12,7 @@ websocket_api: system_health: # Enables the frontend frontend: + themes: !include_dir_merge_named themes # Enables configuration UI config: # Show links to resources in log and frontend diff --git a/packages/darksky_weather.yaml b/packages/darksky_weather.yaml new file mode 100644 index 0000000..9753ee5 --- /dev/null +++ b/packages/darksky_weather.yaml @@ -0,0 +1,42 @@ +weather: + - platform: darksky + api_key: !secret darksky_api_key + name: Local Weather + mode: daily + +sensor: + - platform: darksky + api_key: !secret darksky_api_key + name: dark_sky + forecast: + - 0 + - 1 + - 2 + - 3 + - 4 + - 5 + monitored_conditions: + - icon + - summary + - nearest_storm_distance + - nearest_storm_bearing + - humidity + - temperature + - temperature_high + - temperature_low + - apparent_temperature + - apparent_temperature_high + - apparent_temperature_low + - wind_speed + - wind_bearing + - precip_type + - precip_probability + - precip_accumulation + - precip_intensity + - precip_intensity_max + - uv_index + - daily_summary + - pressure + - visibility + scan_interval: + minutes: 10 diff --git a/packages/indoor_security_light.yaml b/packages/indoor_security_light.yaml index 356b5ca..dce4f4b 100644 --- a/packages/indoor_security_light.yaml +++ b/packages/indoor_security_light.yaml @@ -22,7 +22,7 @@ automation: trigger: platform: sun event: sunset - offset: "-00:30:00" + offset: "-00:45:00" action: - service: switch.turn_on data: diff --git a/packages/irrigation/common.yaml b/packages/irrigation/common.yaml new file mode 100644 index 0000000..613b43a --- /dev/null +++ b/packages/irrigation/common.yaml @@ -0,0 +1,39 @@ +homeassistant: + customize: + input_boolean.irrigation_pump: + friendly_name: "Well Pump" + icon: mdi:water-pump + input_boolean.irrigation_enabled: + friendly_name: "Irrigation System Enabled" + icon: mdi:water + variable.irrigation_next_trigger: + friendly_name: "Countdown to Next Scheduled Run" + icon: mdi:alarm + variable.irrigation_next_trigger_dt: + friendly_name: "Next Scheduled Run at" + icon: mdi:alarm + variable.irrigation_next_schedule_name: + friendly_name: "Next Schedule to Run" + icon: mdi:calendar + variable.irrigation_running_names: + friendly_name: "Zones Running Now" + icon: mdi:view-list + variable.irrigation_queue_names: + friendly_name: "Zones Queued to Run" + icon: mdi:view-list + +input_boolean: + irrigation_pump: + irrigation_enabled: + +variable: + irrigation_next_trigger: + value: '00:00:00' + irrigation_next_trigger_dt: + value: '' + irrigation_next_schedule_name: + value: '' + irrigation_running_names: + value: '' + irrigation_queue_names: + value: '' diff --git a/packages/irrigation/zone1.yaml b/packages/irrigation/zone1.yaml new file mode 100644 index 0000000..97951ca --- /dev/null +++ b/packages/irrigation/zone1.yaml @@ -0,0 +1,57 @@ +homeassistant: + customize: + variable.irrigation_zone_1_schedule_countdown: + friendly_name: "Countdown to Next Scheduled Run" + icon: mdi:timer-sand + variable.irrigation_zone_1_schedule_next_dt: + friendly_name: "Time of Next Scheduled Run" + icon: mdi:clock + input_boolean.irrigation_zone_1_schedule_enabled: + friendly_name: "Schedule (enabled/disabled)" + input_boolean.irrigation_zone_1_state: + friendly_name: "Zone State (manual override)" + +variable: + irrigation_zone_1_schedule_countdown: + value: 'disabled' + attributes: + icon: mdi:clock + irrigation_zone_1_schedule_next_dt: + value: 'disabled' + +input_boolean: + irrigation_zone_1_schedule_enabled: + irrigation_zone_1_state: + +input_number: + irrigation_zone_1_duration: + name: "Duration in minutes" + min: 1 + max: 30 + step: 1 + icon: mdi:timer-sand + irrigation_zone_1_schedule_base_hour: + name: "Hour of Day" + # unit_of_measurement: hours + initial: 6 + min: 0 + max: 24 + step: 1 + icon: mdi:clock + irrigation_zone_1_schedule_base_minute: + name: "Minute of Day" + # unit_of_measurement: minutes + initial: 0 + min: 0 + max: 45 + step: 15 + icon: mdi:clock + +# use sensor.irrigation_zone_1_schedule_repeatin to get corresponding value for option +input_select: + irrigation_zone_1_schedule_delta: + name: Repeat Every + icon: mdi:repeat + # options are set from the websocket client + options: + - 12 Hours diff --git a/packages/irrigation/zone2.yaml b/packages/irrigation/zone2.yaml new file mode 100644 index 0000000..12afb07 --- /dev/null +++ b/packages/irrigation/zone2.yaml @@ -0,0 +1,57 @@ +homeassistant: + customize: + variable.irrigation_zone_2_schedule_countdown: + friendly_name: "Countdown to Next Scheduled Run" + icon: mdi:timer-sand + variable.irrigation_zone_2_schedule_next_dt: + friendly_name: "Time of Next Scheduled Run" + icon: mdi:clock + input_boolean.irrigation_zone_2_schedule_enabled: + friendly_name: "Schedule (enabled/disabled)" + input_boolean.irrigation_zone_2_state: + friendly_name: "Zone State (manual override)" + +variable: + irrigation_zone_2_schedule_countdown: + value: 'disabled' + attributes: + icon: mdi:clock + irrigation_zone_2_schedule_next_dt: + value: 'disabled' + +input_boolean: + irrigation_zone_2_schedule_enabled: + irrigation_zone_2_state: + +input_number: + irrigation_zone_2_duration: + name: "Duration in minutes" + min: 1 + max: 30 + step: 1 + icon: mdi:timer-sand + irrigation_zone_2_schedule_base_hour: + name: "Hour of Day" + # unit_of_measurement: hours + initial: 6 + min: 0 + max: 24 + step: 1 + icon: mdi:clock + irrigation_zone_2_schedule_base_minute: + name: "Minute of Day" + # unit_of_measurement: minutes + initial: 0 + min: 0 + max: 45 + step: 15 + icon: mdi:clock + +# use sensor.irrigation_zone_2_schedule_repeatin to get corresponding value for option +input_select: + irrigation_zone_2_schedule_delta: + name: Repeat Every + icon: mdi:repeat + # options are set from the websocket client + options: + - 12 Hours diff --git a/packages/irrigation/zone3.yaml b/packages/irrigation/zone3.yaml new file mode 100644 index 0000000..7ad28d3 --- /dev/null +++ b/packages/irrigation/zone3.yaml @@ -0,0 +1,57 @@ +homeassistant: + customize: + variable.irrigation_zone_3_schedule_countdown: + friendly_name: "Countdown to Next Scheduled Run" + icon: mdi:timer-sand + variable.irrigation_zone_3_schedule_next_dt: + friendly_name: "Time of Next Scheduled Run" + icon: mdi:clock + input_boolean.irrigation_zone_3_schedule_enabled: + friendly_name: "Schedule (enabled/disabled)" + input_boolean.irrigation_zone_3_state: + friendly_name: "Zone State (manual override)" + +variable: + irrigation_zone_3_schedule_countdown: + value: 'disabled' + attributes: + icon: mdi:clock + irrigation_zone_3_schedule_next_dt: + value: 'disabled' + +input_boolean: + irrigation_zone_3_schedule_enabled: + irrigation_zone_3_state: + +input_number: + irrigation_zone_3_duration: + name: "Duration in minutes" + min: 1 + max: 30 + step: 1 + icon: mdi:timer-sand + irrigation_zone_3_schedule_base_hour: + name: "Hour of Day" + # unit_of_measurement: hours + initial: 6 + min: 0 + max: 24 + step: 1 + icon: mdi:clock + irrigation_zone_3_schedule_base_minute: + name: "Minute of Day" + # unit_of_measurement: minutes + initial: 0 + min: 0 + max: 59 + step: 1 + icon: mdi:clock + +# use sensor.irrigation_zone_3_schedule_repeatin to get corresponding value for option +input_select: + irrigation_zone_3_schedule_delta: + name: Repeat Every + icon: mdi:repeat + # options are set from the websocket client + options: + - 12 Hours diff --git a/packages/irrigation/zone4.yaml b/packages/irrigation/zone4.yaml new file mode 100644 index 0000000..8010f10 --- /dev/null +++ b/packages/irrigation/zone4.yaml @@ -0,0 +1,57 @@ +homeassistant: + customize: + variable.irrigation_zone_4_schedule_countdown: + friendly_name: "Countdown to Next Scheduled Run" + icon: mdi:timer-sand + variable.irrigation_zone_4_schedule_next_dt: + friendly_name: "Time of Next Scheduled Run" + icon: mdi:clock + input_boolean.irrigation_zone_4_schedule_enabled: + friendly_name: "Schedule (enabled/disabled)" + input_boolean.irrigation_zone_4_state: + friendly_name: "Zone State (manual override)" + +variable: + irrigation_zone_4_schedule_countdown: + value: 'disabled' + attributes: + icon: mdi:clock + irrigation_zone_4_schedule_next_dt: + value: 'disabled' + +input_boolean: + irrigation_zone_4_schedule_enabled: + irrigation_zone_4_state: + +input_number: + irrigation_zone_4_duration: + name: "Duration in minutes" + min: 1 + max: 30 + step: 1 + icon: mdi:timer-sand + irrigation_zone_4_schedule_base_hour: + name: "Hour of Day" + # unit_of_measurement: hours + initial: 6 + min: 0 + max: 24 + step: 1 + icon: mdi:clock + irrigation_zone_4_schedule_base_minute: + name: "Minute of Day" + # unit_of_measurement: minutes + initial: 0 + min: 0 + max: 59 + step: 1 + icon: mdi:clock + +# use sensor.irrigation_zone_4_schedule_repeatin to get corresponding value for option +input_select: + irrigation_zone_4_schedule_delta: + name: Repeat Every + icon: mdi:repeat + # options are set from the websocket client + options: + - 12 Hours diff --git a/packages/irrigation/zone5.yaml b/packages/irrigation/zone5.yaml new file mode 100644 index 0000000..47f32ca --- /dev/null +++ b/packages/irrigation/zone5.yaml @@ -0,0 +1,57 @@ +homeassistant: + customize: + variable.irrigation_zone_5_schedule_countdown: + friendly_name: "Countdown to Next Scheduled Run" + icon: mdi:timer-sand + variable.irrigation_zone_5_schedule_next_dt: + friendly_name: "Time of Next Scheduled Run" + icon: mdi:clock + input_boolean.irrigation_zone_5_schedule_enabled: + friendly_name: "Schedule (enabled/disabled)" + input_boolean.irrigation_zone_5_state: + friendly_name: "Zone State (manual override)" + +variable: + irrigation_zone_5_schedule_countdown: + value: 'disabled' + attributes: + icon: mdi:clock + irrigation_zone_5_schedule_next_dt: + value: 'disabled' + +input_boolean: + irrigation_zone_5_schedule_enabled: + irrigation_zone_5_state: + +input_number: + irrigation_zone_5_duration: + name: "Duration in minutes" + min: 1 + max: 30 + step: 1 + icon: mdi:timer-sand + irrigation_zone_5_schedule_base_hour: + name: "Hour of Day" + # unit_of_measurement: hours + initial: 6 + min: 0 + max: 24 + step: 1 + icon: mdi:clock + irrigation_zone_5_schedule_base_minute: + name: "Minute of Day" + # unit_of_measurement: minutes + initial: 0 + min: 0 + max: 45 + step: 15 + icon: mdi:clock + +# use sensor.irrigation_zone_5_schedule_repeatin to get corresponding value for option +input_select: + irrigation_zone_5_schedule_delta: + name: Repeat Every + icon: mdi:repeat + # options are set from the websocket client + options: + - 12 Hours diff --git a/packages/irrigation/zone6.yaml b/packages/irrigation/zone6.yaml new file mode 100644 index 0000000..6741279 --- /dev/null +++ b/packages/irrigation/zone6.yaml @@ -0,0 +1,57 @@ +homeassistant: + customize: + variable.irrigation_zone_6_schedule_countdown: + friendly_name: "Countdown to Next Scheduled Run" + icon: mdi:timer-sand + variable.irrigation_zone_6_schedule_next_dt: + friendly_name: "Time of Next Scheduled Run" + icon: mdi:clock + input_boolean.irrigation_zone_6_schedule_enabled: + friendly_name: "Schedule (enabled/disabled)" + input_boolean.irrigation_zone_6_state: + friendly_name: "Zone State (manual override)" + +variable: + irrigation_zone_6_schedule_countdown: + value: 'disabled' + attributes: + icon: mdi:clock + irrigation_zone_6_schedule_next_dt: + value: 'disabled' + +input_boolean: + irrigation_zone_6_schedule_enabled: + irrigation_zone_6_state: + +input_number: + irrigation_zone_6_duration: + name: "Duration in minutes" + min: 1 + max: 30 + step: 1 + icon: mdi:timer-sand + irrigation_zone_6_schedule_base_hour: + name: "Hour of Day" + # unit_of_measurement: hours + initial: 6 + min: 0 + max: 24 + step: 1 + icon: mdi:clock + irrigation_zone_6_schedule_base_minute: + name: "Minute of Day" + # unit_of_measurement: minutes + initial: 0 + min: 0 + max: 45 + step: 15 + icon: mdi:clock + +# use sensor.irrigation_zone_6_schedule_repeatin to get corresponding value for option +input_select: + irrigation_zone_6_schedule_delta: + name: Repeat Every + icon: mdi:repeat + # options are set from the websocket client + options: + - 12 Hours diff --git a/packages/irrigation/zone7.yaml b/packages/irrigation/zone7.yaml new file mode 100644 index 0000000..e0b90e9 --- /dev/null +++ b/packages/irrigation/zone7.yaml @@ -0,0 +1,57 @@ +homeassistant: + customize: + variable.irrigation_zone_7_schedule_countdown: + friendly_name: "Countdown to Next Scheduled Run" + icon: mdi:timer-sand + variable.irrigation_zone_7_schedule_next_dt: + friendly_name: "Time of Next Scheduled Run" + icon: mdi:clock + input_boolean.irrigation_zone_7_schedule_enabled: + friendly_name: "Schedule (enabled/disabled)" + input_boolean.irrigation_zone_7_state: + friendly_name: "Zone State (manual override)" + +variable: + irrigation_zone_7_schedule_countdown: + value: 'disabled' + attributes: + icon: mdi:clock + irrigation_zone_7_schedule_next_dt: + value: 'disabled' + +input_boolean: + irrigation_zone_7_schedule_enabled: + irrigation_zone_7_state: + +input_number: + irrigation_zone_7_duration: + name: "Duration in minutes" + min: 1 + max: 30 + step: 1 + icon: mdi:timer-sand + irrigation_zone_7_schedule_base_hour: + name: "Hour of Day" + # unit_of_measurement: hours + initial: 6 + min: 0 + max: 24 + step: 1 + icon: mdi:clock + irrigation_zone_7_schedule_base_minute: + name: "Minute of Day" + # unit_of_measurement: minutes + initial: 0 + min: 0 + max: 45 + step: 15 + icon: mdi:clock + +# use sensor.irrigation_zone_7_schedule_repeatin to get corresponding value for option +input_select: + irrigation_zone_7_schedule_delta: + name: Repeat Every + icon: mdi:repeat + # options are set from the websocket client + options: + - 12 Hours diff --git a/packages/outside.yaml b/packages/outside.yaml new file mode 100644 index 0000000..b8aa7c3 --- /dev/null +++ b/packages/outside.yaml @@ -0,0 +1,57 @@ +# {"Time":"2020-12-08T02:09:02","BME280":{"Temperature":21.5,"Humidity":39.1,"DewPoint":6.9,"Pressure":906.5},"PressureUnit":"hPa","TempUnit":"C"} + +homeassistant: + customize: + sensor.bme_outside_temperature: + friendly_name: 'Outside Temperature' + + +sensor: + - platform: mqtt + name: "bme_outside_temperature" + state_topic: "tele/outside/SENSOR" + qos: 0 + unit_of_measurement: "F" + value_template: "{{ value_json.BME280.Temperature * 9 / 5 + 32 }}" + - platform: mqtt + name: "bme_outside_dew_point_temperature" + state_topic: "tele/outside/SENSOR" + qos: 0 + unit_of_measurement: "F" + value_template: "{{ value_json.BME280.DewPoint * 9 / 5 + 32 }}" + - platform: mqtt + name: "bme_outside_humidity" + state_topic: "tele/outside/SENSOR" + qos: 0 + unit_of_measurement: "%" + value_template: "{{ value_json.BME280.Humidity }}" + - platform: mqtt + name: "bme_outside_barometric_pressure" + state_topic: "tele/outside/SENSOR" + qos: 0 + unit_of_measurement: "inHg" + value_template: "{{ value_json.BME280.Pressure / 33.86 }}" + +switch: + - platform: mqtt + name: "Outside North Circuit 1" + state_topic: "stat/outside/POWER1" + command_topic: "cmnd/outside/POWER1" + availability_topic: "tele/outside/LWT" + qos: 1 + payload_on: "ON" + payload_off: "OFF" + payload_available: "Online" + payload_not_available: "Offline" + retain: false + - platform: mqtt + name: "Outside North Circuit 2" + state_topic: "stat/outside/POWER2" + command_topic: "cmnd/outside/POWER2" + availability_topic: "tele/outside/LWT" + qos: 1 + payload_on: "ON" + payload_off: "OFF" + payload_available: "Online" + payload_not_available: "Offline" + retain: false diff --git a/packages/weather.yaml b/packages/weather.yaml deleted file mode 100644 index ee28807..0000000 --- a/packages/weather.yaml +++ /dev/null @@ -1,34 +0,0 @@ -weather: - - platform: darksky - api_key: 21b1870b9e52cf1ce234798ddfd0fbce - name: Praire City Weather - mode: daily - - -sensor: - - platform: darksky - api_key: 21b1870b9e52cf1ce234798ddfd0fbce - name: forecast - forecast: - - 0 - - 2 - - 3 - hourly_forecast: - - 0 - - 1 - - 2 - - 3 - monitored_conditions: - - summary - - icon - - temperature - - dew_point - - wind_speed - - wind_bearing - - cloud_cover - - temperature_high - - temperature_low - - moon_phase - - sunset_time - - alerts - scan_interval: 00:10 diff --git a/python_scripts/state_change.py b/python_scripts/state_change.py new file mode 100644 index 0000000..378e284 --- /dev/null +++ b/python_scripts/state_change.py @@ -0,0 +1,31 @@ +#================================================================================================== +# python_scripts/set_statev2.py +#================================================================================================== + +#-------------------------------------------------------------------------------------------------- +# Set the state or other attributes for the entity specified in the Automation Action +#-------------------------------------------------------------------------------------------------- + +# service: +# python_script.set_statev2 +# {"entity_id": "light.keukeneiland","state":"on","icon":"mdi:door","friendly_name":"test"} + +inputEntity = data.get('entity_id') +if inputEntity is None: + logger.warning("===== entity_id is required if you want to set something.") +else: + inputStateObject = hass.states.get(inputEntity) + inputState = inputStateObject.state + inputAttributesObject = inputStateObject.attributes.copy() + + for item in data: + newAttribute = data.get(item) + logger.debug("===== item = {0}; value = {1}".format(item,newAttribute)) + if item == 'entity_id': + continue # already handled + elif item == 'state': + inputState = newAttribute + else: + inputAttributesObject[item] = newAttribute + + hass.states.set(inputEntity, inputState, inputAttributesObject) diff --git a/python_scripts/states_change.py b/python_scripts/states_change.py new file mode 100644 index 0000000..2f7875b --- /dev/null +++ b/python_scripts/states_change.py @@ -0,0 +1,49 @@ +# EXEMPLE 1 +# - service: python_script.state_change +# data_template: +# entity_id: person.one, person.two +# state: 'not_home' + +# EXEMPLE 2 +# - service: python_script.state_change +# data_template: +# entity_id: +# - person.one +# - person.two +# state: 'not_home' + +# EXEMPLE 3 +# - service: python_script.state_change +# data_template: +# entity_id: > +# {% set entities = state_attr('group.family_persons', 'entity_id') %} +# {{ states.person | selectattr('entity_id', 'in', entities) +# | selectattr('state', 'eq', 'home') +# | map(attribute='entity_id') +# | join(', ') }} +# state: 'not_home' + +if isinstance(data.get('entity_id'), str): + EntityList = data.get('entity_id').split(', ') +else: + EntityList = data.get('entity_id') +for item in EntityList: + inputEntity = item + if inputEntity is None: + logger.warning("===== entity_id is required if you want to set something.") + else: + inputStateObject = hass.states.get(inputEntity) + inputState = inputStateObject.state + inputAttributesObject = inputStateObject.attributes.copy() + + for item in data: + newAttribute = data.get(item) + logger.debug("===== item = {0}; value = {1}".format(item,newAttribute)) + if item == 'entity_id': + continue # already handled + elif item == 'state': + inputState = newAttribute + logger.info(inputEntity + ': ' 'state' + '> ' + inputState) + else: + inputAttributesObject[item] = newAttribute + hass.states.set(inputEntity, inputState, inputAttributesObject) diff --git a/templates/button-card-templates/default.yaml b/templates/button-card-templates/default.yaml new file mode 100644 index 0000000..6bfca80 --- /dev/null +++ b/templates/button-card-templates/default.yaml @@ -0,0 +1,38 @@ +tap_action: + action: toggle +hold_action: + action: more-info +show_state: true +size: 30% +state: + - styles: + card: + - filter: opacity(50%) + icon: + - filter: grayscale(100%) + value: 'off' + - styles: + card: + - filter: opacity(25%) + icon: + - filter: grayscale(100%) + value: unavailable +styles: + card: + - width: 150px + - height: 150px + grid: + - grid-template-areas: '"i" "n" "s"' + - grid-template-columns: 1fr + - grid-template-rows: 1fr min-content min-content + img_cell: + - justify-content: start + - margin-left: 20px + - margin-bottom: 30px + name: + - justify-self: start + - margin-left: 10px + state: + - justify-self: start + - margin-left: 10px + - font-weight: lighter diff --git a/templates/button-card-templates/large_value.yaml b/templates/button-card-templates/large_value.yaml new file mode 100644 index 0000000..b5449e5 --- /dev/null +++ b/templates/button-card-templates/large_value.yaml @@ -0,0 +1,30 @@ +show_label: true +show_state: true +size: 100% +styles: + card: + - width: 140px + - height: 140px + - margin: 5px + grid: + - grid-template-areas: '"s s" "i n" "i l"' + - grid-template-columns: 20% 80% + - grid-template-rows: 60% 20% 10% + img_cell: + label: + # - margin-left: 10px + # - font-weight: lighter + - justify-self: start + - font-size: 1em + name: + - justify-self: start + - overflow: visible + - font-size: 1.7em + - font-weight: bold + state: + # - margin-right: 30px + - justify-self: center + - align-self: center + - font-size: 2.2em + - font-weight: bold + - overflow: visible diff --git a/themes/clear/clear.yaml b/themes/clear/clear.yaml new file mode 100644 index 0000000..ca8d4e8 --- /dev/null +++ b/themes/clear/clear.yaml @@ -0,0 +1,60 @@ +clear: + # Background image + lovelace-background: 'center / cover no-repeat url(data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQECAgICAgICAgICAgMDAwMDAwMDAwP/2wBDAQEBAQEBAQIBAQICAgECAgMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwP/wgARCAMABVYDAREAAhEBAxEB/8QAHAABAAMBAQEBAQAAAAAAAAAAAAECAwQFBgcI/8QAGgEBAQEBAQEBAAAAAAAAAAAAAAECAwQFBv/aAAwDAQACEAMQAAAB/tb9z8oAdEx7nPx2SaAAAAAAAAAAAAEVwd96uX6J87872Y5AAAAAAAAAAAADl6OLroAAAAACZPa8nj6ufKm9RaAAAAAAAAAAAAPG7+v5j1/SAAAAAAAzuvm+v2ua9wAAAAAAAAAAABBVoAAAAAAUmqygDs82gOiY9rHlvIFTYAAAAAAAAAAAIODvvLVHVnl+g+D89144gAAAAAAAAAADm6Xj6gAAAAAJj2fJ5OvlxEW03ooAAAAAAAAAAA8b0ev5j1/SAAAAAAAAza+c6/Z572AAAAAAAAAAAELVQAAAAABXOqygAdfn2Oic/a5+WyABU0AAAAAAAAAAIOHvctUAdWOX33h+B144gAAAAAAAAADl6Xj7UAAAAACU9rx+Pq58gBBXXWAAAAAAAAAAAeP6PX816/oQAAAAAAAACjfzvT7HNrsAAAAAAAAAAEtGgAAAAABVYUAAbcLvMe1z8t0AAAUoAAAAAAAACDi7XLVAAHVnl974PgdeeQAAAAAAAAA5uji7UAAAAACY9nyePq5cgAAK66RaAAAAAAAAAPI7+r5n2fQAAAAAAAAAAzb+e6fY5r2AAAAAAAAABaygAAAAACqwoAAHo+V7PPzWAAABKTYAAAAAAAABwd9ZaoAAA6c8vvfD8DrzyAAAAAAAAHLu8fWgAAAAAI9fy+Tq58gAAAWvSwoAAAAAAAA8nv6vm/V9EAAAAAAAAAACl18/r6/PewAAAAAAAAiqNAAAAAACs1EAAAD6jx+WyAAAACbLIAAAAAAAIOLvvPdAAAAHTjn9z4vh9WeQAAAAAAA5t3j6AAAAAAJPX8vj6+XMAAAAQU1uKAAAAAAAHk9/V896vfFoAAAAAAAAAAAo14PT63PeoAAAAAAAiWqgAAAAACssZoAAAH0fn8YAAAAAkmwAAAAAARXF33nqgAAAADpzz+58HwurPIAAAAAAc+7x9QAAAAAEnreXy9XPkAAAAABTW4AAAAAAB5Xf0/Per6AAAAAAAAAAAAAGd14PT63PeoAAAAAAiWFAAAAAAGc1EoAAAA+q8flAAAAAEooAAAAACx7nLw+H6fV5fb0AAAAADtxx+38Pwts4AAAAAAGO7xdqAAAAAEdmOPd5+O3PIAAAAAVnvRQAAAAAB5/Xv836/oRQAAAAAAAAAAAARSa8Lr9XC9QAAAAAIWsoAAAAAGznnN4ugAAAA+l8nnugAAAAlAAAAAABZPb5eLrnGF+a9Xu83r3AAAAHZjj9n4/jbTAAAAAAAAx05OlUAAAAjrzx1mB08Ma4yAAAAIWutKAAAAAAAHn9O/zvr98AAAAAAAAAAAACEUuvC6/VwvUAAAAACFrKAAAABtOejAwdcXQAAADr459WcLyAAACZJAAAAAALJ7XPxdeeQEHzXr9vm9e4AAA7Mcfs/F8faZAAAAAAAAGOnH0sAAAEnXnjqwAOjjjTngAAAFi7AAAAAAAAA4Ovf5z1e+AAAAAAAAAAACUAFG/C6fUxvUAAAAACCrQAAAGzneYAGLri6AAADfldJn18ee6AAAAAAAAAC0e3z8PXnkABB816/b5vXuAAOvHH7LxfI2zgAAAAAAAAAY7vJ0KAAmOrPHXOQAB0cueucAACFreigAAAAAAAAB5/Tv8AP+n3QAAAAAAAAAAAAAUuvE39PG9QAAAAABWUAADRjRkAAYumTYAAHTxo1k9XHC8gAAAAAAAAk9nHi688gAAIt+c9Ps83r2AHXjl9h4/j6zAAAAAAAAAAAy1eXrqACY6c8tJkAAAdHHnrjIAio1QAAAAAAAAAAODr3+f9Hvi0AAAAAAAATEzMgAAFLrxN/UxvQAAAAACCtpQCazF5kAAAY3pk6ACybTlnjqBonq8+F0AAAAAAAEp7OPH1Z5AAAAQfOer2ed07Drxy+w8nyNc4AAAAAAAAAAAGW7y9NAdOOWjIAAAEm/HnrzyIquqAAAAAAAAAAABw9e/gen3wAAAAAAAAWkAAAAFGvE39PHXQAAAAACFhYJNZzvMgAAADG9MnQSnRONkpy9IA0k9PPC8zKAAAAAAWPYx4+mcwAAAAIPnvR7ctX67yfI1mAAAAAAAAAAAABjrXN0vTnlpMgAAAADo489MTPWgAAAAAAAAAAAAOLp28L0+2AAAAAAAAAAAAACjXjdPpZXoAAAAABC1XXOLsgAAAADF0pdbzlZBly7gAaJ6k890AAAAACPR58fRx5QAAAAAINLy3nIAAAAAAAAAAAAAZdeeuMIAAAAAFbbydHLIAAAAAHP11piaZgAAAAAg+f9fv5NdAAAAAAK3WLp044gAAAAAcmvR5fT3QoAAAAAF2bZyAAAAAAIWUAz5+kAAaTPqTz3kAAAAA6sS+XQ5dc84AAAAABN5w0mAAAAAAAAAAAABh3xnuXzNeeQAAAAK283XrW3u8/Lo54AAAAAw6b4PR1vJ6Hl43kAAAABfF9Pr4OveBkAAAABW687fso13c/L1Y4AAAAAc+uvnb9kLlrpCgAAAAaTF5mKhQAAAAAAAK8e4AAGielnhZAAABJ1c5fIDocuqecAAAAAAbOOk5gAAAAAAAAAAKw65prIF8zXGQAAAIt5e3WKA7vPz6OWAAAAOfprh9PUC2Xo+bjfMAAAEHi+n2cHbsAhkAAABW68/Xro0B3Y83TjgAAABz67edv2AQZ3cKAAABpMWQAAAAAAAAAZ8/QAAANJPSnC0gAAHVzmkAAbOPU84AAAAAA3nG8wAAAAAAAAAAMeuabyABfLTOUAACtvP06VtAA7eHPo5ZAAAw6Xh9HUAC8nf5uV8gABFeN6fVw9u4ACScgAAKNcG/XRoADtx5ejHEAADC9vP6euFAAyu4UAASXmLIAAAAAAAAABny7gAAAaM+lnjZAAOnE0zAAANnLpvAAAAAAAbThdgAAAAAAAAAZdJTeQAAL4mvOAARXN161tAABO3hjfngADDeuL0dCgAC2Z3+fleQAQeP6fXw9uwAAEwzAAKtcHT10ugAAjs5+boxxAAx118/XrKAAIM7uAAC7N5AAAAAAAAAABny7AAAADRPQnG4B0YzpmAAAAbOXU4AAAAAADacbzAAAAAAAAEGfbNNQAAAC0mnMBFc/TpWgAABJ18Oe/PAGPTXH36wAAAC0d/n42zAPH9Hs4evYAAAAWkAq1wdPVRoAAADs5+fozxAwdeHXqAAAAi3K7hQNJi8yAAABFQq0AAAAAU5dgAAAANE9DPKydOM3zAAAAANnPpvAAAAAAAbZ43YAAAAAAAGXSU3kAAAAC2WuMxbz9N1tAAAAEnXx5bc8471yd+sAAAAAvJ2+flaTyO/r4unYAAAAASlWuHfqpdAAAAAdnPzdGOWN6cGvXAAAAAFZ62W0zbOQAAAAAAAAAABTPYAAAAAayexy4aSAAAAAAaufTOIAAAAAA3nCzIAAAAAAx6ZrqAAAAAAWkz1utoAAAAAG/PGHTYAAAAAFpOedePp2AAAAAAg4teql0AAAAABvz5c7sAAAAABBW6vMgAAAAAAAAAAAUx3AAAAAk9Xlx6s5AAAAAAA2c+mcAAAAABZjVyAAAAAAGXTNNwAAAAAACFrNAAAAAAAAAAAAAcPTvhemmcgAAAACF49eirQAAAAAFJrKbszaQAAAAAUuoXSZIAAAABNaXERSbgAAAAtnphdAAAASery49OcAAAAAAAAbue84SAAAAXY1cwAAAAAIMuua6gAAAAAAAELRQAAAAAAAAAAAXi13w10A0zgAAAAQvHfRW6AgAAAAFZrKdIJLs2ZAAAAFJql0JNGJQAAAAaMSghc5sAAAD1uM83XXC6AAAk9PHHpzkAAAAAAAADVy6HIAAAaTG05gAAAAADHrmuoAAAAAAAABC1lAAAAAAAAAAA4uno59dAAjTOJAAAKry3vVQBCxaAAAKTWU6AAXYtYAAAKTVWgATW4AAAEpoyQARLnNgAAD2vPIPO31wuwAJT0+fHpzgAAAAAAAAADVjdyAAF5jRzSAAAAACKpuV0AAAAAAAAAAi2toAAAAAAAAAg5Nd8NdAAAk0zkAAQvLe9VAAELFoAAquedgAAXYsgAAo1VoAASl2ZQACSzMoAAIXO7AAF5n0+EEL5++uV0Aj0scujOQAAAAAAAAAANXPZzlALTOjkAAAAAAKbldQAAAAAAAAAACq1ugAAAAAAABBy67YXoAAAJS8zMgELy3vVoAAARUKAKrnNgAAAXZtIAKXVVAAAA0mJACaMkAAAENZ3QA1mO7n5E9YELwa642yejz59EyAAAAAAAAAAABoxs5C7Gk5gAAAAACm811AAAAAAAAAAAAKrRsAAAAAAAK5b1xvQAAAAE0YmKrzXvVQAAABC1tELnNgAAAAXZtMil1VoAAAADSYlBoxIAAAAKrRoazHdjyylJ6wAXg116846M5AAAAAAAAAAAAA0Y6Xn0nMAAAAABWe5GoAAAAAAAAAAAABVatAAAAAACDl11xdAAAAABKWTmdqqAAAAAIto1nNgAAAAAXZhaqAAAAAJNGLsTIAAAAAIurTPdjygacvaAALRMAAAAAAAAAAAAAbzPU89dcwAAAAABTUrYAAAAAAAAAAAAAIWLQAAAAABx67ZXYAAAAAkJg7VUAAAAACrWTcAAAAAAAAAAAAAk2c5QAAAAADSY68ecATj2AAATJKAAAAAAAAAAAAbTPZnkIuY1kAAAAAAU1mtAAAAAAAAAAAAACCLQAAAAAIOTXbK7AAAAEmkwIXndogAAAAACl1lOgAAAAAAAAAAAA2crIAAAAABdnpzxIAJx6gAABMkgAAAAAAAAAAG0z1Z5yARcLgAAAAAAZ7laAAAAAAAAAAAAAEVF0AAAAABBy3rk2AAAJsszIBC887VUAAAAACq5zYAAAAAAAAAAA2c7IAAAAABeY6ZxAAE49YAAAFpCAAAAAAAAAAb5z1TnIABDEXIAAAAAAz3I1AAAAAAAAAAAAABCxaAAAAABBzXrk0AAJJmZAAKrz3tCgAAAAAVWk2AAAAAAAAAATW4lAAAAAALzPTnjKAACcesAAAAWmZAAAAAAAAANZnqnOQAACrNbgAAAAABVdSNAAAAAAAAAAAAABBWaAAAAAAHLrpndgAWmZAAAKri6QoAAAAAFWsmwAAAAAAAABs5ygAAAAAGkz0Z4SAAARPUAAAABaSYAAAAAAAA1meqYAAAAEXEOYAAAAACqbRQAAAAAAAAAAAAAhaygAAAAARXPrpm0BaSUAAAAquLrVQAAAAAK3WbYAAAAAAABNpzsgAAAAAFpnpnEAAABn0gAAAAC2UgAAAAAAGsz1TmAAAAAIYi4AAAAAAFOiKAAAAAAAAAAAAAES1UAAAAAAc+umd1aRAAAAAFbcXWAAAAAACrVLoAAAAAAAaTnaQAAAAAC7PROKAAAAGfSAAAAABaLSgAAAAAdOc6uRAAAAAAK6wQAAAAACCukUAAAAAAAAAAAAAIIugAAAAAKnNekgAAAAAFGsnQAAAAAAZXaAAAAAABZm6AAAAAAE6c8bIAAAAL8/QAAAABC52wayWAAAAAOjE9HlK1z65qAAAAEFN4jWZltAAAAAEFbFQAAAAAAAAAAAAAARbWgAAAAIOe9arZJgAAAACtuN6AAAAAACk1RoAAAAAACS8zZkAAAAACU6JylAAAAPb8Pv8X1+cAAACDK2ASaSWAAAAOjE9DmkFU594AAAAGfTFbkC0WgAAACKrUAAAAAAAAAAAAAAAAiq0AAABBz3tW0C0kyAAAAVusruFAAAAAApNVaAAAAAAAAF2LSAAAAAASnROUoAAAPtPjfX8b1+fx/VwAAAis1gAA1mZAAAOjE7+aYAFbOfpmAAACu+cXIAEypQAABGsgACLQAAAAAAAAAAIVAAFbGgAAEGDrW0ACYlAAAKtZXYAAAAAApNUaAAkmQAAAAAAXZsyAAAAABMbuUoAAB9h8j6w8f1efyPVwAAisrSgAAaTNoAA3w9DlkAACtc/SRQAFdc41kAABmzKAASNRQAABAAAAAAAAAAIlqoAACxYABC896xaAABMkwABW3K9IAAAAAAKzVGgAAJSYAAAAAAF2LIAAAAABKbTnKAAS19d8n6YHkerh5Hp4gKpUUAAABriTAG+Z3cgAAAFbObrkAV1hrIAAAE5s5ACu4oAAACEigAAAAAAAAhAAAAELFAQuOukKAAABMkoBVcr0AAAAAAFGqTQAAAAAAAAAAEl7mzIAAAAAEpvnlaAAPq/l/UAHlenj5Pp4QZ7sKAAAAJTTEk3w7uZAAAAArZh05qrrCwAAAABKzVNZAAAAAFNIQAAAAAAAISgAAAAAQYb61aAAAAAtMiFzuwAAAAABSapNAAAAAAAAAAAASXuLWAAAAAATJtOcoAPqvlfTAA8b1cfM9PKKAAAAAEnRzndykgAAAAArZz9ecXIAAAAACVYAAAAAAISKAAAAAAEAAAAAAALhrpW6AAAAAAmTJsAAAAAAQ1WUAAAAAAAAAAAAAXuJAAAAAAJTbPOUA+n+X9IAY7nzft4U3m6AAAAAASehxaQAAAAAKaxTWc9ZAAAAACNM6rZXUAAAAAFstMstylgAAAAAAAAAAAAkNZ3WO9gAAAABFpnO7hQAAAABVYlAAAAAAGjELRoAAAAASmjEWwoAAAAAvMaTIA+m+Z9ADHc+b9vDHUF7LoAAAAABJ6HFpAAAAAprFLkKz1kAAAAI0zoCtldQAAAAWy1zRCZblLAAAAAAAAAAABIakGV1jvYAAAARaZAzu4UAAAAVWJQAAAAABoxKCrVGgAAABKaMARbCgAAAC8xpMgAfTfM+gMdT5328cdZAF6tQAAAAAEyd/KaQAAAKXNdQAQlbgAAAC03KgCtzDIAAAsaYhQCY9FKAAAAAAAAAAAmaAAz1rK6AAAAkAAo1AAAAKtVUAAAAAAaznMgArdZ3YAAAtJpMEALFsUAAANJnSYAAH0fzfoY6nz3s446gAAvZagAAAAAJO7lnSAABW5rYAAIsrcAAAtpqZQABW5i5AAFo0xAAArDdroAAAAAAAAABM0AABnblrQAAAAAArNQAACrVLoAAAAACTXPOyAACjWWtgAC0ms5yAACtsAAA0mdJgAAD3fB7/AJ32cctQAAAWq9gAAAAAEx3cs6wAM9ZrYAAACVuQALTRQAABFzCACY0zJAAAIMeitAAAAAAAAATNAAAAZ2560AAAAABBVqFAFVq0AAAAAANZzmQAAAUuqXYAlNZhIAAAItAAuzeZAAAFcenHQAAAAXS6AAAAAATHdzzpkKbxS5AAAAApoBfIAAAACKrQtGmYgAAACKy1IoAAAAAAACZoAAAADO6y1QAAAAAC0aihWagAAAAAAGkxIAAAAKrS6EmkwAAAAAIoXmbzIAAAHPfQAAAAALyaSAAAAAASdnOLitgAAAAAiq2XlQAAAAAIsg0zJAAAAAIMtq2AAAAAAATnSUAAAAAU1ct6jIAAAAACLatVUAAAAACUvMygAAAAAqtbrSYAAAAAAi20zpjIAAAA5t9wAAAABKayAAAAAADqxLMRYAAAAAAAAAAAAAol8wAAAAACDLaKAAAAAAZqUAAAAACKw1sAAAAAAUaq0AAAAAACaMSAAAAAAAAAAAAATJfOQAAAAMr3igAAABJcAAAAAAHTzzviQU6ZigAAAAAAAAAAACQgtFoAAAAAAgz3K0AAAAAzooAAAAAEGVoAAAAAAo1W6AAAAAAAEpeZlAAAAAAAAAAAAJiUAAAAAHp+f1eN6eUUAAAJLgAAAAAA6eed8QCCnTMUAAAAAAAAAAASEAFotAAAAAAEGe5WgAAAGdFAAAAAAgytAAAAAAFGq3QAAAAAAAAlLzMoAAAAAAAAAABMSgAAAAAHteP24bz5Xo5wAACUtYAAAAAAOjE2wAAVVlQAAAAAAAAAEFbAABaJlAAAAAAVnrMAAAAAAAAAAELndAAAAAACrVGgAAAAAAAABKaMkAAAAAAAAAAmLSAAAAAAD1fN6xhvPl+jnAABa5AAAAAAHRzbZgAAAiwgAAAAAAAAFarQAAAtFoAAAAAAgprMUAAAAAAAABC5tFAAAAAApbVoAAAAAAAAAADSYkAAAAAAAAAlJkAAAAAAA9XzesDDefL9HOAC1yAAAAAAOjm2zAAAABFhAAAAAAAAK1WgAAABaLQAAAAABBTWYoAAAAAAACFzaKAAAAABS2rQAAAAAAAAAAAGkxIAAAAAAABKTIAAAAAAAPV83rAGG8+X6OcFrkAAAAAAdHNtmAAAAACLCAAAAAAAVqtAAAAAC0WgAAAAACCmsxQAAAAAAELm0UAAAAACltWgAAAAAAAAAAABJeYkAAAAAAAlJkAAAAAAAA9XzesADi645evMgAAAAAGmXTiAAAAAACugAAAAAAghlQAAAAAFswAAAAAADLYAAAAAACjcAAAAAAELndgAAAAATEpW0AAAAACyWZAAAAAAAtMgAAAAAAAD1PN6gBhrPnd8EtYAAAAAEI3y2zAAAAAFUsAAAAAAAEUZAAAAAExKAAAAAADLVigAAAAAAIKzQAAAAAArdUaAAAAAk2mLJjd0UAAAACUtYAAAAAAAJkmQAAAAAAAD1PP6QMdZ8/tiASlrAAAABBEoHRmbZAAAAEz1AAAAAAAABFGQAAABMWgAAAAACKpUUAAAAAAABC0aAAAAAAFVq0AAABK6zFkEGV1VQAAACXsAAAAAAAAEyTIAAAAAAAPS4ekZWcPXmAAJsAAAAi0ADfE1gAACCliwAAAAAAAACCLCAAAWzbAAAAAAApUWAAAAAAAAAQtZoAAAAAAVWl0AABJrnEoABld1tAAAksyAAAAAAAAAJS0gAAAAAAHfx9GVnH0wAAAJsAAAAAAGsmkAACtkAAAAAAAAAAArYAALRaAAAAAABTSAAAAAAAAAACCJqKAAAAAAqtWgABrOdpAABBlrpCgAEskgAAAAAAAAAEpMgAAAAAA6efbk3mAAAATVmQAAAAABpGkoBIsAAAAAAAAAAAEJTQAWi0oAAAAAEFbIoAAAAAAAAAABLRQAAAAABFsKAL5xYAAAEFNahQBaZAAAAAAAAAAAExZAAAAAAJz0UAAAACAAAAAAAXiZQQAAAAAAAAAAAACLAiVAAAAAAEIoAAAAAAAAAAACCLYAAAAAAABaSYAAAAArqwAAAAAAAAAAAAACYtIAAAAAK3QAAAAAAAAAAAAFotAAAAAAAAAAAAAAhFTKAAAAAAIuYAAAAAAAAAAAAABW0oAAAAABLSAAAAAACtpQAAAAAAAAAAAABKTIAAAABW6AAAAAFoFaAAAAAA2wgigAAAAAAAAAAAAAAAAAAABaIspYAAAAAJE0uYAAAAABKlpYUAAAAAlpAAAAAAAK2lAAAAAAAAAAAAAAlJkAAAAEXQAAAAExaBWooAAAADXCYFaigAAAAAAAAAAAAACAAAAAtomBXUrYAAAACzKBFhAAAABMpQKaAAAAATJKAAAAAACtpQAAAAAAAAAAAAAAJklAAAAJmgAAAAAAAAAAALRMACtRQAAAAAAAAAAAAAJaQAAACZoACtRQAAAF8gAKaAAAATFoAEVWgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALRMAACtRQAAAAAAAAAAAAAJaQAACZoAACtRQAAF8gAAKaAAATFoAAEVWgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAINc6AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH/8QAJhAAAgICAgICAwEBAQEAAAAAABEBAgMwEkAgUBAhBDFgcBOAwP/aAAgBAQABBQLwx45vMQo7lrTYw5b4L4c1c9O1e+5TJjxcO7+T+Rw6FqxaMmK2OfXIQimKbzFVCEIQhCEIQhCEIQhCEIQhCEL4tM2EIxZL4b4ctc1Oxe25TJjxcPBdn8j8jh8IQhCEIQhCEIQhFqRaMmGcc8TicTicTicTicTicTicTicTicTicTicTicTiIQhCEIQhCEIQhCEIQvmmPnMViI7lpfjiy2w3w5aZqda9tyZjxcO7+R+Rw6lqxaMmOcc+sRTHN5iqhCEIQhCEIQhCEIQhCEIQhCF8WliEIQhGLJfDfDlrmr1L2EIQhCEIQhCEKZMeLh3fyM/D4QhCEIQhCEIQhCEIQi1ItGTFbHKEIQhCEIQhCEIQhCEIQhCEIQhCEIQhCEIQhCKY5vMViI7lrPTiy2w2xZa5q9K9tyMePhqXT/Iz8OxasWjJjnHPTQhCEIQhCEIQtURER3LWevFltitiy1zV6F778ePj3c+fj2rVi0XpNJ/hbS9uLLbFbFlrmrutbcjHj493Pm49y1YtF8c0n1CEIQhCEIQhHE4nE4nE4nE4nE4nE4nE4kVZjwVrGf8b/mcTicTicTicTicTicTiYfx5zWpipjqhCEIQhCEIQhFqMQhCEIQhCEIRWiKyxCEIQhCEIQvlCEIQhCEIQhCM2CMhxQhCEIQhCEIQhCEIQhCEIQhCLUraL4uE8TicTicTicTicTicTicTicTicTicTicTicTicTicSKQTSDicTicTicTicTicSJi0aluiJkx4uHwmZ/x/wDnrw4LZrUx1x16FqvZWq+Ynu5sH/QS7lqxaL45pO1C1IiohFq6qzNZiYtGhbkzHi4eCZnwf89OHDbNamOuOvStR6q1XjE93Nh/6CXctWLRfHNJ2rVEeM10qSs2rMTyjqKZMeLh5JmfBOPzw4bZrUxxjr1LVeitV5xPdzYeYpjuWryi+OaTtWiI85r5r5raazExaOl+zHj4aP2Z8HDxxYrZbUpXHXrWq/KIWmJ7ubDzP13LVi0XpNJ6cRpmPJfXzWZrMTyjoKZMePhqTM2CafOLDbLamOMdexarF8Iiq1xPdzYeYpjooWq1eUXxzSdq8IqIQhCEIQhE1EIUnEXjW3EiXvTMePhsTM2HgYsM5bUpXHXtWq/iIW2J7uXFzEu5asWi9OE74jbMfC0VnjMKYQhCEIQhCEIRjiK7kUrWsdz6e2forZ7rWRExO7Nat7IQhCEIQhCEIlQVs92SYSEIQhCEIQhCEcRCEIQhCEIQhCEIQvOszWYlxriq+K2W+J7cz8Rsn6J+/ilnsvZfETMES9mXLy3TKJmbfFLPZey+VsiO3WZqRL1RVeFbLfE9mZ8InVP0T9+FLPVey8ImYIl6suXlun6Jl+FLvVey3ru1makfYhCEIQiKryrO+J687p+ifvyrZ6LWXlH0RL0ZcnLdP0S7CEIQilnovbj5IQhCEIQhd+szUiX5RC0VtvierOiPL9H70Vs/K1lohwRL8suTlumUTL0Us/K1loQvJehieJExPhELVW2+OnPZrZ+FrLVEoiX4ZcvLdMxBMvVWz8LWW+I7CEIQhCEIQis8SPsRFVsrO+OjPbrL+LWWyPoiX8ZcnLdP0TL2Vs/i1lsQvhdlCEIQhCEIQiriYqt1Z3wIQhCEIQhCEL4kQhCEIQhCEIXQ5fW2PoyZOQhCEIQhCEIQj9E/YhCEIQhCEIQjn9CEIQhCEIQhCEIQhCEIQhCEIQhCEIQhCELbjx8ehWdsRvn1lrPdKgmXunvIWu+PjqRjx8elWdcRvnpLtWs90/RP3unuoXytd6cdNKcepE6YjfPrLWe79E/e6fWovTiIQhCEUx8etE+cR6Vda0vd+ifvzWlCEIQtKEIQhaFoXmvpCFBbHxEIRxKY+IhCEIQhCEIQhCEIQhCEIjxXQQhCEIQhCEIQhCEIQhCELqWliEIQhCF8oQj9E/YhCEIQhCEIQvhCEIQhCF8IQhfCEIQhCF8oQhCEIQhCEI4iEIRWjOMLwtXj80px7UT761nu/RMv3VavzRakwUx8e5AvVroWl75+9q9YitH6atX0F6y1VuQt0x3Vuir9NWr99aq3fvfMepiH6atX1F6yarSvFMW6Y7CFuirF6WtX1l6yarchbl11uiH6atX/AAkwu7MdVboh+mrV9pesmq+ULUhbpgQhCEIQhCEIQhCEIW6Kv01avuLurciarchbl0ELdEMXpaU5CX8vPejfGtCEIQhCEIR9b6UfxMMXultn73T0F3/rVnwdmlH4TD/hpl7pn3efAIQhCELoUo/KYfeQhCEIQhCEIQhCEIQhCFoQhCEIWlCEIQhddCEIQhCEIQhC+EIQhCEIQhCFszYerSj0TD/gp+90+/zYenWr1TD/AMPzYejWr2THvpl/yOfHFRCEIQhCEIQhCK1e6Y7y6CEIQhCEIQhC6SEIQhCEIQhC+EIQhCEIQhCEIQhCEIQhCEIQuxe8Ui95vMTu/RW3L1UR8THdmP4iI0XvFIvebz8RO79FbcvURHhMd2Y/hojRe8Ui9pvPS/RWz7q0rymO7Mb12F4rSheK0xGi94pF7zeep+itn6yY7sx6yY60RotaKRe83nrforL9EtUx3Zj0SFpmPFb4jTe03nVG+sv0C2LuzG9dFC1r5Qty60Tviz/iZj3kR11vrX1q/o5hdqtf8RyY+ZMLsVr/AInekWEutFf8VvTkfrqRH+L3pyP10oj/ABm9OR+uhEf43l476p/0qF7S9+PRrb+liPiY9lay6dZ/iVvWpC8FqQvS2surE/zC0ryXrrWXXj+YXmtC8l6a1l2Yn/wmv/cy/wDi+f/EADARAAICAQMDBAEDAwQDAAAAAAARAQIDEiEwEDFAIDJQcUEiQmAEYXITUWJwgKHA/9oACAEDAQE/AfRe+iP7kuZc+bEGbDTPTRczYL4L6LeWuadt5MmSb/4+b/T/ANPr/Xf2+BS9sdtVe5hzVzVce7y1zxHpQhF7aY/uS5lyIQhCEIQhCEIQhCEIQhCEIQhEVEIy4KZqaLmbBkwX02Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3FIuXsZLzfb9ohCIhmk0mk0mk0mk0mk0mk0mk0mk0mk0mk0mk0mk0n9P/Tav139ppEIQhCEIQhCEIQik2x21V7mHLGWrj3eShCEIQhCEIQhCEIQhC63vFY/uTLlz8Fmw0zU0XM2G+C+m3wu0GTJq2j2+qN/HwYNf67+3xKXtS2qvcw5a5quPd468ZF7afslzLkQhCEIQhCEIQhCEIQhCEIQhCEIQhCEZsFc1NFzNgvhvpsIQhCEIQhCEIQhCEIQhCEIQhCEIQhHYyWm+0e00mk0mk0mkUwRDEIQhCEIQhCEIQhCEIwf0+r9d/aIQhCEIQhCEIQhCEIQilrY7aq9zDlrlq493irnXBa2n7J33nhXiRAhCEIQhGXDTNTRczYbYb6bCEIQhCEIQhCEIQhC5uxkvq2j2iEIQhCF4uDBq/Xf2+RS1qW1V7mHLXLVx3EIQhCEIQhCEIQhCF5EzMy582I48uKuaumxlxWw2028/sZMmraPb5uDBq/Vb2+VS9qW1V7mLLXLVx3+XQhCEIQhCEIQhEVEIQhCEIQhCMuGmaumxlxXw30W83sZL6tv2iEIQhCEIQhdUIQhCEIQhCEIw4NX6re3zKXtS2qvcxZa5Ycd/OiBCEIQhCEIQhCEIQhCEIQhCEIQhCEIQiVEOexkya527GHLF/029whCEIQhCEIRnzUw1c+4yWtltrv3NJpNJpNJpNJpNJpNJpNJpEaTSaTSaTSaTSaTSaTSKC2/0TQ0mk0mk0mk0mk0mk0mk0mmDSaTSaTSaTSaTSaTSaTSaTDk0bT7RRO8CgUCgUCgUCgUCgUCgUCgUCgUCgUCgUCgUCgUCgUCgpM0tqr3MWSuWrjuIQhCEIQhCELohCEIQhCEIQi39itvxPHNZjYQhCEIQhC55UQ57GTJrn/j1xZde1vdx5s1cVXPcva2S2q3cQhCEIQhCEIQhCEIQhCEIQhdLS/oQia8aF4OLLo2n2m07x5SEUtaltVe5iyVy1cd/KtP+3WtvxPFMMmF487byZMk3/xEIXTFk17T7hCEIQhCM2WuGrnuXvbJbVbv4iEIQhCF0tL+hCEImohCEIQhCEIQhCEIQhCEIQhCEYsmjafad94EIQhCEIQhCEIQhCEIQvVS1qW1V7mLJXLVx38i0sQhCK2/E8U11ExMeLO28mTJN5/4+rcxZNe0+715csYque5e9sltVu/m2mZ+hSKRSKRSKS1JFIpFIpFIpFIvFxZJptPtO+8CEIQhCEIQhCEIQhCFwUtak6q9zHkjJDjv40z662/E8UwyYXhzK3kyZNf+PDjyatp93py5a4que5e9sltVu/mzL+uGYXm4sujafad948ytppOqvcx5IyQ47+JM8NbfifTMo1bv0TVk1mPBnbeTJeb/AOPFuYsmrafd1y5IxVc9y9rZLard/JgXWZmfriRNeqkUikUikUikUikUikUikUikUikUikUikUikUikUmLJNNp9p33jzKWtSdVe5jyRkhx38KZ462/E9Z2Jl+qYZMLnaMmTXt+3lx5de0+4y5a4que5e9sltVu/mzL+uWaiF5eLJo2n2nfzK2mk6qmPJGSHHfwJnlrb8SSoJl9UIQhE1ZNVKnmyVm0bc+WLWnVO4hCEIQhCEIQhCEIQhCEIQhCETzRDJou3YQhCEIQhCEIRWjJqhCEIQhCEIQhGKk1rvzVrNpUGTDp3jsIQhCEIQhCEIwUtq1/jnmfJmNUExMSua+N7x3570W8dvLnouSImSIXS1V9clav66TDJiY5MWLT+q3u5q1m0qCtYrCjplxL9Ve3JjxTbefaLpG/JM+XMMmJjmvR7x3FyotRfXkz6JjiiJkiF6LVXFWr+hdZhk1mOFSYsWnefdzVrNpUFaaYUdUZMS/VXtxY8Wrefb6Y34pnzZrEkwubJje9e5ubm5ubm5ubm5ubm5v0tRfXjz6kIQhCEIiGRCEIQhE0EIQhCERRiEIQiasmqEIQhCEY8Wnefd6kL11rqlQVrFYUerJjW9e3Bjxat59oo5p8+YZNZgQhC4r0e8dxCEIQhCEIQhCLUX0IQhCEIQhCEIQhCEIRIhCEIQhCEIQhMiFwWheqIfBMMnb1Y8enee/ChCEIrWbSoK00woEIQhCEZMa3jt6sePVvPtO3NM/AzDJheieK9HvHfntVfXhzPMmJcVqreO3orV/XFMRJML0YsWn9VvdzVrNpUFaxWFHFkxreO3opj1bz24439Ez8HMMmF0nkvR7x3EIQhCEIQhCEItRfQhCEIQhCEIQhCESIQhCEIQhCF0TFyWqIivJMMmqEY8Wnee4hCEIQhCEIXStZtKgrSKwhCEIQhCEIRkxreOwjHj1bz7TtzT8LMPohCEIQhCEIQi9HvHcQhCEIQhCEIQi1VOwhCEIQhCEIQukiEIQhCEIQhCEaRCEIQhCEIQhH+nv/YQhCEIQhCEIRpZTDp3nuIQhCEIQhCEIiszJWsVjm/0XL/aIQhCEIQhCELpLEIQhCEIQhCF1QhCEIQhC5bS/rwL0/MctrfiPm6UW89xcsQ5UEViOaIfSY5YjwInjrZ/fHaz+vCvR7xx2t+I558NCEIQhCEIQhCEIQhCEIQhFKLee/NETO0Fa6Y9CEIQhCEIRFfRMccRzz1ieOJf3w2s/rxLU/McNpW0fN0ot57+lC4IhkREc0Q/VMcMR6ULmjhYytn9+u1n9CEIQhCEIQhCEIQhCEItT8x67WX34CEIQhCEIQhCEIQhCELx6UW89+aIZELmiOCY9cRwLgQhCEIgQhCEIcPT+RC6RL+/TaX9CEIQhCEIQhCEIQhCEIQi1HvAhCEW27CEIQhCEIQhCF0QhCEIQhCEIQhCEIQhCEQaTSaTSaTSaTSaTSaTSaTSaTSaTSaSmNbz3EIQhCEIQhCERVkViBCEIQhC6IQhEVEIQhCEIRNRCEIiohCEIQhCEIQuOPTe+n7N2/z6Yl/fWZf15Vq/mOtrLaPiYnwq1W89+aIZELmiOSY6xHlRPW99P2TL3n1xLLS/rzLV/MFrLbwEIQhCEIQhCEIQhCEIQhCEIRHgVqt5782mZ2Ihc0RzTBEc07dEIQhCEIQhCIL30/ZLneRC+DtKLV/POvjK2f3zRKIU80T5s781rRX7+GtZffWY+brZ7T35olES+aJ8uZfNay+/hpnohCJr/tzoQhCEIQhCEIQhCEIQhCEIQhC6IQhCEIQhCELpWX98aEIjYhSIQhCEIQhCEIjyZl81rL78BeNaV9+uY518ZWz++aJREvmifHnfmtMR9/DTPDMfN1l/fNGxG/NE+LMvmtZffiIQhCEIQhCEIQhCESIQhCEIQhFq/mPAQhCEIQhCEIQhCEIQhCEIQhC6IQhCEIQhCEIRE80bEbiEIQhCEIQhCI28OZfNMr76IQhCEIXm2shjGMYxjGMYyYEIQhCEIQhCEL42sv76oQhCEIQhCI2I35onwZ3EIQhCEIQhCEWlffGhCEIQhCEIQuiEIQhCEIQhCJEIQhCEIQhCEIlCEIQhCEIQhCEIQhCEIQhCEIQhCEIQhCEIQhC6IQhCEIQhCEIXPXniOeeeY33EIQhCEIQuVC55iJFyT81EMisQIXJFWLmiPARMc01iSariy4v3V82YfHM/NRDIhc0Q+eI8OY5phkwuLJj/AHV4UIQhCEIQhCEL1zuIQhCEIRIhCELohCEIQhdEIQheQhCEIQuCIZFUIQhCFwRViEIQhCEIQhCERUQhCELnmOaYZNUIQhCELrkx/ur8JMv5pMiFzRD54jx5jmmGTC4r0/MeavTM/NdyIXEvREPniPJmOaYZMLivT8x5sw+sz813Ihc0RzxHlzHN3JhcV6R3jwEIQhCEIQhCETBIhCEIQhCEIQhCEIQhCEIQhCEIQhCEIQhCEIQhCEIQhCEIQhCERVCEIQhCEIQhCNIhCEIQhCEIQhC82YEIQhCEIQhCEJiEIXpmUTL82Z+cieeJ8yZInlmeeZ4JlEy/NmfnonnifKmesTxzPPM8EyiZfpQhCEIQhCEIQhcUiEIQuJCEIRMeAhCEIQhCEIQhCEIQhCEIQhCERxoQuOIEIQhCEIQhCF0n0xIhCEIQhE7c8zwTKJl/CTHBEeuY+MifGiOeZfrifXMrnmeCZRLkQhCEIQhc69MzwyIQhCI4UIQhdUIQhCF58c8CEIREc87iEIQhCER6Z6IQhCEIXqmRCEIQhCJl+UhCJEIQhCEIQhCEIQhCEIQhCEIQuqEIQhCEIQhCEIQhCEIQhCEIQhCEIQhCEIgQhCEIQhCEIQvOies8qET8NM/wiJ8+Z5YkfPM/DzHzS5lzxHxk/wA+iPmZh/zGI+bmH5y8hCEIXRCEIQhCEIQhCF40RzxHioXw8w/v+PRHPEfPzD+/45Ec8R/AZh/f8aiOeI/gdv8A3/J453zT8NMr+UxPLMvpE/JTK50IXRCEIQhCEL4BCEIQhCEIQhC8VCEIQhCEIQhEcc7iEL5GZXrQhCEIQvBQhCEIQhCEIQhCEIQhCEIQhCEIQhCEIQhCEIXRCEIQhCEIQhCEIQheBAhCEIQukz6WRuIQhCELqhCEIQhCEIQhCEIQhCEIQhCEIQhCELpM/wAvj1zvwRP/AFZE/wAJQhCEIQhCEIQhCEIQhCEIQuqEIQhCEIQhCEbfxZf+Yy/+L8//xAArEQEBAQACAAUEAQUBAAMAAAARAAECEgMQITBAIDFQcWAiMkFhcEJRgcD/2gAIAQIBAT8B+jeWZM/M45mfu58ePPDbnw3hpvy+PH/O+85l4nid/TP7Zs1+X4Xh9v6uX2+A2cs38U/SzNvIu0zdpmZmZmZmZmZmZmZmfLiZ+5ycuWceeG3PN4abMzMzMzMzMzMzMzMzccfXZmZmZmZmZnMvE8Tt6Z/b5t2mZmZmZmZmZmZmZmZvC8Pt/Vy+0zMzMzMzMzMzNnPNu12u12u12u12u12u12u12u12u12u12u12u12u0zMzMzMzMzdpmZnz3Sd+l+P63HD9/Ty45zw258eXDTfj8OP+d95LxPE3l6Z/b9LPx/C8Pt/Vy+3xGzX8M+xukzMzMzMzMzMzMzMzMzM+XHMz9zMzM3LOPPDbnx3hpvxeHH/ADszMzMzMzM3bM9dvE8Xt6Z/bMzMzM/F8Lw+39XL7TMzMzMzMzMzMzMzZyzZmZmZmZmZmZmZm7TMzMzMzMzN2mZmZm3kTMzMzMzMzMzMzMzMzMz5ccP37PLjnPDblx3hpvw+Of52ZmZmZmZmZufidv17LMzMzMzMzMzMzN4fh9v6uX28mZmZmZmZmZmZmZmbNfwTMzMzM/O48ev79vlxznhty47x034PHi+u+/4nidvTP7fm+F4T/Vy+3y81/FMzMzMzMzPwOPHr+/d5cc54bc85cNNmZmZmZmZmbjj67MzMzMzMzM3PxO3p/j5vh+G/1b9vJmZmZmZmZmZmZmZmZmbOTMzMzMzMzMzMzMzMzMzMz7bs7Ozs7Ozs7Ozs7Ozdrtdrtdrtdrtdrtdrtdrtb4m7vpeH43b03+67Xa7Xa7Xa7Xa7Xa7Xa5+L0z/dy58uWuzMzMzMzMzNx5npv2mZmZmZmZm3k3PNz1z7Xbbtt227bdtu23bbtt227bdtu22buzMzMzMzMzM3h+LvH03+27MzMzMzMzMzMzMzMzMzdrtv3s5t2u12u12u12u12u12u12u12u12u12u12u12u12u12u2zdrtdrtdrtdrtdrt7rMzMzMzMzNvJmbw/F7em/eZmZmZmbn4mcM/3cuW8td+Dx5H6mZmZmZmbdfPlxPXPt7jmXa7Xa7Xa7Xa7Xa7Xa7Xa7Xa7Xh+L19P8Azdsu12u12u12u12u12u12u12u12u12u12u12u12u0+WaWcmZmZmZmZmZmZmZmZmZmZmZmZmZ82ZmZmZmZmZmZmZmbeT9Ph+J29N/u9nnzzhn+7eW8tdmZmZmZmZmZmZs5e0/Ty4nrn2+b4fidfTft8lmZmzkzMzMzMzMz5MzMzMz9bMzMzPl6eTk5OTk5OTk5OTk5OTk5OTk5OTk5OW8n6m8PxM5em/3Tk5OTk5OXPxM4Z/u5cu2u/F48j9exu/Xyw9f8fN4eJ19N+058ZycnJycnJs3NnJycnJycnJycnJycnL09hycnJycnJy9Pa3H4u6+z4fidvTfv9PPnnDP927vLXfj8eR9W77PLieufb5vDn19N+3zs1/CMzab5MzMzMzMzMzM26+3w8Tt6b95m5+JnDP928u2uzMzMzMzMzMzMzMzM3HkTM5MzMzMzPlyw9c+0z5ek5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTlw8Tr6b9pyZmZmZmZmZmZmZmZmZmbNzffZm7ZOTk5OTk5OTk5OTk5OTk5OfG3fd4c30373Ln1z/du7y135ecj9eT7vLieufb5vDn19P8fOzX4D+FbeTMzMzMzMzM28t312ZmZmZmZmZmZmZmZmZmY097Hbnw6+ufb3uHDt679rlm8d97w+O8ePrMzMzMzMzPly/1MzMzMzMzNxfvMzMzMzMzMzMzMzMzMzMzMz8TdfxnHjmfucuWZvuZ62GeXPgeufb3OHB9d+3luZyw25Z1319zw+HX137+/wCnlyz/AOPczPN9x8n8JvJ/GccP39G57WY2YfRz4Hrn29rhwfXft9G5nLDblnXT2vD4dfXfv8Pc/wA+1mfhGZmZmfJmZmZm3kzMzM/huOH7mZmbZnJycnJycs9bPSdnZ2dm58T1z7TMzMzcOL679pnZ2dnbf6sNteOzMzMzeHwPXfvMzM+TMzMz7G5/mZmZmbPJmZmZmZmZmZmZmZmZmZmZmZmZmZmZn2N1/GccP37HLH6s9bDJmZmfLnxPXPt9XDi+u/byZmZm03Dbc6/V4fDr6795mZmZt+v7exyw9ZmZmzyZmZmZmZmZmZmZmZmZmZmZmZn4G6/i/W44fv2tx+jPWz09rnxPXPt9HDi+u/b2txwtzeP0cOB679/f+3tcs/MNvJmZmZmZmZ/AccP3MzMzMzM2zZ62ekzMzMzM+XLieufby48X137eTMzMzMzb624eXDieu/f3X3uWfQzMzMzMzM27MzMzMzMzMzMzMzMzMzMzMzMzbtvJmZmZmZmZmbtdrtdrtdrtdrtdrtdrtdpmZmZmZmZmbMJmZmZmZmZtsMmZmZmZmZm64/6mZmZmZmZm0248evrv3mZmZmZmZnyz0mZmZmZmZm2ZmZmZmZmZu0zMzMzMzMzMzMzMzM+7vJ/X4zjh+/xmZ81/FZyZmZmZmZt5P6mZmZmZmZmZmfdZmZmZmZmfLMJmZmZmZmZmZn5eTPmzMzMzPmzPtszPusz7jM+bMzMzPlm+zuv4zMP3+MzPmv41s1+veT8dmZmZmZmZmZmZmbM+MzMzMzMzMzMzMzM2TMzM+bMzMzMzMzMzMzM+yzMzPtMzMz77MzZymZu1vJmZmZmZmZmZmZmZmZ+l9/PSZmZmZmZmZmZmZmZmZmZmZmZmZmZmbNu23bbtt227bdtu23bZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZm7TMzbyLtv05vnuv4zM/mu6ebMzM28mZmZmZmZmZmZmZmZmZmZmZmZmZmZnyzPmszMzMzMzMz5MzMzMzMzPmzMzMzMzMzMzMzMzMzPmzMzMzMzPwd38Nu2b77Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Oz77/AAjd/Dbvnm/8HZmZmZ8mZmZmZmZmZmZmZmZ+lmZmZmZmZm7Wa/jGZmZmZn4rMzMzMzMzMzMzMzMzMzMzMzMzMzMzM/gN342a/NfffrZ2dnZ2dnZ2fyb+GfkZv4zN/gD+GfbZmZmZmZmZmzX8YzMzMzMzMzP4JmZmZmZmZmfaZmZmZmZnyZmZmZmZmZmfdffzlMzMzMzMzMzMzMzMzMzPkzMzMzMzMzPvszMzMzMzMzMzMzMzMzMzPvszMzMzM/M3kTMzMzMzMzPwGZmZmZmZmZmZmZmZmZmZ+AzMzMzMzMzMzMzMzMzM+TMzMzMzMzPkzMzMzMzMz7js7Ozs7Ozs7Ozs7OzPu8uR+/LNJmZmZmZmbM82ZmZmZmb182ZmZmZmZmZmZmZmZmZmZ958mZmZmZmfgM7MzMzMzMzMz5MzMzMzMzPmzMzMzN4Pjf8Ajn8nlyP37+Z/wTwfG/8AHKZmZmZ9/lzP3MzM3afYz62ZmZn2GZmZmZmZmZmZmZmZmZmbtMzMzM+wzMzM/AfrZmZmZmZmZmZmZmZmZmZ9vwvG/wDPL4vLkfv2M0+vM9jfxj/FPC8X/wA8vf36OXI/ftZpP0Z7W/yd+T4Xi/8Anl8HlyP37maeeZ7m5/wDwfE3f6dmZmZmZmZmblp72aWTMzMzMzMz5MzMzMzMzM3adnZ2dnZ2dnZ2dnZ2dnZmZmZmZmZmfJmZmZmZmZ8mZmZmZmZmZ+azMzMzMzMz5MzP08eO89MuPHOGGfA3D3cz4Gb7u75Zr83N/iHHjvPTLjxzhhnwtw9vM+Fm+3u/Rmvzc3+HceO8tMuOZwwyZmZ9/cPZz6WZmZmZn6mZmZmZt36WzX5ubMzMzMz9bMzMzPwGZmZ9pmZmZmZmZmZmZmfLjx3lplx45xwz424fhN32M1/k/HjvLTLjxzjhnsszMzMzMzM+W+kzM+0/U+y/Tu+1nKZmZmZmZmZmZmZmZmZmZmZmZmZmZn2GZmZmZmZmfaZmZn2mZmZ9njxzjhnuMzMzMzMzPluHnnynz3fczfhMzMzMzM2cvoZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmfoZmZmZmfmbhZnzd33s35ub+c3fw28rN+bvv5v8Xfc+/yuXLzzX8Zm/wAi4c+v6vv8jly+nNfxmb/IuPPeP6l+NvL62dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ29fiszs7Ozs7Ozv8Kfg8eXX9X3+Ju/8AF+PLr+r7/C3f+M8eXX9X3+Bu/wDG/Df/AK9/d3+TM/lOPF/Xwdz/AIzx4v68mZmZmZmZn6dz8wzMzMzMzMzMzPwGZmZmZmZmZmZmZmZ8mZmZmZmZmZmZ8mZmZmZmZmZnzzHyZmZ9pmZm3Pwz+EZmZmZmZmZ/PZj8ffmv1v8AL8x/7EzOz8B2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2fgs+4/8AcN38bm/zxt38fm/KfNn/APF8f//EADYQAAEBAwoFAQcEAwAAAAAAAAERAkBhABIhMDEyQVBwsSBRYIHhYhBCUrLB8PGAgpHRIqHA/9oACAEBAAY/AuCGJkgD7CU9juMGhyMpzP7mcWS9oK/1bPsxi/ifh8uCEUShgfvHMoYmSB9h7Z7HcYNDkZTme4xZLygr1N7Z9mMXsT8PlyQiiUMDmEMTJA+w4ZzPcYEcjKcz3GLJd0FfTe2fZjF7E/D5dEMoYHLoYmSB9hxzme4wI5GU5nuMWS6oK9Te2fZjF7E/D5dkNkoYHLIYmSB9hUzme4wI5GU5nuMWTyLmgr/Vs+zGD/lifh8vCGUMDlaB9hVzme4wI5GU5nuMQXFBXqb2z7MYvYn4fL0hlDA9DQrZzPcYEcjKcz3GIPKvQV6+9s+zGb2J+Hy+IZQwPQaASpCk2/1Kczc+XxWoKALzXLzIMshAP9xMa9ca6L8ov7wMkIfEIlDAvtlWoeUEvV90D2UynM3Pl8VaCgC81y8yDLIQD7UuMayOQKL+8DJC+IZQwOUKHdBL1cM5m58vipQUD3muXmQZZs3iXONVHIlF/eSF8Qy2OUKHVAJer7o4rJTmbny+ONBQPea5eZBlkUbxLrGojkii9vKx8QiWxyhQ5oJerapnM3Pl8cKCz3muXmU1mz7pLvHLFF7d9Qy2OUKHL1bVc5m58vj2oLMTy8ymsijeJeY5YovbvqGWxyiivSXqrVZubSQWYnl5lNZFH3SXuOWKL276hlscoUV1P88q+ayEyxDbXRr6P5516Gum2nbKVFchztDbvWILa+azd38OKG2sQW7ZyhztDbVILa+aLu/hzQ21VFuWqK6nO0NtQgtr5ou7+HVDbUR64Q28VFtfNZu7+HdDbxRz+nO0NvBGvms3d/Dyht4I9eR9ka9BZv4e0NvsjmlH8dFRr0Fm/h9jmq47OFPRCCzoeFXHZyp6GQWdEwqY9MILOi4ccemEFnRK8EOGO3TCdEwkmHDD2x26YQdEwqY7dMUdEwyaGeQ6uU2Z9D9C1NmlUNKofpUhpUps30RnsfuZ+oeVNm+ic9juPqHdTZvorPY7j6h1U2aLz2O4+oc4aMzme4+ocYaNzhQuH9aVKfzJT2HJwjoyp/MlPYcnKOi6n8yU9hy0pU/mSnsOWlKn8yU9hy0qU9hy/Tip0QQvSnRH1SQvCnROOlkdLI6WR0sjpVFxTRmOlUdKo6VR/wCYL//EAC0QAAMAAQEFCQADAQEBAQAAAAABEVEhMDFBYeEQIEBxgZGhsfBQwdHxYHDA/9oACAEBAAE/IY8MjwyPDI8M3OaXgfC5i1QJGpHhkeGR4ZHhkeGR4ZHhkeGR4ZHhkeGR4ZHhkeGR4ZHhkeGR4ZHhkeGR4ZHhkeGR4ZHhkeGPRV6JDjc0m5f2xMpdz7kIx9C5pvcz0D5YfHxcM/F45LmRkZGRkZGRkZGRkZGRiYSSbb0SJK1W3v6InIaaIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyM1T7uL+K/YavV2kZGRkZGRkZGRkZGRkZGNdzfHNYaJNpt+Hv5PCEZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZH2RkZGRkZGRkZGRkZGRkHcMjwyPDI8Mjw+5+MEv8AwXMSpES7eiiiiiiiiiiiiiiiiiiiiiiiihqKvcOMJuWnuy/0L/QXma7mPmEY+he03ue1yD/p8SEIQhCEIQhCEIQhCEIQhCGXi8clzIyMjIyMjIyMjIyMjIxOJJVvchSbxt7+iIQnYhCEIQhCEIQhCEIQhCEIQ1j9ei/gv2EbKKKKKKKKKKKKKKKKGO5vjmsNEK9W9f8Axor9Cv0K/Qr9Cv0K/Qr9Cv0K/Qr9Cv0K/Qr9Cv0K/Qr9Cv0K/Qr9Cv0K/Qr9Cv0K/Qf5pt//APfefiIh2Bf+C5iMiJERERERERERERERERERERERERERERERERERERDiVe4rxwL+2RERERCo/ky5SP0Ery3vyD/p8SIiIiIiIiIiIiIiIiIiIiIiIiIiIiISs/F45LntkzJJVvRJCkupt7+iIiIiIiIhEREREREREREREREREREREREREREREKp+K/gv2G/VkRERERERERERERERERERERERDeup/HNYaJl6t6/+NeHZERERERERERERERERERDhERERERERRji/8FzFqQkkUUUUUUUUUUUUUUUUUUUUUUUUUUUNRVtQY/wX9sooooooWH8meaR+gnv5P9A/6fEhCEIQhCEIQhCEIQhCGrrxeOXmUUUUUUUUUUUUUJxJat7kKTQbb39EQhCEIQhCEIQhCEIQhCEIQhCEzYr0/ofsIyiiiiiiiiiiiiiiiiiiihjWm3xzWGiJeret/jRfIvkXyL5F8i+RfIvkXyL5F8i+RfIvkXyL5F8i+RfIvkVyL5F8i+RfIvkXyL5F8i+RfIvkXyL5F8iuRfIvkXyL5F8i+RfIxBP+C5iFoJEIQhCEJ2QhCEIQhCEIQhCEIOJVuJDH5Ny/t7FVfyZ5pH6C+/k/8DPHwcdXXi8eXMhCEIQhCEIQgnbSWre5CUr1bj9FsZ2IQhCEIQhCEIQhCENR+EfvAl1b1IQhCEIQhCEIQhCEIQhBq10/jmuaIV6t5DX9NeDe3AAGiEIQhCEEJES8Y2kq9wx/gv7ezR38mfkfAuv5P9E/2vgYZOLx12yTbSSre5CF3jfiXjdez9H7w8UxLqfxzWGiaerep/jX8A3soQhCEIQhCE8Bok3jeV/Bf29qjv5M/I+BVfyd5piEIQhCEIQhCEJZOLx1IQhCEIQhCEIK3FW3uQla1b8S2sGpt9e3X6B45/gnmQhCEIQhCEIQhCEIQhCEIQg3Lqfw8rmiaerep/jRCEIQhCEIQhCDIQhCEIQhCEIQeznBOCcE4JwTgnBOCcE4JwTgjHyRj56kY+epGPnqRj56kY+epGPnqRj56kY+epGPnqRj56kY+epGPnqRj56lSob0S/Mc5Ly9VHvXl9lkW3eXWfB/RkY+epGPnqRj56kY+epGPnqRj56kY+epGPnqRj56kY+epGPnqRj56k8eiE99W4I+ZuG4s4sRgjBGCMEYIwRgjBGCMEYIwRgjAvQX+up5CMEYIwRgjBGCMEYIwRgjBr4CNf8Ax5CND3/fUjBGCMEYIwRgjBGCMEYIwRgiRCMEYIwRgjBGCMEYIwRgjBGCMEYFk0kXov2YY2ZsJpxp70zynlPKeU8p5TynlPKeU8p5TynlPKeU8p5TynlPKeU8p5TynlPKNdyfunlYY+TVbyH87yMfPUjHz1Ix89SMfPUjHz1Ix89SMfPUjHz1Ix89SMfPUjHz1Ix89SMfPUjHz1Ix8kY+epGPnqRj56kY+epGPnqRj56kY+epGPnqRj56kY+epGPnqRj5ENWtTfl5lX/pGPnqRj56kY+epGPnqRj56kY+epGPnqRj56kY+epGPnqfoCeGQhCEIQhCHoIQhCEIQhCEINEit6JIWtcbb3gINUaRNNRp6prDGNCt6tsP6MhCEIQhCEPIAjRP7bgj5gwb4s4t+B3pdXUnIhCEIQhCEvATqc/oQhfR7/vaQhCEIQhCEIQhCEFJppF7fswxuzRGnGnvTIQhCEIQhCEIQhCEIQhCEIQg3Lqfunlcyeerep1IQhCEIQhCEJ2kIQhCEITsS1e89B6Dir1RCEIQhCELpPTiuDRYH+p4ZCEIQhCCEIQhCEIQhCEE7LUb3IUterb3jku40RppNNRp7mhrajZ+rYf0ex3YHgaJ/bcEKDie7cWcWyEIQhCEIQhCEIQhDeF1E2EpvD6O7bR7/vxq000i9Fw+eGNmaGmnGnvT8PCEIQaNdP45rmST1T8hr/SEIQhCEIQhCdiEIQhCEIR1e/u8ZexCEIQhCHIfsy6Sc4qPU3we2q5Mjw/Yjw/Yjw/Yjw/Yjw/Yjw/Yjw/Yjw/Yjw/Yjw/Yjw/Yjw/Yjw/Yjw/Yjw/Yjw/Yjw/Yjw/Yjw/Yjw/YTiYbeiUJa1W3vHIR4I8EeCPBHgcGnSajTWjQ0g2z5tth/RkeH7EeH7EeH7EeH7EeH7EeH7EYNO8Wif23BEoye7cWcWyMjIyMjIyMjIyMjIyMjIyMjIyMjN6S/wBdSPDI8EeCPBHgjwR4ZvD6O9qX0e/72GpGRkZGRkZGRkZGRkZGRkZGRkZGRkc4vsuHzGw02TWjTWqZHgjwR4I8EeCPBHgjwR4I8EeCPBHgjwR4I8EeCPBHgjwR4ZHh+xHh+xHh+xHh+xHh+xHh+wljDT5ap5XMhGm0+Bo1/pHh+xHh+xHh+xHh+xHh+xHh+xHh+xHh+xHh+xHh+xHh+xHh+xHh+xWH7EeH7EeH7EeH7EeH7EeH7EeH7EeH7Dd7T9iPD9iPD9iPD9iPD9iPD9iPD9h29J+UI8P2I8P2I8P2I8P2I8P2I8P2Kw/btol6rJ+wLk/BpNkit7kLSvVt7+i2DSRpqp6NPc0MfeN7th/R93doXyE/tuCEBET3b4s4t+H3hdXXvb89/wBbG+j3/fjULu19lw+eGNNmmo1o096fjGBFT+Oa5kA9U/U6+E4j2PHXqu6lSNHyRkZGVy9VkV+BcmRkZGRkZGRkZGRkZGRkZGRicSSre5C0r1be/otk0Rpqp6NPc0PYVvdsP6MjIyeIvlp/bcEIkCe7cWcWyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIxupL/XUojKwb89/0RkZGRkZGRkZGRjno9/2RkZPFIXdr7Lh88MbjTUa0ae9MjIyMjIyMjIyMjIyMjIyMjKKIyMjIyMjIyMjEsRo/dPK5kI1V6DX+kZGRkZGRkZGRkZGRkZRGRkYze0VgrBWCsFYKwVgrBWCsD96XmVgrAnOQTLgVjusardxWRa3/AI89umZIq3okJSuNt7xyW0aI00mno01o0Xyre/K+WGSxF4eif23BCRAnu3xZxb8XvC6upOHE357/AK2t9Hv+/GrWSS/qfPA6NNRrRp8H4xgRV9PK5jIPd6D67aEIa8drx17CVEi7YQhCDaL1WR6jT65PagAahzYr0CEIQhCEIQhCDRppxp6NNaNcyTCquLjeNerfjeJPXbMkp8cc+pCEIQhCEIQhBelR/Bf9FNXqsEIQhCEIQhCGi1cSRUqotM+fyXDbAAHTX7Z5C20E+H+eZCEIQhCEIQhC0yTPx+Y5/wCbUAD80Em/bgAErdPbuCPDI8MjwyPDI8MjwysSc4rIr9C5MjIyMjIyMjIyMjI3wZ6wjHYHB46dkZGRkZGRkZGRkZGRmpbR7/vxbn5djNaPsjIyMjIyMjIyMjGisZtWa8z8h/0RkZGRkZGRkZGRkZ+YnU15jmr/AKKWr1WCMjIyMjIyMjIyM3avRLiO3xJvZRkZGRkZGRkZGRkZGRktf/RxWnyWCPDH+xPPUjIyMjIyMjIyMjI+xlq9XRq8BsuDI8MjwyPDI8MjwyPDI8MjwyPDI8MjwzitEZCPDI8MjwyPDI8MjwyPDI8MjwyPDI8MjwyPDI8MjwyPDI8MjIyMjIyMjGlW7isiFq/5yexjZ6wjIyMdgcHjpt6acfsjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyFPLuQ0e7ZNFYzavufkL12X5ide45q/6LWr1WNju1eiQ59ym992jIyMjIyMjIyMea/+j3r9FjufADz12Uc3vxzZq9WRkZBprZJiMjIyMjIyMjIyMjIyMjIyMjIyMjIQhCEIQYVbuKySv4XJnkPIeQ8h5DyHkN+mv0QhCEI6bn10IQhCEIQhCEIQpv3/AH4drpwIQhCC0IQhCEIaGpqVkIQhD9heuw/QTqQhCEHb1f8ARS1eqx39xy63soQhCEIQhCEIMlr/AOjsr9Fg8h5DyHk7HwA89SEIQhCCEi1f45sjerIQhDVwHPA8h5DyHkPIeQ8guUhCEIQhCEIQhCEIQhCEIQhCEIQaVbuKyIWruw357/rYQ0e766benn4Vr5bBpo93ebDrVkIQhCEZ+gvUhCEIbp0OpGQhCEIM2r/opavVYIQhDdqxuonEyhCEIQhO8haxz1+ixsPgB56kIQhDdVP6eY69XvIQhCE7DZEIQhBGEIQhCEIQhCEIQhCEITZvar1WRDV/ztnIX5tlDTc+um3a+fg2vlsk53G0huu7L9hevc3RdGyY9X/RS1eqx3PkB7aQ1/8ARz1+ixsvjB569xeldBv1exaTGp3OI9lPEgAY1W7iskLU/wBjsesIQhCEIQhCEdHu4cum3a+fgWvltE8kHoPUhCEIQhCEJzNyfV17NwXR1JzIQhCEIQhBm9T6iEq/52b1872tBktbHPX6LG0+MHnr2SxdHNjV1b1IQhCEIQhOw4IIwhCEIQhCEIQhCEIQhCEJtQAJEN3Tm6m+cfohCEIQhCEIQhDR7vroQhCEIQhCEIQglWu2AAQ1eW2AALQdb1216Pd/fntmbVfvMZw/HzbYAAcSst6/RY2wAClD8r/3mS6u7YAAl/hQAAACVaSVb3IQtav+JeAjpucHgiIiIiIiIiIiIiIiITt2vl4BoiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiI/YXp2aEREREREREREREREMisovssEIiIiIiIiIiIiIiIiGS8yChEREREREREREREREQzcKMiIiIiIiIiIiIiIiCRDRkIiIiIiIiIiIiGPVq+545MhCEIQhCCZtJat7kR1xv+JEIQhCEIQhCEIQhCOm5weNnXXgQhCEIQhCEIQhq8iEIQhCEIQhCEIQg/FHfgfQhCMhCEIQhCENCsZtX6LBCE2bTzIyEEQhCEIQhCDIQjFqQhCEIQhChQQg7IQhCEIQg0mo1U+BRVq+545PYJVxKti1r3/jkvCT0fo9jfV7vvbtfLwjXh9D9hem2bSVjVX3ZsWS8+8ndi3w7q0FrsIJTutXYwaNR6pjWq3tzxyfeAnbi1bFLXq/xyXhpaPd9EIQhCdjCEIQhCEIQhCD8MxCEIQhCEIQhCEIQhCE5m/H+uhCEIQhCEIQhBxK2UxCEIQnYhCEIQehOffARCEIQg++BJxFqQhCE7CU77oneTtuBf6kQQNyj1TLOLbc/6ZPMnmTzFWitF6nq/wCJEEEEEEEEEEEEEEEEEEEEEEEDzR7vohCEFRCEIQhCEIQhCD1IIIIIIIIIIIIIIIIIIIIIIIGIQhCEIQhCEIQhCE7N2bvvpsQCETIIIHErNTUggggggggjsQQQOLsQQQQQQR2FCCCCBwgggggggghEyCCOxPMnmTzJ5k8yeZPMnmTzGjyTzJ5k8yeZXxyf9I4BhO40mo9Ux7Zbc/6fYldFvN4f64LxU9Hu7Urt2/FNeC5A++mz3G/sbSVjGr2zc2ifa3s9xv7Ups2r2sf5P+kJJKLd2QhCDpRxpjTTVN6Z8mQVy/xIhCEIQhCEIQhCEIQhCEIQhCDzR7hX5EIQhCEIQhCEIPxjEIQhCEIQhCEIQbo3PvoQhCEIQhCEIaobipTVkIQhCEIQhCdh6EZCEIQhCEIQghkIQhCEIQhCEaEqJTauhj66Jv8A8QlFFu7YiIiIiIgoREREREREREREREREREREREREREREREREREREb81/roTG3ceNhERERERERERERERERERYRvSX+CIiIiIiIiIiIiIiIichq1IONGiIiIiIiIiIiIiIiIiIS+GpORERERERERERERERERERERERERERERERERERCV4CRERERERERERERERERER6QiWiSIiIi/gv2F6eCa/jN4XRs95u7Gg1NHtqarf4pKiU23pjd3YQhCEIQhCEIQhCEIQhCEIQhCEIQ3p7vvp3YQhCEIQhCEIQg48vGzbQ35bvohCEIQhOxJ3GgcbahCEIQhCEIQhCEIQhCEIQVCVEIQhCEIQhCEPSEIhCEJ/Ab91OhERERERERNuxEREREREREREREREREREREREREREREREREREREREREREREREREREREREREb4t30REREREREQiIQhCDRocEREREREREREREREREQ0IiIiIiIiIiIiIiIiIiIiIiIiIiIiIJFtvTES/hd4fV02LW3a8bNvvy3fW2aTGptqeESokW29MRLh4NeC3h9XQmyhCEIQhCEIQhCDjxrW2hvi3fRCdpCEIQhCEHY421PBgAAKSEIQhCEIQhCHoCTYwhCEIQhCEIIhCEIQhCEIQhCG9Pd9kIQhCEIQhCE20GIQhCEIQhCEIQhCEIQhCEIQhBiEIQhCEIQhCdjfOH0Qm0dDghCEIQhCEIQgzCEIQhCEIQhCE7CjbeiNBCEIQhCE2yd2zdT04Ob/we43TxrXjZtnEtdxEQhCEIQhCEIQVepCEIQhCEIQhCDlIQhCEIQhCEIQREIQhCEIQhCEIRNNnGCMEYIwRgjBGCMEYIwRgaQiIhRkREREREREREREREcuPz0IkJ8w44EREREREREREREREOcOxQiIiIiIiIiIiIiIiLtiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIhoREREREREREREREQ4tX2ERERERERERERERERERC8yIiIiIiIiIiIiIiIiIiHCMERERERERERERERERERERCjIiIiIiIiIiIiIiIiIhaCoiIiIiIiIiIiI36+aPlH2tk3Bu9xO7L8T/wbu4Gpsm+4n/GNbJuavbiHnt2/Bp3bJwTuyrUc1/R97AA1DeQhCEFGQhCEIQ/Mf8ABIQhCdiahCEIQnYyEIQmyhCEITxwAAAJsADi1Y9SEIQhNgAehCEIQhCEIQhCDwQhCEJtwABbYAAlBRkIQhCE7d+jmv8AHmu89B19/UTve/EXpsPUDU395/xj2zcGe2NPM12zfh09sm0LXuzu79HNf49Nune5v76iTZBprTbLu67KeNnceg3ds3PM12zePEaid2ybQndlvF819g3Bu7RO9m/vq2lBqeNa8e3NuG5t2/Fp3bJwTuy1KVI83n+w62wABT68N6yTa0HtptoZEIQhCEIQhCEITbAAIPUhCEIQhCEIQhwJtgADXjf/AP8A/wCSIQhCEIQhCEILQhCE7rO/yXFsI/z+JhHAe2TbVaNCEwm9f2uW1bGrtkoNbWnl2Q8vG31W/wC9u1Nq3t0oNbVK7fiPYM7/ACXFsI/z+JhdvAe2TbVaNCEwm9f2uWzb7WrtEp2tbOnl3IeXjb6rf97dqbNvbpTta2aV2/EewZX+S4thF/8A0JhEIQgsbZNtVo0IXnxX7hsW+61SEIQhCEIJTusQhCEIQgrIQhCEPLxtdVvIQhCEIQnfQhCEIQg9tBIiEIQeOxVEEIQnYhCEIQhDishCEITsZX+S4thH+HBMLvp7ZNtVvELz4r9w77fjUrsIeXjeIvDN7dKd+mq399K7fiPYPr/JcWwj/DgmFsUQhCEIQhCEIQVap6keHxX+ciEIQZCEIQhCE7sIQhCEIQg13VWxhHyIQhCEIQhCEIQhCEIQhCEIQhDiohCEIQhCEIQhBqd1kIQhCEIQhCEKFGxoQhCComxhCEIcR7Hh89BCEIQhCEIQwZCEIQhCEIQhBVOp6kfPiu1vZtdsJs2u1Vs4OPLxvFW3YhBkIQhCEIQhCEKFBCEIQhCEIMwgmfYhCEIQhCEJ2oQhCEIQhNrwHtk2nVvFLz4r+/Ib2rW3aErtmp5eN4i8A3t0ptWhK7fiPw6rbzyYx1Gp41PO3ceXjG4a7Z426q8altGM0Rretmlt4ZOCx17Wp41PbtTy8U3Nu34Hcb/4dCVaJueeTGM0Rretilt4ZOCx17rU8ant2p5eIbm3b8HuN/8ADpbpO5/0+Q6NNRoiIiIiIiIm3lq1r9de/PDOd9bdqeGcXZEREREREREREREREREOERERERERERERERERERERERERERELQ0IiIiIiIiIiIiIiIiIiIiIiIiIiIiIsERERERERERERERERERF2oXC7nnkxps01GvCT1e/gsdfGt7FPbtTwjc27fh05/EIXC7nnkxps01GvBT1e/gsdfGt7NPbtTwTc27fiU5/EIXC7nnkxps09GvAT1e/gsdfGt7VPbtTwDc27fi05/Ebj645kREREREREREREREREREcR6ERERERERERERERERERF4CEREREREREREREREREQiEIQhCEIQhCDngIRERERERERERERERERDREREREREREREREREREIOSIiIiIiIiIiIiIiIiIS/hkJFv8AxzY2269W/AcR6P8AmW749raJEPPsh5fyU8XRzY63W62QhCEIQhCEITshOzgv0f8AETbvUhCEIQhCEIQhCEJ2JtmIQhCEIQnYUdsHPkQhCEIQhO0hCEIQhCEIQhCEIQhCEIQhCEIQhCE7dxXRzHrq7SEIQmyhCEIcJ++xevjUtu/CNUhCEIQhCEIQhB7BBQQhCEHHlsITwi8DuC6DV6vYRkZGRkZGRkZGRkZGRkY73PvukZGRkZGRkZGRkZGRkZGRkZGRkZGRkffS278NNu8CMjIyMjIxRsGyIyMjIyMohGRkZGRkZGRkZGRkZGRkZGRkZGRkZqRkZGRkZGRkZGRkZGRkZwHQb/EcB+Na7qW3b/jEpsmp/F7yLCIsIiwiLCIsIiwiLCIsIiwiLCIsEREREREREREREREREREJ+NhOyfxMRORERERERERERERERERERYRFhEWERYRERERERERERERERYIwiLCIsIiwRERERERERERERERERERERERERERERERERGhEREREREREREREREXjE/8A44kNbXeJQa/jGv8A0cZGRkZGRkZGRkZGJdrRGRkZGRkZGRkZGRiU7WvHwhCEIQhCdxrZwjIyMmzhGR9iMjIyMjIyMjIyMjJt4yMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyM1/iGv4xrxCXda/wDRtfxjXhku+1/+Gh//2gAMAwEAAgADAAAAEG24ekkkkkkkkkkkkkAfttttttttttttt5JJJJJJ5PJJJJJJJJJJJJJUkkkkkkkWkkkkkkkkkkkkk5JJJJJIA23bbG4SSSSSSSSSSST4kl//AP8A/wD/AP8A/wD/AP8A4JJJJJJKTcrbbbbbbbbbbbZAAAAAAAADpJJJJJJJJJJJJJJJJJJJW22AK/8A6Ekkkkkkkkkkjbb5bbbbbbbbbbb8kkkkklWABxbbbbbbbbbbaD//AP8A/wD/AP8A/wCtbbbbbbbbbbatttttttpJJIbSSSVpJJJJJJJJJG222dJJJJJJJJJI7/8A/wD/AP8A47bbNySSSSSSSSSGkkkkkkkkkk5ySSSSSSSSSBJJJJJJIgAADtJJJJSSSSSSSSSJJJJJfkkkkkkkkk8AAAAAAb//AP8A/jttttttttsc22222222222Vttttttttsf8A/wD/AP8A/wDn/wD/AP8AbbbbbJJJJJJJJP8Abbbbbs22222222G222222Kkkkkl1bbbbbbbbHJJJJJJJJJJJJBJJJJJJJJtttttttr//AP8A/ttttttTbbbbbbdrbbbbbewAAAAAACbbbbbbY0kkkkkhDbbbbbbdSSSSSSSSSSSSSVFttttttuJJJJJJJrbbbbdtttttr/8A/wD/AP8A+K//AP8A/wD/AOWSSSSSSRW222225bbbbbbY9JJJJJJJ622222222222230kkkkkkmdttttt87bbbbYkkkkkn/8A/wD/AP8A5xH22222+kkkkkkkk+AAAAGlrJJJJJnJJJJJJJJ4bbbbbbbbbbbbc1JJJJJJJm//AP8A/wD0m/8A/wD/ALLbbbfkkkkkkhoAZAAAAPSSSSSSSSST/wD/APtkmj//AP8A9P8A/wD/AP8A/wD/APxP/wD/AP8A/wD/AP8A/wD/AFts+2222228kkkkH/72222H9tttttttttqiSSZAAAtbbbbbbbbbbnttru22mJJJqJJJJJJJJJJ2/wD/AP8A/wD/AP8A/wD9tttbJJJJJJC222+kkkySSb+i222222222q222wpJI7bbbbbbbbbbWbav/wD/AP8A2262222222222275JJJJJJJJF2222UAAAAAAH9t22223dtlkkESSSSSSSSRttttvf+NtttttttttttiANJJJJBEr/wD/AP8A/wD/AP8A/wD/AP8AyCSSSSSSSSAAAAAWSSSSSSc+y22222dv3JJl7bbbbbaDJJJJJOgSSSSSSSSSSSSSYi222226122222222222223P/wD/AP8A/wD/APtttttv7bbbbbb0QAAAAAGMA/8A/oAAAAABi9tttttvJJJJJJJJJJJJJJKUkkkkksUkkkkkkakkkkkl1/8A/wD/AP8A81tttttt77bbbbbaEkkkkkkwkgAAeJJJJJY4AAAAAAA1JJJJJJJJJJJJJ+m22222va22222rD22222gqf/8A/wD/AMAySSSSSHsgAAAAAIttttttttvbbbS2222k7Yf/AP8A/wD/AP8AG22222222222ZttttttlCSEkkklySgkkkl1bX/8A/wD/AA/+G2223Ml7ttttmkkkkkkkkkkAAAAi2232AAKZJJJJJJP/AP8A/wD/AP8A/wD/AP8A1ttR/wD/APPbaRJJJObbApJJTgACkkknBttX/wD/APjJIbJJIHJJJJJJJJJJNttttU23w2222JJJJJJJbbbbbbbbbb1bbbfpJCJJJKCSS7JJJmSTfbbbRbbUAAAN/wD/AJ6SSWn/AP6kkkkkkkkkkl//AP8A/wC+lIkkkku222222+W222222233/wD/AP8A6gCrbbbTtsTbbbbBsk22226SUW2223iTs2223C2+22226wAAAAAP/wD/AP8A+tBtttttt/8A/wD/AP8A/wDJJJJJJJJX/wD/AP8A/wAw9ttttld1ttttsE9//wD/AP8A9cpJJJJJcV//AP8A/wCap/8A/wD/AP8A/wD/AP8A/wD9tttttppJJJJJJrbbbbbb4JJJJJJJ/wD/AP8A/wD/APvbbbbbaObbbbbbPySSSSSSy222222ONttttt/NtttttvNttttu22222npJJJJJJIEkkkkk1/8A/wD/AP8A7VtttttttKSSSSSSSSSSSSSvckkkkkUAAAAAACN222223pW22222E622220AAAAA3/8A/wD/AP8A/wD8dttttlJJJJJJaAAAAAAAAAW22222222222jN5ySSSR8Af/8A/wD+xACSSSSXIASSSSZvyySSSaySSSakkkkkkkkkqSSSS0kkkkkkpJJJJJJJJJAkkkkkkkkkkkwSTuAAANLbeSSSTgAB9tttsABTtttmLbX2223udtsbbbbbbbbbbbTJJIw222223XJJJJJJJJJJASSSSSSSSSTUAAB1ttlJJJMkklIAAJ9ttuAAAdttlv8A/wDckkitlJOIAAAAAAAAAAAMJKJJJJJJJBJJJJJJJJJJJxJJJJJJJJNEkkkhn/bSSSSrbb7bbbc22jbbbe226/8A/wD9W2z22LlAAAAAAAAAAAACfFJJJJJJJSSSSSSSSSSSST7bbbbbbbRpJJJJevltttttftNtttthstttttzP2SSSSSNrS21aAAAAAAAAAAAAADNbbbbbbDAAAAAAAAAAAAACgAAAAAASpJJJJJMNtttttu//AP8A/wD/AP8AN/8A/wD/AP8AoX//AP8A/wD8UyJJJb//AP8A/wD/AP8A/wD/AP8A/wDoW222223v/wD/AP8A/wD/AP8A/wD/AP8A/wD7bbbbbbfttttttHEkkkkkkBtttttttttttttgkkkkkkkNtbbbWbbbbbbbbbbbbcJaSSSSSSRkkkkkkkkkkkkkk7bbbbbb9tttttDyDbbbbbbKbbbbbbbbbbbb3bbbbbbZFtrbbbfkkkkkkkkkkkiySZJJJJJJJSSSSSSSSSSSSSSoAAAAAAj2222NAAUkkkkkkLbbbbbbbbbbb2SSSSSSTEkkkkkkgttttttttttqgAApJJJJJJOAAAAAAAAAAAAAAwAAAAAAg222Xbbf2222222oAAAAAAAAAApJJJJJJOzbbckkkknAAAAAAAAAGpJJJ2222222iAAAAAAAAAAAAAAO222222X223JJJM8kkkkkkRJJJJJJJJJkkkkkkki6SSSAAAAAASSSSSSSSX22222CSSSSSSESSSSSSSSSSSSSSCSSSSSSp/sJJJJNf/8A/wD/AP8AwAAAAAAAALSSSSSSTzJJJIAAAAAB7bbbbbbblttttttttttttsbbbbbbbbbbbbbbV/8A/wD/AP8A9khJJJJJRNttttttAAAAAAAAVttttttk/wD/AP8A/wD/AP8A/wD/AGUkkkkkkYSSSSSSa222222/ttttttttttttttuAAAAAAPwAAAAAAQAAAAAAI2222223wAAAAAAckkkkk222228pAAAAAB2OSSSSTOYkkkkktz//AP8A/wD/AP8A/wD/AP8A/wD/AMAAAAAA/lJJJJJGEkkkkkku2222220pttttttkEkkkmpJJJJf8AfSSSSTgDiSSSSm27bbbbr2222222222222222SSSSSekgbbbbYe222222ySSSSSSSSVSSSSSSXW222y6SSSC22zkkknjbbv/AP8A+ttt22219ttySSSSSSSSSSQJJLgAAAu22okkkrJJJJJJJySQNttttttgkkkkkkpAAAFLtts0AAAVJJYAAABSSTSSSTSSR6SSSW22222222212222tttckkk1JJNf/wD/AP8A/wDbbbbIAAAAAAFkkkkkkl223iSO2Ckkkki2xUkkkjySxJJJJVt/JJJJLkkkkkkkkkAAAAARJEAAAAGtv7bbbbbbv/8A/wD/AP8A/wD/AP8A/wBxtttttt2pJJJIXnbbbbYuobbbbbbipJJJJKaBJJJJJT//AP8A/wD/AP8AykkkkkkmUkkkkk8pJJJJJJFttttttttttttlpJJJJJIySSAAMpJJJJJAu222222wSSSSSSYSSSSSSSwAAAAAAISSSSSSQwAAAAAIf/8A/wD/AP8A8AAAAAAAAAAAAAAWSSSSSSXElJIO/wD/AP8A/wD/AKpJJJJJAKAAAAAOsAAAAAAqtttttttttttttnMAAAAABnEkkkkkrttttttsZttttttyUkkkkkrtskg27/8A/wD/AP8A+qSSSSQfigAAADr/AAAAAACAq222222222222cAwAAAAGbcSSSSSu222222xtm22223NpSSSSSu22SwbbEkkkkkk/kkkk0km7bbb/kkjbbbYskZtttttttttttu//jbbbbQAAttttBJJJJJJl/8A6SSSSt/8rbbbQAAAttttm222222zZJJG223bbbZW22/bbaFttjgAAAAAAAAACkkkvbbbbbbbv/8A9ySSSSSQYAABSSSaAAAT/wD/AEkkkn22226SSSSSSfwAJJJJIm23pJJJI23skkksbbbbbbbbbbv/AP8A/wCSSSSSSbSSSSSSSSSQf/8A/wD5JJH/AP8A/ttsySSSUAAAACSSSSSSTTY22222umu22220kgJJJJKdttttttttptttttf8kkkkkInW222222/bbbbb2zbbbbbK222222SSSSSSJJJJJJJf7bbbbbTjbbbbbZ7JJJJJLb//AP8A/wD/APt222222Q222222xEkkkkkkO2222228+22222xu2222ySSSSSXbbbbbbbO2222222222222wBJJJJJJ8AAAAAAF222222xgAAAAAAySSSSSSXoAAAAAAIAAAAAAm222224AAAAEAAAAAAAr9kkkkkkkkkkkkl2SSSSSSSzbbbbbfkkkkkki/wD/AP8A/wD/APgAAAAAAAHf/wD/AP8A/wD/AP8A/wD/AP8ASSSSSSTgAAAQAAAAAACtv2SSSSSSSSSSSXbZJJJJJJLNtttt+SSSSSSL/wD/AP8A/wD/AOAAAAAAAAAd/wD/AP8A/wD/AP8A/wD/AP0kkkkkkhu22zJJJJJJNqSSHkkkkkkkkkkibbbe222222k2222kkkkkkmttttttte222222222zFttttttttttySSSSSSQBJJOtttttttzbbbYbbbbbbbbbdm222Dbbbbbb7AAAAAAAAAAaSSSSSSpJJJJJJJJJJMySSSSSSSSSG2222222ABJOtttttttzbbbbYbbbbbbbbdm2222Dbbbbbb7AAAAAAAAAaSSSSSSpJJJJJJJJJJJMySSSSSSSSG2222222wABOtttttttzbbbbbYbbbbbbbdm22222Dbbbbbb7AAAAAAAAaSSSSSSpJJJJJJJJJJJJIySSSSSSSG22222222AAFX/AP8A/wD/AP8A2222223gAAAAAARkkkkkkl7bbbbbZNttttttsSSSSSSTSSSSSST6SSSSSSAkkkkkkmEkkkkkkkkgAMMAAAAAADbbbbbadtttttttxttttttAAAAAAAbbbbbbbbfbbbbbbYbbbbba9jbbbbbMAAAAAAABbbbbbbbbYAEJMAAAAA7ZpJJJJ9ttttttttwttttvpJJJJJKZttttttttyJJJJJJJpJJJIFvlJJJJcAAAAAAAAB//AP8A/wD/AP8A+AqSTv8A/wD/APJJGAAAJiSSSSSSSST5bbbcAAAAAABrbbbbbbbbbW2222223bbbR7bbrbbbH/8A/wD/AP8A/wD/AP6SSSSSSSQoAAAmkkkkkkiSSQW2222222222ttts2222222Ntttttttttt1JJJJJJCkksW22/Ekl+kkkkkkkkkki2222223X/8A/wD8tttttttqSYAAAAAAAAAAADttoAAAAAAjtttttttttttqtttttttiS3AAAAkSW9tttttttttttwAAAAAA0kkkkltttttttMlttttttttttttrA9ttttttuAAAAAAAAAAAAENtttttttpSSSSSRttttttttttttttySSSSSSSSSSSSySSSSSSySSSSSSSSSSSSSXCSSSSSSR2222220222222z6SSSSSS72222223SSSSSSSSSSSSSSG22222SSSSSSUySSSSSz6SSSSSSSSSSSSSSSSSSSSUO22222xx222222A6SSSSS7222222z6SSSSSSSSSSSSSSG2222wAAAAAyUAAAAAj/6SSSSSSSSSSSSSS9ttttk/JJJJJMkkJJJJJUigAAAAGJJJJJJJoAAAAAAAAAAAAAAARJJJJSSSSSSSSSSSSV//wCkkkkkkkkkkkkklWSSSSv/APpJJJR/9xJJJL//AMSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSV/wD/APpJJJJJJJJJJJJJVkkkr/8A/wCkklH/AP8AeSSS/wD/APxJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJa222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222223/xAAtEQADAAEDAwQCAQQDAQEAAAAAARExITBRQEFhECBx4aHwUIGxwdFgkfFwwP/aAAgBAwEBPxD2K5HwhsobI+rjeCHyNCNOz7p8r91HROnZ9muV/ldiPhkfDI+GR8Mj4ZHwyPhkfDI+GR8Mj4ZHwyPhkfDI+GR8Mj4ZHwyPhkfDI+GR8Mj4ZHwyPhkfDEz+CMjIyMjIyMjIyMjIyMaMCQ4iqT8+RVCrIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMayS+K5+v7iU0S0IyMjIyMjIyMjIyMjIyMRHtL+x+DGhMrj6IyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMTMjIyMjIyMjIyMjIyMjIyGrz7aKKEc3Yjuw2UUN31P/AP8A/wBiF8+o5I07Punyv3UcF6dn2a5X+V2JxJxJxJxJxJxJxJxJxJxJxJxJxJxJxJxJxJxJxJxJxJxJxJxOQojIyMjIyMjIyMjIyMeivRIcRon59dMjsiiiiiiiiiiiiiiiiiiiiiiiihrpGnC5+v7mjRY6H/8A9MfF/Y/BhwmVx9EIQhCEIQhCEIQhCEIQhCCd69iiiiiiiiiiiiiihPkoooor18iYQzMN9alPn2sSPh90+V+6jQv4fZrlf5Xbp0uSIiIiIiIiIiIiIiIiIcK9Ei3An59qcdQySrp9MjxXP1/cSSUWOjQnxP2PwY4JlcfREREREREREREREREREREREREREREREJHvpe+hC89iNSNSiiiiiiiiiiiiiiiiiiiiiiiiiiihOtXkooooooZkfD7p8r91G7+m+zXK/wArsWWWWWWWWWWWWWWWWWWWWJ+5RRRRRRRRRRRRQ1FbSSH2BPyV4K8FeCvBXgrwJiqDEqLLLLLLLLLLLLLLLLLLLLHuvgrn6/uKNFgooooooooooooooooooooRHxf2PwYUJlcfRCEIQhCEIQhCEIQhCCohCEIQhCEIQhBCEIQhCC1DNu5svpEdXnZAHjB2fdPlfuo3f032a5X7p0YAAC3jaSvAx8H92yAJQXR6dHiufr+4lNFjp0pkT9j8GBC5XH1w+jAAAXLG+l3ezkQ3ttXoofO2+r+H3T5X7qMq/h9muV+6de2kr0SGPgT87acE70OhX4rn6/uJTRY6lCZE/Y/BjQuVx9dCld9KfPTNJkEEEEEEEEEEEEEECF8kEEEEEEEEEEEDj/TfdPlfuo5J+H2a5X7oUpSlKUpSlKUolc7zaSvAx40/uIIIIIIIIIIIIEpgSTRBBBBBBBBBBBBBBavjrn6EktFjq0JkT9j8GNi5XH1vpb/deSCCCCCCCCCD4nxPifE+J8T4nxPifE+J8SCCCCCCCCCCCCCCtoIMykr0/wBijsfz9kEEEEEEEEEEEGRjYXP1yxh1H/HheCOCOCOCOCOCOCOCOCOCOCOCOCOCOBKu2hHBHBHBHBHBHBHBHBHBHBHBHA1KvBGlILWqWhHBHBHBHBHBHBHBHBHBHBHBHAlfYSsIjgjgjgjgjgjgjgjgjgjgjgjgjgjgpnX/AB8CSyJniPEeI8R4jxHiPEeI8R4jxHiPEeI8R4jxHiPEeI8R4jxHiPEeITtBP2MwQXK4+iCCCCCCCCCCCCBJIggggggggggggga6YHzBCEIQhCEIMa3AAiIiIiIiIiIiIiIiIiGYgkHSWifnyyIWjq0aFp2v5+yoqKioqKioqKioyofC5+uR/fW/HheOhAADR6KKKKKKKKKGoq8DHiFeiWqwRERERERERERERCoSIhCEIQhCEIQhCEHPnf8AHwKGg0yEIQhCEIQhCEIQhCEIQhCeihCZE/YzCBcrj6IiIiIiIiIiIiIiIiIhaEREREREREREREREV0w9VPkIiIiIiIiIiIiFrGNePZfG8ySkSHMWiftfsCTTqcaEpeL+dkAyDfC5+uWOD6348Lx0a2QBpJVvQa8XtBPVY2QBO2JUoujAAAe96/4FEpU+nAAACe1KZE/YzCBMrj63k5tYCx7Q+Y2lJGNY0RkZGRkZGRkZGRkZGRkZGRkZGRkY0YEiWSaT8+WR8Mj4ZHwyPhkfDFDqqaIsa/kjIyMjIyMzbfC5+uWOmu348LwRkZGRkZGRkZGRkZGRkZGRkZGRkZqR8Mj4ZHwyPhkfDI+GNNavAziTHgZ4GeBngZ4GeBiGqTh4GeBngZ4GeBngYnORihREZGRkZGRkZGRkZGRkZGRkZGRkZGNNdv8AjyhaMiZXDK4ZXDK4ZXDK4ZXDK4ZXDK4ZXDK4ZXDK4ZXDK4ZXDK4ZXDK4ZHwRkZGRkZGJegn58MxcTK4+iMjIyMjIyMjIyMjIyMVRGRkZGRkZGOeiwRkZGRkZGMfIRkZGRkZH7FrGMePo0JgSHtFon7XsJtOrItf/AHe3Ot8Ln65Y8PrfjwvHTpzR+1uavBjLZZyx1r3zv+BNJkT6Ru7C+yJ+xmPiZXH1vJ+6+ixvAhKyijIyMljGMZGRkZGRkZGRkZGRkZGRkZGaGBIeRaJ+1kZGRkZGRkZGRih1ZIf/AHEZGZ9thc/XLHx9b8eF4IyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjKwyiMjRpCEZGRkZGRkZGRlD1qsEZGJ2DxdTVVVVVVVU/wA7/gWjImRkZGRkZGRkZGRkZGRkZGRkY02RkZGRkZGRkZGIeB+fDMXEyuCMjIyMjIyMjIyMjIyMVIyMjL6LG3Tk9WSVjGr9y1jGPGNlZWVlZWVlZWVlZWVlY0StxIY0YfncTadWUKTCn5/eDINsLn65Y4NrfjwiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiE1hmmexjIRERERERERERERERES1WBUJEoREREREREREREREREREREREREREREREREREQ1u5/Amkq1T6GIiIiIiIiIiIiIiF3Qa/Yf1KFxvJz0vosERERERERERERERERCHyDIrGNWREXuAtYxxERERERERERERERERERIdnbkhEREREREREREREREQtHVkof8A0JJJJJJJJJJJJJJJJJJJJJJJJJJJINaixvMaIzt4AAZDwMePdAAVuLJPvq+3BEREREREREREREREREd1H9iDP3f7+N4ABARxfz4+BNNb3YWN9tvO0uDLoRkZGRkZGRkZGRkY6RjtP/sRkZGRkZGRkZGRkZGRjV0a0GuCPhkfDI+GR8Mj4ZHwyPhkfDI+GR8Mj4ZHwyPhkfDI+GR8Mj4ZHwyPhkfDI+GR8Mj4ZHwyPhkYzeiwRjZ/JGRkZGRkZGRkZGRjSIgiIxupCMjIyMjIyMjIyMjG6mJCKMaRkZGRkZGRkZGRkYk24k6Rw/0+yMjIyMjIyMjIyMjIyMjA76CDNJ5rj6IyMjIyMjIyMjIyMczf+QoUS0FU6ho3HPRYIxXqVrGNIyMjIyMjIyMjI/b+/UojIyMjIyMjIyMjIyMdKPA7WupO3osEZGRjX8kZGRkZGRkZGRjSIiiIyMjG6lgjIyMjIyMjIyMbqYpEZGRRjCMjIyMjIyMjIxMcWSOf+n2RkZGR7XcQK7wRkZQ7Sea4+iMjIyMjIyMjIxz4U/IkkoseqbTqG7CMjIyMjIyMjIymix0sREREREREREKYxrx7D1IiIiIiIg/6nP3vAAGm1HgyUIiIiIiIiIiIiIiIiIiIiIiIiIiIiIhm9FgjIyMjIx38kEEEEEEFURFEUUUUaiWqWmx/zGtDQUUUUISNDWjXv/16JamN/wCn2RkZGRkfogiIiIiIiIbAO/ju/bD9dn0RkZGRkZGRjnwJ+RIUS0IiIiIhaOoTq2GuiwRERERERERERERERERERERERERERFtLWMYRlFFFEG9j9+pRRRRRRRRRRRRQ60eB2tCiiiiiiiiiiiiiiiiiiiinoiiiiiiihu9UUUUUUJ2iEJFsZSwVFRUVFQ/U8EnvQkYjeMqKioqE64sn7vPvYaKKKKK9GCCKKKKKK9Df319e5r4P7hJJFol702nUJ33U0WP4FCxjGj9jXTttfoF3mk9Hgz10amixtNIiIiIVIhIkREREREREREREXohERERlMREREREREREREJIyuMiIhK6JaiUh/p9kRERERERERERDXtAd1G1g/wDr9ERERFv/AKRJJRY2U46hkhERERbRY2kRERERERERERERERERbi1jyMaP0a/G5+oXeAAOtHgzV0IAANWixuAAf/f0TNEJEotuEdVj0X1eCTbQkYxozVosn7vPvfAAAQDcAB+vz69DX/8AQJJItEttaCdXo10WP4VSaj107EEEEEEEEEEEEEH6hSiiiiiiiiiiiih1o8CIYFFFFFFFFFFFFFEZT0WCiiiiiiiiiiiih2KFpvf/APubz/8AvE0W5+3x9lFFFFFFFFFFFFEUsk0s7sGsXyX+hSoloUUUUUUUUUUUUJNao7BRRRRRRRRRRRRQ1MkZRRRRRRRRRRERERERERERERERDSSrwYaERERERERERERERERERERFeXdhyEREREREREREREREREREPX4IiIiIiIiIiIiIiIiIiIiE5p26uH71BoxqbjyFMWd7OeCLHYhqsbsNXkaTyNR7jaWrG66Q+NtC7R6KvAx4uieR7cOTfa/HRJ/9FFFFFFFFFFFFFFFFFFFFFFFFFH79PVqjTW09BEF7KKKKKKKKKKHPV4J6w1WNuGr9Wqoxpp7Tc1YzfrD42lo6si9D2Di1eDBXSFPkIiIiIiIiIiITyCIiIiIiIiIiIiIiIiI0G78EREREREREREREREREREREREREQnPjqF/tIiIiIiGjHBERERERERENeIWxe1PZzngiIiIiIhK1WCIiIiIiIiIiE5ZERERENGoxw4yIiIiIiIiIcSrG6RERERDT4NCIiIiIiIiL0KNVkQvuNpKvA7SumAAACnJ78Bb569MAAAFpp26WH6BPe1Rqe5rxC1i9699NXjYhqse+Grz72k1GNl7sDr94GjTt7wGha1EECUdWRaCEIaIwUIIIIIIIIIIIIIIIIIIIIIIIF8gnyT5J8k+RloyJ8k+SfJPknyT5J8k+SfJPknyT5J8kjS7EEEEEEEEEEEEEEEEEEEEEEECLvgjyR5I8keSPJHkjyR5I8keSPJHkjyR5I8keSPJHkjyIAggggggggascsgggY8QtiIIIIIIIIEkQQQQIfwQQQQQQQQQIWqwQQQQIWryQQQQQQQQNWoOWQQQNJasepCEIQhCDTOPSEIKWLMuW/ZTadWRaejBjLqlOT18g3278dVDR463BpNDTT9GPELWLbT9afG5DVY9e49tpNDTT9G5qxu7cNHj1WsRY3c9yqdWRaa5MJdYryGAzvuv4KKKKKKKKKKKKKKKKKKKKKKKKKKKKWjx1uDVQmIFrFuplPgm7DVYIavdaqE7h1lFFFFFFFFFFFFLOBCRauUU1KKIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIhOhZEvQtSLgi4IuCLgi4IuCLgi4IuCLgi4IuCLgi4Q0XwRERERERERERERERERERERERERERERERERERERCmCIiIiIiIiIiIiIiIiIWmqyK0sREREREREREREREREaIoqGRUREREREREREREREREREtHg0IiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIcSrGrwRERERERERERERERERCiIPV15IiIi2Gp1WAvVTVZ32p1qe/+4XbbSQ22/Rj1CEq3p6PHVNpKsY3jeQobbded1qdRLRZIQQQd7IhCEIQhCEIQhCCCCCCCCCCCCCCCCCCCCCCCCCCCCCBJEEEEEEEEEEEEEEMJ7LA1WQQQNdRBUQQQQQQQQQQQQQPNHjqG0lWMbxvIUNtuvPuiIiIiIiIiIiIaIiIiIiIiIiIiIiIiIiIiIiIiIiIiIToXvJeqyREREREREREREREREREQ4+OtT38B7B6+5jVCEq3oaPHTOLVjVqRERERERERERERERERCUPV15IiIiIiIi22uknos+kRERERERER31vxEREREREREREREREREREREREREREREREREREREQoREREREREREREREREReidDERERERoPUiIiIiIiIiGqoZJVvS0eDQiIiIiIiIiIiIiIiIiIiIiIiIcSrKPG8hQ2268/woAADzRZ2wA+U351oAAAAJTfAAL6PPseu0zaohKt4AA+Dx0TaSrGN43lroGm3W9f4UAwFndAAW9VnfAAJ1qe/gMQau4ACmqGSVb0NHjoHpqym8bwABChtt152oIIIIIIIIIIIIIiCCCCCCCCCCCCBOyyPVrkgggggggggggggRnoQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQJIgggggggggggggVPTI0++8jtWN9iWu+rz233rkIIIIIIIIII3EqQQNTdiPlBonGREREREREREREREQyeiwRERERERERERERERERERERERERERERERERERERERERERERERERERERERERERETcY0WRB5IGhERERERERERERERCvgJFoiIiIiIiIiIiIiIiIiIStXn0iIiIiIiIiIiIiIiIiIiBK1WCIiIiIiIiIiIiIiIiIQRlEZERERERERERERkR8r/K2kr7WpvITyNNOPavosfzTGiyLWLPsa2v6YSmi3oavPRw1WN5CRjGj2kvQ17oiIiIiIiIiJ7wHOwAahUVcFXBVwVcCISSSSSSSQPdFj3ASXckkkkkkkaIa9wIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiL0ySSSSMxERERERERFUQhNM+4DgiIiIiIiIiP6ASLRbwADuPpAIR1WN5KRjGj2gH+QRGRkZGRkYlNhqe5ufJqRkZGRkZGR+9+hYIyMjIyMjIxN4fvdZGRkZGRkZGTpUlllRUVFRUVFQ0sojIyMjIxM0QtYs7DXuf8BKaLe7j2GiMjIyMjIyMjIyMjIyMjGrVYIyMjIyMjIyMjIyMjIyCMY0ftSNMez/AD69sZGRkZGRkZGRkY0yMjI/SMjIyMjIyMjIx18jTWfZTRY24yMj3GuCMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMV9iTaIi8kZGRkZGRkZGRjZEZGRjW1wJTRb3cfUw+N5CRjGjIyMjIyP2/wCfXrjqkJ5GmnH6U0WNxPs/RuDd/i0m0QtYs7jU9KfG/wBx9XDVY3mkkYxo9pT+0Tdg0UUUUUUUUUUUUUVWol0WN7/+g63v/wC53wABciCCCCCCCCCCCCBI9BCaFFFFFFFFFFFFehU/BBBBBBBBBBBBBBAkWu9jfQnoUUUUUUUUUUUUUOUY3ThRRXtQtY96xruusposDV3UqJQa7rfanx1eRKE9Hjdbhe5T53Uu73sFPgho8bsPkvcT3e2thC1j3r9Wu66qmix6tXbSolPVrut9qfHU5Ep6z0eNtueynztpd3vYKfHrDR424fJn1T2+2thCVjnr9rQggggggggggggggamy107EEEEDV/JJo9hK6ISJEEEEHdWy9ClZWVlIiCCCCCCCCCCCCCCCCCCCCCDUJEQQQQNNHjahBBBBO7PdZBBBBBBBBBBBBA0kqxr8e2GjwQQQQQQQQMvkZ9qez21sIWsY9fS5Gp7278e+nyY0fuSuiIfPv7i97c63IlPfDR43077+495tJVjG8e+ejx7/AOoG7q/en7+2thCVjNrKKKKKKKKK3pRsvj29jsVFRUVFRUVFRD+SiiiiiF8lRUVFRUVFRUOnoUUUUNNEZRRRRRRRGTo5RKbLdnv6vkoooo7r3npqxm3goooooooodrR49rT5Gm9WUUUUUUUV7nYRRRRRRRRQxq9trvvvUUUM3osFFFFFFFFFFFFFFFFFFFFFFFFFFFFDRGUUUUUUUUUUUUUUUUUUUUUUUUUUUJmKSiiiiiiiiiisMooooooooooooooTuc+qXd72Btv424aPHq0XkztylFDTREIQhCEITda32U0WOtb3nvpUSm7DR4306Jd3vYKfG7DR4HA3dXupnbXTvwRkZGRkZGRkZGRkZGRkY9dBifgjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyP0aIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIxMiMjIyMjIyMjIyMjIyMTMSmm93HvtVQaaz1jTREZGRkZGRkZGRkZGNNOPqm7osejVGp1Tc65KbyV3+4+haTQ00+qb3UJ5GmnH1Dd0WPY1RqdQ3OuSm8ld/uPo2k0NNPqG96IWnkk0IiIiIiIiHFvsiIiIiIiIaIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIuCOERwiOERwiOERcIjhEcIjhEcIjhEcIjhEcIjhEcIjhEcIjhEcDRERERERERERFvU+CIiIiIiIiIiIiIiIiIiE5ZERERERERERERERERERERERERERDRocOMiIiIiIiIiIiIiIiIiIiIiIiIiIiIcIiIiIiIiIiIiIiIiIiL1zl7jc329lqfxeRqdJT4Mb3cfTtJoaacfSN9HnL2G5vt7bU/i8jU6KnwY3u4+paTQ1NH0TfS5yGMjc3291qfxeRqdBT4Mb3cfVtUamj6Bvp53zxvxERERERERERERERERERF232+CsrKysrKysrKysrKys1ZEREREREREREREREREREQpNMb0XXeiSrEj3kXfJERERERERERERERERERDXBEREREREREREREREREREREREXtxlkyNXfa461q7rfXpzf7D3NEqx7ePSnzuNzoWulxFn2NUm6IPu2gBOjapjaa3wACdEnBK6reAAN2e0lNWM/h7AvztPTo2uj/qDPtapBBBBBBBBF7mtlqkEEEEEEEEEEEEEEEEEEDR+iCCCCCCCCCCCCCCCCCCCCCCIgggggggggggggggggggWm+ywyCCCCCCCJasa/grKysrKGXyIIIIIIIGkiUgggggggggggggggggggaZIIIIIIIIIIIIIII9IYyRsjIyMjIyMj32iMjIyMjI+napGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGKojIyMjIyMjIyMjIyMjIxnh+7AzbwRkZGRkZGRrVFFrn3Nzp2uN9ufO01ejwN3/gicM7yfswN342qfPsbnUtdTERERERERERERERERERERERERERwiOERwiOERwiOERwiOERwiOERwiOERwiOERwiOERwiOERwiOERwiOERwiOERwhoREQkyyOERwiOERwiOERwiOERwiOERwiOERwiOEROyIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIURoRERERERERERERERERex6kREREREREREREREJ31iIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIhoRERERERERERERERF/MtUS53271ic6Bu7yd61r/ijXdbqVPA1N1Lu99u7yV69r/g1RUVFRUVFRUVFRUL1a7ojIyMjIyMjIyMjEn7GmRkZGRkZGRkZGRi5es3GmyMjIyMjIyMjIyMjIyMjJ/ANEZGRkZGRkZGR/zjW0lP8A5E1sJT/8OL//xAAsEQEBAQABBAEEAgEEAwEBAAAAEQFhITAxUXEQIEBBgfBQkaHh8WDB0bHA/9oACAECAQE/ELntc9rntc9sjltbd1mMXPeLntc9rntc9rntc9rntc9rntc9rntc9rntc9rntc9rntc9rntc9rntc9rntc9rntnXZnlm3dw6L/8A4/0nb7/Ly30Li4uLi4uLi4uLi4uLjcF3czMVeDH+7I25rHC4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uP8AZHPf/C5nTFxcXFxcXFxcXFxcXFxcZG3N6snlcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXG49rntc9rntc9pSljjltddSlmEIQhCEIQhCEIQhCEIQhCGbdmeWcbv1V/Y+P8ARZvtKUpSlKUpSlKUpSlKUpSll6CEIQhCEIQhCG5LuzMa78GP91Vms24zGpSlKUpSlKUpSlKUpSlKUsP9Bz3/AMIQhCEIQhCEIQhCGTtzywOUIQhCEIQhCEIQhCEIQhCEJSlKUpSlKUpS2vHj7gXV1Fy3TbvldXV1dZrF1dXV1dXV1dXV1dXV1dXV1dXV1dXV1lbM8oy6urq6/sfH+iTfa6urq6urq6urq6urq6urq6urq6urq613197cYu9Mxq+DH+66urq6pmtXV1dXV1dXV1dXV1dXV1dXV1dXV1dXW/8AoOe/+P8A9XcXV1dXV1dXV1dXV1dXV1dXV1dZrNuIufx6urq6urq6urq6urq6urql1dXV1dXVY45be3UIQhCGZxCEIQhCEIQhCEIQhCGbdmeWCQhCEITP/wAP9Am+1VVVVVVVVVVVVVhvpQhCEIQhCEIQ3B4MxX1Mx/ulKUpSzOMxvhVVVVVVVVVVVVVUfwc9/wDCEIQhCEIQhCEIQhCEIZO3GJynlPKeU8p5TynlPKeU8p5TynlPKeU8p5TynlPKE8p5TynlPKeU8p5TynlPKeU8pQnlPKeU8p5Tynlhjlu927+YAAAAM3d2ZnVjkqqqqqv7Xx/p032qqqqqqqqqqqqv6O8AA3OZd8NtzOmf91VVVVVms24y8/DAAA8Q/wDv/hZ0z8cAAAMjqwwqqqqqqqqqqqqzv72QBu3rv5mZu7M8s8dt/cGP7o38HgHe3czLvhpv/sdvNjNv4P8AsTnv/j8rN3NuMMc/4Dd/WdqlKUpSlKUpTPZVVVVVVVVVZd2Z5ZY6lVVVVVVVX9wY/qfVrWta1rWta1rbegpSlKUpSlKUps5d8Ntx/wA1VVVVVVVWbuM2qqqqqqqqqr+13/hVKUpSlKUpSlKUpSlKUpSlKZvNuP5ClKUpSlKUpSlN0pSlKUpSlKUpTdaqqqqqqrmczmczmczmczmczmczN/vVe1e1e1e1e1e1e1e1e1e1e1e1e27zLu9HWiZjfN//AKV7V7V7V7V7V7V7V7V7V7V7V7Z03b+mKvurWta1rWta1rW36gvf2ta1rWta1rbvOu636P0/ojHI5HI5HI5HI5HI5HI5GlKzWLWta1rWta1rW1aX/wDDJXNuav2v2v2v2v2v2v2v2v2v2v2v2v2v2v2v2v2v2v2v2v2v2v2v2v2v22P2zPY1B56q9q9q9q9q9q9q9q9q9q9q9q9q9q9q9q9q9q9q9q9q9q9q9q9q9q9q9t0/a/avavavavavavavavavavbc3HV1dXV1dXV1dXVmzvAANxnXXjZ4+jJ25ah/7PbACg34YoPqqqqqqqqqqqrXo0/m/m/m/m/m/m/m/m/m/m/m1+Cq/uE7mYJlRyjlHKOUco5RyjlHKOUco5RyjlHKOVdWv9uWaZc8I5RyjlHKOUco5RyjlHKOUco5RyjlHKOUco5RyjlHKOUco5brVa6uMMc/lAAAANf5P5M7QAbtbs7IBveAAOht0Z4VVZu5tzyxz/2FVVVVVcb114x+8H4YAAE+m+FVVVVVW18Kq/T+wRVVVVV+tVVVVVVVVVVVWm/+izc3LnhVVVVVVVVVVVVVVV+0M3m3PLDHPeAAXtACqqqvZALnttZ5buZs3ccmOTHJjkxyY5McmOTHJjkxyY5McmOTHJjkxyY5McmOTEOtxl0ZvRcXFxcXGRtzerFpn/s5McmOTHJjkxyYpN3N/TGtPquLi4uLi4uLi4uLi4uLi4uLi4uLjHo3S57xcXFxcXFz3ivTPC4uLi4uOjHoFz3i57xc94ue8XPeLnvGTXTFxcXFxcXFxcXFxcXFxcXFxcXFxcWU/wDlmmXNyLi4uLi4uLi4uLi4uLi4uLi4uLi4ue3JjkxyY5McmOTGYzbm4xPOVyY5McmOTHJjkxyY5McmOTHJjkxyYvti4uLi4uLi45McmOTHJjkxyY5MX2xcXFxcXFxc+uGG5ubN/D8PGzx2M3c255YY/wCz9tBvwx+8H4/gb4+6nx3sMysydM/H31/0Wbm5c8fmZu5txhjvZv3bvrs5v3VCEIG7mbNQhCEIQhCEIQhCEPGzx2s2bc8s8eH/APaEKTfhjeh1QhCEIQhCEIQhCEIQhCEIY9G+EI+hud+EIQhCEIQuMwQhm5rNx0z8kAAAAAAADPVP/lmmXN6IQhCEIQhCEIQhCEIQhCEIQhmc248J572ahCG9sqqqs+6qq6urq63K3I3e9Wm/C6urq6urq6urq6us3c65vV/eaytvwxcHX8vbo0vS/put+O7uDM3WdPy9dV/xZty5vRdXV1dXV1dXV1dXV1dXV3u5u5tzyi5XV1dXV1dXV1dXV1dXXVdbrx3c1V363V1dXV1u1eF4XheF4XheF4XheF4XheG4zLvhtrp4Wta1rWta1rWvOueW75N/M/8A/wD/AOa3ZmdXj3qu+9XfervvV33q771d96u+9XfervvV33q771d96u+9Y1mNBXV1dXV1dXV1dXV1dXWmOphEaurq6urq6urq6urq6u70zyhNdfXrvAAM3d2YxudaiuVcq5VyrlXKuVcq5VyrlXKuXU8ota1rWta1rWtbdapa1rWta1rWta1rWuLi4uLi42a2YuLi4uLi4uLi4uN3M67r+EuNi4uLi4uLi4uLi4uLi4uNnnPyvPTGTd3Dkx1DNyri4uLi4uLi4uLi4xUxjCYub01/aYuLi4uLi4uLi4uLj+01kzpnj6UNYFxcXFxcXFxcXFx56Z5Y/wBfFxs1cXFxcXFxcXFxcXGddjIyZq57xjnXwXFxcXFxcXFxcXFxlvXfC4uMx7XPa57XPa57XPa57XPa57XPa57XPeNwuMwue1z2ue1z2ue1z2ue1z2ue1z2ue1z2ue1z2ue1z2ue1xcXFxcXFxs357W7mdd8MvguLi43c71bFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcZt6Z5Y9W/Yt1zz2tNTGGJn16P7TO1/aayZ0zx9mDWjtY/wBfP+fs3KuLi4uLi4uLi4zrsxkzpn2T6PHapt3wuLi4uM3N7VxcXFxcXFxcXFxcXFxcXFxcXFxcXEIQhCEIbua2Pv8A+n9vG/SEIQj6Kqqqqqqqqrfxury9KUpSmL1/aXM5nM5nM5mqmNRMczmczmbrem+Gn9jFKUpSlNN/9hms6Z4czmczm+kMwapSlKUremeUP/X/AOVKUpTevz9EIQhCGbdmM6eF1dXV1dbZ0eFKUpSlK3z4XEIQjtP/AFhCEIQhCEIQhCEIQhCEIQhCEIQ3c36VVVcx42eOxVVVVVVVVV+lVVVVVVVVVVVVX6eVpVVVVXlZ5+lVVY3UxBMQhCEIXH9Yiqqq/rFZuZ0zwhCEIQyRtqaqqrNvTPLH+vn/AChCEIQhG9c8/Sqqs67MZmYmKqqq/TwnhCEIQ6vhcQhCEIQhCEIQ3CEIQhCEIQhCEIQhCEIQurq6urq6urq6u/Tri6uq1+C6urq6urq6urq6urq6urq6urq6urq6u/Tbn72LvvV33q771d96u+9XfervvV33q771d96u+9XfervvV33q771d96u+9ZutmWorpdXV1dXV1dXV15mefsxupjGYmdrX+xn2a/2NeOnZzsbk369d6Y/tMXV1dXV1dXV1dbV1dXWXdmMyF1dXV1dXV1dXUeuePszL8fS6urq6urq6urrNv2bvpdXV1dXV1dXV1m/k7lWdPo/gd0ACqqqqqqqq/Tcnwqqqqqqqqqr9MOre4ABm9c8/RWpiMTO2AFvTX9giv7RWbOmZ07YAZzM1tub9P6xFVVVVVVVfozd3ZjOmKqqqqqqqPXPCsy/C9sAOhl/R+nP8KAAAB/8A4bl1/AQhCEIQhCEIQ4qUpSlKUpSlKVx3wACnmb57wADF6/trCYhCEIQhCEIQh5tIQhCEIQhCEIQzV3k7wABa1mO9/wD8ZenjvAAM3jdb07wADPxwAN2ddbdGfgNq6urq6urq6urq6urq73uryt/AXV1dXV1dXV1dXV1dXV1dXV1dXV1dXV1dXV1dXW2dd8/Td3F1dXV1dXV1dXV1dZu79Lq6urq6urq6urq6urrdZ8rq6urq6urq6urq6urq6urq6urq6urq6urq6urrN3V1u6urq6urq6urq6usPkhCEIQhCEZ1142EIQhCEIQhCEIQhDdzt3EIQhCEIQhCELemMurfKEIQhCEIQhCEIQhcVVVVVVVVVVVVVVVV+mczrvlCG7mt2IQhCEIQhm1m5iEIVVVVVVVuM+fohC5vbuIQjuQhDdxUIQhCEIXcU+V1dXV1dXV1W/R+vzd3vdXnb+I6rq6urq6urq6urq6urq6urq6urq6urq6l13z9u5W5udq6urq6zdXV1dXV1dXV1us+7N7O7fj7avZv272b9H8j7vHXXiZ4VVVVVVVVVVVVVVX7wN74AAll3yqqqqqqqqqqqqqqyPxgAAB0dd8/eBu1uzvAAM+8BvYX7wL9+bF+8Cqqqqqqqqqiz9qUvGu+qta1cN+j9KUpSlKUpSlKUpSlKUpSlKqq0uqqqqqqqqrrPlvlSlKUpSlKUpSlKUpSlKUza1rWta1rWta1rWta2mdelfA+B8D4HwPgfA+Bu99KUpSlKUpSlKUzalKU3alKUpSlKUpSlKUpSlKUpSlKUpa1rWta1qXwvhfC+GuP1XUv7+2nz9fGzx+bu9+HXfP+MzJ8/lZv13f8Z/Ibt679IQhH0Znc6vEzwhCEIQhCEIQhCEIQhCEIQhCENwhCEIQhCEIQhUOu+VVVVVVVVVVVVVVVVVVVQhCEIQhCEIQj6MmfKEIQhCEIQhCFxcQhCEIQhCEIZhuEIQhCEIQhCELi4hCEIQhCEIQuKqqqqqqqqqsc+W7euqq6urq6urrbi6urq6urq6urq6urq6urq6urq6urq6urq6urrXPlr43vXcZvf3tcmuTXJrk1ya5NcmuTXJrk1ya5NcmuTXJrk1ya5NcmuTXJrk1ya5NcmuTXJrk1ml1dXV1dXV1dXV1dXV1dZrfldXV1dXV1dXV1dXV1dXV1dXV1dXV1dXV1dXV1dXV1dXV1dXV1dXV1dXV1dXV1dXV1dXV1dXV1dXV1dXV1dXV1dXV1dXV1dXWufK6urq72Nz8qHTPP1h03x382/P5ub3829N/8G3Z8/dSlKUpSm7qlKUpSlKUpSlKUpSlKUpu/5VVVm8Wta1rWta1rWp/OVVVVVVVVVVVVVVVVVVVmqqqqqqqqqrNKUpSlKUpVVfrVVVVVVVVVUpSlKUpSlKUpSlKUpSlKUpSlKUpSlKUps/KlKUpXa38aHTPK6urq6urrN72budca/JdXV1dXV1dXV1dXV1dXV1dXV1dXV1dXV1dXV1dZpdXV1dXV1dXV1dXV1dXWaXV1dXV1dV2Cqil1dXV1dXV1dXV1dXV1u7q6urq6urq6urq6urq6urq6urq6urq739j5X77q6urq6urq6urraurq6urq6urq6urq6urq6urq6ut1nz2c3vePCnz+b1XV1dXV1dXV1dXV1dXV1T5+2r2Kurq6urq6urq6urq6urv491dXV1dXV1dXV1dXV1sfK6urq6urq6u9zeiqqqqqqqqqqq2PlVVVVVVWd8AAeF+1VVVVVVVVVVVVVVVVVVaqqqqqqqqqsvvgAFVVVVVVVVVf8CAAaVVVVVV7oAG7PwAABsKqqqqqqqsKqqqqqqqqsU8+fzQAAAAvfAAMr5VVVVVVVVVmz/AAAAAAN2qqqqqqqqq2O2AFVVVVVVVfpvRVVVVVVVVWe551l9VKUpSlKUpSlMs6qqqqqqqqqrN6pSlKUpSlKUpSlKUpSlKUpSlfRVVVVVVVVWW9G7qlKUpSlKUpSlKUpSlKUpSlKUuxSlKUpSlKUpSl1SlKUpSlKUpSm7t6qqqqqqr3aqqqKU3dXV1dXV1dXV1dXV14WF3eutfDwzW9cUpSlKUpSlNM675+m39KUpSlKUpSl19KpSlKUpSlKUpSlKUpSlKUpSlKZr9rq6urq6urq6urq6us3cU3dUpSlKUpSlM3dXu1X6+lSlKUpSlKUpSlLqlKUpSlKUpSl1dUpSlKUpTx6fG/+t7+52vAz7GbGbeudqfXfP2bn7zteWZP8Dm/re3mfZudrMvf3b/jNcmvxv8A63sAMpV1dXV1dbu/wurq6urq6uvAz7n81msrLnhVVVVWJ13yurq6urrb+lKUpSlM3dZsXV1dXV384AAAH+e3ZAKurq6urrd7ADNq6urq6urq6urq6urq6ut1vwurq6urq6ta1rWta1rWtSlrWta1rWta1/RdXV1dXV1dXfr4t/jf/W/dnXs7k+FxcXFxcY9GFv31cM3Ny54+6HXfPYx+8XFxcXFxnXwzMz829jI6b4XFxcXFxcZM+VxcXFxcXFxsXFxcXFxk1cXFxcXFxcXFxcXFxcXG7fhcXFxcXFxcbPzvC4uLi4uL9nj3+N/9auLi4zqzp2sT4+zDowt672d9cMxuXFxcXGMzrvlcXFxcXFxcXFxjPOfZmXYzMzFxcXFxcXFxcXFz8Pd/WdrNXFxceWZO1v2YuLi4uLi4uLi4uLi4uLjdxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxn6auLi4uLi4uLi4uLi4uNmt6drwfxb/AOt+mZWZO3uVuT6eBh57emuGbm5c+kOu+e5+zPpmVmTuZt/B3b3sysydzc/B3fy83vblbk+26urq6uvN+zPP/wBZHd//AOprnotVVVVVVVVa64a/alKUpSlKUpSm6yPClKUpSlKUpSlWXr36qqqqv998AAzYpSlKUpSlKUpevfAAN9V1dXV1dXV1dXV1dXV1dXV1dXV1dXV1dXV1dXV1SlKUpSlKUpSm7VKUr7f70x/em9/czcm+G++O7Drvlmxm3ubsbtfo3uw6Z5Xbf2/kfmw6b47u7/js3u7v6zsf3pj+9N/B3M3Jvhvvjtw675+ubGbe1uxu36/o3tw6Z5+vh/I/Nh03x293/IZvb3f1nZEf3BqlKUz2VVVVVVVVVs3Jvhvrjs4nXVVVZrFKUpSlK+jd3VVVZrwpSlKUpSPyuqqqx/KVVVVVVVVVVVVVVVVVVVrnTfClKUpSlKVVVV1SlKUpSl1VVVVVVVVVUpSlMKqqqqrdilKUpmlKUpSlKbvxilKUpX1Ef3Rv35v63vbmbk3w31x9+Z+9+/N7G79+b98Pn782dcfyPzc2dN/wu7fvzZ8ffuzsZv37v6zsiP7I1VVVVVYQhCEIQhCEIQ3c3JvhmuEIQhnvVVVVVVhVVVjSqqqqqwqqqHyqqqqqs2dcYb8oQhCEIQhCEIQhCEIQhCEIQhDM503whCEIQhCEIQhcVVVVxCEIQhCEIQhCG7VVVVVVkfCEIQhVVVVVX6IQhDSqqqqq/uje3UIQhCEIQhCEN3Nyb4UcL9MTrvlVVVVVVWbFVY3aqqqqqqrPZVQ+VVVVVVVWMN+VVVVVVVVVVVbuYhCEIQhCEIYeNVW7mIQhCEIQhCGYQhCEIQhCEIQhCEIQhCEIQhCEIQhCEIQhCEIQuKrc50TynlPKeU8p5TynlPPd3O9uZuTWmuEOu+e7mxc8t293N/WofPd8KfPf3Y3b3Z9N8N2d7N/xma/RnbuLi4uLi4uLi4uLi43e/bpnhhvz+ZnVjPOd6xhvz3bmN297MvwuLi4uLi4uLi4uLi4uLjouLi4uLi4uLi4uLi4uLjo6Li4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uN9O5m5i54/Kp0zx9M2dcfyPzdz9536fPb3Y3b3sy/H+b3e7pua5uYuePyKdM8fZmzrj+R+bufvO/T57O7G7e9mX4/wA3u9/YhkLm9F1dXV1dXV1dXV1dXV1dXV1dXV1dXW29M8fddzwr3rk1ya5NcmuTXJrk1ya5NcmuTXJrk1ya5NcmuTXJrk1ya5NZuv3sXV1dXV1dbfK6urq6urq6urq6urq6rP2ze/vXJrk1ya5NcmuTXJq77XV1dXV1dXV1dXV1dXXVdXV1dXV1dXV1dXV1dXV1dXV1dXV1dXV1dXV1dXV1dXV1dXV1dXV1dXV1dXV1dXV1dXV1dXV1dXV1dbpdXV1dXV1dXV1dXV1dXfrvqaZuYuePxL9M8fm5l+OzufvO94Zt/wDD938PfU0zcxc8fhX6Z4/NzL8dvc/ed7wzb/4bu/i76mmbmLnj8C/TPH5uZfju7n7zveGbf/C938eLdXV1dXV1dXV1dXV1dXWzJi6urq6urq6urq6urq6zd/Aurq6urq6urq6urq6urrdXV1dXV1dXV1dXV1dXWbt6efwLq6urq6urq6urq6urq6zdXV1dXV1dXV1dXV1dXW6ZpdXV1dXV1dXV1dXV1dXe/t/H33dMzMyZ4727+s+n7M7nlmT83d72ZWZP8Xu/4Dc/FoumbmZMzp3gAHlERLriqqqqqrOrMn5u6qqqqqqqqv0zp+GAABVVVVVVVVVXtADftDldoAX8MAADd/DAADzt8M2dM8KUpTCqqqqqqlKUp+zOznRVVVVVVVVVVVVVVVVVVW/oqqqqqqqqv0zoqqqqqqqqqqqqqqUpSlKUpSlKUqqqqqq3VVVXWbVVVVV+lVVVVVVVVVVVVVqqqqqqqqqqv0/Y8OmffmxcXFxcXFxcXFxcXFxcXGM859+TFxcXFxcXFxcXFxcXFxcXFxcXFxcXELn3b+md/Jn+Fv37t+PvrNzfvuLi4uLi4uLi4uLi4uLi4uLi4uLi4uNi4uLi4uLi4uLi4uLi4uP4H5O5+8/NzZ9u7+s7+ZP8Pm/bu347Wbfn7N38nc73hdXV1dXV1dXV1dXV1dXV1dXV1dXV1dXV1dbfOLq6urq6urq6urq6urq6urq6urq6urq6urq6urqveq965NV71dXV1dXV1dXV1dXV1dXV325NcmuTXJrk1ya5NcmuTXJrk1ya5NcmuTXJrk1ya5NcmuTXJrk1ya5NV7XV1dXV1dXV1dXV1dXV1d9rvvVe2639rq6urq6urq6urq6zXtd96u+9XV1dXV1dXV1dXV1dXV1dXV1dXV1dXV1dXV1tXV1dXV1dXV1dXV1d/M3J1/8AGd3vZt/N3PyN39YzZ3d2ddU+Gb+t/wAXu1+jfzc2d7d/xu527i4uLi4uLi4uLi43fX1zYuLi4uLi4uLi4uIU+Prm/rfzri4uLi4uLi4uLi43b9f0b293MXFxcZub29wuLjMYhcXFxcXFxcXFxcbvfuLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLjZ/g92/bm/rfzdzv5v63s7s+7NvZ3f1n25s/8c3b9+b+t/N3O/m/rfv3Z9/hm37939Z9+bP8A+Gh//8QALBAAAgECBQUAAgMBAQEBAQAAAAERYaEhUYGR8DFBcdHhMPFAscEgEFBgwP/aAAgBAQABPxD9Az9Az9Az9AzqAqxHnHWLrYhTeEknq2+rbfV9yMj2Z+gZ+gZ+gZ+gZ+gZ+gZ+gZ+gZ+gZ+gZ+gZ+gZ+gZ+gZ+gZ+gZ+gZ+gZ+gZ+gZ+gZ+gZ+gZ+gZ+gZ+gZikNCW2nCsQBMXQct/2f0OvRMAxWU3Vuz6tisR3bRHYfLXhLdjBKylDyZDyZDyZDyZDyZDyZDyZDyZDyZDyZDyZDyZDyZDyZDyZDyZDyZDyZDyZDyZDyZDyZDyZDyZDyZDyY6UnYtPyZc+3koPZlB7MoPZlB7MoPZlB7MoPZlB7MoPZlB7MoPZlB7MoPZjy5JBNtt9EhGDj0JSPm33GzwbbMadHTBlB7MoPZlB7MoPZlB7MoPZlB7MoPZlB7MoPZlB7MoPZlB7MoPZlB7MoPZlB7MoPZlB7MoPZlB7MoPZlB7MoPZlB7MoPZkGYE8QSPqzo3XDHM5DNtty228W23i22UHsyg9mUHsyg9mUHsyg9mUHsyg9mUHsyg9mUHsyg9mUHsyg9mPXK5Yt2ZFAyYZW0NC/QIoPZlB7MoPZlB7MoPZlB7MoPZlB7MoPZlB7MoPZlB7MoPZlB7MoPZlB7MoPZlB7MoPZlB7MoPZlB7MoPZlB7MoPZlB7McrBJz4ZQezKD2ZQezKD2ZQezKD2ZQezKD2ZQezKD2ZQezKD2ZQezJZPZjtgoPDx+H6Bn6Bn6Bn6BksnuiWT3RLJ7o6earoecfButurrHn8JJrVt9W2+r7ksnuiWT3QkdnuihdFC6KF0ULooXRQuihdFC6KF0ULooXRQuihdFC6KF0ULooXRQuihdFC6KF0ULooXRQuiSwEUttrAgiTa6mJ+Tr/RRv7FG/sRSEwZjZTPFuz6tisRmTRDYiy1rqn4E1S12NdjXY12NdjXY12NdjXY12NdjXY12NdjXY12NdjXY12NdjXY12NdjXYfOkW1wO/byUrr2Urr2Urr2Urr2Urr2Urr2Urr2Urr2Urr2Urr2Urr2Urr2Urr2OjtJIhtt9liRESXYaR38338f+jV4MbL3gS5BLkEuQS5BLkEuQS5BLkEuQS5BLkEuQS5BLkEuQS5BLkEuQS5BLkEuQS5BLkEuQRJpUgmlfXMdcPo3m3LbbbbabbeLbbcttlC6KF0ULooXRQuihdFC6KF0ULooXRQuihdFC6KF0ULooXRGuualuzNAyKTnNEJLk90ig9xQe4oPcUHuKD3FB7ig9xQe4oPcUHuKD3FB7ig9xQe4oPcUHuKD3FB7ig9xQe4oPcUHuKD3FB7hqwhz5wEsnuiWT3RLJ7olk90Sye6JZPdEsnuiWT3RLJ7olk90Sye6JZPdEsnuiWT3Qx4JOPKJZPdEsnuiWT3RLJ7olk90ULsoXZ003V1PW3W3V1UnXhJTq25ltvq+5QuyhdlC7KF2ULsoXZQuyhdlC7KF2ULsoXZQuyhdlC7KF2ULsoXZQuyhdlC7KF2ULsoXZQuyhdlC7JaBIpbbZhyTQ+o8dTF/wBFC7KF2ULsoXZEuahti8pnin2fVsUOwhqOjLLWuqfgTVKhdlC7KF2ULsoXZQuyhdlC7KF2ULsoXZQuyhdlC7KF2ULsoXZQuyhdlC7KF2ULsnaFbfA28kEIhEIhEIhEIhEIhEIhDizSxBtvsiCyUzEj5t9yhdlC7KF2ULsblEf2NV2uyhdlC7KF2ULsoXZQuyhdlC7KF2ULsoXZQuyhdlC7KF2ULsoXZQuyhdlC7KF2ULsRKG6kJSvr2G64fRpM5G22222228W225bbKF2ULsoXZQuyhdlC7KF2ULsoXZQuyhdlC7KF2ULsoXZQuyhdlC7PLBTluzNAzE2+2hYLk90iEQiEQiEQiEQiEQiEQiEQiEQiEQiEQiEQhksO/wDRQuyhdlC7KF2ULsoXZQuyhdlC7KF2ULsoXZQuyhdidCV2ULsoXZQuyhdlC7KF2VFf0RpOQuph5FDe3UQmKElOrbiW2+rKiv6Kiv6Kiv6Kiv6Kiv6Kiv6Kiv6Kiv6Kiv6Kiv6Kiv6Kiv6Kiv6Kiv6Kiv6Kiv6Kiv6Kiv6Kiv6Kiv6Kiv6Kiv6Kiv6Kiv6Kiv6Kiv6Kiv6JJAiltt+iPJpI+o8dDr/RUV/RUV/RUV/RUV/RUV/RUV/QvemobPHym6p9n1bFCCCaht0sta6p+BNUpUJUJUJUJUJUJUJUJUJUJUJUJUJUJUJUJUJUJUJUHzU7Vvaw+PPSor+ior+ior+ior+ior+ior+ior+ior+ior+ior+ior+ior+ior+hYcpJEttvssDAAmYkdPd9yRIkSJEhzG6yJUJUJUJUJUJUJUJUJUJUJUJUJUJUJUJUJUJUJUJUJDymm5StdGjF1wxvbbctttttttvFttqW2yor+ior+ior+ior+ior+ior+ior+ior+ior+ior+ior+ior+ior+ior+ior+ior+ior+ior+ior+ior+ha5dW7MigZOJPN4qS5PCqX+FTc/RU3P0VNz9FTc/RU3P0VNz9FTc/RU3P0VNz9FTc/RU3P0VNz9FTc/RU3P0VNz9FTc/RU3P0VNz9FTc/RU3P0NHeXl+ipufoqbn6Km5+ipufoqbn6Km5+ipufoqbn6Km5+ipufoqbn6Km5+ipufoqbn6GzBPc/RU3P0VNz9FTc/RU3P0VNz9FTc/RU3P0QhNtLA6WG62FJpKEkt23Mtt9Wedvp52+nnb6edvp52+nnb6edvp52+iUYN2+nnb6edvp52+nnb6edvp52+nnb6edvp52+nnb6edvp52+nnb6edvp52+nnb6edvp52+iNIIS219Ikm0zyf6f0QQQQQQQQQ/wBrq2PlM8U+z6tijGo4MFgZaP66ExIIIIIIIIIIIIIIIIIIMVlCdy/07Hnb6edvp52+nnb6edvp52+nnb6edvp52+nnb6edvp52+nnb6SJsJIUtt9liYQ0MepI+bfcggggggggcsGOHe3087fTzt9PO3087fTzt9PO3087fTzt9PO3087fTzt9PO3087fTzt9PO3087fTzt9PO30SiZuoXilfZYw2XDGzNhs223i23i223LbZ52+nnb6edvp52+nnb6edvp52+nnb6edvp52+nnb6edvp52+nnb6edvp52+nnb6edvp52+nnb6edvp52+iaPCxbszt2DujTWvCe6X+EEEEEEEEEEEEEEEEEEGRPHx0PK308rfTyt9PK308rfTyt9PK308rfTyt9PK308rfTyt9PK308rfTsJ2+nnb6edvp52+nnb6edvp52+nnb6edvosAlCSu2+rbfV/jTjB9P4LExJFLbIApSPyf6f1+PL4NnipaFZ9WxRkUGCwUtas+ifwcS/KuxcR5/M9MwkhS230SRhoQxfVI7+b7+PxtJqGNNOH/BWqBsoXikfZdmy4Y2222222223LbeLbbxbb/k3KCbszQM7/w1rBcnul/n8FuMF1/PJgun9/ijXmhGvNCNeaEa80I15oRrzQjXmhGvNCNeaEa80I15oRrzQSSzIIIIIIIIIIIIIIHIXCSZdlnXEbApSaj/ALP6IIIIIIIIIIIIMpi2eKloVn1bFDKmZKGk0e/WU+jWJAgQIECBAgQIECBAgLm99qLFxBHN80I15oRrzQjXmhGvNCNeaEa80I15oRrzQjXmhGvNCNeaEa80GKWwSRi23gksDCyhi8GkfWvm+5BBBBBBBBBBBA0ahkr+iCCCCCCCCCCCCCBK4EQjohMZkPDJj1HJtts22225bbxbbeLbZGvNCNeaEa80I15oRrzQjXmhGvNCNeaEa80I15oRrzQjXmhGvNCNeaEa80I15oRrzQjXmhGvNCNeaEa80I15oRrzQjXmhGvNCNeaGiQU9hkYBO1yWvBKuqa1SI15oRrzQjXmhGvNCNeaEa80I15oRrzQjXmhGvNCNeaEa80I15oRrzQRLBTzQjm+aEa80I15oRrzQjXmhGvNCNeaEa80I15oRrzQjXmhGvNCNeaEa80HTwUx/ZBBBBBBBBBBxN+zib9nE37OJv2cTfs4m/ZxN+zib9nE37OJv2cTfs4m/Yv2Xs4WHCw4WHCw4WHCw4WHCw4WHCw4WHCw4WDXi+kG5bfHgRjI4xKoQfV11C39KqRpg+ZbP+h4w3wsOFhwsOFhwsOFhwsOFhwsOFhwsOFgqvtJvlv9lk1x4SxSZLNt2O4RcXosEkcjfs5G/ZyN+zkb9nI37ORv2cjfs5G/ZyN+zkb9nI37ORv2cjfs5G/YncJFipaSrs8ejsxqm04NYNNuU9zkb9nI37ORv2cjfs5G/ZyN+zkb9nI37ORv2cjfs5G/YkZJM23CSbxuKk68k3paetTCOly/9Dgb9nA37OBv2cDfs4G/ZwN+zgb9nA37OBv2cDfs4G/ZwN+xrdPCl+xq3LV37ORv2cjfs5G/ZyN+zkb9nI37ORv2cjfs5G/ZyN+zkb9nI37ORv2cjfsf8ApzCXRk9Gh0eHR7B0skHDTTeDRD9vpD9vpD9vpD9vpD9vpD9vpD9vpD9vpD9vpD9vpD9vpD9vpD9vpD9vpD9vpD9vpD9vpD9vpD9vpD9vpD9vpD9vpD9vpD9vpD9vpD9vpD9n7MwApaQ6OnAOpeGtuHJ9i91/hwsOFhwsOFhwsOFhwsOFhwsOFhwsOFhwsG+vyHC3s4WHCw4WHCw4WHCw4WHCw4WHCw4WHC3s0glLwv1Jzw3VGBVZDnYc7DnYc7DnYc7DnYc7DnYc7BQgafVRj3EdmiOS2+Eclt8I5Lb4RyW3wjktvhHJbfCOS2+Eclt8I5Lb4RyW3w8ONCdOaE6c0J05oTpzQnTmhOnNCdOaE6c0J05oTpzQnTmhOnNCdOaErD9Qbb0MI756V0Omb7k6c0HFPJpBQ0NNNNDhM9WDwfEtn/AEPGG/FbfDxW3w8Vt8PFbfDxW3w8Vt8PFbfDxW3w8Vt8PFbfCI6hNkm+y6Yb+yFLEcEvLfYFmnosIRHgjwR4I8EeCPBHgjwR4I8EeCPBHgjwR4I8C8OknXJfQcW00TWDTXQhktiGS2IZLYhktiGS2IZLYhktiGS2IZLYUySRtuEksWxDAzLHDBMlXNkMlsYMUkmjtfK+Ov48Bs3LjmhOnNCdOaE6c0J05oTpzQnTmhOnNCdOaE6c0J05oTpzQnTmhOnNCdOaE6c0J05oMKMNPolLBkdng6PDpNQ5A0hg001g0TpzQnTmhOnNCdOaE6c0J05oTpzQnTmhOnNCdOaE6c0J05oTpzQnTmhOnNCdOaE6c0J05oTpzQnTmhOnNCdOaE6c0J05oTpzQjkiGS2EEdF2DIwQwDyGtOFyeGC91/hOnNCdOaE6c0J05oTpzQnTmhOnNCdOaE6c0J05oTpzQnTmhOnNBzynlCVOaE6c0J05oTpzQnTmhOnNCdOaE6c0J05oTpzQlTmh2RkWVup4caHhxoNUrUdUdqrDoTpzQnTmhOnNCdOaE6c0J05oTpzQnTmhOnNDEGaE/RPHBrs+woSE8GmqhE4NHnb6edvp52+nnb6edvp52+nnb6edvp3J8YfTzt9PO3087fTzt9PO3087fTzt9PO3087fTzt9PO3087fTzt9F8N+EKW29TCuv08FdvN9yCCB1ZyBNhQ008Gmh1XVzYeD+rZ/0PGG4IIIIIIIMX7abJPdl0w3wlieC+LiXcgwWL0WEI87fTzt9PO3087fTzt9PO3087fTzt9PO3087fTzt9PO3087fTzt9PO3087fTzt9PO30ivpu0QqXj1GybTwawaaxTIIIIIIIEzJKW24SSxbErgNlomSrmyCCCKkeTD0eT2QQQQQQQQQQQQQQQQQQQQQQQQQQQQNvTU+iXR/ZP8Hh0dQcghDBpp9GiCCCCCCCCCCCCCCCCCCCCCBSPO3087fTzt9PO30Tx4WLdmdkJb4poX9rBO6PO3087fTzt9PO3087fTzt9PO3087fTzt9PO3087fTzt9PO30cu+Pj6edvp52+nnb6edvp52+nnb6edvp52+nnb6L7zsUdPpBBBBDPT6uHSqoedvp52+nnb6edvp52+nnb6edvp52+nDP8ACRe2D4F2wa7MUhDJ9U2k7o7NH7x6P3j0fvHo/ePR+8ej949H7x6P3j0fvHo/ePR+8ej949H7x6P3j0fvHo/ePR+8ej949H7x6P3j0OoWkGlti8HfptpXR6ZvuVGzKjZlRsyo2ZUbMXK3SBhQ001DTQ+9H6GB4P6tn/Q8Yb/ePR+8ej949H7x6P3j0fvHoTXhTYz+y6bg8SxnFfKXewLBYvRYQig9mUHsyg9mUHsyg9mUHsyg9mUHsyg9mUHsyg9mUHsyg9mUHsyg9mUHsyg9mUHsyg9mUHsx6N5J1UYKGpw0NYNNOU9io2ZUbMqNmVGzKjZie0kxvBJJkOCWWWCZKubIeT2IeT2IeT2IeT2IeT2IyPZjsO8rjiSHk9iHk9iHk9iHk9iHk9iHk9hyuz2GmxaezKD2ZQezKD2ZQezKD2ZQezKD2ZQezKD2ZQezKD2ZQezKD2ZQezKD2ZQezKD2ZQezKD2ZQezFPjfKEuj+y9no8Ojy3tgSGDTTWDRUbMqNmVGzKjZlRsyo2ZUbMqNmVGzKjZlRsyo2ZUbMqNmVGzKjZlRsyo2ZUbMqNmfoGfvHo/ePR+8ej949H7x6P3j0dFsDlkGRghjfk0N6Sd0fvHo/ePR+8ej949H7x6P3j0fvHo/ePR+8ej949H7x6P3j0fvHobczy9H756P3z0fvno/fPR++ej989H756E+pdlLC3U/ePR+8ej949H7x6P3j0fvHoTmO7o2FVh0/o/fPR++ej989H756P3z0fvno6vb64sO2Wf8A71xp4P6J/jXZiwyU+q7t3R2a/hvoPpIUttmBdXMSPm33/AsMu0CbChpp4NNDEhu1bDwf1bP+h49f+FpbitilvsumAqeJYser5a/sE9Fh/HQnTTquy5P+jGmnDwawafVP/hJtpJS3gku4tOo/CX4OmKwa6My4vGv/AC3B1/jsqEvg+iHR/ZP8Hh0fWfaCEMGmn0a/hpSJR+Dsdg+7dmdkO/8ATQsF/wATuvzNT5/6gzOyy+/hgleGxVU/5e8L4lmxKZe/XubWKfiSldeyldeyldeyIcp4PahN8GuzFDET6qVLd0Tg0Urr2Urr2Urr2Urr2Urr2Urr2Urr2Urr2Urr2Urr2Urr2Urr2Urr2Urr2Urr2Urr2OrtQiG23qQnSuYkfNvuQyGQyGQyGQyGQxQZdoE2FDTTwaaHNp3pMsPB3Vs/6Hj1pXXspXXsUsNW9S/2WKwFTxLO8OrlN3IMFi9FhCKV17KV17KV17KV17KV17KV17KV17KV17KV17KV17KV17KV17KV17KV17KV17KV17KV17KV17KV17KV17KV17KV17KV17KV17FjjJ1UrCu3XQQ4NQ11WHspXXsTWkpN4JKMbjkgl6rQsSldeyldeyldeyldeyldeyldeyldeyldeyldeyPFKGujlezASO3FRd1KV17KV17Gy6q6IbIZDIZDIZDIZDIZDIZDIZDIZDIZDIZDIZDIZDGpCXweCQ6P6JP8Hh0dHfeAIYNNPo0Urr2Urr2Urr2Urr2Urr2Urr2Urr2Urr2Urr2Urr2Urr2Urr2Urr2Urr2Urr2Urr2Jva69iR2uvZSuvZSuvZSuvZSuvZSuvZSuvZSuvZSuvZSuvZL36DlZBnZDE+ZaIusE7rsUrr2Urr2Urr2Urr2Urr2Urr2Urr2Urr2Urr2Urr2Urr2Urr2Urr2N3bHyvZSuvZSuvZSuvYn3HZSsL9Tma9nM17OZr2czXs5mvZzNezma9nM17OZr2czXsRnCdkrCqx6HM17OZr2JSYnVYLN4imF5OVLe5zNeyFktiFktiFktiFktjvdYdNJ/jXZiO6U+q7t3SdmhLuyFkiFkiFkiFkiFkiFkiFkiFkiFkiFkiFkiFkiFkhxNqAsW8iK6DoJpXzb7kLJbELJbELJbELJbELJbELJbELJbELJbELJbELJbELJbCo7tAbChpp4NNDXuO5S19/q3/wAHj16E1b5Ndl0wFTxLFTuteErsCzT0WH8tKRpJ1Xbz/wBEpwPFERjOUdZFpAm/CVfyrjHkfHUbSUsbly/5bIkGDwSRdH9k7tHh0dI7aCGhw00+jTIWS2IWS2IWS2IWS2IWS2IWS2IWS2IWS2IWS2IWS2IWS2IWS2IWS2IWS2RCyWxCyX5EdmdH37DOyEM03loWD/id0QslsQslsQslsQslsQslsQslsQslsQslsQslsQslsQslsQslsQslsNX2U+CHSFPgTiJZMFh9/JBBK8O6dqqg94S9JZsUwlj3cYv/ANhktiGS2IZLYhktiGS2ECJp4dtPa7PsISDbqoxbuicGiGdvpDO30hnb6Qzt9IZ2+kM7fSGdvpDO30hnb6Qzt9IZ2+kM7fROySxbwSS63IQsvoS0yVH3fXQi8U006fTzt9PO3087fTzt9PO3087fTzt9PO3087fTzt9PO3087fRKCBoDYUNI5TTQrO1iGNpbMfkN4LIggggggggggggggggggggggggggghLInp/SMGPnmJCyWxCyWxCyWxCyWxCyWxCyWxCyWxCyWxCyWxCyWxCyWxCyWxCyWxLEUXdugvpV3Oy5Key5DJbEMlsQyWxDJbEMlsQyWxDJbEMlsQyWxDJbEMlsQyWxDJbDpLQablYHdOOrTt26mG+ejEpbJ0yfchktiGS2IZLYhktiGS2IZLYhktiGS2IZLYhktiGS2IZLYaQ4AbbcJJJS228EkiIMmaKGroy6YEd0deyXlb6eVvp5W+nlb6eVvp5W+nlb6eVvp5W+nlb6eVvp5W+nlb6Mb8Oihi2RSPSGW6Wu0m+i5DJbEMlsQyWxDJbEMlsQyWxDJbEMlsQyWxDJbEMlsQyWxDJbGFgLiNM+2Vb4dZEOKPuIU2IU2IU2IU2IU2IU2IU2IU2IU2IU2IU2IU2I0CENE9sOhCmxCmxCmxCmxCmxCmxCmxCmxCmxCmxCmxCmxCmxCmwp0Ely4EKbEKbEKbH6h+j9Q/R+ofo/UP0fqH6P1D9E6xsHpwm2DXZiEhtPqmnLd0dmig9mUHsyg9mUHsyg9mUHsyg9mUHsyg9mUHsxPJJjeCSTHLLTb9XDwoig9mQCHSPS+IIeT2ZQezKD2ZQezKD2ZQezKD2ZQezKD2ZQezKD2ZQezKD2YkjlJpqjGrA14WVIeTIeTIeTIeTIeTIeTIeTIeTIeTIeTIeTIeTIeTIeTIeTIeTIeTIeTIeTIeTIeTIeTIeTIeTIeTHhi+iGMJNJR41ZDyezMEHHZw8PhDyezKD2ZQezKD2ZQezKD2ZQezKD2ZQezKD2ZQezMAHRQ5byRBB0UOEskJI00kacppNNNdGnmMVIaXo4hJx0KD2ZQezKD2ZQezKD2ZQezKD2ZQezKD2ZQezKD2YxGht+riUrzDTNtpm3LbTlsR1uqhwmTFtbnuLFsnSvcoPZlB7MoPZlB7MoPZlB7MoPZlB7MoPZlB7MeB4AbbYJJYttvBJIbOQug03T6vukfRd+r7JUHsyg9mUHsyg9mUHsyg9mUHsyg9mUHsyg9mUHsyg9mUHsxDVuOySctkjDoZDhMlUhxSGqMhFNdDE9L4koPZlB7MoPZlB7MoPZlB7MoPZlB7MoPZlB7MoPZkdolllKR93XJDmchty25bbfVtjUhtsxtGB2cM/QM/QM/QM/QM/QM/QM/QM/QM/QM/QM/UP0MWInsoeH0oPZjd9nPhn6Bn6Bn6Bn6Bn6Bn6Bn6Bn6Bn6Bn6Bn6Bn6Bn6Bn6Bn6Bn6Bn6BlC6KF0ULooXRQuihdFC6OpxsHvBP8AGuzFF8p9V3bujs0QyGQyGQyGQyGJhJKW8El1bHpLUv1eGFEULooXRQuiA1Jt4w/8hkMhkMhkMhkMhkMhkMxOtf01KpQuihdFC6KF0ULooXRQuihdFC6KF0ULooXRQuihdFC6KF0ULooXRQuihdFC6KF0ULobJS8EurlDsK+vhDIZDGbA8vhBDIZDIZDIZDIYnYF3byRsgXZLJEMhilNNYNYpp4p5ohrARYPDD6EMhkMhkMhkMhkMhOBusX2XNhy225bbltvFshkMSXw11XZO6ayFPCfSRS2TpkQyGQyGQyGQyGNpGxINttpJJYttvBJIamtrIbLu8kfRd+rIZDIZDKF0ULooXRQuihdFC6KF0ULooXQluGl0WEt2Sx6na50R0TJVzZDIZiYR+DqWvmSGQyGQyGQyGQyGS2MjDpCu5khpzkbbltuW2+rbkoXRQuhu1DV0NMV4eZDIZDIZDIZDIZDFsWsdMPpQuihdFC6KF0ULooXRQuihdFC6KF0ULooXRQuihdFC6KF0ULooXRQuihdE8r/SeV/pPK/0nlf6Tyv9J5X+k8r/AEnlf6QklsHvBN8GuzGXxE+qnFu6JwaOU/TlP05T9OU/TlP05T9E7aSk3gkn1uLWUn6udixJ5X+k8r/SeV/pPK/0nlf6Oiqlz7fBDK/0hlchlchlchlchlchlchlchlchlchlchlchlcwYrBqv0ixgTh+cyEQiEQiEQiEQiEQiEQiEQiEQiEQiEQiEYHTlgr180I5X+kcr/SOV/pHK/0jlf6P0Pp56fCeV/pPK/0nlf6Tyv9J5X+k8r/AEnlf6MpNCnFugz2BTglksSOV/pHK/0jlf6Ryv8ARQaalNOU03Ke5FWAnR9lEEEEEEEEd4jLF9lzf9BybbltuW23Le5HK/0jlf6Ryv8ASOV/ol4b6NTgmTUnd50Y8WydMmQQQQQQOEbbSSTbbaSSWLbbwSSGs2tMhDabLvmkfRd+rI5X+kcr/SOV/pHK/wBI5X+nhcnlf6Tyv9J5X+k8r/SeV/pPK/0nlf6OPT7KVLdklPUjLgug8EyWOLzZyn6cp+nKfpyn6SWKUNV+iES4TCTa8fojlf6Ryv8ASOV/pHK/0jlf6Ryv9I5X+kI0hgpwV1OuSGw5G25bbltvq25J5X+k8r/SeV/pPK/0cEOS8/SRY5+nKfpyn6cp+nKfpyn6cp+nKfpHi8XbHpfqTyv9J5X+k8r/AEnlf6Tyv9J5X+k8r/SeV/pPK/0nlf6Tyv8ASeV/pPK/0nlf6Tyv9J5X+k8r/SeV/pPK/wBJ5X+k8r/SdOaE6c0J05oTpzQnTmhOnNCdOaE6c0J05oTMTbB7cJtg12YqvlPqu6fdNdmiCCCBM2ksW+iFpgN9kyX4JKfj6N8ehBBBBBBBBBBBBApTlOGjpXC/tVEEEEEEEEEEEEEEEEEEEEeBuBfXwgggggggbwF8IIIIIFJL0XdseYKixhLJYE6c0J05oTpzQnTmhOnNCdOaCQ004axTTcp5ocqYknR9l2EqEqEqEqEqEFuTZYvtPP8AwNjbblty225bJ05oTpzQnTmhOnNCdOaE6c0FF6yaxhMngSAx7nVqkqEqEqEqDUGxJJNttwkl1bfZIcuAPuJstMEfRd+rJ05oTpzQnTmhOnNCdOaE6c0J05oJllzQgggggcX4dl3byVSXmHRHRMlXN9yCCCCCCGYNyXcWviSdOaE6c0J05oTpzQnTmhHnZ106pX3wdckI5slnLbbbbfd4E6c0J05oTpzQnTmhOnNCdOaDkocc0GmMUeONidOaE6c0J05oTpzQnTmg5YuJ/q3UnTmhOnNCdOaE6c0J05oTpzQnTmhOnNCdOaE6c0J05oTpzQnTmhOnNCdOaE6c0J05oTpzQnTmhOnNCdOaE6c0IWS2IWS2IWS2IWS2IWS2IWS2IWS2IWS2IWS2IWS2IiJp9Xon+NdmKMbT6qFLd012aIWS2IWS2FJpJG3gkkJJaTeiwWSIWS2IWS2IWS2IWS2IWS2IWS2IWS2IWS2IWS2FRifa44/wQslsiFktiFktiFktiFktiFktiFktiFktiFktiFktiFktiFktiFkthJLFJJqgnC0klFjVELJbELJbELJbELJbELJbELJbELJbELJbELJbELJbELJbELJbELJbELJbELJbELJbELJbEJdkJwJLR1IWS2IWS2IWS2IWS2IWS2IWS2IWS2IWS2IWS2OkfT+vn/C2Xou7eSHTNF2Sy/CsGmsGsU11TzQhVAk6Psv/AASuG2Wi5uuSHi23i25bfdv8KjrrsmTPbQbJ/wDraSbbSSTbbhJJYttvBJIc7ZCPAwbK6Tsu/f8AEo7pbELJbIhZLYhZLYcY47KFLdklmQsUdjomSrmyFktiFktiFktiFktiFktiFktiFktiFktiEsUkmhUIEnDxP/CDDbrylfd1yQ8TxG3Lbxbb6tshZLYhZLYhZLYhZLYhZLYhZLYhZLYhZLYSQ0th7w+nZ/8AsCVgKeyy+kLJbELJbELJbELJbELJbELJbELJbELJbELJbDTskR+Xyt9PK308rfTyt9PK308rfTyt9PK308rfTyt9PK30kpLeNN8GuzHVnJ0xT7spwaEzaSct4JJfRKS3L9cOlFiedvp52+nnb6edvp52+nnb6edvp52+nnb6edvp52+jIvnvf04wIqQQQQQQQQQQQQJRinDR0bcJeqIIIIIIIIIIIIIIIIqdEvr4QQQQQQQQQQQRYNhnl8PO30ist+FGLeSxJvLfhRgluedvp52+nnb6edvp52+nnb6edvp52+nnb6edvomTTUGsU11T3Iyfg6PpHNCCI3psuvVVvPIhs222bblt9W9zzt9PO3087fTzt9PO3087fTzt9PO3087fTzt9FXzlGCZPEmR5XdsmOEm20kk228EksW230SGM3krhGDZf0nZdyCCCCCCCCCCCKnnb6OuG7KMW8lj1JCYaCZKpBBBBBBBBBBFTCMwdTtyfEkC5lN1h3Su5khjNhs5beLbfd4nnb6edvp52+nnb6edvp52+nnb6edvp52+nnb6NGobt9Gt18OPp52+kWLePbDpfqedvp52+nnb6edvp52+nnb6edvp52+nnb6edvp52+nnb6edvp52+nnb6edvp52+nnb6edvp52+nnb6edvp52+nnb6a90a90a90a90a90a90a90a90a90a90a90a90Q6RpkaePpCydiFJY13afgowknldE8ronldE8ronldE8ronldE8ronldE8ronldE8ronldDGU+Vyt3EEa2NdjXY12NdjXY12NdjXY12NdjXY12FHFSmqoZIjVcMakK2IVsQrYhWxCtiFbEK2IVsQrYhWxCtiFbEK2I5taoboUryWPmhCtiFbEK2IVsQrYhWxCtiFbEK2IVsQrYhWxCth2sYtdlKnSg8rQsqEEEEEEEEEEEECTTTUprFNPFPNHasiwxFGTYabbblt4tt4t5sgggggggggggglVEdZ6NdWqBJiYXWpyJyPpn1IVsQrYhWxCtiFbEK2IVsQrYhWxCtiFbEK2IVsO0xwuiwlvJVHx5GYoT2a90a90a90a90a90a90a90a90a90a90a90a90TWKlNVQnGN0SKIzTJ2z/tszYzblttS2QrYhWxCtiFbEK2IVsQrYhWxCtiFbEK2IVsNWoc29GMczDw6bkK2IVsQrYhWxCtiFbEK2IVsQrYhWxCtiFbEK2FPBS9UYMHNiFbEK2IVsQrYhWxCtiFbEK2IVsQrYhEIhEIhEIhEIhEIhDXMwkjFtvshMSksX1SO7myEQiEQiEQiEQiEQiEQiEQiEJhSSPQ6FC7KF2ULsoXZQuyhdlC7KF2ULsoXZQuyhdjGLWHZY4/CEQiEQiEQiEQiEQiEQiEYHRL6+EIhEIhEIhEIhEIhEIhEIhEIS8Yx/sof2ULsoXZQuyhdlC7KF2ULsoXZQuyhdlC7KF2ULsoXZQuyhdlC7KF2ULsoXZQuyhdlC7KF2ULsaE28EsW28Es3iKd4KdXjL8aiwYkylK7KF2ULsoXZQuyhdlC7KF2ULsoXZQuyhdjB0Oyly3kseoyZgsxwnshl/ZQuyhdlC7KF2ULsoXZQuyhdlC7KF2ULsoXZgCV2BBuX1zl+x2wax8vH6ULsoXZQuyhdlC7KF2ULsoXZQuyhdlC7KF2Onglh3cvH4JJpX+iIlLypeBQuyhdlC7KF2ULsoXZQuyhdlC7KF2ULsStwldiCI89THEsfLxIrBr+yhdlC7KF2ULsoXZQuyhdlC7KF2daA7kt4OuT7k6c0J05oTpzQnTmhOnNCdOaE6c0J05oTpzQTnISRLbb7LATgZYvqkdDrm+5OnNCdOaE6c0J05oTpzQnTmhOnNCdOaE6c0J05oTpzQnTmhOnNCdOaE6c0J05oTpzQZF0/oHT8bsTDsnv8J05oTpzQnTmhOnNCdOaE6c0J05oTpzQnTmhOnNCdOaE6c0J05oNkpbSS5kM8Cw/t8J05oTpzQnTmhOnNCdOaE6c0J05oTpzQnTmhOnNCdOaE6c0J05oTpzQnTmhOnNCdOaDHion+yGQQQQQQQQQQQQQQQQQQQQQQQRGLaSXVk6yE6vGX2+idOaE6c0Ehymr+hM1Ka5oTpzQnTmhOnNCdOaE6c0J05oTpzQnTmhOnNBlMKOy7t5ImwozHCbdc2TpzQnTmhOnNCCCCCCCCCCBiQo/wSYtq/onTmhPNX9E3g2p/u3UnS/onS/onS/onS/onS/onS/onS/onS/onS/onmuaE3gmo7vO3QnTmhOnNBIcpq/oRpKaqsrE6X9E6X9E6X9E6X9E6X9E6X9E6X9E6X9E6X9CY4UX9ELtPd8ROnNCdOaDsSif7p0J0v6J05oTpzQnTmhOnNCdOaE6c0J05oTpzQflkIbLBobESOhi39mT7kLJbELJbELJbELJbELJbELJbELJbDVSBCSUtt9kQzTVi1EK7mb/AImG6fTIo6GH4OgKFx0IWX5cFi4OiXRev8RLxhT46kLJbELJbELJbELJbELJbELJbELJbELJbELJbELJbELJbELJbELJbELJbELJbELJbELJbELJbELJbELJbELJbELJbDSJtpJLFtxCWbJ7wE6uIb8a/wDKcOUJpqV+B46HZd28lUbEKi7JELJbELJbELJbELJbDTrC2IWS2IWS2IWS2IWS2IWS2IWS2IWS2IWS2EsJJtRYVZ1/5XgfX+/v4V9Gr/5aUoZJK/X4FJwkITDV5/8AK8Sif78kRg1+DyGpaQhprBolA27oS2/syzPK308rfTyt9PK308rfRCkYSSSltvokpMIaWL7K7mbIIIIIIIIIIIIIIIIIIIIIME+h/wCqf0edvp52+nnb6edvp52+nnb6SYt4dlHW/Q87fTzt9PO3087fTzt9PO3087fTzt9PO3087fTzt9PO3087fTzt9HnBPD+/hBBBBBBBBBBBBBBBBBBBBBJinD/s87fTzt9PO3087fTzt9PO3087fTzt9PO3087fTzt9PO3087fTzt9PO3087fTzt9PO3087fTzt9MOLgl1fGYqAnVx7/R52+nnb6edvp52+nnb6edvomTlO30SNSnb6edvp52+nnb6edvp52+kxgOyjFvJY9RpDeHZRglv1PO3087fTzt9PO3087fTzt9H3Tj4+nnb6edvp52+nnb6edvp52+naTx8dLmPF2fTyt9PK308rfTyt9PK308rfTHg3j4636nnb6edvp52+nnb6edvp52+mPBPDx1v0PK308rfTyt9PK308rfTyt9EzSrPoiTrj3UfTzt9PO3087fTzt9PO30Uu9volK92QQQQQQdYuv9mAggggg6N45b7twFnElR29FR29DEuBDTiGthEibLsSn4Ov9lTcvRU3L0VNy9CGUlmkkobbfRJQI8dDF4NI6HXNlR29FR29FR29FR29FR29FR29FR29FR29FR29FR29FR29FR29FR29FR29FR29FR29FR29FR29FR29FR29FR29FR29FR29C8c8jdnTp/RCpCpCpCovE5j+/hAhUhUhUhUhUhUhUhUhUhUhUhUgRwS4/uxUdvRUdvRUdvRUdvRUdvRUdvRUdvRUdvRUdvRUdvRUdvRUdvRUdvRUdvRUdvRUdvRUdvRUdvRUdvRUdvRUdvRUdvRUdvQnqpn+7EKkCBAgQIECBAgQIECBAgQIS7mKa0vVr5+iGbt6IZu3ohm7eiGbt6IZu3ohm7eiGbt6IZu3oSJym7ehKSm7eio7eio7eio7ehXGOiwlvJYHUM47KVCW3UqO3oqO3oqO3oqO3oqO3oqO3oqO3oqO3ofabnT0Qrb0VHb0VHb0VHb0KiE3OmFehDN7r0VHb0VHb0VHb0VHb0VHb0VHb0VHb0QzdvQrYNudMbdSo7eio7eio7eio7ehWwTcd3Kxt0Kjt6Kjt6Kjt6Kjt6Kjt6Kjt6Kjt6Kjt6Eqcpu3oSEpu3oqO3oqO3oqO3oStwpt6Epdaunoqbl6Km5eipuXoqbl6Km5eipuXoqbl6Km5ejGE5eVjToVLPRU3L0VNy9FTcvQuZtEfYx0Ov8AR407ERt1/wCGJCQhp9GiQKWqif8AZ/f/AIxkhtmkklLbfRJC0w2y8pHczfH/ACcA+h5UdP6/9659P7+fnkwXT+/5U+K6/wB/f4PQxzK3z9fjTbShNJK/8eGeF3byRsAXZL8bU+f/AHpl1/o64v8AHPg+v9/f/Z8F07vP5+NNtKE0kr/xJtwhCV7v8fXLr/f/ALOnKR+T/T+hCQkihJf+TpzQnTmhOnNCdOaCXIQQ0+jWwtxw6MtG+kC65ZnWMjykdDrmydOaE6c0J05oTpzQnTmhOnNCdOaE6c0J05oTpzQnTmhOnNCdOaE6c0J05oTpzQnTmhOnNCdOaE6c0J05oTpzQnTmhOnNCdOaE6c0J05oNg9ZHjhR4dP6HYnG7G3QnTmhOnNCdOaE6c0J05oTpzQnTmhOnNCdOaE6c0J05oTpzQnTmhNYtrmgzeC6f2QQQQQQQQQQQQQQQQQQQQQQQQQQQTYqJ/snS/onTmhOnNCdOaE6c0J05oTpzQnTmhOnNCdOaE6c0J05oTpzQnTmhJYtpJdXJMOjveP+ROnNCdOaE6c0J05oTpzQnTmhOnNCdOaE6c0J05oTpzQnTmgkyU1f0IkPDLu3kqjTBUUuEtidOaE6c0J05oTpzQnTmhOnNCdOaE6c0J05oTpzQnTmhOnNBs8p5Qngwn+vOBNi2r+idOaE6c0J05oTpzQnTmhOnNCdOaE6c0J05oTpzQnTmhPNX9E3g2pzzsTeCaju87dCdOaE6c0J05oTpzQnTmhOnNCdOaE6c0J05oTpzQnTmhOnNBMSmr+hnRHqxCr3ZBBBBBBBBBBBB1ChP+ySYhi7vQ6iVISRQkiCCgtkUFsigtkUFsigtkQyWyJMGlPhYlBbIoLZFBbIoLZFBbIoLZFBbIoLZFBbIoLZFBbIoLZFBbIoLZFBbIoLZFBbIoLZFBbIoLZFBbIoLZFBbIoLZFBbIoLZFBbIS6WSdFEN12X9mNUsCSXZJQlRELJELJELJELJELJELJELJELJELJELJELJELJELJDSeDSjwI7FHhELJbIhZLZELJbIhZLZELJbIhZLZELJbIhZLZELJbIhZLZELJbIhZLZELJbIhZLZELJbIhZLZELJbIhZLZELJbIhZLZELJbIhZLZELJbIhZLZELJbIhZLZELJbIhZLZDXrC2RQWyKC2RQWyKC2RQWyKC2RQWyKC2RQWyKC2RQWyKC2RQWyG5NNiahppYpiHgNng4UtkygtkUFsigtkUFsigtkUFsigtkUFsigtkUFsigtkUFshI+zZEkJRwpTzJsCjhQ1migtkUFsigtkUFsigtkUFsigtkUFsigtkUFsigtkUFsigtkYgk0LEguzZFBbIoLZFBbIoLZFBbIoLZFBbIoLZFBbIoLZFBbIoLZFBbIoLZFBbIoLZFBbIoLZFBbIoLZFBbIoLZFBbIoLZFBbIoLZCWhJPhC/op7uFj8KC2RQWyKC2RQWyKC2RQWyKC2RQWyKC2RQWyKC2RQWyKC2RPlpJerhY0QlkgSwSSRQWyKC2RQWy/AnP8rAIhOiLpgsEui/8a7r88GK6f1/Nanz+ZpNNNSng0+49+o3CdPxpNoQkkhf+LSHo+6Y99h9ms1+bgE/f5TGhdRa17vP5+ZjS8F6vOiqJJEkoS6L/AJjXmhGvNCNeaEa80I15oRrzQjXmhHN80EjznlCNeaEa80I15oRrzQjXmhGvNCNeaEa80I15oRrzQjXmhGvNCNeaEa80I15oRrzQjXmhGvNCNeaEa80I15oRrzQjXmgtlPHos4hLBYJdEQQQQqRrzQjXmhGvNCNeaEa80I15oRrzQjXmhGvNCNeaEa80I15oRrzQjXmh1in/ABRkEEEEEEEEEEEEEEEEEEEEEEEEEEEDl5IIIIIIIIIIIIIGjTTxTwafcmYjd7snTIjXmhGvNCNeaEa80I15oRrzQjXmhGvNBI8+aCRIRBBAoh6PumOeHo+zWaIIIIIIIIIIIIIE4l171+ka80I15oRrzQjXmhGvNCNeaEa80I15oRrzQjXmhGvNCNeaEa80I15oRrzQjXmhGvNCNeaEa80I15oRrzQjXmhGvNCRCmeUFPee7wxt0I15oRrzQjXmhGvNCNeaEa80I15oRrzQjXmhGvNCNeaEa80I15oS5cperwxosOokElgl0XERrzQjXmhGvNCNeaEa80IRCIRCIRCIRCIFDIRCIRCIRCIRCIRCIRCIRCIRCIRCIRCIRglR2LFMosEoS6IoXZQuyhdlC7KF2NOxCIRCIRCIRCIRCIRCIRCGk8GhC7YeWULsoXZQuyhdlC7KF2ULsoXZQuyhdlC7KF2ULsoXZQuyhdlC7KF2ULsoXZQuyhdlC7KF2ULsoXZQuyhdj60f2ULsoXZQuyhdlC7KF2ULsoXZQuyhdlC7KF2ULsbk01KeDTnEW8EvV6HQoXZQuyhdlC7KF2ULsSvtdiV2/shl/ZDL+yGX9kMv7IZf2R5rw+6ZPhrw5cNZooXZQuyhdlC7KF2ULsoXZQuyhdlC7KF2ULsoXY9ilj5eP0of2ULsoXZQuyhdlC7KF2ULsoXZQuyhdlC7KF2ULsoXZQuyhdlC7KF2ULsoXYlbhK7FOCxfV87EIhEIhEIhEIhEIhEIhEIa8vBerzoqiQSShLoiEQiEQiEQiFktiFktiFktiFktiFktiFktiFktiFktiFktiFkthQ+ynwiFktiFktiFktiFktiFktiFktiFktiFktiFktiFktiFktiFktiFktiFktiFktiFktiFktiFktiFkthLRwnRRi4hLBJJLoo/B3F+eDFdP6/muXTr/f5mk000mng0+jJ0EvwnT/lKRJL8C2GlRx0JUNLbqQslsQslsQslsQslsQslsQslsQslsQslsQslsQslsQslsQslsQslsJxJJP8Asin8NjQliKeib7uOYELJbELJbELJbELJbELJbELJbELJbELJbELJbELJbELJbELJbELJbEuWoXq4WNFUSiSRJdFBCyWxCyWxCyWxCyWxCyWxCyWxCyW35MXkggggggggggggggggga3TTou7hIlCwSwSSwSIIIIIIIIIHPGYPO3087fTzt9PO3087fTzt9PO3087fTzt9PO3087fTzt9PO3087fTSEEEEEEEEEEEEEEEEEEEEEEEEEEEEEvkgggggggggggggaNNPFPBprqSpMdNjx6Hnb6edvopd7fRQ72+nnb6edvp52+nnb6edvp52+nnb6edvp52+iFhvw46XHPD0fZrNEEEEEEEEEEEEECcSwfev08rfTyt9PK308rfTyt9PK308rfTyt9PK308rfTyt9PK308rfTyt9PK308rfTyt9E7cJy3T6KXB493HW/Q87fTzt9PO3087fTzt9PO3087fTzt9PO3087fTzt9PO3087fRzS3CdXHWiqJESWCXREEEEEEEEHnb6edvp52+nnb6edvp52+nnb6edvp52+nnb6edvph72+iT3x8fTzt9PO3087fTzt9PO3087fTzt9PO3087fTzt9PO3087fTzt9PO3087fSUuj7RHh16CVKFglgklgkeR5HkeR5HkeR5HkeR5DMEEEEEEEEEEEEDlgyB9cO2B52+nnb6edvp52+nnb6edvp52+nnb6edvp52+nnb6edvp52+nnb6edvp52+nnb6edvp52+nnb6edvp52+nnb6edvp52+nnb6edvp52+kuKePj6edvp52+nnb6edvp52+nnb6edvp52+nnb6edvp52+nnb6edvo0aablPqo+jmhi/fNk6CkKBBBBBBBBBBBApYej7pjWhu3W552+nnb6edvp52+nnb6edvp52+nnb6edvp52+nnb6edvp52+k2KePjrfqedvp52+nnb6edvp52+nnb6edvp52+nnb6edvp52+nnb6edvp52+nnb6Jm4Tt9FLm+752IIIIIIIIIIIIIHvknV/4qiREk4S6KPp52+nnb6edvp52+nnb6edvp52+nnb6edvpBBBBBBBBBBBA4WIpcmuq/1EEEEEEEEEEEEECxz5rrSyzMY012I7R2ihBBBBBBBBBBBBA0pIIIIIIIIIIIIIIRB4IIIIIIIIIIIIIIIIIIIIIIIIIIIIGjx7kEEEEEEEEEEEEE4+1DT70jvImtwmlOCmcPMdiNeaEa80I15oRrzQjXmhGvNCNeaEa80I15oRrzQjXmhGvNCNeaEGHL7OmFehGvNCNeaEa80I15oRrzQjXmhGvNCNeaEa80I15oRrzQjXmhGvNCObt6FfBNXh12I15oRrzQjXmhGvNCNeaEa80I15oRrzQjXmhGvNCNeaEa80I15oRrzQ6DGdMbEa80I15oRrzQjXmhGvNCNeaEa80I15oRrzQjXmhGvNCNeaEa80I15oTr0pYRl5IIIIIIIIIIOZv2czfs5m/ZzN+zmb9nM37OZv2czfs5m/ZzN+zmb9iSWrvG42Pp4UvC4kOUoaq/Y6OkNdVL3WPQoXZQuyhdlC7KF2ULsoXZQuyhdlC7KF2JdIadgsU2XZUd3p4SCSSSShJYJJdEkK8Vg6P/AB0Es04NVfspXfspXfspXfspXfspXfspXfspXfspXfspXfspXfspXfsZnCYa4kIRsGsfL9lK79lK79lK79lK79lK79lK79lK79lK79lK79lK79lK79jTi1d+yFlBCKV37KV37KV37KV37KV37KV37KV37KV37KV37KV37KV37KV37KV37KV37KV37KV37KV37KV37KV37KV37KV37KV37KV37KV37KV37KV37KV37KV37KV37OoS0xKF2ULsoXZQuyhdlC7KF2ULsoXZQuyhdkN4CVXjRY9Rk0tYLopeF+pSu/YmdsfL9lK79lK79lK79lK79lK79lK79lK79lK79lK79lK79lK79kPpLdFLwq8RuctS3V+yld+yld+yld+yld+yld+yld+yld+yld+yld+yld+yld+yld+yld+ybBLDu5eN+hzN+yld+yld+yld+yld+yld+yld+yld+yld+yld+yld+yld+yld+yld+yld+yld+zpGsfLxuUrv2Urv2Urv2Urv2Urv2Urv2Urv2Urv2Urv2Urv2Urv2Urv2Urv2ReUvKlw1uRZS8qXKe5Su/ZSu/ZSu/ZSu/ZSu/ZSu/ZSu/ZSu/ZSu/ZJ1nFB17pu+51X4kJL/Yxpeiy/9TacrBoWmTXVf6qfie6Q0j8Nl2X9mJJEkkklCSwSS6JL/wBQmTXR87DGaahr8U+C6f38/wCOw9H+JuMWNz/8HuLVfiQrZCV6KoxpeCXRZff+E58/iQubdFlVjbbl4t/mnwXTu8/n8Ppn1/v8z3lars0LWVqu6f4kYPeKbr3a3fJ36o8rfTyt9PK308rfTyt9PK308rfRCS34UdbjUpcbEMlsQyWxDJbEMlsJE5UJ+CJ0UrqouiGS2IZLYhktiGS2IZLYhktiGS2JSRCvDCGy7L+zEqJJJJKEkkkkuyXY4wOMDjA4wEJ2TXRxZ0Gs01DVP+wIIPDt46kMlsQyWxDJbEMlsQyWxBBBBBBBBA0eWxDJbEMlsQyWxDJbEMlseVjyt9PK308rfTyt9PK308rfTyt9PK308rfTyt9PK308rfTyt9PK308rfTyt9PK308rfTyt9PK308rfTyt9Gl50IIIIIGrx6aHlb6eVvp5W+nlb6eVvp5W+nlb6Ilsw8daLHqPOXELoo6fSGS2IZLYhktiGS2IZLYSPvY8rfTyt9PK308rfTyt9PK308rfSPfLyi7GjcuG3QhktiGS2IZLYhktiGS2IZLYhktiGS2IZLYhktiGS2IZLYhkth10RVxYhktiGS2IZLYhktiGS2EtNkQpsQpsQpsQpsQpsQpsQpsQpsQpsQpsQpsQpsQpsQpsKng4nwsSFNiFNiFNiFNiFNiFNiFNiFNiFNiFNiFNiFNiFNibKiqjBogyoqoWBDJbEMlsQyWxDJbEMlsQyWxHJbf+68ptWtcMV/00Jh+CRLT2eBDyezIeT2ZDyezIeT2ZDyezIeT2YkjlJpqjInRp91D3VCHk9iHk9iHk9iHk9iHk9hrQNK+nRuuyyzMSSSSUJKEkoSS6JL/tKZJ0f+Ogxmkhrm3/TTgun4YeT2IeT2IeT2IeT2IeT2Ia7Pb+cz6JMh5Mh5Mh5Mh5Mh5Mh5Mh5Mh5P/AKQjb6Ld0Qwlpx2UPD6Q8nsQ8nsQ8nsQ8nsQ8nsQ8nsQ8nsxS+znwQ8nsQ8nsQ8nsQ8nsQ8nsMXo23RR0qxyctNt0IeT2IeT2IeT2IeT2IeT2IeT2IeT2IeT2IeT2IeT2IeT2IeT2IeT2HdEnVwQ8nsQ8nsQ8nsQ8nsQ8nsQ8nsQ8nsKejT2/NiTYPr/AGQyGQyGQyGQyGQyGQyGQxhK/Y8ZSdVkyHk9iHk9iHk9iHk9iWT2IeT/AONSU2rQr6kQ8nsQ8nsQ8nsPDqmNt/hUrFESq6+/+HtG0r8N8lTNiREkoSUJJQkl2X4VpDwa6PL4NGycql1Qh5PYh5PYh5PYc5PZkPJ7EPJ7EPJ7EPJ7EPJ7EPJ7EPJ7EPJ7EPJ7DPo09v8AjpmOXZ7Mh5PYh5PYh5PYh5PYh5PYh5PYh5PYh5PYh5PYbLs9v5r7kQ8nsQ8nsQ8nsNCXKXiw56dll9/EnPn/AIjYKeylWOTlptuhDyexDyexDyexDyexDyexDyexDyexDyexDyexDyexDyexDyexDyew7ojq4/oh5PYh5PYh5PYh5PYh5PYh5PYh5PYh5PYh5PYh5PYh5PYh5PYh5PYh5PYh5PYh5PYh5PYh5PYh5PYh5PYh5PYh5PYh5PYhOz2Y7A058dSHk9iHk9iHk9iHk9iHk9iHk9iHk9iHk9iHk9iHk9iHk9iHk9iHk9h3KTqocNCllTVd1+LW5Bq0K61X/iEl/sY0v9fjTacoQle6/wDHtjpX4brsqZsSSSSSSShJYJJdl+NC5NdHzsMZpqGv/G/5cHj+ehG24SvRVGNLwS6LL7+ROf8AzUHX80+C6d3nRU/l9M+v9/fzMaV+xCStVl+KKx1hxLjD/Tt1JEuN2ad2ad2ad2ad2ad2ad2ad2ad2ad2ad2ad2ad2KDlQn5Y5pklFobnwpmJEkkkklCSwSS7IggggggggggggSuMJro+dhGnHH8IIIIIIIIIIIIIFLtO5BBBBBBBBBBBBH/hxWDCYnEjldkcrsjldkcrsjldkcrsjldkcrsjldkcrsjldkcrsjldkcrseWN2SpclS5KlyVLkqXJUuSpclS5KlyVLkqXJUuSpcnDfWFMKZMfHouil4fSOV2RyuyOV2RyuyOV2RyuyOV2RyuyOV2RyuyOV2RyuyOV2NRJLHp3jyTeW7JUuSpclS5KlyVLkqXJUuSpclS5KlyVLkqXJUuMeCiuLNG7NG7NG7NG7NG7NG7NG7NG7NG7NG7NG7NG7NG7FLCFPlmjdmjdmjdmjdmjdmjdmjdmjdmjdmjdmjdmjdmjdmndkqxXTvLI5XZHK7I5XZHK7I5XZHK7I5XZHK7I5XZHK7I5XZHK7I5XYkpX9vESNJxErpiRyuyOV2Ryu/wDnD7dF+KvPN9EhnZRHjo/t9Wz/AAH/AI/zLD2xKa5imdsIcDq1vyz4Lp3efwQn9MaacP8AIk24QhP7eZ31qvy9Q/ohREYdIGPKxbo8qP8Am4hdS/sq/wB/lSkhRHY0H5ewvzJNuF1EJXu/8RPiuv8Af38rGp3f+ISSULoNR4/L/gL/AF/gw+3RfirzzfRIZ2YYpDp0Z5vq3/7/AID/AMf5lh7YlNcxTO2EOB1a3458F07vP5/6hP6Y004f4km3CEJ/bz/9761X4+of0dP/ABpNQ8UxjysW6PKj/m4hdS/sq/3+NKf/AHqaD8fYX5km3C6iEr3f+L/2fFdf7+/jY1O7/wAQkkoXT/1qPH4/8Bf6/wAGAgWC/BVm9khrYyQ/HP7fVsjXmhGvNCNeaEa80H7m6N/0QQQQQQQQQQQQQKD2xKa5imZCX9iza34ZsF071+EEEECU/ojXmhGvNCNeaEa80I15oRrzQjXmhGvNCLz5oJTDUggggQ8VNSNeaEa80I15oRrzQjXmhGvNCNeaEa80FYnMf3YgiFSFSFRq1DxTJfdt0f8AjIIIIIIIIIIIIIIIIIIIIIIIIRBBBAuUmZLvXyRrzQjXmhGvNCNeaEa80I15oRrzQjXmhGvNCEQQQQmNFnHKEa80I15oRrzQjXmhGvNCNeaEa80I15oR6Jur/wAIIIIIIIIIIIIIFJwsWxJ1bfd+sOhGvNCNeaEa80I15oKeMpyz+kEEEEEEEE3ul3YkKFMcoRrzQjXmhGvNCNbehos45QjXmhGvNCNeaEa80I15oRrzQjXmhGvNBPcov9eBGvNCNeaEa80I15oRrzQjXmn/AJAgSwX4q+7eyQ8PhKUh/wC0b7vq3/323o/zLD4To/8AHmmZCX9iza3/AHPgund5/P8Atqf++olH/bXdf99c+n9nTBf9NJqHimMfNuj/AMf82fM7rP7+dqPH/fbWr/Mk24WLYtc2+r/xU/74BP3/ALe9O7EklC6f9tR4/wC/8Bf6/wAEYBLBHgK7t2HR8JSkN/3Rvu+rZBBBBBBBA84Nqj9k6c0J05oTpzQnTmhOnNCdOaE6c0J05oTpzQnTmhOnNCdOaE6c0IyMnR/48MUxHaSupfN/QnTmhOnNCdOaE81zQdvBNRnn8JUJUJUJUJUJUJUJUG7yIaIIIEzwQo5EqEqEqEqEqEqEqEqDeqIIIIHYn0/siMEQQQQQQQOShw0xjYRLo/8AGTpzQnTmhOnNCdOaE6c0J05oTpzQnTmhOnNCdOaE6c0J05oTpzQnTmhOnNCdOaE6c0J05oTpzQnTmhOnNCdOaE6c0J05oTpzQnTmhOnNCdOaD30J7pd69OpOnNCdOaE6c0J05oTpzQnTmhOnNCdOaE6c0J05oTpzQnTmhOnNCdOaDfAggggk8FFX6J05oTpzQnTmhOnNCdOaE6c0J05oTpzQnTmhOnNCdOaE6c0J05oJjhRzQi5t9X/ioQQQQQQQQdUonvX6Tpf0TpzQnTmhOnNB7do7+hQULoQQQQQQQQN+0E6c0J05oTpzQnTmhklF/rIIIIIIIIGR6SSkN0Cwxb7vuTpf0Tpf0Tpf0Tpf0Tpf0Tpf0Tpf0Tpf0Tpf0Tpf0M+oqP8Ax4E6c0J05oTpzQnTmhOnNCdOaE6c0J05oTpzQnTmhOnNCdOaE6c0I6AjlNfroIxIS/sVHYggc8F0/v4QQQQQQQQQQS+SGiBM8EKBBBBBBBBBBB3Vt6IIHYn0/uhEYIgggggggggclDhpjnzbo/8AGQQQQQQQQQQQQQQQQQQQQQQQQQQQQSZndZ1VSCCCCCCCCCCCCCJGLpEE6c0J05oI+ia5oTpzQnTmhOnNCdOaE6c0J05oTpzQnTmhOnNCdOaE6c0J05oTpzQTHCieUGp2nu+InTmhOnNCdOaE6c0J05oTpzQnTmhOnNCdOaE6c0HPFRP926k6X9E6c0GnaO74hMlCiNfROnNCdOaE6c0J05oTpzQnTmhOnNCdOaE6c0J05oNlkQQPeLiOyz8k6c0J05oTpzQnTmhOnNCdOaE6c0J05oTpzT8v+A/8f5kJkJ0fOqO0Eq+DiCfBdP7+flk8kOY7iUfl7i1RpuYISjBfkaTUPFMfV2P/AB/zZ8zus/v52oOwvzJNuF1EJXu/8X5Z8V1/v6MandiSShflajwSZHZfjh5PYh5PYh5PYh5PYh5PYh5PYh5PYh5PYh5PYh5PYh5PYh5PYc2KaX9+CPyw8iD3Cw6IPOpbDWzXZ+/5jbXZtjuiOjj+/wAzUqGsGObu+x/4/wA0fmj9JfZf66Dk22nLx6EPJ7EPJ7EPJ7EPJ7EPJ7EPJ7EPJ7EPJ7EPJ7EPJ7EPJ7EPJ7EPJ7DvoTq4sQ8nsQ8nsQ8nsQ8nsQ8nsQ8nsQ8nsQ8nsQ8nsQ8nsQ8nsQ8nsQ8nsQ8nsSJh7dhYqUnsQ8nsQ8nsQ8nsQ8nsQ8nsQ8nsQ8nsQ8nsQ8nsQ8nsQ8nsQ8nsQ8nsQ8nsQ8nsQ8nsQ8nsQ8nsQ8nsQ8nsQ8nsQ8nsQ8nsQ8nsQ8nsQ8nsQ8nsNeLTjLP4R+NnB+GdU/xyePz4dHIl8R/41ODNB2f8pv8A97D0f5mpwfQ65fFH/KWmbfRf66Dbbl4t/m7a1f8ABTbShNJK/lJd3+XDorTT+vJ9hnB+GdU/wyePz4dHIl8R/wANTgzQdn/Ib/57D0f5mpwfQ65fFH/IWmbfRf66Dbbl4t/m7a1f8NNtKE0kr+Ql3f5peb3MJERirrdf9B8riGmlxplBbIoLZFBbIoLZFBbIoLZCV9lHhELJbIhZLYhZLYhZLYhZLYhZLYhZLYhZLYhZLYhZLYhZLYhZLYhZLYhZLYXFbKoXH9P+oT6pDTJbIhZLZELJbIhZLZELJbIhZLZELJbIhZLZELJbIhZLZELJbIhZLZELJbIhZLZELJbIhZLZELJbIhZLZELJbIhZLZELJbIhZLZEGCSnwsCEQsiFkQsiFkQshl0cUIWRCyIWRCyIWRCyIWRCyIWRCyIWRCyIWQ0moaUMS3RR2cIhZLZELJbIhZLZELJbIhZLZELJbIhZLZELJbIhZLYhZLYhZLYhZLYhZLYhZLYhZLYhZLYhZLYhZLYhZLYhZLYhZLYZHRT2UXY0m5aTfhFBbIoLZFBbIoLZFBbIoLZFBbIoLZFBbIoLZFBbIoLZFBbIfoSVXCKC2RQWyKC2RQWyKC2RQWyKC2RQWyKC2RQWyKC2RQWyKC2RQWyKC2RQWyKC2RQWyKC2RQWyINKSqoWIpKUlsigtkUFsigtkUFsigtkUFsigtkUFsigtkUFsigtkUFsigtkUFsigtkUFsigtkUFsigtkUFsigtkLrNNkUFsigtkUFsigtkUFsigtkUFsigtkUFsigtkUFsigtkUFsigtl/7OFCHAdP6HhmIafb/lKfH58MvgS+I/C1/EgwXX+vw9h6P8zSahjGp2f8RCV7L/AFjbbl9fzdtav+OxpWqzE01K/iJd3/DnChDgOn9DwzENPt/6lPj8+GXwJfEfja/hQYLr/X4+w9H+ZpNQxjU7P+EhK9l/rG23L6/m7a1f8ljStVmJpqV/CS7v+LOFCHAdP6FhtiGn1TEp8fnwy+BL4j8rX8CDBdf6/L2Ho/zNJqGManZ/wEJXsv8AWNtuX1/N21q/5bGp3QmmpX8BLu/48jmpWMkZc9O4uwv7KF2ULsoXZQuyhdlC7KF2ULsoXZQuyhdlC7KF2KuDjn6T7yKF2ULsoXZQuyhdlC7KF2ULsoXZQuyhdlC7KF2NCxX9kIhEIhEIhEIhEIhEIhEIhEEMrsoXZQuyhdlC7KF2ULsoXZQuyhdlC7KF2ULsoXYmGOnUhkQyIZEMiGRDIhkQyIZEMiGRDIhkJBz0IRCIRCIRCIRCIRCIRCIRCIGkuVj5ZQuyhdlC7KF2ULsoXZQuyhdlC7KF2ULsoXZQuzCwXnFzFChdlC7KF2ULsoXZQuyhdlC7KF2ULsoXZQuyhdiG0kpb6LEw7iU+6bweRQuyhdlC7KF2ULsoXZQuyhdlC7KF2ULsoXZQuyLp079SEQiEQiEQiEQiEQiEQiEQhJTiQiEQiEQiEQiEQiEQiEQiEQiEQiF/zgCaMF1SvgkMDGxLbxbb7sTjx+eSG49Gd6Ov5G4xY3P8Br8qXd/mbSUsY1Oy/gNT5On5u+tV+RjaSUt9EIXNur/xUGk001KYx5WLdHlR/kSnx/BT7P8AiqwhusF1S8IQjTDEtvq3uedvp52+ih3w8fTzt9PO3087fTzt9PO3087fTzt9PO3087fTzt9Gq2PI8iKkkPx7nejqQQQQQQQQQOEpbG5f8KPxrvexBBBBBBBBBBBBA8MWxG3XDso+nnb6edvp52+nnb6edvp52+nnb6edvp52+nnb6edvp52+nnb6edvp52+nnb6edvo5d8fH0bJwyCCCCCCCCCCCCCXFPSDzt9PO3087fTzt9PO3087fTzt9PO3087fRM31x7YfRXfLfV/4iCBo008UyZg8XTCzPO3087fTzt9PO3087fTzt9PO3087fTzt9FLvb6eVvp52+nnb6edvp52+nnb6edvp52+nnb6edvp52+nnb6edvp52+nnb6edvp52+nnb6Lsbt9PO3087fTzt9PO3087fTzt9PO3087fTzt9PO3087fTzt9PO3087fTzt9PO33/ANUnUZYL+z/BJNjM5bbxb2I15oRrzQjXmhGvNBIsyCCCCCCCBqcyNeaEa80I15oRrzQjm+aHRfj0Td6Ov4Og0qdiCCCCCCCCCCCCCCCCCCBogggggggg7rIIIIIIIIIIIIIISMTp2IIIIIIIIIIIIIIIIIIIEpXsyNeaEa80I15oRrzQjXmhGvNCNeaEa80I15oRrzQjXmhGvNCNeaEa80O8pqv9IIIIIIIFJwsWKSvf0QqQqQqQqNWoeKZXvN/jIIIIIIIFLwJEQQQQQQQQQQQQQQQQQQQZNiCCCCCCCCCCCCCCP/ELgNlgu1TG2Njbblt9WyGQyGQyGQyGULooXRQuihdFC6KF0ULooXRQuihdFC6KF0ULooXQ1HV7OVjR1IZDIZDIZAjdsPKKF0ULooXRQuihdFC6KF0ULooXRQuihdFC6KF0ULooXRQuihdFC6KF0ULooXQ8q6Ia7EMhkMhkMb1eiIZDIZDIZDIZDIZDIZDIZ0Gb8EMhkMhkMhkMhkMhkMhkMhkMhkMhkMhkMcvJDXYhkMhkMhkMhkMhkMhkMhjuqeUUrr2Urr2Urr2Urr2Urr2JzhK6Ite7IZDIZDIZDGpUNSmPMFK0woyldeyldeyldeyldeyldexM7YeUTyuihdFC6KF0ULooXRQuihdFC6KF0ULooXRQuihdFC6KF0ULooXRQuihdFC6KF0JdyuihdFC6KF0ULooXRQuihdFC6KF0ULooXRQuihdFC6ELEGywX9mNtm2223Lb7/hTjx+eTM7POjr/Nk8nT/juPRfm6Enj+Q1I1H5mu6/4SnBGo7v8PU0H/CU/wAlPs/zNJm2k28W2k2z9QvR+oXo/UL0fqF6P1C9H6hej9QvR+oXo/UL0fqF6KDZFBbIoLZFBbIoLZFBbIoLZFBbIoLZFBbIoLZFBbIoLZFBbIoLZHYej/4hZELIhZELIhZELIhZELIhZELIhZELIhZELIhZELIhZELIhZELIhZELIhZELIhZDR9lsQyWyIWS2Qk6wtkQsiFkQsiFkQsiFkQsiFkQsiFkQsiFkQsjDIaT7IhZLZELJbIhZLZELJbIhZLZELJbIhZLZELJbIhZLZELJbIhZLZELJbIhZLZELJbIhZLZELJbIhZLZELJbIhZLZELJbIhZLZELJbIhZLZELJbIhZLZDZ2WyGi7NkUFsigtkUFsigtkUFsigtkUFsigtkUFsigtkUFsigtkUFsj9Aj9Aj9QhKzPCKC2RQWyKC2RQWyKC2RQWyKC2RQWyKC2RQWyJOzZDR6F6P0CP0CKDZFBbIoLZFBbIoLZFBbIoLZFBbIoLZFBbIoLZFBbIoLZFBbIoLZFBbIoLZFBbIoLZFBbIoLZFBbIoLZFBbIoLZFBbIXY0tkUFsigtkUFsigtkUFsigtkUFsigtkUFsigtkUFsigtl/Mkw786/zWhL/wCW1I1H50o/M1H81Ps/5HceiJMV1/v8qTaF1FrXu+djuLVf/KSnBCUHfWq/Kl/41+VIakajB/lSj8zcDc/zk+z/AB0LooXRQuihdFC6KF0ULooXRQuihdFC6IMX1/8AZMV1/soXRQuihdFC6KF0ULooXRQuihdFC6E7CLoWte752/8Ae4tV/OhksiWRLIlkSyJZEsiWRLIlkJR/731qiGQyGQyGQyGQyGQxM+xQuihdFC6Gy7EMhkMhkMhkMhkMhid4x/RQuihdDZ9v6Iu39FC6KF0ULooXRQuihdFC6KF0ULooXQoEMhkMhkMhkMhkMhkMhkMh5DZ2uihdFC6KF0ULooXRQuihdFC6KF0ULooXRQuihdFC6KF0ULooXRQuihdFC6KF0ULooXRQuihdFC6KF0ULooXRQuihdCXcv6IZDIZDIZDIZDIf8lKRKPP/AD3Fqv5qcePxJf8APcWq/ClPgSj/AIakajx+HuPRf8yef/zaUiUef++4tV/NTjx+BL/vuLVf9pSJR/01Kgaj/vuPRf8Acnn/APhof//Z) fixed' + + # Colors + text-color: '#636B75' # Grey text + text-medium-color: '#8c96a5' # Medium grey text + text-light-color: '#BAC0C6' # Light grey text + accent-color: '#00a1ff' # Blue + background-color: '#F7F8F9' # Light grey background + background-color-2: '#F4F5F6' # Light grey background + background-card-color: 'rgba(255,255,255,1.0)' # White background + border-color: '#E8E8E8' # Light grey border + + # Header + primary-color: 'var(--text-color)' # Background + text-primary-color: '#FFF' # Text + + # Left Menu + paper-listbox-background-color: 'var(--background-color)' # Background + + # UI + paper-card-header-color: 'var(--text-color)' # Title in settings + primary-background-color: 'var(--background-color)' # Background color (also title background in left menu) + + # Card + paper-card-background-color: 'var(--background-card-color)' # Background + dark-primary-color: 'var(--text-color)' + primary-text-color: 'var(--text-color)' + paper-listbox-color: 'var(--text-color)' + light-primary-color: 'var(--text-light-color)' + secondary-text-color: 'var(--text-medium-color)' + disabled-text-color: 'var(--text-light-color)' + paper-dialog-button-color: 'var(--text-color)' + secondary-background-color: 'var(--background-color-2)' # Background more info title + + # Icons + paper-item-icon-color: 'var(--text-light-color)' # Off + paper-item-icon-active-color: 'var(--accent-color)' # On + + # Switches + switch-checked-button-color: '#FFF' # Knob On + switch-unchecked-button-color: '#FFF' # Knob Off + switch-checked-track-color: '#0077FF' # Background On + switch-unchecked-track-color: 'var(--disabled-text-color)' # Background Off + + # Slider + paper-slider-active-color: 'var(--accent-color)' # Line On + paper-slider-container-color: '#e5e7ea' # Line Off + paper-slider-knob-color: 'var(--text-light-color)' # Knob On + paper-slider-knob-start-color: 'var(--text-light-color)' # Knob Off + + # Shadows + ha-card-box-shadow: 'inset 0px 0px 0px 1px var(--border-color)' + + # HACS + hacs-badge-color: 'var(--accent-color)' # New Badge + hacs-status-installed: 'var(--text-color)' # Installed Icon + hacs-status-pending-restart: 'var(--text-light-color)' # Restart Icon + hacs-status-pending-update: 'var(--accent-color)' # Update Icon \ No newline at end of file diff --git a/themes/dark_themes/dark_themes.yaml b/themes/dark_themes/dark_themes.yaml new file mode 100644 index 0000000..2373727 --- /dev/null +++ b/themes/dark_themes/dark_themes.yaml @@ -0,0 +1,269 @@ +Dark Blue: + ha-card-border-radius: 2px + ha-card-box-shadow: "1px 1px 2px 0px rgba(0,0,0,0.3)" + + primary-color: "#00A0E2" + light-primary-color: "#19BAFF" + graph-color: var(--primary-color) + primary-background-color: "#3c3f40" + secondary-background-color: var(--primary-background-color) + divider-color: "#292a2e" + disabled-color: "#666666" + + primary-text-color: "#FBFBFB" + secondary-text-color: var(--primary-color) + text-primary-color: var(--primary-text-color) + disabled-text-color: "#7A7D90" + + sidebar-icon-color: var(--primary-color) + sidebar-text-color: var(--primary-text-color) + sidebar-background-color: "#323536" + sidebar-selected-background-color: var(--primary-background-color) + sidebar-selected-icon-color: var(--light-primary-color) + sidebar-selected-text-color: var(--sidebar-selected-icon-color) + + state-icon-color: var(--primary-color) + state-icon-active-color: "#ffdd00" + state-icon-unavailable-color: var(--disabled-text-color) + + paper-slider-knob-color: var(--primary-color) + paper-slider-knob-start-color: var(--paper-slider-knob-color) + paper-slider-pin-color: var(--paper-slider-knob-color) + paper-slider-active-color: var(--paper-slider-knob-color) + paper-slider-secondary-color: var(--light-primary-color) + label-badge-background-color: var(--divider-color) + label-badge-text-color: var(--primary-text-color) + label-badge-red: var(--light-primary-color) + + paper-card-background-color: "#4d5052" + paper-listbox-background-color: var(--primary-background-color) + + paper-toggle-button-checked-button-color: var(--primary-color) + paper-toggle-button-checked-bar-color: var(--light-primary-color) + paper-toggle-button-unchecked-button-color: var(--disabled-color) + paper-toggle-button-unchecked-bar-color: "#333333" + + switch-checked-color: var(--paper-toggle-button-checked-button-color) + switch-unchecked-button-color: var(--paper-toggle-button-unchecked-button-color) + switch-unchecked-color: var(--paper-toggle-button-unchecked-bar-color) + switch-unchecked-track-color: var(--paper-toggle-button-unchecked-bar-color) + + table-row-background-color: var(--divider-color) + table-row-alternative-background-color: var(--secondary-background-color) + card-background-color: var(--primary-background-color) + +Dark Green: + ha-card-border-radius: 2px + ha-card-box-shadow: "1px 1px 2px 0px rgba(0,0,0,0.3)" + + primary-color: "#27ae61" + light-primary-color: "#50FA9A" + graph-color: var(--primary-color) + primary-background-color: "#39403c" + secondary-background-color: var(--primary-background-color) + divider-color: "#292a2e" + disabled-color: "#666666" + + primary-text-color: "#FBFBFB" + secondary-text-color: var(--primary-color) + text-primary-color: var(--primary-text-color) + disabled-text-color: "#7A7D90" + + sidebar-icon-color: var(--primary-color) + sidebar-text-color: var(--primary-text-color) + sidebar-background-color: "#323633" + sidebar-selected-background-color: var(--primary-background-color) + sidebar-selected-icon-color: var(--light-primary-color) + sidebar-selected-text-color: var(--sidebar-selected-icon-color) + + state-icon-color: var(--primary-color) + state-icon-active-color: "#ffdd00" + state-icon-unavailable-color: var(--disabled-text-color) + + paper-slider-knob-color: var(--primary-color) + paper-slider-knob-start-color: var(--paper-slider-knob-color) + paper-slider-pin-color: var(--paper-slider-knob-color) + paper-slider-active-color: var(--paper-slider-knob-color) + paper-slider-secondary-color: var(--light-primary-color) + label-badge-background-color: var(--divider-color) + label-badge-text-color: var(--primary-text-color) + label-badge-red: var(--light-primary-color) + + paper-card-background-color: "#4e524f" + paper-listbox-background-color: var(--primary-background-color) + + paper-toggle-button-checked-button-color: var(--primary-color) + paper-toggle-button-checked-bar-color: var(--light-primary-color) + paper-toggle-button-unchecked-button-color: var(--disabled-color) + paper-toggle-button-unchecked-bar-color: "#333333" + + switch-checked-color: var(--paper-toggle-button-checked-button-color) + switch-unchecked-button-color: var(--paper-toggle-button-unchecked-button-color) + switch-unchecked-color: var(--paper-toggle-button-unchecked-bar-color) + switch-unchecked-track-color: var(--paper-toggle-button-unchecked-bar-color) + + table-row-background-color: var(--divider-color) + table-row-alternative-background-color: var(--secondary-background-color) + card-background-color: var(--primary-background-color) + +Dark Orange: + ha-card-border-radius: 2px + ha-card-box-shadow: "1px 1px 2px 0px rgba(0,0,0,0.3)" + + primary-color: "#E77E23" + light-primary-color: "#FF9940" + graph-color: var(--primary-color) + primary-background-color: "#403e3c" + secondary-background-color: var(--primary-background-color) + divider-color: "#292a2e" + disabled-color: "#666666" + + primary-text-color: "#FBFBFB" + secondary-text-color: var(--primary-color) + text-primary-color: var(--primary-text-color) + disabled-text-color: "#7A7D90" + + sidebar-icon-color: var(--primary-color) + sidebar-text-color: var(--primary-text-color) + sidebar-background-color: "#363332" + sidebar-selected-background-color: var(--primary-background-color) + sidebar-selected-icon-color: var(--light-primary-color) + sidebar-selected-text-color: var(--sidebar-selected-icon-color) + + state-icon-color: var(--primary-color) + state-icon-active-color: "#ffdd00" + state-icon-unavailable-color: var(--disabled-text-color) + + paper-slider-knob-color: var(--primary-color) + paper-slider-knob-start-color: var(--paper-slider-knob-color) + paper-slider-pin-color: var(--paper-slider-knob-color) + paper-slider-active-color: var(--paper-slider-knob-color) + paper-slider-secondary-color: var(--light-primary-color) + label-badge-background-color: var(--divider-color) + label-badge-text-color: var(--primary-text-color) + label-badge-red: var(--light-primary-color) + + paper-card-background-color: "#524f4d" + paper-listbox-background-color: var(--primary-background-color) + + paper-toggle-button-checked-button-color: var(--primary-color) + paper-toggle-button-checked-bar-color: var(--light-primary-color) + paper-toggle-button-unchecked-button-color: var(--disabled-color) + paper-toggle-button-unchecked-bar-color: "#333333" + + switch-checked-color: var(--paper-toggle-button-checked-button-color) + switch-unchecked-button-color: var(--paper-toggle-button-unchecked-button-color) + switch-unchecked-color: var(--paper-toggle-button-unchecked-bar-color) + switch-unchecked-track-color: var(--paper-toggle-button-unchecked-bar-color) + + table-row-background-color: var(--divider-color) + table-row-alternative-background-color: var(--secondary-background-color) + card-background-color: var(--primary-background-color) + +Dark Turqoise: + ha-card-border-radius: 2px + ha-card-box-shadow: "1px 1px 2px 0px rgba(0,0,0,0.3)" + + primary-color: "#00BCD4" + light-primary-color: "#19E4FF" + graph-color: var(--primary-color) + primary-background-color: "#3c3f40" + secondary-background-color: var(--primary-background-color) + divider-color: "#292a2e" + disabled-color: "#666666" + + primary-text-color: "#FBFBFB" + secondary-text-color: var(--primary-color) + text-primary-color: var(--primary-text-color) + disabled-text-color: "#7A7D90" + + sidebar-icon-color: var(--primary-color) + sidebar-text-color: var(--primary-text-color) + sidebar-background-color: "#323536" + sidebar-selected-background-color: var(--primary-background-color) + sidebar-selected-icon-color: var(--light-primary-color) + sidebar-selected-text-color: var(--sidebar-selected-icon-color) + + state-icon-color: var(--primary-color) + state-icon-active-color: "#ffdd00" + state-icon-unavailable-color: var(--disabled-text-color) + + paper-slider-knob-color: var(--primary-color) + paper-slider-knob-start-color: var(--paper-slider-knob-color) + paper-slider-pin-color: var(--paper-slider-knob-color) + paper-slider-active-color: var(--paper-slider-knob-color) + paper-slider-secondary-color: var(--light-primary-color) + label-badge-background-color: var(--divider-color) + label-badge-text-color: var(--primary-text-color) + label-badge-red: var(--light-primary-color) + + paper-card-background-color: "#4d5152" + paper-listbox-background-color: var(--primary-background-color) + + paper-toggle-button-checked-button-color: var(--primary-color) + paper-toggle-button-checked-bar-color: var(--light-primary-color) + paper-toggle-button-unchecked-button-color: var(--disabled-color) + paper-toggle-button-unchecked-bar-color: "#333333" + + switch-checked-color: var(--paper-toggle-button-checked-button-color) + switch-unchecked-button-color: var(--paper-toggle-button-unchecked-button-color) + switch-unchecked-color: var(--paper-toggle-button-unchecked-bar-color) + switch-unchecked-track-color: var(--paper-toggle-button-unchecked-bar-color) + + table-row-background-color: var(--divider-color) + table-row-alternative-background-color: var(--secondary-background-color) + card-background-color: var(--primary-background-color) + +Dark Yellow: + ha-card-border-radius: 2px + ha-card-box-shadow: "1px 1px 2px 0px rgba(0,0,0,0.3)" + + primary-color: "#e5ba0e" + light-primary-color: "#FFD429" + graph-color: var(--primary-color) + primary-background-color: "#403f3c" + secondary-background-color: var(--primary-background-color) + divider-color: "#292a2e" + disabled-color: "#666666" + + primary-text-color: "#FBFBFB" + secondary-text-color: var(--primary-color) + text-primary-color: var(--primary-text-color) + disabled-text-color: "#7A7D90" + + sidebar-icon-color: var(--primary-color) + sidebar-text-color: var(--primary-text-color) + sidebar-background-color: "#363432" + sidebar-selected-background-color: var(--primary-background-color) + sidebar-selected-icon-color: var(--light-primary-color) + sidebar-selected-text-color: var(--sidebar-selected-icon-color) + + state-icon-color: var(--primary-color) + state-icon-active-color: "#ffdd00" + state-icon-unavailable-color: var(--disabled-text-color) + + paper-slider-knob-color: var(--primary-color) + paper-slider-knob-start-color: var(--paper-slider-knob-color) + paper-slider-pin-color: var(--paper-slider-knob-color) + paper-slider-active-color: var(--paper-slider-knob-color) + paper-slider-secondary-color: var(--light-primary-color) + label-badge-background-color: var(--divider-color) + label-badge-text-color: var(--primary-text-color) + label-badge-red: var(--light-primary-color) + + paper-card-background-color: "#52514d" + paper-listbox-background-color: var(--primary-background-color) + + paper-toggle-button-checked-button-color: var(--primary-color) + paper-toggle-button-checked-bar-color: var(--light-primary-color) + paper-toggle-button-unchecked-button-color: var(--disabled-color) + paper-toggle-button-unchecked-bar-color: "#333333" + + switch-checked-color: var(--paper-toggle-button-checked-button-color) + switch-unchecked-button-color: var(--paper-toggle-button-unchecked-button-color) + switch-unchecked-color: var(--paper-toggle-button-unchecked-bar-color) + switch-unchecked-track-color: var(--paper-toggle-button-unchecked-bar-color) + + table-row-background-color: var(--divider-color) + table-row-alternative-background-color: var(--secondary-background-color) + card-background-color: var(--primary-background-color) diff --git a/ui-lovelace.yaml b/ui-lovelace.yaml deleted file mode 100644 index afeaa43..0000000 --- a/ui-lovelace.yaml +++ /dev/null @@ -1,13 +0,0 @@ -title: 238 McHaley -# custom cards registration -resources: - - url: /hacsfiles/banner-card/banner-card.js - type: module - - url: /hacsfiles/button-card/button-card.js - type: module - -views: - - !include /config/lovelace/uci-lighting.yaml - - !include /config/lovelace/temps.yaml - - !include /config/lovelace/closet.yaml - - !include /config/lovelace/lights.yaml diff --git a/www/community/banner-card/.gitignore b/www/community/banner-card/.gitignore deleted file mode 100644 index dcbcbde..0000000 --- a/www/community/banner-card/.gitignore +++ /dev/null @@ -1 +0,0 @@ -*.js.gz diff --git a/www/community/banner-card/banner-card.js b/www/community/banner-card/banner-card.js deleted file mode 100644 index 1f6b096..0000000 --- a/www/community/banner-card/banner-card.js +++ /dev/null @@ -1,4 +0,0 @@ -!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t=t||self).BannerCard=e()}(this,function(){"use strict";const t=new WeakMap,e=e=>"function"==typeof e&&t.has(e),i=void 0!==window.customElements&&void 0!==window.customElements.polyfillWrapFlushCallback,s=(t,e,i=null)=>{let s=e;for(;s!==i;){const e=s.nextSibling;t.removeChild(s),s=e}},n={},r={},o=`{{lit-${String(Math.random()).slice(2)}}}`,a=`\x3c!--${o}--\x3e`,c=new RegExp(`${o}|${a}`),l="$lit$";class h{constructor(t,e){this.parts=[],this.element=e;let i=-1,s=0;const n=[],r=e=>{const a=e.content,h=document.createTreeWalker(a,133,null,!1);let d=0;for(;h.nextNode();){i++;const e=h.currentNode;if(1===e.nodeType){if(e.hasAttributes()){const n=e.attributes;let r=0;for(let t=0;t=0&&r++;for(;r-- >0;){const n=t.strings[s],r=p.exec(n)[2],o=r.toLowerCase()+l,a=e.getAttribute(o).split(c);this.parts.push({type:"attribute",index:i,name:r,strings:a}),e.removeAttribute(o),s+=a.length-1}}"TEMPLATE"===e.tagName&&r(e)}else if(3===e.nodeType){const t=e.data;if(t.indexOf(o)>=0){const r=e.parentNode,o=t.split(c),a=o.length-1;for(let t=0;t-1!==t.index,u=()=>document.createComment(""),p=/([ \x09\x0a\x0c\x0d])([^\0-\x1F\x7F-\x9F \x09\x0a\x0c\x0d"'>=/]+)([ \x09\x0a\x0c\x0d]*=[ \x09\x0a\x0c\x0d]*(?:[^ \x09\x0a\x0c\x0d"'`<>=]*|"[^"]*|'[^']*))$/;class m{constructor(t,e,i){this._parts=[],this.template=t,this.processor=e,this.options=i}update(t){let e=0;for(const i of this._parts)void 0!==i&&i.setValue(t[e]),e++;for(const t of this._parts)void 0!==t&&t.commit()}_clone(){const t=i?this.template.element.content.cloneNode(!0):document.importNode(this.template.element.content,!0),e=this.template.parts;let s=0,n=0;const r=t=>{const i=document.createTreeWalker(t,133,null,!1);let o=i.nextNode();for(;snull===t||!("object"==typeof t||"function"==typeof t);class g{constructor(t,e,i){this.dirty=!0,this.element=t,this.name=e,this.strings=i,this.parts=[];for(let t=0;tthis.handleEvent(t)}setValue(t){this._pendingValue=t}commit(){for(;e(this._pendingValue);){const t=this._pendingValue;this._pendingValue=n,t(this)}if(this._pendingValue===n)return;const t=this._pendingValue,i=this.value,s=null==t||null!=i&&(t.capture!==i.capture||t.once!==i.once||t.passive!==i.passive),r=null!=t&&(null==i||s);s&&this.element.removeEventListener(this.eventName,this._boundHandleEvent,this._options),r&&(this._options=C(t),this.element.addEventListener(this.eventName,this._boundHandleEvent,this._options)),this.value=t,this._pendingValue=n}handleEvent(t){"function"==typeof this.value?this.value.call(this.eventContext||this.element,t):this.value.handleEvent(t)}}const C=t=>t&&(x?{capture:t.capture,passive:t.passive,once:t.once}:t.capture);const N=new class{handleAttributeExpressions(t,e,i,s){const n=e[0];if("."===n){return new w(t,e.slice(1),i).parts}return"@"===n?[new P(t,e.slice(1),s.eventContext)]:"?"===n?[new b(t,e.slice(1),i)]:new g(t,e,i).parts}handleTextExpression(t){return new _(t)}};function $(t){let e=k.get(t.type);void 0===e&&(e={stringsArray:new WeakMap,keyString:new Map},k.set(t.type,e));let i=e.stringsArray.get(t.strings);if(void 0!==i)return i;const s=t.strings.join(o);return void 0===(i=e.keyString.get(s))&&(i=new h(t,t.getTemplateElement()),e.keyString.set(s,i)),e.stringsArray.set(t.strings,i),i}const k=new Map,A=new WeakMap;(window.litHtmlVersions||(window.litHtmlVersions=[])).push("1.0.0");const E=(t,...e)=>new f(t,e,"html",N),T=133;function V(t,e){const{element:{content:i},parts:s}=t,n=document.createTreeWalker(i,T,null,!1);let r=O(s),o=s[r],a=-1,c=0;const l=[];let h=null;for(;n.nextNode();){a++;const t=n.currentNode;for(t.previousSibling===h&&(h=null),e.has(t)&&(l.push(t),null===h&&(h=t)),null!==h&&c++;void 0!==o&&o.index===a;)o.index=null!==h?-1:o.index-c,o=s[r=O(s,r)]}l.forEach(t=>t.parentNode.removeChild(t))}const z=t=>{let e=11===t.nodeType?0:1;const i=document.createTreeWalker(t,T,null,!1);for(;i.nextNode();)e++;return e},O=(t,e=-1)=>{for(let i=e+1;i`${t}--${e}`;let M=!0;void 0===window.ShadyCSS?M=!1:void 0===window.ShadyCSS.prepareTemplateDom&&(console.warn("Incompatible ShadyCSS version detected.Please update to at least @webcomponents/webcomponentsjs@2.0.2 and@webcomponents/shadycss@1.3.1."),M=!1);const U=t=>e=>{const i=j(e.type,t);let s=k.get(i);void 0===s&&(s={stringsArray:new WeakMap,keyString:new Map},k.set(i,s));let n=s.stringsArray.get(e.strings);if(void 0!==n)return n;const r=e.strings.join(o);if(void 0===(n=s.keyString.get(r))){const i=e.getTemplateElement();M&&window.ShadyCSS.prepareTemplateDom(i,t),n=new h(e,i),s.keyString.set(r,n)}return s.stringsArray.set(e.strings,n),n},R=["html","svg"],q=new Set,I=(t,e,i)=>{q.add(i);const s=t.querySelectorAll("style");if(0===s.length)return void window.ShadyCSS.prepareTemplateStyles(e.element,i);const n=document.createElement("style");for(let t=0;t{R.forEach(e=>{const i=k.get(j(e,t));void 0!==i&&i.keyString.forEach(t=>{const{element:{content:e}}=t,i=new Set;Array.from(e.querySelectorAll("style")).forEach(t=>{i.add(t)}),V(t,i)})})})(i),function(t,e,i=null){const{element:{content:s},parts:n}=t;if(null==i)return void s.appendChild(e);const r=document.createTreeWalker(s,T,null,!1);let o=O(n),a=0,c=-1;for(;r.nextNode();){for(c++,r.currentNode===i&&(a=z(e),i.parentNode.insertBefore(e,i));-1!==o&&n[o].index===c;){if(a>0){for(;-1!==o;)n[o].index+=a,o=O(n,o);return}o=O(n,o)}}}(e,n,e.element.content.firstChild),window.ShadyCSS.prepareTemplateStyles(e.element,i),window.ShadyCSS.nativeShadow){const i=e.element.content.querySelector("style");t.insertBefore(i.cloneNode(!0),t.firstChild)}else{e.element.content.insertBefore(n,e.element.content.firstChild);const t=new Set;t.add(n),V(e,t)}};window.JSCompiler_renameProperty=(t,e)=>t;const F={toAttribute(t,e){switch(e){case Boolean:return t?"":null;case Object:case Array:return null==t?t:JSON.stringify(t)}return t},fromAttribute(t,e){switch(e){case Boolean:return null!==t;case Number:return null===t?null:Number(t);case Object:case Array:return JSON.parse(t)}return t}},H=(t,e)=>e!==t&&(e==e||t==t),B={attribute:!0,type:String,converter:F,reflect:!1,hasChanged:H},D=Promise.resolve(!0),L=1,W=4,J=8,Y=16,G=32,K="finalized";class Q extends HTMLElement{constructor(){super(),this._updateState=0,this._instanceProperties=void 0,this._updatePromise=D,this._hasConnectedResolver=void 0,this._changedProperties=new Map,this._reflectingProperties=void 0,this.initialize()}static get observedAttributes(){this.finalize();const t=[];return this._classProperties.forEach((e,i)=>{const s=this._attributeNameForProperty(i,e);void 0!==s&&(this._attributeToPropertyMap.set(s,i),t.push(s))}),t}static _ensureClassProperties(){if(!this.hasOwnProperty(JSCompiler_renameProperty("_classProperties",this))){this._classProperties=new Map;const t=Object.getPrototypeOf(this)._classProperties;void 0!==t&&t.forEach((t,e)=>this._classProperties.set(e,t))}}static createProperty(t,e=B){if(this._ensureClassProperties(),this._classProperties.set(t,e),e.noAccessor||this.prototype.hasOwnProperty(t))return;const i="symbol"==typeof t?Symbol():`__${t}`;Object.defineProperty(this.prototype,t,{get(){return this[i]},set(e){const s=this[t];this[i]=e,this._requestUpdate(t,s)},configurable:!0,enumerable:!0})}static finalize(){const t=Object.getPrototypeOf(this);if(t.hasOwnProperty(K)||t.finalize(),this[K]=!0,this._ensureClassProperties(),this._attributeToPropertyMap=new Map,this.hasOwnProperty(JSCompiler_renameProperty("properties",this))){const t=this.properties,e=[...Object.getOwnPropertyNames(t),..."function"==typeof Object.getOwnPropertySymbols?Object.getOwnPropertySymbols(t):[]];for(const i of e)this.createProperty(i,t[i])}}static _attributeNameForProperty(t,e){const i=e.attribute;return!1===i?void 0:"string"==typeof i?i:"string"==typeof t?t.toLowerCase():void 0}static _valueHasChanged(t,e,i=H){return i(t,e)}static _propertyValueFromAttribute(t,e){const i=e.type,s=e.converter||F,n="function"==typeof s?s:s.fromAttribute;return n?n(t,i):t}static _propertyValueToAttribute(t,e){if(void 0===e.reflect)return;const i=e.type,s=e.converter;return(s&&s.toAttribute||F.toAttribute)(t,i)}initialize(){this._saveInstanceProperties(),this._requestUpdate()}_saveInstanceProperties(){this.constructor._classProperties.forEach((t,e)=>{if(this.hasOwnProperty(e)){const t=this[e];delete this[e],this._instanceProperties||(this._instanceProperties=new Map),this._instanceProperties.set(e,t)}})}_applyInstanceProperties(){this._instanceProperties.forEach((t,e)=>this[e]=t),this._instanceProperties=void 0}connectedCallback(){this._updateState=this._updateState|G,this._hasConnectedResolver&&(this._hasConnectedResolver(),this._hasConnectedResolver=void 0)}disconnectedCallback(){}attributeChangedCallback(t,e,i){e!==i&&this._attributeToProperty(t,i)}_propertyToAttribute(t,e,i=B){const s=this.constructor,n=s._attributeNameForProperty(t,i);if(void 0!==n){const t=s._propertyValueToAttribute(e,i);if(void 0===t)return;this._updateState=this._updateState|J,null==t?this.removeAttribute(n):this.setAttribute(n,t),this._updateState=this._updateState&~J}}_attributeToProperty(t,e){if(this._updateState&J)return;const i=this.constructor,s=i._attributeToPropertyMap.get(t);if(void 0!==s){const t=i._classProperties.get(s)||B;this._updateState=this._updateState|Y,this[s]=i._propertyValueFromAttribute(e,t),this._updateState=this._updateState&~Y}}_requestUpdate(t,e){let i=!0;if(void 0!==t){const s=this.constructor,n=s._classProperties.get(t)||B;s._valueHasChanged(this[t],e,n.hasChanged)?(this._changedProperties.has(t)||this._changedProperties.set(t,e),!0!==n.reflect||this._updateState&Y||(void 0===this._reflectingProperties&&(this._reflectingProperties=new Map),this._reflectingProperties.set(t,n))):i=!1}!this._hasRequestedUpdate&&i&&this._enqueueUpdate()}requestUpdate(t,e){return this._requestUpdate(t,e),this.updateComplete}async _enqueueUpdate(){let t,e;this._updateState=this._updateState|W;const i=this._updatePromise;this._updatePromise=new Promise((i,s)=>{t=i,e=s});try{await i}catch(t){}this._hasConnected||await new Promise(t=>this._hasConnectedResolver=t);try{const t=this.performUpdate();null!=t&&await t}catch(t){e(t)}t(!this._hasRequestedUpdate)}get _hasConnected(){return this._updateState&G}get _hasRequestedUpdate(){return this._updateState&W}get hasUpdated(){return this._updateState&L}performUpdate(){this._instanceProperties&&this._applyInstanceProperties();let t=!1;const e=this._changedProperties;try{(t=this.shouldUpdate(e))&&this.update(e)}catch(e){throw t=!1,e}finally{this._markUpdated()}t&&(this._updateState&L||(this._updateState=this._updateState|L,this.firstUpdated(e)),this.updated(e))}_markUpdated(){this._changedProperties=new Map,this._updateState=this._updateState&~W}get updateComplete(){return this._getUpdateComplete()}_getUpdateComplete(){return this._updatePromise}shouldUpdate(t){return!0}update(t){void 0!==this._reflectingProperties&&this._reflectingProperties.size>0&&(this._reflectingProperties.forEach((t,e)=>this._propertyToAttribute(e,this[e],t)),this._reflectingProperties=void 0)}updated(t){}firstUpdated(t){}}Q[K]=!0;const X="adoptedStyleSheets"in Document.prototype&&"replace"in CSSStyleSheet.prototype,Z=Symbol();class tt{constructor(t,e){if(e!==Z)throw new Error("CSSResult is not constructable. Use `unsafeCSS` or `css` instead.");this.cssText=t}get styleSheet(){return void 0===this._styleSheet&&(X?(this._styleSheet=new CSSStyleSheet,this._styleSheet.replaceSync(this.cssText)):this._styleSheet=null),this._styleSheet}toString(){return this.cssText}}(window.litElementVersions||(window.litElementVersions=[])).push("2.2.1");const et=t=>t.flat?t.flat(1/0):function t(e,i=[]){for(let s=0,n=e.length;s(t.add(e),t),new Set).forEach(t=>e.unshift(t))}else t&&e.push(t);return e}initialize(){super.initialize(),this.renderRoot=this.createRenderRoot(),window.ShadowRoot&&this.renderRoot instanceof window.ShadowRoot&&this.adoptStyles()}createRenderRoot(){return this.attachShadow({mode:"open"})}adoptStyles(){const t=this.constructor._styles;0!==t.length&&(void 0===window.ShadyCSS||window.ShadyCSS.nativeShadow?X?this.renderRoot.adoptedStyleSheets=t.map(t=>t.styleSheet):this._needsShimAdoptedStyleSheets=!0:window.ShadyCSS.ScopingShim.prepareAdoptedCssText(t.map(t=>t.cssText),this.localName))}connectedCallback(){super.connectedCallback(),this.hasUpdated&&void 0!==window.ShadyCSS&&window.ShadyCSS.styleElement(this)}update(t){super.update(t);const e=this.render();e instanceof f&&this.constructor.render(e,this.renderRoot,{scopeName:this.localName,eventContext:this}),this._needsShimAdoptedStyleSheets&&(this._needsShimAdoptedStyleSheets=!1,this.constructor._styles.forEach(t=>{const e=document.createElement("style");e.textContent=t.cssText,this.renderRoot.appendChild(e)}))}render(){}}it.finalized=!0,it.render=(t,e,i)=>{const n=i.scopeName,r=A.has(e),o=e instanceof ShadowRoot&&M&&t instanceof f,a=o&&!q.has(n),c=a?document.createDocumentFragment():e;if(((t,e,i)=>{let n=A.get(e);void 0===n&&(s(e,e.firstChild),A.set(e,n=new _(Object.assign({templateFactory:$},i))),n.appendInto(e)),n.setValue(t),n.commit()})(t,c,Object.assign({templateFactory:U(n)},i)),a){const t=A.get(c);A.delete(c),t.value instanceof m&&I(c,t.value.template,n),s(e,e.firstChild),e.appendChild(c),A.set(e,t)}!r&&o&&window.ShadyCSS.styleElement(e.host)};var st=((t,...e)=>{const i=e.reduce((e,i,s)=>e+(t=>{if(t instanceof tt)return t.cssText;if("number"==typeof t)return t;throw new Error(`Value passed to 'css' function must be a 'css' function result: ${t}. Use 'unsafeCSS' to pass non-literal values, but\n take care to ensure page security.`)})(i)+t[s+1],t[0]);return new tt(i,Z)})`:host{--bc-font-size-heading:var(--banner-card-heading-size, 3em);--bc-font-size-entity-value:var(--banner-card-entity-value-size, 1.5em);--bc-font-size-media-title:var(--banner-card-media-title-size, 0.9em);--bc-spacing:var(--banner-card-spacing, 4px);--bc-button-size:var(--banner-card-button-size, 32px);--bc-heading-color-dark:var( - --banner-card-heading-color-dark, - var(--primary-text-color) - );--bc-heading-color-light:var(--banner-card-heading-color-light, #fff)}ha-card{display:flex;flex-direction:column;align-items:center}a{cursor:pointer}paper-icon-button{width:var(--bc-button-size);height:var(--bc-button-size);padding:var(--bc-spacing)}.heading{display:flex;justify-content:center;align-items:center;font-size:var(--bc-font-size-heading);font-weight:300;cursor:pointer}ha-icon.heading-icon{--iron-icon-width:1em;--iron-icon-height:1em;margin:0 var(--bc-spacing)}.overlay-strip{background:rgba(0,0,0,.3);overflow:hidden;width:100%}.entities{padding:calc(var(--bc-spacing) * 2) 0;color:#fff;display:grid}.entity-state{display:flex;flex-direction:column;align-items:center;margin-top:var(--bc-spacing);margin-bottom:var(--bc-spacing);box-shadow:-1px 0 0 0 #fff;width:100%}.media-title{flex:1 0;overflow:hidden;font-weight:300;font-size:var(--bc-font-size-media-title);white-space:nowrap;text-overflow:ellipsis}.media-controls{display:flex;flex:0 0 calc(var(--bc-button-size) * 3)}.entity-state.expand .entity-value{width:100%}.entity-state-left{margin-right:auto;margin-left:16px}.entity-state-right{margin-left:auto;margin-right:16px}.entity-name{font-weight:700;white-space:nowrap;padding-top:calc(var(--bc-spacing) * 2);padding-bottom:calc(var(--bc-spacing) * 2)}.entity-value{display:flex;width:100%;flex:1 0;font-size:var(--bc-font-size-entity-value);align-items:center;justify-content:center}.entity-value.error{display:inline-block;word-wrap:break-word;font-size:16px;width:90%}.entity-value ha-icon{color:#fff}mwc-button{--mdc-theme-primary:white}mwc-switch{--mdc-theme-secondary:white}`;function nt({state:t,attributes:e},i=!1){return"string"==typeof i&&e.hasOwnProperty(i)?e[i]:t}function rt(t){return"object"==typeof t?(e=t,i=t=>!1===t?null:t,Object.entries(e).reduce((t,[e,s])=>({...t,[e]:i(s,e)}),{})):{entity:t};var e,i}const ot={"=":(t,e)=>e.includes(t),">":(t,e)=>t>e[0],"<":(t,e)=>t!e.includes(t)};function at(t,e){if(["string","number","boolean"].includes(typeof t))return ot["="](e,[t]);if(Array.isArray(t)){const[i,...s]=t;return ot.hasOwnProperty(i)?ot[i](e,s):ot["="](e,s)}throw new Error(`Couldn't find a valid matching strategy for '${t}'`)}var ct="banner-card";!function(t){console.info(`%c${ct}: ${t}`,"font-weight: bold")}("0.9.0");const lt=/^(mdi|hass):/;function ht(t){return"string"==typeof t&&t.match(lt)}function dt(t,e=null){return e?E`${t}`:E`${t}`}class ut extends it{static get properties(){return{config:Object,color:String,entities:Array,entityValues:Array,rowSize:Number,_hass:Object}}static get styles(){return[st]}constructor(){super(),this.config={},this.entities=[],this._hass={}}setConfig(t){if(void 0===t.heading)throw new Error("You need to define a heading");if(this.entities=(t.entities||[]).map(rt),this.config=t,this.color=t.color||function(t,e,i){if(!t||"#"!==t[0])return i;if(3===(t=t.substring(1)).length){const[e,i,s]=t;t=[e,e,i,i,s,s].join("")}if(6!==t.length)return i;const s=[parseInt(t.slice(0,2),16)/255,parseInt(t.slice(2,4),16)/255,parseInt(t.slice(4,6),16)/255],[n,r,o]=s.map(t=>t<=.03928?t/12.92:Math.pow((t+.055)/1.055,2.4));return.2126*n+.7152*r+.0722*o>.179?i:e}(t.background,"var(--bc-heading-color-light)","var(--bc-heading-color-dark)"),void 0!==t.row_size){if(t.row_size<1)throw new Error("row_size must be at least 1");this.rowSize=t.row_size}this.rowSize=this.rowSize||3}set hass(t){this._hass=t,this.entityValues=this.entities.filter(e=>(function(t,e){if(!e.hasOwnProperty(t.entity))return!1;if(t.when){const{state:i,entity:s=t.entity,attributes:n}="string"==typeof t.when?{state:t.when}:t.when,r=e[s];return!(void 0!==i&&!at(i,r.state))&&Object.entries(n||{}).every(([t,e])=>{return at(e,r.attributes[t])})}return!0})(e,t.states)).map(t=>this.parseEntity(t))}parseEntity(t){const e=this._hass;if(!e.states.hasOwnProperty(t.entity))return{...t,error:"Entity not ready"};const i=e.states[t.entity],s=i.attributes,n={};if(t.map_state&&i.state in t.map_state){const e=t.map_state[i.state],s=typeof e;"string"===s?n.value=e:"object"===s&&Object.entries(e).forEach(([t,e])=>{n[t]=e})}const r={name:s.friendly_name,state:i.state,value:nt(i,t.attribute),unit:s.unit_of_measurement,attributes:s,domain:t.entity.split(".")[0]};return s.hasOwnProperty("current_position")&&(r.state=s.current_position),{...r,...t,...n}}grid(t=1){return"full"===t||t>this.rowSize?`grid-column: span ${this.rowSize};`:`grid-column: span ${t};`}_service(t,e,i){return()=>this._hass.callService(t,e,{entity_id:i})}render(){return E`${this.renderHeading()} ${this.renderEntities()}`}renderHeading(){let t=this.config.heading;if(!1===t)return null;Array.isArray(t)||(t=[t]);return E`

this.config.link&&this.navigate(this.config.link)} style=color:${this.color}>${t.map(t=>ht(t)?E``:E`${t}`)}

`}renderEntities(){return 0===this.entityValues.length?null:E`
${this.entityValues.map(t=>{if(t.error)return E`
${dt(t.error)} ${t.entity}
`;const e={...t,onClick:()=>this.openEntityPopover(t.entity)};if(t.action)return this.renderCustom({...e,action:()=>{const{service:e,...i}=t.action,[s,n]=e.split(".");this._hass.callService(s,n,{entity_id:t.entity,...i})}});if(!t.attribute)switch(t.domain){case"light":case"switch":case"input_boolean":return this.renderAsToggle(e);case"cover":return this.renderDomainCover(e);case"media_player":return this.renderDomainMediaPlayer(e)}return this.renderDomainDefault(e)})}
`}renderValue({icon:t,value:e,image:i,action:s,click:n},r){return t||ht(e)?E``:!0===i?E``:r()}renderDomainDefault({value:t,unit:e,image:i,icon:s,name:n,size:r,onClick:o}){const a=this.renderValue({icon:s,image:i,value:t,click:o},()=>E`${t} ${e}`);return E`${dt(n,o)} ${a}`}renderCustom({value:t,unit:e,action:i,image:s,icon:n,name:r,size:o,onClick:a}){const c=this.renderValue({icon:n,image:s,value:t,click:i},()=>E`${t} ${e}`);return E`
${dt(r,a)} ${c}
`}renderDomainMediaPlayer({onClick:t,attributes:e,size:i,name:s,state:n,entity:r,domain:o}){const a="playing"===n,c=a?"media_pause":"media_play",l=[e.media_artist,e.media_title].join(" – ");return E`
${dt(s,t)}
${l}
`}renderAsToggle({onClick:t,size:e,name:i,state:s,domain:n,entity:r}){return E`
${dt(i,t)}
`}renderDomainCover({onClick:t,size:e,name:i,state:s,entity:n}){const r="closed"===s||0===s,o="open"===s||100===s;return E`
${dt(i,t)}
`}getCardSize(){return 3}navigate(t){history.pushState(null,"",t),this.fire("location-changed",{replace:!0})}openEntityPopover(t){this.fire("hass-more-info",{entityId:t})}fire(t,e,i){i=i||{},e=null==e?{}:e;const s=new Event(t,{bubbles:void 0===i.bubbles||i.bubbles,cancelable:Boolean(i.cancelable),composed:void 0===i.composed||i.composed});return s.detail=e,this.dispatchEvent(s),s}}return window.customElements.define("banner-card",ut),ut}); diff --git a/www/community/button-card/.gitignore b/www/community/button-card/.gitignore deleted file mode 100644 index dcbcbde..0000000 --- a/www/community/button-card/.gitignore +++ /dev/null @@ -1 +0,0 @@ -*.js.gz diff --git a/www/community/button-card/button-card.js b/www/community/button-card/button-card.js index 77be32e..a8d06c6 100644 --- a/www/community/button-card/button-card.js +++ b/www/community/button-card/button-card.js @@ -1,1050 +1,8 @@ -function t(t, e, n, i) { - var s, - r = arguments.length, - a = r < 3 ? e : null === i ? i = Object.getOwnPropertyDescriptor(e, n) : i;if ("object" == typeof Reflect && "function" == typeof Reflect.decorate) a = Reflect.decorate(t, e, n, i);else for (var o = t.length - 1; o >= 0; o--) (s = t[o]) && (a = (r < 3 ? s(a) : r > 3 ? s(e, n, a) : s(e, n)) || a);return r > 3 && a && Object.defineProperty(e, n, a), a; - /** - * @license - * Copyright (c) 2017 The Polymer Project Authors. All rights reserved. - * This code may only be used under the BSD style license found at - * http://polymer.github.io/LICENSE.txt - * The complete set of authors may be found at - * http://polymer.github.io/AUTHORS.txt - * The complete set of contributors may be found at - * http://polymer.github.io/CONTRIBUTORS.txt - * Code distributed by Google as part of the polymer project is also - * subject to an additional IP rights grant found at - * http://polymer.github.io/PATENTS.txt - */ -}const e = new WeakMap(), - n = t => (...n) => { - const i = t(...n);return e.set(i, !0), i; -}, - i = t => "function" == typeof t && e.has(t), - s = void 0 !== window.customElements && void 0 !== window.customElements.polyfillWrapFlushCallback, - r = (t, e, n = null) => { - for (; e !== n;) { - const n = e.nextSibling;t.removeChild(e), e = n; +function t(t,e,i,n){var r,s=arguments.length,a=s<3?e:null===n?n=Object.getOwnPropertyDescriptor(e,i):n;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)a=Reflect.decorate(t,e,i,n);else for(var o=t.length-1;o>=0;o--)(r=t[o])&&(a=(s<3?r(a):s>3?r(e,i,a):r(e,i))||a);return s>3&&a&&Object.defineProperty(e,i,a),a}const e="undefined"!=typeof window&&null!=window.customElements&&void 0!==window.customElements.polyfillWrapFlushCallback,i=(t,e,i=null)=>{for(;e!==i;){const i=e.nextSibling;t.removeChild(e),e=i}},n=`{{lit-${String(Math.random()).slice(2)}}}`,r=`\x3c!--${n}--\x3e`,s=new RegExp(`${n}|${r}`);class a{constructor(t,e){this.parts=[],this.element=e;const i=[],r=[],a=document.createTreeWalker(e.content,133,null,!1);let l=0,u=-1,d=0;const{strings:p,values:{length:f}}=t;for(;d0;){const e=p[d],i=h.exec(e)[2],n=i.toLowerCase()+"$lit$",r=t.getAttribute(n);t.removeAttribute(n);const a=r.split(s);this.parts.push({type:"attribute",index:u,name:i,strings:a}),d+=a.length-1}}"TEMPLATE"===t.tagName&&(r.push(t),a.currentNode=t.content)}else if(3===t.nodeType){const e=t.data;if(e.indexOf(n)>=0){const n=t.parentNode,r=e.split(s),a=r.length-1;for(let e=0;e{const i=t.length-e.length;return i>=0&&t.slice(i)===e},l=t=>-1!==t.index,c=()=>document.createComment(""),h=/([ \x09\x0a\x0c\x0d])([^\0-\x1F\x7F-\x9F "'>=/]+)([ \x09\x0a\x0c\x0d]*=[ \x09\x0a\x0c\x0d]*(?:[^ \x09\x0a\x0c\x0d"'`<>=]*|"[^"]*|'[^']*))$/;function u(t,e){const{element:{content:i},parts:n}=t,r=document.createTreeWalker(i,133,null,!1);let s=p(n),a=n[s],o=-1,l=0;const c=[];let h=null;for(;r.nextNode();){o++;const t=r.currentNode;for(t.previousSibling===h&&(h=null),e.has(t)&&(c.push(t),null===h&&(h=t)),null!==h&&l++;void 0!==a&&a.index===o;)a.index=null!==h?-1:a.index-l,s=p(n,s),a=n[s]}c.forEach((t=>t.parentNode.removeChild(t)))}const d=t=>{let e=11===t.nodeType?0:1;const i=document.createTreeWalker(t,133,null,!1);for(;i.nextNode();)e++;return e},p=(t,e=-1)=>{for(let i=e+1;i(...e)=>{const i=t(...e);return f.set(i,!0),i},m=t=>"function"==typeof t&&f.has(t),_={},b={};class y{constructor(t,e,i){this.__parts=[],this.template=t,this.processor=e,this.options=i}update(t){let e=0;for(const i of this.__parts)void 0!==i&&i.setValue(t[e]),e++;for(const i of this.__parts)void 0!==i&&i.commit()}_clone(){const t=e?this.template.element.content.cloneNode(!0):document.importNode(this.template.element.content,!0),i=[],n=this.template.parts,r=document.createTreeWalker(t,133,null,!1);let s,a=0,o=0,c=r.nextNode();for(;at}),w=` ${n} `;class S{constructor(t,e,i,n){this.strings=t,this.values=e,this.type=i,this.processor=n}getHTML(){const t=this.strings.length-1;let e="",i=!1;for(let s=0;s-1||i)&&-1===t.indexOf("--\x3e",a+1);const o=h.exec(t);e+=null===o?t+(i?w:r):t.substr(0,o.index)+o[1]+o[2]+"$lit$"+o[3]+n}return e+=this.strings[t],e}getTemplateElement(){const t=document.createElement("template");let e=this.getHTML();return void 0!==v&&(e=v.createHTML(e)),t.innerHTML=e,t}}const x=t=>null===t||!("object"==typeof t||"function"==typeof t),k=t=>Array.isArray(t)||!(!t||!t[Symbol.iterator]);class O{constructor(t,e,i){this.dirty=!0,this.element=t,this.name=e,this.strings=i,this.parts=[];for(let n=0;n{try{const t={get capture(){return N=!0,!1}};window.addEventListener("test",t,t),window.removeEventListener("test",t,t)}catch(t){}})();class P{constructor(t,e,i){this.value=void 0,this.__pendingValue=void 0,this.element=t,this.eventName=e,this.eventContext=i,this.__boundHandleEvent=t=>this.handleEvent(t)}setValue(t){this.__pendingValue=t}commit(){for(;m(this.__pendingValue);){const t=this.__pendingValue;this.__pendingValue=_,t(this)}if(this.__pendingValue===_)return;const t=this.__pendingValue,e=this.value,i=null==t||null!=e&&(t.capture!==e.capture||t.once!==e.once||t.passive!==e.passive),n=null!=t&&(null==e||i);i&&this.element.removeEventListener(this.eventName,this.__boundHandleEvent,this.__options),n&&(this.__options=j(t),this.element.addEventListener(this.eventName,this.__boundHandleEvent,this.__options)),this.value=t,this.__pendingValue=_}handleEvent(t){"function"==typeof this.value?this.value.call(this.eventContext||this.element,t):this.value.handleEvent(t)}}const j=t=>t&&(N?{capture:t.capture,passive:t.passive,once:t.once}:t.capture);function $(t){let e=H.get(t.type);void 0===e&&(e={stringsArray:new WeakMap,keyString:new Map},H.set(t.type,e));let i=e.stringsArray.get(t.strings);if(void 0!==i)return i;const r=t.strings.join(n);return i=e.keyString.get(r),void 0===i&&(i=new a(t,t.getTemplateElement()),e.keyString.set(r,i)),e.stringsArray.set(t.strings,i),i}const H=new Map,R=new WeakMap;const V=new class{handleAttributeExpressions(t,e,i,n){const r=e[0];if("."===r){return new E(t,e.slice(1),i).parts}if("@"===r)return[new P(t,e.slice(1),n.eventContext)];if("?"===r)return[new C(t,e.slice(1),i)];return new O(t,e,i).parts}handleTextExpression(t){return new M(t)}};"undefined"!=typeof window&&(window.litHtmlVersions||(window.litHtmlVersions=[])).push("1.3.0");const D=(t,...e)=>new S(t,e,"html",V),L=(t,e)=>`${t}--${e}`;let F=!0;void 0===window.ShadyCSS?F=!1:void 0===window.ShadyCSS.prepareTemplateDom&&(console.warn("Incompatible ShadyCSS version detected. Please update to at least @webcomponents/webcomponentsjs@2.0.2 and @webcomponents/shadycss@1.3.1."),F=!1);const I=t=>e=>{const i=L(e.type,t);let r=H.get(i);void 0===r&&(r={stringsArray:new WeakMap,keyString:new Map},H.set(i,r));let s=r.stringsArray.get(e.strings);if(void 0!==s)return s;const o=e.strings.join(n);if(s=r.keyString.get(o),void 0===s){const i=e.getTemplateElement();F&&window.ShadyCSS.prepareTemplateDom(i,t),s=new a(e,i),r.keyString.set(o,s)}return r.stringsArray.set(e.strings,s),s},z=["html","svg"],U=new Set,q=(t,e,i)=>{U.add(t);const n=i?i.element:document.createElement("template"),r=e.querySelectorAll("style"),{length:s}=r;if(0===s)return void window.ShadyCSS.prepareTemplateStyles(n,t);const a=document.createElement("style");for(let c=0;c{z.forEach((e=>{const i=H.get(L(e,t));void 0!==i&&i.keyString.forEach((t=>{const{element:{content:e}}=t,i=new Set;Array.from(e.querySelectorAll("style")).forEach((t=>{i.add(t)})),u(t,i)}))}))})(t);const o=n.content;i?function(t,e,i=null){const{element:{content:n},parts:r}=t;if(null==i)return void n.appendChild(e);const s=document.createTreeWalker(n,133,null,!1);let a=p(r),o=0,l=-1;for(;s.nextNode();)for(l++,s.currentNode===i&&(o=d(e),i.parentNode.insertBefore(e,i));-1!==a&&r[a].index===l;){if(o>0){for(;-1!==a;)r[a].index+=o,a=p(r,a);return}a=p(r,a)}}(i,a,o.firstChild):o.insertBefore(a,o.firstChild),window.ShadyCSS.prepareTemplateStyles(n,t);const l=o.querySelector("style");if(window.ShadyCSS.nativeShadow&&null!==l)e.insertBefore(l.cloneNode(!0),e.firstChild);else if(i){o.insertBefore(a,o.firstChild);const t=new Set;t.add(a),u(i,t)}};window.JSCompiler_renameProperty=(t,e)=>t;const Y={toAttribute(t,e){switch(e){case Boolean:return t?"":null;case Object:case Array:return null==t?t:JSON.stringify(t)}return t},fromAttribute(t,e){switch(e){case Boolean:return null!==t;case Number:return null===t?null:Number(t);case Object:case Array:return JSON.parse(t)}return t}},B=(t,e)=>e!==t&&(e==e||t==t),W={attribute:!0,type:String,converter:Y,reflect:!1,hasChanged:B};class G extends HTMLElement{constructor(){super(),this.initialize()}static get observedAttributes(){this.finalize();const t=[];return this._classProperties.forEach(((e,i)=>{const n=this._attributeNameForProperty(i,e);void 0!==n&&(this._attributeToPropertyMap.set(n,i),t.push(n))})),t}static _ensureClassProperties(){if(!this.hasOwnProperty(JSCompiler_renameProperty("_classProperties",this))){this._classProperties=new Map;const t=Object.getPrototypeOf(this)._classProperties;void 0!==t&&t.forEach(((t,e)=>this._classProperties.set(e,t)))}}static createProperty(t,e=W){if(this._ensureClassProperties(),this._classProperties.set(t,e),e.noAccessor||this.prototype.hasOwnProperty(t))return;const i="symbol"==typeof t?Symbol():"__"+t,n=this.getPropertyDescriptor(t,i,e);void 0!==n&&Object.defineProperty(this.prototype,t,n)}static getPropertyDescriptor(t,e,i){return{get(){return this[e]},set(n){const r=this[t];this[e]=n,this.requestUpdateInternal(t,r,i)},configurable:!0,enumerable:!0}}static getPropertyOptions(t){return this._classProperties&&this._classProperties.get(t)||W}static finalize(){const t=Object.getPrototypeOf(this);if(t.hasOwnProperty("finalized")||t.finalize(),this.finalized=!0,this._ensureClassProperties(),this._attributeToPropertyMap=new Map,this.hasOwnProperty(JSCompiler_renameProperty("properties",this))){const t=this.properties,e=[...Object.getOwnPropertyNames(t),..."function"==typeof Object.getOwnPropertySymbols?Object.getOwnPropertySymbols(t):[]];for(const i of e)this.createProperty(i,t[i])}}static _attributeNameForProperty(t,e){const i=e.attribute;return!1===i?void 0:"string"==typeof i?i:"string"==typeof t?t.toLowerCase():void 0}static _valueHasChanged(t,e,i=B){return i(t,e)}static _propertyValueFromAttribute(t,e){const i=e.type,n=e.converter||Y,r="function"==typeof n?n:n.fromAttribute;return r?r(t,i):t}static _propertyValueToAttribute(t,e){if(void 0===e.reflect)return;const i=e.type,n=e.converter;return(n&&n.toAttribute||Y.toAttribute)(t,i)}initialize(){this._updateState=0,this._updatePromise=new Promise((t=>this._enableUpdatingResolver=t)),this._changedProperties=new Map,this._saveInstanceProperties(),this.requestUpdateInternal()}_saveInstanceProperties(){this.constructor._classProperties.forEach(((t,e)=>{if(this.hasOwnProperty(e)){const t=this[e];delete this[e],this._instanceProperties||(this._instanceProperties=new Map),this._instanceProperties.set(e,t)}}))}_applyInstanceProperties(){this._instanceProperties.forEach(((t,e)=>this[e]=t)),this._instanceProperties=void 0}connectedCallback(){this.enableUpdating()}enableUpdating(){void 0!==this._enableUpdatingResolver&&(this._enableUpdatingResolver(),this._enableUpdatingResolver=void 0)}disconnectedCallback(){}attributeChangedCallback(t,e,i){e!==i&&this._attributeToProperty(t,i)}_propertyToAttribute(t,e,i=W){const n=this.constructor,r=n._attributeNameForProperty(t,i);if(void 0!==r){const t=n._propertyValueToAttribute(e,i);if(void 0===t)return;this._updateState=8|this._updateState,null==t?this.removeAttribute(r):this.setAttribute(r,t),this._updateState=-9&this._updateState}}_attributeToProperty(t,e){if(8&this._updateState)return;const i=this.constructor,n=i._attributeToPropertyMap.get(t);if(void 0!==n){const t=i.getPropertyOptions(n);this._updateState=16|this._updateState,this[n]=i._propertyValueFromAttribute(e,t),this._updateState=-17&this._updateState}}requestUpdateInternal(t,e,i){let n=!0;if(void 0!==t){const r=this.constructor;i=i||r.getPropertyOptions(t),r._valueHasChanged(this[t],e,i.hasChanged)?(this._changedProperties.has(t)||this._changedProperties.set(t,e),!0!==i.reflect||16&this._updateState||(void 0===this._reflectingProperties&&(this._reflectingProperties=new Map),this._reflectingProperties.set(t,i))):n=!1}!this._hasRequestedUpdate&&n&&(this._updatePromise=this._enqueueUpdate())}requestUpdate(t,e){return this.requestUpdateInternal(t,e),this.updateComplete}async _enqueueUpdate(){this._updateState=4|this._updateState;try{await this._updatePromise}catch(e){}const t=this.performUpdate();return null!=t&&await t,!this._hasRequestedUpdate}get _hasRequestedUpdate(){return 4&this._updateState}get hasUpdated(){return 1&this._updateState}performUpdate(){if(!this._hasRequestedUpdate)return;this._instanceProperties&&this._applyInstanceProperties();let t=!1;const e=this._changedProperties;try{t=this.shouldUpdate(e),t?this.update(e):this._markUpdated()}catch(i){throw t=!1,this._markUpdated(),i}t&&(1&this._updateState||(this._updateState=1|this._updateState,this.firstUpdated(e)),this.updated(e))}_markUpdated(){this._changedProperties=new Map,this._updateState=-5&this._updateState}get updateComplete(){return this._getUpdateComplete()}_getUpdateComplete(){return this._updatePromise}shouldUpdate(t){return!0}update(t){void 0!==this._reflectingProperties&&this._reflectingProperties.size>0&&(this._reflectingProperties.forEach(((t,e)=>this._propertyToAttribute(e,this[e],t))),this._reflectingProperties=void 0),this._markUpdated()}updated(t){}firstUpdated(t){}}G.finalized=!0;const Z=(t,e)=>"method"===e.kind&&e.descriptor&&!("value"in e.descriptor)?Object.assign(Object.assign({},e),{finisher(i){i.createProperty(e.key,t)}}):{kind:"field",key:Symbol(),placement:"own",descriptor:{},initializer(){"function"==typeof e.initializer&&(this[e.key]=e.initializer.call(this))},finisher(i){i.createProperty(e.key,t)}};function J(t){return(e,i)=>void 0!==i?((t,e,i)=>{e.constructor.createProperty(i,t)})(t,e,i):Z(t,e)}const X=(t,e,i)=>{Object.defineProperty(e,i,t)},K=(t,e)=>({kind:"method",placement:"prototype",key:e.key,descriptor:t});const Q=window.ShadowRoot&&(void 0===window.ShadyCSS||window.ShadyCSS.nativeShadow)&&"adoptedStyleSheets"in Document.prototype&&"replace"in CSSStyleSheet.prototype,tt=Symbol();class et{constructor(t,e){if(e!==tt)throw new Error("CSSResult is not constructable. Use `unsafeCSS` or `css` instead.");this.cssText=t}get styleSheet(){return void 0===this._styleSheet&&(Q?(this._styleSheet=new CSSStyleSheet,this._styleSheet.replaceSync(this.cssText)):this._styleSheet=null),this._styleSheet}toString(){return this.cssText}}(window.litElementVersions||(window.litElementVersions=[])).push("2.4.0");const it={};class nt extends G{static getStyles(){return this.styles}static _getUniqueStyles(){if(this.hasOwnProperty(JSCompiler_renameProperty("_styles",this)))return;const t=this.getStyles();if(Array.isArray(t)){const e=(t,i)=>t.reduceRight(((t,i)=>Array.isArray(i)?e(i,t):(t.add(i),t)),i),i=e(t,new Set),n=[];i.forEach((t=>n.unshift(t))),this._styles=n}else this._styles=void 0===t?[]:[t];this._styles=this._styles.map((t=>{if(t instanceof CSSStyleSheet&&!Q){const e=Array.prototype.slice.call(t.cssRules).reduce(((t,e)=>t+e.cssText),"");return new et(String(e),tt)}return t}))}initialize(){super.initialize(),this.constructor._getUniqueStyles(),this.renderRoot=this.createRenderRoot(),window.ShadowRoot&&this.renderRoot instanceof window.ShadowRoot&&this.adoptStyles()}createRenderRoot(){return this.attachShadow({mode:"open"})}adoptStyles(){const t=this.constructor._styles;0!==t.length&&(void 0===window.ShadyCSS||window.ShadyCSS.nativeShadow?Q?this.renderRoot.adoptedStyleSheets=t.map((t=>t instanceof CSSStyleSheet?t:t.styleSheet)):this._needsShimAdoptedStyleSheets=!0:window.ShadyCSS.ScopingShim.prepareAdoptedCssText(t.map((t=>t.cssText)),this.localName))}connectedCallback(){super.connectedCallback(),this.hasUpdated&&void 0!==window.ShadyCSS&&window.ShadyCSS.styleElement(this)}update(t){const e=this.render();super.update(t),e!==it&&this.constructor.render(e,this.renderRoot,{scopeName:this.localName,eventContext:this}),this._needsShimAdoptedStyleSheets&&(this._needsShimAdoptedStyleSheets=!1,this.constructor._styles.forEach((t=>{const e=document.createElement("style");e.textContent=t.cssText,this.renderRoot.appendChild(e)})))}render(){return it}}nt.finalized=!0,nt.render=(t,e,n)=>{if(!n||"object"!=typeof n||!n.scopeName)throw new Error("The `scopeName` option is required.");const r=n.scopeName,s=R.has(e),a=F&&11===e.nodeType&&!!e.host,o=a&&!U.has(r),l=o?document.createDocumentFragment():e;if(((t,e,n)=>{let r=R.get(e);void 0===r&&(i(e,e.firstChild),R.set(e,r=new M(Object.assign({templateFactory:$},n))),r.appendInto(e)),r.setValue(t),r.commit()})(t,l,Object.assign({templateFactory:I(r)},n)),o){const t=R.get(l);R.delete(l);const n=t.value instanceof y?t.value.template:void 0;q(r,l,n),i(e,e.firstChild),e.appendChild(l),R.set(e,t)}!s&&a&&window.ShadyCSS.styleElement(e.host)};class rt{constructor(t){this.startPress=e=>{t().then((t=>{t&&t.startPress(e)}))},this.endPress=()=>{t().then((t=>{t&&t.endPress()}))},this.startFocus=()=>{t().then((t=>{t&&t.startFocus()}))},this.endFocus=()=>{t().then((t=>{t&&t.endFocus()}))},this.startHover=()=>{t().then((t=>{t&&t.startHover()}))},this.endHover=()=>{t().then((t=>{t&&t.endHover()}))}}}const st=new WeakMap,at=g((t=>e=>{if(!(e instanceof T)||e instanceof A||"style"!==e.committer.name||e.committer.parts.length>1)throw new Error("The `styleMap` directive must be used in the style attribute and must be the only part in the attribute.");const{committer:i}=e,{style:n}=i.element;let r=st.get(e);void 0===r&&(n.cssText=i.strings.join(" "),st.set(e,r=new Set)),r.forEach((e=>{e in t||(r.delete(e),-1===e.indexOf("-")?n[e]=null:n.removeProperty(e))}));for(const s in t)r.add(s),-1===s.indexOf("-")?n[s]=t[s]:n.setProperty(s,t[s])})),ot=new WeakMap,lt=g((t=>e=>{if(!(e instanceof M))throw new Error("unsafeHTML can only be used in text bindings");const i=ot.get(e);if(void 0!==i&&x(t)&&t===i.value&&e.value===i.fragment)return;const n=document.createElement("template");n.innerHTML=t;const r=document.importNode(n.content,!0);e.setValue(r),ot.set(e,{value:t,fragment:r})}));class ct{constructor(t){this.classes=new Set,this.changed=!1,this.element=t;const e=(t.getAttribute("class")||"").split(/\s+/);for(const i of e)this.classes.add(i)}add(t){this.classes.add(t),this.changed=!0}remove(t){this.classes.delete(t),this.changed=!0}commit(){if(this.changed){let t="";this.classes.forEach((e=>t+=e+" ")),this.element.setAttribute("class",t)}}}const ht=new WeakMap,ut=g((t=>e=>{if(!(e instanceof T)||e instanceof A||"class"!==e.committer.name||e.committer.parts.length>1)throw new Error("The `classMap` directive must be used in the `class` attribute and must be the only part in the attribute.");const{committer:i}=e,{element:n}=i;let r=ht.get(e);void 0===r&&(n.setAttribute("class",i.strings.join(" ")),ht.set(e,r=new Set));const s=n.classList||new ct(n);r.forEach((e=>{e in t||(s.remove(e),r.delete(e))}));for(const a in t){const e=t[a];e!=r.has(a)&&(e?(s.add(a),r.add(a)):(s.remove(a),r.delete(a)))}"function"==typeof s.commit&&s.commit()}));var dt=/d{1,4}|M{1,4}|YY(?:YY)?|S{1,3}|Do|ZZ|Z|([HhMsDm])\1?|[aA]|"[^"]*"|'[^']*'/g,pt="[^\\s]+",ft=/\[([^]*?)\]/gm;function gt(t,e){for(var i=[],n=0,r=t.length;n-1?n:null}};function _t(t){for(var e=[],i=1;i3?0:(t-t%10!=10?1:0)*t%10]}},St=_t({},wt),xt=function(t,e){for(void 0===e&&(e=2),t=String(t);t.length0?"-":"+")+xt(100*Math.floor(Math.abs(e)/60)+Math.abs(e)%60,4)},Z:function(t){var e=t.getTimezoneOffset();return(e>0?"-":"+")+xt(Math.floor(Math.abs(e)/60),2)+":"+xt(Math.abs(e)%60,2)}},Ot=function(t){return+t-1},Tt=[null,"[1-9]\\d?"],Mt=[null,pt],Ct=["isPm",pt,function(t,e){var i=t.toLowerCase();return i===e.amPm[0]?0:i===e.amPm[1]?1:null}],Et=["timezoneOffset","[^\\s]*?[\\+\\-]\\d\\d:?\\d\\d|[^\\s]*?Z?",function(t){var e=(t+"").match(/([+-]|\d\d)/gi);if(e){var i=60*+e[1]+parseInt(e[2],10);return"+"===e[0]?i:-i}return 0}],At=(mt("monthNamesShort"),mt("monthNames"),{default:"ddd MMM DD YYYY HH:mm:ss",shortDate:"M/D/YY",mediumDate:"MMM D, YYYY",longDate:"MMMM D, YYYY",fullDate:"dddd, MMMM D, YYYY",isoDate:"YYYY-MM-DD",isoDateTime:"YYYY-MM-DDTHH:mm:ssZ",shortTime:"HH:mm",mediumTime:"HH:mm:ss",longTime:"HH:mm:ss.SSS"});var Nt=function(t,e,i){if(void 0===e&&(e=At.default),void 0===i&&(i={}),"number"==typeof t&&(t=new Date(t)),"[object Date]"!==Object.prototype.toString.call(t)||isNaN(t.getTime()))throw new Error("Invalid Date pass to format");var n=[];e=(e=At[e]||e).replace(ft,(function(t,e){return n.push(e),"@@@"}));var r=_t(_t({},St),i);return(e=e.replace(dt,(function(e){return kt[e](t,r)}))).replace(/@@@/g,(function(){return n.shift()}))};function Pt(t){var e=t.split(":").map(Number);return 3600*e[0]+60*e[1]+e[2]}var jt=function(){try{(new Date).toLocaleDateString("i")}catch(t){return"RangeError"===t.name}return!1}()?function(t,e){return t.toLocaleDateString(e,{year:"numeric",month:"long",day:"numeric"})}:function(t){return Nt(t,"mediumDate")},$t=function(){try{(new Date).toLocaleString("i")}catch(t){return"RangeError"===t.name}return!1}()?function(t,e){return t.toLocaleString(e,{year:"numeric",month:"long",day:"numeric",hour:"numeric",minute:"2-digit"})}:function(t){return Nt(t,"haDateTime")},Ht=function(){try{(new Date).toLocaleTimeString("i")}catch(t){return"RangeError"===t.name}return!1}()?function(t,e){return t.toLocaleTimeString(e,{hour:"numeric",minute:"2-digit"})}:function(t){return Nt(t,"shortTime")},Rt=function(t){return t<10?"0"+t:t};function Vt(t){return t.substr(0,t.indexOf("."))}var Dt="hass:bookmark",Lt=["closed","locked","off"],Ft=new Set(["fan","input_boolean","light","switch","group","automation"]),It=function(t,e,i,n){n=n||{},i=null==i?{}:i;var r=new Event(e,{bubbles:void 0===n.bubbles||n.bubbles,cancelable:Boolean(n.cancelable),composed:void 0===n.composed||n.composed});return r.detail=i,t.dispatchEvent(r),r},zt=new Set(["call-service","divider","section","weblink","cast","select"]),Ut={alert:"toggle",automation:"toggle",climate:"climate",cover:"cover",fan:"toggle",group:"group",input_boolean:"toggle",input_number:"input-number",input_select:"input-select",input_text:"input-text",light:"toggle",lock:"lock",media_player:"media-player",remote:"toggle",scene:"scene",script:"script",sensor:"sensor",timer:"timer",switch:"toggle",vacuum:"toggle",water_heater:"climate",input_datetime:"input-datetime"},qt={alert:"hass:alert",automation:"hass:playlist-play",calendar:"hass:calendar",camera:"hass:video",climate:"hass:thermostat",configurator:"hass:settings",conversation:"hass:text-to-speech",device_tracker:"hass:account",fan:"hass:fan",group:"hass:google-circles-communities",history_graph:"hass:chart-line",homeassistant:"hass:home-assistant",homekit:"hass:home-automation",image_processing:"hass:image-filter-frames",input_boolean:"hass:drawing",input_datetime:"hass:calendar-clock",input_number:"hass:ray-vertex",input_select:"hass:format-list-bulleted",input_text:"hass:textbox",light:"hass:lightbulb",mailbox:"hass:mailbox",notify:"hass:comment-alert",person:"hass:account",plant:"hass:flower",proximity:"hass:apple-safari",remote:"hass:remote",scene:"hass:google-pages",script:"hass:file-document",sensor:"hass:eye",simple_alarm:"hass:bell",sun:"hass:white-balance-sunny",switch:"hass:flash",timer:"hass:timer",updater:"hass:cloud-upload",vacuum:"hass:robot-vacuum",water_heater:"hass:thermometer",weblink:"hass:open-in-new"};function Yt(t,e){if(t in qt)return qt[t];switch(t){case"alarm_control_panel":switch(e){case"armed_home":return"hass:bell-plus";case"armed_night":return"hass:bell-sleep";case"disarmed":return"hass:bell-outline";case"triggered":return"hass:bell-ring";default:return"hass:bell"}case"binary_sensor":return e&&"off"===e?"hass:radiobox-blank":"hass:checkbox-marked-circle";case"cover":return"closed"===e?"hass:window-closed":"hass:window-open";case"lock":return e&&"unlocked"===e?"hass:lock-open":"hass:lock";case"media_player":return e&&"off"!==e&&"idle"!==e?"hass:cast-connected":"hass:cast";case"zwave":switch(e){case"dead":return"hass:emoticon-dead";case"sleeping":return"hass:sleep";case"initializing":return"hass:timer-sand";default:return"hass:z-wave"}default:return console.warn("Unable to find icon for domain "+t+" ("+e+")"),Dt}}var Bt=function(t){It(window,"haptic",t)},Wt=function(t,e){return function(t,e,i){void 0===i&&(i=!0);var n,r=Vt(e),s="group"===r?"homeassistant":r;switch(r){case"lock":n=i?"unlock":"lock";break;case"cover":n=i?"open_cover":"close_cover";break;default:n=i?"turn_on":"turn_off"}return t.callService(s,n,{entity_id:e})}(t,e,Lt.includes(t.states[e].state))},Gt=function(t,e,i,n,r){var s;if(r&&i.double_tap_action?s=i.double_tap_action:n&&i.hold_action?s=i.hold_action:!n&&i.tap_action&&(s=i.tap_action),s||(s={action:"more-info"}),!s.confirmation||s.confirmation.exemptions&&s.confirmation.exemptions.some((function(t){return t.user===e.user.id}))||confirm(s.confirmation.text||"Are you sure you want to "+s.action+"?"))switch(s.action){case"more-info":(s.entity||i.entity||i.camera_image)&&(It(t,"hass-more-info",{entityId:s.entity?s.entity:i.entity?i.entity:i.camera_image}),s.haptic&&Bt(s.haptic));break;case"navigate":s.navigation_path&&(function(t,e,i){void 0===i&&(i=!1),i?history.replaceState(null,"",e):history.pushState(null,"",e),It(window,"location-changed",{replace:i})}(0,s.navigation_path),s.haptic&&Bt(s.haptic));break;case"url":s.url_path&&window.open(s.url_path),s.haptic&&Bt(s.haptic);break;case"toggle":i.entity&&(Wt(e,i.entity),s.haptic&&Bt(s.haptic));break;case"call-service":if(!s.service)return;var a=s.service.split(".",2),o=a[0],l=a[1],c=Object.assign({},s.service_data);"entity"===c.entity_id&&(c.entity_id=i.entity),e.callService(o,l,c),s.haptic&&Bt(s.haptic)}},Zt={humidity:"hass:water-percent",illuminance:"hass:brightness-5",temperature:"hass:thermometer",pressure:"hass:gauge",power:"hass:flash",signal_strength:"hass:wifi"},Jt={binary_sensor:function(t){var e=t.state&&"off"===t.state;switch(t.attributes.device_class){case"battery":return e?"hass:battery":"hass:battery-outline";case"cold":return e?"hass:thermometer":"hass:snowflake";case"connectivity":return e?"hass:server-network-off":"hass:server-network";case"door":return e?"hass:door-closed":"hass:door-open";case"garage_door":return e?"hass:garage":"hass:garage-open";case"gas":case"power":case"problem":case"safety":case"smoke":return e?"hass:shield-check":"hass:alert";case"heat":return e?"hass:thermometer":"hass:fire";case"light":return e?"hass:brightness-5":"hass:brightness-7";case"lock":return e?"hass:lock":"hass:lock-open";case"moisture":return e?"hass:water-off":"hass:water";case"motion":return e?"hass:walk":"hass:run";case"occupancy":return e?"hass:home-outline":"hass:home";case"opening":return e?"hass:square":"hass:square-outline";case"plug":return e?"hass:power-plug-off":"hass:power-plug";case"presence":return e?"hass:home-outline":"hass:home";case"sound":return e?"hass:music-note-off":"hass:music-note";case"vibration":return e?"hass:crop-portrait":"hass:vibrate";case"window":return e?"hass:window-closed":"hass:window-open";default:return e?"hass:radiobox-blank":"hass:checkbox-marked-circle"}},cover:function(t){var e="closed"!==t.state;switch(t.attributes.device_class){case"garage":return e?"hass:garage-open":"hass:garage";case"door":return e?"hass:door-open":"hass:door-closed";case"shutter":return e?"hass:window-shutter-open":"hass:window-shutter";case"blind":return e?"hass:blinds-open":"hass:blinds";case"window":return e?"hass:window-open":"hass:window-closed";default:return Yt("cover",t.state)}},sensor:function(t){var e=t.attributes.device_class;if(e&&e in Zt)return Zt[e];if("battery"===e){var i=Number(t.state);if(isNaN(i))return"hass:battery-unknown";var n=10*Math.round(i/10);return n>=100?"hass:battery":n<=0?"hass:battery-alert":"hass:battery-"+n}var r=t.attributes.unit_of_measurement;return"°C"===r||"°F"===r?"hass:thermometer":Yt("sensor")},input_datetime:function(t){return t.attributes.has_date?t.attributes.has_time?Yt("input_datetime"):"hass:calendar":"hass:clock"}};const Xt=(t,e,i,n)=>{n=n||{},i=null==i?{}:i;const r=new Event(e,{bubbles:void 0===n.bubbles||n.bubbles,cancelable:Boolean(n.cancelable),composed:void 0===n.composed||n.composed});return r.detail=i,t.dispatchEvent(r),r},Kt=(t,e)=>{if(t===e)return!0;if(t&&e&&"object"==typeof t&&"object"==typeof e){if(t.constructor!==e.constructor)return!1;let i,n;if(Array.isArray(t)){if(n=t.length,n!==e.length)return!1;for(i=n;0!=i--;)if(!Kt(t[i],e[i]))return!1;return!0}if(t instanceof Map&&e instanceof Map){if(t.size!==e.size)return!1;for(i of t.entries())if(!e.has(i[0]))return!1;for(i of t.entries())if(!Kt(i[1],e.get(i[0])))return!1;return!0}if(t instanceof Set&&e instanceof Set){if(t.size!==e.size)return!1;for(i of t.entries())if(!e.has(i[0]))return!1;return!0}if(ArrayBuffer.isView(t)&&ArrayBuffer.isView(e)){if(n=t.length,n!==e.length)return!1;for(i=n;0!=i--;)if(t[i]!==e[i])return!1;return!0}if(t.constructor===RegExp)return t.source===e.source&&t.flags===e.flags;if(t.valueOf!==Object.prototype.valueOf)return t.valueOf()===e.valueOf();if(t.toString!==Object.prototype.toString)return t.toString()===e.toString();const r=Object.keys(t);if(n=r.length,n!==Object.keys(e).length)return!1;for(i=n;0!=i--;)if(!Object.prototype.hasOwnProperty.call(e,r[i]))return!1;for(i=n;0!=i--;){const n=r[i];if(!Kt(t[n],e[n]))return!1}return!0}return t!=t&&e!=e},Qt="ontouchstart"in window||navigator.maxTouchPoints>0||navigator.msMaxTouchPoints>0;class te extends HTMLElement{constructor(){super(),this.holdTime=500,this.held=!1,this.cancelled=!1,this.isRepeating=!1,this.ripple=document.createElement("mwc-ripple")}connectedCallback(){Object.assign(this.style,{position:"absolute",width:Qt?"100px":"50px",height:Qt?"100px":"50px",transform:"translate(-50%, -50%)",pointerEvents:"none",zIndex:"999"}),this.appendChild(this.ripple),this.ripple.primary=!0,["touchcancel","mouseout","mouseup","touchmove","mousewheel","wheel","scroll"].forEach((t=>{document.addEventListener(t,(()=>{this.cancelled=!0,this.timer&&(this.stopAnimation(),clearTimeout(this.timer),this.timer=void 0,this.isRepeating&&this.repeatTimeout&&(clearInterval(this.repeatTimeout),this.isRepeating=!1))}),{passive:!0})}))}bind(t,e){t.actionHandler&&Kt(e,t.actionHandler.options)||(t.actionHandler?(t.removeEventListener("touchstart",t.actionHandler.start),t.removeEventListener("touchend",t.actionHandler.end),t.removeEventListener("touchcancel",t.actionHandler.end),t.removeEventListener("mousedown",t.actionHandler.start),t.removeEventListener("click",t.actionHandler.end),t.removeEventListener("keyup",t.actionHandler.handleEnter)):t.addEventListener("contextmenu",(t=>{const e=t||window.event;return e.preventDefault&&e.preventDefault(),e.stopPropagation&&e.stopPropagation(),e.cancelBubble=!0,e.returnValue=!1,!1})),t.actionHandler={options:e},e.disabled||(t.actionHandler.start=i=>{let n,r;this.cancelled=!1,i.touches?(n=i.touches[0].pageX,r=i.touches[0].pageY):(n=i.pageX,r=i.pageY),e.hasHold&&(this.held=!1,this.timer=window.setTimeout((()=>{this.startAnimation(n,r),this.held=!0,e.repeat&&!this.isRepeating&&(this.isRepeating=!0,this.repeatTimeout=setInterval((()=>{Xt(t,"action",{action:"hold"})}),e.repeat))}),this.holdTime))},t.actionHandler.end=t=>{if(["touchend","touchcancel"].includes(t.type)&&this.cancelled)return void(this.isRepeating&&this.repeatTimeout&&(clearInterval(this.repeatTimeout),this.isRepeating=!1));const i=t.target;t.cancelable&&t.preventDefault(),e.hasHold&&(clearTimeout(this.timer),this.isRepeating&&this.repeatTimeout&&clearInterval(this.repeatTimeout),this.isRepeating=!1,this.stopAnimation(),this.timer=void 0),e.hasHold&&this.held?e.repeat||Xt(i,"action",{action:"hold"}):e.hasDoubleClick?"click"===t.type&&t.detail<2||!this.dblClickTimeout?this.dblClickTimeout=window.setTimeout((()=>{this.dblClickTimeout=void 0,Xt(i,"action",{action:"tap"})}),250):(clearTimeout(this.dblClickTimeout),this.dblClickTimeout=void 0,Xt(i,"action",{action:"double_tap"})):Xt(i,"action",{action:"tap"})},t.actionHandler.handleEnter=t=>{13===t.keyCode&&t.currentTarget.actionHandler.end(t)},t.addEventListener("touchstart",t.actionHandler.start,{passive:!0}),t.addEventListener("touchend",t.actionHandler.end),t.addEventListener("touchcancel",t.actionHandler.end),t.addEventListener("mousedown",t.actionHandler.start,{passive:!0}),t.addEventListener("click",t.actionHandler.end),t.addEventListener("keyup",t.actionHandler.handleEnter)))}startAnimation(t,e){Object.assign(this.style,{left:t+"px",top:e+"px",display:null}),this.ripple.disabled=!1,this.ripple.startPress(),this.ripple.unbounded=!0}stopAnimation(){this.ripple.endPress(),this.ripple.disabled=!0,this.style.display="none"}}customElements.define("button-card-action-handler",te);const ee=(t,e)=>{const i=(()=>{const t=document.body;if(t.querySelector("button-card-action-handler"))return t.querySelector("button-card-action-handler");const e=document.createElement("button-card-action-handler");return t.appendChild(e),e})();i&&i.bind(t,e)},ie=g(((t={})=>e=>{ee(e.committer.element,t)}));function ne(t,e){(function(t){return"string"==typeof t&&t.includes(".")&&1===parseFloat(t)})(t)&&(t="100%");var i=function(t){return"string"==typeof t&&t.includes("%")}(t);return t=360===e?t:Math.min(e,Math.max(0,parseFloat(t))),i&&(t=parseInt(String(t*e),10)/100),Math.abs(t-e)<1e-6?1:t=360===e?(t<0?t%e+e:t%e)/parseFloat(String(e)):t%e/parseFloat(String(e))}function re(t){return Math.min(1,Math.max(0,t))}function se(t){return t=parseFloat(t),(isNaN(t)||t<0||t>1)&&(t=1),t}function ae(t){return t<=1?100*Number(t)+"%":t}function oe(t){return 1===t.length?"0"+t:String(t)}function le(t,e,i){t=ne(t,255),e=ne(e,255),i=ne(i,255);var n=Math.max(t,e,i),r=Math.min(t,e,i),s=0,a=0,o=(n+r)/2;if(n===r)a=0,s=0;else{var l=n-r;switch(a=o>.5?l/(2-n-r):l/(n+r),n){case t:s=(e-i)/l+(e1&&(i-=1),i<1/6?t+6*i*(e-t):i<.5?e:i<2/3?t+(e-t)*(2/3-i)*6:t}function he(t,e,i){t=ne(t,255),e=ne(e,255),i=ne(i,255);var n=Math.max(t,e,i),r=Math.min(t,e,i),s=0,a=n,o=n-r,l=0===n?0:o/n;if(n===r)s=0;else{switch(n){case t:s=(e-i)/o+(e>16,g:(65280&t)>>8,b:255&t}}(e)),this.originalInput=e;var r=me(e);this.originalInput=e,this.r=r.r,this.g=r.g,this.b=r.b,this.a=r.a,this.roundA=Math.round(100*this.a)/100,this.format=null!==(n=i.format)&&void 0!==n?n:r.format,this.gradientType=i.gradientType,this.r<1&&(this.r=Math.round(this.r)),this.g<1&&(this.g=Math.round(this.g)),this.b<1&&(this.b=Math.round(this.b)),this.isValid=r.ok}return t.prototype.isDark=function(){return this.getBrightness()<128},t.prototype.isLight=function(){return!this.isDark()},t.prototype.getBrightness=function(){var t=this.toRgb();return(299*t.r+587*t.g+114*t.b)/1e3},t.prototype.getLuminance=function(){var t=this.toRgb(),e=t.r/255,i=t.g/255,n=t.b/255;return.2126*(e<=.03928?e/12.92:Math.pow((e+.055)/1.055,2.4))+.7152*(i<=.03928?i/12.92:Math.pow((i+.055)/1.055,2.4))+.0722*(n<=.03928?n/12.92:Math.pow((n+.055)/1.055,2.4))},t.prototype.getAlpha=function(){return this.a},t.prototype.setAlpha=function(t){return this.a=se(t),this.roundA=Math.round(100*this.a)/100,this},t.prototype.toHsv=function(){var t=he(this.r,this.g,this.b);return{h:360*t.h,s:t.s,v:t.v,a:this.a}},t.prototype.toHsvString=function(){var t=he(this.r,this.g,this.b),e=Math.round(360*t.h),i=Math.round(100*t.s),n=Math.round(100*t.v);return 1===this.a?"hsv("+e+", "+i+"%, "+n+"%)":"hsva("+e+", "+i+"%, "+n+"%, "+this.roundA+")"},t.prototype.toHsl=function(){var t=le(this.r,this.g,this.b);return{h:360*t.h,s:t.s,l:t.l,a:this.a}},t.prototype.toHslString=function(){var t=le(this.r,this.g,this.b),e=Math.round(360*t.h),i=Math.round(100*t.s),n=Math.round(100*t.l);return 1===this.a?"hsl("+e+", "+i+"%, "+n+"%)":"hsla("+e+", "+i+"%, "+n+"%, "+this.roundA+")"},t.prototype.toHex=function(t){return void 0===t&&(t=!1),ue(this.r,this.g,this.b,t)},t.prototype.toHexString=function(t){return void 0===t&&(t=!1),"#"+this.toHex(t)},t.prototype.toHex8=function(t){return void 0===t&&(t=!1),function(t,e,i,n,r){var s=[oe(Math.round(t).toString(16)),oe(Math.round(e).toString(16)),oe(Math.round(i).toString(16)),oe(de(n))];return r&&s[0].startsWith(s[0].charAt(1))&&s[1].startsWith(s[1].charAt(1))&&s[2].startsWith(s[2].charAt(1))&&s[3].startsWith(s[3].charAt(1))?s[0].charAt(0)+s[1].charAt(0)+s[2].charAt(0)+s[3].charAt(0):s.join("")}(this.r,this.g,this.b,this.a,t)},t.prototype.toHex8String=function(t){return void 0===t&&(t=!1),"#"+this.toHex8(t)},t.prototype.toRgb=function(){return{r:Math.round(this.r),g:Math.round(this.g),b:Math.round(this.b),a:this.a}},t.prototype.toRgbString=function(){var t=Math.round(this.r),e=Math.round(this.g),i=Math.round(this.b);return 1===this.a?"rgb("+t+", "+e+", "+i+")":"rgba("+t+", "+e+", "+i+", "+this.roundA+")"},t.prototype.toPercentageRgb=function(){var t=function(t){return Math.round(100*ne(t,255))+"%"};return{r:t(this.r),g:t(this.g),b:t(this.b),a:this.a}},t.prototype.toPercentageRgbString=function(){var t=function(t){return Math.round(100*ne(t,255))};return 1===this.a?"rgb("+t(this.r)+"%, "+t(this.g)+"%, "+t(this.b)+"%)":"rgba("+t(this.r)+"%, "+t(this.g)+"%, "+t(this.b)+"%, "+this.roundA+")"},t.prototype.toName=function(){if(0===this.a)return"transparent";if(this.a<1)return!1;for(var t="#"+ue(this.r,this.g,this.b,!1),e=0,i=Object.entries(ge);e=0;return e||!n||!t.startsWith("hex")&&"name"!==t?("rgb"===t&&(i=this.toRgbString()),"prgb"===t&&(i=this.toPercentageRgbString()),"hex"!==t&&"hex6"!==t||(i=this.toHexString()),"hex3"===t&&(i=this.toHexString(!0)),"hex4"===t&&(i=this.toHex8String(!0)),"hex8"===t&&(i=this.toHex8String()),"name"===t&&(i=this.toName()),"hsl"===t&&(i=this.toHslString()),"hsv"===t&&(i=this.toHsvString()),i||this.toHexString()):"name"===t&&0===this.a?this.toName():this.toRgbString()},t.prototype.toNumber=function(){return(Math.round(this.r)<<16)+(Math.round(this.g)<<8)+Math.round(this.b)},t.prototype.clone=function(){return new t(this.toString())},t.prototype.lighten=function(e){void 0===e&&(e=10);var i=this.toHsl();return i.l+=e/100,i.l=re(i.l),new t(i)},t.prototype.brighten=function(e){void 0===e&&(e=10);var i=this.toRgb();return i.r=Math.max(0,Math.min(255,i.r-Math.round(-e/100*255))),i.g=Math.max(0,Math.min(255,i.g-Math.round(-e/100*255))),i.b=Math.max(0,Math.min(255,i.b-Math.round(-e/100*255))),new t(i)},t.prototype.darken=function(e){void 0===e&&(e=10);var i=this.toHsl();return i.l-=e/100,i.l=re(i.l),new t(i)},t.prototype.tint=function(t){return void 0===t&&(t=10),this.mix("white",t)},t.prototype.shade=function(t){return void 0===t&&(t=10),this.mix("black",t)},t.prototype.desaturate=function(e){void 0===e&&(e=10);var i=this.toHsl();return i.s-=e/100,i.s=re(i.s),new t(i)},t.prototype.saturate=function(e){void 0===e&&(e=10);var i=this.toHsl();return i.s+=e/100,i.s=re(i.s),new t(i)},t.prototype.greyscale=function(){return this.desaturate(100)},t.prototype.spin=function(e){var i=this.toHsl(),n=(i.h+e)%360;return i.h=n<0?360+n:n,new t(i)},t.prototype.mix=function(e,i){void 0===i&&(i=50);var n=this.toRgb(),r=new t(e).toRgb(),s=i/100;return new t({r:(r.r-n.r)*s+n.r,g:(r.g-n.g)*s+n.g,b:(r.b-n.b)*s+n.b,a:(r.a-n.a)*s+n.a})},t.prototype.analogous=function(e,i){void 0===e&&(e=6),void 0===i&&(i=30);var n=this.toHsl(),r=360/i,s=[this];for(n.h=(n.h-(r*e>>1)+720)%360;--e;)n.h=(n.h+r)%360,s.push(new t(n));return s},t.prototype.complement=function(){var e=this.toHsl();return e.h=(e.h+180)%360,new t(e)},t.prototype.monochromatic=function(e){void 0===e&&(e=6);for(var i=this.toHsv(),n=i.h,r=i.s,s=i.v,a=[],o=1/e;e--;)a.push(new t({h:n,s:r,v:s})),s=(s+o)%1;return a},t.prototype.splitcomplement=function(){var e=this.toHsl(),i=e.h;return[this,new t({h:(i+72)%360,s:e.s,l:e.l}),new t({h:(i+216)%360,s:e.s,l:e.l})]},t.prototype.triad=function(){return this.polyad(3)},t.prototype.tetrad=function(){return this.polyad(4)},t.prototype.polyad=function(e){for(var i=this.toHsl(),n=i.h,r=[this],s=360/e,a=1;at&&"object"==typeof t;return t.reduce(((t,i)=>(Object.keys(i).forEach((n=>{const r=t[n],s=i[n];Array.isArray(r)&&Array.isArray(s)?t[n]=r.concat(...s):e(r)&&e(s)?t[n]=Me(r,s):t[n]=s})),t)),{})}function Ce(t,e){let i=[];return t&&t.forEach((t=>{let n=t;e&&e.forEach((e=>{e.id&&t.id&&e.id==t.id&&(n=Me(n,e))})),i.push(n)})),e&&(i=i.concat(e.filter((e=>!t||!t.find((t=>!(!t.id||!e.id)&&t.id==e.id)))))),i}const Ee=((t,...e)=>{const i=e.reduce(((e,i,n)=>e+(t=>{if(t instanceof et)return t.cssText;if("number"==typeof t)return t;throw new Error(`Value passed to 'css' function must be a 'css' function result: ${t}. Use 'unsafeCSS' to pass non-literal values, but\n take care to ensure page security.`)})(i)+t[n+1]),t[0]);return new et(i,tt)})` + :host { + position: relative; + display: block; } -}, - a = {}, - o = {}, - l = `{{lit-${String(Math.random()).slice(2)}}}`, - c = `\x3c!--${l}--\x3e`, - h = new RegExp(`${l}|${c}`);class u { - constructor(t, e) { - this.parts = [], this.element = e;const n = [], - i = [], - s = document.createTreeWalker(e.content, 133, null, !1);let r = 0, - a = -1, - o = 0;const { strings: c, values: { length: u } } = t;for (; o < u;) { - const t = s.nextNode();if (null !== t) { - if (a++, 1 === t.nodeType) { - if (t.hasAttributes()) { - const e = t.attributes, - { length: n } = e;let i = 0;for (let t = 0; t < n; t++) d(e[t].name, "$lit$") && i++;for (; i-- > 0;) { - const e = c[o], - n = m.exec(e)[2], - i = n.toLowerCase() + "$lit$", - s = t.getAttribute(i);t.removeAttribute(i);const r = s.split(h);this.parts.push({ type: "attribute", index: a, name: n, strings: r }), o += r.length - 1; - } - }"TEMPLATE" === t.tagName && (i.push(t), s.currentNode = t.content); - } else if (3 === t.nodeType) { - const e = t.data;if (e.indexOf(l) >= 0) { - const i = t.parentNode, - s = e.split(h), - r = s.length - 1;for (let e = 0; e < r; e++) { - let n, - r = s[e];if ("" === r) n = p();else { - const t = m.exec(r);null !== t && d(t[2], "$lit$") && (r = r.slice(0, t.index) + t[1] + t[2].slice(0, -"$lit$".length) + t[3]), n = document.createTextNode(r); - }i.insertBefore(n, t), this.parts.push({ type: "node", index: ++a }); - }"" === s[r] ? (i.insertBefore(p(), t), n.push(t)) : t.data = s[r], o += r; - } - } else if (8 === t.nodeType) if (t.data === l) { - const e = t.parentNode;null !== t.previousSibling && a !== r || (a++, e.insertBefore(p(), t)), r = a, this.parts.push({ type: "node", index: a }), null === t.nextSibling ? t.data = "" : (n.push(t), a--), o++; - } else { - let e = -1;for (; -1 !== (e = t.data.indexOf(l, e + 1));) this.parts.push({ type: "node", index: -1 }), o++; - } - } else s.currentNode = i.pop(); - }for (const l of n) l.parentNode.removeChild(l); - } -}const d = (t, e) => { - const n = t.length - e.length;return n >= 0 && t.slice(n) === e; -}, - f = t => -1 !== t.index, - p = () => document.createComment(""), - m = /([ \x09\x0a\x0c\x0d])([^\0-\x1F\x7F-\x9F "'>=/]+)([ \x09\x0a\x0c\x0d]*=[ \x09\x0a\x0c\x0d]*(?:[^ \x09\x0a\x0c\x0d"'`<>=]*|"[^"]*|'[^']*))$/; -/** - * @license - * Copyright (c) 2017 The Polymer Project Authors. All rights reserved. - * This code may only be used under the BSD style license found at - * http://polymer.github.io/LICENSE.txt - * The complete set of authors may be found at - * http://polymer.github.io/AUTHORS.txt - * The complete set of contributors may be found at - * http://polymer.github.io/CONTRIBUTORS.txt - * Code distributed by Google as part of the polymer project is also - * subject to an additional IP rights grant found at - * http://polymer.github.io/PATENTS.txt - */ -class g { - constructor(t, e, n) { - this.__parts = [], this.template = t, this.processor = e, this.options = n; - }update(t) { - let e = 0;for (const n of this.__parts) void 0 !== n && n.setValue(t[e]), e++;for (const n of this.__parts) void 0 !== n && n.commit(); - }_clone() { - const t = s ? this.template.element.content.cloneNode(!0) : document.importNode(this.template.element.content, !0), - e = [], - n = this.template.parts, - i = document.createTreeWalker(t, 133, null, !1);let r, - a = 0, - o = 0, - l = i.nextNode();for (; a < n.length;) if (r = n[a], f(r)) { - for (; o < r.index;) o++, "TEMPLATE" === l.nodeName && (e.push(l), i.currentNode = l.content), null === (l = i.nextNode()) && (i.currentNode = e.pop(), l = i.nextNode());if ("node" === r.type) { - const t = this.processor.handleTextExpression(this.options);t.insertAfterNode(l.previousSibling), this.__parts.push(t); - } else this.__parts.push(...this.processor.handleAttributeExpressions(l, r.name, r.strings, this.options));a++; - } else this.__parts.push(void 0), a++;return s && (document.adoptNode(t), customElements.upgrade(t)), t; - } -} -/** - * @license - * Copyright (c) 2017 The Polymer Project Authors. All rights reserved. - * This code may only be used under the BSD style license found at - * http://polymer.github.io/LICENSE.txt - * The complete set of authors may be found at - * http://polymer.github.io/AUTHORS.txt - * The complete set of contributors may be found at - * http://polymer.github.io/CONTRIBUTORS.txt - * Code distributed by Google as part of the polymer project is also - * subject to an additional IP rights grant found at - * http://polymer.github.io/PATENTS.txt - */const b = ` ${l} `;class _ { - constructor(t, e, n, i) { - this.strings = t, this.values = e, this.type = n, this.processor = i; - }getHTML() { - const t = this.strings.length - 1;let e = "", - n = !1;for (let i = 0; i < t; i++) { - const t = this.strings[i], - s = t.lastIndexOf("\x3c!--");n = (s > -1 || n) && -1 === t.indexOf("--\x3e", s + 1);const r = m.exec(t);e += null === r ? t + (n ? b : c) : t.substr(0, r.index) + r[1] + r[2] + "$lit$" + r[3] + l; - }return e += this.strings[t], e; - }getTemplateElement() { - const t = document.createElement("template");return t.innerHTML = this.getHTML(), t; - } -} -/** - * @license - * Copyright (c) 2017 The Polymer Project Authors. All rights reserved. - * This code may only be used under the BSD style license found at - * http://polymer.github.io/LICENSE.txt - * The complete set of authors may be found at - * http://polymer.github.io/AUTHORS.txt - * The complete set of contributors may be found at - * http://polymer.github.io/CONTRIBUTORS.txt - * Code distributed by Google as part of the polymer project is also - * subject to an additional IP rights grant found at - * http://polymer.github.io/PATENTS.txt - */const y = t => null === t || !("object" == typeof t || "function" == typeof t), - v = t => Array.isArray(t) || !(!t || !t[Symbol.iterator]);class w { - constructor(t, e, n) { - this.dirty = !0, this.element = t, this.name = e, this.strings = n, this.parts = [];for (let i = 0; i < n.length - 1; i++) this.parts[i] = this._createPart(); - }_createPart() { - return new S(this); - }_getValue() { - const t = this.strings, - e = t.length - 1;let n = "";for (let i = 0; i < e; i++) { - n += t[i];const e = this.parts[i];if (void 0 !== e) { - const t = e.value;if (y(t) || !v(t)) n += "string" == typeof t ? t : String(t);else for (const e of t) n += "string" == typeof e ? e : String(e); - } - }return n += t[e], n; - }commit() { - this.dirty && (this.dirty = !1, this.element.setAttribute(this.name, this._getValue())); - } -}class S { - constructor(t) { - this.value = void 0, this.committer = t; - }setValue(t) { - t === a || y(t) && t === this.value || (this.value = t, i(t) || (this.committer.dirty = !0)); - }commit() { - for (; i(this.value);) { - const t = this.value;this.value = a, t(this); - }this.value !== a && this.committer.commit(); - } -}class x { - constructor(t) { - this.value = void 0, this.__pendingValue = void 0, this.options = t; - }appendInto(t) { - this.startNode = t.appendChild(p()), this.endNode = t.appendChild(p()); - }insertAfterNode(t) { - this.startNode = t, this.endNode = t.nextSibling; - }appendIntoPart(t) { - t.__insert(this.startNode = p()), t.__insert(this.endNode = p()); - }insertAfterPart(t) { - t.__insert(this.startNode = p()), this.endNode = t.endNode, t.endNode = this.startNode; - }setValue(t) { - this.__pendingValue = t; - }commit() { - for (; i(this.__pendingValue);) { - const t = this.__pendingValue;this.__pendingValue = a, t(this); - }const t = this.__pendingValue;t !== a && (y(t) ? t !== this.value && this.__commitText(t) : t instanceof _ ? this.__commitTemplateResult(t) : t instanceof Node ? this.__commitNode(t) : v(t) ? this.__commitIterable(t) : t === o ? (this.value = o, this.clear()) : this.__commitText(t)); - }__insert(t) { - this.endNode.parentNode.insertBefore(t, this.endNode); - }__commitNode(t) { - this.value !== t && (this.clear(), this.__insert(t), this.value = t); - }__commitText(t) { - const e = this.startNode.nextSibling, - n = "string" == typeof (t = null == t ? "" : t) ? t : String(t);e === this.endNode.previousSibling && 3 === e.nodeType ? e.data = n : this.__commitNode(document.createTextNode(n)), this.value = t; - }__commitTemplateResult(t) { - const e = this.options.templateFactory(t);if (this.value instanceof g && this.value.template === e) this.value.update(t.values);else { - const n = new g(e, t.processor, this.options), - i = n._clone();n.update(t.values), this.__commitNode(i), this.value = n; - } - }__commitIterable(t) { - Array.isArray(this.value) || (this.value = [], this.clear());const e = this.value;let n, - i = 0;for (const s of t) n = e[i], void 0 === n && (n = new x(this.options), e.push(n), 0 === i ? n.appendIntoPart(this) : n.insertAfterPart(e[i - 1])), n.setValue(s), n.commit(), i++;i < e.length && (e.length = i, this.clear(n && n.endNode)); - }clear(t = this.startNode) { - r(this.startNode.parentNode, t.nextSibling, this.endNode); - } -}class k { - constructor(t, e, n) { - if (this.value = void 0, this.__pendingValue = void 0, 2 !== n.length || "" !== n[0] || "" !== n[1]) throw new Error("Boolean attributes can only contain a single expression");this.element = t, this.name = e, this.strings = n; - }setValue(t) { - this.__pendingValue = t; - }commit() { - for (; i(this.__pendingValue);) { - const t = this.__pendingValue;this.__pendingValue = a, t(this); - }if (this.__pendingValue === a) return;const t = !!this.__pendingValue;this.value !== t && (t ? this.element.setAttribute(this.name, "") : this.element.removeAttribute(this.name), this.value = t), this.__pendingValue = a; - } -}class M extends w { - constructor(t, e, n) { - super(t, e, n), this.single = 2 === n.length && "" === n[0] && "" === n[1]; - }_createPart() { - return new T(this); - }_getValue() { - return this.single ? this.parts[0].value : super._getValue(); - }commit() { - this.dirty && (this.dirty = !1, this.element[this.name] = this._getValue()); - } -}class T extends S {}let C = !1;try { - const t = { get capture() { - return C = !0, !1; - } };window.addEventListener("test", t, t), window.removeEventListener("test", t, t); -} catch (oe) {}class N { - constructor(t, e, n) { - this.value = void 0, this.__pendingValue = void 0, this.element = t, this.eventName = e, this.eventContext = n, this.__boundHandleEvent = t => this.handleEvent(t); - }setValue(t) { - this.__pendingValue = t; - }commit() { - for (; i(this.__pendingValue);) { - const t = this.__pendingValue;this.__pendingValue = a, t(this); - }if (this.__pendingValue === a) return;const t = this.__pendingValue, - e = this.value, - n = null == t || null != e && (t.capture !== e.capture || t.once !== e.once || t.passive !== e.passive), - s = null != t && (null == e || n);n && this.element.removeEventListener(this.eventName, this.__boundHandleEvent, this.__options), s && (this.__options = E(t), this.element.addEventListener(this.eventName, this.__boundHandleEvent, this.__options)), this.value = t, this.__pendingValue = a; - }handleEvent(t) { - "function" == typeof this.value ? this.value.call(this.eventContext || this.element, t) : this.value.handleEvent(t); - } -}const E = t => t && (C ? { capture: t.capture, passive: t.passive, once: t.once } : t.capture) -/** - * @license - * Copyright (c) 2017 The Polymer Project Authors. All rights reserved. - * This code may only be used under the BSD style license found at - * http://polymer.github.io/LICENSE.txt - * The complete set of authors may be found at - * http://polymer.github.io/AUTHORS.txt - * The complete set of contributors may be found at - * http://polymer.github.io/CONTRIBUTORS.txt - * Code distributed by Google as part of the polymer project is also - * subject to an additional IP rights grant found at - * http://polymer.github.io/PATENTS.txt - */;const O = new class { - handleAttributeExpressions(t, e, n, i) { - const s = e[0];if ("." === s) { - return new M(t, e.slice(1), n).parts; - }return "@" === s ? [new N(t, e.slice(1), i.eventContext)] : "?" === s ? [new k(t, e.slice(1), n)] : new w(t, e, n).parts; - }handleTextExpression(t) { - return new x(t); - } -}(); -/** - * @license - * Copyright (c) 2017 The Polymer Project Authors. All rights reserved. - * This code may only be used under the BSD style license found at - * http://polymer.github.io/LICENSE.txt - * The complete set of authors may be found at - * http://polymer.github.io/AUTHORS.txt - * The complete set of contributors may be found at - * http://polymer.github.io/CONTRIBUTORS.txt - * Code distributed by Google as part of the polymer project is also - * subject to an additional IP rights grant found at - * http://polymer.github.io/PATENTS.txt - */function P(t) { - let e = A.get(t.type);void 0 === e && (e = { stringsArray: new WeakMap(), keyString: new Map() }, A.set(t.type, e));let n = e.stringsArray.get(t.strings);if (void 0 !== n) return n;const i = t.strings.join(l);return n = e.keyString.get(i), void 0 === n && (n = new u(t, t.getTemplateElement()), e.keyString.set(i, n)), e.stringsArray.set(t.strings, n), n; -}const A = new Map(), - $ = new WeakMap(); -/** - * @license - * Copyright (c) 2017 The Polymer Project Authors. All rights reserved. - * This code may only be used under the BSD style license found at - * http://polymer.github.io/LICENSE.txt - * The complete set of authors may be found at - * http://polymer.github.io/AUTHORS.txt - * The complete set of contributors may be found at - * http://polymer.github.io/CONTRIBUTORS.txt - * Code distributed by Google as part of the polymer project is also - * subject to an additional IP rights grant found at - * http://polymer.github.io/PATENTS.txt - */ -/** - * @license - * Copyright (c) 2017 The Polymer Project Authors. All rights reserved. - * This code may only be used under the BSD style license found at - * http://polymer.github.io/LICENSE.txt - * The complete set of authors may be found at - * http://polymer.github.io/AUTHORS.txt - * The complete set of contributors may be found at - * http://polymer.github.io/CONTRIBUTORS.txt - * Code distributed by Google as part of the polymer project is also - * subject to an additional IP rights grant found at - * http://polymer.github.io/PATENTS.txt - */ -(window.litHtmlVersions || (window.litHtmlVersions = [])).push("1.1.2");const j = (t, ...e) => new _(t, e, "html", O) -/** - * @license - * Copyright (c) 2017 The Polymer Project Authors. All rights reserved. - * This code may only be used under the BSD style license found at - * http://polymer.github.io/LICENSE.txt - * The complete set of authors may be found at - * http://polymer.github.io/AUTHORS.txt - * The complete set of contributors may be found at - * http://polymer.github.io/CONTRIBUTORS.txt - * Code distributed by Google as part of the polymer project is also - * subject to an additional IP rights grant found at - * http://polymer.github.io/PATENTS.txt - */;function V(t, e) { - const { element: { content: n }, parts: i } = t, - s = document.createTreeWalker(n, 133, null, !1);let r = H(i), - a = i[r], - o = -1, - l = 0;const c = [];let h = null;for (; s.nextNode();) { - o++;const t = s.currentNode;for (t.previousSibling === h && (h = null), e.has(t) && (c.push(t), null === h && (h = t)), null !== h && l++; void 0 !== a && a.index === o;) a.index = null !== h ? -1 : a.index - l, r = H(i, r), a = i[r]; - }c.forEach(t => t.parentNode.removeChild(t)); -}const R = t => { - let e = 11 === t.nodeType ? 0 : 1;const n = document.createTreeWalker(t, 133, null, !1);for (; n.nextNode();) e++;return e; -}, - H = (t, e = -1) => { - for (let n = e + 1; n < t.length; n++) { - const e = t[n];if (f(e)) return n; - }return -1; -}; -/** - * @license - * Copyright (c) 2017 The Polymer Project Authors. All rights reserved. - * This code may only be used under the BSD style license found at - * http://polymer.github.io/LICENSE.txt - * The complete set of authors may be found at - * http://polymer.github.io/AUTHORS.txt - * The complete set of contributors may be found at - * http://polymer.github.io/CONTRIBUTORS.txt - * Code distributed by Google as part of the polymer project is also - * subject to an additional IP rights grant found at - * http://polymer.github.io/PATENTS.txt - */ -const D = (t, e) => `${t}--${e}`;let F = !0;void 0 === window.ShadyCSS ? F = !1 : void 0 === window.ShadyCSS.prepareTemplateDom && (console.warn("Incompatible ShadyCSS version detected. Please update to at least @webcomponents/webcomponentsjs@2.0.2 and @webcomponents/shadycss@1.3.1."), F = !1);const I = t => e => { - const n = D(e.type, t);let i = A.get(n);void 0 === i && (i = { stringsArray: new WeakMap(), keyString: new Map() }, A.set(n, i));let s = i.stringsArray.get(e.strings);if (void 0 !== s) return s;const r = e.strings.join(l);if (s = i.keyString.get(r), void 0 === s) { - const n = e.getTemplateElement();F && window.ShadyCSS.prepareTemplateDom(n, t), s = new u(e, n), i.keyString.set(r, s); - }return i.stringsArray.set(e.strings, s), s; -}, - L = ["html", "svg"], - z = new Set(), - U = (t, e, n) => { - z.add(t);const i = n ? n.element : document.createElement("template"), - s = e.querySelectorAll("style"), - { length: r } = s;if (0 === r) return void window.ShadyCSS.prepareTemplateStyles(i, t);const a = document.createElement("style");for (let c = 0; c < r; c++) { - const t = s[c];t.parentNode.removeChild(t), a.textContent += t.textContent; - }(t => { - L.forEach(e => { - const n = A.get(D(e, t));void 0 !== n && n.keyString.forEach(t => { - const { element: { content: e } } = t, - n = new Set();Array.from(e.querySelectorAll("style")).forEach(t => { - n.add(t); - }), V(t, n); - }); - }); - })(t);const o = i.content;n ? function (t, e, n = null) { - const { element: { content: i }, parts: s } = t;if (null == n) return void i.appendChild(e);const r = document.createTreeWalker(i, 133, null, !1);let a = H(s), - o = 0, - l = -1;for (; r.nextNode();) { - for (l++, r.currentNode === n && (o = R(e), n.parentNode.insertBefore(e, n)); -1 !== a && s[a].index === l;) { - if (o > 0) { - for (; -1 !== a;) s[a].index += o, a = H(s, a);return; - }a = H(s, a); - } - } - }(n, a, o.firstChild) : o.insertBefore(a, o.firstChild), window.ShadyCSS.prepareTemplateStyles(i, t);const l = o.querySelector("style");if (window.ShadyCSS.nativeShadow && null !== l) e.insertBefore(l.cloneNode(!0), e.firstChild);else if (n) { - o.insertBefore(a, o.firstChild);const t = new Set();t.add(a), V(n, t); - } -};window.JSCompiler_renameProperty = (t, e) => t;const Y = { toAttribute(t, e) { - switch (e) {case Boolean: - return t ? "" : null;case Object:case Array: - return null == t ? t : JSON.stringify(t);}return t; - }, fromAttribute(t, e) { - switch (e) {case Boolean: - return null !== t;case Number: - return null === t ? null : Number(t);case Object:case Array: - return JSON.parse(t);}return t; - } }, - q = (t, e) => e !== t && (e == e || t == t), - B = { attribute: !0, type: String, converter: Y, reflect: !1, hasChanged: q }, - W = Promise.resolve(!0);class G extends HTMLElement { - constructor() { - super(), this._updateState = 0, this._instanceProperties = void 0, this._updatePromise = W, this._hasConnectedResolver = void 0, this._changedProperties = new Map(), this._reflectingProperties = void 0, this.initialize(); - }static get observedAttributes() { - this.finalize();const t = [];return this._classProperties.forEach((e, n) => { - const i = this._attributeNameForProperty(n, e);void 0 !== i && (this._attributeToPropertyMap.set(i, n), t.push(i)); - }), t; - }static _ensureClassProperties() { - if (!this.hasOwnProperty(JSCompiler_renameProperty("_classProperties", this))) { - this._classProperties = new Map();const t = Object.getPrototypeOf(this)._classProperties;void 0 !== t && t.forEach((t, e) => this._classProperties.set(e, t)); - } - }static createProperty(t, e = B) { - if (this._ensureClassProperties(), this._classProperties.set(t, e), e.noAccessor || this.prototype.hasOwnProperty(t)) return;const n = "symbol" == typeof t ? Symbol() : `__${t}`;Object.defineProperty(this.prototype, t, { get() { - return this[n]; - }, set(e) { - const i = this[t];this[n] = e, this._requestUpdate(t, i); - }, configurable: !0, enumerable: !0 }); - }static finalize() { - const t = Object.getPrototypeOf(this);if (t.hasOwnProperty("finalized") || t.finalize(), this.finalized = !0, this._ensureClassProperties(), this._attributeToPropertyMap = new Map(), this.hasOwnProperty(JSCompiler_renameProperty("properties", this))) { - const t = this.properties, - e = [...Object.getOwnPropertyNames(t), ...("function" == typeof Object.getOwnPropertySymbols ? Object.getOwnPropertySymbols(t) : [])];for (const n of e) this.createProperty(n, t[n]); - } - }static _attributeNameForProperty(t, e) { - const n = e.attribute;return !1 === n ? void 0 : "string" == typeof n ? n : "string" == typeof t ? t.toLowerCase() : void 0; - }static _valueHasChanged(t, e, n = q) { - return n(t, e); - }static _propertyValueFromAttribute(t, e) { - const n = e.type, - i = e.converter || Y, - s = "function" == typeof i ? i : i.fromAttribute;return s ? s(t, n) : t; - }static _propertyValueToAttribute(t, e) { - if (void 0 === e.reflect) return;const n = e.type, - i = e.converter;return (i && i.toAttribute || Y.toAttribute)(t, n); - }initialize() { - this._saveInstanceProperties(), this._requestUpdate(); - }_saveInstanceProperties() { - this.constructor._classProperties.forEach((t, e) => { - if (this.hasOwnProperty(e)) { - const t = this[e];delete this[e], this._instanceProperties || (this._instanceProperties = new Map()), this._instanceProperties.set(e, t); - } - }); - }_applyInstanceProperties() { - this._instanceProperties.forEach((t, e) => this[e] = t), this._instanceProperties = void 0; - }connectedCallback() { - this._updateState = 32 | this._updateState, this._hasConnectedResolver && (this._hasConnectedResolver(), this._hasConnectedResolver = void 0); - }disconnectedCallback() {}attributeChangedCallback(t, e, n) { - e !== n && this._attributeToProperty(t, n); - }_propertyToAttribute(t, e, n = B) { - const i = this.constructor, - s = i._attributeNameForProperty(t, n);if (void 0 !== s) { - const t = i._propertyValueToAttribute(e, n);if (void 0 === t) return;this._updateState = 8 | this._updateState, null == t ? this.removeAttribute(s) : this.setAttribute(s, t), this._updateState = -9 & this._updateState; - } - }_attributeToProperty(t, e) { - if (8 & this._updateState) return;const n = this.constructor, - i = n._attributeToPropertyMap.get(t);if (void 0 !== i) { - const t = n._classProperties.get(i) || B;this._updateState = 16 | this._updateState, this[i] = n._propertyValueFromAttribute(e, t), this._updateState = -17 & this._updateState; - } - }_requestUpdate(t, e) { - let n = !0;if (void 0 !== t) { - const i = this.constructor, - s = i._classProperties.get(t) || B;i._valueHasChanged(this[t], e, s.hasChanged) ? (this._changedProperties.has(t) || this._changedProperties.set(t, e), !0 !== s.reflect || 16 & this._updateState || (void 0 === this._reflectingProperties && (this._reflectingProperties = new Map()), this._reflectingProperties.set(t, s))) : n = !1; - }!this._hasRequestedUpdate && n && this._enqueueUpdate(); - }requestUpdate(t, e) { - return this._requestUpdate(t, e), this.updateComplete; - }async _enqueueUpdate() { - let t, e;this._updateState = 4 | this._updateState;const n = this._updatePromise;this._updatePromise = new Promise((n, i) => { - t = n, e = i; - });try { - await n; - } catch (i) {}this._hasConnected || (await new Promise(t => this._hasConnectedResolver = t));try { - const t = this.performUpdate();null != t && (await t); - } catch (i) { - e(i); - }t(!this._hasRequestedUpdate); - }get _hasConnected() { - return 32 & this._updateState; - }get _hasRequestedUpdate() { - return 4 & this._updateState; - }get hasUpdated() { - return 1 & this._updateState; - }performUpdate() { - this._instanceProperties && this._applyInstanceProperties();let t = !1;const e = this._changedProperties;try { - t = this.shouldUpdate(e), t && this.update(e); - } catch (n) { - throw t = !1, n; - } finally { - this._markUpdated(); - }t && (1 & this._updateState || (this._updateState = 1 | this._updateState, this.firstUpdated(e)), this.updated(e)); - }_markUpdated() { - this._changedProperties = new Map(), this._updateState = -5 & this._updateState; - }get updateComplete() { - return this._getUpdateComplete(); - }_getUpdateComplete() { - return this._updatePromise; - }shouldUpdate(t) { - return !0; - }update(t) { - void 0 !== this._reflectingProperties && this._reflectingProperties.size > 0 && (this._reflectingProperties.forEach((t, e) => this._propertyToAttribute(e, this[e], t)), this._reflectingProperties = void 0); - }updated(t) {}firstUpdated(t) {} -}G.finalized = !0; -/** - * @license - * Copyright (c) 2017 The Polymer Project Authors. All rights reserved. - * This code may only be used under the BSD style license found at - * http://polymer.github.io/LICENSE.txt - * The complete set of authors may be found at - * http://polymer.github.io/AUTHORS.txt - * The complete set of contributors may be found at - * http://polymer.github.io/CONTRIBUTORS.txt - * Code distributed by Google as part of the polymer project is also - * subject to an additional IP rights grant found at - * http://polymer.github.io/PATENTS.txt - */ -const J = (t, e) => "method" !== e.kind || !e.descriptor || "value" in e.descriptor ? { kind: "field", key: Symbol(), placement: "own", descriptor: {}, initializer() { - "function" == typeof e.initializer && (this[e.key] = e.initializer.call(this)); - }, finisher(n) { - n.createProperty(e.key, t); - } } : Object.assign({}, e, { finisher(n) { - n.createProperty(e.key, t); - } });function Z(t) { - return (e, n) => void 0 !== n ? ((t, e, n) => { - e.constructor.createProperty(n, t); - })(t, e, n) : J(t, e); -} -/** -@license -Copyright (c) 2019 The Polymer Project Authors. All rights reserved. -This code may only be used under the BSD style license found at -http://polymer.github.io/LICENSE.txt The complete set of authors may be found at -http://polymer.github.io/AUTHORS.txt The complete set of contributors may be -found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as -part of the polymer project is also subject to an additional IP rights grant -found at http://polymer.github.io/PATENTS.txt -*/const X = "adoptedStyleSheets" in Document.prototype && "replace" in CSSStyleSheet.prototype, - K = Symbol();class Q { - constructor(t, e) { - if (e !== K) throw new Error("CSSResult is not constructable. Use `unsafeCSS` or `css` instead.");this.cssText = t; - }get styleSheet() { - return void 0 === this._styleSheet && (X ? (this._styleSheet = new CSSStyleSheet(), this._styleSheet.replaceSync(this.cssText)) : this._styleSheet = null), this._styleSheet; - }toString() { - return this.cssText; - } -} -/** - * @license - * Copyright (c) 2017 The Polymer Project Authors. All rights reserved. - * This code may only be used under the BSD style license found at - * http://polymer.github.io/LICENSE.txt - * The complete set of authors may be found at - * http://polymer.github.io/AUTHORS.txt - * The complete set of contributors may be found at - * http://polymer.github.io/CONTRIBUTORS.txt - * Code distributed by Google as part of the polymer project is also - * subject to an additional IP rights grant found at - * http://polymer.github.io/PATENTS.txt - */ -(window.litElementVersions || (window.litElementVersions = [])).push("2.2.1");const tt = t => t.flat ? t.flat(1 / 0) : function t(e, n = []) { - for (let i = 0, s = e.length; i < s; i++) { - const s = e[i];Array.isArray(s) ? t(s, n) : n.push(s); - }return n; -}(t);class et extends G { - static finalize() { - super.finalize.call(this), this._styles = this.hasOwnProperty(JSCompiler_renameProperty("styles", this)) ? this._getUniqueStyles() : this._styles || []; - }static _getUniqueStyles() { - const t = this.styles, - e = [];if (Array.isArray(t)) { - tt(t).reduceRight((t, e) => (t.add(e), t), new Set()).forEach(t => e.unshift(t)); - } else t && e.push(t);return e; - }initialize() { - super.initialize(), this.renderRoot = this.createRenderRoot(), window.ShadowRoot && this.renderRoot instanceof window.ShadowRoot && this.adoptStyles(); - }createRenderRoot() { - return this.attachShadow({ mode: "open" }); - }adoptStyles() { - const t = this.constructor._styles;0 !== t.length && (void 0 === window.ShadyCSS || window.ShadyCSS.nativeShadow ? X ? this.renderRoot.adoptedStyleSheets = t.map(t => t.styleSheet) : this._needsShimAdoptedStyleSheets = !0 : window.ShadyCSS.ScopingShim.prepareAdoptedCssText(t.map(t => t.cssText), this.localName)); - }connectedCallback() { - super.connectedCallback(), this.hasUpdated && void 0 !== window.ShadyCSS && window.ShadyCSS.styleElement(this); - }update(t) { - super.update(t);const e = this.render();e instanceof _ && this.constructor.render(e, this.renderRoot, { scopeName: this.localName, eventContext: this }), this._needsShimAdoptedStyleSheets && (this._needsShimAdoptedStyleSheets = !1, this.constructor._styles.forEach(t => { - const e = document.createElement("style");e.textContent = t.cssText, this.renderRoot.appendChild(e); - })); - }render() {} -}et.finalized = !0, et.render = (t, e, n) => { - if (!n || "object" != typeof n || !n.scopeName) throw new Error("The `scopeName` option is required.");const i = n.scopeName, - s = $.has(e), - a = F && 11 === e.nodeType && !!e.host, - o = a && !z.has(i), - l = o ? document.createDocumentFragment() : e;if (((t, e, n) => { - let i = $.get(e);void 0 === i && (r(e, e.firstChild), $.set(e, i = new x(Object.assign({ templateFactory: P }, n))), i.appendInto(e)), i.setValue(t), i.commit(); - })(t, l, Object.assign({ templateFactory: I(i) }, n)), o) { - const t = $.get(l);$.delete(l);const n = t.value instanceof g ? t.value.template : void 0;U(i, l, n), r(e, e.firstChild), e.appendChild(l), $.set(e, t); - }!s && a && window.ShadyCSS.styleElement(e.host); -}; -/** - * @license - * Copyright (c) 2018 The Polymer Project Authors. All rights reserved. - * This code may only be used under the BSD style license found at - * http://polymer.github.io/LICENSE.txt - * The complete set of authors may be found at - * http://polymer.github.io/AUTHORS.txt - * The complete set of contributors may be found at - * http://polymer.github.io/CONTRIBUTORS.txt - * Code distributed by Google as part of the polymer project is also - * subject to an additional IP rights grant found at - * http://polymer.github.io/PATENTS.txt - */ -const nt = new WeakMap(), - it = n(t => e => { - if (!(e instanceof S) || e instanceof T || "style" !== e.committer.name || e.committer.parts.length > 1) throw new Error("The `styleMap` directive must be used in the style attribute and must be the only part in the attribute.");const { committer: n } = e, - { style: i } = n.element;nt.has(e) || (i.cssText = n.strings.join(" "));const s = nt.get(e);for (const r in s) r in t || (-1 === r.indexOf("-") ? i[r] = null : i.removeProperty(r));for (const r in t) -1 === r.indexOf("-") ? i[r] = t[r] : i.setProperty(r, t[r]);nt.set(e, t); -}), - st = new WeakMap(), - rt = n(t => e => { - if (!(e instanceof x)) throw new Error("unsafeHTML can only be used in text bindings");const n = st.get(e);if (void 0 !== n && y(t) && t === n.value && e.value === n.fragment) return;const i = document.createElement("template");i.innerHTML = t;const s = document.importNode(i.content, !0);e.setValue(s), st.set(e, { value: t, fragment: s }); -}), - at = n(t => e => { - if (void 0 === t && e instanceof S) { - if (t !== e.value) { - const t = e.committer.name;e.committer.element.removeAttribute(t); - } - } else e.setValue(t); -}), - ot = new WeakMap(), - lt = n(t => e => { - if (!(e instanceof S) || e instanceof T || "class" !== e.committer.name || e.committer.parts.length > 1) throw new Error("The `classMap` directive must be used in the `class` attribute and must be the only part in the attribute.");const { committer: n } = e, - { element: i } = n;ot.has(e) || (i.className = n.strings.join(" "));const { classList: s } = i, - r = ot.get(e);for (const a in r) a in t || s.remove(a);for (const a in t) { - const e = t[a];if (!r || e !== r[a]) { - s[e ? "add" : "remove"](a); - } - }ot.set(e, t); -});var ct = {}, - ht = /d{1,4}|M{1,4}|YY(?:YY)?|S{1,3}|Do|ZZ|([HhMsDm])\1?|[aA]|"[^"]*"|'[^']*'/g, - ut = "[^\\s]+", - dt = /\[([^]*?)\]/gm, - ft = function () {};function pt(t, e) { - for (var n = [], i = 0, s = t.length; i < s; i++) n.push(t[i].substr(0, e));return n; -}function mt(t) { - return function (e, n, i) { - var s = i[t].indexOf(n.charAt(0).toUpperCase() + n.substr(1).toLowerCase());~s && (e.month = s); - }; -}function gt(t, e) { - for (t = String(t), e = e || 2; t.length < e;) t = "0" + t;return t; -}var bt = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"], - _t = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"], - yt = pt(_t, 3), - vt = pt(bt, 3);ct.i18n = { dayNamesShort: vt, dayNames: bt, monthNamesShort: yt, monthNames: _t, amPm: ["am", "pm"], DoFn: function (t) { - return t + ["th", "st", "nd", "rd"][t % 10 > 3 ? 0 : (t - t % 10 != 10) * t % 10]; - } };var wt = { D: function (t) { - return t.getDate(); - }, DD: function (t) { - return gt(t.getDate()); - }, Do: function (t, e) { - return e.DoFn(t.getDate()); - }, d: function (t) { - return t.getDay(); - }, dd: function (t) { - return gt(t.getDay()); - }, ddd: function (t, e) { - return e.dayNamesShort[t.getDay()]; - }, dddd: function (t, e) { - return e.dayNames[t.getDay()]; - }, M: function (t) { - return t.getMonth() + 1; - }, MM: function (t) { - return gt(t.getMonth() + 1); - }, MMM: function (t, e) { - return e.monthNamesShort[t.getMonth()]; - }, MMMM: function (t, e) { - return e.monthNames[t.getMonth()]; - }, YY: function (t) { - return gt(String(t.getFullYear()), 4).substr(2); - }, YYYY: function (t) { - return gt(t.getFullYear(), 4); - }, h: function (t) { - return t.getHours() % 12 || 12; - }, hh: function (t) { - return gt(t.getHours() % 12 || 12); - }, H: function (t) { - return t.getHours(); - }, HH: function (t) { - return gt(t.getHours()); - }, m: function (t) { - return t.getMinutes(); - }, mm: function (t) { - return gt(t.getMinutes()); - }, s: function (t) { - return t.getSeconds(); - }, ss: function (t) { - return gt(t.getSeconds()); - }, S: function (t) { - return Math.round(t.getMilliseconds() / 100); - }, SS: function (t) { - return gt(Math.round(t.getMilliseconds() / 10), 2); - }, SSS: function (t) { - return gt(t.getMilliseconds(), 3); - }, a: function (t, e) { - return t.getHours() < 12 ? e.amPm[0] : e.amPm[1]; - }, A: function (t, e) { - return t.getHours() < 12 ? e.amPm[0].toUpperCase() : e.amPm[1].toUpperCase(); - }, ZZ: function (t) { - var e = t.getTimezoneOffset();return (e > 0 ? "-" : "+") + gt(100 * Math.floor(Math.abs(e) / 60) + Math.abs(e) % 60, 4); - } }, - St = { D: ["\\d\\d?", function (t, e) { - t.day = e; - }], Do: ["\\d\\d?" + ut, function (t, e) { - t.day = parseInt(e, 10); - }], M: ["\\d\\d?", function (t, e) { - t.month = e - 1; - }], YY: ["\\d\\d?", function (t, e) { - var n = +("" + new Date().getFullYear()).substr(0, 2);t.year = "" + (e > 68 ? n - 1 : n) + e; - }], h: ["\\d\\d?", function (t, e) { - t.hour = e; - }], m: ["\\d\\d?", function (t, e) { - t.minute = e; - }], s: ["\\d\\d?", function (t, e) { - t.second = e; - }], YYYY: ["\\d{4}", function (t, e) { - t.year = e; - }], S: ["\\d", function (t, e) { - t.millisecond = 100 * e; - }], SS: ["\\d{2}", function (t, e) { - t.millisecond = 10 * e; - }], SSS: ["\\d{3}", function (t, e) { - t.millisecond = e; - }], d: ["\\d\\d?", ft], ddd: [ut, ft], MMM: [ut, mt("monthNamesShort")], MMMM: [ut, mt("monthNames")], a: [ut, function (t, e, n) { - var i = e.toLowerCase();i === n.amPm[0] ? t.isPm = !1 : i === n.amPm[1] && (t.isPm = !0); - }], ZZ: ["[^\\s]*?[\\+\\-]\\d\\d:?\\d\\d|[^\\s]*?Z", function (t, e) { - var n, - i = (e + "").match(/([+-]|\d\d)/gi);i && (n = 60 * i[1] + parseInt(i[2], 10), t.timezoneOffset = "+" === i[0] ? n : -n); - }] };function xt(t) { - var e = t.split(":").map(Number);return 3600 * e[0] + 60 * e[1] + e[2]; -}St.dd = St.d, St.dddd = St.ddd, St.DD = St.D, St.mm = St.m, St.hh = St.H = St.HH = St.h, St.MM = St.M, St.ss = St.s, St.A = St.a, ct.masks = { default: "ddd MMM DD YYYY HH:mm:ss", shortDate: "M/D/YY", mediumDate: "MMM D, YYYY", longDate: "MMMM D, YYYY", fullDate: "dddd, MMMM D, YYYY", shortTime: "HH:mm", mediumTime: "HH:mm:ss", longTime: "HH:mm:ss.SSS" }, ct.format = function (t, e, n) { - var i = n || ct.i18n;if ("number" == typeof t && (t = new Date(t)), "[object Date]" !== Object.prototype.toString.call(t) || isNaN(t.getTime())) throw new Error("Invalid Date in fecha.format");e = ct.masks[e] || e || ct.masks.default;var s = [];return (e = (e = e.replace(dt, function (t, e) { - return s.push(e), "@@@"; - })).replace(ht, function (e) { - return e in wt ? wt[e](t, i) : e.slice(1, e.length - 1); - })).replace(/@@@/g, function () { - return s.shift(); - }); -}, ct.parse = function (t, e, n) { - var i = n || ct.i18n;if ("string" != typeof e) throw new Error("Invalid format in fecha.parse");if (e = ct.masks[e] || e, t.length > 1e3) return null;var s = {}, - r = [], - a = [];e = e.replace(dt, function (t, e) { - return a.push(e), "@@@"; - });var o, - l = (o = e, o.replace(/[|\\{()[^$+*?.-]/g, "\\$&")).replace(ht, function (t) { - if (St[t]) { - var e = St[t];return r.push(e[1]), "(" + e[0] + ")"; - }return t; - });l = l.replace(/@@@/g, function () { - return a.shift(); - });var c = t.match(new RegExp(l, "i"));if (!c) return null;for (var h = 1; h < c.length; h++) r[h - 1](s, c[h], i);var u, - d = new Date();return !0 === s.isPm && null != s.hour && 12 != +s.hour ? s.hour = +s.hour + 12 : !1 === s.isPm && 12 == +s.hour && (s.hour = 0), null != s.timezoneOffset ? (s.minute = +(s.minute || 0) - +s.timezoneOffset, u = new Date(Date.UTC(s.year || d.getFullYear(), s.month || 0, s.day || 1, s.hour || 0, s.minute || 0, s.second || 0, s.millisecond || 0))) : u = new Date(s.year || d.getFullYear(), s.month || 0, s.day || 1, s.hour || 0, s.minute || 0, s.second || 0, s.millisecond || 0), u; -};(function () { - try { - new Date().toLocaleDateString("i"); - } catch (t) { - return "RangeError" === t.name; - } -})(), function () { - try { - new Date().toLocaleString("i"); - } catch (t) { - return "RangeError" === t.name; - } -}(), function () { - try { - new Date().toLocaleTimeString("i"); - } catch (t) { - return "RangeError" === t.name; - } -}();var kt = function (t) { - return t < 10 ? "0" + t : t; -};var Mt = ["closed", "locked", "off"], - Tt = function (t, e, n, i) { - i = i || {}, n = null == n ? {} : n;var s = new Event(e, { bubbles: void 0 === i.bubbles || i.bubbles, cancelable: Boolean(i.cancelable), composed: void 0 === i.composed || i.composed });return s.detail = n, t.dispatchEvent(s), s; -}, - Ct = { alert: "hass:alert", automation: "hass:playlist-play", calendar: "hass:calendar", camera: "hass:video", climate: "hass:thermostat", configurator: "hass:settings", conversation: "hass:text-to-speech", device_tracker: "hass:account", fan: "hass:fan", group: "hass:google-circles-communities", history_graph: "hass:chart-line", homeassistant: "hass:home-assistant", homekit: "hass:home-automation", image_processing: "hass:image-filter-frames", input_boolean: "hass:drawing", input_datetime: "hass:calendar-clock", input_number: "hass:ray-vertex", input_select: "hass:format-list-bulleted", input_text: "hass:textbox", light: "hass:lightbulb", mailbox: "hass:mailbox", notify: "hass:comment-alert", person: "hass:account", plant: "hass:flower", proximity: "hass:apple-safari", remote: "hass:remote", scene: "hass:google-pages", script: "hass:file-document", sensor: "hass:eye", simple_alarm: "hass:bell", sun: "hass:white-balance-sunny", switch: "hass:flash", timer: "hass:timer", updater: "hass:cloud-upload", vacuum: "hass:robot-vacuum", water_heater: "hass:thermometer", weblink: "hass:open-in-new" };var Nt = function (t, e) { - Tt(t, "haptic", e); -}, - Et = function (t, e, n, i, s) { - var r;if (s && n.double_tap_action ? r = n.double_tap_action : i && n.hold_action ? r = n.hold_action : !i && n.tap_action && (r = n.tap_action), r || (r = { action: "more-info" }), !r.confirmation || r.confirmation.exemptions && r.confirmation.exemptions.some(function (t) { - return t.user === e.user.id; - }) || confirm(r.confirmation.text || "Are you sure you want to " + r.action + "?")) switch (r.action) {case "more-info": - (n.entity || n.camera_image) && (Tt(t, "hass-more-info", { entityId: r.entity ? r.entity : n.entity ? n.entity : n.camera_image }), r.haptic && Nt(t, r.haptic));break;case "navigate": - r.navigation_path && (function (t, e, n) { - void 0 === n && (n = !1), n ? history.replaceState(null, "", e) : history.pushState(null, "", e), Tt(window, "location-changed", { replace: n }); - }(0, r.navigation_path), r.haptic && Nt(t, r.haptic));break;case "url": - r.url_path && window.open(r.url_path), r.haptic && Nt(t, r.haptic);break;case "toggle": - n.entity && (function (t, e) { - (function (t, e, n) { - void 0 === n && (n = !0);var i, - s = function (t) { - return t.substr(0, t.indexOf(".")); - }(e), - r = "group" === s ? "homeassistant" : s;switch (s) {case "lock": - i = n ? "unlock" : "lock";break;case "cover": - i = n ? "open_cover" : "close_cover";break;default: - i = n ? "turn_on" : "turn_off";}t.callService(r, i, { entity_id: e }); - })(t, e, Mt.includes(t.states[e].state)); - }(e, n.entity), r.haptic && Nt(t, r.haptic));break;case "call-service": - if (!r.service) return;var a = r.service.split(".", 2), - o = a[0], - l = a[1], - c = Object.assign({}, r.service_data);"entity" === c.entity_id && (c.entity_id = n.entity), e.callService(o, l, c), r.haptic && Nt(t, r.haptic);} -};const Ot = "ontouchstart" in window || navigator.maxTouchPoints > 0 || navigator.msMaxTouchPoints > 0;class Pt extends HTMLElement { - constructor() { - super(), this.holdTime = 500, this.ripple = document.createElement("paper-ripple"), this.timer = void 0, this.held = !1, this.cooldownStart = !1, this.cooldownEnd = !1, this.nbClicks = 0; - }connectedCallback() { - Object.assign(this.style, { borderRadius: "50%", position: "absolute", width: Ot ? "100px" : "50px", height: Ot ? "100px" : "50px", transform: "translate(-50%, -50%)", pointerEvents: "none" }), this.appendChild(this.ripple), this.ripple.style.color = "#03a9f4", this.ripple.style.color = "var(--primary-color)", ["touchcancel", "mouseout", "mouseup", "touchmove", "mousewheel", "wheel", "scroll"].forEach(t => { - document.addEventListener(t, () => { - clearTimeout(this.timer), this.stopAnimation(), this.timer = void 0; - }, { passive: !0 }); - }); - }bind(t) { - if (t.longPress) return;t.longPress = !0, t.addEventListener("contextmenu", t => { - const e = t || window.event;return e.preventDefault && e.preventDefault(), e.stopPropagation && e.stopPropagation(), e.cancelBubble = !0, e.returnValue = !1, !1; - });const e = e => { - if (this.cooldownStart) return;let n, i;this.held = !1, e.touches ? (n = e.touches[0].pageX, i = e.touches[0].pageY) : (n = e.pageX, i = e.pageY), this.timer = window.setTimeout(() => { - this.startAnimation(n, i), this.held = !0, t.repeat && !t.isRepeating && (t.isRepeating = !0, this.repeatTimeout = setInterval(() => { - t.dispatchEvent(new Event("ha-hold")); - }, t.repeat)); - }, this.holdTime), this.cooldownStart = !0, window.setTimeout(() => this.cooldownStart = !1, 100); - }, - n = e => { - this.cooldownEnd || ["touchend", "touchcancel"].includes(e.type) && void 0 === this.timer ? t.isRepeating && this.repeatTimeout && (clearInterval(this.repeatTimeout), t.isRepeating = !1) : (clearTimeout(this.timer), t.isRepeating && this.repeatTimeout && clearInterval(this.repeatTimeout), t.isRepeating = !1, this.stopAnimation(), this.timer = void 0, this.held ? t.repeat || t.dispatchEvent(new Event("ha-hold")) : t.hasDblClick ? 0 === this.nbClicks ? (this.nbClicks += 1, this.dblClickTimeout = window.setTimeout(() => { - 1 === this.nbClicks && (this.nbClicks = 0, t.dispatchEvent(new Event("ha-click"))); - }, 250)) : (this.nbClicks = 0, clearTimeout(this.dblClickTimeout), t.dispatchEvent(new Event("ha-dblclick"))) : t.dispatchEvent(new Event("ha-click")), this.cooldownEnd = !0, window.setTimeout(() => this.cooldownEnd = !1, 100)); - };t.addEventListener("touchstart", e, { passive: !0 }), t.addEventListener("touchend", n), t.addEventListener("touchcancel", n);const i = "MacIntel" === navigator.platform && navigator.maxTouchPoints > 1 && !window.MSStream, - s = /iPad|iPhone|iPod/.test(navigator.platform);i || s || (t.addEventListener("mousedown", e, { passive: !0 }), t.addEventListener("click", n)); - }startAnimation(t, e) { - Object.assign(this.style, { left: `${t}px`, top: `${e}px`, display: null }), this.ripple.holdDown = !0, this.ripple.simulatedRipple(); - }stopAnimation() { - this.ripple.holdDown = !1, this.style.display = "none"; - } -}customElements.define("long-press-button-card", Pt);const At = t => { - const e = (() => { - const t = document.body;if (t.querySelector("long-press-button-card")) return t.querySelector("long-press-button-card");const e = document.createElement("long-press-button-card");return t.appendChild(e), e; - })();e && e.bind(t); -}, - $t = n(() => t => { - At(t.committer.element); -});function jt(t, e) { - (function (t) { - return "string" == typeof t && t.includes(".") && 1 === parseFloat(t); - })(t) && (t = "100%");var n = function (t) { - return "string" == typeof t && t.includes("%"); - }(t);return t = 360 === e ? t : Math.min(e, Math.max(0, parseFloat(t))), n && (t = parseInt(String(t * e), 10) / 100), Math.abs(t - e) < 1e-6 ? 1 : t = 360 === e ? (t < 0 ? t % e + e : t % e) / parseFloat(String(e)) : t % e / parseFloat(String(e)); -}function Vt(t) { - return Math.min(1, Math.max(0, t)); -}function Rt(t) { - return t = parseFloat(t), (isNaN(t) || t < 0 || t > 1) && (t = 1), t; -}function Ht(t) { - return t <= 1 ? 100 * Number(t) + "%" : t; -}function Dt(t) { - return 1 === t.length ? "0" + t : String(t); -}function Ft(t, e, n) { - t = jt(t, 255), e = jt(e, 255), n = jt(n, 255);var i = Math.max(t, e, n), - s = Math.min(t, e, n), - r = 0, - a = 0, - o = (i + s) / 2;if (i === s) a = 0, r = 0;else { - var l = i - s;switch (a = o > .5 ? l / (2 - i - s) : l / (i + s), i) {case t: - r = (e - n) / l + (e < n ? 6 : 0);break;case e: - r = (n - t) / l + 2;break;case n: - r = (t - e) / l + 4;}r /= 6; - }return { h: r, s: a, l: o }; -}function It(t, e, n) { - t = jt(t, 255), e = jt(e, 255), n = jt(n, 255);var i = Math.max(t, e, n), - s = Math.min(t, e, n), - r = 0, - a = i, - o = i - s, - l = 0 === i ? 0 : o / i;if (i === s) r = 0;else { - switch (i) {case t: - r = (e - n) / o + (e < n ? 6 : 0);break;case e: - r = (n - t) / o + 2;break;case n: - r = (t - e) / o + 4;}r /= 6; - }return { h: r, s: l, v: a }; -}function Lt(t, e, n, i) { - var s = [Dt(Math.round(t).toString(16)), Dt(Math.round(e).toString(16)), Dt(Math.round(n).toString(16))];return i && s[0].charAt(0) === s[0].charAt(1) && s[1].charAt(0) === s[1].charAt(1) && s[2].charAt(0) === s[2].charAt(1) ? s[0].charAt(0) + s[1].charAt(0) + s[2].charAt(0) : s.join(""); -}function zt(t) { - return Ut(t) / 255; -}function Ut(t) { - return parseInt(t, 16); -}var Yt = { aliceblue: "#f0f8ff", antiquewhite: "#faebd7", aqua: "#00ffff", aquamarine: "#7fffd4", azure: "#f0ffff", beige: "#f5f5dc", bisque: "#ffe4c4", black: "#000000", blanchedalmond: "#ffebcd", blue: "#0000ff", blueviolet: "#8a2be2", brown: "#a52a2a", burlywood: "#deb887", cadetblue: "#5f9ea0", chartreuse: "#7fff00", chocolate: "#d2691e", coral: "#ff7f50", cornflowerblue: "#6495ed", cornsilk: "#fff8dc", crimson: "#dc143c", cyan: "#00ffff", darkblue: "#00008b", darkcyan: "#008b8b", darkgoldenrod: "#b8860b", darkgray: "#a9a9a9", darkgreen: "#006400", darkgrey: "#a9a9a9", darkkhaki: "#bdb76b", darkmagenta: "#8b008b", darkolivegreen: "#556b2f", darkorange: "#ff8c00", darkorchid: "#9932cc", darkred: "#8b0000", darksalmon: "#e9967a", darkseagreen: "#8fbc8f", darkslateblue: "#483d8b", darkslategray: "#2f4f4f", darkslategrey: "#2f4f4f", darkturquoise: "#00ced1", darkviolet: "#9400d3", deeppink: "#ff1493", deepskyblue: "#00bfff", dimgray: "#696969", dimgrey: "#696969", dodgerblue: "#1e90ff", firebrick: "#b22222", floralwhite: "#fffaf0", forestgreen: "#228b22", fuchsia: "#ff00ff", gainsboro: "#dcdcdc", ghostwhite: "#f8f8ff", gold: "#ffd700", goldenrod: "#daa520", gray: "#808080", green: "#008000", greenyellow: "#adff2f", grey: "#808080", honeydew: "#f0fff0", hotpink: "#ff69b4", indianred: "#cd5c5c", indigo: "#4b0082", ivory: "#fffff0", khaki: "#f0e68c", lavender: "#e6e6fa", lavenderblush: "#fff0f5", lawngreen: "#7cfc00", lemonchiffon: "#fffacd", lightblue: "#add8e6", lightcoral: "#f08080", lightcyan: "#e0ffff", lightgoldenrodyellow: "#fafad2", lightgray: "#d3d3d3", lightgreen: "#90ee90", lightgrey: "#d3d3d3", lightpink: "#ffb6c1", lightsalmon: "#ffa07a", lightseagreen: "#20b2aa", lightskyblue: "#87cefa", lightslategray: "#778899", lightslategrey: "#778899", lightsteelblue: "#b0c4de", lightyellow: "#ffffe0", lime: "#00ff00", limegreen: "#32cd32", linen: "#faf0e6", magenta: "#ff00ff", maroon: "#800000", mediumaquamarine: "#66cdaa", mediumblue: "#0000cd", mediumorchid: "#ba55d3", mediumpurple: "#9370db", mediumseagreen: "#3cb371", mediumslateblue: "#7b68ee", mediumspringgreen: "#00fa9a", mediumturquoise: "#48d1cc", mediumvioletred: "#c71585", midnightblue: "#191970", mintcream: "#f5fffa", mistyrose: "#ffe4e1", moccasin: "#ffe4b5", navajowhite: "#ffdead", navy: "#000080", oldlace: "#fdf5e6", olive: "#808000", olivedrab: "#6b8e23", orange: "#ffa500", orangered: "#ff4500", orchid: "#da70d6", palegoldenrod: "#eee8aa", palegreen: "#98fb98", paleturquoise: "#afeeee", palevioletred: "#db7093", papayawhip: "#ffefd5", peachpuff: "#ffdab9", peru: "#cd853f", pink: "#ffc0cb", plum: "#dda0dd", powderblue: "#b0e0e6", purple: "#800080", rebeccapurple: "#663399", red: "#ff0000", rosybrown: "#bc8f8f", royalblue: "#4169e1", saddlebrown: "#8b4513", salmon: "#fa8072", sandybrown: "#f4a460", seagreen: "#2e8b57", seashell: "#fff5ee", sienna: "#a0522d", silver: "#c0c0c0", skyblue: "#87ceeb", slateblue: "#6a5acd", slategray: "#708090", slategrey: "#708090", snow: "#fffafa", springgreen: "#00ff7f", steelblue: "#4682b4", tan: "#d2b48c", teal: "#008080", thistle: "#d8bfd8", tomato: "#ff6347", turquoise: "#40e0d0", violet: "#ee82ee", wheat: "#f5deb3", white: "#ffffff", whitesmoke: "#f5f5f5", yellow: "#ffff00", yellowgreen: "#9acd32" };function qt(t) { - var e = { r: 0, g: 0, b: 0 }, - n = 1, - i = null, - s = null, - r = null, - a = !1, - o = !1;return "string" == typeof t && (t = function (t) { - if (0 === (t = t.trim().toLowerCase()).length) return !1;var e = !1;if (Yt[t]) t = Yt[t], e = !0;else if ("transparent" === t) return { r: 0, g: 0, b: 0, a: 0, format: "name" };var n = Jt.rgb.exec(t);if (n) return { r: n[1], g: n[2], b: n[3] };if (n = Jt.rgba.exec(t)) return { r: n[1], g: n[2], b: n[3], a: n[4] };if (n = Jt.hsl.exec(t)) return { h: n[1], s: n[2], l: n[3] };if (n = Jt.hsla.exec(t)) return { h: n[1], s: n[2], l: n[3], a: n[4] };if (n = Jt.hsv.exec(t)) return { h: n[1], s: n[2], v: n[3] };if (n = Jt.hsva.exec(t)) return { h: n[1], s: n[2], v: n[3], a: n[4] };if (n = Jt.hex8.exec(t)) return { r: Ut(n[1]), g: Ut(n[2]), b: Ut(n[3]), a: zt(n[4]), format: e ? "name" : "hex8" };if (n = Jt.hex6.exec(t)) return { r: Ut(n[1]), g: Ut(n[2]), b: Ut(n[3]), format: e ? "name" : "hex" };if (n = Jt.hex4.exec(t)) return { r: Ut(n[1] + n[1]), g: Ut(n[2] + n[2]), b: Ut(n[3] + n[3]), a: zt(n[4] + n[4]), format: e ? "name" : "hex8" };if (n = Jt.hex3.exec(t)) return { r: Ut(n[1] + n[1]), g: Ut(n[2] + n[2]), b: Ut(n[3] + n[3]), format: e ? "name" : "hex" };return !1; - }(t)), "object" == typeof t && (Zt(t.r) && Zt(t.g) && Zt(t.b) ? (e = function (t, e, n) { - return { r: 255 * jt(t, 255), g: 255 * jt(e, 255), b: 255 * jt(n, 255) }; - }(t.r, t.g, t.b), a = !0, o = "%" === String(t.r).substr(-1) ? "prgb" : "rgb") : Zt(t.h) && Zt(t.s) && Zt(t.v) ? (i = Ht(t.s), s = Ht(t.v), e = function (t, e, n) { - t = 6 * jt(t, 360), e = jt(e, 100), n = jt(n, 100);var i = Math.floor(t), - s = t - i, - r = n * (1 - e), - a = n * (1 - s * e), - o = n * (1 - (1 - s) * e), - l = i % 6;return { r: 255 * [n, a, r, r, o, n][l], g: 255 * [o, n, n, a, r, r][l], b: 255 * [r, r, o, n, n, a][l] }; - }(t.h, i, s), a = !0, o = "hsv") : Zt(t.h) && Zt(t.s) && Zt(t.l) && (i = Ht(t.s), r = Ht(t.l), e = function (t, e, n) { - var i, s, r;function a(t, e, n) { - return n < 0 && (n += 1), n > 1 && (n -= 1), n < 1 / 6 ? t + 6 * n * (e - t) : n < .5 ? e : n < 2 / 3 ? t + (e - t) * (2 / 3 - n) * 6 : t; - }if (t = jt(t, 360), e = jt(e, 100), n = jt(n, 100), 0 === e) s = n, r = n, i = n;else { - var o = n < .5 ? n * (1 + e) : n + e - n * e, - l = 2 * n - o;i = a(l, o, t + 1 / 3), s = a(l, o, t), r = a(l, o, t - 1 / 3); - }return { r: 255 * i, g: 255 * s, b: 255 * r }; - }(t.h, i, r), a = !0, o = "hsl"), Object.prototype.hasOwnProperty.call(t, "a") && (n = t.a)), n = Rt(n), { ok: a, format: t.format || o, r: Math.min(255, Math.max(e.r, 0)), g: Math.min(255, Math.max(e.g, 0)), b: Math.min(255, Math.max(e.b, 0)), a: n }; -}var Bt = "(?:[-\\+]?\\d*\\.\\d+%?)|(?:[-\\+]?\\d+%?)", - Wt = "[\\s|\\(]+(" + Bt + ")[,|\\s]+(" + Bt + ")[,|\\s]+(" + Bt + ")\\s*\\)?", - Gt = "[\\s|\\(]+(" + Bt + ")[,|\\s]+(" + Bt + ")[,|\\s]+(" + Bt + ")[,|\\s]+(" + Bt + ")\\s*\\)?", - Jt = { CSS_UNIT: new RegExp(Bt), rgb: new RegExp("rgb" + Wt), rgba: new RegExp("rgba" + Gt), hsl: new RegExp("hsl" + Wt), hsla: new RegExp("hsla" + Gt), hsv: new RegExp("hsv" + Wt), hsva: new RegExp("hsva" + Gt), hex3: /^#?([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/, hex6: /^#?([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/, hex4: /^#?([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/, hex8: /^#?([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/ };function Zt(t) { - return Boolean(Jt.CSS_UNIT.exec(String(t))); -}var Xt = function () { - function t(e, n) { - if (void 0 === e && (e = ""), void 0 === n && (n = {}), e instanceof t) return e;this.originalInput = e;var i = qt(e);this.originalInput = e, this.r = i.r, this.g = i.g, this.b = i.b, this.a = i.a, this.roundA = Math.round(100 * this.a) / 100, this.format = n.format || i.format, this.gradientType = n.gradientType, this.r < 1 && (this.r = Math.round(this.r)), this.g < 1 && (this.g = Math.round(this.g)), this.b < 1 && (this.b = Math.round(this.b)), this.isValid = i.ok; - }return t.prototype.isDark = function () { - return this.getBrightness() < 128; - }, t.prototype.isLight = function () { - return !this.isDark(); - }, t.prototype.getBrightness = function () { - var t = this.toRgb();return (299 * t.r + 587 * t.g + 114 * t.b) / 1e3; - }, t.prototype.getLuminance = function () { - var t = this.toRgb(), - e = t.r / 255, - n = t.g / 255, - i = t.b / 255;return .2126 * (e <= .03928 ? e / 12.92 : Math.pow((e + .055) / 1.055, 2.4)) + .7152 * (n <= .03928 ? n / 12.92 : Math.pow((n + .055) / 1.055, 2.4)) + .0722 * (i <= .03928 ? i / 12.92 : Math.pow((i + .055) / 1.055, 2.4)); - }, t.prototype.getAlpha = function () { - return this.a; - }, t.prototype.setAlpha = function (t) { - return this.a = Rt(t), this.roundA = Math.round(100 * this.a) / 100, this; - }, t.prototype.toHsv = function () { - var t = It(this.r, this.g, this.b);return { h: 360 * t.h, s: t.s, v: t.v, a: this.a }; - }, t.prototype.toHsvString = function () { - var t = It(this.r, this.g, this.b), - e = Math.round(360 * t.h), - n = Math.round(100 * t.s), - i = Math.round(100 * t.v);return 1 === this.a ? "hsv(" + e + ", " + n + "%, " + i + "%)" : "hsva(" + e + ", " + n + "%, " + i + "%, " + this.roundA + ")"; - }, t.prototype.toHsl = function () { - var t = Ft(this.r, this.g, this.b);return { h: 360 * t.h, s: t.s, l: t.l, a: this.a }; - }, t.prototype.toHslString = function () { - var t = Ft(this.r, this.g, this.b), - e = Math.round(360 * t.h), - n = Math.round(100 * t.s), - i = Math.round(100 * t.l);return 1 === this.a ? "hsl(" + e + ", " + n + "%, " + i + "%)" : "hsla(" + e + ", " + n + "%, " + i + "%, " + this.roundA + ")"; - }, t.prototype.toHex = function (t) { - return void 0 === t && (t = !1), Lt(this.r, this.g, this.b, t); - }, t.prototype.toHexString = function (t) { - return void 0 === t && (t = !1), "#" + this.toHex(t); - }, t.prototype.toHex8 = function (t) { - return void 0 === t && (t = !1), function (t, e, n, i, s) { - var r, - a = [Dt(Math.round(t).toString(16)), Dt(Math.round(e).toString(16)), Dt(Math.round(n).toString(16)), Dt((r = i, Math.round(255 * parseFloat(r)).toString(16)))];return s && a[0].charAt(0) === a[0].charAt(1) && a[1].charAt(0) === a[1].charAt(1) && a[2].charAt(0) === a[2].charAt(1) && a[3].charAt(0) === a[3].charAt(1) ? a[0].charAt(0) + a[1].charAt(0) + a[2].charAt(0) + a[3].charAt(0) : a.join(""); - }(this.r, this.g, this.b, this.a, t); - }, t.prototype.toHex8String = function (t) { - return void 0 === t && (t = !1), "#" + this.toHex8(t); - }, t.prototype.toRgb = function () { - return { r: Math.round(this.r), g: Math.round(this.g), b: Math.round(this.b), a: this.a }; - }, t.prototype.toRgbString = function () { - var t = Math.round(this.r), - e = Math.round(this.g), - n = Math.round(this.b);return 1 === this.a ? "rgb(" + t + ", " + e + ", " + n + ")" : "rgba(" + t + ", " + e + ", " + n + ", " + this.roundA + ")"; - }, t.prototype.toPercentageRgb = function () { - var t = function (t) { - return Math.round(100 * jt(t, 255)) + "%"; - };return { r: t(this.r), g: t(this.g), b: t(this.b), a: this.a }; - }, t.prototype.toPercentageRgbString = function () { - var t = function (t) { - return Math.round(100 * jt(t, 255)); - };return 1 === this.a ? "rgb(" + t(this.r) + "%, " + t(this.g) + "%, " + t(this.b) + "%)" : "rgba(" + t(this.r) + "%, " + t(this.g) + "%, " + t(this.b) + "%, " + this.roundA + ")"; - }, t.prototype.toName = function () { - if (0 === this.a) return "transparent";if (this.a < 1) return !1;for (var t = "#" + Lt(this.r, this.g, this.b, !1), e = 0, n = Object.keys(Yt); e < n.length; e++) { - var i = n[e];if (Yt[i] === t) return i; - }return !1; - }, t.prototype.toString = function (t) { - var e = Boolean(t);t = t || this.format;var n = !1, - i = this.a < 1 && this.a >= 0;return e || !i || !t.startsWith("hex") && "name" !== t ? ("rgb" === t && (n = this.toRgbString()), "prgb" === t && (n = this.toPercentageRgbString()), "hex" !== t && "hex6" !== t || (n = this.toHexString()), "hex3" === t && (n = this.toHexString(!0)), "hex4" === t && (n = this.toHex8String(!0)), "hex8" === t && (n = this.toHex8String()), "name" === t && (n = this.toName()), "hsl" === t && (n = this.toHslString()), "hsv" === t && (n = this.toHsvString()), n || this.toHexString()) : "name" === t && 0 === this.a ? this.toName() : this.toRgbString(); - }, t.prototype.clone = function () { - return new t(this.toString()); - }, t.prototype.lighten = function (e) { - void 0 === e && (e = 10);var n = this.toHsl();return n.l += e / 100, n.l = Vt(n.l), new t(n); - }, t.prototype.brighten = function (e) { - void 0 === e && (e = 10);var n = this.toRgb();return n.r = Math.max(0, Math.min(255, n.r - Math.round(-e / 100 * 255))), n.g = Math.max(0, Math.min(255, n.g - Math.round(-e / 100 * 255))), n.b = Math.max(0, Math.min(255, n.b - Math.round(-e / 100 * 255))), new t(n); - }, t.prototype.darken = function (e) { - void 0 === e && (e = 10);var n = this.toHsl();return n.l -= e / 100, n.l = Vt(n.l), new t(n); - }, t.prototype.tint = function (t) { - return void 0 === t && (t = 10), this.mix("white", t); - }, t.prototype.shade = function (t) { - return void 0 === t && (t = 10), this.mix("black", t); - }, t.prototype.desaturate = function (e) { - void 0 === e && (e = 10);var n = this.toHsl();return n.s -= e / 100, n.s = Vt(n.s), new t(n); - }, t.prototype.saturate = function (e) { - void 0 === e && (e = 10);var n = this.toHsl();return n.s += e / 100, n.s = Vt(n.s), new t(n); - }, t.prototype.greyscale = function () { - return this.desaturate(100); - }, t.prototype.spin = function (e) { - var n = this.toHsl(), - i = (n.h + e) % 360;return n.h = i < 0 ? 360 + i : i, new t(n); - }, t.prototype.mix = function (e, n) { - void 0 === n && (n = 50);var i = this.toRgb(), - s = new t(e).toRgb(), - r = n / 100;return new t({ r: (s.r - i.r) * r + i.r, g: (s.g - i.g) * r + i.g, b: (s.b - i.b) * r + i.b, a: (s.a - i.a) * r + i.a }); - }, t.prototype.analogous = function (e, n) { - void 0 === e && (e = 6), void 0 === n && (n = 30);var i = this.toHsl(), - s = 360 / n, - r = [this];for (i.h = (i.h - (s * e >> 1) + 720) % 360; --e;) i.h = (i.h + s) % 360, r.push(new t(i));return r; - }, t.prototype.complement = function () { - var e = this.toHsl();return e.h = (e.h + 180) % 360, new t(e); - }, t.prototype.monochromatic = function (e) { - void 0 === e && (e = 6);for (var n = this.toHsv(), i = n.h, s = n.s, r = n.v, a = [], o = 1 / e; e--;) a.push(new t({ h: i, s: s, v: r })), r = (r + o) % 1;return a; - }, t.prototype.splitcomplement = function () { - var e = this.toHsl(), - n = e.h;return [this, new t({ h: (n + 72) % 360, s: e.s, l: e.l }), new t({ h: (n + 216) % 360, s: e.s, l: e.l })]; - }, t.prototype.triad = function () { - return this.polyad(3); - }, t.prototype.tetrad = function () { - return this.polyad(4); - }, t.prototype.polyad = function (e) { - for (var n = this.toHsl(), i = n.h, s = [this], r = 360 / e, a = 1; a < e; a++) s.push(new t({ h: (i + a * r) % 360, s: n.s, l: n.l }));return s; - }, t.prototype.equals = function (e) { - return this.toRgbString() === new t(e).toRgbString(); - }, t; -}();function Kt(t, e) { - return void 0 === t && (t = ""), void 0 === e && (e = {}), new Xt(t, e); -}function Qt(t) { - return t.substr(0, t.indexOf(".")); -}function te(t) { - return "var" === t.substring(0, 3) ? window.getComputedStyle(document.documentElement).getPropertyValue(t.substring(4).slice(0, -1)).trim() : t; -}function ee(t, e) { - const n = new Xt(te(t));if (n.isValid) { - const t = n.mix("black", 100 - e).toString();if (t) return t; - }return t; -}function ne(...t) { - const e = t => t && "object" == typeof t;return t.reduce((t, n) => (Object.keys(n).forEach(i => { - const s = t[i], - r = n[i];Array.isArray(s) && Array.isArray(r) ? t[i] = s.concat(...r) : e(s) && e(r) ? t[i] = ne(s, r) : t[i] = r; - }), t), {}); -}function ie(t, e) { - let n = [];return t && t.forEach(t => { - let i = t;e && e.forEach(e => { - e.id && t.id && e.id == t.id && (i = ne(i, e)); - }), n.push(i); - }), e && (n = n.concat(e.filter(e => !t || !t.find(t => !(!t.id || !e.id) && t.id == e.id)))), n; -}const se = ((t, ...e) => { - const n = e.reduce((e, n, i) => e + (t => { - if (t instanceof Q) return t.cssText;if ("number" == typeof t) return t;throw new Error(`Value passed to 'css' function must be a 'css' function result: ${t}. Use 'unsafeCSS' to pass non-literal values, but\n take care to ensure page security.`); - })(n) + t[i + 1], t[0]);return new Q(n, K); -})` ha-card { cursor: pointer; overflow: hidden; @@ -1053,6 +11,7 @@ const nt = new WeakMap(), display: flex; justify-content: center; align-items: center; + line-height: normal; -webkit-touch-callout: none; /* iOS Safari */ -webkit-user-select: none; /* Safari */ @@ -1066,9 +25,33 @@ const nt = new WeakMap(), pointer-events: none; cursor: default; } + .tooltip .tooltiptext { + opacity: 0; + text-align: center; + padding: 4px; + border-radius: var(--ha-card-border-radius, 4px); + box-shadow: var( + --ha-card-box-shadow, + 0px 2px 1px -1px rgba(0, 0, 0, 0.2), + 0px 1px 1px 0px rgba(0, 0, 0, 0.14), + 0px 1px 3px 0px rgba(0, 0, 0, 0.12) + ); + background: var(--ha-card-background, var(--card-background-color, white)); + border: 1px solid var(--primary-text-color); + color: var(--primary-text-color); + position: absolute; + z-index: 9999; + } + .tooltip:hover span.tooltiptext { + opacity: 1; + transition-delay: 1.5s; + } ha-icon { display: inline-block; margin: auto; + --mdc-icon-size: 100%; + --iron-icon-width: 100%; + --iron-icon-height: 100%; } ha-card.button-card-main { padding: 4% 0px; @@ -1096,13 +79,14 @@ const nt = new WeakMap(), right: 0; top: 0; bottom: 0; - z-index: 1; + z-index: 50; display: flex; } #lock { -webkit-animation-fill-mode: both; animation-fill-mode: both; margin: unset; + width: 24px; } .invalid { animation: blink 1s cubic-bezier(0.68, -0.55, 0.27, 1.55) infinite; @@ -1113,9 +97,15 @@ const nt = new WeakMap(), transition: visibility 0s 1s, opacity 1s linear; } @keyframes blink { - 0%{opacity:0;} - 50%{opacity:1;} - 100%{opacity:0;} + 0% { + opacity: 0; + } + 50% { + opacity: 1; + } + 100% { + opacity: 0; + } } @-webkit-keyframes rotating /* Safari and Chrome */ { from { @@ -1210,13 +200,13 @@ const nt = new WeakMap(), } #container.vertical { - grid-template-areas: "i" "n" "s" "l"; + grid-template-areas: 'i' 'n' 's' 'l'; grid-template-columns: 1fr; grid-template-rows: 1fr min-content min-content min-content; } /* Vertical No Icon */ #container.vertical.no-icon { - grid-template-areas: "n" "s" "l"; + grid-template-areas: 'n' 's' 'l'; grid-template-columns: 1fr; grid-template-rows: 1fr min-content 1fr; } @@ -1232,7 +222,7 @@ const nt = new WeakMap(), /* Vertical No Icon No Name */ #container.vertical.no-icon.no-name { - grid-template-areas: "s" "l"; + grid-template-areas: 's' 'l'; grid-template-columns: 1fr; grid-template-rows: 1fr 1fr; } @@ -1245,7 +235,7 @@ const nt = new WeakMap(), /* Vertical No Icon No State */ #container.vertical.no-icon.no-state { - grid-template-areas: "n" "l"; + grid-template-areas: 'n' 'l'; grid-template-columns: 1fr; grid-template-rows: 1fr 1fr; } @@ -1258,7 +248,7 @@ const nt = new WeakMap(), /* Vertical No Icon No Label */ #container.vertical.no-icon.no-label { - grid-template-areas: "n" "s"; + grid-template-areas: 'n' 's'; grid-template-columns: 1fr; grid-template-rows: 1fr 1fr; } @@ -1271,7 +261,7 @@ const nt = new WeakMap(), /* Vertical No Icon No Label No Name */ #container.vertical.no-icon.no-label.no-name { - grid-template-areas: "s"; + grid-template-areas: 's'; grid-template-columns: 1fr; grid-template-rows: 1fr; } @@ -1280,7 +270,7 @@ const nt = new WeakMap(), } /* Vertical No Icon No Label No State */ #container.vertical.no-icon.no-label.no-state { - grid-template-areas: "n"; + grid-template-areas: 'n'; grid-template-columns: 1fr; grid-template-rows: 1fr; } @@ -1290,7 +280,7 @@ const nt = new WeakMap(), /* Vertical No Icon No Name No State */ #container.vertical.no-icon.no-name.no-state { - grid-template-areas: "l"; + grid-template-areas: 'l'; grid-template-columns: 1fr; grid-template-rows: 1fr; } @@ -1299,52 +289,52 @@ const nt = new WeakMap(), } #container.icon_name_state { - grid-template-areas: "i n" "l l"; + grid-template-areas: 'i n' 'l l'; grid-template-columns: 40% 1fr; grid-template-rows: 1fr min-content; } #container.icon_name { - grid-template-areas: "i n" "s s" "l l"; + grid-template-areas: 'i n' 's s' 'l l'; grid-template-columns: 40% 1fr; grid-template-rows: 1fr min-content min-content; } #container.icon_state { - grid-template-areas: "i s" "n n" "l l"; + grid-template-areas: 'i s' 'n n' 'l l'; grid-template-columns: 40% 1fr; grid-template-rows: 1fr min-content min-content; } #container.name_state { - grid-template-areas: "i" "n" "l"; + grid-template-areas: 'i' 'n' 'l'; grid-template-columns: 1fr; grid-template-rows: 1fr min-content min-content; } #container.name_state.no-icon { - grid-template-areas: "n" "l"; + grid-template-areas: 'n' 'l'; grid-template-columns: 1fr; grid-template-rows: 1fr 1fr; } #container.name_state.no-icon #name { - align-self: end + align-self: end; } #container.name_state.no-icon #label { - align-self: start + align-self: start; } #container.name_state.no-icon.no-label { - grid-template-areas: "n"; + grid-template-areas: 'n'; grid-template-columns: 1fr; grid-template-rows: 1fr; } #container.name_state.no-icon.no-label #name { - align-self: center + align-self: center; } /* icon_name_state2nd default */ #container.icon_name_state2nd { - grid-template-areas: "i n" "i s" "i l"; + grid-template-areas: 'i n' 'i s' 'i l'; grid-template-columns: 40% 1fr; grid-template-rows: 1fr min-content 1fr; } @@ -1360,7 +350,7 @@ const nt = new WeakMap(), /* icon_name_state2nd No Label */ #container.icon_name_state2nd.no-label { - grid-template-areas: "i n" "i s"; + grid-template-areas: 'i n' 'i s'; grid-template-columns: 40% 1fr; grid-template-rows: 1fr 1fr; } @@ -1373,7 +363,7 @@ const nt = new WeakMap(), /* icon_state_name2nd Default */ #container.icon_state_name2nd { - grid-template-areas: "i s" "i n" "i l"; + grid-template-areas: 'i s' 'i n' 'i l'; grid-template-columns: 40% 1fr; grid-template-rows: 1fr min-content 1fr; } @@ -1389,7 +379,7 @@ const nt = new WeakMap(), /* icon_state_name2nd No Label */ #container.icon_state_name2nd.no-label { - grid-template-areas: "i s" "i n"; + grid-template-areas: 'i s' 'i n'; grid-template-columns: 40% 1fr; grid-template-rows: 1fr 1fr; } @@ -1401,349 +391,149 @@ const nt = new WeakMap(), } #container.icon_label { - grid-template-areas: "i l" "n n" "s s"; + grid-template-areas: 'i l' 'n n' 's s'; grid-template-columns: 40% 1fr; grid-template-rows: 1fr min-content min-content; } - [style*="--aspect-ratio"] > :first-child { + [style*='--aspect-ratio'] > :first-child { width: 100%; } - [style*="--aspect-ratio"] > img { + [style*='--aspect-ratio'] > img { height: auto; } - @supports (--custom:property) { - [style*="--aspect-ratio"] { + @supports (--custom: property) { + [style*='--aspect-ratio'] { position: relative; } - [style*="--aspect-ratio"]::before { - content: ""; + [style*='--aspect-ratio']::before { + content: ''; display: block; padding-bottom: calc(100% / (var(--aspect-ratio))); } - [style*="--aspect-ratio"] > :first-child { + [style*='--aspect-ratio'] > :first-child { position: absolute; top: 0; left: 0; height: 100%; } } -`;console.info("%c BUTTON-CARD \n%c Version 3.1.1 ", "color: orange; font-weight: bold; background: black", "color: white; font-weight: bold; background: dimgray");let re = class extends et { - disconnectedCallback() { - super.disconnectedCallback(), this._clearInterval(); - }connectedCallback() { - if (super.connectedCallback(), this.config && this.config.entity && "timer" === Qt(this.config.entity)) { - const t = this.hass.states[this.config.entity];this._startInterval(t); - } - }static get styles() { - return se; - }render() { - return this._stateObj = this.config.entity ? this.hass.states[this.config.entity] : void 0, this.config && this.hass ? this._cardHtml() : j``; - }shouldUpdate(t) { - const e = !!(this._hasTemplate || this.config.state && this.config.state.find(t => "template" === t.operator) || t.has("_timeRemaining"));return function (t, e, n) { - if (e.has("config") || n) return !0;if (t.config.entity) { - const n = e.get("hass");return !n || n.states[t.config.entity] !== t.hass.states[t.config.entity]; - }return !1; - }(this, t, e); - }updated(t) { - if (super.updated(t), this.config && this.config.entity && "timer" === Qt(this.config.entity) && t.has("hass")) { - const e = this.hass.states[this.config.entity], - n = t.get("hass");(n ? n.states[this.config.entity] : void 0) !== e ? this._startInterval(e) : e || this._clearInterval(); - } - }_clearInterval() { - this._interval && (window.clearInterval(this._interval), this._interval = void 0); - }_startInterval(t) { - this._clearInterval(), this._calculateRemaining(t), "active" === t.state && (this._interval = window.setInterval(() => this._calculateRemaining(t), 1e3)); - }_calculateRemaining(t) { - this._timeRemaining = function (t) { - var e = xt(t.attributes.remaining);if ("active" === t.state) { - var n = new Date().getTime(), - i = new Date(t.last_changed).getTime();e = Math.max(e - (n - i) / 1e3, 0); - }return e; - }(t); - }_computeTimeDisplay(t) { - if (t) return function (t) { - var e = Math.floor(t / 3600), - n = Math.floor(t % 3600 / 60), - i = Math.floor(t % 3600 % 60);return e > 0 ? e + ":" + kt(n) + ":" + kt(i) : n > 0 ? n + ":" + kt(i) : i > 0 ? "" + i : null; - }(this._timeRemaining || xt(t.attributes.duration)); - }_getMatchingConfigState(t) { - if (!this.config.state) return;const e = this.config.state.find(t => "template" === t.operator);if (!t && !e) return;let n;const i = this.config.state.find(e => { - if (!e.operator) return t && this._getTemplateOrValue(t, e.value) == t.state;switch (e.operator) {case "==": - return t && t.state == this._getTemplateOrValue(t, e.value);case "<=": - return t && t.state <= this._getTemplateOrValue(t, e.value);case "<": - return t && t.state < this._getTemplateOrValue(t, e.value);case ">=": - return t && t.state >= this._getTemplateOrValue(t, e.value);case ">": - return t && t.state > this._getTemplateOrValue(t, e.value);case "!=": - return t && t.state != this._getTemplateOrValue(t, e.value);case "regex": - return !(!t || !t.state.match(this._getTemplateOrValue(t, e.value)));case "template": - return this._getTemplateOrValue(t, e.value);case "default": - return n = e, !1;default: - return !1;} - });return !i && n ? n : i; - }_evalTemplate(t, e) { - return new Function("states", "entity", "user", "hass", "variables", `'use strict'; ${e}`).call(this, this.hass.states, t, this.hass.user, this.hass, this.config.variables); - }_objectEvalTemplate(t, e) { - const n = JSON.parse(JSON.stringify(e));return this._getTemplateOrValue(t, n); - }_getTemplateOrValue(t, e) { - if (["number", "boolean"].includes(typeof e)) return e;if (!e) return e;if (["object"].includes(typeof e)) return Object.keys(e).forEach(n => { - e[n] = this._getTemplateOrValue(t, e[n]); - }), e;const n = e.trim();return "[[[" === n.substring(0, 3) && "]]]" === n.slice(-3) ? this._evalTemplate(t, n.slice(3, -3)) : e; - }_getDefaultColorForState(t) { - switch (t.state) {case "on": - return this.config.color_on;case "off": - return this.config.color_off;default: - return this.config.default_color;} - }_getColorForLightEntity(t, e) { - let n = this.config.default_color;return t && (t.attributes.rgb_color ? (n = `rgb(${t.attributes.rgb_color.join(",")})`, t.attributes.brightness && (n = ee(n, (t.attributes.brightness + 245) / 5))) : e && t.attributes.color_temp && t.attributes.min_mireds && t.attributes.max_mireds ? (n = function (t, e, n) { - const i = new Xt("rgb(255, 160, 0)"), - s = new Xt("rgb(166, 209, 255)"), - r = new Xt("white"), - a = (t - e) / (n - e) * 100;return a < 50 ? Kt(s).mix(r, 2 * a).toRgbString() : Kt(r).mix(i, 2 * (a - 50)).toRgbString(); - }(t.attributes.color_temp, t.attributes.min_mireds, t.attributes.max_mireds), t.attributes.brightness && (n = ee(n, (t.attributes.brightness + 245) / 5))) : n = t.attributes.brightness ? ee(this._getDefaultColorForState(t), (t.attributes.brightness + 245) / 5) : this._getDefaultColorForState(t)), n; - }_buildCssColorAttribute(t, e) { - let n, - i = "";return e && e.color ? i = e.color : "auto" !== this.config.color && t && "off" === t.state ? i = this.config.color_off : this.config.color && (i = this.config.color), n = "auto" == i || "auto-no-temperature" == i ? this._getColorForLightEntity(t, "auto-no-temperature" !== i) : i || (t ? this._getDefaultColorForState(t) : this.config.default_color), n; - }_buildIcon(t, e) { - if (!this.config.show_icon) return;let n;return e && e.icon ? n = e.icon : this.config.icon ? n = this.config.icon : t && t.attributes && (n = t.attributes.icon ? t.attributes.icon : function (t, e) { - if (t in Ct) return Ct[t];switch (t) {case "alarm_control_panel": - switch (e) {case "armed_home": - return "hass:bell-plus";case "armed_night": - return "hass:bell-sleep";case "disarmed": - return "hass:bell-outline";case "triggered": - return "hass:bell-ring";default: - return "hass:bell";}case "binary_sensor": - return e && "off" === e ? "hass:radiobox-blank" : "hass:checkbox-marked-circle";case "cover": - return "closed" === e ? "hass:window-closed" : "hass:window-open";case "lock": - return e && "unlocked" === e ? "hass:lock-open" : "hass:lock";case "media_player": - return e && "off" !== e && "idle" !== e ? "hass:cast-connected" : "hass:cast";case "zwave": - switch (e) {case "dead": - return "hass:emoticon-dead";case "sleeping": - return "hass:sleep";case "initializing": - return "hass:timer-sand";default: - return "hass:z-wave";}default: - return console.warn("Unable to find icon for domain " + t + " (" + e + ")"), "hass:bookmark";} - }(Qt(t.entity_id), t.state)), this._getTemplateOrValue(t, n); - }_buildEntityPicture(t, e) { - if (!this.config.show_entity_picture || !t && !e && !this.config.entity_picture) return;let n;return e && e.entity_picture ? n = e.entity_picture : this.config.entity_picture ? n = this.config.entity_picture : t && (n = t.attributes && t.attributes.entity_picture ? t.attributes.entity_picture : void 0), this._getTemplateOrValue(t, n); - }_buildStyleGeneric(t, e, n) { - let i = {};if (this.config.styles && this.config.styles[n] && (i = Object.assign(i, ...this.config.styles[n])), e && e.styles && e.styles[n]) { - let t = {};t = Object.assign(t, ...e.styles[n]), i = Object.assign(Object.assign({}, i), t); - }return Object.keys(i).forEach(e => { - i[e] = this._getTemplateOrValue(t, i[e]); - }), i; - }_buildCustomStyleGeneric(t, e, n) { - let i = {};if (this.config.styles && this.config.styles.custom_fields && this.config.styles.custom_fields[n] && (i = Object.assign(i, ...this.config.styles.custom_fields[n])), e && e.styles && e.styles.custom_fields && e.styles.custom_fields[n]) { - let t = {};t = Object.assign(t, ...e.styles.custom_fields[n]), i = Object.assign(Object.assign({}, i), t); - }return Object.keys(i).forEach(e => { - i[e] = this._getTemplateOrValue(t, i[e]); - }), i; - }_buildName(t, e) { - if (!1 === this.config.show_name) return;let n;var i;return e && e.name ? n = e.name : this.config.name ? n = this.config.name : t && (n = t.attributes && t.attributes.friendly_name ? t.attributes.friendly_name : (i = t.entity_id).substr(i.indexOf(".") + 1)), this._getTemplateOrValue(t, n); - }_buildStateString(t) { - let e;if (this.config.show_state && t && t.state) { - const n = ((t, e) => { - let n;const i = Qt(e.entity_id);return "binary_sensor" === i ? (e.attributes.device_class && (n = t(`state.${i}.${e.attributes.device_class}.${e.state}`)), n || (n = t(`state.${i}.default.${e.state}`))) : n = e.attributes.unit_of_measurement && !["unknown", "unavailable"].includes(e.state) ? e.state : "zwave" === i ? ["initializing", "dead"].includes(e.state) ? t(`state.zwave.query_stage.${e.state}`, "query_stage", e.attributes.query_stage) : t(`state.zwave.default.${e.state}`) : t(`state.${i}.${e.state}`), n || (n = t(`state.default.${e.state}`) || t(`component.${i}.state.${e.state}`) || e.state), n; - })(this.hass.localize, t), - i = this._buildUnits(t);i ? e = `${t.state} ${i}` : "timer" === Qt(t.entity_id) ? "idle" === t.state || 0 === this._timeRemaining ? e = n : (e = this._computeTimeDisplay(t), "paused" === t.state && (e += ` (${n})`)) : e = n; - }return e; - }_buildUnits(t) { - let e;return t && this.config.show_units && (e = t.attributes && t.attributes.unit_of_measurement && !this.config.units ? t.attributes.unit_of_measurement : this.config.units ? this.config.units : void 0), e; - }_buildLastChanged(t, e) { - return this.config.show_last_changed && t ? j` - ` : void 0; - }_buildLabel(t, e) { - if (!this.config.show_label) return;let n;return n = e && e.label ? e.label : this.config.label, this._getTemplateOrValue(t, n); - }_buildCustomFields(t, e) { - let n = j``;const i = {}, - s = {};return this.config.custom_fields && Object.keys(this.config.custom_fields).forEach(e => { - const n = this.config.custom_fields[e];n.card ? s[e] = this._objectEvalTemplate(t, n.card) : i[e] = this._getTemplateOrValue(t, n); - }), e && e.custom_fields && Object.keys(e.custom_fields).forEach(n => { - const r = e.custom_fields[n];r.card ? s[n] = this._objectEvalTemplate(t, r.card) : i[n] = this._getTemplateOrValue(t, r); - }), Object.keys(i).forEach(s => { - if (null != i[s]) { - const r = Object.assign(Object.assign({}, this._buildCustomStyleGeneric(t, e, s)), { "grid-area": s });n = j`${n} -
${rt(i[s])}
`; - } - }), Object.keys(s).forEach(i => { - if (null != s[i]) { - const r = Object.assign(Object.assign({}, this._buildCustomStyleGeneric(t, e, i)), { "grid-area": i }), - a = function (t) { - var e = function (t, e) { - return n("hui-error-card", { type: "error", error: t, config: e }); - }, - n = function (t, n) { - var i = window.document.createElement(t);try { - i.setConfig(n); - } catch (i) { - return console.error(t, i), e(i.message, n); - }return i; - };if (!t || "object" != typeof t || !t.type) return e("No type defined", t);var i = t.type;if (i = i.startsWith("custom:") ? i.substr("custom:".length) : "hui-" + i + "-card", customElements.get(i)) return n(i, t);var s = e("Custom element doesn't exist: " + t.type + ".", t);s.style.display = "None";var r = setTimeout(function () { - s.style.display = ""; - }, 2e3);return customElements.whenDefined(t.type).then(function () { - clearTimeout(r), Tt(s, "ll-rebuild", {}, s); - }), s; - }(s[i]);a.hass = this.hass, n = j`${n} -
${a}
`; - } - }), n; - }_isClickable(t) { - let e = !0;if ("toggle" === this.config.tap_action.action && "none" === this.config.hold_action.action && "none" === this.config.double_tap_action.action || "toggle" === this.config.hold_action.action && "none" === this.config.tap_action.action && "none" === this.config.double_tap_action.action || "toggle" === this.config.double_tap_action.action && "none" === this.config.tap_action.action && "none" === this.config.hold_action.action) { - if (t) switch (Qt(t.entity_id)) {case "sensor":case "binary_sensor":case "device_tracker": - e = !1;break;default: - e = !0;} else e = !1; - } else e = "none" != this.config.tap_action.action || "none" != this.config.hold_action.action || "none" != this.config.double_tap_action.action;return e; - }_rotate(t) { - return !(!t || !t.spin); - }_blankCardColoredHtml(t) { - const e = Object.assign({ background: "none", "box-shadow": "none" }, t);return j` - +`;const Ae=(t,e,i,n)=>{if(!((t,e,i)=>{const[n,r]=t.split(".",2);return Number(n)>e||Number(n)===e&&Number(r)>=i})(t.config.version,0,109))return function(t,e){let i;const n=ke(e.entity_id);return"binary_sensor"===n?(e.attributes.device_class&&(i=t(`state.${n}.${e.attributes.device_class}.${e.state}`)),i||(i=t(`state.${n}.default.${e.state}`))):i=e.attributes.unit_of_measurement&&!["unknown","unavailable"].includes(e.state)?e.state:"zwave"===n?["initializing","dead"].includes(e.state)?t("state.zwave.query_stage."+e.state,"query_stage",e.attributes.query_stage):t("state.zwave.default."+e.state):t(`state.${n}.${e.state}`),i||(i=t("state.default."+e.state)||t(`component.${n}.state.${e.state}`)||e.state),i}(e,i);if("unknown"===i.state||"unavailable"===i.state)return e("state.default."+i.state);if(i.attributes.unit_of_measurement)return`${i.state} ${i.attributes.unit_of_measurement}`;const r=ke(i.entity_id);if("input_datetime"===r){let t;if(!i.attributes.has_time)return t=new Date(i.attributes.year,i.attributes.month-1,i.attributes.day),jt(t,n);if(!i.attributes.has_date){const e=new Date;return t=new Date(e.getFullYear(),e.getMonth(),e.getDay(),i.attributes.hour,i.attributes.minute),Ht(t,n)}return t=new Date(i.attributes.year,i.attributes.month-1,i.attributes.day,i.attributes.hour,i.attributes.minute),$t(t,n)}return i.attributes.device_class&&e(`component.${r}.state.${i.attributes.device_class}.${i.state}`)||e(`component.${r}.state._.${i.state}`)||i.state};var Ne=Function.prototype.toString,Pe=Object.create,je=Object.defineProperty,$e=Object.getOwnPropertyDescriptor,He=Object.getOwnPropertyNames,Re=Object.getOwnPropertySymbols,Ve=Object.getPrototypeOf,De=Object.prototype,Le=De.hasOwnProperty,Fe=De.propertyIsEnumerable,Ie="function"==typeof Re,ze="function"==typeof WeakMap,Ue=function(t,e){if(!t.constructor)return Pe(null);var i=t.constructor,n=t.__proto__||Ve(t);if(i===e.Object)return n===e.Object.prototype?{}:Pe(n);if(~Ne.call(i).indexOf("[native code]"))try{return new i}catch(r){}return Pe(n)},qe=function(t,e,i,n){var r=Ue(t,e);for(var s in n.set(t,r),t)Le.call(t,s)&&(r[s]=i(t[s],n));if(Ie){var a=Re(t),o=a.length;if(o)for(var l=0,c=void 0;l{Ze&&t(),window.loadCardHelpers&&(Ze=await window.loadCardHelpers(),window.cardHelpers=Ze,t())}));console.info("%c BUTTON-CARD \n%c Version 3.4.0 ","color: orange; font-weight: bold; background: black","color: white; font-weight: bold; background: dimgray");let Xe=class extends nt{constructor(){super(...arguments),this._cards={},this._cardsConfig={},this._entities=[],this._initial_setup_complete=!1,this._rippleHandlers=new rt((()=>this._ripple))}set hass(t){this._hass=t,Object.keys(this._cards).forEach((t=>{this._cards[t].hass=this._hass})),this._initial_setup_complete||this._initConnected()}disconnectedCallback(){super.disconnectedCallback(),this._clearInterval()}connectedCallback(){super.connectedCallback(),this._initial_setup_complete?this._startTimerCountdown():this._initConnected()}_initConnected(){void 0!==this._hass&&void 0!==this._config&&this.isConnected&&(this._initial_setup_complete=!0,this._startTimerCountdown())}_startTimerCountdown(){if(this._config&&this._config.entity&&"timer"===ke(this._config.entity)){const t=this._hass.states[this._config.entity];this._startInterval(t)}}_createCard(t){if(Ze)return Ze.createCardElement(t);{const e=function(t,e){void 0===e&&(e=!1);var i=function(t,e){return n("hui-error-card",{type:"error",error:t,config:e})},n=function(t,e){var n=window.document.createElement(t);try{n.setConfig(e)}catch(n){return console.error(t,n),i(n.message,e)}return n};if(!t||"object"!=typeof t||!e&&!t.type)return i("No type defined",t);var r=t.type;if(r&&r.startsWith("custom:"))r=r.substr("custom:".length);else if(e)if(zt.has(r))r="hui-"+r+"-row";else{if(!t.entity)return i("Invalid config given.",t);var s=t.entity.split(".",1)[0];r="hui-"+(Ut[s]||"text")+"-entity-row"}else r="hui-"+r+"-card";if(customElements.get(r))return n(r,t);var a=i("Custom element doesn't exist: "+t.type+".",t);a.style.display="None";var o=setTimeout((function(){a.style.display=""}),2e3);return customElements.whenDefined(t.type).then((function(){clearTimeout(o),It(a,"ll-rebuild",{},a)})),a}(t);return Je.then((()=>{It(e,"ll-rebuild",{})})),e}}static get styles(){return Ee}render(){if(!this._config||!this._hass)return D``;this._stateObj=this._config.entity?this._hass.states[this._config.entity]:void 0;try{return this._evaledVariables=this._config.variables?this._objectEvalTemplate(this._stateObj,this._config.variables):void 0,this._cardHtml()}catch(t){t.stack?console.error(t.stack):console.error(t);const e=document.createElement("hui-error-card");return e.setConfig({type:"error",error:t.toString(),origConfig:this._config}),D` ${e} `}}shouldUpdate(t){return!(!this._hasTemplate&&!t.has("_timeRemaining")&&!function(t,e){if(e.has("_config"))return!0;const i=e.get("_hass");if(i)return t._entities.some((function(e){return(null==i?void 0:i.states[e])!==t._hass.states[e]}));return!1}(this,t))&&(this._expandTriggerGroups(),!0)}updated(t){if(super.updated(t),this._config&&this._config.entity&&"timer"===ke(this._config.entity)&&t.has("_hass")){const e=this._hass.states[this._config.entity],i=t.get("_hass");(i?i.states[this._config.entity]:void 0)!==e?this._startInterval(e):e||this._clearInterval()}}_clearInterval(){this._interval&&(window.clearInterval(this._interval),this._interval=void 0)}_startInterval(t){this._clearInterval(),this._calculateRemaining(t),"active"===t.state&&(this._interval=window.setInterval((()=>this._calculateRemaining(t)),1e3))}_calculateRemaining(t){t.attributes.remaining&&(this._timeRemaining=function(t){var e=Pt(t.attributes.remaining);if("active"===t.state){var i=(new Date).getTime(),n=new Date(t.last_changed).getTime();e=Math.max(e-(i-n)/1e3,0)}return e}(t))}_computeTimeDisplay(t){if(t)return function(t){var e=Math.floor(t/3600),i=Math.floor(t%3600/60),n=Math.floor(t%3600%60);return e>0?e+":"+Rt(i)+":"+Rt(n):i>0?i+":"+Rt(n):n>0?""+n:null}(this._timeRemaining||Pt(t.attributes.duration))}_getMatchingConfigState(t){if(!this._config.state)return;const e=this._config.state.find((t=>"template"===t.operator));if(!t&&!e)return;let i;const n=this._config.state.find((e=>{if(!e.operator)return t&&this._getTemplateOrValue(t,e.value)==t.state;switch(e.operator){case"==":return t&&t.state==this._getTemplateOrValue(t,e.value);case"<=":return t&&t.state<=this._getTemplateOrValue(t,e.value);case"<":return t&&t.state=":return t&&t.state>=this._getTemplateOrValue(t,e.value);case">":return t&&t.state>this._getTemplateOrValue(t,e.value);case"!=":return t&&t.state!=this._getTemplateOrValue(t,e.value);case"regex":return!(!t||!t.state.match(this._getTemplateOrValue(t,e.value)));case"template":return this._getTemplateOrValue(t,e.value);case"default":return i=e,!1;default:return!1}}));return!n&&i?i:n}_evalTemplate(t,e){try{return new Function("states","entity","user","hass","variables","html","'use strict'; "+e).call(this,this._hass.states,t,this._hass.user,this._hass,this._evaledVariables,D)}catch(i){const t=e.length<=100?e.trim():e.trim().substring(0,98)+"...";throw i.message=`${i.name}: ${i.message} in '${t}'`,i.name="ButtonCardJSTemplateError",i}}_objectEvalTemplate(t,e){const i=Ge(e);return this._getTemplateOrValue(t,i)}_getTemplateOrValue(t,e){if(["number","boolean"].includes(typeof e))return e;if(!e)return e;if("object"==typeof e)return Object.keys(e).forEach((i=>{e[i]=this._getTemplateOrValue(t,e[i])})),e;const i=e.trim();return"[[["===i.substring(0,3)&&"]]]"===i.slice(-3)?this._evalTemplate(t,i.slice(3,-3)):e}_getDefaultColorForState(t){switch(t.state){case"on":return this._config.color_on;case"off":return this._config.color_off;default:return this._config.default_color}}_getColorForLightEntity(t,e){let i=this._config.default_color;return t&&("on"===t.state?t.attributes.rgb_color?(i=`rgb(${t.attributes.rgb_color.join(",")})`,t.attributes.brightness&&(i=Te(i,(t.attributes.brightness+245)/5))):e&&t.attributes.color_temp&&t.attributes.min_mireds&&t.attributes.max_mireds?(i=function(t,e,i){const n=new Se("rgb(255, 160, 0)"),r=new Se("rgb(166, 209, 255)"),s=new Se("white"),a=(t-e)/(i-e)*100;return a<50?xe(r).mix(s,2*a).toRgbString():xe(s).mix(n,2*(a-50)).toRgbString()}(t.attributes.color_temp,t.attributes.min_mireds,t.attributes.max_mireds),t.attributes.brightness&&(i=Te(i,(t.attributes.brightness+245)/5))):i=t.attributes.brightness?Te(this._getDefaultColorForState(t),(t.attributes.brightness+245)/5):this._getDefaultColorForState(t):i=this._getDefaultColorForState(t)),i}_buildCssColorAttribute(t,e){let i,n="";return(null==e?void 0:e.color)?n=e.color:"auto"!==this._config.color&&"off"===(null==t?void 0:t.state)?n=this._config.color_off:this._config.color&&(n=this._config.color),i="auto"==n||"auto-no-temperature"==n?this._getColorForLightEntity(t,"auto-no-temperature"!==n):n||(t?this._getDefaultColorForState(t):this._config.default_color),i}_buildIcon(t,e){if(!this._config.show_icon)return;let i;if(null==e?void 0:e.icon)i=e.icon;else if(this._config.icon)i=this._config.icon;else{if(!t)return;i=function(t){if(!t)return Dt;if(t.attributes.icon)return t.attributes.icon;var e=Vt(t.entity_id);return e in Jt?Jt[e](t):Yt(e,t.state)}(t)}return this._getTemplateOrValue(t,i)}_buildEntityPicture(t,e){if(!this._config.show_entity_picture||!t&&!e&&!this._config.entity_picture)return;let i;return(null==e?void 0:e.entity_picture)?i=e.entity_picture:this._config.entity_picture?i=this._config.entity_picture:t&&(i=t.attributes&&t.attributes.entity_picture?t.attributes.entity_picture:void 0),this._getTemplateOrValue(t,i)}_buildStyleGeneric(t,e,i){var n,r;let s={};if((null===(n=this._config.styles)||void 0===n?void 0:n[i])&&(s=Object.assign(s,...this._config.styles[i])),null===(r=null==e?void 0:e.styles)||void 0===r?void 0:r[i]){let t={};t=Object.assign(t,...e.styles[i]),s=Object.assign(Object.assign({},s),t)}return Object.keys(s).forEach((e=>{s[e]=this._getTemplateOrValue(t,s[e])})),s}_buildCustomStyleGeneric(t,e,i){var n,r,s,a;let o={};if((null===(r=null===(n=this._config.styles)||void 0===n?void 0:n.custom_fields)||void 0===r?void 0:r[i])&&(o=Object.assign(o,...this._config.styles.custom_fields[i])),null===(a=null===(s=null==e?void 0:e.styles)||void 0===s?void 0:s.custom_fields)||void 0===a?void 0:a[i]){let t={};t=Object.assign(t,...e.styles.custom_fields[i]),o=Object.assign(Object.assign({},o),t)}return Object.keys(o).forEach((e=>{o[e]=this._getTemplateOrValue(t,o[e])})),o}_buildName(t,e){if(!1===this._config.show_name)return;let i;var n;return(null==e?void 0:e.name)?i=e.name:this._config.name?i=this._config.name:t&&(i=t.attributes&&t.attributes.friendly_name?t.attributes.friendly_name:(n=t.entity_id).substr(n.indexOf(".")+1)),this._getTemplateOrValue(t,i)}_buildStateString(t){var e;let i;if(this._config.show_state&&t&&t.state){const n=this._buildUnits(t);n?i=`${t.state} ${n}`:"timer"===ke(t.entity_id)?"idle"===t.state||0===this._timeRemaining?i=Ae(this._hass,this._hass.localize,t,this._hass.language):(i=this._computeTimeDisplay(t),"paused"===t.state&&(i+=` (${Ae(this._hass,this._hass.localize,t,this._hass.language)})`)):i=(null===(e=this._config)||void 0===e?void 0:e.show_units)||"sensor"!==ke(t.entity_id)?Ae(this._hass,this._hass.localize,t,this._hass.language):t.state}return i}_buildUnits(t){var e;let i;return t&&this._config.show_units&&(i=(null===(e=t.attributes)||void 0===e?void 0:e.unit_of_measurement)&&!this._config.units?t.attributes.unit_of_measurement:this._config.units?this._config.units:void 0),i}_buildLastChanged(t,e){return this._config.show_last_changed&&t?D` + + `:void 0}_buildLabel(t,e){if(!this._config.show_label)return;let i;return i=(null==e?void 0:e.label)?e.label:this._config.label,this._getTemplateOrValue(t,i)}_buildCustomFields(t,e){let i=D``;const n={},r={};return this._config.custom_fields&&Object.keys(this._config.custom_fields).forEach((e=>{const i=this._config.custom_fields[e];i.card?r[e]=this._objectEvalTemplate(t,i.card):n[e]=this._getTemplateOrValue(t,i)})),(null==e?void 0:e.custom_fields)&&Object.keys(e.custom_fields).forEach((i=>{const s=e.custom_fields[i];s.card?r[i]=this._objectEvalTemplate(t,s.card):n[i]=this._getTemplateOrValue(t,s)})),Object.keys(n).forEach((r=>{if(null!=n[r]){const s=Object.assign(Object.assign({},this._buildCustomStyleGeneric(t,e,r)),{"grid-area":r});i=D` + ${i} +
+ ${n[r]&&"html"===n[r].type?n[r]:lt(n[r])} +
+ `}})),Object.keys(r).forEach((n=>{if(null!=r[n]){const s=Object.assign(Object.assign({},this._buildCustomStyleGeneric(t,e,n)),{"grid-area":n});let a;Kt(this._cardsConfig[n],r[n])?a=this._cards[n]:(a=this._createCard(r[n]),this._cards[n]=a,this._cardsConfig[n]=Ge(r[n])),a.hass=this._hass,i=D` + ${i} +
+ ${a} +
+ `}})),i}_isClickable(t){let e=!0;const i=this._getTemplateOrValue(t,this._config.tap_action.action),n=this._getTemplateOrValue(t,this._config.hold_action.action),r=this._getTemplateOrValue(t,this._config.double_tap_action.action);return e="none"!=i||"none"!=n||"none"!=r,e}_rotate(t){return!!(null==t?void 0:t.spin)}_blankCardColoredHtml(t){const e=Object.assign({background:"none","box-shadow":"none"},t);return D` +
- `; - }_cardHtml() { - const t = this._getMatchingConfigState(this._stateObj), - e = this._buildCssColorAttribute(this._stateObj, t);let n = e, - i = {}, - s = {};const r = {}, - a = this._buildStyleGeneric(this._stateObj, t, "lock"), - o = this._buildStyleGeneric(this._stateObj, t, "card"), - l = { "button-card-main": !0, disabled: !this._isClickable(this._stateObj) };switch (o.width && (this.style.setProperty("flex", "0 0 auto"), this.style.setProperty("max-width", "fit-content")), this.config.color_type) {case "blank-card": - return this._blankCardColoredHtml(o);case "card":case "label-card": - { - const t = function (t) { - const e = new Xt(te(t));return e.isValid && e.getLuminance() > .5 ? "rgb(62, 62, 62)" : "rgb(234, 234, 234)"; - }(e);i.color = t, s.color = t, i["background-color"] = e, i = Object.assign(Object.assign({}, i), o), n = "inherit";break; - }default: - i = o;}return this.config.aspect_ratio ? (r["--aspect-ratio"] = this.config.aspect_ratio, i.position = "absolute") : r.display = "inline", this.style.setProperty("--button-card-light-color", this._getColorForLightEntity(this._stateObj, !0)), this.style.setProperty("--button-card-light-color-no-temperature", this._getColorForLightEntity(this._stateObj, !1)), s = Object.assign(Object.assign({}, s), a), j` -
+ `}_cardHtml(){var t,e;const i=this._getMatchingConfigState(this._stateObj),n=this._buildCssColorAttribute(this._stateObj,i);let r=n,s={},a={};const o={},l=this._buildStyleGeneric(this._stateObj,i,"lock"),c=this._buildStyleGeneric(this._stateObj,i,"card"),h=this._buildStyleGeneric(this._stateObj,i,"tooltip"),u={"button-card-main":!0,tooltip:!!(null===(t=this._config)||void 0===t?void 0:t.tooltip),disabled:!this._isClickable(this._stateObj)};switch(c.width&&(this.style.setProperty("flex","0 0 auto"),this.style.setProperty("max-width","fit-content")),this._config.color_type){case"blank-card":return this._blankCardColoredHtml(c);case"card":case"label-card":{const t=function(t){const e=new Se(Oe(t));return e.isValid&&e.getLuminance()>.5?"rgb(62, 62, 62)":"rgb(234, 234, 234)"}(n);s.color=t,a.color=t,s["background-color"]=n,s=Object.assign(Object.assign({},s),c),r="inherit";break}default:s=c}this._config.aspect_ratio?(o["--aspect-ratio"]=this._config.aspect_ratio,s.position="absolute"):o.display="inline",this.style.setProperty("--button-card-light-color",this._getColorForLightEntity(this._stateObj,!0)),this.style.setProperty("--button-card-light-color-no-temperature",this._getColorForLightEntity(this._stateObj,!1)),a=Object.assign(Object.assign({},a),l);const d=this._config.extra_styles?D` + + `:D``;return D` + ${d} +
- ${this._buttonContent(this._stateObj, t, n)} - ${this._getLock(s)} + ${this._buttonContent(this._stateObj,i,r)} + ${(null===(e=this._config)||void 0===e?void 0:e.tooltip)?D` + ${this._getTemplateOrValue(this._stateObj,this._config.tooltip)} + `:""} +
- `; - }_getLock(t) { - return this.config.lock && this._getTemplateOrValue(this._stateObj, this.config.lock.enabled) ? j` -
this._handleUnlockType(t, "tap")} - @ha-hold=${t => this._handleUnlockType(t, "hold")} - @ha-dblclick=${t => this._handleUnlockType(t, "double_tap")} - .hasDblClick=${"double_tap" === this.config.lock.unlock} - .longpress=${$t()} - .config="${this.config}" + ${this._getLock(a)} + `}_getLock(t){return this._config.lock&&this._getTemplateOrValue(this._stateObj,this._config.lock.enabled)?D` +
- ` : j``; - }_buttonContent(t, e, n) { - const i = this._buildName(t, e), - s = this._buildStateString(t), - r = function (t, e) { - if (!t && !e) return;let n;return n = e ? t ? `${t}: ${e}` : e : t, n; - }(i, s);switch (this.config.layout) {case "icon_name_state":case "name_state": - return this._gridHtml(t, e, this.config.layout, n, r, void 0);default: - return this._gridHtml(t, e, this.config.layout, n, i, s);} - }_gridHtml(t, e, n, i, s, r) { - const a = this._getIconHtml(t, e, i), - o = [n], - l = this._buildLabel(t, e), - c = this._buildStyleGeneric(t, e, "name"), - h = this._buildStyleGeneric(t, e, "state"), - u = this._buildStyleGeneric(t, e, "label"), - d = this._buildLastChanged(t, u), - f = this._buildStyleGeneric(t, e, "grid");return a || o.push("no-icon"), s || o.push("no-name"), r || o.push("no-state"), l || d || o.push("no-label"), j` -
- ${a || ""} - ${s ? j`
${rt(s)}
` : ""} - ${r ? j`
${r}
` : ""} - ${l && !d ? j`
${rt(l)}
` : ""} - ${d || ""} - ${this._buildCustomFields(t, e)} + `:D``}_buttonContent(t,e,i){const n=this._buildName(t,e),r=this._config.show_state&&this._config.state_display?this._getTemplateOrValue(t,this._config.state_display):void 0,s=r||this._buildStateString(t),a=function(t,e){if(!t&&!e)return;let i;return i=e?t?`${t}: ${e}`:e:t,i}(n,s);switch(this._config.layout){case"icon_name_state":case"name_state":return this._gridHtml(t,e,this._config.layout,i,a,void 0);default:return this._gridHtml(t,e,this._config.layout,i,n,s)}}_gridHtml(t,e,i,n,r,s){const a=this._getIconHtml(t,e,n),o=[i],l=this._buildLabel(t,e),c=this._buildStyleGeneric(t,e,"name"),h=this._buildStyleGeneric(t,e,"state"),u=this._buildStyleGeneric(t,e,"label"),d=this._buildLastChanged(t,u),p=this._buildStyleGeneric(t,e,"grid");return a||o.push("no-icon"),r||o.push("no-name"),s||o.push("no-state"),l||d||o.push("no-label"),D` +
+ ${a||""} + ${r?D` +
+ ${"html"===r.type?r:lt(r)} +
+ `:""} + ${s?D` +
+ ${"html"===s.type?s:lt(s)} +
+ `:""} + ${l&&!d?D` +
+ ${"html"===l.type?l:lt(l)} +
+ `:""} + ${d||""} ${this._buildCustomFields(t,e)}
- `; - }_getIconHtml(t, e, n) { - const i = this._buildIcon(t, e), - s = this._buildEntityPicture(t, e), - r = this._buildStyleGeneric(t, e, "entity_picture"), - a = this._buildStyleGeneric(t, e, "icon"), - o = this._buildStyleGeneric(t, e, "img_cell"), - l = this._buildStyleGeneric(t, e, "card"), - c = Object.assign({ color: n, width: this.config.size, position: this.config.aspect_ratio || l.height ? "absolute" : "relative" }, a), - h = Object.assign(Object.assign({}, c), r);return i || s ? j` -
- ${i && !s ? j`` : ""} - ${s ? j`` : ""} + `}_getIconHtml(t,e,i){const n=this._buildIcon(t,e),r=this._buildEntityPicture(t,e),s=this._buildStyleGeneric(t,e,"entity_picture"),a=this._buildStyleGeneric(t,e,"icon"),o=this._buildStyleGeneric(t,e,"img_cell"),l=this._buildStyleGeneric(t,e,"card"),c=Object.assign({color:i,width:this._config.size,position:this._config.aspect_ratio||l.height?"absolute":"relative"},a),h=Object.assign(Object.assign({},c),s),u=this._buildLiveStream(h);return n||r?D` +
+ ${!n||r||u?"":D` + + `} + ${u||""} + ${r&&!u?D` + + `:""}
- ` : void 0; - }setConfig(t) { - if (!t) throw new Error("Invalid configuration");const e = function () { - var t = document.querySelector("home-assistant");if (t = (t = (t = (t = (t = (t = (t = (t = t && t.shadowRoot) && t.querySelector("home-assistant-main")) && t.shadowRoot) && t.querySelector("app-drawer-layout partial-panel-resolver")) && t.shadowRoot || t) && t.querySelector("ha-panel-lovelace")) && t.shadowRoot) && t.querySelector("hui-root")) { - var e = t.lovelace;return e.current_view = t.___curView, e; - }return null; - }() || function () { - let t = document.querySelector("hc-main");if (t = t && t.shadowRoot, t = t && t.querySelector("hc-lovelace"), t = t && t.shadowRoot, t = t && t.querySelector("hui-view"), t) { - const e = t.lovelace;return e.current_view = t.___curView, e; - }return null; - }();let n = Object.assign({}, t), - i = n.template, - s = t.state;for (; i && e.config.button_card_templates && e.config.button_card_templates[i];) n = ne(e.config.button_card_templates[i], n), s = ie(e.config.button_card_templates[i].state, s), i = e.config.button_card_templates[i].template;n.state = s, this.config = Object.assign({ tap_action: { action: "toggle" }, hold_action: { action: "none" }, double_tap_action: { action: "none" }, layout: "vertical", size: "40%", color_type: "icon", show_name: !0, show_state: !1, show_icon: !0, show_units: !0, show_label: !1, show_entity_picture: !1 }, n), this.config.lock = Object.assign({ enabled: !1, duration: 5, unlock: "tap" }, this.config.lock), this.config.default_color = "var(--primary-text-color)", "icon" !== this.config.color_type ? this.config.color_off = "var(--paper-card-background-color)" : this.config.color_off = "var(--paper-item-icon-color)", this.config.color_on = "var(--paper-item-icon-active-color)";const r = JSON.stringify(this.config), - a = new RegExp("\\[\\[\\[.*\\]\\]\\]", "gm");this._hasTemplate = !!r.match(a); - }getCardSize() { - return 3; - }_evalActions(t, e) { - const n = this.config.entity ? this.hass.states[this.config.entity] : void 0, - i = JSON.parse(JSON.stringify(t)), - s = t => t ? (Object.keys(t).forEach(e => { - "object" == typeof t[e] ? t[e] = s(t[e]) : t[e] = this._getTemplateOrValue(n, t[e]); - }), t) : t;return i[e] = s(i[e]), !i[e].confirmation && i.confirmation && (i[e].confirmation = s(i.confirmation)), i; - }_handleTap(t) { - const e = t.target.config;Et(this, this.hass, this._evalActions(e, "tap_action"), !1, !1); - }_handleHold(t) { - const e = t.target.config;Et(this, this.hass, this._evalActions(e, "hold_action"), !0, !1); - }_handleDblTap(t) { - const e = t.target.config;Et(this, this.hass, this._evalActions(e, "double_tap_action"), !1, !0); - }_handleUnlockType(t, e) { - t.target.config.lock.unlock === e && this._handleLock(t); - }_handleLock(t) { - t.stopPropagation();const e = this.shadowRoot.getElementById("lock");if (!e) return;if (this.config.lock.exemptions) { - if (!this.hass.user.name || !this.hass.user.id) return;let t = !1;if (this.config.lock.exemptions.forEach(e => { - (!t && e.user === this.hass.user.id || e.username === this.hass.user.name) && (t = !0); - }), !t) return e.classList.add("invalid"), void window.setTimeout(() => { - e && e.classList.remove("invalid"); - }, 3e3); - }const n = this.shadowRoot.getElementById("overlay"), - i = this.shadowRoot.getElementById("card");n.style.setProperty("pointer-events", "none");const s = document.createElement("paper-ripple");if (e) { - i.appendChild(s);const t = document.createAttribute("icon");t.value = "mdi:lock-open-outline", e.attributes.setNamedItem(t), e.classList.add("hidden"); - }window.setTimeout(() => { - if (n.style.setProperty("pointer-events", ""), e) { - e.classList.remove("hidden");const t = document.createAttribute("icon");t.value = "mdi:lock-outline", e.attributes.setNamedItem(t), i.removeChild(s); - } - }, 1e3 * this.config.lock.duration); - }_stopPropagation(t) { - t.stopPropagation(); - } -};var ae;t([Z()], re.prototype, "hass", void 0), t([Z()], re.prototype, "config", void 0), t([Z()], re.prototype, "_timeRemaining", void 0), t([Z()], re.prototype, "_hasTemplate", void 0), t([Z()], re.prototype, "_stateObj", void 0), re = t([(ae = "button-card", t => "function" == typeof t ? ((t, e) => (window.customElements.define(t, e), e))(ae, t) : ((t, e) => { - const { kind: n, elements: i } = e;return { kind: n, elements: i, finisher(e) { - window.customElements.define(t, e); - } }; -})(ae, t))], re); + `:void 0}_buildLiveStream(t){return this._config.show_live_stream&&this._config.entity&&"camera"===ke(this._config.entity)?D` + + `:void 0}_configFromLLTemplates(t,e){const i=e.template;if(!i)return e;let n,r={};const s=i&&Array.isArray(i)?i:[i];return null==s||s.forEach((e=>{var i;if(!(null===(i=t.config.button_card_templates)||void 0===i?void 0:i[e]))throw new Error(`Button-card template '${e}' is missing!`);const s=this._configFromLLTemplates(t,t.config.button_card_templates[e]);r=Me(r,s),n=Ce(n,s.state)})),r=Me(r,e),r.state=Ce(n,e.state),r}setConfig(t){if(!t)throw new Error("Invalid configuration");this._cards={},this._cardsConfig={};const e=function(){var t=document.querySelector("home-assistant");if(t=(t=(t=(t=(t=(t=(t=(t=t&&t.shadowRoot)&&t.querySelector("home-assistant-main"))&&t.shadowRoot)&&t.querySelector("app-drawer-layout partial-panel-resolver"))&&t.shadowRoot||t)&&t.querySelector("ha-panel-lovelace"))&&t.shadowRoot)&&t.querySelector("hui-root")){var e=t.lovelace;return e.current_view=t.___curView,e}return null}()||function(){let t=document.querySelector("hc-main");if(t=t&&t.shadowRoot,t=t&&t.querySelector("hc-lovelace"),t=t&&t.shadowRoot,t=t&&(t.querySelector("hui-view")||t.querySelector("hui-panel-view")),t){const e=t.lovelace;return e.current_view=t.___curView,e}return null}();let i=Ge(t);i=this._configFromLLTemplates(e,i),this._config=Object.assign(Object.assign({type:"custom:button-card",group_expand:!1,hold_action:{action:"none"},double_tap_action:{action:"none"},layout:"vertical",size:"40%",color_type:"icon",show_name:!0,show_state:!1,show_icon:!0,show_units:!0,show_label:!1,show_entity_picture:!1,show_live_stream:!1,card_size:3},i),{default_color:"DUMMY",color_off:"DUMMY",color_on:"DUMMY",lock:Object.assign({enabled:!1,duration:5,unlock:"tap"},i.lock)}),this._config.entity&&Ft.has(ke(this._config.entity))?this._config=Object.assign({tap_action:{action:"toggle"}},this._config):this._config=Object.assign({tap_action:{action:"more-info"}},this._config),this._config.default_color="var(--primary-text-color)","icon"!==this._config.color_type?this._config.color_off="var(--card-background-color)":this._config.color_off="var(--paper-item-icon-color)",this._config.color_on="var(--paper-item-icon-active-color)";const n=JSON.stringify(this._config);if(this._entities=[],Array.isArray(this._config.triggers_update)?this._entities=[...this._config.triggers_update]:"string"==typeof this._config.triggers_update&&"all"!==this._config.triggers_update&&this._entities.push(this._config.triggers_update),"all"!==this._config.triggers_update){const t=new RegExp(/states\[\s*('|\\")([a-zA-Z0-9_]+\.[a-zA-Z0-9_]+)\1\s*\]/,"gm"),e=new RegExp(/states\[\s*('|\\")([a-zA-Z0-9_]+\.[a-zA-Z0-9_]+)\1\s*\]/,"m"),i=n.match(t);null==i||i.forEach((t=>{const i=t.match(e);i&&!this._entities.includes(i[2])&&this._entities.push(i[2])}))}this._config.entity&&!this._entities.includes(this._config.entity)&&this._entities.push(this._config.entity),this._expandTriggerGroups();const r=new RegExp("\\[\\[\\[.*\\]\\]\\]","m");this._hasTemplate=!("all"!==this._config.triggers_update||!n.match(r)),this._initial_setup_complete||this._initConnected()}_loopGroup(t){t&&t.forEach((t=>{var e,i;(null===(e=this._hass)||void 0===e?void 0:e.states[t])&&("group"===ke(t)&&(null===(i=this._hass.states[t].attributes)||void 0===i?void 0:i.entity_id)?this._loopGroup(this._hass.states[t].attributes.entity_id):this._entities.includes(t)||this._entities.push(t))}))}_expandTriggerGroups(){var t;this._hass&&(null===(t=this._config)||void 0===t?void 0:t.group_expand)&&this._entities&&this._entities.forEach((t=>{var e,i;"group"===ke(t)&&this._loopGroup(null===(i=null===(e=this._hass)||void 0===e?void 0:e.states[t].attributes)||void 0===i?void 0:i.entity_id)}))}getCardSize(){var t;return(null===(t=this._config)||void 0===t?void 0:t.card_size)||3}_evalActions(t,e){const i=Ge(t),n=t=>t?(Object.keys(t).forEach((e=>{"object"==typeof t[e]?t[e]=n(t[e]):t[e]=this._getTemplateOrValue(this._stateObj,t[e])})),t):t;return i[e]=n(i[e]),!i[e].confirmation&&i.confirmation&&(i[e].confirmation=n(i.confirmation)),i}handleRippleActivate(t){this._ripple.then((e=>e&&e.startPress&&this._rippleHandlers.startPress(t)))}handleRippleDeactivate(){this._ripple.then((t=>t&&t.endPress&&this._rippleHandlers.endPress()))}handleRippleFocus(){this._ripple.then((t=>t&&t.startFocus&&this._rippleHandlers.startFocus()))}handleRippleBlur(){this._ripple.then((t=>t&&t.endFocus&&this._rippleHandlers.endFocus()))}_handleAction(t){var e;if(null===(e=t.detail)||void 0===e?void 0:e.action)switch(t.detail.action){case"tap":this._handleTap();break;case"hold":this._handleHold();break;case"double_tap":this._handleDblTap()}}_handleTap(){const t=this._config;t&&Gt(this,this._hass,this._evalActions(t,"tap_action"),!1,!1)}_handleHold(){const t=this._config;t&&Gt(this,this._hass,this._evalActions(t,"hold_action"),!0,!1)}_handleDblTap(){const t=this._config;t&&Gt(this,this._hass,this._evalActions(t,"double_tap_action"),!1,!0)}_handleUnlockType(t){const e=this._config;e&&e.lock.unlock===t.detail.action&&this._handleLock()}_handleLock(){const t=this.shadowRoot.getElementById("lock");if(!t)return;if(this._config.lock.exemptions){if(!this._hass.user.name||!this._hass.user.id)return;let e=!1;if(this._config.lock.exemptions.forEach((t=>{(!e&&t.user===this._hass.user.id||t.username===this._hass.user.name)&&(e=!0)})),!e)return t.classList.add("invalid"),void window.setTimeout((()=>{t&&t.classList.remove("invalid")}),3e3)}const e=this.shadowRoot.getElementById("overlay");if(e.style.setProperty("pointer-events","none"),t){const e=document.createAttribute("icon");e.value="mdi:lock-open-outline",t.attributes.setNamedItem(e),t.classList.add("hidden")}window.setTimeout((()=>{if(e.style.setProperty("pointer-events",""),t){t.classList.remove("hidden");const e=document.createAttribute("icon");e.value="mdi:lock-outline",t.attributes.setNamedItem(e)}}),1e3*this._config.lock.duration)}_stopPropagation(t){t.stopPropagation()}};var Ke,Qe,ti;t([J()],Xe.prototype,"_hass",void 0),t([J()],Xe.prototype,"_config",void 0),t([J()],Xe.prototype,"_timeRemaining",void 0),t([(Ke="mwc-ripple",(t,e)=>{const i={async get(){return await this.updateComplete,this.renderRoot.querySelector(Ke)},enumerable:!0,configurable:!0};return void 0!==e?X(i,t,e):K(i,t)})],Xe.prototype,"_ripple",void 0),t([(Qe={passive:!0},(t,e)=>void 0!==e?((t,e,i)=>{Object.assign(e[i],t)})(Qe,t,e):((t,e)=>Object.assign(Object.assign({},e),{finisher(i){Object.assign(i.prototype[e.key],t)}}))(Qe,t))],Xe.prototype,"handleRippleActivate",null),Xe=t([(ti="button-card",t=>"function"==typeof t?((t,e)=>(window.customElements.define(t,e),e))(ti,t):((t,e)=>{const{kind:i,elements:n}=e;return{kind:i,elements:n,finisher(e){window.customElements.define(t,e)}}})(ti,t))],Xe); diff --git a/www/community/lovelace-darksky-card/lovelace-darksky-card.js b/www/community/lovelace-darksky-card/lovelace-darksky-card.js new file mode 100644 index 0000000..3897c99 --- /dev/null +++ b/www/community/lovelace-darksky-card/lovelace-darksky-card.js @@ -0,0 +1,787 @@ +// ##### +// ##### Get the LitElement and HTML classes from an already defined HA Lovelace class +// ##### +var LitElement = LitElement || Object.getPrototypeOf(customElements.get("home-assistant-main")); +var html = LitElement.prototype.html; + +// ##### +// ##### Custom Card Definition begins +// ##### + +class DarkSkyWeatherCard extends LitElement { + +// ##### +// ##### Define Render Template +// ##### + + render() { +// Handle Configuration Flags +// var icons = this.config.static_icons ? "static" : "animated"; + var currentText = this.config.entity_current_text ? html`${this._hass.states[this.config.entity_current_text].state}` : ``; + var apparentTemp = this.config.entity_apparent_temp ? html`${this.localeText.feelsLike} ${this.current.apparent} ${this.getUOM("temperature")}` : ``; + var summary = this.config.entity_daily_summary ? html`
${this._hass.states[this.config.entity_daily_summary].state}
` : ``; + var separator = this.config.show_separator ? html`
` : ``; + + + +// Build HTML + return html` + + + ${this.current.conditions} + ${this.current.temperature}${this.getUOM('temperature')} + ${currentText} + ${apparentTemp} + ${separator} + +
    +
  • + ${this.getSlot().l1} + ${this.getSlot().l2} + ${this.getSlot().l3} + ${this.getSlot().l4} + ${this.getSlot().l5} +
  • +
  • + ${this.getSlot().r1} + ${this.getSlot().r2} + ${this.getSlot().r3} + ${this.getSlot().r4} + ${this.getSlot().r5} +
  • +
+
+
+ ${this.forecast.map(daily => html` +
+ ${(daily.date).toLocaleDateString(this.config.locale,{weekday: 'short'})} +
+ ${this.config.old_daily_format ? html`
${Math.round(this._hass.states[daily.temphigh].state)}${this.getUOM("temperature")} +
${Math.round(this._hass.states[daily.templow].state)}${this.getUOM("temperature")}` : + html`
${Math.round(this._hass.states[daily.templow].state)} / ${Math.round(this._hass.states[daily.temphigh].state)}${this.getUOM("temperature")}`} + ${this.config.entity_pop_1 && this.config.entity_pop_2 && this.config.entity_pop_3 && this.config.entity_pop_4 && this.config.entity_pop_5 ? html`
${Math.round(this._hass.states[daily.pop].state)} %` : ``} +
${ this.config.tooltips ? this._hass.states[daily.summary].state : ""}
+
`)} +
+ ${summary} +
+ `; + } + + +// ##### +// ##### slots - returns the value to be displyed in a specific current condition slot +// ##### + + getSlot() { + return { + 'r1' : this.slotValue('r1',this.config.slot_r1), + 'r2' : this.slotValue('r2',this.config.slot_r2), + 'r3' : this.slotValue('r3',this.config.slot_r3), + 'r4' : this.slotValue('r4',this.config.slot_r4), + 'r5' : this.slotValue('r5',this.config.slot_r5), + 'l1' : this.slotValue('l1',this.config.slot_l1), + 'l2' : this.slotValue('l2',this.config.slot_l2), + 'l3' : this.slotValue('l3',this.config.slot_l3), + 'l4' : this.slotValue('l4',this.config.slot_l4), + 'l5' : this.slotValue('l5',this.config.slot_l5), + } + } + +// ##### +// ##### slots - calculates the specific slot value +// ##### + + slotValue(slot,value){ + var sunNext = this.config.alt_sun_next ? html`
  • ${this._hass.states[this.config.alt_sun_next].state}
  • ` : this.config.entity_sun ? this.sunSet.next : ""; + var sunFollowing = this.config.alt_sun_following ? html`
  • ${this._hass.states[this.config.alt_sun_following].state}
  • ` : this.config.entity_sun ? this.sunSet.following : ""; + var daytimeHigh = this.config.alt_daytime_high ? html`
  • ${this._hass.states[this.config.alt_daytime_high].state}
  • ` : this.config.entity_daytime_high ? html`
  • ${this.localeText.maxToday} ${Math.round(this._hass.states[this.config.entity_daytime_high].state)} ${this.getUOM('temperature')}
  • ` : ``; + var intensity = this.config.entity_pop_intensity ? html` - ${this._hass.states[this.config.entity_pop_intensity].state} ${this.getUOM('intensity')}` : ``; + var pop = this.config.alt_pop ? html`
  • ${this._hass.states[this.config.alt_pop].state}
  • ` : this.config.entity_pop ? html`
  • ${Math.round(this._hass.states[this.config.entity_pop].state)} %${intensity}
  • ` : ``; + var visibility = this.config.alt_visibility ? html`
  • ${this._hass.states[this.config.alt_visibility].state}
  • ` : this.config.entity_visibility ? html`
  • ${this.current.visibility} ${this.getUOM('length')}
  • ` : ``; + var wind = this.config.alt_wind ? html`
  • ${this._hass.states[this.config.alt_wind].state}
  • ` : this.config.entity_wind_bearing && this.config.entity_wind_speed ? html`
  • ${this.current.beaufort}${this.current.windBearing} ${this.current.windSpeed} ${this.getUOM('length')}/h
  • ` : ``; + var humidity = this.config.alt_humidity ? html`
  • ${this._hass.states[this.config.alt_humidity].state}
  • ` : this.config.entity_humidity ? html`
  • ${this.current.humidity} %
  • ` : ``; + var pressure = this.config.alt_pressure ? html`
  • ${this._hass.states[this.config.alt_pressure].state}
  • ` : this.config.entity_pressure ? html`
  • ${this.current.pressure} ${this.getUOM('air_pressure')}
  • ` : ``; + var precipIntensityCurrent = this.config.entity_precip_intensity_current ? html`
  • ${this._hass.states[this.config.entity_precip_intensity_current].state} ${this.getUOM('intensity')}` : ``; + var precipCurrent = this.config.entity_precip_current ? html`
  • ${this._hass.states[this.config.entity_precip_current].state} ${this.getUOM('precipitation')}` : ``; + + switch (value){ + case 'pop': return pop; + case 'humidity': return humidity; + case 'pressure': return pressure; + case 'sun_following': return sunFollowing; + case 'daytime_high': return daytimeHigh; + case 'wind': return wind; + case 'visibility': return visibility; + case 'sun_next': return sunNext; + case 'precip_rate_current': return precipIntensityCurrent; + case 'precip_current': return precipCurrent; + case 'empty': return html` `; + case 'remove': return ``; + } + + // If no value can be matched pass back a default for the slot + switch (slot){ + case 'l1': return daytimeHigh; + case 'l2': return wind; + case 'l3': return visibility; + case 'l4': return sunNext; + case 'l5': return precipCurrent; + case 'r1': return pop; + case 'r2': return humidity; + case 'r3': return pressure; + case 'r4': return sunFollowing; + case 'r5': return precipIntensityCurrent; + } + } + + +// ##### +// ##### windDirections - returns set of possible wind directions by specified language +// ##### + + get windDirections() { + const windDirections_en = ['N','NNE','NE','ENE','E','ESE','SE','SSE','S','SSW','SW','WSW','W','WNW','NW','NNW','N']; + const windDirections_fr = ['N','NNE','NE','ENE','E','ESE','SE','SSE','S','SSO','SO','OSO','O','ONO','NO','NNO','N']; + const windDirections_de = ['N','NNO','NO','ONO','O','OSO','SO','SSO','S','SSW','SW','WSW','W','WNW','NW','NNW','N']; + const windDirections_nl = ['N','NNO','NO','ONO','O','OZO','ZO','ZZO','Z','ZZW','ZW','WZW','W','WNW','NW','NNW','N']; + const windDirections_he = ['צפון','צ-צ-מז','צפון מזרח','מז-צ-מז','מזרח','מז-ד-מז','דרום מזרח','ד-ד-מז','דרום','ד-ד-מע','דרום מערב','מע-ד-מע','מערב','מע-צ-מע','צפון מערב','צ-צ-מע','צפון']; + const windDirections_da = ['N','NNØ','NØ','ØNØ','Ø','ØSØ','SØ','SSØ','S','SSV','SV','VSV','V','VNV','NV','NNV','N']; + + switch (this.config.locale) { + case "it" : + case "fr" : + return windDirections_fr; + case "de" : + return windDirections_de; + case "nl" : + return windDirections_nl; + case "he" : + return windDirections_he; + case "da" : + return windDirections_da; + default : + return windDirections_en; + } + } + +// ##### +// ##### feelsLikeText returns set of possible "Feels Like" text by specified language +// ##### + + get localeText() { + switch (this.config.locale) { + case "it" : + return { + feelsLike: "Percepito", + maxToday: "Max oggi:", + } + case "fr" : + return { + feelsLike: "Se sent comme", + maxToday: "Max aujourd'hui:", + } + case "de" : + return { + feelsLike: "Gefühlt", + maxToday: "Max heute:", + } + case "nl" : + return { + feelsLike: "Voelt als", + maxToday: "Max vandaag:", + } + case "pl" : + return { + feelsLike: "Odczuwalne", + maxToday: "Najwyższa dziś:", + } + case "he" : + return { + feelsLike: "מרגיש כמו:", + maxToday: "מקסימלי היום:", + } + case "da" : + return { + feelsLike: "Føles som", + maxToday: "Højeste i dag" + } + default : + return { + feelsLike: "Feels like", + maxToday: "Today's High", + } + } + } + +// ##### +// ##### dayOrNight : returns day or night depending on the position of the sun. +// ##### + + get dayOrNight() { + const transformDayNight = { "below_horizon": "night", "above_horizon": "day", }; + return this.config.entity_sun ? transformDayNight[this._hass.states[this.config.entity_sun].state] : 'day'; + } + + +// ##### +// ##### weatherIcons: returns icon names based on current conditions text +// ##### + + get weatherIcons() { + return { + 'clear-day': 'day', + 'clear-night': 'night', + 'rain': 'rainy-5', + 'snow': 'snowy-6', + 'sleet': 'rainy-6', + 'wind': 'cloudy', + 'fog': 'cloudy', + 'cloudy': 'cloudy', + 'partly-cloudy-day': 'cloudy-day-3', + 'partly-cloudy-night': 'cloudy-night-3', + 'hail': 'rainy-7', + 'lightning': 'thunder', + 'thunderstorm': 'thunder', + 'windy-variant': html`cloudy-${this.dayOrNight}-3`, + 'exceptional': '!!', + } + } + + +// ##### +// ##### forecast : returns forcasted weather information for the next 5 days +// ##### + + get forecast() { + var forecastDate1 = new Date(); + forecastDate1.setDate(forecastDate1.getDate()+1); + var forecastDate2 = new Date(); + forecastDate2.setDate(forecastDate2.getDate()+2); + var forecastDate3 = new Date(); + forecastDate3.setDate(forecastDate3.getDate()+3); + var forecastDate4 = new Date(); + forecastDate4.setDate(forecastDate4.getDate()+4); + var forecastDate5 = new Date(); + forecastDate5.setDate(forecastDate5.getDate()+5); + + + const forecast1 = { date: forecastDate1, + dayIndex: '1', + condition: this.config.entity_forecast_icon_1, + temphigh: this.config.entity_forecast_high_temp_1, + templow: this.config.entity_forecast_low_temp_1, + pop: this.config.entity_pop_1, + summary: this.config.entity_summary_1, }; + const forecast2 = { date: forecastDate2, + dayIndex: '2', + condition: this.config.entity_forecast_icon_2, + temphigh: this.config.entity_forecast_high_temp_2, + templow: this.config.entity_forecast_low_temp_2, + pop: this.config.entity_pop_2, + summary: this.config.entity_summary_2, }; + const forecast3 = { date: forecastDate3, + dayIndex: '3', + condition: this.config.entity_forecast_icon_3, + temphigh: this.config.entity_forecast_high_temp_3, + templow: this.config.entity_forecast_low_temp_3, + pop: this.config.entity_pop_3, + summary: this.config.entity_summary_3, }; + const forecast4 = { date: forecastDate4, + dayIndex: '4', + condition: this.config.entity_forecast_icon_4, + temphigh: this.config.entity_forecast_high_temp_4, + templow: this.config.entity_forecast_low_temp_4, + pop: this.config.entity_pop_4, + summary: this.config.entity_summary_4, }; + const forecast5 = { date: forecastDate5, + dayIndex: '5', + condition: this.config.entity_forecast_icon_5, + temphigh: this.config.entity_forecast_high_temp_5, + templow: this.config.entity_forecast_low_temp_5, + pop: this.config.entity_pop_5, + summary: this.config.entity_summary_5, }; + + return [forecast1, forecast2, forecast3, forecast4, forecast5]; + } + + +// ##### +// ##### current : Returns current weather information +// ##### + + get current() { + var conditions = this._hass.states[this.config.entity_current_conditions].state; + var humidity = this.config.entity_humidity ? this._hass.states[this.config.entity_humidity].state : 0; + var pressure = this.config.entity_pressure ? Math.round(this._hass.states[this.config.entity_pressure].state) : 0; + var temperature = Math.round(this._hass.states[this.config.entity_temperature].state); + var visibility = this.config.entity_visibility ? this._hass.states[this.config.entity_visibility].state : 0; + var windBearing = this.config.entity_wind_bearing ? isNaN(this._hass.states[this.config.entity_wind_bearing].state) ? this._hass.states[this.config.entity_wind_bearing].state : this.windDirections[(Math.round((this._hass.states[this.config.entity_wind_bearing].state / 360) * 16))] : 0; + var windSpeed = this.config.entity_wind_speed ? Math.round(this._hass.states[this.config.entity_wind_speed].state) : 0; + var apparent = this.config.entity_apparent_temp ? Math.round(this._hass.states[this.config.entity_apparent_temp].state) : 0; + var beaufort = this.config.show_beaufort ? html`Bft: ${this.beaufortWind} - ` : ``; + + return { + 'conditions': conditions, + 'humidity': humidity, + 'pressure': pressure, + 'temperature': temperature, + 'visibility': visibility, + 'windBearing': windBearing, + 'windSpeed': windSpeed, + 'apparent' : apparent, + 'beaufort' : beaufort, + } + } + +// ##### +// ##### sunSetAndRise: returns set and rise information +// ##### + +get sunSet() { + var nextSunSet ; + var nextSunRise; + if (this.config.time_format) { + nextSunSet = new Date(this._hass.states[this.config.entity_sun].attributes.next_setting).toLocaleTimeString(this.config.locale, {hour: '2-digit', minute:'2-digit',hour12: this.is12Hour}); + nextSunRise = new Date(this._hass.states[this.config.entity_sun].attributes.next_rising).toLocaleTimeString(this.config.locale, {hour: '2-digit', minute:'2-digit', hour12: this.is12Hour}); + } + else { + nextSunSet = new Date(this._hass.states[this.config.entity_sun].attributes.next_setting).toLocaleTimeString(this.config.locale, {hour: '2-digit', minute:'2-digit'}); + nextSunRise = new Date(this._hass.states[this.config.entity_sun].attributes.next_rising).toLocaleTimeString(this.config.locale, {hour: '2-digit', minute:'2-digit'}); + } + var nextDate = new Date(); + nextDate.setDate(nextDate.getDate() + 1); + if (this._hass.states[this.config.entity_sun].state == "above_horizon" ) { + nextSunRise = nextDate.toLocaleDateString(this.config.locale,{weekday: 'short'}) + " " + nextSunRise; + return { + 'next': html`
  • ${nextSunSet}
  • `, + 'following': html`
  • ${nextSunRise}
  • `, + 'nextText': nextSunSet, + 'followingText': nextSunRise, + }; + } else { + if (new Date().getDate() != new Date(this._hass.states[this.config.entity_sun].attributes.next_rising).getDate()) { + nextSunRise = nextDate.toLocaleDateString(this.config.locale,{weekday: 'short'}) + " " + nextSunRise; + nextSunSet = nextDate.toLocaleDateString(this.config.locale,{weekday: 'short'}) + " " + nextSunSet; + } + return { + 'next': html`
  • ${nextSunRise}
  • `, + 'following': html`
  • ${nextSunSet}
  • `, + 'nextText': nextSunRise, + 'followingText': nextSunSet, + }; + } +} + + +// ##### +// ##### beaufortWind - returns the wind speed on th beaufort scale +// ##### + +get beaufortWind() { + if (this.config.entity_wind_speed) { + switch (this._hass.states[this.config.entity_wind_speed].attributes.unit_of_measurement) { + case 'mph': + if (this._hass.states[this.config.entity_wind_speed].state >= 73) return 12; + if (this._hass.states[this.config.entity_wind_speed].state >= 64) return 11; + if (this._hass.states[this.config.entity_wind_speed].state >= 55) return 10; + if (this._hass.states[this.config.entity_wind_speed].state >= 47) return 9; + if (this._hass.states[this.config.entity_wind_speed].state >= 39) return 8; + if (this._hass.states[this.config.entity_wind_speed].state >= 31) return 7; + if (this._hass.states[this.config.entity_wind_speed].state >= 25) return 6; + if (this._hass.states[this.config.entity_wind_speed].state >= 18) return 5; + if (this._hass.states[this.config.entity_wind_speed].state >= 13) return 4; + if (this._hass.states[this.config.entity_wind_speed].state >= 8) return 3; + if (this._hass.states[this.config.entity_wind_speed].state >=3) return 2; + if (this._hass.states[this.config.entity_wind_speed].state >= 1) return 1; + default: // Assume m/s + if ((this._hass.states[this.config.entity_wind_speed].state * 3.6) >= 118) return 12; + if ((this._hass.states[this.config.entity_wind_speed].state * 3.6) >= 103) return 11; + if ((this._hass.states[this.config.entity_wind_speed].state * 3.6) >= 89) return 10; + if ((this._hass.states[this.config.entity_wind_speed].state * 3.6) >= 75) return 9; + if ((this._hass.states[this.config.entity_wind_speed].state * 3.6) >= 62) return 8; + if ((this._hass.states[this.config.entity_wind_speed].state * 3.6) >= 50) return 7; + if ((this._hass.states[this.config.entity_wind_speed].state * 3.6) >= 39) return 6; + if ((this._hass.states[this.config.entity_wind_speed].state * 3.6) >= 29) return 5; + if ((this._hass.states[this.config.entity_wind_speed].state * 3.6) >= 20) return 4; + if ((this._hass.states[this.config.entity_wind_speed].state * 3.6) >= 12) return 3; + if ((this._hass.states[this.config.entity_wind_speed].state * 3.6) >=6) return 2; + if ((this._hass.states[this.config.entity_wind_speed].state * 3.6) >= 1) return 1; + } + } + return 0; +} + + +// ##### +// ##### is12Hour - returns true if 12 hour clock or false if 24 +// ##### + +get is12Hour() { + var hourFormat= this.config.time_format ? this.config.time_format : 12 + switch (hourFormat) { + case 24: + return false; + default: + return true; + } +} + + +// ##### +// ##### style: returns the CSS style classes for the card +// #### + +style() { + + // Get config flags or set defaults if not configured + var tooltipBGColor = this.config.tooltip_bg_color || "rgb( 75,155,239)"; + var tooltipFGColor = this.config.tooltip_fg_color || "#fff"; + var tooltipBorderColor = this.config.tooltip_border_color || "rgb(255,161,0)"; + var tooltipBorderWidth = this.config.tooltip_border_width || "1"; + var tooltipCaretSize = this.config.tooltip_caret_size || "5"; + var tooltipWidth = this.config.tooltip_width || "110"; + var tooltipLeftOffset = this.config.tooltip_left_offset || "-12"; + var tooltipVisible = this.config.tooltips ? "visible" : "hidden"; + var tempTopMargin = this.config.temp_top_margin || "0.05em"; + var tempFontWeight = this.config.temp_font_weight || "300"; + var tempFontSize = this.config.temp_font_size || "4em"; + var tempRightPos = this.config.temp_right_pos || "0.85em"; + var tempUOMTopMargin = this.config.temp_uom_top_margin || "-9px"; + var tempUOMRightMargin = this.config.temp_uom_right_margin || "7px"; + var apparentTopMargin = this.config.apparent_top_margin || "45px"; + var apparentRightPos = this.config.apparent_right_pos || "1em"; + var apparentRightMargin = this.config.apparent_right_margin || "1em"; + var currentTextTopMargin = this.config.current_text_top_margin || "39px"; + var currentTextLeftPos = this.config.current_text_left_pos || "5em"; + var currentTextFontSize = this.config.current_text_font_size || "1.5em"; + var largeIconTopMargin = this.config.large_icon_top_margin || "-3.5em"; + var largeIconLeftPos = this.config.large_icon_left_pos || "0em"; + var currentDataTopMargin = this.config.current_data_top_margin ? this.config.current_data_top_margin : this.config.show_separator ? "1em" : "7em"; + var separatorTopMargin = this.config.separator_top_margin || "6em"; + + return html` + .clear { + clear: both; + } + + .card { + margin: auto; + padding-top: 2em; + padding-bottom: 1em; + padding-left: 1em; + padding-right: 1em; + position: relative; + } + + .ha-icon { + height: 18px; + margin-right: 5px; + color: var(--paper-item-icon-color); + } + + .line { + margin-top: ${separatorTopMargin}; + margin-left: 1em; + margin-right: 1em; + } + + .temp { + font-weight: ${tempFontWeight}; + font-size: ${tempFontSize}; + color: var(--primary-text-color); + position: absolute; + right: ${tempRightPos}; + margin-top: ${tempTopMargin}; + } + + .tempc { + font-weight: ${tempFontWeight}; + font-size: 1.5em; + vertical-align: super; + color: var(--primary-text-color); + position: absolute; + right: 1em; + margin-top: ${tempUOMTopMargin}; + margin-right: ${tempUOMRightMargin}; + } + + .apparent { + color: var(--primary-text-color); + position: absolute; + right: ${apparentRightPos}; + margin-top: ${apparentTopMargin}; + margin-right: ${apparentRightMargin}; + } + + .currentText { + font-size: ${currentTextFontSize}; + color: var(--secondary-text-color); + position: absolute; + left: ${currentTextLeftPos}; + margin-top: ${currentTextTopMargin}; + } + + .pop { + font-weight: 400; + color: var(--primary-text-color); + } + + .variations { + display: flex; + flex-flow: row wrap; + justify-content: space-between; + font-weight: 300; + color: var(--primary-text-color); + list-style: none; + padding: 0.2em; + margin-top: ${currentDataTopMargin}; + } + + .unit { + font-size: 0.8em; + } + + .forecast { + width: 100%; + margin: 0 auto; + height: 9em; + } + + .day { + display: block; + width: 20%; + float: left; + text-align: center; + color: var(--primary-text-color); + border-right: .1em solid #d9d9d9; + line-height: 1.5; + box-sizing: border-box; + margin-top: 1em; + } + + .dayname { + text-transform: uppercase; + } + + .forecast .day:first-child { + margin-left: 20; + } + + .forecast .day:nth-last-child(1) { + border-right: none; + margin-right: 0; + } + + .highTemp { + font-weight: bold; + } + + .lowTemp { + color: var(--secondary-text-color); + } + + .icon.bigger { + width: 10em; + height: 10em; + margin-top: ${largeIconTopMargin}; + position: absolute; + left: ${largeIconLeftPos}; + } + + .icon { + width: 50px; + height: 50px; + margin-right: 5px; + display: inline-block; + vertical-align: middle; + background-size: contain; + background-position: center center; + background-repeat: no-repeat; + text-indent: -9999px; + } + + .weather { + font-weight: 300; + font-size: 1.5em; + color: var(--primary-text-color); + text-align: left; + position: absolute; + top: -0.5em; + left: 6em; + word-wrap: break-word; + width: 30%; + } + + .fcasttooltip { + position: relative; + display: inline-block; + } + + .fcasttooltip .fcasttooltiptext { + visibility: hidden; + width: ${tooltipWidth}px; + background-color: ${tooltipBGColor}; + color: ${tooltipFGColor}; + text-align: center; + border-radius: 6px; + border-style: solid; + border-color: ${tooltipBorderColor}; + border-width: ${tooltipBorderWidth}px; + padding: 5px 0; + + /* Position the tooltip */ + position: absolute; + z-index: 1; + bottom: 50%; + left: 0%; + margin-left: ${tooltipLeftOffset}px; + } + + .fcasttooltip .fcasttooltiptext:after { + content: ""; + position: absolute; + top: 100%; + left: 50%; + margin-left: -${tooltipCaretSize}px; + border-width: ${tooltipCaretSize}px; + border-style: solid; + border-color: ${tooltipBorderColor} transparent transparent transparent; + } + + .fcasttooltip:hover .fcasttooltiptext { + visibility: ${tooltipVisible}; + } + ` +} + +// ##### +// ##### getUOM: gets UOM for specified measure in either metric or imperial +// ##### + + getUOM(measure) { + + const lengthUnit = this._hass.config.unit_system.length; + + switch (measure) { + case 'air_pressure': + return lengthUnit === 'km' ? 'hPa' : 'mbar'; + case 'length': + return lengthUnit; + case 'precipitation': + return lengthUnit === 'km' ? 'mm' : 'in'; + case 'intensity': + return lengthUnit === 'km' ? 'mm/h' : 'in/h' + default: + return this._hass.config.unit_system[measure] || ''; + } + } + +// ##### +// ##### Assign the external hass object to an internal class var. +// ##### This is called everytime a state change occurs in HA +// ##### + + set hass(hass) { + + var interval = this.config.refresh_interval || 30; + var doRefresh = false; + + // Make sure hass is assigned first time. + if (!this._initialized) { + this._initialized= true; + this._lasRefresh = new Date(); + doRefresh = true; + } + + var now = new Date(); + + // Check if refresh interval has been exceeded and refresh if necessary + if (Math.round((now - this._lastRefresh)/1000) > interval ) { doRefresh = true; } + + if (doRefresh) { + this._lastRefresh = new Date(); + this._hass = hass; + this.updateValues(); + } + } + + +// ##### +// updateValues - Updates card values as changes happen in the hass object +// ##### + + updateValues() { + const root = this.shadowRoot; + if (root.childElementCount > 0) { + +// Current Conditions + root.getElementById("temperature-text").textContent = `${this.current.temperature}`; + root.getElementById("icon-bigger").textContent = `${this.current.conditions}`; + root.getElementById("icon-bigger").style.backgroundImage = `none, url(/local/icons/weather_icons/${this.config.static_icons ? "static" : "animated"}/${this.weatherIcons[this.current.conditions]}.svg)`; + +// Forecast blocks + this.forecast.forEach((daily) => { + root.getElementById("fcast-dayName-" + daily.dayIndex).textContent = `${(daily.date).toLocaleDateString(this.config.locale,{weekday: 'short'})}`; + root.getElementById("fcast-icon-" + daily.dayIndex).style.backgroundImage = `none, url(/local/icons/weather_icons/${this.config.static_icons ? "static" : "animated"}/${this.weatherIcons[this._hass.states[daily.condition].state]}.svg`; + root.getElementById("fcast-high-" + daily.dayIndex).textContent = `${Math.round(this._hass.states[daily.temphigh].state)}${this.getUOM("temperature")}`; + root.getElementById("fcast-low-" + daily.dayIndex).textContent = `${Math.round(this._hass.states[daily.templow].state)}${this.config.old_daily_format ? this.getUOM("temperature") : ""}`; + if (this.config.entity_pop_1 && this.config.entity_pop_2 && this.config.entity_pop_3 && this.config.entity_pop_4 && this.config.entity_pop_5) { root.getElementById("fcast-pop-" + daily.dayIndex).textContent = `${Math.round(this._hass.states[daily.pop].state)} %` } + root.getElementById("fcast-summary-" + daily.dayIndex).textContent = `${this._hass.states[daily.summary].state}`; + }); + +// Optional Entities + if (this.config.entity_current_text) { root.getElementById("current-text").textContent = `${this._hass.states[this.config.entity_current_text].state}` } + if (this.config.entity_apparent_temp) { root.getElementById("apparent-text").textContent = `${this.current.apparent}` } + if (this.config.entity_pressure && !this.config.alt_pressure) { root.getElementById("pressure-text").textContent = `${this.current.pressure}` } + if (this.config.entity_humidity && !this.config.alt_humidity) { root.getElementById("humidity-text").textContent = `${this.current.humidity}` } + if (this.config.show_beaufort && !this.config.alt_wind) { root.getElementById("beaufort-text").textContent = `Bft: ${this.beaufortWind} - ` } + if (this.config.entity_wind_bearing && !this.config.alt_wind) { root.getElementById("wind-bearing-text").textContent = `${this.current.windBearing} ` } + if (this.config.entity_wind_speed && !this.config.alt_wind) { root.getElementById("wind-speed-text").textContent = `${this.current.windSpeed}` } + if (this.config.entity_visibility && !this.config.alt_visibility) { root.getElementById("visibility-text").textContent = `${this.current.visibility}` } + if (this.config.entity_pop && !this.config.alt_pop) { root.getElementById("pop-text").textContent = `${Math.round(this._hass.states[this.config.entity_pop].state)}` } + if (this.config.entity_pop_intensity && !this.config.alt_pop) { root.getElementById("pop-intensity-text").textContent = ` - ${this._hass.states[this.config.entity_pop_intensity].state} ${this.getUOM('intensity')}` } + if (this.config.entity_daytime_high && !this.config.alt_daytime_high) { root.getElementById("daytime-high-text").textContent = `${Math.round(this._hass.states[this.config.entity_daytime_high].state)}` } + if (this.config.entity_sun && !this.config.alt_sun_next) { root.getElementById("sun-next-text").textContent = `${this.sunSet.nextText}` } + if (this.config.entity_sun && !this.config.alt_sun_following) { root.getElementById("sun-following-text").textContent = `${this.sunSet.followingText}` } + if (this.config.entity_daily_summary) { root.getElementById("daily-summary-text").textContent = `${this._hass.states[this.config.entity_daily_summary].state}` } + if (this.config.entity_precip_intensity_current) { root.getElementById("intensity_current-text").textContent = `${this._hass.states[this.config.entity_precip_intensity_current].state}` } + if (this.config.entity_precip_current) { root.getElementById("precip_current-text").textContent = `${this._hass.states[this.config.entity_precip_current].state} ` } + + +// Alt Text + if (this.config.alt_sun_next) { root.getElementById("alt-sun-next").textContent = `${this._hass.states[this.config.alt_sun_next].state}` } + if (this.config.alt_sun_following) { root.getElementById("alt-sun-following").textContent = `${this._hass.states[this.config.alt_sun_following].state}` } + if (this.config.alt_pop) { root.getElementById("alt-pop").textContent = `${this._hass.states[this.config.alt_pop].state}` } + if (this.config.alt_wind) { root.getElementById("alt-wind").textContent = `${this._hass.states[this.config.alt_wind].state}` } + if (this.config.alt_pressure) { root.getElementById("alt-pressure").textContent = `${this._hass.states[this.config.alt_pressure].state}` } + if (this.config.alt_humidity) { root.getElementById("alt-humidity").textContent = `${this._hass.states[this.config.alt_humidity].state}` } + if (this.config.alt_daytime_high) { root.getElementById("alt-daytime-high").textContent = `${this._hass.states[this.config.alt_daytime_high].state}` } + if (this.config.alt_visibility) { root.getElementById("alt-visibility").textContent = `${this._hass.states[this.config.alt_visibility].state}` } + } + } + + +// ##### +// ##### Assigns the configuration vlaues to an internal class var +// ##### This is called everytime a config change is made +// ##### + + setConfig(config) { this.config = config; } + + +// ##### +// ##### Sets the card size so HA knows how to put in columns +// ##### + + getCardSize() { return 3 } + +} + +// ##### +// ##### Register the card as a customElement +// ##### +customElements.define('lovelace-darksky-card', DarkSkyWeatherCard); + diff --git a/www/community/lovelace-time-picker-card/time-picker-card.js b/www/community/lovelace-time-picker-card/time-picker-card.js new file mode 100644 index 0000000..958e417 --- /dev/null +++ b/www/community/lovelace-time-picker-card/time-picker-card.js @@ -0,0 +1,528 @@ +/*! ***************************************************************************** +Copyright (c) Microsoft Corporation. All rights reserved. +Licensed under the Apache License, Version 2.0 (the "License"); you may not use +this file except in compliance with the License. You may obtain a copy of the +License at http://www.apache.org/licenses/LICENSE-2.0 + +THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED +WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, +MERCHANTABLITY OR NON-INFRINGEMENT. + +See the Apache Version 2.0 License for specific language governing permissions +and limitations under the License. +***************************************************************************** */ +function e(e,t,i,n){var s,r=arguments.length,o=r<3?t:null===n?n=Object.getOwnPropertyDescriptor(t,i):n;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)o=Reflect.decorate(e,t,i,n);else for(var a=e.length-1;a>=0;a--)(s=e[a])&&(o=(r<3?s(o):r>3?s(t,i,o):s(t,i))||o);return r>3&&o&&Object.defineProperty(t,i,o),o}var t=/d{1,4}|M{1,4}|YY(?:YY)?|S{1,3}|Do|ZZ|Z|([HhMsDm])\1?|[aA]|"[^"]*"|'[^']*'/g,i="[^\\s]+",n=/\[([^]*?)\]/gm;function s(e,t){for(var i=[],n=0,s=e.length;n-1?n:null}};function o(e){for(var t=[],i=1;i3?0:(e-e%10!=10?1:0)*e%10]}},h=o({},l),u=function(e,t){for(void 0===t&&(t=2),e=String(e);e.length0?"-":"+")+u(100*Math.floor(Math.abs(t)/60)+Math.abs(t)%60,4)},Z:function(e){var t=e.getTimezoneOffset();return(t>0?"-":"+")+u(Math.floor(Math.abs(t)/60),2)+":"+u(Math.abs(t)%60,2)}},m=function(e){return+e-1},g=[null,"[1-9]\\d?"],f=[null,i],v=["isPm",i,function(e,t){var i=e.toLowerCase();return i===t.amPm[0]?0:i===t.amPm[1]?1:null}],y=["timezoneOffset","[^\\s]*?[\\+\\-]\\d\\d:?\\d\\d|[^\\s]*?Z?",function(e){var t=(e+"").match(/([+-]|\d\d)/gi);if(t){var i=60*+t[1]+parseInt(t[2],10);return"+"===t[0]?i:-i}return 0}],_=(r("monthNamesShort"),r("monthNames"),{default:"ddd MMM DD YYYY HH:mm:ss",shortDate:"M/D/YY",mediumDate:"MMM D, YYYY",longDate:"MMMM D, YYYY",fullDate:"dddd, MMMM D, YYYY",isoDate:"YYYY-MM-DD",isoDateTime:"YYYY-MM-DDTHH:mm:ssZ",shortTime:"HH:mm",mediumTime:"HH:mm:ss",longTime:"HH:mm:ss.SSS"});var S=function(e,i,s){if(void 0===i&&(i=_.default),void 0===s&&(s={}),"number"==typeof e&&(e=new Date(e)),"[object Date]"!==Object.prototype.toString.call(e)||isNaN(e.getTime()))throw new Error("Invalid Date pass to format");var r=[];i=(i=_[i]||i).replace(n,(function(e,t){return r.push(t),"@@@"}));var a=o(o({},h),s);return(i=i.replace(t,(function(t){return p[t](e,a)}))).replace(/@@@/g,(function(){return r.shift()}))};(function(){try{(new Date).toLocaleDateString("i")}catch(e){return"RangeError"===e.name}})(),function(){try{(new Date).toLocaleString("i")}catch(e){return"RangeError"===e.name}}(),function(){try{(new Date).toLocaleTimeString("i")}catch(e){return"RangeError"===e.name}}();function b(e){return e.substr(0,e.indexOf("."))} +/** + * @license + * Copyright (c) 2017 The Polymer Project Authors. All rights reserved. + * This code may only be used under the BSD style license found at + * http://polymer.github.io/LICENSE.txt + * The complete set of authors may be found at + * http://polymer.github.io/AUTHORS.txt + * The complete set of contributors may be found at + * http://polymer.github.io/CONTRIBUTORS.txt + * Code distributed by Google as part of the polymer project is also + * subject to an additional IP rights grant found at + * http://polymer.github.io/PATENTS.txt + */const w="undefined"!=typeof window&&null!=window.customElements&&void 0!==window.customElements.polyfillWrapFlushCallback,x=(e,t,i=null)=>{for(;t!==i;){const i=t.nextSibling;e.removeChild(t),t=i}},C=`{{lit-${String(Math.random()).slice(2)}}}`,E=`\x3c!--${C}--\x3e`,N=new RegExp(`${C}|${E}`);class P{constructor(e,t){this.parts=[],this.element=t;const i=[],n=[],s=document.createTreeWalker(t.content,133,null,!1);let r=0,o=-1,a=0;const{strings:c,values:{length:d}}=e;for(;a0;){const t=c[a],i=O.exec(t)[2],n=i.toLowerCase()+"$lit$",s=e.getAttribute(n);e.removeAttribute(n);const r=s.split(N);this.parts.push({type:"attribute",index:o,name:i,strings:r}),a+=r.length-1}}"TEMPLATE"===e.tagName&&(n.push(e),s.currentNode=e.content)}else if(3===e.nodeType){const t=e.data;if(t.indexOf(C)>=0){const n=e.parentNode,s=t.split(N),r=s.length-1;for(let t=0;t{const i=e.length-t.length;return i>=0&&e.slice(i)===t},T=e=>-1!==e.index,k=()=>document.createComment(""),O=/([ \x09\x0a\x0c\x0d])([^\0-\x1F\x7F-\x9F "'>=/]+)([ \x09\x0a\x0c\x0d]*=[ \x09\x0a\x0c\x0d]*(?:[^ \x09\x0a\x0c\x0d"'`<>=]*|"[^"]*|'[^']*))$/;function $(e,t){const{element:{content:i},parts:n}=e,s=document.createTreeWalker(i,133,null,!1);let r=A(n),o=n[r],a=-1,c=0;const d=[];let l=null;for(;s.nextNode();){a++;const e=s.currentNode;for(e.previousSibling===l&&(l=null),t.has(e)&&(d.push(e),null===l&&(l=e)),null!==l&&c++;void 0!==o&&o.index===a;)o.index=null!==l?-1:o.index-c,r=A(n,r),o=n[r]}d.forEach(e=>e.parentNode.removeChild(e))}const V=e=>{let t=11===e.nodeType?0:1;const i=document.createTreeWalker(e,133,null,!1);for(;i.nextNode();)t++;return t},A=(e,t=-1)=>{for(let i=t+1;i"function"==typeof e&&H.has(e),I={},U={}; +/** + * @license + * Copyright (c) 2017 The Polymer Project Authors. All rights reserved. + * This code may only be used under the BSD style license found at + * http://polymer.github.io/LICENSE.txt + * The complete set of authors may be found at + * http://polymer.github.io/AUTHORS.txt + * The complete set of contributors may be found at + * http://polymer.github.io/CONTRIBUTORS.txt + * Code distributed by Google as part of the polymer project is also + * subject to an additional IP rights grant found at + * http://polymer.github.io/PATENTS.txt + */ +class D{constructor(e,t,i){this.__parts=[],this.template=e,this.processor=t,this.options=i}update(e){let t=0;for(const i of this.__parts)void 0!==i&&i.setValue(e[t]),t++;for(const e of this.__parts)void 0!==e&&e.commit()}_clone(){const e=w?this.template.element.content.cloneNode(!0):document.importNode(this.template.element.content,!0),t=[],i=this.template.parts,n=document.createTreeWalker(e,133,null,!1);let s,r=0,o=0,a=n.nextNode();for(;re}),R=` ${C} `;class Y{constructor(e,t,i,n){this.strings=e,this.values=t,this.type=i,this.processor=n}getHTML(){const e=this.strings.length-1;let t="",i=!1;for(let n=0;n-1||i)&&-1===e.indexOf("--\x3e",s+1);const r=O.exec(e);t+=null===r?e+(i?R:E):e.substr(0,r.index)+r[1]+r[2]+"$lit$"+r[3]+C}return t+=this.strings[e],t}getTemplateElement(){const e=document.createElement("template");let t=this.getHTML();return void 0!==L&&(t=L.createHTML(t)),e.innerHTML=t,e}} +/** + * @license + * Copyright (c) 2017 The Polymer Project Authors. All rights reserved. + * This code may only be used under the BSD style license found at + * http://polymer.github.io/LICENSE.txt + * The complete set of authors may be found at + * http://polymer.github.io/AUTHORS.txt + * The complete set of contributors may be found at + * http://polymer.github.io/CONTRIBUTORS.txt + * Code distributed by Google as part of the polymer project is also + * subject to an additional IP rights grant found at + * http://polymer.github.io/PATENTS.txt + */const z=e=>null===e||!("object"==typeof e||"function"==typeof e),F=e=>Array.isArray(e)||!(!e||!e[Symbol.iterator]);class B{constructor(e,t,i){this.dirty=!0,this.element=e,this.name=t,this.strings=i,this.parts=[];for(let e=0;e{try{const e={get capture(){return K=!0,!1}};window.addEventListener("test",e,e),window.removeEventListener("test",e,e)}catch(e){}})();class Q{constructor(e,t,i){this.value=void 0,this.__pendingValue=void 0,this.element=e,this.eventName=t,this.eventContext=i,this.__boundHandleEvent=e=>this.handleEvent(e)}setValue(e){this.__pendingValue=e}commit(){for(;j(this.__pendingValue);){const e=this.__pendingValue;this.__pendingValue=I,e(this)}if(this.__pendingValue===I)return;const e=this.__pendingValue,t=this.value,i=null==e||null!=t&&(e.capture!==t.capture||e.once!==t.once||e.passive!==t.passive),n=null!=e&&(null==t||i);i&&this.element.removeEventListener(this.eventName,this.__boundHandleEvent,this.__options),n&&(this.__options=X(e),this.element.addEventListener(this.eventName,this.__boundHandleEvent,this.__options)),this.value=e,this.__pendingValue=I}handleEvent(e){"function"==typeof this.value?this.value.call(this.eventContext||this.element,e):this.value.handleEvent(e)}}const X=e=>e&&(K?{capture:e.capture,passive:e.passive,once:e.once}:e.capture) +/** + * @license + * Copyright (c) 2017 The Polymer Project Authors. All rights reserved. + * This code may only be used under the BSD style license found at + * http://polymer.github.io/LICENSE.txt + * The complete set of authors may be found at + * http://polymer.github.io/AUTHORS.txt + * The complete set of contributors may be found at + * http://polymer.github.io/CONTRIBUTORS.txt + * Code distributed by Google as part of the polymer project is also + * subject to an additional IP rights grant found at + * http://polymer.github.io/PATENTS.txt + */;function ee(e){let t=te.get(e.type);void 0===t&&(t={stringsArray:new WeakMap,keyString:new Map},te.set(e.type,t));let i=t.stringsArray.get(e.strings);if(void 0!==i)return i;const n=e.strings.join(C);return i=t.keyString.get(n),void 0===i&&(i=new P(e,e.getTemplateElement()),t.keyString.set(n,i)),t.stringsArray.set(e.strings,i),i}const te=new Map,ie=new WeakMap; +/** + * @license + * Copyright (c) 2017 The Polymer Project Authors. All rights reserved. + * This code may only be used under the BSD style license found at + * http://polymer.github.io/LICENSE.txt + * The complete set of authors may be found at + * http://polymer.github.io/AUTHORS.txt + * The complete set of contributors may be found at + * http://polymer.github.io/CONTRIBUTORS.txt + * Code distributed by Google as part of the polymer project is also + * subject to an additional IP rights grant found at + * http://polymer.github.io/PATENTS.txt + */const ne=new +/** + * @license + * Copyright (c) 2017 The Polymer Project Authors. All rights reserved. + * This code may only be used under the BSD style license found at + * http://polymer.github.io/LICENSE.txt + * The complete set of authors may be found at + * http://polymer.github.io/AUTHORS.txt + * The complete set of contributors may be found at + * http://polymer.github.io/CONTRIBUTORS.txt + * Code distributed by Google as part of the polymer project is also + * subject to an additional IP rights grant found at + * http://polymer.github.io/PATENTS.txt + */ +class{handleAttributeExpressions(e,t,i,n){const s=t[0];if("."===s){return new J(e,t.slice(1),i).parts}if("@"===s)return[new Q(e,t.slice(1),n.eventContext)];if("?"===s)return[new G(e,t.slice(1),i)];return new B(e,t,i).parts}handleTextExpression(e){return new W(e)}}; +/** + * @license + * Copyright (c) 2017 The Polymer Project Authors. All rights reserved. + * This code may only be used under the BSD style license found at + * http://polymer.github.io/LICENSE.txt + * The complete set of authors may be found at + * http://polymer.github.io/AUTHORS.txt + * The complete set of contributors may be found at + * http://polymer.github.io/CONTRIBUTORS.txt + * Code distributed by Google as part of the polymer project is also + * subject to an additional IP rights grant found at + * http://polymer.github.io/PATENTS.txt + */"undefined"!=typeof window&&(window.litHtmlVersions||(window.litHtmlVersions=[])).push("1.3.0");const se=(e,...t)=>new Y(e,t,"html",ne) +/** + * @license + * Copyright (c) 2017 The Polymer Project Authors. All rights reserved. + * This code may only be used under the BSD style license found at + * http://polymer.github.io/LICENSE.txt + * The complete set of authors may be found at + * http://polymer.github.io/AUTHORS.txt + * The complete set of contributors may be found at + * http://polymer.github.io/CONTRIBUTORS.txt + * Code distributed by Google as part of the polymer project is also + * subject to an additional IP rights grant found at + * http://polymer.github.io/PATENTS.txt + */,re=(e,t)=>`${e}--${t}`;let oe=!0;void 0===window.ShadyCSS?oe=!1:void 0===window.ShadyCSS.prepareTemplateDom&&(console.warn("Incompatible ShadyCSS version detected. Please update to at least @webcomponents/webcomponentsjs@2.0.2 and @webcomponents/shadycss@1.3.1."),oe=!1);const ae=e=>t=>{const i=re(t.type,e);let n=te.get(i);void 0===n&&(n={stringsArray:new WeakMap,keyString:new Map},te.set(i,n));let s=n.stringsArray.get(t.strings);if(void 0!==s)return s;const r=t.strings.join(C);if(s=n.keyString.get(r),void 0===s){const i=t.getTemplateElement();oe&&window.ShadyCSS.prepareTemplateDom(i,e),s=new P(t,i),n.keyString.set(r,s)}return n.stringsArray.set(t.strings,s),s},ce=["html","svg"],de=new Set,le=(e,t,i)=>{de.add(e);const n=i?i.element:document.createElement("template"),s=t.querySelectorAll("style"),{length:r}=s;if(0===r)return void window.ShadyCSS.prepareTemplateStyles(n,e);const o=document.createElement("style");for(let e=0;e{ce.forEach(t=>{const i=te.get(re(t,e));void 0!==i&&i.keyString.forEach(e=>{const{element:{content:t}}=e,i=new Set;Array.from(t.querySelectorAll("style")).forEach(e=>{i.add(e)}),$(e,i)})})})(e);const a=n.content;i?function(e,t,i=null){const{element:{content:n},parts:s}=e;if(null==i)return void n.appendChild(t);const r=document.createTreeWalker(n,133,null,!1);let o=A(s),a=0,c=-1;for(;r.nextNode();){c++;for(r.currentNode===i&&(a=V(t),i.parentNode.insertBefore(t,i));-1!==o&&s[o].index===c;){if(a>0){for(;-1!==o;)s[o].index+=a,o=A(s,o);return}o=A(s,o)}}}(i,o,a.firstChild):a.insertBefore(o,a.firstChild),window.ShadyCSS.prepareTemplateStyles(n,e);const c=a.querySelector("style");if(window.ShadyCSS.nativeShadow&&null!==c)t.insertBefore(c.cloneNode(!0),t.firstChild);else if(i){a.insertBefore(o,a.firstChild);const e=new Set;e.add(o),$(i,e)}};window.JSCompiler_renameProperty=(e,t)=>e;const he={toAttribute(e,t){switch(t){case Boolean:return e?"":null;case Object:case Array:return null==e?e:JSON.stringify(e)}return e},fromAttribute(e,t){switch(t){case Boolean:return null!==e;case Number:return null===e?null:Number(e);case Object:case Array:return JSON.parse(e)}return e}},ue=(e,t)=>t!==e&&(t==t||e==e),pe={attribute:!0,type:String,converter:he,reflect:!1,hasChanged:ue};class me extends HTMLElement{constructor(){super(),this.initialize()}static get observedAttributes(){this.finalize();const e=[];return this._classProperties.forEach((t,i)=>{const n=this._attributeNameForProperty(i,t);void 0!==n&&(this._attributeToPropertyMap.set(n,i),e.push(n))}),e}static _ensureClassProperties(){if(!this.hasOwnProperty(JSCompiler_renameProperty("_classProperties",this))){this._classProperties=new Map;const e=Object.getPrototypeOf(this)._classProperties;void 0!==e&&e.forEach((e,t)=>this._classProperties.set(t,e))}}static createProperty(e,t=pe){if(this._ensureClassProperties(),this._classProperties.set(e,t),t.noAccessor||this.prototype.hasOwnProperty(e))return;const i="symbol"==typeof e?Symbol():"__"+e,n=this.getPropertyDescriptor(e,i,t);void 0!==n&&Object.defineProperty(this.prototype,e,n)}static getPropertyDescriptor(e,t,i){return{get(){return this[t]},set(n){const s=this[e];this[t]=n,this.requestUpdateInternal(e,s,i)},configurable:!0,enumerable:!0}}static getPropertyOptions(e){return this._classProperties&&this._classProperties.get(e)||pe}static finalize(){const e=Object.getPrototypeOf(this);if(e.hasOwnProperty("finalized")||e.finalize(),this.finalized=!0,this._ensureClassProperties(),this._attributeToPropertyMap=new Map,this.hasOwnProperty(JSCompiler_renameProperty("properties",this))){const e=this.properties,t=[...Object.getOwnPropertyNames(e),..."function"==typeof Object.getOwnPropertySymbols?Object.getOwnPropertySymbols(e):[]];for(const i of t)this.createProperty(i,e[i])}}static _attributeNameForProperty(e,t){const i=t.attribute;return!1===i?void 0:"string"==typeof i?i:"string"==typeof e?e.toLowerCase():void 0}static _valueHasChanged(e,t,i=ue){return i(e,t)}static _propertyValueFromAttribute(e,t){const i=t.type,n=t.converter||he,s="function"==typeof n?n:n.fromAttribute;return s?s(e,i):e}static _propertyValueToAttribute(e,t){if(void 0===t.reflect)return;const i=t.type,n=t.converter;return(n&&n.toAttribute||he.toAttribute)(e,i)}initialize(){this._updateState=0,this._updatePromise=new Promise(e=>this._enableUpdatingResolver=e),this._changedProperties=new Map,this._saveInstanceProperties(),this.requestUpdateInternal()}_saveInstanceProperties(){this.constructor._classProperties.forEach((e,t)=>{if(this.hasOwnProperty(t)){const e=this[t];delete this[t],this._instanceProperties||(this._instanceProperties=new Map),this._instanceProperties.set(t,e)}})}_applyInstanceProperties(){this._instanceProperties.forEach((e,t)=>this[t]=e),this._instanceProperties=void 0}connectedCallback(){this.enableUpdating()}enableUpdating(){void 0!==this._enableUpdatingResolver&&(this._enableUpdatingResolver(),this._enableUpdatingResolver=void 0)}disconnectedCallback(){}attributeChangedCallback(e,t,i){t!==i&&this._attributeToProperty(e,i)}_propertyToAttribute(e,t,i=pe){const n=this.constructor,s=n._attributeNameForProperty(e,i);if(void 0!==s){const e=n._propertyValueToAttribute(t,i);if(void 0===e)return;this._updateState=8|this._updateState,null==e?this.removeAttribute(s):this.setAttribute(s,e),this._updateState=-9&this._updateState}}_attributeToProperty(e,t){if(8&this._updateState)return;const i=this.constructor,n=i._attributeToPropertyMap.get(e);if(void 0!==n){const e=i.getPropertyOptions(n);this._updateState=16|this._updateState,this[n]=i._propertyValueFromAttribute(t,e),this._updateState=-17&this._updateState}}requestUpdateInternal(e,t,i){let n=!0;if(void 0!==e){const s=this.constructor;i=i||s.getPropertyOptions(e),s._valueHasChanged(this[e],t,i.hasChanged)?(this._changedProperties.has(e)||this._changedProperties.set(e,t),!0!==i.reflect||16&this._updateState||(void 0===this._reflectingProperties&&(this._reflectingProperties=new Map),this._reflectingProperties.set(e,i))):n=!1}!this._hasRequestedUpdate&&n&&(this._updatePromise=this._enqueueUpdate())}requestUpdate(e,t){return this.requestUpdateInternal(e,t),this.updateComplete}async _enqueueUpdate(){this._updateState=4|this._updateState;try{await this._updatePromise}catch(e){}const e=this.performUpdate();return null!=e&&await e,!this._hasRequestedUpdate}get _hasRequestedUpdate(){return 4&this._updateState}get hasUpdated(){return 1&this._updateState}performUpdate(){if(!this._hasRequestedUpdate)return;this._instanceProperties&&this._applyInstanceProperties();let e=!1;const t=this._changedProperties;try{e=this.shouldUpdate(t),e?this.update(t):this._markUpdated()}catch(t){throw e=!1,this._markUpdated(),t}e&&(1&this._updateState||(this._updateState=1|this._updateState,this.firstUpdated(t)),this.updated(t))}_markUpdated(){this._changedProperties=new Map,this._updateState=-5&this._updateState}get updateComplete(){return this._getUpdateComplete()}_getUpdateComplete(){return this._updatePromise}shouldUpdate(e){return!0}update(e){void 0!==this._reflectingProperties&&this._reflectingProperties.size>0&&(this._reflectingProperties.forEach((e,t)=>this._propertyToAttribute(t,this[t],e)),this._reflectingProperties=void 0),this._markUpdated()}updated(e){}firstUpdated(e){}}me.finalized=!0; +/** + * @license + * Copyright (c) 2017 The Polymer Project Authors. All rights reserved. + * This code may only be used under the BSD style license found at + * http://polymer.github.io/LICENSE.txt + * The complete set of authors may be found at + * http://polymer.github.io/AUTHORS.txt + * The complete set of contributors may be found at + * http://polymer.github.io/CONTRIBUTORS.txt + * Code distributed by Google as part of the polymer project is also + * subject to an additional IP rights grant found at + * http://polymer.github.io/PATENTS.txt + */ +const ge=e=>t=>"function"==typeof t?((e,t)=>(window.customElements.define(e,t),t))(e,t):((e,t)=>{const{kind:i,elements:n}=t;return{kind:i,elements:n,finisher(t){window.customElements.define(e,t)}}})(e,t),fe=(e,t)=>"method"===t.kind&&t.descriptor&&!("value"in t.descriptor)?Object.assign(Object.assign({},t),{finisher(i){i.createProperty(t.key,e)}}):{kind:"field",key:Symbol(),placement:"own",descriptor:{},initializer(){"function"==typeof t.initializer&&(this[t.key]=t.initializer.call(this))},finisher(i){i.createProperty(t.key,e)}};function ve(e){return(t,i)=>void 0!==i?((e,t,i)=>{t.constructor.createProperty(i,e)})(e,t,i):fe(e,t)} +/** +@license +Copyright (c) 2019 The Polymer Project Authors. All rights reserved. +This code may only be used under the BSD style license found at +http://polymer.github.io/LICENSE.txt The complete set of authors may be found at +http://polymer.github.io/AUTHORS.txt The complete set of contributors may be +found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as +part of the polymer project is also subject to an additional IP rights grant +found at http://polymer.github.io/PATENTS.txt +*/const ye=window.ShadowRoot&&(void 0===window.ShadyCSS||window.ShadyCSS.nativeShadow)&&"adoptedStyleSheets"in Document.prototype&&"replace"in CSSStyleSheet.prototype,_e=Symbol();class Se{constructor(e,t){if(t!==_e)throw new Error("CSSResult is not constructable. Use `unsafeCSS` or `css` instead.");this.cssText=e}get styleSheet(){return void 0===this._styleSheet&&(ye?(this._styleSheet=new CSSStyleSheet,this._styleSheet.replaceSync(this.cssText)):this._styleSheet=null),this._styleSheet}toString(){return this.cssText}}const be=(e,...t)=>{const i=t.reduce((t,i,n)=>t+(e=>{if(e instanceof Se)return e.cssText;if("number"==typeof e)return e;throw new Error(`Value passed to 'css' function must be a 'css' function result: ${e}. Use 'unsafeCSS' to pass non-literal values, but\n take care to ensure page security.`)})(i)+e[n+1],e[0]);return new Se(i,_e)}; +/** + * @license + * Copyright (c) 2017 The Polymer Project Authors. All rights reserved. + * This code may only be used under the BSD style license found at + * http://polymer.github.io/LICENSE.txt + * The complete set of authors may be found at + * http://polymer.github.io/AUTHORS.txt + * The complete set of contributors may be found at + * http://polymer.github.io/CONTRIBUTORS.txt + * Code distributed by Google as part of the polymer project is also + * subject to an additional IP rights grant found at + * http://polymer.github.io/PATENTS.txt + */ +(window.litElementVersions||(window.litElementVersions=[])).push("2.4.0");const we={};class xe extends me{static getStyles(){return this.styles}static _getUniqueStyles(){if(this.hasOwnProperty(JSCompiler_renameProperty("_styles",this)))return;const e=this.getStyles();if(Array.isArray(e)){const t=(e,i)=>e.reduceRight((e,i)=>Array.isArray(i)?t(i,e):(e.add(i),e),i),i=t(e,new Set),n=[];i.forEach(e=>n.unshift(e)),this._styles=n}else this._styles=void 0===e?[]:[e];this._styles=this._styles.map(e=>{if(e instanceof CSSStyleSheet&&!ye){const t=Array.prototype.slice.call(e.cssRules).reduce((e,t)=>e+t.cssText,"");return new Se(String(t),_e)}return e})}initialize(){super.initialize(),this.constructor._getUniqueStyles(),this.renderRoot=this.createRenderRoot(),window.ShadowRoot&&this.renderRoot instanceof window.ShadowRoot&&this.adoptStyles()}createRenderRoot(){return this.attachShadow({mode:"open"})}adoptStyles(){const e=this.constructor._styles;0!==e.length&&(void 0===window.ShadyCSS||window.ShadyCSS.nativeShadow?ye?this.renderRoot.adoptedStyleSheets=e.map(e=>e instanceof CSSStyleSheet?e:e.styleSheet):this._needsShimAdoptedStyleSheets=!0:window.ShadyCSS.ScopingShim.prepareAdoptedCssText(e.map(e=>e.cssText),this.localName))}connectedCallback(){super.connectedCallback(),this.hasUpdated&&void 0!==window.ShadyCSS&&window.ShadyCSS.styleElement(this)}update(e){const t=this.render();super.update(e),t!==we&&this.constructor.render(t,this.renderRoot,{scopeName:this.localName,eventContext:this}),this._needsShimAdoptedStyleSheets&&(this._needsShimAdoptedStyleSheets=!1,this.constructor._styles.forEach(e=>{const t=document.createElement("style");t.textContent=e.cssText,this.renderRoot.appendChild(t)}))}render(){return we}}xe.finalized=!0,xe.render=(e,t,i)=>{if(!i||"object"!=typeof i||!i.scopeName)throw new Error("The `scopeName` option is required.");const n=i.scopeName,s=ie.has(t),r=oe&&11===t.nodeType&&!!t.host,o=r&&!de.has(n),a=o?document.createDocumentFragment():t;if(((e,t,i)=>{let n=ie.get(t);void 0===n&&(x(t,t.firstChild),ie.set(t,n=new W(Object.assign({templateFactory:ee},i))),n.appendInto(t)),n.setValue(e),n.commit()})(e,a,Object.assign({templateFactory:ae(n)},i)),o){const e=ie.get(a);ie.delete(a);const i=e.value instanceof D?e.value.template:void 0;le(n,a,i),x(t,t.firstChild),t.appendChild(a),ie.set(t,e)}!s&&r&&window.ShadyCSS.styleElement(t.host)}; +/** + * @license + * Copyright (c) 2018 The Polymer Project Authors. All rights reserved. + * This code may only be used under the BSD style license found at + * http://polymer.github.io/LICENSE.txt + * The complete set of authors may be found at + * http://polymer.github.io/AUTHORS.txt + * The complete set of contributors may be found at + * http://polymer.github.io/CONTRIBUTORS.txt + * Code distributed by Google as part of the polymer project is also + * subject to an additional IP rights grant found at + * http://polymer.github.io/PATENTS.txt + */ +class Ce{constructor(e){this.classes=new Set,this.changed=!1,this.element=e;const t=(e.getAttribute("class")||"").split(/\s+/);for(const e of t)this.classes.add(e)}add(e){this.classes.add(e),this.changed=!0}remove(e){this.classes.delete(e),this.changed=!0}commit(){if(this.changed){let e="";this.classes.forEach(t=>e+=t+" "),this.element.setAttribute("class",e)}}}const Ee=new WeakMap,Ne=(Pe=e=>t=>{if(!(t instanceof q)||t instanceof Z||"class"!==t.committer.name||t.committer.parts.length>1)throw new Error("The `classMap` directive must be used in the `class` attribute and must be the only part in the attribute.");const{committer:i}=t,{element:n}=i;let s=Ee.get(t);void 0===s&&(n.setAttribute("class",i.strings.join(" ")),Ee.set(t,s=new Set));const r=n.classList||new Ce(n);s.forEach(t=>{t in e||(r.remove(t),s.delete(t))});for(const t in e){const i=e[t];i!=s.has(t)&&(i?(r.add(t),s.add(t)):(r.remove(t),s.delete(t)))}"function"==typeof r.commit&&r.commit()},(...e)=>{const t=Pe(...e);return H.set(t,!0),t});var Pe,Me,Te,ke,Oe;!function(e){let t,i;!function(e){e.LEFT="left",e.CENTER="center",e.RIGHT="right"}(t=e.AlignControls||(e.AlignControls={})),function(e){e.HEADER="header",e.INSIDE="inside"}(i=e.Name||(e.Name={}))}(Me||(Me={})),function(e){e.UP="up",e.DOWN="down"}(Te||(Te={})),function(e){e.AM="AM",e.PM="PM"}(ke||(ke={}));let $e=Oe=class extends xe{get amClass(){return{"time-period":!0,active:this.period===ke.AM}}get pmClass(){return{"time-period":!0,active:this.period===ke.PM}}render(){return se`
    + ${"single"===this.mode?this.renderSingle():this.renderDouble()} +
    `}onTimePeriodChange(){const e=new CustomEvent(Oe.EVENT_TOGGLE);this.dispatchEvent(e)}renderSingle(){return se`
    + ${this.period} +
    `}renderDouble(){return se`
    + AM +
    +
    + PM +
    `}static get styles(){return be` + .time-period-selector { + padding: 0 8px; + } + + .time-period { + width: 30px; + padding: 8px; + background: var(--tpc-off-color); + color: var(--tpc-text-color); + text-align: center; + font-size: 1em; + cursor: pointer; + } + + .time-period.active { + background: var(--tpc-accent-color); + } + `}};var Ve;$e.EVENT_TOGGLE="toggle",e([ve()],$e.prototype,"period",void 0),e([ve()],$e.prototype,"mode",void 0),$e=Oe=e([ge("time-period")],$e);let Ae=Ve=class extends xe{render(){return se` +
    + ${this.renderStepChanger(Te.UP)} + + ${this.renderStepChanger(Te.DOWN)} +
    + `}onInputChange({target:{value:e}}){this.unit.setStringValue(e);const t=new CustomEvent(Ve.EVENT_UPDATE);this.dispatchEvent(t)}onStepChangerClick(e){const t=new CustomEvent(Ve.EVENT_STEP_CHANGE,{detail:{direction:e}});this.dispatchEvent(t)}renderStepChanger(e){return se` +
    this.onStepChangerClick(e)}> + + +
    + `}static get styles(){return be` + .time-unit { + display: flex; + flex-direction: column; + align-items: center; + padding: 0 8px; + } + + .time-picker-icon { + width: 30px; + padding: 8px; + text-align: center; + cursor: pointer; + color: var(--tpc-icon-color); + } + + .time-input { + width: 30px; + padding: 8px 8px 6px; + background: var(--tpc-elements-background-color); + border: 0; + border-bottom: 2px solid var(--tpc-elements-background-color); + color: var(--tpc-text-color, #fff); + text-align: center; + font-size: 1em; + -moz-appearance: textfield; + + transition: border-color 0.2s ease-in-out; + } + + .time-input:focus { + outline: none; + } + + .time-input:invalid { + box-shadow: none; + outline: none; + border: 0; + border-bottom: 2px solid red; + } + + .time-input::-webkit-inner-spin-button, + .time-input::-webkit-outer-spin-button { + -webkit-appearance: none; + margin: 0; + } + `}};Ae.EVENT_UPDATE="update",Ae.EVENT_STEP_CHANGE="stepChange",e([ve()],Ae.prototype,"unit",void 0),Ae=Ve=e([ge("time-unit")],Ae);const He=Me.AlignControls.CENTER,je=Me.Name.HEADER;var Ie;let Ue=Ie=class extends xe{get entity(){return this.hass.states[this.config.entity]}get datetimeEntities(){return Object.keys(this.hass.states).filter(e=>"input_datetime"===b(e)&&!0===this.hass.states[e].attributes.has_time)}render(){var e,t,i,n,s,r,o,a,c;return se`
    + + + ${this.datetimeEntities.map(e=>se`${e}`)} + + + +
    +
    + + Show name? +
    + + + ${Object.values(Me.Name).map(e=>se`${e}`)} + + +
    +
    + + +
    +
    +
    + + Show seconds? +
    + +
    +
    +
    + + 12-Hour mode +
    + ${12===this.config.hour_mode?se`
    + + "Single" hour mode layout +
    `:""} +
    +
    + + + ${Object.values(Me.AlignControls).map(e=>se`${e}`)} + + +
    + + Link Values +
    +
    +
    +
    + + Embedded +
    +
    +
    `}setConfig(e){this.config=e}onHourModeChange({target:{checked:e}}){const t=e?12:24,i=Object.assign(Object.assign({},this.config),{hour_mode:t});this.dispatch(i)}onHideNameChange({target:{checked:e}}){const t=Object.assign(Object.assign({},this.config.hide),{name:!e}),i=Object.assign(Object.assign({},this.config),{hide:t});this.dispatch(i)}onHideSecondsChange({target:{checked:e}}){const t=Object.assign(Object.assign({},this.config.hide),{seconds:!e}),i=Object.assign(Object.assign({},this.config),{hide:t});this.dispatch(i)}onLayoutAlignChange({detail:{value:e}}){const t=Object.assign(Object.assign({},this.config.layout),{align_controls:e}),i=Object.assign(Object.assign({},this.config),{layout:t});this.dispatch(i)}onLayoutNameChange({detail:{value:e}}){const t=Object.assign(Object.assign({},this.config.layout),{name:e}),i=Object.assign(Object.assign({},this.config),{layout:t});this.dispatch(i)}onHourModeLayoutChange({target:{checked:e}}){const t=e?"single":"double",i=Object.assign(Object.assign({},this.config),{layout:Object.assign(Object.assign({},this.config.layout),{hour_mode:t})});this.dispatch(i)}onLinkValuesChange({target:{checked:e}}){const t=Object.assign(Object.assign({},this.config),{link_values:e});this.dispatch(t)}onEmbeddedChange({target:{checked:e}}){const t=Object.assign(Object.assign({},this.config),{layout:{embedded:e}});this.dispatch(t)}onValueChange(e){const t=e.target.configValue;let i=e.detail.value;if(Ie.NUMBER_PROPERTIES.indexOf(t)>-1&&(i=parseInt(i),isNaN(i)))return;if(this.config[t]===i)return;const n=Object.assign(Object.assign({},this.config),{[t]:i});this.dispatch(n)}dispatch(e){const t=new CustomEvent(Ie.CONFIG_CHANGED_EVENT,{bubbles:!0,composed:!0,detail:{config:e}});this.dispatchEvent(t)}static get styles(){return be` + ha-switch { + padding: 16px 0; + margin-right: 16px; + } + .side-by-side { + display: flex; + } + .side-by-side > * { + flex: 1; + padding-right: 4px; + } + .suffix { + margin: 0 8px; + } + `}};Ue.NUMBER_PROPERTIES=["hour_step","minute_step","second_step","hour_mode"],Ue.CONFIG_CHANGED_EVENT="config-changed",e([ve({type:Object})],Ue.prototype,"hass",void 0),e([ve()],Ue.prototype,"config",void 0),Ue=Ie=e([ge("time-picker-card-editor")],Ue);class De{constructor(e,t,i){this._value=e,this._step=t,this._limit=i}get value(){return this._value}setStringValue(e){this.isValidString(e)&&this.setValue(parseInt(e))}stepUpdate(e,t=this._step){e===Te.UP?this.increment(t):this.decrement(t)}toString(){return this.value<10?"0"+this.value:this.value.toString()}increment(e=this._step){this.setValue(this.value+e)}decrement(e=this._step){this.setValue(this.value-e)}setValue(e){isNaN(e)||((e>=this._limit||e<0)&&(e=(e+this._limit)%this._limit),this._value=e)}isValidString(e){const t=parseInt(e);return!isNaN(t)&&t>=0&&t=this._limit||t<0}}Re.VALUE_LIMIT=60;class Ye extends De{constructor(e,t=5){super(e,t,Ye.VALUE_LIMIT),this.minValue=0,this.maxValue=Ye.VALUE_LIMIT-1}willOverflow(e){const t=e===Te.UP?this.value+this._step:this.value-this._step;return t>=this._limit||t<0}}Ye.VALUE_LIMIT=60;class ze{constructor(e,t,i,n=!1){this.hour=e,this.minute=t,this.second=i,this._linkValues=n}hourStep(e){this.hour.stepUpdate(e)}minuteStep(e){this._linkValues&&this.minute.willOverflow(e)&&this.hour.stepUpdate(e,1),this.minute.stepUpdate(e)}secondStep(e){this._linkValues&&this.second.willOverflow(e)&&this.minute.stepUpdate(e,1),this.second.stepUpdate(e)}get value(){return`${this.hour.value}:${this.minute.value}:${this.second.value}`}}class Fe{static error(e,t){const i=document.createElement("hui-error-card");return i.setConfig({type:"error",error:e,origConfig:t}),se`${i}`}static headerName(e){return se`
    ${e}
    `}static nestedName(e,t){return se` +
    ${e}
    `}}console.info("%c TIME-PICKER-CARD \n%c Version 1.1.1 ","color: orange; font-weight: bold; background: black","color: white; font-weight: bold; background: dimgray"),window.customCards=window.customCards||[],window.customCards.push({type:"time-picker-card",name:"Time Picker Card",description:"A Time Picker card for setting the time value of Input Datetime entities."});let Be=class extends xe{get entity(){return this.hass.states[this.config.entity]}get isEmbedded(){var e;return!0===(null===(e=this.config.layout)||void 0===e?void 0:e.embedded)}get hasNameInHeader(){var e,t,i;return Boolean(this.name)&&!1===Boolean(null===(e=this.config.hide)||void 0===e?void 0:e.name)&&(null===(t=this.config.layout)||void 0===t?void 0:t.name)!==Me.Name.INSIDE&&!1===Boolean(null===(i=this.config.layout)||void 0===i?void 0:i.embedded)}get hasNameInside(){var e,t,i;return Boolean(this.name)&&!1===Boolean(null===(e=this.config.hide)||void 0===e?void 0:e.name)&&((null===(t=this.config.layout)||void 0===t?void 0:t.name)===Me.Name.INSIDE||Boolean(null===(i=this.config.layout)||void 0===i?void 0:i.embedded))}get name(){var e;return this.config.name||(null===(e=this.entity)||void 0===e?void 0:e.attributes.friendly_name)}get shouldShowPeriod(){return 12===this.config.hour_mode}get layoutAlign(){var e,t;return null!==(t=null===(e=this.config.layout)||void 0===e?void 0:e.align_controls)&&void 0!==t?t:He}get haCardClass(){return{embedded:this.isEmbedded}}get rowClass(){return{"time-picker-row":!0,"with-header-name":this.hasNameInHeader,embedded:this.isEmbedded}}get contentClass(){return{"time-picker-content":!0,["layout-"+this.layoutAlign]:!0}}render(){var e,t,i;if(!this.entity)return Fe.error("Entity not found",this.config);if("input_datetime"!==b(this.entity.entity_id))return Fe.error("You must set an input_datetime entity",this.config);if(!this.entity.attributes.has_time)return Fe.error("You must set an input_datetime entity that sets has_time: true",this.config);const{hour:n,minute:s,second:r}=this.entity.attributes,o=new Le(n,this.config.hour_step,this.config.hour_mode),a=new Re(s,this.config.minute_step),c=new Ye(r,this.config.second_step);return this.time=new ze(o,a,c,this.config.link_values),this.period=o.value>=12?ke.PM:ke.AM,se` + + ${this.hasNameInHeader?Fe.headerName(this.name):""} +
    + ${this.hasNameInside?Fe.nestedName(this.name,this.entity):""} + +
    + +
    :
    + + ${!1===(null===(e=this.config.hide)||void 0===e?void 0:e.seconds)?se`
    :
    + `:""} + ${this.shouldShowPeriod?se``:""} +
    +
    +
    + `}setConfig(e){if(!e)throw new Error("Invalid configuration");if(!e.entity)throw new Error("You must set an entity");if(e.hour_mode&&12!==e.hour_mode&&24!==e.hour_mode)throw new Error("Invalid hour_mode: select either 12 or 24");this.config=e}getCardSize(){return 3}onPeriodToggle(){this.time.hour.togglePeriod(),this.callHassService()}onHourStepChange(e){this.time.hourStep(e.detail.direction),this.callHassService()}onMinuteStepChange(e){this.time.minuteStep(e.detail.direction),this.callHassService()}onSecondStepChange(e){this.time.secondStep(e.detail.direction),this.callHassService()}callHassService(){if(!this.hass)throw new Error("Unable to update datetime");return this.hass.callService("input_datetime","set_datetime",{entity_id:this.entity.entity_id,time:this.time.value})}static get styles(){return be` + :host { + --tpc-elements-background-color: var( + --time-picker-elements-background-color, + var(--primary-color) + ); + + --tpc-icon-color: var(--time-picker-icon-color, var(--primary-text-color)); + --tpc-text-color: var(--time-picker-text-color, #fff); + --tpc-accent-color: var(--time-picker-accent-color, var(--primary-color)); + --tpc-off-color: var(--time-picker-off-color, var(--disabled-text-color)); + + --tpc-border-radius: var(--time-picker-border-radius, var(--ha-card-border-radius, 4px)); + } + + ha-card.embedded { + box-shadow: none; + } + + .time-picker-header { + padding: 16px; + color: var(--tpc-text-color); + background-color: var(--tpc-elements-background-color); + border-top-left-radius: var(--tpc-border-radius); + border-top-right-radius: var(--tpc-border-radius); + font-size: 1em; + text-align: center; + } + + .time-picker-row { + display: flex; + flex-direction: row; + align-items: center; + padding: 16px; + } + + .time-picker-row.embedded { + padding: 0; + } + + .time-picker-row.with-header-name { + padding: 8px 16px 16px; + } + + .time-picker-content { + display: flex; + flex-direction: row; + align-items: center; + flex: 1 0 auto; + } + + .time-picker-content.layout-left { + justify-content: flex-start; + } + + .time-picker-content.layout-center { + justify-content: center; + } + + .time-picker-content.layout-right { + justify-content: flex-end; + } + + .entity-name-inside { + margin-left: 16px; + } + `}static getStubConfig(e,t){return{entity:t.find(e=>"input_datetime"===b(e))||"input_datetime.example_entity",hour_mode:24,hour_step:1,minute_step:5}}static getConfigElement(){return document.createElement("time-picker-card-editor")}};e([ve({type:Object})],Be.prototype,"hass",void 0),e([ve()],Be.prototype,"config",void 0),e([ve()],Be.prototype,"time",void 0),e([ve()],Be.prototype,"period",void 0),Be=e([ge("time-picker-card")],Be);export{Be as TimePickerCard}; diff --git a/www/icons/weather_icons/LICENSE b/www/icons/weather_icons/LICENSE new file mode 100644 index 0000000..c51f3a6 --- /dev/null +++ b/www/icons/weather_icons/LICENSE @@ -0,0 +1,6 @@ +The works contained in this archive were created by amCharts (https://www.amcharts.com/) +and is licensed under Creative Commons Attribution 4.0 International Public License: + +https://creativecommons.org/licenses/by/4.0/ + +If in doubt, email amCharts at contact@amcharts.com \ No newline at end of file diff --git a/www/icons/weather_icons/animated/cloudy-day-1.svg b/www/icons/weather_icons/animated/cloudy-day-1.svg new file mode 100644 index 0000000..c1f1922 --- /dev/null +++ b/www/icons/weather_icons/animated/cloudy-day-1.svg @@ -0,0 +1,175 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/www/icons/weather_icons/animated/cloudy-day-2.svg b/www/icons/weather_icons/animated/cloudy-day-2.svg new file mode 100644 index 0000000..712a564 --- /dev/null +++ b/www/icons/weather_icons/animated/cloudy-day-2.svg @@ -0,0 +1,176 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/www/icons/weather_icons/animated/cloudy-day-3.svg b/www/icons/weather_icons/animated/cloudy-day-3.svg new file mode 100644 index 0000000..ddcd408 --- /dev/null +++ b/www/icons/weather_icons/animated/cloudy-day-3.svg @@ -0,0 +1,175 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/www/icons/weather_icons/animated/cloudy-night-1.svg b/www/icons/weather_icons/animated/cloudy-night-1.svg new file mode 100644 index 0000000..bc6360d --- /dev/null +++ b/www/icons/weather_icons/animated/cloudy-night-1.svg @@ -0,0 +1,198 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/www/icons/weather_icons/animated/cloudy-night-2.svg b/www/icons/weather_icons/animated/cloudy-night-2.svg new file mode 100644 index 0000000..391ceeb --- /dev/null +++ b/www/icons/weather_icons/animated/cloudy-night-2.svg @@ -0,0 +1,198 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/www/icons/weather_icons/animated/cloudy-night-3.svg b/www/icons/weather_icons/animated/cloudy-night-3.svg new file mode 100644 index 0000000..2425af9 --- /dev/null +++ b/www/icons/weather_icons/animated/cloudy-night-3.svg @@ -0,0 +1,198 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/www/icons/weather_icons/animated/cloudy.svg b/www/icons/weather_icons/animated/cloudy.svg new file mode 100644 index 0000000..d94fd30 --- /dev/null +++ b/www/icons/weather_icons/animated/cloudy.svg @@ -0,0 +1,500 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/www/icons/weather_icons/animated/day.svg b/www/icons/weather_icons/animated/day.svg new file mode 100644 index 0000000..6566b16 --- /dev/null +++ b/www/icons/weather_icons/animated/day.svg @@ -0,0 +1,521 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/www/icons/weather_icons/animated/night.svg b/www/icons/weather_icons/animated/night.svg new file mode 100644 index 0000000..0c13686 --- /dev/null +++ b/www/icons/weather_icons/animated/night.svg @@ -0,0 +1,503 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/www/icons/weather_icons/animated/rainy-1.svg b/www/icons/weather_icons/animated/rainy-1.svg new file mode 100644 index 0000000..575df6c --- /dev/null +++ b/www/icons/weather_icons/animated/rainy-1.svg @@ -0,0 +1,157 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/www/icons/weather_icons/animated/rainy-2.svg b/www/icons/weather_icons/animated/rainy-2.svg new file mode 100644 index 0000000..faa8cf0 --- /dev/null +++ b/www/icons/weather_icons/animated/rainy-2.svg @@ -0,0 +1,133 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/www/icons/weather_icons/animated/rainy-3.svg b/www/icons/weather_icons/animated/rainy-3.svg new file mode 100644 index 0000000..bfb2a4c --- /dev/null +++ b/www/icons/weather_icons/animated/rainy-3.svg @@ -0,0 +1,157 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/www/icons/weather_icons/animated/rainy-4.svg b/www/icons/weather_icons/animated/rainy-4.svg new file mode 100644 index 0000000..52252b4 --- /dev/null +++ b/www/icons/weather_icons/animated/rainy-4.svg @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/www/icons/weather_icons/animated/rainy-5.svg b/www/icons/weather_icons/animated/rainy-5.svg new file mode 100644 index 0000000..52461db --- /dev/null +++ b/www/icons/weather_icons/animated/rainy-5.svg @@ -0,0 +1,90 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/www/icons/weather_icons/animated/rainy-6.svg b/www/icons/weather_icons/animated/rainy-6.svg new file mode 100644 index 0000000..ea384b1 --- /dev/null +++ b/www/icons/weather_icons/animated/rainy-6.svg @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/www/icons/weather_icons/animated/rainy-7.svg b/www/icons/weather_icons/animated/rainy-7.svg new file mode 100644 index 0000000..220dcf8 --- /dev/null +++ b/www/icons/weather_icons/animated/rainy-7.svg @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/www/icons/weather_icons/animated/snowy-1.svg b/www/icons/weather_icons/animated/snowy-1.svg new file mode 100644 index 0000000..1045466 --- /dev/null +++ b/www/icons/weather_icons/animated/snowy-1.svg @@ -0,0 +1,230 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/www/icons/weather_icons/animated/snowy-2.svg b/www/icons/weather_icons/animated/snowy-2.svg new file mode 100644 index 0000000..f01f8b7 --- /dev/null +++ b/www/icons/weather_icons/animated/snowy-2.svg @@ -0,0 +1,237 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/www/icons/weather_icons/animated/snowy-3.svg b/www/icons/weather_icons/animated/snowy-3.svg new file mode 100644 index 0000000..8c51ef8 --- /dev/null +++ b/www/icons/weather_icons/animated/snowy-3.svg @@ -0,0 +1,268 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/www/icons/weather_icons/animated/snowy-4.svg b/www/icons/weather_icons/animated/snowy-4.svg new file mode 100644 index 0000000..e282d18 --- /dev/null +++ b/www/icons/weather_icons/animated/snowy-4.svg @@ -0,0 +1,94 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/www/icons/weather_icons/animated/snowy-5.svg b/www/icons/weather_icons/animated/snowy-5.svg new file mode 100644 index 0000000..463d6d8 --- /dev/null +++ b/www/icons/weather_icons/animated/snowy-5.svg @@ -0,0 +1,166 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/www/icons/weather_icons/animated/snowy-6.svg b/www/icons/weather_icons/animated/snowy-6.svg new file mode 100644 index 0000000..beba9e4 --- /dev/null +++ b/www/icons/weather_icons/animated/snowy-6.svg @@ -0,0 +1,225 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/www/icons/weather_icons/animated/thunder.svg b/www/icons/weather_icons/animated/thunder.svg new file mode 100644 index 0000000..a27ff55 --- /dev/null +++ b/www/icons/weather_icons/animated/thunder.svg @@ -0,0 +1,268 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/www/icons/weather_icons/animated/weather-sprite.svg b/www/icons/weather_icons/animated/weather-sprite.svg new file mode 100644 index 0000000..b0117b7 --- /dev/null +++ b/www/icons/weather_icons/animated/weather-sprite.svg @@ -0,0 +1,1245 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/www/icons/weather_icons/animated/weather.svg b/www/icons/weather_icons/animated/weather.svg new file mode 100644 index 0000000..7258930 --- /dev/null +++ b/www/icons/weather_icons/animated/weather.svg @@ -0,0 +1,1245 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/www/icons/weather_icons/animated/weather_sagittarius.svg b/www/icons/weather_icons/animated/weather_sagittarius.svg new file mode 100644 index 0000000..1e85eb7 --- /dev/null +++ b/www/icons/weather_icons/animated/weather_sagittarius.svg @@ -0,0 +1,9 @@ + + + Created with Sketch. + + + + + + \ No newline at end of file diff --git a/www/icons/weather_icons/animated/weather_sunset.svg b/www/icons/weather_icons/animated/weather_sunset.svg new file mode 100644 index 0000000..b90dc3f --- /dev/null +++ b/www/icons/weather_icons/animated/weather_sunset.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + diff --git a/www/icons/weather_icons/static/cloudy-day-1.svg b/www/icons/weather_icons/static/cloudy-day-1.svg new file mode 100644 index 0000000..0779355 --- /dev/null +++ b/www/icons/weather_icons/static/cloudy-day-1.svg @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/www/icons/weather_icons/static/cloudy-day-2.svg b/www/icons/weather_icons/static/cloudy-day-2.svg new file mode 100644 index 0000000..e8d51a2 --- /dev/null +++ b/www/icons/weather_icons/static/cloudy-day-2.svg @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/www/icons/weather_icons/static/cloudy-day-3.svg b/www/icons/weather_icons/static/cloudy-day-3.svg new file mode 100644 index 0000000..6ccf90b --- /dev/null +++ b/www/icons/weather_icons/static/cloudy-day-3.svg @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/www/icons/weather_icons/static/cloudy-night-1.svg b/www/icons/weather_icons/static/cloudy-night-1.svg new file mode 100644 index 0000000..1f43b45 --- /dev/null +++ b/www/icons/weather_icons/static/cloudy-night-1.svg @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/www/icons/weather_icons/static/cloudy-night-2.svg b/www/icons/weather_icons/static/cloudy-night-2.svg new file mode 100644 index 0000000..054d6ef --- /dev/null +++ b/www/icons/weather_icons/static/cloudy-night-2.svg @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/www/icons/weather_icons/static/cloudy-night-3.svg b/www/icons/weather_icons/static/cloudy-night-3.svg new file mode 100644 index 0000000..3e9df40 --- /dev/null +++ b/www/icons/weather_icons/static/cloudy-night-3.svg @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/www/icons/weather_icons/static/cloudy.svg b/www/icons/weather_icons/static/cloudy.svg new file mode 100644 index 0000000..a95dacd --- /dev/null +++ b/www/icons/weather_icons/static/cloudy.svg @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/www/icons/weather_icons/static/day.svg b/www/icons/weather_icons/static/day.svg new file mode 100644 index 0000000..e638ec7 --- /dev/null +++ b/www/icons/weather_icons/static/day.svg @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/www/icons/weather_icons/static/night.svg b/www/icons/weather_icons/static/night.svg new file mode 100644 index 0000000..918ffd5 --- /dev/null +++ b/www/icons/weather_icons/static/night.svg @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/www/icons/weather_icons/static/rainy-1.svg b/www/icons/weather_icons/static/rainy-1.svg new file mode 100644 index 0000000..f3bdb19 --- /dev/null +++ b/www/icons/weather_icons/static/rainy-1.svg @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/www/icons/weather_icons/static/rainy-2.svg b/www/icons/weather_icons/static/rainy-2.svg new file mode 100644 index 0000000..294e2d0 --- /dev/null +++ b/www/icons/weather_icons/static/rainy-2.svg @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/www/icons/weather_icons/static/rainy-3.svg b/www/icons/weather_icons/static/rainy-3.svg new file mode 100644 index 0000000..2576756 --- /dev/null +++ b/www/icons/weather_icons/static/rainy-3.svg @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/www/icons/weather_icons/static/rainy-4.svg b/www/icons/weather_icons/static/rainy-4.svg new file mode 100644 index 0000000..ca0a309 --- /dev/null +++ b/www/icons/weather_icons/static/rainy-4.svg @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/www/icons/weather_icons/static/rainy-5.svg b/www/icons/weather_icons/static/rainy-5.svg new file mode 100644 index 0000000..423a393 --- /dev/null +++ b/www/icons/weather_icons/static/rainy-5.svg @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/www/icons/weather_icons/static/rainy-6.svg b/www/icons/weather_icons/static/rainy-6.svg new file mode 100644 index 0000000..ebb00e6 --- /dev/null +++ b/www/icons/weather_icons/static/rainy-6.svg @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/www/icons/weather_icons/static/rainy-7.svg b/www/icons/weather_icons/static/rainy-7.svg new file mode 100644 index 0000000..6535324 --- /dev/null +++ b/www/icons/weather_icons/static/rainy-7.svg @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/www/icons/weather_icons/static/snowy-1.svg b/www/icons/weather_icons/static/snowy-1.svg new file mode 100644 index 0000000..d8217db --- /dev/null +++ b/www/icons/weather_icons/static/snowy-1.svg @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/www/icons/weather_icons/static/snowy-2.svg b/www/icons/weather_icons/static/snowy-2.svg new file mode 100644 index 0000000..ba822b9 --- /dev/null +++ b/www/icons/weather_icons/static/snowy-2.svg @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/www/icons/weather_icons/static/snowy-3.svg b/www/icons/weather_icons/static/snowy-3.svg new file mode 100644 index 0000000..28fde7a --- /dev/null +++ b/www/icons/weather_icons/static/snowy-3.svg @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/www/icons/weather_icons/static/snowy-4.svg b/www/icons/weather_icons/static/snowy-4.svg new file mode 100644 index 0000000..6825143 --- /dev/null +++ b/www/icons/weather_icons/static/snowy-4.svg @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/www/icons/weather_icons/static/snowy-5.svg b/www/icons/weather_icons/static/snowy-5.svg new file mode 100644 index 0000000..08345bf --- /dev/null +++ b/www/icons/weather_icons/static/snowy-5.svg @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/www/icons/weather_icons/static/snowy-6.svg b/www/icons/weather_icons/static/snowy-6.svg new file mode 100644 index 0000000..f24d541 --- /dev/null +++ b/www/icons/weather_icons/static/snowy-6.svg @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/www/icons/weather_icons/static/thunder.svg b/www/icons/weather_icons/static/thunder.svg new file mode 100644 index 0000000..e2da380 --- /dev/null +++ b/www/icons/weather_icons/static/thunder.svg @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/www/icons/weather_icons/static/weather-sprite.svg b/www/icons/weather_icons/static/weather-sprite.svg new file mode 100644 index 0000000..654fb42 --- /dev/null +++ b/www/icons/weather_icons/static/weather-sprite.svg @@ -0,0 +1,1245 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/www/icons/weather_icons/static/weather.svg b/www/icons/weather_icons/static/weather.svg new file mode 100644 index 0000000..c194c2c --- /dev/null +++ b/www/icons/weather_icons/static/weather.svg @@ -0,0 +1,622 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/www/icons/weather_icons/static/weather_sagittarius.svg b/www/icons/weather_icons/static/weather_sagittarius.svg new file mode 100644 index 0000000..1e85eb7 --- /dev/null +++ b/www/icons/weather_icons/static/weather_sagittarius.svg @@ -0,0 +1,9 @@ + + + Created with Sketch. + + + + + + \ No newline at end of file diff --git a/www/icons/weather_icons/static/weather_sunset.svg b/www/icons/weather_icons/static/weather_sunset.svg new file mode 100644 index 0000000..b90dc3f --- /dev/null +++ b/www/icons/weather_icons/static/weather_sunset.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + diff --git a/www/panels.yaml b/www/panels.yaml new file mode 100644 index 0000000..21862a1 --- /dev/null +++ b/www/panels.yaml @@ -0,0 +1,10 @@ +panel_custom: + - name: panel-redirect + # url_path needs to be unique for each panel_custom config + url_path: redirect-states + sidebar_title: States + sidebar_icon: hass:gesture-tap-button + module_url: /local/panel-redirect.js + config: + # Where you want to redirect to + target: /developer-tools/state diff --git a/www/sidebar-order.yaml b/www/sidebar-order.yaml new file mode 100644 index 0000000..5a66098 --- /dev/null +++ b/www/sidebar-order.yaml @@ -0,0 +1,35 @@ +title: 238 McHaley +order: + - item: map + hide: true + - item: history + hide: true + - item: overview + hide: true + - item: logbook + hide: true + - item: 'Media Browser' + hide: true + - item: 'Visual Studio Code' + hide: true + - item: ESPHome + hide: true + - item: HACS + hide: true + - item: Setup + icon: "mdi:vector-link" + bottom: true + + - new_item: true + item: Server Controls + href: /config/server_control + icon: "mdi:server" + bottom: true +# exceptions: +# - user: sysadmin +# base_order: true +# order: +# - item: map +# hide: false +# - item: developer tools +# hide: true diff --git a/www/wupws_icons/00.png b/www/wupws_icons/00.png new file mode 100644 index 0000000..991ca0c Binary files /dev/null and b/www/wupws_icons/00.png differ diff --git a/www/wupws_icons/01.png b/www/wupws_icons/01.png new file mode 100644 index 0000000..1129ba7 Binary files /dev/null and b/www/wupws_icons/01.png differ diff --git a/www/wupws_icons/02.png b/www/wupws_icons/02.png new file mode 100644 index 0000000..1129ba7 Binary files /dev/null and b/www/wupws_icons/02.png differ diff --git a/www/wupws_icons/03.png b/www/wupws_icons/03.png new file mode 100644 index 0000000..501c80f Binary files /dev/null and b/www/wupws_icons/03.png differ diff --git a/www/wupws_icons/04.png b/www/wupws_icons/04.png new file mode 100644 index 0000000..501c80f Binary files /dev/null and b/www/wupws_icons/04.png differ diff --git a/www/wupws_icons/05.png b/www/wupws_icons/05.png new file mode 100644 index 0000000..7fb036c Binary files /dev/null and b/www/wupws_icons/05.png differ diff --git a/www/wupws_icons/06.png b/www/wupws_icons/06.png new file mode 100644 index 0000000..4e96070 Binary files /dev/null and b/www/wupws_icons/06.png differ diff --git a/www/wupws_icons/07.png b/www/wupws_icons/07.png new file mode 100644 index 0000000..7fb036c Binary files /dev/null and b/www/wupws_icons/07.png differ diff --git a/www/wupws_icons/08.png b/www/wupws_icons/08.png new file mode 100644 index 0000000..549b9f2 Binary files /dev/null and b/www/wupws_icons/08.png differ diff --git a/www/wupws_icons/09.png b/www/wupws_icons/09.png new file mode 100644 index 0000000..7fffd6e Binary files /dev/null and b/www/wupws_icons/09.png differ diff --git a/www/wupws_icons/10.png b/www/wupws_icons/10.png new file mode 100644 index 0000000..4e96070 Binary files /dev/null and b/www/wupws_icons/10.png differ diff --git a/www/wupws_icons/11.png b/www/wupws_icons/11.png new file mode 100644 index 0000000..7fffd6e Binary files /dev/null and b/www/wupws_icons/11.png differ diff --git a/www/wupws_icons/12.png b/www/wupws_icons/12.png new file mode 100644 index 0000000..3249ddf Binary files /dev/null and b/www/wupws_icons/12.png differ diff --git a/www/wupws_icons/13.png b/www/wupws_icons/13.png new file mode 100644 index 0000000..61363a5 Binary files /dev/null and b/www/wupws_icons/13.png differ diff --git a/www/wupws_icons/14.png b/www/wupws_icons/14.png new file mode 100644 index 0000000..87b023d Binary files /dev/null and b/www/wupws_icons/14.png differ diff --git a/www/wupws_icons/15.png b/www/wupws_icons/15.png new file mode 100644 index 0000000..ef167a7 Binary files /dev/null and b/www/wupws_icons/15.png differ diff --git a/www/wupws_icons/16.png b/www/wupws_icons/16.png new file mode 100644 index 0000000..87b023d Binary files /dev/null and b/www/wupws_icons/16.png differ diff --git a/www/wupws_icons/17.png b/www/wupws_icons/17.png new file mode 100644 index 0000000..d0e4572 Binary files /dev/null and b/www/wupws_icons/17.png differ diff --git a/www/wupws_icons/18.png b/www/wupws_icons/18.png new file mode 100644 index 0000000..d0e4572 Binary files /dev/null and b/www/wupws_icons/18.png differ diff --git a/www/wupws_icons/19.png b/www/wupws_icons/19.png new file mode 100644 index 0000000..9363b69 Binary files /dev/null and b/www/wupws_icons/19.png differ diff --git a/www/wupws_icons/20.png b/www/wupws_icons/20.png new file mode 100644 index 0000000..9363b69 Binary files /dev/null and b/www/wupws_icons/20.png differ diff --git a/www/wupws_icons/21.png b/www/wupws_icons/21.png new file mode 100644 index 0000000..9363b69 Binary files /dev/null and b/www/wupws_icons/21.png differ diff --git a/www/wupws_icons/22.png b/www/wupws_icons/22.png new file mode 100644 index 0000000..9363b69 Binary files /dev/null and b/www/wupws_icons/22.png differ diff --git a/www/wupws_icons/23.png b/www/wupws_icons/23.png new file mode 100644 index 0000000..034613c Binary files /dev/null and b/www/wupws_icons/23.png differ diff --git a/www/wupws_icons/24.png b/www/wupws_icons/24.png new file mode 100644 index 0000000..034613c Binary files /dev/null and b/www/wupws_icons/24.png differ diff --git a/www/wupws_icons/25.png b/www/wupws_icons/25.png new file mode 100644 index 0000000..ef167a7 Binary files /dev/null and b/www/wupws_icons/25.png differ diff --git a/www/wupws_icons/26.png b/www/wupws_icons/26.png new file mode 100644 index 0000000..6fd0edf Binary files /dev/null and b/www/wupws_icons/26.png differ diff --git a/www/wupws_icons/27.png b/www/wupws_icons/27.png new file mode 100644 index 0000000..3ac0084 Binary files /dev/null and b/www/wupws_icons/27.png differ diff --git a/www/wupws_icons/28.png b/www/wupws_icons/28.png new file mode 100644 index 0000000..da28b87 Binary files /dev/null and b/www/wupws_icons/28.png differ diff --git a/www/wupws_icons/29.png b/www/wupws_icons/29.png new file mode 100644 index 0000000..1f86e1f Binary files /dev/null and b/www/wupws_icons/29.png differ diff --git a/www/wupws_icons/30.png b/www/wupws_icons/30.png new file mode 100644 index 0000000..75e3024 Binary files /dev/null and b/www/wupws_icons/30.png differ diff --git a/www/wupws_icons/31.png b/www/wupws_icons/31.png new file mode 100644 index 0000000..72a6fba Binary files /dev/null and b/www/wupws_icons/31.png differ diff --git a/www/wupws_icons/32.png b/www/wupws_icons/32.png new file mode 100644 index 0000000..cf6c72c Binary files /dev/null and b/www/wupws_icons/32.png differ diff --git a/www/wupws_icons/33.png b/www/wupws_icons/33.png new file mode 100644 index 0000000..2bd500c Binary files /dev/null and b/www/wupws_icons/33.png differ diff --git a/www/wupws_icons/34.png b/www/wupws_icons/34.png new file mode 100644 index 0000000..cd4ff91 Binary files /dev/null and b/www/wupws_icons/34.png differ diff --git a/www/wupws_icons/35.png b/www/wupws_icons/35.png new file mode 100644 index 0000000..4e96070 Binary files /dev/null and b/www/wupws_icons/35.png differ diff --git a/www/wupws_icons/36.png b/www/wupws_icons/36.png new file mode 100644 index 0000000..cf6c72c Binary files /dev/null and b/www/wupws_icons/36.png differ diff --git a/www/wupws_icons/37.png b/www/wupws_icons/37.png new file mode 100644 index 0000000..53f9a01 Binary files /dev/null and b/www/wupws_icons/37.png differ diff --git a/www/wupws_icons/38.png b/www/wupws_icons/38.png new file mode 100644 index 0000000..73d8746 Binary files /dev/null and b/www/wupws_icons/38.png differ diff --git a/www/wupws_icons/39.png b/www/wupws_icons/39.png new file mode 100644 index 0000000..7fffd6e Binary files /dev/null and b/www/wupws_icons/39.png differ diff --git a/www/wupws_icons/40.png b/www/wupws_icons/40.png new file mode 100644 index 0000000..a07b18a Binary files /dev/null and b/www/wupws_icons/40.png differ diff --git a/www/wupws_icons/41.png b/www/wupws_icons/41.png new file mode 100644 index 0000000..3ddb81d Binary files /dev/null and b/www/wupws_icons/41.png differ diff --git a/www/wupws_icons/42.png b/www/wupws_icons/42.png new file mode 100644 index 0000000..0973cd6 Binary files /dev/null and b/www/wupws_icons/42.png differ diff --git a/www/wupws_icons/43.png b/www/wupws_icons/43.png new file mode 100644 index 0000000..0973cd6 Binary files /dev/null and b/www/wupws_icons/43.png differ diff --git a/www/wupws_icons/44.png b/www/wupws_icons/44.png new file mode 100644 index 0000000..a526328 Binary files /dev/null and b/www/wupws_icons/44.png differ diff --git a/www/wupws_icons/45.png b/www/wupws_icons/45.png new file mode 100644 index 0000000..ee46f55 Binary files /dev/null and b/www/wupws_icons/45.png differ diff --git a/www/wupws_icons/46.png b/www/wupws_icons/46.png new file mode 100644 index 0000000..058d686 Binary files /dev/null and b/www/wupws_icons/46.png differ diff --git a/www/wupws_icons/47.png b/www/wupws_icons/47.png new file mode 100644 index 0000000..0849477 Binary files /dev/null and b/www/wupws_icons/47.png differ diff --git a/www/wupws_icons/na.png b/www/wupws_icons/na.png new file mode 100644 index 0000000..a526328 Binary files /dev/null and b/www/wupws_icons/na.png differ