This commit is contained in:
2026-01-30 23:31:00 -06:00
commit a39095b3de
2665 changed files with 263970 additions and 0 deletions

1
.HA_VERSION Normal file
View File

@@ -0,0 +1 @@
2025.12.5

28
.cloud/acme_account.pem Normal file
View File

@@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDL813tfu3dUdiV
U9b9vVjWeVIWfhcAyT7JDDI2vWTDTiB9pBMYsT84PwSg35AsELEenMIM1KwU6IyK
nI+nLIZkgvAc4QftW1zPBcwJ1O31+LhFG7XTGpCYKIGNpUMzksYvdKkqoV4O6qRs
9Viokmt5pvZNQr0ws1a8po00kq6fvUwIMV7Ba725zS7zQCZAADByr6oIqT+czexN
vuJuQZMVUdPVyZq0pF8oSCGdpAFIQr8F/xRo17qE52WQ17gxAA69iDfqhET/+wQz
JQMp/Vjwp5uX5Agp1tIE2XyskK3MY3YGJWzIg4+A0G9o/njUNvx7pMpw7wCCdA6x
84IyXi1lAgMBAAECggEAAW/cxs5Y4zEPL1gooN+LZ3Fx4l4vj84bLuPy259gfR7J
DFGSX99p1F3fTLnehGz2roJIvLnn33rW+KgLPBCMfttMUUvFmEbHQ98k1aHmLlA7
DiOYl5ztjWDlrseODmg3lMCD65y48q51C5576i4j6zbBsAArIJN25jvfLVJmQBJc
Ycie405sZfiK6Ck0sHKxnGYk5/+cqm5/SaoD5h1CNRf1WvfwRO3qRSmD/D1IJ3FS
SrF3qwIun9kMeei7+65NycAZAKb61bVdsZMXjThz11uggScdqw10givx/5HwK825
Wemu2edBswzpEj+2s9MS3mIi+oweZmQToZXy0+mbaQKBgQD6ZyiBAdEVWih1O/U5
L/FWgpW79UsIi98szvPJ8sYCD/ALRijkhZCkIu2r6u8EGZBq0qBZ6b6WnANjpxOS
Ms06kzob5uEF9EdpV+zyaE8xUUb9SPoGjdLAD8rIcw2jqaORP/yI6YO2vPcxeKgA
UHRpmhKMJUrS6FPPLVnN/gaqaQKBgQDQgmbh8m/+OkDcQQBwyf1ayZEZGiZEWQ3z
YcvA87PwL0lq5+TFx9qnb11tYQ9q1OU0c9C8ujnqX3BfE+xnLvdinoSULuRCIRLh
KTuZYnUnls04jprTFYCj+YSzG3hfPhzPfe2y5XQ3ZjsHPaiwfTswoqR6Ee2qwV0V
J2o2A5bznQKBgQDveMCPwAEJfpO6qoC3FFal+XThsJD1t27UF4em1vru9fcHkS2C
fwn5Lz5FcATt0tT+lDiuRJD00HedUiexZcxH/I1SKdeCLkAtSt1cZs11yNkvWh9j
LTckXvX8BaxBnPbE7oDBHzHMDaQKN+3Tfx4V8DdUuEV6tp2QQTrlec8+IQKBgBwm
ft1ibdxU4QzbecPAgYQQUpahASmZHFkPiwKx5Ek5GSBlzm0lXk/cqTBrOjmiJI/A
Ux4nxknuOK2dcv07Sgr2e8/FxOtoq7PabUF4GXkO0wYfuqdk78kzlsbXnpi9OgaJ
ad4NPHN+SdngaTXqsmMOkkYoxX2YPYjtmVlRgr/BAoGBALeCe4bHZocSp6YKOCa1
h29hJ/ZN7LkI4rzwkOA8JCsSaR0r0p0pJg6uQCf6fTz5UaOhjV+6B1L/O5kqkDxv
kKNRtbZ/eeH8FqrZFj6twoZo5Fr7F2TiZIWfyDG1mai7pckHEw1xhhRho9eOGWH/
hjRws1Fu0KnaB3YP1g+iT4rM
-----END PRIVATE KEY-----

13
.cloud/acme_reg.json Normal file
View File

@@ -0,0 +1,13 @@
{
"body": {
"contact": [],
"key": {
"e": "AQAB",
"kty": "RSA",
"n": "y_Nd7X7t3VHYlVPW_b1Y1nlSFn4XAMk-yQwyNr1kw04gfaQTGLE_OD8EoN-QLBCxHpzCDNSsFOiMipyPpyyGZILwHOEH7VtczwXMCdTt9fi4RRu10xqQmCiBjaVDM5LGL3SpKqFeDuqkbPVYqJJreab2TUK9MLNWvKaNNJKun71MCDFewWu9uc0u80AmQAAwcq-qCKk_nM3sTb7ibkGTFVHT1cmatKRfKEghnaQBSEK_Bf8UaNe6hOdlkNe4MQAOvYg36oRE__sEMyUDKf1Y8Kebl-QIKdbSBNl8rJCtzGN2BiVsyIOPgNBvaP541Db8e6TKcO8AgnQOsfOCMl4tZQ"
},
"status": "valid"
},
"terms_of_service": "https://letsencrypt.org/documents/LE-SA-v1.6-August-18-2025.pdf",
"uri": "https://acme-v02.api.letsencrypt.org/acme/acct/2885294806"
}

View File

@@ -0,0 +1,5 @@
{
"id_token": "eyJraWQiOiJqZGhOQ1A4U1lRdUZoUTdBS1VuZnM5a0RCXC9YTG0wc2o2MzFKRGhpSWptOD0iLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiIzYWZlMDAxNy1lMWViLTQxOTgtYWJmYi01OGVkZWQ5YTg0ZGIiLCJzdWJfc291cmNlIjoiZGRiX2xlZ2FjeSIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJjdXN0b206c3ViLWV4cCI6IjIwMjYtMTItMTciLCJpc3MiOiJodHRwczpcL1wvY29nbml0by1pZHAudXMtZWFzdC0xLmFtYXpvbmF3cy5jb21cL3VzLWVhc3QtMV84N2xsNVdPUDgiLCJjb2duaXRvOnVzZXJuYW1lIjoiM2FmZTAwMTctZTFlYi00MTk4LWFiZmItNThlZGVkOWE4NGRiIiwiYXVkIjoiNjBpMnV2aHZiaXJlZjJtZnRqN3JnY3J0OXUiLCJldmVudF9pZCI6IjA2MTcyNDJlLTIyOTgtNDE3ZS1hMDNkLWI4MjVlMzFhMjA2YyIsInRva2VuX3VzZSI6ImlkIiwiYXV0aF90aW1lIjoxNzY1OTQ0MjQxLCJleHAiOjE3Njk4Mzc4NTMsImlhdCI6MTc2OTgzNDI1MywiZW1haWwiOiJjaHJpc3JvYmVydGNoYXN0ZWVuQGdtYWlsLmNvbSJ9.GSIUSy5WfZiIy018M0nns_U4eN7Px8fuunWLGa-wQwV2NLhcVhFodq9_8zhMF5dyqCY9Pw-t5NPyLJbmtByM5eUy8uftpBTXMm0AwUTAzqwnS5mJ7Lbl90NawK4twoqBhNGGXdgsu3LlRpLO0AO9phmMYWOx-qFM3ROmNJCC6VdOY-DcAujN7-MOLtC1p83n55-4KV49RwhdQsLW-t5ANJ6eUkEGR6e_BH2BRiPgEdP_h8uOBBv8IABpc5x17atcmtdrB65mNppafOGPqiyTENmKldwvQwJxDv4VhbV5QQzzkxdE6UETs5ANduF4OauCceKzjlxqMfmCDw_BUw9ZyA",
"access_token": "eyJraWQiOiJGTzR4d08xNUJzbDZVVFpwd1ROek8wRVUzeXMxRm1QaERCWmpmU1wvS1llZz0iLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiIzYWZlMDAxNy1lMWViLTQxOTgtYWJmYi01OGVkZWQ5YTg0ZGIiLCJldmVudF9pZCI6IjA2MTcyNDJlLTIyOTgtNDE3ZS1hMDNkLWI4MjVlMzFhMjA2YyIsInRva2VuX3VzZSI6ImFjY2VzcyIsInNjb3BlIjoiYXdzLmNvZ25pdG8uc2lnbmluLnVzZXIuYWRtaW4iLCJhdXRoX3RpbWUiOjE3NjU5NDQyNDEsImlzcyI6Imh0dHBzOlwvXC9jb2duaXRvLWlkcC51cy1lYXN0LTEuYW1hem9uYXdzLmNvbVwvdXMtZWFzdC0xXzg3bGw1V09QOCIsImV4cCI6MTc2OTgzNzg1MywiaWF0IjoxNzY5ODM0MjUzLCJqdGkiOiI5YzJmNmI1Yy1mZWE1LTRkYmEtOGExMy01ZjJhY2QzZTE5ZTIiLCJjbGllbnRfaWQiOiI2MGkydXZodmJpcmVmMm1mdGo3cmdjcnQ5dSIsInVzZXJuYW1lIjoiM2FmZTAwMTctZTFlYi00MTk4LWFiZmItNThlZGVkOWE4NGRiIn0.RSBe4mQoVVCB6r0rlB03xls2M4U2y70pbCXPYHTqRoSqdLwy7nVqD1rN4Frq4zgc7Sw3w9lgXOAm7kPc-1KDIE-bcGw-VVY7VhsjjxBuqfNdp_ZcNpYLl1QziJAkJMaoQ1hxSqF-lzr_0r3v19NvBTT8iHNMHh0taOnAgjlkD7_qQAE0bb_0hVs_eiZYL8cUt_j37NAklt7NePLEkODlywPM2sOYN0HB8gFR6BemImeK3njwKh91WNY0u2alJFftch9-PdHs5ezAb_1QgUnUVYyQfLMfYo-obp-r0CKCajyhDJ-Lsl3sK0nPK7LIkaZgU_tZuQo18OIAF9etv4Or2A",
"refresh_token": "eyJjdHkiOiJKV1QiLCJlbmMiOiJBMjU2R0NNIiwiYWxnIjoiUlNBLU9BRVAifQ.NcXogyi12pNEpyYbSr-5fr5BEyJgIKIsA9jIcSOb7RQvAjQSvu35HXp8g6RF107MhLfS6rjNpD_TXEpBQubPM-BtFwYJztfX39UEKRtndETQwL4tEB8IissG80yld8lACFk5WO-Uw4r0BfdYEBSXnjhbZVzROXr9x_xzA7TjC_IeYLbt70mnisqEb-4KK_EO91aYNMhDd7JRnqAHdUdXDyLBciYTY76j8hRYniOwm4NRcl8WZzbYNCpTjyyrwJnDuZdALcWvqU9I_oN7UiWTVl0w9ESE6WN2FzylMrVsTUPhpsTHcM1LzLoipHhQljeIAUTnpImWoGSnjz-U_VDEeg.oLWMDRxUYKixRPtP.H4VnlFIn765-xvlVqOs4-84fPq0j0irjGOz2Ke9qPKkmJKAX3v_3JZwiEYwzjZgsguEYXBfIuF3ATq2ZN-os8cFNk4pXQcy7UpXYHAzQgayu6r0-9rzoGIYxHftB7kQbOM31p_AbnaxwBa_PLyluQEdq45lWbdIcN3SlUmkmf5O8gmw-cjgbAswHyP-UFJWgh7OQ0doIqJW7HWB8g0e44EG5iUl_tRY44Pja0k3tuvifkyBoAG2QEO27c-uNdXaLk4iYBamBhY8HPx69QPVNSH8ImrqXWVXHcSf7iUEj_WhuH_05qakSli-CPGir3Yja8ozjhn-cW2oRdZcV7fc8ZG6c54WaI22Mek21Br4Cn9yG2cTRQHe0XlD_JBASdfvTyFtotvevjkpF4c3hjAABX94-U3cCW37bR6zY7g8IKKy7zoZX3RtgNRHIlMZ8T9g3VGlDZ6yvoEyKq67MS3_hr-skHjIZpxspEAgQtU5tJhwn2rxI7wYRvFpYQusqMYPa1fEZUb4eV7puEmKdKUlVQ_YX4F-RAL3MNNKMHJBHMtJ6TPBe9Ok_wl4KKzZZjS2ZsETz3ZcnbqMH1zYVCpIW4Dsr5Qc2L7dqgKsjDm9-r9erAxNJMFQvcA4WTzgyRgEqyNgAOpdwTZd7xXGbBSLF7rJ-FgQu1C4DOdE-ZvTJlsg2EcA8BXC3R4I0saXFDTYse_DePqn1YsDXotdeATi6nTeWgPOMl9Ashk35lTBnKgkwX5yc8_-iPBUfTwVaOA7Yn1B1MHnN6_scuT_EmUM4kB7_aUuawbo_NuhPJ_dSaMHrqrc-9TCGpSttcmX73CbrZKlrcIRvX3CyLuMbzvuYHli3003l18RhEBheaDr9KUGN3nMeooxgQoOFFl03Ibw8CoI0joGAqgGU0YCVx8ISZV7t7Cx2DTg2Ht6YNs3xZkmtDx_c8bj-QJfRxf9HDKKGHioOtRmovv1YlWuW8iHcYS2lh59GZ_etQolWfwS_Vq6R6cQctuiebIc80NAbnZNYXRHPHDKzX3fscR7eDmAywP8WB65YeAYmzGKeGt7ST7qgAVoWlZad97IMb41pLGCbd-dd49NF_XxJxbqOu-QRDoQ0-hwt2lrwCT_Zm0v2SWf5hl0aNXWT6pw5J_cOPyATKPC81ANFIONs_cXDevOsKzRFDwg8L4pgKn_mufsdF_w98il5Yx3EGfQxKOlDZsFedMY93KqpuTmzGHot9fd1_JsowA1iTRhEYM6Q1CipgcEqKoAkpbrkm1j8whc0orKXVkXR7il2gvqL8T4nWHyso9QuPlrD9aiU1ue6S_2GmIhfC6zGqbWHBxJpQ_Ev3Q.JDKlK0S5cpYzFNmFcKVAHw"
}

View File

@@ -0,0 +1,61 @@
-----BEGIN CERTIFICATE-----
MIIFSjCCBDKgAwIBAgISBRdgzVwLLHjVBRf2MqWC/8EHMA0GCSqGSIb3DQEBCwUA
MDMxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MQwwCgYDVQQD
EwNSMTIwHhcNMjUxMjE3MDQxNzAzWhcNMjYwMzE3MDQxNzAyWjA4MTYwNAYDVQQD
Ey1kbHdnc3Iya3dvb3ZlajQxaXR2amtjN2NhYnR5bWZwMy51aS5uYWJ1LmNhc2Ew
ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCLQGcrdY1EBhh3VuhEp4sM
BxpmRAHjJES6+eDFjJP+cGdeYQbUKmqNPcBmG4dTAVlRWlANHA4/wKp5cWkD2zgY
EA5aUSiDS9uYxDx/L/+x1A9r/i+36benoga+Mdp6mY9d2EwSoaUakl/4ezXHmC3O
H83W41QL7mlsTcqJiQ93nCDVaxM/wwLugjKpQFX41qrmptr413Ep2fro53p2TYvW
E8XXBsHazb3fPbEDPNO6aTDlsjyizQz+BkGeUUbuHeESZLYky3sjYEZKmCHZusTI
LP3bCd/GQP6Mx04davAXfJCw1FsCZnJLvp5/30m43uIQiOpRWHusPxp3bwBlzoP7
AgMBAAGjggJRMIICTTAOBgNVHQ8BAf8EBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUH
AwEGCCsGAQUFBwMCMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFPcU+gXZtVD57D8r
/VR5OE844iIuMB8GA1UdIwQYMBaAFAC1KfItjm8x6JtMrXg++tzpDNHSMDMGCCsG
AQUFBwEBBCcwJTAjBggrBgEFBQcwAoYXaHR0cDovL3IxMi5pLmxlbmNyLm9yZy8w
TgYDVR0RBEcwRYItZGx3Z3NyMmt3b292ZWo0MWl0dmprYzdjYWJ0eW1mcDMudWku
bmFidS5jYXNhghRob21lLmJvb2tzYW5kYm9wcy51czATBgNVHSAEDDAKMAgGBmeB
DAECATAtBgNVHR8EJjAkMCKgIKAehhxodHRwOi8vcjEyLmMubGVuY3Iub3JnLzgu
Y3JsMIIBAwYKKwYBBAHWeQIEAgSB9ASB8QDvAHUAFoMtq/CpJQ8P8DqlRf/Iv8gj
0IdL9gQpJ/jnHzMT9foAAAGbKrwpYwAABAMARjBEAiB8Txgs6WCzDQN/6paWk+01
qWZiEi/QN3qxqDsMGTFrsgIgBKBVV9lmrx3As4ubDI/ORj5iIhJ9E+knZzEN8W+4
hGQAdgAOV5S8866pPjMbLJkHs/eQ35vCPXEyJd0hqSWsYcVOIQAAAZsqvDEHAAAE
AwBHMEUCIC4SfSql7nTK5BWNulG6oh+vs5Lyb3nTidIuOCUFrdiCAiEAo5CQzleQ
VKWH3bCtnn+ScWpC2wP8YC+ohVmPTtvMWuUwDQYJKoZIhvcNAQELBQADggEBALxf
1UZwk0GLz0IeYFLUg5B1O1i86iDgCuA8GnKmJG9hnU7kKcv7nDiFuC7A2csACmVy
Vn+SkCDhlP2UOQq7O6zaSPjd7KGrEdfl4WAS2kHoOR8RkJhg5t4HuJQR4WekoWcI
n+nDa4EEHzhWzVX66D/lY0DaISXSX8K2cxW2T1zJVepI62lE9z246vf+NE2JqpHq
QC8wWKZI1aPQ06MQBDer6W7CDiATCwRaG8yKWfkH73puvPcJiRzTlB4CWHggFhBT
/7Hxzidf25B25S9nyFglZlSNgzEw4RMuTe3qJKaTmbv0WPzEKI7xV7k09IAi7kh7
CP598yDPn765ztqKGMs=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIFBjCCAu6gAwIBAgIRAMISMktwqbSRcdxA9+KFJjwwDQYJKoZIhvcNAQELBQAw
TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh
cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMjQwMzEzMDAwMDAw
WhcNMjcwMzEyMjM1OTU5WjAzMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNTGV0J3Mg
RW5jcnlwdDEMMAoGA1UEAxMDUjEyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
CgKCAQEA2pgodK2+lP474B7i5Ut1qywSf+2nAzJ+Npfs6DGPpRONC5kuHs0BUT1M
5ShuCVUxqqUiXXL0LQfCTUA83wEjuXg39RplMjTmhnGdBO+ECFu9AhqZ66YBAJpz
kG2Pogeg0JfT2kVhgTU9FPnEwF9q3AuWGrCf4yrqvSrWmMebcas7dA8827JgvlpL
Thjp2ypzXIlhZZ7+7Tymy05v5J75AEaz/xlNKmOzjmbGGIVwx1Blbzt05UiDDwhY
XS0jnV6j/ujbAKHS9OMZTfLuevYnnuXNnC2i8n+cF63vEzc50bTILEHWhsDp7CH4
WRt/uTp8n1wBnWIEwii9Cq08yhDsGwIDAQABo4H4MIH1MA4GA1UdDwEB/wQEAwIB
hjAdBgNVHSUEFjAUBggrBgEFBQcDAgYIKwYBBQUHAwEwEgYDVR0TAQH/BAgwBgEB
/wIBADAdBgNVHQ4EFgQUALUp8i2ObzHom0yteD763OkM0dIwHwYDVR0jBBgwFoAU
ebRZ5nu25eQBc4AIiMgaWPbpm24wMgYIKwYBBQUHAQEEJjAkMCIGCCsGAQUFBzAC
hhZodHRwOi8veDEuaS5sZW5jci5vcmcvMBMGA1UdIAQMMAowCAYGZ4EMAQIBMCcG
A1UdHwQgMB4wHKAaoBiGFmh0dHA6Ly94MS5jLmxlbmNyLm9yZy8wDQYJKoZIhvcN
AQELBQADggIBAI910AnPanZIZTKS3rVEyIV29BWEjAK/duuz8eL5boSoVpHhkkv3
4eoAeEiPdZLj5EZ7G2ArIK+gzhTlRQ1q4FKGpPPaFBSpqV/xbUb5UlAXQOnkHn3m
FVj+qYv87/WeY+Bm4sN3Ox8BhyaU7UAQ3LeZ7N1X01xxQe4wIAAE3JVLUCiHmZL+
qoCUtgYIFPgcg350QMUIWgxPXNGEncT921ne7nluI02V8pLUmClqXOsCwULw+PVO
ZCB7qOMxxMBoCUeL2Ll4oMpOSr5pJCpLN3tRA2s6P1KLs9TSrVhOk+7LX28NMUlI
usQ/nxLJID0RhAeFtPjyOCOscQBA53+NRjSCak7P4A5jX7ppmkcJECL+S0i3kXVU
y5Me5BbrU8973jZNv/ax6+ZK6TM8jWmimL6of6OrX7ZU6E2WqazzsFrLG3o2kySb
zlhSgJ81Cl4tv3SbYiYXnJExKQvzf83DYotox3f0fwv7xln1A2ZLplCb0O+l/AK0
YE0DS2FPxSAHi0iwMfW2nNHJrXcY3LLHD77gRgje4Eveubi2xxa+Nmk/hmhLdIET
iVDFanoCrMVIpQ59XWHkzdFmoHXHBV7oibVjGSO7ULSQ7MJ1Nz51phuDJSgAIU7A
0zrLnOrAj/dfrlEWRhCvAgbuwLZX1A2sjNjXoPOHbsPiy+lO1KF8/XY7
-----END CERTIFICATE-----

28
.cloud/remote_private.pem Normal file
View File

@@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCLQGcrdY1EBhh3
VuhEp4sMBxpmRAHjJES6+eDFjJP+cGdeYQbUKmqNPcBmG4dTAVlRWlANHA4/wKp5
cWkD2zgYEA5aUSiDS9uYxDx/L/+x1A9r/i+36benoga+Mdp6mY9d2EwSoaUakl/4
ezXHmC3OH83W41QL7mlsTcqJiQ93nCDVaxM/wwLugjKpQFX41qrmptr413Ep2fro
53p2TYvWE8XXBsHazb3fPbEDPNO6aTDlsjyizQz+BkGeUUbuHeESZLYky3sjYEZK
mCHZusTILP3bCd/GQP6Mx04davAXfJCw1FsCZnJLvp5/30m43uIQiOpRWHusPxp3
bwBlzoP7AgMBAAECggEABc0S2NDWp/A1Eeuqu2/bYnUW5XTzbmBmOJlzHE4evQMD
wcvHxz9V6jjM8uc2SP/z/Gg6kE4MTepoPHm8BtWzTYiFs7+Dyr+pqbPEujiB4Qk7
jaOTBO9iOXdauCh6UipjIMuDYWQe7UkOlHRvKVFIPzkJKZv8AvWiQ+L6iz/ZeoF7
CvDfgEb9bO7U/Tk7domjiyhPjqZOObmfTyJq1WDPOgbwdACkZBVFBlds4zdDrbCd
pX5ggW+dNWHWzmWpbAcNo9C4uT/4OST4HcyK0gkxXdqgyJeq8uhCirmdom8OMvGc
gFLtVIUFT388yh0b9RLnXFBC/2LJ99kfGYqIw1i3mQKBgQDA6eyppDUc+xOKgmqM
2uOreiQXFbnZNsc9AZlz30adwvIeFjJz3dQ1i4qDeVMjVLNAPPd1PdRlsbfZ6Ir9
H08A4P5vvOlwHPFOdSKE2mGcZwQcurqqFm9o1TB2K+RNzS5UiBPdmyvoMqG0d9Jr
VE8SdIw2UHQL4AgmeAkvG0U8PQKBgQC4yhCSMbiu9HofUrcw5mlSpqQA/SOgArCt
l6Ea/m2fQGenSjTT99SnUzBaUUhdeZSL7OUy3il7kOHc1tSAXwz65B8EwOVMnPdV
8L1mEkTd1MYQs3+eA2HUcrGlSVHqZXpgNXuRFvg031uisubGOLYG2sodhfdhurWi
yld69muslwKBgQC28mCc9HvmMvlyJoHzeHXmndtyBQmNxBQod8bWC01FuaWxAK3y
EdH4wY+nZTyBygaAChFHH0647lQDlDOTHsjmdXj18HqU9u2k0RLeWNeu4kcVE8SI
HuSiz2K4/qDxY37nbXEhfNGjz7holCV54adnQh3iOGQFCv3PtZBIGx2KpQKBgGY9
/x80fY/n2u4b0RowUlQVuaaGaUCuXF0gCVarMbIsa22HRGWHuVR/VcCTOqvlikhF
Yadcfq1Mw3tyLg99B+yFbZgutnBGZR9a3SBtuUbX5GL3PgQKsQVgFGR0hetgDG7R
CLaFc/2lG8mQnNlOJYDza2McbXzYVolk1TRGxdqvAoGBALegpkxgZDwGrWkt5Upm
dbIse9pHSCoS0K6dkbMXQ5juFRJ9JC8hVDTjV+Sl1kJX2Yxtzyvij1eoTSVdZhbk
GSHHG0EsN9MdWx+1hdd4YVZCfRLZ2nx+E1wBpwA6sXzycLbF2J9keNJy8MAj81XU
+tJtXq5VMEsVQei9jyADSYO5
-----END PRIVATE KEY-----

1
.ha_run.lock Normal file
View File

@@ -0,0 +1 @@
{"pid": 67, "version": 1, "ha_version": "2025.12.5", "start_ts": 1767163487.6344073}

7
.shopping_list.json Normal file
View File

@@ -0,0 +1,7 @@
[
{
"name": "Cancel AA card by July if there is still a fee",
"id": "2ab4a3762cb8457788c153501f797884",
"complete": false
}
]

91
.storage/alarmo.storage Normal file
View File

@@ -0,0 +1,91 @@
{
"version": 6,
"minor_version": 3,
"key": "alarmo.storage",
"data": {
"config": {
"code_arm_required": false,
"code_mode_change_required": false,
"code_disarm_required": true,
"code_format": "number",
"disarm_after_trigger": false,
"ignore_blocking_sensors_after_trigger": false,
"master": {
"enabled": true,
"name": "master"
},
"mqtt": {
"enabled": false,
"state_topic": "alarmo/state",
"state_payload": {},
"command_topic": "alarmo/command",
"command_payload": {},
"require_code": true,
"event_topic": "alarmo/event"
}
},
"areas": [
{
"area_id": "1765607864",
"name": "Alarmo",
"modes": {
"armed_away": {
"enabled": true,
"exit_time": 60,
"entry_time": 60,
"trigger_time": 1800
},
"armed_home": {
"enabled": true,
"exit_time": null,
"entry_time": null,
"trigger_time": 1800
},
"armed_vacation": {
"enabled": true,
"exit_time": 60,
"entry_time": 60,
"trigger_time": 1800
}
}
}
],
"sensors": [
{
"entity_id": "binary_sensor.night_light_1_motion",
"type": "motion",
"modes": [
"armed_away",
"armed_vacation"
],
"use_exit_delay": true,
"use_entry_delay": true,
"always_on": false,
"arm_on_close": false,
"allow_open": true,
"trigger_unavailable": false,
"auto_bypass": false,
"auto_bypass_modes": [],
"area": "1765607864",
"enabled": true,
"entry_delay": null
}
],
"users": [
{
"user_id": "1765608036",
"name": "Chris",
"enabled": true,
"code": "JDJiJDEwJFdIaUxManNTZTR0bmphcEd1ZkVpcHVIamlWSk1lai5rV2lvRjRFMnIvWGRWZG1QdUxTaFZ5",
"can_arm": true,
"can_disarm": true,
"is_override_code": false,
"code_format": "number",
"code_length": 4,
"area_limit": []
}
],
"automations": [],
"sensor_groups": []
}
}

8
.storage/alexa Normal file
View File

@@ -0,0 +1,8 @@
{
"version": 1,
"minor_version": 1,
"key": "alexa",
"data": {
"authorized": true
}
}

View File

@@ -0,0 +1,16 @@
{
"version": 1,
"minor_version": 1,
"key": "application_credentials",
"data": {
"items": [
{
"id": "google_555321766228_fr9p85c52459cgp2d5qbnpkvnh9rha58_apps_googleusercontent_com",
"domain": "google",
"client_id": "555321766228-fr9p85c52459cgp2d5qbnpkvnh9rha58.apps.googleusercontent.com",
"client_secret": "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",
"name": "Home Todo"
}
]
}
}

View File

@@ -0,0 +1,55 @@
{
"version": 1,
"minor_version": 2,
"key": "assist_pipeline.pipelines",
"data": {
"items": [
{
"conversation_engine": "conversation.home_assistant",
"conversation_language": "en",
"id": "01je2pa13016yx8r5pepwdhnza",
"language": "en",
"name": "Home Assistant",
"stt_engine": "stt.home_assistant_cloud",
"stt_language": "en-US",
"tts_engine": "tts.home_assistant_cloud",
"tts_language": "en-US",
"tts_voice": "AriaNeural",
"wake_word_entity": null,
"wake_word_id": null,
"prefer_local_intents": false
},
{
"conversation_engine": "conversation.home_assistant",
"conversation_language": "en",
"id": "01jfp2jjrck1222zqhdv41exwv",
"language": "en",
"name": "Home Assistant Cloud",
"stt_engine": "stt.home_assistant_cloud",
"stt_language": "en-US",
"tts_engine": "tts.home_assistant_cloud",
"tts_language": "en-US",
"tts_voice": "JennyNeural",
"wake_word_entity": null,
"wake_word_id": null,
"prefer_local_intents": false
},
{
"conversation_engine": "conversation.home_assistant",
"conversation_language": "en",
"id": "01k3wj61hbbjyfx892xzxx1wej",
"language": "en",
"name": "Local Assistant",
"stt_engine": "stt.speech_to_phrase",
"stt_language": "en",
"tts_engine": "tts.piper",
"tts_language": "en_US",
"tts_voice": "en_US-lessac-high",
"wake_word_entity": null,
"wake_word_id": null,
"prefer_local_intents": false
}
],
"preferred_item": "01je2pa13016yx8r5pepwdhnza"
}
}

287
.storage/auth Normal file
View File

@@ -0,0 +1,287 @@
{
"version": 1,
"minor_version": 1,
"key": "auth",
"data": {
"users": [
{
"id": "23e04b69df624a0fbff05ad27b5a1f0a",
"group_ids": [
"system-read-only"
],
"is_owner": false,
"is_active": true,
"name": "Home Assistant Content",
"system_generated": true,
"local_only": false
},
{
"id": "5e62410fa88e4aa7ab4ac7b9c4ba5dc1",
"group_ids": [
"system-admin"
],
"is_owner": false,
"is_active": true,
"name": "Supervisor",
"system_generated": true,
"local_only": false
},
{
"id": "4a285b563f6a4a54b657acf1926c0b5b",
"group_ids": [
"system-admin"
],
"is_owner": true,
"is_active": true,
"name": "Christopher Chasteen",
"system_generated": false,
"local_only": false
},
{
"id": "9ba78f5ab4e646758f3c093bd79ac9c8",
"group_ids": [
"system-admin"
],
"is_owner": false,
"is_active": true,
"name": "Gaby",
"system_generated": false,
"local_only": false
},
{
"id": "5f60cc00c4f249e3894b3148caccc231",
"group_ids": [
"system-admin"
],
"is_owner": false,
"is_active": true,
"name": "Home Assistant Cloud",
"system_generated": true,
"local_only": true
}
],
"groups": [
{
"id": "system-admin",
"name": "Administrators"
},
{
"id": "system-users",
"name": "Users"
},
{
"id": "system-read-only",
"name": "Read Only"
}
],
"credentials": [
{
"id": "52deb678d729425b9b028dddde7ac87a",
"user_id": "4a285b563f6a4a54b657acf1926c0b5b",
"auth_provider_type": "homeassistant",
"auth_provider_id": null,
"data": {
"username": "cchasteen99"
}
},
{
"id": "5d346781e9be4d4c8324f6b0df61f273",
"user_id": "9ba78f5ab4e646758f3c093bd79ac9c8",
"auth_provider_type": "homeassistant",
"auth_provider_id": null,
"data": {
"username": "gaby"
}
}
],
"refresh_tokens": [
{
"id": "6d75dc71b2114f498af0cf7dff68c828",
"user_id": "23e04b69df624a0fbff05ad27b5a1f0a",
"client_id": null,
"client_name": null,
"client_icon": null,
"token_type": "system",
"created_at": "2024-12-02T03:23:29.428978+00:00",
"access_token_expiration": 1800.0,
"token": "0497acf0545d56e8105d6e404fce1bdad392981b4392ef49aa7b094cc0d1dda54dd5a018ec7825c79af3d726ab7d87d9072c9afa5e8282c5a47feb13f6283a19",
"jwt_key": "ac27bccb4303fa53bf33e7d3284e244ceea94ba596049eb67279da5db44783dda6803b6c0e54941761f89121ea9df300d867228f47c0761f43432f16dd7cf379",
"last_used_at": null,
"last_used_ip": null,
"expire_at": null,
"credential_id": null,
"version": "2024.11.3"
},
{
"id": "fbb4f0f123ab4998861d270407b33e63",
"user_id": "5e62410fa88e4aa7ab4ac7b9c4ba5dc1",
"client_id": null,
"client_name": null,
"client_icon": null,
"token_type": "system",
"created_at": "2024-12-02T03:23:29.575481+00:00",
"access_token_expiration": 1800.0,
"token": "d3ac2bf124ba5c79f5e17369eb8ee4eff291f0a9be35fe217fb8d64f6ea63b6add2655f78de2508d66f182e7fa0cfb74d7dff01a35b588ae83408d49ace1b6e6",
"jwt_key": "459e08285d3d747c62642c5ae49ac21bc868c8e158c3a126b6fbfb5f5b14b1d9d4023851bebcabba69dc0d18ce003ccc60a4bb7a13b8e8b99145ef28e684c40f",
"last_used_at": "2026-01-31T05:20:55.136543+00:00",
"last_used_ip": "172.30.32.2",
"expire_at": null,
"credential_id": null,
"version": "2024.11.3"
},
{
"id": "704364b1f4e4428c995916b69cedfeef",
"user_id": "4a285b563f6a4a54b657acf1926c0b5b",
"client_id": "https://home-assistant.io/android",
"client_name": null,
"client_icon": null,
"token_type": "normal",
"created_at": "2024-12-22T00:24:53.536492+00:00",
"access_token_expiration": 1800.0,
"token": "837c0a76c5b2d1a95e39b397a50b1bb0c8057285e82775a42b3581c4305c677a285afe2700f6cea49f72f06bfb7b75ca0044aba658acf9bd6210b602dbe65ae4",
"jwt_key": "64176f4819042bbbe8dd895c73d187b3bcffa75e54ac4e5bbd7219061daac65dc78b33ac85b5e839fbcf02fd272e2dc3bc0b959000099489c3a44591baa73c15",
"last_used_at": "2025-12-17T05:08:12.117027+00:00",
"last_used_ip": "127.0.0.1",
"expire_at": 1773724092.117027,
"credential_id": "52deb678d729425b9b028dddde7ac87a",
"version": "2024.12.2"
},
{
"id": "4b3cc63775a442678e4960cd65d3a47c",
"user_id": "4a285b563f6a4a54b657acf1926c0b5b",
"client_id": "http://homeassistant.local:8123/",
"client_name": null,
"client_icon": null,
"token_type": "normal",
"created_at": "2025-05-20T03:23:23.159821+00:00",
"access_token_expiration": 1800.0,
"token": "ec1a87659e469a457b43d93f4c03d6e3f6ea4c56c6930bef2fc22cb82479bbb3c2bd10478acea51b66a0e9086869cdd0c8a9f7fd8d6c8d5fd0b09bf615951123",
"jwt_key": "4a66d37ba9ce8e808f1ad432f810389dab7949ab35cb6faed314b535332d61b93b8855ab149abf374c8ca2480ce3f98e232fb84d59e8fc6e3e71eb461a2b0915",
"last_used_at": "2025-12-17T02:50:00.545690+00:00",
"last_used_ip": "192.168.0.230",
"expire_at": 1773715800.54569,
"credential_id": "52deb678d729425b9b028dddde7ac87a",
"version": "2025.2.4"
},
{
"id": "ef12c765610f4858be358423fab344b3",
"user_id": "4a285b563f6a4a54b657acf1926c0b5b",
"client_id": "http://homeassistant.local:8123/",
"client_name": null,
"client_icon": null,
"token_type": "normal",
"created_at": "2025-09-20T23:31:23.642363+00:00",
"access_token_expiration": 1800.0,
"token": "79e79506bd209926860e0c3bb10f8d6d89742bf1241dae4ba8626282dca088468ed13d0a8356fbb4ebc47adad404b03d7a2c0153f25fba2d8e4725e8e4c23d87",
"jwt_key": "89d88eae84aa1047589c8a5a1aaf60ec7b2bb1b361ce7ce974d1573445f7480dbe7ad4af028ad1c06c679a8316981c21a9d34c7a141749d421fcbef87c50cfae",
"last_used_at": "2025-12-17T02:14:45.852533+00:00",
"last_used_ip": "192.168.0.123",
"expire_at": 1773713685.852533,
"credential_id": "52deb678d729425b9b028dddde7ac87a",
"version": "2025.9.3"
},
{
"id": "5749ca3473be40f6beac7a5eb1f47203",
"user_id": "4a285b563f6a4a54b657acf1926c0b5b",
"client_id": null,
"client_name": "Bruno",
"client_icon": null,
"token_type": "long_lived_access_token",
"created_at": "2025-11-23T18:53:14.867761+00:00",
"access_token_expiration": 315360000.0,
"token": "8df3a254dd2afd475424ca6da2ff92a757e9332b63f06d55c74e31f5f724654c2afdfaffdf929f23517038cfed23a8481ec3097d21717af8cf0bb4cc6ef47eb1",
"jwt_key": "8d62520fbd3d3ad9fe787dd929e8748f0df23845cffefe5e7c21151573aee8cb0b44c06a72c77d275b0806d1fd3af09a43e20a63dd2cb60db144aedd5a438651",
"last_used_at": "2025-11-23T18:53:14.867903+00:00",
"last_used_ip": null,
"expire_at": null,
"credential_id": null,
"version": "2025.11.3"
},
{
"id": "8a068792df4d49cca291252014104743",
"user_id": "4a285b563f6a4a54b657acf1926c0b5b",
"client_id": "http://192.168.0.107:8123/",
"client_name": null,
"client_icon": null,
"token_type": "normal",
"created_at": "2025-12-17T03:08:25.443606+00:00",
"access_token_expiration": 1800.0,
"token": "58718e7aae911fb523229e47f75fe0b75e1dff07f814863a2ac2715f27cce78aec7cadc48d627ab05e669269434ac1573aba0de7844f386b9ef48705a8a5cf4e",
"jwt_key": "79b3db5567d7f3cad9c9231c705cccbe0125e32fc8bf025ca501a40200ead8e35498b5a91d69c30af43f2ddd359148d39df1b6183b73afd167a7b171d4609e4c",
"last_used_at": "2026-01-31T04:35:48.406826+00:00",
"last_used_ip": "192.168.0.232",
"expire_at": 1777610148.406826,
"credential_id": "52deb678d729425b9b028dddde7ac87a",
"version": "2025.12.2"
},
{
"id": "24706e287c5841358cc7beb68c9067fa",
"user_id": "4a285b563f6a4a54b657acf1926c0b5b",
"client_id": "https://home.booksandbops.us/",
"client_name": null,
"client_icon": null,
"token_type": "normal",
"created_at": "2025-12-17T04:36:12.132968+00:00",
"access_token_expiration": 1800.0,
"token": "3cf55728f26dda23ec1867cc1342e70a2f651db4d46ac532b11e1927f05ed3639c5ece5488a03beefcb902a453e5270ff7233121cd29721f63e10636544bcaa3",
"jwt_key": "4e595a8f7a8cfff58f7826eeb52aa113a89a9d12f2881e11a1641032970e0f6e6c33ddb4f08a4350660777f2deeb93cb15172ca669647637287e2240d6135409",
"last_used_at": "2025-12-17T05:12:41.246519+00:00",
"last_used_ip": "127.0.0.1",
"expire_at": 1773724361.246519,
"credential_id": "52deb678d729425b9b028dddde7ac87a",
"version": "2025.12.3"
},
{
"id": "ccceefe94cac4657a15d0fa5994a916b",
"user_id": "4a285b563f6a4a54b657acf1926c0b5b",
"client_id": "https://home-assistant.io/android",
"client_name": null,
"client_icon": null,
"token_type": "normal",
"created_at": "2025-12-17T05:23:10.854950+00:00",
"access_token_expiration": 1800.0,
"token": "5c55e3e9992dcbf2fc27f574e587577b0f5927b01f34c89d125f8476c4b55f31c4a02ec0405113f628a4abebb52532829e3bab0123208d83128f299c12c34d4c",
"jwt_key": "2ff0f6cfdcf1d99c514c29a814d47bc9515912493475e351c5682fd47f8fbf1924e35c5fb9174fef1b9254bab385a5d4b7238bc834d0c58c1543383dba62d0af",
"last_used_at": "2026-01-30T04:32:28.151064+00:00",
"last_used_ip": "192.168.0.194",
"expire_at": 1777523548.151064,
"credential_id": "52deb678d729425b9b028dddde7ac87a",
"version": "2025.12.3"
},
{
"id": "f9e9618e8af944758381e2da407fff7b",
"user_id": "4a285b563f6a4a54b657acf1926c0b5b",
"client_id": null,
"client_name": "python",
"client_icon": null,
"token_type": "long_lived_access_token",
"created_at": "2025-12-31T06:02:15.664635+00:00",
"access_token_expiration": 315360000.0,
"token": "2612d35c2bc27aef742a7bfd2e9deda1d46e8e77832d3a95c4b44e7b98e89883b5b47600a6890c2d9da6192354c07b3e1e71de89db1995a620237436c8f1797f",
"jwt_key": "657d49e9c6ef506c4785fe1e84be9a54a7b4333394f49545160d1f9d948a1a9452c9e1bd9a12d753fbf3bcf1fe0f9082b05df5bbe137e7735121c424704853d0",
"last_used_at": "2025-12-31T06:02:15.664775+00:00",
"last_used_ip": null,
"expire_at": null,
"credential_id": null,
"version": "2025.12.3"
},
{
"id": "477f4babaa044ef09d2145eb5fc7cf41",
"user_id": "9ba78f5ab4e646758f3c093bd79ac9c8",
"client_id": "https://home-assistant.io/android",
"client_name": null,
"client_icon": null,
"token_type": "normal",
"created_at": "2024-12-23T23:28:01.038637+00:00",
"access_token_expiration": 1800.0,
"token": "a55121810ab946f004439dbba500e15ffe8862964ef4f7536f3279f451a8c23c3f331d0859893b9bf1fb1c8f28eedc29e28eeb2265c05cb37b1e2203eeab3770",
"jwt_key": "19dc7365cc33b800ddd124d9fc04c7af0fcc42c4d67d06265f9b0748f1cb5c6ee5b9c64cb2709b66cca269a3a33240fec98a03b9abd5dafbe6d72c39de973b2e",
"last_used_at": "2026-01-29T04:35:39.896016+00:00",
"last_used_ip": "127.0.0.1",
"expire_at": 1777437339.896016,
"credential_id": "5d346781e9be4d4c8324f6b0df61f273",
"version": "2024.12.2"
}
]
}
}

10
.storage/auth_module.totp Normal file
View File

@@ -0,0 +1,10 @@
{
"version": 1,
"minor_version": 1,
"key": "auth_module.totp",
"data": {
"users": {
"4a285b563f6a4a54b657acf1926c0b5b": "GMSADWNDZ45UIQHOHHQUEPXMEQOG7HP3"
}
}
}

View File

@@ -0,0 +1,17 @@
{
"version": 1,
"minor_version": 1,
"key": "auth_provider.homeassistant",
"data": {
"users": [
{
"username": "cchasteen99",
"password": "JDJiJDEyJGtYTDBaT3I0SXFKR3lEdEpRZEx4L2VrenBYS0NzdVlkSlBNUXBtOWtHSE85STdSZUJNeUxD"
},
{
"username": "gaby",
"password": "JDJiJDEyJGVtSkMwOTgzOWFubXhLMWlUL2JtUC45eUtCaFNodENySzhEWnUuV1EudHJWY3paekg4Ny5t"
}
]
}
}

54
.storage/backup Normal file
View File

@@ -0,0 +1,54 @@
{
"version": 1,
"minor_version": 7,
"key": "backup",
"data": {
"backups": [
{
"backup_id": "83e9a9da",
"failed_addons": [],
"failed_agent_ids": [],
"failed_folders": []
},
{
"backup_id": "66dc2464",
"failed_addons": [],
"failed_agent_ids": [],
"failed_folders": []
},
{
"backup_id": "d4c81911",
"failed_addons": [],
"failed_agent_ids": [],
"failed_folders": []
}
],
"config": {
"agents": {},
"automatic_backups_configured": false,
"create_backup": {
"agent_ids": [
"hassio.local",
"cloud.cloud"
],
"include_addons": [],
"include_all_addons": true,
"include_database": true,
"include_folders": null,
"name": null,
"password": "D01G-CLSG-9MS5-2J8O-LFID-EE4G-2Z36"
},
"last_attempted_automatic_backup": "2026-01-30T05:44:31.001585-06:00",
"last_completed_automatic_backup": "2026-01-30T05:48:51.732548-06:00",
"retention": {
"copies": 3,
"days": null
},
"schedule": {
"days": [],
"recurrence": "daily",
"time": null
}
}
}
}

View File

@@ -0,0 +1,6 @@
{
"version": 1,
"minor_version": 1,
"key": "bluetooth.passive_update_processor",
"data": {}
}

10
.storage/camera Normal file
View File

@@ -0,0 +1,10 @@
{
"version": 1,
"minor_version": 1,
"key": "camera",
"data": {
"camera.catcamfeed": {
"preload_stream": false
}
}
}

71
.storage/cloud Normal file
View File

@@ -0,0 +1,71 @@
{
"version": 1,
"minor_version": 4,
"key": "cloud",
"data": {
"alexa_default_expose": [
"climate",
"cover",
"fan",
"humidifier",
"light",
"lock",
"scene",
"script",
"sensor",
"switch",
"vacuum",
"water_heater"
],
"alexa_entity_configs": {},
"alexa_settings_version": 3,
"cloud_user": "5f60cc00c4f249e3894b3148caccc231",
"cloudhooks": {
"bf13749f00cc14d1be9198c33ec3754dadc7c684ec69a5ac06c708cdbce4a20e": {
"webhook_id": "bf13749f00cc14d1be9198c33ec3754dadc7c684ec69a5ac06c708cdbce4a20e",
"cloudhook_id": "7526246479ee4c2dbf2e0c057a22b2a4",
"cloudhook_url": "https://hooks.nabu.casa/gAAAAABnafIBY2GmvegZf3g5cfAL5_TWiRc-ryrGIvcXydAsQn0DKASAUUhVCxc0uri_ochooiy863WV5_9y2eic6eBy631MHy_Efy_FVUnQuJ3LB7pkxJ3E8T7LboGg0vhjz6fKGy1c0iX6CJAtGsKZCx_He8fJIwVztFXB6HWhukLJo6vdZQI=",
"managed": true
},
"73288d9f71a8eaa618fb8a78b3f3e54600ca53eebf8a01b4ce22a3b81640e3cf": {
"webhook_id": "73288d9f71a8eaa618fb8a78b3f3e54600ca53eebf8a01b4ce22a3b81640e3cf",
"cloudhook_id": "befff0f1fc3749f99b93f1b11f98db97",
"cloudhook_url": "https://hooks.nabu.casa/gAAAAABnhH0jyCEgeZgbKT1tTb1wHU0JZyftWTdCEAFTF623sDfroC53AG0dx-CwE-Cl1Voj8Bu3WSODvWIMa-pMv90bJcg-yTNxVhTkFdD_69IZJozer7NcvxCzROdaFz_ZM9tSx2tOWXVzTVgy8CnwmBYpUwaGzxibZK9sBbsG0FqtPsslpCU=",
"managed": true
},
"a308d88905706ccc063aa2ae8aafa5012c3eafc67b0fe95f8aafd77147adc111": {
"webhook_id": "a308d88905706ccc063aa2ae8aafa5012c3eafc67b0fe95f8aafd77147adc111",
"cloudhook_id": "8ae61e69cac543eb8f3017012f420cc6",
"cloudhook_url": "https://hooks.nabu.casa/gAAAAABpQj4_zy_6oerTAckSwqDXSzct0oOHfkICEPBrPut7__ncO0UosZk7v3MUwp7Brm8GQ7FAnRfyvOB9-REPWNIpiYFq1lZF9vfAcuA0cmB2yElVGlwl1mtZqQV2MKmvDRANqPPCWsZjuNIJ6vYFOcoiFcUvBPFQfXFIgG08wRJs7VTO7So=",
"managed": true
}
},
"alexa_enabled": true,
"google_enabled": true,
"remote_enabled": true,
"cloud_ice_servers_enabled": true,
"google_connected": true,
"google_default_expose": [
"climate",
"cover",
"fan",
"humidifier",
"light",
"lock",
"scene",
"script",
"sensor",
"switch",
"vacuum",
"water_heater"
],
"google_entity_configs": {},
"google_settings_version": 3,
"google_local_webhook_id": "54c1728df63c4d56f80caa9c1a88ec925400c37b3a5d5cac3ed26acbe2b8efbc",
"instance_id": "c6aa15133c224fb28b425e802909af33",
"google_secure_devices_pin": null,
"remote_domain": "dlwgsr2kwoovej41itvjkc7cabtymfp3.ui.nabu.casa",
"remote_allow_remote_enable": true,
"username": "3afe0017-e1eb-4198-abfb-58eded9a84db"
}
}

10
.storage/core.analytics Normal file
View File

@@ -0,0 +1,10 @@
{
"version": 1,
"minor_version": 1,
"key": "core.analytics",
"data": {
"onboarded": true,
"preferences": {},
"uuid": null
}
}

126
.storage/core.area_registry Normal file
View File

@@ -0,0 +1,126 @@
{
"version": 1,
"minor_version": 9,
"key": "core.area_registry",
"data": {
"areas": [
{
"aliases": [],
"floor_id": null,
"humidity_entity_id": null,
"icon": null,
"id": "android_shortcuts",
"labels": [],
"name": "Android Shortcuts",
"picture": null,
"temperature_entity_id": null,
"created_at": "2024-12-23T23:55:08.472924+00:00",
"modified_at": "2024-12-23T23:55:08.472926+00:00"
},
{
"aliases": [],
"floor_id": null,
"humidity_entity_id": null,
"icon": "mdi:bed",
"id": "bedroom",
"labels": [],
"name": "Bedroom",
"picture": null,
"temperature_entity_id": null,
"created_at": "2024-12-02T03:25:15.379030+00:00",
"modified_at": "2024-12-23T23:21:01.258001+00:00"
},
{
"aliases": [],
"floor_id": null,
"humidity_entity_id": null,
"icon": null,
"id": "garage",
"labels": [],
"name": "Garage",
"picture": null,
"temperature_entity_id": null,
"created_at": "2024-12-02T03:27:10.231632+00:00",
"modified_at": "2024-12-02T03:27:10.231639+00:00"
},
{
"aliases": [],
"floor_id": null,
"humidity_entity_id": null,
"icon": null,
"id": "groupmembers",
"labels": [],
"name": "GroupMembers",
"picture": null,
"temperature_entity_id": null,
"created_at": "2024-12-29T05:59:18.684703+00:00",
"modified_at": "2024-12-29T05:59:18.684711+00:00"
},
{
"aliases": [],
"floor_id": null,
"humidity_entity_id": null,
"icon": null,
"id": "kitchen",
"labels": [],
"name": "Kitchen",
"picture": null,
"temperature_entity_id": null,
"created_at": "2024-12-02T03:25:15.379006+00:00",
"modified_at": "2024-12-02T03:25:15.379007+00:00"
},
{
"aliases": [],
"floor_id": null,
"humidity_entity_id": null,
"icon": null,
"id": "living_room",
"labels": [],
"name": "Living Room",
"picture": null,
"temperature_entity_id": null,
"created_at": "2024-12-02T03:25:15.378954+00:00",
"modified_at": "2024-12-02T03:25:15.378955+00:00"
},
{
"aliases": [],
"floor_id": null,
"humidity_entity_id": null,
"icon": "mdi:bathtub",
"id": "master_bath",
"labels": [],
"name": "Master Bath",
"picture": null,
"temperature_entity_id": null,
"created_at": "2024-12-03T04:10:12.010435+00:00",
"modified_at": "2024-12-03T04:10:12.010443+00:00"
},
{
"aliases": [],
"floor_id": null,
"humidity_entity_id": null,
"icon": null,
"id": "misc",
"labels": [],
"name": "Misc",
"picture": null,
"temperature_entity_id": null,
"created_at": "2024-12-23T23:24:34.611993+00:00",
"modified_at": "2024-12-23T23:24:34.611996+00:00"
},
{
"aliases": [],
"floor_id": null,
"humidity_entity_id": null,
"icon": null,
"id": "unused",
"labels": [],
"name": "Unused",
"picture": null,
"temperature_entity_id": null,
"created_at": "2025-02-17T05:52:55.864141+00:00",
"modified_at": "2025-02-17T05:52:55.864151+00:00"
}
]
}
}

View File

@@ -0,0 +1,43 @@
{
"version": 1,
"minor_version": 2,
"key": "core.category_registry",
"data": {
"categories": {
"scene": [
{
"category_id": "01JG8GZ8K9SG3SH5Q5T5DX0DQ3",
"created_at": "2024-12-29T06:17:04.873929+00:00",
"icon": "mdi:coffin",
"modified_at": "2024-12-29T06:17:04.873938+00:00",
"name": "z - unused"
},
{
"category_id": "01JG8GZSHMPFN7HM48G527VR0Y",
"created_at": "2024-12-29T06:17:22.228319+00:00",
"icon": null,
"modified_at": "2024-12-29T06:17:22.228328+00:00",
"name": "bedroom"
}
],
"automation": [
{
"category_id": "01KAQRTES3MQ0ZR5CFVZKRPEF0",
"created_at": "2025-11-23T07:10:59.875598+00:00",
"icon": "mdi:bell-cancel",
"modified_at": "2025-11-23T07:10:59.875607+00:00",
"name": "unmonitored"
}
],
"helpers": [
{
"category_id": "01KFV8JVCQG55VB4C0KD6AWHEB",
"created_at": "2026-01-25T19:02:40.279110+00:00",
"icon": null,
"modified_at": "2026-01-25T19:02:40.279119+00:00",
"name": "NH"
}
]
}
}
}

19
.storage/core.config Normal file
View File

@@ -0,0 +1,19 @@
{
"version": 1,
"minor_version": 4,
"key": "core.config",
"data": {
"latitude": 30.2711286,
"longitude": -97.7436995,
"elevation": 0,
"unit_system_v2": "us_customary",
"location_name": "Home",
"time_zone": "America/Chicago",
"external_url": null,
"internal_url": "http://192.168.0.107:8123",
"currency": "USD",
"country": "US",
"language": "en",
"radius": 100
}
}

View File

@@ -0,0 +1,52 @@
{
"version": 1,
"minor_version": 5,
"key": "core.config_entries",
"data": {
"entries": [
{"created_at":"2024-12-02T03:23:30.334426+00:00","data":{},"disabled_by":null,"discovery_keys":{},"domain":"sun","entry_id":"01JE2PA14YS95P1MVHDGHSQEB2","minor_version":1,"modified_at":"2024-12-02T03:23:30.334430+00:00","options":{},"pref_disable_new_entities":false,"pref_disable_polling":false,"source":"import","subentries":[],"title":"Sun","unique_id":null,"version":1},
{"created_at":"2024-12-02T03:23:30.523058+00:00","data":{},"disabled_by":null,"discovery_keys":{},"domain":"hassio","entry_id":"01JE2PA1AVW34BP3R20RJ09CVZ","minor_version":1,"modified_at":"2024-12-02T03:23:30.523060+00:00","options":{},"pref_disable_new_entities":false,"pref_disable_polling":false,"source":"system","subentries":[],"title":"Supervisor","unique_id":null,"version":1},
{"created_at":"2024-12-02T03:23:30.523216+00:00","data":{},"disabled_by":null,"discovery_keys":{},"domain":"go2rtc","entry_id":"01JE2PA1AV7BK003CH3AYYVXCB","minor_version":1,"modified_at":"2024-12-02T03:23:30.523218+00:00","options":{},"pref_disable_new_entities":false,"pref_disable_polling":false,"source":"system","subentries":[],"title":"go2rtc","unique_id":null,"version":1},
{"created_at":"2024-12-02T03:24:01.579421+00:00","data":{"integration_created_addon":true,"url":"ws://core-matter-server:5580/ws","use_addon":true},"disabled_by":"user","discovery_keys":{"zeroconf":[{"domain":"zeroconf","key":["_matter._tcp.local.","9BB562B20DADE416-04184D40477BA3E1._matter._tcp.local."],"version":1}]},"domain":"matter","entry_id":"01JE2PAZNB0X2F1Q1EFCC0KVR3","minor_version":1,"modified_at":"2024-12-02T03:24:01.579432+00:00","options":{},"pref_disable_new_entities":false,"pref_disable_polling":false,"source":"zeroconf","subentries":[],"title":"Matter","unique_id":null,"version":1},
{"created_at":"2024-12-02T03:25:41.074867+00:00","data":{"language":"en","tld":"com"},"disabled_by":null,"discovery_keys":{},"domain":"google_translate","entry_id":"01JE2PE0TJRASHVFFAN0FZQMCB","minor_version":1,"modified_at":"2024-12-02T03:25:41.074869+00:00","options":{},"pref_disable_new_entities":false,"pref_disable_polling":false,"source":"onboarding","subentries":[],"title":"Google Translate text-to-speech","unique_id":null,"version":1},
{"created_at":"2024-12-02T03:25:41.076780+00:00","data":{},"disabled_by":null,"discovery_keys":{},"domain":"shopping_list","entry_id":"01JE2PE0TMFCS836DRMGVW5Q7X","minor_version":1,"modified_at":"2024-12-02T03:25:41.076782+00:00","options":{},"pref_disable_new_entities":false,"pref_disable_polling":false,"source":"onboarding","subentries":[],"title":"Shopping list","unique_id":"shopping_list","version":1},
{"created_at":"2024-12-02T03:25:41.085989+00:00","data":{"track_home":true},"disabled_by":null,"discovery_keys":{},"domain":"met","entry_id":"01JE2PE0TXTYWZGJSBJ1EW6XEF","minor_version":1,"modified_at":"2024-12-02T03:25:41.085992+00:00","options":{},"pref_disable_new_entities":false,"pref_disable_polling":false,"source":"onboarding","subentries":[],"title":"Home","unique_id":null,"version":1},
{"created_at":"2024-12-02T03:25:41.129580+00:00","data":{},"disabled_by":"user","discovery_keys":{},"domain":"radio_browser","entry_id":"01JE2PE0W9W5S3HFZ4PFK720ZP","minor_version":1,"modified_at":"2024-12-02T03:25:41.129583+00:00","options":{},"pref_disable_new_entities":false,"pref_disable_polling":false,"source":"onboarding","subentries":[],"title":"Radio Browser","unique_id":null,"version":1},
{"created_at":"2024-12-02T03:57:02.029143+00:00","data":{"device":{"baudrate":115200,"flow_control":null,"path":"/dev/serial/by-id/usb-Itead_Sonoff_Zigbee_3.0_USB_Dongle_Plus_V2_925aeb2ca41fef11bc674ad0639e525b-if00-port0"},"radio_type":"ezsp"},"disabled_by":null,"discovery_keys":{},"domain":"zha","entry_id":"01JE2R7DPDQ8PCN6MMVWNFMDFN","minor_version":1,"modified_at":"2025-11-09T07:27:07.991390+00:00","options":{},"pref_disable_new_entities":false,"pref_disable_polling":false,"source":"usb","subentries":[],"title":"Sonoff Zigbee 3.0 USB Dongle Plus V2","unique_id":"epid=e2:79:42:76:51:67:88:a3","version":5},
{"created_at":"2024-12-02T04:23:05.406722+00:00","data":{"token":"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"},"disabled_by":null,"discovery_keys":{},"domain":"hacs","entry_id":"01JE2SQ4DY1D75FFMD9PGMNER0","minor_version":1,"modified_at":"2025-01-08T17:44:25.507697+00:00","options":{"appdaemon":false,"country":"ALL","experimental":true,"sidepanel_icon":"hacs:hacs","sidepanel_title":"HACS"},"pref_disable_new_entities":false,"pref_disable_polling":false,"source":"user","subentries":[],"title":"","unique_id":null,"version":1},
{"created_at":"2024-12-13T03:18:32.293923+00:00","data":{"api_key":"30cf6a5c-b6d9-4378-bae9-beb5eca4e5e2","delay":10},"disabled_by":"user","discovery_keys":{},"domain":"govee","entry_id":"01JEZ0CV35V3GN0H3TXQCRZCKH","minor_version":1,"modified_at":"2025-01-08T15:36:13.275027+00:00","options":{"api_key":"30cf6a5c-b6d9-4378-bae9-beb5eca4e5e2","delay":10,"disable_attribute_updates":"","offline_is_off":false,"use_assumed_state":true},"pref_disable_new_entities":false,"pref_disable_polling":false,"source":"user","subentries":[],"title":"govee","unique_id":null,"version":1},
{"created_at":"2024-12-22T02:19:08.905189+00:00","data":{},"disabled_by":null,"discovery_keys":{},"domain":"cloud","entry_id":"01JFP2JJ79E0W23JMRAX0PNWV2","minor_version":1,"modified_at":"2024-12-22T02:19:08.905210+00:00","options":{},"pref_disable_new_entities":false,"pref_disable_polling":false,"source":"system","subentries":[],"title":"Home Assistant Cloud","unique_id":null,"version":1},
{"created_at":"2024-12-22T03:14:09.105589+00:00","data":{"host":"192.168.0.1","location":"http://192.168.0.1:1900/crjig/rootDesc.xml","mac_address":"40:ed:00:b0:72:a8","original_udn":"uuid:4c80b450-a7bf-432b-a26e-f6767135d368","st":"urn:schemas-upnp-org:device:InternetGatewayDevice:1","udn":"uuid:4c80b450-a7bf-432b-a26e-f6767135d368"},"disabled_by":null,"discovery_keys":{"ssdp":[{"domain":"ssdp","key":"uuid:4c80b450-a7bf-432b-a26e-f6767135d368","version":1}]},"domain":"upnp","entry_id":"01JFP5Q92H27CVE2AF3TK6ZSE4","minor_version":1,"modified_at":"2024-12-22T03:14:09.105595+00:00","options":{"force_poll":false},"pref_disable_new_entities":false,"pref_disable_polling":false,"source":"ignore","subentries":[],"title":"Archer AXE75","unique_id":"uuid:4c80b450-a7bf-432b-a26e-f6767135d368::urn:schemas-upnp-org:device:InternetGatewayDevice:1","version":1},
{"created_at":"2024-12-22T03:14:11.036716+00:00","data":{"host":"192.168.0.178"},"disabled_by":null,"discovery_keys":{"zeroconf":[{"domain":"zeroconf","key":["_printer._tcp.local.","Brother HL-L3290CDW series._printer._tcp.local."],"version":1}]},"domain":"brother","entry_id":"01JFP5QAYW3YSVDEJFCRQF18PH","minor_version":1,"modified_at":"2024-12-22T08:30:28.457495+00:00","options":{},"pref_disable_new_entities":false,"pref_disable_polling":false,"source":"ignore","subentries":[],"title":"HL-L3290CDW U65177L2N135900","unique_id":"u65177l2n135900","version":1},
{"created_at":"2024-12-22T04:14:43.550917+00:00","data":{"password":"idvh3PQTjzAsrx","username":"cchas@att.net"},"disabled_by":null,"discovery_keys":{},"domain":"vesync","entry_id":"01JFP966AYW48GQ7FPY42M80G0","minor_version":2,"modified_at":"2025-09-14T05:33:48.930142+00:00","options":{},"pref_disable_new_entities":false,"pref_disable_polling":false,"source":"user","subentries":[],"title":"cchas@att.net","unique_id":null,"version":1},
{"created_at":"2024-12-23T21:08:53.009581+00:00","data":{"host":"192.168.0.178","name":"Brother HL-L3290CDW series"},"disabled_by":null,"discovery_keys":{"zeroconf":[{"domain":"zeroconf","key":["_ipp._tcp.local.","Brother HL-L3290CDW series._ipp._tcp.local."],"version":1}]},"domain":"ipp","entry_id":"01JFTNKWMH2NZWD4AZNTAQ1RBF","minor_version":1,"modified_at":"2025-06-18T00:40:07.430257+00:00","options":{},"pref_disable_new_entities":false,"pref_disable_polling":false,"source":"ignore","subentries":[],"title":"Brother HL-L3290CDW series","unique_id":"e3248000-80ce-11db-8000-ac50de540638","version":1},
{"created_at":"2024-12-23T23:28:01.327388+00:00","data":{"app_data":{"push_token":"dlY0ndudT9WS0qU5RQFHyA:APA91bFtFZj-urS-AFTCwqMvxi8UT1pC-A5-_Jg5nXiyEvLV_5UJ15fKwsSJ0kxaf4qNA2nWcwrSfvh-H124yYPprFPWWRxs291pzyhfOTWs17XhtsNMsVc","push_url":"https://mobile-apps.home-assistant.io/api/sendPush/android/v1","push_websocket_channel":true},"app_id":"io.homeassistant.companion.android","app_name":"Home Assistant","app_version":"2025.11.4-full (19134)","cloudhook_url":"https://hooks.nabu.casa/gAAAAABnafIBY2GmvegZf3g5cfAL5_TWiRc-ryrGIvcXydAsQn0DKASAUUhVCxc0uri_ochooiy863WV5_9y2eic6eBy631MHy_Efy_FVUnQuJ3LB7pkxJ3E8T7LboGg0vhjz6fKGy1c0iX6CJAtGsKZCx_He8fJIwVztFXB6HWhukLJo6vdZQI=","device_id":"04816a43418fc7ed","device_name":"Gabys Phone","manufacturer":"samsung","model":"SM-G781U","os_name":"Android","os_version":"33","supports_encryption":false,"user_id":"9ba78f5ab4e646758f3c093bd79ac9c8","webhook_id":"bf13749f00cc14d1be9198c33ec3754dadc7c684ec69a5ac06c708cdbce4a20e"},"disabled_by":null,"discovery_keys":{},"domain":"mobile_app","entry_id":"01JFTXJN9FW8ZK5WNKFDK1Y69F","minor_version":1,"modified_at":"2025-11-30T05:40:04.580113+00:00","options":{},"pref_disable_new_entities":false,"pref_disable_polling":false,"source":"registration","subentries":[],"title":"Gabys Phone","unique_id":"io.homeassistant.companion.android-04816a43418fc7ed","version":1},
{"created_at":"2024-12-29T05:57:06.238349+00:00","data":{},"disabled_by":null,"discovery_keys":{},"domain":"group","entry_id":"01JG8FTP1Y3ZDPG4QRNTMTM78B","minor_version":1,"modified_at":"2025-04-09T15:53:09.458251+00:00","options":{"all":false,"entities":["light.lamp_upper_light_5","light.lamp_top_light"],"group_type":"light","hide_members":false,"name":"Lamp Lights"},"pref_disable_new_entities":false,"pref_disable_polling":false,"source":"user","subentries":[],"title":"Lamp Lights","unique_id":null,"version":1},
{"created_at":"2024-12-29T05:57:38.517408+00:00","data":{},"disabled_by":null,"discovery_keys":{},"domain":"group","entry_id":"01JG8FVNJNPSSM4GF1GFC8JR4N","minor_version":1,"modified_at":"2025-04-09T15:45:37.394760+00:00","options":{"all":false,"entities":["light.master_bath_bulb_1_light","light.master_bath_bulb_2_light","light.master_bath_bulb_3_light_4","light.master_bath_bulb_4_light_3"],"group_type":"light","hide_members":false,"name":"Master Bathroom Lights"},"pref_disable_new_entities":false,"pref_disable_polling":false,"source":"user","subentries":[],"title":"Master Bathroom Lights","unique_id":null,"version":1},
{"created_at":"2024-12-29T06:00:04.933481+00:00","data":{},"disabled_by":null,"discovery_keys":{},"domain":"group","entry_id":"01JG8G04J5WK0KFJ9T2PYKPCH2","minor_version":1,"modified_at":"2024-12-29T06:00:04.933489+00:00","options":{"entities":["light.bedroom_ceiling_1","light.bedroom_ceiling_2","light.bedroom_ceiling_3","light.bedroom_ceiling_4"],"group_type":"light","hide_members":false,"name":"Bedroom Ceiling Lights"},"pref_disable_new_entities":false,"pref_disable_polling":false,"source":"user","subentries":[],"title":"Bedroom Ceiling Lights","unique_id":null,"version":1},
{"created_at":"2025-01-08T23:42:37.542830+00:00","data":{},"disabled_by":null,"discovery_keys":{"zeroconf":[{"domain":"zeroconf","key":["_googlecast._tcp.local.","Smart-TV-Pro-bb692722b9fab739e50351095f302eaf._googlecast._tcp.local."],"version":1}]},"domain":"cast","entry_id":"01JH44RWZ6B46DWN2WHN4MWBJ9","minor_version":1,"modified_at":"2025-01-08T23:42:37.542849+00:00","options":{},"pref_disable_new_entities":false,"pref_disable_polling":false,"source":"ignore","subentries":[],"title":"cast","unique_id":"cast","version":1},
{"created_at":"2025-01-13T02:40:35.981345+00:00","data":{"app_data":{"push_token":"fwjGG3PMS1eL2ABHS1b-ks:APA91bFMsyNgQ1CCWxuopOTSkvBiWLFbXRdLispA2uvie9JG-0HpWWfpH73j5kTis3PD931fwornCgQcOxnNbeJUL90314ttWaT86cM2Cx-uwoPxnFYHbfs","push_url":"https://mobile-apps.home-assistant.io/api/sendPush/android/v1","push_websocket_channel":true},"app_id":"io.homeassistant.companion.android","app_name":"Home Assistant","app_version":"2025.1.2-full (14946)","cloudhook_url":"https://hooks.nabu.casa/gAAAAABnhH0jyCEgeZgbKT1tTb1wHU0JZyftWTdCEAFTF623sDfroC53AG0dx-CwE-Cl1Voj8Bu3WSODvWIMa-pMv90bJcg-yTNxVhTkFdD_69IZJozer7NcvxCzROdaFz_ZM9tSx2tOWXVzTVgy8CnwmBYpUwaGzxibZK9sBbsG0FqtPsslpCU=","device_id":"7d87873685c6e6fb","device_name":"Pixel 3a","manufacturer":"Google","model":"Pixel 3a","os_name":"Android","os_version":"32","supports_encryption":false,"user_id":"4a285b563f6a4a54b657acf1926c0b5b","webhook_id":"73288d9f71a8eaa618fb8a78b3f3e54600ca53eebf8a01b4ce22a3b81640e3cf"},"disabled_by":null,"discovery_keys":{},"domain":"mobile_app","entry_id":"01JHERHN4DSFB0VPVXT2KS52TW","minor_version":1,"modified_at":"2025-01-13T02:40:35.981348+00:00","options":{},"pref_disable_new_entities":false,"pref_disable_polling":false,"source":"registration","subentries":[],"title":"Pixel 3a","unique_id":"io.homeassistant.companion.android-7d87873685c6e6fb","version":1},
{"created_at":"2025-01-26T07:14:28.292950+00:00","data":{"auth_implementation":"google_555321766228_fr9p85c52459cgp2d5qbnpkvnh9rha58_apps_googleusercontent_com","credential_type":"web_auth","token":{"access_token":"ya29.a0AUMWg_IsQlP8FeiYvyTC6OkZ2A_yswtXZi3VjF39ovj5PCm_JY8-XJ5MwhkYAA3ogF1oz4p98jPskWs4nM8ZOyEKCllcPznzxcR9DbHGLg_aMgn2bq-05Up5i7h6ipNBqaXseQ7YuXmJIC2g2MlzNLhIY_osQuAAFevwf0CzlhYY2LzowfwgYJznsVnjL8vaMfWdNXHcXwaCgYKAdUSARASFQHGX2MiyqiGSn1vxkz7Ot9e8yO-Wg0209","expires_at":1769837426.4982033,"expires_in":3599,"refresh_token":"1//0f57iGZxHVwYUCgYIARAAGA8SNwF-L9Irq-vJpwdGsu6dOhhVNbp2gEEzv96h8QD2Bt6XYMOIM9zUZPwuuvFR9iAFk4TS6JIuDR4","scope":"https://www.googleapis.com/auth/calendar","token_type":"Bearer"}},"disabled_by":null,"discovery_keys":{},"domain":"google","entry_id":"01JJGQCFA4BSXH2EK2EN1ZD3AA","minor_version":1,"modified_at":"2026-01-31T04:30:27.498280+00:00","options":{"calendar_access":"read_write"},"pref_disable_new_entities":false,"pref_disable_polling":false,"source":"user","subentries":[],"title":"chastifur@gmail.com","unique_id":"chastifur@gmail.com","version":1},
{"created_at":"2025-03-28T19:35:31.169045+00:00","data":{},"disabled_by":null,"discovery_keys":{},"domain":"group","entry_id":"01JQF417309ZB7P11CXKJSE6J0","minor_version":1,"modified_at":"2025-03-28T19:35:31.169061+00:00","options":{"entities":["switch.stair_light_lower_switch","switch.stair_light_upper_switch_2"],"group_type":"switch","hide_members":false,"name":"Stair Group a"},"pref_disable_new_entities":false,"pref_disable_polling":false,"source":"user","subentries":[],"title":"Stair Group a","unique_id":null,"version":1},
{"created_at":"2025-03-28T19:35:49.117769+00:00","data":{},"disabled_by":null,"discovery_keys":{},"domain":"group","entry_id":"01JQF41RKXQK3WPFPRNNA9H9BF","minor_version":1,"modified_at":"2025-10-02T23:46:55.056653+00:00","options":{"all":false,"entities":["switch.upper_landing_a_switch","switch.upper_landing_b_switch"],"group_type":"switch","hide_members":false,"name":"Stair Group b"},"pref_disable_new_entities":false,"pref_disable_polling":false,"source":"user","subentries":[],"title":"Stair Group b","unique_id":null,"version":1},
{"created_at":"2025-04-07T02:38:30.398376+00:00","data":{},"disabled_by":null,"discovery_keys":{},"domain":"wake_on_lan","entry_id":"01JR71T6FYKBW7EQ1SVTRH11BV","minor_version":1,"modified_at":"2025-04-07T02:38:30.398379+00:00","options":{"mac":"9c:6b:00:82:eb:84"},"pref_disable_new_entities":false,"pref_disable_polling":false,"source":"user","subentries":[],"title":"Wake on LAN 9c:6b:00:82:eb:84","unique_id":null,"version":1},
{"created_at":"2025-04-07T03:03:21.792042+00:00","data":{"host":"192.168.0.11","name":"65Q750G"},"disabled_by":null,"discovery_keys":{"zeroconf":[{"domain":"zeroconf","key":["_androidtvremote2._tcp.local.","65Q750G._androidtvremote2._tcp.local."],"version":1}]},"domain":"androidtv_remote","entry_id":"01JR737PY0XGFAG98YEZMWYM5D","minor_version":1,"modified_at":"2025-04-07T21:15:40.507702+00:00","options":{},"pref_disable_new_entities":false,"pref_disable_polling":false,"source":"ignore","subentries":[],"title":"65Q750G","unique_id":"48:9e:9d:98:78:3e","version":1},
{"created_at":"2025-04-07T03:04:57.965006+00:00","data":{"device_name":"home-assistant-voice-0981b4","host":"192.168.0.209","noise_psk":"","password":"","port":6053},"disabled_by":null,"discovery_keys":{"dhcp":[{"domain":"dhcp","key":"20f83b0981b4","version":1}],"zeroconf":[{"domain":"zeroconf","key":["_esphomelib._tcp.local.","home-assistant-voice-0981b4._esphomelib._tcp.local."],"version":1}]},"domain":"esphome","entry_id":"01JR73AMVCG4FGGV23MYMXWNJT","minor_version":1,"modified_at":"2025-12-27T05:55:44.923927+00:00","options":{"allow_service_calls":false},"pref_disable_new_entities":false,"pref_disable_polling":false,"source":"zeroconf","subentries":[],"title":"Home Assistant Voice 0981b4","unique_id":"20:f8:3b:09:81:b4","version":1},
{"created_at":"2025-08-30T04:00:39.092446+00:00","data":{"host":"core-piper","port":10200},"disabled_by":null,"discovery_keys":{"hassio":[{"domain":"hassio","key":"171efec4d2ac41b18bfcdf3d446c78db","version":1}]},"domain":"wyoming","entry_id":"01K3WJ4TNM2DMZ015BXWJZEKQA","minor_version":1,"modified_at":"2025-08-30T04:00:39.092449+00:00","options":{},"pref_disable_new_entities":false,"pref_disable_polling":false,"source":"hassio","subentries":[],"title":"Piper","unique_id":"171efec4d2ac41b18bfcdf3d446c78db","version":1},
{"created_at":"2025-08-30T04:01:16.785556+00:00","data":{"host":"core-whisper","port":10300},"disabled_by":"user","discovery_keys":{"hassio":[{"domain":"hassio","key":"9fe3a8ee23c14b3da445da71f3f49246","version":1}]},"domain":"wyoming","entry_id":"01K3WJ5ZFH909FVC2NBKV0PGMY","minor_version":1,"modified_at":"2025-08-30T04:01:16.785568+00:00","options":{},"pref_disable_new_entities":false,"pref_disable_polling":false,"source":"hassio","subentries":[],"title":"Whisper","unique_id":"9fe3a8ee23c14b3da445da71f3f49246","version":1},
{"created_at":"2025-09-10T17:55:03.491031+00:00","data":{"host":"core-speech-to-phrase","port":10300},"disabled_by":null,"discovery_keys":{"hassio":[{"domain":"hassio","key":"324ccffd4e294f36a1f86fd7bb1208be","version":1}]},"domain":"wyoming","entry_id":"01K4TC8JP3C7VWBHSSYS4H5HDT","minor_version":1,"modified_at":"2025-09-10T17:55:03.491035+00:00","options":{},"pref_disable_new_entities":false,"pref_disable_polling":false,"source":"hassio","subentries":[],"title":"Speech-to-Phrase","unique_id":"324ccffd4e294f36a1f86fd7bb1208be","version":1},
{"created_at":"2025-09-14T05:33:55.068550+00:00","data":{},"disabled_by":null,"discovery_keys":{},"domain":"backup","entry_id":"01K53BECFWDC572ETZWMDH3PG7","minor_version":1,"modified_at":"2025-09-14T05:33:55.068551+00:00","options":{},"pref_disable_new_entities":false,"pref_disable_polling":false,"source":"system","subentries":[],"title":"Backup","unique_id":null,"version":1},
{"created_at":"2025-09-29T01:43:24.300615+00:00","data":{},"disabled_by":null,"discovery_keys":{},"domain":"template","entry_id":"01K69J72WC9WPC7PP9TAJ90315","minor_version":1,"modified_at":"2025-09-29T01:43:24.300619+00:00","options":{"advanced_options":{},"name":"Upper Landing A ","template_type":"switch"},"pref_disable_new_entities":false,"pref_disable_polling":false,"source":"user","subentries":[],"title":"Upper Landing A ","unique_id":null,"version":1},
{"created_at":"2025-10-07T21:58:17.090044+00:00","data":{"auth_implementation":"google_555321766228_fr9p85c52459cgp2d5qbnpkvnh9rha58_apps_googleusercontent_com","credential_type":"web_auth","token":{"access_token":"ya29.a0AUMWg_LC8bICm62e7IpB4TqM2Y0bVbCMC0_8Oj8FtrbygvuvNDD6fYamrbfjmeWKOhZu4s6VkG4TvHZj0uSD52yVag9KgoEdcHpXvpnbhoot_o3Xp4XNoQSWXphIDND7p1GSzGwq3wjWS4-73quE1TMWzfrUnsAsNmgvBCqj5wsJPiL4JVsQWBSv5ThAzXVkwOkHZGAPRwaCgYKAZASARMSFQHGX2MiHmJ8iBwLWkYO2DOFirC0BA0209","expires_at":1769837426.5019999,"expires_in":3599,"refresh_token":"1//0fhn0W37Cuhv-CgYIARAAGA8SNwF-L9IrwJA77btC2T0PH0Kc9AdDJi-R7apg8H9pqvV35apnP2OYDRDFIeQ52jf8uJsVpl71pI0","scope":"https://www.googleapis.com/auth/calendar","token_type":"Bearer"}},"disabled_by":null,"discovery_keys":{},"domain":"google","entry_id":"01K70AXB81Z95CMX993D4Z9K08","minor_version":1,"modified_at":"2026-01-31T04:30:27.502054+00:00","options":{"calendar_access":"read_write"},"pref_disable_new_entities":false,"pref_disable_polling":false,"source":"user","subentries":[],"title":"chrisrobertchasteen@gmail.com","unique_id":"chrisrobertchasteen@gmail.com","version":1},
{"created_at":"2025-11-09T07:16:33.408367+00:00","data":{"password":"pp4wcT67#%rOau","username":"chastifur@gmail.com"},"disabled_by":null,"discovery_keys":{},"domain":"litterrobot","entry_id":"01K9KQJJG0AXR3PCXD8Y3NCHGM","minor_version":1,"modified_at":"2025-11-09T07:16:33.408373+00:00","options":{},"pref_disable_new_entities":false,"pref_disable_polling":false,"source":"user","subentries":[],"title":"chastifur@gmail.com","unique_id":null,"version":1},
{"created_at":"2025-12-13T06:37:44.883815+00:00","data":{},"disabled_by":null,"discovery_keys":{},"domain":"alarmo","entry_id":"01KCB6VYHK4W6DCTV260B9RD3M","minor_version":1,"modified_at":"2025-12-13T06:37:44.883818+00:00","options":{},"pref_disable_new_entities":false,"pref_disable_polling":false,"source":"user","subentries":[],"title":"Alarmo","unique_id":"10d15907fccf","version":"1.0.0"},
{"created_at":"2025-12-13T07:37:14.695902+00:00","data":{"cloudhook":false,"webhook_id":"76fbd9cba36808425304ca71e4d97bb79d9fc68181086c71068510df1f9cb2a2"},"disabled_by":null,"discovery_keys":{},"domain":"twilio","entry_id":"01KCBA8WP7AW7EBDD3TA6ZTPQV","minor_version":1,"modified_at":"2025-12-13T07:37:14.695903+00:00","options":{},"pref_disable_new_entities":false,"pref_disable_polling":false,"source":"user","subentries":[],"title":"Twilio Webhook","unique_id":null,"version":1},
{"created_at":"2025-12-17T05:23:11.164165+00:00","data":{"app_data":{"push_token":"fv-M1yFoS6ye6ChUGihRWK:APA91bEhhzD0peUdfKW_0jaf_1sa4BkkcsxcbirCv-Coc12h-AaolEtCxVUcQ4L9WTMv_kHO0_uZMOSspH-CFMqABUyCuvv-nHyq_CUY-6M9s_KcZrY2hrE","push_url":"https://mobile-apps.home-assistant.io/api/sendPush/android/v1","push_websocket_channel":true},"app_id":"io.homeassistant.companion.android","app_name":"Home Assistant","app_version":"2025.11.4-full (19134)","cloudhook_url":"https://hooks.nabu.casa/gAAAAABpQj4_zy_6oerTAckSwqDXSzct0oOHfkICEPBrPut7__ncO0UosZk7v3MUwp7Brm8GQ7FAnRfyvOB9-REPWNIpiYFq1lZF9vfAcuA0cmB2yElVGlwl1mtZqQV2MKmvDRANqPPCWsZjuNIJ6vYFOcoiFcUvBPFQfXFIgG08wRJs7VTO7So=","device_id":"9a80e426cd178688","device_name":"Chris phone ","manufacturer":"OnePlus","model":"CPH2513","os_name":"Android","os_version":"34","supports_encryption":false,"user_id":"4a285b563f6a4a54b657acf1926c0b5b","webhook_id":"a308d88905706ccc063aa2ae8aafa5012c3eafc67b0fe95f8aafd77147adc111"},"disabled_by":null,"discovery_keys":{},"domain":"mobile_app","entry_id":"01KCNC69NWB4NZT8NBA49SEJXS","minor_version":1,"modified_at":"2026-01-01T06:28:45.971434+00:00","options":{},"pref_disable_new_entities":false,"pref_disable_polling":false,"source":"registration","subentries":[],"title":"CPH2513","unique_id":"io.homeassistant.companion.android-9a80e426cd178688","version":1},
{"created_at":"2025-12-27T05:27:59.111111+00:00","data":{"device_id":"uuid:37f177fe-ff75-4f3e-a7fb-b976f7ffed67","mac":"08:c3:b3:e6:b1:74","type":"urn:schemas-upnp-org:device:MediaRenderer:1","url":"http://192.168.0.13:16006"},"disabled_by":null,"discovery_keys":{"ssdp":[{"domain":"ssdp","key":"uuid:37f177fe-ff75-4f3e-a7fb-b976f7ffed67","version":1}]},"domain":"dlna_dmr","entry_id":"01KDF4E8W7S74JZ1XETBJ4GHGN","minor_version":1,"modified_at":"2026-01-26T01:54:53.761323+00:00","options":{},"pref_disable_new_entities":false,"pref_disable_polling":false,"source":"ignore","subentries":[],"title":"65Q750G","unique_id":"uuid:37f177fe-ff75-4f3e-a7fb-b976f7ffed67","version":1},
{"created_at":"2025-12-28T01:19:03.740660+00:00","data":{},"disabled_by":null,"discovery_keys":{"dhcp":[{"domain":"dhcp","key":"ec71dbf4e5e5","version":1}]},"domain":"reolink","entry_id":"01KDH8K6HWZF40QG05JW36EG2H","minor_version":1,"modified_at":"2025-12-28T01:19:03.740667+00:00","options":{},"pref_disable_new_entities":false,"pref_disable_polling":false,"source":"ignore","subentries":[],"title":"cat (192.168.0.37)","unique_id":"ec:71:db:f4:e5:e5","version":1},
{"created_at":"2025-12-28T01:19:35.727105+00:00","data":{"baichuan_only":false,"baichuan_port":9000,"firmware_check_time":24386.20798790512,"host":"192.168.0.210","password":"7s8J!G59xOCkY$","port":443,"privacy_mode_supported":true,"use_https":true,"username":"admin"},"disabled_by":null,"discovery_keys":{"dhcp":[{"domain":"dhcp","key":"8c7ab330bb52","version":1}]},"domain":"reolink","entry_id":"01KDH8M5SF0CTB4F4G6TXAP6SZ","minor_version":1,"modified_at":"2026-01-04T08:00:45.377655+00:00","options":{"protocol":"rtsp"},"pref_disable_new_entities":false,"pref_disable_polling":false,"source":"user","subentries":[],"title":"cat cam 1","unique_id":"8c:7a:b3:30:bb:52","version":1},
{"created_at":"2025-12-28T01:30:36.768549+00:00","data":{},"disabled_by":null,"discovery_keys":{},"domain":"generic","entry_id":"01KDH98BB0VPBMK5MYNVK08ZZE","minor_version":1,"modified_at":"2025-12-28T01:55:54.232191+00:00","options":{"authentication":"basic","content_type":"image/jpeg","framerate":15.0,"limit_refetch_to_url_change":false,"rtsp_transport":"tcp","stream_source":"rtsp://admin:7s8J!G59xOCkY$@192.168.0.210:554/h264Preview_01_sub","use_wallclock_as_timestamps":false,"verify_ssl":true},"pref_disable_new_entities":false,"pref_disable_polling":false,"source":"user","subentries":[],"title":"192_168_0_210","unique_id":null,"version":1},
{"created_at":"2026-01-01T08:13:00.067008+00:00","data":{},"disabled_by":null,"discovery_keys":{},"domain":"derivative","entry_id":"01KDW9W0S2Z43343QVEG0AMT2S","minor_version":4,"modified_at":"2026-01-01T08:13:00.067010+00:00","options":{"name":"Master Bath Humidity","round":2.0,"source":"sensor.third_reality_inc_3rths24bz_humidity","time_window":{"hours":0,"minutes":3,"seconds":0},"unit_time":"min"},"pref_disable_new_entities":false,"pref_disable_polling":false,"source":"user","subentries":[],"title":"Master Bath Humidity","unique_id":null,"version":1}
]
}
}

View File

@@ -0,0 +1,119 @@
{
"version": 1,
"minor_version": 12,
"key": "core.device_registry",
"data": {
"devices": [
{"area_id":null,"config_entries":["01JE2PA14YS95P1MVHDGHSQEB2"],"config_entries_subentries":{"01JE2PA14YS95P1MVHDGHSQEB2":[null]},"configuration_url":null,"connections":[],"created_at":"2024-12-02T03:23:30.336026+00:00","disabled_by":null,"entry_type":"service","hw_version":null,"id":"14a11eca4be5aa432db00cfbf145e34b","identifiers":[["sun","01JE2PA14YS95P1MVHDGHSQEB2"]],"labels":[],"manufacturer":null,"model":null,"model_id":null,"modified_at":"2024-12-02T03:23:30.336051+00:00","name_by_user":null,"name":"Sun","primary_config_entry":"01JE2PA14YS95P1MVHDGHSQEB2","serial_number":null,"sw_version":null,"via_device_id":null},
{"area_id":null,"config_entries":["01JE2PA1AVW34BP3R20RJ09CVZ"],"config_entries_subentries":{"01JE2PA1AVW34BP3R20RJ09CVZ":[null]},"configuration_url":null,"connections":[],"created_at":"2024-12-02T03:23:30.536524+00:00","disabled_by":null,"entry_type":"service","hw_version":null,"id":"b3b64f05673833a7e2c96a3620bcbc51","identifiers":[["hassio","core"]],"labels":[],"manufacturer":"Home Assistant","model":"Home Assistant Core","model_id":null,"modified_at":"2025-12-31T06:44:48.625420+00:00","name_by_user":null,"name":"Home Assistant Core","primary_config_entry":"01JE2PA1AVW34BP3R20RJ09CVZ","serial_number":null,"sw_version":"2025.12.5","via_device_id":null},
{"area_id":null,"config_entries":["01JE2PA1AVW34BP3R20RJ09CVZ"],"config_entries_subentries":{"01JE2PA1AVW34BP3R20RJ09CVZ":[null]},"configuration_url":null,"connections":[],"created_at":"2024-12-02T03:23:30.536596+00:00","disabled_by":null,"entry_type":"service","hw_version":null,"id":"48a3f586f66d10f66b59e994e73ec467","identifiers":[["hassio","supervisor"]],"labels":[],"manufacturer":"Home Assistant","model":"Home Assistant Supervisor","model_id":null,"modified_at":"2026-01-31T04:56:02.709870+00:00","name_by_user":null,"name":"Home Assistant Supervisor","primary_config_entry":"01JE2PA1AVW34BP3R20RJ09CVZ","serial_number":null,"sw_version":"2026.01.1","via_device_id":null},
{"area_id":null,"config_entries":["01JE2PA1AVW34BP3R20RJ09CVZ"],"config_entries_subentries":{"01JE2PA1AVW34BP3R20RJ09CVZ":[null]},"configuration_url":null,"connections":[],"created_at":"2024-12-02T03:23:30.536629+00:00","disabled_by":null,"entry_type":"service","hw_version":null,"id":"021ffffebb8a7f2ba0188a809dfae075","identifiers":[["hassio","host"]],"labels":[],"manufacturer":"Home Assistant","model":"Home Assistant Host","model_id":null,"modified_at":"2024-12-02T03:23:30.536637+00:00","name_by_user":null,"name":"Home Assistant Host","primary_config_entry":"01JE2PA1AVW34BP3R20RJ09CVZ","serial_number":null,"sw_version":null,"via_device_id":null},
{"area_id":null,"config_entries":["01JE2PA1AVW34BP3R20RJ09CVZ"],"config_entries_subentries":{"01JE2PA1AVW34BP3R20RJ09CVZ":[null]},"configuration_url":null,"connections":[],"created_at":"2024-12-02T03:23:30.536657+00:00","disabled_by":null,"entry_type":"service","hw_version":null,"id":"0920de3445765a6c5d24f8d79c846900","identifiers":[["hassio","OS"]],"labels":[],"manufacturer":"Home Assistant","model":"Home Assistant Operating System","model_id":null,"modified_at":"2025-11-30T03:24:26.552859+00:00","name_by_user":null,"name":"Home Assistant Operating System","primary_config_entry":"01JE2PA1AVW34BP3R20RJ09CVZ","serial_number":null,"sw_version":"16.3","via_device_id":null},
{"area_id":null,"config_entries":["01JE2PE0TXTYWZGJSBJ1EW6XEF"],"config_entries_subentries":{"01JE2PE0TXTYWZGJSBJ1EW6XEF":[null]},"configuration_url":"https://www.met.no/en","connections":[],"created_at":"2024-12-02T03:25:43.733767+00:00","disabled_by":null,"entry_type":"service","hw_version":null,"id":"7745bef489d43140f57b21188c566ad9","identifiers":[["met","01JE2PE0TXTYWZGJSBJ1EW6XEF"]],"labels":[],"manufacturer":"Met.no","model":"Forecast","model_id":null,"modified_at":"2024-12-02T03:25:43.733791+00:00","name_by_user":null,"name":"Forecast","primary_config_entry":"01JE2PE0TXTYWZGJSBJ1EW6XEF","serial_number":null,"sw_version":null,"via_device_id":null},
{"area_id":null,"config_entries":["01JE2PA1AVW34BP3R20RJ09CVZ"],"config_entries_subentries":{"01JE2PA1AVW34BP3R20RJ09CVZ":[null]},"configuration_url":"homeassistant://hassio/addon/core_matter_server","connections":[],"created_at":"2024-12-02T03:28:29.913024+00:00","disabled_by":null,"entry_type":"service","hw_version":null,"id":"4e75e0d7f2057fb7de5bb7f5f76eaaff","identifiers":[["hassio","core_matter_server"]],"labels":[],"manufacturer":"Official add-ons","model":"Home Assistant Add-on","model_id":null,"modified_at":"2026-01-31T04:56:02.709345+00:00","name_by_user":null,"name":"Matter Server","primary_config_entry":"01JE2PA1AVW34BP3R20RJ09CVZ","serial_number":null,"sw_version":"8.1.2","via_device_id":null},
{"area_id":"living_room","config_entries":["01JE2R7DPDQ8PCN6MMVWNFMDFN"],"config_entries_subentries":{"01JE2R7DPDQ8PCN6MMVWNFMDFN":[null]},"configuration_url":null,"connections":[["zigbee","7c:c6:b6:ff:fe:49:79:3c"]],"created_at":"2024-12-02T03:57:05.804714+00:00","disabled_by":null,"entry_type":null,"hw_version":null,"id":"a6b0311faa8ba235909a9316b2313cc8","identifiers":[["zha","7c:c6:b6:ff:fe:49:79:3c"]],"labels":[],"manufacturer":"","model":"Generic Zigbee Coordinator (EZSP)","model_id":null,"modified_at":"2025-12-31T06:45:29.435097+00:00","name_by_user":"Zigbee Stick","name":" Generic Zigbee Coordinator (EZSP)","primary_config_entry":"01JE2R7DPDQ8PCN6MMVWNFMDFN","serial_number":null,"sw_version":null,"via_device_id":"a6b0311faa8ba235909a9316b2313cc8"},
{"area_id":"bedroom","config_entries":["01JE2R7DPDQ8PCN6MMVWNFMDFN"],"config_entries_subentries":{"01JE2R7DPDQ8PCN6MMVWNFMDFN":[null]},"configuration_url":null,"connections":[["zigbee","00:15:8d:00:0a:f3:89:88"]],"created_at":"2024-12-02T04:00:22.585818+00:00","disabled_by":null,"entry_type":null,"hw_version":null,"id":"ebab4eee8659e99e53cb945fa322cde2","identifiers":[["zha","00:15:8d:00:0a:f3:89:88"]],"labels":["monitored"],"manufacturer":"LUMI","model":"lumi.remote.b1acn01","model_id":null,"modified_at":"2025-12-31T06:29:19.872572+00:00","name_by_user":"Bedroom Bed Button","name":"LUMI lumi.remote.b1acn01","primary_config_entry":"01JE2R7DPDQ8PCN6MMVWNFMDFN","serial_number":null,"sw_version":null,"via_device_id":"a6b0311faa8ba235909a9316b2313cc8"},
{"area_id":null,"config_entries":["01JE2SQ4DY1D75FFMD9PGMNER0"],"config_entries_subentries":{"01JE2SQ4DY1D75FFMD9PGMNER0":[null]},"configuration_url":"homeassistant://hacs","connections":[],"created_at":"2024-12-02T04:23:07.671714+00:00","disabled_by":null,"entry_type":"service","hw_version":null,"id":"33e6090fad1c7f4b0c6e9a04d973e531","identifiers":[["hacs","0717a0cd-745c-48fd-9b16-c8534c9704f9-bc944b0f-fd42-4a58-a072-ade38d1444cd"]],"labels":[],"manufacturer":"hacs.xyz","model":"","model_id":null,"modified_at":"2025-02-15T22:18:20.149511+00:00","name_by_user":null,"name":"HACS","primary_config_entry":"01JE2SQ4DY1D75FFMD9PGMNER0","serial_number":null,"sw_version":"2.0.5","via_device_id":null},
{"area_id":null,"config_entries":["01JE2SQ4DY1D75FFMD9PGMNER0"],"config_entries_subentries":{"01JE2SQ4DY1D75FFMD9PGMNER0":[null]},"configuration_url":"homeassistant://hacs/repository/312080478","connections":[],"created_at":"2024-12-02T04:24:44.267192+00:00","disabled_by":null,"entry_type":"service","hw_version":null,"id":"250233086672d987efd142a6d3c01b20","identifiers":[["hacs","312080478"]],"labels":[],"manufacturer":"LaggAt","model":"integration","model_id":null,"modified_at":"2024-12-02T04:24:44.267239+00:00","name_by_user":null,"name":"govee","primary_config_entry":"01JE2SQ4DY1D75FFMD9PGMNER0","serial_number":null,"sw_version":null,"via_device_id":null},
{"area_id":"master_bath","config_entries":["01JE2R7DPDQ8PCN6MMVWNFMDFN"],"config_entries_subentries":{"01JE2R7DPDQ8PCN6MMVWNFMDFN":[null]},"configuration_url":null,"connections":[["zigbee","00:15:8d:00:0a:f4:5c:83"]],"created_at":"2024-12-03T02:15:03.183633+00:00","disabled_by":null,"entry_type":null,"hw_version":null,"id":"984d2d9da0ce0b58b3ab4649bb82cb09","identifiers":[["zha","00:15:8d:00:0a:f4:5c:83"]],"labels":["monitored"],"manufacturer":"LUMI","model":"lumi.remote.b1acn01","model_id":null,"modified_at":"2025-11-23T07:22:10.891292+00:00","name_by_user":"MasterBathButton2","name":"LUMI lumi.remote.b1acn01","primary_config_entry":"01JE2R7DPDQ8PCN6MMVWNFMDFN","serial_number":null,"sw_version":null,"via_device_id":"a6b0311faa8ba235909a9316b2313cc8"},
{"area_id":null,"config_entries":["01JE2R7DPDQ8PCN6MMVWNFMDFN"],"config_entries_subentries":{"01JE2R7DPDQ8PCN6MMVWNFMDFN":[null]},"configuration_url":null,"connections":[["zigbee","00:15:8d:00:0a:f3:b9:b8"]],"created_at":"2024-12-04T03:47:03.947001+00:00","disabled_by":null,"entry_type":null,"hw_version":null,"id":"0e833fe5953d72d36def81f42ddec2dd","identifiers":[["zha","00:15:8d:00:0a:f3:b9:b8"]],"labels":["monitored"],"manufacturer":"LUMI","model":"lumi.remote.b1acn01","model_id":null,"modified_at":"2025-11-23T07:22:10.904413+00:00","name_by_user":"Master Bath Outer","name":"LUMI lumi.remote.b1acn01","primary_config_entry":"01JE2R7DPDQ8PCN6MMVWNFMDFN","serial_number":null,"sw_version":null,"via_device_id":"a6b0311faa8ba235909a9316b2313cc8"},
{"area_id":null,"config_entries":["01JEZ0CV35V3GN0H3TXQCRZCKH"],"config_entries_subentries":{"01JEZ0CV35V3GN0H3TXQCRZCKH":[null]},"configuration_url":null,"connections":[],"created_at":"2024-12-02T04:29:24.058659+00:00","disabled_by":"config_entry","entry_type":null,"hw_version":null,"id":"69c8478efdc84520141b9030fad78d99","identifiers":[["govee","govee_govee_B6:FE:D4:AD:FC:1F:CE:A2"]],"labels":[],"manufacturer":"Govee","model":"H5080","model_id":null,"modified_at":"2025-09-20T23:14:34.015785+00:00","name_by_user":"govee plug 1","name":"Fan","primary_config_entry":"01JEZ0CV35V3GN0H3TXQCRZCKH","serial_number":null,"sw_version":null,"via_device_id":null},
{"area_id":null,"config_entries":["01JEZ0CV35V3GN0H3TXQCRZCKH"],"config_entries_subentries":{"01JEZ0CV35V3GN0H3TXQCRZCKH":[null]},"configuration_url":null,"connections":[],"created_at":"2024-12-02T04:29:24.061888+00:00","disabled_by":"config_entry","entry_type":null,"hw_version":null,"id":"18d8945de4c9dd9ed60853bb73427f9d","identifiers":[["govee","govee_govee_3B:D9:D4:AD:FC:1F:E2:40"]],"labels":[],"manufacturer":"Govee","model":"H5080","model_id":null,"modified_at":"2025-09-20T23:14:34.016488+00:00","name_by_user":null,"name":"Fountain Plug","primary_config_entry":"01JEZ0CV35V3GN0H3TXQCRZCKH","serial_number":null,"sw_version":null,"via_device_id":null},
{"area_id":null,"config_entries":["01JEZ0CV35V3GN0H3TXQCRZCKH"],"config_entries_subentries":{"01JEZ0CV35V3GN0H3TXQCRZCKH":[null]},"configuration_url":null,"connections":[],"created_at":"2024-12-02T04:29:24.063917+00:00","disabled_by":"config_entry","entry_type":null,"hw_version":null,"id":"191572e88f483df7e49e6a07231c6ec0","identifiers":[["govee","govee_govee_91:99:A4:C1:38:81:F9:86"]],"labels":[],"manufacturer":"Govee","model":"H6110","model_id":null,"modified_at":"2025-09-20T23:14:34.016864+00:00","name_by_user":null,"name":"Bookcase","primary_config_entry":"01JEZ0CV35V3GN0H3TXQCRZCKH","serial_number":null,"sw_version":null,"via_device_id":null},
{"area_id":null,"config_entries":["01JEZ0CV35V3GN0H3TXQCRZCKH"],"config_entries_subentries":{"01JEZ0CV35V3GN0H3TXQCRZCKH":[null]},"configuration_url":null,"connections":[],"created_at":"2024-12-13T03:18:33.370343+00:00","disabled_by":"config_entry","entry_type":null,"hw_version":null,"id":"4b32c38fbef38b529871c154a37baca8","identifiers":[["govee","govee_govee_8A:D1:D0:C9:07:A9:FD:52"]],"labels":[],"manufacturer":"Govee","model":"H5080","model_id":null,"modified_at":"2025-09-20T23:14:34.017129+00:00","name_by_user":null,"name":"Smart Plug 3","primary_config_entry":"01JEZ0CV35V3GN0H3TXQCRZCKH","serial_number":null,"sw_version":null,"via_device_id":null},
{"area_id":null,"config_entries":["01JEZ0CV35V3GN0H3TXQCRZCKH"],"config_entries_subentries":{"01JEZ0CV35V3GN0H3TXQCRZCKH":[null]},"configuration_url":null,"connections":[],"created_at":"2024-12-13T03:18:33.370897+00:00","disabled_by":"config_entry","entry_type":null,"hw_version":null,"id":"8a9c147b67efe0deac968f2dbbc65f8f","identifiers":[["govee","govee_govee_D9:42:D0:C9:07:A7:85:9C"]],"labels":[],"manufacturer":"Govee","model":"H5080","model_id":null,"modified_at":"2025-09-20T23:14:34.017363+00:00","name_by_user":null,"name":"Smart Plug 4","primary_config_entry":"01JEZ0CV35V3GN0H3TXQCRZCKH","serial_number":null,"sw_version":null,"via_device_id":null},
{"area_id":null,"config_entries":["01JEZ0CV35V3GN0H3TXQCRZCKH"],"config_entries_subentries":{"01JEZ0CV35V3GN0H3TXQCRZCKH":[null]},"configuration_url":null,"connections":[],"created_at":"2024-12-13T03:18:33.371417+00:00","disabled_by":"config_entry","entry_type":null,"hw_version":null,"id":"fb0050754cf384566c825998315a6121","identifiers":[["govee","govee_govee_D1:2F:D0:C9:07:B3:01:E6"]],"labels":[],"manufacturer":"Govee","model":"H5080","model_id":null,"modified_at":"2025-09-20T23:14:34.017585+00:00","name_by_user":null,"name":"Smart Plug 5","primary_config_entry":"01JEZ0CV35V3GN0H3TXQCRZCKH","serial_number":null,"sw_version":null,"via_device_id":null},
{"area_id":null,"config_entries":["01JEZ0CV35V3GN0H3TXQCRZCKH"],"config_entries_subentries":{"01JEZ0CV35V3GN0H3TXQCRZCKH":[null]},"configuration_url":null,"connections":[],"created_at":"2024-12-13T03:18:33.371931+00:00","disabled_by":"config_entry","entry_type":null,"hw_version":null,"id":"7b2c2a46b6149ba52fccbd8f76da9448","identifiers":[["govee","govee_govee_C9:80:D0:C9:07:98:6F:0A"]],"labels":[],"manufacturer":"Govee","model":"H5080","model_id":null,"modified_at":"2025-09-20T23:14:34.017849+00:00","name_by_user":null,"name":"Smart Plug 6","primary_config_entry":"01JEZ0CV35V3GN0H3TXQCRZCKH","serial_number":null,"sw_version":null,"via_device_id":null},
{"area_id":"bedroom","config_entries":["01JE2R7DPDQ8PCN6MMVWNFMDFN"],"config_entries_subentries":{"01JE2R7DPDQ8PCN6MMVWNFMDFN":[null]},"configuration_url":null,"connections":[["zigbee","28:2c:02:bf:ff:ec:57:97"]],"created_at":"2024-12-16T01:50:17.079878+00:00","disabled_by":null,"entry_type":null,"hw_version":null,"id":"d8478de5f4ea431ada272ac28ad7a2a0","identifiers":[["zha","28:2c:02:bf:ff:ec:57:97"]],"labels":["monitored"],"manufacturer":"Third Reality, Inc","model":"3RSP019BZ","model_id":null,"modified_at":"2025-12-31T06:30:16.252102+00:00","name_by_user":"Bedroom Corner Fan","name":"Third Reality, Inc 3RSP019BZ","primary_config_entry":"01JE2R7DPDQ8PCN6MMVWNFMDFN","serial_number":null,"sw_version":"0x10013065","via_device_id":"a6b0311faa8ba235909a9316b2313cc8"},
{"area_id":null,"config_entries":["01JE2R7DPDQ8PCN6MMVWNFMDFN"],"config_entries_subentries":{"01JE2R7DPDQ8PCN6MMVWNFMDFN":[null]},"configuration_url":null,"connections":[["zigbee","28:2c:02:bf:ff:ec:29:f7"]],"created_at":"2024-12-16T01:50:19.123802+00:00","disabled_by":null,"entry_type":null,"hw_version":null,"id":"886a5ef4c95ee2fa22bbfe49009e8dc7","identifiers":[["zha","28:2c:02:bf:ff:ec:29:f7"]],"labels":["monitored"],"manufacturer":"Third Reality, Inc","model":"3RSP019BZ","model_id":null,"modified_at":"2025-12-09T15:48:16.753486+00:00","name_by_user":"Hallway Plug","name":"Third Reality, Inc 3RSP019BZ","primary_config_entry":"01JE2R7DPDQ8PCN6MMVWNFMDFN","serial_number":null,"sw_version":"0x10013065","via_device_id":"a6b0311faa8ba235909a9316b2313cc8"},
{"area_id":"garage","config_entries":["01JE2R7DPDQ8PCN6MMVWNFMDFN"],"config_entries_subentries":{"01JE2R7DPDQ8PCN6MMVWNFMDFN":[null]},"configuration_url":null,"connections":[["zigbee","28:2c:02:bf:ff:ec:75:e7"]],"created_at":"2024-12-16T01:51:34.894442+00:00","disabled_by":null,"entry_type":null,"hw_version":null,"id":"8e17de57e7c9665e843c590b6571d6a6","identifiers":[["zha","28:2c:02:bf:ff:ec:75:e7"]],"labels":["monitored"],"manufacturer":"Third Reality, Inc","model":"3RSP019BZ","model_id":null,"modified_at":"2025-12-09T15:45:34.020298+00:00","name_by_user":"3d printer light","name":"Third Reality, Inc 3RSP019BZ","primary_config_entry":"01JE2R7DPDQ8PCN6MMVWNFMDFN","serial_number":null,"sw_version":"0x10013065","via_device_id":"a6b0311faa8ba235909a9316b2313cc8"},
{"area_id":null,"config_entries":["01JE2R7DPDQ8PCN6MMVWNFMDFN"],"config_entries_subentries":{"01JE2R7DPDQ8PCN6MMVWNFMDFN":[null]},"configuration_url":null,"connections":[["zigbee","28:2c:02:bf:ff:ec:5b:d5"]],"created_at":"2024-12-16T01:51:36.865113+00:00","disabled_by":null,"entry_type":null,"hw_version":null,"id":"8860a33d7f6298f79282948f52d3b5db","identifiers":[["zha","28:2c:02:bf:ff:ec:5b:d5"]],"labels":[],"manufacturer":"Third Reality, Inc","model":"3RSP019BZ","model_id":null,"modified_at":"2025-09-14T05:33:54.406091+00:00","name_by_user":"Office Fan Plug","name":"Third Reality, Inc 3RSP019BZ","primary_config_entry":"01JE2R7DPDQ8PCN6MMVWNFMDFN","serial_number":null,"sw_version":"0x1001305c","via_device_id":"a6b0311faa8ba235909a9316b2313cc8"},
{"area_id":null,"config_entries":["01KCNC69NWB4NZT8NBA49SEJXS"],"config_entries_subentries":{"01KCNC69NWB4NZT8NBA49SEJXS":[null]},"configuration_url":null,"connections":[],"created_at":"2024-12-22T00:24:53.645149+00:00","disabled_by":null,"entry_type":null,"hw_version":null,"id":"7fbfcf84ce84cf89811ed041532155f4","identifiers":[["mobile_app","9a80e426cd178688"]],"labels":[],"manufacturer":"OnePlus","model":"CPH2513","model_id":null,"modified_at":"2026-01-01T06:28:45.970719+00:00","name_by_user":null,"name":"Chris phone ","primary_config_entry":"01KCNC69NWB4NZT8NBA49SEJXS","serial_number":null,"sw_version":"34","via_device_id":null},
{"area_id":"misc","config_entries":["01JFP966AYW48GQ7FPY42M80G0"],"config_entries_subentries":{"01JFP966AYW48GQ7FPY42M80G0":[null]},"configuration_url":null,"connections":[],"created_at":"2024-12-22T04:14:44.867415+00:00","disabled_by":null,"entry_type":null,"hw_version":null,"id":"e80267ac3e329caa3f04e7f7948cb477","identifiers":[["vesync","vsaq52505b449159ce3821f41cda96a7"]],"labels":[],"manufacturer":"VeSync","model":"Core200S","model_id":null,"modified_at":"2025-11-09T07:27:10.565256+00:00","name_by_user":null,"name":"bedroom air purifier","primary_config_entry":"01JFP966AYW48GQ7FPY42M80G0","serial_number":null,"sw_version":"1.1.15","via_device_id":null},
{"area_id":null,"config_entries":["01JFP966AYW48GQ7FPY42M80G0"],"config_entries_subentries":{"01JFP966AYW48GQ7FPY42M80G0":[null]},"configuration_url":null,"connections":[],"created_at":"2024-12-22T04:14:44.870987+00:00","disabled_by":null,"entry_type":null,"hw_version":null,"id":"e8b15c69099402eabeaf4488b6239a92","identifiers":[["vesync","vsaqca39a5b4bf2b562ba86b5f49a916"]],"labels":[],"manufacturer":"VeSync","model":"LAP-V102S-WUS","model_id":null,"modified_at":"2025-11-09T07:27:10.564932+00:00","name_by_user":null,"name":"Vital 100S","primary_config_entry":"01JFP966AYW48GQ7FPY42M80G0","serial_number":null,"sw_version":"1.0.08","via_device_id":null},
{"area_id":null,"config_entries":["01JFTXJN9FW8ZK5WNKFDK1Y69F"],"config_entries_subentries":{"01JFTXJN9FW8ZK5WNKFDK1Y69F":[null]},"configuration_url":null,"connections":[],"created_at":"2024-12-23T23:28:01.327466+00:00","disabled_by":null,"entry_type":null,"hw_version":null,"id":"efa17782a42adb5af7f2cfafe8573719","identifiers":[["mobile_app","04816a43418fc7ed"]],"labels":[],"manufacturer":"samsung","model":"SM-G781U","model_id":null,"modified_at":"2024-12-23T23:28:01.327483+00:00","name_by_user":null,"name":"Gabys Phone","primary_config_entry":"01JFTXJN9FW8ZK5WNKFDK1Y69F","serial_number":null,"sw_version":"33","via_device_id":null},
{"area_id":"groupmembers","config_entries":["01JE2R7DPDQ8PCN6MMVWNFMDFN"],"config_entries_subentries":{"01JE2R7DPDQ8PCN6MMVWNFMDFN":[null]},"configuration_url":null,"connections":[["zigbee","28:2c:02:bf:ff:ea:9c:af"]],"created_at":"2024-12-28T23:17:39.673298+00:00","disabled_by":null,"entry_type":null,"hw_version":null,"id":"ced4292fbcbaa682b7971380652ca751","identifiers":[["zha","28:2c:02:bf:ff:ea:9c:af"]],"labels":["monitored"],"manufacturer":"Third Reality, Inc","model":"3RSS009Z","model_id":null,"modified_at":"2025-12-31T06:33:26.019376+00:00","name_by_user":"upper landing Office Adjacent","name":"Third Reality, Inc 3RSS009Z","primary_config_entry":"01JE2R7DPDQ8PCN6MMVWNFMDFN","serial_number":null,"sw_version":"0x0000001e","via_device_id":"a6b0311faa8ba235909a9316b2313cc8"},
{"area_id":"groupmembers","config_entries":["01JE2R7DPDQ8PCN6MMVWNFMDFN"],"config_entries_subentries":{"01JE2R7DPDQ8PCN6MMVWNFMDFN":[null]},"configuration_url":null,"connections":[["zigbee","28:2c:02:bf:ff:ea:a3:22"]],"created_at":"2024-12-28T23:17:49.936083+00:00","disabled_by":null,"entry_type":null,"hw_version":null,"id":"9f3ed8a617d6da4c6eb0a2fd412deba4","identifiers":[["zha","28:2c:02:bf:ff:ea:a3:22"]],"labels":["monitored"],"manufacturer":"Third Reality, Inc","model":"3RSS009Z","model_id":null,"modified_at":"2025-12-31T06:33:19.276927+00:00","name_by_user":"Stair Light upper","name":"Third Reality, Inc 3RSS009Z","primary_config_entry":"01JE2R7DPDQ8PCN6MMVWNFMDFN","serial_number":null,"sw_version":"0x0000001e","via_device_id":"a6b0311faa8ba235909a9316b2313cc8"},
{"area_id":"bedroom","config_entries":["01JE2R7DPDQ8PCN6MMVWNFMDFN"],"config_entries_subentries":{"01JE2R7DPDQ8PCN6MMVWNFMDFN":[null]},"configuration_url":null,"connections":[["zigbee","28:2c:02:bf:ff:ea:98:c3"]],"created_at":"2024-12-28T23:18:00.262083+00:00","disabled_by":null,"entry_type":null,"hw_version":null,"id":"09112e3aa200fc6c51b9d8ef8cdd1986","identifiers":[["zha","28:2c:02:bf:ff:ea:98:c3"]],"labels":["monitored"],"manufacturer":"Third Reality, Inc","model":"3RSS009Z","model_id":null,"modified_at":"2025-11-23T07:22:10.902730+00:00","name_by_user":"Bedroom Ceiling Lights","name":"Third Reality, Inc 3RSS009Z","primary_config_entry":"01JE2R7DPDQ8PCN6MMVWNFMDFN","serial_number":null,"sw_version":"0x0000001e","via_device_id":"a6b0311faa8ba235909a9316b2313cc8"},
{"area_id":"bedroom","config_entries":["01JE2R7DPDQ8PCN6MMVWNFMDFN"],"config_entries_subentries":{"01JE2R7DPDQ8PCN6MMVWNFMDFN":[null]},"configuration_url":null,"connections":[["zigbee","28:2c:02:bf:ff:ea:9c:9a"]],"created_at":"2024-12-28T23:18:45.297594+00:00","disabled_by":null,"entry_type":null,"hw_version":null,"id":"63701be14b86c1fe10d30a39b5e4b768","identifiers":[["zha","28:2c:02:bf:ff:ea:9c:9a"]],"labels":["monitored"],"manufacturer":"Third Reality, Inc","model":"3RSS009Z","model_id":null,"modified_at":"2025-12-31T06:29:56.031433+00:00","name_by_user":"Bedroom ceiling fan","name":"Third Reality, Inc 3RSS009Z","primary_config_entry":"01JE2R7DPDQ8PCN6MMVWNFMDFN","serial_number":null,"sw_version":"0x0000001e","via_device_id":"a6b0311faa8ba235909a9316b2313cc8"},
{"area_id":"living_room","config_entries":["01JE2R7DPDQ8PCN6MMVWNFMDFN"],"config_entries_subentries":{"01JE2R7DPDQ8PCN6MMVWNFMDFN":[null]},"configuration_url":null,"connections":[["zigbee","f4:42:50:c3:7d:ff:00:00"]],"created_at":"2025-01-07T03:02:00.494016+00:00","disabled_by":null,"entry_type":null,"hw_version":null,"id":"5bc89d5269cf97fcdff11311559cc80d","identifiers":[["zha","f4:42:50:c3:7d:ff:00:00"]],"labels":["monitored"],"manufacturer":"Third Reality, Inc","model":"3RSNL02043Z","model_id":null,"modified_at":"2025-11-23T07:22:10.892286+00:00","name_by_user":"Night Light 1","name":"Third Reality, Inc 3RSNL02043Z","primary_config_entry":"01JE2R7DPDQ8PCN6MMVWNFMDFN","serial_number":null,"sw_version":"0x00000052","via_device_id":"a6b0311faa8ba235909a9316b2313cc8"},
{"area_id":null,"config_entries":["01JE2SQ4DY1D75FFMD9PGMNER0"],"config_entries_subentries":{"01JE2SQ4DY1D75FFMD9PGMNER0":[null]},"configuration_url":"homeassistant://hacs/repository/680112919","connections":[],"created_at":"2025-01-08T05:02:51.448263+00:00","disabled_by":null,"entry_type":"service","hw_version":null,"id":"7006cf694f7f000ffcd5237cb314a3a7","identifiers":[["hacs","680112919"]],"labels":[],"manufacturer":"Clooos","model":"plugin","model_id":null,"modified_at":"2025-01-08T05:02:51.448283+00:00","name_by_user":null,"name":"Bubble Card","primary_config_entry":"01JE2SQ4DY1D75FFMD9PGMNER0","serial_number":null,"sw_version":null,"via_device_id":null},
{"area_id":"groupmembers","config_entries":["01JE2R7DPDQ8PCN6MMVWNFMDFN"],"config_entries_subentries":{"01JE2R7DPDQ8PCN6MMVWNFMDFN":[null]},"configuration_url":null,"connections":[["zigbee","28:2c:02:bf:ff:ee:7f:18"]],"created_at":"2025-01-09T01:52:43.122189+00:00","disabled_by":null,"entry_type":null,"hw_version":null,"id":"ee6192c8e7833df8a2b4e3ce759be2b5","identifiers":[["zha","28:2c:02:bf:ff:ee:7f:18"]],"labels":["monitored"],"manufacturer":"Third Reality, Inc","model":"3RSS009Z","model_id":null,"modified_at":"2025-12-31T06:33:12.489804+00:00","name_by_user":"Stair Light lower","name":"Third Reality, Inc 3RSS009Z","primary_config_entry":"01JE2R7DPDQ8PCN6MMVWNFMDFN","serial_number":null,"sw_version":"0x0000001e","via_device_id":"a6b0311faa8ba235909a9316b2313cc8"},
{"area_id":"bedroom","config_entries":["01JE2R7DPDQ8PCN6MMVWNFMDFN"],"config_entries_subentries":{"01JE2R7DPDQ8PCN6MMVWNFMDFN":[null]},"configuration_url":null,"connections":[["zigbee","28:2c:02:bf:ff:ee:8f:fb"]],"created_at":"2025-01-09T01:53:42.217469+00:00","disabled_by":null,"entry_type":null,"hw_version":null,"id":"2b036805d62affb4e7933ee034279bad","identifiers":[["zha","28:2c:02:bf:ff:ee:8f:fb"]],"labels":["monitored"],"manufacturer":"Third Reality, Inc","model":"3RSS009Z","model_id":null,"modified_at":"2025-11-23T07:22:10.903560+00:00","name_by_user":"Office Ceiling Fan","name":"Third Reality, Inc 3RSS009Z","primary_config_entry":"01JE2R7DPDQ8PCN6MMVWNFMDFN","serial_number":null,"sw_version":"0x0000001e","via_device_id":"a6b0311faa8ba235909a9316b2313cc8"},
{"area_id":null,"config_entries":["01JHERHN4DSFB0VPVXT2KS52TW"],"config_entries_subentries":{"01JHERHN4DSFB0VPVXT2KS52TW":[null]},"configuration_url":null,"connections":[],"created_at":"2025-01-13T02:40:35.981458+00:00","disabled_by":null,"entry_type":null,"hw_version":null,"id":"881187d0e75ff02bf021dd5f225e47cc","identifiers":[["mobile_app","7d87873685c6e6fb"]],"labels":[],"manufacturer":"Google","model":"Pixel 3a","model_id":null,"modified_at":"2025-01-13T02:40:35.981487+00:00","name_by_user":null,"name":"Pixel 3a","primary_config_entry":"01JHERHN4DSFB0VPVXT2KS52TW","serial_number":null,"sw_version":"32","via_device_id":null},
{"area_id":null,"config_entries":["01JE2SQ4DY1D75FFMD9PGMNER0"],"config_entries_subentries":{"01JE2SQ4DY1D75FFMD9PGMNER0":[null]},"configuration_url":"homeassistant://hacs/repository/497319128","connections":[],"created_at":"2025-01-13T03:24:27.485120+00:00","disabled_by":null,"entry_type":"service","hw_version":null,"id":"6d9cbeece1a8412e5985599c7b3acb21","identifiers":[["hacs","497319128"]],"labels":[],"manufacturer":"NemesisRE","model":"plugin","model_id":null,"modified_at":"2025-01-13T03:24:27.485162+00:00","name_by_user":null,"name":"Kiosk Mode","primary_config_entry":"01JE2SQ4DY1D75FFMD9PGMNER0","serial_number":null,"sw_version":null,"via_device_id":null},
{"area_id":"living_room","config_entries":["01JE2R7DPDQ8PCN6MMVWNFMDFN"],"config_entries_subentries":{"01JE2R7DPDQ8PCN6MMVWNFMDFN":[null]},"configuration_url":null,"connections":[["zigbee","a4:c1:38:72:67:f9:5d:d1"]],"created_at":"2025-01-13T23:08:45.204271+00:00","disabled_by":null,"entry_type":null,"hw_version":null,"id":"4b17e41fc231eb5e696ef7c63c724a38","identifiers":[["zha","a4:c1:38:72:67:f9:5d:d1"]],"labels":["monitored"],"manufacturer":"eWeLight","model":"ZB-CL01","model_id":null,"modified_at":"2025-12-31T06:33:50.446801+00:00","name_by_user":"Livingroom FigureCase","name":"eWeLight ZB-CL01","primary_config_entry":"01JE2R7DPDQ8PCN6MMVWNFMDFN","serial_number":null,"sw_version":null,"via_device_id":"a6b0311faa8ba235909a9316b2313cc8"},
{"area_id":"living_room","config_entries":["01JE2R7DPDQ8PCN6MMVWNFMDFN"],"config_entries_subentries":{"01JE2R7DPDQ8PCN6MMVWNFMDFN":[null]},"configuration_url":null,"connections":[["zigbee","a4:c1:38:26:5d:39:64:49"]],"created_at":"2025-01-18T02:06:05.603198+00:00","disabled_by":null,"entry_type":null,"hw_version":null,"id":"f3e8dcade53ac6843fde79376c099a3c","identifiers":[["zha","a4:c1:38:26:5d:39:64:49"]],"labels":["monitored"],"manufacturer":"eWeLight","model":"ZB-CL01","model_id":null,"modified_at":"2025-11-23T07:22:10.893509+00:00","name_by_user":"Movie case right","name":"eWeLight ZB-CL01","primary_config_entry":"01JE2R7DPDQ8PCN6MMVWNFMDFN","serial_number":null,"sw_version":null,"via_device_id":"a6b0311faa8ba235909a9316b2313cc8"},
{"area_id":"bedroom","config_entries":["01JE2R7DPDQ8PCN6MMVWNFMDFN"],"config_entries_subentries":{"01JE2R7DPDQ8PCN6MMVWNFMDFN":[null]},"configuration_url":null,"connections":[["zigbee","00:15:8d:00:0a:f3:60:52"]],"created_at":"2025-01-25T00:20:07.191278+00:00","disabled_by":null,"entry_type":null,"hw_version":null,"id":"052358b767ecf6b2609acf2fe20f7319","identifiers":[["zha","00:15:8d:00:0a:f3:60:52"]],"labels":["monitored"],"manufacturer":"LUMI","model":"lumi.remote.b1acn01","model_id":null,"modified_at":"2025-12-31T06:30:44.955554+00:00","name_by_user":"Bedroom doorway button","name":"LUMI lumi.remote.b1acn01","primary_config_entry":"01JE2R7DPDQ8PCN6MMVWNFMDFN","serial_number":null,"sw_version":null,"via_device_id":"a6b0311faa8ba235909a9316b2313cc8"},
{"area_id":null,"config_entries":["01JE2PA1AVW34BP3R20RJ09CVZ"],"config_entries_subentries":{"01JE2PA1AVW34BP3R20RJ09CVZ":[null]},"configuration_url":"homeassistant://hassio/addon/core_configurator","connections":[],"created_at":"2025-01-27T00:05:47.402413+00:00","disabled_by":null,"entry_type":"service","hw_version":null,"id":"f77d5455b3eccf6f811be5fa572a9808","identifiers":[["hassio","core_configurator"]],"labels":[],"manufacturer":"Official add-ons","model":"Home Assistant Add-on","model_id":null,"modified_at":"2025-01-27T00:05:47.402458+00:00","name_by_user":null,"name":"File editor","primary_config_entry":"01JE2PA1AVW34BP3R20RJ09CVZ","serial_number":null,"sw_version":"5.8.0","via_device_id":null},
{"area_id":null,"config_entries":["01JE2SQ4DY1D75FFMD9PGMNER0"],"config_entries_subentries":{"01JE2SQ4DY1D75FFMD9PGMNER0":[null]},"configuration_url":"homeassistant://hacs/repository/737780218","connections":[],"created_at":"2025-01-27T19:08:57.600690+00:00","disabled_by":null,"entry_type":"service","hw_version":null,"id":"0fcf2a08555414de170d3c21b9ab71e1","identifiers":[["hacs","737780218"]],"labels":[],"manufacturer":"elchininet","model":"plugin","model_id":null,"modified_at":"2025-01-27T19:08:57.600797+00:00","name_by_user":null,"name":"Custom Sidebar","primary_config_entry":"01JE2SQ4DY1D75FFMD9PGMNER0","serial_number":null,"sw_version":null,"via_device_id":null},
{"area_id":null,"config_entries":["01JE2R7DPDQ8PCN6MMVWNFMDFN"],"config_entries_subentries":{"01JE2R7DPDQ8PCN6MMVWNFMDFN":[null]},"configuration_url":null,"connections":[["zigbee","00:15:8d:00:8b:7b:7b:21"]],"created_at":"2025-02-05T20:33:34.377944+00:00","disabled_by":null,"entry_type":null,"hw_version":null,"id":"47f0f4d6680cc047c758ff166b1d214c","identifiers":[["zha","00:15:8d:00:8b:7b:7b:21"]],"labels":[],"manufacturer":"LUMI","model":"lumi.remote.b1acn01","model_id":null,"modified_at":"2025-02-15T22:45:17.589420+00:00","name_by_user":"aqara 7","name":"LUMI lumi.remote.b1acn01","primary_config_entry":"01JE2R7DPDQ8PCN6MMVWNFMDFN","serial_number":null,"sw_version":null,"via_device_id":"a6b0311faa8ba235909a9316b2313cc8"},
{"area_id":null,"config_entries":["01JE2R7DPDQ8PCN6MMVWNFMDFN"],"config_entries_subentries":{"01JE2R7DPDQ8PCN6MMVWNFMDFN":[null]},"configuration_url":null,"connections":[["zigbee","00:15:8d:00:8b:7b:7a:4d"]],"created_at":"2025-02-05T20:40:43.476307+00:00","disabled_by":null,"entry_type":null,"hw_version":null,"id":"4fb39838d69748ace7b3ed8c3d917814","identifiers":[["zha","00:15:8d:00:8b:7b:7a:4d"]],"labels":[],"manufacturer":"LUMI","model":"lumi.remote.b1acn01","model_id":null,"modified_at":"2025-02-15T22:45:17.589885+00:00","name_by_user":"aqara 10","name":"LUMI lumi.remote.b1acn01","primary_config_entry":"01JE2R7DPDQ8PCN6MMVWNFMDFN","serial_number":null,"sw_version":null,"via_device_id":"a6b0311faa8ba235909a9316b2313cc8"},
{"area_id":null,"config_entries":["01JE2R7DPDQ8PCN6MMVWNFMDFN"],"config_entries_subentries":{"01JE2R7DPDQ8PCN6MMVWNFMDFN":[null]},"configuration_url":null,"connections":[["zigbee","00:15:8d:00:8b:7b:7b:7e"]],"created_at":"2025-02-05T20:41:21.692531+00:00","disabled_by":null,"entry_type":null,"hw_version":null,"id":"9903b7ebb3846207e70fd2b4137696f9","identifiers":[["zha","00:15:8d:00:8b:7b:7b:7e"]],"labels":[],"manufacturer":"LUMI","model":"lumi.remote.b1acn01","model_id":null,"modified_at":"2025-02-15T22:45:17.590374+00:00","name_by_user":"aqara 8","name":"LUMI lumi.remote.b1acn01","primary_config_entry":"01JE2R7DPDQ8PCN6MMVWNFMDFN","serial_number":null,"sw_version":null,"via_device_id":"a6b0311faa8ba235909a9316b2313cc8"},
{"area_id":null,"config_entries":["01JE2R7DPDQ8PCN6MMVWNFMDFN"],"config_entries_subentries":{"01JE2R7DPDQ8PCN6MMVWNFMDFN":[null]},"configuration_url":null,"connections":[["zigbee","00:15:8d:00:8b:7d:19:5b"]],"created_at":"2025-02-05T20:42:11.785552+00:00","disabled_by":null,"entry_type":null,"hw_version":null,"id":"a5b95319cae01f36d393a7ef03dca7d6","identifiers":[["zha","00:15:8d:00:8b:7d:19:5b"]],"labels":[],"manufacturer":"LUMI","model":"lumi.remote.b1acn01","model_id":null,"modified_at":"2025-02-15T22:45:17.590851+00:00","name_by_user":"aqara 9","name":"LUMI lumi.remote.b1acn01","primary_config_entry":"01JE2R7DPDQ8PCN6MMVWNFMDFN","serial_number":null,"sw_version":null,"via_device_id":"a6b0311faa8ba235909a9316b2313cc8"},
{"area_id":null,"config_entries":["01JE2R7DPDQ8PCN6MMVWNFMDFN"],"config_entries_subentries":{"01JE2R7DPDQ8PCN6MMVWNFMDFN":[null]},"configuration_url":null,"connections":[["zigbee","00:15:8d:00:8b:7d:16:b4"]],"created_at":"2025-02-05T20:44:41.684423+00:00","disabled_by":null,"entry_type":null,"hw_version":null,"id":"3126ead552545da0409ab0f342d6fa54","identifiers":[["zha","00:15:8d:00:8b:7d:16:b4"]],"labels":[],"manufacturer":"LUMI","model":"lumi.remote.b1acn01","model_id":null,"modified_at":"2025-02-15T22:45:17.591413+00:00","name_by_user":"aqara 11","name":"LUMI lumi.remote.b1acn01","primary_config_entry":"01JE2R7DPDQ8PCN6MMVWNFMDFN","serial_number":null,"sw_version":null,"via_device_id":"a6b0311faa8ba235909a9316b2313cc8"},
{"area_id":null,"config_entries":["01JE2R7DPDQ8PCN6MMVWNFMDFN"],"config_entries_subentries":{"01JE2R7DPDQ8PCN6MMVWNFMDFN":[null]},"configuration_url":null,"connections":[["zigbee","00:15:8d:00:8b:7b:7c:8a"]],"created_at":"2025-02-05T20:45:17.381719+00:00","disabled_by":null,"entry_type":null,"hw_version":null,"id":"bef801c7731e52758cc4a92a3dcceb02","identifiers":[["zha","00:15:8d:00:8b:7b:7c:8a"]],"labels":[],"manufacturer":"LUMI","model":"lumi.remote.b1acn01","model_id":null,"modified_at":"2025-02-15T22:45:17.591943+00:00","name_by_user":"aqara 12","name":"LUMI lumi.remote.b1acn01","primary_config_entry":"01JE2R7DPDQ8PCN6MMVWNFMDFN","serial_number":null,"sw_version":null,"via_device_id":"a6b0311faa8ba235909a9316b2313cc8"},
{"area_id":null,"config_entries":["01JE2R7DPDQ8PCN6MMVWNFMDFN"],"config_entries_subentries":{"01JE2R7DPDQ8PCN6MMVWNFMDFN":[null]},"configuration_url":null,"connections":[["zigbee","00:15:8d:00:8b:7b:73:d9"]],"created_at":"2025-02-05T20:47:16.180712+00:00","disabled_by":null,"entry_type":null,"hw_version":null,"id":"fbc1b1f2012dd58ab3ff7a99d9433a80","identifiers":[["zha","00:15:8d:00:8b:7b:73:d9"]],"labels":[],"manufacturer":"LUMI","model":"lumi.remote.b1acn01","model_id":null,"modified_at":"2025-02-15T22:45:17.592456+00:00","name_by_user":"aqara 6","name":"LUMI lumi.remote.b1acn01","primary_config_entry":"01JE2R7DPDQ8PCN6MMVWNFMDFN","serial_number":null,"sw_version":null,"via_device_id":"a6b0311faa8ba235909a9316b2313cc8"},
{"area_id":null,"config_entries":["01JE2R7DPDQ8PCN6MMVWNFMDFN"],"config_entries_subentries":{"01JE2R7DPDQ8PCN6MMVWNFMDFN":[null]},"configuration_url":null,"connections":[["zigbee","a4:c1:38:74:a6:df:92:66"]],"created_at":"2025-02-06T01:18:41.625036+00:00","disabled_by":null,"entry_type":null,"hw_version":null,"id":"bad245c273e380f605b53cf12e4a323e","identifiers":[["zha","a4:c1:38:74:a6:df:92:66"]],"labels":["monitored","kitchen_sink"],"manufacturer":"eWeLink","model":"SNZB-03","model_id":null,"modified_at":"2025-11-23T07:22:10.906060+00:00","name_by_user":"Leak sensor 1","name":"eWeLink SNZB-03","primary_config_entry":"01JE2R7DPDQ8PCN6MMVWNFMDFN","serial_number":null,"sw_version":"0x10033607","via_device_id":"a6b0311faa8ba235909a9316b2313cc8"},
{"area_id":null,"config_entries":["01JE2R7DPDQ8PCN6MMVWNFMDFN"],"config_entries_subentries":{"01JE2R7DPDQ8PCN6MMVWNFMDFN":[null]},"configuration_url":null,"connections":[["zigbee","a4:c1:38:f3:1f:0c:63:b0"]],"created_at":"2025-02-06T01:22:34.714078+00:00","disabled_by":null,"entry_type":null,"hw_version":null,"id":"1ed578dcc765852dc917e7331a8e8899","identifiers":[["zha","a4:c1:38:f3:1f:0c:63:b0"]],"labels":["downstair_bathroom_sink","monitored"],"manufacturer":"eWeLink","model":"SNZB-03","model_id":null,"modified_at":"2025-11-23T07:22:10.906615+00:00","name_by_user":"Leak sensor 2","name":"eWeLink SNZB-03","primary_config_entry":"01JE2R7DPDQ8PCN6MMVWNFMDFN","serial_number":null,"sw_version":"0x10033607","via_device_id":"a6b0311faa8ba235909a9316b2313cc8"},
{"area_id":null,"config_entries":["01JE2R7DPDQ8PCN6MMVWNFMDFN"],"config_entries_subentries":{"01JE2R7DPDQ8PCN6MMVWNFMDFN":[null]},"configuration_url":null,"connections":[["zigbee","a4:c1:38:f1:2f:da:9c:85"]],"created_at":"2025-02-06T01:26:57.849668+00:00","disabled_by":null,"entry_type":null,"hw_version":null,"id":"f8a00c0fd21488da42cb95ef06c2e278","identifiers":[["zha","a4:c1:38:f1:2f:da:9c:85"]],"labels":["monitored","officesink"],"manufacturer":"eWeLink","model":"SNZB-03","model_id":null,"modified_at":"2025-11-23T07:22:10.907038+00:00","name_by_user":"Leak sensor 3","name":"eWeLink SNZB-03","primary_config_entry":"01JE2R7DPDQ8PCN6MMVWNFMDFN","serial_number":null,"sw_version":"0x10033607","via_device_id":"a6b0311faa8ba235909a9316b2313cc8"},
{"area_id":null,"config_entries":["01JE2R7DPDQ8PCN6MMVWNFMDFN"],"config_entries_subentries":{"01JE2R7DPDQ8PCN6MMVWNFMDFN":[null]},"configuration_url":null,"connections":[["zigbee","a4:c1:38:91:6b:af:67:12"]],"created_at":"2025-02-06T01:28:39.986430+00:00","disabled_by":null,"entry_type":null,"hw_version":null,"id":"3b6c02595d52f95b6d9143d0588c9bce","identifiers":[["zha","a4:c1:38:91:6b:af:67:12"]],"labels":["monitored","bedroomsink"],"manufacturer":"eWeLink","model":"SNZB-03","model_id":null,"modified_at":"2025-11-23T07:22:10.907434+00:00","name_by_user":"Leak sensor 4","name":"eWeLink SNZB-03","primary_config_entry":"01JE2R7DPDQ8PCN6MMVWNFMDFN","serial_number":null,"sw_version":"0x10033607","via_device_id":"a6b0311faa8ba235909a9316b2313cc8"},
{"area_id":null,"config_entries":["01JE2R7DPDQ8PCN6MMVWNFMDFN"],"config_entries_subentries":{"01JE2R7DPDQ8PCN6MMVWNFMDFN":[null]},"configuration_url":null,"connections":[["zigbee","a4:c1:38:4f:93:24:ec:3e"]],"created_at":"2025-02-15T22:10:58.517976+00:00","disabled_by":null,"entry_type":null,"hw_version":null,"id":"235a167cfb925bbe6127061eba485382","identifiers":[["zha","a4:c1:38:4f:93:24:ec:3e"]],"labels":["monitored","waterheater"],"manufacturer":"eWeLink","model":"SNZB-03","model_id":null,"modified_at":"2025-11-23T07:22:10.907769+00:00","name_by_user":"Leak Sensor 5","name":"eWeLink SNZB-03","primary_config_entry":"01JE2R7DPDQ8PCN6MMVWNFMDFN","serial_number":null,"sw_version":"0x10033607","via_device_id":"a6b0311faa8ba235909a9316b2313cc8"},
{"area_id":null,"config_entries":["01JFP966AYW48GQ7FPY42M80G0"],"config_entries_subentries":{"01JFP966AYW48GQ7FPY42M80G0":[null]},"configuration_url":null,"connections":[],"created_at":"2025-02-15T22:45:14.921569+00:00","disabled_by":null,"entry_type":null,"hw_version":null,"id":"25f9137077e6f1c2948f2cd348f693e9","identifiers":[["vesync","vsaq0f17b224937815db3db7f8f2cc4a"]],"labels":[],"manufacturer":"VeSync","model":"Dual200S","model_id":null,"modified_at":"2025-02-15T22:45:14.921611+00:00","name_by_user":null,"name":"bedroom moisturize me ","primary_config_entry":"01JFP966AYW48GQ7FPY42M80G0","serial_number":null,"sw_version":null,"via_device_id":null},
{"area_id":null,"config_entries":["01JE2SQ4DY1D75FFMD9PGMNER0"],"config_entries_subentries":{"01JE2SQ4DY1D75FFMD9PGMNER0":[null]},"configuration_url":"homeassistant://hacs/repository/249381778","connections":[],"created_at":"2025-02-16T21:59:20.912695+00:00","disabled_by":null,"entry_type":"service","hw_version":null,"id":"c0351a3de6c8d846f71ead1c49874982","identifiers":[["hacs","249381778"]],"labels":[],"manufacturer":"rospogrigio, postlund","model":"integration","model_id":null,"modified_at":"2025-02-16T21:59:20.912797+00:00","name_by_user":null,"name":"Local Tuya","primary_config_entry":"01JE2SQ4DY1D75FFMD9PGMNER0","serial_number":null,"sw_version":null,"via_device_id":null},
{"area_id":"groupmembers","config_entries":["01JE2R7DPDQ8PCN6MMVWNFMDFN"],"config_entries_subentries":{"01JE2R7DPDQ8PCN6MMVWNFMDFN":[null]},"configuration_url":null,"connections":[["zigbee","28:2c:02:bf:ff:ee:8f:0b"]],"created_at":"2025-03-28T19:27:57.605769+00:00","disabled_by":null,"entry_type":null,"hw_version":null,"id":"ce0a9c0f70a7cddd92616d63aacd4270","identifiers":[["zha","28:2c:02:bf:ff:ee:8f:0b"]],"labels":["monitored"],"manufacturer":"Third Reality, Inc","model":"3RSS009Z","model_id":null,"modified_at":"2025-12-09T16:07:04.952788+00:00","name_by_user":"Upper Landing Stair Adjacent","name":"Third Reality, Inc 3RSS009Z","primary_config_entry":"01JE2R7DPDQ8PCN6MMVWNFMDFN","serial_number":null,"sw_version":"0x0000001e","via_device_id":"a6b0311faa8ba235909a9316b2313cc8"},
{"area_id":"living_room","config_entries":["01JR71T6FYKBW7EQ1SVTRH11BV"],"config_entries_subentries":{"01JR71T6FYKBW7EQ1SVTRH11BV":[null]},"configuration_url":null,"connections":[["mac","9c:6b:00:82:eb:84"]],"created_at":"2025-04-07T02:38:30.400489+00:00","disabled_by":null,"entry_type":null,"hw_version":null,"id":"19133a3a1631081f9cde1dfdce814289","identifiers":[],"labels":[],"manufacturer":null,"model":null,"model_id":null,"modified_at":"2025-04-07T02:39:00.374507+00:00","name_by_user":"Wake The Cats Eye","name":"Wake on LAN 9c:6b:00:82:eb:84","primary_config_entry":null,"serial_number":null,"sw_version":null,"via_device_id":null},
{"area_id":"bedroom","config_entries":["01JR73AMVCG4FGGV23MYMXWNJT"],"config_entries_subentries":{"01JR73AMVCG4FGGV23MYMXWNJT":[null]},"configuration_url":null,"connections":[["mac","20:f8:3b:09:81:b4"]],"created_at":"2025-02-07T20:24:54.923774+00:00","disabled_by":null,"entry_type":null,"hw_version":null,"id":"a8e9bb3083a61502f9f45102de706b89","identifiers":[],"labels":[],"manufacturer":"Nabu Casa","model":"Home Assistant Voice PE","model_id":null,"modified_at":"2025-08-30T03:59:17.565996+00:00","name_by_user":null,"name":"Home Assistant Voice 0981b4","primary_config_entry":"01JR73AMVCG4FGGV23MYMXWNJT","serial_number":null,"sw_version":"25.6.0 (ESPHome 2025.6.2)","via_device_id":null},
{"area_id":null,"config_entries":["01JE2R7DPDQ8PCN6MMVWNFMDFN"],"config_entries_subentries":{"01JE2R7DPDQ8PCN6MMVWNFMDFN":[null]},"configuration_url":null,"connections":[["zigbee","7c:b9:4c:6b:a4:52:00:00"]],"created_at":"2025-04-09T15:41:44.724363+00:00","disabled_by":null,"entry_type":null,"hw_version":null,"id":"e5d02288501bd627adeb3f0c3f0cd438","identifiers":[["zha","7c:b9:4c:6b:a4:52:00:00"]],"labels":["monitored"],"manufacturer":"Third Reality, Inc","model":"3RCB01057Z","model_id":null,"modified_at":"2025-11-23T07:22:10.908085+00:00","name_by_user":"Master bath bulb 1","name":"Third Reality, Inc 3RCB01057Z","primary_config_entry":"01JE2R7DPDQ8PCN6MMVWNFMDFN","serial_number":null,"sw_version":"0x00000038","via_device_id":"a6b0311faa8ba235909a9316b2313cc8"},
{"area_id":null,"config_entries":["01JE2R7DPDQ8PCN6MMVWNFMDFN"],"config_entries_subentries":{"01JE2R7DPDQ8PCN6MMVWNFMDFN":[null]},"configuration_url":null,"connections":[["zigbee","7c:b9:4c:6b:a1:47:00:00"]],"created_at":"2025-04-09T15:41:47.513569+00:00","disabled_by":null,"entry_type":null,"hw_version":null,"id":"781d88d0750fab7b6f31d0768bfd0401","identifiers":[["zha","7c:b9:4c:6b:a1:47:00:00"]],"labels":[],"manufacturer":"Third Reality, Inc","model":"3RCB01057Z","model_id":null,"modified_at":"2025-09-14T05:33:54.407017+00:00","name_by_user":"Lamp lower","name":"Third Reality, Inc 3RCB01057Z","primary_config_entry":"01JE2R7DPDQ8PCN6MMVWNFMDFN","serial_number":null,"sw_version":"0x00000038","via_device_id":"a6b0311faa8ba235909a9316b2313cc8"},
{"area_id":null,"config_entries":["01JE2R7DPDQ8PCN6MMVWNFMDFN"],"config_entries_subentries":{"01JE2R7DPDQ8PCN6MMVWNFMDFN":[null]},"configuration_url":null,"connections":[["zigbee","7c:b9:4c:63:2d:e7:00:00"]],"created_at":"2025-04-09T15:42:00.063114+00:00","disabled_by":null,"entry_type":null,"hw_version":null,"id":"7007113e562ec2dcaec9bc58c572aaa2","identifiers":[["zha","7c:b9:4c:63:2d:e7:00:00"]],"labels":["monitored"],"manufacturer":"Third Reality, Inc","model":"3RCB01057Z","model_id":null,"modified_at":"2025-11-23T07:22:10.908419+00:00","name_by_user":"Master bath bulb 4","name":"Third Reality, Inc 3RCB01057Z","primary_config_entry":"01JE2R7DPDQ8PCN6MMVWNFMDFN","serial_number":null,"sw_version":"0x00000038","via_device_id":"a6b0311faa8ba235909a9316b2313cc8"},
{"area_id":null,"config_entries":["01JE2R7DPDQ8PCN6MMVWNFMDFN"],"config_entries_subentries":{"01JE2R7DPDQ8PCN6MMVWNFMDFN":[null]},"configuration_url":null,"connections":[["zigbee","b4:e8:42:86:5f:60:00:00"]],"created_at":"2025-04-09T15:42:08.430328+00:00","disabled_by":null,"entry_type":null,"hw_version":null,"id":"9201a4b298d275159a1b7a76601ede56","identifiers":[["zha","b4:e8:42:86:5f:60:00:00"]],"labels":["monitored"],"manufacturer":"Third Reality, Inc","model":"3RCB01057Z","model_id":null,"modified_at":"2025-11-23T07:22:10.908729+00:00","name_by_user":"Master bath bulb 3","name":"Third Reality, Inc 3RCB01057Z","primary_config_entry":"01JE2R7DPDQ8PCN6MMVWNFMDFN","serial_number":null,"sw_version":"0x00000038","via_device_id":"a6b0311faa8ba235909a9316b2313cc8"},
{"area_id":null,"config_entries":["01JE2R7DPDQ8PCN6MMVWNFMDFN"],"config_entries_subentries":{"01JE2R7DPDQ8PCN6MMVWNFMDFN":[null]},"configuration_url":null,"connections":[["zigbee","b4:e8:42:86:55:8c:00:00"]],"created_at":"2025-04-09T15:42:08.622807+00:00","disabled_by":null,"entry_type":null,"hw_version":null,"id":"4ed9228b5d70e67ea576f4d0527b2437","identifiers":[["zha","b4:e8:42:86:55:8c:00:00"]],"labels":[],"manufacturer":"Third Reality, Inc","model":"3RCB01057Z","model_id":null,"modified_at":"2025-09-14T05:33:54.407161+00:00","name_by_user":"Lamp upper","name":"Third Reality, Inc 3RCB01057Z","primary_config_entry":"01JE2R7DPDQ8PCN6MMVWNFMDFN","serial_number":null,"sw_version":"0x00000038","via_device_id":"a6b0311faa8ba235909a9316b2313cc8"},
{"area_id":null,"config_entries":["01JE2R7DPDQ8PCN6MMVWNFMDFN"],"config_entries_subentries":{"01JE2R7DPDQ8PCN6MMVWNFMDFN":[null]},"configuration_url":null,"connections":[["zigbee","7c:b9:4c:6b:9f:db:00:00"]],"created_at":"2025-04-09T15:42:12.442627+00:00","disabled_by":null,"entry_type":null,"hw_version":null,"id":"8da72f490818d47c97c324d00612c391","identifiers":[["zha","7c:b9:4c:6b:9f:db:00:00"]],"labels":["monitored"],"manufacturer":"Third Reality, Inc","model":"3RCB01057Z","model_id":null,"modified_at":"2025-11-23T07:22:10.909008+00:00","name_by_user":"Master bath bulb 2","name":"Third Reality, Inc 3RCB01057Z","primary_config_entry":"01JE2R7DPDQ8PCN6MMVWNFMDFN","serial_number":null,"sw_version":"0x00000038","via_device_id":"a6b0311faa8ba235909a9316b2313cc8"},
{"area_id":null,"config_entries":["01JE2R7DPDQ8PCN6MMVWNFMDFN"],"config_entries_subentries":{"01JE2R7DPDQ8PCN6MMVWNFMDFN":[null]},"configuration_url":null,"connections":[["zigbee","7c:b9:4c:63:20:cc:00:00"]],"created_at":"2025-04-09T15:52:29.600851+00:00","disabled_by":null,"entry_type":null,"hw_version":null,"id":"a8b279dc583333b6f848bb719cd9370d","identifiers":[["zha","7c:b9:4c:63:20:cc:00:00"]],"labels":[],"manufacturer":"Third Reality, Inc","model":"3RCB01057Z","model_id":null,"modified_at":"2025-09-14T05:33:54.407254+00:00","name_by_user":"Lamp top","name":"Third Reality, Inc 3RCB01057Z","primary_config_entry":"01JE2R7DPDQ8PCN6MMVWNFMDFN","serial_number":null,"sw_version":"0x00000038","via_device_id":"a6b0311faa8ba235909a9316b2313cc8"},
{"area_id":null,"config_entries":["01JE2R7DPDQ8PCN6MMVWNFMDFN"],"config_entries_subentries":{"01JE2R7DPDQ8PCN6MMVWNFMDFN":[null]},"configuration_url":null,"connections":[["zigbee","7c:b9:4c:78:53:8e:00:00"]],"created_at":"2025-04-09T15:57:10.886890+00:00","disabled_by":null,"entry_type":null,"hw_version":null,"id":"047a342c6f6709cd88b1ce4f32c13051","identifiers":[["zha","7c:b9:4c:78:53:8e:00:00"]],"labels":[],"manufacturer":"Third Reality, Inc","model":"3RCB01057Z","model_id":null,"modified_at":"2025-09-14T05:33:54.407311+00:00","name_by_user":"Table light","name":"Third Reality, Inc 3RCB01057Z","primary_config_entry":"01JE2R7DPDQ8PCN6MMVWNFMDFN","serial_number":null,"sw_version":"0x00000038","via_device_id":"a6b0311faa8ba235909a9316b2313cc8"},
{"area_id":null,"config_entries":["01JE2PA1AVW34BP3R20RJ09CVZ"],"config_entries_subentries":{"01JE2PA1AVW34BP3R20RJ09CVZ":[null]},"configuration_url":"homeassistant://hassio/addon/core_piper","connections":[],"created_at":"2025-08-30T04:01:17.726900+00:00","disabled_by":null,"entry_type":"service","hw_version":null,"id":"a6f4fcf9773a9ebea1712108ffb237bd","identifiers":[["hassio","core_piper"]],"labels":[],"manufacturer":"Official add-ons","model":"Home Assistant Add-on","model_id":null,"modified_at":"2025-12-13T07:33:54.012014+00:00","name_by_user":null,"name":"Piper","primary_config_entry":"01JE2PA1AVW34BP3R20RJ09CVZ","serial_number":null,"sw_version":"2.1.1","via_device_id":null},
{"area_id":null,"config_entries":["01JE2PA1AVW34BP3R20RJ09CVZ"],"config_entries_subentries":{"01JE2PA1AVW34BP3R20RJ09CVZ":[null]},"configuration_url":"homeassistant://hassio/addon/core_whisper","connections":[],"created_at":"2025-08-30T04:01:17.727348+00:00","disabled_by":null,"entry_type":"service","hw_version":null,"id":"ae44276fcaa8b2957fe65fa4fad449b4","identifiers":[["hassio","core_whisper"]],"labels":[],"manufacturer":"Official add-ons","model":"Home Assistant Add-on","model_id":null,"modified_at":"2025-12-13T07:33:54.012066+00:00","name_by_user":null,"name":"Whisper","primary_config_entry":"01JE2PA1AVW34BP3R20RJ09CVZ","serial_number":null,"sw_version":"3.0.1","via_device_id":null},
{"area_id":null,"config_entries":["01JE2PA1AVW34BP3R20RJ09CVZ"],"config_entries_subentries":{"01JE2PA1AVW34BP3R20RJ09CVZ":[null]},"configuration_url":"homeassistant://hassio/addon/core_speech-to-phrase","connections":[],"created_at":"2025-09-10T17:56:17.577314+00:00","disabled_by":null,"entry_type":"service","hw_version":null,"id":"ce0db12aef730bce65d43ef3fc259d29","identifiers":[["hassio","core_speech-to-phrase"]],"labels":[],"manufacturer":"Official add-ons","model":"Home Assistant Add-on","model_id":null,"modified_at":"2025-09-10T17:56:17.577487+00:00","name_by_user":null,"name":"Speech-to-Phrase","primary_config_entry":"01JE2PA1AVW34BP3R20RJ09CVZ","serial_number":null,"sw_version":"1.4.1","via_device_id":null},
{"area_id":null,"config_entries":["01K53BECFWDC572ETZWMDH3PG7"],"config_entries_subentries":{"01K53BECFWDC572ETZWMDH3PG7":[null]},"configuration_url":"homeassistant://config/backup","connections":[],"created_at":"2025-09-14T05:33:55.078922+00:00","disabled_by":null,"entry_type":"service","hw_version":null,"id":"1921e82735fa1585aedb4492bb5938b3","identifiers":[["backup","backup_manager"]],"labels":[],"manufacturer":"Home Assistant","model":"Home Assistant Backup","model_id":null,"modified_at":"2025-12-31T06:44:49.115426+00:00","name_by_user":null,"name":"Backup","primary_config_entry":"01K53BECFWDC572ETZWMDH3PG7","serial_number":null,"sw_version":"2025.12.5","via_device_id":null},
{"area_id":null,"config_entries":["01JE2PA1AVW34BP3R20RJ09CVZ"],"config_entries_subentries":{"01JE2PA1AVW34BP3R20RJ09CVZ":[null]},"configuration_url":"homeassistant://hassio/addon/d5369777_music_assistant","connections":[],"created_at":"2025-11-03T00:08:43.131725+00:00","disabled_by":null,"entry_type":"service","hw_version":null,"id":"b78f9c386eeb09f789d187fd42a9788c","identifiers":[["hassio","d5369777_music_assistant"]],"labels":[],"manufacturer":"Music Assistant","model":"Home Assistant Add-on","model_id":null,"modified_at":"2026-01-31T04:56:02.709569+00:00","name_by_user":null,"name":"Music Assistant","primary_config_entry":"01JE2PA1AVW34BP3R20RJ09CVZ","serial_number":null,"sw_version":"2.7.2","via_device_id":null},
{"area_id":null,"config_entries":["01JE2PA1AVW34BP3R20RJ09CVZ"],"config_entries_subentries":{"01JE2PA1AVW34BP3R20RJ09CVZ":[null]},"configuration_url":"homeassistant://hassio/addon/2a018ee8_squeezelite","connections":[],"created_at":"2025-11-04T02:53:43.127497+00:00","disabled_by":null,"entry_type":"service","hw_version":null,"id":"5e0251a30811df242ea2fe5fc1228fcd","identifiers":[["hassio","2a018ee8_squeezelite"]],"labels":[],"manufacturer":"Squeezelite for Logitech Media Server Addon for Home Assistant","model":"Home Assistant Add-on","model_id":null,"modified_at":"2025-11-04T02:53:43.127512+00:00","name_by_user":null,"name":"Squeezelite","primary_config_entry":"01JE2PA1AVW34BP3R20RJ09CVZ","serial_number":null,"sw_version":"0.0.21","via_device_id":null},
{"area_id":null,"config_entries":["01JE2PA1AVW34BP3R20RJ09CVZ"],"config_entries_subentries":{"01JE2PA1AVW34BP3R20RJ09CVZ":[null]},"configuration_url":"homeassistant://hassio/addon/a0d7b954_spotify","connections":[],"created_at":"2025-11-04T03:28:42.774361+00:00","disabled_by":null,"entry_type":"service","hw_version":null,"id":"375aa9e2465c298b7dfd40aadd1e8751","identifiers":[["hassio","a0d7b954_spotify"]],"labels":[],"manufacturer":"Home Assistant Community Add-ons","model":"Home Assistant Add-on","model_id":null,"modified_at":"2025-12-13T07:33:54.012159+00:00","name_by_user":null,"name":"Spotify Connect","primary_config_entry":"01JE2PA1AVW34BP3R20RJ09CVZ","serial_number":null,"sw_version":"0.17.0","via_device_id":null},
{"area_id":"living_room","config_entries":["01K9KQJJG0AXR3PCXD8Y3NCHGM"],"config_entries_subentries":{"01K9KQJJG0AXR3PCXD8Y3NCHGM":[null]},"configuration_url":null,"connections":[],"created_at":"2024-12-29T05:50:44.020063+00:00","disabled_by":null,"entry_type":null,"hw_version":null,"id":"9d81266973e5ff00a4e9f974e8b3b621","identifiers":[["litterrobot","LR4C187718"]],"labels":[],"manufacturer":"Whisker","model":"Litter-Robot 4","model_id":null,"modified_at":"2025-11-09T07:27:10.925070+00:00","name_by_user":null,"name":"Litter-Robot 4","primary_config_entry":"01K9KQJJG0AXR3PCXD8Y3NCHGM","serial_number":"LR4C187718","sw_version":"ESP: 1.1.75 / PIC: 10500.3072.2.93 / TOF: 5.0.2.1","via_device_id":null},
{"area_id":null,"config_entries":["01K9KQJJG0AXR3PCXD8Y3NCHGM"],"config_entries_subentries":{"01K9KQJJG0AXR3PCXD8Y3NCHGM":[null]},"configuration_url":null,"connections":[],"created_at":"2025-09-14T05:50:32.116778+00:00","disabled_by":null,"entry_type":null,"hw_version":null,"id":"10ca6ddb3fd866229110338ebd81d989","identifiers":[["litterrobot","PET-193d8c25-73bb-4843-8c37-a4e5df8987b5"]],"labels":[],"manufacturer":"Whisker","model":"Domestic_shorthair cat","model_id":null,"modified_at":"2025-11-09T07:27:10.929101+00:00","name_by_user":null,"name":"Arturo","primary_config_entry":"01K9KQJJG0AXR3PCXD8Y3NCHGM","serial_number":null,"sw_version":null,"via_device_id":null},
{"area_id":null,"config_entries":["01K9KQJJG0AXR3PCXD8Y3NCHGM"],"config_entries_subentries":{"01K9KQJJG0AXR3PCXD8Y3NCHGM":[null]},"configuration_url":null,"connections":[],"created_at":"2025-09-14T05:50:32.117890+00:00","disabled_by":null,"entry_type":null,"hw_version":null,"id":"74e3b79ec6979e84665f1a770cf8ba0d","identifiers":[["litterrobot","PET-aa03c134-af7c-487b-951f-13756112a701"]],"labels":[],"manufacturer":"Whisker","model":"American_shorthair cat","model_id":null,"modified_at":"2025-11-09T07:27:10.929643+00:00","name_by_user":null,"name":"Emrys","primary_config_entry":"01K9KQJJG0AXR3PCXD8Y3NCHGM","serial_number":null,"sw_version":null,"via_device_id":null},
{"area_id":null,"config_entries":["01JE2SQ4DY1D75FFMD9PGMNER0"],"config_entries_subentries":{"01JE2SQ4DY1D75FFMD9PGMNER0":[null]},"configuration_url":"homeassistant://hacs/repository/989077527","connections":[],"created_at":"2025-11-30T03:49:47.588228+00:00","disabled_by":null,"entry_type":"service","hw_version":null,"id":"2732411e069c551daf1de7cb06910946","identifiers":[["hacs","989077527"]],"labels":[],"manufacturer":"cavefire","model":"integration","model_id":null,"modified_at":"2025-11-30T03:49:47.588440+00:00","name_by_user":null,"name":"openid","primary_config_entry":"01JE2SQ4DY1D75FFMD9PGMNER0","serial_number":null,"sw_version":null,"via_device_id":null},
{"area_id":null,"config_entries":["01JE2PA1AVW34BP3R20RJ09CVZ"],"config_entries_subentries":{"01JE2PA1AVW34BP3R20RJ09CVZ":[null]},"configuration_url":"homeassistant://hassio/addon/a0d7b954_tailscale","connections":[],"created_at":"2025-11-30T05:27:48.474089+00:00","disabled_by":null,"entry_type":"service","hw_version":null,"id":"924718a806a34c291e08665b03918a9e","identifiers":[["hassio","a0d7b954_tailscale"]],"labels":[],"manufacturer":"Home Assistant Community Add-ons","model":"Home Assistant Add-on","model_id":null,"modified_at":"2025-11-30T05:27:48.474110+00:00","name_by_user":null,"name":"Tailscale","primary_config_entry":"01JE2PA1AVW34BP3R20RJ09CVZ","serial_number":null,"sw_version":"0.26.1","via_device_id":null},
{"area_id":null,"config_entries":["01JE2R7DPDQ8PCN6MMVWNFMDFN"],"config_entries_subentries":{"01JE2R7DPDQ8PCN6MMVWNFMDFN":[null]},"configuration_url":null,"connections":[["zigbee","ff:ff:b4:0e:06:02:fc:22"]],"created_at":"2025-12-08T20:52:39.754157+00:00","disabled_by":null,"entry_type":null,"hw_version":null,"id":"3e9f76ced7f9bfd888f501c832f56d79","identifiers":[["zha","ff:ff:b4:0e:06:02:fc:22"]],"labels":[],"manufacturer":"Third Reality, Inc","model":"3RTHS24BZ","model_id":null,"modified_at":"2025-12-08T20:53:26.784163+00:00","name_by_user":"Humidity sensor 1","name":"Third Reality, Inc 3RTHS24BZ","primary_config_entry":"01JE2R7DPDQ8PCN6MMVWNFMDFN","serial_number":null,"sw_version":"0x00000025","via_device_id":"a6b0311faa8ba235909a9316b2313cc8"},
{"area_id":null,"config_entries":["01JE2R7DPDQ8PCN6MMVWNFMDFN"],"config_entries_subentries":{"01JE2R7DPDQ8PCN6MMVWNFMDFN":[null]},"configuration_url":null,"connections":[["zigbee","ff:ff:b4:0e:06:03:14:0d"]],"created_at":"2025-12-09T15:09:51.777015+00:00","disabled_by":null,"entry_type":null,"hw_version":null,"id":"1798eca71ca7e6a2057f26460893dc1f","identifiers":[["zha","ff:ff:b4:0e:06:03:14:0d"]],"labels":[],"manufacturer":"Third Reality, Inc","model":"3RTHS24BZ","model_id":null,"modified_at":"2025-12-09T15:10:06.650788+00:00","name_by_user":"Humidity 2","name":"Third Reality, Inc 3RTHS24BZ","primary_config_entry":"01JE2R7DPDQ8PCN6MMVWNFMDFN","serial_number":null,"sw_version":"0x00000025","via_device_id":"a6b0311faa8ba235909a9316b2313cc8"},
{"area_id":"bedroom","config_entries":["01JE2R7DPDQ8PCN6MMVWNFMDFN"],"config_entries_subentries":{"01JE2R7DPDQ8PCN6MMVWNFMDFN":[null]},"configuration_url":null,"connections":[["zigbee","ff:ff:b4:0e:06:06:41:ca"]],"created_at":"2025-12-11T18:48:35.033465+00:00","disabled_by":null,"entry_type":null,"hw_version":null,"id":"899ae685d236a458fa035a61d30367fa","identifiers":[["zha","ff:ff:b4:0e:06:06:41:ca"]],"labels":["monitored"],"manufacturer":"Third Reality, Inc","model":"3RSS009Z","model_id":null,"modified_at":"2025-12-31T06:32:00.682651+00:00","name_by_user":"Master bath fan","name":"Third Reality, Inc 3RSS009Z","primary_config_entry":"01JE2R7DPDQ8PCN6MMVWNFMDFN","serial_number":null,"sw_version":"0x0000001e","via_device_id":"a6b0311faa8ba235909a9316b2313cc8"},
{"area_id":null,"config_entries":["01JE2SQ4DY1D75FFMD9PGMNER0"],"config_entries_subentries":{"01JE2SQ4DY1D75FFMD9PGMNER0":[null]},"configuration_url":"homeassistant://hacs/repository/307098646","connections":[],"created_at":"2025-12-13T06:36:48.563621+00:00","disabled_by":null,"entry_type":"service","hw_version":null,"id":"376045287ad5290ff6cc0356ea3b1ce8","identifiers":[["hacs","307098646"]],"labels":[],"manufacturer":"nielsfaber","model":"integration","model_id":null,"modified_at":"2025-12-13T06:36:48.563664+00:00","name_by_user":null,"name":"Alarmo","primary_config_entry":"01JE2SQ4DY1D75FFMD9PGMNER0","serial_number":null,"sw_version":null,"via_device_id":null},
{"area_id":"misc","config_entries":["01KCB6VYHK4W6DCTV260B9RD3M"],"config_entries_subentries":{"01KCB6VYHK4W6DCTV260B9RD3M":[null]},"configuration_url":null,"connections":[],"created_at":"2025-12-13T06:37:44.884851+00:00","disabled_by":null,"entry_type":null,"hw_version":null,"id":"acf965d02296e7bf52a3c05c0cee8bad","identifiers":[["alarmo","10d15907fccf"]],"labels":[],"manufacturer":"@nielsfaber","model":"Alarmo","model_id":null,"modified_at":"2025-12-13T06:37:53.100453+00:00","name_by_user":null,"name":"Alarmo","primary_config_entry":"01KCB6VYHK4W6DCTV260B9RD3M","serial_number":null,"sw_version":"1.10.13","via_device_id":null},
{"area_id":null,"config_entries":["01JE2SQ4DY1D75FFMD9PGMNER0"],"config_entries_subentries":{"01JE2SQ4DY1D75FFMD9PGMNER0":[null]},"configuration_url":"homeassistant://hacs/repository/939311749","connections":[],"created_at":"2025-12-15T03:24:11.076092+00:00","disabled_by":null,"entry_type":"service","hw_version":null,"id":"269d4c1881008301bd823d54665682af","identifiers":[["hacs","939311749"]],"labels":[],"manufacturer":"alexpfau","model":"plugin","model_id":null,"modified_at":"2025-12-15T03:24:11.076645+00:00","name_by_user":null,"name":"Calendar Card Pro","primary_config_entry":"01JE2SQ4DY1D75FFMD9PGMNER0","serial_number":null,"sw_version":null,"via_device_id":null},
{"area_id":"living_room","config_entries":["01JE2R7DPDQ8PCN6MMVWNFMDFN"],"config_entries_subentries":{"01JE2R7DPDQ8PCN6MMVWNFMDFN":[null]},"configuration_url":null,"connections":[["zigbee","ff:ff:b4:0e:06:06:47:70"]],"created_at":"2025-12-15T03:35:30.737897+00:00","disabled_by":null,"entry_type":null,"hw_version":null,"id":"411a10657bf317baba94da81ed456d3e","identifiers":[["zha","ff:ff:b4:0e:06:06:47:70"]],"labels":[],"manufacturer":"Third Reality, Inc","model":"3RSS009Z","model_id":null,"modified_at":"2025-12-15T03:44:43.241637+00:00","name_by_user":"unused switch","name":"Third Reality, Inc 3RSS009Z","primary_config_entry":"01JE2R7DPDQ8PCN6MMVWNFMDFN","serial_number":null,"sw_version":"0x0000001e","via_device_id":"a6b0311faa8ba235909a9316b2313cc8"},
{"area_id":"living_room","config_entries":["01KDH8M5SF0CTB4F4G6TXAP6SZ"],"config_entries_subentries":{"01KDH8M5SF0CTB4F4G6TXAP6SZ":[null]},"configuration_url":"https://192.168.0.210:443","connections":[["mac","8c:7a:b3:30:bb:52"]],"created_at":"2025-12-28T01:19:39.350151+00:00","disabled_by":null,"entry_type":null,"hw_version":"IPC_NT1NA45MP","id":"50c1be5a8001d3b2c9341a0a9013b5d4","identifiers":[["onvif","8c:7a:b3:30:bb:52"],["reolink","9527000K323YAM2G"]],"labels":[],"manufacturer":"Reolink","model":"E1 Pro","model_id":"E Series E330","modified_at":"2025-12-31T06:40:05.071501+00:00","name_by_user":null,"name":"cat cam 1","primary_config_entry":"01KDH8M5SF0CTB4F4G6TXAP6SZ","serial_number":"9527000K323YAM2G","sw_version":"v3.1.0.4417_2412122130","via_device_id":null},
{"area_id":"living_room","config_entries":["01KDH98BB0VPBMK5MYNVK08ZZE"],"config_entries_subentries":{"01KDH98BB0VPBMK5MYNVK08ZZE":[null]},"configuration_url":null,"connections":[],"created_at":"2025-12-28T01:30:36.769781+00:00","disabled_by":null,"entry_type":null,"hw_version":null,"id":"3d32fca1951073312072807194d69498","identifiers":[["generic","01KDH98BB0VPBMK5MYNVK08ZZE"]],"labels":[],"manufacturer":"Generic","model":null,"model_id":null,"modified_at":"2025-12-28T01:30:45.928530+00:00","name_by_user":"Cat Cam Feed","name":"192_168_0_210","primary_config_entry":"01KDH98BB0VPBMK5MYNVK08ZZE","serial_number":null,"sw_version":null,"via_device_id":null},
{"area_id":null,"config_entries":["01JE2SQ4DY1D75FFMD9PGMNER0"],"config_entries_subentries":{"01JE2SQ4DY1D75FFMD9PGMNER0":[null]},"configuration_url":"homeassistant://hacs/repository/394082552","connections":[],"created_at":"2025-12-28T01:58:12.841284+00:00","disabled_by":null,"entry_type":"service","hw_version":null,"id":"4352fe50de17b82f448500509c8403f9","identifiers":[["hacs","394082552"]],"labels":[],"manufacturer":"dermotduffy","model":"plugin","model_id":null,"modified_at":"2025-12-28T01:58:12.841304+00:00","name_by_user":null,"name":"Advanced Camera Card","primary_config_entry":"01JE2SQ4DY1D75FFMD9PGMNER0","serial_number":null,"sw_version":null,"via_device_id":null},
{"area_id":null,"config_entries":["01JE2PA1AVW34BP3R20RJ09CVZ"],"config_entries_subentries":{"01JE2PA1AVW34BP3R20RJ09CVZ":[null]},"configuration_url":"homeassistant://hassio/addon/core_samba","connections":[],"created_at":"2026-01-31T04:56:02.709713+00:00","disabled_by":null,"entry_type":"service","hw_version":null,"id":"f2c219cf3a169075d30362fc47cd2092","identifiers":[["hassio","core_samba"]],"labels":[],"manufacturer":"Official add-ons","model":"Home Assistant Add-on","model_id":null,"modified_at":"2026-01-31T04:56:02.709737+00:00","name_by_user":null,"name":"Samba share","primary_config_entry":"01JE2PA1AVW34BP3R20RJ09CVZ","serial_number":null,"sw_version":"12.5.4","via_device_id":null}
],
"deleted_devices": [
{"area_id":null,"config_entries":["01JE2PA1AVW34BP3R20RJ09CVZ"],"config_entries_subentries":{"01JE2PA1AVW34BP3R20RJ09CVZ":[null]},"connections":[],"created_at":"2024-12-02T04:16:23.167541+00:00","disabled_by":null,"disabled_by_undefined":true,"identifiers":[["hassio","cb646a50_get"]],"id":"8c86e72f9b4b1e723c1e11420d8d8246","labels":[],"modified_at":"2024-12-02T04:18:04.003423+00:00","name_by_user":null,"orphaned_timestamp":null},
{"area_id":null,"config_entries":["01JE2PA1AVW34BP3R20RJ09CVZ"],"config_entries_subentries":{"01JE2PA1AVW34BP3R20RJ09CVZ":[null]},"connections":[],"created_at":"2025-11-04T02:53:43.127285+00:00","disabled_by":null,"disabled_by_undefined":false,"identifiers":[["hassio","2a018ee8_squeezelite1"]],"id":"7447759e3f364f084f58ef4fef289fd3","labels":[],"modified_at":"2025-11-04T02:58:42.750902+00:00","name_by_user":null,"orphaned_timestamp":null},
{"area_id":null,"config_entries":["01JE2R7DPDQ8PCN6MMVWNFMDFN"],"config_entries_subentries":{"01JE2R7DPDQ8PCN6MMVWNFMDFN":[null]},"connections":[["zigbee","00:15:8d:00:0a:f3:89:48"]],"created_at":"2024-12-04T03:30:01.766338+00:00","disabled_by":"user","disabled_by_undefined":false,"identifiers":[["zha","00:15:8d:00:0a:f3:89:48"]],"id":"ca93d3e4bd1c8311594c93931ce0b269","labels":[],"modified_at":"2025-11-23T17:30:35.199602+00:00","name_by_user":"pending return","orphaned_timestamp":null},
{"area_id":null,"config_entries":["01JE2PA1AVW34BP3R20RJ09CVZ"],"config_entries_subentries":{"01JE2PA1AVW34BP3R20RJ09CVZ":[null]},"connections":[],"created_at":"2025-12-17T05:04:26.573243+00:00","disabled_by":null,"disabled_by_undefined":false,"identifiers":[["hassio","a0d7b954_nginxproxymanager"]],"id":"ebadfc9caddd8f8694ef8eae10d9d6cd","labels":[],"modified_at":"2025-12-17T05:13:15.092364+00:00","name_by_user":null,"orphaned_timestamp":null},
{"area_id":null,"config_entries":[],"config_entries_subentries":{},"connections":[],"created_at":"2024-12-02T03:26:55.783716+00:00","disabled_by":null,"disabled_by_undefined":false,"identifiers":[["tuya","tys3F3Oj0ssOzFCHtUe"]],"id":"72892ed9db345cf7128591bdd32679fd","labels":[],"modified_at":"2026-01-30T03:35:33.924182+00:00","name_by_user":null,"orphaned_timestamp":1769744133.9055798},
{"area_id":null,"config_entries":[],"config_entries_subentries":{},"connections":[],"created_at":"2024-12-02T03:26:55.638706+00:00","disabled_by":null,"disabled_by_undefined":false,"identifiers":[["tuya","eb951df86c9d584b4dxweb"]],"id":"ae0f584b8a4c29d9560123f19f4b4cae","labels":[],"modified_at":"2026-01-30T03:35:37.586118+00:00","name_by_user":null,"orphaned_timestamp":1769744137.5860384},
{"area_id":null,"config_entries":[],"config_entries_subentries":{},"connections":[],"created_at":"2024-12-02T03:26:55.639248+00:00","disabled_by":null,"disabled_by_undefined":false,"identifiers":[["tuya","eb266309af73dd8168qvda"]],"id":"db2d7eba70cfbb9407f6daf550c633f9","labels":[],"modified_at":"2026-01-30T03:35:37.588317+00:00","name_by_user":null,"orphaned_timestamp":1769744137.5860384},
{"area_id":null,"config_entries":[],"config_entries_subentries":{},"connections":[],"created_at":"2024-12-02T03:26:55.639728+00:00","disabled_by":"user","disabled_by_undefined":false,"identifiers":[["tuya","eb2e83f79423674858pd1v"]],"id":"58b0b27e9307aa59cc938fed19598d6b","labels":[],"modified_at":"2026-01-30T03:35:37.590040+00:00","name_by_user":"unused7","orphaned_timestamp":1769744137.5860384},
{"area_id":null,"config_entries":[],"config_entries_subentries":{},"connections":[],"created_at":"2024-12-02T03:26:55.639885+00:00","disabled_by":"user","disabled_by_undefined":false,"identifiers":[["tuya","eb9c02171d09cebfc2mldh"]],"id":"b3b33083b1117f141e89a43787484471","labels":[],"modified_at":"2026-01-30T03:35:37.590483+00:00","name_by_user":"unused5","orphaned_timestamp":1769744137.5860384},
{"area_id":null,"config_entries":[],"config_entries_subentries":{},"connections":[],"created_at":"2024-12-02T03:26:55.640035+00:00","disabled_by":"user","disabled_by_undefined":false,"identifiers":[["tuya","ebd3b761cb158547e2plxf"]],"id":"6fd412561d4776f81b43b1a5e226ad89","labels":[],"modified_at":"2026-01-30T03:35:37.590922+00:00","name_by_user":"unused6","orphaned_timestamp":1769744137.5860384},
{"area_id":null,"config_entries":[],"config_entries_subentries":{},"connections":[],"created_at":"2024-12-02T03:26:55.640205+00:00","disabled_by":"user","disabled_by_undefined":false,"identifiers":[["tuya","eb5dfbb788a613dd337jxm"]],"id":"22451fef0266a6f4a4947785e17153f3","labels":[],"modified_at":"2026-01-30T03:35:37.591327+00:00","name_by_user":"unused8","orphaned_timestamp":1769744137.5860384},
{"area_id":"unused","config_entries":[],"config_entries_subentries":{},"connections":[],"created_at":"2024-12-02T03:26:55.640434+00:00","disabled_by":"user","disabled_by_undefined":false,"identifiers":[["tuya","eb5a3e353ef0ebed56dkgt"]],"id":"508a4085cc89b12429e4973467e58998","labels":[],"modified_at":"2026-01-30T03:35:37.591671+00:00","name_by_user":"unused","orphaned_timestamp":1769744137.5860384},
{"area_id":null,"config_entries":[],"config_entries_subentries":{},"connections":[],"created_at":"2024-12-02T03:26:55.640580+00:00","disabled_by":"user","disabled_by_undefined":false,"identifiers":[["tuya","eb4a288f8b6acfbb51wwba"]],"id":"e5a218d2a67e4b70c38af2c9b253714c","labels":[],"modified_at":"2026-01-30T03:35:37.592043+00:00","name_by_user":"unused4","orphaned_timestamp":1769744137.5860384},
{"area_id":null,"config_entries":[],"config_entries_subentries":{},"connections":[],"created_at":"2024-12-02T03:26:55.640724+00:00","disabled_by":"user","disabled_by_undefined":false,"identifiers":[["tuya","eb4761301bfb6e145876pn"]],"id":"5c7ee32fd4ec498948c09a7d53546625","labels":[],"modified_at":"2026-01-30T03:35:37.592392+00:00","name_by_user":"unused2","orphaned_timestamp":1769744137.5860384},
{"area_id":null,"config_entries":[],"config_entries_subentries":{},"connections":[],"created_at":"2024-12-02T03:26:55.640870+00:00","disabled_by":"user","disabled_by_undefined":false,"identifiers":[["tuya","eb5267bd31fd931b26bcmk"]],"id":"8bbb95568635b8ae51254088009e9472","labels":[],"modified_at":"2026-01-30T03:35:37.592725+00:00","name_by_user":"unused3","orphaned_timestamp":1769744137.5860384},
{"area_id":"garage","config_entries":[],"config_entries_subentries":{},"connections":[],"created_at":"2024-12-02T03:26:55.639552+00:00","disabled_by":null,"disabled_by_undefined":false,"identifiers":[["tuya","eb9771d7485c6103c1gohf"]],"id":"439c9948bce28a6cb03062294f5fa3f2","labels":[],"modified_at":"2026-01-30T03:35:37.593103+00:00","name_by_user":null,"orphaned_timestamp":1769744137.5860384},
{"area_id":null,"config_entries":["01JE2PA1AVW34BP3R20RJ09CVZ"],"config_entries_subentries":{"01JE2PA1AVW34BP3R20RJ09CVZ":[null]},"connections":[],"created_at":"2025-12-31T05:56:36.749132+00:00","disabled_by":null,"disabled_by_undefined":false,"identifiers":[["hassio","a0d7b954_vscode"]],"id":"f958e992824582d715ca5082a6bcf892","labels":[],"modified_at":"2026-01-31T04:56:02.634130+00:00","name_by_user":null,"orphaned_timestamp":null}
]
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,72 @@
{
"version": 1,
"minor_version": 2,
"key": "core.label_registry",
"data": {
"labels": [
{
"color": null,
"description": null,
"icon": "mdi:fan",
"label_id": "bedroom_fan",
"name": "Bedroom Fan",
"created_at": "2024-12-23T23:55:42.769576+00:00",
"modified_at": "2024-12-23T23:55:42.769578+00:00"
},
{
"color": null,
"description": null,
"icon": null,
"label_id": "kitchen_sink",
"name": "kitchen sink",
"created_at": "2025-02-15T20:30:44.679131+00:00",
"modified_at": "2025-02-15T20:30:44.679141+00:00"
},
{
"color": null,
"description": null,
"icon": null,
"label_id": "downstair_bathroom_sink",
"name": "Downstair Bathroom Sink",
"created_at": "2025-02-15T22:13:40.836699+00:00",
"modified_at": "2025-02-15T22:13:40.836708+00:00"
},
{
"color": null,
"description": null,
"icon": null,
"label_id": "officesink",
"name": "OfficeSink",
"created_at": "2025-02-15T22:13:54.343477+00:00",
"modified_at": "2025-02-15T22:13:54.343484+00:00"
},
{
"color": null,
"description": null,
"icon": null,
"label_id": "bedroomsink",
"name": "BedroomSink",
"created_at": "2025-02-15T22:14:08.089260+00:00",
"modified_at": "2025-02-15T22:14:08.089262+00:00"
},
{
"color": null,
"description": null,
"icon": null,
"label_id": "waterheater",
"name": "WaterHeater",
"created_at": "2025-02-15T22:14:19.496443+00:00",
"modified_at": "2025-02-15T22:14:19.496451+00:00"
},
{
"color": null,
"description": null,
"icon": "mdi:alarm-light",
"label_id": "monitored",
"name": "Monitored",
"created_at": "2025-11-23T07:22:10.879929+00:00",
"modified_at": "2025-11-23T07:22:10.879937+00:00"
}
]
}
}

8
.storage/core.logger Normal file
View File

@@ -0,0 +1,8 @@
{
"version": 1,
"minor_version": 1,
"key": "core.logger",
"data": {
"logs": {}
}
}

2146
.storage/core.restore_state Normal file

File diff suppressed because it is too large Load Diff

8
.storage/core.uuid Normal file
View File

@@ -0,0 +1,8 @@
{
"version": 1,
"minor_version": 1,
"key": "core.uuid",
"data": {
"uuid": "3350d98d61e8432e830448e3f05fc88e"
}
}

View File

@@ -0,0 +1,185 @@
{
"version": 1,
"minor_version": 1,
"key": "esphome.01JR73AMVCG4FGGV23MYMXWNJT",
"data": {
"device_info": {
"uses_password": false,
"name": "home-assistant-voice-0981b4",
"friendly_name": "Home Assistant Voice 0981b4",
"mac_address": "20:F8:3B:09:81:B4",
"compilation_time": "Jun 29 2025, 14:08:10",
"model": "esp32-s3-devkitc-1",
"manufacturer": "Espressif",
"has_deep_sleep": false,
"esphome_version": "2025.6.2",
"project_name": "Nabu Casa.Home Assistant Voice PE",
"project_version": "25.6.0",
"webserver_port": 0,
"legacy_voice_assistant_version": 1,
"voice_assistant_feature_flags": 61,
"legacy_bluetooth_proxy_version": 0,
"bluetooth_proxy_feature_flags": 0,
"zwave_proxy_feature_flags": 0,
"zwave_home_id": 0,
"suggested_area": "",
"bluetooth_mac_address": "",
"api_encryption_supported": false,
"devices": [],
"areas": [],
"area": {
"area_id": 0,
"name": ""
}
},
"services": [],
"api_version": {
"major": 1,
"minor": 10
},
"media_player": [
{
"object_id": "media_player",
"key": 2232357057,
"name": "Media Player",
"disabled_by_default": false,
"icon": "",
"entity_category": 0,
"device_id": 0,
"supports_pause": true,
"supported_formats": [
{
"format": "flac",
"sample_rate": 48000,
"num_channels": 1,
"purpose": 1,
"sample_bytes": 2
},
{
"format": "flac",
"sample_rate": 48000,
"num_channels": 2,
"purpose": 0,
"sample_bytes": 2
}
],
"feature_flags": 0
}
],
"binary_sensor": [],
"light": [
{
"object_id": "led_ring",
"key": 1459351809,
"name": "LED Ring",
"disabled_by_default": false,
"icon": "mdi:circle-outline",
"entity_category": 1,
"device_id": 0,
"supported_color_modes": [
35
],
"min_mireds": 0.0,
"max_mireds": 0.0,
"effects": [],
"legacy_supports_brightness": true,
"legacy_supports_rgb": true,
"legacy_supports_white_value": false,
"legacy_supports_color_temperature": false
}
],
"select": [
{
"object_id": "wake_word_sensitivity",
"key": 666792156,
"name": "Wake word sensitivity",
"disabled_by_default": false,
"icon": "",
"entity_category": 1,
"device_id": 0,
"options": [
"Slightly sensitive",
"Moderately sensitive",
"Very sensitive"
]
}
],
"button": [
{
"object_id": "restart",
"key": 1203400786,
"name": "Restart",
"disabled_by_default": true,
"icon": "mdi:restart",
"entity_category": 1,
"device_id": 0,
"device_class": "restart"
}
],
"update": [
{
"object_id": "home_assistant_voice_0981b4",
"key": 1265841631,
"name": "",
"disabled_by_default": false,
"icon": "",
"entity_category": 1,
"device_id": 0,
"device_class": ""
}
],
"event": [
{
"object_id": "button_press",
"key": 2698747613,
"name": "Button press",
"disabled_by_default": false,
"icon": "mdi:button-pointer",
"entity_category": 0,
"device_id": 0,
"device_class": "button",
"event_types": [
"double_press",
"easter_egg_press",
"long_press",
"triple_press"
]
}
],
"switch": [
{
"object_id": "mute",
"key": 2974103762,
"name": "Mute",
"disabled_by_default": false,
"icon": "mdi:microphone-off",
"entity_category": 1,
"device_id": 0,
"assumed_state": false,
"device_class": ""
},
{
"object_id": "wake_sound",
"key": 3517166597,
"name": "Wake sound",
"disabled_by_default": false,
"icon": "mdi:bullhorn",
"entity_category": 1,
"device_id": 0,
"assumed_state": false,
"device_class": ""
},
{
"object_id": "beta_firmware",
"key": 3963827797,
"name": "Beta firmware",
"disabled_by_default": true,
"icon": "mdi:test-tube",
"entity_category": 1,
"device_id": 0,
"assumed_state": false,
"device_class": ""
}
]
}
}

View File

@@ -0,0 +1,10 @@
{
"version": 1,
"minor_version": 1,
"key": "frontend.system_data",
"data": {
"core": {
"default_panel": "my-dashboard-1"
}
}
}

View File

@@ -0,0 +1,18 @@
{
"version": 1,
"minor_version": 1,
"key": "frontend.user_data_4a285b563f6a4a54b657acf1926c0b5b",
"data": {
"language": {
"language": "en",
"number_format": "language",
"time_format": "language",
"date_format": "language",
"time_zone": "local",
"first_weekday": "language"
},
"core": {
"showAdvanced": true
}
}
}

View File

@@ -0,0 +1,15 @@
{
"version": 1,
"minor_version": 1,
"key": "frontend.user_data_9ba78f5ab4e646758f3c093bd79ac9c8",
"data": {
"language": {
"language": "en",
"number_format": "language",
"time_format": "language",
"date_format": "language",
"time_zone": "local",
"first_weekday": "language"
}
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

13
.storage/hacs.critical Normal file
View File

@@ -0,0 +1,13 @@
{
"version": "6",
"minor_version": 1,
"key": "hacs.critical",
"data": [
{
"repository": "test/test",
"reason": "Security issues, known to steal auth tokens.",
"link": "https://github.com/hacs/default/pull/2",
"acknowledged": true
}
]
}

12732
.storage/hacs.data Normal file

File diff suppressed because it is too large Load Diff

10
.storage/hacs.hacs Normal file
View File

@@ -0,0 +1,10 @@
{
"version": "6",
"minor_version": 1,
"key": "hacs.hacs",
"data": {
"archived_repositories": [],
"renamed_repositories": {},
"ignored_repositories": []
}
}

42025
.storage/hacs.repositories Normal file

File diff suppressed because it is too large Load Diff

8
.storage/hassio Normal file
View File

@@ -0,0 +1,8 @@
{
"version": 1,
"minor_version": 1,
"key": "hassio",
"data": {
"hassio_user": "5e62410fa88e4aa7ab4ac7b9c4ba5dc1"
}
}

File diff suppressed because it is too large Load Diff

19
.storage/http Normal file
View File

@@ -0,0 +1,19 @@
{
"version": 1,
"minor_version": 1,
"key": "http",
"data": {
"use_x_forwarded_for": true,
"trusted_proxies": [
"192.168.0.249"
],
"ip_ban_enabled": true,
"cors_allowed_origins": [
"https://cast.home-assistant.io"
],
"server_port": 8123,
"use_x_frame_options": true,
"ssl_profile": "modern",
"login_attempts_threshold": -1
}
}

8
.storage/http.auth Normal file
View File

@@ -0,0 +1,8 @@
{
"version": 1,
"minor_version": 1,
"key": "http.auth",
"data": {
"content_user": "23e04b69df624a0fbff05ad27b5a1f0a"
}
}

16
.storage/image Normal file
View File

@@ -0,0 +1,16 @@
{
"version": 1,
"minor_version": 1,
"key": "image",
"data": {
"items": [
{
"id": "45f6eb5e47c9a4396f538f6440ddadee",
"filesize": 36430,
"content_type": "image/png",
"name": "fpfin.png",
"uploaded_at": "2026-01-25T18:59:19.511080+00:00"
}
]
}
}

8
.storage/input_boolean Normal file
View File

@@ -0,0 +1,8 @@
{
"version": 1,
"minor_version": 1,
"key": "input_boolean",
"data": {
"items": []
}
}

29
.storage/input_button Normal file
View File

@@ -0,0 +1,29 @@
{
"version": 1,
"minor_version": 1,
"key": "input_button",
"data": {
"items": [
{
"id": "piholepause5",
"name": "PiHolePause5",
"icon": "mdi:advertisements"
},
{
"id": "pauseads",
"name": "Pauseads",
"icon": "mdi:advertisements"
},
{
"id": "fix_stair_light",
"name": "fix_stair_light",
"icon": "mdi:glass-fragile"
},
{
"id": "dummybuttonfornh",
"name": "DummyButtonForNH",
"icon": "mdi:bug-stop"
}
]
}
}

View File

@@ -0,0 +1,20 @@
{
"version": 1,
"minor_version": 1,
"key": "lovelace.dashboard_darkboard",
"data": {
"config": {
"kiosk_mode": {
"hide_header": true
},
"views": [
{
"title": "Home",
"type": "sections",
"cards": [],
"sections": []
}
]
}
}
}

12
.storage/lovelace.map Normal file
View File

@@ -0,0 +1,12 @@
{
"version": 1,
"minor_version": 1,
"key": "lovelace.map",
"data": {
"config": {
"strategy": {
"type": "map"
}
}
}
}

View File

@@ -0,0 +1,557 @@
{
"version": 1,
"minor_version": 1,
"key": "lovelace.my_dashboard_1",
"data": {
"config": {
"views": [
{
"title": "Home",
"sections": [
{
"type": "grid",
"cards": [
{
"type": "heading",
"heading_style": "title",
"icon": "mdi:map-marker",
"heading": "Rooms"
},
{
"type": "custom:bubble-card",
"card_type": "button",
"button_type": "name",
"name": "Bedroom",
"icon": "mdi:bed",
"sub_button": [
{
"entity": "light.lamp_lights",
"icon": "mdi:desk-lamp",
"tap_action": {
"action": "toggle"
},
"hold_action": {
"action": "more-info"
}
},
{
"entity": "switch.bedside_fan_switch",
"icon": "mdi:fan",
"tap_action": {
"action": "toggle"
},
"hold_action": {
"action": "more-info"
}
},
{
"entity": "switch.bedroom_ceiling_fan_switch",
"icon": "mdi:ceiling-fan",
"tap_action": {
"action": "toggle"
},
"hold_action": {
"action": "more-info"
}
},
{
"entity": "switch.bedroom_ceiling_lights_switch_3",
"tap_action": {
"action": "toggle"
},
"hold_action": {
"action": "more-info"
}
},
{
"entity": "fan.bedroom_air_purifier",
"icon": "mdi:air-filter",
"tap_action": {
"action": "toggle"
}
}
]
},
{
"type": "custom:bubble-card",
"card_type": "button",
"button_type": "name",
"name": "Living room",
"icon": "",
"sub_button": [
{
"entity": "switch.stair_light_lower_switch",
"icon": "mdi:stairs",
"tap_action": {
"action": "toggle"
},
"hold_action": {
"action": "more-info"
}
},
{
"entity": "switch.upper_landing_a_switch",
"icon": "mdi:door-sliding-open",
"tap_action": {
"action": "toggle"
},
"hold_action": {
"action": "more-info"
}
},
{
"entity": "light.night_light_1_light",
"tap_action": {
"action": "toggle"
},
"hold_action": {
"action": "more-info"
}
},
{
"entity": "light.bookcase",
"icon": "mdi:bookshelf",
"tap_action": {
"action": "toggle"
},
"hold_action": {
"action": "more-info"
}
},
{
"entity": "light.figurecase_light",
"icon": "mdi:book-open-variant-outline",
"tap_action": {
"action": "toggle"
},
"hold_action": {
"action": "more-info"
}
},
{
"entity": "light.movie_case_right_light",
"icon": "mdi:movie-open",
"tap_action": {
"action": "toggle"
},
"hold_action": {
"action": "more-info"
}
}
]
},
{
"type": "custom:bubble-card",
"card_type": "button",
"button_type": "name",
"name": "Bathroom",
"icon": "mdi:bathtub",
"sub_button": [
{
"entity": "light.master_bathroom_lights",
"icon": "mdi:string-lights",
"tap_action": {
"action": "toggle"
},
"hold_action": {
"action": "more-info"
}
}
]
},
{
"type": "custom:bubble-card",
"card_type": "button",
"button_type": "name",
"name": "Office",
"icon": "mdi:desk",
"sub_button": [
{
"entity": "switch.office_ceiling_fan_switch_2",
"icon": "mdi:ceiling-fan",
"tap_action": {
"action": "toggle"
},
"hold_action": {
"action": "more-info"
}
},
{
"entity": "switch.office_fan_plug_switch_2",
"icon": "mdi:fan",
"tap_action": {
"action": "toggle"
}
}
]
},
{
"type": "custom:bubble-card",
"card_type": "button",
"button_type": "name",
"name": "Garage",
"icon": "mdi:garage",
"sub_button": [
{
"entity": "switch.3d_printer_light_switch",
"name": "3d printer light",
"tap_action": {
"action": "toggle"
}
}
]
}
]
},
{
"type": "grid",
"cards": [
{
"type": "heading",
"heading": "Misc",
"heading_style": "title"
},
{
"type": "weather-forecast",
"entity": "weather.forecast_home",
"forecast_type": "daily"
},
{
"type": "gauge",
"entity": "sensor.emrys_throne_waste_drawer"
}
]
},
{
"type": "grid",
"cards": [
{
"type": "heading",
"heading": "New section"
},
{
"entities": [
"calendar.critical_events",
{
"entity": "calendar.home_tasks_2"
},
{
"entity": "calendar.holidays_in_united_states_2"
}
],
"days_to_show": 30,
"weather": {
"position": "date",
"date": {
"show_conditions": true,
"show_high_temp": true,
"show_low_temp": false,
"icon_size": "14px",
"font_size": "12px",
"color": "var(--primary-text-color)"
},
"event": {
"show_conditions": true,
"show_temp": true,
"icon_size": "14px",
"font_size": "12px",
"color": "var(--primary-text-color)"
}
},
"type": "custom:calendar-card-pro"
}
]
}
],
"type": "sections",
"max_columns": 5,
"dense_section_placement": true,
"cards": []
},
{
"type": "sections",
"max_columns": 4,
"title": "Batteries",
"path": "batteries",
"icon": "mdi:clock-digital",
"dense_section_placement": true,
"sections": [
{
"type": "grid",
"cards": [
{
"type": "heading",
"heading": "New section"
},
{
"type": "tile",
"entity": "timer.night_light_1_off_timer",
"grid_options": {
"columns": 12,
"rows": 2
}
}
]
}
],
"cards": []
},
{
"type": "sections",
"max_columns": 4,
"icon": "mdi:alarm-light",
"path": "",
"sections": [
{
"type": "grid",
"cards": [
{
"type": "heading",
"heading": "New section"
},
{
"states": [
"arm_home",
"arm_away",
"arm_vacation"
],
"type": "alarm-panel",
"entity": "alarm_control_panel.alarmo"
}
]
}
]
},
{
"type": "sections",
"max_columns": 4,
"title": "Cats",
"path": "cats",
"icon": "mdi:cat",
"sections": [
{
"type": "grid",
"cards": [
{
"type": "heading",
"heading": "New section"
},
{
"type": "statistic",
"entity": "sensor.emrys_weight",
"period": {
"calendar": {
"period": "month"
}
},
"stat_type": "mean"
},
{
"type": "statistic",
"entity": "sensor.arturo_weight",
"period": {
"calendar": {
"period": "month"
}
},
"stat_type": "mean"
},
{
"chart_type": "line",
"period": "week",
"type": "statistics-graph",
"entities": [
"sensor.emrys_weight",
"sensor.arturo_weight"
],
"stat_types": [
"mean"
],
"title": "Cats weight",
"days_to_show": 120
}
]
},
{
"type": "grid",
"cards": [
{
"type": "heading",
"heading": "New section"
},
{
"type": "custom:advanced-camera-card",
"cameras": [
{
"camera_entity": "camera.cat_cam_1_fluent",
"title": "Cat Camera"
}
],
"live": {
"preload": true,
"lazy_unload": [
"hidden"
],
"auto_play": [
"visible"
],
"auto_pause": [
"hidden"
],
"show_image_during_load": true
},
"menu": {
"buttons": {
"camera_ui": {
"icon": "mdi:account",
"enabled": false
},
"ptz_controls": {
"enabled": true
}
}
}
},
{
"type": "tile",
"entity": "switch.cat_cam_1_privacy_mode"
}
]
}
]
},
{
"type": "sections",
"max_columns": 4,
"icon": "mdi:lan-disconnect",
"path": "",
"sections": [
{
"type": "grid",
"cards": [
{
"type": "heading",
"heading": "New section"
},
{
"type": "markdown",
"content": "http://192.168.0.249:20720/admin/api.php?disable=300&auth=c874330af40074c580efdb36d0476e216d3bc3ffb860dcfd9361da5a457770f5",
"title": "Pause adblock"
}
]
}
]
},
{
"type": "sections",
"max_columns": 4,
"icon": "mdi:battery-20",
"path": "",
"sections": [
{
"type": "grid",
"cards": [
{
"type": "heading",
"heading": "New section"
},
{
"type": "tile",
"entity": "sensor.bedroom_button_battery"
},
{
"type": "tile",
"entity": "sensor.bedroom_ceiling_fan_battery"
},
{
"type": "tile",
"entity": "sensor.bedroom_ceiling_lights_battery_3"
},
{
"type": "tile",
"entity": "sensor.gabys_phone_battery_level"
},
{
"type": "tile",
"entity": "sensor.masterbathbutton2_battery"
},
{
"type": "tile",
"entity": "sensor.office_ceiling_fan_battery_2"
},
{
"type": "tile",
"entity": "sensor.stair_light_lower_battery"
},
{
"type": "tile",
"entity": "sensor.stair_light_upper_battery_2"
},
{
"type": "tile",
"entity": "sensor.bedroom_button_battery"
},
{
"type": "tile",
"entity": "sensor.bedroom_ceiling_fan_battery"
},
{
"type": "tile",
"entity": "sensor.bedroom_ceiling_lights_battery_3"
},
{
"type": "tile",
"entity": "sensor.leak_sensor_1_battery"
},
{
"type": "tile",
"entity": "sensor.leak_sensor_2_battery"
},
{
"type": "tile",
"entity": "sensor.leak_sensor_3_battery"
},
{
"type": "tile",
"entity": "sensor.leak_sensor_4_battery"
},
{
"type": "tile",
"entity": "sensor.leak_sensor_5_battery"
},
{
"type": "tile",
"entity": "sensor.master_bath_outer_battery"
},
{
"type": "tile",
"entity": "sensor.masterbathbutton2_battery"
},
{
"type": "tile",
"entity": "sensor.office_ceiling_fan_battery_2"
},
{
"type": "tile",
"entity": "sensor.room_exit_button_battery"
},
{
"type": "tile",
"entity": "sensor.stair_light_lower_battery"
},
{
"type": "tile",
"entity": "sensor.stair_light_upper_battery_2"
},
{
"type": "tile",
"entity": "sensor.upper_landing_b_battery"
}
]
}
]
}
]
}
}
}

647
.storage/lovelace.new_home Normal file
View File

@@ -0,0 +1,647 @@
{
"version": 1,
"minor_version": 1,
"key": "lovelace.new_home",
"data": {
"config": {
"views": [
{
"title": "Home",
"sections": [
{
"type": "grid",
"cards": [
{
"type": "heading",
"heading": "Rooms"
},
{
"type": "custom:bubble-card",
"card_type": "button",
"button_type": "name",
"name": "Kitchen",
"icon": "mdi:fridge",
"tap_action": {
"action": "none"
},
"button_action": {
"tap_action": {
"navigation_path": "kitchen"
},
"hold_action": {
"action": "navigate",
"navigation_path": "kitchen"
}
},
"hold_action": {
"action": "navigate",
"navigation_path": "kitchen"
},
"sub_button": [
{
"entity": "light.kitchen_lights_new_home"
}
]
},
{
"type": "custom:bubble-card",
"card_type": "button",
"button_type": "name",
"name": "Master Bedroom",
"icon": "mdi:chess-king",
"tap_action": {
"action": "none"
},
"button_action": {
"tap_action": {
"navigation_path": "master-bedroom"
},
"hold_action": {
"action": "navigate",
"navigation_path": "master-bedroom"
}
},
"hold_action": {
"action": "navigate",
"navigation_path": "master-bedroom"
}
},
{
"type": "custom:bubble-card",
"card_type": "button",
"button_type": "name",
"name": "Guest Bedroom",
"icon": "mdi:bed",
"tap_action": {
"action": "none"
},
"button_action": {
"tap_action": {
"navigation_path": "guest-bedroom"
},
"hold_action": {
"action": "navigate",
"navigation_path": "guest-bedroom"
}
},
"hold_action": {
"action": "navigate",
"navigation_path": "guest-bedroom"
}
},
{
"type": "custom:bubble-card",
"card_type": "button",
"button_type": "name",
"name": "Entry",
"icon": "mdi:door",
"tap_action": {
"action": "none"
},
"button_action": {
"tap_action": {
"navigation_path": "entry"
},
"hold_action": {
"action": "navigate",
"navigation_path": "entry"
}
},
"hold_action": {
"action": "navigate",
"navigation_path": "entry"
}
}
]
},
{
"type": "grid",
"cards": [
{
"type": "heading",
"heading": "New section"
},
{
"states": [
"arm_home",
"arm_away",
"arm_vacation"
],
"type": "alarm-panel",
"entity": "alarm_control_panel.alarmo"
}
]
},
{
"type": "grid",
"cards": []
}
]
},
{
"type": "sections",
"max_columns": 4,
"title": "Kitchen",
"path": "kitchen",
"icon": "mdi:fridge",
"sections": [
{
"type": "grid",
"cards": [
{
"type": "heading",
"heading": "Lights",
"heading_style": "title"
},
{
"show_name": true,
"show_icon": true,
"type": "button",
"entity": "light.kitchen_lights_new_home",
"hold_action": {
"action": "more-info"
}
}
]
}
],
"header": {
"card": {
"type": "markdown",
"text_only": true,
"content": "# Kitchen\nTap to turn things on/off, hold to see more options ✨"
}
}
},
{
"type": "sections",
"max_columns": 4,
"title": "Master Bedroom",
"path": "master-bedroom",
"icon": "mdi:chess-king",
"sections": [
{
"type": "grid",
"cards": [
{
"type": "heading",
"heading": "New section"
}
]
}
]
},
{
"type": "sections",
"max_columns": 4,
"title": "Guest Bedroom",
"path": "guest-bedroom",
"icon": "mdi:bed",
"sections": [
{
"type": "grid",
"cards": [
{
"type": "heading",
"heading": "New section"
}
]
}
]
},
{
"type": "sections",
"max_columns": 4,
"title": "entry",
"path": "entry",
"icon": "mdi:door",
"sections": [
{
"type": "grid",
"cards": [
{
"type": "heading",
"heading": "New section"
}
]
}
]
},
{
"type": "sections",
"max_columns": 4,
"title": "Map",
"path": "map",
"icon": "mdi:map-marker-radius",
"sections": [
{
"type": "grid",
"cards": [
{
"type": "picture-elements",
"elements": [
{
"type": "image",
"entity": "light.nh_primary_bedroom_lights",
"state_image": {
"on": "/local/images/light_on.svg",
"off": "/local/images/light_off.svg"
},
"tap_action": {
"action": "toggle"
},
"style": {
"top": "17%",
"left": "5%",
"width": "2.4%",
"transform": "translate(-50%, -50%)"
}
},
{
"type": "image",
"entity": "light.nh_primary_bath_lights",
"state_image": {
"on": "/local/images/light_on.svg",
"off": "/local/images/light_off.svg"
},
"tap_action": {
"action": "toggle"
},
"style": {
"top": "28%",
"left": "5%",
"width": "2.4%",
"transform": "translate(-50%, -50%)"
}
},
{
"type": "image",
"entity": "light.nh_primary_closet_lights",
"state_image": {
"on": "/local/images/light_on.svg",
"off": "/local/images/light_off.svg"
},
"tap_action": {
"action": "toggle"
},
"style": {
"top": "50%",
"left": "5%",
"width": "2.4%",
"transform": "translate(-50%, -50%)"
}
},
{
"type": "image",
"entity": "light.nh_primary_toilet_lights",
"state_image": {
"on": "/local/images/light_on.svg",
"off": "/local/images/light_off.svg"
},
"tap_action": {
"action": "toggle"
},
"style": {
"top": "42%",
"left": "13%",
"width": "2.4%",
"transform": "translate(-50%, -50%)"
}
},
{
"type": "image",
"entity": "light.nh_entry_lights",
"state_image": {
"on": "/local/images/light_on.svg",
"off": "/local/images/light_off.svg"
},
"tap_action": {
"action": "toggle"
},
"style": {
"top": "70%",
"left": "22%",
"width": "2.4%",
"transform": "translate(-50%, -50%)"
}
},
{
"type": "image",
"entity": "light.nh_garage_lights",
"state_image": {
"on": "/local/images/light_on.svg",
"off": "/local/images/light_off.svg"
},
"tap_action": {
"action": "toggle"
},
"style": {
"top": "70%",
"left": "10%",
"width": "2.4%",
"transform": "translate(-50%, -50%)"
}
},
{
"type": "image",
"entity": "light.nh_garage_entry_lights",
"state_image": {
"on": "/local/images/light_on.svg",
"off": "/local/images/light_off.svg"
},
"tap_action": {
"action": "toggle"
},
"style": {
"top": "62%",
"left": "17%",
"width": "2.4%",
"transform": "translate(-50%, -50%)"
}
},
{
"type": "image",
"entity": "light.nh_laundry_lights",
"state_image": {
"on": "/local/images/light_on.svg",
"off": "/local/images/light_off.svg"
},
"tap_action": {
"action": "toggle"
},
"style": {
"top": "51%",
"left": "12%",
"width": "2.4%",
"transform": "translate(-50%, -50%)"
}
},
{
"type": "image",
"entity": "light.nh_storage_lights",
"state_image": {
"on": "/local/images/light_on.svg",
"off": "/local/images/light_off.svg"
},
"tap_action": {
"action": "toggle"
},
"style": {
"top": "51%",
"left": "16%",
"width": "2.4%",
"transform": "translate(-50%, -50%)"
}
},
{
"type": "image",
"entity": "light.nh_kitchen_lights",
"state_image": {
"on": "/local/images/light_on.svg",
"off": "/local/images/light_off.svg"
},
"tap_action": {
"action": "toggle"
},
"style": {
"top": "52%",
"left": "30%",
"width": "2.4%",
"transform": "translate(-50%, -50%)"
}
},
{
"type": "image",
"entity": "light.nh_pantry_lights",
"state_image": {
"on": "/local/images/light_on.svg",
"off": "/local/images/light_off.svg"
},
"tap_action": {
"action": "toggle"
},
"style": {
"top": "60%",
"left": "33%",
"width": "2.4%",
"transform": "translate(-50%, -50%)"
}
},
{
"type": "image",
"entity": "light.nh_powder_bath_lights",
"state_image": {
"on": "/local/images/light_on.svg",
"off": "/local/images/light_off.svg"
},
"tap_action": {
"action": "toggle"
},
"style": {
"top": "60%",
"left": "28%",
"width": "2.4%",
"transform": "translate(-50%, -50%)"
}
},
{
"type": "image",
"entity": "light.nh_office_lights",
"state_image": {
"on": "/local/images/light_on.svg",
"off": "/local/images/light_off.svg"
},
"tap_action": {
"action": "toggle"
},
"style": {
"top": "82%",
"left": "30%",
"width": "2.4%",
"transform": "translate(-50%, -50%)"
}
},
{
"type": "image",
"entity": "light.nh_living_room_lights",
"state_image": {
"on": "/local/images/light_on.svg",
"off": "/local/images/light_off.svg"
},
"tap_action": {
"action": "toggle"
},
"style": {
"top": "25%",
"left": "25%",
"width": "2.4%",
"transform": "translate(-50%, -50%)"
}
},
{
"type": "image",
"entity": "light.nh_patio_lights",
"state_image": {
"on": "/local/images/light_on.svg",
"off": "/local/images/light_off.svg"
},
"tap_action": {
"action": "toggle"
},
"style": {
"top": "10%",
"left": "23%",
"width": "2.4%",
"transform": "translate(-50%, -50%)"
}
},
{
"type": "image",
"entity": "light.nh_library_lights",
"state_image": {
"on": "/local/images/light_on.svg",
"off": "/local/images/light_off.svg"
},
"tap_action": {
"action": "toggle"
},
"style": {
"top": "85%",
"left": "55%",
"width": "2.4%",
"transform": "translate(-50%, -50%)"
}
},
{
"type": "image",
"entity": "light.nh_loft_lights",
"state_image": {
"on": "/local/images/light_on.svg",
"off": "/local/images/light_off.svg"
},
"tap_action": {
"action": "toggle"
},
"style": {
"top": "45%",
"left": "70%",
"width": "2.4%",
"transform": "translate(-50%, -50%)"
}
},
{
"type": "image",
"entity": "light.nh_guest_lights",
"state_image": {
"on": "/local/images/light_on.svg",
"off": "/local/images/light_off.svg"
},
"tap_action": {
"action": "toggle"
},
"style": {
"top": "65%",
"left": "70%",
"width": "2.4%",
"transform": "translate(-50%, -50%)"
}
},
{
"type": "image",
"entity": "light.nh_guest_bath_lights",
"state_image": {
"on": "/local/images/light_on.svg",
"off": "/local/images/light_off.svg"
},
"tap_action": {
"action": "toggle"
},
"style": {
"top": "73%",
"left": "45%",
"width": "2.4%",
"transform": "translate(-50%, -50%)"
}
},
{
"type": "image",
"entity": "light.nh_movie_lights",
"state_image": {
"on": "/local/images/light_on.svg",
"off": "/local/images/light_off.svg"
},
"tap_action": {
"action": "toggle"
},
"style": {
"top": "60%",
"left": "45%",
"width": "2.4%",
"transform": "translate(-50%, -50%)"
}
},
{
"type": "image",
"entity": "light.nh_upstairs_hall_lights",
"state_image": {
"on": "/local/images/light_on.svg",
"off": "/local/images/light_off.svg"
},
"tap_action": {
"action": "toggle"
},
"style": {
"top": "65%",
"left": "58%",
"width": "2.4%",
"transform": "translate(-50%, -50%)"
}
},
{
"type": "image",
"entity": "light.nh_stair_lights",
"state_image": {
"on": "/local/images/light_on.svg",
"off": "/local/images/light_off.svg"
},
"tap_action": {
"action": "toggle"
},
"style": {
"top": "45%",
"left": "56%",
"width": "2.4%",
"transform": "translate(-50%, -50%)"
}
}
],
"image": {
"media_content_id": "media-source://image_upload/45f6eb5e47c9a4396f538f6440ddadee",
"media_content_type": "image/png",
"metadata": {
"title": "fpfin.png",
"thumbnail": "/api/image/serve/45f6eb5e47c9a4396f538f6440ddadee/256x256",
"media_class": "image",
"navigateIds": [
{},
{
"media_content_type": "app",
"media_content_id": "media-source://image_upload"
}
]
}
},
"grid_options": {
"columns": "full",
"rows": "auto"
}
}
],
"column_span": 4
}
]
}
]
}
}
}

View File

@@ -0,0 +1,43 @@
{
"version": 1,
"minor_version": 1,
"key": "lovelace_dashboards",
"data": {
"items": [
{
"id": "map",
"icon": "mdi:map",
"title": "Map",
"url_path": "map",
"show_in_sidebar": true,
"mode": "storage",
"require_admin": false
},
{
"id": "my_dashboard_1",
"show_in_sidebar": true,
"title": "My dashboard 1",
"require_admin": false,
"mode": "storage",
"url_path": "my-dashboard-1"
},
{
"id": "dashboard_darkboard",
"show_in_sidebar": false,
"title": "Darkboard",
"require_admin": false,
"mode": "storage",
"url_path": "dashboard-darkboard"
},
{
"id": "new_home",
"show_in_sidebar": true,
"icon": "mdi:home",
"title": "New Home",
"require_admin": false,
"mode": "storage",
"url_path": "new-home"
}
]
}
}

View File

@@ -0,0 +1,34 @@
{
"version": 1,
"minor_version": 1,
"key": "lovelace_resources",
"data": {
"items": [
{
"id": "8fc8acc609894063aac434ef37af1d36",
"url": "/hacsfiles/Bubble-Card/bubble-card.js?hacstag=680112919230",
"type": "module"
},
{
"id": "764f87cd307b41ffb4491ea743427729",
"url": "/hacsfiles/kiosk-mode/kiosk-mode.js?hacstag=497319128902",
"type": "module"
},
{
"id": "91712a5ce5e94b43a7141f83d7750e52",
"url": "/hacsfiles/custom-sidebar/custom-sidebar.js?hacstag=7377802181110",
"type": "module"
},
{
"id": "c83e141a2c2a4ec0bf2446a612e2b427",
"url": "/hacsfiles/calendar-card-pro/calendar-card-pro.js?hacstag=939311749310",
"type": "module"
},
{
"id": "4208c7441471440c84b3ea8dead19e92",
"url": "/hacsfiles/advanced-camera-card/advanced-camera-card.js?hacstag=3940825527260",
"type": "module"
}
]
}
}

10
.storage/mobile_app Normal file
View File

@@ -0,0 +1,10 @@
{
"version": 1,
"minor_version": 1,
"key": "mobile_app",
"data": {
"deleted_ids": [
"837c37d8eeeab61e356ae0584dae4e0912b24629a2430b8eaa01fb031b1b8a2e"
]
}
}

13
.storage/onboarding Normal file
View File

@@ -0,0 +1,13 @@
{
"version": 4,
"minor_version": 1,
"key": "onboarding",
"data": {
"done": [
"user",
"core_config",
"analytics",
"integration"
]
}
}

18
.storage/person Normal file
View File

@@ -0,0 +1,18 @@
{
"version": 2,
"minor_version": 1,
"key": "person",
"data": {
"items": [
{
"id": "christopher_chasteen",
"name": "Christopher Chasteen",
"user_id": "4a285b563f6a4a54b657acf1926c0b5b",
"device_trackers": [
"device_tracker.chris_phone",
"device_tracker.pixel_3a"
]
}
]
}
}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,105 @@
{
"version": 1,
"minor_version": 2,
"key": "repairs.issue_registry",
"data": {
"issues": [
{
"created": "2025-01-18T02:32:22.388559+00:00",
"dismissed_version": null,
"domain": "hacs",
"is_persistent": false,
"issue_id": "restart_required_172733314_tags/2.0.3"
},
{
"created": "2025-02-05T02:15:26.564647+00:00",
"dismissed_version": null,
"domain": "hacs",
"is_persistent": false,
"issue_id": "restart_required_172733314_tags/2.0.5"
},
{
"created": "2025-09-14T05:37:02.203310+00:00",
"dismissed_version": null,
"domain": "hacs",
"is_persistent": false,
"issue_id": "restart_required_249381778_tags/v5.2.4"
},
{
"created": "2025-12-13T06:36:48.560837+00:00",
"dismissed_version": null,
"domain": "hacs",
"is_persistent": false,
"issue_id": "restart_required_307098646_tags/v1.10.13"
},
{
"created": "2025-12-20T01:09:38.496893+00:00",
"dismissed_version": null,
"domain": "cloud",
"is_persistent": false,
"issue_id": "connection_error_2026-12-17 00:00:00+00:00"
},
{
"created": "2025-12-20T01:09:40.871353+00:00",
"dismissed_version": null,
"domain": "homeassistant",
"is_persistent": false,
"issue_id": "config_entry_reauth_vesync_01JFP966AYW48GQ7FPY42M80G0"
},
{
"created": "2025-12-20T01:11:15.598983+00:00",
"dismissed_version": null,
"domain": "hassio",
"is_persistent": false,
"issue_id": "3e4e8553cc4947e18bc54ea66be0de5b"
},
{
"created": "2025-12-20T01:51:59.482337+00:00",
"dismissed_version": null,
"domain": "hassio",
"is_persistent": false,
"issue_id": "c94402f948f641358bf982cdd8eddff8"
},
{
"created": "2026-01-03T16:00:00.131529+00:00",
"dismissed_version": null,
"domain": "automation",
"is_persistent": true,
"issue_id": "automation.battery_check_service_not_found_notify.mobile_app_cph2513",
"breaks_in_ha_version": null,
"data": null,
"is_fixable": true,
"issue_domain": null,
"learn_more_url": null,
"severity": "error",
"translation_key": "service_not_found",
"translation_placeholders": {
"service": "notify.mobile_app_cph2513",
"entity_id": "automation.battery_check",
"name": "Battery Check",
"edit": "/config/automation/edit/1736309562377"
}
},
{
"created": "2026-01-04T11:52:28.396717+00:00",
"dismissed_version": null,
"domain": "automation",
"is_persistent": true,
"issue_id": "automation.litter_box_full_service_not_found_notify.mobile_app_cph2513",
"breaks_in_ha_version": null,
"data": null,
"is_fixable": true,
"issue_domain": null,
"learn_more_url": null,
"severity": "error",
"translation_key": "service_not_found",
"translation_placeholders": {
"service": "notify.mobile_app_cph2513",
"entity_id": "automation.litter_box_full",
"name": "Litter box full",
"edit": "/config/automation/edit/1757829250031"
}
}
]
}
}

28
.storage/timer Normal file
View File

@@ -0,0 +1,28 @@
{
"version": 1,
"minor_version": 1,
"key": "timer",
"data": {
"items": [
{
"id": "night_light_1_off_timer",
"name": "night_light_1_off_timer",
"duration": "0:00:00",
"restore": false
},
{
"id": "night_light_activation_timer",
"name": "Night light activation timer",
"duration": "0:00:30",
"restore": false
},
{
"id": "stair_light_delay",
"name": "stair_light_delay",
"icon": "mdi:stairs-box",
"duration": "0:00:10",
"restore": false
}
]
}
}

View File

@@ -0,0 +1,6 @@
{
"version": 1,
"minor_version": 1,
"key": "trace.saved_traces",
"data": {}
}

703
automations.yaml Normal file
View File

@@ -0,0 +1,703 @@
- id: '1734313996462'
alias: Auto - Toggle Office Fan
description: ''
triggers:
- device_id: a5b95319cae01f36d393a7ef03dca7d6
domain: zha
type: remote_button_short_press
subtype: remote_button_short_press
trigger: device
conditions: []
actions:
- type: toggle
device_id: 8860a33d7f6298f79282948f52d3b5db
entity_id: e9b394087fd6f9eb7f1d5baddb5f93dd
domain: switch
mode: single
- id: '1734839542421'
alias: Auto - Turn On Bedroom Lamp
description: ''
triggers:
- device_id: ebab4eee8659e99e53cb945fa322cde2
domain: zha
type: remote_button_short_press
subtype: remote_button_short_press
trigger: device
- trigger: state
entity_id:
- input_button.bedroom_lights
- device_id: 052358b767ecf6b2609acf2fe20f7319
domain: zha
type: remote_button_short_press
subtype: remote_button_short_press
trigger: device
conditions:
- condition: state
entity_id: light.lamp_lights
state: 'off'
actions:
- action: scene.turn_on
metadata: {}
data: {}
target:
entity_id: scene.standard_bedroom_lighting
mode: single
- id: '1734839572697'
alias: Auto - turn off all bedroom lights
description: ''
triggers:
- device_id: ebab4eee8659e99e53cb945fa322cde2
domain: zha
type: remote_button_short_press
subtype: remote_button_short_press
trigger: device
- trigger: state
entity_id:
- input_button.bedroom_lights
- device_id: 052358b767ecf6b2609acf2fe20f7319
domain: zha
type: remote_button_short_press
subtype: remote_button_short_press
trigger: device
conditions:
- condition: state
entity_id: light.lamp_lights
state: 'on'
actions:
- action: scene.turn_on
metadata: {}
data: {}
target:
entity_id: scene.bedroom_lights_off
mode: single
- id: '1734839634818'
alias: Auto - Toggle bedside Fan
description: ''
triggers:
- device_id: ebab4eee8659e99e53cb945fa322cde2
domain: zha
type: remote_button_double_press
subtype: remote_button_double_press
trigger: device
- trigger: conversation
command:
- Fan on
- Fan off
- Fan
conditions: []
actions:
- type: toggle
device_id: d8478de5f4ea431ada272ac28ad7a2a0
entity_id: 52f171757001dac9fb71b7f00719b1aa
domain: switch
mode: single
- id: '1734839743730'
alias: Auto - Activate Bedtime Scene
description: ''
triggers:
- device_id: ebab4eee8659e99e53cb945fa322cde2
domain: zha
type: remote_button_long_press
subtype: remote_button_long_press
trigger: device
- trigger: conversation
command:
- Night time
- GoodNight
- 'Bed time '
- Good night
- Sleep
- Bed time
conditions: []
actions:
- action: timer.start
target:
entity_id: timer.stair_light_delay
data:
duration: 00:00:30
- action: scene.turn_on
metadata: {}
data: {}
target:
entity_id: scene.bedtime
mode: single
- id: '1734839849692'
alias: 'Auto -Turn on Bathroom lights to standard '
description: ''
triggers:
- device_id: 984d2d9da0ce0b58b3ab4649bb82cb09
domain: zha
type: remote_button_short_press
subtype: remote_button_short_press
trigger: device
- device_id: 0e833fe5953d72d36def81f42ddec2dd
domain: zha
type: remote_button_short_press
subtype: remote_button_short_press
trigger: device
- trigger: state
entity_id:
- input_button.bathroom_lights
conditions:
- condition: state
entity_id: light.master_bathroom_lights
state: 'off'
actions:
- action: scene.turn_on
metadata: {}
data: {}
target:
entity_id: scene.bathroom_standard_lighting
mode: single
- id: '1734839869997'
alias: Auto - Turn off bathroom lights
description: ''
triggers:
- device_id: 984d2d9da0ce0b58b3ab4649bb82cb09
domain: zha
type: remote_button_short_press
subtype: remote_button_short_press
trigger: device
- device_id: 0e833fe5953d72d36def81f42ddec2dd
domain: zha
type: remote_button_short_press
subtype: remote_button_short_press
trigger: device
- trigger: state
entity_id:
- input_button.bathroom_lights
conditions:
- condition: state
entity_id: light.master_bathroom_lights
state: 'on'
actions:
- action: scene.turn_on
metadata: {}
data: {}
target:
entity_id: scene.bathroom_lights_off
mode: single
- id: '1734840098396'
alias: Auto - Dim Bathroom Lights
description: ''
triggers:
- device_id: 984d2d9da0ce0b58b3ab4649bb82cb09
domain: zha
type: remote_button_long_press
subtype: remote_button_long_press
trigger: device
- device_id: 0e833fe5953d72d36def81f42ddec2dd
domain: zha
type: remote_button_long_press
subtype: remote_button_long_press
trigger: device
conditions: []
actions:
- action: scene.turn_on
metadata: {}
data: {}
target:
entity_id: scene.bathroom_dim_lighting
mode: single
- id: '1734840131118'
alias: Auto - Brighten Bathroom Lights
description: ''
triggers:
- device_id: 984d2d9da0ce0b58b3ab4649bb82cb09
domain: zha
type: remote_button_double_press
subtype: remote_button_double_press
trigger: device
- device_id: 0e833fe5953d72d36def81f42ddec2dd
domain: zha
type: remote_button_double_press
subtype: remote_button_double_press
trigger: device
conditions: []
actions:
- action: scene.turn_on
metadata: {}
data: {}
target:
entity_id: scene.bathroom_standard_lighting
mode: single
- id: '1735433657952'
alias: Auto- Toggle Office Ceiling fan
description: ''
triggers:
- device_id: a5b95319cae01f36d393a7ef03dca7d6
domain: zha
type: remote_button_short_press
subtype: remote_button_short_press
trigger: device
- trigger: conversation
command:
- Turn on office ceiling fan
- Turn off office ceiling fan
- Office ceiling fan
- Office Fan
conditions: []
actions:
- type: toggle
device_id: 2b036805d62affb4e7933ee034279bad
entity_id: 9a3c29c75ef69842b1ad5297bd7f7840
domain: switch
mode: single
- id: '1735449997148'
alias: Auto - Stair Lights Toggle
description: ''
triggers:
- trigger: state
entity_id:
- input_button.stair_lights_button
- device_id: 3126ead552545da0409ab0f342d6fa54
domain: zha
type: remote_button_short_press
subtype: remote_button_short_press
trigger: device
conditions: []
actions:
- type: toggle
device_id: 9f3ed8a617d6da4c6eb0a2fd412deba4
entity_id: e1817ddf4ec101285543a0f720da0869
domain: switch
mode: single
- id: '1736309562377'
alias: Battery Check
description: ''
use_blueprint:
path: sbyx/low-battery-level-detection-notification-for-all-battery-sensors.yaml
input:
actions:
- action: notify.mobile_app_cph2513
metadata: {}
data:
message: '{{sensors}} running low on battery'
- id: '1737936582270'
alias: AUTO - PausePihole5
description: ''
triggers:
- trigger: state
entity_id:
- input_button.piholepause5
conditions: []
actions:
- action: rest_command.pause_ads_5
metadata: {}
data: {}
mode: single
- id: '1738806022551'
alias: Leak alert
description: ''
triggers:
- trigger: state
entity_id:
- binary_sensor.leak_sensor_1_moisture
from: 'off'
to: 'on'
- trigger: state
entity_id:
- binary_sensor.leak_sensor_2_moisture
from: 'off'
to: 'on'
- trigger: state
entity_id:
- binary_sensor.leak_sensor_3_moisture
from: 'off'
to: 'on'
- trigger: state
entity_id:
- binary_sensor.leak_sensor_4_moisture
from: 'off'
to: 'on'
conditions: []
actions:
- action: notify.mobile_app_chris_phone
metadata: {}
data:
message: '"{{ trigger.to_state.name }} has detected a leak."'
mode: single
- id: '1738960937138'
alias: Auto - dim the lights
description: ''
triggers:
- trigger: conversation
command: Dim the lights
conditions: []
actions:
- action: scene.turn_on
metadata: {}
data: {}
target:
entity_id: scene.bedroom_dim_lighting
mode: single
- id: '1738961099534'
alias: Auto - toggle bedroom ceiling fan
description: ''
triggers:
- trigger: conversation
command:
- Ceiling fan
- Ceiling fan on
- Ceiling fan off
conditions: []
actions:
- type: toggle
device_id: 63701be14b86c1fe10d30a39b5e4b768
entity_id: d67305df23edeec936324f58be2ae39a
domain: switch
mode: single
- id: '1739747188817'
alias: Auto - Leave bedroom
description: ''
triggers:
- device_id: 052358b767ecf6b2609acf2fe20f7319
domain: zha
type: remote_button_double_press
subtype: remote_button_double_press
trigger: device
conditions: []
actions:
- action: scene.turn_on
metadata: {}
data: {}
target:
entity_id: scene.bedroom_all_off
mode: single
- id: '1739771718054'
alias: Auto - Turn off bedroom ceiling lights
description: ''
triggers:
- trigger: conversation
command:
- Ceiling Lights Off
- Ceiling Lights
- Ceiling Lights On
conditions: []
actions:
- type: toggle
device_id: 09112e3aa200fc6c51b9d8ef8cdd1986
entity_id: dfc24301041b14afaf323e2477d9d195
domain: switch
mode: single
- id: '1741510283924'
alias: Bedtime voice command
description: ''
triggers:
- trigger: conversation
command: Bedtime
conditions: []
actions:
- action: scene.turn_on
metadata: {}
data: {}
target:
entity_id: scene.bedtime
mode: single
- id: '1742177845418'
alias: Auto - All Bedroom Fans Off
description: ''
triggers:
- trigger: conversation
command:
- Stillness
- Calm
- No Fans
- All Fans Off
- Quiet
conditions: []
actions:
- action: scene.turn_on
metadata: {}
data: {}
target:
entity_id: scene.all_bedroom_fans_off
mode: single
- id: '1755458947925'
alias: Auto - Daytime
description: ''
triggers:
- trigger: conversation
command:
- Good Morning
- Daytime
conditions: []
actions:
- action: scene.turn_on
metadata: {}
data: {}
target:
entity_id: scene.daytime
mode: single
- id: '1757829250031'
alias: Litter box full
description: ''
triggers:
- type: value
device_id: 9d81266973e5ff00a4e9f974e8b3b621
entity_id: a6ce899e27d1c5693573de49cb8757a1
domain: sensor
trigger: device
above: 90
conditions: []
actions:
- action: notify.mobile_app_cph2513
metadata: {}
data:
title: Litter Robot Alert!
message: Empty the waste drawer please.
mode: single
- id: '1758413619299'
alias: Living Room Night Lights
description: ''
triggers:
- trigger: time
at: '20:00:00'
conditions: []
actions:
- action: scene.turn_on
metadata: {}
data: {}
target:
entity_id: scene.living_room_evening_lights
mode: single
- id: '1758413678482'
alias: Living Room dark
description: ''
triggers:
- trigger: time
at: 00:00:00
conditions: []
actions:
- action: scene.turn_on
metadata: {}
data: {}
target:
entity_id: scene.living_room_bedtime
mode: single
- id: '1758413851787'
alias: Manual toggle for living room lights
description: ''
triggers:
- device_id: 3126ead552545da0409ab0f342d6fa54
domain: zha
type: remote_button_long_press
subtype: remote_button_long_press
trigger: device
conditions: []
actions:
- choose:
- conditions:
- condition: device
type: is_off
device_id: f3e8dcade53ac6843fde79376c099a3c
entity_id: 444a937220ba5b3144e86837feda7104
domain: light
sequence:
- action: scene.turn_on
metadata: {}
data: {}
target:
entity_id: scene.living_room_evening_lights
- conditions:
- condition: device
type: is_on
device_id: f3e8dcade53ac6843fde79376c099a3c
entity_id: 444a937220ba5b3144e86837feda7104
domain: light
sequence:
- action: scene.turn_on
metadata: {}
data: {}
target:
entity_id: scene.living_room_bedtime
mode: single
- id: '1759109671739'
alias: Synchronize states stairs b
description: ''
use_blueprint:
path: adchevrier/synchronize-the-on-off-state-of-2-entities.yaml
input:
entity_1: switch.upper_landing_b_switch
entity_2: switch.stair_light_lower_switch
- id: '1759110875124'
alias: Synchronize stairs A
description: ''
use_blueprint:
path: adchevrier/synchronize-the-on-off-state-of-2-entities.yaml
input:
entity_1: switch.inverse_of_upper_landing_switch
entity_2: switch.stair_light_upper_switch_2
- id: '1759874345238'
alias: Critical Event Reminders
description: ''
triggers:
- trigger: calendar
entity_id: calendar.critical_events
event: start
offset: -00:05:00
conditions: []
actions:
- action: notify.mobile_app_chris_phone
metadata: {}
data:
message: TTS
data:
ttl: 0
priority: high
media_stream: alarm_stream_max
tts_text: Appointment Reminder, 5 minutes, Appointment reminder, 5 minutes.
- action: notify.twilio_call
data:
message: You have an appointment please do not be late
target:
- '+12144589878'
enabled: false
mode: single
- id: '1763881743942'
alias: Unavailable entity detection & notification
description: ''
use_blueprint:
path: gmlupatelli/unavailable_entities_notification.yaml
input:
actions:
- condition: template
value_template: '{{ final_unavailable_entities | length > 0 }}'
- action: notify.mobile_app_chris_phone
metadata: {}
data:
title: Unavailable Entities Detected
message: List of entities - {{ entities }}
enabled: true
- action: persistent_notification.create
metadata: {}
data:
title: Debug Lists
message: "Excluded: {{ excluded_entities }} \nIncluded: {{ included_entities_raw
}}\nUnavailable: {{ unavailable_entities }}\nFinal: {{ final_unavailable_entities
}} \n"
enabled: false
include:
label_id: monitored
- id: '1765293318958'
alias: Master Bath Humidity
description: ''
triggers:
- type: humidity
device_id: 3e9f76ced7f9bfd888f501c832f56d79
entity_id: e66f293c56bd9ee5f54565c8d15d1ea6
domain: sensor
trigger: device
above: 50
for:
hours: 0
minutes: 1
seconds: 0
conditions:
- condition: device
type: is_off
device_id: 899ae685d236a458fa035a61d30367fa
entity_id: 8641f9a2f5bf882b9feaf3ed14de0c85
domain: switch
actions:
- action: notify.mobile_app_chris_phone
metadata: {}
data:
message: Humidity sensor test!
enabled: false
- type: turn_on
device_id: 899ae685d236a458fa035a61d30367fa
entity_id: 8641f9a2f5bf882b9feaf3ed14de0c85
domain: switch
- device_id: 899ae685d236a458fa035a61d30367fa
domain: number
entity_id: 24d4c7aec0f40b0e4d92b8492dc784c2
type: set_value
value: 3600
mode: single
- id: '1765608922704'
alias: Alarmo - Pending
description: ''
triggers:
- trigger: state
entity_id:
- alarm_control_panel.alarmo
to:
- pending
conditions: []
actions:
- action: notify.mobile_app_chris_phone
metadata: {}
data:
message: Pending, BEEP BEEP BEEP
mode: single
- id: '1765609013023'
alias: Alarmo - Triggered alarm
description: ''
triggers:
- trigger: state
entity_id:
- alarm_control_panel.alarmo
to:
- triggered
conditions: []
actions:
- action: notify.mobile_app_chris_phone
metadata: {}
data:
message: Too late!!!
mode: single
- id: '1766899102626'
alias: Alarmo - Away started
description: ''
triggers:
- trigger: state
entity_id:
- alarm_control_panel.alarmo
to:
- armed_away
- armed_vacation
conditions:
- condition: state
entity_id: switch.cat_cam_1_privacy_mode
state:
- 'on'
actions:
- action: switch.turn_off
metadata: {}
target:
entity_id: switch.cat_cam_1_privacy_mode
data: {}
mode: single
- id: '1766899152715'
alias: Alarmo - Home/Disarmed Set
description: ''
triggers:
- trigger: state
entity_id:
- alarm_control_panel.alarmo
to:
- disarmed
- armed_home
conditions:
- condition: state
entity_id: switch.cat_cam_1_privacy_mode
state:
- 'off'
actions:
- action: switch.turn_on
metadata: {}
target:
entity_id: switch.cat_cam_1_privacy_mode
data: {}
mode: single
- id: '1767255316522'
alias: Bathroom Humidity Exhaust Fan
description: ''
use_blueprint:
path: Blackshome/bathroom-humidity-exhaust-fan.yaml
input:
trigger: sensor.master_bath_humidity
fan_switch:
entity_id: switch.master_bath_fan
include_manual_fan_switch: enable_manual_fan_switch_auto_off

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,44 @@
blueprint:
name: Synchronize states
description: Synchronize the on/off state of 2 entities
domain: automation
input:
entity_1:
name: First entity
selector:
entity: {}
entity_2:
name: Second entity
selector:
entity: {}
source_url: https://community.home-assistant.io/t/synchronize-the-on-off-state-of-2-entities/259010
mode: restart
max_exceeded: silent
variables:
entity_1: !input entity_1
entity_2: !input entity_2
trigger:
- platform: state
entity_id: !input entity_1
to:
- 'off'
- 'on'
- platform: state
entity_id: !input entity_2
to:
- 'off'
- 'on'
condition:
- condition: template
value_template: '{{ states(entity_1) != states(entity_2) }}'
- condition: template
value_template: '{{ trigger.to_state.state != trigger.from_state.state }}'
- condition: template
value_template: '{{ trigger.to_state.context.parent_id is none or (trigger.to_state.context.id
!= this.context.id and trigger.to_state.context.parent_id != this.context.id)
}}'
action:
- service: homeassistant.turn_{{ trigger.to_state.state }}
data:
entity_id: '{% if trigger.from_state.entity_id == entity_1 %} {{ entity_2 }} {%
else %} {{ entity_1 }} {% endif %}'

View File

@@ -0,0 +1,156 @@
blueprint:
name: Unavailable entity detection & notification
description: >
Regularly test all entities' status to check for unavailability.
Supports exclusion by entities, devices, areas, and labels for flexible filtering.
domain: automation
source_url: https://github.com/gmlupatelli/blueprints_repo/blob/master/unavailable_entities_notification/unavailable_entities_notification.yaml
input:
time:
name: Time to test on
description: Test is run at configured time
default: '10:00:00'
selector:
time: {}
monday_enabled:
name: Monday
default: true
selector:
boolean: {}
tuesday_enabled:
name: Tuesday
default: true
selector:
boolean: {}
wednesday_enabled:
name: Wednesday
default: true
selector:
boolean: {}
thursday_enabled:
name: Thursday
default: true
selector:
boolean: {}
friday_enabled:
name: Friday
default: true
selector:
boolean: {}
saturday_enabled:
name: Saturday
default: true
selector:
boolean: {}
sunday_enabled:
name: Sunday
default: true
selector:
boolean: {}
exclude:
name: Excluded Entities
description: Entities (e.g. smartphone) to exclude. Entities, devices, areas, and labels are supported!
default: {}
selector:
target: {}
include:
name: Included Entities
description: Entities (e.g. smartphone) to include. Entities, devices, areas, and labels are supported!
default: {}
selector:
target: {}
actions:
name: Actions
description: Notifications or similar to be run. {{entities}} is replaced with a formatted list.
default: []
selector:
action: {}
variables:
monday_enabled: !input monday_enabled
tuesday_enabled: !input tuesday_enabled
wednesday_enabled: !input wednesday_enabled
thursday_enabled: !input thursday_enabled
friday_enabled: !input friday_enabled
saturday_enabled: !input saturday_enabled
sunday_enabled: !input sunday_enabled
current_day: '{{ now().weekday() | int }}'
exclude: !input exclude
include: !input include
excluded_entities: >
{% set excluded = [] %}
{% if exclude.entity_id is defined %}
{% set excluded = excluded + ( [exclude.entity_id] if exclude.entity_id is string else exclude.entity_id ) %}
{% endif %}
{% if exclude.device_id is defined %}
{% for d in ([exclude.device_id] if exclude.device_id is string else exclude.device_id) %}
{% set excluded = excluded + device_entities(d) %}
{% endfor %}
{% endif %}
{% if exclude.area_id is defined %}
{% for a in ([exclude.area_id] if exclude.area_id is string else exclude.area_id) %}
{% set excluded = excluded + area_entities(a) %}
{% for d in area_devices(a) %}
{% set excluded = excluded + device_entities(d) %}
{% endfor %}
{% endfor %}
{% endif %}
{% if exclude.label_id is defined %}
{% for l in ([exclude.label_id] if exclude.label_id is string else exclude.label_id) %}
{% set excluded = excluded + label_entities(l) %}
{% endfor %}
{% endif %}
{{ excluded }}
# Build included list
included_entities_raw: >
{{ label_entities('monitored') }}
# Collect ALL unavailable entities (no filtering yet)
unavailable_entities: >
{% set unavail = states | selectattr('state','eq','unavailable') | map(attribute='entity_id') | list %}
{{ unavail }}
# Apply include/exclude filtering only here
final_unavailable_entities: >
{% set ns = namespace(final=[]) %}
{% set included_list = included_entities_raw %}
{% set excluded_list = excluded_entities %}
{% for entity in unavailable_entities %}
{% set dev_id = device_id(entity) %}
{% if entity in included_list and not entity in excluded_list and (not device_attr(dev_id,'disabled_by')) %}
{% set ns.final = ns.final + [state_attr(entity,'friendly_name') ~ ' (' ~ entity ~ ')'] %}
{% endif %}
{% endfor %}
{{ ns.final }}
entities: "{{ '- ' }}{{ final_unavailable_entities | join('\n- ') }}"
trigger:
- platform: time
at: !input time
condition:
- condition: template
value_template: >
{{
(current_day == 0 and monday_enabled) or
(current_day == 1 and tuesday_enabled) or
(current_day == 2 and wednesday_enabled) or
(current_day == 3 and thursday_enabled) or
(current_day == 4 and friday_enabled) or
(current_day == 5 and saturday_enabled) or
(current_day == 6 and sunday_enabled)
}}
- condition: template
value_template: '{{ final_unavailable_entities | length > 0 }}'
action: !input actions
mode: single
max_exceeded: silent

View File

@@ -0,0 +1,58 @@
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
author: Home Assistant
input:
motion_entity:
name: Motion Sensor
selector:
entity:
filter:
- device_class: occupancy
domain: binary_sensor
- device_class: motion
domain: binary_sensor
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
triggers:
trigger: state
entity_id: !input motion_entity
from: "off"
to: "on"
actions:
- alias: "Turn on the light"
action: light.turn_on
target: !input light_target
- alias: "Wait until there is no motion from device"
wait_for_trigger:
trigger: state
entity_id: !input motion_entity
from: "on"
to: "off"
- alias: "Wait the number of seconds that has been set"
delay: !input no_motion_wait
- alias: "Turn off the light"
action: light.turn_off
target: !input light_target

View File

@@ -0,0 +1,50 @@
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
author: Home Assistant
input:
person_entity:
name: Person
selector:
entity:
filter:
domain: person
zone_entity:
name: Zone
selector:
entity:
filter:
domain: zone
notify_device:
name: Device to notify
description: Device needs to run the official Home Assistant app to receive notifications.
selector:
device:
filter:
integration: mobile_app
triggers:
trigger: 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 }}"
conditions:
condition: template
# The first case handles leaving the Home zone which has a special state when zoning called 'home'.
# The second case handles leaving all other zones.
value_template: "{{ zone_entity == 'zone.home' and trigger.from_state.state == 'home' and trigger.to_state.state != 'home' or trigger.from_state.state == zone_state and trigger.to_state.state != zone_state }}"
actions:
- alias: "Notify that a person has left the zone"
domain: mobile_app
type: notify
device_id: !input notify_device
message: "{{ person_name }} has left {{ zone_state }}"

View File

@@ -0,0 +1,75 @@
blueprint:
name: Low battery level detection & notification for all battery sensors
description: Regularly test all sensors with 'battery' device-class for crossing
a certain battery level threshold and if so execute an action.
domain: automation
input:
threshold:
name: Battery warning level threshold
description: Battery sensors below threshold are assumed to be low-battery (as
well as binary battery sensors with value 'on').
default: 20
selector:
number:
min: 5.0
max: 100.0
unit_of_measurement: '%'
mode: slider
step: 5.0
time:
name: Time to test on
description: Test is run at configured time
default: '10:00:00'
selector:
time: {}
day:
name: Weekday to test on
description: 'Test is run at configured time either everyday (0) or on a given
weekday (1: Monday ... 7: Sunday)'
default: 0
selector:
number:
min: 0.0
max: 7.0
mode: slider
step: 1.0
exclude:
name: Excluded Sensors
description: Battery sensors (e.g. smartphone) to exclude from detection. Only
entities are supported, devices must be expanded!
default:
entity_id: []
selector:
target:
entity:
- device_class:
- battery
actions:
name: Actions
description: Notifications or similar to be run. {{sensors}} is replaced with
the names of sensors being low on battery.
selector:
action: {}
source_url: https://gist.github.com/sbyx/1f6f434f0903b872b84c4302637d0890
variables:
day: !input day
threshold: !input threshold
exclude: !input exclude
sensors: "{% set result = namespace(sensors=[]) %} {% for state in states.sensor
| selectattr('attributes.device_class', '==', 'battery') %}\n {% if 0 <= state.state
| int(-1) < threshold | int and not state.entity_id in exclude.entity_id %}\n
\ {% set result.sensors = result.sensors + [state.name ~ ' (' ~ state.state
~ ' %)'] %}\n {% endif %}\n{% endfor %} {% for state in states.binary_sensor
| selectattr('attributes.device_class', '==', 'battery') | selectattr('state',
'==', 'on') %}\n {% if not state.entity_id in exclude.entity_id %}\n {% set
result.sensors = result.sensors + [state.name] %}\n {% endif %}\n{% endfor %}
{{result.sensors|join(', ')}}"
trigger:
- platform: time
at: !input time
condition:
- '{{ sensors != '''' and (day | int == 0 or day | int == now().isoweekday()) }}'
action:
- choose: []
default: !input actions
mode: single

View File

@@ -0,0 +1,86 @@
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
author: Home Assistant
input:
notify_device:
name: Device to notify
description: Device needs to run the official Home Assistant app to receive notifications.
selector:
device:
filter:
integration: mobile_app
title:
name: "Title"
description: "The title of the button shown in the notification."
default: ""
selector:
text:
message:
name: "Message"
description: "The message body"
selector:
text:
confirm_text:
name: "Confirmation Text"
description: "Text to show on the confirmation button"
default: "Confirm"
selector:
text:
confirm_action:
name: "Confirmation Action"
description: "Action to run when notification is confirmed"
default: []
selector:
action:
dismiss_text:
name: "Dismiss Text"
description: "Text to show on the dismiss button"
default: "Dismiss"
selector:
text:
dismiss_action:
name: "Dismiss Action"
description: "Action to run when notification is dismissed"
default: []
selector:
action:
mode: restart
sequence:
- alias: "Set up variables"
variables:
action_confirm: "{{ 'CONFIRM_' ~ context.id }}"
action_dismiss: "{{ 'DISMISS_' ~ context.id }}"
- alias: "Send notification"
domain: mobile_app
type: notify
device_id: !input notify_device
title: !input title
message: !input message
data:
actions:
- action: "{{ action_confirm }}"
title: !input confirm_text
- action: "{{ action_dismiss }}"
title: !input dismiss_text
- alias: "Awaiting response"
wait_for_trigger:
- platform: event
event_type: mobile_app_notification_action
event_data:
action: "{{ action_confirm }}"
- platform: event
event_type: mobile_app_notification_action
event_data:
action: "{{ action_dismiss }}"
- choose:
- conditions: "{{ wait.trigger.event.data.action == action_confirm }}"
sequence: !input confirm_action
- conditions: "{{ wait.trigger.event.data.action == action_dismiss }}"
sequence: !input dismiss_action

View File

@@ -0,0 +1,27 @@
blueprint:
name: Invert a binary sensor
description: Creates a binary_sensor which holds the inverted value of a reference binary_sensor
domain: template
source_url: https://github.com/home-assistant/core/blob/dev/homeassistant/components/template/blueprints/inverted_binary_sensor.yaml
input:
reference_entity:
name: Binary sensor to be inverted
description: The binary_sensor which needs to have its value inverted
selector:
entity:
domain: binary_sensor
variables:
reference_entity: !input reference_entity
binary_sensor:
state: >
{% if states(reference_entity) == 'on' %}
off
{% elif states(reference_entity) == 'off' %}
on
{% else %}
{{ states(reference_entity) }}
{% endif %}
# delay_on: not_used in this example
# delay_off: not_used in this example
# auto_off: not_used in this example
availability: "{{ states(reference_entity) not in ('unknown', 'unavailable') }}"

173
configuration.yaml Normal file
View File

@@ -0,0 +1,173 @@
# Loads default set of integrations. Do not remove.
default_config:
homeassistant:
auth_mfa_modules:
- type: totp
# Load frontend themes from the themes folder
frontend:
themes: !include_dir_merge_named themes
template:
- switch:
- name: "Inverse of Upper Landing Switch"
unique_id: inverse_Upper_Landing_A
state: >
{{ is_state('switch.upper_landing_a_switch', 'off') }}
turn_on:
service: switch.turn_off
target:
entity_id: switch.upper_landing_a_switch
turn_off:
service: switch.turn_on
target:
entity_id: switch.upper_landing_a_switch
automation: !include automations.yaml
script: !include scripts.yaml
scene: !include scenes.yaml
rest_command:
pause_ads_5:
url: "http://192.168.0.248:20720/admin/api.php?disable=300&auth=c874330af40074c580efdb36d0476e216d3bc3ffb860dcfd9361da5a457770f5"
panel_custom:
- name: panel_automations
sidebar_title: Automations
sidebar_icon: mdi:cogs
url_path: 'config/automation/dashboard'
module_url: /api/hassio/app/entrypoint.js
embed_iframe: true
require_admin: true
- name: panel_integrations
sidebar_title: Integrations
sidebar_icon: mdi:cogs
url_path: 'config/integrations/dashboard'
module_url: /api/hassio/app/entrypoint.js
embed_iframe: true
require_admin: true
- name: panel_devices
sidebar_title: Devices
sidebar_icon: mdi:cogs
url_path: 'config/devices/dashboard'
module_url: /api/hassio/app/entrypoint.js
embed_iframe: true
require_admin: true
http:
use_x_forwarded_for: true
trusted_proxies:
- 192.168.0.249
twilio:
account_sid: "AC7732cb92a1dc8b59749e8d695495793c"
auth_token: "9ec61ee9906106e8597f6151df910466"
notify:
- name: twilio_call
platform: twilio_call
from_number: "+16828463062"
# Groups
light:
- platform: group
name: "_NH_PRIMARY_BEDROOM_LIGHTS"
entities:
- light.lamp_lights
- platform: group
name: "_NH_PRIMARY_BATH_LIGHTS"
entities:
- light.lamp_lights
- platform: group
name: "_NH_PRIMARY_CLOSET_LIGHTS"
entities:
- light.lamp_lights
- platform: group
name: "_NH_ENTRY_LIGHTS"
entities:
- light.lamp_lights
- platform: group
name: "_NH_GARAGE_LIGHTS"
entities:
- light.lamp_lights
- platform: group
name: "_NH_KITCHEN_LIGHTS"
entities:
- light.lamp_lights
- platform: group
name: "_NH_LIVING_ROOM_LIGHTS"
entities:
- light.lamp_lights
- platform: group
name: "_NH_POWDER_BATH_LIGHTS"
entities:
- light.lamp_lights
- platform: group
name: "_NH_OFFICE_LIGHTS"
entities:
- light.lamp_lights
- platform: group
name: "_NH_STAIR_LIGHTS"
entities:
- light.lamp_lights
- platform: group
name: "_NH_PATIO_LIGHTS"
entities:
- light.lamp_lights
- platform: group
name: "_NH_LAUNDRY_LIGHTS"
entities:
- light.lamp_lights
- platform: group
name: "_NH_GARAGE_ENTRY_LIGHTS"
entities:
- light.lamp_lights
- platform: group
name: "_NH_PANTRY_LIGHTS"
entities:
- light.lamp_lights
- platform: group
name: "_NH_STORAGE_LIGHTS"
entities:
- light.lamp_lights
- platform: group
name: "_NH_PRIMARY_TOILET_LIGHTS"
entities:
- light.lamp_lights
- platform: group
name: "_NH_LIBRARY_LIGHTS"
entities:
- light.lamp_lights
- platform: group
name: "_NH_GUEST_LIGHTS"
entities:
- light.lamp_lights
- platform: group
name: "_NH_MOVIE_LIGHTS"
entities:
- light.lamp_lights
- platform: group
name: "_NH_GUEST_BATH_LIGHTS"
entities:
- light.lamp_lights
- platform: group
name: "_NH_UPSTAIRS_HALL_LIGHTS"
entities:
- light.lamp_lights
- platform: group
name: "_NH_LOFT_LIGHTS"
entities:
- light.lamp_lights

View File

@@ -0,0 +1,578 @@
"""The Alarmo Integration."""
import re
import base64
import logging
import concurrent.futures
import bcrypt
from homeassistant.core import (
HomeAssistant,
asyncio,
callback,
)
from homeassistant.const import (
ATTR_CODE,
ATTR_NAME,
)
from homeassistant.helpers import device_registry as dr
from homeassistant.helpers import entity_registry as er
from homeassistant.config_entries import ConfigEntry
from homeassistant.helpers.service import (
async_register_admin_service,
)
from homeassistant.helpers.dispatcher import (
async_dispatcher_send,
async_dispatcher_connect,
)
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
from homeassistant.components.alarm_control_panel import DOMAIN as PLATFORM
from . import const
from .card import async_register_card
from .mqtt import MqttHandler
from .event import EventHandler
from .panel import (
async_register_panel,
async_unregister_panel,
)
from .store import async_get_registry
from .sensors import (
ATTR_GROUP,
ATTR_ENTITIES,
ATTR_NEW_ENTITY_ID,
SensorHandler,
)
from .websockets import async_register_websockets
from .automations import AutomationHandler
_LOGGER = logging.getLogger(__name__)
# Max number of threads to start when checking user codes.
MAX_WORKERS = 4
# Number of rounds of hashing when computing user hashes.
BCRYPT_NUM_ROUNDS = 10
async def async_setup(hass, config):
"""Track states and offer events for sensors."""
return True
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
"""Set up Alarmo integration from a config entry."""
session = async_get_clientsession(hass)
store = await async_get_registry(hass)
coordinator = AlarmoCoordinator(hass, session, entry, store)
device_registry = dr.async_get(hass)
device_registry.async_get_or_create(
config_entry_id=entry.entry_id,
identifiers={(const.DOMAIN, coordinator.id)},
name=const.NAME,
model=const.NAME,
sw_version=const.VERSION,
manufacturer=const.MANUFACTURER,
)
hass.data.setdefault(const.DOMAIN, {})
hass.data[const.DOMAIN] = {"coordinator": coordinator, "areas": {}, "master": None}
if entry.unique_id is None:
hass.config_entries.async_update_entry(entry, unique_id=coordinator.id, data={})
await hass.config_entries.async_forward_entry_setups(entry, [PLATFORM])
# Register the panel (frontend)
await async_register_panel(hass)
await async_register_card(hass)
# Websocket support
await async_register_websockets(hass)
# Register custom services
register_services(hass)
return True
async def async_unload_entry(hass, entry):
"""Unload Alarmo config entry."""
unload_ok = all(
await asyncio.gather(
*[hass.config_entries.async_forward_entry_unload(entry, PLATFORM)]
)
)
if not unload_ok:
return False
async_unregister_panel(hass)
coordinator = hass.data[const.DOMAIN]["coordinator"]
await coordinator.async_unload()
return True
async def async_remove_entry(hass, entry):
"""Remove Alarmo config entry."""
async_unregister_panel(hass)
coordinator = hass.data[const.DOMAIN]["coordinator"]
await coordinator.async_delete_config()
del hass.data[const.DOMAIN]
async def async_migrate_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Handle migration of config entry."""
return True
class AlarmoCoordinator(DataUpdateCoordinator):
"""Define an object to hold Alarmo device."""
def __init__(self, hass, session, entry, store):
"""Initialize."""
self.id = entry.unique_id
self.hass = hass
self.entry = entry
self.store = store
self._subscriptions = []
self._subscriptions.append(
async_dispatcher_connect(
hass, "alarmo_platform_loaded", self.setup_alarm_entities
)
)
self.register_events()
super().__init__(hass, _LOGGER, config_entry=entry, name=const.DOMAIN)
@callback
def setup_alarm_entities(self):
"""Set up alarm_control_panel entities based on areas in storage."""
self.hass.data[const.DOMAIN]["sensor_handler"] = SensorHandler(self.hass)
self.hass.data[const.DOMAIN]["automation_handler"] = AutomationHandler(
self.hass
)
self.hass.data[const.DOMAIN]["mqtt_handler"] = MqttHandler(self.hass)
self.hass.data[const.DOMAIN]["event_handler"] = EventHandler(self.hass)
areas = self.store.async_get_areas()
config = self.store.async_get_config()
for item in areas.values():
async_dispatcher_send(self.hass, "alarmo_register_entity", item)
if len(areas) > 1 and config["master"]["enabled"]:
async_dispatcher_send(self.hass, "alarmo_register_master", config["master"])
async def async_update_config(self, data):
"""Update the main configuration."""
if "master" in data:
old_config = self.store.async_get_config()
if old_config[const.ATTR_MASTER] != data["master"]:
if self.hass.data[const.DOMAIN]["master"]:
await self.async_remove_entity("master")
if data["master"]["enabled"]:
async_dispatcher_send(
self.hass, "alarmo_register_master", data["master"]
)
else:
automations = self.hass.data[const.DOMAIN][
"automation_handler"
].get_automations_by_area(None)
if len(automations):
for el in automations:
self.store.async_delete_automation(el)
async_dispatcher_send(self.hass, "alarmo_automations_updated")
self.store.async_update_config(data)
async_dispatcher_send(self.hass, "alarmo_config_updated")
async def async_update_area_config(
self,
area_id: str | None = None,
data: dict = {},
):
"""Update area configuration."""
if const.ATTR_REMOVE in data:
# delete an area
res = self.store.async_get_area(area_id)
if not res:
return
sensors = self.store.async_get_sensors()
sensors = dict(filter(lambda el: el[1]["area"] == area_id, sensors.items()))
if sensors:
for el in sensors.keys():
self.store.async_delete_sensor(el)
async_dispatcher_send(self.hass, "alarmo_sensors_updated")
automations = self.hass.data[const.DOMAIN][
"automation_handler"
].get_automations_by_area(area_id)
if len(automations):
for el in automations:
self.store.async_delete_automation(el)
async_dispatcher_send(self.hass, "alarmo_automations_updated")
self.store.async_delete_area(area_id)
await self.async_remove_entity(area_id)
if (
len(self.store.async_get_areas()) == 1
and self.hass.data[const.DOMAIN]["master"]
):
await self.async_remove_entity("master")
elif self.store.async_get_area(area_id):
# modify an area
entry = self.store.async_update_area(area_id, data)
if "name" not in data:
async_dispatcher_send(self.hass, "alarmo_config_updated", area_id)
else:
await self.async_remove_entity(area_id)
async_dispatcher_send(self.hass, "alarmo_register_entity", entry)
else:
# create an area
entry = self.store.async_create_area(data)
async_dispatcher_send(self.hass, "alarmo_register_entity", entry)
config = self.store.async_get_config()
if len(self.store.async_get_areas()) == 2 and config["master"]["enabled"]:
async_dispatcher_send(
self.hass, "alarmo_register_master", config["master"]
)
def async_update_sensor_config(self, entity_id: str, data: dict):
"""Update sensor configuration."""
group = None
if ATTR_GROUP in data:
group = data[ATTR_GROUP]
del data[ATTR_GROUP]
if ATTR_NEW_ENTITY_ID in data:
# delete old sensor entry when changing the entity_id
new_entity_id = data[ATTR_NEW_ENTITY_ID]
del data[ATTR_NEW_ENTITY_ID]
self.store.async_delete_sensor(entity_id)
self.assign_sensor_to_group(new_entity_id, group)
self.assign_sensor_to_group(entity_id, None)
entity_id = new_entity_id
if const.ATTR_REMOVE in data:
self.store.async_delete_sensor(entity_id)
self.assign_sensor_to_group(entity_id, None)
elif self.store.async_get_sensor(entity_id):
self.store.async_update_sensor(entity_id, data)
self.assign_sensor_to_group(entity_id, group)
else:
self.store.async_create_sensor(entity_id, data)
self.assign_sensor_to_group(entity_id, group)
async_dispatcher_send(self.hass, "alarmo_sensors_updated")
def _validate_user_code(self, user_id: str, data: dict):
user_with_code = self.async_authenticate_user(data[ATTR_CODE])
if user_id:
if const.ATTR_OLD_CODE not in data:
return "No code provided"
if not self.async_authenticate_user(data[const.ATTR_OLD_CODE], user_id):
return "Wrong code provided"
if user_with_code and user_with_code[const.ATTR_USER_ID] != user_id:
return "User with same code already exists"
elif user_with_code:
return "User with same code already exists"
return
def _validate_user_name(self, user_id: str, data: dict):
if not data[ATTR_NAME]:
return "User name must not be empty"
for user in self.store.async_get_users().values():
if (
data[ATTR_NAME] == user[ATTR_NAME]
and user_id != user[const.ATTR_USER_ID]
):
return "User with same name already exists"
return
def async_update_user_config(self, user_id: str | None = None, data: dict = {}):
"""Update user configuration."""
if const.ATTR_REMOVE in data:
self.store.async_delete_user(user_id)
return
if ATTR_NAME in data:
err = self._validate_user_name(user_id, data)
if err:
_LOGGER.error(err)
return err
if ATTR_CODE in data:
err = self._validate_user_code(user_id, data)
if err:
_LOGGER.error(err)
return err
if data.get(ATTR_CODE):
data[const.ATTR_CODE_FORMAT] = (
"number" if data[ATTR_CODE].isdigit() else "text"
)
data[const.ATTR_CODE_LENGTH] = len(data[ATTR_CODE])
hashed = bcrypt.hashpw(
data[ATTR_CODE].encode("utf-8"),
bcrypt.gensalt(rounds=BCRYPT_NUM_ROUNDS),
)
hashed = base64.b64encode(hashed)
data[ATTR_CODE] = hashed.decode()
if not user_id:
self.store.async_create_user(data)
return
else:
if ATTR_CODE in data:
del data[const.ATTR_OLD_CODE]
self.store.async_update_user(user_id, data)
return
def async_authenticate_user(self, code: str, user_id: str | None = None):
"""Authenticate a user by code."""
def check_user_code(user, code):
"""Returns the supplied user object if the code matches, None otherwise."""
if not user[const.ATTR_ENABLED]:
return
elif not user[ATTR_CODE] and not code:
return user
elif user[ATTR_CODE]:
hash = base64.b64decode(user[ATTR_CODE])
if bcrypt.checkpw(code.encode("utf-8"), hash):
return user
if user_id:
return check_user_code(self.store.async_get_user(user_id), code)
users = self.store.async_get_users()
with concurrent.futures.ThreadPoolExecutor(max_workers=MAX_WORKERS) as executor:
futures = [
executor.submit(check_user_code, user, code) for user in users.values()
]
for future in concurrent.futures.as_completed(futures):
if future.result():
executor.shutdown(wait=False, cancel_futures=True)
return future.result()
def async_update_automation_config(
self,
automation_id: str | None = None,
data: dict = {},
):
"""Update automation configuration."""
if const.ATTR_REMOVE in data:
self.store.async_delete_automation(automation_id)
elif not automation_id:
self.store.async_create_automation(data)
else:
self.store.async_update_automation(automation_id, data)
async_dispatcher_send(self.hass, "alarmo_automations_updated")
def register_events(self):
"""Register event handlers."""
# handle push notifications with action buttons
@callback
async def async_handle_push_event(event):
if not event.data:
return
action = (
event.data.get("actionName")
if "actionName" in event.data
else event.data.get("action")
)
if action not in const.EVENT_ACTIONS:
return
if self.hass.data[const.DOMAIN]["master"]:
alarm_entity = self.hass.data[const.DOMAIN]["master"]
elif len(self.hass.data[const.DOMAIN]["areas"]) == 1:
alarm_entity = next(
iter(self.hass.data[const.DOMAIN]["areas"].values())
)
else:
_LOGGER.info(
"Cannot process the push action, since there are multiple areas."
)
return
arm_mode = (
alarm_entity._revert_state
if alarm_entity._revert_state in const.ARM_MODES
else alarm_entity._arm_mode
)
res = re.search(r"^ALARMO_ARM_", action)
if res:
arm_mode = action.replace("ALARMO_", "").lower().replace("arm", "armed")
if not arm_mode:
_LOGGER.info(
"Cannot process the push action, since the arm mode is not known."
)
return
if action == const.EVENT_ACTION_FORCE_ARM:
_LOGGER.info("Received request for force arming")
alarm_entity.async_handle_arm_request(
arm_mode, skip_code=True, bypass_open_sensors=True
)
elif action == const.EVENT_ACTION_RETRY_ARM:
_LOGGER.info("Received request for retry arming")
alarm_entity.async_handle_arm_request(arm_mode, skip_code=True)
elif action == const.EVENT_ACTION_DISARM:
_LOGGER.info("Received request for disarming")
alarm_entity.alarm_disarm(None, skip_code=True)
else:
_LOGGER.info(
"Received request for arming with mode %s",
arm_mode,
)
alarm_entity.async_handle_arm_request(arm_mode, skip_code=True)
self._subscriptions.append(
self.hass.bus.async_listen(const.PUSH_EVENT, async_handle_push_event)
)
async def async_remove_entity(self, area_id: str):
"""Remove an alarm_control_panel entity."""
entity_registry = er.async_get(self.hass)
if area_id == "master":
entity = self.hass.data[const.DOMAIN]["master"]
entity_registry.async_remove(entity.entity_id)
self.hass.data[const.DOMAIN]["master"] = None
else:
entity = self.hass.data[const.DOMAIN]["areas"][area_id]
entity_registry.async_remove(entity.entity_id)
self.hass.data[const.DOMAIN]["areas"].pop(area_id, None)
def async_get_sensor_groups(self):
"""Fetch a list of sensor groups (websocket API hook)."""
groups = self.store.async_get_sensor_groups()
return list(groups.values())
def async_get_group_for_sensor(self, entity_id: str):
"""Fetch the group ID for a given sensor."""
groups = self.async_get_sensor_groups()
result = next((el for el in groups if entity_id in el[ATTR_ENTITIES]), None)
return result["group_id"] if result else None
def assign_sensor_to_group(self, entity_id: str, group_id: str):
"""Assign a sensor to a group."""
updated = False
old_group = self.async_get_group_for_sensor(entity_id)
if old_group and group_id != old_group:
# remove sensor from group
el = self.store.async_get_sensor_group(old_group)
if len(el[ATTR_ENTITIES]) > 2:
self.store.async_update_sensor_group(
old_group,
{ATTR_ENTITIES: [x for x in el[ATTR_ENTITIES] if x != entity_id]},
)
else:
self.store.async_delete_sensor_group(old_group)
updated = True
if group_id:
# add sensor to group
group = self.store.async_get_sensor_group(group_id)
if not group:
_LOGGER.error(
"Failed to assign entity %s to group %s",
entity_id,
group_id,
)
elif entity_id not in group[ATTR_ENTITIES]:
self.store.async_update_sensor_group(
group_id, {ATTR_ENTITIES: group[ATTR_ENTITIES] + [entity_id]}
)
updated = True
if updated:
async_dispatcher_send(self.hass, "alarmo_sensors_updated")
def async_update_sensor_group_config(
self,
group_id: str | None = None,
data: dict = {},
):
"""Update sensor group configuration."""
if const.ATTR_REMOVE in data:
self.store.async_delete_sensor_group(group_id)
elif not group_id:
self.store.async_create_sensor_group(data)
else:
self.store.async_update_sensor_group(group_id, data)
async_dispatcher_send(self.hass, "alarmo_sensors_updated")
async def async_unload(self):
"""Remove all alarmo objects."""
# remove alarm_control_panel entities
areas = list(self.hass.data[const.DOMAIN]["areas"].keys())
for area in areas:
await self.async_remove_entity(area)
if self.hass.data[const.DOMAIN]["master"]:
await self.async_remove_entity("master")
del self.hass.data[const.DOMAIN]["sensor_handler"]
del self.hass.data[const.DOMAIN]["automation_handler"]
del self.hass.data[const.DOMAIN]["mqtt_handler"]
del self.hass.data[const.DOMAIN]["event_handler"]
# remove subscriptions for coordinator
while len(self._subscriptions):
self._subscriptions.pop()()
async def async_delete_config(self):
"""Wipe alarmo storage."""
await self.store.async_delete()
@callback
def register_services(hass):
"""Register services used by alarmo component."""
coordinator = hass.data[const.DOMAIN]["coordinator"]
async def async_srv_toggle_user(call):
"""Enable a user by service call."""
name = call.data.get(ATTR_NAME)
enable = True if call.service == const.SERVICE_ENABLE_USER else False
users = coordinator.store.async_get_users()
user = next(
(item for item in list(users.values()) if item[ATTR_NAME] == name), None
)
if user is None:
_LOGGER.warning(
"Failed to %s user, no match for name '%s'",
"enable" if enable else "disable",
name,
)
return
coordinator.store.async_update_user(
user[const.ATTR_USER_ID], {const.ATTR_ENABLED: enable}
)
_LOGGER.debug(
"User user '%s' was %s", name, "enabled" if enable else "disabled"
)
async_register_admin_service(
hass,
const.DOMAIN,
const.SERVICE_ENABLE_USER,
async_srv_toggle_user,
schema=const.SERVICE_TOGGLE_USER_SCHEMA,
)
async_register_admin_service(
hass,
const.DOMAIN,
const.SERVICE_DISABLE_USER,
async_srv_toggle_user,
schema=const.SERVICE_TOGGLE_USER_SCHEMA,
)

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,380 @@
"""Automations."""
import re
import copy
import logging
from homeassistant.core import (
HomeAssistant,
callback,
)
from homeassistant.const import (
CONF_TYPE,
ATTR_SERVICE,
ATTR_ENTITY_ID,
CONF_SERVICE_DATA,
)
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.template import Template, is_template_string
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.translation import async_get_translations
from homeassistant.components.binary_sensor.device_condition import (
ENTITY_CONDITIONS,
)
from . import const
from .helpers import (
friendly_name_for_entity_id,
)
from .sensors import (
STATE_OPEN,
STATE_CLOSED,
STATE_UNAVAILABLE,
)
from .alarm_control_panel import AlarmoBaseEntity
_LOGGER = logging.getLogger(__name__)
EVENT_ARM_FAILURE = "arm_failure"
def validate_area(trigger, area_id, hass):
"""Validate area for trigger."""
if const.ATTR_AREA not in trigger:
return False
elif trigger[const.ATTR_AREA]:
return trigger[const.ATTR_AREA] == area_id
elif len(hass.data[const.DOMAIN]["areas"]) == 1:
return True
else:
return area_id is None
def validate_modes(trigger, mode):
"""Validate modes for trigger."""
if const.ATTR_MODES not in trigger:
return False
elif not trigger[const.ATTR_MODES]:
return True
else:
return mode in trigger[const.ATTR_MODES]
def validate_trigger(trigger, to_state, from_state=None):
"""Validate trigger condition."""
if const.ATTR_EVENT not in trigger:
return False
elif trigger[const.ATTR_EVENT] == "untriggered" and from_state == "triggered":
return True
elif trigger[const.ATTR_EVENT] == to_state:
return True
else:
return False
class AutomationHandler:
"""Handle automations."""
def __init__(self, hass: HomeAssistant):
"""Initialize automation handler."""
self.hass = hass
self._config = None
self._subscriptions = []
self._sensorTranslationCache = {}
self._alarmTranslationCache = {}
self._sensorTranslationLang = None
self._alarmTranslationLang = None
def async_update_config():
"""Automation config updated, reload the configuration."""
self._config = self.hass.data[const.DOMAIN][
"coordinator"
].store.async_get_automations()
self._subscriptions.append(
async_dispatcher_connect(
hass, "alarmo_automations_updated", async_update_config
)
)
async_update_config()
@callback
async def async_alarm_state_changed(
area_id: str, old_state: str, new_state: str
):
if not old_state:
# ignore automations at startup/restoring
return
if area_id:
alarm_entity = self.hass.data[const.DOMAIN]["areas"][area_id]
else:
alarm_entity = self.hass.data[const.DOMAIN]["master"]
if not alarm_entity:
return
_LOGGER.debug(
"state of %s is updated from %s to %s",
alarm_entity.entity_id,
old_state,
new_state,
)
if new_state in const.ARM_MODES:
# we don't distinguish between armed modes for automations
# they are handled separately
new_state = "armed"
for automation_id, config in self._config.items():
if not config[const.ATTR_ENABLED]:
continue
for trigger in config[const.ATTR_TRIGGERS]:
if (
validate_area(trigger, area_id, self.hass)
and validate_modes(trigger, alarm_entity._arm_mode)
and validate_trigger(trigger, new_state, old_state)
):
await self.async_execute_automation(automation_id, alarm_entity)
self._subscriptions.append(
async_dispatcher_connect(
self.hass, "alarmo_state_updated", async_alarm_state_changed
)
)
@callback
async def async_handle_event(event: str, area_id: str, args: dict = {}):
if event != const.EVENT_FAILED_TO_ARM:
return
if area_id:
alarm_entity = self.hass.data[const.DOMAIN]["areas"][area_id]
else:
alarm_entity = self.hass.data[const.DOMAIN]["master"]
_LOGGER.debug(
"%s has failed to arm",
alarm_entity.entity_id,
)
for automation_id, config in self._config.items():
if not config[const.ATTR_ENABLED]:
continue
for trigger in config[const.ATTR_TRIGGERS]:
if (
validate_area(trigger, area_id, self.hass)
and validate_modes(trigger, alarm_entity._arm_mode)
and validate_trigger(trigger, EVENT_ARM_FAILURE)
):
await self.async_execute_automation(automation_id, alarm_entity)
self._subscriptions.append(
async_dispatcher_connect(self.hass, "alarmo_event", async_handle_event)
)
def __del__(self):
"""Prepare for removal."""
while len(self._subscriptions):
self._subscriptions.pop()()
async def async_execute_automation(
self, automation_id: str, alarm_entity: AlarmoBaseEntity
):
"""Execute the specified automation."""
# automation is a dict of AutomationEntry
_LOGGER.debug(
"Executing automation %s",
automation_id,
)
actions = self._config[automation_id][const.ATTR_ACTIONS]
for action in actions:
try:
service_data = copy.copy(action[CONF_SERVICE_DATA])
if action.get(ATTR_ENTITY_ID):
service_data[ATTR_ENTITY_ID] = action[ATTR_ENTITY_ID]
if self._config[automation_id][CONF_TYPE] == const.ATTR_NOTIFICATION:
# replace wildcards within service_data struct
for key, val in service_data.items():
if type(val) is str:
service_data[key] = await self.replace_wildcards_in_string(
val, alarm_entity
)
elif type(val) is dict:
for subkey, subval in service_data[key].items():
if type(subval) is str:
service_data[key][
subkey
] = await self.replace_wildcards_in_string(
subval, alarm_entity
)
domain, service = action[ATTR_SERVICE].split(".")
await self.hass.async_create_task(
self.hass.services.async_call(
domain,
service,
service_data,
blocking=False,
context={},
)
)
except HomeAssistantError as e:
_LOGGER.error(
"Execution of action %s failed, reason: %s",
automation_id,
e,
)
def get_automations_by_area(self, area_id: str):
"""Get automations for specified area."""
result = []
for automation_id, config in self._config.items():
if any(
el[const.ATTR_AREA] == area_id for el in config[const.ATTR_TRIGGERS]
):
result.append(automation_id)
return result
async def replace_wildcards_in_string(
self, input: str, alarm_entity: AlarmoBaseEntity
):
"""Look for wildcards in string and replace them with content."""
# process wildcard '{{open_sensors}}'
res = re.search(r"{{open_sensors(\|lang=([^}]+))?(\|format=short)?}}", input)
if res:
lang = res.group(2) if res.group(2) else "en"
names_only = True if res.group(3) else False
open_sensors = ""
if alarm_entity.open_sensors:
parts = []
for entity_id, status in alarm_entity.open_sensors.items():
if names_only:
parts.append(friendly_name_for_entity_id(entity_id, self.hass))
else:
parts.append(
await self.async_get_open_sensor_string(
entity_id, status, lang
)
)
open_sensors = ", ".join(parts)
input = input.replace(res.group(0), open_sensors)
# process wildcard '{{bypassed_sensors}}'
if "{{bypassed_sensors}}" in input:
bypassed_sensors = ""
if alarm_entity.bypassed_sensors and len(alarm_entity.bypassed_sensors):
parts = []
for entity_id in alarm_entity.bypassed_sensors:
name = friendly_name_for_entity_id(entity_id, self.hass)
parts.append(name)
bypassed_sensors = ", ".join(parts)
input = input.replace("{{bypassed_sensors}}", bypassed_sensors)
# process wildcard '{{arm_mode}}'
res = re.search(r"{{arm_mode(\|lang=([^}]+))?}}", input)
if res:
lang = res.group(2) if res.group(2) else "en"
arm_mode = await self.async_get_arm_mode_string(alarm_entity.arm_mode, lang)
input = input.replace(res.group(0), arm_mode)
# process wildcard '{{changed_by}}'
if "{{changed_by}}" in input:
changed_by = alarm_entity.changed_by if alarm_entity.changed_by else ""
input = input.replace("{{changed_by}}", changed_by)
# process wildcard '{{delay}}'
if "{{delay}}" in input:
delay = str(alarm_entity.delay) if alarm_entity.delay else ""
input = input.replace("{{delay}}", delay)
# process HA templates
if is_template_string(input):
input = Template(input, self.hass).async_render()
return input
async def async_get_open_sensor_string(
self, entity_id: str, state: str, language: str
):
"""Get translation for sensor states."""
if self._sensorTranslationCache and self._sensorTranslationLang == language:
translations = self._sensorTranslationCache
else:
translations = await async_get_translations(
self.hass, language, "device_automation", ["binary_sensor"]
)
self._sensorTranslationCache = translations
self._sensorTranslationLang = language
entity = self.hass.states.get(entity_id)
device_type = (
entity.attributes["device_class"]
if entity and "device_class" in entity.attributes
else None
)
if state == STATE_OPEN:
translation_key = (
f"component.binary_sensor.device_automation.condition_type.{ENTITY_CONDITIONS[device_type][0]['type']}"
if device_type in ENTITY_CONDITIONS
else None
)
if translation_key and translation_key in translations:
string = translations[translation_key]
else:
string = "{entity_name} is open"
elif state == STATE_CLOSED:
translation_key = (
f"component.binary_sensor.device_automation.condition_type.{ENTITY_CONDITIONS[device_type][1]['type']}"
if device_type in ENTITY_CONDITIONS
else None
)
if translation_key and translation_key in translations:
string = translations[translation_key]
else:
string = "{entity_name} is closed"
elif state == STATE_UNAVAILABLE:
string = "{entity_name} is unavailable"
else:
string = "{entity_name} is unknown"
name = friendly_name_for_entity_id(entity_id, self.hass)
string = string.replace("{entity_name}", name)
return string
async def async_get_arm_mode_string(self, arm_mode: str, language: str):
"""Get translation for alarm arm mode."""
if self._alarmTranslationCache and self._alarmTranslationLang == language:
translations = self._alarmTranslationCache
else:
translations = await async_get_translations(
self.hass, language, "entity_component", ["alarm_control_panel"]
)
self._alarmTranslationCache = translations
self._alarmTranslationLang = language
translation_key = (
f"component.alarm_control_panel.entity_component._.state.{arm_mode}"
if arm_mode
else None
)
if translation_key and translation_key in translations:
return translations[translation_key]
elif arm_mode:
return " ".join(w.capitalize() for w in arm_mode.split("_"))
else:
return ""

View File

@@ -0,0 +1,34 @@
"""WebSocket handler and registration for Alarmo card update events."""
import voluptuous as vol
from homeassistant.core import callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.components.websocket_api import decorators, async_register_command
@decorators.websocket_command(
{
vol.Required("type"): "alarmo_updated",
}
)
@decorators.async_response
async def handle_subscribe_updates(hass, connection, msg):
"""Handle subscribe updates."""
@callback
def handle_event(event: str, area_id: str, args: dict = {}):
"""Forward events to websocket."""
data = dict(**args, **{"event": event, "area_id": area_id})
connection.send_message(
{"id": msg["id"], "type": "event", "event": {"data": data}}
)
connection.subscriptions[msg["id"]] = async_dispatcher_connect(
hass, "alarmo_event", handle_event
)
connection.send_result(msg["id"])
async def async_register_card(hass):
"""Publish event to lovelace when alarm changes."""
async_register_command(hass, handle_subscribe_updates)

View File

@@ -0,0 +1,30 @@
"""Config flow for the Alarmo component."""
import secrets
from homeassistant import config_entries
from .const import (
NAME,
DOMAIN,
)
class AlarmoConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
"""Config flow for Alarmo."""
VERSION = "1.0.0"
CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_POLL
async def async_step_user(self, user_input=None):
"""Handle a flow initialized by the user."""
# Only a single instance of the integration
if self._async_current_entries():
return self.async_abort(reason="single_instance_allowed")
id = secrets.token_hex(6)
await self.async_set_unique_id(id)
self._abort_if_unique_id_configured(updates=user_input)
return self.async_create_entry(title=NAME, data={})

View File

@@ -0,0 +1,234 @@
"""Store constants."""
import datetime
import voluptuous as vol
from homeassistant.const import (
ATTR_NAME,
CONF_CODE,
CONF_MODE,
ATTR_ENTITY_ID,
)
from homeassistant.helpers import config_validation as cv
from homeassistant.components.alarm_control_panel import (
AlarmControlPanelState,
AlarmControlPanelEntityFeature,
)
VERSION = "1.10.13"
NAME = "Alarmo"
MANUFACTURER = "@nielsfaber"
DOMAIN = "alarmo"
CUSTOM_COMPONENTS = "custom_components"
INTEGRATION_FOLDER = DOMAIN
PANEL_FOLDER = "frontend"
PANEL_FILENAME = "dist/alarm-panel.js"
PANEL_URL = "/api/panel_custom/alarmo"
PANEL_TITLE = NAME
PANEL_ICON = "mdi:shield-home"
PANEL_NAME = "alarm-panel"
INITIALIZATION_TIME = datetime.timedelta(seconds=60)
SENSOR_ARM_TIME = datetime.timedelta(seconds=5)
STATES = [
AlarmControlPanelState.ARMED_AWAY,
AlarmControlPanelState.ARMED_HOME,
AlarmControlPanelState.ARMED_NIGHT,
AlarmControlPanelState.ARMED_CUSTOM_BYPASS,
AlarmControlPanelState.ARMED_VACATION,
AlarmControlPanelState.DISARMED,
AlarmControlPanelState.TRIGGERED,
AlarmControlPanelState.PENDING,
AlarmControlPanelState.ARMING,
]
ARM_MODES = [
AlarmControlPanelState.ARMED_AWAY,
AlarmControlPanelState.ARMED_HOME,
AlarmControlPanelState.ARMED_NIGHT,
AlarmControlPanelState.ARMED_CUSTOM_BYPASS,
AlarmControlPanelState.ARMED_VACATION,
]
ARM_MODE_TO_STATE = {
"away": AlarmControlPanelState.ARMED_AWAY,
"home": AlarmControlPanelState.ARMED_HOME,
"night": AlarmControlPanelState.ARMED_NIGHT,
"custom": AlarmControlPanelState.ARMED_CUSTOM_BYPASS,
"vacation": AlarmControlPanelState.ARMED_VACATION,
}
STATE_TO_ARM_MODE = {
AlarmControlPanelState.ARMED_AWAY: "away",
AlarmControlPanelState.ARMED_HOME: "home",
AlarmControlPanelState.ARMED_NIGHT: "night",
AlarmControlPanelState.ARMED_CUSTOM_BYPASS: "custom",
AlarmControlPanelState.ARMED_VACATION: "vacation",
}
COMMAND_ARM_NIGHT = "arm_night"
COMMAND_ARM_AWAY = "arm_away"
COMMAND_ARM_HOME = "arm_home"
COMMAND_ARM_CUSTOM_BYPASS = "arm_custom_bypass"
COMMAND_ARM_VACATION = "arm_vacation"
COMMAND_DISARM = "disarm"
COMMANDS = [
COMMAND_DISARM,
COMMAND_ARM_AWAY,
COMMAND_ARM_NIGHT,
COMMAND_ARM_HOME,
COMMAND_ARM_CUSTOM_BYPASS,
COMMAND_ARM_VACATION,
]
EVENT_DISARM = "disarm"
EVENT_LEAVE = "leave"
EVENT_ARM = "arm"
EVENT_ENTRY = "entry"
EVENT_TRIGGER = "trigger"
EVENT_FAILED_TO_ARM = "failed_to_arm"
EVENT_COMMAND_NOT_ALLOWED = "command_not_allowed"
EVENT_INVALID_CODE_PROVIDED = "invalid_code_provided"
EVENT_NO_CODE_PROVIDED = "no_code_provided"
EVENT_TRIGGER_TIME_EXPIRED = "trigger_time_expired"
EVENT_READY_TO_ARM_MODES_CHANGED = "ready_to_arm_modes_changed"
ATTR_MODES = "modes"
ATTR_ARM_MODE = "arm_mode"
ATTR_CODE_DISARM_REQUIRED = "code_disarm_required"
ATTR_CODE_MODE_CHANGE_REQUIRED = "code_mode_change_required"
ATTR_REMOVE = "remove"
ATTR_OLD_CODE = "old_code"
ATTR_TRIGGER_TIME = "trigger_time"
ATTR_EXIT_TIME = "exit_time"
ATTR_ENTRY_TIME = "entry_time"
ATTR_ENABLED = "enabled"
ATTR_USER_ID = "user_id"
ATTR_CAN_ARM = "can_arm"
ATTR_CAN_DISARM = "can_disarm"
ATTR_DISARM_AFTER_TRIGGER = "disarm_after_trigger"
ATTR_IGNORE_BLOCKING_SENSORS_AFTER_TRIGGER = "ignore_blocking_sensors_after_trigger"
ATTR_REMOVE = "remove"
ATTR_IS_OVERRIDE_CODE = "is_override_code"
ATTR_AREA_LIMIT = "area_limit"
ATTR_CODE_FORMAT = "code_format"
ATTR_CODE_LENGTH = "code_length"
ATTR_AUTOMATION_ID = "automation_id"
ATTR_TYPE = "type"
ATTR_AREA = "area"
ATTR_MASTER = "master"
ATTR_TRIGGERS = "triggers"
ATTR_ACTIONS = "actions"
ATTR_EVENT = "event"
ATTR_REQUIRE_CODE = "require_code"
ATTR_NOTIFICATION = "notification"
ATTR_VERSION = "version"
ATTR_STATE_PAYLOAD = "state_payload"
ATTR_COMMAND_PAYLOAD = "command_payload"
ATTR_FORCE = "force"
ATTR_SKIP_DELAY = "skip_delay"
ATTR_CONTEXT_ID = "context_id"
PUSH_EVENT = "mobile_app_notification_action"
EVENT_ACTION_FORCE_ARM = "ALARMO_FORCE_ARM"
EVENT_ACTION_RETRY_ARM = "ALARMO_RETRY_ARM"
EVENT_ACTION_DISARM = "ALARMO_DISARM"
EVENT_ACTION_ARM_AWAY = "ALARMO_ARM_AWAY"
EVENT_ACTION_ARM_HOME = "ALARMO_ARM_HOME"
EVENT_ACTION_ARM_NIGHT = "ALARMO_ARM_NIGHT"
EVENT_ACTION_ARM_VACATION = "ALARMO_ARM_VACATION"
EVENT_ACTION_ARM_CUSTOM_BYPASS = "ALARMO_ARM_CUSTOM_BYPASS"
EVENT_ACTIONS = [
EVENT_ACTION_FORCE_ARM,
EVENT_ACTION_RETRY_ARM,
EVENT_ACTION_DISARM,
EVENT_ACTION_ARM_AWAY,
EVENT_ACTION_ARM_HOME,
EVENT_ACTION_ARM_NIGHT,
EVENT_ACTION_ARM_VACATION,
EVENT_ACTION_ARM_CUSTOM_BYPASS,
]
MODES_TO_SUPPORTED_FEATURES = {
AlarmControlPanelState.ARMED_AWAY: AlarmControlPanelEntityFeature.ARM_AWAY,
AlarmControlPanelState.ARMED_HOME: AlarmControlPanelEntityFeature.ARM_HOME,
AlarmControlPanelState.ARMED_NIGHT: AlarmControlPanelEntityFeature.ARM_NIGHT,
AlarmControlPanelState.ARMED_CUSTOM_BYPASS: AlarmControlPanelEntityFeature.ARM_CUSTOM_BYPASS, # noqa: E501
AlarmControlPanelState.ARMED_VACATION: AlarmControlPanelEntityFeature.ARM_VACATION,
}
SERVICE_ARM = "arm"
SERVICE_DISARM = "disarm"
SERVICE_SKIP_DELAY = "skip_delay"
CONF_ALARM_ARMED_AWAY = "armed_away"
CONF_ALARM_ARMED_CUSTOM_BYPASS = "armed_custom_bypass"
CONF_ALARM_ARMED_HOME = "armed_home"
CONF_ALARM_ARMED_NIGHT = "armed_night"
CONF_ALARM_ARMED_VACATION = "armed_vacation"
CONF_ALARM_ARMING = "arming"
CONF_ALARM_DISARMED = "disarmed"
CONF_ALARM_PENDING = "pending"
CONF_ALARM_TRIGGERED = "triggered"
SERVICE_ARM_SCHEMA = cv.make_entity_service_schema(
{
vol.Required(ATTR_ENTITY_ID): cv.entity_id,
vol.Optional(CONF_CODE, default=""): cv.string,
vol.Optional(CONF_MODE, default=AlarmControlPanelState.ARMED_AWAY): vol.In(
[
"away",
"home",
"night",
"custom",
"vacation",
CONF_ALARM_ARMED_AWAY,
CONF_ALARM_ARMED_HOME,
CONF_ALARM_ARMED_NIGHT,
CONF_ALARM_ARMED_CUSTOM_BYPASS,
CONF_ALARM_ARMED_VACATION,
]
),
vol.Optional(ATTR_SKIP_DELAY, default=False): cv.boolean,
vol.Optional(ATTR_FORCE, default=False): cv.boolean,
vol.Optional(ATTR_CONTEXT_ID): int,
}
)
SERVICE_DISARM_SCHEMA = cv.make_entity_service_schema(
{
vol.Required(ATTR_ENTITY_ID): cv.entity_id,
vol.Optional(CONF_CODE, default=""): cv.string,
vol.Optional(ATTR_CONTEXT_ID): int,
}
)
SERVICE_SKIP_DELAY_SCHEMA = cv.make_entity_service_schema(
{
vol.Required(ATTR_ENTITY_ID): cv.entity_id,
}
)
SERVICE_ENABLE_USER = "enable_user"
SERVICE_DISABLE_USER = "disable_user"
SERVICE_TOGGLE_USER_SCHEMA = vol.Schema(
{
vol.Required(ATTR_NAME, default=""): cv.string,
}
)

View File

@@ -0,0 +1,89 @@
"""fire events in HA for use with automations."""
from homeassistant.core import callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from . import const
class EventHandler:
"""Class to handle events from Alarmo and fire HA events."""
def __init__(self, hass):
"""Class constructor."""
self.hass = hass
self._subscription = async_dispatcher_connect(
self.hass, "alarmo_event", self.async_handle_event
)
def __del__(self):
"""Class destructor."""
self._subscription()
@callback
def async_handle_event(self, event: str, area_id: str, args: dict = {}):
"""Handle event."""
if area_id:
alarm_entity = self.hass.data[const.DOMAIN]["areas"][area_id]
else:
alarm_entity = self.hass.data[const.DOMAIN]["master"]
if event in [
const.EVENT_FAILED_TO_ARM,
const.EVENT_COMMAND_NOT_ALLOWED,
const.EVENT_INVALID_CODE_PROVIDED,
const.EVENT_NO_CODE_PROVIDED,
]:
reasons = {
const.EVENT_FAILED_TO_ARM: "open_sensors",
const.EVENT_COMMAND_NOT_ALLOWED: "not_allowed",
const.EVENT_INVALID_CODE_PROVIDED: "invalid_code",
const.EVENT_NO_CODE_PROVIDED: "invalid_code",
}
data = dict(
**args,
**{
"area_id": area_id,
"entity_id": alarm_entity.entity_id,
"reason": reasons[event],
},
)
if "open_sensors" in data:
data["sensors"] = list(data["open_sensors"].keys())
del data["open_sensors"]
self.hass.bus.async_fire("alarmo_failed_to_arm", data)
elif event in [const.EVENT_ARM, const.EVENT_DISARM]:
data = dict(
**args,
**{
"area_id": area_id,
"entity_id": alarm_entity.entity_id,
"action": event,
},
)
if "arm_mode" in data:
data["mode"] = const.STATE_TO_ARM_MODE[data["arm_mode"]]
del data["arm_mode"]
self.hass.bus.async_fire("alarmo_command_success", data)
elif event == const.EVENT_READY_TO_ARM_MODES_CHANGED:
supported_modes = dict(
filter(
lambda el: el[1] & alarm_entity.supported_features,
const.MODES_TO_SUPPORTED_FEATURES.items(),
)
)
modes = {
k.value: (k.value in args["modes"]) for k in supported_modes.keys()
}
data = {
"area_id": area_id,
"entity_id": alarm_entity.entity_id,
**modes,
}
self.hass.bus.async_fire("alarmo_ready_to_arm_modes_updated", data)

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,19 @@
"""Helper functions for Alarmo integration."""
from homeassistant.core import (
HomeAssistant,
)
def friendly_name_for_entity_id(entity_id: str, hass: HomeAssistant):
"""Helper to get friendly name for entity."""
state = hass.states.get(entity_id)
if state and state.attributes.get("friendly_name"):
return state.attributes["friendly_name"]
return entity_id
def omit(obj: dict, blacklisted_keys: list):
"""Helper to omit blacklisted keys from a dict."""
return {key: val for key, val in obj.items() if key not in blacklisted_keys}

View File

@@ -0,0 +1,8 @@
{
"services": {
"arm": "mdi:shield-lock",
"disarm": "mdi:shield-off",
"enable_user": "mdi:account-lock-open",
"disable_user": "mdi:account-lock-closed"
}
}

View File

@@ -0,0 +1,21 @@
{
"domain": "alarmo",
"name": "Alarmo",
"after_dependencies": [
"mqtt",
"notify"
],
"codeowners": [
"@nielsfaber"
],
"config_flow": true,
"dependencies": [
"http",
"panel_custom"
],
"documentation": "https://github.com/nielsfaber/alarmo",
"iot_class": "local_push",
"issue_tracker": "https://github.com/nielsfaber/alarmo/issues",
"requirements": [],
"version": "1.10.13"
}

View File

@@ -0,0 +1,319 @@
"""Class to handle MQTT integration."""
import json
import logging
from homeassistant.core import (
HomeAssistant,
callback,
)
from homeassistant.util import slugify
from homeassistant.components import mqtt
from homeassistant.helpers.json import JSONEncoder
from homeassistant.components.mqtt import (
DOMAIN as ATTR_MQTT,
)
from homeassistant.components.mqtt import (
CONF_STATE_TOPIC,
CONF_COMMAND_TOPIC,
)
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from . import const
from .helpers import (
friendly_name_for_entity_id,
)
_LOGGER = logging.getLogger(__name__)
CONF_EVENT_TOPIC = "event_topic"
class MqttHandler:
"""Class to handle MQTT integration."""
def __init__(self, hass: HomeAssistant): # noqa: PLR0915
"""Class constructor."""
self.hass = hass
self._config = None
self._subscribed_topics = []
self._subscriptions = []
@callback
def async_update_config(_args=None):
"""Mqtt config updated, reload the configuration."""
old_config = self._config
new_config = self.hass.data[const.DOMAIN][
"coordinator"
].store.async_get_config()
if old_config and old_config[ATTR_MQTT] == new_config[ATTR_MQTT]:
# only update MQTT config if some parameters are changed
return
self._config = new_config
if (
not old_config
or old_config[ATTR_MQTT][CONF_COMMAND_TOPIC]
!= new_config[ATTR_MQTT][CONF_COMMAND_TOPIC]
):
# re-subscribing is only needed if the command topic has changed
self.hass.add_job(self._async_subscribe_topics())
_LOGGER.debug("MQTT config was (re)loaded")
self._subscriptions.append(
async_dispatcher_connect(hass, "alarmo_config_updated", async_update_config)
)
async_update_config()
@callback
def async_alarm_state_changed(area_id: str, old_state: str, new_state: str):
if not self._config[ATTR_MQTT][const.ATTR_ENABLED]:
return
topic = self._config[ATTR_MQTT][CONF_STATE_TOPIC]
if not topic: # do not publish if no topic is provided
return
if area_id and len(self.hass.data[const.DOMAIN]["areas"]) > 1:
# handle the sending of a state update for a specific area
area = self.hass.data[const.DOMAIN]["areas"][area_id]
topic = topic.rsplit("/", 1)
topic.insert(1, slugify(area.name))
topic = "/".join(topic)
payload_config = self._config[ATTR_MQTT][const.ATTR_STATE_PAYLOAD]
if payload_config.get(new_state):
message = payload_config[new_state]
else:
message = new_state
hass.async_create_task(
mqtt.async_publish(self.hass, topic, message, retain=True)
)
_LOGGER.debug(
"Published state '%s' on topic '%s'",
message,
topic,
)
self._subscriptions.append(
async_dispatcher_connect(
self.hass, "alarmo_state_updated", async_alarm_state_changed
)
)
@callback
def async_handle_event(event: str, area_id: str, args: dict = {}):
if not self._config[ATTR_MQTT][const.ATTR_ENABLED]:
return
topic = self._config[ATTR_MQTT][CONF_EVENT_TOPIC]
if not topic: # do not publish if no topic is provided
return
if area_id and len(self.hass.data[const.DOMAIN]["areas"]) > 1:
# handle the sending of a state update for a specific area
area = self.hass.data[const.DOMAIN]["areas"][area_id]
topic = topic.rsplit("/", 1)
topic.insert(1, slugify(area.name))
topic = "/".join(topic)
if event == const.EVENT_ARM:
payload = {
"event": f"{event.upper()}_{args['arm_mode'].split('_', 1).pop(1).upper()}", # noqa: E501
"delay": args["delay"],
}
elif event == const.EVENT_TRIGGER:
payload = {
"event": event.upper(),
"delay": args["delay"],
"sensors": [
{
"entity_id": entity,
"name": friendly_name_for_entity_id(entity, self.hass),
}
for (entity, state) in args["open_sensors"].items()
],
}
elif event == const.EVENT_FAILED_TO_ARM:
payload = {
"event": event.upper(),
"sensors": [
{
"entity_id": entity,
"name": friendly_name_for_entity_id(entity, self.hass),
}
for (entity, state) in args["open_sensors"].items()
],
}
elif event == const.EVENT_COMMAND_NOT_ALLOWED:
payload = {
"event": event.upper(),
"state": args["state"],
"command": args["command"].upper(),
}
elif event in [
const.EVENT_INVALID_CODE_PROVIDED,
const.EVENT_NO_CODE_PROVIDED,
]:
payload = {"event": event.upper()}
else:
return
payload = json.dumps(payload, cls=JSONEncoder)
hass.async_create_task(mqtt.async_publish(self.hass, topic, payload))
self._subscriptions.append(
async_dispatcher_connect(self.hass, "alarmo_event", async_handle_event)
)
def __del__(self):
"""Prepare for removal."""
while len(self._subscribed_topics):
self._subscribed_topics.pop()()
while len(self._subscriptions):
self._subscriptions.pop()()
async def _async_subscribe_topics(self):
"""Install a listener for the command topic."""
if len(self._subscribed_topics):
while len(self._subscribed_topics):
self._subscribed_topics.pop()()
_LOGGER.debug("Removed subscribed topics")
if not self._config[ATTR_MQTT][const.ATTR_ENABLED]:
return
self._subscribed_topics.append(
await mqtt.async_subscribe(
self.hass,
self._config[ATTR_MQTT][CONF_COMMAND_TOPIC],
self.async_message_received,
)
)
_LOGGER.debug(
"Subscribed to topic %s",
self._config[ATTR_MQTT][CONF_COMMAND_TOPIC],
)
@callback
async def async_message_received(self, msg): # noqa: PLR0915, PLR0912
"""Handle new MQTT messages."""
command = None
code = None
area = None
bypass_open_sensors = False
skip_delay = False
try:
payload = json.loads(msg.payload)
payload = {k.lower(): v for k, v in payload.items()}
if "command" in payload:
command = payload["command"]
elif "cmd" in payload:
command = payload["cmd"]
elif "action" in payload:
command = payload["action"]
elif "state" in payload:
command = payload["state"]
if "code" in payload:
code = payload["code"]
elif "pin" in payload:
code = payload["pin"]
elif "password" in payload:
code = payload["password"]
elif "pincode" in payload:
code = payload["pincode"]
if payload.get("area"):
area = payload["area"]
if (payload.get("bypass_open_sensors")) or (payload.get("force")):
bypass_open_sensors = payload["bypass_open_sensors"]
if payload.get(const.ATTR_SKIP_DELAY):
skip_delay = payload[const.ATTR_SKIP_DELAY]
except ValueError:
# no JSON structure found
command = msg.payload
code = None
if type(command) is str:
command = command.lower()
else:
_LOGGER.warning("Received unexpected command")
return
payload_config = self._config[ATTR_MQTT][const.ATTR_COMMAND_PAYLOAD]
skip_code = not self._config[ATTR_MQTT][const.ATTR_REQUIRE_CODE]
command_payloads = {}
for item in const.COMMANDS:
if payload_config.get(item):
command_payloads[item] = payload_config[item].lower()
else:
command_payloads[item] = item.lower()
if command not in list(command_payloads.values()):
_LOGGER.warning("Received unexpected command: %s", command)
return
if area:
res = list(
filter(
lambda el: slugify(el.name) == area,
self.hass.data[const.DOMAIN]["areas"].values(),
)
)
if not res:
_LOGGER.warning(
"Area %s does not exist",
area,
)
return
entity = res[0]
elif (
self._config[const.ATTR_MASTER][const.ATTR_ENABLED]
and len(self.hass.data[const.DOMAIN]["areas"]) > 1
):
entity = self.hass.data[const.DOMAIN]["master"]
elif len(self.hass.data[const.DOMAIN]["areas"]) == 1:
entity = next(iter(self.hass.data[const.DOMAIN]["areas"].values()))
else:
_LOGGER.warning("No area specified")
return
_LOGGER.debug(
"Received command %s",
command,
)
if command == command_payloads[const.COMMAND_DISARM]:
entity.alarm_disarm(code, skip_code=skip_code)
elif command == command_payloads[const.COMMAND_ARM_AWAY]:
await entity.async_alarm_arm_away(
code, skip_code, bypass_open_sensors, skip_delay
)
elif command == command_payloads[const.COMMAND_ARM_NIGHT]:
await entity.async_alarm_arm_night(
code, skip_code, bypass_open_sensors, skip_delay
)
elif command == command_payloads[const.COMMAND_ARM_HOME]:
await entity.async_alarm_arm_home(
code, skip_code, bypass_open_sensors, skip_delay
)
elif command == command_payloads[const.COMMAND_ARM_CUSTOM_BYPASS]:
await entity.async_alarm_arm_custom_bypass(
code, skip_code, bypass_open_sensors, skip_delay
)
elif command == command_payloads[const.COMMAND_ARM_VACATION]:
await entity.async_alarm_arm_vacation(
code, skip_code, bypass_open_sensors, skip_delay
)

View File

@@ -0,0 +1,50 @@
"""Panel registration for Alarmo integration."""
import os
import logging
from homeassistant.components import frontend, panel_custom
from homeassistant.components.http import StaticPathConfig
from .const import (
DOMAIN,
PANEL_URL,
PANEL_ICON,
PANEL_NAME,
PANEL_TITLE,
PANEL_FOLDER,
PANEL_FILENAME,
CUSTOM_COMPONENTS,
INTEGRATION_FOLDER,
)
_LOGGER = logging.getLogger(__name__)
async def async_register_panel(hass):
"""Register the panel."""
root_dir = os.path.join(hass.config.path(CUSTOM_COMPONENTS), INTEGRATION_FOLDER)
panel_dir = os.path.join(root_dir, PANEL_FOLDER)
view_url = os.path.join(panel_dir, PANEL_FILENAME)
await hass.http.async_register_static_paths(
[StaticPathConfig(PANEL_URL, view_url, cache_headers=False)]
)
await panel_custom.async_register_panel(
hass,
webcomponent_name=PANEL_NAME,
frontend_url_path=DOMAIN,
module_url=PANEL_URL,
sidebar_title=PANEL_TITLE,
sidebar_icon=PANEL_ICON,
require_admin=True,
config={},
config_panel_domain=DOMAIN,
)
def async_unregister_panel(hass):
"""Unregister the panel."""
frontend.async_remove_panel(hass, DOMAIN)
_LOGGER.debug("Removing panel")

View File

@@ -0,0 +1,680 @@
"""Sensor handling for Alarmo integration."""
import logging
from types import SimpleNamespace
import homeassistant.util.dt as dt_util
from homeassistant.core import (
CoreState,
HomeAssistant,
callback,
)
from homeassistant.const import (
STATE_ON,
ATTR_NAME,
STATE_OFF,
ATTR_STATE,
STATE_OPEN,
STATE_CLOSED,
STATE_UNKNOWN,
STATE_UNAVAILABLE,
ATTR_LAST_TRIP_TIME,
EVENT_HOMEASSISTANT_STARTED,
)
from homeassistant.helpers.event import (
async_track_point_in_time,
async_track_state_change_event,
)
from homeassistant.components.lock import LockState
from homeassistant.helpers.dispatcher import (
async_dispatcher_connect,
)
from homeassistant.components.alarm_control_panel import AlarmControlPanelState
from . import const
ATTR_USE_EXIT_DELAY = "use_exit_delay"
ATTR_USE_ENTRY_DELAY = "use_entry_delay"
ATTR_ALWAYS_ON = "always_on"
ATTR_ARM_ON_CLOSE = "arm_on_close"
ATTR_ALLOW_OPEN = "allow_open"
ATTR_TRIGGER_UNAVAILABLE = "trigger_unavailable"
ATTR_AUTO_BYPASS = "auto_bypass"
ATTR_AUTO_BYPASS_MODES = "auto_bypass_modes"
ATTR_GROUP = "group"
ATTR_GROUP_ID = "group_id"
ATTR_TIMEOUT = "timeout"
ATTR_EVENT_COUNT = "event_count"
ATTR_ENTITIES = "entities"
ATTR_NEW_ENTITY_ID = "new_entity_id"
ATTR_ENTRY_DELAY = "entry_delay"
SENSOR_STATES_OPEN = [STATE_ON, STATE_OPEN, LockState.UNLOCKED]
SENSOR_STATES_CLOSED = [STATE_OFF, STATE_CLOSED, LockState.LOCKED]
SENSOR_TYPE_DOOR = "door"
SENSOR_TYPE_WINDOW = "window"
SENSOR_TYPE_MOTION = "motion"
SENSOR_TYPE_TAMPER = "tamper"
SENSOR_TYPE_ENVIRONMENTAL = "environmental"
SENSOR_TYPE_OTHER = "other"
SENSOR_TYPES = [
SENSOR_TYPE_DOOR,
SENSOR_TYPE_WINDOW,
SENSOR_TYPE_MOTION,
SENSOR_TYPE_TAMPER,
SENSOR_TYPE_ENVIRONMENTAL,
SENSOR_TYPE_OTHER,
]
_LOGGER = logging.getLogger(__name__)
def parse_sensor_state(state):
"""Parse the state of a sensor into open/closed/unavailable/unknown."""
if not state or not state.state:
return STATE_UNAVAILABLE
elif state.state == STATE_UNAVAILABLE:
return STATE_UNAVAILABLE
elif state.state in SENSOR_STATES_OPEN:
return STATE_OPEN
elif state.state in SENSOR_STATES_CLOSED:
return STATE_CLOSED
else:
return STATE_UNKNOWN
def sensor_state_allowed(state, sensor_config, alarm_state): # noqa: PLR0911
"""Return whether the sensor state is permitted or a state change should occur."""
if state != STATE_OPEN and (
state != STATE_UNAVAILABLE or not sensor_config[ATTR_TRIGGER_UNAVAILABLE]
):
# sensor has the safe state
return True
elif alarm_state == AlarmControlPanelState.TRIGGERED:
# alarm is already triggered
return True
elif sensor_config[ATTR_ALWAYS_ON]:
# alarm should always be triggered by always-on sensor
return False
elif (
alarm_state == AlarmControlPanelState.ARMING
and not sensor_config[ATTR_USE_EXIT_DELAY]
):
# arming should be aborted if sensor without exit delay is active
return False
elif alarm_state in const.ARM_MODES:
# normal triggering case
return False
elif alarm_state == AlarmControlPanelState.PENDING:
# Allow both immediate and delayed sensors
# during pending for timer shortening/immediate trigger
# This enables per-sensor entry delay logic
# to process subsequent triggers during countdown
return False
else:
return True
class SensorHandler:
"""Class to handle sensors for Alarmo."""
def __init__(self, hass: HomeAssistant):
"""Initialize the sensor handler."""
self._config = None
self.hass = hass
self._state_listener = None
self._subscriptions = []
self._arm_timers = {}
self._groups = {}
self._group_events = {}
self._startup_complete = False
self._unavailable_state_mem = {}
@callback
def async_update_sensor_config():
"""Sensor config updated, reload the configuration."""
self._config = self.hass.data[const.DOMAIN][
"coordinator"
].store.async_get_sensors()
self._groups = self.hass.data[const.DOMAIN][
"coordinator"
].store.async_get_sensor_groups()
self._group_events = {}
self.async_watch_sensor_states()
# Store the callback for later registration
self._async_update_sensor_config = async_update_sensor_config
@callback
def _setup_sensor_listeners():
"""Register sensor listeners and perform initial setup."""
self._subscriptions.append(
async_dispatcher_connect(
hass, "alarmo_state_updated", self.async_watch_sensor_states
)
)
self._subscriptions.append(
async_dispatcher_connect(
hass, "alarmo_sensors_updated", self._async_update_sensor_config
)
)
# Do the initial sensor setup now that HA is running
self._async_update_sensor_config()
# Evaluate initial sensor states for all areas on startup
for area_id in self.hass.data[const.DOMAIN]["areas"].keys():
self.update_ready_to_arm_status(area_id)
# If area is armed, validate sensors and trigger if needed
# Schedule this to run in the event loop since it may call async methods
hass.async_create_task(
self._async_evaluate_armed_state_on_startup(area_id)
)
def handle_startup(_event):
self._startup_complete = True
# Schedule the setup to run in the event loop (from thread pool executor)
hass.loop.call_soon_threadsafe(_setup_sensor_listeners)
if hass.state == CoreState.running:
self._startup_complete = True
# Schedule in event loop since we're in __init__ (sync context)
hass.loop.call_soon_threadsafe(_setup_sensor_listeners)
else:
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STARTED, handle_startup)
def __del__(self):
"""Prepare for removal."""
if self._state_listener:
self._state_listener()
self._state_listener = None
while len(self._subscriptions):
self._subscriptions.pop()()
def async_watch_sensor_states(
self,
area_id: str | None = None,
old_state: str | None = None,
state: str | None = None,
):
"""Watch sensors based on the state of the alarm entities."""
sensors_list = []
for area in self.hass.data[const.DOMAIN]["areas"].keys():
sensors_list.extend(self.active_sensors_for_alarm_state(area))
if self._state_listener:
self._state_listener()
if sensors_list:
self._state_listener = async_track_state_change_event(
self.hass, sensors_list, self.async_sensor_state_changed
)
else:
self._state_listener = None
# clear previous sensor group events that are not active for current alarm state
for group_id in self._group_events.keys():
self._group_events[group_id] = dict(
filter(
lambda el: el[0] in sensors_list,
self._group_events[group_id].items(),
)
)
# handle initial sensor states
if area_id and old_state is None:
sensors_list = self.active_sensors_for_alarm_state(area_id)
for entity in sensors_list:
state = self.hass.states.get(entity)
sensor_state = parse_sensor_state(state)
if state and state.state and sensor_state != STATE_UNKNOWN:
_LOGGER.debug(
"Initial state for %s is %s",
entity,
parse_sensor_state(state),
)
if area_id:
self.update_ready_to_arm_status(area_id)
def active_sensors_for_alarm_state(self, area_id: str, to_state: str | None = None):
"""Compose a list of sensors that are active for the state."""
alarm_entity = self.hass.data[const.DOMAIN]["areas"][area_id]
if to_state:
state = to_state
else:
state = (
alarm_entity.arm_mode if alarm_entity.arm_mode else alarm_entity.state
)
entities = []
for entity, config in self._config.items():
if config["area"] != area_id or not config["enabled"]:
continue
elif (
alarm_entity.bypassed_sensors
and entity in alarm_entity.bypassed_sensors
):
continue
elif state in config[const.ATTR_MODES] or config[ATTR_ALWAYS_ON]:
entities.append(entity)
elif not to_state and config["type"] != SENSOR_TYPE_MOTION:
# always watch all sensors other than motion sensors,
# to indicate readiness for arming
entities.append(entity)
return entities
def validate_arming_event(
self, area_id: str, target_state: str | None = None, **kwargs
):
"""Check whether all sensors have the correct state prior to arming."""
use_delay = kwargs.get("use_delay", False)
bypass_open_sensors = kwargs.get("bypass_open_sensors", False)
sensors_list = self.active_sensors_for_alarm_state(area_id, target_state)
open_sensors = {}
bypassed_sensors = []
alarm_state = target_state
if use_delay and alarm_state in const.ARM_MODES:
alarm_state = AlarmControlPanelState.ARMING
elif use_delay and alarm_state == AlarmControlPanelState.TRIGGERED:
alarm_state = AlarmControlPanelState.PENDING
for entity in sensors_list:
sensor_config = self._config[entity]
state = self.hass.states.get(entity)
sensor_state = parse_sensor_state(state)
if not state or not state.state:
# entity does not exist in HA
res = False
else:
res = sensor_state_allowed(sensor_state, sensor_config, alarm_state)
if not res and target_state in const.ARM_MODES:
# sensor is active while arming
if bypass_open_sensors or (
sensor_config[ATTR_AUTO_BYPASS]
and target_state in sensor_config[ATTR_AUTO_BYPASS_MODES]
):
# sensor may be bypassed
bypassed_sensors.append(entity)
elif sensor_config[ATTR_ALLOW_OPEN] and sensor_state == STATE_OPEN:
# sensor is permitted to be open during/after arming
continue
else:
open_sensors[entity] = sensor_state
return (open_sensors, bypassed_sensors)
def get_entry_delay_for_trigger(
self, open_sensors: dict[str, str], area_id: str, arm_mode: str
) -> int | None:
"""Calculate entry delay based on type of sensor trigger."""
# Check if this is a group trigger
if ATTR_GROUP_ID in open_sensors:
# For groups: only check for immediate triggers, otherwise use area default
for entity_id in open_sensors:
if entity_id != ATTR_GROUP_ID and entity_id in self._config:
sensor_config = self._config[entity_id]
if not sensor_config[ATTR_USE_ENTRY_DELAY]:
return 0
# Groups always use area default (maintainer's preference)
return None
else:
# Individual sensor trigger
entity_id = next(iter(open_sensors.keys()))
sensor_config = self._config[entity_id]
if not sensor_config[ATTR_USE_ENTRY_DELAY]:
return 0
# Use sensor's entry delay if set
if (
ATTR_ENTRY_DELAY in sensor_config
and sensor_config[ATTR_ENTRY_DELAY] is not None
):
return sensor_config[ATTR_ENTRY_DELAY]
# Fall back to area default (None means use area default)
return None
@callback
def async_sensor_state_changed(self, event): # noqa: PLR0915, PLR0912
"""Callback fired when a sensor state has changed."""
entity = event.data["entity_id"]
old_state = parse_sensor_state(event.data["old_state"])
new_state = parse_sensor_state(event.data["new_state"])
sensor_config = self._config[entity]
if old_state == STATE_UNKNOWN:
# sensor is unknown at startup,
# state which comes after is considered as initial state
_LOGGER.debug(
"Initial state for %s is %s",
entity,
new_state,
)
self.update_ready_to_arm_status(sensor_config["area"])
return
if old_state == new_state:
# not a state change - ignore
return
_LOGGER.debug(
"entity %s changed: old_state=%s, new_state=%s",
entity,
old_state,
new_state,
)
if (
new_state == STATE_UNAVAILABLE
and not sensor_config[ATTR_TRIGGER_UNAVAILABLE]
):
# temporarily store the prior state until the sensor becomes available again
self._unavailable_state_mem[entity] = old_state
elif entity in self._unavailable_state_mem:
# if sensor was unavailable, check the state before that,
# do not act if the sensor reverted to its prior state.
prior_state = self._unavailable_state_mem.pop(entity)
if old_state == STATE_UNAVAILABLE and prior_state == new_state:
_LOGGER.debug(
"state transition from %s to %s to %s detected, ignoring.",
prior_state,
old_state,
new_state,
)
return
alarm_entity = self.hass.data[const.DOMAIN]["areas"][sensor_config["area"]]
alarm_state = alarm_entity.state
if (
alarm_entity.arm_mode
and alarm_entity.arm_mode not in sensor_config[const.ATTR_MODES]
and not sensor_config[ATTR_ALWAYS_ON]
):
# sensor is not active in this arm mode, ignore
self.update_ready_to_arm_status(sensor_config["area"])
return
res = sensor_state_allowed(new_state, sensor_config, alarm_state)
if (
sensor_config[ATTR_ARM_ON_CLOSE]
and alarm_state == AlarmControlPanelState.ARMING
):
# we are arming and sensor is configured to arm on closing
if new_state == STATE_CLOSED:
self.start_arm_timer(entity)
else:
self.stop_arm_timer(entity)
if res:
# sensor state is OK,
# but we still need to clean up group events for closed sensors
# A sensor that has closed should not contribute to future group triggers
# until it opens again
# Clear closed sensors from group events to
# prevent stale events from triggering groups later
if new_state == STATE_CLOSED:
for group_id in list(self._group_events.keys()):
if entity in self._group_events[group_id]:
del self._group_events[group_id][entity]
# Clean up empty group entries
if not self._group_events[group_id]:
del self._group_events[group_id]
self.update_ready_to_arm_status(sensor_config["area"])
return
open_sensors = self.process_group_event(entity, new_state)
if not open_sensors:
# triggered sensor is part of a group and should be ignored
self.update_ready_to_arm_status(sensor_config["area"])
return
if sensor_config[ATTR_ALWAYS_ON]:
# immediate trigger due to always on sensor
_LOGGER.info(
"Alarm is triggered due to an always-on sensor: %s",
entity,
)
alarm_entity.async_trigger(entry_delay=0, open_sensors=open_sensors)
elif alarm_state == AlarmControlPanelState.ARMING:
# sensor triggered while arming, abort arming
_LOGGER.debug(
"Arming was aborted due to a sensor being active: %s",
entity,
)
alarm_entity.async_arm_failure(open_sensors)
elif alarm_state in const.ARM_MODES:
# standard alarm trigger - calculate entry delay override
_LOGGER.info(
"Alarm is triggered due to sensor: %s",
entity,
)
entry_delay = self.get_entry_delay_for_trigger(
open_sensors, sensor_config["area"], alarm_entity.arm_mode
)
if entry_delay == 0:
# immediate trigger (no entry delay)
alarm_entity.async_trigger(entry_delay=0, open_sensors=open_sensors)
else:
# use calculated delay (could be None for area default)
alarm_entity.async_trigger(
entry_delay=entry_delay, open_sensors=open_sensors
)
elif alarm_state == AlarmControlPanelState.PENDING:
# trigger while in pending state
# calculate entry delay for possible timer shortening
_LOGGER.info(
"Alarm is triggered due to sensor: %s",
entity,
)
entry_delay = self.get_entry_delay_for_trigger(
open_sensors, sensor_config["area"], alarm_entity.arm_mode
)
if entry_delay == 0:
# immediate trigger
alarm_entity.async_trigger(entry_delay=0, open_sensors=open_sensors)
else:
# use calculated delay for possible timer shortening
alarm_entity.async_trigger(
entry_delay=entry_delay, open_sensors=open_sensors
)
self.update_ready_to_arm_status(sensor_config["area"])
def start_arm_timer(self, entity):
"""Start timer for automatical arming."""
@callback
def timer_finished(now):
_LOGGER.debug("timer finished")
sensor_config = self._config[entity]
alarm_entity = self.hass.data[const.DOMAIN]["areas"][sensor_config["area"]]
if alarm_entity.state == AlarmControlPanelState.ARMING:
alarm_entity.async_arm(alarm_entity.arm_mode, skip_delay=True)
now = dt_util.utcnow()
if entity in self._arm_timers:
self.stop_arm_timer(entity)
self._arm_timers[entity] = async_track_point_in_time(
self.hass, timer_finished, now + const.SENSOR_ARM_TIME
)
def stop_arm_timer(self, entity=None):
"""Cancel timer(s) for automatical arming."""
if entity and entity in self._arm_timers:
self._arm_timers[entity]()
elif not entity:
for key in self._arm_timers.keys():
self._arm_timers[key]()
def process_group_event(self, entity: str, state: str) -> dict:
"""Check if sensor entity is member of a group to evaluate trigger."""
group_id = None
for group in self._groups.values():
if entity in group[ATTR_ENTITIES]:
group_id = group[ATTR_GROUP_ID]
break
open_sensors = {entity: state}
if group_id is None:
return open_sensors
group = self._groups[group_id]
group_events = (
self._group_events[group_id]
if group_id in self._group_events.keys()
else {}
)
now = dt_util.now()
group_events[entity] = {ATTR_STATE: state, ATTR_LAST_TRIP_TIME: now}
self._group_events[group_id] = group_events
recent_events = {
entity: (now - event[ATTR_LAST_TRIP_TIME]).total_seconds()
for (entity, event) in group_events.items()
}
recent_events = dict(
filter(lambda el: el[1] <= group[ATTR_TIMEOUT], recent_events.items())
)
if len(recent_events.keys()) < group[ATTR_EVENT_COUNT]:
_LOGGER.debug(
"tripped sensor %s was ignored since it belongs to group %s",
entity,
group[ATTR_NAME],
)
return {}
else:
for key in recent_events.keys():
open_sensors[key] = group_events[key][ATTR_STATE]
# Add group info for override delay calculation
open_sensors[ATTR_GROUP_ID] = group_id
_LOGGER.debug(
"tripped sensor %s caused the triggering of group %s",
entity,
group[ATTR_NAME],
)
return open_sensors
def update_ready_to_arm_status(self, area_id):
"""Calculate whether the system is ready for arming."""
alarm_entity = self.hass.data[const.DOMAIN]["areas"][area_id]
arm_modes = [
mode
for (mode, config) in alarm_entity._config[const.ATTR_MODES].items()
if config[const.ATTR_ENABLED]
]
if alarm_entity.state in const.ARM_MODES or (
alarm_entity.state == AlarmControlPanelState.ARMING
and alarm_entity.arm_mode
):
arm_modes.remove(alarm_entity.arm_mode)
def arm_mode_is_ready(mode):
(blocking_sensors, _bypassed_sensors) = self.validate_arming_event(
area_id, mode
)
if alarm_entity.state == AlarmControlPanelState.DISARMED:
# exclude motion sensors when determining readiness
blocking_sensors = dict(
filter(
lambda el: self._config[el[0]]["type"] != SENSOR_TYPE_MOTION,
blocking_sensors.items(),
)
)
result = not (blocking_sensors)
return result
arm_modes = list(filter(arm_mode_is_ready, arm_modes))
prev_arm_modes = alarm_entity._ready_to_arm_modes
if arm_modes != prev_arm_modes:
alarm_entity.update_ready_to_arm_modes(arm_modes)
async def _async_evaluate_armed_state_on_startup(self, area_id):
"""Evaluate sensors when alarm is armed on startup and trigger if necessary.
On startup, we don't know the actual previous state of sensors
(they might have changed while HA was down).
This method simulates state changes for all sensors currently in violation,
allowing the standard async_sensor_state_changed logic to re-evaluate them
with full group logic, entry delays, etc.
"""
alarm_entity = self.hass.data[const.DOMAIN]["areas"][area_id]
# Only evaluate if the alarm is in an armed state
if alarm_entity.state not in const.ARM_MODES:
return
_LOGGER.debug(
"Evaluating sensors on startup for area %s (state: %s)",
area_id,
alarm_entity.state,
)
# Get all active sensors for the current armed mode
sensors_list = self.active_sensors_for_alarm_state(area_id)
for entity_id in sensors_list:
sensor_config = self._config[entity_id]
state = self.hass.states.get(entity_id)
sensor_state = parse_sensor_state(state)
if sensor_state == STATE_UNKNOWN:
# Skip unknown sensors - they'll be handled when they become known
continue
# Check if sensor state is allowed in current alarm state
res = sensor_state_allowed(sensor_state, sensor_config, alarm_entity.state)
if not res:
# Sensor is in a violation state
# (open or unavailable when it shouldn't be)
# Simulate a state change to trigger standard processing
_LOGGER.info(
"Sensor %s is %s on startup while alarm is %s - simulating state change for evaluation", # noqa: E501
entity_id,
sensor_state,
alarm_entity.state,
)
# Create a synthetic event that mimics
# a state change from closed to current state
# We use STATE_CLOSED as old state
# (not STATE_UNKNOWN which would trigger early return)
old_state = SimpleNamespace(state=STATE_CLOSED)
# Create event with the structure expected by async_sensor_state_changed
event = SimpleNamespace(
data={
"entity_id": entity_id,
"old_state": old_state,
"new_state": state,
}
)
# Process through the standard sensor state change handler
# This will handle groups, entry delays, always-on sensors, etc.
self.async_sensor_state_changed(event)

View File

@@ -0,0 +1,77 @@
arm:
fields:
entity_id:
example: "alarm_control_panel.alarm"
required: true
selector:
entity:
integration: alarmo
domain: alarm_control_panel
code:
example: "1234"
required: false
selector:
text:
mode:
example: "away"
required: false
default: away
selector:
select:
translation_key: "arm_mode"
options:
- away
- night
- home
- vacation
- custom
skip_delay:
example: false
required: false
default: false
selector:
boolean:
force:
example: false
required: false
default: false
selector:
boolean:
disarm:
fields:
entity_id:
example: "alarm_control_panel.alarm"
required: true
selector:
entity:
integration: alarmo
domain: alarm_control_panel
code:
example: "1234"
required: false
selector:
text:
skip_delay:
fields:
entity_id:
example: "alarm_control_panel.alarm"
required: true
selector:
entity:
integration: alarmo
domain: alarm_control_panel
enable_user:
fields:
name:
example: "Frank"
required: true
selector:
text:
disable_user:
fields:
name:
example: "Frank"
required: true
selector:
text:

View File

@@ -0,0 +1,721 @@
"""Storage handler for Alarmo integration."""
import time
import logging
from typing import cast
from collections import OrderedDict
from collections.abc import MutableMapping
import attr
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.storage import Store
from homeassistant.components.alarm_control_panel import CodeFormat
from . import const
from .helpers import omit
from .sensors import (
SENSOR_TYPE_OTHER,
)
_LOGGER = logging.getLogger(__name__)
DATA_REGISTRY = f"{const.DOMAIN}_storage"
STORAGE_KEY = f"{const.DOMAIN}.storage"
STORAGE_VERSION_MAJOR = 6
STORAGE_VERSION_MINOR = 3
SAVE_DELAY = 10
@attr.s(slots=True, frozen=True)
class ModeEntry:
"""Mode storage Entry."""
enabled = attr.ib(type=bool, default=False)
exit_time = attr.ib(type=int, default=None)
entry_time = attr.ib(type=int, default=None)
trigger_time = attr.ib(type=int, default=None)
@attr.s(slots=True, frozen=True)
class MqttConfig:
"""MQTT storage Entry."""
enabled = attr.ib(type=bool, default=False)
state_topic = attr.ib(type=str, default="alarmo/state")
state_payload = attr.ib(type=dict, default={})
command_topic = attr.ib(type=str, default="alarmo/command")
command_payload = attr.ib(type=dict, default={})
require_code = attr.ib(type=bool, default=True)
event_topic = attr.ib(type=str, default="alarmo/event")
@attr.s(slots=True, frozen=True)
class MasterConfig:
"""Master storage Entry."""
enabled = attr.ib(type=bool, default=True)
name = attr.ib(type=str, default="master")
@attr.s(slots=True, frozen=True)
class AreaEntry:
"""Area storage Entry."""
area_id = attr.ib(type=str, default=None)
name = attr.ib(type=str, default=None)
modes = attr.ib(
type=[str, ModeEntry],
default={
const.CONF_ALARM_ARMED_AWAY: ModeEntry(),
const.CONF_ALARM_ARMED_HOME: ModeEntry(),
const.CONF_ALARM_ARMED_NIGHT: ModeEntry(),
const.CONF_ALARM_ARMED_CUSTOM_BYPASS: ModeEntry(),
const.CONF_ALARM_ARMED_VACATION: ModeEntry(),
},
)
@attr.s(slots=True, frozen=True)
class Config:
"""(General) Config storage Entry."""
code_arm_required = attr.ib(type=bool, default=False)
code_mode_change_required = attr.ib(type=bool, default=False)
code_disarm_required = attr.ib(type=bool, default=False)
code_format = attr.ib(type=str, default=CodeFormat.NUMBER)
disarm_after_trigger = attr.ib(type=bool, default=False)
ignore_blocking_sensors_after_trigger = attr.ib(type=bool, default=False)
master = attr.ib(type=MasterConfig, default=MasterConfig())
mqtt = attr.ib(type=MqttConfig, default=MqttConfig())
@attr.s(slots=True, frozen=True)
class SensorEntry:
"""Sensor storage Entry."""
entity_id = attr.ib(type=str, default=None)
type = attr.ib(type=str, default=SENSOR_TYPE_OTHER)
modes = attr.ib(type=list, default=[])
use_exit_delay = attr.ib(type=bool, default=True)
use_entry_delay = attr.ib(type=bool, default=True)
always_on = attr.ib(type=bool, default=False)
arm_on_close = attr.ib(type=bool, default=False)
allow_open = attr.ib(type=bool, default=False)
trigger_unavailable = attr.ib(type=bool, default=False)
auto_bypass = attr.ib(type=bool, default=False)
auto_bypass_modes = attr.ib(type=list, default=[])
area = attr.ib(type=str, default=None)
enabled = attr.ib(type=bool, default=True)
entry_delay = attr.ib(type=int, default=None)
@attr.s(slots=True, frozen=True)
class UserEntry:
"""User storage Entry."""
user_id = attr.ib(type=str, default=None)
name = attr.ib(type=str, default="")
enabled = attr.ib(type=bool, default=True)
code = attr.ib(type=str, default="")
can_arm = attr.ib(type=bool, default=False)
can_disarm = attr.ib(type=bool, default=False)
is_override_code = attr.ib(type=bool, default=False)
code_format = attr.ib(type=str, default="")
code_length = attr.ib(type=int, default=0)
area_limit = attr.ib(type=list, default=[])
@attr.s(slots=True, frozen=True)
class AlarmoTriggerEntry:
"""Trigger storage Entry."""
event = attr.ib(type=str, default="")
area = attr.ib(type=str, default=None)
modes = attr.ib(type=list, default=[])
@attr.s(slots=True, frozen=True)
class EntityTriggerEntry:
"""Trigger storage Entry."""
entity_id = attr.ib(type=str, default=None)
state = attr.ib(type=str, default=None)
@attr.s(slots=True, frozen=True)
class ActionEntry:
"""Action storage Entry."""
service = attr.ib(type=str, default="")
entity_id = attr.ib(type=str, default=None)
data = attr.ib(type=dict, default={})
@attr.s(slots=True, frozen=True)
class AutomationEntry:
"""Automation storage Entry."""
automation_id = attr.ib(type=str, default=None)
type = attr.ib(type=str, default=None)
name = attr.ib(type=str, default="")
triggers = attr.ib(type=[AlarmoTriggerEntry], default=[])
actions = attr.ib(type=[ActionEntry], default=[])
enabled = attr.ib(type=bool, default=True)
@attr.s(slots=True, frozen=True)
class SensorGroupEntry:
"""Sensor group storage Entry."""
group_id = attr.ib(type=str, default=None)
name = attr.ib(type=str, default="")
entities = attr.ib(type=list, default=[])
timeout = attr.ib(type=int, default=0)
event_count = attr.ib(type=int, default=2)
def parse_automation_entry(data: dict):
"""Parse automation entry from dict to proper types."""
def create_trigger_entity(config: dict):
if "event" in config:
return AlarmoTriggerEntry(**config)
else:
return EntityTriggerEntry(**config)
output = {}
if "triggers" in data:
output["triggers"] = list(map(create_trigger_entity, data["triggers"]))
if "actions" in data:
output["actions"] = list(map(lambda el: ActionEntry(**el), data["actions"]))
if "automation_id" in data:
output["automation_id"] = data["automation_id"]
if "name" in data:
output["name"] = data["name"]
if "type" in data:
output["type"] = data["type"]
if "enabled" in data:
output["enabled"] = data["enabled"]
return output
class MigratableStore(Store):
"""Storage class that can migrate data between versions."""
async def _async_migrate_func(
self, old_major_version: int, old_minor_version: int, data: dict
):
def migrate_automation(data):
if old_major_version <= 2:
data["triggers"] = [
{
"event": el["state"] if "state" in el else el["event"],
"area": el.get("area"),
"modes": data["modes"],
}
for el in data["triggers"]
]
data["type"] = (
"notification" if data.get("is_notification") else "action"
)
if old_major_version <= 5:
data["actions"] = [
{
"service": el.get("service"),
"entity_id": el.get("entity_id"),
"data": el.get("service_data"),
}
for el in data["actions"]
]
return attr.asdict(AutomationEntry(**parse_automation_entry(data)))
if old_major_version == 1:
area_id = str(int(time.time()))
data["areas"] = [
attr.asdict(
AreaEntry(
**{
"name": "Alarmo",
"modes": {
mode: attr.asdict(
ModeEntry(
enabled=bool(config["enabled"]),
exit_time=int(config["leave_time"] or 0),
entry_time=int(config["entry_time"] or 0),
trigger_time=int(
data["config"]["trigger_time"] or 0
),
)
)
for (mode, config) in data["config"]["modes"].items()
},
},
area_id=area_id,
)
)
]
if "sensors" in data:
for sensor in data["sensors"]:
sensor["area"] = area_id
if old_major_version <= 3:
data["sensors"] = [
attr.asdict(
SensorEntry(
**{
**omit(sensor, ["immediate", "name"]),
"use_exit_delay": not sensor["immediate"]
and not sensor["always_on"],
"use_entry_delay": not sensor["immediate"]
and not sensor["always_on"],
"auto_bypass_modes": sensor["modes"]
if sensor.get("auto_bypass")
else [],
}
)
)
for sensor in data["sensors"]
]
if old_major_version <= 4:
data["sensors"] = [
attr.asdict(
SensorEntry(
**omit(sensor, ["name"]),
)
)
for sensor in data["sensors"]
]
data["automations"] = [
migrate_automation(automation) for automation in data["automations"]
]
if old_major_version <= 5 or (old_major_version == 6 and old_minor_version < 2):
data["config"] = attr.asdict(
Config(
**omit(data["config"], ["code_mode_change_required"]),
code_mode_change_required=data["config"]["code_arm_required"],
)
)
if old_major_version <= 5 or (old_major_version == 6 and old_minor_version < 3):
data["sensor_groups"] = [
attr.asdict(
SensorGroupEntry(
**{
**omit(sensorGroup, ["entities"]),
"entities": list(set(sensorGroup["entities"])),
}
)
)
for sensorGroup in data["sensor_groups"]
]
return data
class AlarmoStorage:
"""Class to hold alarmo configuration data."""
def __init__(self, hass: HomeAssistant) -> None:
"""Initialize the storage."""
self.hass = hass
self.config: Config = Config()
self.areas: MutableMapping[str, AreaEntry] = {}
self.sensors: MutableMapping[str, SensorEntry] = {}
self.users: MutableMapping[str, UserEntry] = {}
self.automations: MutableMapping[str, AutomationEntry] = {}
self.sensor_groups: MutableMapping[str, SensorGroupEntry] = {}
self._store = MigratableStore(
hass,
STORAGE_VERSION_MAJOR,
STORAGE_KEY,
minor_version=STORAGE_VERSION_MINOR,
)
async def async_load(self) -> None: # noqa: PLR0912
"""Load the registry of schedule entries."""
data = await self._store.async_load()
config: Config = Config()
areas: OrderedDict[str, AreaEntry] = OrderedDict()
sensors: OrderedDict[str, SensorEntry] = OrderedDict()
users: OrderedDict[str, UserEntry] = OrderedDict()
automations: OrderedDict[str, AutomationEntry] = OrderedDict()
sensor_groups: OrderedDict[str, SensorGroupEntry] = OrderedDict()
if data is not None:
config = Config(
code_arm_required=data["config"]["code_arm_required"],
code_mode_change_required=data["config"]["code_mode_change_required"],
code_disarm_required=data["config"]["code_disarm_required"],
code_format=data["config"]["code_format"],
disarm_after_trigger=data["config"]["disarm_after_trigger"],
ignore_blocking_sensors_after_trigger=data["config"].get(
"ignore_blocking_sensors_after_trigger", False
),
)
if "mqtt" in data["config"]:
config = attr.evolve(
config,
**{
"mqtt": MqttConfig(**data["config"]["mqtt"]),
},
)
if "master" in data["config"]:
config = attr.evolve(
config,
**{
"master": MasterConfig(**data["config"]["master"]),
},
)
if "areas" in data:
for area in data["areas"]:
modes = {
mode: ModeEntry(
enabled=config["enabled"],
exit_time=config["exit_time"],
entry_time=config["entry_time"],
trigger_time=config["trigger_time"],
)
for (mode, config) in area["modes"].items()
}
areas[area["area_id"]] = AreaEntry(
area_id=area["area_id"], name=area["name"], modes=modes
)
if "sensors" in data:
for sensor in data["sensors"]:
sensors[sensor["entity_id"]] = SensorEntry(**sensor)
if "users" in data:
for user in data["users"]:
users[user["user_id"]] = UserEntry(**omit(user, ["is_admin"]))
if "automations" in data:
for automation in data["automations"]:
automations[automation["automation_id"]] = AutomationEntry(
**parse_automation_entry(automation)
)
if "sensor_groups" in data:
for group in data["sensor_groups"]:
sensor_groups[group["group_id"]] = SensorGroupEntry(**group)
self.config = config
self.areas = areas
self.sensors = sensors
self.automations = automations
self.users = users
self.sensor_groups = sensor_groups
if not areas:
await self.async_factory_default()
async def async_factory_default(self):
"""Reset to factory default configuration."""
self.async_create_area(
{
"name": "Alarmo",
"modes": {
const.CONF_ALARM_ARMED_AWAY: attr.asdict(
ModeEntry(
enabled=True, exit_time=60, entry_time=60, trigger_time=1800
)
),
const.CONF_ALARM_ARMED_HOME: attr.asdict(
ModeEntry(enabled=True, trigger_time=1800)
),
},
}
)
@callback
def async_schedule_save(self) -> None:
"""Schedule saving the registry of alarmo."""
self._store.async_delay_save(self._data_to_save, SAVE_DELAY)
async def async_save(self) -> None:
"""Save the registry of alarmo."""
await self._store.async_save(self._data_to_save())
@callback
def _data_to_save(self) -> dict:
"""Return data for the registry for alarmo to store in a file."""
store_data = {
"config": attr.asdict(self.config),
}
store_data["areas"] = [attr.asdict(entry) for entry in self.areas.values()]
store_data["sensors"] = [attr.asdict(entry) for entry in self.sensors.values()]
store_data["users"] = [attr.asdict(entry) for entry in self.users.values()]
store_data["automations"] = [
attr.asdict(entry) for entry in self.automations.values()
]
store_data["sensor_groups"] = [
attr.asdict(entry) for entry in self.sensor_groups.values()
]
return store_data
async def async_delete(self):
"""Delete config."""
_LOGGER.warning("Removing alarmo configuration data!")
await self._store.async_remove()
self.config = Config()
self.areas = {}
self.sensors = {}
self.users = {}
self.automations = {}
self.sensor_groups = {}
await self.async_factory_default()
@callback
def async_get_config(self):
"""Get current config."""
return attr.asdict(self.config)
@callback
def async_update_config(self, changes: dict):
"""Update existing config."""
old = self.config
new = self.config = attr.evolve(old, **changes)
self.async_schedule_save()
return attr.asdict(new)
@callback
def async_update_mode_config(self, mode: str, changes: dict):
"""Update existing config."""
modes = self.config.modes
old = self.config.modes[mode] if mode in self.config.modes else ModeEntry()
new = attr.evolve(old, **changes)
modes[mode] = new
self.config = attr.evolve(self.config, **{"modes": modes})
self.async_schedule_save()
return new
@callback
def async_get_area(self, area_id) -> AreaEntry:
"""Get an existing AreaEntry by id."""
res = self.areas.get(area_id)
return attr.asdict(res) if res else None
@callback
def async_get_areas(self):
"""Get an existing AreaEntry by id."""
res = {}
for key, val in self.areas.items():
res[key] = attr.asdict(val)
return res
@callback
def async_create_area(self, data: dict) -> AreaEntry:
"""Create a new AreaEntry."""
area_id = str(int(time.time()))
new_area = AreaEntry(**data, area_id=area_id)
self.areas[area_id] = new_area
self.async_schedule_save()
return attr.asdict(new_area)
@callback
def async_delete_area(self, area_id: str) -> None:
"""Delete AreaEntry."""
if area_id in self.areas:
del self.areas[area_id]
self.async_schedule_save()
return True
return False
@callback
def async_update_area(self, area_id: str, changes: dict) -> AreaEntry:
"""Update existing self."""
old = self.areas[area_id]
new = self.areas[area_id] = attr.evolve(old, **changes)
self.async_schedule_save()
return attr.asdict(new)
@callback
def async_get_sensor(self, entity_id) -> SensorEntry:
"""Get an existing SensorEntry by id."""
res = self.sensors.get(entity_id)
return attr.asdict(res) if res else None
@callback
def async_get_sensors(self):
"""Get an existing SensorEntry by id."""
res = {}
for key, val in self.sensors.items():
res[key] = attr.asdict(val)
return res
@callback
def async_create_sensor(self, entity_id: str, data: dict) -> SensorEntry:
"""Create a new SensorEntry."""
if entity_id in self.sensors:
return False
new_sensor = SensorEntry(**data, entity_id=entity_id)
self.sensors[entity_id] = new_sensor
self.async_schedule_save()
return new_sensor
@callback
def async_delete_sensor(self, entity_id: str) -> None:
"""Delete SensorEntry."""
if entity_id in self.sensors:
del self.sensors[entity_id]
self.async_schedule_save()
return True
return False
@callback
def async_update_sensor(self, entity_id: str, changes: dict) -> SensorEntry:
"""Update existing SensorEntry."""
old = self.sensors[entity_id]
new = self.sensors[entity_id] = attr.evolve(old, **changes)
self.async_schedule_save()
return new
@callback
def async_get_user(self, user_id) -> UserEntry:
"""Get an existing UserEntry by id."""
res = self.users.get(user_id)
return attr.asdict(res) if res else None
@callback
def async_get_users(self):
"""Get an existing UserEntry by id."""
res = {}
for key, val in self.users.items():
res[key] = attr.asdict(val)
return res
@callback
def async_create_user(self, data: dict) -> UserEntry:
"""Create a new UserEntry."""
user_id = str(int(time.time()))
new_user = UserEntry(**data, user_id=user_id)
self.users[user_id] = new_user
self.async_schedule_save()
return new_user
@callback
def async_delete_user(self, user_id: str) -> None:
"""Delete UserEntry."""
if user_id in self.users:
del self.users[user_id]
self.async_schedule_save()
return True
return False
@callback
def async_update_user(self, user_id: str, changes: dict) -> UserEntry:
"""Update existing UserEntry."""
old = self.users[user_id]
new = self.users[user_id] = attr.evolve(old, **changes)
self.async_schedule_save()
return new
@callback
def async_get_automations(self):
"""Get an existing AutomationEntry by id."""
res = {}
for key, val in self.automations.items():
res[key] = attr.asdict(val)
return res
@callback
def async_create_automation(self, data: dict) -> AutomationEntry:
"""Create a new AutomationEntry."""
automation_id = str(int(time.time()))
new_automation = AutomationEntry(
**parse_automation_entry(data), automation_id=automation_id
)
self.automations[automation_id] = new_automation
self.async_schedule_save()
return new_automation
@callback
def async_delete_automation(self, automation_id: str) -> None:
"""Delete AutomationEntry."""
if automation_id in self.automations:
del self.automations[automation_id]
self.async_schedule_save()
return True
return False
@callback
def async_update_automation(
self, automation_id: str, changes: dict
) -> AutomationEntry:
"""Update existing AutomationEntry."""
old = self.automations[automation_id]
new = self.automations[automation_id] = attr.evolve(
old, **parse_automation_entry(changes)
)
self.async_schedule_save()
return new
@callback
def async_get_sensor_group(self, group_id) -> SensorGroupEntry:
"""Get an existing SensorGroupEntry by id."""
res = self.sensor_groups.get(group_id)
return attr.asdict(res) if res else None
@callback
def async_get_sensor_groups(self):
"""Get an existing SensorGroupEntry by id."""
res = {}
for key, val in self.sensor_groups.items():
res[key] = attr.asdict(val)
return res
@callback
def async_create_sensor_group(self, data: dict) -> SensorGroupEntry:
"""Create a new SensorGroupEntry."""
group_id = str(int(time.time()))
new_group = SensorGroupEntry(**data, group_id=group_id)
self.sensor_groups[group_id] = new_group
self.async_schedule_save()
return group_id
@callback
def async_delete_sensor_group(self, group_id: str) -> None:
"""Delete SensorGroupEntry."""
if group_id in self.sensor_groups:
del self.sensor_groups[group_id]
self.async_schedule_save()
return True
return False
@callback
def async_update_sensor_group(
self, group_id: str, changes: dict
) -> SensorGroupEntry:
"""Update existing SensorGroupEntry."""
old = self.sensor_groups[group_id]
new = self.sensor_groups[group_id] = attr.evolve(old, **changes)
self.async_schedule_save()
return new
async def async_get_registry(hass: HomeAssistant) -> AlarmoStorage:
"""Return alarmo storage instance."""
task = hass.data.get(DATA_REGISTRY)
if task is None:
async def _load_reg() -> AlarmoStorage:
registry = AlarmoStorage(hass)
await registry.async_load()
return registry
task = hass.data[DATA_REGISTRY] = hass.async_create_task(_load_reg())
return cast(AlarmoStorage, await task)

View File

@@ -0,0 +1,594 @@
"""WebSocket handler and registration for Alarmo configuration management."""
import voluptuous as vol
import homeassistant.util.dt as dt_util
from homeassistant.core import callback
from homeassistant.const import (
ATTR_CODE,
ATTR_NAME,
ATTR_STATE,
ATTR_SERVICE,
ATTR_ENTITY_ID,
ATTR_CODE_FORMAT,
CONF_SERVICE_DATA,
)
from homeassistant.helpers import config_validation as cv
from homeassistant.components import websocket_api
from homeassistant.components.http import HomeAssistantView
from homeassistant.components.mqtt import (
DOMAIN as ATTR_MQTT,
)
from homeassistant.components.mqtt import (
CONF_STATE_TOPIC,
CONF_COMMAND_TOPIC,
)
from homeassistant.helpers.dispatcher import (
async_dispatcher_send,
async_dispatcher_connect,
)
from homeassistant.components.websocket_api import decorators, async_register_command
from homeassistant.components.alarm_control_panel import (
ATTR_CODE_ARM_REQUIRED,
CodeFormat,
)
from homeassistant.components.http.data_validator import RequestDataValidator
from . import const
from .mqtt import (
CONF_EVENT_TOPIC,
)
from .sensors import (
ATTR_GROUP,
ATTR_TIMEOUT,
SENSOR_TYPES,
ATTR_ENTITIES,
ATTR_GROUP_ID,
ATTR_ALWAYS_ON,
ATTR_ALLOW_OPEN,
ATTR_AUTO_BYPASS,
ATTR_ENTRY_DELAY,
ATTR_EVENT_COUNT,
ATTR_ARM_ON_CLOSE,
ATTR_NEW_ENTITY_ID,
ATTR_USE_EXIT_DELAY,
ATTR_USE_ENTRY_DELAY,
ATTR_AUTO_BYPASS_MODES,
ATTR_TRIGGER_UNAVAILABLE,
)
@callback
@decorators.websocket_command(
{
vol.Required("type"): "alarmo_config_updated",
}
)
@decorators.async_response
async def handle_subscribe_updates(hass, connection, msg):
"""Handle subscribe updates."""
@callback
def async_handle_event():
"""Forward events to websocket."""
connection.send_message(
{
"id": msg["id"],
"type": "event",
}
)
connection.subscriptions[msg["id"]] = async_dispatcher_connect(
hass, "alarmo_update_frontend", async_handle_event
)
connection.send_result(msg["id"])
class AlarmoConfigView(HomeAssistantView):
"""Login to Home Assistant cloud."""
url = "/api/alarmo/config"
name = "api:alarmo:config"
@RequestDataValidator(
vol.Schema(
{
vol.Optional(ATTR_CODE_ARM_REQUIRED): cv.boolean,
vol.Optional(const.ATTR_CODE_DISARM_REQUIRED): cv.boolean,
vol.Optional(
const.ATTR_IGNORE_BLOCKING_SENSORS_AFTER_TRIGGER
): cv.boolean,
vol.Optional(const.ATTR_CODE_MODE_CHANGE_REQUIRED): cv.boolean,
vol.Optional(ATTR_CODE_FORMAT): vol.In(
[CodeFormat.NUMBER, CodeFormat.TEXT]
),
vol.Optional(const.ATTR_TRIGGER_TIME): cv.positive_int,
vol.Optional(const.ATTR_DISARM_AFTER_TRIGGER): cv.boolean,
vol.Optional(ATTR_MQTT): vol.Schema(
{
vol.Required(const.ATTR_ENABLED): cv.boolean,
vol.Required(CONF_STATE_TOPIC): cv.string,
vol.Optional(const.ATTR_STATE_PAYLOAD): vol.Schema(
{
vol.Optional(const.CONF_ALARM_DISARMED): cv.string,
vol.Optional(const.CONF_ALARM_ARMED_HOME): cv.string,
vol.Optional(const.CONF_ALARM_ARMED_AWAY): cv.string,
vol.Optional(const.CONF_ALARM_ARMED_NIGHT): cv.string,
vol.Optional(
const.CONF_ALARM_ARMED_CUSTOM_BYPASS
): cv.string,
vol.Optional(
const.CONF_ALARM_ARMED_VACATION
): cv.string,
vol.Optional(const.CONF_ALARM_PENDING): cv.string,
vol.Optional(const.CONF_ALARM_ARMING): cv.string,
vol.Optional(const.CONF_ALARM_TRIGGERED): cv.string,
}
),
vol.Required(CONF_COMMAND_TOPIC): cv.string,
vol.Optional(const.ATTR_COMMAND_PAYLOAD): vol.Schema(
{
vol.Optional(const.COMMAND_ARM_AWAY): cv.string,
vol.Optional(const.COMMAND_ARM_HOME): cv.string,
vol.Optional(const.COMMAND_ARM_NIGHT): cv.string,
vol.Optional(
const.COMMAND_ARM_CUSTOM_BYPASS
): cv.string,
vol.Optional(const.COMMAND_ARM_VACATION): cv.string,
vol.Optional(const.COMMAND_DISARM): cv.string,
}
),
vol.Required(const.ATTR_REQUIRE_CODE): cv.boolean,
vol.Required(CONF_EVENT_TOPIC): cv.string,
}
),
vol.Optional(const.ATTR_MASTER): vol.Schema(
{
vol.Required(const.ATTR_ENABLED): cv.boolean,
vol.Optional(ATTR_NAME): cv.string,
}
),
}
)
)
async def post(self, request, data):
"""Handle config update request."""
hass = request.app["hass"]
coordinator = hass.data[const.DOMAIN]["coordinator"]
await coordinator.async_update_config(data)
async_dispatcher_send(hass, "alarmo_update_frontend")
return self.json({"success": True})
class AlarmoAreaView(HomeAssistantView):
"""Login to Home Assistant cloud."""
url = "/api/alarmo/area"
name = "api:alarmo:area"
mode_schema = vol.Schema(
{
vol.Required(const.ATTR_ENABLED): cv.boolean,
vol.Required(const.ATTR_EXIT_TIME): vol.Any(cv.positive_int, None),
vol.Required(const.ATTR_ENTRY_TIME): vol.Any(cv.positive_int, None),
vol.Optional(const.ATTR_TRIGGER_TIME): vol.Any(cv.positive_int, None),
}
)
@RequestDataValidator(
vol.Schema(
{
vol.Optional("area_id"): cv.string,
vol.Optional(ATTR_NAME): cv.string,
vol.Optional(const.ATTR_REMOVE): cv.boolean,
vol.Optional(const.ATTR_MODES): vol.Schema(
{
vol.Optional(const.CONF_ALARM_ARMED_AWAY): mode_schema,
vol.Optional(const.CONF_ALARM_ARMED_HOME): mode_schema,
vol.Optional(const.CONF_ALARM_ARMED_NIGHT): mode_schema,
vol.Optional(const.CONF_ALARM_ARMED_CUSTOM_BYPASS): mode_schema,
vol.Optional(const.CONF_ALARM_ARMED_VACATION): mode_schema,
}
),
}
)
)
async def post(self, request, data):
"""Handle config update request."""
hass = request.app["hass"]
coordinator = hass.data[const.DOMAIN]["coordinator"]
if "area_id" in data:
area = data["area_id"]
del data["area_id"]
else:
area = None
await coordinator.async_update_area_config(area, data)
async_dispatcher_send(hass, "alarmo_update_frontend")
return self.json({"success": True})
class AlarmoSensorView(HomeAssistantView):
"""Login to Home Assistant cloud."""
url = "/api/alarmo/sensors"
name = "api:alarmo:sensors"
@RequestDataValidator(
vol.Schema(
{
vol.Required(ATTR_ENTITY_ID): cv.entity_id,
vol.Optional(const.ATTR_REMOVE): cv.boolean,
vol.Optional(const.ATTR_TYPE): vol.In(SENSOR_TYPES),
vol.Optional(const.ATTR_MODES): vol.All(
cv.ensure_list, [vol.In(const.ARM_MODES)]
),
vol.Optional(ATTR_USE_EXIT_DELAY): cv.boolean,
vol.Optional(ATTR_USE_ENTRY_DELAY): cv.boolean,
vol.Optional(ATTR_ARM_ON_CLOSE): cv.boolean,
vol.Optional(ATTR_ALLOW_OPEN): cv.boolean,
vol.Optional(ATTR_ALWAYS_ON): cv.boolean,
vol.Optional(ATTR_TRIGGER_UNAVAILABLE): cv.boolean,
vol.Optional(ATTR_AUTO_BYPASS): cv.boolean,
vol.Optional(ATTR_AUTO_BYPASS_MODES): vol.All(
cv.ensure_list, [vol.In(const.ARM_MODES)]
),
vol.Optional(const.ATTR_AREA): cv.string,
vol.Optional(const.ATTR_ENABLED): cv.boolean,
vol.Optional(ATTR_GROUP): vol.Any(cv.string, None),
vol.Optional(ATTR_ENTRY_DELAY): vol.Any(cv.positive_int, None),
vol.Optional(ATTR_NEW_ENTITY_ID): cv.string,
}
)
)
async def post(self, request, data):
"""Handle config update request."""
hass = request.app["hass"]
coordinator = hass.data[const.DOMAIN]["coordinator"]
entity = data[ATTR_ENTITY_ID]
del data[ATTR_ENTITY_ID]
coordinator.async_update_sensor_config(entity, data)
async_dispatcher_send(hass, "alarmo_update_frontend")
return self.json({"success": True})
class AlarmoUserView(HomeAssistantView):
"""Login to Home Assistant cloud."""
url = "/api/alarmo/users"
name = "api:alarmo:users"
@RequestDataValidator(
vol.Schema(
{
vol.Optional(const.ATTR_USER_ID): cv.string,
vol.Optional(const.ATTR_REMOVE): cv.boolean,
vol.Optional(ATTR_NAME): cv.string,
vol.Optional(const.ATTR_ENABLED): cv.boolean,
vol.Optional(ATTR_CODE): cv.string,
vol.Optional(const.ATTR_OLD_CODE): cv.string,
vol.Optional(const.ATTR_CAN_ARM): cv.boolean,
vol.Optional(const.ATTR_CAN_DISARM): cv.boolean,
vol.Optional(const.ATTR_IS_OVERRIDE_CODE): cv.boolean,
vol.Optional(const.ATTR_AREA_LIMIT): vol.All(
cv.ensure_list, [cv.string]
),
}
)
)
async def post(self, request, data):
"""Handle config update request."""
hass = request.app["hass"]
coordinator = hass.data[const.DOMAIN]["coordinator"]
user_id = None
if const.ATTR_USER_ID in data:
user_id = data[const.ATTR_USER_ID]
del data[const.ATTR_USER_ID]
err = coordinator.async_update_user_config(user_id, data)
async_dispatcher_send(hass, "alarmo_update_frontend")
return self.json({"success": not isinstance(err, str), "error": err})
class AlarmoAutomationView(HomeAssistantView):
"""Login to Home Assistant cloud."""
url = "/api/alarmo/automations"
name = "api:alarmo:automations"
@RequestDataValidator(
vol.Schema(
{
vol.Optional(const.ATTR_AUTOMATION_ID): cv.string,
vol.Optional(ATTR_NAME): cv.string,
vol.Optional(const.ATTR_TYPE): cv.string,
vol.Optional(const.ATTR_TRIGGERS): vol.All(
cv.ensure_list,
[
vol.Any(
vol.Schema(
{
vol.Required(const.ATTR_EVENT): cv.string,
vol.Optional(const.ATTR_AREA): vol.Any(
int,
cv.string,
),
vol.Optional(const.ATTR_MODES): vol.All(
cv.ensure_list, [vol.In(const.ARM_MODES)]
),
}
),
vol.Schema(
{
vol.Required(ATTR_ENTITY_ID): cv.string,
vol.Required(ATTR_STATE): cv.string,
}
),
)
],
),
vol.Optional(const.ATTR_ACTIONS): vol.All(
cv.ensure_list,
[
vol.Schema(
{
vol.Optional(ATTR_ENTITY_ID): cv.string,
vol.Required(ATTR_SERVICE): cv.string,
vol.Optional(CONF_SERVICE_DATA): dict,
}
)
],
),
vol.Optional(const.ATTR_ENABLED): cv.boolean,
vol.Optional(const.ATTR_REMOVE): cv.boolean,
}
)
)
async def post(self, request, data):
"""Handle config update request."""
hass = request.app["hass"]
coordinator = hass.data[const.DOMAIN]["coordinator"]
automation_id = None
if const.ATTR_AUTOMATION_ID in data:
automation_id = data[const.ATTR_AUTOMATION_ID]
del data[const.ATTR_AUTOMATION_ID]
coordinator.async_update_automation_config(automation_id, data)
async_dispatcher_send(hass, "alarmo_update_frontend")
return self.json({"success": True})
class AlarmoSensorGroupView(HomeAssistantView):
"""Login to Home Assistant cloud."""
url = "/api/alarmo/sensor_groups"
name = "api:alarmo:sensor_groups"
@RequestDataValidator(
vol.Schema(
{
vol.Optional(ATTR_GROUP_ID): cv.string,
vol.Optional(ATTR_NAME): cv.string,
vol.Optional(ATTR_ENTITIES): vol.All(
cv.ensure_list, vol.Unique(), [cv.string]
),
vol.Optional(ATTR_TIMEOUT): cv.positive_int,
vol.Optional(ATTR_EVENT_COUNT): cv.positive_int,
vol.Optional(const.ATTR_REMOVE): cv.boolean,
}
)
)
async def post(self, request, data):
"""Handle config update request."""
hass = request.app["hass"]
coordinator = hass.data[const.DOMAIN]["coordinator"]
group_id = None
if ATTR_GROUP_ID in data:
group_id = data[ATTR_GROUP_ID]
del data[ATTR_GROUP_ID]
coordinator.async_update_sensor_group_config(group_id, data)
async_dispatcher_send(hass, "alarmo_update_frontend")
return self.json({"success": True})
@callback
def websocket_get_config(hass, connection, msg):
"""Publish config data."""
coordinator = hass.data[const.DOMAIN]["coordinator"]
config = coordinator.store.async_get_config()
connection.send_result(msg["id"], config)
@callback
def websocket_get_areas(hass, connection, msg):
"""Publish area data."""
coordinator = hass.data[const.DOMAIN]["coordinator"]
areas = coordinator.store.async_get_areas()
connection.send_result(msg["id"], areas)
@callback
def websocket_get_sensors(hass, connection, msg):
"""Publish sensor data."""
coordinator = hass.data[const.DOMAIN]["coordinator"]
sensors = coordinator.store.async_get_sensors()
for entity_id in sensors.keys():
group = coordinator.async_get_group_for_sensor(entity_id)
sensors[entity_id]["group"] = group
connection.send_result(msg["id"], sensors)
@callback
def websocket_get_users(hass, connection, msg):
"""Publish user data."""
coordinator = hass.data[const.DOMAIN]["coordinator"]
users = coordinator.store.async_get_users()
connection.send_result(msg["id"], users)
@callback
def websocket_get_automations(hass, connection, msg):
"""Publish automations data."""
coordinator = hass.data[const.DOMAIN]["coordinator"]
automations = coordinator.store.async_get_automations()
connection.send_result(msg["id"], automations)
@callback
def websocket_get_alarm_entities(hass, connection, msg):
"""Publish alarm entity data."""
result = [
{"entity_id": entity.entity_id, "area_id": area_id}
for (area_id, entity) in hass.data[const.DOMAIN]["areas"].items()
]
if hass.data[const.DOMAIN]["master"]:
result.append(
{"entity_id": hass.data[const.DOMAIN]["master"].entity_id, "area_id": 0}
)
connection.send_result(msg["id"], result)
@callback
def websocket_get_sensor_groups(hass, connection, msg):
"""Publish sensor_group data."""
coordinator = hass.data[const.DOMAIN]["coordinator"]
groups = coordinator.store.async_get_sensor_groups()
connection.send_result(msg["id"], groups)
@callback
def websocket_get_countdown(hass, connection, msg):
"""Publish countdown time for alarm entity."""
entity_id = msg["entity_id"]
item = next(
(
entity
for entity in hass.data[const.DOMAIN]["areas"].values()
if entity.entity_id == entity_id
),
None,
)
if (
hass.data[const.DOMAIN]["master"]
and not item
and hass.data[const.DOMAIN]["master"].entity_id == entity_id
):
item = hass.data[const.DOMAIN]["master"]
data = {
"delay": item.delay if item else 0,
"remaining": round((item.expiration - dt_util.utcnow()).total_seconds(), 2)
if item and item.expiration
else 0,
}
connection.send_result(msg["id"], data)
@callback
def websocket_get_ready_to_arm_modes(hass, connection, msg):
"""Publish ready_to_arm_modes for alarm entity."""
entity_id = msg["entity_id"]
item = next(
(
entity
for entity in hass.data[const.DOMAIN]["areas"].values()
if entity.entity_id == entity_id
),
None,
)
if (
hass.data[const.DOMAIN]["master"]
and not item
and hass.data[const.DOMAIN]["master"].entity_id == entity_id
):
item = hass.data[const.DOMAIN]["master"]
data = {"modes": item._ready_to_arm_modes if item else None}
connection.send_result(msg["id"], data)
async def async_register_websockets(hass):
"""Register websocket handlers."""
hass.http.register_view(AlarmoConfigView)
hass.http.register_view(AlarmoSensorView)
hass.http.register_view(AlarmoUserView)
hass.http.register_view(AlarmoAutomationView)
hass.http.register_view(AlarmoAreaView)
hass.http.register_view(AlarmoSensorGroupView)
async_register_command(hass, handle_subscribe_updates)
async_register_command(
hass,
"alarmo/config",
websocket_get_config,
websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend(
{vol.Required("type"): "alarmo/config"}
),
)
async_register_command(
hass,
"alarmo/areas",
websocket_get_areas,
websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend(
{vol.Required("type"): "alarmo/areas"}
),
)
async_register_command(
hass,
"alarmo/sensors",
websocket_get_sensors,
websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend(
{vol.Required("type"): "alarmo/sensors"}
),
)
async_register_command(
hass,
"alarmo/users",
websocket_get_users,
websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend(
{vol.Required("type"): "alarmo/users"}
),
)
async_register_command(
hass,
"alarmo/automations",
websocket_get_automations,
websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend(
{vol.Required("type"): "alarmo/automations"}
),
)
async_register_command(
hass,
"alarmo/entities",
websocket_get_alarm_entities,
websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend(
{vol.Required("type"): "alarmo/entities"}
),
)
async_register_command(
hass,
"alarmo/sensor_groups",
websocket_get_sensor_groups,
websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend(
{vol.Required("type"): "alarmo/sensor_groups"}
),
)
async_register_command(
hass,
"alarmo/countdown",
websocket_get_countdown,
websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend(
{
vol.Required("type"): "alarmo/countdown",
vol.Required("entity_id"): cv.entity_id,
}
),
)
async_register_command(
hass,
"alarmo/ready_to_arm_modes",
websocket_get_ready_to_arm_modes,
websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend(
{
vol.Required("type"): "alarmo/ready_to_arm_modes",
vol.Required("entity_id"): cv.entity_id,
}
),
)

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