Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7eadb4c49c | ||
|
|
f92b773514 | ||
|
|
4cf44009a2 |
1
.HA_VERSION
Normal file
1
.HA_VERSION
Normal file
@@ -0,0 +1 @@
|
|||||||
|
2021.10.5
|
||||||
327
.storage/auth
Normal file
327
.storage/auth
Normal file
@@ -0,0 +1,327 @@
|
|||||||
|
{
|
||||||
|
"version": 1,
|
||||||
|
"key": "auth",
|
||||||
|
"data": {
|
||||||
|
"users": [
|
||||||
|
{
|
||||||
|
"id": "7e128bb4681b4f9680cff80d395082c8",
|
||||||
|
"group_ids": [
|
||||||
|
"system-admin"
|
||||||
|
],
|
||||||
|
"is_owner": false,
|
||||||
|
"is_active": true,
|
||||||
|
"name": "Supervisor",
|
||||||
|
"system_generated": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "e2ac2db8725247b0bb84b03718a01c39",
|
||||||
|
"group_ids": [
|
||||||
|
"system-admin"
|
||||||
|
],
|
||||||
|
"is_owner": true,
|
||||||
|
"is_active": true,
|
||||||
|
"name": "Dan",
|
||||||
|
"system_generated": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"groups": [
|
||||||
|
{
|
||||||
|
"id": "system-admin",
|
||||||
|
"name": "Administrators"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "system-users",
|
||||||
|
"name": "Users"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "system-read-only",
|
||||||
|
"name": "Read Only"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"credentials": [
|
||||||
|
{
|
||||||
|
"id": "940968b2abe54eda8d33a50fa40637bf",
|
||||||
|
"user_id": "e2ac2db8725247b0bb84b03718a01c39",
|
||||||
|
"auth_provider_type": "homeassistant",
|
||||||
|
"auth_provider_id": null,
|
||||||
|
"data": {
|
||||||
|
"username": "dan"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"refresh_tokens": [
|
||||||
|
{
|
||||||
|
"id": "6c2c924255cd43e082bbd9ec0d13f160",
|
||||||
|
"user_id": "7e128bb4681b4f9680cff80d395082c8",
|
||||||
|
"client_id": null,
|
||||||
|
"client_name": null,
|
||||||
|
"client_icon": null,
|
||||||
|
"token_type": "system",
|
||||||
|
"created_at": "2020-05-29T17:56:30.594677+00:00",
|
||||||
|
"access_token_expiration": 1800.0,
|
||||||
|
"token": "ad8e000af3c797b8d1b40671c13211d88f4bec1e28d66bbd0110648c3e1f5d89ae473e6683c914dbee11008067de6fea0acebed9e7e98ea4d04274f343b4c693",
|
||||||
|
"jwt_key": "2bebb9fbbecd2a42d24db8f53bab9a493faee41eb64a90ff1140cb849e0e9737f6e50323d4967049262e80a723b50c10af7b487b4d7858dca075adb70e8a8e52",
|
||||||
|
"last_used_at": "2021-11-04T05:13:50.720694+00:00",
|
||||||
|
"last_used_ip": "172.30.32.2",
|
||||||
|
"credential_id": null,
|
||||||
|
"version": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "30bed7edcb3b483b9c258fc19e6b67e6",
|
||||||
|
"user_id": "e2ac2db8725247b0bb84b03718a01c39",
|
||||||
|
"client_id": "http://192.168.86.40:8123/",
|
||||||
|
"client_name": null,
|
||||||
|
"client_icon": null,
|
||||||
|
"token_type": "normal",
|
||||||
|
"created_at": "2020-05-29T17:57:53.352192+00:00",
|
||||||
|
"access_token_expiration": 1800.0,
|
||||||
|
"token": "9f2e16a48be4c4e20cb8b59ef06cbd0fbea461ad532f9f3e8c4822142778d027f442481d780ec2ac1dfa8a793c6ad8aaffa65a2ce785bb6b966cebdc9507281c",
|
||||||
|
"jwt_key": "0d48d3ef9b5a750a51e587f123b6d22661c7dd97fb57d9f39766a4c6777369adce1c078bf6b813c8608bdb412eee4ceec9ef1952198e1147bde29580574e895c",
|
||||||
|
"last_used_at": "2020-08-27T15:45:56.542944+00:00",
|
||||||
|
"last_used_ip": "192.168.86.199",
|
||||||
|
"credential_id": null,
|
||||||
|
"version": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "fcfe376305564c99bbda3ba26c03f579",
|
||||||
|
"user_id": "e2ac2db8725247b0bb84b03718a01c39",
|
||||||
|
"client_id": "https://house.dandembinski.com/",
|
||||||
|
"client_name": null,
|
||||||
|
"client_icon": null,
|
||||||
|
"token_type": "normal",
|
||||||
|
"created_at": "2020-05-29T19:10:57.949284+00:00",
|
||||||
|
"access_token_expiration": 1800.0,
|
||||||
|
"token": "6a1bb9dd79d3bc02a40730a50dd1688dbd37532d7821b941cada99f81e880cccc5d659165b00018e3a205ed9434b318cc56c14ebd14a4c35c5c87a0edb2635eb",
|
||||||
|
"jwt_key": "0ad684830d6643bed4cd41d1d41e96f1102362d821dbc13dddbd65a54bf95e7f05881214b27eaf7316dc561f8f097fe78511dcfe65f52fe232ec3c15ed52e5b8",
|
||||||
|
"last_used_at": "2020-07-20T12:18:33.184240+00:00",
|
||||||
|
"last_used_ip": "192.168.86.198",
|
||||||
|
"credential_id": null,
|
||||||
|
"version": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "46c858b4e420475ebc54b3d1a9d58dd2",
|
||||||
|
"user_id": "e2ac2db8725247b0bb84b03718a01c39",
|
||||||
|
"client_id": null,
|
||||||
|
"client_name": "nodered",
|
||||||
|
"client_icon": null,
|
||||||
|
"token_type": "long_lived_access_token",
|
||||||
|
"created_at": "2020-05-29T19:20:52.141510+00:00",
|
||||||
|
"access_token_expiration": 315360000.0,
|
||||||
|
"token": "5c0355f71cb377e769ef10bd182b8ff06b5e29ece64cb40335f8d4ffea9915286306c6e9bf51fe52b6aa04989d0763970b5675c5b2a1d92070f8c0e2dd33380a",
|
||||||
|
"jwt_key": "4b49e05c4c91a0b7e8a51f0bc8cb6f531afc798139cb8c1f8aa354463d762475c891853cdfae5227ec3b2c285fee20e059ea7400497803b86d5766f17d91c8ae",
|
||||||
|
"last_used_at": "2020-05-29T19:20:52.142065+00:00",
|
||||||
|
"last_used_ip": null,
|
||||||
|
"credential_id": null,
|
||||||
|
"version": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "19575d9287324e3489345fe9db5a4936",
|
||||||
|
"user_id": "e2ac2db8725247b0bb84b03718a01c39",
|
||||||
|
"client_id": "https://house.dandembinski.com/",
|
||||||
|
"client_name": null,
|
||||||
|
"client_icon": null,
|
||||||
|
"token_type": "normal",
|
||||||
|
"created_at": "2020-05-29T21:32:22.614204+00:00",
|
||||||
|
"access_token_expiration": 1800.0,
|
||||||
|
"token": "08a756075d2f15addae8983a725011dd532bd7b57750d11ebb8b37c8feeeb153028e84ed73494ecf1b721ef39ceb9d2836221fb6566a5f53c4e8164c930a20ac",
|
||||||
|
"jwt_key": "45e9b816dfd5b419729ff97fb90fb7602c171b623062f6554693a4142de719f60adf59c195f3c6002faa5903924bc5e2f263f07bf67bd2801526655456082f11",
|
||||||
|
"last_used_at": "2021-11-04T05:14:27.221933+00:00",
|
||||||
|
"last_used_ip": "192.168.86.1",
|
||||||
|
"credential_id": null,
|
||||||
|
"version": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "b5eeca33991941a8a41030ca2c6d349e",
|
||||||
|
"user_id": "e2ac2db8725247b0bb84b03718a01c39",
|
||||||
|
"client_id": "http://house.dandembinski.com/",
|
||||||
|
"client_name": null,
|
||||||
|
"client_icon": null,
|
||||||
|
"token_type": "normal",
|
||||||
|
"created_at": "2020-05-29T21:44:35.781505+00:00",
|
||||||
|
"access_token_expiration": 1800.0,
|
||||||
|
"token": "ee1c89a76a734f28857b02a4450bcedbd38ac077b6b8e758c9f6622618513b1a6c6334ca0c3d52682cfc01a1514ee94ca0be05f2f927ac2e95b43e2b3fe98ec8",
|
||||||
|
"jwt_key": "7b35033548dd9eb840ac0eb6134a90ee16bbe05625c7a0d76d25e5b9dfae358e5ea4022d571879dca848676b101216c147452de5ddb08a189753e39321b24e98",
|
||||||
|
"last_used_at": "2021-06-11T14:37:40.221368+00:00",
|
||||||
|
"last_used_ip": "192.168.86.198",
|
||||||
|
"credential_id": null,
|
||||||
|
"version": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "1c1b24887db846099d6a7f5ff867f70b",
|
||||||
|
"user_id": "e2ac2db8725247b0bb84b03718a01c39",
|
||||||
|
"client_id": "https://home-assistant.io/android",
|
||||||
|
"client_name": null,
|
||||||
|
"client_icon": null,
|
||||||
|
"token_type": "normal",
|
||||||
|
"created_at": "2020-05-30T00:48:00.660451+00:00",
|
||||||
|
"access_token_expiration": 1800.0,
|
||||||
|
"token": "d26165ea00a1e93ef83d2b41edee7816ddae5677af7d532876ff5cd90fd4641686c942b96cc4b894ad7194d673cacd25e8612069d208b85867e2c6fc2181e967",
|
||||||
|
"jwt_key": "46f60304aab09ba84a79173449142a15c031247be8a205abd45541c073163b22e429efa460ff5f50b0b256c3c760bdbb0238cbf2ae5194a7bd6184aeb287b900",
|
||||||
|
"last_used_at": "2021-11-04T02:54:24.337524+00:00",
|
||||||
|
"last_used_ip": "192.168.86.36",
|
||||||
|
"credential_id": null,
|
||||||
|
"version": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "df8c4c4bef054d49b81233979d289f63",
|
||||||
|
"user_id": "e2ac2db8725247b0bb84b03718a01c39",
|
||||||
|
"client_id": "https://house.dandembinski.com/",
|
||||||
|
"client_name": null,
|
||||||
|
"client_icon": null,
|
||||||
|
"token_type": "normal",
|
||||||
|
"created_at": "2020-05-30T00:52:06.132796+00:00",
|
||||||
|
"access_token_expiration": 1800.0,
|
||||||
|
"token": "d6490c1d9a076075d66b78d985793454449c86da2f94e701c08dc6b3f28430f8b551ad94bf983ecdd34ebaa5667b781a6a487b03c3c03add69f04832b8529099",
|
||||||
|
"jwt_key": "74eea0472bf0a9ae8de80d91dc6a749a68849a4ad14c50f3d81a69923cc5e5d2d67e248414c771b85c5859a194812197d902037bea7b80db8f7702dfb4fb7970",
|
||||||
|
"last_used_at": "2020-08-26T15:15:40.903869+00:00",
|
||||||
|
"last_used_ip": "192.168.86.198",
|
||||||
|
"credential_id": null,
|
||||||
|
"version": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "343a96be8a4546aa927d46fe6af00fd0",
|
||||||
|
"user_id": "e2ac2db8725247b0bb84b03718a01c39",
|
||||||
|
"client_id": "https://house.dandembinski.com/",
|
||||||
|
"client_name": null,
|
||||||
|
"client_icon": null,
|
||||||
|
"token_type": "normal",
|
||||||
|
"created_at": "2020-07-21T15:49:59.553351+00:00",
|
||||||
|
"access_token_expiration": 1800.0,
|
||||||
|
"token": "0e06da7483669a4bc6e2e258af50f56f243529782ffe796f2d09886d28ed388ad04ecd17e1786dfabf770e919107742b8cab0e9f8a76bcc325c511fcc7db6cf5",
|
||||||
|
"jwt_key": "61232b195e40bd7ccec9c2d819045da50148e438043e101d75174c6317bada7df8e456914a0b5f0b98124e5caf99052f21a38e0e35807d308466701c0e157d4b",
|
||||||
|
"last_used_at": "2021-05-23T18:20:23.167244+00:00",
|
||||||
|
"last_used_ip": "192.168.86.198",
|
||||||
|
"credential_id": null,
|
||||||
|
"version": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "041349f25ed44541a85639cc1f08a937",
|
||||||
|
"user_id": "e2ac2db8725247b0bb84b03718a01c39",
|
||||||
|
"client_id": "http://192.168.86.40:8123/",
|
||||||
|
"client_name": null,
|
||||||
|
"client_icon": null,
|
||||||
|
"token_type": "normal",
|
||||||
|
"created_at": "2020-08-26T15:22:39.788527+00:00",
|
||||||
|
"access_token_expiration": 1800.0,
|
||||||
|
"token": "48998af2f96ac72599e4b2de1b74a44ad1606282b407b5101b0259ba8578d86dbaf3129ec86c26b215539e87d0f4c77ae8ff1eb8c3b6f7df16f5c24afc7325b1",
|
||||||
|
"jwt_key": "2c0e67d51d64856009d6c86736cce01fc0d1ba17b7dd1b0f6c45ccbafce527fecb922261484351fd3d7ca4fee51b4d0aeb12c5c46503e010d96dec6364c9d0de",
|
||||||
|
"last_used_at": "2020-09-01T05:47:09.903831+00:00",
|
||||||
|
"last_used_ip": "192.168.86.163",
|
||||||
|
"credential_id": null,
|
||||||
|
"version": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "0583c68546c5471399811e7776ef743b",
|
||||||
|
"user_id": "e2ac2db8725247b0bb84b03718a01c39",
|
||||||
|
"client_id": "https://house.dandembinski.com/",
|
||||||
|
"client_name": null,
|
||||||
|
"client_icon": null,
|
||||||
|
"token_type": "normal",
|
||||||
|
"created_at": "2021-05-30T11:48:34.952165+00:00",
|
||||||
|
"access_token_expiration": 1800.0,
|
||||||
|
"token": "265ef95c0a386a84b200cf2bfe70f5712feec53e9864d87f335bccb2c3d5db6db6747ce76bef0cd6cc7a009d07b09022bc3fe280d96399a16ce64f519f4d8bf2",
|
||||||
|
"jwt_key": "2c55003fa049504ead0f3079b26984fd31f2c48213631d752538a341209b823060fe367ddb8f5fd0e92dc22251e98cbed147c60a51a7a8b36bd7cd5a4f36dcb3",
|
||||||
|
"last_used_at": "2021-10-27T14:24:21.227406+00:00",
|
||||||
|
"last_used_ip": "192.168.86.1",
|
||||||
|
"credential_id": "940968b2abe54eda8d33a50fa40637bf",
|
||||||
|
"version": "2021.5.5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "696e4e819d2646d5b3f26db96c4b249c",
|
||||||
|
"user_id": "e2ac2db8725247b0bb84b03718a01c39",
|
||||||
|
"client_id": "http://192.168.86.40:8123/",
|
||||||
|
"client_name": null,
|
||||||
|
"client_icon": null,
|
||||||
|
"token_type": "normal",
|
||||||
|
"created_at": "2021-08-24T12:50:08.936245+00:00",
|
||||||
|
"access_token_expiration": 1800.0,
|
||||||
|
"token": "b569881512898fb71e8443b7641c83d5e562834c74c804d405b86b7886afaf456a7c0f2cb4b1525f1a5d5816fd9ab786c1371563db934fd36479176fcc1e8aa3",
|
||||||
|
"jwt_key": "c24b749ff1a7c9e9108310bb1c8cc283cce287cbf8130f73a5871c90d70d20a17a0a656d525068c2e54d1dca0799164d2741a3c0ae39c0071e1379a644b5c189",
|
||||||
|
"last_used_at": "2021-08-31T15:13:14.867288+00:00",
|
||||||
|
"last_used_ip": "192.168.86.28",
|
||||||
|
"credential_id": "940968b2abe54eda8d33a50fa40637bf",
|
||||||
|
"version": "2021.8.8"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "47ab2ae9cbd8424f81195a3d12680039",
|
||||||
|
"user_id": "e2ac2db8725247b0bb84b03718a01c39",
|
||||||
|
"client_id": "http://house.dandembinski.com/",
|
||||||
|
"client_name": null,
|
||||||
|
"client_icon": null,
|
||||||
|
"token_type": "normal",
|
||||||
|
"created_at": "2021-09-25T02:52:33.244560+00:00",
|
||||||
|
"access_token_expiration": 1800.0,
|
||||||
|
"token": "cd06129e6dfa54e8db8a5f6559478011fb1b931d2c448ce697b30c5335d41d827d80d61188e5704b22a7357f15cc474dcf9340c21f0a865a468c855f61f6d08a",
|
||||||
|
"jwt_key": "5fed4b07949f2354dfb112eb4f7614d89468c5a7dd7b1e0bae174f1421e5d2ebf2048f7086c95945ed06c197b23dfdce6c9e92bf8765d484c48acf757c8d6677",
|
||||||
|
"last_used_at": "2021-09-25T03:28:09.039643+00:00",
|
||||||
|
"last_used_ip": "192.168.86.1",
|
||||||
|
"credential_id": "940968b2abe54eda8d33a50fa40637bf",
|
||||||
|
"version": "2021.9.6"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cd93d6c34ec042de8e5a16ee8d10ae95",
|
||||||
|
"user_id": "e2ac2db8725247b0bb84b03718a01c39",
|
||||||
|
"client_id": null,
|
||||||
|
"client_name": "alexa",
|
||||||
|
"client_icon": null,
|
||||||
|
"token_type": "long_lived_access_token",
|
||||||
|
"created_at": "2021-10-08T18:34:55.438178+00:00",
|
||||||
|
"access_token_expiration": 315360000.0,
|
||||||
|
"token": "833793e994ef8167e6ee95fa7a71a8120885738cf919bdf0ba6e15ce42e3ed4cc2b7d558ffb86ca5117a8cc1d3cd979f143b92bdbe2e6e3805793564c144a8f2",
|
||||||
|
"jwt_key": "d8ddd1cb3f712bdc25b8c9d14a9a8320e6c6ce31c9efb81b11a7b80d73e0261b11fed98ac69361668366b489f005de15943c9538d46ee13a6307910f4c1e0f3b",
|
||||||
|
"last_used_at": "2021-10-08T18:34:55.439116+00:00",
|
||||||
|
"last_used_ip": null,
|
||||||
|
"credential_id": null,
|
||||||
|
"version": "2021.9.6"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "9f434eebc801473cb2cca812dfc7f962",
|
||||||
|
"user_id": "e2ac2db8725247b0bb84b03718a01c39",
|
||||||
|
"client_id": "https://house.dandembinski.com/",
|
||||||
|
"client_name": null,
|
||||||
|
"client_icon": null,
|
||||||
|
"token_type": "normal",
|
||||||
|
"created_at": "2021-10-18T19:10:21.306301+00:00",
|
||||||
|
"access_token_expiration": 1800.0,
|
||||||
|
"token": "ad4e7579362846c8a8c35be76b6a3cf0262796c02e1caa6046be8349fe6679dc197a6f1ec463bebdc6037179e8703af791945af32a5b78714817424e2fb9dfbe",
|
||||||
|
"jwt_key": "9d401dd56ee3571d5672bf67dc380a4f9e1ad5fd0affe44657f2e2fcc7f0ce81bb440dcd26b475bedd5fe85ea11351187f1602c4dd8a6a21f85e75238561758a",
|
||||||
|
"last_used_at": "2021-10-18T19:10:21.307226+00:00",
|
||||||
|
"last_used_ip": "192.168.86.1",
|
||||||
|
"credential_id": "940968b2abe54eda8d33a50fa40637bf",
|
||||||
|
"version": "2021.10.5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "efe7250858bf4a1e8108715613f8be2f",
|
||||||
|
"user_id": "e2ac2db8725247b0bb84b03718a01c39",
|
||||||
|
"client_id": "http://house.dandembinski.com/",
|
||||||
|
"client_name": null,
|
||||||
|
"client_icon": null,
|
||||||
|
"token_type": "normal",
|
||||||
|
"created_at": "2021-11-03T18:45:47.481806+00:00",
|
||||||
|
"access_token_expiration": 1800.0,
|
||||||
|
"token": "e79efde17ced310ce046722abf625e495ab8919d2fbfbe5f6bc38341ae7bc4bbc76222bd1413dff0bbe9511732dd356663982519c9a55828343f9d0b5a29177d",
|
||||||
|
"jwt_key": "decab182d5d7aaef7de8b179ae96b939266ddafd3c381a14c7d0c13f7dbbcd14617e3df02503ee52641df361fe7a77fb4ae8a8b9c068cbbafa3fb0f1bc0c4f50",
|
||||||
|
"last_used_at": "2021-11-03T19:55:15.923429+00:00",
|
||||||
|
"last_used_ip": "192.168.86.1",
|
||||||
|
"credential_id": "940968b2abe54eda8d33a50fa40637bf",
|
||||||
|
"version": "2021.10.5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "4773ad9c3d2842619b71b173ce104c1f",
|
||||||
|
"user_id": "e2ac2db8725247b0bb84b03718a01c39",
|
||||||
|
"client_id": "https://house.dandembinski.com/",
|
||||||
|
"client_name": null,
|
||||||
|
"client_icon": null,
|
||||||
|
"token_type": "normal",
|
||||||
|
"created_at": "2021-11-03T18:46:57.611006+00:00",
|
||||||
|
"access_token_expiration": 1800.0,
|
||||||
|
"token": "8821cc18a22a74045b8ad6264f52c2afcbaebe4a50a857fe210c66231bf442148c307956e4d10d3694f71d7f045093243abb31ca1e73d1fa630dc4a63ddad9ae",
|
||||||
|
"jwt_key": "607f92e59edff2c44eb7896ee2e96d03608127bd7c0d07c0be7241ac8be05ae4422de5967bb39336fbaef0c65524471aca9352a083533b37121876e2b55e2a72",
|
||||||
|
"last_used_at": "2021-11-04T03:44:57.901906+00:00",
|
||||||
|
"last_used_ip": "192.168.86.1",
|
||||||
|
"credential_id": "940968b2abe54eda8d33a50fa40637bf",
|
||||||
|
"version": "2021.10.5"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
12
.storage/auth_provider.homeassistant
Normal file
12
.storage/auth_provider.homeassistant
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"data": {
|
||||||
|
"users": [
|
||||||
|
{
|
||||||
|
"password": "JDJiJDEyJC41dU1CRHNNM3A2akMuSFVQNVB1YXViaVFCT3dPV2UuTmdxWDl5R0xOOVpqby5lY3RPVVhL",
|
||||||
|
"username": "dan"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"key": "auth_provider.homeassistant",
|
||||||
|
"version": 1
|
||||||
|
}
|
||||||
36
.storage/core.area_registry
Normal file
36
.storage/core.area_registry
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
{
|
||||||
|
"version": 1,
|
||||||
|
"key": "core.area_registry",
|
||||||
|
"data": {
|
||||||
|
"areas": [
|
||||||
|
{
|
||||||
|
"name": "Kitchen",
|
||||||
|
"id": "ac918f0c0e314c3e9cd18b486842bb24"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Bedroom",
|
||||||
|
"id": "8e5446424de1458db58dda26c21a5285"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Office",
|
||||||
|
"id": "cccf16afc4f5422898e671517a120ed6"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Entryway",
|
||||||
|
"id": "03ff2a472caf42b7bd88ce96b7325862"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Living",
|
||||||
|
"id": "living"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Basement",
|
||||||
|
"id": "basement"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Dinning Room",
|
||||||
|
"id": "dinning_room"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
14
.storage/core.config
Normal file
14
.storage/core.config
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"data": {
|
||||||
|
"elevation": 206,
|
||||||
|
"external_url": "https://house.dandembinski.com",
|
||||||
|
"internal_url": "http://192.168.86.40:8123/",
|
||||||
|
"latitude": 41.39887176326016,
|
||||||
|
"location_name": "Home",
|
||||||
|
"longitude": -82.23310858011247,
|
||||||
|
"time_zone": "America/New_York",
|
||||||
|
"unit_system": "imperial"
|
||||||
|
},
|
||||||
|
"key": "core.config",
|
||||||
|
"version": 1
|
||||||
|
}
|
||||||
415
.storage/core.config_entries
Normal file
415
.storage/core.config_entries
Normal file
@@ -0,0 +1,415 @@
|
|||||||
|
{
|
||||||
|
"version": 1,
|
||||||
|
"key": "core.config_entries",
|
||||||
|
"data": {
|
||||||
|
"entries": [
|
||||||
|
{
|
||||||
|
"entry_id": "d449b87b7a8a4de2a1ee7ad105ac30b1",
|
||||||
|
"version": 1,
|
||||||
|
"domain": "met",
|
||||||
|
"title": "Home",
|
||||||
|
"data": {
|
||||||
|
"track_home": true
|
||||||
|
},
|
||||||
|
"options": {},
|
||||||
|
"pref_disable_new_entities": false,
|
||||||
|
"pref_disable_polling": false,
|
||||||
|
"source": "onboarding",
|
||||||
|
"unique_id": null,
|
||||||
|
"disabled_by": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entry_id": "77d68408d0a44c3c8ffa3747452065f1",
|
||||||
|
"version": 1,
|
||||||
|
"domain": "hue",
|
||||||
|
"title": "Philips hue",
|
||||||
|
"data": {
|
||||||
|
"host": "192.168.86.44",
|
||||||
|
"username": "84TbphY7UA59qtwukn1sx7vn3LX98WffZSy5n-rg"
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"allow_hue_groups": false,
|
||||||
|
"allow_unreachable": true
|
||||||
|
},
|
||||||
|
"pref_disable_new_entities": false,
|
||||||
|
"pref_disable_polling": false,
|
||||||
|
"source": "import",
|
||||||
|
"unique_id": "00178812ec3b",
|
||||||
|
"disabled_by": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entry_id": "487ba0d979a943a6b590f834f80258d0",
|
||||||
|
"version": 1,
|
||||||
|
"domain": "mqtt",
|
||||||
|
"title": "192.168.86.198",
|
||||||
|
"data": {
|
||||||
|
"broker": "192.168.86.198",
|
||||||
|
"port": 1888,
|
||||||
|
"password": "1234",
|
||||||
|
"username": "hassio",
|
||||||
|
"birth_message": {
|
||||||
|
"topic": "homeassistant/status",
|
||||||
|
"payload": "online",
|
||||||
|
"qos": 0,
|
||||||
|
"retain": false
|
||||||
|
},
|
||||||
|
"will_message": {
|
||||||
|
"topic": "homeassistant/status",
|
||||||
|
"payload": "offline",
|
||||||
|
"qos": 0,
|
||||||
|
"retain": false
|
||||||
|
},
|
||||||
|
"discovery": true
|
||||||
|
},
|
||||||
|
"options": {},
|
||||||
|
"pref_disable_new_entities": false,
|
||||||
|
"pref_disable_polling": false,
|
||||||
|
"source": "user",
|
||||||
|
"unique_id": null,
|
||||||
|
"disabled_by": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entry_id": "4034b3eddaef4bee93ba6eb68f6d9e5e",
|
||||||
|
"version": 1,
|
||||||
|
"domain": "mobile_app",
|
||||||
|
"title": "SM-N960U",
|
||||||
|
"data": {
|
||||||
|
"app_data": {
|
||||||
|
"push_url": "https://mobile-apps.home-assistant.io/api/sendPush/android/v1",
|
||||||
|
"push_token": "fyPvng4VdH4:APA91bF403pSt2EUPcwwAJ4eSLHwCMWXjn0Q4-KJf8whlEqJGC7VRGmujrQhUu64rngMhFvYo8rR7VrYW20k84Tt-6bG2AymdjI90pXwYOb_9uaxThOCzl8GfGcSKGCzB0oxmqWKmFtB"
|
||||||
|
},
|
||||||
|
"app_id": "io.homeassistant.companion.android",
|
||||||
|
"app_name": "Home Assistant",
|
||||||
|
"app_version": "2021.10.0-full (761)",
|
||||||
|
"device_id": "98756dd6f94f600d",
|
||||||
|
"device_name": "SM-N960U",
|
||||||
|
"manufacturer": "samsung",
|
||||||
|
"model": "SM-N960U",
|
||||||
|
"os_name": "Android",
|
||||||
|
"os_version": "29",
|
||||||
|
"supports_encryption": false,
|
||||||
|
"user_id": "e2ac2db8725247b0bb84b03718a01c39",
|
||||||
|
"webhook_id": "eb9d2fbd9f8fea4200d7752b689aecfe6a2ca2215d2cbe0600b75a4fd20a6797"
|
||||||
|
},
|
||||||
|
"options": {},
|
||||||
|
"pref_disable_new_entities": false,
|
||||||
|
"pref_disable_polling": false,
|
||||||
|
"source": "registration",
|
||||||
|
"unique_id": "io.homeassistant.companion.android-98756dd6f94f600d",
|
||||||
|
"disabled_by": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entry_id": "52efee6cb13d42f3a5a13a89bd813400",
|
||||||
|
"version": 1,
|
||||||
|
"domain": "owntracks",
|
||||||
|
"title": "OwnTracks",
|
||||||
|
"data": {
|
||||||
|
"cloudhook": false,
|
||||||
|
"secret": "3b54858961193ac9527c65ec53817048",
|
||||||
|
"webhook_id": "3fce02123ef2e6efe569acbb7a1144839645e96c496eb5594dcde83f46b0e5d9"
|
||||||
|
},
|
||||||
|
"options": {},
|
||||||
|
"pref_disable_new_entities": false,
|
||||||
|
"pref_disable_polling": false,
|
||||||
|
"source": "user",
|
||||||
|
"unique_id": null,
|
||||||
|
"disabled_by": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entry_id": "24c023984771440bb36266e23675c31a",
|
||||||
|
"version": 1,
|
||||||
|
"domain": "roku",
|
||||||
|
"title": "Ignored",
|
||||||
|
"data": {
|
||||||
|
"host": "192.168.86.30"
|
||||||
|
},
|
||||||
|
"options": {},
|
||||||
|
"pref_disable_new_entities": false,
|
||||||
|
"pref_disable_polling": false,
|
||||||
|
"source": "ignore",
|
||||||
|
"unique_id": "YR00AV370849",
|
||||||
|
"disabled_by": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entry_id": "8595c96fda7a4a2eaa4e9c7434236f57",
|
||||||
|
"version": 1,
|
||||||
|
"domain": "samsungtv",
|
||||||
|
"title": "Ignored",
|
||||||
|
"data": {
|
||||||
|
"id": "7f8d7dec-32da-4314-9391-cb1acc922b95",
|
||||||
|
"manufacturer": "Samsung Electronics",
|
||||||
|
"model": "UN55J6200"
|
||||||
|
},
|
||||||
|
"options": {},
|
||||||
|
"pref_disable_new_entities": false,
|
||||||
|
"pref_disable_polling": false,
|
||||||
|
"source": "ignore",
|
||||||
|
"unique_id": "192.168.86.42",
|
||||||
|
"disabled_by": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entry_id": "7edb292b13634d9c9ef43ff79665efd3",
|
||||||
|
"version": 1,
|
||||||
|
"domain": "brother",
|
||||||
|
"title": "Ignored",
|
||||||
|
"data": {},
|
||||||
|
"options": {},
|
||||||
|
"pref_disable_new_entities": false,
|
||||||
|
"pref_disable_polling": false,
|
||||||
|
"source": "ignore",
|
||||||
|
"unique_id": "u63883m5n395203",
|
||||||
|
"disabled_by": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entry_id": "94d2d69d16ba44d3a5fb345f3d2944cb",
|
||||||
|
"version": 1,
|
||||||
|
"domain": "cast",
|
||||||
|
"title": "Ignored",
|
||||||
|
"data": {},
|
||||||
|
"options": {},
|
||||||
|
"pref_disable_new_entities": false,
|
||||||
|
"pref_disable_polling": false,
|
||||||
|
"source": "ignore",
|
||||||
|
"unique_id": "cast",
|
||||||
|
"disabled_by": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entry_id": "de42e81841a94b7783ba796bba7deaa6",
|
||||||
|
"version": 1,
|
||||||
|
"domain": "ipp",
|
||||||
|
"title": "Ignored",
|
||||||
|
"data": {
|
||||||
|
"host": "192.168.86.32",
|
||||||
|
"name": "Brother HL-L2360D series"
|
||||||
|
},
|
||||||
|
"options": {},
|
||||||
|
"pref_disable_new_entities": false,
|
||||||
|
"pref_disable_polling": false,
|
||||||
|
"source": "ignore",
|
||||||
|
"unique_id": "e3248000-80ce-11db-8000-30055ca5f565",
|
||||||
|
"disabled_by": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entry_id": "41d744a4348146db8d6f8cd07a36e1b7",
|
||||||
|
"version": 1,
|
||||||
|
"domain": "upnp",
|
||||||
|
"title": "Ignored",
|
||||||
|
"data": {},
|
||||||
|
"options": {},
|
||||||
|
"pref_disable_new_entities": false,
|
||||||
|
"pref_disable_polling": false,
|
||||||
|
"source": "ignore",
|
||||||
|
"unique_id": "uuid:3f422f86-082a-4aec-9740-e71645c0fb9d::urn:schemas-upnp-org:device:InternetGatewayDevice:2",
|
||||||
|
"disabled_by": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entry_id": "babd9cc14c314f6db8c5ce578dfa9564",
|
||||||
|
"version": 3,
|
||||||
|
"domain": "zha",
|
||||||
|
"title": "HubZ Smart Home Controller, s/n: 90F00055 - Silicon Labs",
|
||||||
|
"data": {
|
||||||
|
"device": {
|
||||||
|
"baudrate": 57600,
|
||||||
|
"path": "/dev/serial/by-id/usb-Silicon_Labs_HubZ_Smart_Home_Controller_90F00055-if01-port0"
|
||||||
|
},
|
||||||
|
"radio_type": "ezsp"
|
||||||
|
},
|
||||||
|
"options": {},
|
||||||
|
"pref_disable_new_entities": false,
|
||||||
|
"pref_disable_polling": false,
|
||||||
|
"source": "user",
|
||||||
|
"unique_id": null,
|
||||||
|
"disabled_by": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entry_id": "69831ce2912c4cccbd488b95302d08b7",
|
||||||
|
"version": 1,
|
||||||
|
"domain": "upnp",
|
||||||
|
"title": "Ignored",
|
||||||
|
"data": {},
|
||||||
|
"options": {},
|
||||||
|
"pref_disable_new_entities": false,
|
||||||
|
"pref_disable_polling": false,
|
||||||
|
"source": "ignore",
|
||||||
|
"unique_id": "uuid:45ed9cb0-ec89-40f9-9ab6-cbcef376e442::urn:schemas-upnp-org:device:InternetGatewayDevice:2",
|
||||||
|
"disabled_by": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entry_id": "8f3041c2c7134aef825c276d667c92a3",
|
||||||
|
"version": 1,
|
||||||
|
"domain": "upnp",
|
||||||
|
"title": "Ignored",
|
||||||
|
"data": {},
|
||||||
|
"options": {},
|
||||||
|
"pref_disable_new_entities": false,
|
||||||
|
"pref_disable_polling": false,
|
||||||
|
"source": "ignore",
|
||||||
|
"unique_id": "uuid:ab6abbcf-b79f-49dc-be9a-1ff32d48463a::urn:schemas-upnp-org:device:InternetGatewayDevice:2",
|
||||||
|
"disabled_by": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entry_id": "796149ee9cd94b5d8297bb0f78096a3f",
|
||||||
|
"version": 1,
|
||||||
|
"domain": "homekit_controller",
|
||||||
|
"title": "Ignored",
|
||||||
|
"data": {
|
||||||
|
"AccessoryIP": "192.168.86.34",
|
||||||
|
"AccessoryPort": 34127
|
||||||
|
},
|
||||||
|
"options": {},
|
||||||
|
"pref_disable_new_entities": false,
|
||||||
|
"pref_disable_polling": false,
|
||||||
|
"source": "ignore",
|
||||||
|
"unique_id": "30:0b:8b:2b:f8:2c",
|
||||||
|
"disabled_by": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entry_id": "6f0422dffc2011eab962e33c52698c7d",
|
||||||
|
"version": 1,
|
||||||
|
"domain": "sonarr",
|
||||||
|
"title": "192.168.86.198",
|
||||||
|
"data": {
|
||||||
|
"base_path": "/api",
|
||||||
|
"port": 8989,
|
||||||
|
"ssl": false,
|
||||||
|
"verify_ssl": false,
|
||||||
|
"host": "192.168.86.198",
|
||||||
|
"api_key": "0db45f76dbfc432ab5c86d8346d5165e"
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"upcoming_days": 1,
|
||||||
|
"wanted_max_items": 50
|
||||||
|
},
|
||||||
|
"pref_disable_new_entities": false,
|
||||||
|
"pref_disable_polling": false,
|
||||||
|
"source": "user",
|
||||||
|
"unique_id": null,
|
||||||
|
"disabled_by": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entry_id": "966199e4041f11eb892b391fb72793f3",
|
||||||
|
"version": 1,
|
||||||
|
"domain": "ecobee",
|
||||||
|
"title": "ecobee",
|
||||||
|
"data": {
|
||||||
|
"api_key": "8DSRpsILZJNUIJXvSzY6C4ufrMQNQsHa",
|
||||||
|
"refresh_token": "5bQ1gz8GSwHc3XIyTlM8qKkDjqb715Qr"
|
||||||
|
},
|
||||||
|
"options": {},
|
||||||
|
"pref_disable_new_entities": false,
|
||||||
|
"pref_disable_polling": false,
|
||||||
|
"source": "user",
|
||||||
|
"unique_id": null,
|
||||||
|
"disabled_by": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entry_id": "5b411d516be8bea4ccc31c46e6a36c0c",
|
||||||
|
"version": 1,
|
||||||
|
"domain": "hassio",
|
||||||
|
"title": "Supervisor",
|
||||||
|
"data": {},
|
||||||
|
"options": {},
|
||||||
|
"pref_disable_new_entities": false,
|
||||||
|
"pref_disable_polling": false,
|
||||||
|
"source": "system",
|
||||||
|
"unique_id": "hassio",
|
||||||
|
"disabled_by": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entry_id": "50aa91eb44e7c92d2b4a8a0ba028351e",
|
||||||
|
"version": 2,
|
||||||
|
"domain": "samsungtv",
|
||||||
|
"title": "Living room (UN55J6200)",
|
||||||
|
"data": {
|
||||||
|
"host": "192.168.86.42",
|
||||||
|
"mac": "78:bd:bc:79:42:85"
|
||||||
|
},
|
||||||
|
"options": {},
|
||||||
|
"pref_disable_new_entities": false,
|
||||||
|
"pref_disable_polling": false,
|
||||||
|
"source": "ignore",
|
||||||
|
"unique_id": "7f8d7dec-32da-4314-9391-cb1acc922b95",
|
||||||
|
"disabled_by": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entry_id": "4701417a1902ab7be569765641579e22",
|
||||||
|
"version": 1,
|
||||||
|
"domain": "upnp",
|
||||||
|
"title": "OnHub",
|
||||||
|
"data": {},
|
||||||
|
"options": {},
|
||||||
|
"pref_disable_new_entities": false,
|
||||||
|
"pref_disable_polling": false,
|
||||||
|
"source": "ignore",
|
||||||
|
"unique_id": "uuid:29422ada-a94b-4a3d-bcc9-aa24e2ac5c1e::urn:schemas-upnp-org:device:InternetGatewayDevice:2",
|
||||||
|
"disabled_by": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entry_id": "107d2d027a46461e1852309411b07b24",
|
||||||
|
"version": 1,
|
||||||
|
"domain": "homekit_controller",
|
||||||
|
"title": "Roku 4630X 5DE3",
|
||||||
|
"data": {
|
||||||
|
"AccessoryIP": "192.168.86.30",
|
||||||
|
"AccessoryPort": 55290
|
||||||
|
},
|
||||||
|
"options": {},
|
||||||
|
"pref_disable_new_entities": false,
|
||||||
|
"pref_disable_polling": false,
|
||||||
|
"source": "ignore",
|
||||||
|
"unique_id": "9d:11:93:91:d3:c7",
|
||||||
|
"disabled_by": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entry_id": "168e1b19293508dafb169cd8270442a4",
|
||||||
|
"version": 1,
|
||||||
|
"domain": "zwave_js",
|
||||||
|
"title": "Z-Wave JS",
|
||||||
|
"data": {
|
||||||
|
"url": "ws://core-zwave-js:3000",
|
||||||
|
"usb_path": "/dev/serial/by-id/usb-Silicon_Labs_HubZ_Smart_Home_Controller_90F00055-if00-port0",
|
||||||
|
"network_key": "1C07307043F183F395CECE7572FAA2B7",
|
||||||
|
"use_addon": true,
|
||||||
|
"integration_created_addon": false,
|
||||||
|
"s2_access_control_key": "65C8447AAFEF6C433E86C2B7209F6CD6",
|
||||||
|
"s2_authenticated_key": "AD3DF19206BC91EA5FB2D3B910E18E86",
|
||||||
|
"s2_unauthenticated_key": "4E8DF41AD19402953C04B7086A08035C"
|
||||||
|
},
|
||||||
|
"options": {},
|
||||||
|
"pref_disable_new_entities": false,
|
||||||
|
"pref_disable_polling": false,
|
||||||
|
"source": "hassio",
|
||||||
|
"unique_id": 4189927064,
|
||||||
|
"disabled_by": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entry_id": "58119655eea8bc6c0f5a9ac217b73372",
|
||||||
|
"version": 1,
|
||||||
|
"domain": "spotify",
|
||||||
|
"title": "Spotify",
|
||||||
|
"data": {},
|
||||||
|
"options": {},
|
||||||
|
"pref_disable_new_entities": false,
|
||||||
|
"pref_disable_polling": false,
|
||||||
|
"source": "ignore",
|
||||||
|
"unique_id": null,
|
||||||
|
"disabled_by": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entry_id": "737ca8cab48eb7350044f5ddf9742f3c",
|
||||||
|
"version": 1,
|
||||||
|
"domain": "upnp",
|
||||||
|
"title": "OnHub",
|
||||||
|
"data": {
|
||||||
|
"hostname": "192.168.86.1"
|
||||||
|
},
|
||||||
|
"options": {},
|
||||||
|
"pref_disable_new_entities": false,
|
||||||
|
"pref_disable_polling": false,
|
||||||
|
"source": "ignore",
|
||||||
|
"unique_id": "uuid:107841be-833f-4bda-83e7-bbb4922d5fae::urn:schemas-upnp-org:device:InternetGatewayDevice:2",
|
||||||
|
"disabled_by": null
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
725
.storage/core.device_registry
Normal file
725
.storage/core.device_registry
Normal file
@@ -0,0 +1,725 @@
|
|||||||
|
{
|
||||||
|
"version": 1,
|
||||||
|
"key": "core.device_registry",
|
||||||
|
"data": {
|
||||||
|
"devices": [
|
||||||
|
{
|
||||||
|
"config_entries": [
|
||||||
|
"77d68408d0a44c3c8ffa3747452065f1"
|
||||||
|
],
|
||||||
|
"connections": [
|
||||||
|
[
|
||||||
|
"mac",
|
||||||
|
"00:17:88:12:ec:3b"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"identifiers": [
|
||||||
|
[
|
||||||
|
"hue",
|
||||||
|
"001788FFFE12EC3B"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"manufacturer": "Signify",
|
||||||
|
"model": "BSB001",
|
||||||
|
"name": "Philips hue",
|
||||||
|
"sw_version": "01043155",
|
||||||
|
"entry_type": null,
|
||||||
|
"id": "1f01297a279d458eba50b1a7fced66e1",
|
||||||
|
"via_device_id": null,
|
||||||
|
"area_id": null,
|
||||||
|
"name_by_user": null,
|
||||||
|
"disabled_by": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"config_entries": [
|
||||||
|
"77d68408d0a44c3c8ffa3747452065f1"
|
||||||
|
],
|
||||||
|
"connections": [],
|
||||||
|
"identifiers": [
|
||||||
|
[
|
||||||
|
"hue",
|
||||||
|
"00:00:00:00:00:43:9a:80"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"manufacturer": "Philips",
|
||||||
|
"model": "ZGPSWITCH",
|
||||||
|
"name": "Hue Tap 1",
|
||||||
|
"sw_version": null,
|
||||||
|
"entry_type": null,
|
||||||
|
"id": "be9781e5f1c34bd496e75ecac9d1dcc6",
|
||||||
|
"via_device_id": "1f01297a279d458eba50b1a7fced66e1",
|
||||||
|
"area_id": null,
|
||||||
|
"name_by_user": null,
|
||||||
|
"disabled_by": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"config_entries": [
|
||||||
|
"77d68408d0a44c3c8ffa3747452065f1"
|
||||||
|
],
|
||||||
|
"connections": [],
|
||||||
|
"identifiers": [
|
||||||
|
[
|
||||||
|
"hue",
|
||||||
|
"00:17:88:01:00:d8:5c:9c-0b"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"manufacturer": "Philips",
|
||||||
|
"model": "LCT001",
|
||||||
|
"name": "Lamp left",
|
||||||
|
"sw_version": "5.127.1.26581",
|
||||||
|
"entry_type": null,
|
||||||
|
"id": "9d17b8ce7d7c40eb9046f414524209cb",
|
||||||
|
"via_device_id": "1f01297a279d458eba50b1a7fced66e1",
|
||||||
|
"area_id": "living",
|
||||||
|
"name_by_user": null,
|
||||||
|
"disabled_by": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"config_entries": [
|
||||||
|
"77d68408d0a44c3c8ffa3747452065f1"
|
||||||
|
],
|
||||||
|
"connections": [],
|
||||||
|
"identifiers": [
|
||||||
|
[
|
||||||
|
"hue",
|
||||||
|
"00:17:88:01:00:fa:c0:cd-0b"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"manufacturer": "Philips",
|
||||||
|
"model": "LCT001",
|
||||||
|
"name": "Table",
|
||||||
|
"sw_version": "5.127.1.26581",
|
||||||
|
"entry_type": null,
|
||||||
|
"id": "93d1f0f9d47146a7bec94a5514fe6173",
|
||||||
|
"via_device_id": "1f01297a279d458eba50b1a7fced66e1",
|
||||||
|
"area_id": "living",
|
||||||
|
"name_by_user": null,
|
||||||
|
"disabled_by": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"config_entries": [
|
||||||
|
"77d68408d0a44c3c8ffa3747452065f1"
|
||||||
|
],
|
||||||
|
"connections": [],
|
||||||
|
"identifiers": [
|
||||||
|
[
|
||||||
|
"hue",
|
||||||
|
"00:17:88:01:00:ef:e3:c3-0b"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"manufacturer": "Philips",
|
||||||
|
"model": "LCT001",
|
||||||
|
"name": "Lamp right",
|
||||||
|
"sw_version": "5.127.1.26581",
|
||||||
|
"entry_type": null,
|
||||||
|
"id": "e0b8f163c2354efb96ea5d9f2f05fad2",
|
||||||
|
"via_device_id": "1f01297a279d458eba50b1a7fced66e1",
|
||||||
|
"area_id": "living",
|
||||||
|
"name_by_user": null,
|
||||||
|
"disabled_by": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"config_entries": [
|
||||||
|
"4034b3eddaef4bee93ba6eb68f6d9e5e"
|
||||||
|
],
|
||||||
|
"connections": [],
|
||||||
|
"identifiers": [
|
||||||
|
[
|
||||||
|
"mobile_app",
|
||||||
|
"98756dd6f94f600d"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"manufacturer": "samsung",
|
||||||
|
"model": "SM-N960U",
|
||||||
|
"name": "SM-N960U",
|
||||||
|
"sw_version": "29",
|
||||||
|
"entry_type": null,
|
||||||
|
"id": "e65bc44886b84792ba48ae70e4fa57d7",
|
||||||
|
"via_device_id": null,
|
||||||
|
"area_id": null,
|
||||||
|
"name_by_user": null,
|
||||||
|
"disabled_by": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"config_entries": [
|
||||||
|
"52efee6cb13d42f3a5a13a89bd813400"
|
||||||
|
],
|
||||||
|
"connections": [],
|
||||||
|
"identifiers": [
|
||||||
|
[
|
||||||
|
"owntracks",
|
||||||
|
"dan_dansphone"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"manufacturer": null,
|
||||||
|
"model": null,
|
||||||
|
"name": null,
|
||||||
|
"sw_version": null,
|
||||||
|
"entry_type": null,
|
||||||
|
"id": "b4bf3f88d4924c94a93ff59c144c47c6",
|
||||||
|
"via_device_id": null,
|
||||||
|
"area_id": null,
|
||||||
|
"name_by_user": null,
|
||||||
|
"disabled_by": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"config_entries": [
|
||||||
|
"babd9cc14c314f6db8c5ce578dfa9564"
|
||||||
|
],
|
||||||
|
"connections": [
|
||||||
|
[
|
||||||
|
"zigbee",
|
||||||
|
"00:0d:6f:00:0a:ff:73:f8"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"identifiers": [
|
||||||
|
[
|
||||||
|
"zha",
|
||||||
|
"00:0d:6f:00:0a:ff:73:f8"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"manufacturer": "ZHA",
|
||||||
|
"model": "EZSP = Silicon Labs EmberZNet protocol: Elelabs, HUSBZB-1, Telegesis",
|
||||||
|
"name": "Zigbee Coordinator",
|
||||||
|
"sw_version": null,
|
||||||
|
"entry_type": null,
|
||||||
|
"id": "f05eae618c3d4f908b9022a05e88ce95",
|
||||||
|
"via_device_id": null,
|
||||||
|
"area_id": null,
|
||||||
|
"name_by_user": null,
|
||||||
|
"disabled_by": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"config_entries": [
|
||||||
|
"babd9cc14c314f6db8c5ce578dfa9564"
|
||||||
|
],
|
||||||
|
"connections": [
|
||||||
|
[
|
||||||
|
"zigbee",
|
||||||
|
"b0:ce:18:14:03:0b:3e:1d"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"identifiers": [
|
||||||
|
[
|
||||||
|
"zha",
|
||||||
|
"b0:ce:18:14:03:0b:3e:1d"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"manufacturer": "sengled",
|
||||||
|
"model": "E11-G13",
|
||||||
|
"name": "sengled E11-G13",
|
||||||
|
"sw_version": "0x00000003",
|
||||||
|
"entry_type": null,
|
||||||
|
"id": "9ea4a15795c44e8db70f13f26cca9a93",
|
||||||
|
"via_device_id": "f05eae618c3d4f908b9022a05e88ce95",
|
||||||
|
"area_id": null,
|
||||||
|
"name_by_user": null,
|
||||||
|
"disabled_by": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"config_entries": [
|
||||||
|
"babd9cc14c314f6db8c5ce578dfa9564"
|
||||||
|
],
|
||||||
|
"connections": [
|
||||||
|
[
|
||||||
|
"zigbee",
|
||||||
|
"b0:ce:18:14:03:0b:3e:fd"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"identifiers": [
|
||||||
|
[
|
||||||
|
"zha",
|
||||||
|
"b0:ce:18:14:03:0b:3e:fd"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"manufacturer": "sengled",
|
||||||
|
"model": "E11-G13",
|
||||||
|
"name": "sengled E11-G13",
|
||||||
|
"sw_version": "0x00000003",
|
||||||
|
"entry_type": null,
|
||||||
|
"id": "c57a16aa70724d0191fdb72f346d2dbb",
|
||||||
|
"via_device_id": "f05eae618c3d4f908b9022a05e88ce95",
|
||||||
|
"area_id": null,
|
||||||
|
"name_by_user": null,
|
||||||
|
"disabled_by": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"config_entries": [
|
||||||
|
"babd9cc14c314f6db8c5ce578dfa9564"
|
||||||
|
],
|
||||||
|
"connections": [
|
||||||
|
[
|
||||||
|
"zigbee",
|
||||||
|
"b0:ce:18:14:03:0b:22:ba"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"identifiers": [
|
||||||
|
[
|
||||||
|
"zha",
|
||||||
|
"b0:ce:18:14:03:0b:22:ba"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"manufacturer": "sengled",
|
||||||
|
"model": "E11-G13",
|
||||||
|
"name": "sengled E11-G13",
|
||||||
|
"sw_version": "0x00000003",
|
||||||
|
"entry_type": null,
|
||||||
|
"id": "2b0f187abe74477fab0d035f8817a8f6",
|
||||||
|
"via_device_id": "f05eae618c3d4f908b9022a05e88ce95",
|
||||||
|
"area_id": null,
|
||||||
|
"name_by_user": null,
|
||||||
|
"disabled_by": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"config_entries": [
|
||||||
|
"babd9cc14c314f6db8c5ce578dfa9564"
|
||||||
|
],
|
||||||
|
"connections": [
|
||||||
|
[
|
||||||
|
"zigbee",
|
||||||
|
"b0:ce:18:14:03:14:68:d6"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"identifiers": [
|
||||||
|
[
|
||||||
|
"zha",
|
||||||
|
"b0:ce:18:14:03:14:68:d6"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"manufacturer": "sengled",
|
||||||
|
"model": "E11-G13",
|
||||||
|
"name": "sengled E11-G13",
|
||||||
|
"sw_version": "0x00000009",
|
||||||
|
"entry_type": null,
|
||||||
|
"id": "1e92b72b37af42708165f56b484d1990",
|
||||||
|
"via_device_id": "f05eae618c3d4f908b9022a05e88ce95",
|
||||||
|
"area_id": null,
|
||||||
|
"name_by_user": null,
|
||||||
|
"disabled_by": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"config_entries": [
|
||||||
|
"babd9cc14c314f6db8c5ce578dfa9564"
|
||||||
|
],
|
||||||
|
"connections": [
|
||||||
|
[
|
||||||
|
"zigbee",
|
||||||
|
"b0:ce:18:14:03:0b:40:af"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"identifiers": [
|
||||||
|
[
|
||||||
|
"zha",
|
||||||
|
"b0:ce:18:14:03:0b:40:af"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"manufacturer": "sengled",
|
||||||
|
"model": "E11-G13",
|
||||||
|
"name": "sengled E11-G13",
|
||||||
|
"sw_version": "0x00000003",
|
||||||
|
"entry_type": null,
|
||||||
|
"id": "1828828e7283407894093611afecfa76",
|
||||||
|
"via_device_id": "f05eae618c3d4f908b9022a05e88ce95",
|
||||||
|
"area_id": null,
|
||||||
|
"name_by_user": null,
|
||||||
|
"disabled_by": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"config_entries": [
|
||||||
|
"6f0422dffc2011eab962e33c52698c7d"
|
||||||
|
],
|
||||||
|
"connections": [],
|
||||||
|
"identifiers": [
|
||||||
|
[
|
||||||
|
"sonarr",
|
||||||
|
"6f0422dffc2011eab962e33c52698c7d"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"manufacturer": "Sonarr",
|
||||||
|
"model": null,
|
||||||
|
"name": "Activity Sensor",
|
||||||
|
"sw_version": "3.0.6.1342",
|
||||||
|
"entry_type": "service",
|
||||||
|
"id": "6f365493fc2011ea9f59eb7dc9b07531",
|
||||||
|
"via_device_id": null,
|
||||||
|
"area_id": null,
|
||||||
|
"name_by_user": null,
|
||||||
|
"disabled_by": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"config_entries": [
|
||||||
|
"966199e4041f11eb892b391fb72793f3"
|
||||||
|
],
|
||||||
|
"connections": [],
|
||||||
|
"identifiers": [
|
||||||
|
[
|
||||||
|
"ecobee",
|
||||||
|
"411928765012"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"manufacturer": "ecobee",
|
||||||
|
"model": "ecobee3 lite Smart Thermostat",
|
||||||
|
"name": "Home",
|
||||||
|
"sw_version": null,
|
||||||
|
"entry_type": null,
|
||||||
|
"id": "97300b68041f11ebacaa396522618ab4",
|
||||||
|
"via_device_id": null,
|
||||||
|
"area_id": null,
|
||||||
|
"name_by_user": null,
|
||||||
|
"disabled_by": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"config_entries": [
|
||||||
|
"d449b87b7a8a4de2a1ee7ad105ac30b1"
|
||||||
|
],
|
||||||
|
"connections": [],
|
||||||
|
"identifiers": [
|
||||||
|
[
|
||||||
|
"met"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"manufacturer": "Met.no",
|
||||||
|
"model": "Forecast",
|
||||||
|
"name": "Forecast",
|
||||||
|
"sw_version": null,
|
||||||
|
"entry_type": "service",
|
||||||
|
"id": "aabcb64f5f211c38784f45faa5029cea",
|
||||||
|
"via_device_id": null,
|
||||||
|
"area_id": null,
|
||||||
|
"name_by_user": null,
|
||||||
|
"disabled_by": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"config_entries": [
|
||||||
|
"5b411d516be8bea4ccc31c46e6a36c0c"
|
||||||
|
],
|
||||||
|
"connections": [],
|
||||||
|
"identifiers": [
|
||||||
|
[
|
||||||
|
"hassio",
|
||||||
|
"core_ssh"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"manufacturer": "Official add-ons",
|
||||||
|
"model": "Home Assistant Add-on",
|
||||||
|
"name": "Terminal & SSH",
|
||||||
|
"sw_version": "9.2.1",
|
||||||
|
"entry_type": "service",
|
||||||
|
"id": "cd25a61cedb94647fd69655d947cab82",
|
||||||
|
"via_device_id": null,
|
||||||
|
"area_id": null,
|
||||||
|
"name_by_user": null,
|
||||||
|
"disabled_by": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"config_entries": [
|
||||||
|
"5b411d516be8bea4ccc31c46e6a36c0c"
|
||||||
|
],
|
||||||
|
"connections": [],
|
||||||
|
"identifiers": [
|
||||||
|
[
|
||||||
|
"hassio",
|
||||||
|
"core_git_pull"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"manufacturer": "Official add-ons",
|
||||||
|
"model": "Home Assistant Add-on",
|
||||||
|
"name": "Git pull",
|
||||||
|
"sw_version": "7.12",
|
||||||
|
"entry_type": "service",
|
||||||
|
"id": "a3ec358d220baf426ed6c78cc15e543c",
|
||||||
|
"via_device_id": null,
|
||||||
|
"area_id": null,
|
||||||
|
"name_by_user": null,
|
||||||
|
"disabled_by": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"config_entries": [
|
||||||
|
"5b411d516be8bea4ccc31c46e6a36c0c"
|
||||||
|
],
|
||||||
|
"connections": [],
|
||||||
|
"identifiers": [
|
||||||
|
[
|
||||||
|
"hassio",
|
||||||
|
"OS"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"manufacturer": "Home Assistant",
|
||||||
|
"model": "Home Assistant Operating System",
|
||||||
|
"name": "Home Assistant Operating System",
|
||||||
|
"sw_version": "6.5",
|
||||||
|
"entry_type": "service",
|
||||||
|
"id": "5dbbb7f52f9bcacdd90ffa48bf809110",
|
||||||
|
"via_device_id": null,
|
||||||
|
"area_id": null,
|
||||||
|
"name_by_user": null,
|
||||||
|
"disabled_by": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"config_entries": [
|
||||||
|
"babd9cc14c314f6db8c5ce578dfa9564"
|
||||||
|
],
|
||||||
|
"connections": [
|
||||||
|
[
|
||||||
|
"zigbee",
|
||||||
|
"00:12:4b:00:22:ea:ee:33"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"identifiers": [
|
||||||
|
[
|
||||||
|
"zha",
|
||||||
|
"00:12:4b:00:22:ea:ee:33"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"manufacturer": "eWeLink",
|
||||||
|
"model": "SA-003-Zigbee",
|
||||||
|
"name": "eWeLink SA-003-Zigbee",
|
||||||
|
"sw_version": null,
|
||||||
|
"entry_type": null,
|
||||||
|
"id": "262d5b6e6b377055fd1deefbb25b01fe",
|
||||||
|
"via_device_id": "f05eae618c3d4f908b9022a05e88ce95",
|
||||||
|
"area_id": "basement",
|
||||||
|
"name_by_user": null,
|
||||||
|
"disabled_by": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"config_entries": [
|
||||||
|
"5b411d516be8bea4ccc31c46e6a36c0c"
|
||||||
|
],
|
||||||
|
"connections": [],
|
||||||
|
"identifiers": [
|
||||||
|
[
|
||||||
|
"hassio",
|
||||||
|
"core_zwave_js"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"manufacturer": "Official add-ons",
|
||||||
|
"model": "Home Assistant Add-on",
|
||||||
|
"name": "Z-Wave JS",
|
||||||
|
"sw_version": "0.1.45",
|
||||||
|
"entry_type": "service",
|
||||||
|
"id": "bc6b9946fb0cfcc330a8296e0e3290ae",
|
||||||
|
"via_device_id": null,
|
||||||
|
"area_id": null,
|
||||||
|
"name_by_user": null,
|
||||||
|
"disabled_by": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"config_entries": [
|
||||||
|
"168e1b19293508dafb169cd8270442a4"
|
||||||
|
],
|
||||||
|
"connections": [],
|
||||||
|
"identifiers": [
|
||||||
|
[
|
||||||
|
"zwave_js",
|
||||||
|
"4189927064-1"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"manufacturer": "Sigma Designs (Former Zensys)",
|
||||||
|
"model": "HUSBZB-1",
|
||||||
|
"name": "QuickStick Combo",
|
||||||
|
"sw_version": "4.32",
|
||||||
|
"entry_type": null,
|
||||||
|
"id": "3017c2b633b22d41be13f03bd56672f0",
|
||||||
|
"via_device_id": null,
|
||||||
|
"area_id": null,
|
||||||
|
"name_by_user": null,
|
||||||
|
"disabled_by": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"config_entries": [
|
||||||
|
"168e1b19293508dafb169cd8270442a4"
|
||||||
|
],
|
||||||
|
"connections": [],
|
||||||
|
"identifiers": [
|
||||||
|
[
|
||||||
|
"zwave_js",
|
||||||
|
"4189927064-2"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"manufacturer": "Nortek Security & Control LLC",
|
||||||
|
"model": "PS15Z",
|
||||||
|
"name": "Plug-In Appliance Module",
|
||||||
|
"sw_version": "5.41",
|
||||||
|
"entry_type": null,
|
||||||
|
"id": "b43320eeef9b58d28234494e0aef12b8",
|
||||||
|
"via_device_id": null,
|
||||||
|
"area_id": null,
|
||||||
|
"name_by_user": null,
|
||||||
|
"disabled_by": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"config_entries": [
|
||||||
|
"babd9cc14c314f6db8c5ce578dfa9564"
|
||||||
|
],
|
||||||
|
"connections": [
|
||||||
|
[
|
||||||
|
"zigbee",
|
||||||
|
"00:12:4b:00:23:b7:78:94"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"identifiers": [
|
||||||
|
[
|
||||||
|
"zha",
|
||||||
|
"00:12:4b:00:23:b7:78:94"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"manufacturer": "SONOFF",
|
||||||
|
"model": "S31 Lite zb",
|
||||||
|
"name": "SONOFF S31 Lite zb",
|
||||||
|
"sw_version": null,
|
||||||
|
"entry_type": null,
|
||||||
|
"id": "9f5c4bbc8fe9161e5f20e44bb9bbacef",
|
||||||
|
"via_device_id": "f05eae618c3d4f908b9022a05e88ce95",
|
||||||
|
"area_id": "",
|
||||||
|
"name_by_user": null,
|
||||||
|
"disabled_by": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"config_entries": [
|
||||||
|
"5b411d516be8bea4ccc31c46e6a36c0c"
|
||||||
|
],
|
||||||
|
"connections": [],
|
||||||
|
"identifiers": [
|
||||||
|
[
|
||||||
|
"hassio",
|
||||||
|
"45df7312_zigbee2mqtt_edge"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"manufacturer": "Home Assistant Add-on: Zigbee2mqtt",
|
||||||
|
"model": "Home Assistant Add-on",
|
||||||
|
"name": "Zigbee2mqtt Edge",
|
||||||
|
"sw_version": "edge",
|
||||||
|
"entry_type": "service",
|
||||||
|
"id": "dfb728261d65045160bc85ec10bb43bf",
|
||||||
|
"via_device_id": null,
|
||||||
|
"area_id": null,
|
||||||
|
"name_by_user": null,
|
||||||
|
"disabled_by": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"config_entries": [
|
||||||
|
"487ba0d979a943a6b590f834f80258d0"
|
||||||
|
],
|
||||||
|
"connections": [],
|
||||||
|
"identifiers": [
|
||||||
|
[
|
||||||
|
"mqtt",
|
||||||
|
"zigbee2mqtt_0x00158d00070b32d6"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"manufacturer": "Xiaomi",
|
||||||
|
"model": "Aqara door & window contact sensor (MCCGQ11LM)",
|
||||||
|
"name": "Door",
|
||||||
|
"sw_version": "Zigbee2MQTT 1.21.1-dev",
|
||||||
|
"entry_type": null,
|
||||||
|
"id": "14e3db9dff274f58c9065e162ee7b8a6",
|
||||||
|
"via_device_id": null,
|
||||||
|
"area_id": null,
|
||||||
|
"name_by_user": null,
|
||||||
|
"disabled_by": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"config_entries": [
|
||||||
|
"5b411d516be8bea4ccc31c46e6a36c0c"
|
||||||
|
],
|
||||||
|
"connections": [],
|
||||||
|
"identifiers": [
|
||||||
|
[
|
||||||
|
"hassio",
|
||||||
|
"a0d7b954_appdaemon"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"manufacturer": "Home Assistant Community Add-ons",
|
||||||
|
"model": "Home Assistant Add-on",
|
||||||
|
"name": "AppDaemon 4",
|
||||||
|
"sw_version": "0.7.0",
|
||||||
|
"entry_type": "service",
|
||||||
|
"id": "a34971bea044ae5aa10d467f25c77bf9",
|
||||||
|
"via_device_id": null,
|
||||||
|
"area_id": null,
|
||||||
|
"name_by_user": null,
|
||||||
|
"disabled_by": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"config_entries": [
|
||||||
|
"168e1b19293508dafb169cd8270442a4"
|
||||||
|
],
|
||||||
|
"connections": [],
|
||||||
|
"identifiers": [
|
||||||
|
[
|
||||||
|
"zwave_js",
|
||||||
|
"4189927064-3"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"manufacturer": "Wink Inc.",
|
||||||
|
"model": "D/W SENSOR",
|
||||||
|
"name": "Door/Window Sensor",
|
||||||
|
"sw_version": "3.80",
|
||||||
|
"entry_type": null,
|
||||||
|
"id": "576d2874d1e67620b848c789be7d9e07",
|
||||||
|
"via_device_id": null,
|
||||||
|
"area_id": null,
|
||||||
|
"name_by_user": "Zwave Test Sensor",
|
||||||
|
"disabled_by": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"config_entries": [
|
||||||
|
"487ba0d979a943a6b590f834f80258d0"
|
||||||
|
],
|
||||||
|
"connections": [],
|
||||||
|
"identifiers": [
|
||||||
|
[
|
||||||
|
"mqtt",
|
||||||
|
"zigbee2mqtt_0x00158d0006f39e88"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"manufacturer": "Xiaomi",
|
||||||
|
"model": "Aqara wireless switch (WXKG11LM)",
|
||||||
|
"name": "Office Switch",
|
||||||
|
"sw_version": "Zigbee2MQTT 1.21.1-dev",
|
||||||
|
"entry_type": null,
|
||||||
|
"id": "c587194eb713c5cc45e0fef31080796b",
|
||||||
|
"via_device_id": null,
|
||||||
|
"area_id": null,
|
||||||
|
"name_by_user": null,
|
||||||
|
"disabled_by": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"deleted_devices": [
|
||||||
|
{
|
||||||
|
"config_entries": [
|
||||||
|
"5b411d516be8bea4ccc31c46e6a36c0c"
|
||||||
|
],
|
||||||
|
"connections": [],
|
||||||
|
"identifiers": [
|
||||||
|
[
|
||||||
|
"hassio",
|
||||||
|
"45df7312_zigbee2mqtt"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"id": "02f26deab8f5be50ea518504ffab750f",
|
||||||
|
"orphaned_timestamp": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"config_entries": [
|
||||||
|
"babd9cc14c314f6db8c5ce578dfa9564"
|
||||||
|
],
|
||||||
|
"connections": [
|
||||||
|
[
|
||||||
|
"zigbee",
|
||||||
|
"00:15:8d:00:07:0b:32:d6"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"identifiers": [
|
||||||
|
[
|
||||||
|
"zha",
|
||||||
|
"00:15:8d:00:07:0b:32:d6"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"id": "d353ec958e7f6bff604a280eb32fe472",
|
||||||
|
"orphaned_timestamp": null
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
1774
.storage/core.entity_registry
Normal file
1774
.storage/core.entity_registry
Normal file
File diff suppressed because it is too large
Load Diff
818
.storage/core.restore_state
Normal file
818
.storage/core.restore_state
Normal file
@@ -0,0 +1,818 @@
|
|||||||
|
{
|
||||||
|
"version": 1,
|
||||||
|
"key": "core.restore_state",
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"state": {
|
||||||
|
"entity_id": "person.dan",
|
||||||
|
"state": "home",
|
||||||
|
"attributes": {
|
||||||
|
"editable": false,
|
||||||
|
"id": "dan",
|
||||||
|
"latitude": 41.399037,
|
||||||
|
"longitude": -82.2331938,
|
||||||
|
"gps_accuracy": 11,
|
||||||
|
"source": "device_tracker.sm_n960u",
|
||||||
|
"user_id": "e2ac2db8725247b0bb84b03718a01c39",
|
||||||
|
"friendly_name": "Dan"
|
||||||
|
},
|
||||||
|
"last_changed": "2021-11-04T05:13:46.408785+00:00",
|
||||||
|
"last_updated": "2021-11-04T05:14:08.377704+00:00",
|
||||||
|
"context": {
|
||||||
|
"id": "95eebc813860505bd3e4287ab2eb2565",
|
||||||
|
"parent_id": null,
|
||||||
|
"user_id": null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"last_seen": "2021-11-04T05:14:08.459128+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"state": {
|
||||||
|
"entity_id": "input_boolean.sleeping",
|
||||||
|
"state": "off",
|
||||||
|
"attributes": {
|
||||||
|
"editable": true,
|
||||||
|
"friendly_name": "sleeping"
|
||||||
|
},
|
||||||
|
"last_changed": "2021-11-04T05:13:52.487414+00:00",
|
||||||
|
"last_updated": "2021-11-04T05:13:52.487414+00:00",
|
||||||
|
"context": {
|
||||||
|
"id": "5b0bbb1f93020aac91d380d9a3f4c5b4",
|
||||||
|
"parent_id": null,
|
||||||
|
"user_id": null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"last_seen": "2021-11-04T05:14:08.459128+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"state": {
|
||||||
|
"entity_id": "input_boolean.thermostat_mode",
|
||||||
|
"state": "on",
|
||||||
|
"attributes": {
|
||||||
|
"editable": true,
|
||||||
|
"friendly_name": "thermostat_mode"
|
||||||
|
},
|
||||||
|
"last_changed": "2021-11-04T05:13:52.488063+00:00",
|
||||||
|
"last_updated": "2021-11-04T05:13:52.488063+00:00",
|
||||||
|
"context": {
|
||||||
|
"id": "9953f1b2aaecb34e721e5e456610b84f",
|
||||||
|
"parent_id": null,
|
||||||
|
"user_id": null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"last_seen": "2021-11-04T05:14:08.459128+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"state": {
|
||||||
|
"entity_id": "sensor.sm_n960u_battery_level",
|
||||||
|
"state": "40",
|
||||||
|
"attributes": {
|
||||||
|
"unit_of_measurement": "%",
|
||||||
|
"friendly_name": "SM-N960U Battery Level",
|
||||||
|
"icon": "mdi:battery-40",
|
||||||
|
"device_class": "battery"
|
||||||
|
},
|
||||||
|
"last_changed": "2021-11-04T05:14:05.497508+00:00",
|
||||||
|
"last_updated": "2021-11-04T05:14:05.497508+00:00",
|
||||||
|
"context": {
|
||||||
|
"id": "3346e60960804ef1b9937ada634a62b7",
|
||||||
|
"parent_id": null,
|
||||||
|
"user_id": null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"last_seen": "2021-11-04T05:14:08.459128+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"state": {
|
||||||
|
"entity_id": "sensor.sm_n960u_battery_state",
|
||||||
|
"state": "discharging",
|
||||||
|
"attributes": {
|
||||||
|
"friendly_name": "SM-N960U Battery State",
|
||||||
|
"icon": "mdi:battery-minus",
|
||||||
|
"device_class": "battery"
|
||||||
|
},
|
||||||
|
"last_changed": "2021-11-04T05:14:05.498621+00:00",
|
||||||
|
"last_updated": "2021-11-04T05:14:05.498621+00:00",
|
||||||
|
"context": {
|
||||||
|
"id": "475654b0adab3b5eb54f0fb4f77c88e5",
|
||||||
|
"parent_id": null,
|
||||||
|
"user_id": null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"last_seen": "2021-11-04T05:14:08.459128+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"state": {
|
||||||
|
"entity_id": "sensor.sm_n960u_wifi_connection",
|
||||||
|
"state": "DD",
|
||||||
|
"attributes": {
|
||||||
|
"is_hidden": false,
|
||||||
|
"friendly_name": "SM-N960U Wifi Connection",
|
||||||
|
"icon": "mdi:wifi"
|
||||||
|
},
|
||||||
|
"last_changed": "2021-11-04T05:14:05.499582+00:00",
|
||||||
|
"last_updated": "2021-11-04T05:14:05.499582+00:00",
|
||||||
|
"context": {
|
||||||
|
"id": "1de69abbf1fc179cc4a79da504ac81be",
|
||||||
|
"parent_id": null,
|
||||||
|
"user_id": null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"last_seen": "2021-11-04T05:14:08.459128+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"state": {
|
||||||
|
"entity_id": "sensor.sm_n960u_bluetooth_connection",
|
||||||
|
"state": "0",
|
||||||
|
"attributes": {
|
||||||
|
"connected_not_paired_devices": "[]",
|
||||||
|
"connected_paired_devices": "[]",
|
||||||
|
"paired_devices": "[88:E0:34:36:91:72, 00:06:66:1E:27:3A, 00:1E:AE:9D:26:78]",
|
||||||
|
"unit_of_measurement": "connection(s)",
|
||||||
|
"friendly_name": "SM-N960U Bluetooth Connection",
|
||||||
|
"icon": "mdi:bluetooth"
|
||||||
|
},
|
||||||
|
"last_changed": "2021-11-04T05:14:05.500458+00:00",
|
||||||
|
"last_updated": "2021-11-04T05:14:05.500458+00:00",
|
||||||
|
"context": {
|
||||||
|
"id": "15c94add5b754c5aedc5151c025d20fc",
|
||||||
|
"parent_id": null,
|
||||||
|
"user_id": null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"last_seen": "2021-11-04T05:14:08.459128+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"state": {
|
||||||
|
"entity_id": "sensor.sm_n960u_geocoded_location",
|
||||||
|
"state": "353 Seeley St, Amherst, OH 44001, USA",
|
||||||
|
"attributes": {
|
||||||
|
"Administrative Area": "Ohio",
|
||||||
|
"Country": "United States",
|
||||||
|
"ISO Country Code": "US",
|
||||||
|
"Latitude": 41.399014,
|
||||||
|
"Locality": "Amherst",
|
||||||
|
"Longitude": -82.23327,
|
||||||
|
"Postal Code": "44001",
|
||||||
|
"Sub Administrative Area": "Lorain County",
|
||||||
|
"Sub Locality": "null",
|
||||||
|
"Sub Thoroughfare": "353",
|
||||||
|
"Thoroughfare": "Seeley Street",
|
||||||
|
"friendly_name": "SM-N960U Geocoded Location",
|
||||||
|
"icon": "mdi:map"
|
||||||
|
},
|
||||||
|
"last_changed": "2021-11-04T05:14:05.501326+00:00",
|
||||||
|
"last_updated": "2021-11-04T05:14:05.501326+00:00",
|
||||||
|
"context": {
|
||||||
|
"id": "6d7704b3ee7e56dd2caa255a103fcf39",
|
||||||
|
"parent_id": null,
|
||||||
|
"user_id": null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"last_seen": "2021-11-04T05:14:08.459128+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"state": {
|
||||||
|
"entity_id": "sensor.sm_n960u_light_sensor",
|
||||||
|
"state": "9",
|
||||||
|
"attributes": {
|
||||||
|
"unit_of_measurement": "lx",
|
||||||
|
"friendly_name": "SM-N960U Light Sensor",
|
||||||
|
"icon": "mdi:brightness-5",
|
||||||
|
"device_class": "illuminance"
|
||||||
|
},
|
||||||
|
"last_changed": "2021-11-04T05:14:05.502176+00:00",
|
||||||
|
"last_updated": "2021-11-04T05:14:05.502176+00:00",
|
||||||
|
"context": {
|
||||||
|
"id": "6a0fca08dee08e01bb82cc1b40ca8848",
|
||||||
|
"parent_id": null,
|
||||||
|
"user_id": null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"last_seen": "2021-11-04T05:14:08.459128+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"state": {
|
||||||
|
"entity_id": "sensor.sm_n960u_next_alarm",
|
||||||
|
"state": "2021-11-04T13:00:00.000Z",
|
||||||
|
"attributes": {
|
||||||
|
"Local Time": "Thu Nov 04 09:00:00 EDT 2021",
|
||||||
|
"Package": "com.urbandroid.sleep",
|
||||||
|
"Time in Milliseconds": 1636030800000,
|
||||||
|
"friendly_name": "SM-N960U Next Alarm",
|
||||||
|
"icon": "mdi:alarm",
|
||||||
|
"device_class": "timestamp"
|
||||||
|
},
|
||||||
|
"last_changed": "2021-11-04T05:14:05.503030+00:00",
|
||||||
|
"last_updated": "2021-11-04T05:14:05.503030+00:00",
|
||||||
|
"context": {
|
||||||
|
"id": "0b53def8535d77ca1d0d1226caba5e56",
|
||||||
|
"parent_id": null,
|
||||||
|
"user_id": null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"last_seen": "2021-11-04T05:14:08.459128+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"state": {
|
||||||
|
"entity_id": "sensor.sm_n960u_storage_sensor",
|
||||||
|
"state": "33",
|
||||||
|
"attributes": {
|
||||||
|
"Free internal storage": "37GB",
|
||||||
|
"Total internal storage": "110GB",
|
||||||
|
"unit_of_measurement": "%",
|
||||||
|
"friendly_name": "SM-N960U Storage Sensor",
|
||||||
|
"icon": "mdi:harddisk"
|
||||||
|
},
|
||||||
|
"last_changed": "2021-11-04T05:14:05.503878+00:00",
|
||||||
|
"last_updated": "2021-11-04T05:14:05.503878+00:00",
|
||||||
|
"context": {
|
||||||
|
"id": "4f662d6492e980be5258fcd743e0e065",
|
||||||
|
"parent_id": null,
|
||||||
|
"user_id": null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"last_seen": "2021-11-04T05:14:08.459128+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"state": {
|
||||||
|
"entity_id": "sensor.sm_n960u_audio_sensor",
|
||||||
|
"state": "normal",
|
||||||
|
"attributes": {
|
||||||
|
"friendly_name": "SM-N960U Audio Sensor",
|
||||||
|
"icon": "mdi:volume-high"
|
||||||
|
},
|
||||||
|
"last_changed": "2021-11-04T05:14:05.504892+00:00",
|
||||||
|
"last_updated": "2021-11-04T05:14:05.504892+00:00",
|
||||||
|
"context": {
|
||||||
|
"id": "ff9aaf5f8dd93951b77c337c845037a3",
|
||||||
|
"parent_id": null,
|
||||||
|
"user_id": null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"last_seen": "2021-11-04T05:14:08.459128+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"state": {
|
||||||
|
"entity_id": "sensor.sm_n960u_do_not_disturb_sensor",
|
||||||
|
"state": "off",
|
||||||
|
"attributes": {
|
||||||
|
"friendly_name": "SM-N960U Do Not Disturb Sensor",
|
||||||
|
"icon": "mdi:minus-circle"
|
||||||
|
},
|
||||||
|
"last_changed": "2021-11-04T05:14:05.505777+00:00",
|
||||||
|
"last_updated": "2021-11-04T05:14:05.505777+00:00",
|
||||||
|
"context": {
|
||||||
|
"id": "87f2d8a0c1b84b64250481f22158a3aa",
|
||||||
|
"parent_id": null,
|
||||||
|
"user_id": null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"last_seen": "2021-11-04T05:14:08.459128+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"state": {
|
||||||
|
"entity_id": "sensor.sm_n960u_last_reboot",
|
||||||
|
"state": "2021-10-18T19:15:27Z",
|
||||||
|
"attributes": {
|
||||||
|
"Local Time": "Mon Oct 18 15:15:27 EDT 2021",
|
||||||
|
"Time in Milliseconds": 1634584527588,
|
||||||
|
"friendly_name": "SM-N960U Last Reboot",
|
||||||
|
"icon": "mdi:restart",
|
||||||
|
"device_class": "timestamp"
|
||||||
|
},
|
||||||
|
"last_changed": "2021-11-04T05:14:05.506643+00:00",
|
||||||
|
"last_updated": "2021-11-04T05:14:05.506643+00:00",
|
||||||
|
"context": {
|
||||||
|
"id": "8c5162b3e93e75d68c88bd0c263693e9",
|
||||||
|
"parent_id": null,
|
||||||
|
"user_id": null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"last_seen": "2021-11-04T05:14:08.459128+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"state": {
|
||||||
|
"entity_id": "sensor.sm_n960u_pressure_sensor",
|
||||||
|
"state": "1004.3",
|
||||||
|
"attributes": {
|
||||||
|
"unit_of_measurement": "hPa",
|
||||||
|
"friendly_name": "SM-N960U Pressure Sensor",
|
||||||
|
"icon": "mdi:gauge",
|
||||||
|
"device_class": "pressure"
|
||||||
|
},
|
||||||
|
"last_changed": "2021-11-04T05:14:05.507497+00:00",
|
||||||
|
"last_updated": "2021-11-04T05:14:05.507497+00:00",
|
||||||
|
"context": {
|
||||||
|
"id": "b728aa2ec0e7b0c7d00a5e9a40065fcd",
|
||||||
|
"parent_id": null,
|
||||||
|
"user_id": null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"last_seen": "2021-11-04T05:14:08.459128+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"state": {
|
||||||
|
"entity_id": "sensor.sm_n960u_proximity_sensor",
|
||||||
|
"state": "8",
|
||||||
|
"attributes": {
|
||||||
|
"friendly_name": "SM-N960U Proximity Sensor",
|
||||||
|
"icon": "mdi:leak"
|
||||||
|
},
|
||||||
|
"last_changed": "2021-11-04T05:14:05.508351+00:00",
|
||||||
|
"last_updated": "2021-11-04T05:14:05.508351+00:00",
|
||||||
|
"context": {
|
||||||
|
"id": "b18f2822f6764866ca724b9a6dc516e5",
|
||||||
|
"parent_id": null,
|
||||||
|
"user_id": null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"last_seen": "2021-11-04T05:14:08.459128+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"state": {
|
||||||
|
"entity_id": "sensor.sm_n960u_charger_type",
|
||||||
|
"state": "none",
|
||||||
|
"attributes": {
|
||||||
|
"friendly_name": "SM-N960U Charger Type",
|
||||||
|
"icon": "mdi:battery"
|
||||||
|
},
|
||||||
|
"last_changed": "2021-11-04T05:14:05.509209+00:00",
|
||||||
|
"last_updated": "2021-11-04T05:14:05.509209+00:00",
|
||||||
|
"context": {
|
||||||
|
"id": "cab1705f444adf645792386c43c4e339",
|
||||||
|
"parent_id": null,
|
||||||
|
"user_id": null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"last_seen": "2021-11-04T05:14:08.459128+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"state": {
|
||||||
|
"entity_id": "sensor.sm_n960u_battery_health",
|
||||||
|
"state": "good",
|
||||||
|
"attributes": {
|
||||||
|
"friendly_name": "SM-N960U Battery Health",
|
||||||
|
"icon": "mdi:battery-heart-variant"
|
||||||
|
},
|
||||||
|
"last_changed": "2021-11-04T05:14:05.510046+00:00",
|
||||||
|
"last_updated": "2021-11-04T05:14:05.510046+00:00",
|
||||||
|
"context": {
|
||||||
|
"id": "244be23fdabffb8b4ea0b61b1ed7140a",
|
||||||
|
"parent_id": null,
|
||||||
|
"user_id": null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"last_seen": "2021-11-04T05:14:08.459128+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"state": {
|
||||||
|
"entity_id": "sensor.sm_n960u_battery_temperature",
|
||||||
|
"state": "21.1",
|
||||||
|
"attributes": {
|
||||||
|
"unit_of_measurement": "\u00b0F",
|
||||||
|
"friendly_name": "SM-N960U Battery Temperature",
|
||||||
|
"icon": "mdi:battery",
|
||||||
|
"device_class": "temperature"
|
||||||
|
},
|
||||||
|
"last_changed": "2021-11-04T05:14:05.510912+00:00",
|
||||||
|
"last_updated": "2021-11-04T05:14:05.510912+00:00",
|
||||||
|
"context": {
|
||||||
|
"id": "7bc0575c2888443928183f54e66127dc",
|
||||||
|
"parent_id": null,
|
||||||
|
"user_id": null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"last_seen": "2021-11-04T05:14:08.459128+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"state": {
|
||||||
|
"entity_id": "binary_sensor.sm_n960u_is_charging",
|
||||||
|
"state": "off",
|
||||||
|
"attributes": {
|
||||||
|
"friendly_name": "SM-N960U Is Charging",
|
||||||
|
"icon": "mdi:power-plug-off",
|
||||||
|
"device_class": "plug"
|
||||||
|
},
|
||||||
|
"last_changed": "2021-11-04T05:14:05.511812+00:00",
|
||||||
|
"last_updated": "2021-11-04T05:14:05.511812+00:00",
|
||||||
|
"context": {
|
||||||
|
"id": "3342a971325ccfe14627403f8d17ec81",
|
||||||
|
"parent_id": null,
|
||||||
|
"user_id": null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"last_seen": "2021-11-04T05:14:08.459128+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"state": {
|
||||||
|
"entity_id": "device_tracker.sm_n960u",
|
||||||
|
"state": "home",
|
||||||
|
"attributes": {
|
||||||
|
"source_type": "gps",
|
||||||
|
"latitude": 41.399037,
|
||||||
|
"longitude": -82.2331938,
|
||||||
|
"gps_accuracy": 11,
|
||||||
|
"altitude": 172.0,
|
||||||
|
"course": 0,
|
||||||
|
"speed": 0,
|
||||||
|
"vertical_accuracy": 3,
|
||||||
|
"friendly_name": "Dans Phone"
|
||||||
|
},
|
||||||
|
"last_changed": "2021-11-04T05:14:05.617412+00:00",
|
||||||
|
"last_updated": "2021-11-04T05:14:05.617412+00:00",
|
||||||
|
"context": {
|
||||||
|
"id": "0db767e3b3c40817a15c400461939be2",
|
||||||
|
"parent_id": null,
|
||||||
|
"user_id": null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"last_seen": "2021-11-04T05:14:08.459128+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"state": {
|
||||||
|
"entity_id": "device_tracker.dan_dansphone",
|
||||||
|
"state": "home",
|
||||||
|
"attributes": {
|
||||||
|
"source_type": "gps",
|
||||||
|
"battery_level": 74,
|
||||||
|
"latitude": 41.3990354,
|
||||||
|
"longitude": -82.2331867,
|
||||||
|
"gps_accuracy": 11,
|
||||||
|
"friendly_name": "Dan"
|
||||||
|
},
|
||||||
|
"last_changed": "2021-11-04T05:14:05.805204+00:00",
|
||||||
|
"last_updated": "2021-11-04T05:14:05.805204+00:00",
|
||||||
|
"context": {
|
||||||
|
"id": "c3b74675c117e4e49aa810a1b6143e41",
|
||||||
|
"parent_id": null,
|
||||||
|
"user_id": null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"last_seen": "2021-11-04T05:14:08.459128+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"state": {
|
||||||
|
"entity_id": "sensor.sengled_e11_g13_1d3e0b03_smartenergy_metering",
|
||||||
|
"state": "0.0",
|
||||||
|
"attributes": {
|
||||||
|
"state_class": "measurement",
|
||||||
|
"device_type": "Electric Metering",
|
||||||
|
"status": "NO_ALARMS",
|
||||||
|
"unit_of_measurement": "W",
|
||||||
|
"friendly_name": "sengled E11-G13 1d3e0b03 smartenergy_metering",
|
||||||
|
"device_class": "power"
|
||||||
|
},
|
||||||
|
"last_changed": "2021-10-16T16:24:26.176723+00:00",
|
||||||
|
"last_updated": "2021-10-16T16:24:26.176723+00:00",
|
||||||
|
"context": {
|
||||||
|
"id": "49cfe0ff36514b4af8b072c16314bcac",
|
||||||
|
"parent_id": null,
|
||||||
|
"user_id": null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"last_seen": "2021-11-04T05:08:04.659722+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"state": {
|
||||||
|
"entity_id": "sensor.sengled_e11_g13_fd3e0b03_smartenergy_metering",
|
||||||
|
"state": "0.0",
|
||||||
|
"attributes": {
|
||||||
|
"state_class": "measurement",
|
||||||
|
"device_type": "Electric Metering",
|
||||||
|
"status": "NO_ALARMS",
|
||||||
|
"unit_of_measurement": "W",
|
||||||
|
"friendly_name": "sengled E11-G13 fd3e0b03 smartenergy_metering",
|
||||||
|
"device_class": "power"
|
||||||
|
},
|
||||||
|
"last_changed": "2021-10-16T16:24:26.186477+00:00",
|
||||||
|
"last_updated": "2021-10-16T16:24:26.186477+00:00",
|
||||||
|
"context": {
|
||||||
|
"id": "a22aabe45def5a96e4443fe15353c503",
|
||||||
|
"parent_id": null,
|
||||||
|
"user_id": null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"last_seen": "2021-11-04T05:08:04.659722+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"state": {
|
||||||
|
"entity_id": "sensor.sengled_e11_g13_ba220b03_smartenergy_metering",
|
||||||
|
"state": "0.0",
|
||||||
|
"attributes": {
|
||||||
|
"state_class": "measurement",
|
||||||
|
"device_type": "Electric Metering",
|
||||||
|
"status": "NO_ALARMS",
|
||||||
|
"unit_of_measurement": "W",
|
||||||
|
"friendly_name": "sengled E11-G13 ba220b03 smartenergy_metering",
|
||||||
|
"device_class": "power"
|
||||||
|
},
|
||||||
|
"last_changed": "2021-10-16T16:24:26.195594+00:00",
|
||||||
|
"last_updated": "2021-10-16T16:24:26.195594+00:00",
|
||||||
|
"context": {
|
||||||
|
"id": "9393768168ad98f6a66d28236affbdea",
|
||||||
|
"parent_id": null,
|
||||||
|
"user_id": null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"last_seen": "2021-11-04T05:08:04.659722+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"state": {
|
||||||
|
"entity_id": "sensor.sengled_e11_g13_d6681403_smartenergy_metering",
|
||||||
|
"state": "0.0",
|
||||||
|
"attributes": {
|
||||||
|
"state_class": "measurement",
|
||||||
|
"device_type": "Electric Metering",
|
||||||
|
"status": "NO_ALARMS",
|
||||||
|
"unit_of_measurement": "W",
|
||||||
|
"friendly_name": "sengled E11-G13 d6681403 smartenergy_metering",
|
||||||
|
"device_class": "power"
|
||||||
|
},
|
||||||
|
"last_changed": "2021-10-16T16:24:26.203577+00:00",
|
||||||
|
"last_updated": "2021-10-16T16:24:26.203577+00:00",
|
||||||
|
"context": {
|
||||||
|
"id": "f7c792c8652b800ba4543afbda30e7fc",
|
||||||
|
"parent_id": null,
|
||||||
|
"user_id": null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"last_seen": "2021-11-04T05:08:04.659722+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"state": {
|
||||||
|
"entity_id": "light.sengled_e11_g13_1d3e0b03_level_on_off",
|
||||||
|
"state": "off",
|
||||||
|
"attributes": {
|
||||||
|
"supported_color_modes": [
|
||||||
|
"brightness"
|
||||||
|
],
|
||||||
|
"off_brightness": null,
|
||||||
|
"friendly_name": "Entry Way",
|
||||||
|
"supported_features": 41
|
||||||
|
},
|
||||||
|
"last_changed": "2021-11-03T19:02:52.766372+00:00",
|
||||||
|
"last_updated": "2021-11-03T19:02:52.766372+00:00",
|
||||||
|
"context": {
|
||||||
|
"id": "a026b1a46e98bb86de7020317d6db862",
|
||||||
|
"parent_id": null,
|
||||||
|
"user_id": "e2ac2db8725247b0bb84b03718a01c39"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"last_seen": "2021-11-04T05:08:04.659722+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"state": {
|
||||||
|
"entity_id": "light.sengled_e11_g13_fd3e0b03_level_on_off",
|
||||||
|
"state": "on",
|
||||||
|
"attributes": {
|
||||||
|
"supported_color_modes": [
|
||||||
|
"brightness"
|
||||||
|
],
|
||||||
|
"color_mode": "brightness",
|
||||||
|
"brightness": 254,
|
||||||
|
"off_brightness": null,
|
||||||
|
"friendly_name": "Dinning Rom",
|
||||||
|
"supported_features": 41
|
||||||
|
},
|
||||||
|
"last_changed": "2021-11-04T02:54:47.852214+00:00",
|
||||||
|
"last_updated": "2021-11-04T02:54:47.852214+00:00",
|
||||||
|
"context": {
|
||||||
|
"id": "fed24a2309b0693ebc1a686aa8929fbf",
|
||||||
|
"parent_id": null,
|
||||||
|
"user_id": "e2ac2db8725247b0bb84b03718a01c39"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"last_seen": "2021-11-04T05:08:04.659722+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"state": {
|
||||||
|
"entity_id": "light.sengled_e11_g13_ba220b03_level_on_off",
|
||||||
|
"state": "on",
|
||||||
|
"attributes": {
|
||||||
|
"supported_color_modes": [
|
||||||
|
"brightness"
|
||||||
|
],
|
||||||
|
"color_mode": "brightness",
|
||||||
|
"brightness": 254,
|
||||||
|
"off_brightness": null,
|
||||||
|
"friendly_name": "Living Room",
|
||||||
|
"supported_features": 41
|
||||||
|
},
|
||||||
|
"last_changed": "2021-11-04T02:54:49.821120+00:00",
|
||||||
|
"last_updated": "2021-11-04T02:54:49.821120+00:00",
|
||||||
|
"context": {
|
||||||
|
"id": "51feff96d1757df097d5b32378f3234d",
|
||||||
|
"parent_id": null,
|
||||||
|
"user_id": "e2ac2db8725247b0bb84b03718a01c39"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"last_seen": "2021-11-04T05:08:04.659722+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"state": {
|
||||||
|
"entity_id": "light.sengled_e11_g13_d6681403_level_on_off",
|
||||||
|
"state": "off",
|
||||||
|
"attributes": {
|
||||||
|
"supported_color_modes": [
|
||||||
|
"brightness"
|
||||||
|
],
|
||||||
|
"off_brightness": null,
|
||||||
|
"friendly_name": "Office",
|
||||||
|
"supported_features": 41
|
||||||
|
},
|
||||||
|
"last_changed": "2021-11-04T02:54:29.770893+00:00",
|
||||||
|
"last_updated": "2021-11-04T02:54:29.770893+00:00",
|
||||||
|
"context": {
|
||||||
|
"id": "43cbf18fb0b0d9c32fb05c2b3ef84736",
|
||||||
|
"parent_id": null,
|
||||||
|
"user_id": "e2ac2db8725247b0bb84b03718a01c39"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"last_seen": "2021-11-04T05:08:04.659722+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"state": {
|
||||||
|
"entity_id": "sensor.sengled_e11_g13_af400b03_smartenergy_metering",
|
||||||
|
"state": "unavailable",
|
||||||
|
"attributes": {
|
||||||
|
"state_class": "measurement",
|
||||||
|
"unit_of_measurement": "W",
|
||||||
|
"friendly_name": "sengled E11-G13 af400b03 smartenergy_metering",
|
||||||
|
"device_class": "power"
|
||||||
|
},
|
||||||
|
"last_changed": "2021-10-18T21:19:45.565788+00:00",
|
||||||
|
"last_updated": "2021-10-18T21:19:45.565788+00:00",
|
||||||
|
"context": {
|
||||||
|
"id": "800ad05f1a8eb77f0956aadd2ea75917",
|
||||||
|
"parent_id": null,
|
||||||
|
"user_id": null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"last_seen": "2021-11-04T05:08:04.659722+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"state": {
|
||||||
|
"entity_id": "light.sengled_e11_g13_af400b03_level_on_off",
|
||||||
|
"state": "unavailable",
|
||||||
|
"attributes": {
|
||||||
|
"supported_color_modes": [
|
||||||
|
"brightness"
|
||||||
|
],
|
||||||
|
"friendly_name": "sengled E11-G13 af400b03 level, on_off",
|
||||||
|
"supported_features": 41
|
||||||
|
},
|
||||||
|
"last_changed": "2021-10-18T21:19:45.567171+00:00",
|
||||||
|
"last_updated": "2021-10-18T21:19:45.567171+00:00",
|
||||||
|
"context": {
|
||||||
|
"id": "09d6691729b885ee199dc24dcec127f7",
|
||||||
|
"parent_id": null,
|
||||||
|
"user_id": null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"last_seen": "2021-11-04T05:08:04.659722+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"state": {
|
||||||
|
"entity_id": "switch.ewelink_sa_003_zigbee_33eeea22_on_off",
|
||||||
|
"state": "off",
|
||||||
|
"attributes": {
|
||||||
|
"friendly_name": "Grow Fan"
|
||||||
|
},
|
||||||
|
"last_changed": "2021-11-04T01:30:38.495714+00:00",
|
||||||
|
"last_updated": "2021-11-04T01:30:38.495714+00:00",
|
||||||
|
"context": {
|
||||||
|
"id": "bc5be675ff9831e771a6af652e942cf2",
|
||||||
|
"parent_id": null,
|
||||||
|
"user_id": "e2ac2db8725247b0bb84b03718a01c39"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"last_seen": "2021-11-04T05:08:04.659722+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"state": {
|
||||||
|
"entity_id": "switch.sonoff_s31_lite_zb_9478b723_on_off",
|
||||||
|
"state": "off",
|
||||||
|
"attributes": {
|
||||||
|
"friendly_name": "Dinning Room Switch"
|
||||||
|
},
|
||||||
|
"last_changed": "2021-11-04T04:00:30.856952+00:00",
|
||||||
|
"last_updated": "2021-11-04T04:00:30.856952+00:00",
|
||||||
|
"context": {
|
||||||
|
"id": "1d4c560ea0ac84de0a13512e5d0308fb",
|
||||||
|
"parent_id": null,
|
||||||
|
"user_id": "e2ac2db8725247b0bb84b03718a01c39"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"last_seen": "2021-11-04T05:08:04.659722+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"state": {
|
||||||
|
"entity_id": "sensor.sengled_e11_g13_1d3e0b03_smartenergy_metering_summation_delivered",
|
||||||
|
"state": "3.679",
|
||||||
|
"attributes": {
|
||||||
|
"state_class": "total_increasing",
|
||||||
|
"device_type": "Electric Metering",
|
||||||
|
"status": "NO_ALARMS",
|
||||||
|
"unit_of_measurement": "kWh",
|
||||||
|
"friendly_name": "sengled E11-G13 1d3e0b03 smartenergy_metering summation_delivered",
|
||||||
|
"device_class": "energy"
|
||||||
|
},
|
||||||
|
"last_changed": "2021-10-16T16:24:26.182056+00:00",
|
||||||
|
"last_updated": "2021-10-16T16:24:26.182056+00:00",
|
||||||
|
"context": {
|
||||||
|
"id": "fa83264b523ca69852a66bb380e804f5",
|
||||||
|
"parent_id": null,
|
||||||
|
"user_id": null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"last_seen": "2021-11-04T05:08:04.659722+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"state": {
|
||||||
|
"entity_id": "sensor.sengled_e11_g13_fd3e0b03_smartenergy_metering_summation_delivered",
|
||||||
|
"state": "72.683",
|
||||||
|
"attributes": {
|
||||||
|
"state_class": "total_increasing",
|
||||||
|
"device_type": "Electric Metering",
|
||||||
|
"status": "NO_ALARMS",
|
||||||
|
"unit_of_measurement": "kWh",
|
||||||
|
"friendly_name": "sengled E11-G13 fd3e0b03 smartenergy_metering summation_delivered",
|
||||||
|
"device_class": "energy"
|
||||||
|
},
|
||||||
|
"last_changed": "2021-10-16T16:24:26.190871+00:00",
|
||||||
|
"last_updated": "2021-10-16T16:24:26.190871+00:00",
|
||||||
|
"context": {
|
||||||
|
"id": "913ad5b77c65bf860bd0291db183aa99",
|
||||||
|
"parent_id": null,
|
||||||
|
"user_id": null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"last_seen": "2021-11-04T05:08:04.659722+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"state": {
|
||||||
|
"entity_id": "sensor.sengled_e11_g13_ba220b03_smartenergy_metering_summation_delivered",
|
||||||
|
"state": "85.364",
|
||||||
|
"attributes": {
|
||||||
|
"state_class": "total_increasing",
|
||||||
|
"device_type": "Electric Metering",
|
||||||
|
"status": "NO_ALARMS",
|
||||||
|
"unit_of_measurement": "kWh",
|
||||||
|
"friendly_name": "sengled E11-G13 ba220b03 smartenergy_metering summation_delivered",
|
||||||
|
"device_class": "energy"
|
||||||
|
},
|
||||||
|
"last_changed": "2021-10-16T16:24:26.199573+00:00",
|
||||||
|
"last_updated": "2021-10-16T16:24:26.199573+00:00",
|
||||||
|
"context": {
|
||||||
|
"id": "3b95d4f04e71ea6aeefa7b6467796c2d",
|
||||||
|
"parent_id": null,
|
||||||
|
"user_id": null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"last_seen": "2021-11-04T05:08:04.659722+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"state": {
|
||||||
|
"entity_id": "sensor.sengled_e11_g13_d6681403_smartenergy_metering_summation_delivered",
|
||||||
|
"state": "74.519",
|
||||||
|
"attributes": {
|
||||||
|
"state_class": "total_increasing",
|
||||||
|
"device_type": "Electric Metering",
|
||||||
|
"status": "NO_ALARMS",
|
||||||
|
"unit_of_measurement": "kWh",
|
||||||
|
"friendly_name": "sengled E11-G13 d6681403 smartenergy_metering summation_delivered",
|
||||||
|
"device_class": "energy"
|
||||||
|
},
|
||||||
|
"last_changed": "2021-10-16T16:24:26.207400+00:00",
|
||||||
|
"last_updated": "2021-10-16T16:24:26.207400+00:00",
|
||||||
|
"context": {
|
||||||
|
"id": "4d2cfd95c15c4131eca477133f44fafc",
|
||||||
|
"parent_id": null,
|
||||||
|
"user_id": null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"last_seen": "2021-11-04T05:08:04.659722+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"state": {
|
||||||
|
"entity_id": "sensor.sengled_e11_g13_af400b03_smartenergy_metering_summation_delivered",
|
||||||
|
"state": "unavailable",
|
||||||
|
"attributes": {
|
||||||
|
"state_class": "total_increasing",
|
||||||
|
"unit_of_measurement": "kWh",
|
||||||
|
"friendly_name": "sengled E11-G13 af400b03 smartenergy_metering summation_delivered",
|
||||||
|
"device_class": "energy"
|
||||||
|
},
|
||||||
|
"last_changed": "2021-10-18T21:19:45.566571+00:00",
|
||||||
|
"last_updated": "2021-10-18T21:19:45.566571+00:00",
|
||||||
|
"context": {
|
||||||
|
"id": "17d55716b8c3b76afcedb3c0c1723bf9",
|
||||||
|
"parent_id": null,
|
||||||
|
"user_id": null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"last_seen": "2021-11-04T05:08:04.659722+00:00"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
7
.storage/core.uuid
Normal file
7
.storage/core.uuid
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"data": {
|
||||||
|
"uuid": "4c0daec8e41144b3b5db3b54c0c0fbb5"
|
||||||
|
},
|
||||||
|
"key": "core.uuid",
|
||||||
|
"version": 1
|
||||||
|
}
|
||||||
14
.storage/frontend.user_data_e2ac2db8725247b0bb84b03718a01c39
Normal file
14
.storage/frontend.user_data_e2ac2db8725247b0bb84b03718a01c39
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"version": 1,
|
||||||
|
"key": "frontend.user_data_e2ac2db8725247b0bb84b03718a01c39",
|
||||||
|
"data": {
|
||||||
|
"core": {
|
||||||
|
"showAdvanced": true
|
||||||
|
},
|
||||||
|
"language": {
|
||||||
|
"language": "en",
|
||||||
|
"number_format": "language",
|
||||||
|
"time_format": "language"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
7
.storage/hassio
Normal file
7
.storage/hassio
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"data": {
|
||||||
|
"hassio_user": "7e128bb4681b4f9680cff80d395082c8"
|
||||||
|
},
|
||||||
|
"key": "hassio",
|
||||||
|
"version": 1
|
||||||
|
}
|
||||||
17
.storage/http
Normal file
17
.storage/http
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"version": 1,
|
||||||
|
"key": "http",
|
||||||
|
"data": {
|
||||||
|
"use_x_forwarded_for": true,
|
||||||
|
"trusted_proxies": [
|
||||||
|
"192.168.86.198"
|
||||||
|
],
|
||||||
|
"login_attempts_threshold": -1,
|
||||||
|
"ip_ban_enabled": true,
|
||||||
|
"ssl_profile": "modern",
|
||||||
|
"server_port": 8123,
|
||||||
|
"cors_allowed_origins": [
|
||||||
|
"https://cast.home-assistant.io"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
16
.storage/input_boolean
Normal file
16
.storage/input_boolean
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"data": {
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"id": "sleeping",
|
||||||
|
"name": "sleeping"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "thermostat_mode",
|
||||||
|
"name": "thermostat_mode"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"key": "input_boolean",
|
||||||
|
"version": 1
|
||||||
|
}
|
||||||
32
.storage/lovelace
Normal file
32
.storage/lovelace
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
{
|
||||||
|
"data": {
|
||||||
|
"config": {
|
||||||
|
"title": "Home",
|
||||||
|
"views": [
|
||||||
|
{
|
||||||
|
"badges": [
|
||||||
|
{
|
||||||
|
"entity": "binary_sensor.updater"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entity": "person.dan"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entity": "sun.sun"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"cards": [
|
||||||
|
{
|
||||||
|
"entity": "weather.home",
|
||||||
|
"type": "weather-forecast"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"path": "default_view",
|
||||||
|
"title": "Home"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"key": "lovelace",
|
||||||
|
"version": 1
|
||||||
|
}
|
||||||
120
.storage/lovelace.20_21
Normal file
120
.storage/lovelace.20_21
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
{
|
||||||
|
"version": 1,
|
||||||
|
"key": "lovelace.20_21",
|
||||||
|
"data": {
|
||||||
|
"config": {
|
||||||
|
"title": "Home",
|
||||||
|
"views": [
|
||||||
|
{
|
||||||
|
"path": "default_view",
|
||||||
|
"title": "Home",
|
||||||
|
"cards": [
|
||||||
|
{
|
||||||
|
"type": "weather-forecast",
|
||||||
|
"entity": "weather.home_2",
|
||||||
|
"show_forecast": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "entities",
|
||||||
|
"entities": [
|
||||||
|
{
|
||||||
|
"entity": "light.table"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entity": "light.lamp_left"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entity": "light.lamp_right"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entity": "light.sengled_e11_g13_d6681403_level_on_off"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entity": "light.sengled_e11_g13_1d3e0b03_level_on_off"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entity": "light.sengled_e11_g13_ba220b03_level_on_off"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entity": "light.sengled_e11_g13_fd3e0b03_level_on_off"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Lights",
|
||||||
|
"show_header_toggle": true,
|
||||||
|
"state_color": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "entities",
|
||||||
|
"entities": [
|
||||||
|
{
|
||||||
|
"entity": "sensor.home_humidity"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entity": "sensor.home_temperature"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entity": "sensor.thermostat_away_mode"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entity": "sensor.thermostat_climate_mode"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entity": "sensor.thermostat_current_status"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entity": "sensor.thermostat_outside_temperature"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Sensor",
|
||||||
|
"state_color": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "glance",
|
||||||
|
"entities": [
|
||||||
|
{
|
||||||
|
"entity": "person.dan"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entity": "binary_sensor.door_contact"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "entities",
|
||||||
|
"entities": [
|
||||||
|
"switch.sonoff_s31_lite_zb_9478b723_on_off",
|
||||||
|
"switch.plug_in_appliance_module"
|
||||||
|
],
|
||||||
|
"title": "Switch"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "HVAC",
|
||||||
|
"path": "hvac",
|
||||||
|
"badges": [],
|
||||||
|
"cards": [
|
||||||
|
{
|
||||||
|
"type": "thermostat",
|
||||||
|
"entity": "climate.home"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Grow",
|
||||||
|
"path": "grow",
|
||||||
|
"badges": [],
|
||||||
|
"cards": [
|
||||||
|
{
|
||||||
|
"type": "entities",
|
||||||
|
"entities": [
|
||||||
|
"switch.ewelink_sa_003_zigbee_33eeea22_on_off"
|
||||||
|
],
|
||||||
|
"title": "Basement"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
16
.storage/lovelace_dashboards
Normal file
16
.storage/lovelace_dashboards
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"version": 1,
|
||||||
|
"key": "lovelace_dashboards",
|
||||||
|
"data": {
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"require_admin": false,
|
||||||
|
"show_in_sidebar": true,
|
||||||
|
"title": "21",
|
||||||
|
"url_path": "20-21",
|
||||||
|
"mode": "storage",
|
||||||
|
"id": "20_21"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
223
.storage/mobile_app
Normal file
223
.storage/mobile_app
Normal file
@@ -0,0 +1,223 @@
|
|||||||
|
{
|
||||||
|
"version": 1,
|
||||||
|
"key": "mobile_app",
|
||||||
|
"data": {
|
||||||
|
"binary_sensor": {
|
||||||
|
"eb9d2fbd9f8fea4200d7752b689aecfe6a2ca2215d2cbe0600b75a4fd20a6797_is_charging": {
|
||||||
|
"added": true,
|
||||||
|
"attributes": {},
|
||||||
|
"device_class": "plug",
|
||||||
|
"icon": "mdi:power-plug-off",
|
||||||
|
"name": "Is Charging",
|
||||||
|
"state": false,
|
||||||
|
"type": "binary_sensor",
|
||||||
|
"unique_id": "is_charging",
|
||||||
|
"webhook_id": "eb9d2fbd9f8fea4200d7752b689aecfe6a2ca2215d2cbe0600b75a4fd20a6797"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"deleted_ids": [],
|
||||||
|
"sensor": {
|
||||||
|
"eb9d2fbd9f8fea4200d7752b689aecfe6a2ca2215d2cbe0600b75a4fd20a6797_audio_sensor": {
|
||||||
|
"added": true,
|
||||||
|
"attributes": {},
|
||||||
|
"icon": "mdi:volume-high",
|
||||||
|
"name": "Audio Sensor",
|
||||||
|
"state": "normal",
|
||||||
|
"type": "sensor",
|
||||||
|
"unique_id": "audio_sensor",
|
||||||
|
"webhook_id": "eb9d2fbd9f8fea4200d7752b689aecfe6a2ca2215d2cbe0600b75a4fd20a6797"
|
||||||
|
},
|
||||||
|
"eb9d2fbd9f8fea4200d7752b689aecfe6a2ca2215d2cbe0600b75a4fd20a6797_battery_health": {
|
||||||
|
"added": true,
|
||||||
|
"attributes": {},
|
||||||
|
"icon": "mdi:battery-heart-variant",
|
||||||
|
"name": "Battery Health",
|
||||||
|
"state": "good",
|
||||||
|
"type": "sensor",
|
||||||
|
"unique_id": "battery_health",
|
||||||
|
"webhook_id": "eb9d2fbd9f8fea4200d7752b689aecfe6a2ca2215d2cbe0600b75a4fd20a6797"
|
||||||
|
},
|
||||||
|
"eb9d2fbd9f8fea4200d7752b689aecfe6a2ca2215d2cbe0600b75a4fd20a6797_battery_level": {
|
||||||
|
"added": true,
|
||||||
|
"attributes": {},
|
||||||
|
"device_class": "battery",
|
||||||
|
"icon": "mdi:battery-80",
|
||||||
|
"name": "Battery Level",
|
||||||
|
"state": 81,
|
||||||
|
"type": "sensor",
|
||||||
|
"unique_id": "battery_level",
|
||||||
|
"unit_of_measurement": "%",
|
||||||
|
"webhook_id": "eb9d2fbd9f8fea4200d7752b689aecfe6a2ca2215d2cbe0600b75a4fd20a6797"
|
||||||
|
},
|
||||||
|
"eb9d2fbd9f8fea4200d7752b689aecfe6a2ca2215d2cbe0600b75a4fd20a6797_battery_state": {
|
||||||
|
"added": true,
|
||||||
|
"attributes": {},
|
||||||
|
"device_class": "battery",
|
||||||
|
"icon": "mdi:battery-minus",
|
||||||
|
"name": "Battery State",
|
||||||
|
"state": "discharging",
|
||||||
|
"type": "sensor",
|
||||||
|
"unique_id": "battery_state",
|
||||||
|
"webhook_id": "eb9d2fbd9f8fea4200d7752b689aecfe6a2ca2215d2cbe0600b75a4fd20a6797"
|
||||||
|
},
|
||||||
|
"eb9d2fbd9f8fea4200d7752b689aecfe6a2ca2215d2cbe0600b75a4fd20a6797_bluetooth_connection": {
|
||||||
|
"added": true,
|
||||||
|
"attributes": {
|
||||||
|
"connected_not_paired_devices": "[]",
|
||||||
|
"connected_paired_devices": "[]",
|
||||||
|
"paired_devices": "[B0:B4:48:83:AA:66, 00:06:66:1E:27:3A, 00:00:AB:CD:21:05, 08:EB:ED:D5:AB:96, 00:1E:AE:9D:26:78, 91:60:86:65:77:53]"
|
||||||
|
},
|
||||||
|
"icon": "mdi:bluetooth",
|
||||||
|
"name": "Bluetooth Connection",
|
||||||
|
"state": 0,
|
||||||
|
"type": "sensor",
|
||||||
|
"unique_id": "bluetooth_connection",
|
||||||
|
"unit_of_measurement": "connection(s)",
|
||||||
|
"webhook_id": "eb9d2fbd9f8fea4200d7752b689aecfe6a2ca2215d2cbe0600b75a4fd20a6797"
|
||||||
|
},
|
||||||
|
"eb9d2fbd9f8fea4200d7752b689aecfe6a2ca2215d2cbe0600b75a4fd20a6797_charger_type": {
|
||||||
|
"added": true,
|
||||||
|
"attributes": {},
|
||||||
|
"icon": "mdi:battery",
|
||||||
|
"name": "Charger Type",
|
||||||
|
"state": "none",
|
||||||
|
"type": "sensor",
|
||||||
|
"unique_id": "charger_type",
|
||||||
|
"webhook_id": "eb9d2fbd9f8fea4200d7752b689aecfe6a2ca2215d2cbe0600b75a4fd20a6797"
|
||||||
|
},
|
||||||
|
"eb9d2fbd9f8fea4200d7752b689aecfe6a2ca2215d2cbe0600b75a4fd20a6797_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": "eb9d2fbd9f8fea4200d7752b689aecfe6a2ca2215d2cbe0600b75a4fd20a6797"
|
||||||
|
},
|
||||||
|
"eb9d2fbd9f8fea4200d7752b689aecfe6a2ca2215d2cbe0600b75a4fd20a6797_geocoded_location": {
|
||||||
|
"added": true,
|
||||||
|
"attributes": {
|
||||||
|
"Administrative Area": "Ohio",
|
||||||
|
"Country": "United States",
|
||||||
|
"ISO Country Code": "US",
|
||||||
|
"Latitude": 41.399014,
|
||||||
|
"Locality": "Amherst",
|
||||||
|
"Longitude": -82.23327,
|
||||||
|
"Postal Code": "44001",
|
||||||
|
"Sub Administrative Area": "Lorain County",
|
||||||
|
"Sub Locality": "null",
|
||||||
|
"Sub Thoroughfare": "353",
|
||||||
|
"Thoroughfare": "Seeley Street"
|
||||||
|
},
|
||||||
|
"icon": "mdi:map",
|
||||||
|
"name": "Geocoded Location",
|
||||||
|
"state": "353 Seeley St, Amherst, OH 44001, USA",
|
||||||
|
"type": "sensor",
|
||||||
|
"unique_id": "geocoded_location",
|
||||||
|
"webhook_id": "eb9d2fbd9f8fea4200d7752b689aecfe6a2ca2215d2cbe0600b75a4fd20a6797"
|
||||||
|
},
|
||||||
|
"eb9d2fbd9f8fea4200d7752b689aecfe6a2ca2215d2cbe0600b75a4fd20a6797_last_reboot": {
|
||||||
|
"added": true,
|
||||||
|
"attributes": {
|
||||||
|
"Local Time": "Wed May 05 04:39:19 EDT 2021",
|
||||||
|
"Time in Milliseconds": 1620203959196
|
||||||
|
},
|
||||||
|
"device_class": "timestamp",
|
||||||
|
"icon": "mdi:restart",
|
||||||
|
"name": "Last Reboot",
|
||||||
|
"state": "2021-05-05T08:39:19Z",
|
||||||
|
"type": "sensor",
|
||||||
|
"unique_id": "last_reboot",
|
||||||
|
"webhook_id": "eb9d2fbd9f8fea4200d7752b689aecfe6a2ca2215d2cbe0600b75a4fd20a6797"
|
||||||
|
},
|
||||||
|
"eb9d2fbd9f8fea4200d7752b689aecfe6a2ca2215d2cbe0600b75a4fd20a6797_light_sensor": {
|
||||||
|
"added": true,
|
||||||
|
"attributes": {},
|
||||||
|
"device_class": "illuminance",
|
||||||
|
"icon": "mdi:brightness-5",
|
||||||
|
"name": "Light Sensor",
|
||||||
|
"state": "19",
|
||||||
|
"type": "sensor",
|
||||||
|
"unique_id": "light_sensor",
|
||||||
|
"unit_of_measurement": "lx",
|
||||||
|
"webhook_id": "eb9d2fbd9f8fea4200d7752b689aecfe6a2ca2215d2cbe0600b75a4fd20a6797"
|
||||||
|
},
|
||||||
|
"eb9d2fbd9f8fea4200d7752b689aecfe6a2ca2215d2cbe0600b75a4fd20a6797_next_alarm": {
|
||||||
|
"added": true,
|
||||||
|
"attributes": {
|
||||||
|
"Local Time": "Thu May 20 08:00:00 EDT 2021",
|
||||||
|
"Package": "com.urbandroid.sleep",
|
||||||
|
"Time in Milliseconds": 1621512000000
|
||||||
|
},
|
||||||
|
"device_class": "timestamp",
|
||||||
|
"icon": "mdi:alarm",
|
||||||
|
"name": "Next Alarm",
|
||||||
|
"state": "2021-05-20T12:00:00.000Z",
|
||||||
|
"type": "sensor",
|
||||||
|
"unique_id": "next_alarm",
|
||||||
|
"webhook_id": "eb9d2fbd9f8fea4200d7752b689aecfe6a2ca2215d2cbe0600b75a4fd20a6797"
|
||||||
|
},
|
||||||
|
"eb9d2fbd9f8fea4200d7752b689aecfe6a2ca2215d2cbe0600b75a4fd20a6797_pressure_sensor": {
|
||||||
|
"added": true,
|
||||||
|
"attributes": {},
|
||||||
|
"device_class": "pressure",
|
||||||
|
"icon": "mdi:gauge",
|
||||||
|
"name": "Pressure Sensor",
|
||||||
|
"state": "1002.5",
|
||||||
|
"type": "sensor",
|
||||||
|
"unique_id": "pressure_sensor",
|
||||||
|
"unit_of_measurement": "hPa",
|
||||||
|
"webhook_id": "eb9d2fbd9f8fea4200d7752b689aecfe6a2ca2215d2cbe0600b75a4fd20a6797"
|
||||||
|
},
|
||||||
|
"eb9d2fbd9f8fea4200d7752b689aecfe6a2ca2215d2cbe0600b75a4fd20a6797_proximity_sensor": {
|
||||||
|
"added": true,
|
||||||
|
"attributes": {},
|
||||||
|
"icon": "mdi:leak",
|
||||||
|
"name": "Proximity Sensor",
|
||||||
|
"state": 8,
|
||||||
|
"type": "sensor",
|
||||||
|
"unique_id": "proximity_sensor",
|
||||||
|
"webhook_id": "eb9d2fbd9f8fea4200d7752b689aecfe6a2ca2215d2cbe0600b75a4fd20a6797"
|
||||||
|
},
|
||||||
|
"eb9d2fbd9f8fea4200d7752b689aecfe6a2ca2215d2cbe0600b75a4fd20a6797_storage_sensor": {
|
||||||
|
"added": true,
|
||||||
|
"attributes": {
|
||||||
|
"Free internal storage": "50GB",
|
||||||
|
"Total internal storage": "110GB"
|
||||||
|
},
|
||||||
|
"icon": "mdi:harddisk",
|
||||||
|
"name": "Storage Sensor",
|
||||||
|
"state": 45,
|
||||||
|
"type": "sensor",
|
||||||
|
"unique_id": "storage_sensor",
|
||||||
|
"unit_of_measurement": "%",
|
||||||
|
"webhook_id": "eb9d2fbd9f8fea4200d7752b689aecfe6a2ca2215d2cbe0600b75a4fd20a6797"
|
||||||
|
},
|
||||||
|
"eb9d2fbd9f8fea4200d7752b689aecfe6a2ca2215d2cbe0600b75a4fd20a6797_wifi_connection": {
|
||||||
|
"added": true,
|
||||||
|
"attributes": {
|
||||||
|
"is_hidden": false
|
||||||
|
},
|
||||||
|
"icon": "mdi:wifi",
|
||||||
|
"name": "Wifi Connection",
|
||||||
|
"state": "DD",
|
||||||
|
"type": "sensor",
|
||||||
|
"unique_id": "wifi_connection",
|
||||||
|
"webhook_id": "eb9d2fbd9f8fea4200d7752b689aecfe6a2ca2215d2cbe0600b75a4fd20a6797"
|
||||||
|
},
|
||||||
|
"eb9d2fbd9f8fea4200d7752b689aecfe6a2ca2215d2cbe0600b75a4fd20a6797_battery_temperature": {
|
||||||
|
"unique_id": "battery_temperature",
|
||||||
|
"state": 28.8,
|
||||||
|
"type": "sensor",
|
||||||
|
"icon": "mdi:battery",
|
||||||
|
"attributes": {},
|
||||||
|
"name": "Battery Temperature",
|
||||||
|
"device_class": "temperature",
|
||||||
|
"unit_of_measurement": "\u00b0C",
|
||||||
|
"webhook_id": "eb9d2fbd9f8fea4200d7752b689aecfe6a2ca2215d2cbe0600b75a4fd20a6797",
|
||||||
|
"added": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
11
.storage/onboarding
Normal file
11
.storage/onboarding
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"data": {
|
||||||
|
"done": [
|
||||||
|
"user",
|
||||||
|
"core_config",
|
||||||
|
"integration"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"key": "onboarding",
|
||||||
|
"version": 3
|
||||||
|
}
|
||||||
17
.storage/person
Normal file
17
.storage/person
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"version": 2,
|
||||||
|
"key": "person",
|
||||||
|
"data": {
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"device_trackers": [
|
||||||
|
"device_tracker.sm_n960u"
|
||||||
|
],
|
||||||
|
"id": "dan",
|
||||||
|
"name": "Dan",
|
||||||
|
"user_id": "e2ac2db8725247b0bb84b03718a01c39",
|
||||||
|
"picture": null
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
53
.storage/zha.storage
Normal file
53
.storage/zha.storage
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
{
|
||||||
|
"version": 1,
|
||||||
|
"key": "zha.storage",
|
||||||
|
"data": {
|
||||||
|
"devices": [
|
||||||
|
{
|
||||||
|
"name": "Silicon Labs EZSP",
|
||||||
|
"ieee": "00:0d:6f:00:0a:ff:73:f8",
|
||||||
|
"last_seen": 1636001002.7798755
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "sengled E11-G13",
|
||||||
|
"ieee": "b0:ce:18:14:03:0b:3e:1d",
|
||||||
|
"last_seen": 1636002128.3008697
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "sengled E11-G13",
|
||||||
|
"ieee": "b0:ce:18:14:03:0b:3e:fd",
|
||||||
|
"last_seen": 1636002335.1381133
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "sengled E11-G13",
|
||||||
|
"ieee": "b0:ce:18:14:03:0b:22:ba",
|
||||||
|
"last_seen": 1636002187.7172956
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "sengled E11-G13",
|
||||||
|
"ieee": "b0:ce:18:14:03:14:68:d6",
|
||||||
|
"last_seen": 1636002481.4106457
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "sengled E11-G13",
|
||||||
|
"ieee": "b0:ce:18:14:03:0b:40:af",
|
||||||
|
"last_seen": 1634584548.0082166
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "LUMI lumi.sensor_magnet.aq2",
|
||||||
|
"ieee": "00:15:8d:00:07:0b:32:d6",
|
||||||
|
"last_seen": 1631821141.6237457
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "unk_manufacturer unk_model",
|
||||||
|
"ieee": "00:12:4b:00:22:ea:ee:33",
|
||||||
|
"last_seen": 1636002199.043916
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "SONOFF S31 Lite zb",
|
||||||
|
"ieee": "00:12:4b:00:23:b7:78:94",
|
||||||
|
"last_seen": 1636002438.6431177
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
73
.storage/zwave_js.legacy_zwave_migration
Normal file
73
.storage/zwave_js.legacy_zwave_migration
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
{
|
||||||
|
"version": 1,
|
||||||
|
"key": "zwave_js.legacy_zwave_migration",
|
||||||
|
"data": {
|
||||||
|
"168e1b19293508dafb169cd8270442a4": {
|
||||||
|
"sensor.zwave_test_sensor_battery_level": {
|
||||||
|
"node_id": 3,
|
||||||
|
"endpoint_index": 0,
|
||||||
|
"command_class": 128,
|
||||||
|
"value_property_name": "level",
|
||||||
|
"value_property_key_name": null,
|
||||||
|
"value_id": "3-128-0-level",
|
||||||
|
"device_id": "576d2874d1e67620b848c789be7d9e07",
|
||||||
|
"domain": "sensor",
|
||||||
|
"entity_id": "sensor.zwave_test_sensor_battery_level",
|
||||||
|
"unique_id": "4189927064.3-128-0-level",
|
||||||
|
"unit_of_measurement": "%"
|
||||||
|
},
|
||||||
|
"binary_sensor.door_window_sensor_access_control_window_door_is_open": {
|
||||||
|
"node_id": 3,
|
||||||
|
"endpoint_index": 0,
|
||||||
|
"command_class": 113,
|
||||||
|
"value_property_name": "Access Control",
|
||||||
|
"value_property_key_name": "Door state",
|
||||||
|
"value_id": "3-113-0-Access Control-Door state",
|
||||||
|
"device_id": "576d2874d1e67620b848c789be7d9e07",
|
||||||
|
"domain": "binary_sensor",
|
||||||
|
"entity_id": "binary_sensor.door_window_sensor_access_control_window_door_is_open",
|
||||||
|
"unique_id": "4189927064.3-113-0-Access Control-Door state.22",
|
||||||
|
"unit_of_measurement": null
|
||||||
|
},
|
||||||
|
"binary_sensor.zwave_test_sensor_low_battery_level": {
|
||||||
|
"node_id": 3,
|
||||||
|
"endpoint_index": 0,
|
||||||
|
"command_class": 128,
|
||||||
|
"value_property_name": "isLow",
|
||||||
|
"value_property_key_name": null,
|
||||||
|
"value_id": "3-128-0-isLow",
|
||||||
|
"device_id": "576d2874d1e67620b848c789be7d9e07",
|
||||||
|
"domain": "binary_sensor",
|
||||||
|
"entity_id": "binary_sensor.zwave_test_sensor_low_battery_level",
|
||||||
|
"unique_id": "4189927064.3-128-0-isLow",
|
||||||
|
"unit_of_measurement": null
|
||||||
|
},
|
||||||
|
"switch.plug_in_appliance_module": {
|
||||||
|
"node_id": 2,
|
||||||
|
"endpoint_index": 0,
|
||||||
|
"command_class": 37,
|
||||||
|
"value_property_name": "currentValue",
|
||||||
|
"value_property_key_name": null,
|
||||||
|
"value_id": "2-37-0-currentValue",
|
||||||
|
"device_id": "b43320eeef9b58d28234494e0aef12b8",
|
||||||
|
"domain": "switch",
|
||||||
|
"entity_id": "switch.plug_in_appliance_module",
|
||||||
|
"unique_id": "4189927064.2-37-0-currentValue",
|
||||||
|
"unit_of_measurement": null
|
||||||
|
},
|
||||||
|
"select.plug_in_appliance_module_local_protection_state": {
|
||||||
|
"node_id": 2,
|
||||||
|
"endpoint_index": 0,
|
||||||
|
"command_class": 117,
|
||||||
|
"value_property_name": "local",
|
||||||
|
"value_property_key_name": null,
|
||||||
|
"value_id": "2-117-0-local",
|
||||||
|
"device_id": "b43320eeef9b58d28234494e0aef12b8",
|
||||||
|
"domain": "select",
|
||||||
|
"entity_id": "select.plug_in_appliance_module_local_protection_state",
|
||||||
|
"unique_id": "4189927064.2-117-0-local",
|
||||||
|
"unit_of_measurement": null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
311
OZW_Log.txt
Normal file
311
OZW_Log.txt
Normal file
@@ -0,0 +1,311 @@
|
|||||||
|
2021-09-16 15:12:31.001 Always, OpenZwave Version 1.4.3469 Starting Up
|
||||||
|
2021-09-16 15:12:43.568 Info, Setting Up Provided Network Key for Secure Communications
|
||||||
|
2021-09-16 15:12:43.569 Info, mgr, Added driver for controller /dev/ttyUSB0
|
||||||
|
2021-09-16 15:12:43.569 Info, Opening controller /dev/ttyUSB0
|
||||||
|
2021-09-16 15:12:43.570 Info, Trying to open serial port /dev/ttyUSB0 (attempt 1)
|
||||||
|
2021-09-16 15:12:43.574 Info, Serial port /dev/ttyUSB0 opened (attempt 1)
|
||||||
|
2021-09-16 15:12:43.575 Detail, contrlr, Queuing (Command) FUNC_ID_ZW_GET_VERSION: 0x01, 0x03, 0x00, 0x15, 0xe9
|
||||||
|
2021-09-16 15:12:43.575 Detail, contrlr, Queuing (Command) FUNC_ID_ZW_MEMORY_GET_ID: 0x01, 0x03, 0x00, 0x20, 0xdc
|
||||||
|
2021-09-16 15:12:43.575 Detail, contrlr, Queuing (Command) FUNC_ID_ZW_GET_CONTROLLER_CAPABILITIES: 0x01, 0x03, 0x00, 0x05, 0xf9
|
||||||
|
2021-09-16 15:12:43.575 Detail, contrlr, Queuing (Command) FUNC_ID_SERIAL_API_GET_CAPABILITIES: 0x01, 0x03, 0x00, 0x07, 0xfb
|
||||||
|
2021-09-16 15:12:43.575 Detail, contrlr, Queuing (Command) FUNC_ID_ZW_GET_SUC_NODE_ID: 0x01, 0x03, 0x00, 0x56, 0xaa
|
||||||
|
2021-09-16 15:12:43.576 Detail,
|
||||||
|
2021-09-16 15:12:43.576 Info, contrlr, Sending (Command) message (Callback ID=0x00, Expected Reply=0x15) - FUNC_ID_ZW_GET_VERSION: 0x01, 0x03, 0x00, 0x15, 0xe9
|
||||||
|
2021-09-16 15:12:43.580 Detail, contrlr, Received: 0x01, 0x10, 0x01, 0x15, 0x5a, 0x2d, 0x57, 0x61, 0x76, 0x65, 0x20, 0x34, 0x2e, 0x30, 0x35, 0x00, 0x01, 0x97
|
||||||
|
2021-09-16 15:12:43.580 Detail,
|
||||||
|
2021-09-16 15:12:43.580 Info, contrlr, Received reply to FUNC_ID_ZW_GET_VERSION:
|
||||||
|
2021-09-16 15:12:43.580 Info, contrlr, Static Controller library, version Z-Wave 4.05
|
||||||
|
2021-09-16 15:12:43.580 Detail, Node045, Expected reply was received
|
||||||
|
2021-09-16 15:12:43.580 Detail, Node045, Message transaction complete
|
||||||
|
2021-09-16 15:12:43.580 Detail,
|
||||||
|
2021-09-16 15:12:43.580 Detail, contrlr, Removing current message
|
||||||
|
2021-09-16 15:12:43.581 Detail,
|
||||||
|
2021-09-16 15:12:43.581 Info, contrlr, Sending (Command) message (Callback ID=0x00, Expected Reply=0x20) - FUNC_ID_ZW_MEMORY_GET_ID: 0x01, 0x03, 0x00, 0x20, 0xdc
|
||||||
|
2021-09-16 15:12:43.590 Detail, contrlr, Received: 0x01, 0x08, 0x01, 0x20, 0xf9, 0xbd, 0x36, 0x98, 0x01, 0x3d
|
||||||
|
2021-09-16 15:12:43.590 Detail,
|
||||||
|
2021-09-16 15:12:43.590 Info, contrlr, Received reply to FUNC_ID_ZW_MEMORY_GET_ID. Home ID = 0xf9bd3698. Our node ID = 1
|
||||||
|
2021-09-16 15:12:43.590 Detail, Node189, Expected reply was received
|
||||||
|
2021-09-16 15:12:43.590 Detail, Node189, Message transaction complete
|
||||||
|
2021-09-16 15:12:43.590 Detail,
|
||||||
|
2021-09-16 15:12:43.590 Detail, contrlr, Removing current message
|
||||||
|
2021-09-16 15:12:43.590 Detail,
|
||||||
|
2021-09-16 15:12:43.590 Info, contrlr, Sending (Command) message (Callback ID=0x00, Expected Reply=0x05) - FUNC_ID_ZW_GET_CONTROLLER_CAPABILITIES: 0x01, 0x03, 0x00, 0x05, 0xf9
|
||||||
|
2021-09-16 15:12:43.593 Detail, contrlr, Received: 0x01, 0x04, 0x01, 0x05, 0x1c, 0xe3
|
||||||
|
2021-09-16 15:12:43.594 Detail,
|
||||||
|
2021-09-16 15:12:43.594 Info, contrlr, Received reply to FUNC_ID_ZW_GET_CONTROLLER_CAPABILITIES:
|
||||||
|
2021-09-16 15:12:43.594 Info, contrlr, There is a SUC ID Server (SIS) in this network.
|
||||||
|
2021-09-16 15:12:43.594 Info, contrlr, The PC controller is an inclusion static update controller (SUC) and was the original primary before the SIS was added.
|
||||||
|
2021-09-16 15:12:43.594 Detail, Node227, Expected reply was received
|
||||||
|
2021-09-16 15:12:43.594 Detail, Node227, Message transaction complete
|
||||||
|
2021-09-16 15:12:43.594 Detail,
|
||||||
|
2021-09-16 15:12:43.594 Detail, contrlr, Removing current message
|
||||||
|
2021-09-16 15:12:43.594 Detail,
|
||||||
|
2021-09-16 15:12:43.594 Info, contrlr, Sending (Command) message (Callback ID=0x00, Expected Reply=0x07) - FUNC_ID_SERIAL_API_GET_CAPABILITIES: 0x01, 0x03, 0x00, 0x07, 0xfb
|
||||||
|
2021-09-16 15:12:43.602 Detail, contrlr, Received: 0x01, 0x2b, 0x01, 0x07, 0x04, 0x20, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0xfe, 0x81, 0xff, 0x88, 0xcf, 0x1f, 0x00, 0x00, 0xfb, 0x9f, 0x7d, 0xa0, 0x67, 0x00, 0x80, 0x80, 0x00, 0x80, 0x86, 0x00, 0x00, 0x00, 0xe8, 0x73, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x60, 0x00, 0x00, 0x03
|
||||||
|
2021-09-16 15:12:43.602 Detail,
|
||||||
|
2021-09-16 15:12:43.602 Info, contrlr, Received reply to FUNC_ID_SERIAL_API_GET_CAPABILITIES
|
||||||
|
2021-09-16 15:12:43.602 Info, contrlr, Serial API Version: 4.32
|
||||||
|
2021-09-16 15:12:43.603 Info, contrlr, Manufacturer ID: 0x0000
|
||||||
|
2021-09-16 15:12:43.603 Info, contrlr, Product Type: 0x0001
|
||||||
|
2021-09-16 15:12:43.603 Info, contrlr, Product ID: 0x0001
|
||||||
|
2021-09-16 15:12:43.603 Detail, contrlr, Queuing (Command) FUNC_ID_ZW_GET_RANDOM: 0x01, 0x04, 0x00, 0x1c, 0x20, 0xc7
|
||||||
|
2021-09-16 15:12:43.603 Detail, contrlr, Queuing (Command) FUNC_ID_SERIAL_API_GET_INIT_DATA: 0x01, 0x03, 0x00, 0x02, 0xfe
|
||||||
|
2021-09-16 15:12:43.603 Detail, contrlr, Queuing (Command) FUNC_ID_SERIAL_API_SET_TIMEOUTS: 0x01, 0x05, 0x00, 0x06, 0x64, 0x0f, 0x97
|
||||||
|
2021-09-16 15:12:43.604 Detail, contrlr, Queuing (Command) FUNC_ID_SERIAL_API_APPL_NODE_INFORMATION: 0x01, 0x07, 0x00, 0x03, 0x01, 0x02, 0x01, 0x00, 0xf9
|
||||||
|
2021-09-16 15:12:43.604 Detail, Node032, Expected reply was received
|
||||||
|
2021-09-16 15:12:43.604 Detail, Node032, Message transaction complete
|
||||||
|
2021-09-16 15:12:43.604 Detail,
|
||||||
|
2021-09-16 15:12:43.604 Detail, contrlr, Removing current message
|
||||||
|
2021-09-16 15:12:43.604 Detail,
|
||||||
|
2021-09-16 15:12:43.604 Info, contrlr, Sending (Command) message (Callback ID=0x00, Expected Reply=0x56) - FUNC_ID_ZW_GET_SUC_NODE_ID: 0x01, 0x03, 0x00, 0x56, 0xaa
|
||||||
|
2021-09-16 15:12:43.607 Detail, contrlr, Received: 0x01, 0x04, 0x01, 0x56, 0x01, 0xad
|
||||||
|
2021-09-16 15:12:43.608 Detail,
|
||||||
|
2021-09-16 15:12:43.608 Info, contrlr, Received reply to GET_SUC_NODE_ID. Node ID = 1
|
||||||
|
2021-09-16 15:12:43.608 Detail, Node173, Expected reply was received
|
||||||
|
2021-09-16 15:12:43.608 Detail, Node173, Message transaction complete
|
||||||
|
2021-09-16 15:12:43.608 Detail,
|
||||||
|
2021-09-16 15:12:43.608 Detail, contrlr, Removing current message
|
||||||
|
2021-09-16 15:12:43.608 Detail,
|
||||||
|
2021-09-16 15:12:43.608 Info, contrlr, Sending (Command) message (Callback ID=0x00, Expected Reply=0x1c) - FUNC_ID_ZW_GET_RANDOM: 0x01, 0x04, 0x00, 0x1c, 0x20, 0xc7
|
||||||
|
2021-09-16 15:12:43.663 Detail, contrlr, Received: 0x01, 0x25, 0x01, 0x1c, 0x01, 0x20, 0xdb, 0xa7, 0xa9, 0xfb, 0xc6, 0xb5, 0x6c, 0xe2, 0x53, 0x4d, 0xf7, 0x0e, 0x4d, 0x33, 0xb2, 0x03, 0xa8, 0x69, 0x03, 0x4b, 0x87, 0x9b, 0x2d, 0xff, 0xe1, 0x08, 0x03, 0x3d, 0x44, 0xb4, 0xa4, 0xb0, 0x69
|
||||||
|
2021-09-16 15:12:43.663 Detail,
|
||||||
|
2021-09-16 15:12:43.663 Info, contrlr, Received reply to FUNC_ID_ZW_GET_RANDOM: true
|
||||||
|
2021-09-16 15:12:43.663 Detail, Node032, Expected reply was received
|
||||||
|
2021-09-16 15:12:43.664 Detail, Node032, Message transaction complete
|
||||||
|
2021-09-16 15:12:43.664 Detail,
|
||||||
|
2021-09-16 15:12:43.664 Detail, contrlr, Removing current message
|
||||||
|
2021-09-16 15:12:43.664 Detail,
|
||||||
|
2021-09-16 15:12:43.664 Info, contrlr, Sending (Command) message (Callback ID=0x00, Expected Reply=0x02) - FUNC_ID_SERIAL_API_GET_INIT_DATA: 0x01, 0x03, 0x00, 0x02, 0xfe
|
||||||
|
2021-09-16 15:12:43.721 Detail, contrlr, Received: 0x01, 0x25, 0x01, 0x02, 0x05, 0x08, 0x1d, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0xcf
|
||||||
|
2021-09-16 15:12:43.721 Detail,
|
||||||
|
2021-09-16 15:12:43.721 Info, mgr, Driver with Home ID of 0xf9bd3698 is now ready.
|
||||||
|
2021-09-16 15:12:43.721 Info,
|
||||||
|
2021-09-16 15:12:43.725 Info, contrlr, Received reply to FUNC_ID_SERIAL_API_GET_INIT_DATA:
|
||||||
|
2021-09-16 15:12:43.726 Info, contrlr, Node 001 - Known
|
||||||
|
2021-09-16 15:12:43.726 Detail, Node001, AdvanceQueries queryPending=0 queryRetries=0 queryStage=CacheLoad live=1
|
||||||
|
2021-09-16 15:12:43.726 Detail, Node001, QueryStage_CacheLoad
|
||||||
|
2021-09-16 15:12:43.726 Info, Node001, Node Identity Codes: 0000:0001:0001
|
||||||
|
2021-09-16 15:12:43.726 Detail, Node001, QueryStage_Associations
|
||||||
|
2021-09-16 15:12:43.726 Detail, Node001, QueryStage_Neighbors
|
||||||
|
2021-09-16 15:12:43.726 Detail, contrlr, Requesting routing info (neighbor list) for Node 1
|
||||||
|
2021-09-16 15:12:43.726 Detail, Node001, Queuing (Command) Get Routing Info (Node=1): 0x01, 0x07, 0x00, 0x80, 0x01, 0x00, 0x00, 0x03, 0x7a
|
||||||
|
2021-09-16 15:12:43.726 Detail, Node001, Queuing (Query) Query Stage Complete (Neighbors)
|
||||||
|
2021-09-16 15:12:43.726 Info, contrlr, Node 002 - Known
|
||||||
|
2021-09-16 15:12:43.726 Detail, Node002, AdvanceQueries queryPending=0 queryRetries=0 queryStage=CacheLoad live=1
|
||||||
|
2021-09-16 15:12:43.726 Detail, Node002, QueryStage_CacheLoad
|
||||||
|
2021-09-16 15:12:43.726 Info, Node002, Node Identity Codes: 014f:5250:3030
|
||||||
|
2021-09-16 15:12:43.726 Info, Node002, NoOperation::Set - Routing=true
|
||||||
|
2021-09-16 15:12:43.727 Detail, Node002, Queuing (NoOp) NoOperation_Set (Node=2): 0x01, 0x09, 0x00, 0x13, 0x02, 0x02, 0x00, 0x00, 0x25, 0x0a, 0xca
|
||||||
|
2021-09-16 15:12:43.727 Detail, Node002, Queuing (Query) Query Stage Complete (CacheLoad)
|
||||||
|
2021-09-16 15:12:43.727 Detail, Node008, Expected reply was received
|
||||||
|
2021-09-16 15:12:43.727 Detail, Node008, Message transaction complete
|
||||||
|
2021-09-16 15:12:43.727 Detail,
|
||||||
|
2021-09-16 15:12:43.727 Detail, contrlr, Removing current message
|
||||||
|
2021-09-16 15:12:43.727 Detail, Node001, Notification: DriverReady
|
||||||
|
2021-09-16 15:12:43.730 Detail, Node001, Notification: NodeAdded
|
||||||
|
2021-09-16 15:12:43.735 Detail, Node001, Notification: NodeProtocolInfo
|
||||||
|
2021-09-16 15:12:43.753 Detail, Node001, Notification: EssentialNodeQueriesComplete
|
||||||
|
2021-09-16 15:12:43.757 Detail, Node001, Notification: ValueAdded
|
||||||
|
2021-09-16 15:12:43.765 Detail, Node002, Notification: NodeAdded
|
||||||
|
2021-09-16 15:12:43.768 Detail, Node002, Notification: NodeProtocolInfo
|
||||||
|
2021-09-16 15:12:43.771 Detail, Node002, Notification: EssentialNodeQueriesComplete
|
||||||
|
2021-09-16 15:12:43.788 Detail, Node002, Notification: ValueAdded
|
||||||
|
2021-09-16 15:12:43.832 Detail, Node002, Notification: ValueAdded
|
||||||
|
2021-09-16 15:12:43.834 Detail, Node002, Notification: ValueAdded
|
||||||
|
2021-09-16 15:12:43.836 Detail, Node002, Notification: ValueAdded
|
||||||
|
2021-09-16 15:12:43.849 Detail, Node002, Notification: ValueAdded
|
||||||
|
2021-09-16 15:12:43.851 Detail, Node002, Notification: ValueAdded
|
||||||
|
2021-09-16 15:12:43.854 Detail, Node002, Notification: ValueAdded
|
||||||
|
2021-09-16 15:12:43.862 Detail, Node002, Notification: ValueAdded
|
||||||
|
2021-09-16 15:12:43.864 Detail, Node002, Notification: ValueAdded
|
||||||
|
2021-09-16 15:12:43.869 Detail, Node002, Notification: ValueAdded
|
||||||
|
2021-09-16 15:12:43.872 Detail, Node002, Notification: ValueAdded
|
||||||
|
2021-09-16 15:12:43.893 Detail, Node002, Notification: ValueAdded
|
||||||
|
2021-09-16 15:12:43.901 Detail, Node002, Notification: ValueAdded
|
||||||
|
2021-09-16 15:12:43.903 Detail, Node002, Notification: ValueAdded
|
||||||
|
2021-09-16 15:12:43.905 Detail, Node002, Notification: ValueAdded
|
||||||
|
2021-09-16 15:12:43.913 Detail, Node002, Notification: ValueAdded
|
||||||
|
2021-09-16 15:12:43.915 Detail, Node002, Notification: NodeNaming
|
||||||
|
2021-09-16 15:12:43.917 Detail,
|
||||||
|
2021-09-16 15:12:43.917 Info, contrlr, Sending (Command) message (Callback ID=0x00, Expected Reply=0x06) - FUNC_ID_SERIAL_API_SET_TIMEOUTS: 0x01, 0x05, 0x00, 0x06, 0x64, 0x0f, 0x97
|
||||||
|
2021-09-16 15:12:43.921 Detail, contrlr, Received: 0x01, 0x05, 0x01, 0x06, 0x64, 0x0f, 0x96
|
||||||
|
2021-09-16 15:12:43.921 Detail,
|
||||||
|
2021-09-16 15:12:43.921 Info, contrlr, Received reply to FUNC_ID_SERIAL_API_SET_TIMEOUTS
|
||||||
|
2021-09-16 15:12:43.921 Detail, Node015, Expected reply was received
|
||||||
|
2021-09-16 15:12:43.921 Detail, Node015, Message transaction complete
|
||||||
|
2021-09-16 15:12:43.921 Detail,
|
||||||
|
2021-09-16 15:12:43.921 Detail, contrlr, Removing current message
|
||||||
|
2021-09-16 15:12:43.921 Detail,
|
||||||
|
2021-09-16 15:12:43.922 Info, contrlr, Sending (Command) message (Callback ID=0x00, Expected Reply=0x00) - FUNC_ID_SERIAL_API_APPL_NODE_INFORMATION: 0x01, 0x07, 0x00, 0x03, 0x01, 0x02, 0x01, 0x00, 0xf9
|
||||||
|
2021-09-16 15:12:43.924 Detail, contrlr, Removing current message
|
||||||
|
2021-09-16 15:12:43.925 Detail,
|
||||||
|
2021-09-16 15:12:43.925 Info, Node001, Sending (Command) message (Callback ID=0x00, Expected Reply=0x80) - Get Routing Info (Node=1): 0x01, 0x07, 0x00, 0x80, 0x01, 0x00, 0x00, 0x03, 0x7a
|
||||||
|
2021-09-16 15:12:43.931 Detail, Node001, Received: 0x01, 0x20, 0x01, 0x80, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5c
|
||||||
|
2021-09-16 15:12:43.931 Detail,
|
||||||
|
2021-09-16 15:12:43.931 Info, Node001, Received reply to FUNC_ID_ZW_GET_ROUTING_INFO
|
||||||
|
2021-09-16 15:12:43.932 Info, Node001, Neighbors of this node are:
|
||||||
|
2021-09-16 15:12:43.932 Info, Node001, Node 2
|
||||||
|
2021-09-16 15:12:43.932 Detail, Expected reply was received
|
||||||
|
2021-09-16 15:12:43.932 Detail, Message transaction complete
|
||||||
|
2021-09-16 15:12:43.932 Detail,
|
||||||
|
2021-09-16 15:12:43.932 Detail, Node001, Removing current message
|
||||||
|
2021-09-16 15:12:43.932 Detail,
|
||||||
|
2021-09-16 15:12:43.932 Info, Node002, Sending (NoOp) message (Callback ID=0x0a, Expected Reply=0x13) - NoOperation_Set (Node=2): 0x01, 0x09, 0x00, 0x13, 0x02, 0x02, 0x00, 0x00, 0x25, 0x0a, 0xca
|
||||||
|
2021-09-16 15:12:43.939 Detail, Node002, Received: 0x01, 0x04, 0x01, 0x13, 0x01, 0xe8
|
||||||
|
2021-09-16 15:12:43.939 Detail, Node002, ZW_SEND_DATA delivered to Z-Wave stack
|
||||||
|
2021-09-16 15:12:43.955 Detail, Node002, Received: 0x01, 0x05, 0x00, 0x13, 0x0a, 0x00, 0xe3
|
||||||
|
2021-09-16 15:12:43.955 Detail, Node002, ZW_SEND_DATA Request with callback ID 0x0a received (expected 0x0a)
|
||||||
|
2021-09-16 15:12:43.955 Info, Node002, Request RTT 23 Average Request RTT 23
|
||||||
|
2021-09-16 15:12:43.956 Detail, Expected callbackId was received
|
||||||
|
2021-09-16 15:12:43.956 Detail, Expected reply was received
|
||||||
|
2021-09-16 15:12:43.956 Detail, Message transaction complete
|
||||||
|
2021-09-16 15:12:43.956 Detail,
|
||||||
|
2021-09-16 15:12:43.956 Detail, Node002, Removing current message
|
||||||
|
2021-09-16 15:12:43.956 Detail, Node002, Notification: Notification - NoOperation
|
||||||
|
2021-09-16 15:12:43.994 Detail, Node001, Query Stage Complete (Neighbors)
|
||||||
|
2021-09-16 15:12:43.994 Detail, Node001, AdvanceQueries queryPending=0 queryRetries=0 queryStage=Session live=1
|
||||||
|
2021-09-16 15:12:43.994 Detail, Node001, QueryStage_Session
|
||||||
|
2021-09-16 15:12:43.994 Detail, Node001, QueryStage_Dynamic
|
||||||
|
2021-09-16 15:12:43.994 Detail, Node001, QueryStage_Configuration
|
||||||
|
2021-09-16 15:12:43.994 Detail, Node001, QueryStage_Complete
|
||||||
|
2021-09-16 15:12:43.994 Warning, CheckCompletedNodeQueries m_allNodesQueried=0 m_awakeNodesQueried=0
|
||||||
|
2021-09-16 15:12:43.994 Warning, CheckCompletedNodeQueries all=0, deadFound=0 sleepingOnly=0
|
||||||
|
2021-09-16 15:12:43.994 Detail, Node001, Notification: NodeQueriesComplete
|
||||||
|
2021-09-16 15:12:44.008 Detail, Node002, Query Stage Complete (CacheLoad)
|
||||||
|
2021-09-16 15:12:44.008 Detail, Node002, AdvanceQueries queryPending=0 queryRetries=0 queryStage=Associations live=1
|
||||||
|
2021-09-16 15:12:44.008 Detail, Node002, QueryStage_Associations
|
||||||
|
2021-09-16 15:12:44.008 Detail, Node002, QueryStage_Neighbors
|
||||||
|
2021-09-16 15:12:44.008 Detail, Requesting routing info (neighbor list) for Node 2
|
||||||
|
2021-09-16 15:12:44.009 Detail, Node002, Queuing (Command) Get Routing Info (Node=2): 0x01, 0x07, 0x00, 0x80, 0x02, 0x00, 0x00, 0x03, 0x79
|
||||||
|
2021-09-16 15:12:44.009 Detail, Node002, Queuing (Query) Query Stage Complete (Neighbors)
|
||||||
|
2021-09-16 15:12:44.009 Detail,
|
||||||
|
2021-09-16 15:12:44.010 Info, Node002, Sending (Command) message (Callback ID=0x00, Expected Reply=0x80) - Get Routing Info (Node=2): 0x01, 0x07, 0x00, 0x80, 0x02, 0x00, 0x00, 0x03, 0x79
|
||||||
|
2021-09-16 15:12:44.018 Detail, Node002, Received: 0x01, 0x20, 0x01, 0x80, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5f
|
||||||
|
2021-09-16 15:12:44.018 Detail,
|
||||||
|
2021-09-16 15:12:44.018 Info, Node002, Received reply to FUNC_ID_ZW_GET_ROUTING_INFO
|
||||||
|
2021-09-16 15:12:44.018 Info, Node002, Neighbors of this node are:
|
||||||
|
2021-09-16 15:12:44.018 Info, Node002, Node 1
|
||||||
|
2021-09-16 15:12:44.018 Detail, Expected reply was received
|
||||||
|
2021-09-16 15:12:44.018 Detail, Message transaction complete
|
||||||
|
2021-09-16 15:12:44.018 Detail,
|
||||||
|
2021-09-16 15:12:44.018 Detail, Node002, Removing current message
|
||||||
|
2021-09-16 15:12:44.019 Detail, Node002, Query Stage Complete (Neighbors)
|
||||||
|
2021-09-16 15:12:44.019 Detail, Node002, AdvanceQueries queryPending=0 queryRetries=0 queryStage=Session live=1
|
||||||
|
2021-09-16 15:12:44.019 Detail, Node002, QueryStage_Session
|
||||||
|
2021-09-16 15:12:44.019 Detail, Node002, Queuing (Query) SwitchAllCmd_Get (Node=2): 0x01, 0x09, 0x00, 0x13, 0x02, 0x02, 0x27, 0x02, 0x25, 0x0b, 0xee
|
||||||
|
2021-09-16 15:12:44.019 Detail, Node002, Queuing (Query) PowerlevelCmd_Get (Node=2): 0x01, 0x09, 0x00, 0x13, 0x02, 0x02, 0x73, 0x02, 0x25, 0x0c, 0xbd
|
||||||
|
2021-09-16 15:12:44.019 Detail, Node002, Queuing (Query) ProtectionCmd_Get (Node=2): 0x01, 0x09, 0x00, 0x13, 0x02, 0x02, 0x75, 0x02, 0x25, 0x0d, 0xba
|
||||||
|
2021-09-16 15:12:44.019 Detail, Node002, Queuing (Query) Query Stage Complete (Session)
|
||||||
|
2021-09-16 15:12:44.019 Detail,
|
||||||
|
2021-09-16 15:12:44.019 Info, Node002, Sending (Query) message (Callback ID=0x0b, Expected Reply=0x04) - SwitchAllCmd_Get (Node=2): 0x01, 0x09, 0x00, 0x13, 0x02, 0x02, 0x27, 0x02, 0x25, 0x0b, 0xee
|
||||||
|
2021-09-16 15:12:44.026 Detail, Node002, Received: 0x01, 0x04, 0x01, 0x13, 0x01, 0xe8
|
||||||
|
2021-09-16 15:12:44.026 Detail, Node002, ZW_SEND_DATA delivered to Z-Wave stack
|
||||||
|
2021-09-16 15:12:44.042 Detail, Node002, Received: 0x01, 0x05, 0x00, 0x13, 0x0b, 0x00, 0xe2
|
||||||
|
2021-09-16 15:12:44.042 Detail, Node002, ZW_SEND_DATA Request with callback ID 0x0b received (expected 0x0b)
|
||||||
|
2021-09-16 15:12:44.043 Info, Node002, Request RTT 22 Average Request RTT 22
|
||||||
|
2021-09-16 15:12:44.043 Detail, Expected callbackId was received
|
||||||
|
2021-09-16 15:12:44.051 Detail, Node002, Received: 0x01, 0x09, 0x00, 0x04, 0x00, 0x02, 0x03, 0x27, 0x03, 0xff, 0x28
|
||||||
|
2021-09-16 15:12:44.051 Detail,
|
||||||
|
2021-09-16 15:12:44.051 Info, Node002, Response RTT 31 Average Response RTT 31
|
||||||
|
2021-09-16 15:12:44.051 Detail, Node002, Initial read of value
|
||||||
|
2021-09-16 15:12:44.051 Info, Node002, Received SwitchAll report from node 2: On and Off Enabled
|
||||||
|
2021-09-16 15:12:44.051 Detail, Node002, Expected reply and command class was received
|
||||||
|
2021-09-16 15:12:44.052 Detail, Node002, Message transaction complete
|
||||||
|
2021-09-16 15:12:44.052 Detail,
|
||||||
|
2021-09-16 15:12:44.052 Detail, Node002, Removing current message
|
||||||
|
2021-09-16 15:12:44.052 Detail, Node002, Notification: ValueChanged
|
||||||
|
2021-09-16 15:12:44.066 Detail,
|
||||||
|
2021-09-16 15:12:44.066 Info, Node002, Sending (Query) message (Callback ID=0x0c, Expected Reply=0x04) - PowerlevelCmd_Get (Node=2): 0x01, 0x09, 0x00, 0x13, 0x02, 0x02, 0x73, 0x02, 0x25, 0x0c, 0xbd
|
||||||
|
2021-09-16 15:12:44.074 Detail, Node002, Received: 0x01, 0x04, 0x01, 0x13, 0x01, 0xe8
|
||||||
|
2021-09-16 15:12:44.074 Detail, Node002, ZW_SEND_DATA delivered to Z-Wave stack
|
||||||
|
2021-09-16 15:12:44.089 Detail, Node002, Received: 0x01, 0x05, 0x00, 0x13, 0x0c, 0x00, 0xe5
|
||||||
|
2021-09-16 15:12:44.089 Detail, Node002, ZW_SEND_DATA Request with callback ID 0x0c received (expected 0x0c)
|
||||||
|
2021-09-16 15:12:44.089 Info, Node002, Request RTT 22 Average Request RTT 22
|
||||||
|
2021-09-16 15:12:44.089 Detail, Expected callbackId was received
|
||||||
|
2021-09-16 15:12:44.098 Detail, Node002, Received: 0x01, 0x0a, 0x00, 0x04, 0x00, 0x02, 0x04, 0x73, 0x03, 0x00, 0x00, 0x87
|
||||||
|
2021-09-16 15:12:44.098 Detail,
|
||||||
|
2021-09-16 15:12:44.098 Info, Node002, Response RTT 31 Average Response RTT 31
|
||||||
|
2021-09-16 15:12:44.098 Info, Node002, Received a PowerLevel report: PowerLevel=Normal, Timeout=0
|
||||||
|
2021-09-16 15:12:44.098 Detail, Node002, Initial read of value
|
||||||
|
2021-09-16 15:12:44.098 Detail, Node002, Initial read of value
|
||||||
|
2021-09-16 15:12:44.099 Detail, Node002, Expected reply and command class was received
|
||||||
|
2021-09-16 15:12:44.099 Detail, Node002, Message transaction complete
|
||||||
|
2021-09-16 15:12:44.099 Detail,
|
||||||
|
2021-09-16 15:12:44.099 Detail, Node002, Removing current message
|
||||||
|
2021-09-16 15:12:44.099 Detail, Node002, Notification: ValueChanged
|
||||||
|
2021-09-16 15:12:44.121 Detail, Node002, Notification: ValueChanged
|
||||||
|
2021-09-16 15:12:44.125 Detail,
|
||||||
|
2021-09-16 15:12:44.125 Info, Node002, Sending (Query) message (Callback ID=0x0d, Expected Reply=0x04) - ProtectionCmd_Get (Node=2): 0x01, 0x09, 0x00, 0x13, 0x02, 0x02, 0x75, 0x02, 0x25, 0x0d, 0xba
|
||||||
|
2021-09-16 15:12:44.132 Detail, Node002, Received: 0x01, 0x04, 0x01, 0x13, 0x01, 0xe8
|
||||||
|
2021-09-16 15:12:44.132 Detail, Node002, ZW_SEND_DATA delivered to Z-Wave stack
|
||||||
|
2021-09-16 15:12:44.148 Detail, Node002, Received: 0x01, 0x05, 0x00, 0x13, 0x0d, 0x00, 0xe4
|
||||||
|
2021-09-16 15:12:44.148 Detail, Node002, ZW_SEND_DATA Request with callback ID 0x0d received (expected 0x0d)
|
||||||
|
2021-09-16 15:12:44.148 Info, Node002, Request RTT 23 Average Request RTT 22
|
||||||
|
2021-09-16 15:12:44.149 Detail, Expected callbackId was received
|
||||||
|
2021-09-16 15:12:44.157 Detail, Node002, Received: 0x01, 0x09, 0x00, 0x04, 0x00, 0x02, 0x03, 0x75, 0x03, 0x00, 0x85
|
||||||
|
2021-09-16 15:12:44.157 Detail,
|
||||||
|
2021-09-16 15:12:44.157 Info, Node002, Response RTT 32 Average Response RTT 31
|
||||||
|
2021-09-16 15:12:44.157 Info, Node002, Received a Protection report: Unprotected
|
||||||
|
2021-09-16 15:12:44.157 Detail, Node002, Initial read of value
|
||||||
|
2021-09-16 15:12:44.158 Detail, Node002, Expected reply and command class was received
|
||||||
|
2021-09-16 15:12:44.158 Detail, Node002, Message transaction complete
|
||||||
|
2021-09-16 15:12:44.158 Detail,
|
||||||
|
2021-09-16 15:12:44.158 Detail, Node002, Removing current message
|
||||||
|
2021-09-16 15:12:44.158 Detail, Node002, Notification: ValueChanged
|
||||||
|
2021-09-16 15:12:44.169 Detail, Node002, Query Stage Complete (Session)
|
||||||
|
2021-09-16 15:12:44.169 Detail, Node002, AdvanceQueries queryPending=0 queryRetries=0 queryStage=Dynamic live=1
|
||||||
|
2021-09-16 15:12:44.169 Detail, Node002, QueryStage_Dynamic
|
||||||
|
2021-09-16 15:12:44.170 Detail, Node002, Queuing (Send) SwitchBinaryCmd_Get (Node=2): 0x01, 0x09, 0x00, 0x13, 0x02, 0x02, 0x25, 0x02, 0x25, 0x0e, 0xe9
|
||||||
|
2021-09-16 15:12:44.170 Detail, Node002, Queuing (Query) Query Stage Complete (Dynamic)
|
||||||
|
2021-09-16 15:12:44.170 Detail,
|
||||||
|
2021-09-16 15:12:44.170 Info, Node002, Sending (Send) message (Callback ID=0x0e, Expected Reply=0x04) - SwitchBinaryCmd_Get (Node=2): 0x01, 0x09, 0x00, 0x13, 0x02, 0x02, 0x25, 0x02, 0x25, 0x0e, 0xe9
|
||||||
|
2021-09-16 15:12:44.177 Detail, Node002, Received: 0x01, 0x04, 0x01, 0x13, 0x01, 0xe8
|
||||||
|
2021-09-16 15:12:44.177 Detail, Node002, ZW_SEND_DATA delivered to Z-Wave stack
|
||||||
|
2021-09-16 15:12:44.192 Detail, Node002, Received: 0x01, 0x05, 0x00, 0x13, 0x0e, 0x00, 0xe7
|
||||||
|
2021-09-16 15:12:44.193 Detail, Node002, ZW_SEND_DATA Request with callback ID 0x0e received (expected 0x0e)
|
||||||
|
2021-09-16 15:12:44.193 Info, Node002, Request RTT 22 Average Request RTT 22
|
||||||
|
2021-09-16 15:12:44.193 Detail, Expected callbackId was received
|
||||||
|
2021-09-16 15:12:44.201 Detail, Node002, Received: 0x01, 0x09, 0x00, 0x04, 0x00, 0x02, 0x03, 0x25, 0x03, 0x00, 0xd5
|
||||||
|
2021-09-16 15:12:44.201 Detail,
|
||||||
|
2021-09-16 15:12:44.202 Info, Node002, Response RTT 31 Average Response RTT 31
|
||||||
|
2021-09-16 15:12:44.202 Info, Node002, Received SwitchBinary report from node 2: level=Off
|
||||||
|
2021-09-16 15:12:44.202 Detail, Node002, Initial read of value
|
||||||
|
2021-09-16 15:12:44.202 Detail, Node002, Expected reply and command class was received
|
||||||
|
2021-09-16 15:12:44.202 Detail, Node002, Message transaction complete
|
||||||
|
2021-09-16 15:12:44.202 Detail,
|
||||||
|
2021-09-16 15:12:44.202 Detail, Node002, Removing current message
|
||||||
|
2021-09-16 15:12:44.202 Detail, Node002, Notification: ValueChanged
|
||||||
|
2021-09-16 15:12:44.207 Detail, Node002, Query Stage Complete (Dynamic)
|
||||||
|
2021-09-16 15:12:44.207 Detail, Node002, AdvanceQueries queryPending=0 queryRetries=0 queryStage=Configuration live=1
|
||||||
|
2021-09-16 15:12:44.207 Detail, Node002, QueryStage_Configuration
|
||||||
|
2021-09-16 15:12:44.207 Detail, Node002, QueryStage_Complete
|
||||||
|
2021-09-16 15:12:44.207 Warning, CheckCompletedNodeQueries m_allNodesQueried=0 m_awakeNodesQueried=0
|
||||||
|
2021-09-16 15:12:44.208 Warning, CheckCompletedNodeQueries all=1, deadFound=0 sleepingOnly=1
|
||||||
|
2021-09-16 15:12:44.208 Info, Node query processing complete.
|
||||||
|
2021-09-16 15:12:44.208 Detail, Node002, Notification: NodeQueriesComplete
|
||||||
|
2021-09-16 15:12:44.220 Detail, contrlr, Notification: AllNodesQueried
|
||||||
|
2021-09-16 16:48:44.778 Info, mgr, Manager::WriteConfig completed for driver with home ID of 0xf9bd3698
|
||||||
|
2021-09-16 16:48:45.783 Info, mgr, Driver for controller /dev/ttyUSB0 pending removal
|
||||||
|
2021-09-16 16:48:45.784 Detail, Notification: DriverRemoved
|
||||||
|
2021-09-16 16:48:45.784 Always, ***************************************************************************
|
||||||
|
2021-09-16 16:48:45.784 Always, ********************* Cumulative Network Statistics *********************
|
||||||
|
2021-09-16 16:48:45.784 Always, *** General
|
||||||
|
2021-09-16 16:48:45.784 Always, Driver run time: . . . 0 days, 1 hours, 36 minutes
|
||||||
|
2021-09-16 16:48:45.784 Always, Frames processed: . . . . . . . . . . . . . . . . . . . . 24
|
||||||
|
2021-09-16 16:48:45.784 Always, Total messages successfully received: . . . . . . . . . . 24
|
||||||
|
2021-09-16 16:48:45.784 Always, Total Messages successfully sent: . . . . . . . . . . . . 16
|
||||||
|
2021-09-16 16:48:45.784 Always, ACKs received from controller: . . . . . . . . . . . . . 16
|
||||||
|
2021-09-16 16:48:45.784 Always, *** Errors
|
||||||
|
2021-09-16 16:48:45.784 Always, Unsolicited messages received while waiting for ACK: . . 0
|
||||||
|
2021-09-16 16:48:45.784 Always, Reads aborted due to timeouts: . . . . . . . . . . . . . 0
|
||||||
|
2021-09-16 16:48:45.784 Always, Bad checksum errors: . . . . . . . . . . . . . . . . . . 0
|
||||||
|
2021-09-16 16:48:45.784 Always, CANs received from controller: . . . . . . . . . . . . . 0
|
||||||
|
2021-09-16 16:48:45.785 Always, NAKs received from controller: . . . . . . . . . . . . . 0
|
||||||
|
2021-09-16 16:48:45.785 Always, Out of frame data flow errors: . . . . . . . . . . . . . 0
|
||||||
|
2021-09-16 16:48:45.785 Always, Messages retransmitted: . . . . . . . . . . . . . . . . . 0
|
||||||
|
2021-09-16 16:48:45.785 Always, Messages dropped and not delivered: . . . . . . . . . . . 0
|
||||||
|
2021-09-16 16:48:45.785 Always, ***************************************************************************
|
||||||
|
2021-09-16 16:48:47.796 Info, mgr, Driver for controller /dev/ttyUSB0 removed
|
||||||
|
2021-09-16 16:48:48.800 Error, mgr, Manager::GetDriver failed - Home ID 0xf9bd3698 is unknown
|
||||||
|
2021-09-16 16:48:48.800 Warning, Exception: Manager.cpp:373 - 100 - Invalid HomeId passed to GetDriver
|
||||||
|
2021-09-16 16:48:48.800 Info, mgr, GetSendQueueCount() failed - _homeId -105040232 not found
|
||||||
15
appdaemon/appdaemon.yaml
Normal file
15
appdaemon/appdaemon.yaml
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
---
|
||||||
|
secrets: /config/secrets.yaml
|
||||||
|
appdaemon:
|
||||||
|
latitude: 52.379189
|
||||||
|
longitude: 4.899431
|
||||||
|
elevation: 2
|
||||||
|
time_zone: Europe/Amsterdam
|
||||||
|
plugins:
|
||||||
|
HASS:
|
||||||
|
type: hass
|
||||||
|
http:
|
||||||
|
url: http://127.0.0.1:5050
|
||||||
|
admin:
|
||||||
|
api:
|
||||||
|
hadashboard:
|
||||||
BIN
appdaemon/apps/__pycache__/hello.cpython-39.pyc
Normal file
BIN
appdaemon/apps/__pycache__/hello.cpython-39.pyc
Normal file
Binary file not shown.
4
appdaemon/apps/apps.yaml
Normal file
4
appdaemon/apps/apps.yaml
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
hello_world:
|
||||||
|
module: hello
|
||||||
|
class: HelloWorld
|
||||||
13
appdaemon/apps/hello.py
Normal file
13
appdaemon/apps/hello.py
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import appdaemon.plugins.hass.hassapi as hass
|
||||||
|
|
||||||
|
#
|
||||||
|
# Hellow World App
|
||||||
|
#
|
||||||
|
# Args:
|
||||||
|
#
|
||||||
|
|
||||||
|
class HelloWorld(hass.Hass):
|
||||||
|
|
||||||
|
def initialize(self):
|
||||||
|
self.log("Hello from AppDaemon")
|
||||||
|
self.log("You are now ready to run Apps!")
|
||||||
250
appdaemon/compiled/css/default/hello_application.css
Normal file
250
appdaemon/compiled/css/default/hello_application.css
Normal file
@@ -0,0 +1,250 @@
|
|||||||
|
|
||||||
|
html {
|
||||||
|
font-size: 100%;
|
||||||
|
-webkit-text-size-adjust: 100%;
|
||||||
|
-ms-text-size-adjust: 100%;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
background-color: #222;
|
||||||
|
font-size: 15px;
|
||||||
|
color: #fff;
|
||||||
|
padding: 0;
|
||||||
|
line-height: 1;
|
||||||
|
font-family: 'Helvetica Neue', 'Helvetica', 'Open Sans', 'Arial'
|
||||||
|
}
|
||||||
|
|
||||||
|
b, strong {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
text-decoration: none;
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
border: 0;
|
||||||
|
-ms-interpolation-mode: bicubic;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
img, object {
|
||||||
|
max-width: 100%;
|
||||||
|
-webkit-border-radius: 5px;
|
||||||
|
-moz-border-radius: 5px;
|
||||||
|
border-radius: 5px;}
|
||||||
|
|
||||||
|
iframe {
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
border-collapse: collapse;
|
||||||
|
border-spacing: 0;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
td {
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul, ol {
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1, h2, h3, h4, h5, p {
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
h1 {
|
||||||
|
margin-bottom: 6px;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 100%;
|
||||||
|
font-weight: 200;
|
||||||
|
}
|
||||||
|
h2 {
|
||||||
|
font-size: 300%;
|
||||||
|
font-weight: 400;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
h3 {
|
||||||
|
font-size: 125%;
|
||||||
|
font-weight: 300;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
input {
|
||||||
|
background-color: #444;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard_main {
|
||||||
|
margin: 0px auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gridster {
|
||||||
|
margin: 0px auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-background {
|
||||||
|
pointer-events: none;
|
||||||
|
width: 100%!important;
|
||||||
|
height: 100%;
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
opacity: 0.1;
|
||||||
|
font-size: 1375%;
|
||||||
|
text-align: center;
|
||||||
|
margin-top: 82px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-nostyle {
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gridster ul {
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gs-w {
|
||||||
|
width: 100%;
|
||||||
|
display: table;
|
||||||
|
cursor: pointer;
|
||||||
|
z-index: auto !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.iframe {
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
-webkit-border-radius: 5px;
|
||||||
|
-moz-border-radius: 5px;
|
||||||
|
border-radius: 5px;}
|
||||||
|
|
||||||
|
.widget {
|
||||||
|
padding: 0px 0px;
|
||||||
|
text-align: center;
|
||||||
|
width: 100%;
|
||||||
|
display: table-cell;
|
||||||
|
vertical-align: middle;
|
||||||
|
background-color: #444444;
|
||||||
|
-webkit-border-radius: 5px;
|
||||||
|
-moz-border-radius: 5px;
|
||||||
|
border-radius: 5px;}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-inactive {
|
||||||
|
color: #888;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-active {
|
||||||
|
color: #aaff00;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#container {
|
||||||
|
padding-top: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modalDialog {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
background: rgba(0,0,0,0.8);
|
||||||
|
z-index: 9999;
|
||||||
|
opacity:0;
|
||||||
|
-webkit-transition: opacity 400ms ease-in;
|
||||||
|
-moz-transition: opacity 400ms ease-in;
|
||||||
|
transition: opacity 400ms ease-in;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modalDialogOpen {
|
||||||
|
opacity:0.95;
|
||||||
|
pointer-events: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modalDialogClose {
|
||||||
|
opacity:0;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modalDialog > div {
|
||||||
|
width: 275px;
|
||||||
|
position: relative;
|
||||||
|
margin: 3% auto;
|
||||||
|
padding: 5px 20px 13px 20px;
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modalDialogCloseButton {
|
||||||
|
line-height: 50px;
|
||||||
|
position: absolute;
|
||||||
|
right: -25px;
|
||||||
|
text-align: center;
|
||||||
|
top: -20px;
|
||||||
|
width: 50px;
|
||||||
|
text-decoration: none;
|
||||||
|
font-weight: bold;
|
||||||
|
-webkit-border-radius: 25px;
|
||||||
|
-moz-border-radius: 25px;
|
||||||
|
border-radius: 25px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modalDialogCloseButton:hover { background: #444; }
|
||||||
|
|
||||||
|
.widget-basedisplay-default-label .unit {
|
||||||
|
font-size: 225%;
|
||||||
|
font-weight: 400;
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: top;
|
||||||
|
margin-left: 5px;
|
||||||
|
margin-top: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.widget-basedisplay-default-label .value {
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.widget-basedisplay-default-label .valueunit {
|
||||||
|
width: 100%;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.widget-basedisplay-default-label .title {
|
||||||
|
position: absolute;
|
||||||
|
top: 5px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.widget-basedisplay-default-label .title2 {
|
||||||
|
position: absolute;
|
||||||
|
top: 23px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.widget-basedisplay-default-label .state_text {
|
||||||
|
position: absolute;
|
||||||
|
bottom: -3px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.widget-baseclock-default-clock-clock.date {
|
||||||
|
position: absolute;
|
||||||
|
top: 5px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.widget-baseclock-default-clock-clock.time {
|
||||||
|
position: absolute;
|
||||||
|
top: 45px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
324
appdaemon/compiled/css/default/house_application.css
Normal file
324
appdaemon/compiled/css/default/house_application.css
Normal file
@@ -0,0 +1,324 @@
|
|||||||
|
|
||||||
|
html {
|
||||||
|
font-size: 100%;
|
||||||
|
-webkit-text-size-adjust: 100%;
|
||||||
|
-ms-text-size-adjust: 100%;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
background-color: #222;
|
||||||
|
font-size: 15px;
|
||||||
|
color: #fff;
|
||||||
|
padding: 0;
|
||||||
|
line-height: 1;
|
||||||
|
font-family: 'Helvetica Neue', 'Helvetica', 'Open Sans', 'Arial'
|
||||||
|
}
|
||||||
|
|
||||||
|
b, strong {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
text-decoration: none;
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
border: 0;
|
||||||
|
-ms-interpolation-mode: bicubic;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
img, object {
|
||||||
|
max-width: 100%;
|
||||||
|
-webkit-border-radius: 5px;
|
||||||
|
-moz-border-radius: 5px;
|
||||||
|
border-radius: 5px;}
|
||||||
|
|
||||||
|
iframe {
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
border-collapse: collapse;
|
||||||
|
border-spacing: 0;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
td {
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul, ol {
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1, h2, h3, h4, h5, p {
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
h1 {
|
||||||
|
margin-bottom: 6px;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 100%;
|
||||||
|
font-weight: 200;
|
||||||
|
}
|
||||||
|
h2 {
|
||||||
|
font-size: 300%;
|
||||||
|
font-weight: 400;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
h3 {
|
||||||
|
font-size: 125%;
|
||||||
|
font-weight: 300;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
input {
|
||||||
|
background-color: #444;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard_main {
|
||||||
|
margin: 0px auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gridster {
|
||||||
|
margin: 0px auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-background {
|
||||||
|
pointer-events: none;
|
||||||
|
width: 100%!important;
|
||||||
|
height: 100%;
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
opacity: 0.1;
|
||||||
|
font-size: 1375%;
|
||||||
|
text-align: center;
|
||||||
|
margin-top: 82px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-nostyle {
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gridster ul {
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gs-w {
|
||||||
|
width: 100%;
|
||||||
|
display: table;
|
||||||
|
cursor: pointer;
|
||||||
|
z-index: auto !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.iframe {
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
-webkit-border-radius: 5px;
|
||||||
|
-moz-border-radius: 5px;
|
||||||
|
border-radius: 5px;}
|
||||||
|
|
||||||
|
.widget {
|
||||||
|
padding: 0px 0px;
|
||||||
|
text-align: center;
|
||||||
|
width: 100%;
|
||||||
|
display: table-cell;
|
||||||
|
vertical-align: middle;
|
||||||
|
background-color: #444444;
|
||||||
|
-webkit-border-radius: 5px;
|
||||||
|
-moz-border-radius: 5px;
|
||||||
|
border-radius: 5px;}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-inactive {
|
||||||
|
color: #888;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-active {
|
||||||
|
color: #aaff00;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#container {
|
||||||
|
padding-top: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modalDialog {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
background: rgba(0,0,0,0.8);
|
||||||
|
z-index: 9999;
|
||||||
|
opacity:0;
|
||||||
|
-webkit-transition: opacity 400ms ease-in;
|
||||||
|
-moz-transition: opacity 400ms ease-in;
|
||||||
|
transition: opacity 400ms ease-in;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modalDialogOpen {
|
||||||
|
opacity:0.95;
|
||||||
|
pointer-events: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modalDialogClose {
|
||||||
|
opacity:0;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modalDialog > div {
|
||||||
|
width: 275px;
|
||||||
|
position: relative;
|
||||||
|
margin: 3% auto;
|
||||||
|
padding: 5px 20px 13px 20px;
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modalDialogCloseButton {
|
||||||
|
line-height: 50px;
|
||||||
|
position: absolute;
|
||||||
|
right: -25px;
|
||||||
|
text-align: center;
|
||||||
|
top: -20px;
|
||||||
|
width: 50px;
|
||||||
|
text-decoration: none;
|
||||||
|
font-weight: bold;
|
||||||
|
-webkit-border-radius: 25px;
|
||||||
|
-moz-border-radius: 25px;
|
||||||
|
border-radius: 25px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modalDialogCloseButton:hover { background: #444; }
|
||||||
|
|
||||||
|
.widget-baseclock-default-clock-clock.date {
|
||||||
|
position: absolute;
|
||||||
|
top: 5px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.widget-baseclock-default-clock-clock.time {
|
||||||
|
position: absolute;
|
||||||
|
top: 45px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.widget-basedisplay-default-outside-temp .unit {
|
||||||
|
font-size: 225%;
|
||||||
|
font-weight: 400;
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: top;
|
||||||
|
margin-left: 5px;
|
||||||
|
margin-top: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.widget-basedisplay-default-outside-temp .value {
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.widget-basedisplay-default-outside-temp .valueunit {
|
||||||
|
width: 100%;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.widget-basedisplay-default-outside-temp .title {
|
||||||
|
position: absolute;
|
||||||
|
top: 5px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.widget-basedisplay-default-outside-temp .title2 {
|
||||||
|
position: absolute;
|
||||||
|
top: 23px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.widget-basedisplay-default-outside-temp .state_text {
|
||||||
|
position: absolute;
|
||||||
|
bottom: -3px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.widget-baseclimate-default-inside-temp .title {
|
||||||
|
position: absolute;
|
||||||
|
top: 5px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.widget-baseclimate-default-inside-temp .title2 {
|
||||||
|
position: absolute;
|
||||||
|
top: 23px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.widget-baseclimate-default-inside-temp .level {
|
||||||
|
font-size: 250%;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.widget-baseclimate-default-inside-temp .units {
|
||||||
|
font-size: 100%;
|
||||||
|
font-weight: 400;
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: top;
|
||||||
|
margin-left: 5px;
|
||||||
|
margin-top: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.widget-baseclimate-default-inside-temp .levelunits {
|
||||||
|
position: absolute;
|
||||||
|
top: 43px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.widget-baseclimate-default-inside-temp .secondary-icon {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0px;
|
||||||
|
font-size: 20px;
|
||||||
|
width: 32px;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.widget-baseclimate-default-inside-temp .level2 {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.widget-baseclimate-default-inside-temp .units2 {
|
||||||
|
font-size: 65%;
|
||||||
|
font-weight: 400;
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: top;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.widget-baseclimate-default-inside-temp .levelunits2 {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 5px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.widget-baseclimate-default-inside-temp .secondary-icon.plus {
|
||||||
|
right: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.widget-baseclimate-default-inside-temp .secondary-icon.plus i {
|
||||||
|
padding-top: 10px;
|
||||||
|
padding-left: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.widget-baseclimate-default-inside-temp .secondary-icon.minus {
|
||||||
|
left: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.widget-baseclimate-default-inside-temp .secondary-icon.minus i {
|
||||||
|
padding-top: 10px;
|
||||||
|
padding-right: 30px;
|
||||||
|
}
|
||||||
6
appdaemon/compiled/html/default/hello_body.html
Normal file
6
appdaemon/compiled/html/default/hello_body.html
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<! body tags ->
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<! body tags go here ->
|
||||||
|
|
||||||
6
appdaemon/compiled/html/default/hello_head.html
Normal file
6
appdaemon/compiled/html/default/hello_head.html
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<! head tags ->
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<! head tags go here ->
|
||||||
|
|
||||||
6
appdaemon/compiled/html/default/house_body.html
Normal file
6
appdaemon/compiled/html/default/house_body.html
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<! body tags ->
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<! body tags go here ->
|
||||||
|
|
||||||
6
appdaemon/compiled/html/default/house_head.html
Normal file
6
appdaemon/compiled/html/default/house_head.html
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<! head tags ->
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<! head tags go here ->
|
||||||
|
|
||||||
3115
appdaemon/compiled/javascript/application.js
Normal file
3115
appdaemon/compiled/javascript/application.js
Normal file
File diff suppressed because it is too large
Load Diff
134
appdaemon/compiled/javascript/default/hello_init.js
Normal file
134
appdaemon/compiled/javascript/default/hello_init.js
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
var myDeviceID;
|
||||||
|
$(function(){ //DOM Ready
|
||||||
|
|
||||||
|
function navigate(url)
|
||||||
|
{
|
||||||
|
window.location.href = url;
|
||||||
|
}
|
||||||
|
|
||||||
|
$(document).attr("title", "Hello Panel");
|
||||||
|
content_width = (120 + 5) * 8 + 5
|
||||||
|
$('.gridster').width(content_width)
|
||||||
|
$(".gridster ul").gridster({
|
||||||
|
widget_margins: [5, 5],
|
||||||
|
widget_base_dimensions: [120, 120],
|
||||||
|
avoid_overlapped_widgets: true,
|
||||||
|
max_rows: 15,
|
||||||
|
max_size_x: 8,
|
||||||
|
shift_widgets_up: false
|
||||||
|
}).data('gridster').disable();
|
||||||
|
|
||||||
|
// Add Widgets
|
||||||
|
|
||||||
|
var gridster = $(".gridster ul").gridster().data('gridster');
|
||||||
|
|
||||||
|
gridster.add_widget('<li><div data-bind="attr: {style: widget_style}" class="widget widget-basedisplay-default-label" id="default-label"><h1 class="title" data-bind="text: title, attr:{ style: title_style}"></h1><h1 class="title2" data-bind="text: title2, attr:{ style: title2_style}"></h1><div class="valueunit" data-bind="attr:{ style: container_style}"><h2 class="value" data-bind="html: value, attr:{ style: value_style}"></h2><p class="unit" data-bind="html: unit, attr:{ style: unit_style}"></p></div><h1 class="state_text" data-bind="text: state_text, attr: {style: state_text_style}"></h1></div></li>', 2, 2, 1, 1)
|
||||||
|
|
||||||
|
gridster.add_widget('<li><div data-bind="attr: {style: widget_style}" class="widget widget-baseclock-default-clock-clock" id="default-clock-clock"><h1 class="date"data-bind="text: date, attr: {style: date_style}"></h1><h2 class="time" data-bind="text: time, attr: {style: time_style}"></h2></div></li>', 1, 1, 3, 1)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
var widgets = {}
|
||||||
|
// Initialize Widgets
|
||||||
|
|
||||||
|
widgets["default-label"] = new basedisplay("default-label", "", "default", {'widget_type': 'basedisplay', 'fields': {'title': '', 'title2': '', 'value': 'Hello World', 'unit': '', 'state_text': ''}, 'static_css': {'title_style': 'color: #fff;', 'title2_style': 'color: #fff;', 'unit_style': '', 'value_style': 'color: #fff;', 'state_text_style': 'color: #fff;', 'widget_style': 'background-color: #444;', 'container_style': ''}, 'css': {}, 'icons': [], 'static_icons': [], 'namespace': 'default'})
|
||||||
|
|
||||||
|
widgets["default-clock-clock"] = new baseclock("default-clock-clock", "", "default", {'widget_type': 'baseclock', 'fields': {'date': '', 'time': ''}, 'static_css': {'date_style': 'color: #fff;', 'time_style': 'color: #aa00ff;', 'widget_style': 'background-color: #444;'}, 'static_icons': [], 'icons': [], 'css': {}, 'entity': 'clock.clock', 'title_is_friendly_name': 1, 'namespace': 'default'})
|
||||||
|
|
||||||
|
|
||||||
|
// Setup click handler to cancel timeout navigations
|
||||||
|
|
||||||
|
$( ".gridster" ).click(function(){
|
||||||
|
clearTimeout(myTimeout);
|
||||||
|
if (myTimeoutSticky) {
|
||||||
|
myTimeout = setTimeout(function() { navigate(myTimeoutUrl); }, myTimeoutDelay);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Set up timeout
|
||||||
|
|
||||||
|
var myTimeout;
|
||||||
|
var myTimeoutUrl;
|
||||||
|
var myTimeoutDelay;
|
||||||
|
var myTimeoutSticky = 0;
|
||||||
|
if (location.search != "")
|
||||||
|
{
|
||||||
|
console.log("begin")
|
||||||
|
var query = location.search.substr(1);
|
||||||
|
var result = {};
|
||||||
|
query.split("&").forEach(function(part) {
|
||||||
|
var item = part.split("=");
|
||||||
|
result[item[0]] = decodeURIComponent(item[1]);
|
||||||
|
});
|
||||||
|
|
||||||
|
if ("deviceid" in result)
|
||||||
|
{
|
||||||
|
myDeviceID = result.deviceid;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
setCookie('ADdevID', myDeviceID);
|
||||||
|
}
|
||||||
|
catch (e)
|
||||||
|
{
|
||||||
|
console.log(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
myDeviceID = getCookie('ADdevID');
|
||||||
|
}
|
||||||
|
catch (e)
|
||||||
|
{
|
||||||
|
console.log(e);
|
||||||
|
myDeviceID = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ("timeout" in result && "return" in result)
|
||||||
|
{
|
||||||
|
url = result.return
|
||||||
|
argcount = 0
|
||||||
|
for (arg in result)
|
||||||
|
{
|
||||||
|
if (arg != "timeout" && arg != "return" && arg != "sticky")
|
||||||
|
{
|
||||||
|
if (argcount == 0)
|
||||||
|
{
|
||||||
|
url += "?";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
url += "&";
|
||||||
|
}
|
||||||
|
argcount ++;
|
||||||
|
url += arg + "=" + result[arg]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ("sticky" in result)
|
||||||
|
{
|
||||||
|
myTimeoutSticky = (result.sticky == "1");
|
||||||
|
}
|
||||||
|
myTimeoutUrl = url;
|
||||||
|
myTimeoutDelay = result.timeout * 1000;
|
||||||
|
myTimeout = setTimeout(function() { navigate(url); }, result.timeout * 1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
myDeviceID = getCookie('ADdevID');
|
||||||
|
}
|
||||||
|
catch (e)
|
||||||
|
{
|
||||||
|
console.log(e);
|
||||||
|
myDeviceID = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start listening for AD Events
|
||||||
|
|
||||||
|
window.dashstream = new DashStream("ws", location.protocol, document.domain, location.port, "Hello Panel", widgets);
|
||||||
|
|
||||||
|
});
|
||||||
138
appdaemon/compiled/javascript/default/house_init.js
Normal file
138
appdaemon/compiled/javascript/default/house_init.js
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
var myDeviceID;
|
||||||
|
$(function(){ //DOM Ready
|
||||||
|
|
||||||
|
function navigate(url)
|
||||||
|
{
|
||||||
|
window.location.href = url;
|
||||||
|
}
|
||||||
|
|
||||||
|
$(document).attr("title", "Main Panel");
|
||||||
|
content_width = (120 + 5) * 8 + 5
|
||||||
|
$('.gridster').width(content_width)
|
||||||
|
$(".gridster ul").gridster({
|
||||||
|
widget_margins: [5, 5],
|
||||||
|
widget_base_dimensions: [120, 120],
|
||||||
|
avoid_overlapped_widgets: true,
|
||||||
|
max_rows: 15,
|
||||||
|
max_size_x: 8,
|
||||||
|
shift_widgets_up: false
|
||||||
|
}).data('gridster').disable();
|
||||||
|
|
||||||
|
// Add Widgets
|
||||||
|
|
||||||
|
var gridster = $(".gridster ul").gridster().data('gridster');
|
||||||
|
|
||||||
|
gridster.add_widget('<li><div data-bind="attr: {style: widget_style}" class="widget widget-baseclock-default-clock-clock" id="default-clock-clock"><h1 class="date"data-bind="text: date, attr: {style: date_style}"></h1><h2 class="time" data-bind="text: time, attr: {style: time_style}"></h2></div></li>', 2, 2, 1, 1)
|
||||||
|
|
||||||
|
gridster.add_widget('<li><div data-bind="attr: {style: widget_style}" class="widget widget-basedisplay-default-outside-temp" id="default-outside-temp"><h1 class="title" data-bind="text: title, attr:{ style: title_style}"></h1><h1 class="title2" data-bind="text: title2, attr:{ style: title2_style}"></h1><div class="valueunit" data-bind="attr:{ style: container_style}"><h2 class="value" data-bind="html: value, attr:{ style: value_style}"></h2><p class="unit" data-bind="html: unit, attr:{ style: unit_style}"></p></div><h1 class="state_text" data-bind="text: state_text, attr: {style: state_text_style}"></h1></div></li>', 1, 1, 3, 1)
|
||||||
|
|
||||||
|
gridster.add_widget('<li><div data-bind="attr: {style: widget_style}" class="widget widget-baseclimate-default-inside-temp" id="default-inside-temp"><h1 class="title" data-bind="text: title, attr:{style: title_style}"></h1><h1 class="title2" data-bind="text: title2, attr:{style: title2_style}"></h1><div class="levelunits"><h2 class="level" data-bind="text: level, attr:{ style: level_style}"></h2><p class="units" data-bind="html: units, attr:{ style: unit_style}"></p></div><div class="levelunits2"><p class="level2" data-bind="text: level2, attr:{style: level2_style}"></p><p class="units2" data-bind="html: units, attr:{style: unit2_style}"></p></div><p class="secondary-icon minus"><i data-bind="attr: {class: icon_down, style: level_down_style}" id="level-down"></i></p><p class="secondary-icon plus"><i data-bind="attr: {class: icon_up, style: level_up_style}" id="level-up"></i></p></div></li>', 1, 1, 4, 1)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
var widgets = {}
|
||||||
|
// Initialize Widgets
|
||||||
|
|
||||||
|
widgets["default-clock-clock"] = new baseclock("default-clock-clock", "", "default", {'widget_type': 'baseclock', 'fields': {'date': '', 'time': ''}, 'static_css': {'date_style': 'color: #fff;', 'time_style': 'color: #aa00ff;', 'widget_style': 'background-color: #444;'}, 'static_icons': [], 'icons': [], 'css': {}, 'entity': 'clock.clock', 'title_is_friendly_name': 1, 'use_comma': 0, 'precision': 1, 'use_hass_icon': 1, 'namespace': 'default'})
|
||||||
|
|
||||||
|
widgets["default-outside-temp"] = new basedisplay("default-outside-temp", "", "default", {'widget_type': 'basedisplay', 'entity': 'sensor.thermostat_outside_temperature', 'entity_to_sub_entity_attribute': '', 'sub_entity': '', 'sub_entity_to_entity_attribute': '', 'fields': {'title': 'Outside Temperature', 'title2': '', 'value': '', 'unit': '', 'state_text': ''}, 'static_css': {'title_style': 'color: #fff;', 'title2_style': 'color: #fff;', 'state_text_style': 'color: #fff;font-size: 100%;', 'widget_style': 'background-color: #444;', 'container_style': ''}, 'css': {'value_style': 'color: #00aaff;font-size: 250%;', 'text_style': 'color: #fff;font-size: 100%;', 'unit_style': 'color: #00aaff;font-size: 100%;'}, 'icons': [], 'static_icons': [], 'units': '°F', 'precision': 1, 'use_comma': 0, 'use_hass_icon': 1, 'namespace': 'default'})
|
||||||
|
|
||||||
|
widgets["default-inside-temp"] = new baseclimate("default-inside-temp", "", "default", {'widget_type': 'baseclimate', 'entity': 'climate.home', 'post_service': {'service': 'climate/set_temperature', 'entity_id': 'climate.home'}, 'fields': {'title': 'Thermostat', 'title2': '', 'units': '', 'level': '', 'level2': ''}, 'icons': [], 'css': {}, 'static_icons': {'icon_up': 'fas-plus', 'icon_down': 'fas-minus'}, 'static_css': {'title_style': 'color: #fff;', 'title2_style': 'color: #fff;', 'level_style': 'color: #00aaff;', 'level2_style': 'color: #00aaff;', 'level_up_style': 'color: #888;', 'level_down_style': 'color: #888;', 'widget_style': 'background-color: #444;', 'unit_style': 'color: #00aaff;', 'unit2_style': 'color: #00aaff;'}, 'use_comma': 0, 'precision': 1, 'use_hass_icon': 1, 'namespace': 'default'})
|
||||||
|
|
||||||
|
|
||||||
|
// Setup click handler to cancel timeout navigations
|
||||||
|
|
||||||
|
$( ".gridster" ).click(function(){
|
||||||
|
clearTimeout(myTimeout);
|
||||||
|
if (myTimeoutSticky) {
|
||||||
|
myTimeout = setTimeout(function() { navigate(myTimeoutUrl); }, myTimeoutDelay);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Set up timeout
|
||||||
|
|
||||||
|
var myTimeout;
|
||||||
|
var myTimeoutUrl;
|
||||||
|
var myTimeoutDelay;
|
||||||
|
var myTimeoutSticky = 0;
|
||||||
|
if (location.search != "")
|
||||||
|
{
|
||||||
|
console.log("begin")
|
||||||
|
var query = location.search.substr(1);
|
||||||
|
var result = {};
|
||||||
|
query.split("&").forEach(function(part) {
|
||||||
|
var item = part.split("=");
|
||||||
|
result[item[0]] = decodeURIComponent(item[1]);
|
||||||
|
});
|
||||||
|
|
||||||
|
if ("deviceid" in result)
|
||||||
|
{
|
||||||
|
myDeviceID = result.deviceid;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
setCookie('ADdevID', myDeviceID);
|
||||||
|
}
|
||||||
|
catch (e)
|
||||||
|
{
|
||||||
|
console.log(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
myDeviceID = getCookie('ADdevID');
|
||||||
|
}
|
||||||
|
catch (e)
|
||||||
|
{
|
||||||
|
console.log(e);
|
||||||
|
myDeviceID = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ("timeout" in result && "return" in result)
|
||||||
|
{
|
||||||
|
url = result.return
|
||||||
|
argcount = 0
|
||||||
|
for (arg in result)
|
||||||
|
{
|
||||||
|
if (arg != "timeout" && arg != "return" && arg != "sticky")
|
||||||
|
{
|
||||||
|
if (argcount == 0)
|
||||||
|
{
|
||||||
|
url += "?";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
url += "&";
|
||||||
|
}
|
||||||
|
argcount ++;
|
||||||
|
url += arg + "=" + result[arg]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ("sticky" in result)
|
||||||
|
{
|
||||||
|
myTimeoutSticky = (result.sticky == "1");
|
||||||
|
}
|
||||||
|
myTimeoutUrl = url;
|
||||||
|
myTimeoutDelay = result.timeout * 1000;
|
||||||
|
myTimeout = setTimeout(function() { navigate(url); }, result.timeout * 1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
myDeviceID = getCookie('ADdevID');
|
||||||
|
}
|
||||||
|
catch (e)
|
||||||
|
{
|
||||||
|
console.log(e);
|
||||||
|
myDeviceID = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start listening for AD Events
|
||||||
|
|
||||||
|
window.dashstream = new DashStream("ws", location.protocol, document.domain, location.port, "Main Panel", widgets);
|
||||||
|
|
||||||
|
});
|
||||||
14
appdaemon/dashboards/Hello.dash
Normal file
14
appdaemon/dashboards/Hello.dash
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
#
|
||||||
|
# Main arguments, all optional
|
||||||
|
#
|
||||||
|
title: Hello Panel
|
||||||
|
widget_dimensions: [120, 120]
|
||||||
|
widget_margins: [5, 5]
|
||||||
|
columns: 8
|
||||||
|
|
||||||
|
label:
|
||||||
|
widget_type: label
|
||||||
|
text: Hello World
|
||||||
|
|
||||||
|
layout:
|
||||||
|
- label(2x2), clock.clock
|
||||||
36
appdaemon/dashboards/House.dash
Normal file
36
appdaemon/dashboards/House.dash
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
title: Main Panel
|
||||||
|
widget_dimensions: [120, 120]
|
||||||
|
widget_size: [1, 1]
|
||||||
|
widget_margins: [5, 5]
|
||||||
|
columns: 8
|
||||||
|
global_parameters:
|
||||||
|
use_comma: 0
|
||||||
|
precision: 1
|
||||||
|
use_hass_icon: 1
|
||||||
|
namespace: default
|
||||||
|
layout:
|
||||||
|
- clock.clock (2x2), outside_temp, inside_temp
|
||||||
|
# - set_temp, spacer, test
|
||||||
|
|
||||||
|
test:
|
||||||
|
widget_type: label
|
||||||
|
title: test
|
||||||
|
|
||||||
|
outside_temp:
|
||||||
|
widget_type: sensor
|
||||||
|
title: Outside Temperature
|
||||||
|
units: "°F"
|
||||||
|
precision: 1
|
||||||
|
entity: sensor.thermostat_outside_temperature
|
||||||
|
|
||||||
|
#inside_temp:
|
||||||
|
# widget_type: sensor
|
||||||
|
# title: Inside Temperature
|
||||||
|
# units: "°F"
|
||||||
|
# precision: 1
|
||||||
|
# entity: sensor.home_temperature
|
||||||
|
|
||||||
|
inside_temp:
|
||||||
|
widget_type: climate
|
||||||
|
title: Thermostat
|
||||||
|
entity: climate.home
|
||||||
252
automations.yaml
252
automations.yaml
@@ -1,252 +0,0 @@
|
|||||||
- alias: 'Come home lights on if dark'
|
|
||||||
trigger:
|
|
||||||
- platform: state
|
|
||||||
entity_id: device_tracker.dan_dansphone
|
|
||||||
to: 'home'
|
|
||||||
condition:
|
|
||||||
condition: and
|
|
||||||
conditions:
|
|
||||||
- condition: state
|
|
||||||
entity_id: device_tracker.dan_dansphone
|
|
||||||
state: 'home'
|
|
||||||
- condition: sun
|
|
||||||
after: sunset
|
|
||||||
after_offset: '-01:00:00'
|
|
||||||
action:
|
|
||||||
- service: light.turn_on
|
|
||||||
entity_id: group.lightautomation
|
|
||||||
- service: notify.notify_dan
|
|
||||||
data:
|
|
||||||
title: 'Lights on'
|
|
||||||
message: 'All lights turned on'
|
|
||||||
- delay: '00:05:00'
|
|
||||||
- service: light.turn_off
|
|
||||||
entity_id: light.sengled_e11g13_030b3e1d_1
|
|
||||||
- service: notify.notify_dan
|
|
||||||
data:
|
|
||||||
title: 'EntryWay Light Off - Beacon'
|
|
||||||
message: 'Entryway light turned off. Welcome Home.'
|
|
||||||
|
|
||||||
- alias: 'Leave and lights turn off'
|
|
||||||
trigger:
|
|
||||||
platform: state
|
|
||||||
entity_id: device_tracker.dan_dansphone
|
|
||||||
to: 'not_home'
|
|
||||||
condition:
|
|
||||||
condition: state
|
|
||||||
entity_id: group.all_lights
|
|
||||||
state: 'on'
|
|
||||||
action:
|
|
||||||
- service: light.turn_off
|
|
||||||
entity_id: group.all_lights
|
|
||||||
- service: notify.notify_dan
|
|
||||||
data:
|
|
||||||
title: 'Lights off'
|
|
||||||
message: 'All lights have been turned off'
|
|
||||||
|
|
||||||
- alias: 'Switch on'
|
|
||||||
trigger:
|
|
||||||
platform: sun
|
|
||||||
event: sunset
|
|
||||||
offset: '-01:00:00'
|
|
||||||
action:
|
|
||||||
service: switch.turn_on
|
|
||||||
entity_id: switch.linear_ps15z2_plugin_appliance_module_switch
|
|
||||||
|
|
||||||
- alias: 'Switch off'
|
|
||||||
trigger:
|
|
||||||
platform: time
|
|
||||||
at: '00:00:00'
|
|
||||||
action:
|
|
||||||
service: switch.turn_off
|
|
||||||
entity_id: switch.linear_ps15z2_plugin_appliance_module_switch
|
|
||||||
|
|
||||||
#- alias: 'Lights_On_Beacon'
|
|
||||||
# trigger:
|
|
||||||
# - platform: state
|
|
||||||
# entity_id: sensor.Dan, sensor.Guest
|
|
||||||
# from: 'not_home'
|
|
||||||
# condition:
|
|
||||||
# condition: and
|
|
||||||
# conditions:
|
|
||||||
# - condition: sun
|
|
||||||
# after: sunset
|
|
||||||
# after_offset: "-1:00:00"
|
|
||||||
# - condition: state
|
|
||||||
# entity_id: group.lightautomation
|
|
||||||
# state: 'off'
|
|
||||||
# action:
|
|
||||||
# - service: light.turn_on
|
|
||||||
# entity_id: group.lightautomation
|
|
||||||
# - service: notify.notify_dan
|
|
||||||
# data:
|
|
||||||
# title: 'Lights on - Beacon'
|
|
||||||
# message: 'All lights turned on via beacon'
|
|
||||||
#
|
|
||||||
#- alias: 'Entryway_Off_Beacon'
|
|
||||||
# trigger:
|
|
||||||
# - platform: state
|
|
||||||
# entity_id: sensor.Dan
|
|
||||||
# from: 'EntryWay - HB'
|
|
||||||
# to: 'Bedroom'
|
|
||||||
# condition:
|
|
||||||
# condition: state
|
|
||||||
# entity_id: light.sengled_e11g13_030b3e1d_1
|
|
||||||
# state: 'on'
|
|
||||||
# action:
|
|
||||||
# - service: light.turn_off
|
|
||||||
# entity_id: light.sengled_e11g13_030b3e1d_1
|
|
||||||
# - service: notify.notify_dan
|
|
||||||
# data:
|
|
||||||
# title: 'EntryWay Light Off - Beacon'
|
|
||||||
# message: 'Entryway light turned off. Welcome Home.'
|
|
||||||
#
|
|
||||||
#- alias: 'Lights_off_Beacon'
|
|
||||||
# trigger:
|
|
||||||
# platform: state
|
|
||||||
# entity_id: sensor.Dan, sensor.Guest
|
|
||||||
# to: 'not_home'
|
|
||||||
# condition:
|
|
||||||
# condition: state
|
|
||||||
# entity_id: group.all_lights
|
|
||||||
# state: 'on'
|
|
||||||
# action:
|
|
||||||
# - service: light.turn_off
|
|
||||||
# entity_id: group.all_lights
|
|
||||||
# - service: notify.notify_dan
|
|
||||||
# data:
|
|
||||||
# title: 'Lights off - Beacon'
|
|
||||||
# message: 'All lights have been turned off via beacon'
|
|
||||||
|
|
||||||
- alias: 'Lights on when home'
|
|
||||||
trigger:
|
|
||||||
- platform: sun
|
|
||||||
event: sunset
|
|
||||||
offset: '-01:00:00'
|
|
||||||
condition:
|
|
||||||
condition: and
|
|
||||||
conditions:
|
|
||||||
- condition: template
|
|
||||||
##value_template: "{% if not is_state('sensor.dan', 'not_home') or not is_state('sensor.guest', 'not_home') or is_state('device_tracker.dan_dansphone', 'home') %}true{% endif %}"
|
|
||||||
value_template: "{% if is_state('device_tracker.dan_dansphone', 'home') %}true{% endif %}"
|
|
||||||
- condition: state
|
|
||||||
entity_id: group.lightlivingroom
|
|
||||||
state: 'off'
|
|
||||||
action:
|
|
||||||
- service: light.turn_on
|
|
||||||
entity_id: group.lightlivingroom
|
|
||||||
- service: notify.notify_dan
|
|
||||||
data:
|
|
||||||
title: 'Living Room Lights on - Sunset'
|
|
||||||
message: 'Living Room Lights turned on. But you probably know that.'
|
|
||||||
|
|
||||||
- alias: 'IFTT'
|
|
||||||
trigger:
|
|
||||||
platform: event
|
|
||||||
event_type: ifttt_webhook_received
|
|
||||||
event_data:
|
|
||||||
action: call_service
|
|
||||||
action:
|
|
||||||
service_template: '{{ trigger.event.data.service }}'
|
|
||||||
data_template:
|
|
||||||
entity_id: '{{ trigger.event.data.entity_id }}'
|
|
||||||
|
|
||||||
- alias: 'Sleep - Lights Off'
|
|
||||||
trigger:
|
|
||||||
platform: state
|
|
||||||
entity_id: sensor.sleep_Info
|
|
||||||
to: 'startsleep'
|
|
||||||
action:
|
|
||||||
- service: light.turn_off
|
|
||||||
entity_id: group.all_lights
|
|
||||||
|
|
||||||
- alias: 'Sleep - Lights On'
|
|
||||||
trigger:
|
|
||||||
platform: state
|
|
||||||
entity_id: sensor.sleep_Info
|
|
||||||
to: 'stopsleep'
|
|
||||||
action:
|
|
||||||
- service: light.turn_on
|
|
||||||
entity_id: group.all_lights
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#########Climate Automations######
|
|
||||||
|
|
||||||
- alias: 'Set Away Mode'
|
|
||||||
trigger:
|
|
||||||
platform: state
|
|
||||||
entity_id: device_tracker.dan_dansphone
|
|
||||||
to: 'not_home'
|
|
||||||
from: 'home'
|
|
||||||
condition:
|
|
||||||
condition: template
|
|
||||||
value_template: "{% if not is_state('climate.home', 'off') %}true{% endif %}"
|
|
||||||
action:
|
|
||||||
- service: climate.set_preset_mode
|
|
||||||
entity_id: climate.home
|
|
||||||
data:
|
|
||||||
preset_mode: 'away'
|
|
||||||
- service: notify.notify_dan
|
|
||||||
data:
|
|
||||||
title: 'Left House - Set thermostate to Away'
|
|
||||||
message: 'Thermostat set to away mode'
|
|
||||||
- service: input_boolean.turn_on
|
|
||||||
data:
|
|
||||||
entity_id: input_boolean.thermostat_mode
|
|
||||||
|
|
||||||
- alias: 'Leave Work Set Home Mode'
|
|
||||||
trigger:
|
|
||||||
platform: state
|
|
||||||
entity_id: device_tracker.dan_dansphone
|
|
||||||
to: 'not_home'
|
|
||||||
from: 'Work'
|
|
||||||
condition:
|
|
||||||
- condition: template
|
|
||||||
value_template: "{% if not is_state('climate.home', 'off') %}true{% endif %}"
|
|
||||||
- condition: time
|
|
||||||
weekday:
|
|
||||||
- mon
|
|
||||||
- tue
|
|
||||||
- wed
|
|
||||||
- thu
|
|
||||||
action:
|
|
||||||
- service: climate.set_preset_mode
|
|
||||||
entity_id: climate.home
|
|
||||||
data:
|
|
||||||
preset_mode: 'home'
|
|
||||||
- service: notify.notify_dan
|
|
||||||
data:
|
|
||||||
title: 'Left Work - Set thermostate to Home'
|
|
||||||
message: 'Thermostat set to home mode'
|
|
||||||
- service: input_boolean.turn_off
|
|
||||||
data:
|
|
||||||
entity_id: input_boolean.thermostat_mode
|
|
||||||
|
|
||||||
- alias: 'Set Home Mode'
|
|
||||||
trigger:
|
|
||||||
platform: state
|
|
||||||
entity_id: device_tracker.dan_dansphone
|
|
||||||
to: 'home'
|
|
||||||
from: 'not_home'
|
|
||||||
condition:
|
|
||||||
- condition: template
|
|
||||||
value_template: "{% if not is_state('climate.home', 'off') %}true{% endif %}"
|
|
||||||
- condition: state
|
|
||||||
entity_id: input_boolean.thermostat_mode
|
|
||||||
state: 'on'
|
|
||||||
action:
|
|
||||||
- service: climate.set_preset_mode
|
|
||||||
entity_id: climate.home
|
|
||||||
data:
|
|
||||||
preset_mode: 'home'
|
|
||||||
- service: notify.notify_dan
|
|
||||||
data:
|
|
||||||
title: 'Back Home - Set thermostate to Home'
|
|
||||||
message: 'Thermostat set to home mode'
|
|
||||||
- service: input_boolean.turn_off
|
|
||||||
data:
|
|
||||||
entity_id: input_boolean.thermostat_mode
|
|
||||||
|
|
||||||
|
|||||||
50
blueprints/automation/homeassistant/motion_light.yaml
Normal file
50
blueprints/automation/homeassistant/motion_light.yaml
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
blueprint:
|
||||||
|
name: Motion-activated Light
|
||||||
|
description: Turn on a light when motion is detected.
|
||||||
|
domain: automation
|
||||||
|
source_url: https://github.com/home-assistant/core/blob/dev/homeassistant/components/automation/blueprints/motion_light.yaml
|
||||||
|
input:
|
||||||
|
motion_entity:
|
||||||
|
name: Motion Sensor
|
||||||
|
selector:
|
||||||
|
entity:
|
||||||
|
domain: binary_sensor
|
||||||
|
device_class: motion
|
||||||
|
light_target:
|
||||||
|
name: Light
|
||||||
|
selector:
|
||||||
|
target:
|
||||||
|
entity:
|
||||||
|
domain: light
|
||||||
|
no_motion_wait:
|
||||||
|
name: Wait time
|
||||||
|
description: Time to leave the light on after last motion is detected.
|
||||||
|
default: 120
|
||||||
|
selector:
|
||||||
|
number:
|
||||||
|
min: 0
|
||||||
|
max: 3600
|
||||||
|
unit_of_measurement: seconds
|
||||||
|
|
||||||
|
# If motion is detected within the delay,
|
||||||
|
# we restart the script.
|
||||||
|
mode: restart
|
||||||
|
max_exceeded: silent
|
||||||
|
|
||||||
|
trigger:
|
||||||
|
platform: state
|
||||||
|
entity_id: !input motion_entity
|
||||||
|
from: "off"
|
||||||
|
to: "on"
|
||||||
|
|
||||||
|
action:
|
||||||
|
- service: light.turn_on
|
||||||
|
target: !input light_target
|
||||||
|
- wait_for_trigger:
|
||||||
|
platform: state
|
||||||
|
entity_id: !input motion_entity
|
||||||
|
from: "on"
|
||||||
|
to: "off"
|
||||||
|
- delay: !input no_motion_wait
|
||||||
|
- service: light.turn_off
|
||||||
|
target: !input light_target
|
||||||
43
blueprints/automation/homeassistant/notify_leaving_zone.yaml
Normal file
43
blueprints/automation/homeassistant/notify_leaving_zone.yaml
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
blueprint:
|
||||||
|
name: Zone Notification
|
||||||
|
description: Send a notification to a device when a person leaves a specific zone.
|
||||||
|
domain: automation
|
||||||
|
source_url: https://github.com/home-assistant/core/blob/dev/homeassistant/components/automation/blueprints/notify_leaving_zone.yaml
|
||||||
|
input:
|
||||||
|
person_entity:
|
||||||
|
name: Person
|
||||||
|
selector:
|
||||||
|
entity:
|
||||||
|
domain: person
|
||||||
|
zone_entity:
|
||||||
|
name: Zone
|
||||||
|
selector:
|
||||||
|
entity:
|
||||||
|
domain: zone
|
||||||
|
notify_device:
|
||||||
|
name: Device to notify
|
||||||
|
description: Device needs to run the official Home Assistant app to receive notifications.
|
||||||
|
selector:
|
||||||
|
device:
|
||||||
|
integration: mobile_app
|
||||||
|
|
||||||
|
trigger:
|
||||||
|
platform: state
|
||||||
|
entity_id: !input person_entity
|
||||||
|
|
||||||
|
variables:
|
||||||
|
zone_entity: !input zone_entity
|
||||||
|
# This is the state of the person when it's in this zone.
|
||||||
|
zone_state: "{{ states[zone_entity].name }}"
|
||||||
|
person_entity: !input person_entity
|
||||||
|
person_name: "{{ states[person_entity].name }}"
|
||||||
|
|
||||||
|
condition:
|
||||||
|
condition: template
|
||||||
|
value_template: "{{ trigger.from_state.state == zone_state and trigger.to_state.state != zone_state }}"
|
||||||
|
|
||||||
|
action:
|
||||||
|
domain: mobile_app
|
||||||
|
type: notify
|
||||||
|
device_id: !input notify_device
|
||||||
|
message: "{{ person_name }} has left {{ zone_state }}"
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
blueprint:
|
||||||
|
name: Confirmable Notification
|
||||||
|
description: >-
|
||||||
|
A script that sends an actionable notification with a confirmation before
|
||||||
|
running the specified action.
|
||||||
|
domain: script
|
||||||
|
source_url: https://github.com/home-assistant/core/blob/master/homeassistant/components/script/blueprints/confirmable_notification.yaml
|
||||||
|
input:
|
||||||
|
notify_device:
|
||||||
|
name: Device to notify
|
||||||
|
description: Device needs to run the official Home Assistant app to receive notifications.
|
||||||
|
selector:
|
||||||
|
device:
|
||||||
|
integration: mobile_app
|
||||||
|
title:
|
||||||
|
name: "Title"
|
||||||
|
description: "The title of the button shown in the notification."
|
||||||
|
default: ""
|
||||||
|
selector:
|
||||||
|
text:
|
||||||
|
message:
|
||||||
|
name: "Message"
|
||||||
|
description: "The message body"
|
||||||
|
selector:
|
||||||
|
text:
|
||||||
|
confirm_text:
|
||||||
|
name: "Confirmation Text"
|
||||||
|
description: "Text to show on the confirmation button"
|
||||||
|
default: "Confirm"
|
||||||
|
selector:
|
||||||
|
text:
|
||||||
|
confirm_action:
|
||||||
|
name: "Confirmation Action"
|
||||||
|
description: "Action to run when notification is confirmed"
|
||||||
|
default: []
|
||||||
|
selector:
|
||||||
|
action:
|
||||||
|
dismiss_text:
|
||||||
|
name: "Dismiss Text"
|
||||||
|
description: "Text to show on the dismiss button"
|
||||||
|
default: "Dismiss"
|
||||||
|
selector:
|
||||||
|
text:
|
||||||
|
dismiss_action:
|
||||||
|
name: "Dismiss Action"
|
||||||
|
description: "Action to run when notification is dismissed"
|
||||||
|
default: []
|
||||||
|
selector:
|
||||||
|
action:
|
||||||
|
|
||||||
|
mode: restart
|
||||||
|
|
||||||
|
sequence:
|
||||||
|
- alias: "Send notification"
|
||||||
|
domain: mobile_app
|
||||||
|
type: notify
|
||||||
|
device_id: !input notify_device
|
||||||
|
title: !input title
|
||||||
|
message: !input message
|
||||||
|
data:
|
||||||
|
actions:
|
||||||
|
- action: "CONFIRM"
|
||||||
|
title: !input confirm_text
|
||||||
|
- action: "DISMISS"
|
||||||
|
title: !input dismiss_text
|
||||||
|
- alias: "Awaiting response"
|
||||||
|
wait_for_trigger:
|
||||||
|
- platform: event
|
||||||
|
event_type: mobile_app_notification_action
|
||||||
|
- choose:
|
||||||
|
- conditions: "{{ wait.trigger.event.data.action == 'CONFIRM' }}"
|
||||||
|
sequence: !input confirm_action
|
||||||
|
- conditions: "{{ wait.trigger.event.data.action == 'DISMISS' }}"
|
||||||
|
sequence: !input dismiss_action
|
||||||
12
climate.yaml
12
climate.yaml
@@ -1,12 +0,0 @@
|
|||||||
- alias: 'Set Away Mode'
|
|
||||||
trigger:
|
|
||||||
platform: state
|
|
||||||
entity_id: device_tracker.dan_dansphone
|
|
||||||
to: 'not_home'
|
|
||||||
condition: template
|
|
||||||
value_template: "{% if not is_state('thermostat_current_status', 'off') %}true{% endif %}"
|
|
||||||
action:
|
|
||||||
- service: set_away_mode
|
|
||||||
away_mode: on
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
- platform: template
|
|
||||||
sensors:
|
|
||||||
- thermostat_current_status:
|
|
||||||
value_template: '{{ states.climate.house.attributes.operation }}'
|
|
||||||
|
|
||||||
- thermostat_away_mode:
|
|
||||||
value_template: '{{ states.climate.house.attributes.away_mode }}'
|
|
||||||
@@ -1,69 +1,38 @@
|
|||||||
homeassistant:
|
|
||||||
# Name of the location where Home Assistant is running
|
|
||||||
name: Home
|
|
||||||
# Location required to calculate the time the sun rises and sets
|
|
||||||
latitude: 41.3568
|
|
||||||
longitude: -82.262
|
|
||||||
# Impacts weather/sunrise data (altitude above sea level in meters)
|
|
||||||
elevation: 244
|
|
||||||
# metric for Metric, imperial for Imperial
|
|
||||||
unit_system: imperial
|
|
||||||
# Pick yours from here: http://en.wikipedia.org/wiki/List_of_tz_database_time_zones
|
|
||||||
time_zone: America/New_York
|
|
||||||
# Customization file
|
|
||||||
customize: !include customize.yaml
|
|
||||||
|
|
||||||
# Show links to resources in log and frontend
|
|
||||||
#introduction:
|
|
||||||
|
|
||||||
# Enables the frontend
|
|
||||||
frontend:
|
frontend:
|
||||||
lovelace:
|
lovelace:
|
||||||
mode: yaml
|
mode: yaml
|
||||||
# Enables configuration UI
|
|
||||||
config:
|
|
||||||
|
|
||||||
recorder:
|
recorder:
|
||||||
db_url: postgresql://ha:password@docker.dandev.us/homeassistant
|
db_url: mysql://house:x3147qwfsbb9ctWy@192.168.86.198/house?charset=utf8
|
||||||
purge_interval: 0
|
#purge_interval: 0
|
||||||
|
auto_purge: false
|
||||||
map:
|
history:
|
||||||
|
# log:
|
||||||
|
system_health:
|
||||||
|
mobile_app:
|
||||||
|
# hassio:
|
||||||
|
|
||||||
|
|
||||||
|
# Configure a default setup of Home Assistant (frontend, api, etc)
|
||||||
|
config:
|
||||||
|
default_config:
|
||||||
|
|
||||||
#namecheapdns:
|
|
||||||
# domain: danshouse.space
|
|
||||||
# password: c6dc6147fab242a2a4c50688790995c5
|
|
||||||
|
|
||||||
http:
|
http:
|
||||||
# Secrets are defined in the file secrets.yaml
|
use_x_forwarded_for: true
|
||||||
#api_password: ICYN30blah
|
trusted_proxies:
|
||||||
# Uncomment this if you are using SSL/TLS, running in Docker container, etc.
|
- 192.168.86.198
|
||||||
|
alexa:
|
||||||
|
|
||||||
base_url: icyn30.duckdns.org:8123
|
# Text to speech
|
||||||
ssl_certificate: /ssl/fullchain.pem
|
tts:
|
||||||
ssl_key: /ssl/privkey.pem
|
- platform: google_translate
|
||||||
# Checks for available updates
|
|
||||||
# Note: This component will send some information about your system to
|
|
||||||
# the developers to assist with development of Home Assistant.
|
|
||||||
# For more information, please see:
|
|
||||||
# https://home-assistant.io/blog/2016/10/25/explaining-the-updater/
|
|
||||||
updater:
|
|
||||||
# Optional, allows Home Assistant developers to focus on popular components.
|
|
||||||
# include_used_components: true
|
|
||||||
|
|
||||||
# Discover some devices automatically
|
group: !include groups.yaml
|
||||||
#discovery:
|
automation: !include automations.yaml
|
||||||
|
script: !include scripts.yaml
|
||||||
|
scene: !include scenes.yaml
|
||||||
|
|
||||||
# Allows you to issue voice commands from the frontend in enabled browsers
|
|
||||||
conversation:
|
|
||||||
|
|
||||||
# Enables support for tracking state changes over time
|
|
||||||
history:
|
|
||||||
|
|
||||||
# View all events in a logbook
|
|
||||||
logbook:
|
|
||||||
|
|
||||||
# Track the sun
|
|
||||||
sun:
|
|
||||||
|
|
||||||
#weather
|
#weather
|
||||||
weather:
|
weather:
|
||||||
@@ -72,46 +41,21 @@ weather:
|
|||||||
latitude: 41.469249
|
latitude: 41.469249
|
||||||
longitude: -81.713146
|
longitude: -81.713146
|
||||||
name: Amherst
|
name: Amherst
|
||||||
|
sun:
|
||||||
|
|
||||||
|
hue:
|
||||||
|
bridges:
|
||||||
|
- host: 192.168.86.44
|
||||||
|
allow_unreachable: true
|
||||||
|
|
||||||
# sensors
|
# sensors
|
||||||
sensor:
|
sensor:
|
||||||
- platform: yr
|
#- platform: yr
|
||||||
# - platform: darksky
|
- platform: sonarr
|
||||||
# api_key: 19b4b95365ea88c65b085805ef046ba0
|
api_key: 0db45f76dbfc432ab5c86d8346d5165e
|
||||||
# latitude: 41.469249
|
host: 192.168.86.198:8989
|
||||||
# longitude: -81.713146
|
days: 7
|
||||||
# monitored_conditions:
|
# climate template sensors
|
||||||
# - icon
|
|
||||||
# - precip_type
|
|
||||||
# - precip_intensity
|
|
||||||
# - precip_probability
|
|
||||||
# - apparent_temperature
|
|
||||||
# - apparent_temperature_max
|
|
||||||
# - apparent_temperature_min
|
|
||||||
# - precip_intensity_max
|
|
||||||
# - hourly_summary
|
|
||||||
# - platform: mqtt
|
|
||||||
# state_topic: "owntracks/HA/DansPhone"
|
|
||||||
# name: "Phone Battery"
|
|
||||||
# unit_of_measurement: "%"
|
|
||||||
# value_template: '{{ value_json.batt }}'
|
|
||||||
# - platform: mqtt_room
|
|
||||||
# device_id: e2c56db5dffb48d2b060d0f5a71096e0_0000_0000
|
|
||||||
# name: 'Dan'
|
|
||||||
# state_topic: 'happy-bubbles/presence/ha'
|
|
||||||
# away_timeout: 60
|
|
||||||
# - platform: mqtt_room
|
|
||||||
# device_id: 74278bdab64445208f0c720eaf059935_0000_0000
|
|
||||||
# name: 'Guest'
|
|
||||||
# state_topic: 'happy-bubbles/presence/ha'
|
|
||||||
# away_timeout: 60
|
|
||||||
# - platform: sonarr
|
|
||||||
# api_key: c544cae167164f11947c84ad4e04f61c
|
|
||||||
# host: 192.168.86.20
|
|
||||||
# monitored_conditions:
|
|
||||||
# - upcoming
|
|
||||||
# - diskspace
|
|
||||||
# days: 7
|
|
||||||
- platform: template
|
- platform: template
|
||||||
sensors:
|
sensors:
|
||||||
thermostat_current_status:
|
thermostat_current_status:
|
||||||
@@ -128,83 +72,33 @@ sensor:
|
|||||||
sensors:
|
sensors:
|
||||||
thermostat_outside_temperature:
|
thermostat_outside_temperature:
|
||||||
unit_of_measurement: "°F"
|
unit_of_measurement: "°F"
|
||||||
value_template: "{{ float(states.weather.home.attributes.temperature) }}"
|
value_template: "{{ float(states.weather.home_2.attributes.temperature) }}"
|
||||||
|
#name: "Outside Temperature"
|
||||||
|
#MQTT sensors
|
||||||
- platform: mqtt
|
- platform: mqtt
|
||||||
state_topic: "upstairstemp/sensor/temp"
|
state_topic: "house/sensor/bedroom_temperature/state"
|
||||||
name: "Upstairs Temperature"
|
name: "Upstairs Temperature"
|
||||||
unit_of_measurement: "°F"
|
unit_of_measurement: "°F"
|
||||||
|
- platform: mqtt
|
||||||
|
state_topic: "house/sensor/bedroom_humidity/state"
|
||||||
|
name: "Upstairs Humidity"
|
||||||
|
unit_of_measurement: "%"
|
||||||
|
- platform: mqtt
|
||||||
|
state_topic: "house/sensor/living_room_brightness/state"
|
||||||
|
name: "Living Room Brightness"
|
||||||
- platform: mqtt
|
- platform: mqtt
|
||||||
state_topic: "DansPhone/sleepinfo"
|
state_topic: "DansPhone/sleepinfo"
|
||||||
name: "sleep Info"
|
name: "sleep Info"
|
||||||
|
|
||||||
|
|
||||||
# Text to speech
|
|
||||||
#tts:
|
|
||||||
# - platform: google
|
|
||||||
|
|
||||||
#mqtt:
|
|
||||||
# broker: core-mosquitto
|
|
||||||
# username: HA
|
|
||||||
# password: login
|
|
||||||
|
|
||||||
#device_tracker:
|
|
||||||
# - platform: owntracks
|
|
||||||
|
|
||||||
ifttt:
|
|
||||||
key: bO6GhKW9xCaQoIigKnq1qF
|
|
||||||
|
|
||||||
telegram_bot:
|
|
||||||
- platform: broadcast
|
|
||||||
api_key: 374239722:AAGd4y_LsEoOe5IUUblKXehCQFJV1s2JdQo
|
|
||||||
allowed_chat_ids:
|
|
||||||
438758485
|
|
||||||
|
|
||||||
notify:
|
#Alexa Intents
|
||||||
name: notify_dan
|
intent_script:
|
||||||
platform: telegram
|
ActivateScene:
|
||||||
chat_id: 438758485
|
action:
|
||||||
|
service: scene.turn_on
|
||||||
hue:
|
target:
|
||||||
bridges:
|
entity_id: scene.{{ Scene | replace(" ", "_") }}
|
||||||
- host: 192.168.86.44
|
speech:
|
||||||
allow_unreachable: true
|
type: plain
|
||||||
|
text: OK
|
||||||
|
|
||||||
alexa:
|
|
||||||
|
|
||||||
zwave:
|
|
||||||
usb_path: /dev/ttyUSB0
|
|
||||||
|
|
||||||
zha:
|
|
||||||
usb_path: /dev/ttyUSB1
|
|
||||||
database_path: /config/zigbee.db
|
|
||||||
|
|
||||||
ecobee:
|
|
||||||
api_key: 8DSRpsILZJNUIJXvSzY6C4ufrMQNQsHa
|
|
||||||
|
|
||||||
input_boolean:
|
|
||||||
thermostat_mode:
|
|
||||||
name: Thermostat Away Mode
|
|
||||||
|
|
||||||
influxdb:
|
|
||||||
host: docker.dandev.us
|
|
||||||
include:
|
|
||||||
entities:
|
|
||||||
- sensor.home_temperature
|
|
||||||
- sensor.home_humidity
|
|
||||||
- sensor.thermostat_outside_temperature
|
|
||||||
- sensor.upstairs_temperature
|
|
||||||
- device_tracker.dan_dansphone
|
|
||||||
- sensor.thermostate_current_status
|
|
||||||
- sensor.thermostate_climate_mode
|
|
||||||
- light.sengled_e11g13_030b22ba_1
|
|
||||||
- light.sengled_e11g13_030b3e1d_1
|
|
||||||
- light.sengled_e11g13_030b3efd_1
|
|
||||||
- light.sengled_e11g13_030b40af_1
|
|
||||||
- light.sengled_e11g13_031468d6_1
|
|
||||||
- light.table
|
|
||||||
|
|
||||||
group: !include groups.yaml
|
|
||||||
automation: !include automations.yaml
|
|
||||||
script: !include scripts.yaml
|
|
||||||
zone: !include zones.yaml
|
|
||||||
intent_script: !include intents.yaml
|
|
||||||
12
confirm.yaml
12
confirm.yaml
@@ -1,12 +0,0 @@
|
|||||||
>
|
|
||||||
{{ [
|
|
||||||
"OK",
|
|
||||||
"Sure",
|
|
||||||
"I gotchu",
|
|
||||||
"'ight",
|
|
||||||
"Done",
|
|
||||||
"No worries",
|
|
||||||
"Ain't no thang",
|
|
||||||
"As you wish",
|
|
||||||
"No problem"
|
|
||||||
] | random }}
|
|
||||||
68
custom_components/hacs/__init__.py
Normal file
68
custom_components/hacs/__init__.py
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
"""
|
||||||
|
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/
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import TYPE_CHECKING, Any
|
||||||
|
|
||||||
|
from homeassistant.config_entries import ConfigEntry
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
import voluptuous as vol
|
||||||
|
|
||||||
|
from .const import DOMAIN, PLATFORMS
|
||||||
|
from .enums import HacsDisabledReason
|
||||||
|
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,
|
||||||
|
)
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from .base import HacsBase
|
||||||
|
|
||||||
|
CONFIG_SCHEMA = vol.Schema({DOMAIN: hacs_config_combined()}, extra=vol.ALLOW_EXTRA)
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup(hass: HomeAssistant, config: dict[str, Any]) -> bool:
|
||||||
|
"""Set up this integration using yaml."""
|
||||||
|
|
||||||
|
return await hacs_yaml_setup(hass, config)
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
|
||||||
|
"""Set up this integration using UI."""
|
||||||
|
config_entry.add_update_listener(async_reload_entry)
|
||||||
|
|
||||||
|
return await hacs_ui_setup(hass, config_entry)
|
||||||
|
|
||||||
|
|
||||||
|
async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
|
||||||
|
"""Handle removal of an entry."""
|
||||||
|
hacs: HacsBase = hass.data[DOMAIN]
|
||||||
|
|
||||||
|
for task in hacs.recuring_tasks:
|
||||||
|
# Cancel all pending tasks
|
||||||
|
task()
|
||||||
|
|
||||||
|
try:
|
||||||
|
if hass.data.get("frontend_panels", {}).get("hacs"):
|
||||||
|
hacs.log.info("Removing sidepanel")
|
||||||
|
hass.components.frontend.async_remove_panel("hacs")
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
unload_ok = await hass.config_entries.async_unload_platforms(config_entry, PLATFORMS)
|
||||||
|
|
||||||
|
hacs.disable_hacs(HacsDisabledReason.REMOVED)
|
||||||
|
hass.data.pop(DOMAIN, None)
|
||||||
|
|
||||||
|
return unload_ok
|
||||||
|
|
||||||
|
|
||||||
|
async def async_reload_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> None:
|
||||||
|
"""Reload the HACS config entry."""
|
||||||
|
await async_unload_entry(hass, config_entry)
|
||||||
|
await async_setup_entry(hass, config_entry)
|
||||||
1
custom_components/hacs/api/__init__.py
Normal file
1
custom_components/hacs/api/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
"""Initialize HACS API"""
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
"""API Handler for acknowledge_critical_repository"""
|
||||||
|
from homeassistant.components import websocket_api
|
||||||
|
import homeassistant.helpers.config_validation as cv
|
||||||
|
import voluptuous as vol
|
||||||
|
|
||||||
|
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))
|
||||||
24
custom_components/hacs/api/check_local_path.py
Normal file
24
custom_components/hacs/api/check_local_path.py
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
"""API Handler for check_local_path"""
|
||||||
|
from homeassistant.components import websocket_api
|
||||||
|
import homeassistant.helpers.config_validation as cv
|
||||||
|
import voluptuous as vol
|
||||||
|
|
||||||
|
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))
|
||||||
15
custom_components/hacs/api/get_critical_repositories.py
Normal file
15
custom_components/hacs/api/get_critical_repositories.py
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
"""API Handler for get_critical_repositories"""
|
||||||
|
from homeassistant.components import websocket_api
|
||||||
|
import voluptuous as vol
|
||||||
|
|
||||||
|
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))
|
||||||
28
custom_components/hacs/api/hacs_config.py
Normal file
28
custom_components/hacs/api/hacs_config.py
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
"""API Handler for hacs_config"""
|
||||||
|
from homeassistant.components import websocket_api
|
||||||
|
import voluptuous as vol
|
||||||
|
|
||||||
|
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))
|
||||||
15
custom_components/hacs/api/hacs_removed.py
Normal file
15
custom_components/hacs/api/hacs_removed.py
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
"""API Handler for hacs_removed"""
|
||||||
|
from homeassistant.components import websocket_api
|
||||||
|
import voluptuous as vol
|
||||||
|
|
||||||
|
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))
|
||||||
65
custom_components/hacs/api/hacs_repositories.py
Normal file
65
custom_components/hacs/api/hacs_repositories.py
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
"""API Handler for hacs_repositories"""
|
||||||
|
from homeassistant.components import websocket_api
|
||||||
|
import voluptuous as vol
|
||||||
|
|
||||||
|
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
|
||||||
|
and not repo.ignored_by_country_configuration
|
||||||
|
):
|
||||||
|
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))
|
||||||
113
custom_components/hacs/api/hacs_repository.py
Normal file
113
custom_components/hacs/api/hacs_repository.py
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
"""API Handler for hacs_repository"""
|
||||||
|
from aiogithubapi import AIOGitHubAPIException
|
||||||
|
from homeassistant.components import websocket_api
|
||||||
|
import homeassistant.helpers.config_validation as cv
|
||||||
|
import voluptuous as vol
|
||||||
|
|
||||||
|
from custom_components.hacs.share import get_hacs
|
||||||
|
from custom_components.hacs.utils.logger import getLogger
|
||||||
|
|
||||||
|
|
||||||
|
@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(ignore_issues=True, force=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))
|
||||||
121
custom_components/hacs/api/hacs_repository_data.py
Normal file
121
custom_components/hacs/api/hacs_repository_data.py
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
"""API Handler for hacs_repository_data"""
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from aiogithubapi import AIOGitHubAPIException
|
||||||
|
from homeassistant.components import websocket_api
|
||||||
|
import homeassistant.helpers.config_validation as cv
|
||||||
|
import voluptuous as vol
|
||||||
|
|
||||||
|
from custom_components.hacs.exceptions import HacsException
|
||||||
|
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
|
||||||
|
from custom_components.hacs.utils.logger import getLogger
|
||||||
|
|
||||||
|
_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"], {}))
|
||||||
54
custom_components/hacs/api/hacs_settings.py
Normal file
54
custom_components/hacs/api/hacs_settings.py
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
"""API Handler for hacs_settings"""
|
||||||
|
from homeassistant.components import websocket_api
|
||||||
|
import homeassistant.helpers.config_validation as cv
|
||||||
|
import voluptuous as vol
|
||||||
|
|
||||||
|
from custom_components.hacs.share import get_hacs
|
||||||
|
from custom_components.hacs.utils.logger import getLogger
|
||||||
|
|
||||||
|
_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"], {}))
|
||||||
24
custom_components/hacs/api/hacs_status.py
Normal file
24
custom_components/hacs/api/hacs_status.py
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
"""API Handler for hacs_status"""
|
||||||
|
from homeassistant.components import websocket_api
|
||||||
|
import voluptuous as vol
|
||||||
|
|
||||||
|
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.core.lovelace_mode,
|
||||||
|
"reloading_data": hacs.status.reloading_data,
|
||||||
|
"upgrading_all": hacs.status.upgrading_all,
|
||||||
|
"disabled": hacs.system.disabled,
|
||||||
|
"disabled_reason": hacs.system.disabled_reason,
|
||||||
|
"has_pending_tasks": hacs.queue.has_pending_tasks,
|
||||||
|
"stage": hacs.stage,
|
||||||
|
}
|
||||||
|
connection.send_message(websocket_api.result_message(msg["id"], content))
|
||||||
236
custom_components/hacs/base.py
Normal file
236
custom_components/hacs/base.py
Normal file
@@ -0,0 +1,236 @@
|
|||||||
|
"""Base HACS class."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from dataclasses import asdict, dataclass, field
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
import math
|
||||||
|
import pathlib
|
||||||
|
from typing import TYPE_CHECKING, Any
|
||||||
|
|
||||||
|
from aiogithubapi import (
|
||||||
|
GitHub,
|
||||||
|
GitHubAPI,
|
||||||
|
GitHubAuthenticationException,
|
||||||
|
GitHubRatelimitException,
|
||||||
|
)
|
||||||
|
from aiogithubapi.objects.repository import AIOGitHubAPIRepository
|
||||||
|
from aiohttp.client import ClientSession
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.loader import Integration
|
||||||
|
from queueman.manager import QueueManager
|
||||||
|
|
||||||
|
from .const import REPOSITORY_HACS_DEFAULT
|
||||||
|
from .enums import (
|
||||||
|
ConfigurationType,
|
||||||
|
HacsCategory,
|
||||||
|
HacsDisabledReason,
|
||||||
|
HacsStage,
|
||||||
|
LovelaceMode,
|
||||||
|
)
|
||||||
|
from .exceptions import HacsException
|
||||||
|
from .utils.decode import decode_content
|
||||||
|
from .utils.logger import getLogger
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from .hacsbase.data import HacsData
|
||||||
|
from .helpers.classes.repository import HacsRepository
|
||||||
|
from .operational.factory import HacsTaskFactory
|
||||||
|
from .tasks.manager import HacsTaskManager
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class HacsConfiguration:
|
||||||
|
"""HacsConfiguration class."""
|
||||||
|
|
||||||
|
appdaemon_path: str = "appdaemon/apps/"
|
||||||
|
appdaemon: bool = False
|
||||||
|
config: dict[str, Any] = field(default_factory=dict)
|
||||||
|
config_entry: dict[str, str] = field(default_factory=dict)
|
||||||
|
config_type: ConfigurationType | None = None
|
||||||
|
country: str = "ALL"
|
||||||
|
debug: bool = False
|
||||||
|
dev: bool = False
|
||||||
|
experimental: bool = False
|
||||||
|
frontend_compact: bool = False
|
||||||
|
frontend_mode: str = "Grid"
|
||||||
|
frontend_repo_url: str = ""
|
||||||
|
frontend_repo: str = ""
|
||||||
|
netdaemon_path: str = "netdaemon/apps/"
|
||||||
|
netdaemon: bool = False
|
||||||
|
onboarding_done: bool = False
|
||||||
|
plugin_path: str = "www/community/"
|
||||||
|
python_script_path: str = "python_scripts/"
|
||||||
|
python_script: bool = False
|
||||||
|
release_limit: int = 5
|
||||||
|
sidepanel_icon: str = "hacs:hacs"
|
||||||
|
sidepanel_title: str = "HACS"
|
||||||
|
theme_path: str = "themes/"
|
||||||
|
theme: bool = False
|
||||||
|
token: str = None
|
||||||
|
|
||||||
|
def to_json(self) -> str:
|
||||||
|
"""Return a json string."""
|
||||||
|
return asdict(self)
|
||||||
|
|
||||||
|
def update_from_dict(self, data: dict) -> None:
|
||||||
|
"""Set attributes from dicts."""
|
||||||
|
if not isinstance(data, dict):
|
||||||
|
raise HacsException("Configuration is not valid.")
|
||||||
|
|
||||||
|
for key in data:
|
||||||
|
self.__setattr__(key, data[key])
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class HacsFrontend:
|
||||||
|
"""HacsFrontend."""
|
||||||
|
|
||||||
|
version_running: str | None = None
|
||||||
|
version_available: str | None = None
|
||||||
|
version_expected: str | None = None
|
||||||
|
update_pending: bool = False
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class HacsCore:
|
||||||
|
"""HACS Core info."""
|
||||||
|
|
||||||
|
config_path: pathlib.Path | None = None
|
||||||
|
ha_version: str | None = None
|
||||||
|
lovelace_mode = LovelaceMode("yaml")
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class HacsCommon:
|
||||||
|
"""Common for HACS."""
|
||||||
|
|
||||||
|
categories: set[str] = field(default_factory=set)
|
||||||
|
default: list[str] = field(default_factory=list)
|
||||||
|
installed: list[str] = field(default_factory=list)
|
||||||
|
renamed_repositories: dict[str, str] = field(default_factory=dict)
|
||||||
|
archived_repositories: list[str] = field(default_factory=list)
|
||||||
|
skip: list[str] = field(default_factory=list)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class HacsStatus:
|
||||||
|
"""HacsStatus."""
|
||||||
|
|
||||||
|
startup: bool = True
|
||||||
|
new: bool = False
|
||||||
|
background_task: bool = False
|
||||||
|
reloading_data: bool = False
|
||||||
|
upgrading_all: bool = False
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class HacsSystem:
|
||||||
|
"""HACS System info."""
|
||||||
|
|
||||||
|
disabled_reason: HacsDisabledReason | None = None
|
||||||
|
running: bool = False
|
||||||
|
stage = HacsStage.SETUP
|
||||||
|
action: bool = False
|
||||||
|
|
||||||
|
@property
|
||||||
|
def disabled(self) -> bool:
|
||||||
|
"""Return if HACS is disabled."""
|
||||||
|
return self.disabled_reason is not None
|
||||||
|
|
||||||
|
|
||||||
|
class HacsBase:
|
||||||
|
"""Base HACS class."""
|
||||||
|
|
||||||
|
_repositories = []
|
||||||
|
_repositories_by_full_name = {}
|
||||||
|
_repositories_by_id = {}
|
||||||
|
|
||||||
|
common = HacsCommon()
|
||||||
|
configuration = HacsConfiguration()
|
||||||
|
core = HacsCore()
|
||||||
|
data: HacsData | None = None
|
||||||
|
factory: HacsTaskFactory | None = None
|
||||||
|
frontend = HacsFrontend()
|
||||||
|
github: GitHub | None = None
|
||||||
|
githubapi: GitHubAPI | None = None
|
||||||
|
hass: HomeAssistant | None = None
|
||||||
|
integration: Integration | None = None
|
||||||
|
log: logging.Logger = getLogger()
|
||||||
|
queue: QueueManager | None = None
|
||||||
|
recuring_tasks = []
|
||||||
|
repositories: list[HacsRepository] = []
|
||||||
|
repository: AIOGitHubAPIRepository | None = None
|
||||||
|
session: ClientSession | None = None
|
||||||
|
stage: HacsStage | None = None
|
||||||
|
status = HacsStatus()
|
||||||
|
system = HacsSystem()
|
||||||
|
tasks: HacsTaskManager | None = None
|
||||||
|
version: str | None = None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def integration_dir(self) -> pathlib.Path:
|
||||||
|
"""Return the HACS integration dir."""
|
||||||
|
return self.integration.file_path
|
||||||
|
|
||||||
|
async def async_set_stage(self, stage: HacsStage | None) -> None:
|
||||||
|
"""Set HACS stage."""
|
||||||
|
if stage and self.stage == stage:
|
||||||
|
return
|
||||||
|
|
||||||
|
self.stage = stage
|
||||||
|
if stage is not None:
|
||||||
|
self.log.info("Stage changed: %s", self.stage)
|
||||||
|
self.hass.bus.async_fire("hacs/stage", {"stage": self.stage})
|
||||||
|
await self.tasks.async_execute_runtume_tasks()
|
||||||
|
|
||||||
|
def disable_hacs(self, reason: HacsDisabledReason) -> None:
|
||||||
|
"""Disable HACS."""
|
||||||
|
self.system.disabled_reason = reason
|
||||||
|
if reason != HacsDisabledReason.REMOVED:
|
||||||
|
self.log.error("HACS is disabled - %s", reason)
|
||||||
|
|
||||||
|
def enable_hacs(self) -> None:
|
||||||
|
"""Enable HACS."""
|
||||||
|
self.system.disabled_reason = None
|
||||||
|
self.log.info("HACS is enabled")
|
||||||
|
|
||||||
|
def enable_hacs_category(self, category: HacsCategory):
|
||||||
|
"""Enable HACS category."""
|
||||||
|
if category not in self.common.categories:
|
||||||
|
self.log.info("Enable category: %s", category)
|
||||||
|
self.common.categories.add(category)
|
||||||
|
|
||||||
|
def disable_hacs_category(self, category: HacsCategory):
|
||||||
|
"""Disable HACS category."""
|
||||||
|
if category in self.common.categories:
|
||||||
|
self.log.info("Disabling category: %s", category)
|
||||||
|
self.common.categories.pop(category)
|
||||||
|
|
||||||
|
async def async_can_update(self) -> int:
|
||||||
|
"""Helper to calculate the number of repositories we can fetch data for."""
|
||||||
|
try:
|
||||||
|
response = await self.githubapi.rate_limit()
|
||||||
|
if ((limit := response.data.resources.core.remaining or 0) - 1000) >= 15:
|
||||||
|
return math.floor((limit - 1000) / 15)
|
||||||
|
self.log.error(
|
||||||
|
"GitHub API ratelimited - %s remaining", response.data.resources.core.remaining
|
||||||
|
)
|
||||||
|
self.disable_hacs(HacsDisabledReason.RATE_LIMIT)
|
||||||
|
except GitHubAuthenticationException as exception:
|
||||||
|
self.log.error("GitHub authentication failed - %s", exception)
|
||||||
|
self.disable_hacs(HacsDisabledReason.INVALID_TOKEN)
|
||||||
|
except GitHubRatelimitException as exception:
|
||||||
|
self.log.error("GitHub API ratelimited - %s", exception)
|
||||||
|
self.disable_hacs(HacsDisabledReason.RATE_LIMIT)
|
||||||
|
except BaseException as exception: # pylint: disable=broad-except
|
||||||
|
self.log.exception(exception)
|
||||||
|
|
||||||
|
return 0
|
||||||
|
|
||||||
|
async def async_github_get_hacs_default_file(self, filename: str) -> dict[str, Any]:
|
||||||
|
"""Get the content of a default file."""
|
||||||
|
response = await self.githubapi.repos.contents.get(
|
||||||
|
repository=REPOSITORY_HACS_DEFAULT, path=filename
|
||||||
|
)
|
||||||
|
return json.loads(decode_content(response.data.content))
|
||||||
157
custom_components/hacs/config_flow.py
Normal file
157
custom_components/hacs/config_flow.py
Normal file
@@ -0,0 +1,157 @@
|
|||||||
|
"""Adds config flow for HACS."""
|
||||||
|
from aiogithubapi import GitHubDeviceAPI, GitHubException
|
||||||
|
from aiogithubapi.common.const import OAUTH_USER_LOGIN
|
||||||
|
from awesomeversion import AwesomeVersion
|
||||||
|
from homeassistant import config_entries
|
||||||
|
from homeassistant.const import __version__ as HAVERSION
|
||||||
|
from homeassistant.core import callback
|
||||||
|
from homeassistant.helpers import aiohttp_client
|
||||||
|
from homeassistant.helpers.event import async_call_later
|
||||||
|
from homeassistant.loader import async_get_integration
|
||||||
|
import voluptuous as vol
|
||||||
|
|
||||||
|
from custom_components.hacs.const import CLIENT_ID, DOMAIN, MINIMUM_HA_VERSION
|
||||||
|
from custom_components.hacs.enums import ConfigurationType
|
||||||
|
from custom_components.hacs.helpers.functions.configuration_schema import (
|
||||||
|
RELEASE_LIMIT,
|
||||||
|
hacs_config_option_schema,
|
||||||
|
)
|
||||||
|
from custom_components.hacs.mixin import HacsMixin
|
||||||
|
|
||||||
|
|
||||||
|
class HacsFlowHandler(HacsMixin, 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
|
||||||
|
self.activation = None
|
||||||
|
self._progress_task = None
|
||||||
|
self._login_device = None
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
return await self.async_step_device(user_input)
|
||||||
|
|
||||||
|
## Initial form
|
||||||
|
return await self._show_config_form(user_input)
|
||||||
|
|
||||||
|
async def async_step_device(self, _user_input):
|
||||||
|
"""Handle device steps"""
|
||||||
|
|
||||||
|
async def _wait_for_activation(_=None):
|
||||||
|
if self._login_device is None or self._login_device.expires_in is None:
|
||||||
|
async_call_later(self.hass, 1, _wait_for_activation)
|
||||||
|
return
|
||||||
|
|
||||||
|
response = await self.device.activation(device_code=self._login_device.device_code)
|
||||||
|
self.activation = response.data
|
||||||
|
self.hass.async_create_task(
|
||||||
|
self.hass.config_entries.flow.async_configure(flow_id=self.flow_id)
|
||||||
|
)
|
||||||
|
|
||||||
|
if not self.activation:
|
||||||
|
integration = await async_get_integration(self.hass, DOMAIN)
|
||||||
|
if not self.device:
|
||||||
|
self.device = GitHubDeviceAPI(
|
||||||
|
client_id=CLIENT_ID,
|
||||||
|
session=aiohttp_client.async_get_clientsession(self.hass),
|
||||||
|
**{"client_name": f"HACS/{integration.version}"},
|
||||||
|
)
|
||||||
|
async_call_later(self.hass, 1, _wait_for_activation)
|
||||||
|
try:
|
||||||
|
response = await self.device.register()
|
||||||
|
self._login_device = response.data
|
||||||
|
return self.async_show_progress(
|
||||||
|
step_id="device",
|
||||||
|
progress_action="wait_for_device",
|
||||||
|
description_placeholders={
|
||||||
|
"url": OAUTH_USER_LOGIN,
|
||||||
|
"code": self._login_device.user_code,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
except GitHubException as exception:
|
||||||
|
self.hacs.log.error(exception)
|
||||||
|
return self.async_abort(reason="github")
|
||||||
|
|
||||||
|
return self.async_show_progress_done(next_step_id="device_done")
|
||||||
|
|
||||||
|
async def _show_config_form(self, user_input):
|
||||||
|
"""Show the configuration form to edit location data."""
|
||||||
|
if not user_input:
|
||||||
|
user_input = {}
|
||||||
|
if AwesomeVersion(HAVERSION) < MINIMUM_HA_VERSION:
|
||||||
|
return self.async_abort(
|
||||||
|
reason="min_ha_version",
|
||||||
|
description_placeholders={"version": MINIMUM_HA_VERSION},
|
||||||
|
)
|
||||||
|
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,
|
||||||
|
)
|
||||||
|
|
||||||
|
async def async_step_device_done(self, _user_input):
|
||||||
|
"""Handle device steps"""
|
||||||
|
return self.async_create_entry(title="", data={"token": self.activation.access_token})
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
@callback
|
||||||
|
def async_get_options_flow(config_entry):
|
||||||
|
return HacsOptionsFlowHandler(config_entry)
|
||||||
|
|
||||||
|
|
||||||
|
class HacsOptionsFlowHandler(HacsMixin, 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."""
|
||||||
|
if user_input is not None:
|
||||||
|
limit = int(user_input.get(RELEASE_LIMIT, 5))
|
||||||
|
if limit <= 0 or limit > 100:
|
||||||
|
return self.async_abort(reason="release_limit_value")
|
||||||
|
return self.async_create_entry(title="", data=user_input)
|
||||||
|
|
||||||
|
if self.hacs.configuration is None:
|
||||||
|
return self.async_abort(reason="not_setup")
|
||||||
|
|
||||||
|
if self.hacs.configuration.config_type == ConfigurationType.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))
|
||||||
302
custom_components/hacs/const.py
Normal file
302
custom_components/hacs/const.py
Normal file
@@ -0,0 +1,302 @@
|
|||||||
|
"""Constants for HACS"""
|
||||||
|
from aiogithubapi.common.const import ACCEPT_HEADERS
|
||||||
|
|
||||||
|
NAME_LONG = "HACS (Home Assistant Community Store)"
|
||||||
|
NAME_SHORT = "HACS"
|
||||||
|
DOMAIN = "hacs"
|
||||||
|
CLIENT_ID = "395a8e669c5de9f7c6e8"
|
||||||
|
MINIMUM_HA_VERSION = "2021.2.0"
|
||||||
|
PROJECT_URL = "https://github.com/hacs/integration/"
|
||||||
|
|
||||||
|
|
||||||
|
ISSUE_URL = f"{PROJECT_URL}issues"
|
||||||
|
DOMAIN_DATA = f"{NAME_SHORT.lower()}_data"
|
||||||
|
|
||||||
|
PACKAGE_NAME = "custom_components.hacs"
|
||||||
|
|
||||||
|
REPOSITORY_HACS_DEFAULT = "hacs/default"
|
||||||
|
REPOSITORY_HACS_INTEGRATION = "hacs/integration"
|
||||||
|
|
||||||
|
PLATFORMS = ["sensor"]
|
||||||
|
|
||||||
|
HACS_ACTION_GITHUB_API_HEADERS = {
|
||||||
|
"User-Agent": "HACS/action",
|
||||||
|
"Accept": ACCEPT_HEADERS["preview"],
|
||||||
|
}
|
||||||
|
|
||||||
|
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."
|
||||||
|
|
||||||
|
STARTUP = """
|
||||||
|
-------------------------------------------------------------------
|
||||||
|
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",
|
||||||
|
]
|
||||||
57
custom_components/hacs/enums.py
Normal file
57
custom_components/hacs/enums.py
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
"""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"
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return str(self.value)
|
||||||
|
|
||||||
|
|
||||||
|
class ConfigurationType(str, Enum):
|
||||||
|
YAML = "yaml"
|
||||||
|
CONFIG_ENTRY = "config_entry"
|
||||||
|
|
||||||
|
|
||||||
|
class LovelaceMode(str, Enum):
|
||||||
|
"""Lovelace Modes."""
|
||||||
|
|
||||||
|
STORAGE = "storage"
|
||||||
|
AUTO = "auto"
|
||||||
|
AUTO_GEN = "auto-gen"
|
||||||
|
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"
|
||||||
|
|
||||||
|
|
||||||
|
class HacsDisabledReason(str, Enum):
|
||||||
|
RATE_LIMIT = "rate_limit"
|
||||||
|
REMOVED = "removed"
|
||||||
|
INVALID_TOKEN = "invalid_token"
|
||||||
|
CONSTRAINS = "constrains"
|
||||||
|
LOAD_HACS = "load_hacs"
|
||||||
|
RESTORE = "restore"
|
||||||
21
custom_components/hacs/exceptions.py
Normal file
21
custom_components/hacs/exceptions.py
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
"""Custom Exceptions for HACS."""
|
||||||
|
|
||||||
|
|
||||||
|
class HacsException(Exception):
|
||||||
|
"""Super basic."""
|
||||||
|
|
||||||
|
|
||||||
|
class HacsRepositoryArchivedException(HacsException):
|
||||||
|
"""For repositories that are archived."""
|
||||||
|
|
||||||
|
|
||||||
|
class HacsNotModifiedException(HacsException):
|
||||||
|
"""For responses that are not modified."""
|
||||||
|
|
||||||
|
|
||||||
|
class HacsExpectedException(HacsException):
|
||||||
|
"""For stuff that are expected."""
|
||||||
|
|
||||||
|
|
||||||
|
class HacsRepositoryExistException(HacsException):
|
||||||
|
"""For repositories that are already exist."""
|
||||||
0
custom_components/hacs/hacsbase/__init__.py
Normal file
0
custom_components/hacs/hacsbase/__init__.py
Normal file
208
custom_components/hacs/hacsbase/data.py
Normal file
208
custom_components/hacs/hacsbase/data.py
Normal file
@@ -0,0 +1,208 @@
|
|||||||
|
"""Data handler for HACS."""
|
||||||
|
import asyncio
|
||||||
|
import os
|
||||||
|
|
||||||
|
from homeassistant.core import callback
|
||||||
|
|
||||||
|
from custom_components.hacs.helpers.classes.manifest import HacsManifest
|
||||||
|
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,
|
||||||
|
async_save_to_store_default_encoder,
|
||||||
|
get_store_for_key,
|
||||||
|
)
|
||||||
|
from custom_components.hacs.share import get_hacs
|
||||||
|
from custom_components.hacs.utils.logger import getLogger
|
||||||
|
|
||||||
|
|
||||||
|
def update_repository_from_storage(repository, storage_data):
|
||||||
|
"""Merge in data from storage into the repo data."""
|
||||||
|
repository.data.memorize_storage(storage_data)
|
||||||
|
repository.data.update_data(storage_data)
|
||||||
|
if repository.data.installed:
|
||||||
|
return
|
||||||
|
|
||||||
|
repository.logger.debug("%s Should be installed but is not... Fixing that!", repository)
|
||||||
|
repository.data.installed = True
|
||||||
|
|
||||||
|
|
||||||
|
class HacsData:
|
||||||
|
"""HacsData class."""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
"""Initialize."""
|
||||||
|
self.logger = getLogger()
|
||||||
|
self.hacs = get_hacs()
|
||||||
|
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,
|
||||||
|
"archived_repositories": self.hacs.common.archived_repositories,
|
||||||
|
"renamed_repositories": self.hacs.common.renamed_repositories,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
await self._async_store_content_and_repos()
|
||||||
|
for event in ("hacs/repository", "hacs/config"):
|
||||||
|
self.hacs.hass.bus.async_fire(event, {})
|
||||||
|
|
||||||
|
async def _async_store_content_and_repos(self): # bb: ignore
|
||||||
|
"""Store the main repos file and each repo that is out of date."""
|
||||||
|
# Repositories
|
||||||
|
self.content = {}
|
||||||
|
# Not run concurrently since this is bound by disk I/O
|
||||||
|
for repository in self.hacs.repositories:
|
||||||
|
await self.async_store_repository_data(repository)
|
||||||
|
|
||||||
|
await async_save_to_store(self.hacs.hass, "repositories", self.content)
|
||||||
|
|
||||||
|
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,
|
||||||
|
"etag_repository": repository.data.etag_repository,
|
||||||
|
"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,
|
||||||
|
}
|
||||||
|
self.content[str(repository.data.id)] = data
|
||||||
|
|
||||||
|
if (
|
||||||
|
repository.data.installed
|
||||||
|
and (repository.data.installed_commit or repository.data.installed_version)
|
||||||
|
and (export := repository.data.export_data())
|
||||||
|
):
|
||||||
|
# export_data will return `None` if the memorized
|
||||||
|
# data is already up to date which allows us to avoid
|
||||||
|
# writing data that is already up to date or generating
|
||||||
|
# executor jobs to check the data on disk to see
|
||||||
|
# if a write is needed.
|
||||||
|
await async_save_to_store_default_encoder(
|
||||||
|
self.hacs.hass,
|
||||||
|
f"hacs/{repository.data.id}.hacs",
|
||||||
|
export,
|
||||||
|
)
|
||||||
|
repository.data.memorize_storage(export)
|
||||||
|
|
||||||
|
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") or {}
|
||||||
|
|
||||||
|
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)
|
||||||
|
self.hacs.common.archived_repositories = hacs.get("archived_repositories", [])
|
||||||
|
self.hacs.common.renamed_repositories = hacs.get("renamed_repositories", {})
|
||||||
|
|
||||||
|
# Repositories
|
||||||
|
hass = self.hacs.hass
|
||||||
|
stores = {}
|
||||||
|
|
||||||
|
try:
|
||||||
|
await self.register_unknown_repositories(repositories)
|
||||||
|
|
||||||
|
for entry, repo_data in repositories.items():
|
||||||
|
if self.async_restore_repository(entry, repo_data):
|
||||||
|
stores[entry] = get_store_for_key(hass, f"hacs/{entry}.hacs")
|
||||||
|
|
||||||
|
def _load_from_storage():
|
||||||
|
for entry, store in stores.items():
|
||||||
|
if os.path.exists(store.path) and (data := store.load()):
|
||||||
|
update_repository_from_storage(self.hacs.get_by_id(entry), data)
|
||||||
|
|
||||||
|
await hass.async_add_executor_job(_load_from_storage)
|
||||||
|
self.logger.info("Restore done")
|
||||||
|
except (Exception, BaseException) as exception: # pylint: disable=broad-except
|
||||||
|
self.logger.critical(f"[{exception}] Restore Failed!", exc_info=exception)
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
async def register_unknown_repositories(self, repositories):
|
||||||
|
"""Registry any unknown repositories."""
|
||||||
|
register_tasks = [
|
||||||
|
register_repository(repo_data["full_name"], repo_data["category"], False)
|
||||||
|
for entry, repo_data in repositories.items()
|
||||||
|
if not self.hacs.is_known(entry)
|
||||||
|
]
|
||||||
|
if register_tasks:
|
||||||
|
await asyncio.gather(*register_tasks)
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def async_restore_repository(self, entry, repository_data):
|
||||||
|
full_name = repository_data["full_name"]
|
||||||
|
if not (repository := self.hacs.get_by_name(full_name)):
|
||||||
|
self.logger.error(f"Did not find {full_name} ({entry})")
|
||||||
|
return False
|
||||||
|
# Restore repository attributes
|
||||||
|
self.hacs.async_set_repository_id(repository, 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.etag_repository = repository_data.get("etag_repository")
|
||||||
|
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 = self.hacs.version
|
||||||
|
repository.data.installed = True
|
||||||
|
|
||||||
|
return True
|
||||||
338
custom_components/hacs/hacsbase/hacs.py
Normal file
338
custom_components/hacs/hacsbase/hacs.py
Normal file
@@ -0,0 +1,338 @@
|
|||||||
|
"""Initialize the HACS base."""
|
||||||
|
from datetime import timedelta
|
||||||
|
|
||||||
|
from aiogithubapi import GitHubException
|
||||||
|
from aiogithubapi.exceptions import GitHubNotModifiedException
|
||||||
|
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.store import (
|
||||||
|
async_load_from_store,
|
||||||
|
async_save_to_store,
|
||||||
|
)
|
||||||
|
from custom_components.hacs.share import (
|
||||||
|
get_removed,
|
||||||
|
is_removed,
|
||||||
|
list_removed_repositories,
|
||||||
|
)
|
||||||
|
|
||||||
|
from ..base import HacsBase
|
||||||
|
from ..enums import HacsCategory, HacsStage
|
||||||
|
from ..share import get_factory, get_queue
|
||||||
|
|
||||||
|
|
||||||
|
class Hacs(HacsBase, HacsHelpers):
|
||||||
|
"""The base class of HACS, nested throughout the project."""
|
||||||
|
|
||||||
|
factory = get_factory()
|
||||||
|
queue = get_queue()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def repositories(self):
|
||||||
|
"""Return the full repositories list."""
|
||||||
|
return self._repositories
|
||||||
|
|
||||||
|
def async_set_repositories(self, repositories):
|
||||||
|
"""Set the list of repositories."""
|
||||||
|
self._repositories = []
|
||||||
|
self._repositories_by_id = {}
|
||||||
|
self._repositories_by_full_name = {}
|
||||||
|
|
||||||
|
for repository in repositories:
|
||||||
|
self.async_add_repository(repository)
|
||||||
|
|
||||||
|
def async_set_repository_id(self, repository, repo_id):
|
||||||
|
"""Update a repository id."""
|
||||||
|
existing_repo_id = str(repository.data.id)
|
||||||
|
if existing_repo_id == repo_id:
|
||||||
|
return
|
||||||
|
if existing_repo_id != "0":
|
||||||
|
raise ValueError(
|
||||||
|
f"The repo id for {repository.data.full_name_lower} is already set to {existing_repo_id}"
|
||||||
|
)
|
||||||
|
repository.data.id = repo_id
|
||||||
|
self._repositories_by_id[repo_id] = repository
|
||||||
|
|
||||||
|
def async_add_repository(self, repository):
|
||||||
|
"""Add a repository to the list."""
|
||||||
|
if repository.data.full_name_lower in self._repositories_by_full_name:
|
||||||
|
raise ValueError(f"The repo {repository.data.full_name_lower} is already added")
|
||||||
|
self._repositories.append(repository)
|
||||||
|
repo_id = str(repository.data.id)
|
||||||
|
if repo_id != "0":
|
||||||
|
self._repositories_by_id[repo_id] = repository
|
||||||
|
self._repositories_by_full_name[repository.data.full_name_lower] = repository
|
||||||
|
|
||||||
|
def async_remove_repository(self, repository):
|
||||||
|
"""Remove a repository from the list."""
|
||||||
|
if repository.data.full_name_lower not in self._repositories_by_full_name:
|
||||||
|
return
|
||||||
|
self._repositories.remove(repository)
|
||||||
|
repo_id = str(repository.data.id)
|
||||||
|
if repo_id in self._repositories_by_id:
|
||||||
|
del self._repositories_by_id[repo_id]
|
||||||
|
del self._repositories_by_full_name[repository.data.full_name_lower]
|
||||||
|
|
||||||
|
def get_by_id(self, repository_id):
|
||||||
|
"""Get repository by ID."""
|
||||||
|
return self._repositories_by_id.get(str(repository_id))
|
||||||
|
|
||||||
|
def get_by_name(self, repository_full_name):
|
||||||
|
"""Get repository by full_name."""
|
||||||
|
if repository_full_name is None:
|
||||||
|
return None
|
||||||
|
return self._repositories_by_full_name.get(repository_full_name.lower())
|
||||||
|
|
||||||
|
def is_known(self, repository_id):
|
||||||
|
"""Return a bool if the repository is known."""
|
||||||
|
return str(repository_id) in self._repositories_by_id
|
||||||
|
|
||||||
|
@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
|
||||||
|
self.hass.bus.async_fire("hacs/status", {})
|
||||||
|
|
||||||
|
await self.handle_critical_repositories_startup()
|
||||||
|
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(hours=2)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
self.recuring_tasks.append(
|
||||||
|
self.hass.helpers.event.async_track_time_interval(
|
||||||
|
self.recurring_tasks_all, timedelta(hours=25)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
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.async_github_get_hacs_default_file("critical")
|
||||||
|
except GitHubNotModifiedException:
|
||||||
|
return
|
||||||
|
except GitHubException:
|
||||||
|
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 self.async_can_update()
|
||||||
|
self.log.debug(
|
||||||
|
"Can update %s repositories, items in queue %s",
|
||||||
|
can_update,
|
||||||
|
self.queue.pending_tasks,
|
||||||
|
)
|
||||||
|
if can_update != 0:
|
||||||
|
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 self.status.startup and repository.data.full_name == "hacs/integration":
|
||||||
|
continue
|
||||||
|
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")
|
||||||
|
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 self.common.renamed_repositories.get(repo):
|
||||||
|
repo = self.common.renamed_repositories[repo]
|
||||||
|
if is_removed(repo):
|
||||||
|
continue
|
||||||
|
if repo in self.common.archived_repositories:
|
||||||
|
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))
|
||||||
17
custom_components/hacs/helpers/__init__.py
Normal file
17
custom_components/hacs/helpers/__init__.py
Normal file
@@ -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"""
|
||||||
0
custom_components/hacs/helpers/classes/__init__.py
Normal file
0
custom_components/hacs/helpers/classes/__init__.py
Normal file
47
custom_components/hacs/helpers/classes/manifest.py
Normal file
47
custom_components/hacs/helpers/classes/manifest.py
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
"""
|
||||||
|
Manifest handling of a repository.
|
||||||
|
|
||||||
|
https://hacs.xyz/docs/publish/start#hacsjson
|
||||||
|
"""
|
||||||
|
from typing import List
|
||||||
|
|
||||||
|
import attr
|
||||||
|
|
||||||
|
from custom_components.hacs.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
|
||||||
|
|
||||||
|
if country := manifest.get("country"):
|
||||||
|
if isinstance(country, str):
|
||||||
|
manifest["country"] = [country]
|
||||||
|
|
||||||
|
for key in manifest:
|
||||||
|
setattr(manifest_data, key, manifest[key])
|
||||||
|
return manifest_data
|
||||||
21
custom_components/hacs/helpers/classes/removed.py
Normal file
21
custom_components/hacs/helpers/classes/removed.py
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
"""Object for removed repositories."""
|
||||||
|
import attr
|
||||||
|
|
||||||
|
|
||||||
|
@attr.s(auto_attribs=True)
|
||||||
|
class RemovedRepository:
|
||||||
|
repository: str = None
|
||||||
|
reason: str = None
|
||||||
|
link: str = None
|
||||||
|
removal_type: str = None # archived, not_compliant, critical, dev, broken
|
||||||
|
acknowledged: bool = False
|
||||||
|
|
||||||
|
def update_data(self, data: dict):
|
||||||
|
"""Update data of the repository."""
|
||||||
|
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)
|
||||||
459
custom_components/hacs/helpers/classes/repository.py
Normal file
459
custom_components/hacs/helpers/classes/repository.py
Normal file
@@ -0,0 +1,459 @@
|
|||||||
|
"""Repository."""
|
||||||
|
# pylint: disable=broad-except, no-member
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
import tempfile
|
||||||
|
import zipfile
|
||||||
|
|
||||||
|
from aiogithubapi import AIOGitHubAPIException
|
||||||
|
from queueman import QueueManager
|
||||||
|
|
||||||
|
from custom_components.hacs.exceptions import HacsException, HacsNotModifiedException
|
||||||
|
from custom_components.hacs.helpers import RepositoryHelpers
|
||||||
|
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.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.is_safe_to_remove import is_safe_to_remove
|
||||||
|
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
|
||||||
|
from custom_components.hacs.utils.logger import getLogger
|
||||||
|
|
||||||
|
|
||||||
|
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 ignored_by_country_configuration(self) -> bool:
|
||||||
|
"""Return True if hidden by country."""
|
||||||
|
if self.data.installed:
|
||||||
|
return False
|
||||||
|
configuration = self.hacs.configuration.country.lower()
|
||||||
|
manifest = [entry.lower() for entry in self.repository_manifest.country or []]
|
||||||
|
if configuration == "all":
|
||||||
|
return False
|
||||||
|
if not manifest:
|
||||||
|
return False
|
||||||
|
return configuration not in manifest
|
||||||
|
|
||||||
|
@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:
|
||||||
|
try:
|
||||||
|
self.repository_object, etag = await get_repository(
|
||||||
|
self.hacs.session,
|
||||||
|
self.hacs.configuration.token,
|
||||||
|
self.data.full_name,
|
||||||
|
None if self.data.installed else self.data.etag_repository,
|
||||||
|
)
|
||||||
|
self.data.update_data(self.repository_object.attributes)
|
||||||
|
self.data.etag_repository = etag
|
||||||
|
except HacsNotModifiedException:
|
||||||
|
self.logger.debug(
|
||||||
|
"Did not update %s, content was not modified", self.data.full_name
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
# 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, force=False):
|
||||||
|
"""Common information update steps of the repository."""
|
||||||
|
self.logger.debug("%s Getting repository information", self)
|
||||||
|
|
||||||
|
# Attach repository
|
||||||
|
current_etag = self.data.etag_repository
|
||||||
|
await common_update_data(self, ignore_issues, force)
|
||||||
|
if not self.data.installed and (current_etag == self.data.etag_repository) and not force:
|
||||||
|
self.logger.debug("Did not update %s, content was not modified", self.data.full_name)
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Update last updated
|
||||||
|
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)
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
temp_dir = await self.hacs.hass.async_add_executor_job(tempfile.mkdtemp)
|
||||||
|
temp_file = f"{temp_dir}/{self.data.filename}"
|
||||||
|
|
||||||
|
result = await async_save_file(temp_file, filecontent)
|
||||||
|
with zipfile.ZipFile(temp_file, "r") as zip_file:
|
||||||
|
zip_file.extractall(self.content.path.local)
|
||||||
|
|
||||||
|
def cleanup_temp_dir():
|
||||||
|
"""Cleanup temp_dir."""
|
||||||
|
if os.path.exists(temp_dir):
|
||||||
|
self.logger.debug("Cleaning up %s", temp_dir)
|
||||||
|
shutil.rmtree(temp_dir)
|
||||||
|
|
||||||
|
if result:
|
||||||
|
self.logger.info("%s Download of %s completed", self, content.name)
|
||||||
|
await self.hacs.hass.async_add_executor_job(cleanup_temp_dir)
|
||||||
|
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.async_remove_repository(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."""
|
||||||
|
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
|
||||||
146
custom_components/hacs/helpers/classes/repositorydata.py
Normal file
146
custom_components/hacs/helpers/classes/repositorydata.py
Normal file
@@ -0,0 +1,146 @@
|
|||||||
|
"""Repository data."""
|
||||||
|
from datetime import datetime
|
||||||
|
import json
|
||||||
|
from typing import List, Optional
|
||||||
|
|
||||||
|
import attr
|
||||||
|
from homeassistant.helpers.json import JSONEncoder
|
||||||
|
|
||||||
|
|
||||||
|
@attr.s(auto_attribs=True)
|
||||||
|
class RepositoryData:
|
||||||
|
"""RepositoryData class."""
|
||||||
|
|
||||||
|
archived: bool = False
|
||||||
|
authors: List[str] = []
|
||||||
|
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
|
||||||
|
etag_repository: str = None
|
||||||
|
file_name: str = ""
|
||||||
|
filename: str = ""
|
||||||
|
first_install: bool = False
|
||||||
|
fork: bool = False
|
||||||
|
full_name: str = ""
|
||||||
|
hacs: str = None # Minimum HACS version
|
||||||
|
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
|
||||||
|
_storage_data: Optional[dict] = None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def stars(self):
|
||||||
|
"""Return the stargazers count."""
|
||||||
|
return self.stargazers_count or 0
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
|
"""Return the name."""
|
||||||
|
if self.category in ["integration", "netdaemon"]:
|
||||||
|
return self.domain
|
||||||
|
return self.full_name.split("/")[-1]
|
||||||
|
|
||||||
|
def to_json(self):
|
||||||
|
"""Export to json."""
|
||||||
|
return attr.asdict(self, filter=lambda attr, _: attr.name != "_storage_data")
|
||||||
|
|
||||||
|
def memorize_storage(self, data) -> None:
|
||||||
|
"""Memorize the storage data."""
|
||||||
|
self._storage_data = data
|
||||||
|
|
||||||
|
def export_data(self) -> Optional[dict]:
|
||||||
|
"""Export to json if the data has changed.
|
||||||
|
|
||||||
|
Returns the data to export if the data needs
|
||||||
|
to be written.
|
||||||
|
|
||||||
|
Returns None if the data has not changed.
|
||||||
|
"""
|
||||||
|
export = json.loads(json.dumps(self.to_json(), cls=JSONEncoder))
|
||||||
|
return None if self._storage_data == export else export
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def create_from_dict(source: dict):
|
||||||
|
"""Set attributes from dicts."""
|
||||||
|
data = RepositoryData()
|
||||||
|
for key in source:
|
||||||
|
if key not in data.__dict__:
|
||||||
|
continue
|
||||||
|
if key == "pushed_at":
|
||||||
|
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:
|
||||||
|
setattr(data, key, source[key])
|
||||||
|
else:
|
||||||
|
setattr(data, key, source[key])
|
||||||
|
return data
|
||||||
|
|
||||||
|
def update_data(self, data: dict):
|
||||||
|
"""Update data of the repository."""
|
||||||
|
for key in data:
|
||||||
|
if key not in self.__dict__:
|
||||||
|
continue
|
||||||
|
if key == "pushed_at":
|
||||||
|
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:
|
||||||
|
setattr(self, key, data[key])
|
||||||
|
else:
|
||||||
|
setattr(self, key, data[key])
|
||||||
11
custom_components/hacs/helpers/classes/validate.py
Normal file
11
custom_components/hacs/helpers/classes/validate.py
Normal file
@@ -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
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
"""HACS Configuration Schemas."""
|
||||||
|
# pylint: disable=dangerous-default-value
|
||||||
|
import voluptuous as vol
|
||||||
|
|
||||||
|
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"
|
||||||
|
|
||||||
|
# Options:
|
||||||
|
COUNTRY = "country"
|
||||||
|
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 = {}) -> dict:
|
||||||
|
"""Return a shcema configuration dict for HACS."""
|
||||||
|
if not config:
|
||||||
|
config = {
|
||||||
|
TOKEN: "xxxxxxxxxxxxxxxxxxxxxxxxxxx",
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
vol.Required(TOKEN, default=config.get(TOKEN)): str,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def hacs_config_option_schema(options: dict = {}) -> dict:
|
||||||
|
"""Return a shcema for HACS configuration options."""
|
||||||
|
if not options:
|
||||||
|
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(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(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,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def hacs_config_combined() -> dict:
|
||||||
|
"""Combine the configuration options."""
|
||||||
|
base = hacs_base_config_schema()
|
||||||
|
options = hacs_config_option_schema()
|
||||||
|
|
||||||
|
for option in options:
|
||||||
|
base[option] = options[option]
|
||||||
|
|
||||||
|
return base
|
||||||
234
custom_components/hacs/helpers/functions/download.py
Normal file
234
custom_components/hacs/helpers/functions/download.py
Normal file
@@ -0,0 +1,234 @@
|
|||||||
|
"""Helpers to download repository content."""
|
||||||
|
import os
|
||||||
|
import pathlib
|
||||||
|
import tempfile
|
||||||
|
import zipfile
|
||||||
|
|
||||||
|
import async_timeout
|
||||||
|
import backoff
|
||||||
|
from queueman import QueueManager, concurrent
|
||||||
|
|
||||||
|
from custom_components.hacs.exceptions import HacsException
|
||||||
|
from custom_components.hacs.helpers.functions.filters import (
|
||||||
|
filter_content_return_one_of_type,
|
||||||
|
)
|
||||||
|
from custom_components.hacs.helpers.functions.save import async_save_file
|
||||||
|
from custom_components.hacs.share import get_hacs
|
||||||
|
from custom_components.hacs.utils.logger import getLogger
|
||||||
|
|
||||||
|
_LOGGER = getLogger()
|
||||||
|
|
||||||
|
|
||||||
|
class FileInformation:
|
||||||
|
def __init__(self, url, path, name):
|
||||||
|
self.download_url = url
|
||||||
|
self.path = path
|
||||||
|
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(f"Got status code {request.status} when trying to download {url}")
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def should_try_releases(repository):
|
||||||
|
"""Return a boolean indicating whether to download releases or not."""
|
||||||
|
if repository.data.zip_release:
|
||||||
|
if repository.data.filename.endswith(".zip"):
|
||||||
|
if repository.ref != repository.data.default_branch:
|
||||||
|
return True
|
||||||
|
if repository.ref == repository.data.default_branch:
|
||||||
|
return False
|
||||||
|
if repository.data.category not in ["plugin", "theme"]:
|
||||||
|
return False
|
||||||
|
if not repository.data.releases:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def gather_files_to_download(repository):
|
||||||
|
"""Return a list of file objects to be downloaded."""
|
||||||
|
files = []
|
||||||
|
tree = repository.tree
|
||||||
|
ref = f"{repository.ref}".replace("tags/", "")
|
||||||
|
releaseobjects = repository.releases.objects
|
||||||
|
category = repository.data.category
|
||||||
|
remotelocation = repository.content.path.remote
|
||||||
|
|
||||||
|
if should_try_releases(repository):
|
||||||
|
for release in releaseobjects or []:
|
||||||
|
if ref == release.tag_name:
|
||||||
|
for asset in release.assets or []:
|
||||||
|
files.append(asset)
|
||||||
|
if files:
|
||||||
|
return files
|
||||||
|
|
||||||
|
if repository.content.single:
|
||||||
|
for treefile in tree:
|
||||||
|
if treefile.filename == repository.data.file_name:
|
||||||
|
files.append(
|
||||||
|
FileInformation(treefile.download_url, treefile.full_path, treefile.filename)
|
||||||
|
)
|
||||||
|
return files
|
||||||
|
|
||||||
|
if category == "plugin":
|
||||||
|
for treefile in tree:
|
||||||
|
if treefile.path in ["", "dist"]:
|
||||||
|
if remotelocation == "dist" and not treefile.filename.startswith("dist"):
|
||||||
|
continue
|
||||||
|
if not remotelocation:
|
||||||
|
if not treefile.filename.endswith(".js"):
|
||||||
|
continue
|
||||||
|
if treefile.path != "":
|
||||||
|
continue
|
||||||
|
if not treefile.is_directory:
|
||||||
|
files.append(
|
||||||
|
FileInformation(
|
||||||
|
treefile.download_url, treefile.full_path, treefile.filename
|
||||||
|
)
|
||||||
|
)
|
||||||
|
if files:
|
||||||
|
return files
|
||||||
|
|
||||||
|
if repository.data.content_in_root:
|
||||||
|
if not repository.data.filename:
|
||||||
|
if category == "theme":
|
||||||
|
tree = filter_content_return_one_of_type(repository.tree, "", "yaml", "full_path")
|
||||||
|
|
||||||
|
for path in tree:
|
||||||
|
if path.is_directory:
|
||||||
|
continue
|
||||||
|
if path.full_path.startswith(repository.content.path.remote):
|
||||||
|
files.append(FileInformation(path.download_url, path.full_path, path.filename))
|
||||||
|
return files
|
||||||
|
|
||||||
|
|
||||||
|
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(f"ref: {repository.ref} --- tag: {release.tag_name}")
|
||||||
|
if release.tag_name == repository.ref.split("/")[1]:
|
||||||
|
contents = release.assets
|
||||||
|
|
||||||
|
if not contents:
|
||||||
|
return validate
|
||||||
|
|
||||||
|
for content in contents or []:
|
||||||
|
queue.add(async_download_zip_file(repository, content, validate))
|
||||||
|
|
||||||
|
await queue.execute()
|
||||||
|
except (Exception, BaseException) as exception: # pylint: disable=broad-except
|
||||||
|
validate.errors.append(f"Download was not completed [{exception}]")
|
||||||
|
|
||||||
|
return validate
|
||||||
|
|
||||||
|
|
||||||
|
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.")
|
||||||
|
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:
|
||||||
|
raise HacsException("No content to download")
|
||||||
|
|
||||||
|
for content in contents:
|
||||||
|
if repository.data.content_in_root and repository.data.filename:
|
||||||
|
if content.name != repository.data.filename:
|
||||||
|
continue
|
||||||
|
queue.add(dowload_repository_content(repository, content))
|
||||||
|
await queue.execute()
|
||||||
|
return repository.validate
|
||||||
|
|
||||||
|
|
||||||
|
@concurrent(10)
|
||||||
|
async def dowload_repository_content(repository, content):
|
||||||
|
"""Download content."""
|
||||||
|
try:
|
||||||
|
repository.logger.debug(f"Downloading {content.name}")
|
||||||
|
|
||||||
|
filecontent = await async_download_file(content.download_url)
|
||||||
|
|
||||||
|
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.")
|
||||||
|
|
||||||
|
except (Exception, BaseException) as exception: # pylint: disable=broad-except
|
||||||
|
repository.validate.errors.append(f"Download was not completed [{exception}]")
|
||||||
53
custom_components/hacs/helpers/functions/filters.py
Normal file
53
custom_components/hacs/helpers/functions/filters.py
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
"""Filter functions."""
|
||||||
|
|
||||||
|
|
||||||
|
def filter_content_return_one_of_type(content, namestartswith, filterfiltype, attr="name"):
|
||||||
|
"""Only match 1 of the filter."""
|
||||||
|
contents = []
|
||||||
|
filetypefound = False
|
||||||
|
for filename in content:
|
||||||
|
if isinstance(filename, str):
|
||||||
|
if filename.startswith(namestartswith):
|
||||||
|
if filename.endswith(f".{filterfiltype}"):
|
||||||
|
if not filetypefound:
|
||||||
|
contents.append(filename)
|
||||||
|
filetypefound = True
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
contents.append(filename)
|
||||||
|
else:
|
||||||
|
if getattr(filename, attr).startswith(namestartswith):
|
||||||
|
if getattr(filename, attr).endswith(f".{filterfiltype}"):
|
||||||
|
if not filetypefound:
|
||||||
|
contents.append(filename)
|
||||||
|
filetypefound = True
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
contents.append(filename)
|
||||||
|
return contents
|
||||||
|
|
||||||
|
|
||||||
|
def find_first_of_filetype(content, filterfiltype, attr="name"):
|
||||||
|
"""Find the first of the file type."""
|
||||||
|
filename = ""
|
||||||
|
for _filename in content:
|
||||||
|
if isinstance(_filename, str):
|
||||||
|
if _filename.endswith(f".{filterfiltype}"):
|
||||||
|
filename = _filename
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
if getattr(_filename, attr).endswith(f".{filterfiltype}"):
|
||||||
|
filename = getattr(_filename, attr)
|
||||||
|
break
|
||||||
|
return filename
|
||||||
|
|
||||||
|
|
||||||
|
def get_first_directory_in_directory(content, dirname):
|
||||||
|
"""Return the first directory in dirname or None."""
|
||||||
|
directory = None
|
||||||
|
for path in content:
|
||||||
|
if path.full_path.startswith(dirname) and path.full_path != dirname:
|
||||||
|
if path.is_directory:
|
||||||
|
directory = path.filename
|
||||||
|
break
|
||||||
|
return directory
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
"""Helper to get default repositories."""
|
||||||
|
from typing import List
|
||||||
|
|
||||||
|
from aiogithubapi import (
|
||||||
|
GitHubAuthenticationException,
|
||||||
|
GitHubNotModifiedException,
|
||||||
|
GitHubRatelimitException,
|
||||||
|
)
|
||||||
|
|
||||||
|
from custom_components.hacs.const import REPOSITORY_HACS_DEFAULT
|
||||||
|
from custom_components.hacs.enums import HacsCategory, HacsDisabledReason
|
||||||
|
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:
|
||||||
|
repositories = await hacs.async_github_get_hacs_default_file(default)
|
||||||
|
hacs.log.debug("Got %s elements for %s", len(repositories), default)
|
||||||
|
except GitHubNotModifiedException:
|
||||||
|
hacs.log.debug("Content did not change for %s/%s", REPOSITORY_HACS_DEFAULT, default)
|
||||||
|
|
||||||
|
except GitHubRatelimitException as exception:
|
||||||
|
hacs.log.error(exception)
|
||||||
|
hacs.disable_hacs(HacsDisabledReason.RATE_LIMIT)
|
||||||
|
|
||||||
|
except GitHubAuthenticationException as exception:
|
||||||
|
hacs.log.error(exception)
|
||||||
|
hacs.disable_hacs(HacsDisabledReason.INVALID_TOKEN)
|
||||||
|
|
||||||
|
except BaseException as exception: # pylint: disable=broad-except
|
||||||
|
hacs.log.error(exception)
|
||||||
|
|
||||||
|
return repositories
|
||||||
230
custom_components/hacs/helpers/functions/information.py
Normal file
230
custom_components/hacs/helpers/functions/information.py
Normal file
@@ -0,0 +1,230 @@
|
|||||||
|
"""Return repository information if any."""
|
||||||
|
import json
|
||||||
|
|
||||||
|
from aiogithubapi import AIOGitHubAPIException, AIOGitHubAPINotModifiedException, GitHub
|
||||||
|
from aiogithubapi.const import ACCEPT_HEADERS
|
||||||
|
|
||||||
|
from custom_components.hacs.exceptions import HacsException, HacsNotModifiedException
|
||||||
|
from custom_components.hacs.helpers.functions.template import render_template
|
||||||
|
from custom_components.hacs.share import get_hacs
|
||||||
|
|
||||||
|
|
||||||
|
def info_file(repository):
|
||||||
|
"""get info filename."""
|
||||||
|
if repository.data.render_readme:
|
||||||
|
for filename in ["readme", "readme.md", "README", "README.md", "README.MD"]:
|
||||||
|
if filename in repository.treefiles:
|
||||||
|
return filename
|
||||||
|
return ""
|
||||||
|
for filename in ["info", "info.md", "INFO", "INFO.md", "INFO.MD"]:
|
||||||
|
if filename in repository.treefiles:
|
||||||
|
return filename
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
|
async def get_info_md_content(repository):
|
||||||
|
"""Get the content of info.md"""
|
||||||
|
filename = info_file(repository)
|
||||||
|
if not filename:
|
||||||
|
return ""
|
||||||
|
try:
|
||||||
|
info = await repository.repository_object.get_contents(filename, repository.ref)
|
||||||
|
if info is None:
|
||||||
|
return ""
|
||||||
|
info = info.content.replace("<svg", "<disabled").replace("</svg", "</disabled")
|
||||||
|
return render_template(info, repository)
|
||||||
|
except (
|
||||||
|
ValueError,
|
||||||
|
AIOGitHubAPIException,
|
||||||
|
Exception, # pylint: disable=broad-except
|
||||||
|
):
|
||||||
|
if repository.hacs.system.action:
|
||||||
|
raise HacsException("::error:: No info file found")
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
|
async def get_repository(session, token, repository_full_name, etag=None):
|
||||||
|
"""Return a repository object or None."""
|
||||||
|
hacs = get_hacs()
|
||||||
|
try:
|
||||||
|
github = GitHub(
|
||||||
|
token,
|
||||||
|
session,
|
||||||
|
headers={
|
||||||
|
"User-Agent": f"HACS/{hacs.version}",
|
||||||
|
"Accept": ACCEPT_HEADERS["preview"],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
repository = await github.get_repo(repository_full_name, etag)
|
||||||
|
return repository, github.client.last_response.etag
|
||||||
|
except AIOGitHubAPINotModifiedException as exception:
|
||||||
|
raise HacsNotModifiedException(exception) from exception
|
||||||
|
except (ValueError, AIOGitHubAPIException, Exception) as exception:
|
||||||
|
raise HacsException(exception) from exception
|
||||||
|
|
||||||
|
|
||||||
|
async def get_tree(repository, ref):
|
||||||
|
"""Return the repository tree."""
|
||||||
|
try:
|
||||||
|
tree = await repository.get_tree(ref)
|
||||||
|
return tree
|
||||||
|
except (ValueError, AIOGitHubAPIException) as exception:
|
||||||
|
raise HacsException(exception)
|
||||||
|
|
||||||
|
|
||||||
|
async def get_releases(repository, prerelease=False, returnlimit=5):
|
||||||
|
"""Return the repository releases."""
|
||||||
|
try:
|
||||||
|
releases = await repository.get_releases(prerelease, returnlimit)
|
||||||
|
return releases
|
||||||
|
except (ValueError, AIOGitHubAPIException) as exception:
|
||||||
|
raise HacsException(exception)
|
||||||
|
|
||||||
|
|
||||||
|
def get_frontend_version():
|
||||||
|
"""get the frontend version from the manifest."""
|
||||||
|
manifest = read_hacs_manifest()
|
||||||
|
frontend = 0
|
||||||
|
for requirement in manifest.get("requirements", []):
|
||||||
|
if requirement.startswith("hacs_frontend"):
|
||||||
|
frontend = requirement.split("==")[1]
|
||||||
|
break
|
||||||
|
return frontend
|
||||||
|
|
||||||
|
|
||||||
|
def read_hacs_manifest():
|
||||||
|
"""Reads the HACS manifest file and returns the contents."""
|
||||||
|
hacs = get_hacs()
|
||||||
|
content = {}
|
||||||
|
with open(f"{hacs.core.config_path}/custom_components/hacs/manifest.json") as manifest:
|
||||||
|
content = json.loads(manifest.read())
|
||||||
|
return content
|
||||||
|
|
||||||
|
|
||||||
|
async def get_integration_manifest(repository):
|
||||||
|
"""Return the integration manifest."""
|
||||||
|
if repository.data.content_in_root:
|
||||||
|
manifest_path = "manifest.json"
|
||||||
|
else:
|
||||||
|
manifest_path = f"{repository.content.path.remote}/manifest.json"
|
||||||
|
if not manifest_path in [x.full_path for x in repository.tree]:
|
||||||
|
raise HacsException(f"No file found '{manifest_path}'")
|
||||||
|
try:
|
||||||
|
manifest = await repository.repository_object.get_contents(manifest_path, repository.ref)
|
||||||
|
manifest = json.loads(manifest.content)
|
||||||
|
except (Exception, BaseException) as exception: # pylint: disable=broad-except
|
||||||
|
raise HacsException(f"Could not read manifest.json [{exception}]")
|
||||||
|
|
||||||
|
try:
|
||||||
|
repository.integration_manifest = manifest
|
||||||
|
repository.data.authors = manifest["codeowners"]
|
||||||
|
repository.data.domain = manifest["domain"]
|
||||||
|
repository.data.manifest_name = manifest["name"]
|
||||||
|
repository.data.config_flow = manifest.get("config_flow", False)
|
||||||
|
|
||||||
|
if repository.hacs.system.action:
|
||||||
|
if manifest.get("documentation") is None:
|
||||||
|
raise HacsException("::error:: manifest.json is missing documentation")
|
||||||
|
if manifest.get("homeassistant") is not None:
|
||||||
|
raise HacsException(
|
||||||
|
"::error:: The homeassistant key in manifest.json is no longer valid"
|
||||||
|
)
|
||||||
|
# if manifest.get("issue_tracker") is None:
|
||||||
|
# raise HacsException("The 'issue_tracker' is missing in manifest.json")
|
||||||
|
|
||||||
|
# Set local path
|
||||||
|
repository.content.path.local = repository.localpath
|
||||||
|
|
||||||
|
except KeyError as exception:
|
||||||
|
raise HacsException(f"Missing expected key {exception} in '{manifest_path}'")
|
||||||
|
|
||||||
|
|
||||||
|
def find_file_name(repository):
|
||||||
|
"""Get the filename to target."""
|
||||||
|
if repository.data.category == "plugin":
|
||||||
|
get_file_name_plugin(repository)
|
||||||
|
elif repository.data.category == "integration":
|
||||||
|
get_file_name_integration(repository)
|
||||||
|
elif repository.data.category == "theme":
|
||||||
|
get_file_name_theme(repository)
|
||||||
|
elif repository.data.category == "appdaemon":
|
||||||
|
get_file_name_appdaemon(repository)
|
||||||
|
elif repository.data.category == "python_script":
|
||||||
|
get_file_name_python_script(repository)
|
||||||
|
|
||||||
|
if repository.hacs.system.action:
|
||||||
|
repository.logger.info(f"filename {repository.data.file_name}")
|
||||||
|
repository.logger.info(f"location {repository.content.path.remote}")
|
||||||
|
|
||||||
|
|
||||||
|
def get_file_name_plugin(repository):
|
||||||
|
"""Get the filename to target."""
|
||||||
|
tree = repository.tree
|
||||||
|
releases = repository.releases.objects
|
||||||
|
|
||||||
|
if repository.data.content_in_root:
|
||||||
|
possible_locations = [""]
|
||||||
|
else:
|
||||||
|
possible_locations = ["release", "dist", ""]
|
||||||
|
|
||||||
|
# Handler for plug requirement 3
|
||||||
|
if repository.data.filename:
|
||||||
|
valid_filenames = [repository.data.filename]
|
||||||
|
else:
|
||||||
|
valid_filenames = [
|
||||||
|
f"{repository.data.name.replace('lovelace-', '')}.js",
|
||||||
|
f"{repository.data.name}.js",
|
||||||
|
f"{repository.data.name}.umd.js",
|
||||||
|
f"{repository.data.name}-bundle.js",
|
||||||
|
]
|
||||||
|
|
||||||
|
for location in possible_locations:
|
||||||
|
if location == "release":
|
||||||
|
if not releases:
|
||||||
|
continue
|
||||||
|
release = releases[0]
|
||||||
|
if not release.assets:
|
||||||
|
continue
|
||||||
|
asset = release.assets[0]
|
||||||
|
for filename in valid_filenames:
|
||||||
|
if filename == asset.name:
|
||||||
|
repository.data.file_name = filename
|
||||||
|
repository.content.path.remote = "release"
|
||||||
|
break
|
||||||
|
|
||||||
|
else:
|
||||||
|
for filename in valid_filenames:
|
||||||
|
if f"{location+'/' if location else ''}{filename}" in [x.full_path for x in tree]:
|
||||||
|
repository.data.file_name = filename.split("/")[-1]
|
||||||
|
repository.content.path.remote = location
|
||||||
|
break
|
||||||
|
|
||||||
|
|
||||||
|
def get_file_name_integration(repository):
|
||||||
|
"""Get the filename to target."""
|
||||||
|
|
||||||
|
|
||||||
|
def get_file_name_theme(repository):
|
||||||
|
"""Get the filename to target."""
|
||||||
|
tree = repository.tree
|
||||||
|
|
||||||
|
for treefile in tree:
|
||||||
|
if treefile.full_path.startswith(
|
||||||
|
repository.content.path.remote
|
||||||
|
) and treefile.full_path.endswith(".yaml"):
|
||||||
|
repository.data.file_name = treefile.filename
|
||||||
|
|
||||||
|
|
||||||
|
def get_file_name_appdaemon(repository):
|
||||||
|
"""Get the filename to target."""
|
||||||
|
|
||||||
|
|
||||||
|
def get_file_name_python_script(repository):
|
||||||
|
"""Get the filename to target."""
|
||||||
|
tree = repository.tree
|
||||||
|
|
||||||
|
for treefile in tree:
|
||||||
|
if treefile.full_path.startswith(
|
||||||
|
repository.content.path.remote
|
||||||
|
) and treefile.full_path.endswith(".py"):
|
||||||
|
repository.data.file_name = treefile.filename
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
"""Helper to check if path is safe to remove."""
|
||||||
|
from custom_components.hacs.share import get_hacs
|
||||||
|
|
||||||
|
from ...utils.path import is_safe
|
||||||
|
|
||||||
|
|
||||||
|
def is_safe_to_remove(path: str) -> bool:
|
||||||
|
"""Helper to check if path is safe to remove."""
|
||||||
|
hacs = get_hacs()
|
||||||
|
return is_safe(hacs, path)
|
||||||
35
custom_components/hacs/helpers/functions/misc.py
Normal file
35
custom_components/hacs/helpers/functions/misc.py
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
"""Helper functions: misc"""
|
||||||
|
import re
|
||||||
|
|
||||||
|
from ...utils import version
|
||||||
|
|
||||||
|
RE_REPOSITORY = re.compile(
|
||||||
|
r"(?:(?:.*github.com.)|^)([A-Za-z0-9-]+\/[\w.-]+?)(?:(?:\.git)?|(?:[^\w.-].*)?)$"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def get_repository_name(repository) -> str:
|
||||||
|
"""Return the name of the repository for use in the frontend."""
|
||||||
|
|
||||||
|
if repository.repository_manifest.name is not None:
|
||||||
|
return repository.repository_manifest.name
|
||||||
|
|
||||||
|
if repository.data.category == "integration":
|
||||||
|
if repository.integration_manifest:
|
||||||
|
if "name" in repository.integration_manifest:
|
||||||
|
return repository.integration_manifest["name"]
|
||||||
|
|
||||||
|
return repository.data.full_name.split("/")[-1].replace("-", " ").replace("_", " ").title()
|
||||||
|
|
||||||
|
|
||||||
|
def version_left_higher_then_right(left: str, right: str) -> bool:
|
||||||
|
"""Return a bool if source is newer than target, will also be true if identical."""
|
||||||
|
return version.version_left_higher_then_right(left, right)
|
||||||
|
|
||||||
|
|
||||||
|
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()
|
||||||
13
custom_components/hacs/helpers/functions/path_exsist.py
Normal file
13
custom_components/hacs/helpers/functions/path_exsist.py
Normal file
@@ -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)
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
"""Register a repository."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
from aiogithubapi import AIOGitHubAPIException
|
||||||
|
|
||||||
|
from custom_components.hacs.exceptions import (
|
||||||
|
HacsException,
|
||||||
|
HacsExpectedException,
|
||||||
|
HacsRepositoryExistException,
|
||||||
|
)
|
||||||
|
from custom_components.hacs.share import get_hacs
|
||||||
|
|
||||||
|
from ...repositories import RERPOSITORY_CLASSES
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from ..classes.repository import HacsRepository
|
||||||
|
|
||||||
|
# @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: HacsRepository = 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 HacsRepositoryExistException:
|
||||||
|
return
|
||||||
|
except AIOGitHubAPIException as exception:
|
||||||
|
hacs.common.skip.append(repository.data.full_name)
|
||||||
|
raise HacsException(f"Validation for {full_name} failed with {exception}.") from None
|
||||||
|
|
||||||
|
if str(repository.data.id) != "0" and (exists := hacs.get_by_id(repository.data.id)):
|
||||||
|
hacs.async_remove_repository(exists)
|
||||||
|
|
||||||
|
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.async_add_repository(repository)
|
||||||
50
custom_components/hacs/helpers/functions/save.py
Normal file
50
custom_components/hacs/helpers/functions/save.py
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
"""Download."""
|
||||||
|
import gzip
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
|
||||||
|
import aiofiles
|
||||||
|
|
||||||
|
from custom_components.hacs.utils.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)
|
||||||
79
custom_components/hacs/helpers/functions/store.py
Normal file
79
custom_components/hacs/helpers/functions/store.py
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
"""Storage handers."""
|
||||||
|
# pylint: disable=import-outside-toplevel
|
||||||
|
from homeassistant.helpers.json import JSONEncoder
|
||||||
|
from homeassistant.helpers.storage import Store
|
||||||
|
from homeassistant.util import json as json_util
|
||||||
|
|
||||||
|
from custom_components.hacs.const import VERSION_STORAGE
|
||||||
|
|
||||||
|
from ...utils.logger import getLogger
|
||||||
|
|
||||||
|
_LOGGER = getLogger()
|
||||||
|
|
||||||
|
|
||||||
|
class HACSStore(Store):
|
||||||
|
"""A subclass of Store that allows multiple loads in the executor."""
|
||||||
|
|
||||||
|
def load(self):
|
||||||
|
"""Load the data from disk if version matches."""
|
||||||
|
data = json_util.load_json(self.path)
|
||||||
|
if data == {} or data["version"] != self.version:
|
||||||
|
return None
|
||||||
|
return data["data"]
|
||||||
|
|
||||||
|
|
||||||
|
def get_store_key(key):
|
||||||
|
"""Return the key to use with homeassistant.helpers.storage.Storage."""
|
||||||
|
return key if "/" in key else f"hacs.{key}"
|
||||||
|
|
||||||
|
|
||||||
|
def _get_store_for_key(hass, key, encoder):
|
||||||
|
"""Create a Store object for the key."""
|
||||||
|
return HACSStore(hass, VERSION_STORAGE, get_store_key(key), encoder=encoder)
|
||||||
|
|
||||||
|
|
||||||
|
def get_store_for_key(hass, key):
|
||||||
|
"""Create a Store object for the key."""
|
||||||
|
return _get_store_for_key(hass, key, JSONEncoder)
|
||||||
|
|
||||||
|
|
||||||
|
async def async_load_from_store(hass, key):
|
||||||
|
"""Load the retained data from store and return de-serialized data."""
|
||||||
|
return await get_store_for_key(hass, key).async_load() or {}
|
||||||
|
|
||||||
|
|
||||||
|
async def async_save_to_store_default_encoder(hass, key, data):
|
||||||
|
"""Generate store json safe data to the filesystem.
|
||||||
|
|
||||||
|
The data is expected to be encodable with the default
|
||||||
|
python json encoder. It should have already been passed through
|
||||||
|
JSONEncoder if needed.
|
||||||
|
"""
|
||||||
|
await _get_store_for_key(hass, key, None).async_save(data)
|
||||||
|
|
||||||
|
|
||||||
|
async def async_save_to_store(hass, key, data):
|
||||||
|
"""Generate dynamic data to store and save it to the filesystem.
|
||||||
|
|
||||||
|
The data is only written if the content on the disk has changed
|
||||||
|
by reading the existing content and comparing it.
|
||||||
|
|
||||||
|
If the data has changed this will generate two executor jobs
|
||||||
|
|
||||||
|
If the data has not changed this will generate one executor job
|
||||||
|
"""
|
||||||
|
current = await async_load_from_store(hass, key)
|
||||||
|
if current is None or current != data:
|
||||||
|
await get_store_for_key(hass, key).async_save(data)
|
||||||
|
return
|
||||||
|
_LOGGER.debug(
|
||||||
|
"Did not store data for '%s'. Content did not change",
|
||||||
|
get_store_key(key),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
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()
|
||||||
32
custom_components/hacs/helpers/functions/template.py
Normal file
32
custom_components/hacs/helpers/functions/template.py
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
"""Custom template support."""
|
||||||
|
# pylint: disable=broad-except
|
||||||
|
from jinja2 import Template
|
||||||
|
|
||||||
|
from custom_components.hacs.utils.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
|
||||||
124
custom_components/hacs/helpers/functions/validate_repository.py
Normal file
124
custom_components/hacs/helpers/functions/validate_repository.py
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
"""Helper to do common validation for repositories."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
from aiogithubapi import AIOGitHubAPIException
|
||||||
|
|
||||||
|
from custom_components.hacs.exceptions import (
|
||||||
|
HacsException,
|
||||||
|
HacsNotModifiedException,
|
||||||
|
HacsRepositoryArchivedException,
|
||||||
|
HacsRepositoryExistException,
|
||||||
|
)
|
||||||
|
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
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from custom_components.hacs.helpers.classes.repository import HacsRepository
|
||||||
|
|
||||||
|
|
||||||
|
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: HacsRepository, ignore_issues=False, force=False):
|
||||||
|
"""Common update data."""
|
||||||
|
hacs = get_hacs()
|
||||||
|
releases = []
|
||||||
|
try:
|
||||||
|
repository_object, etag = await get_repository(
|
||||||
|
hacs.session,
|
||||||
|
hacs.configuration.token,
|
||||||
|
repository.data.full_name,
|
||||||
|
etag=None if force or repository.data.installed else repository.data.etag_repository,
|
||||||
|
)
|
||||||
|
repository.repository_object = repository_object
|
||||||
|
if repository.data.full_name.lower() != repository_object.full_name.lower():
|
||||||
|
hacs.common.renamed_repositories[
|
||||||
|
repository.data.full_name
|
||||||
|
] = repository_object.full_name
|
||||||
|
if str(repository_object.id) not in hacs.common.default:
|
||||||
|
hacs.common.default.append(str(repository_object.id))
|
||||||
|
raise HacsRepositoryExistException
|
||||||
|
repository.data.update_data(repository_object.attributes)
|
||||||
|
repository.data.etag_repository = etag
|
||||||
|
except HacsNotModifiedException:
|
||||||
|
return
|
||||||
|
except HacsRepositoryExistException:
|
||||||
|
raise HacsRepositoryExistException from None
|
||||||
|
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.")
|
||||||
|
hacs.common.archived_repositories.append(repository.data.full_name)
|
||||||
|
raise HacsRepositoryArchivedException("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
|
||||||
@@ -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
|
||||||
30
custom_components/hacs/helpers/methods/__init__.py
Normal file
30
custom_components/hacs/helpers/methods/__init__.py
Normal file
@@ -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"""
|
||||||
113
custom_components/hacs/helpers/methods/installation.py
Normal file
113
custom_components/hacs/helpers/methods/installation.py
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
# pylint: disable=missing-class-docstring,missing-module-docstring,missing-function-docstring,no-member
|
||||||
|
from abc import ABC
|
||||||
|
import os
|
||||||
|
import tempfile
|
||||||
|
|
||||||
|
from custom_components.hacs.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
|
||||||
|
from custom_components.hacs.share import get_hacs
|
||||||
|
|
||||||
|
|
||||||
|
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."""
|
||||||
|
hacs = get_hacs()
|
||||||
|
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 = await hacs.hass.async_add_executor_job(BackupNetDaemon, repository)
|
||||||
|
await hacs.hass.async_add_executor_job(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/",
|
||||||
|
)
|
||||||
|
await hacs.hass.async_add_executor_job(persistent_directory.create)
|
||||||
|
|
||||||
|
if repository.data.installed and not repository.content.single:
|
||||||
|
backup = Backup(repository.content.path.local)
|
||||||
|
await hacs.hass.async_add_executor_job(backup.create)
|
||||||
|
|
||||||
|
if repository.data.zip_release and version != repository.data.default_branch:
|
||||||
|
await repository.download_zip_files(repository.validate)
|
||||||
|
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:
|
||||||
|
await hacs.hass.async_add_executor_job(backup.restore)
|
||||||
|
|
||||||
|
if repository.data.installed and not repository.content.single:
|
||||||
|
await hacs.hass.async_add_executor_job(backup.cleanup)
|
||||||
|
|
||||||
|
if persistent_directory is not None:
|
||||||
|
await hacs.hass.async_add_executor_job(persistent_directory.restore)
|
||||||
|
await hacs.hass.async_add_executor_job(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
|
||||||
41
custom_components/hacs/helpers/methods/registration.py
Normal file
41
custom_components/hacs/helpers/methods/registration.py
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
# 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)
|
||||||
@@ -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()
|
||||||
16
custom_components/hacs/helpers/properties/__init__.py
Normal file
16
custom_components/hacs/helpers/properties/__init__.py
Normal file
@@ -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
|
||||||
@@ -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.core.ha_version, self.data.homeassistant
|
||||||
|
):
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
@property
|
||||||
|
def can_install(self):
|
||||||
|
"""kept for legacy compatibility"""
|
||||||
|
return self.can_be_installed
|
||||||
13
custom_components/hacs/helpers/properties/custom.py
Normal file
13
custom_components/hacs/helpers/properties/custom.py
Normal file
@@ -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
|
||||||
23
custom_components/hacs/helpers/properties/pending_update.py
Normal file
23
custom_components/hacs/helpers/properties/pending_update.py
Normal file
@@ -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
|
||||||
29
custom_components/hacs/iconset.js
Normal file
29
custom_components/hacs/iconset.js
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
const hacsIcons = {
|
||||||
|
hacs: {
|
||||||
|
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",
|
||||||
|
keywords: ["hacs", "home assistant community store"],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
window.customIcons = window.customIcons || {};
|
||||||
|
window.customIconsets = window.customIconsets || {};
|
||||||
|
|
||||||
|
// For Home Assistant > 2021.11
|
||||||
|
window.customIcons["hacs"] = {
|
||||||
|
getIcon: async (iconName) => (
|
||||||
|
{ path: hacsIcons[iconName]?.path }
|
||||||
|
),
|
||||||
|
getIconList: async () =>
|
||||||
|
Object.entries(hacsIcons).map(([icon, content]) => ({
|
||||||
|
name: icon,
|
||||||
|
keywords: content.keywords,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
// For Home Assistant < 2021.11
|
||||||
|
window.customIconsets["hacs"] = async () => {
|
||||||
|
return {
|
||||||
|
path: hacsIcons.hacs.path
|
||||||
|
};
|
||||||
|
};
|
||||||
27
custom_components/hacs/manifest.json
Normal file
27
custom_components/hacs/manifest.json
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"codeowners": [
|
||||||
|
"@ludeeus"
|
||||||
|
],
|
||||||
|
"config_flow": true,
|
||||||
|
"dependencies": [
|
||||||
|
"http",
|
||||||
|
"websocket_api",
|
||||||
|
"frontend",
|
||||||
|
"persistent_notification",
|
||||||
|
"lovelace"
|
||||||
|
],
|
||||||
|
"documentation": "https://hacs.xyz/docs/configuration/start",
|
||||||
|
"domain": "hacs",
|
||||||
|
"iot_class": "cloud_polling",
|
||||||
|
"issue_tracker": "https://github.com/hacs/integration/issues",
|
||||||
|
"name": "HACS",
|
||||||
|
"requirements": [
|
||||||
|
"aiofiles>=0.6.0",
|
||||||
|
"aiogithubapi>=21.8.1",
|
||||||
|
"awesomeversion>=21.2.2",
|
||||||
|
"backoff>=1.10.0",
|
||||||
|
"hacs_frontend==20211010111104",
|
||||||
|
"queueman==0.5"
|
||||||
|
],
|
||||||
|
"version": "1.16.0"
|
||||||
|
}
|
||||||
24
custom_components/hacs/mixin.py
Normal file
24
custom_components/hacs/mixin.py
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
"""Mixin classes."""
|
||||||
|
# pylint: disable=too-few-public-methods
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from logging import Logger
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
from .share import get_hacs
|
||||||
|
from .utils.logger import getLogger
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from .hacsbase.hacs import Hacs
|
||||||
|
|
||||||
|
|
||||||
|
class HacsMixin:
|
||||||
|
"""Mixin to provide 'self.hacs' to classes."""
|
||||||
|
|
||||||
|
hacs: Hacs = get_hacs()
|
||||||
|
|
||||||
|
|
||||||
|
class LogMixin:
|
||||||
|
"""Mixin to provide 'self.log' to classes."""
|
||||||
|
|
||||||
|
log: Logger = getLogger()
|
||||||
0
custom_components/hacs/operational/__init__.py
Normal file
0
custom_components/hacs/operational/__init__.py
Normal file
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user