updated lovelace dashboards and migrate tcp sensors to use serial component with SOCAT
This commit is contained in:
parent
4999b85b4e
commit
f4ceef3cfe
|
@ -1 +1 @@
|
|||
2024.9.3
|
||||
2025.4.4
|
|
@ -3,3 +3,7 @@
|
|||
.DS_Store
|
||||
secrets.yaml
|
||||
|
||||
|
||||
# pixi environments
|
||||
.pixi
|
||||
*.egg-info
|
||||
|
|
|
@ -16,9 +16,25 @@
|
|||
"tts_language": null,
|
||||
"tts_voice": null,
|
||||
"wake_word_entity": null,
|
||||
"wake_word_id": null
|
||||
"wake_word_id": null,
|
||||
"prefer_local_intents": false
|
||||
},
|
||||
{
|
||||
"conversation_engine": "01JCRKGQZQRNH78WSFVRBFXK98",
|
||||
"conversation_language": "*",
|
||||
"id": "01jcrc1n1z065tf45ds2a1z8rp",
|
||||
"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": true
|
||||
}
|
||||
],
|
||||
"preferred_item": "01h6w13v4kkqy9bdabx5qbrjy4"
|
||||
"preferred_item": "01jcrc1n1z065tf45ds2a1z8rp"
|
||||
}
|
||||
}
|
987
.storage/auth
987
.storage/auth
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,64 @@
|
|||
{
|
||||
"version": 1,
|
||||
"minor_version": 1,
|
||||
"key": "browser_mod.storage",
|
||||
"data": {
|
||||
"browsers": {
|
||||
"pecan-station": {
|
||||
"last_seen": "2025-04-30T15:50:14.632453+00:00",
|
||||
"registered": true,
|
||||
"locked": true,
|
||||
"camera": false,
|
||||
"settings": {
|
||||
"hideSidebar": null,
|
||||
"hideHeader": null,
|
||||
"defaultPanel": null,
|
||||
"sidebarPanelOrder": null,
|
||||
"sidebarHiddenPanels": null,
|
||||
"sidebarTitle": null,
|
||||
"faviconTemplate": null,
|
||||
"titleTemplate": null,
|
||||
"hideInteractIcon": null,
|
||||
"autoRegister": null,
|
||||
"lockRegister": null
|
||||
},
|
||||
"meta": "default"
|
||||
},
|
||||
"Ingest iPad": {
|
||||
"last_seen": "2025-03-10T20:38:38.304530+00:00",
|
||||
"registered": true,
|
||||
"locked": true,
|
||||
"camera": false,
|
||||
"settings": {
|
||||
"hideSidebar": null,
|
||||
"hideHeader": null,
|
||||
"defaultPanel": "dashboard-ingest",
|
||||
"sidebarPanelOrder": null,
|
||||
"sidebarHiddenPanels": null,
|
||||
"sidebarTitle": null,
|
||||
"faviconTemplate": null,
|
||||
"titleTemplate": null,
|
||||
"hideInteractIcon": null,
|
||||
"autoRegister": null,
|
||||
"lockRegister": null
|
||||
},
|
||||
"meta": "default"
|
||||
}
|
||||
},
|
||||
"version": "2.0",
|
||||
"settings": {
|
||||
"hideSidebar": null,
|
||||
"hideHeader": null,
|
||||
"defaultPanel": null,
|
||||
"sidebarPanelOrder": null,
|
||||
"sidebarHiddenPanels": null,
|
||||
"sidebarTitle": "USDA Assistant",
|
||||
"faviconTemplate": "favicon/favicon.ico",
|
||||
"titleTemplate": null,
|
||||
"hideInteractIcon": null,
|
||||
"autoRegister": null,
|
||||
"lockRegister": null
|
||||
},
|
||||
"user_settings": {}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,76 @@
|
|||
{
|
||||
"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": "4ff51199e4ff4d96827eeef086468459",
|
||||
"cloudhooks": {
|
||||
"541b6ac0d1f592acf1d6dc8ff736c2bad653ee1e85a8b6409348c1eb974c6ce7": {
|
||||
"webhook_id": "541b6ac0d1f592acf1d6dc8ff736c2bad653ee1e85a8b6409348c1eb974c6ce7",
|
||||
"cloudhook_id": "df8dc4b010ad45f389819ad36aed7c5a",
|
||||
"cloudhook_url": "https://hooks.nabu.casa/gAAAAABnN30j6_p0rowd_9HuWevBUbbMdFXr9Ub-Aqyo61X-_ZMoLCPDdnG7ZlnR4r8qSmvgZ2zDH8XkmDcJVeF57oj5iA_wUWnbX1TZepV5VSYPG_sKXUfPR80HyqGNhpkUhSlASFrShwZPF8JIeGdD-Z8a31ttjQgKg2KlxEZuyZ4flV0pNXo=",
|
||||
"managed": true
|
||||
},
|
||||
"559877f352055fb8587094b48f394f450e316432bd9bc5e4fdb8fbaa00493e59": {
|
||||
"webhook_id": "559877f352055fb8587094b48f394f450e316432bd9bc5e4fdb8fbaa00493e59",
|
||||
"cloudhook_id": "54709579fe2b42859d77b2a03f8f6865",
|
||||
"cloudhook_url": "https://hooks.nabu.casa/gAAAAABn02dCWUEMHlGjs-zCOuDsjWDWeCfAhcBBr2Uhg6-qo_5np8ypLA5vZRTe50YNVM187uxJFxg0Zh1HB83A0wC1j-i99MmdHOQ5511hHqazY7Q5pwRDZmfT4f-Qx0mZ169WX3m-39iw8hwgQiaD3rmk1FkeZbt2mcdXt802N6RUBf58zZ8=",
|
||||
"managed": true
|
||||
},
|
||||
"7eb2a43c6447c831a473630a65e2f3c5f335de04f9192c0430bdd129576fbe7a": {
|
||||
"webhook_id": "7eb2a43c6447c831a473630a65e2f3c5f335de04f9192c0430bdd129576fbe7a",
|
||||
"cloudhook_id": "1f056b582cdd4b54841f09f5ec70d48b",
|
||||
"cloudhook_url": "https://hooks.nabu.casa/gAAAAABoEkQ_BAwuMj2DgWl3Iwnyt6yX7ghlprYV2pnoAmz66JN_BxgZ4z1YRXswI1WFtiQarMOynEb0cJMrhxesr-cwaBt7HeYdVzZM-VzU1eRFzyzAq720QC66uoLzReVdFbQ21etyX1xiAJfvtgPPpazeyBuijkaPHMb88AqerBT31Y3Kuq4=",
|
||||
"managed": true
|
||||
},
|
||||
"dd11c41f93a1ec05751fb1b2008d247fc3895548b22b21ddb7d013a02475264b": {
|
||||
"webhook_id": "dd11c41f93a1ec05751fb1b2008d247fc3895548b22b21ddb7d013a02475264b",
|
||||
"cloudhook_id": "de83cb4741374710aac2b1c0040a36fc",
|
||||
"cloudhook_url": "https://hooks.nabu.casa/gAAAAABoEl4WK4dZP79V7lIO5-2iPtGyTUJBxH9pnlgwl086ztO6CogOHo2_VJPyGKGqF2r-98cTXFEzB854UtsDoJQzn-x8PP6dG9Ziz04LRwBWBQu9KN0fi1aFUNNri9jz7H2jjAKvkHsONDU5cADNvm-8NxiFgDT6U-e_kU6jhfug_LYak9I=",
|
||||
"managed": true
|
||||
}
|
||||
},
|
||||
"alexa_enabled": true,
|
||||
"google_enabled": true,
|
||||
"remote_enabled": true,
|
||||
"google_connected": false,
|
||||
"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": "f4c5013efa8e06697da8be43dad0e9acaff71fea647951d6d6c38386a591bba1",
|
||||
"instance_id": "4cf0780e04744cd69da58cb024b0c60b",
|
||||
"google_secure_devices_pin": null,
|
||||
"remote_domain": "wo1d5pxp3jix0xxkusbi44e44ztql5mu.ui.nabu.casa",
|
||||
"remote_allow_remote_enable": true,
|
||||
"username": "4361163a-c63e-4c39-84e4-8bce32ceb9fd"
|
||||
}
|
||||
}
|
|
@ -1,41 +1,60 @@
|
|||
{
|
||||
"version": 1,
|
||||
"minor_version": 7,
|
||||
"minor_version": 8,
|
||||
"key": "core.area_registry",
|
||||
"data": {
|
||||
"areas": [
|
||||
{
|
||||
"aliases": [],
|
||||
"name": "Living Room",
|
||||
"id": "living_room",
|
||||
"picture": null,
|
||||
"icon": null,
|
||||
"floor_id": null,
|
||||
"humidity_entity_id": null,
|
||||
"icon": null,
|
||||
"id": "jc_machine",
|
||||
"labels": [],
|
||||
"created_at": "1970-01-01T00:00:00+00:00",
|
||||
"modified_at": "1970-01-01T00:00:00+00:00"
|
||||
"name": "JC Machine",
|
||||
"picture": null,
|
||||
"temperature_entity_id": null,
|
||||
"created_at": "2025-03-08T19:12:57.095675+00:00",
|
||||
"modified_at": "2025-03-08T19:12:57.095689+00:00"
|
||||
},
|
||||
{
|
||||
"aliases": [],
|
||||
"name": "Kitchen",
|
||||
"id": "kitchen",
|
||||
"picture": null,
|
||||
"icon": null,
|
||||
"floor_id": null,
|
||||
"humidity_entity_id": null,
|
||||
"icon": null,
|
||||
"id": "meyer_machine",
|
||||
"labels": [],
|
||||
"created_at": "1970-01-01T00:00:00+00:00",
|
||||
"modified_at": "1970-01-01T00:00:00+00:00"
|
||||
"name": "Meyer Machine",
|
||||
"picture": null,
|
||||
"temperature_entity_id": null,
|
||||
"created_at": "2025-03-08T19:13:18.112708+00:00",
|
||||
"modified_at": "2025-03-08T19:13:18.112824+00:00"
|
||||
},
|
||||
{
|
||||
"aliases": [],
|
||||
"name": "Bedroom",
|
||||
"id": "bedroom",
|
||||
"picture": null,
|
||||
"icon": null,
|
||||
"floor_id": null,
|
||||
"humidity_entity_id": null,
|
||||
"icon": null,
|
||||
"id": "sheller_machine",
|
||||
"labels": [],
|
||||
"created_at": "1970-01-01T00:00:00+00:00",
|
||||
"modified_at": "1970-01-01T00:00:00+00:00"
|
||||
"name": "Sheller Machine",
|
||||
"picture": null,
|
||||
"temperature_entity_id": null,
|
||||
"created_at": "2025-03-08T19:13:22.445914+00:00",
|
||||
"modified_at": "2025-03-08T19:13:22.445929+00:00"
|
||||
},
|
||||
{
|
||||
"aliases": [],
|
||||
"floor_id": null,
|
||||
"humidity_entity_id": null,
|
||||
"icon": null,
|
||||
"id": "conditioning",
|
||||
"labels": [],
|
||||
"name": "Conditioning",
|
||||
"picture": null,
|
||||
"temperature_entity_id": null,
|
||||
"created_at": "2025-03-08T19:13:27.588309+00:00",
|
||||
"modified_at": "2025-03-08T19:13:27.588325+00:00"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -1,644 +1,95 @@
|
|||
{
|
||||
"version": 1,
|
||||
"minor_version": 3,
|
||||
"minor_version": 5,
|
||||
"key": "core.config_entries",
|
||||
"data": {
|
||||
"entries": [
|
||||
{
|
||||
"created_at": "1970-01-01T00:00:00+00:00",
|
||||
"data": {
|
||||
"broker": "192.168.1.110",
|
||||
"port": 1883,
|
||||
"discovery": true,
|
||||
"discovery_prefix": "homeassistant",
|
||||
"birth_message": {
|
||||
"topic": "homeassistant/status",
|
||||
"payload": "online",
|
||||
"qos": 0,
|
||||
"retain": false
|
||||
},
|
||||
"will_message": {
|
||||
"topic": "homeassistant/status",
|
||||
"payload": "offline",
|
||||
"qos": 0,
|
||||
"retain": true
|
||||
}
|
||||
},
|
||||
"disabled_by": null,
|
||||
"domain": "mqtt",
|
||||
"entry_id": "143eb40c5189f32be0eddf773eaaeceb",
|
||||
"minor_version": 1,
|
||||
"modified_at": "2024-09-30T18:02:15.200209+00:00",
|
||||
"options": {},
|
||||
"pref_disable_new_entities": false,
|
||||
"pref_disable_polling": false,
|
||||
"source": "user",
|
||||
"title": "192.168.1.110",
|
||||
"unique_id": null,
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"created_at": "1970-01-01T00:00:00+00:00",
|
||||
"data": {
|
||||
"host": "172.22.114.136",
|
||||
"mac": "d0:03:df:ca:7c:74",
|
||||
"model": "UN65RU8000FXZA",
|
||||
"ssdp_rendering_control_location": "http://172.22.114.136:9197/dmr"
|
||||
},
|
||||
"disabled_by": null,
|
||||
"domain": "samsungtv",
|
||||
"entry_id": "e2fde3af62ceb6eb0d4db0ce0395100e",
|
||||
"minor_version": 1,
|
||||
"modified_at": "1970-01-01T00:00:00+00:00",
|
||||
"options": {},
|
||||
"pref_disable_new_entities": false,
|
||||
"pref_disable_polling": false,
|
||||
"source": "ignore",
|
||||
"title": "Samsung TV 1048 Right (UN65RU8000FXZA)",
|
||||
"unique_id": "8492f9dc-0f92-4ec9-951b-0d7e9c4918df",
|
||||
"version": 2
|
||||
},
|
||||
{
|
||||
"created_at": "1970-01-01T00:00:00+00:00",
|
||||
"data": {
|
||||
"host": "192.168.1.192",
|
||||
"port": 6053,
|
||||
"password": "",
|
||||
"noise_psk": "OSntu1sSvgNHlNbyi9VYm0somCYIF2bOu11U2ckotX8=",
|
||||
"device_name": "jc-vibratory"
|
||||
},
|
||||
"disabled_by": null,
|
||||
"domain": "esphome",
|
||||
"entry_id": "c0c49687c85dee58eea3dbb414dd9337",
|
||||
"minor_version": 1,
|
||||
"modified_at": "1970-01-01T00:00:00+00:00",
|
||||
"options": {
|
||||
"allow_service_calls": false
|
||||
},
|
||||
"pref_disable_new_entities": false,
|
||||
"pref_disable_polling": false,
|
||||
"source": "user",
|
||||
"title": "jc-vibratory",
|
||||
"unique_id": "60:01:94:cf:79:15",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"created_at": "1970-01-01T00:00:00+00:00",
|
||||
"data": {
|
||||
"host": "172.22.114.176",
|
||||
"port": 443,
|
||||
"verify_ssl": false,
|
||||
"username": "engr-ugaif",
|
||||
"password": "1048Lab&2021",
|
||||
"id": "Cloud Key Gen2 Plus"
|
||||
},
|
||||
"disabled_by": null,
|
||||
"domain": "unifiprotect",
|
||||
"entry_id": "55db3b46f3bf75777e4779fd25ed6bca",
|
||||
"minor_version": 1,
|
||||
"modified_at": "1970-01-01T00:00:00+00:00",
|
||||
"options": {
|
||||
"disable_rtsp": false,
|
||||
"all_updates": false,
|
||||
"override_connection_host": false,
|
||||
"max_media": 1000,
|
||||
"allow_ea": false
|
||||
},
|
||||
"pref_disable_new_entities": false,
|
||||
"pref_disable_polling": false,
|
||||
"source": "user",
|
||||
"title": "Cloud Key Gen2 Plus",
|
||||
"unique_id": "70A741A53E33",
|
||||
"version": 2
|
||||
},
|
||||
{
|
||||
"created_at": "1970-01-01T00:00:00+00:00",
|
||||
"data": {
|
||||
"type": "Setup as remote node"
|
||||
},
|
||||
"disabled_by": null,
|
||||
"domain": "remote_homeassistant",
|
||||
"entry_id": "475e896c4033e0014ade6d0ef18b8329",
|
||||
"minor_version": 1,
|
||||
"modified_at": "1970-01-01T00:00:00+00:00",
|
||||
"options": {},
|
||||
"pref_disable_new_entities": false,
|
||||
"pref_disable_polling": false,
|
||||
"source": "user",
|
||||
"title": "Remote instance",
|
||||
"unique_id": "remote",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"created_at": "1970-01-01T00:00:00+00:00",
|
||||
"data": {
|
||||
"url": "http://172.22.114.136:9197/dmr",
|
||||
"device_id": "uuid:c73ad4db-6241-496a-80fd-994a43d1c980",
|
||||
"type": "urn:schemas-upnp-org:device:MediaRenderer:1",
|
||||
"mac": "d0:03:df:ca:7c:74"
|
||||
},
|
||||
"disabled_by": null,
|
||||
"domain": "dlna_dmr",
|
||||
"entry_id": "d24486392bce9e68c96bb73e691681e4",
|
||||
"minor_version": 1,
|
||||
"modified_at": "1970-01-01T00:00:00+00:00",
|
||||
"options": {},
|
||||
"pref_disable_new_entities": false,
|
||||
"pref_disable_polling": false,
|
||||
"source": "ignore",
|
||||
"title": "[TV] Samsung TV 1048 Right",
|
||||
"unique_id": "uuid:c73ad4db-6241-496a-80fd-994a43d1c980",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"created_at": "1970-01-01T00:00:00+00:00",
|
||||
"data": {
|
||||
"url": "http://172.22.114.134:9197/dmr",
|
||||
"device_id": "uuid:5cac41f8-5fa8-4d42-9c30-8c8b3f6050b0",
|
||||
"type": "urn:schemas-upnp-org:device:MediaRenderer:1",
|
||||
"mac": "24:fc:e5:5a:ff:68"
|
||||
},
|
||||
"disabled_by": null,
|
||||
"domain": "dlna_dmr",
|
||||
"entry_id": "e172a44f6bd05f602b6469920fdc3754",
|
||||
"minor_version": 1,
|
||||
"modified_at": "1970-01-01T00:00:00+00:00",
|
||||
"options": {},
|
||||
"pref_disable_new_entities": false,
|
||||
"pref_disable_polling": false,
|
||||
"source": "ignore",
|
||||
"title": "[TV] Samsung TV 1048 Left",
|
||||
"unique_id": "uuid:5cac41f8-5fa8-4d42-9c30-8c8b3f6050b0",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"created_at": "1970-01-01T00:00:00+00:00",
|
||||
"data": {
|
||||
"url": "http://172.22.114.135:9197/dmr",
|
||||
"device_id": "uuid:8e7e5e53-46a1-4974-9513-05270ad0af77",
|
||||
"type": "urn:schemas-upnp-org:device:MediaRenderer:1",
|
||||
"mac": "24:fc:e5:5b:17:6a"
|
||||
},
|
||||
"disabled_by": null,
|
||||
"domain": "dlna_dmr",
|
||||
"entry_id": "b02ec81df7205825511646b73cbb58f8",
|
||||
"minor_version": 1,
|
||||
"modified_at": "1970-01-01T00:00:00+00:00",
|
||||
"options": {},
|
||||
"pref_disable_new_entities": false,
|
||||
"pref_disable_polling": false,
|
||||
"source": "ignore",
|
||||
"title": "[TV] Samsung TV 1048 Center",
|
||||
"unique_id": "uuid:8e7e5e53-46a1-4974-9513-05270ad0af77",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"created_at": "1970-01-01T00:00:00+00:00",
|
||||
"data": {
|
||||
"host": "172.22.114.135",
|
||||
"mac": "24:fc:e5:5b:17:6a",
|
||||
"model": "UN65RU8000FXZA",
|
||||
"ssdp_rendering_control_location": "http://172.22.114.135:9197/dmr"
|
||||
},
|
||||
"disabled_by": null,
|
||||
"domain": "samsungtv",
|
||||
"entry_id": "6e73159a0788a6dbb3ccf4dc7a26945d",
|
||||
"minor_version": 1,
|
||||
"modified_at": "1970-01-01T00:00:00+00:00",
|
||||
"options": {},
|
||||
"pref_disable_new_entities": false,
|
||||
"pref_disable_polling": false,
|
||||
"source": "ignore",
|
||||
"title": "Samsung TV 1048 Center (UN65RU8000FXZA)",
|
||||
"unique_id": "89947ca0-b33a-4912-b659-f4b1578a7777",
|
||||
"version": 2
|
||||
},
|
||||
{
|
||||
"created_at": "1970-01-01T00:00:00+00:00",
|
||||
"data": {
|
||||
"host": "172.22.114.134",
|
||||
"mac": "24:fc:e5:5a:ff:68",
|
||||
"model": "UN65RU8000FXZA",
|
||||
"ssdp_rendering_control_location": "http://172.22.114.134:9197/dmr"
|
||||
},
|
||||
"disabled_by": null,
|
||||
"domain": "samsungtv",
|
||||
"entry_id": "5c87c2ce60943c4288dfd80aa9db5bf0",
|
||||
"minor_version": 1,
|
||||
"modified_at": "1970-01-01T00:00:00+00:00",
|
||||
"options": {},
|
||||
"pref_disable_new_entities": false,
|
||||
"pref_disable_polling": false,
|
||||
"source": "ignore",
|
||||
"title": "Samsung TV 1048 Left (UN65RU8000FXZA)",
|
||||
"unique_id": "64bfe2c2-7b01-46b8-89d3-88c49373f55e",
|
||||
"version": 2
|
||||
},
|
||||
{
|
||||
"created_at": "1970-01-01T00:00:00+00:00",
|
||||
"data": {},
|
||||
"disabled_by": null,
|
||||
"domain": "remote_homeassistant",
|
||||
"entry_id": "0200f22cdff3cef1905acfee956d71cc",
|
||||
"minor_version": 1,
|
||||
"modified_at": "1970-01-01T00:00:00+00:00",
|
||||
"options": {},
|
||||
"pref_disable_new_entities": false,
|
||||
"pref_disable_polling": false,
|
||||
"source": "ignore",
|
||||
"title": "Remote: Innovation Factory",
|
||||
"unique_id": "311fbea761c143be9c14dc1cd5ab57b2",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"created_at": "1970-01-01T00:00:00+00:00",
|
||||
"data": {},
|
||||
"disabled_by": null,
|
||||
"domain": "sun",
|
||||
"entry_id": "0feafcef6c9ee4eb380cad7190b2f403",
|
||||
"minor_version": 1,
|
||||
"modified_at": "1970-01-01T00:00:00+00:00",
|
||||
"options": {},
|
||||
"pref_disable_new_entities": false,
|
||||
"pref_disable_polling": false,
|
||||
"source": "import",
|
||||
"title": "Sun",
|
||||
"unique_id": null,
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"created_at": "1970-01-01T00:00:00+00:00",
|
||||
"data": {},
|
||||
"disabled_by": null,
|
||||
"domain": "octoprint",
|
||||
"entry_id": "7a20eaee207e2873f5296d198ac8dd63",
|
||||
"minor_version": 1,
|
||||
"modified_at": "1970-01-01T00:00:00+00:00",
|
||||
"options": {},
|
||||
"pref_disable_new_entities": false,
|
||||
"pref_disable_polling": false,
|
||||
"source": "ignore",
|
||||
"title": "OctoPrint Printer: 172.22.114.150",
|
||||
"unique_id": "aef76126-1c40-459c-a460-ca7749222fdd",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"created_at": "1970-01-01T00:00:00+00:00",
|
||||
"data": {
|
||||
"app_id": "io.homeassistant.companion.android",
|
||||
"app_name": "Home Assistant",
|
||||
"app_version": "2024.4.1-full (12576)",
|
||||
"device_name": "lab-phone",
|
||||
"manufacturer": "samsung",
|
||||
"model": "SM-A546U1",
|
||||
"os_name": "Android",
|
||||
"os_version": "34",
|
||||
"supports_encryption": false,
|
||||
"app_data": {
|
||||
"push_websocket_channel": true,
|
||||
"push_url": "https://mobile-apps.home-assistant.io/api/sendPush/android/v1",
|
||||
"push_token": "filDMYq4Sa2akEe_BlkVyg:APA91bHPxz2u6XjWrUIErPeakuxA-_VTCZT5JVa0vD5gkpe0P65aZhDC0Q6uNRQWJ2nQ3a6xRjI7uaB0ywup_WwSQNd8AkTqQlObEPr0AWO6ditliwFuh0EQWGQzU1rYFKiz7gq8zhBW"
|
||||
},
|
||||
"device_id": "2ddef885064fb8ed",
|
||||
"webhook_id": "541b6ac0d1f592acf1d6dc8ff736c2bad653ee1e85a8b6409348c1eb974c6ce7",
|
||||
"user_id": "5ef2c8c082b14074a6e84da694ef2f35"
|
||||
},
|
||||
"disabled_by": null,
|
||||
"domain": "mobile_app",
|
||||
"entry_id": "e3427a6f1a531d4647c57351962f3e1a",
|
||||
"minor_version": 1,
|
||||
"modified_at": "1970-01-01T00:00:00+00:00",
|
||||
"options": {},
|
||||
"pref_disable_new_entities": false,
|
||||
"pref_disable_polling": false,
|
||||
"source": "registration",
|
||||
"title": "lab-phone",
|
||||
"unique_id": "io.homeassistant.companion.android-2ddef885064fb8ed",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"created_at": "1970-01-01T00:00:00+00:00",
|
||||
"data": {
|
||||
"host": "172.22.113.18",
|
||||
"mac": "70:09:71:0d:f6:5d",
|
||||
"model": "UN50AU8000FXZA",
|
||||
"ssdp_rendering_control_location": "http://172.22.113.18:9197/dmr"
|
||||
},
|
||||
"disabled_by": null,
|
||||
"domain": "samsungtv",
|
||||
"entry_id": "54d38b0b04614f1d163c9ba14ce22fed",
|
||||
"minor_version": 1,
|
||||
"modified_at": "1970-01-01T00:00:00+00:00",
|
||||
"options": {},
|
||||
"pref_disable_new_entities": false,
|
||||
"pref_disable_polling": false,
|
||||
"source": "ignore",
|
||||
"title": "Samsung AU8000 50 TV (UN50AU8000FXZA)",
|
||||
"unique_id": "7671ae54-70ac-426b-8fa0-a065cd4bd428",
|
||||
"version": 2
|
||||
},
|
||||
{
|
||||
"created_at": "1970-01-01T00:00:00+00:00",
|
||||
"data": {},
|
||||
"disabled_by": null,
|
||||
"domain": "mjpeg",
|
||||
"entry_id": "f92d6ca163501ea7659047c60ba4e9e5",
|
||||
"minor_version": 1,
|
||||
"modified_at": "1970-01-01T00:00:00+00:00",
|
||||
"options": {
|
||||
"authentication": "basic",
|
||||
"mjpeg_url": "http://meyer:8080",
|
||||
"password": "",
|
||||
"still_image_url": null,
|
||||
"username": null,
|
||||
"verify_ssl": false
|
||||
},
|
||||
"pref_disable_new_entities": false,
|
||||
"pref_disable_polling": false,
|
||||
"source": "user",
|
||||
"title": "Crack Output",
|
||||
"unique_id": null,
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"created_at": "1970-01-01T00:00:00+00:00",
|
||||
"data": {
|
||||
"host": "192.168.1.151",
|
||||
"sleep_period": 0,
|
||||
"model": "SNSW-001X15UL",
|
||||
"gen": 2,
|
||||
"port": 80
|
||||
},
|
||||
"disabled_by": null,
|
||||
"domain": "shelly",
|
||||
"entry_id": "04978edcf23c54a047e4f421779754ad",
|
||||
"minor_version": 1,
|
||||
"modified_at": "2024-09-30T14:21:49.532296+00:00",
|
||||
"options": {
|
||||
"ble_scanner_mode": "passive"
|
||||
},
|
||||
"pref_disable_new_entities": false,
|
||||
"pref_disable_polling": false,
|
||||
"source": "user",
|
||||
"title": "Sheller Drum Enable",
|
||||
"unique_id": "CC7B5C0D0EB4",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"created_at": "1970-01-01T00:00:00+00:00",
|
||||
"data": {
|
||||
"host": "192.168.1.15",
|
||||
"sleep_period": 0,
|
||||
"model": "SNSW-001X15UL",
|
||||
"gen": 2,
|
||||
"port": 80
|
||||
},
|
||||
"disabled_by": null,
|
||||
"domain": "shelly",
|
||||
"entry_id": "ce337fdb50b165d7ba080505d5c73343",
|
||||
"minor_version": 1,
|
||||
"modified_at": "2024-09-30T14:21:49.516809+00:00",
|
||||
"options": {},
|
||||
"pref_disable_new_entities": false,
|
||||
"pref_disable_polling": false,
|
||||
"source": "user",
|
||||
"title": "Sheller Paddle Shaft Enable",
|
||||
"unique_id": "CC7B5C0D316C",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"created_at": "1970-01-01T00:00:00+00:00",
|
||||
"data": {
|
||||
"host": "192.168.1.222",
|
||||
"sleep_period": 0,
|
||||
"model": "SNDM-00100WW",
|
||||
"gen": 2,
|
||||
"port": 80
|
||||
},
|
||||
"disabled_by": null,
|
||||
"domain": "shelly",
|
||||
"entry_id": "779bd6f1f6eebd9fb67b45fa40386e0c",
|
||||
"minor_version": 1,
|
||||
"modified_at": "2024-09-30T14:21:49.537818+00:00",
|
||||
"options": {},
|
||||
"pref_disable_new_entities": false,
|
||||
"pref_disable_polling": false,
|
||||
"source": "user",
|
||||
"title": "Sheller Drum Velocity",
|
||||
"unique_id": "E86BEAE4D350",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"created_at": "1970-01-01T00:00:00+00:00",
|
||||
"data": {
|
||||
"host": "192.168.1.28",
|
||||
"sleep_period": 0,
|
||||
"model": "SNDM-00100WW",
|
||||
"gen": 2
|
||||
},
|
||||
"disabled_by": null,
|
||||
"domain": "shelly",
|
||||
"entry_id": "51355cd442e2d0c51a3a43811555ee77",
|
||||
"minor_version": 1,
|
||||
"modified_at": "2024-09-30T14:21:49.528183+00:00",
|
||||
"options": {},
|
||||
"pref_disable_new_entities": false,
|
||||
"pref_disable_polling": false,
|
||||
"source": "user",
|
||||
"title": "Sheller Paddle Shaft Velocity",
|
||||
"unique_id": "E86BEAE4DF24",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"created_at": "1970-01-01T00:00:00+00:00",
|
||||
"data": {
|
||||
"host": "192.168.1.92",
|
||||
"alias": "TP-LINK_Power Strip_D7C1",
|
||||
"model": "HS300(US)",
|
||||
"connection_parameters": {
|
||||
"device_family": "IOT.SMARTPLUGSWITCH",
|
||||
"encryption_type": "XOR"
|
||||
},
|
||||
"uses_http": false
|
||||
},
|
||||
"disabled_by": null,
|
||||
"domain": "tplink",
|
||||
"entry_id": "37a922a368171d96e691a3439549d7bf",
|
||||
"minor_version": 5,
|
||||
"modified_at": "2024-09-30T14:21:49.546287+00:00",
|
||||
"options": {},
|
||||
"pref_disable_new_entities": false,
|
||||
"pref_disable_polling": false,
|
||||
"source": "user",
|
||||
"title": "TP-LINK_Power Strip_D7C1 HS300(US)",
|
||||
"unique_id": "98:25:4a:f7:d7:c1",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"created_at": "1970-01-01T00:00:00+00:00",
|
||||
"data": {},
|
||||
"disabled_by": null,
|
||||
"domain": "switch_as_x",
|
||||
"entry_id": "e49f29f5d0e10a3ef63369b4c8c7f5c2",
|
||||
"minor_version": 2,
|
||||
"modified_at": "1970-01-01T00:00:00+00:00",
|
||||
"options": {
|
||||
"entity_id": "switch.tp_link_power_strip_d7c1_light_2",
|
||||
"invert": false,
|
||||
"target_domain": "light"
|
||||
},
|
||||
"pref_disable_new_entities": false,
|
||||
"pref_disable_polling": false,
|
||||
"source": "user",
|
||||
"title": "Light",
|
||||
"unique_id": null,
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"created_at": "1970-01-01T00:00:00+00:00",
|
||||
"data": {},
|
||||
"disabled_by": null,
|
||||
"domain": "switch_as_x",
|
||||
"entry_id": "77ef41d0bbf25d6007b4b2968dc60f58",
|
||||
"minor_version": 2,
|
||||
"modified_at": "1970-01-01T00:00:00+00:00",
|
||||
"options": {
|
||||
"entity_id": "switch.tp_link_power_strip_d7c1_light_3",
|
||||
"invert": false,
|
||||
"target_domain": "light"
|
||||
},
|
||||
"pref_disable_new_entities": false,
|
||||
"pref_disable_polling": false,
|
||||
"source": "user",
|
||||
"title": "Light",
|
||||
"unique_id": null,
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"created_at": "1970-01-01T00:00:00+00:00",
|
||||
"data": {},
|
||||
"disabled_by": null,
|
||||
"domain": "switch_as_x",
|
||||
"entry_id": "54af0f5338887810d55839a908975bd3",
|
||||
"minor_version": 2,
|
||||
"modified_at": "1970-01-01T00:00:00+00:00",
|
||||
"options": {
|
||||
"entity_id": "switch.tp_link_power_strip_d7c1_light_4",
|
||||
"invert": false,
|
||||
"target_domain": "light"
|
||||
},
|
||||
"pref_disable_new_entities": false,
|
||||
"pref_disable_polling": false,
|
||||
"source": "user",
|
||||
"title": "Light ",
|
||||
"unique_id": null,
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"created_at": "1970-01-01T00:00:00+00:00",
|
||||
"data": {},
|
||||
"disabled_by": null,
|
||||
"domain": "group",
|
||||
"entry_id": "7451d239431f2b6ea2ee2a1b85ab5c56",
|
||||
"minor_version": 1,
|
||||
"modified_at": "1970-01-01T00:00:00+00:00",
|
||||
"options": {
|
||||
"group_type": "light",
|
||||
"name": "Conveyor Lights",
|
||||
"entities": [
|
||||
"light.tp_link_power_strip_d7c1_light",
|
||||
"light.tp_link_power_strip_d7c1_light_2",
|
||||
"light.tp_link_power_strip_d7c1_light_3",
|
||||
"light.tp_link_power_strip_d7c1_light_4"
|
||||
],
|
||||
"hide_members": false,
|
||||
"all": false
|
||||
},
|
||||
"pref_disable_new_entities": false,
|
||||
"pref_disable_polling": false,
|
||||
"source": "user",
|
||||
"title": "Conveyor Lights",
|
||||
"unique_id": null,
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"created_at": "1970-01-01T00:00:00+00:00",
|
||||
"data": {},
|
||||
"disabled_by": null,
|
||||
"domain": "switch_as_x",
|
||||
"entry_id": "82a8d9eef25b35d19d72cd6ce5f9dcbe",
|
||||
"minor_version": 2,
|
||||
"modified_at": "1970-01-01T00:00:00+00:00",
|
||||
"options": {
|
||||
"entity_id": "switch.tp_link_power_strip_d7c1_light",
|
||||
"invert": false,
|
||||
"target_domain": "light"
|
||||
},
|
||||
"pref_disable_new_entities": false,
|
||||
"pref_disable_polling": false,
|
||||
"source": "user",
|
||||
"title": "Light ",
|
||||
"unique_id": null,
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"created_at": "1970-01-01T00:00:00+00:00",
|
||||
"data": {},
|
||||
"disabled_by": null,
|
||||
"domain": "switch_as_x",
|
||||
"entry_id": "f1e37a42d2b569eaa4ac24c20a31fa24",
|
||||
"minor_version": 2,
|
||||
"modified_at": "1970-01-01T00:00:00+00:00",
|
||||
"options": {
|
||||
"entity_id": "switch.tp_link_power_strip_d7c1_zima_board",
|
||||
"invert": false,
|
||||
"target_domain": "light"
|
||||
},
|
||||
"pref_disable_new_entities": false,
|
||||
"pref_disable_polling": false,
|
||||
"source": "user",
|
||||
"title": "Vibratory Lights",
|
||||
"unique_id": null,
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"created_at": "1970-01-01T00:00:00+00:00",
|
||||
"data": {
|
||||
"host": "192.168.1.139",
|
||||
"port": 80,
|
||||
"sleep_period": 0,
|
||||
"model": "SNSW-001P15UL",
|
||||
"gen": 2
|
||||
},
|
||||
"disabled_by": null,
|
||||
"domain": "shelly",
|
||||
"entry_id": "01J5NH0A41TYMRRGASE2QJ0SQ5",
|
||||
"minor_version": 2,
|
||||
"modified_at": "2024-09-30T14:21:49.522251+00:00",
|
||||
"options": {},
|
||||
"pref_disable_new_entities": false,
|
||||
"pref_disable_polling": false,
|
||||
"source": "user",
|
||||
"title": "shellyplus1pm-c049ef8c7310",
|
||||
"unique_id": "C049EF8C7310",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"created_at": "2024-09-30T14:21:49.515314+00:00",
|
||||
"data": {},
|
||||
"disabled_by": null,
|
||||
"domain": "hassio",
|
||||
"entry_id": "01J91MY5JBJH64GQS4WA5SYVK2",
|
||||
"minor_version": 1,
|
||||
"modified_at": "2024-09-30T14:21:49.515328+00:00",
|
||||
"options": {},
|
||||
"pref_disable_new_entities": false,
|
||||
"pref_disable_polling": false,
|
||||
"source": "system",
|
||||
"title": "Supervisor",
|
||||
"unique_id": "hassio",
|
||||
"version": 1
|
||||
}
|
||||
{"created_at":"1970-01-01T00:00:00+00:00","data":{"broker":"192.168.1.110","port":1883},"disabled_by":null,"discovery_keys":{},"domain":"mqtt","entry_id":"143eb40c5189f32be0eddf773eaaeceb","minor_version":2,"modified_at":"2025-02-12T17:45:41.053251+00:00","options":{"birth_message":{"payload":"online","qos":0,"retain":false,"topic":"homeassistant/status"},"discovery":true,"discovery_prefix":"homeassistant","will_message":{"payload":"offline","qos":0,"retain":true,"topic":"homeassistant/status"}},"pref_disable_new_entities":false,"pref_disable_polling":false,"source":"user","subentries":[],"title":"192.168.1.110","unique_id":null,"version":1},
|
||||
{"created_at":"1970-01-01T00:00:00+00:00","data":{"host":"172.22.114.136","mac":"d0:03:df:ca:7c:74","model":"UN65RU8000FXZA","ssdp_rendering_control_location":"http://172.22.114.136:9197/dmr"},"disabled_by":null,"discovery_keys":{},"domain":"samsungtv","entry_id":"e2fde3af62ceb6eb0d4db0ce0395100e","minor_version":1,"modified_at":"1970-01-01T00:00:00+00:00","options":{},"pref_disable_new_entities":false,"pref_disable_polling":false,"source":"ignore","subentries":[],"title":"Samsung TV 1048 Right (UN65RU8000FXZA)","unique_id":"8492f9dc-0f92-4ec9-951b-0d7e9c4918df","version":2},
|
||||
{"created_at":"1970-01-01T00:00:00+00:00","data":{"host":"172.22.114.176","id":"Cloud Key Gen2 Plus","password":"1048Lab&2021","port":443,"username":"engr-ugaif","verify_ssl":false},"disabled_by":null,"discovery_keys":{},"domain":"unifiprotect","entry_id":"55db3b46f3bf75777e4779fd25ed6bca","minor_version":1,"modified_at":"1970-01-01T00:00:00+00:00","options":{"all_updates":false,"allow_ea":false,"disable_rtsp":false,"max_media":1000,"override_connection_host":false},"pref_disable_new_entities":false,"pref_disable_polling":false,"source":"user","subentries":[],"title":"Cloud Key Gen2 Plus","unique_id":"70A741A53E33","version":2},
|
||||
{"created_at":"1970-01-01T00:00:00+00:00","data":{"type":"Setup as remote node"},"disabled_by":null,"discovery_keys":{},"domain":"remote_homeassistant","entry_id":"475e896c4033e0014ade6d0ef18b8329","minor_version":1,"modified_at":"1970-01-01T00:00:00+00:00","options":{},"pref_disable_new_entities":false,"pref_disable_polling":false,"source":"user","subentries":[],"title":"Remote instance","unique_id":"remote","version":1},
|
||||
{"created_at":"1970-01-01T00:00:00+00:00","data":{"device_id":"uuid:c73ad4db-6241-496a-80fd-994a43d1c980","mac":"d0:03:df:ca:7c:74","type":"urn:schemas-upnp-org:device:MediaRenderer:1","url":"http://172.22.114.136:9197/dmr"},"disabled_by":null,"discovery_keys":{},"domain":"dlna_dmr","entry_id":"d24486392bce9e68c96bb73e691681e4","minor_version":1,"modified_at":"1970-01-01T00:00:00+00:00","options":{},"pref_disable_new_entities":false,"pref_disable_polling":false,"source":"ignore","subentries":[],"title":"[TV] Samsung TV 1048 Right","unique_id":"uuid:c73ad4db-6241-496a-80fd-994a43d1c980","version":1},
|
||||
{"created_at":"1970-01-01T00:00:00+00:00","data":{"device_id":"uuid:5cac41f8-5fa8-4d42-9c30-8c8b3f6050b0","mac":"24:fc:e5:5a:ff:68","type":"urn:schemas-upnp-org:device:MediaRenderer:1","url":"http://172.22.114.134:9197/dmr"},"disabled_by":null,"discovery_keys":{},"domain":"dlna_dmr","entry_id":"e172a44f6bd05f602b6469920fdc3754","minor_version":1,"modified_at":"1970-01-01T00:00:00+00:00","options":{},"pref_disable_new_entities":false,"pref_disable_polling":false,"source":"ignore","subentries":[],"title":"[TV] Samsung TV 1048 Left","unique_id":"uuid:5cac41f8-5fa8-4d42-9c30-8c8b3f6050b0","version":1},
|
||||
{"created_at":"1970-01-01T00:00:00+00:00","data":{"device_id":"uuid:8e7e5e53-46a1-4974-9513-05270ad0af77","mac":"24:fc:e5:5b:17:6a","type":"urn:schemas-upnp-org:device:MediaRenderer:1","url":"http://172.22.114.135:9197/dmr"},"disabled_by":null,"discovery_keys":{},"domain":"dlna_dmr","entry_id":"b02ec81df7205825511646b73cbb58f8","minor_version":1,"modified_at":"1970-01-01T00:00:00+00:00","options":{},"pref_disable_new_entities":false,"pref_disable_polling":false,"source":"ignore","subentries":[],"title":"[TV] Samsung TV 1048 Center","unique_id":"uuid:8e7e5e53-46a1-4974-9513-05270ad0af77","version":1},
|
||||
{"created_at":"1970-01-01T00:00:00+00:00","data":{"host":"172.22.114.135","mac":"24:fc:e5:5b:17:6a","model":"UN65RU8000FXZA","ssdp_rendering_control_location":"http://172.22.114.135:9197/dmr"},"disabled_by":null,"discovery_keys":{},"domain":"samsungtv","entry_id":"6e73159a0788a6dbb3ccf4dc7a26945d","minor_version":1,"modified_at":"1970-01-01T00:00:00+00:00","options":{},"pref_disable_new_entities":false,"pref_disable_polling":false,"source":"ignore","subentries":[],"title":"Samsung TV 1048 Center (UN65RU8000FXZA)","unique_id":"89947ca0-b33a-4912-b659-f4b1578a7777","version":2},
|
||||
{"created_at":"1970-01-01T00:00:00+00:00","data":{"host":"172.22.114.134","mac":"24:fc:e5:5a:ff:68","model":"UN65RU8000FXZA","ssdp_rendering_control_location":"http://172.22.114.134:9197/dmr"},"disabled_by":null,"discovery_keys":{},"domain":"samsungtv","entry_id":"5c87c2ce60943c4288dfd80aa9db5bf0","minor_version":1,"modified_at":"1970-01-01T00:00:00+00:00","options":{},"pref_disable_new_entities":false,"pref_disable_polling":false,"source":"ignore","subentries":[],"title":"Samsung TV 1048 Left (UN65RU8000FXZA)","unique_id":"64bfe2c2-7b01-46b8-89d3-88c49373f55e","version":2},
|
||||
{"created_at":"1970-01-01T00:00:00+00:00","data":{},"disabled_by":null,"discovery_keys":{},"domain":"remote_homeassistant","entry_id":"0200f22cdff3cef1905acfee956d71cc","minor_version":1,"modified_at":"1970-01-01T00:00:00+00:00","options":{},"pref_disable_new_entities":false,"pref_disable_polling":false,"source":"ignore","subentries":[],"title":"Remote: Innovation Factory","unique_id":"311fbea761c143be9c14dc1cd5ab57b2","version":1},
|
||||
{"created_at":"1970-01-01T00:00:00+00:00","data":{},"disabled_by":null,"discovery_keys":{},"domain":"sun","entry_id":"0feafcef6c9ee4eb380cad7190b2f403","minor_version":1,"modified_at":"1970-01-01T00:00:00+00:00","options":{},"pref_disable_new_entities":false,"pref_disable_polling":false,"source":"import","subentries":[],"title":"Sun","unique_id":null,"version":1},
|
||||
{"created_at":"1970-01-01T00:00:00+00:00","data":{},"disabled_by":null,"discovery_keys":{},"domain":"octoprint","entry_id":"7a20eaee207e2873f5296d198ac8dd63","minor_version":1,"modified_at":"1970-01-01T00:00:00+00:00","options":{},"pref_disable_new_entities":false,"pref_disable_polling":false,"source":"ignore","subentries":[],"title":"OctoPrint Printer: 172.22.114.150","unique_id":"aef76126-1c40-459c-a460-ca7749222fdd","version":1},
|
||||
{"created_at":"1970-01-01T00:00:00+00:00","data":{"app_data":{"push_token":"filDMYq4Sa2akEe_BlkVyg:APA91bHPxz2u6XjWrUIErPeakuxA-_VTCZT5JVa0vD5gkpe0P65aZhDC0Q6uNRQWJ2nQ3a6xRjI7uaB0ywup_WwSQNd8AkTqQlObEPr0AWO6ditliwFuh0EQWGQzU1rYFKiz7gq8zhBW","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":"2024.4.1-full (12576)","cloudhook_url":"https://hooks.nabu.casa/gAAAAABnN30j6_p0rowd_9HuWevBUbbMdFXr9Ub-Aqyo61X-_ZMoLCPDdnG7ZlnR4r8qSmvgZ2zDH8XkmDcJVeF57oj5iA_wUWnbX1TZepV5VSYPG_sKXUfPR80HyqGNhpkUhSlASFrShwZPF8JIeGdD-Z8a31ttjQgKg2KlxEZuyZ4flV0pNXo=","device_id":"2ddef885064fb8ed","device_name":"lab-phone","manufacturer":"samsung","model":"SM-A546U1","os_name":"Android","os_version":"34","supports_encryption":false,"user_id":"5ef2c8c082b14074a6e84da694ef2f35","webhook_id":"541b6ac0d1f592acf1d6dc8ff736c2bad653ee1e85a8b6409348c1eb974c6ce7"},"disabled_by":null,"discovery_keys":{},"domain":"mobile_app","entry_id":"e3427a6f1a531d4647c57351962f3e1a","minor_version":1,"modified_at":"2024-11-15T16:56:04.075371+00:00","options":{},"pref_disable_new_entities":false,"pref_disable_polling":false,"source":"registration","subentries":[],"title":"lab-phone","unique_id":"io.homeassistant.companion.android-2ddef885064fb8ed","version":1},
|
||||
{"created_at":"1970-01-01T00:00:00+00:00","data":{"host":"172.22.113.18","mac":"70:09:71:0d:f6:5d","model":"UN50AU8000FXZA","ssdp_rendering_control_location":"http://172.22.113.18:9197/dmr"},"disabled_by":null,"discovery_keys":{},"domain":"samsungtv","entry_id":"54d38b0b04614f1d163c9ba14ce22fed","minor_version":1,"modified_at":"1970-01-01T00:00:00+00:00","options":{},"pref_disable_new_entities":false,"pref_disable_polling":false,"source":"ignore","subentries":[],"title":"Samsung AU8000 50 TV (UN50AU8000FXZA)","unique_id":"7671ae54-70ac-426b-8fa0-a065cd4bd428","version":2},
|
||||
{"created_at":"1970-01-01T00:00:00+00:00","data":{},"disabled_by":null,"discovery_keys":{},"domain":"mjpeg","entry_id":"f92d6ca163501ea7659047c60ba4e9e5","minor_version":1,"modified_at":"1970-01-01T00:00:00+00:00","options":{"authentication":"basic","mjpeg_url":"http://meyer:8080","password":"","still_image_url":null,"username":null,"verify_ssl":false},"pref_disable_new_entities":false,"pref_disable_polling":false,"source":"user","subentries":[],"title":"Crack Output","unique_id":null,"version":1},
|
||||
{"created_at":"1970-01-01T00:00:00+00:00","data":{"gen":2,"host":"192.168.1.152","model":"SNSW-001X15UL","port":80,"sleep_period":0},"disabled_by":null,"discovery_keys":{"zeroconf":[{"domain":"zeroconf","key":["_http._tcp.local.","shellyplus1-cc7b5c0d0eb4._http._tcp.local."],"version":1},{"domain":"zeroconf","key":["_shelly._tcp.local.","shellyplus1-cc7b5c0d0eb4._shelly._tcp.local."],"version":1}]},"domain":"shelly","entry_id":"04978edcf23c54a047e4f421779754ad","minor_version":1,"modified_at":"2025-03-11T14:17:43.900296+00:00","options":{"ble_scanner_mode":"passive"},"pref_disable_new_entities":false,"pref_disable_polling":false,"source":"user","subentries":[],"title":"Sheller Drum Enable","unique_id":"CC7B5C0D0EB4","version":1},
|
||||
{"created_at":"1970-01-01T00:00:00+00:00","data":{"gen":2,"host":"192.168.1.15","model":"SNSW-001X15UL","port":80,"sleep_period":0},"disabled_by":null,"discovery_keys":{"zeroconf":[{"domain":"zeroconf","key":["_http._tcp.local.","shellyplus1-cc7b5c0d316c._http._tcp.local."],"version":1},{"domain":"zeroconf","key":["_shelly._tcp.local.","shellyplus1-cc7b5c0d316c._shelly._tcp.local."],"version":1}]},"domain":"shelly","entry_id":"ce337fdb50b165d7ba080505d5c73343","minor_version":1,"modified_at":"2025-03-11T14:17:43.751327+00:00","options":{},"pref_disable_new_entities":false,"pref_disable_polling":false,"source":"user","subentries":[],"title":"Sheller Paddle Shaft Enable","unique_id":"CC7B5C0D316C","version":1},
|
||||
{"created_at":"1970-01-01T00:00:00+00:00","data":{"gen":2,"host":"192.168.1.222","model":"SNDM-00100WW","port":80,"sleep_period":0},"disabled_by":null,"discovery_keys":{"zeroconf":[{"domain":"zeroconf","key":["_http._tcp.local.","shellyplus010v-e86beae4d350._http._tcp.local."],"version":1},{"domain":"zeroconf","key":["_shelly._tcp.local.","shellyplus010v-e86beae4d350._shelly._tcp.local."],"version":1}]},"domain":"shelly","entry_id":"779bd6f1f6eebd9fb67b45fa40386e0c","minor_version":1,"modified_at":"2025-03-11T14:17:43.885641+00:00","options":{},"pref_disable_new_entities":false,"pref_disable_polling":false,"source":"user","subentries":[],"title":"Sheller Drum Velocity","unique_id":"E86BEAE4D350","version":1},
|
||||
{"created_at":"1970-01-01T00:00:00+00:00","data":{"gen":2,"host":"192.168.1.28","model":"SNDM-00100WW","sleep_period":0},"disabled_by":null,"discovery_keys":{"zeroconf":[{"domain":"zeroconf","key":["_http._tcp.local.","shellyplus010v-e86beae4df24._http._tcp.local."],"version":1},{"domain":"zeroconf","key":["_shelly._tcp.local.","shellyplus010v-e86beae4df24._shelly._tcp.local."],"version":1}]},"domain":"shelly","entry_id":"51355cd442e2d0c51a3a43811555ee77","minor_version":1,"modified_at":"2025-03-11T14:17:43.621631+00:00","options":{},"pref_disable_new_entities":false,"pref_disable_polling":false,"source":"user","subentries":[],"title":"Sheller Paddle Shaft Velocity","unique_id":"E86BEAE4DF24","version":1},
|
||||
{"created_at":"1970-01-01T00:00:00+00:00","data":{"alias":"TP-LINK_Power Strip_D7C1","connection_parameters":{"device_family":"IOT.SMARTPLUGSWITCH","encryption_type":"XOR","https":false},"host":"192.168.1.92","model":"HS300","uses_http":false},"disabled_by":null,"discovery_keys":{"dhcp":[{"domain":"dhcp","key":"98254af7d7c1","version":1}]},"domain":"tplink","entry_id":"37a922a368171d96e691a3439549d7bf","minor_version":5,"modified_at":"2025-01-07T17:42:09.326170+00:00","options":{},"pref_disable_new_entities":false,"pref_disable_polling":false,"source":"user","subentries":[],"title":"TP-LINK_Power Strip_D7C1 HS300(US)","unique_id":"98:25:4a:f7:d7:c1","version":1},
|
||||
{"created_at":"1970-01-01T00:00:00+00:00","data":{},"disabled_by":null,"discovery_keys":{},"domain":"switch_as_x","entry_id":"e49f29f5d0e10a3ef63369b4c8c7f5c2","minor_version":2,"modified_at":"1970-01-01T00:00:00+00:00","options":{"entity_id":"switch.tp_link_power_strip_d7c1_light_2","invert":false,"target_domain":"light"},"pref_disable_new_entities":false,"pref_disable_polling":false,"source":"user","subentries":[],"title":"Light","unique_id":null,"version":1},
|
||||
{"created_at":"1970-01-01T00:00:00+00:00","data":{},"disabled_by":null,"discovery_keys":{},"domain":"switch_as_x","entry_id":"77ef41d0bbf25d6007b4b2968dc60f58","minor_version":2,"modified_at":"1970-01-01T00:00:00+00:00","options":{"entity_id":"switch.tp_link_power_strip_d7c1_light_3","invert":false,"target_domain":"light"},"pref_disable_new_entities":false,"pref_disable_polling":false,"source":"user","subentries":[],"title":"Light","unique_id":null,"version":1},
|
||||
{"created_at":"1970-01-01T00:00:00+00:00","data":{},"disabled_by":null,"discovery_keys":{},"domain":"switch_as_x","entry_id":"54af0f5338887810d55839a908975bd3","minor_version":2,"modified_at":"1970-01-01T00:00:00+00:00","options":{"entity_id":"switch.tp_link_power_strip_d7c1_light_4","invert":false,"target_domain":"light"},"pref_disable_new_entities":false,"pref_disable_polling":false,"source":"user","subentries":[],"title":"Light ","unique_id":null,"version":1},
|
||||
{"created_at":"1970-01-01T00:00:00+00:00","data":{},"disabled_by":null,"discovery_keys":{},"domain":"group","entry_id":"7451d239431f2b6ea2ee2a1b85ab5c56","minor_version":1,"modified_at":"1970-01-01T00:00:00+00:00","options":{"all":false,"entities":["light.tp_link_power_strip_d7c1_light","light.tp_link_power_strip_d7c1_light_2","light.tp_link_power_strip_d7c1_light_3","light.tp_link_power_strip_d7c1_light_4"],"group_type":"light","hide_members":false,"name":"Conveyor Lights"},"pref_disable_new_entities":false,"pref_disable_polling":false,"source":"user","subentries":[],"title":"Conveyor Lights","unique_id":null,"version":1},
|
||||
{"created_at":"1970-01-01T00:00:00+00:00","data":{},"disabled_by":null,"discovery_keys":{},"domain":"switch_as_x","entry_id":"82a8d9eef25b35d19d72cd6ce5f9dcbe","minor_version":2,"modified_at":"1970-01-01T00:00:00+00:00","options":{"entity_id":"switch.tp_link_power_strip_d7c1_light","invert":false,"target_domain":"light"},"pref_disable_new_entities":false,"pref_disable_polling":false,"source":"user","subentries":[],"title":"Light ","unique_id":null,"version":1},
|
||||
{"created_at":"1970-01-01T00:00:00+00:00","data":{},"disabled_by":null,"discovery_keys":{},"domain":"switch_as_x","entry_id":"f1e37a42d2b569eaa4ac24c20a31fa24","minor_version":2,"modified_at":"1970-01-01T00:00:00+00:00","options":{"entity_id":"switch.tp_link_power_strip_d7c1_zima_board","invert":false,"target_domain":"light"},"pref_disable_new_entities":false,"pref_disable_polling":false,"source":"user","subentries":[],"title":"Vibratory Lights","unique_id":null,"version":1},
|
||||
{"created_at":"1970-01-01T00:00:00+00:00","data":{"gen":2,"host":"192.168.1.139","model":"SNSW-001P15UL","port":80,"sleep_period":0},"disabled_by":null,"discovery_keys":{"zeroconf":[{"domain":"zeroconf","key":["_http._tcp.local.","shellyplus1pm-c049ef8c7310._http._tcp.local."],"version":1},{"domain":"zeroconf","key":["_shelly._tcp.local.","shellyplus1pm-c049ef8c7310._shelly._tcp.local."],"version":1}]},"domain":"shelly","entry_id":"01J5NH0A41TYMRRGASE2QJ0SQ5","minor_version":2,"modified_at":"2025-03-10T20:01:44.621850+00:00","options":{},"pref_disable_new_entities":false,"pref_disable_polling":false,"source":"user","subentries":[],"title":"JC Feed","unique_id":"C049EF8C7310","version":1},
|
||||
{"created_at":"2024-09-30T14:21:49.515314+00:00","data":{},"disabled_by":null,"discovery_keys":{},"domain":"hassio","entry_id":"01J91MY5JBJH64GQS4WA5SYVK2","minor_version":1,"modified_at":"2024-09-30T14:21:49.515328+00:00","options":{},"pref_disable_new_entities":false,"pref_disable_polling":false,"source":"system","subentries":[],"title":"Supervisor","unique_id":"hassio","version":1},
|
||||
{"created_at":"2024-10-22T13:58:20.469378+00:00","data":{},"disabled_by":null,"discovery_keys":{},"domain":"browser_mod","entry_id":"01JAT8AZHNSF6WPATHNE5D6XM4","minor_version":1,"modified_at":"2024-10-22T13:58:20.469391+00:00","options":{},"pref_disable_new_entities":false,"pref_disable_polling":false,"source":"user","subentries":[],"title":"Browser Mod","unique_id":null,"version":2},
|
||||
{"created_at":"2024-11-06T18:40:57.619153+00:00","data":{"alias":"Vibratory Conveyor","connection_parameters":{"device_family":"IOT.SMARTPLUGSWITCH","encryption_type":"XOR","https":false},"host":"192.168.1.107","model":"KP115","uses_http":false},"disabled_by":null,"discovery_keys":{"dhcp":[{"domain":"dhcp","key":"54af970993f8","version":1}]},"domain":"tplink","entry_id":"01JC1CF88K7YTRGZT79CYQS536","minor_version":5,"modified_at":"2025-01-07T17:42:07.946491+00:00","options":{},"pref_disable_new_entities":false,"pref_disable_polling":false,"source":"dhcp","subentries":[],"title":"Blower Lights","unique_id":"54:af:97:09:93:f8","version":1},
|
||||
{"created_at":"2024-11-11T21:11:20.531392+00:00","data":{"gen":2,"host":"192.168.1.75","model":"SNSW-001X15UL","sleep_period":0},"disabled_by":null,"discovery_keys":{"zeroconf":[{"domain":"zeroconf","key":["_http._tcp.local.","ShellyPlus1-B8D61A87D2A8._http._tcp.local."],"version":1},{"domain":"zeroconf","key":["_http._tcp.local.","shellyplus1-b8d61a87d2a8._http._tcp.local."],"version":1},{"domain":"zeroconf","key":["_shelly._tcp.local.","shellyplus1-b8d61a87d2a8._shelly._tcp.local."],"version":1}]},"domain":"shelly","entry_id":"01JCEH26PKQ5WZ0GQ495DCG9WM","minor_version":2,"modified_at":"2025-03-10T20:01:44.645332+00:00","options":{"ble_scanner_mode":"disabled"},"pref_disable_new_entities":false,"pref_disable_polling":false,"source":"zeroconf","subentries":[],"title":"Bucket Elevator","unique_id":"B8D61A87D2A8","version":1},
|
||||
{"created_at":"2024-11-11T21:11:23.987940+00:00","data":{"gen":2,"host":"192.168.1.7","model":"SNSW-001X15UL","sleep_period":0},"disabled_by":null,"discovery_keys":{"zeroconf":[{"domain":"zeroconf","key":["_http._tcp.local.","ShellyPlus1-B8D61A8A7508._http._tcp.local."],"version":1},{"domain":"zeroconf","key":["_http._tcp.local.","shellyplus1-b8d61a8a7508._http._tcp.local."],"version":1},{"domain":"zeroconf","key":["_shelly._tcp.local.","shellyplus1-b8d61a8a7508._shelly._tcp.local."],"version":1}]},"domain":"shelly","entry_id":"01JCEH2A2K8Y9VEK9XV8ZPC69H","minor_version":2,"modified_at":"2025-03-10T20:01:44.614068+00:00","options":{},"pref_disable_new_entities":false,"pref_disable_polling":false,"source":"zeroconf","subentries":[],"title":"Sheller Feed","unique_id":"B8D61A8A7508","version":1},
|
||||
{"created_at":"2024-11-15T16:56:03.079266+00:00","data":{},"disabled_by":null,"discovery_keys":{},"domain":"cloud","entry_id":"01JCRC1M87G227CQKT0Z1J30HB","minor_version":1,"modified_at":"2024-11-15T16:56:03.079277+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-11-15T19:06:38.455479+00:00","data":{"api_key":"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","name":"FILLIS","skip_authentication":false},"disabled_by":null,"discovery_keys":{},"domain":"extended_openai_conversation","entry_id":"01JCRKGQZQRNH78WSFVRBFXK98","minor_version":1,"modified_at":"2024-11-18T20:51:09.471467+00:00","options":{"attach_username":false,"chat_model":"gpt-4o-mini","context_threshold":13000,"context_truncate_strategy":"clear","functions":"- spec:\n name: execute_services\n description: Use this function to execute service of devices in Home Assistant.\n parameters:\n type: object\n properties:\n list:\n type: array\n items:\n type: object\n properties:\n domain:\n type: string\n description: The domain of the service\n service:\n type: string\n description: The service to be called\n service_data:\n type: object\n description: The service data object to indicate what to control.\n properties:\n entity_id:\n type: string\n description: The entity_id retrieved from available devices. It\n must start with domain, followed by dot character.\n required:\n - entity_id\n required:\n - domain\n - service\n - service_data\n function:\n type: native\n name: execute_service","max_function_calls_per_conversation":2,"max_tokens":150,"prompt":"I want you to act as smart home manager of Home Assistant.\nI will provide information of smart home along with a question, you will truthfully make correction or answer using information provided in one sentence in everyday language.\n\nCurrent Time: {{now()}}\n\nAvailable Devices:\n```csv\nentity_id,name,state,aliases\n{% for entity in exposed_entities -%}\n{{ entity.entity_id }},{{ entity.name }},{{ entity.state }},{{entity.aliases | join('/')}}\n{% endfor -%}\n```\n\nThe current state of devices is provided in available devices.\nUse execute_services function only for requested action, not for current states.\nOnly ask for clarification before executing a task if the request is ambiguous.\nDo not restate or appreciate what user says, rather make a quick inquiry.","temperature":0.5,"top_p":1.0,"use_tools":false},"pref_disable_new_entities":false,"pref_disable_polling":false,"source":"user","subentries":[],"title":"FILLIS","unique_id":null,"version":1},
|
||||
{"created_at":"2024-11-18T15:35:15.370437+00:00","data":{},"disabled_by":null,"discovery_keys":{},"domain":"go2rtc","entry_id":"01JCZYKV5A7YB4FK4GZKHJ40FR","minor_version":1,"modified_at":"2024-11-18T15:35:15.370466+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-11-18T20:44:07.871851+00:00","data":{"gen":2,"host":"192.168.1.213","model":"SNDM-00100WW","sleep_period":0},"disabled_by":null,"discovery_keys":{"zeroconf":[{"domain":"zeroconf","key":["_http._tcp.local.","shellyplus010v-e86beae47374._http._tcp.local."],"version":1},{"domain":"zeroconf","key":["_shelly._tcp.local.","shellyplus010v-e86beae47374._shelly._tcp.local."],"version":1}]},"domain":"shelly","entry_id":"01JD0G9D9ZXBC64EXQHJXE9AEQ","minor_version":2,"modified_at":"2025-03-10T20:01:44.625876+00:00","options":{},"pref_disable_new_entities":false,"pref_disable_polling":false,"source":"zeroconf","subentries":[],"title":"JC Feed Rate","unique_id":"E86BEAE47374","version":1},
|
||||
{"created_at":"2024-11-20T15:58:18.232682+00:00","data":{},"disabled_by":null,"discovery_keys":{},"domain":"template","entry_id":"01JD54QFKRXGN1QSXE9X70JPAE","minor_version":1,"modified_at":"2024-11-20T15:58:18.232707+00:00","options":{"name":"Drum RPM Error","state":"{% set setpoint = states('input_number.sheller_drum_rpm') | float %}\n{% set current_rpm = states('sensor.shelling_machine_drum_rpm') | float %}\n{{ setpoint - current_rpm }}","template_type":"sensor","unit_of_measurement":"RPM"},"pref_disable_new_entities":false,"pref_disable_polling":false,"source":"user","subentries":[],"title":"Drum RPM Error","unique_id":null,"version":1},
|
||||
{"created_at":"2024-11-20T16:00:09.173007+00:00","data":{},"disabled_by":null,"discovery_keys":{},"domain":"derivative","entry_id":"01JD54TVYMSG343PEB5S3QEE5Q","minor_version":1,"modified_at":"2024-11-20T16:00:09.173032+00:00","options":{"name":"Drum RPM Error Derivative","round":2.0,"source":"sensor.drum_rpm_error","time_window":{"hours":0,"minutes":0,"seconds":0},"unit_time":"s"},"pref_disable_new_entities":false,"pref_disable_polling":false,"source":"user","subentries":[],"title":"Drum RPM Error Derivative","unique_id":null,"version":1},
|
||||
{"created_at":"2024-11-20T16:00:54.272576+00:00","data":{},"disabled_by":null,"discovery_keys":{},"domain":"integration","entry_id":"01JD54W800M5NH8WGPEGQR8Z4M","minor_version":1,"modified_at":"2024-11-20T17:13:04.246060+00:00","options":{"method":"trapezoidal","name":"Drum RPM Error Integral","round":2.0,"source":"sensor.drum_rpm_error","unit_time":"s"},"pref_disable_new_entities":false,"pref_disable_polling":false,"source":"user","subentries":[],"title":"Drum RPM Error Integral","unique_id":null,"version":1},
|
||||
{"created_at":"2024-11-25T19:54:08.723604+00:00","data":{},"disabled_by":null,"discovery_keys":{},"domain":"template","entry_id":"01JDJE6XEKP4RTRT04EWJXBKMC","minor_version":1,"modified_at":"2024-11-25T19:54:08.723627+00:00","options":{"name":"Paddle RPM Error","state":"{% set setpoint = states('input_number.sheller_paddle_rpm') | float %}\n{% set current_rpm = states('sensor.shelling_machine_paddle_rpm') | float %}\n{{ setpoint - current_rpm }}","template_type":"sensor"},"pref_disable_new_entities":false,"pref_disable_polling":false,"source":"user","subentries":[],"title":"Paddle RPM Error","unique_id":null,"version":1},
|
||||
{"created_at":"2025-02-12T17:45:59.457632+00:00","data":{"source":"CC:7B:5C:0D:0E:B6","source_config_entry_id":"04978edcf23c54a047e4f421779754ad","source_device_id":"98bc66339afcb5b0ee18bd93df9ab0f0","source_domain":"shelly","source_model":"SNSW-001X15UL"},"disabled_by":null,"discovery_keys":{},"domain":"bluetooth","entry_id":"01JKXM91D1BNRC2505EHYRESCW","minor_version":1,"modified_at":"2025-04-08T16:25:25.578111+00:00","options":{},"pref_disable_new_entities":false,"pref_disable_polling":false,"source":"integration_discovery","subentries":[],"title":"Sheller Drum Enable (CC:7B:5C:0D:0E:B4)","unique_id":"CC:7B:5C:0D:0E:B6","version":1},
|
||||
{"created_at":"2025-02-12T17:52:17.940035+00:00","data":{"token":"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"},"disabled_by":null,"discovery_keys":{},"domain":"hacs","entry_id":"01JKXMMK0K5E1EZJ90XJCAKPBB","minor_version":1,"modified_at":"2025-02-25T21:28:58.275201+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":"2025-02-19T15:40:35.874625+00:00","data":{},"disabled_by":null,"discovery_keys":{},"domain":"rfid_batches","entry_id":"01JMFDWF52FJ40CHPD6N1F3CQ2","minor_version":1,"modified_at":"2025-02-19T15:40:35.874647+00:00","options":{},"pref_disable_new_entities":false,"pref_disable_polling":false,"source":"ignore","subentries":[],"title":"RFID Batches","unique_id":"6E51E56D","version":1},
|
||||
{"created_at":"2025-02-20T19:16:00.924282+00:00","data":{},"disabled_by":null,"discovery_keys":{},"domain":"derivative","entry_id":"01JMJCKM8W4AC2KEN3RA72QVJ5","minor_version":1,"modified_at":"2025-02-20T20:21:23.307948+00:00","options":{"name":"JC Throughput Rate","round":2.0,"source":"input_number.jc_rolling_pecan_sum","time_window":{"hours":0,"minutes":0,"seconds":5},"unit_time":"s"},"pref_disable_new_entities":false,"pref_disable_polling":false,"source":"user","subentries":[],"title":"JC Throughput Rate","unique_id":null,"version":1},
|
||||
{"created_at":"2025-02-20T20:29:05.834521+00:00","data":{},"disabled_by":null,"discovery_keys":{},"domain":"template","entry_id":"01JMJGSEDAV5NQ29SSFY191FMH","minor_version":1,"modified_at":"2025-02-20T20:39:22.960233+00:00","options":{"name":"JC Filtered Pecan Rate","state":"{% set raw_value = states('sensor.raw_derivative_sensor') | float(0) %}\n{% set previous = states('sensor.smoothed_derivative_sensor') | float(0) %}\n{% set alpha = 0.3 %}\n \n{% if raw_value >= 0 %}\n {{ (alpha * raw_value) + ((1 - alpha) * previous) }}\n{% else %}\n {{ 0 }}\n{% endif %}","state_class":"measurement","template_type":"sensor","unit_of_measurement":"Pecans/s"},"pref_disable_new_entities":false,"pref_disable_polling":false,"source":"user","subentries":[],"title":"JC Filtered Pecan Rate","unique_id":null,"version":1},
|
||||
{"created_at":"2025-02-20T20:42:12.675265+00:00","data":{},"disabled_by":null,"discovery_keys":{},"domain":"template","entry_id":"01JMJHHET3YTENCCSCX905TPBK","minor_version":1,"modified_at":"2025-02-20T20:42:12.675294+00:00","options":{"name":"JC Pecan Rate Error","state":"{% set setpoint = states('number.jc_feedrate_setpoint') | float %}\n{% set current_rate = states('sensor.jc_filtered_pecan_rate') | float %}\n{{ current_rate - setpoint }}","template_type":"sensor"},"pref_disable_new_entities":false,"pref_disable_polling":false,"source":"user","subentries":[],"title":"JC Pecan Rate Error","unique_id":null,"version":1},
|
||||
{"created_at":"2025-03-08T19:26:10.104581+00:00","data":{},"disabled_by":null,"discovery_keys":{"zeroconf":[{"domain":"zeroconf","key":["_home-assistant._tcp.local.","Home._home-assistant._tcp.local."],"version":1}]},"domain":"remote_homeassistant","entry_id":"01JNVKHQ5R3524AF4YR7JR1NY5","minor_version":1,"modified_at":"2025-03-08T19:26:10.104609+00:00","options":{},"pref_disable_new_entities":false,"pref_disable_polling":false,"source":"ignore","subentries":[],"title":"Remote: Home","unique_id":"bc2f715b079245fa8e2ed0f159eddf29","version":1},
|
||||
{"created_at":"2025-03-08T22:30:06.708904+00:00","data":{},"disabled_by":null,"discovery_keys":{},"domain":"template","entry_id":"01JNVY2H3M56KRM6T10PQYW5PQ","minor_version":1,"modified_at":"2025-03-08T22:30:06.708934+00:00","options":{"name":"MoistTech Whole Moisture","state":"{% set values = states('sensor.whole_pecan_moisture') | default('0,0,0') | regex_findall('[\\\\d\\\\.\\\\-]+') %}\n{{ values[0] | float }}","template_type":"sensor","unit_of_measurement":"%"},"pref_disable_new_entities":false,"pref_disable_polling":false,"source":"user","subentries":[],"title":"MoistTech Whole Moisture","unique_id":null,"version":1},
|
||||
{"created_at":"2025-03-08T22:30:33.745961+00:00","data":{},"disabled_by":null,"discovery_keys":{},"domain":"template","entry_id":"01JNVY3BGHH66319YRTB3VDH82","minor_version":1,"modified_at":"2025-03-08T22:30:33.745987+00:00","options":{"name":"MoistTech Moisture Shell","state":"{% set values = states('sensor.whole_pecan_moisture') | default('0,0,0') | regex_findall('[\\\\d\\\\.\\\\-]+') %}\n{{ values[1] | float }}","template_type":"sensor","unit_of_measurement":"%"},"pref_disable_new_entities":false,"pref_disable_polling":false,"source":"user","subentries":[],"title":"MoistTech Moisture Shell","unique_id":null,"version":1},
|
||||
{"created_at":"2025-03-08T22:31:28.316229+00:00","data":{},"disabled_by":null,"discovery_keys":{},"domain":"template","entry_id":"01JNVY50SWV9FAMK7H3BTJW7KP","minor_version":1,"modified_at":"2025-03-08T22:31:28.316257+00:00","options":{"name":"MoistTech Kernel Moisture","state":"{% set values = states('sensor.whole_pecan_moisture') | default('0,0,0') | regex_findall('[\\\\d\\\\.\\\\-]+') %}\n{{ values[2] | float }}","template_type":"sensor","unit_of_measurement":"%"},"pref_disable_new_entities":false,"pref_disable_polling":false,"source":"user","subentries":[],"title":"MoistTech Kernel Moisture","unique_id":null,"version":1},
|
||||
{"created_at":"2025-03-10T20:45:49.983562+00:00","data":{},"disabled_by":null,"discovery_keys":{},"domain":"rfid_batches","entry_id":"01JP0WX10Z7ERWC9SAC548A5FP","minor_version":1,"modified_at":"2025-03-10T20:45:49.983576+00:00","options":{},"pref_disable_new_entities":false,"pref_disable_polling":false,"source":"ignore","subentries":[],"title":"RFID Batches","unique_id":"1ADE4304","version":1},
|
||||
{"created_at":"2025-03-11T13:48:28.798268+00:00","data":{"device_modification":{"attribute_modification":{"hw_version":null,"manufacturer":"ME&E","model":"JC","serial_number":null,"sw_version":null,"via_device_id":null},"device_id":"bd08feb9232d2cb49bb7742ac8a6bd13","device_name":"JC Cracker","entity_modification":{"entities":["f2132fd3b40e6062062421b5a7923067","177cd51d18be8978f0f3d3c03bd134e4","d6b01587f9fde6993eae701d85df4063","50f517b1e53905454a1ba663291b729c","6110c4cf3290251b791ac3175ba5302d","73830ed5b94ac16da41f23dcc0fef8f3","874e4cc6e772d9a0d2108f48ed8b9699","f5e4c06d08c43feb1cbebc59953d9cac","4e995cc33d0cacb73083be9cca041951","bb4a01566216cd351e20e219d612e636","adda4a1f4f0e15dd6990542bd6745645","95036a4e7dbf623a4c77bcb9af3776ba","5ffc341caf05e300ac095fdf698ba697","4e483e0723ab1f29bc6a94d655c81eef"]},"merge_modification":null,"modification_name":"JC Cracker Mod"}},"disabled_by":null,"discovery_keys":{},"domain":"device_tools","entry_id":"01JP2QDHQY8CAXX25PYRGQRD4E","minor_version":1,"modified_at":"2025-03-11T14:43:57.478239+00:00","options":{"device_modification":{"attribute_modification":{"hw_version":null,"manufacturer":"ME&E","model":"JC","serial_number":null,"sw_version":null,"via_device_id":null},"device_id":"bd08feb9232d2cb49bb7742ac8a6bd13","device_name":"JC Cracker","entity_modification":{"entities":["f2132fd3b40e6062062421b5a7923067","177cd51d18be8978f0f3d3c03bd134e4","d6b01587f9fde6993eae701d85df4063","50f517b1e53905454a1ba663291b729c","6110c4cf3290251b791ac3175ba5302d","73830ed5b94ac16da41f23dcc0fef8f3","874e4cc6e772d9a0d2108f48ed8b9699","f5e4c06d08c43feb1cbebc59953d9cac","4e995cc33d0cacb73083be9cca041951","bb4a01566216cd351e20e219d612e636","adda4a1f4f0e15dd6990542bd6745645","95036a4e7dbf623a4c77bcb9af3776ba","5ffc341caf05e300ac095fdf698ba697","4e483e0723ab1f29bc6a94d655c81eef"]},"merge_modification":null,"modification_name":"JC Cracker Mod"}},"pref_disable_new_entities":false,"pref_disable_polling":false,"source":"user","subentries":[],"title":"JC Cracker Mod","unique_id":"JC Cracker Mod","version":1},
|
||||
{"created_at":"2025-03-11T14:12:49.100627+00:00","data":{"device_modification":{"attribute_modification":null,"device_id":"7fb5d449ee7e9b4b92969c8006ef3f8d","device_name":"Meyer Cracker","entity_modification":{"entities":["d18878bb83949542c3c0fa3b67c8a577","162d5c89569e4fac2fa1fc994ec82e4e","3a9812ef5656d9c3cfc3759dfe9dcda0","f2c8299058756338f530143024f54206","c357504fff9a1920c4dc8a876d864cb3","279277d53631d3556b3aacb5d08ba9c2"]},"merge_modification":null,"modification_name":"Meyer Cracker Mod"}},"disabled_by":null,"discovery_keys":{},"domain":"device_tools","entry_id":"01JP2RT3TC42WQMQX86W8PBYGR","minor_version":1,"modified_at":"2025-03-11T14:42:37.662757+00:00","options":{"device_modification":{"attribute_modification":null,"device_id":"7fb5d449ee7e9b4b92969c8006ef3f8d","device_name":"Meyer Cracker","entity_modification":{"entities":["d18878bb83949542c3c0fa3b67c8a577","162d5c89569e4fac2fa1fc994ec82e4e","3a9812ef5656d9c3cfc3759dfe9dcda0","f2c8299058756338f530143024f54206","c357504fff9a1920c4dc8a876d864cb3","279277d53631d3556b3aacb5d08ba9c2"]},"merge_modification":null,"modification_name":"Meyer Cracker Mod"}},"pref_disable_new_entities":false,"pref_disable_polling":false,"source":"user","subentries":[],"title":"Meyer Cracker Mod","unique_id":"Meyer Cracker Mod","version":1},
|
||||
{"created_at":"2025-03-11T14:15:19.168578+00:00","data":{"device_modification":{"attribute_modification":null,"device_id":"ac5aeecdb81f624f67d67ca7cd25d696","device_name":"Sheller Machine","entity_modification":{"entities":["2cd4708456a3e3c5d5b3707932710e28","efb0d43f0df4c3808b5618515f541cb2","c0cbbd4924f42c0c46871a488926eda1","a2f91fbc44eb6159ee0469a3632a7052","65fcc6ffbfb2be888ad0056cb8753788","8ed34b95c20b69e1ade8b05baae878c5","5a9599d2c47a92f788ce46aa28c791a7","772136a67a3a1a1c1a68b3912e3437ff","dd00b743411562e281695d49f5c9b578","10246760e6f40898d03630c23f9bfa5f"]},"merge_modification":{"devices":[]},"modification_name":"Sheller Machine Mod"}},"disabled_by":null,"discovery_keys":{},"domain":"device_tools","entry_id":"01JP2RYPC08P80S3KGJGNQTG5B","minor_version":1,"modified_at":"2025-03-11T14:39:46.389364+00:00","options":{"device_modification":{"attribute_modification":null,"device_id":"ac5aeecdb81f624f67d67ca7cd25d696","device_name":"Sheller Machine","entity_modification":{"entities":["2cd4708456a3e3c5d5b3707932710e28","efb0d43f0df4c3808b5618515f541cb2","c0cbbd4924f42c0c46871a488926eda1","a2f91fbc44eb6159ee0469a3632a7052","65fcc6ffbfb2be888ad0056cb8753788","8ed34b95c20b69e1ade8b05baae878c5","5a9599d2c47a92f788ce46aa28c791a7","772136a67a3a1a1c1a68b3912e3437ff","dd00b743411562e281695d49f5c9b578","10246760e6f40898d03630c23f9bfa5f"]},"merge_modification":{"devices":[]},"modification_name":"Sheller Machine Mod"}},"pref_disable_new_entities":false,"pref_disable_polling":false,"source":"user","subentries":[],"title":"Sheller Machine Mod","unique_id":"Sheller Machine Mod","version":1},
|
||||
{"created_at":"2025-04-08T16:25:33.648397+00:00","data":{},"disabled_by":null,"discovery_keys":{},"domain":"backup","entry_id":"01JRB3H9PGGBS0Y87YRVZFBN7S","minor_version":1,"modified_at":"2025-04-08T16:25:33.648399+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-04-17T15:56:20.036958+00:00","data":{},"disabled_by":null,"discovery_keys":{},"domain":"bluetooth","entry_id":"01JS27E8649HW777MJ0KF33QA2","minor_version":1,"modified_at":"2025-04-17T15:56:57.281299+00:00","options":{"passive":true},"pref_disable_new_entities":false,"pref_disable_polling":false,"source":"integration_discovery","subentries":[],"title":"Realtek Bluetooth Radio (58:10:31:E7:7C:01)","unique_id":"58:10:31:E7:7C:01","version":1},
|
||||
{"created_at":"2025-04-17T15:57:10.033911+00:00","data":{},"disabled_by":null,"discovery_keys":{"bluetooth":[{"domain":"bluetooth","key":"FB:AC:C2:D9:08:D7","version":1}]},"domain":"ibeacon","entry_id":"01JS27FS0HQ474CTAVCV1DQ60H","minor_version":1,"modified_at":"2025-04-17T15:57:10.033912+00:00","options":{},"pref_disable_new_entities":false,"pref_disable_polling":false,"source":"bluetooth","subentries":[],"title":"iBeacon Tracker","unique_id":null,"version":1},
|
||||
{"created_at":"2025-04-17T19:42:42.404412+00:00","data":{},"disabled_by":null,"discovery_keys":{},"domain":"rfid_batches","entry_id":"01JS2MCR74A3D7PYB2M2S8ZA3D","minor_version":1,"modified_at":"2025-04-17T19:42:42.404413+00:00","options":{},"pref_disable_new_entities":false,"pref_disable_polling":false,"source":"ignore","subentries":[],"title":"RFID Batches","unique_id":"810112472622","version":1},
|
||||
{"created_at":"2025-04-17T19:57:08.407019+00:00","data":{"user":"7e6db422e3a54f0982d4375af0f6074d"},"disabled_by":null,"discovery_keys":{},"domain":"voip","entry_id":"01JS2N75XQZ4PBCK7CDCZ1X2KP","minor_version":1,"modified_at":"2025-04-17T19:57:14.572733+00:00","options":{"sip_port":5060},"pref_disable_new_entities":false,"pref_disable_polling":false,"source":"user","subentries":[],"title":"Voice over IP","unique_id":null,"version":1},
|
||||
{"created_at":"2025-04-17T21:32:55.104514+00:00","data":{"host":"192.168.1.1","location":"http://192.168.1.1:34943/rootDesc.xml","mac_address":"f4:e2:c6:e4:d2:f6","original_udn":"uuid:8d6c30fd-0475-44b8-9c12-466689f35e58","st":"urn:schemas-upnp-org:device:InternetGatewayDevice:2","udn":"uuid:8d6c30fd-0475-44b8-9c12-466689f35e58"},"disabled_by":null,"discovery_keys":{"ssdp":[{"domain":"ssdp","key":"uuid:8d6c30fd-0475-44b8-9c12-466689f35e58","version":1}]},"domain":"upnp","entry_id":"01JS2TPHY0WM1EHSFM6PP3N93A","minor_version":1,"modified_at":"2025-04-17T21:32:55.104515+00:00","options":{"force_poll":false},"pref_disable_new_entities":false,"pref_disable_polling":false,"source":"ssdp","subentries":[],"title":"UniFi NeXt-Gen Gateway","unique_id":"uuid:8d6c30fd-0475-44b8-9c12-466689f35e58::urn:schemas-upnp-org:device:InternetGatewayDevice:2","version":1},
|
||||
{"created_at":"2025-04-17T21:33:11.568575+00:00","data":{},"disabled_by":null,"discovery_keys":{},"domain":"rfid_batches","entry_id":"01JS2TQ20GKXGYZ7JEGGG77H56","minor_version":1,"modified_at":"2025-04-17T21:33:11.568576+00:00","options":{},"pref_disable_new_entities":false,"pref_disable_polling":false,"source":"ignore","subentries":[],"title":"RFID Batches","unique_id":"723430","version":1},
|
||||
{"created_at":"2025-04-17T21:33:14.176498+00:00","data":{},"disabled_by":null,"discovery_keys":{},"domain":"rfid_batches","entry_id":"01JS2TQ4J0P53CKM97DHMTCFM0","minor_version":1,"modified_at":"2025-04-17T21:33:14.176499+00:00","options":{},"pref_disable_new_entities":false,"pref_disable_polling":false,"source":"ignore","subentries":[],"title":"RFID Batches","unique_id":"732159","version":1},
|
||||
{"created_at":"2025-04-17T21:33:23.136423+00:00","data":{},"disabled_by":null,"discovery_keys":{},"domain":"rfid_batches","entry_id":"01JS2TQDA0RBZBDCTM7K6WPP5J","minor_version":1,"modified_at":"2025-04-17T21:33:23.136424+00:00","options":{},"pref_disable_new_entities":false,"pref_disable_polling":false,"source":"ignore","subentries":[],"title":"RFID Batches","unique_id":"1B983A04","version":1},
|
||||
{"created_at":"2025-04-17T21:33:24.624344+00:00","data":{},"disabled_by":null,"discovery_keys":{},"domain":"rfid_batches","entry_id":"01JS2TQERG25GFR7SNPNS1MT3H","minor_version":1,"modified_at":"2025-04-17T21:33:24.624345+00:00","options":{},"pref_disable_new_entities":false,"pref_disable_polling":false,"source":"ignore","subentries":[],"title":"RFID Batches","unique_id":"1145FB04","version":1},
|
||||
{"created_at":"2025-04-17T21:33:27.232648+00:00","data":{},"disabled_by":null,"discovery_keys":{},"domain":"rfid_batches","entry_id":"01JS2TQHA0E4YGP66KZFCF389C","minor_version":1,"modified_at":"2025-04-17T21:33:27.232649+00:00","options":{},"pref_disable_new_entities":false,"pref_disable_polling":false,"source":"ignore","subentries":[],"title":"RFID Batches","unique_id":"500033","version":1},
|
||||
{"created_at":"2025-04-17T21:33:28.592388+00:00","data":{},"disabled_by":null,"discovery_keys":{},"domain":"rfid_batches","entry_id":"01JS2TQJMGVXHYK65EQNZN18C6","minor_version":1,"modified_at":"2025-04-17T21:33:28.592389+00:00","options":{},"pref_disable_new_entities":false,"pref_disable_polling":false,"source":"ignore","subentries":[],"title":"RFID Batches","unique_id":"500021","version":1},
|
||||
{"created_at":"2025-04-17T21:33:30.800513+00:00","data":{},"disabled_by":null,"discovery_keys":{},"domain":"rfid_batches","entry_id":"01JS2TQMSGNZ3G13Q16GXK3SFC","minor_version":1,"modified_at":"2025-04-17T21:33:30.800514+00:00","options":{},"pref_disable_new_entities":false,"pref_disable_polling":false,"source":"ignore","subentries":[],"title":"RFID Batches","unique_id":"043a981b6f6180","version":1},
|
||||
{"created_at":"2025-04-17T21:33:32.368316+00:00","data":{},"disabled_by":null,"discovery_keys":{},"domain":"rfid_batches","entry_id":"01JS2TQPAGWAP6KHMVJYC0W8BN","minor_version":1,"modified_at":"2025-04-17T21:33:32.368317+00:00","options":{},"pref_disable_new_entities":false,"pref_disable_polling":false,"source":"ignore","subentries":[],"title":"RFID Batches","unique_id":"0443de1a6f6180","version":1},
|
||||
{"created_at":"2025-04-17T21:33:34.008690+00:00","data":{},"disabled_by":null,"discovery_keys":{},"domain":"rfid_batches","entry_id":"01JS2TQQXR29E050QN9TZQD3AV","minor_version":1,"modified_at":"2025-04-17T21:33:34.008692+00:00","options":{},"pref_disable_new_entities":false,"pref_disable_polling":false,"source":"ignore","subentries":[],"title":"RFID Batches","unique_id":"04e56c11bb2a81","version":1},
|
||||
{"created_at":"2025-04-17T21:33:35.488279+00:00","data":{},"disabled_by":null,"discovery_keys":{},"domain":"rfid_batches","entry_id":"01JS2TQSC0GMMT7GP8Z2D2M998","minor_version":1,"modified_at":"2025-04-17T21:33:35.488280+00:00","options":{},"pref_disable_new_entities":false,"pref_disable_polling":false,"source":"ignore","subentries":[],"title":"RFID Batches","unique_id":"10F61E04","version":1},
|
||||
{"created_at":"2025-04-17T21:33:36.857493+00:00","data":{},"disabled_by":null,"discovery_keys":{},"domain":"rfid_batches","entry_id":"01JS2TQTPSQKFSDZX6JRAER67E","minor_version":1,"modified_at":"2025-04-17T21:33:36.857494+00:00","options":{},"pref_disable_new_entities":false,"pref_disable_polling":false,"source":"ignore","subentries":[],"title":"RFID Batches","unique_id":"041ef610bb2a81","version":1},
|
||||
{"created_at":"2025-04-17T21:33:38.440624+00:00","data":{},"disabled_by":null,"discovery_keys":{},"domain":"rfid_batches","entry_id":"01JS2TQW88QP7XAK0158CHX2MJ","minor_version":1,"modified_at":"2025-04-17T21:33:38.440625+00:00","options":{},"pref_disable_new_entities":false,"pref_disable_polling":false,"source":"ignore","subentries":[],"title":"RFID Batches","unique_id":"10f61e04","version":1},
|
||||
{"created_at":"2025-04-17T21:33:39.816285+00:00","data":{},"disabled_by":null,"discovery_keys":{},"domain":"rfid_batches","entry_id":"01JS2TQXK8MN52BQ9BKFBGZ8XV","minor_version":1,"modified_at":"2025-04-17T21:33:39.816286+00:00","options":{},"pref_disable_new_entities":false,"pref_disable_polling":false,"source":"ignore","subentries":[],"title":"RFID Batches","unique_id":"500032","version":1},
|
||||
{"created_at":"2025-04-17T21:33:41.320433+00:00","data":{},"disabled_by":null,"discovery_keys":{},"domain":"rfid_batches","entry_id":"01JS2TQZ28C3Q08VZ5QK3R5756","minor_version":1,"modified_at":"2025-04-17T21:33:41.320434+00:00","options":{},"pref_disable_new_entities":false,"pref_disable_polling":false,"source":"ignore","subentries":[],"title":"RFID Batches","unique_id":"1A4E4B04","version":1},
|
||||
{"created_at":"2025-04-17T21:33:42.616562+00:00","data":{},"disabled_by":null,"discovery_keys":{},"domain":"rfid_batches","entry_id":"01JS2TR0ARGD97RSD6MRAF8JX4","minor_version":1,"modified_at":"2025-04-17T21:33:42.616563+00:00","options":{},"pref_disable_new_entities":false,"pref_disable_polling":false,"source":"ignore","subentries":[],"title":"RFID Batches","unique_id":"500004","version":1},
|
||||
{"created_at":"2025-04-17T21:33:44.040385+00:00","data":{},"disabled_by":null,"discovery_keys":{},"domain":"rfid_batches","entry_id":"01JS2TR1Q8WM1GCQFKMREK14ZH","minor_version":1,"modified_at":"2025-04-17T21:33:44.040386+00:00","options":{},"pref_disable_new_entities":false,"pref_disable_polling":false,"source":"ignore","subentries":[],"title":"RFID Batches","unique_id":"500005","version":1},
|
||||
{"created_at":"2025-04-17T21:33:45.472517+00:00","data":{},"disabled_by":null,"discovery_keys":{},"domain":"rfid_batches","entry_id":"01JS2TR340Z650GGJP852RV0ZD","minor_version":1,"modified_at":"2025-04-17T21:33:45.472518+00:00","options":{},"pref_disable_new_entities":false,"pref_disable_polling":false,"source":"ignore","subentries":[],"title":"RFID Batches","unique_id":"500001","version":1},
|
||||
{"created_at":"2025-04-17T21:33:47.088485+00:00","data":{},"disabled_by":null,"discovery_keys":{},"domain":"rfid_batches","entry_id":"01JS2TR4PG97Q2527YW6RNN4Q2","minor_version":1,"modified_at":"2025-04-17T21:33:47.088486+00:00","options":{},"pref_disable_new_entities":false,"pref_disable_polling":false,"source":"ignore","subentries":[],"title":"RFID Batches","unique_id":"500012","version":1},
|
||||
{"created_at":"2025-04-17T21:33:48.608462+00:00","data":{},"disabled_by":null,"discovery_keys":{},"domain":"rfid_batches","entry_id":"01JS2TR6601PPQHECZJVSYFB5P","minor_version":1,"modified_at":"2025-04-17T21:33:48.608463+00:00","options":{},"pref_disable_new_entities":false,"pref_disable_polling":false,"source":"ignore","subentries":[],"title":"RFID Batches","unique_id":"500011","version":1},
|
||||
{"created_at":"2025-04-17T21:33:50.040439+00:00","data":{},"disabled_by":null,"discovery_keys":{},"domain":"rfid_batches","entry_id":"01JS2TR7JRZKJXRZRC63T45HWD","minor_version":1,"modified_at":"2025-04-17T21:33:50.040440+00:00","options":{},"pref_disable_new_entities":false,"pref_disable_polling":false,"source":"ignore","subentries":[],"title":"RFID Batches","unique_id":"500002","version":1},
|
||||
{"created_at":"2025-04-17T21:33:51.296396+00:00","data":{},"disabled_by":null,"discovery_keys":{},"domain":"rfid_batches","entry_id":"01JS2TR8T0SFRG9ED3C172MDP2","minor_version":1,"modified_at":"2025-04-17T21:33:51.296398+00:00","options":{},"pref_disable_new_entities":false,"pref_disable_polling":false,"source":"ignore","subentries":[],"title":"RFID Batches","unique_id":"17D18B04","version":1},
|
||||
{"created_at":"2025-04-17T21:33:52.856555+00:00","data":{},"disabled_by":null,"discovery_keys":{},"domain":"rfid_batches","entry_id":"01JS2TRAARAK274K6XCC26415A","minor_version":1,"modified_at":"2025-04-17T21:33:52.856556+00:00","options":{},"pref_disable_new_entities":false,"pref_disable_polling":false,"source":"ignore","subentries":[],"title":"RFID Batches","unique_id":"730494","version":1},
|
||||
{"created_at":"2025-04-17T21:33:54.160598+00:00","data":{},"disabled_by":null,"discovery_keys":{},"domain":"rfid_batches","entry_id":"01JS2TRBKG38583WTPDVRG0Q67","minor_version":1,"modified_at":"2025-04-17T21:33:54.160599+00:00","options":{},"pref_disable_new_entities":false,"pref_disable_polling":false,"source":"ignore","subentries":[],"title":"RFID Batches","unique_id":"732981","version":1},
|
||||
{"created_at":"2025-04-17T21:33:55.568345+00:00","data":{},"disabled_by":null,"discovery_keys":{},"domain":"rfid_batches","entry_id":"01JS2TRCZGCP0AYDHYJV7SE6DN","minor_version":1,"modified_at":"2025-04-17T21:33:55.568346+00:00","options":{},"pref_disable_new_entities":false,"pref_disable_polling":false,"source":"ignore","subentries":[],"title":"RFID Batches","unique_id":"740351","version":1},
|
||||
{"created_at":"2025-04-17T21:33:57.024553+00:00","data":{},"disabled_by":null,"discovery_keys":{},"domain":"rfid_batches","entry_id":"01JS2TRED04QM3WRVZ32M081MW","minor_version":1,"modified_at":"2025-04-17T21:33:57.024555+00:00","options":{},"pref_disable_new_entities":false,"pref_disable_polling":false,"source":"ignore","subentries":[],"title":"RFID Batches","unique_id":"739763","version":1},
|
||||
{"created_at":"2025-04-30T17:29:58.664979+00:00","data":{"app_data":{"push_token":"evqH9xXBckNTlIiTPSjtBY:APA91bGOQ_hCyWDl2oEfgFcpOnCF_kciPJodnJ2uBUxSIDIoCjew5281_MOSag3Cs0Jl1ongRwDy3-fA-RmIMQEpWSfHk8K0PPPMisKhk0vt-AUkTQ0z6E0","push_url":"https://mobile-apps.home-assistant.io/api/sendPushNotification"},"app_id":"io.robbie.HomeAssistant","app_name":"Home Assistant","app_version":"2025.2 (2025.1178)","cloudhook_url":"https://hooks.nabu.casa/gAAAAABoEl4WK4dZP79V7lIO5-2iPtGyTUJBxH9pnlgwl086ztO6CogOHo2_VJPyGKGqF2r-98cTXFEzB854UtsDoJQzn-x8PP6dG9Ziz04LRwBWBQu9KN0fi1aFUNNri9jz7H2jjAKvkHsONDU5cADNvm-8NxiFgDT6U-e_kU6jhfug_LYak9I=","device_id":"98A9293F-CB61-45EC-B9B6-192CF169CDAD","device_name":"Factory’s iPad","manufacturer":"Apple","model":"iPad12,1","no_legacy_encryption":true,"os_name":"iPadOS","os_version":"15.5","secret":"ff886fbce70b2431093a0913ec13393608930b3521e9bafe27761be28655fee3","supports_encryption":true,"user_id":"5ef2c8c082b14074a6e84da694ef2f35","webhook_id":"dd11c41f93a1ec05751fb1b2008d247fc3895548b22b21ddb7d013a02475264b"},"disabled_by":null,"discovery_keys":{},"domain":"mobile_app","entry_id":"01JT3VZ248SG5C78NYX10R3VYC","minor_version":1,"modified_at":"2025-04-30T17:44:59.821405+00:00","options":{},"pref_disable_new_entities":false,"pref_disable_polling":false,"source":"registration","subentries":[],"title":"Factory’s iPad","unique_id":"io.robbie.HomeAssistant-98A9293F-CB61-45EC-B9B6-192CF169CDAD","version":1}
|
||||
]
|
||||
}
|
||||
}
|
|
@ -1,46 +1,89 @@
|
|||
{
|
||||
"version": 1,
|
||||
"minor_version": 8,
|
||||
"minor_version": 9,
|
||||
"key": "core.device_registry",
|
||||
"data": {
|
||||
"devices": [
|
||||
{"area_id":null,"config_entries":["c0c49687c85dee58eea3dbb414dd9337"],"configuration_url":null,"connections":[["mac","60:01:94:cf:79:15"]],"created_at":"1970-01-01T00:00:00+00:00","disabled_by":null,"entry_type":null,"hw_version":null,"id":"130c399e6b93586793dfd365ea5b3ebc","identifiers":[],"labels":[],"manufacturer":"Espressif","model":"esp01_1m","model_id":null,"modified_at":"1970-01-01T00:00:00+00:00","name_by_user":null,"name":"jc-vibratory","primary_config_entry":"c0c49687c85dee58eea3dbb414dd9337","serial_number":null,"sw_version":"2023.7.1 (Jan 18 2024, 19:34:19)","via_device_id":null},
|
||||
{"area_id":null,"config_entries":["55db3b46f3bf75777e4779fd25ed6bca"],"configuration_url":"https://172.22.114.176/protect/devices/65ba9b25013a7903e400256f","connections":[["mac","e4:38:83:0f:d1:b8"]],"created_at":"1970-01-01T00:00:00+00:00","disabled_by":null,"entry_type":null,"hw_version":null,"id":"a00c8648172b02bb56e52bd1083bc8b5","identifiers":[],"labels":[],"manufacturer":"Ubiquiti","model":"UVC G3 Flex","model_id":null,"modified_at":"1970-01-01T00:00:00+00:00","name_by_user":null,"name":"G3 Flex 01","primary_config_entry":"55db3b46f3bf75777e4779fd25ed6bca","serial_number":null,"sw_version":"4.71.147","via_device_id":"437dbead96a87ac711c7a4d99a79c6cb"},
|
||||
{"area_id":null,"config_entries":["55db3b46f3bf75777e4779fd25ed6bca"],"configuration_url":"https://172.22.114.176","connections":[["mac","70:a7:41:a5:3e:33"]],"created_at":"1970-01-01T00:00:00+00:00","disabled_by":null,"entry_type":null,"hw_version":null,"id":"437dbead96a87ac711c7a4d99a79c6cb","identifiers":[["unifiprotect","70A741A53E33"]],"labels":[],"manufacturer":"Ubiquiti","model":"UCK-G2-PLUS","model_id":null,"modified_at":"2024-09-30T14:21:39.083371+00:00","name_by_user":null,"name":"Cloud Key Gen2 Plus","primary_config_entry":"55db3b46f3bf75777e4779fd25ed6bca","serial_number":null,"sw_version":"5.0.34","via_device_id":null},
|
||||
{"area_id":null,"config_entries":["0feafcef6c9ee4eb380cad7190b2f403"],"configuration_url":null,"connections":[],"created_at":"1970-01-01T00:00:00+00:00","disabled_by":null,"entry_type":"service","hw_version":null,"id":"837a78425d2847cdcbe0b38f7d05cb7b","identifiers":[["sun","0feafcef6c9ee4eb380cad7190b2f403"]],"labels":[],"manufacturer":null,"model":null,"model_id":null,"modified_at":"1970-01-01T00:00:00+00:00","name_by_user":null,"name":"Sun","primary_config_entry":"0feafcef6c9ee4eb380cad7190b2f403","serial_number":null,"sw_version":null,"via_device_id":null},
|
||||
{"area_id":null,"config_entries":["143eb40c5189f32be0eddf773eaaeceb"],"configuration_url":null,"connections":[],"created_at":"1970-01-01T00:00:00+00:00","disabled_by":null,"entry_type":null,"hw_version":null,"id":"d96ab4ca2878dfd648ac7c530f4d0268","identifiers":[["mqtt","steinlite-moisture-meter"]],"labels":[],"manufacturer":"Steinlite","model":"SB900","model_id":null,"modified_at":"1970-01-01T00:00:00+00:00","name_by_user":null,"name":"Moisture Meter","primary_config_entry":"143eb40c5189f32be0eddf773eaaeceb","serial_number":null,"sw_version":null,"via_device_id":null},
|
||||
{"area_id":null,"config_entries":["143eb40c5189f32be0eddf773eaaeceb"],"configuration_url":null,"connections":[],"created_at":"1970-01-01T00:00:00+00:00","disabled_by":null,"entry_type":null,"hw_version":null,"id":"682585d3e49461db68dfce310711e240","identifiers":[["mqtt","sheller-scale"]],"labels":[],"manufacturer":"Adam Equipment","model":"CPWplus 15","model_id":null,"modified_at":"1970-01-01T00:00:00+00:00","name_by_user":null,"name":"Sheller Scale","primary_config_entry":"143eb40c5189f32be0eddf773eaaeceb","serial_number":null,"sw_version":null,"via_device_id":null},
|
||||
{"area_id":null,"config_entries":["143eb40c5189f32be0eddf773eaaeceb"],"configuration_url":null,"connections":[],"created_at":"1970-01-01T00:00:00+00:00","disabled_by":null,"entry_type":null,"hw_version":null,"id":"704baf0c713921ab159af1306f741d87","identifiers":[["mqtt","precision-scale"]],"labels":[],"manufacturer":"U.S. Solid","model":"USS-DBS87-310G","model_id":null,"modified_at":"1970-01-01T00:00:00+00:00","name_by_user":null,"name":"Precision Scale","primary_config_entry":"143eb40c5189f32be0eddf773eaaeceb","serial_number":null,"sw_version":null,"via_device_id":null},
|
||||
{"area_id":null,"config_entries":["e3427a6f1a531d4647c57351962f3e1a"],"configuration_url":null,"connections":[],"created_at":"1970-01-01T00:00:00+00:00","disabled_by":null,"entry_type":null,"hw_version":null,"id":"9b53abd12d274cfc0a304b0b15ebbd39","identifiers":[["mobile_app","2ddef885064fb8ed"]],"labels":[],"manufacturer":"samsung","model":"SM-A546U1","model_id":null,"modified_at":"1970-01-01T00:00:00+00:00","name_by_user":null,"name":"lab-phone","primary_config_entry":"e3427a6f1a531d4647c57351962f3e1a","serial_number":null,"sw_version":"34","via_device_id":null},
|
||||
{"area_id":null,"config_entries":["143eb40c5189f32be0eddf773eaaeceb"],"configuration_url":null,"connections":[],"created_at":"1970-01-01T00:00:00+00:00","disabled_by":null,"entry_type":null,"hw_version":null,"id":"feec748cc156a1ca441d38caf620ecfe","identifiers":[["mqtt","moisture-station-scanner"]],"labels":[],"manufacturer":"Netum","model":"C300-HF","model_id":null,"modified_at":"1970-01-01T00:00:00+00:00","name_by_user":null,"name":"RFID Tag Scanner","primary_config_entry":"143eb40c5189f32be0eddf773eaaeceb","serial_number":null,"sw_version":null,"via_device_id":null},
|
||||
{"area_id":null,"config_entries":["143eb40c5189f32be0eddf773eaaeceb"],"configuration_url":null,"connections":[],"created_at":"1970-01-01T00:00:00+00:00","disabled_by":null,"entry_type":null,"hw_version":null,"id":"c9fd6d12a759980d491da079f9e3a545","identifiers":[["mqtt","scale-station-scanner"]],"labels":[],"manufacturer":"Sony","model":"RC-S380","model_id":null,"modified_at":"1970-01-01T00:00:00+00:00","name_by_user":null,"name":"RFID Tag Scanner","primary_config_entry":"143eb40c5189f32be0eddf773eaaeceb","serial_number":null,"sw_version":null,"via_device_id":null},
|
||||
{"area_id":null,"config_entries":["f92d6ca163501ea7659047c60ba4e9e5"],"configuration_url":null,"connections":[],"created_at":"1970-01-01T00:00:00+00:00","disabled_by":null,"entry_type":null,"hw_version":null,"id":"387080b8991325a3e49c3b3e94736b27","identifiers":[["mjpeg","f92d6ca163501ea7659047c60ba4e9e5"]],"labels":[],"manufacturer":null,"model":null,"model_id":null,"modified_at":"1970-01-01T00:00:00+00:00","name_by_user":null,"name":"Crack Output","primary_config_entry":"f92d6ca163501ea7659047c60ba4e9e5","serial_number":null,"sw_version":null,"via_device_id":null},
|
||||
{"area_id":null,"config_entries":["04978edcf23c54a047e4f421779754ad"],"configuration_url":"http://192.168.1.151:80","connections":[["mac","cc:7b:5c:0d:0e:b4"]],"created_at":"1970-01-01T00:00:00+00:00","disabled_by":null,"entry_type":null,"hw_version":"gen2","id":"98bc66339afcb5b0ee18bd93df9ab0f0","identifiers":[],"labels":[],"manufacturer":"Shelly","model":"Shelly Plus 1 UL","model_id":"SNSW-001X15UL","modified_at":"2024-09-30T14:21:49.997137+00:00","name_by_user":"sheller-drum-enable","name":"shellyplus1-cc7b5c0d0eb4","primary_config_entry":"04978edcf23c54a047e4f421779754ad","serial_number":null,"sw_version":"20240819-074322/1.4.2-gc2639da","via_device_id":null},
|
||||
{"area_id":null,"config_entries":["ce337fdb50b165d7ba080505d5c73343"],"configuration_url":"http://192.168.1.15:80","connections":[["mac","cc:7b:5c:0d:31:6c"]],"created_at":"1970-01-01T00:00:00+00:00","disabled_by":null,"entry_type":null,"hw_version":"gen2","id":"74cb8e9e52e8403f6627e79e75994c31","identifiers":[],"labels":[],"manufacturer":"Shelly","model":"Shelly Plus 1 UL","model_id":"SNSW-001X15UL","modified_at":"2024-09-30T14:21:49.990213+00:00","name_by_user":"sheller-paddle-shaft-enable","name":"shellyplus1-cc7b5c0d316c","primary_config_entry":"ce337fdb50b165d7ba080505d5c73343","serial_number":null,"sw_version":"20240819-074322/1.4.2-gc2639da","via_device_id":null},
|
||||
{"area_id":null,"config_entries":["779bd6f1f6eebd9fb67b45fa40386e0c"],"configuration_url":"http://192.168.1.222:80","connections":[["mac","e8:6b:ea:e4:d3:50"]],"created_at":"1970-01-01T00:00:00+00:00","disabled_by":null,"entry_type":null,"hw_version":"gen2","id":"9e9ad673334459b49715731f2df83ff4","identifiers":[],"labels":[],"manufacturer":"Shelly","model":"Shelly Plus 0-10V Dimmer","model_id":"SNDM-00100WW","modified_at":"2024-09-30T14:21:50.092903+00:00","name_by_user":"sheller-drum-velocity","name":"shellyplus010v-e86beae4d350","primary_config_entry":"779bd6f1f6eebd9fb67b45fa40386e0c","serial_number":null,"sw_version":"20240819-074324/1.4.2-gc2639da","via_device_id":null},
|
||||
{"area_id":null,"config_entries":["51355cd442e2d0c51a3a43811555ee77"],"configuration_url":"http://192.168.1.28:80","connections":[["mac","e8:6b:ea:e4:df:24"]],"created_at":"1970-01-01T00:00:00+00:00","disabled_by":null,"entry_type":null,"hw_version":"gen2","id":"78045cb98b008cd63f1d086a45326c7f","identifiers":[],"labels":[],"manufacturer":"Shelly","model":"Shelly Plus 0-10V Dimmer","model_id":"SNDM-00100WW","modified_at":"2024-09-30T14:21:50.449990+00:00","name_by_user":"sheller-paddle-shaft-velocity","name":"shellyplus010v-e86beae4df24","primary_config_entry":"51355cd442e2d0c51a3a43811555ee77","serial_number":null,"sw_version":"20240819-074324/1.4.2-gc2639da","via_device_id":null},
|
||||
{"area_id":null,"config_entries":["143eb40c5189f32be0eddf773eaaeceb"],"configuration_url":null,"connections":[],"created_at":"1970-01-01T00:00:00+00:00","disabled_by":null,"entry_type":null,"hw_version":null,"id":"4a2ee843944a1c105a8a2ac44be60e23","identifiers":[["mqtt","shelling-machine"]],"labels":[],"manufacturer":"ME&E","model":null,"model_id":null,"modified_at":"1970-01-01T00:00:00+00:00","name_by_user":null,"name":"Shelling Machine","primary_config_entry":null,"serial_number":null,"sw_version":null,"via_device_id":null},
|
||||
{"area_id":null,"config_entries":["e49f29f5d0e10a3ef63369b4c8c7f5c2","77ef41d0bbf25d6007b4b2968dc60f58","82a8d9eef25b35d19d72cd6ce5f9dcbe","f1e37a42d2b569eaa4ac24c20a31fa24","37a922a368171d96e691a3439549d7bf","54af0f5338887810d55839a908975bd3"],"configuration_url":null,"connections":[["mac","98:25:4a:f7:d7:c1"]],"created_at":"1970-01-01T00:00:00+00:00","disabled_by":null,"entry_type":null,"hw_version":"2.0","id":"8bb02a4d9bcee08c25823bd8a2ee88f3","identifiers":[["tplink","98:25:4A:F7:D7:C1"]],"labels":[],"manufacturer":"TP-Link","model":"HS300(US)","model_id":null,"modified_at":"1970-01-01T00:00:00+00:00","name_by_user":null,"name":"TP-LINK_Power Strip_D7C1","primary_config_entry":"37a922a368171d96e691a3439549d7bf","serial_number":null,"sw_version":"1.0.12 Build 220121 Rel.175814","via_device_id":null},
|
||||
{"area_id":null,"config_entries":["55db3b46f3bf75777e4779fd25ed6bca"],"configuration_url":"https://172.22.114.176/protect/devices/668da48800dbe603e4002cdf","connections":[["mac","e4:38:83:0f:d1:97"]],"created_at":"1970-01-01T00:00:00+00:00","disabled_by":null,"entry_type":null,"hw_version":null,"id":"cb1143714d798e2e0e5e4b38476c2729","identifiers":[],"labels":[],"manufacturer":"Ubiquiti","model":"UVC G3 Flex","model_id":null,"modified_at":"2024-09-30T14:21:38.970906+00:00","name_by_user":null,"name":"G3 Flex 02","primary_config_entry":"55db3b46f3bf75777e4779fd25ed6bca","serial_number":null,"sw_version":"4.72.36","via_device_id":"437dbead96a87ac711c7a4d99a79c6cb"},
|
||||
{"area_id":null,"config_entries":["55db3b46f3bf75777e4779fd25ed6bca"],"configuration_url":"https://172.22.114.176/protect/devices/668daa1e019ce603e4002d31","connections":[["mac","f4:e2:c6:70:d6:da"]],"created_at":"1970-01-01T00:00:00+00:00","disabled_by":null,"entry_type":null,"hw_version":null,"id":"7bc725f99e3f2e1461c8584f6e852853","identifiers":[],"labels":[],"manufacturer":"Ubiquiti","model":"UVC AI 360","model_id":null,"modified_at":"2024-09-30T14:21:38.949223+00:00","name_by_user":null,"name":"AI 360","primary_config_entry":"55db3b46f3bf75777e4779fd25ed6bca","serial_number":null,"sw_version":"4.72.36","via_device_id":"437dbead96a87ac711c7a4d99a79c6cb"},
|
||||
{"area_id":null,"config_entries":["55db3b46f3bf75777e4779fd25ed6bca"],"configuration_url":"https://172.22.114.176/protect/devices/66954d8403c61e03e4001efe","connections":[["mac","e4:38:83:0f:d1:df"]],"created_at":"1970-01-01T00:00:00+00:00","disabled_by":null,"entry_type":null,"hw_version":null,"id":"01e8126721b48af081f5fc194eb779e0","identifiers":[],"labels":[],"manufacturer":"Ubiquiti","model":"UVC G3 Flex","model_id":null,"modified_at":"2024-09-30T14:21:38.942776+00:00","name_by_user":null,"name":"G3 Flex 03","primary_config_entry":"55db3b46f3bf75777e4779fd25ed6bca","serial_number":null,"sw_version":"4.72.36","via_device_id":"437dbead96a87ac711c7a4d99a79c6cb"},
|
||||
{"area_id":null,"config_entries":["55db3b46f3bf75777e4779fd25ed6bca"],"configuration_url":"https://172.22.114.176/protect/devices/66ab86e30161d503e4001c16","connections":[["mac","ac:8b:a9:9f:a1:d2"]],"created_at":"1970-01-01T00:00:00+00:00","disabled_by":null,"entry_type":null,"hw_version":null,"id":"6148e0856ead1b5ad475a55467c93042","identifiers":[],"labels":[],"manufacturer":"Ubiquiti","model":"UVC G5 Flex","model_id":null,"modified_at":"1970-01-01T00:00:00+00:00","name_by_user":null,"name":"G5 Flex 01","primary_config_entry":"55db3b46f3bf75777e4779fd25ed6bca","serial_number":null,"sw_version":"4.71.149","via_device_id":"437dbead96a87ac711c7a4d99a79c6cb"},
|
||||
{"area_id":null,"config_entries":["55db3b46f3bf75777e4779fd25ed6bca"],"configuration_url":"https://172.22.114.176/protect/devices/66ab87e7017dd503e4001c60","connections":[["mac","e4:38:83:0c:f4:ab"]],"created_at":"1970-01-01T00:00:00+00:00","disabled_by":null,"entry_type":null,"hw_version":null,"id":"de40a755e95c6edd9cc9c549a43f3796","identifiers":[],"labels":[],"manufacturer":"Ubiquiti","model":"UVC G5 Flex","model_id":null,"modified_at":"1970-01-01T00:00:00+00:00","name_by_user":null,"name":"G5 Flex 02","primary_config_entry":"55db3b46f3bf75777e4779fd25ed6bca","serial_number":null,"sw_version":"4.71.149","via_device_id":"437dbead96a87ac711c7a4d99a79c6cb"},
|
||||
{"area_id":null,"config_entries":["37a922a368171d96e691a3439549d7bf"],"configuration_url":null,"connections":[],"created_at":"1970-01-01T00:00:00+00:00","disabled_by":null,"entry_type":null,"hw_version":"2.0","id":"0a6798a5f44c2a8dd6cc493bf4373ebb","identifiers":[["tplink","98:25:4A:F7:D7:C1_8006AD27BB0C6D6EB0092A56145EE0B8224A9BFC00"]],"labels":[],"manufacturer":"TP-Link","model":"Socket for HS300(US)","model_id":null,"modified_at":"1970-01-01T00:00:00+00:00","name_by_user":null,"name":"TP-LINK_Power Strip_D7C1 Zima Board","primary_config_entry":"37a922a368171d96e691a3439549d7bf","serial_number":null,"sw_version":"1.0.12 Build 220121 Rel.175814","via_device_id":"8bb02a4d9bcee08c25823bd8a2ee88f3"},
|
||||
{"area_id":null,"config_entries":["37a922a368171d96e691a3439549d7bf"],"configuration_url":null,"connections":[],"created_at":"1970-01-01T00:00:00+00:00","disabled_by":null,"entry_type":null,"hw_version":"2.0","id":"0c46694b6553f98c5c5d36dee090abcd","identifiers":[["tplink","98:25:4A:F7:D7:C1_8006AD27BB0C6D6EB0092A56145EE0B8224A9BFC01"]],"labels":[],"manufacturer":"TP-Link","model":"Socket for HS300(US)","model_id":null,"modified_at":"1970-01-01T00:00:00+00:00","name_by_user":null,"name":"TP-LINK_Power Strip_D7C1 Light ","primary_config_entry":"37a922a368171d96e691a3439549d7bf","serial_number":null,"sw_version":"1.0.12 Build 220121 Rel.175814","via_device_id":"8bb02a4d9bcee08c25823bd8a2ee88f3"},
|
||||
{"area_id":null,"config_entries":["37a922a368171d96e691a3439549d7bf"],"configuration_url":null,"connections":[],"created_at":"1970-01-01T00:00:00+00:00","disabled_by":null,"entry_type":null,"hw_version":"2.0","id":"2580b7a409e3edbd9fe0594649642454","identifiers":[["tplink","98:25:4A:F7:D7:C1_8006AD27BB0C6D6EB0092A56145EE0B8224A9BFC02"]],"labels":[],"manufacturer":"TP-Link","model":"Socket for HS300(US)","model_id":null,"modified_at":"1970-01-01T00:00:00+00:00","name_by_user":null,"name":"TP-LINK_Power Strip_D7C1 Light","primary_config_entry":"37a922a368171d96e691a3439549d7bf","serial_number":null,"sw_version":"1.0.12 Build 220121 Rel.175814","via_device_id":"8bb02a4d9bcee08c25823bd8a2ee88f3"},
|
||||
{"area_id":null,"config_entries":["37a922a368171d96e691a3439549d7bf"],"configuration_url":null,"connections":[],"created_at":"1970-01-01T00:00:00+00:00","disabled_by":null,"entry_type":null,"hw_version":"2.0","id":"12984d61269adcddd72e9e302db1a4fa","identifiers":[["tplink","98:25:4A:F7:D7:C1_8006AD27BB0C6D6EB0092A56145EE0B8224A9BFC03"]],"labels":[],"manufacturer":"TP-Link","model":"Socket for HS300(US)","model_id":null,"modified_at":"1970-01-01T00:00:00+00:00","name_by_user":null,"name":"TP-LINK_Power Strip_D7C1 Light","primary_config_entry":"37a922a368171d96e691a3439549d7bf","serial_number":null,"sw_version":"1.0.12 Build 220121 Rel.175814","via_device_id":"8bb02a4d9bcee08c25823bd8a2ee88f3"},
|
||||
{"area_id":null,"config_entries":["37a922a368171d96e691a3439549d7bf"],"configuration_url":null,"connections":[],"created_at":"1970-01-01T00:00:00+00:00","disabled_by":null,"entry_type":null,"hw_version":"2.0","id":"a118052d327d7ab956d83b1e50a7addf","identifiers":[["tplink","98:25:4A:F7:D7:C1_8006AD27BB0C6D6EB0092A56145EE0B8224A9BFC04"]],"labels":[],"manufacturer":"TP-Link","model":"Socket for HS300(US)","model_id":null,"modified_at":"1970-01-01T00:00:00+00:00","name_by_user":null,"name":"TP-LINK_Power Strip_D7C1 Light ","primary_config_entry":"37a922a368171d96e691a3439549d7bf","serial_number":null,"sw_version":"1.0.12 Build 220121 Rel.175814","via_device_id":"8bb02a4d9bcee08c25823bd8a2ee88f3"},
|
||||
{"area_id":null,"config_entries":["37a922a368171d96e691a3439549d7bf"],"configuration_url":null,"connections":[],"created_at":"1970-01-01T00:00:00+00:00","disabled_by":null,"entry_type":null,"hw_version":"2.0","id":"24c8694e03be3bb519577b12f4ca13a5","identifiers":[["tplink","98:25:4A:F7:D7:C1_8006AD27BB0C6D6EB0092A56145EE0B8224A9BFC05"]],"labels":[],"manufacturer":"TP-Link","model":"Socket for HS300(US)","model_id":null,"modified_at":"1970-01-01T00:00:00+00:00","name_by_user":null,"name":"TP-LINK_Power Strip_D7C1 Vibratory Conveyor","primary_config_entry":"37a922a368171d96e691a3439549d7bf","serial_number":null,"sw_version":"1.0.12 Build 220121 Rel.175814","via_device_id":"8bb02a4d9bcee08c25823bd8a2ee88f3"},
|
||||
{"area_id":null,"config_entries":["01J5NH0A41TYMRRGASE2QJ0SQ5"],"configuration_url":"http://192.168.1.139:80","connections":[["mac","c0:49:ef:8c:73:10"]],"created_at":"1970-01-01T00:00:00+00:00","disabled_by":null,"entry_type":null,"hw_version":"gen2","id":"bdc5f90b963bab0edf03bfeeff494858","identifiers":[],"labels":[],"manufacturer":"Shelly","model":"Shelly Plus 1PM UL","model_id":"SNSW-001P15UL","modified_at":"2024-09-30T14:21:50.007317+00:00","name_by_user":"JC Vibratory Feed Conveyor","name":"shellyplus1pm-c049ef8c7310","primary_config_entry":"01J5NH0A41TYMRRGASE2QJ0SQ5","serial_number":null,"sw_version":"20240819-074343/1.4.2-gc2639da","via_device_id":null},
|
||||
{"area_id":null,"config_entries":["01J91MY5JBJH64GQS4WA5SYVK2"],"configuration_url":"homeassistant://hassio/addon/a0d7b954_emqx","connections":[],"created_at":"2024-09-30T14:21:50.117815+00:00","disabled_by":null,"entry_type":"service","hw_version":null,"id":"361a4984b7d01d3080714018482ccc7c","identifiers":[["hassio","a0d7b954_emqx"]],"labels":[],"manufacturer":"Home Assistant Community Add-ons","model":"Home Assistant Add-on","model_id":null,"modified_at":"2024-09-30T14:21:50.118023+00:00","name_by_user":null,"name":"EMQX","primary_config_entry":"01J91MY5JBJH64GQS4WA5SYVK2","serial_number":null,"sw_version":"0.7.0","via_device_id":null},
|
||||
{"area_id":null,"config_entries":["01J91MY5JBJH64GQS4WA5SYVK2"],"configuration_url":"homeassistant://hassio/addon/a0d7b954_vscode","connections":[],"created_at":"2024-09-30T14:21:50.119745+00:00","disabled_by":null,"entry_type":"service","hw_version":null,"id":"4337d0ff05fb782c9ec2ae035488a00a","identifiers":[["hassio","a0d7b954_vscode"]],"labels":[],"manufacturer":"Home Assistant Community Add-ons","model":"Home Assistant Add-on","model_id":null,"modified_at":"2024-09-30T21:51:30.243506+00:00","name_by_user":null,"name":"Studio Code Server","primary_config_entry":"01J91MY5JBJH64GQS4WA5SYVK2","serial_number":null,"sw_version":"5.16.1","via_device_id":null},
|
||||
{"area_id":null,"config_entries":["01J91MY5JBJH64GQS4WA5SYVK2"],"configuration_url":"homeassistant://hassio/addon/a0d7b954_ssh","connections":[],"created_at":"2024-09-30T14:21:50.130433+00:00","disabled_by":null,"entry_type":"service","hw_version":null,"id":"9eb0166d98958fe13f7a50eb706ffc4c","identifiers":[["hassio","a0d7b954_ssh"]],"labels":[],"manufacturer":"Home Assistant Community Add-ons","model":"Home Assistant Add-on","model_id":null,"modified_at":"2024-09-30T14:21:50.130703+00:00","name_by_user":null,"name":"Advanced SSH & Web Terminal","primary_config_entry":"01J91MY5JBJH64GQS4WA5SYVK2","serial_number":null,"sw_version":"19.0.0","via_device_id":null},
|
||||
{"area_id":null,"config_entries":["01J91MY5JBJH64GQS4WA5SYVK2"],"configuration_url":null,"connections":[],"created_at":"2024-09-30T14:21:50.133480+00:00","disabled_by":null,"entry_type":"service","hw_version":null,"id":"e8c8f69e8318af2be25fae388c7d3e9c","identifiers":[["hassio","core"]],"labels":[],"manufacturer":"Home Assistant","model":"Home Assistant Core","model_id":null,"modified_at":"2024-09-30T14:21:50.133962+00:00","name_by_user":null,"name":"Home Assistant Core","primary_config_entry":"01J91MY5JBJH64GQS4WA5SYVK2","serial_number":null,"sw_version":"2024.9.3","via_device_id":null},
|
||||
{"area_id":null,"config_entries":["01J91MY5JBJH64GQS4WA5SYVK2"],"configuration_url":null,"connections":[],"created_at":"2024-09-30T14:21:50.142602+00:00","disabled_by":null,"entry_type":"service","hw_version":null,"id":"adffec60f63f0032597d03eba833062f","identifiers":[["hassio","supervisor"]],"labels":[],"manufacturer":"Home Assistant","model":"Home Assistant Supervisor","model_id":null,"modified_at":"2024-09-30T14:21:50.142707+00:00","name_by_user":null,"name":"Home Assistant Supervisor","primary_config_entry":"01J91MY5JBJH64GQS4WA5SYVK2","serial_number":null,"sw_version":"2024.09.1","via_device_id":null},
|
||||
{"area_id":null,"config_entries":["01J91MY5JBJH64GQS4WA5SYVK2"],"configuration_url":null,"connections":[],"created_at":"2024-09-30T14:21:50.143809+00:00","disabled_by":null,"entry_type":"service","hw_version":null,"id":"c0da2b39c57cd1e833c93a1e0d6081a3","identifiers":[["hassio","host"]],"labels":[],"manufacturer":"Home Assistant","model":"Home Assistant Host","model_id":null,"modified_at":"2024-09-30T14:21:50.143950+00:00","name_by_user":null,"name":"Home Assistant Host","primary_config_entry":"01J91MY5JBJH64GQS4WA5SYVK2","serial_number":null,"sw_version":null,"via_device_id":null},
|
||||
{"area_id":null,"config_entries":["01J91MY5JBJH64GQS4WA5SYVK2"],"configuration_url":null,"connections":[],"created_at":"2024-09-30T14:21:50.145121+00:00","disabled_by":null,"entry_type":"service","hw_version":null,"id":"db658cf1b7110495bd574ecbfea00f65","identifiers":[["hassio","OS"]],"labels":[],"manufacturer":"Home Assistant","model":"Home Assistant Operating System","model_id":null,"modified_at":"2024-09-30T14:21:50.145227+00:00","name_by_user":null,"name":"Home Assistant Operating System","primary_config_entry":"01J91MY5JBJH64GQS4WA5SYVK2","serial_number":null,"sw_version":"13.1","via_device_id":null}
|
||||
{"area_id":null,"config_entries":["55db3b46f3bf75777e4779fd25ed6bca"],"config_entries_subentries":{"55db3b46f3bf75777e4779fd25ed6bca":[null]},"configuration_url":"https://172.22.114.176/protect/devices/65ba9b25013a7903e400256f","connections":[["mac","e4:38:83:0f:d1:b8"]],"created_at":"1970-01-01T00:00:00+00:00","disabled_by":null,"entry_type":null,"hw_version":null,"id":"a00c8648172b02bb56e52bd1083bc8b5","identifiers":[],"labels":[],"manufacturer":"Ubiquiti","model":"G3 Flex","model_id":"UVC G3 Flex","modified_at":"2025-04-24T19:41:34.413368+00:00","name_by_user":null,"name":"G3 Flex 01","primary_config_entry":"55db3b46f3bf75777e4779fd25ed6bca","serial_number":null,"sw_version":"4.75.43","via_device_id":"437dbead96a87ac711c7a4d99a79c6cb"},
|
||||
{"area_id":null,"config_entries":["55db3b46f3bf75777e4779fd25ed6bca"],"config_entries_subentries":{"55db3b46f3bf75777e4779fd25ed6bca":[null]},"configuration_url":"https://172.22.114.176","connections":[["mac","70:a7:41:a5:3e:33"]],"created_at":"1970-01-01T00:00:00+00:00","disabled_by":null,"entry_type":null,"hw_version":null,"id":"437dbead96a87ac711c7a4d99a79c6cb","identifiers":[["unifiprotect","70A741A53E33"]],"labels":[],"manufacturer":"Ubiquiti","model":"UCK-G2-PLUS","model_id":null,"modified_at":"2025-04-30T15:19:53.895791+00:00","name_by_user":null,"name":"Cloud Key Gen2 Plus","primary_config_entry":"55db3b46f3bf75777e4779fd25ed6bca","serial_number":null,"sw_version":"5.3.45","via_device_id":null},
|
||||
{"area_id":null,"config_entries":["0feafcef6c9ee4eb380cad7190b2f403"],"config_entries_subentries":{"0feafcef6c9ee4eb380cad7190b2f403":[null]},"configuration_url":null,"connections":[],"created_at":"1970-01-01T00:00:00+00:00","disabled_by":null,"entry_type":"service","hw_version":null,"id":"837a78425d2847cdcbe0b38f7d05cb7b","identifiers":[["sun","0feafcef6c9ee4eb380cad7190b2f403"]],"labels":[],"manufacturer":null,"model":null,"model_id":null,"modified_at":"1970-01-01T00:00:00+00:00","name_by_user":null,"name":"Sun","primary_config_entry":"0feafcef6c9ee4eb380cad7190b2f403","serial_number":null,"sw_version":null,"via_device_id":null},
|
||||
{"area_id":null,"config_entries":["143eb40c5189f32be0eddf773eaaeceb"],"config_entries_subentries":{"143eb40c5189f32be0eddf773eaaeceb":[null]},"configuration_url":null,"connections":[],"created_at":"1970-01-01T00:00:00+00:00","disabled_by":null,"entry_type":null,"hw_version":null,"id":"d96ab4ca2878dfd648ac7c530f4d0268","identifiers":[["mqtt","steinlite-moisture-meter"]],"labels":[],"manufacturer":"Steinlite","model":"SB900","model_id":null,"modified_at":"1970-01-01T00:00:00+00:00","name_by_user":null,"name":"Moisture Meter","primary_config_entry":"143eb40c5189f32be0eddf773eaaeceb","serial_number":null,"sw_version":null,"via_device_id":null},
|
||||
{"area_id":null,"config_entries":["143eb40c5189f32be0eddf773eaaeceb"],"config_entries_subentries":{"143eb40c5189f32be0eddf773eaaeceb":[null]},"configuration_url":null,"connections":[],"created_at":"1970-01-01T00:00:00+00:00","disabled_by":null,"entry_type":null,"hw_version":null,"id":"682585d3e49461db68dfce310711e240","identifiers":[["mqtt","sheller-scale"]],"labels":[],"manufacturer":"Adam Equipment","model":"CPWplus 15","model_id":null,"modified_at":"1970-01-01T00:00:00+00:00","name_by_user":null,"name":"Sheller Scale","primary_config_entry":"143eb40c5189f32be0eddf773eaaeceb","serial_number":null,"sw_version":null,"via_device_id":null},
|
||||
{"area_id":null,"config_entries":["143eb40c5189f32be0eddf773eaaeceb"],"config_entries_subentries":{"143eb40c5189f32be0eddf773eaaeceb":[null]},"configuration_url":null,"connections":[],"created_at":"1970-01-01T00:00:00+00:00","disabled_by":null,"entry_type":null,"hw_version":null,"id":"704baf0c713921ab159af1306f741d87","identifiers":[["mqtt","precision-scale"]],"labels":[],"manufacturer":"U.S. Solid","model":"USS-DBS87-310G","model_id":null,"modified_at":"1970-01-01T00:00:00+00:00","name_by_user":null,"name":"Precision Scale","primary_config_entry":"143eb40c5189f32be0eddf773eaaeceb","serial_number":null,"sw_version":null,"via_device_id":null},
|
||||
{"area_id":null,"config_entries":["e3427a6f1a531d4647c57351962f3e1a"],"config_entries_subentries":{"e3427a6f1a531d4647c57351962f3e1a":[null]},"configuration_url":null,"connections":[],"created_at":"1970-01-01T00:00:00+00:00","disabled_by":null,"entry_type":null,"hw_version":null,"id":"9b53abd12d274cfc0a304b0b15ebbd39","identifiers":[["mobile_app","2ddef885064fb8ed"]],"labels":[],"manufacturer":"samsung","model":"SM-A546U1","model_id":null,"modified_at":"1970-01-01T00:00:00+00:00","name_by_user":null,"name":"lab-phone","primary_config_entry":"e3427a6f1a531d4647c57351962f3e1a","serial_number":null,"sw_version":"34","via_device_id":null},
|
||||
{"area_id":null,"config_entries":["143eb40c5189f32be0eddf773eaaeceb"],"config_entries_subentries":{"143eb40c5189f32be0eddf773eaaeceb":[null]},"configuration_url":null,"connections":[],"created_at":"1970-01-01T00:00:00+00:00","disabled_by":null,"entry_type":null,"hw_version":null,"id":"feec748cc156a1ca441d38caf620ecfe","identifiers":[["mqtt","moisture-station-scanner"]],"labels":[],"manufacturer":"Netum","model":"C300-HF","model_id":null,"modified_at":"1970-01-01T00:00:00+00:00","name_by_user":null,"name":"RFID Tag Scanner","primary_config_entry":"143eb40c5189f32be0eddf773eaaeceb","serial_number":null,"sw_version":null,"via_device_id":null},
|
||||
{"area_id":null,"config_entries":["143eb40c5189f32be0eddf773eaaeceb"],"config_entries_subentries":{"143eb40c5189f32be0eddf773eaaeceb":[null]},"configuration_url":null,"connections":[],"created_at":"1970-01-01T00:00:00+00:00","disabled_by":null,"entry_type":null,"hw_version":null,"id":"c9fd6d12a759980d491da079f9e3a545","identifiers":[["mqtt","scale-station-scanner"]],"labels":[],"manufacturer":"Sony","model":"RC-S380","model_id":null,"modified_at":"1970-01-01T00:00:00+00:00","name_by_user":null,"name":"RFID Tag Scanner","primary_config_entry":"143eb40c5189f32be0eddf773eaaeceb","serial_number":null,"sw_version":null,"via_device_id":null},
|
||||
{"area_id":null,"config_entries":["f92d6ca163501ea7659047c60ba4e9e5"],"config_entries_subentries":{"f92d6ca163501ea7659047c60ba4e9e5":[null]},"configuration_url":null,"connections":[],"created_at":"1970-01-01T00:00:00+00:00","disabled_by":null,"entry_type":null,"hw_version":null,"id":"387080b8991325a3e49c3b3e94736b27","identifiers":[["mjpeg","f92d6ca163501ea7659047c60ba4e9e5"]],"labels":[],"manufacturer":null,"model":null,"model_id":null,"modified_at":"1970-01-01T00:00:00+00:00","name_by_user":null,"name":"Crack Output","primary_config_entry":"f92d6ca163501ea7659047c60ba4e9e5","serial_number":null,"sw_version":null,"via_device_id":null},
|
||||
{"area_id":null,"config_entries":["04978edcf23c54a047e4f421779754ad"],"config_entries_subentries":{"04978edcf23c54a047e4f421779754ad":[null]},"configuration_url":"http://192.168.1.152:80","connections":[["mac","cc:7b:5c:0d:0e:b4"]],"created_at":"1970-01-01T00:00:00+00:00","disabled_by":null,"entry_type":null,"hw_version":"gen2","id":"98bc66339afcb5b0ee18bd93df9ab0f0","identifiers":[["shelly","CC7B5C0D0EB4"]],"labels":[],"manufacturer":"Shelly","model":"Shelly Plus 1 UL","model_id":"SNSW-001X15UL","modified_at":"2025-04-08T16:18:55.552123+00:00","name_by_user":"sheller-drum-enable","name":"shellyplus1-cc7b5c0d0eb4","primary_config_entry":"04978edcf23c54a047e4f421779754ad","serial_number":null,"sw_version":"20250318-152131/1.5.1-g01dd7ff","via_device_id":null},
|
||||
{"area_id":null,"config_entries":["ce337fdb50b165d7ba080505d5c73343"],"config_entries_subentries":{"ce337fdb50b165d7ba080505d5c73343":[null]},"configuration_url":"http://192.168.1.15:80","connections":[["mac","cc:7b:5c:0d:31:6c"]],"created_at":"1970-01-01T00:00:00+00:00","disabled_by":null,"entry_type":null,"hw_version":"gen2","id":"74cb8e9e52e8403f6627e79e75994c31","identifiers":[["shelly","CC7B5C0D316C"]],"labels":[],"manufacturer":"Shelly","model":"Shelly Plus 1 UL","model_id":"SNSW-001X15UL","modified_at":"2025-03-28T16:50:09.870954+00:00","name_by_user":"sheller-paddle-shaft-enable","name":"shellyplus1-cc7b5c0d316c","primary_config_entry":"ce337fdb50b165d7ba080505d5c73343","serial_number":null,"sw_version":"20250318-152131/1.5.1-g01dd7ff","via_device_id":null},
|
||||
{"area_id":null,"config_entries":["779bd6f1f6eebd9fb67b45fa40386e0c"],"config_entries_subentries":{"779bd6f1f6eebd9fb67b45fa40386e0c":[null]},"configuration_url":"http://192.168.1.222:80","connections":[["mac","e8:6b:ea:e4:d3:50"]],"created_at":"1970-01-01T00:00:00+00:00","disabled_by":null,"entry_type":null,"hw_version":"gen2","id":"9e9ad673334459b49715731f2df83ff4","identifiers":[["shelly","E86BEAE4D350"]],"labels":[],"manufacturer":"Shelly","model":"Shelly Plus 0-10V Dimmer","model_id":"SNDM-00100WW","modified_at":"2025-03-28T16:50:10.101529+00:00","name_by_user":"sheller-drum-velocity","name":"shellyplus010v-e86beae4d350","primary_config_entry":"779bd6f1f6eebd9fb67b45fa40386e0c","serial_number":null,"sw_version":"20250318-152134/1.5.1-g01dd7ff","via_device_id":null},
|
||||
{"area_id":null,"config_entries":["51355cd442e2d0c51a3a43811555ee77"],"config_entries_subentries":{"51355cd442e2d0c51a3a43811555ee77":[null]},"configuration_url":"http://192.168.1.28:80","connections":[["mac","e8:6b:ea:e4:df:24"]],"created_at":"1970-01-01T00:00:00+00:00","disabled_by":null,"entry_type":null,"hw_version":"gen2","id":"78045cb98b008cd63f1d086a45326c7f","identifiers":[["shelly","E86BEAE4DF24"]],"labels":[],"manufacturer":"Shelly","model":"Shelly Plus 0-10V Dimmer","model_id":"SNDM-00100WW","modified_at":"2025-02-12T17:45:59.363846+00:00","name_by_user":"sheller-paddle-shaft-velocity","name":"shellyplus010v-e86beae4df24","primary_config_entry":"51355cd442e2d0c51a3a43811555ee77","serial_number":null,"sw_version":"20241011-114443/1.4.4-g6d2a586","via_device_id":null},
|
||||
{"area_id":null,"config_entries":["143eb40c5189f32be0eddf773eaaeceb"],"config_entries_subentries":{"143eb40c5189f32be0eddf773eaaeceb":[null]},"configuration_url":null,"connections":[],"created_at":"1970-01-01T00:00:00+00:00","disabled_by":null,"entry_type":null,"hw_version":null,"id":"4a2ee843944a1c105a8a2ac44be60e23","identifiers":[["mqtt","shelling-machine"]],"labels":[],"manufacturer":"ME&E","model":null,"model_id":null,"modified_at":"2024-11-19T21:54:25.594657+00:00","name_by_user":null,"name":"Shelling Machine","primary_config_entry":"143eb40c5189f32be0eddf773eaaeceb","serial_number":null,"sw_version":null,"via_device_id":null},
|
||||
{"area_id":null,"config_entries":["37a922a368171d96e691a3439549d7bf","f1e37a42d2b569eaa4ac24c20a31fa24","82a8d9eef25b35d19d72cd6ce5f9dcbe","54af0f5338887810d55839a908975bd3","77ef41d0bbf25d6007b4b2968dc60f58","e49f29f5d0e10a3ef63369b4c8c7f5c2"],"config_entries_subentries":{"77ef41d0bbf25d6007b4b2968dc60f58":[null],"f1e37a42d2b569eaa4ac24c20a31fa24":[null],"54af0f5338887810d55839a908975bd3":[null],"37a922a368171d96e691a3439549d7bf":[null],"e49f29f5d0e10a3ef63369b4c8c7f5c2":[null],"82a8d9eef25b35d19d72cd6ce5f9dcbe":[null]},"configuration_url":null,"connections":[["mac","98:25:4a:f7:d7:c1"]],"created_at":"1970-01-01T00:00:00+00:00","disabled_by":null,"entry_type":null,"hw_version":"2.0","id":"8bb02a4d9bcee08c25823bd8a2ee88f3","identifiers":[["tplink","98:25:4A:F7:D7:C1"]],"labels":[],"manufacturer":"TP-Link","model":"HS300","model_id":null,"modified_at":"2025-03-08T19:42:43.429891+00:00","name_by_user":null,"name":"TP-LINK_Power Strip_D7C1","primary_config_entry":"37a922a368171d96e691a3439549d7bf","serial_number":null,"sw_version":"1.0.12 Build 220121 Rel.175814","via_device_id":null},
|
||||
{"area_id":null,"config_entries":["55db3b46f3bf75777e4779fd25ed6bca"],"config_entries_subentries":{"55db3b46f3bf75777e4779fd25ed6bca":[null]},"configuration_url":"https://172.22.114.176/protect/devices/668da48800dbe603e4002cdf","connections":[["mac","e4:38:83:0f:d1:97"]],"created_at":"1970-01-01T00:00:00+00:00","disabled_by":null,"entry_type":null,"hw_version":null,"id":"cb1143714d798e2e0e5e4b38476c2729","identifiers":[],"labels":[],"manufacturer":"Ubiquiti","model":"G3 Flex","model_id":"UVC G3 Flex","modified_at":"2025-04-28T15:39:46.230184+00:00","name_by_user":null,"name":"G3 Flex 02","primary_config_entry":"55db3b46f3bf75777e4779fd25ed6bca","serial_number":null,"sw_version":"4.75.62","via_device_id":"437dbead96a87ac711c7a4d99a79c6cb"},
|
||||
{"area_id":null,"config_entries":["55db3b46f3bf75777e4779fd25ed6bca"],"config_entries_subentries":{"55db3b46f3bf75777e4779fd25ed6bca":[null]},"configuration_url":"https://172.22.114.176/protect/devices/668daa1e019ce603e4002d31","connections":[["mac","f4:e2:c6:70:d6:da"]],"created_at":"1970-01-01T00:00:00+00:00","disabled_by":null,"entry_type":null,"hw_version":null,"id":"7bc725f99e3f2e1461c8584f6e852853","identifiers":[],"labels":[],"manufacturer":"Ubiquiti","model":"AI 360","model_id":"UVC AI 360","modified_at":"2025-04-28T15:39:46.228701+00:00","name_by_user":null,"name":"AI 360","primary_config_entry":"55db3b46f3bf75777e4779fd25ed6bca","serial_number":null,"sw_version":"4.75.62","via_device_id":"437dbead96a87ac711c7a4d99a79c6cb"},
|
||||
{"area_id":null,"config_entries":["55db3b46f3bf75777e4779fd25ed6bca"],"config_entries_subentries":{"55db3b46f3bf75777e4779fd25ed6bca":[null]},"configuration_url":"https://172.22.114.176/protect/devices/66954d8403c61e03e4001efe","connections":[["mac","e4:38:83:0f:d1:df"]],"created_at":"1970-01-01T00:00:00+00:00","disabled_by":null,"entry_type":null,"hw_version":null,"id":"01e8126721b48af081f5fc194eb779e0","identifiers":[],"labels":[],"manufacturer":"Ubiquiti","model":"G3 Flex","model_id":"UVC G3 Flex","modified_at":"2025-04-28T15:39:46.229996+00:00","name_by_user":null,"name":"G3 Flex 03","primary_config_entry":"55db3b46f3bf75777e4779fd25ed6bca","serial_number":null,"sw_version":"4.75.62","via_device_id":"437dbead96a87ac711c7a4d99a79c6cb"},
|
||||
{"area_id":null,"config_entries":["55db3b46f3bf75777e4779fd25ed6bca"],"config_entries_subentries":{"55db3b46f3bf75777e4779fd25ed6bca":[null]},"configuration_url":"https://172.22.114.176/protect/devices/66ab86e30161d503e4001c16","connections":[["mac","ac:8b:a9:9f:a1:d2"]],"created_at":"1970-01-01T00:00:00+00:00","disabled_by":null,"entry_type":null,"hw_version":null,"id":"6148e0856ead1b5ad475a55467c93042","identifiers":[],"labels":[],"manufacturer":"Ubiquiti","model":"G5 Flex","model_id":"UVC G5 Flex","modified_at":"2025-04-28T15:39:46.230587+00:00","name_by_user":null,"name":"G5 Flex 01","primary_config_entry":"55db3b46f3bf75777e4779fd25ed6bca","serial_number":null,"sw_version":"4.75.62","via_device_id":"437dbead96a87ac711c7a4d99a79c6cb"},
|
||||
{"area_id":null,"config_entries":["55db3b46f3bf75777e4779fd25ed6bca"],"config_entries_subentries":{"55db3b46f3bf75777e4779fd25ed6bca":[null]},"configuration_url":"https://172.22.114.176/protect/devices/66ab87e7017dd503e4001c60","connections":[["mac","e4:38:83:0c:f4:ab"]],"created_at":"1970-01-01T00:00:00+00:00","disabled_by":null,"entry_type":null,"hw_version":null,"id":"de40a755e95c6edd9cc9c549a43f3796","identifiers":[],"labels":[],"manufacturer":"Ubiquiti","model":"G5 Flex","model_id":"UVC G5 Flex","modified_at":"2025-04-28T15:39:46.229782+00:00","name_by_user":null,"name":"G5 Flex 02","primary_config_entry":"55db3b46f3bf75777e4779fd25ed6bca","serial_number":null,"sw_version":"4.75.62","via_device_id":"437dbead96a87ac711c7a4d99a79c6cb"},
|
||||
{"area_id":null,"config_entries":["37a922a368171d96e691a3439549d7bf"],"config_entries_subentries":{"37a922a368171d96e691a3439549d7bf":[null]},"configuration_url":null,"connections":[],"created_at":"1970-01-01T00:00:00+00:00","disabled_by":null,"entry_type":null,"hw_version":"2.0","id":"0a6798a5f44c2a8dd6cc493bf4373ebb","identifiers":[["tplink","98:25:4A:F7:D7:C1_8006AD27BB0C6D6EB0092A56145EE0B8224A9BFC00"]],"labels":[],"manufacturer":"TP-Link","model":"Socket for HS300(US)","model_id":null,"modified_at":"1970-01-01T00:00:00+00:00","name_by_user":null,"name":"TP-LINK_Power Strip_D7C1 Zima Board","primary_config_entry":"37a922a368171d96e691a3439549d7bf","serial_number":null,"sw_version":"1.0.12 Build 220121 Rel.175814","via_device_id":"8bb02a4d9bcee08c25823bd8a2ee88f3"},
|
||||
{"area_id":null,"config_entries":["37a922a368171d96e691a3439549d7bf"],"config_entries_subentries":{"37a922a368171d96e691a3439549d7bf":[null]},"configuration_url":null,"connections":[],"created_at":"1970-01-01T00:00:00+00:00","disabled_by":null,"entry_type":null,"hw_version":"2.0","id":"0c46694b6553f98c5c5d36dee090abcd","identifiers":[["tplink","98:25:4A:F7:D7:C1_8006AD27BB0C6D6EB0092A56145EE0B8224A9BFC01"]],"labels":[],"manufacturer":"TP-Link","model":"Socket for HS300(US)","model_id":null,"modified_at":"1970-01-01T00:00:00+00:00","name_by_user":null,"name":"TP-LINK_Power Strip_D7C1 Light ","primary_config_entry":"37a922a368171d96e691a3439549d7bf","serial_number":null,"sw_version":"1.0.12 Build 220121 Rel.175814","via_device_id":"8bb02a4d9bcee08c25823bd8a2ee88f3"},
|
||||
{"area_id":null,"config_entries":["37a922a368171d96e691a3439549d7bf"],"config_entries_subentries":{"37a922a368171d96e691a3439549d7bf":[null]},"configuration_url":null,"connections":[],"created_at":"1970-01-01T00:00:00+00:00","disabled_by":null,"entry_type":null,"hw_version":"2.0","id":"2580b7a409e3edbd9fe0594649642454","identifiers":[["tplink","98:25:4A:F7:D7:C1_8006AD27BB0C6D6EB0092A56145EE0B8224A9BFC02"]],"labels":[],"manufacturer":"TP-Link","model":"Socket for HS300(US)","model_id":null,"modified_at":"1970-01-01T00:00:00+00:00","name_by_user":null,"name":"TP-LINK_Power Strip_D7C1 Light","primary_config_entry":"37a922a368171d96e691a3439549d7bf","serial_number":null,"sw_version":"1.0.12 Build 220121 Rel.175814","via_device_id":"8bb02a4d9bcee08c25823bd8a2ee88f3"},
|
||||
{"area_id":null,"config_entries":["37a922a368171d96e691a3439549d7bf"],"config_entries_subentries":{"37a922a368171d96e691a3439549d7bf":[null]},"configuration_url":null,"connections":[],"created_at":"1970-01-01T00:00:00+00:00","disabled_by":null,"entry_type":null,"hw_version":"2.0","id":"12984d61269adcddd72e9e302db1a4fa","identifiers":[["tplink","98:25:4A:F7:D7:C1_8006AD27BB0C6D6EB0092A56145EE0B8224A9BFC03"]],"labels":[],"manufacturer":"TP-Link","model":"Socket for HS300(US)","model_id":null,"modified_at":"1970-01-01T00:00:00+00:00","name_by_user":null,"name":"TP-LINK_Power Strip_D7C1 Light","primary_config_entry":"37a922a368171d96e691a3439549d7bf","serial_number":null,"sw_version":"1.0.12 Build 220121 Rel.175814","via_device_id":"8bb02a4d9bcee08c25823bd8a2ee88f3"},
|
||||
{"area_id":null,"config_entries":["37a922a368171d96e691a3439549d7bf"],"config_entries_subentries":{"37a922a368171d96e691a3439549d7bf":[null]},"configuration_url":null,"connections":[],"created_at":"1970-01-01T00:00:00+00:00","disabled_by":null,"entry_type":null,"hw_version":"2.0","id":"a118052d327d7ab956d83b1e50a7addf","identifiers":[["tplink","98:25:4A:F7:D7:C1_8006AD27BB0C6D6EB0092A56145EE0B8224A9BFC04"]],"labels":[],"manufacturer":"TP-Link","model":"Socket for HS300(US)","model_id":null,"modified_at":"1970-01-01T00:00:00+00:00","name_by_user":null,"name":"TP-LINK_Power Strip_D7C1 Light ","primary_config_entry":"37a922a368171d96e691a3439549d7bf","serial_number":null,"sw_version":"1.0.12 Build 220121 Rel.175814","via_device_id":"8bb02a4d9bcee08c25823bd8a2ee88f3"},
|
||||
{"area_id":null,"config_entries":["37a922a368171d96e691a3439549d7bf"],"config_entries_subentries":{"37a922a368171d96e691a3439549d7bf":[null]},"configuration_url":null,"connections":[],"created_at":"1970-01-01T00:00:00+00:00","disabled_by":null,"entry_type":null,"hw_version":"2.0","id":"24c8694e03be3bb519577b12f4ca13a5","identifiers":[["tplink","98:25:4A:F7:D7:C1_8006AD27BB0C6D6EB0092A56145EE0B8224A9BFC05"]],"labels":[],"manufacturer":"TP-Link","model":"Socket for HS300(US)","model_id":null,"modified_at":"1970-01-01T00:00:00+00:00","name_by_user":null,"name":"TP-LINK_Power Strip_D7C1 Vibratory Conveyor","primary_config_entry":"37a922a368171d96e691a3439549d7bf","serial_number":null,"sw_version":"1.0.12 Build 220121 Rel.175814","via_device_id":"8bb02a4d9bcee08c25823bd8a2ee88f3"},
|
||||
{"area_id":"jc_machine","config_entries":["01J5NH0A41TYMRRGASE2QJ0SQ5"],"config_entries_subentries":{"01J5NH0A41TYMRRGASE2QJ0SQ5":[null]},"configuration_url":"http://192.168.1.139:80","connections":[["mac","c0:49:ef:8c:73:10"]],"created_at":"1970-01-01T00:00:00+00:00","disabled_by":null,"entry_type":null,"hw_version":"gen2","id":"bdc5f90b963bab0edf03bfeeff494858","identifiers":[["shelly","C049EF8C7310"]],"labels":[],"manufacturer":"Shelly","model":"Shelly Plus 1PM UL","model_id":"SNSW-001P15UL","modified_at":"2025-04-14T17:18:52.934204+00:00","name_by_user":"JC Vibratory Feed Conveyor","name":"shellyplus1pm-c049ef8c7310","primary_config_entry":"01J5NH0A41TYMRRGASE2QJ0SQ5","serial_number":null,"sw_version":"20250318-152121/1.5.1-g01dd7ff","via_device_id":null},
|
||||
{"area_id":null,"config_entries":["01J91MY5JBJH64GQS4WA5SYVK2"],"config_entries_subentries":{"01J91MY5JBJH64GQS4WA5SYVK2":[null]},"configuration_url":"homeassistant://hassio/addon/a0d7b954_emqx","connections":[],"created_at":"2024-09-30T14:21:50.117815+00:00","disabled_by":null,"entry_type":"service","hw_version":null,"id":"361a4984b7d01d3080714018482ccc7c","identifiers":[["hassio","a0d7b954_emqx"]],"labels":[],"manufacturer":"Home Assistant Community Add-ons","model":"Home Assistant Add-on","model_id":null,"modified_at":"2025-04-03T12:49:10.971877+00:00","name_by_user":null,"name":"EMQX","primary_config_entry":"01J91MY5JBJH64GQS4WA5SYVK2","serial_number":null,"sw_version":"0.7.5","via_device_id":null},
|
||||
{"area_id":null,"config_entries":["01J91MY5JBJH64GQS4WA5SYVK2"],"config_entries_subentries":{"01J91MY5JBJH64GQS4WA5SYVK2":[null]},"configuration_url":"homeassistant://hassio/addon/a0d7b954_ssh","connections":[],"created_at":"2024-09-30T14:21:50.130433+00:00","disabled_by":null,"entry_type":"service","hw_version":null,"id":"9eb0166d98958fe13f7a50eb706ffc4c","identifiers":[["hassio","a0d7b954_ssh"]],"labels":[],"manufacturer":"Home Assistant Community Add-ons","model":"Home Assistant Add-on","model_id":null,"modified_at":"2025-02-17T17:27:42.631137+00:00","name_by_user":null,"name":"Advanced SSH & Web Terminal","primary_config_entry":"01J91MY5JBJH64GQS4WA5SYVK2","serial_number":null,"sw_version":"20.0.0","via_device_id":null},
|
||||
{"area_id":null,"config_entries":["01J91MY5JBJH64GQS4WA5SYVK2"],"config_entries_subentries":{"01J91MY5JBJH64GQS4WA5SYVK2":[null]},"configuration_url":null,"connections":[],"created_at":"2024-09-30T14:21:50.133480+00:00","disabled_by":null,"entry_type":"service","hw_version":null,"id":"e8c8f69e8318af2be25fae388c7d3e9c","identifiers":[["hassio","core"]],"labels":[],"manufacturer":"Home Assistant","model":"Home Assistant Core","model_id":null,"modified_at":"2025-04-28T15:39:43.376367+00:00","name_by_user":null,"name":"Home Assistant Core","primary_config_entry":"01J91MY5JBJH64GQS4WA5SYVK2","serial_number":null,"sw_version":"2025.4.4","via_device_id":null},
|
||||
{"area_id":null,"config_entries":["01J91MY5JBJH64GQS4WA5SYVK2"],"config_entries_subentries":{"01J91MY5JBJH64GQS4WA5SYVK2":[null]},"configuration_url":null,"connections":[],"created_at":"2024-09-30T14:21:50.142602+00:00","disabled_by":null,"entry_type":"service","hw_version":null,"id":"adffec60f63f0032597d03eba833062f","identifiers":[["hassio","supervisor"]],"labels":[],"manufacturer":"Home Assistant","model":"Home Assistant Supervisor","model_id":null,"modified_at":"2025-04-28T15:39:43.376487+00:00","name_by_user":null,"name":"Home Assistant Supervisor","primary_config_entry":"01J91MY5JBJH64GQS4WA5SYVK2","serial_number":null,"sw_version":"2025.04.1","via_device_id":null},
|
||||
{"area_id":null,"config_entries":["01J91MY5JBJH64GQS4WA5SYVK2"],"config_entries_subentries":{"01J91MY5JBJH64GQS4WA5SYVK2":[null]},"configuration_url":null,"connections":[],"created_at":"2024-09-30T14:21:50.143809+00:00","disabled_by":null,"entry_type":"service","hw_version":null,"id":"c0da2b39c57cd1e833c93a1e0d6081a3","identifiers":[["hassio","host"]],"labels":[],"manufacturer":"Home Assistant","model":"Home Assistant Host","model_id":null,"modified_at":"2024-09-30T14:21:50.143950+00:00","name_by_user":null,"name":"Home Assistant Host","primary_config_entry":"01J91MY5JBJH64GQS4WA5SYVK2","serial_number":null,"sw_version":null,"via_device_id":null},
|
||||
{"area_id":null,"config_entries":["01JAT8AZHNSF6WPATHNE5D6XM4"],"config_entries_subentries":{"01JAT8AZHNSF6WPATHNE5D6XM4":[null]},"configuration_url":null,"connections":[],"created_at":"2024-10-22T14:25:11.025424+00:00","disabled_by":null,"entry_type":null,"hw_version":null,"id":"4733401a628a81b68b232c34f905301b","identifiers":[["browser_mod","pecan-station"]],"labels":[],"manufacturer":"Browser Mod","model":null,"model_id":null,"modified_at":"2024-10-22T14:25:11.025531+00:00","name_by_user":null,"name":"pecan-station","primary_config_entry":"01JAT8AZHNSF6WPATHNE5D6XM4","serial_number":null,"sw_version":null,"via_device_id":null},
|
||||
{"area_id":null,"config_entries":["01JC1CF88K7YTRGZT79CYQS536"],"config_entries_subentries":{"01JC1CF88K7YTRGZT79CYQS536":[null]},"configuration_url":null,"connections":[["mac","54:af:97:09:93:f8"]],"created_at":"2024-11-06T18:40:57.705358+00:00","disabled_by":null,"entry_type":null,"hw_version":"1.0","id":"15c657490c8db92b1bc833658f6569b6","identifiers":[["tplink","54:AF:97:09:93:F8"]],"labels":[],"manufacturer":"TP-Link","model":"KP115","model_id":null,"modified_at":"2025-01-07T17:42:11.829574+00:00","name_by_user":null,"name":"Vibratory Conveyor","primary_config_entry":"01JC1CF88K7YTRGZT79CYQS536","serial_number":null,"sw_version":"1.0.20 Build 221125 Rel.092759","via_device_id":null},
|
||||
{"area_id":null,"config_entries":["01JCEH26PKQ5WZ0GQ495DCG9WM"],"config_entries_subentries":{"01JCEH26PKQ5WZ0GQ495DCG9WM":[null]},"configuration_url":"http://192.168.1.75:80","connections":[["mac","b8:d6:1a:87:d2:a8"]],"created_at":"2024-11-11T21:13:55.808375+00:00","disabled_by":null,"entry_type":null,"hw_version":"gen2","id":"084130f8681211cd468bd18045eb1ab9","identifiers":[["shelly","B8D61A87D2A8"]],"labels":[],"manufacturer":"Shelly","model":"Shelly Plus 1 UL","model_id":"SNSW-001X15UL","modified_at":"2025-04-08T16:18:28.942959+00:00","name_by_user":null,"name":"shellyplus1-b8d61a87d2a8","primary_config_entry":"01JCEH26PKQ5WZ0GQ495DCG9WM","serial_number":null,"sw_version":"20250318-152131/1.5.1-g01dd7ff","via_device_id":null},
|
||||
{"area_id":null,"config_entries":["01JCEH2A2K8Y9VEK9XV8ZPC69H"],"config_entries_subentries":{"01JCEH2A2K8Y9VEK9XV8ZPC69H":[null]},"configuration_url":"http://192.168.1.7:80","connections":[["mac","b8:d6:1a:8a:75:08"]],"created_at":"2024-11-11T21:13:58.504970+00:00","disabled_by":null,"entry_type":null,"hw_version":"gen2","id":"fde74ce0fd1f87791cb2ca71048bef6d","identifiers":[["shelly","B8D61A8A7508"]],"labels":[],"manufacturer":"Shelly","model":"Shelly Plus 1 UL","model_id":"SNSW-001X15UL","modified_at":"2025-03-20T15:46:20.018112+00:00","name_by_user":null,"name":"shellyplus1-b8d61a8a7508","primary_config_entry":"01JCEH2A2K8Y9VEK9XV8ZPC69H","serial_number":null,"sw_version":"20250318-152131/1.5.1-g01dd7ff","via_device_id":null},
|
||||
{"area_id":null,"config_entries":["01JD0G9D9ZXBC64EXQHJXE9AEQ"],"config_entries_subentries":{"01JD0G9D9ZXBC64EXQHJXE9AEQ":[null]},"configuration_url":"http://192.168.1.213:80","connections":[["mac","e8:6b:ea:e4:73:74"]],"created_at":"2024-11-18T20:44:08.027219+00:00","disabled_by":null,"entry_type":null,"hw_version":"gen2","id":"c737f87bdbd8f7d053768bd9e117eb77","identifiers":[["shelly","E86BEAE47374"]],"labels":[],"manufacturer":"Shelly","model":"Shelly Plus 0-10V Dimmer","model_id":"SNDM-00100WW","modified_at":"2025-03-28T16:50:12.354583+00:00","name_by_user":null,"name":"shellyplus010v-e86beae47374","primary_config_entry":"01JD0G9D9ZXBC64EXQHJXE9AEQ","serial_number":null,"sw_version":"20250318-152134/1.5.1-g01dd7ff","via_device_id":null},
|
||||
{"area_id":null,"config_entries":["55db3b46f3bf75777e4779fd25ed6bca"],"config_entries_subentries":{"55db3b46f3bf75777e4779fd25ed6bca":[null]},"configuration_url":"https://172.22.114.176/protect/devices/67a4e50a0161d803e4000713","connections":[["mac","28:70:4e:13:0b:20"]],"created_at":"2025-02-06T16:36:26.444883+00:00","disabled_by":null,"entry_type":null,"hw_version":null,"id":"a371935516f75019d9202827d7ef3687","identifiers":[],"labels":[],"manufacturer":"Ubiquiti","model":"G5 Flex","model_id":"UVC G5 Flex","modified_at":"2025-04-28T15:39:46.229267+00:00","name_by_user":null,"name":"G5 Flex 03","primary_config_entry":"55db3b46f3bf75777e4779fd25ed6bca","serial_number":null,"sw_version":"4.75.62","via_device_id":"437dbead96a87ac711c7a4d99a79c6cb"},
|
||||
{"area_id":null,"config_entries":["55db3b46f3bf75777e4779fd25ed6bca"],"config_entries_subentries":{"55db3b46f3bf75777e4779fd25ed6bca":[null]},"configuration_url":"https://172.22.114.176/protect/devices/67a4e60f03a0d803e400079d","connections":[["mac","28:70:4e:13:0a:c8"]],"created_at":"2025-02-06T16:40:48.009984+00:00","disabled_by":null,"entry_type":null,"hw_version":null,"id":"a2c601d0660b01099a56a85487019dd1","identifiers":[],"labels":[],"manufacturer":"Ubiquiti","model":"G5 Flex","model_id":"UVC G5 Flex","modified_at":"2025-04-28T15:39:46.230784+00:00","name_by_user":null,"name":"G5 Flex 04","primary_config_entry":"55db3b46f3bf75777e4779fd25ed6bca","serial_number":null,"sw_version":"4.75.62","via_device_id":"437dbead96a87ac711c7a4d99a79c6cb"},
|
||||
{"area_id":null,"config_entries":["55db3b46f3bf75777e4779fd25ed6bca"],"config_entries_subentries":{"55db3b46f3bf75777e4779fd25ed6bca":[null]},"configuration_url":"https://172.22.114.176/protect/devices/67a4e879012bd803e4000908","connections":[["mac","28:70:4e:13:0a:e8"]],"created_at":"2025-02-06T16:51:05.423024+00:00","disabled_by":null,"entry_type":null,"hw_version":null,"id":"4e74c75d01e805bbc0f4915d697dc400","identifiers":[],"labels":[],"manufacturer":"Ubiquiti","model":"G5 Flex","model_id":"UVC G5 Flex","modified_at":"2025-04-28T15:39:46.229033+00:00","name_by_user":null,"name":"G5 Flex 05","primary_config_entry":"55db3b46f3bf75777e4779fd25ed6bca","serial_number":null,"sw_version":"4.75.62","via_device_id":"437dbead96a87ac711c7a4d99a79c6cb"},
|
||||
{"area_id":null,"config_entries":["01J91MY5JBJH64GQS4WA5SYVK2"],"config_entries_subentries":{"01J91MY5JBJH64GQS4WA5SYVK2":[null]},"configuration_url":"homeassistant://hassio/addon/cb646a50_get","connections":[],"created_at":"2024-10-22T13:47:35.159933+00:00","disabled_by":null,"entry_type":"service","hw_version":null,"id":"b75d4a683512046d198747fd8f4f8d55","identifiers":[["hassio","cb646a50_get"]],"labels":[],"manufacturer":"HACS Add-ons Repository","model":"Home Assistant Add-on","model_id":null,"modified_at":"2025-02-17T17:27:42.631347+00:00","name_by_user":null,"name":"Get HACS","primary_config_entry":"01J91MY5JBJH64GQS4WA5SYVK2","serial_number":null,"sw_version":"1.3.1","via_device_id":null},
|
||||
{"area_id":null,"config_entries":["01JKXM91D1BNRC2505EHYRESCW"],"config_entries_subentries":{"01JKXM91D1BNRC2505EHYRESCW":[null]},"configuration_url":null,"connections":[["bluetooth","CC:7B:5C:0D:0E:B6"]],"created_at":"2025-02-12T17:45:59.458394+00:00","disabled_by":null,"entry_type":null,"hw_version":null,"id":"584b8dd35655c0a34f2154293a1d4c82","identifiers":[],"labels":[],"manufacturer":"Espressif Inc. (shelly)","model":"SNSW-001X15UL","model_id":null,"modified_at":"2025-04-08T16:25:25.578351+00:00","name_by_user":null,"name":"Sheller Drum Enable (CC:7B:5C:0D:0E:B6)","primary_config_entry":"01JKXM91D1BNRC2505EHYRESCW","serial_number":null,"sw_version":null,"via_device_id":"98bc66339afcb5b0ee18bd93df9ab0f0"},
|
||||
{"area_id":null,"config_entries":["01JKXMMK0K5E1EZJ90XJCAKPBB"],"config_entries_subentries":{"01JKXMMK0K5E1EZJ90XJCAKPBB":[null]},"configuration_url":"homeassistant://hacs","connections":[],"created_at":"2024-10-22T13:49:17.215255+00:00","disabled_by":null,"entry_type":"service","hw_version":null,"id":"9ea8fbfa22ebda2250a4a9abb50877e0","identifiers":[["hacs","0717a0cd-745c-48fd-9b16-c8534c9704f9-bc944b0f-fd42-4a58-a072-ade38d1444cd"]],"labels":[],"manufacturer":"hacs.xyz","model":"","model_id":null,"modified_at":"2025-04-30T15:58:59.637649+00:00","name_by_user":null,"name":"HACS","primary_config_entry":"01JKXMMK0K5E1EZJ90XJCAKPBB","serial_number":null,"sw_version":"2.0.5","via_device_id":null},
|
||||
{"area_id":null,"config_entries":["01JKXMMK0K5E1EZJ90XJCAKPBB"],"config_entries_subentries":{"01JKXMMK0K5E1EZJ90XJCAKPBB":[null]},"configuration_url":"homeassistant://hacs/repository/700780425","connections":[],"created_at":"2024-11-15T19:02:20.334863+00:00","disabled_by":null,"entry_type":"service","hw_version":null,"id":"6cc029fc86b718ccb3c24c16f7b92fab","identifiers":[["hacs","700780425"]],"labels":[],"manufacturer":"jekalmin","model":"integration","model_id":null,"modified_at":"2025-04-30T15:58:59.637568+00:00","name_by_user":null,"name":"extended_openai_conversation","primary_config_entry":"01JKXMMK0K5E1EZJ90XJCAKPBB","serial_number":null,"sw_version":null,"via_device_id":null},
|
||||
{"area_id":null,"config_entries":["01J91MY5JBJH64GQS4WA5SYVK2"],"config_entries_subentries":{"01J91MY5JBJH64GQS4WA5SYVK2":[null]},"configuration_url":null,"connections":[],"created_at":"2024-09-30T14:21:50.145121+00:00","disabled_by":null,"entry_type":"service","hw_version":null,"id":"db658cf1b7110495bd574ecbfea00f65","identifiers":[["hassio","OS"]],"labels":[],"manufacturer":"Home Assistant","model":"Home Assistant Operating System","model_id":null,"modified_at":"2025-04-15T19:07:09.268572+00:00","name_by_user":null,"name":"Home Assistant Operating System","primary_config_entry":"01J91MY5JBJH64GQS4WA5SYVK2","serial_number":null,"sw_version":"15.2","via_device_id":null},
|
||||
{"area_id":null,"config_entries":["01J91MY5JBJH64GQS4WA5SYVK2"],"config_entries_subentries":{"01J91MY5JBJH64GQS4WA5SYVK2":[null]},"configuration_url":"homeassistant://hassio/addon/5c53de3b_esphome","connections":[],"created_at":"2025-02-17T18:53:46.200555+00:00","disabled_by":null,"entry_type":"service","hw_version":null,"id":"f8e2640249eaf8eef9cc69e25428274b","identifiers":[["hassio","5c53de3b_esphome"]],"labels":[],"manufacturer":"ESPHome","model":"Home Assistant Add-on","model_id":null,"modified_at":"2025-04-30T15:19:50.230036+00:00","name_by_user":null,"name":"ESPHome Device Builder","primary_config_entry":"01J91MY5JBJH64GQS4WA5SYVK2","serial_number":null,"sw_version":"2025.4.1","via_device_id":null},
|
||||
{"area_id":null,"config_entries":["01JKXMMK0K5E1EZJ90XJCAKPBB"],"config_entries_subentries":{"01JKXMMK0K5E1EZJ90XJCAKPBB":[null]},"configuration_url":"homeassistant://hacs/repository/445609628","connections":[],"created_at":"2024-11-19T16:24:46.160618+00:00","disabled_by":null,"entry_type":"service","hw_version":null,"id":"e513618baa485a8b9b14e6d504e35ce1","identifiers":[["hacs","445609628"]],"labels":[],"manufacturer":"Soloam","model":"integration","model_id":null,"modified_at":"2025-04-30T15:58:59.637236+00:00","name_by_user":null,"name":"PID Controller","primary_config_entry":"01JKXMMK0K5E1EZJ90XJCAKPBB","serial_number":null,"sw_version":null,"via_device_id":null},
|
||||
{"area_id":null,"config_entries":["01JT3VZ248SG5C78NYX10R3VYC"],"config_entries_subentries":{"01JT3VZ248SG5C78NYX10R3VYC":[null]},"configuration_url":null,"connections":[],"created_at":"2025-03-10T19:44:30.863385+00:00","disabled_by":null,"entry_type":null,"hw_version":null,"id":"dcf6caf077557f82b96ad86995b9fd59","identifiers":[["mobile_app","98A9293F-CB61-45EC-B9B6-192CF169CDAD"]],"labels":[],"manufacturer":"Apple","model":"iPad12,1","model_id":null,"modified_at":"2025-04-30T17:29:58.667490+00:00","name_by_user":null,"name":"Factory’s iPad","primary_config_entry":"01JT3VZ248SG5C78NYX10R3VYC","serial_number":null,"sw_version":"15.5","via_device_id":null},
|
||||
{"area_id":null,"config_entries":["01JKXMMK0K5E1EZJ90XJCAKPBB"],"config_entries_subentries":{"01JKXMMK0K5E1EZJ90XJCAKPBB":[null]},"configuration_url":"homeassistant://hacs/repository/194140521","connections":[],"created_at":"2024-10-22T13:49:51.003152+00:00","disabled_by":null,"entry_type":"service","hw_version":null,"id":"62403f88b1f1defa8b35404d6c8b1519","identifiers":[["hacs","194140521"]],"labels":[],"manufacturer":"thomasloven","model":"integration","model_id":null,"modified_at":"2025-04-30T15:58:59.637383+00:00","name_by_user":null,"name":"browser_mod","primary_config_entry":"01JKXMMK0K5E1EZJ90XJCAKPBB","serial_number":null,"sw_version":null,"via_device_id":null},
|
||||
{"area_id":null,"config_entries":["01JAT8AZHNSF6WPATHNE5D6XM4"],"config_entries_subentries":{"01JAT8AZHNSF6WPATHNE5D6XM4":[null]},"configuration_url":null,"connections":[],"created_at":"2025-03-10T20:13:06.573887+00:00","disabled_by":null,"entry_type":null,"hw_version":null,"id":"73396d1a6a0c2edf200915f3d71c03df","identifiers":[["browser_mod","Ingest iPad"]],"labels":[],"manufacturer":"Browser Mod","model":null,"model_id":null,"modified_at":"2025-03-10T20:13:06.574104+00:00","name_by_user":null,"name":"Ingest iPad","primary_config_entry":"01JAT8AZHNSF6WPATHNE5D6XM4","serial_number":null,"sw_version":null,"via_device_id":null},
|
||||
{"area_id":null,"config_entries":["01JKXMMK0K5E1EZJ90XJCAKPBB"],"config_entries_subentries":{"01JKXMMK0K5E1EZJ90XJCAKPBB":[null]},"configuration_url":"homeassistant://hacs/repository/755918775","connections":[],"created_at":"2025-03-11T13:46:12.044435+00:00","disabled_by":null,"entry_type":"service","hw_version":null,"id":"a232aae81a9efd67ebff8c2f0c6505ab","identifiers":[["hacs","755918775"]],"labels":[],"manufacturer":"EuleMitKeule","model":"integration","model_id":null,"modified_at":"2025-04-30T15:58:59.637725+00:00","name_by_user":null,"name":"Device Tools","primary_config_entry":"01JKXMMK0K5E1EZJ90XJCAKPBB","serial_number":null,"sw_version":null,"via_device_id":null},
|
||||
{"area_id":"jc_machine","config_entries":["01JP2QDHQY8CAXX25PYRGQRD4E","01J5NH0A41TYMRRGASE2QJ0SQ5"],"config_entries_subentries":{"01JP2QDHQY8CAXX25PYRGQRD4E":[null],"01J5NH0A41TYMRRGASE2QJ0SQ5":[null]},"configuration_url":null,"connections":[],"created_at":"2025-03-11T13:48:33.800889+00:00","disabled_by":null,"entry_type":null,"hw_version":null,"id":"bd08feb9232d2cb49bb7742ac8a6bd13","identifiers":[["device_tools","01JP2QDHQY8CAXX25PYRGQRD4E"]],"labels":[],"manufacturer":"ME&E","model":"JC","model_id":null,"modified_at":"2025-03-11T14:44:01.725284+00:00","name_by_user":null,"name":"JC Cracker","primary_config_entry":"01JP2QDHQY8CAXX25PYRGQRD4E","serial_number":null,"sw_version":null,"via_device_id":null},
|
||||
{"area_id":"meyer_machine","config_entries":["01JP2RT3TC42WQMQX86W8PBYGR"],"config_entries_subentries":{"01JP2RT3TC42WQMQX86W8PBYGR":[null]},"configuration_url":null,"connections":[],"created_at":"2025-03-11T14:12:51.485092+00:00","disabled_by":null,"entry_type":null,"hw_version":null,"id":"7fb5d449ee7e9b4b92969c8006ef3f8d","identifiers":[["device_tools","01JP2RT3TC42WQMQX86W8PBYGR"]],"labels":[],"manufacturer":"ME&E","model":"Meyer","model_id":null,"modified_at":"2025-03-11T14:44:11.799766+00:00","name_by_user":null,"name":"Meyer Cracker","primary_config_entry":"01JP2RT3TC42WQMQX86W8PBYGR","serial_number":null,"sw_version":null,"via_device_id":null},
|
||||
{"area_id":"sheller_machine","config_entries":["04978edcf23c54a047e4f421779754ad","779bd6f1f6eebd9fb67b45fa40386e0c","51355cd442e2d0c51a3a43811555ee77","ce337fdb50b165d7ba080505d5c73343","01JP2RYPC08P80S3KGJGNQTG5B","143eb40c5189f32be0eddf773eaaeceb"],"config_entries_subentries":{"01JP2RYPC08P80S3KGJGNQTG5B":[null],"04978edcf23c54a047e4f421779754ad":[null],"ce337fdb50b165d7ba080505d5c73343":[null],"779bd6f1f6eebd9fb67b45fa40386e0c":[null],"51355cd442e2d0c51a3a43811555ee77":[null],"143eb40c5189f32be0eddf773eaaeceb":[null]},"configuration_url":null,"connections":[],"created_at":"2025-03-11T14:15:21.599606+00:00","disabled_by":null,"entry_type":null,"hw_version":null,"id":"ac5aeecdb81f624f67d67ca7cd25d696","identifiers":[["device_tools","01JP2RYPC08P80S3KGJGNQTG5B"]],"labels":[],"manufacturer":"ME&E","model":"14\" Sheller","model_id":null,"modified_at":"2025-03-11T14:44:26.816716+00:00","name_by_user":null,"name":"Sheller Machine","primary_config_entry":"01JP2RYPC08P80S3KGJGNQTG5B","serial_number":null,"sw_version":null,"via_device_id":null},
|
||||
{"area_id":null,"config_entries":["01J91MY5JBJH64GQS4WA5SYVK2"],"config_entries_subentries":{"01J91MY5JBJH64GQS4WA5SYVK2":[null]},"configuration_url":"homeassistant://hassio/addon/core_configurator","connections":[],"created_at":"2025-03-24T16:13:16.077174+00:00","disabled_by":null,"entry_type":"service","hw_version":null,"id":"5dc30d37c1bf60fa9300c60997f41124","identifiers":[["hassio","core_configurator"]],"labels":[],"manufacturer":"Official add-ons","model":"Home Assistant Add-on","model_id":null,"modified_at":"2025-03-24T16:13:16.077376+00:00","name_by_user":null,"name":"File editor","primary_config_entry":"01J91MY5JBJH64GQS4WA5SYVK2","serial_number":null,"sw_version":"5.8.0","via_device_id":null},
|
||||
{"area_id":null,"config_entries":["01JRB3H9PGGBS0Y87YRVZFBN7S"],"config_entries_subentries":{"01JRB3H9PGGBS0Y87YRVZFBN7S":[null]},"configuration_url":"homeassistant://config/backup","connections":[],"created_at":"2025-04-08T16:25:33.652452+00:00","disabled_by":null,"entry_type":"service","hw_version":null,"id":"833d67c5ea71b9e75c69ce70ce2b86e3","identifiers":[["backup","backup_manager"]],"labels":[],"manufacturer":"Home Assistant","model":"Home Assistant Backup","model_id":null,"modified_at":"2025-04-28T15:39:46.109960+00:00","name_by_user":null,"name":"Backup","primary_config_entry":"01JRB3H9PGGBS0Y87YRVZFBN7S","serial_number":null,"sw_version":"2025.4.4","via_device_id":null},
|
||||
{"area_id":null,"config_entries":["01JKXMMK0K5E1EZJ90XJCAKPBB"],"config_entries_subentries":{"01JKXMMK0K5E1EZJ90XJCAKPBB":[null]},"configuration_url":"homeassistant://hacs/repository/202220932","connections":[],"created_at":"2025-04-08T22:09:31.339524+00:00","disabled_by":null,"entry_type":"service","hw_version":null,"id":"81de29d058e241575299f4994a995c2b","identifiers":[["hacs","202220932"]],"labels":[],"manufacturer":"thomasloven","model":"integration","model_id":null,"modified_at":"2025-04-30T15:58:59.637482+00:00","name_by_user":null,"name":"Favicon changer","primary_config_entry":"01JKXMMK0K5E1EZJ90XJCAKPBB","serial_number":null,"sw_version":null,"via_device_id":null},
|
||||
{"area_id":"jc_machine","config_entries":["01JS27E8649HW777MJ0KF33QA2"],"config_entries_subentries":{"01JS27E8649HW777MJ0KF33QA2":[null]},"configuration_url":null,"connections":[["bluetooth","58:10:31:E7:7C:01"]],"created_at":"2025-04-17T15:56:20.046196+00:00","disabled_by":null,"entry_type":null,"hw_version":"usb:v1D6Bp0246d054F","id":"5cebfa2bd3196fd6bcbb13eaf8b06060","identifiers":[],"labels":[],"manufacturer":"Realtek","model":"Bluetooth Radio (0bda:0852)","model_id":null,"modified_at":"2025-04-17T15:56:24.903881+00:00","name_by_user":null,"name":"hci0 (58:10:31:E7:7C:01)","primary_config_entry":"01JS27E8649HW777MJ0KF33QA2","serial_number":null,"sw_version":"homeassistant","via_device_id":null},
|
||||
{"area_id":null,"config_entries":["143eb40c5189f32be0eddf773eaaeceb"],"config_entries_subentries":{"143eb40c5189f32be0eddf773eaaeceb":[null]},"configuration_url":null,"connections":[],"created_at":"2025-04-17T18:03:13.001134+00:00","disabled_by":null,"entry_type":null,"hw_version":null,"id":"a3919ba3219a48fe522181e5d2bab2b9","identifiers":[["mqtt","barcode-scanner"]],"labels":[],"manufacturer":"Netum","model":"C750","model_id":null,"modified_at":"2025-04-17T18:03:13.001159+00:00","name_by_user":null,"name":"Barcode Tag Scanner","primary_config_entry":"143eb40c5189f32be0eddf773eaaeceb","serial_number":null,"sw_version":null,"via_device_id":null},
|
||||
{"area_id":null,"config_entries":["01JS2N75XQZ4PBCK7CDCZ1X2KP"],"config_entries_subentries":{"01JS2N75XQZ4PBCK7CDCZ1X2KP":[null]},"configuration_url":"http://192.168.1.212","connections":[],"created_at":"2025-04-17T19:56:30.791864+00:00","disabled_by":null,"entry_type":null,"hw_version":null,"id":"309798ef61a8710c832940c131598d1b","identifiers":[["voip","sip:engr-ugaif@192.168.1.212:5060"]],"labels":[],"manufacturer":null,"model":"engr-ugaif","model_id":null,"modified_at":"2025-04-17T19:57:23.810258+00:00","name_by_user":null,"name":"192.168.1.212","primary_config_entry":"01JS2N75XQZ4PBCK7CDCZ1X2KP","serial_number":null,"sw_version":null,"via_device_id":null},
|
||||
{"area_id":null,"config_entries":["01JS2TPHY0WM1EHSFM6PP3N93A"],"config_entries_subentries":{"01JS2TPHY0WM1EHSFM6PP3N93A":[null]},"configuration_url":null,"connections":[["upnp","uuid:8d6c30fd-0475-44b8-9c12-466689f35e58"],["mac","f4:e2:c6:e4:d2:f6"]],"created_at":"2025-04-17T21:32:55.240642+00:00","disabled_by":null,"entry_type":null,"hw_version":null,"id":"0b7a672398058e36e4e48a37b3756762","identifiers":[["upnp_host","192.168.1.1"],["upnp","uuid:8d6c30fd-0475-44b8-9c12-466689f35e58::urn:schemas-upnp-org:device:InternetGatewayDevice:2"],["upnp_serial_number","f4:e2:c6:e4:d2:f6"]],"labels":[],"manufacturer":"Ubiquiti Networks","model":"UXG Lite","model_id":null,"modified_at":"2025-04-30T18:14:08.209676+00:00","name_by_user":null,"name":"UniFi NeXt-Gen Gateway","primary_config_entry":"01JS2TPHY0WM1EHSFM6PP3N93A","serial_number":null,"sw_version":null,"via_device_id":null},
|
||||
{"area_id":null,"config_entries":["55db3b46f3bf75777e4779fd25ed6bca"],"config_entries_subentries":{"55db3b46f3bf75777e4779fd25ed6bca":[null]},"configuration_url":"https://172.22.114.176/protect/devices/6806c4c101b40103e40e3c44","connections":[["mac","28:70:4e:17:70:ec"]],"created_at":"2025-04-21T22:20:49.529297+00:00","disabled_by":null,"entry_type":null,"hw_version":null,"id":"b8f6f84d89447912b6703e9e7fdd1664","identifiers":[],"labels":[],"manufacturer":"Ubiquiti","model":"G5 Flex","model_id":"UVC G5 Flex","modified_at":"2025-04-28T15:39:46.229588+00:00","name_by_user":null,"name":"G5 Flex 06","primary_config_entry":"55db3b46f3bf75777e4779fd25ed6bca","serial_number":null,"sw_version":"4.75.62","via_device_id":"437dbead96a87ac711c7a4d99a79c6cb"},
|
||||
{"area_id":null,"config_entries":["55db3b46f3bf75777e4779fd25ed6bca"],"config_entries_subentries":{"55db3b46f3bf75777e4779fd25ed6bca":[null]},"configuration_url":"https://172.22.114.176/protect/devices/6806cda000570103e40e8640","connections":[["mac","28:70:4e:17:70:b7"]],"created_at":"2025-04-21T22:58:40.161827+00:00","disabled_by":null,"entry_type":null,"hw_version":null,"id":"e5423b32c524f6ad6157c8ca3a156cba","identifiers":[],"labels":[],"manufacturer":"Ubiquiti","model":"G5 Flex","model_id":"UVC G5 Flex","modified_at":"2025-04-28T15:39:46.230383+00:00","name_by_user":null,"name":"G5 Flex 07","primary_config_entry":"55db3b46f3bf75777e4779fd25ed6bca","serial_number":null,"sw_version":"4.75.62","via_device_id":"437dbead96a87ac711c7a4d99a79c6cb"},
|
||||
{"area_id":null,"config_entries":["143eb40c5189f32be0eddf773eaaeceb"],"config_entries_subentries":{"143eb40c5189f32be0eddf773eaaeceb":[null]},"configuration_url":null,"connections":[],"created_at":"2025-04-28T17:07:09.462690+00:00","disabled_by":null,"entry_type":null,"hw_version":null,"id":"abc30051787003e963ee34ee5baf9fc9","identifiers":[["mqtt","scanner_1"]],"labels":[],"manufacturer":"Netum","model":"C750","model_id":null,"modified_at":"2025-04-28T17:07:09.462725+00:00","name_by_user":null,"name":"Barcode Tag Scanner scanner_1","primary_config_entry":"143eb40c5189f32be0eddf773eaaeceb","serial_number":null,"sw_version":null,"via_device_id":null},
|
||||
{"area_id":null,"config_entries":["143eb40c5189f32be0eddf773eaaeceb"],"config_entries_subentries":{"143eb40c5189f32be0eddf773eaaeceb":[null]},"configuration_url":null,"connections":[],"created_at":"2025-04-28T17:07:09.463333+00:00","disabled_by":null,"entry_type":null,"hw_version":null,"id":"da613b80ee3c2a0e321ba6cc1d18ef8f","identifiers":[["mqtt","scanner_2"]],"labels":[],"manufacturer":"Netum","model":"C750","model_id":null,"modified_at":"2025-04-28T17:07:09.463344+00:00","name_by_user":null,"name":"Barcode Tag Scanner scanner_2","primary_config_entry":"143eb40c5189f32be0eddf773eaaeceb","serial_number":null,"sw_version":null,"via_device_id":null},
|
||||
{"area_id":null,"config_entries":["143eb40c5189f32be0eddf773eaaeceb"],"config_entries_subentries":{"143eb40c5189f32be0eddf773eaaeceb":[null]},"configuration_url":null,"connections":[],"created_at":"2025-04-28T17:07:09.464252+00:00","disabled_by":null,"entry_type":null,"hw_version":null,"id":"3924458c8a1bec286d9eabdb1bea7bb5","identifiers":[["mqtt","scanner_3"]],"labels":[],"manufacturer":"Netum","model":"DS-8100","model_id":null,"modified_at":"2025-04-28T17:07:09.464263+00:00","name_by_user":null,"name":"Barcode Tag Scanner scanner_3","primary_config_entry":"143eb40c5189f32be0eddf773eaaeceb","serial_number":null,"sw_version":null,"via_device_id":null},
|
||||
{"area_id":null,"config_entries":["143eb40c5189f32be0eddf773eaaeceb"],"config_entries_subentries":{"143eb40c5189f32be0eddf773eaaeceb":[null]},"configuration_url":null,"connections":[],"created_at":"2025-04-29T19:21:32.491747+00:00","disabled_by":null,"entry_type":null,"hw_version":null,"id":"82395c211fed9b358b604c33ca0bea9e","identifiers":[["mqtt","scanner_4"]],"labels":[],"manufacturer":"Generic","model":"C200","model_id":null,"modified_at":"2025-04-29T20:26:14.633733+00:00","name_by_user":null,"name":"scanner_4","primary_config_entry":"143eb40c5189f32be0eddf773eaaeceb","serial_number":null,"sw_version":null,"via_device_id":null},
|
||||
{"area_id":null,"config_entries":["143eb40c5189f32be0eddf773eaaeceb"],"config_entries_subentries":{"143eb40c5189f32be0eddf773eaaeceb":[null]},"configuration_url":null,"connections":[],"created_at":"2025-04-29T19:21:32.492879+00:00","disabled_by":null,"entry_type":null,"hw_version":null,"id":"c11499726c52d4e88f2d46ec6db288a6","identifiers":[["mqtt","scanner_5"]],"labels":[],"manufacturer":"Generic","model":"C200","model_id":null,"modified_at":"2025-04-29T20:26:14.634494+00:00","name_by_user":null,"name":"scanner_5","primary_config_entry":"143eb40c5189f32be0eddf773eaaeceb","serial_number":null,"sw_version":null,"via_device_id":null},
|
||||
{"area_id":null,"config_entries":["01J91MY5JBJH64GQS4WA5SYVK2"],"config_entries_subentries":{"01J91MY5JBJH64GQS4WA5SYVK2":[null]},"configuration_url":"homeassistant://hassio/addon/a0d7b954_vscode","connections":[],"created_at":"2024-09-30T14:21:50.119745+00:00","disabled_by":null,"entry_type":"service","hw_version":null,"id":"4337d0ff05fb782c9ec2ae035488a00a","identifiers":[["hassio","a0d7b954_vscode"]],"labels":[],"manufacturer":"Home Assistant Community Add-ons","model":"Home Assistant Add-on","model_id":null,"modified_at":"2025-04-29T19:49:42.628821+00:00","name_by_user":null,"name":"Studio Code Server","primary_config_entry":"01J91MY5JBJH64GQS4WA5SYVK2","serial_number":null,"sw_version":"5.19.2","via_device_id":null},
|
||||
{"area_id":null,"config_entries":["143eb40c5189f32be0eddf773eaaeceb"],"config_entries_subentries":{"143eb40c5189f32be0eddf773eaaeceb":[null]},"configuration_url":null,"connections":[],"created_at":"2025-04-29T20:26:14.674232+00:00","disabled_by":null,"entry_type":null,"hw_version":null,"id":"e6057a6d16d6d00efb7d6039743c2548","identifiers":[["mqtt","scanner_6"]],"labels":[],"manufacturer":"Generic","model":"C200","model_id":null,"modified_at":"2025-04-29T20:26:14.674247+00:00","name_by_user":null,"name":"scanner_6","primary_config_entry":"143eb40c5189f32be0eddf773eaaeceb","serial_number":null,"sw_version":null,"via_device_id":null}
|
||||
],
|
||||
"deleted_devices": []
|
||||
"deleted_devices": [
|
||||
{"config_entries":["01JAT8AZHNSF6WPATHNE5D6XM4"],"config_entries_subentries":{"01JAT8AZHNSF6WPATHNE5D6XM4":[null]},"connections":[],"created_at":"2024-10-22T14:25:00.929774+00:00","identifiers":[["browser_mod","fcc41464-a82af7ed"]],"id":"a9c5ef824d4d18dc6dc5b5ac532ca56f","orphaned_timestamp":null,"modified_at":"2024-10-22T14:25:11.023598+00:00"},
|
||||
{"config_entries":["01JAT8AZHNSF6WPATHNE5D6XM4"],"config_entries_subentries":{"01JAT8AZHNSF6WPATHNE5D6XM4":[null]},"connections":[],"created_at":"2024-10-22T14:39:00.251130+00:00","identifiers":[["browser_mod","CAST"]],"id":"8a4ea6b3c054155a422487206c6f9721","orphaned_timestamp":null,"modified_at":"2024-10-22T14:39:04.566708+00:00"},
|
||||
{"config_entries":["01J91MY5JBJH64GQS4WA5SYVK2"],"config_entries_subentries":{"01J91MY5JBJH64GQS4WA5SYVK2":[null]},"connections":[],"created_at":"2025-03-14T14:46:55.910012+00:00","identifiers":[["hassio","a0d7b954_grafana"]],"id":"784a7de235c6a7e30bd8d0e3e216901b","orphaned_timestamp":null,"modified_at":"2025-03-20T15:45:06.991729+00:00"},
|
||||
{"config_entries":[],"config_entries_subentries":{},"connections":[],"created_at":"2024-11-15T19:12:44.589247+00:00","identifiers":[["voip","sip:IPCall@192.168.1.211:5060"]],"id":"8582e1111c8603bb324b69215f997ccb","orphaned_timestamp":1744918805.925204,"modified_at":"2025-04-17T19:40:05.925227+00:00"},
|
||||
{"config_entries":["01JCRJQQPN6G7D20NFABPG3Y3V"],"config_entries_subentries":{"01JCRJQQPN6G7D20NFABPG3Y3V":[null]},"connections":[],"created_at":"2024-11-15T18:52:58.976862+00:00","identifiers":[["openai_conversation","01JCRJQQPN6G7D20NFABPG3Y3V"]],"id":"ccab8330c09c9c861e5585070e06578b","orphaned_timestamp":null,"modified_at":"2025-04-17T19:40:15.927029+00:00"},
|
||||
{"config_entries":["01JKR7TM57W6HDZWKBEZQ859MJ"],"config_entries_subentries":{"01JKR7TM57W6HDZWKBEZQ859MJ":[null]},"connections":[],"created_at":"2025-02-10T15:32:12.084456+00:00","identifiers":[["openai_conversation","01JKR7TM57W6HDZWKBEZQ859MJ"]],"id":"b5e22a86624b56f6a2e92ca2bf634bd4","orphaned_timestamp":null,"modified_at":"2025-04-17T19:40:15.927277+00:00"},
|
||||
{"config_entries":[],"config_entries_subentries":{},"connections":[],"created_at":"2025-04-17T19:39:19.654863+00:00","identifiers":[["voip","sip:IPCall@192.168.1.212:5060"]],"id":"719560bae4c6a4753d29d5ce0a4b5441","orphaned_timestamp":1744919825.216603,"modified_at":"2025-04-17T19:57:05.216620+00:00"}
|
||||
]
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"version": 1,
|
||||
"minor_version": 1,
|
||||
"key": "esphome.dashboard",
|
||||
"data": {
|
||||
"info": {
|
||||
"addon_slug": "5c53de3b_esphome",
|
||||
"host": "127.0.0.1",
|
||||
"port": 65155
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
]
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"version": "6",
|
||||
"minor_version": 1,
|
||||
"key": "hacs.hacs",
|
||||
"data": {
|
||||
"archived_repositories": [],
|
||||
"renamed_repositories": {},
|
||||
"ignored_repositories": []
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -3,17 +3,17 @@
|
|||
"minor_version": 1,
|
||||
"key": "http",
|
||||
"data": {
|
||||
"ssl_profile": "modern",
|
||||
"ip_ban_enabled": true,
|
||||
"cors_allowed_origins": [
|
||||
"https://cast.home-assistant.io"
|
||||
],
|
||||
"server_port": 8123,
|
||||
"ssl_profile": "modern",
|
||||
"server_host": [
|
||||
"0.0.0.0",
|
||||
"::"
|
||||
],
|
||||
"cors_allowed_origins": [
|
||||
"https://cast.home-assistant.io"
|
||||
],
|
||||
"use_x_frame_options": true,
|
||||
"server_port": 8123,
|
||||
"login_attempts_threshold": -1
|
||||
}
|
||||
}
|
|
@ -3,6 +3,31 @@
|
|||
"minor_version": 1,
|
||||
"key": "input_number",
|
||||
"data": {
|
||||
"items": []
|
||||
"items": [
|
||||
{
|
||||
"id": "jc_rolling_pecan_sum",
|
||||
"min": 0.0,
|
||||
"max": 1000000.0,
|
||||
"name": "JC Rolling Pecan Sum",
|
||||
"mode": "slider",
|
||||
"step": 1.0
|
||||
},
|
||||
{
|
||||
"id": "jc_rate_prev_error",
|
||||
"min": -40.0,
|
||||
"max": 40.0,
|
||||
"name": "JC Rate Prev Error",
|
||||
"mode": "slider",
|
||||
"step": 1.0
|
||||
},
|
||||
{
|
||||
"id": "jc_rate_prev_adjustment",
|
||||
"min": -40.0,
|
||||
"max": 40.0,
|
||||
"name": "JC Rate Prev Adjustment",
|
||||
"mode": "slider",
|
||||
"step": 1.0
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
|
@ -11,7 +11,9 @@
|
|||
"options": [
|
||||
"General Yield Sample",
|
||||
"Half Yield Sample",
|
||||
"Tare"
|
||||
"Tare",
|
||||
"Wet Mass Sample",
|
||||
"Dry Mass Sample"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
@ -37,6 +39,38 @@
|
|||
"15",
|
||||
"16"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "activesample",
|
||||
"name": "ActiveSample",
|
||||
"icon": "mdi:scale-balance",
|
||||
"options": [
|
||||
"None",
|
||||
"1",
|
||||
"2",
|
||||
"3",
|
||||
"4",
|
||||
"5",
|
||||
"6",
|
||||
"7",
|
||||
"8",
|
||||
"9",
|
||||
"10",
|
||||
"11",
|
||||
"12",
|
||||
"13",
|
||||
"14",
|
||||
"15",
|
||||
"16",
|
||||
"17",
|
||||
"18",
|
||||
"19",
|
||||
"20",
|
||||
"21",
|
||||
"22",
|
||||
"23",
|
||||
"24"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
{
|
||||
"version": 1,
|
||||
"minor_version": 1,
|
||||
"key": "lovelace.ayoub_testing",
|
||||
"data": {
|
||||
"config": {
|
||||
"views": [
|
||||
{
|
||||
"title": "Home",
|
||||
"sections": [
|
||||
{
|
||||
"type": "grid",
|
||||
"cards": [
|
||||
{
|
||||
"type": "heading",
|
||||
"heading": "New section"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"version": 1,
|
||||
"minor_version": 1,
|
||||
"key": "lovelace.dashboard_ingest",
|
||||
"data": {
|
||||
"config": {
|
||||
"views": [
|
||||
{
|
||||
"title": "Home",
|
||||
"sections": []
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
|
@ -54,6 +54,9 @@
|
|||
{
|
||||
"entity": "input_number.jc_crush_amount",
|
||||
"name": "Select Crush Amount"
|
||||
},
|
||||
{
|
||||
"entity": "number.jc_angle"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -62,38 +65,39 @@
|
|||
{
|
||||
"type": "entities",
|
||||
"entities": [
|
||||
{
|
||||
"entity": "switch.jc_run_control"
|
||||
},
|
||||
{
|
||||
"entity": "switch.shellyplus1pm_c049ef8c7310_switch_0",
|
||||
"secondary_info": "none",
|
||||
"name": "Vibratory Feed Enable"
|
||||
},
|
||||
{
|
||||
"entity": "input_number.jc_plate_frequency"
|
||||
},
|
||||
{
|
||||
"entity": "switch.tp_link_power_strip_d7c1_vibratory_conveyor"
|
||||
},
|
||||
{
|
||||
"entity": "switch.shellyplus1_b8d61a87d2a8_switch_0"
|
||||
}
|
||||
],
|
||||
"show_header_toggle": false
|
||||
},
|
||||
{
|
||||
"type": "entities",
|
||||
"entities": [
|
||||
"number.jc_feed_time",
|
||||
"switch.jc_limit_feed_duration"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "entities",
|
||||
"entities": [
|
||||
{
|
||||
"entity": "input_number.jc_plate_frequency"
|
||||
"entity": "sensor.jc_throughput_count"
|
||||
},
|
||||
{
|
||||
"entity": "input_number.jc_feeder_frequency"
|
||||
"entity": "number.jc_feedrate_setpoint"
|
||||
},
|
||||
{
|
||||
"entity": "input_number.batch_weight"
|
||||
"entity": "sensor.jc_pi_controller_output"
|
||||
}
|
||||
],
|
||||
"title": "Set Manually on Machine (ONLY FOR DATA LOGGING)"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -39,16 +39,12 @@
|
|||
"entity_id": "script.mqtt_disable_torque"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"entity": "input_number.meyer_screw_displacement"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"graph": "line",
|
||||
"type": "sensor",
|
||||
"entity": "sensor.meyer_position_raw",
|
||||
"name": "Current Position",
|
||||
"detail": 1
|
||||
},
|
||||
{
|
||||
"type": "horizontal-stack",
|
||||
"cards": [
|
||||
|
@ -181,7 +177,13 @@
|
|||
{
|
||||
"type": "entities",
|
||||
"entities": [
|
||||
"switch.tp_link_power_strip_d7c1_vibratory_conveyor"
|
||||
{
|
||||
"entity": "switch.run_control",
|
||||
"name": "Meyer Enable"
|
||||
},
|
||||
{
|
||||
"entity": "switch.tp_link_power_strip_d7c1_vibratory_conveyor"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
|
|
@ -33,6 +33,15 @@
|
|||
"entity": "sensor.steinlite_sample_temperature"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "entities",
|
||||
"entities": [
|
||||
{
|
||||
"entity": "sensor.jc_moisttech_pecan_moisture"
|
||||
}
|
||||
],
|
||||
"title": "JC MoistTech Values"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -43,7 +43,7 @@
|
|||
},
|
||||
{
|
||||
"type": "entity",
|
||||
"entity": "sensor.sheller_scale",
|
||||
"entity": "sensor.sheller_scale_stable",
|
||||
"name": "Sheller Scale",
|
||||
"icon": "mdi:scale"
|
||||
}
|
||||
|
@ -58,7 +58,7 @@
|
|||
"cards": [
|
||||
{
|
||||
"type": "entity",
|
||||
"entity": "sensor.precision_scale",
|
||||
"entity": "sensor.precision_scale_stable",
|
||||
"name": "Precision Scale",
|
||||
"icon": "mdi:scale"
|
||||
},
|
||||
|
|
|
@ -37,10 +37,12 @@
|
|||
{
|
||||
"type": "entities",
|
||||
"entities": [
|
||||
"switch.shellyplus1_cc7b5c0d0eb4_switch_0",
|
||||
"light.shellyplus010v_e86beae4d350_light_0",
|
||||
"switch.shellyplus1_cc7b5c0d316c_switch_0",
|
||||
"light.shellyplus010v_e86beae4df24_light_0"
|
||||
{
|
||||
"entity": "switch.shellyplus1_cc7b5c0d0eb4_switch_0"
|
||||
},
|
||||
{
|
||||
"entity": "switch.shellyplus1_cc7b5c0d316c_switch_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
|
@ -49,6 +51,21 @@
|
|||
"sensor.shelling_machine_drum_rpm",
|
||||
"sensor.shelling_machine_paddle_rpm"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "entities",
|
||||
"entities": [
|
||||
{
|
||||
"entity": "switch.shellyplus1_b8d61a87d2a8_switch_0"
|
||||
},
|
||||
{
|
||||
"entity": "switch.shellyplus1_b8d61a8a7508_switch_0"
|
||||
},
|
||||
{
|
||||
"entity": "switch.tp_link_power_strip_d7c1_vibratory_conveyor",
|
||||
"name": "Cracker Output Vibratory Conveyor"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
{
|
||||
"version": 1,
|
||||
"minor_version": 1,
|
||||
"key": "lovelace.moisture_scales",
|
||||
"data": {
|
||||
"config": {
|
||||
"views": [
|
||||
{
|
||||
"title": "Overview",
|
||||
"cards": [
|
||||
{
|
||||
"title": "Precision Scale",
|
||||
"type": "vertical-stack",
|
||||
"cards": [
|
||||
{
|
||||
"type": "horizontal-stack",
|
||||
"cards": [
|
||||
{
|
||||
"type": "entity",
|
||||
"entity": "sensor.precision_scale_stable",
|
||||
"name": "Precision Scale",
|
||||
"icon": "mdi:scale"
|
||||
},
|
||||
{
|
||||
"type": "entity",
|
||||
"entity": "input_select.activesample",
|
||||
"icon": "mdi:weight-gram"
|
||||
},
|
||||
{
|
||||
"type": "entity",
|
||||
"entity": "input_select.mass_sample_mode",
|
||||
"icon": "mdi:weight"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "entity",
|
||||
"icon": "mdi:label-percent-outline",
|
||||
"entity": "sensor.latest_moisture_sample_by_weight"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"type": "sidebar"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
|
@ -57,6 +57,33 @@
|
|||
"require_admin": false,
|
||||
"show_in_sidebar": true,
|
||||
"mode": "storage"
|
||||
},
|
||||
{
|
||||
"id": "moisture_scales",
|
||||
"show_in_sidebar": true,
|
||||
"icon": "mdi:thermometer-water",
|
||||
"title": "Moisture Scales",
|
||||
"require_admin": false,
|
||||
"mode": "storage",
|
||||
"url_path": "moisture-scales"
|
||||
},
|
||||
{
|
||||
"id": "ayoub_testing",
|
||||
"show_in_sidebar": true,
|
||||
"icon": "mdi:test-tube",
|
||||
"title": "Ayoub Testing",
|
||||
"require_admin": false,
|
||||
"mode": "storage",
|
||||
"url_path": "ayoub-testing"
|
||||
},
|
||||
{
|
||||
"id": "dashboard_ingest",
|
||||
"show_in_sidebar": true,
|
||||
"icon": "mdi:download-multiple-outline",
|
||||
"title": "Ingest",
|
||||
"require_admin": false,
|
||||
"mode": "storage",
|
||||
"url_path": "dashboard-ingest"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
{
|
||||
"version": 1,
|
||||
"minor_version": 1,
|
||||
"key": "lovelace_resources",
|
||||
"data": {
|
||||
"items": [
|
||||
{
|
||||
"id": "a93a7b692b1f4df8940975f3599a4a5f",
|
||||
"url": "/hacsfiles/fullscreen-card/fullscreen-card.js?hacstag=29028126708",
|
||||
"type": "module"
|
||||
},
|
||||
{
|
||||
"id": "6b0163845db0475c8163c3f6363f32bd",
|
||||
"url": "/hacsfiles/homeassistant-browser-control-card/browser-control-card.js?hacstag=452251255140",
|
||||
"type": "module"
|
||||
},
|
||||
{
|
||||
"id": "26160932f8534a2d930da18b23be0192",
|
||||
"url": "/browser_mod.js?automatically-added",
|
||||
"type": "module"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -9,7 +9,8 @@
|
|||
"name": "Pecan Lab",
|
||||
"user_id": "5ef2c8c082b14074a6e84da694ef2f35",
|
||||
"device_trackers": [
|
||||
"device_tracker.lab_phone"
|
||||
"device_tracker.lab_phone",
|
||||
"device_tracker.factorys_ipad"
|
||||
]
|
||||
}
|
||||
]
|
||||
|
|
|
@ -62,10 +62,52 @@
|
|||
},
|
||||
{
|
||||
"created": "2024-10-01T13:01:00.227210+00:00",
|
||||
"dismissed_version": null,
|
||||
"dismissed_version": "2024.9.3",
|
||||
"domain": "mqtt",
|
||||
"is_persistent": false,
|
||||
"issue_id": "payload_template_deprecation_/jc/height"
|
||||
},
|
||||
{
|
||||
"created": "2024-10-22T13:49:50.991576+00:00",
|
||||
"dismissed_version": null,
|
||||
"domain": "hacs",
|
||||
"is_persistent": false,
|
||||
"issue_id": "restart_required_194140521_tags/v2.3.1"
|
||||
},
|
||||
{
|
||||
"created": "2024-11-12T18:56:33.104642+00:00",
|
||||
"dismissed_version": null,
|
||||
"domain": "modbus",
|
||||
"is_persistent": false,
|
||||
"issue_id": "deprecated_restart"
|
||||
},
|
||||
{
|
||||
"created": "2024-11-13T16:33:06.238133+00:00",
|
||||
"dismissed_version": null,
|
||||
"domain": "modbus",
|
||||
"is_persistent": false,
|
||||
"issue_id": "duplicate_entity_name"
|
||||
},
|
||||
{
|
||||
"created": "2024-12-18T18:29:06.178442+00:00",
|
||||
"dismissed_version": null,
|
||||
"domain": "hacs",
|
||||
"is_persistent": false,
|
||||
"issue_id": "restart_required_152294445_tags/4.5"
|
||||
},
|
||||
{
|
||||
"created": "2025-03-08T23:03:51.211687+00:00",
|
||||
"dismissed_version": null,
|
||||
"domain": "hassio",
|
||||
"is_persistent": false,
|
||||
"issue_id": "9936e4e952644082be72d7975b6cd64a"
|
||||
},
|
||||
{
|
||||
"created": "2025-03-10T19:45:47.722481+00:00",
|
||||
"dismissed_version": null,
|
||||
"domain": "hacs",
|
||||
"is_persistent": false,
|
||||
"issue_id": "restart_required_194140521_tags/v2.3.3"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
449
.storage/tag
449
.storage/tag
|
@ -6,70 +6,60 @@
|
|||
"items": [
|
||||
{
|
||||
"id": "1B983A04",
|
||||
"name": "take-halves-mass-sample",
|
||||
"last_scanned": "2024-09-30T18:44:49.661014+00:00",
|
||||
"last_scanned": "2025-04-30T16:13:50.356724+00:00",
|
||||
"migrated": true,
|
||||
"device_id": "c9fd6d12a759980d491da079f9e3a545"
|
||||
"device_id": "e6057a6d16d6d00efb7d6039743c2548"
|
||||
},
|
||||
{
|
||||
"id": "1ADE4304",
|
||||
"name": "take-general-mass-sample",
|
||||
"last_scanned": "2024-09-30T18:53:09.940972+00:00",
|
||||
"last_scanned": "2025-04-30T18:26:55.891929+00:00",
|
||||
"migrated": true,
|
||||
"device_id": "c9fd6d12a759980d491da079f9e3a545"
|
||||
"device_id": "e6057a6d16d6d00efb7d6039743c2548"
|
||||
},
|
||||
{
|
||||
"id": "1AECA004",
|
||||
"name": "bin1a",
|
||||
"last_scanned": "2024-07-19T17:27:19.039906+00:00",
|
||||
"last_scanned": "2024-11-06T19:41:23.031062+00:00",
|
||||
"migrated": true,
|
||||
"device_id": "feec748cc156a1ca441d38caf620ecfe"
|
||||
},
|
||||
{
|
||||
"id": "1983D704",
|
||||
"name": "bin1b",
|
||||
"last_scanned": "2024-09-13T16:09:14.338395+00:00",
|
||||
"last_scanned": "2025-04-29T17:04:11.934582+00:00",
|
||||
"migrated": true,
|
||||
"device_id": "feec748cc156a1ca441d38caf620ecfe"
|
||||
},
|
||||
{
|
||||
"id": "72F8A704",
|
||||
"name": "bin2a",
|
||||
"last_scanned": "2024-09-13T16:09:42.455356+00:00",
|
||||
"last_scanned": "2025-04-29T17:04:43.671901+00:00",
|
||||
"migrated": true,
|
||||
"device_id": "feec748cc156a1ca441d38caf620ecfe"
|
||||
},
|
||||
{
|
||||
"id": "1A414204",
|
||||
"name": "bin2b",
|
||||
"last_scanned": "2024-09-11T15:29:01.967216+00:00",
|
||||
"last_scanned": "2024-10-30T21:21:31.399347+00:00",
|
||||
"migrated": true,
|
||||
"device_id": "feec748cc156a1ca441d38caf620ecfe"
|
||||
},
|
||||
{
|
||||
"id": "17D18B04",
|
||||
"name": "bin3a",
|
||||
"last_scanned": "2024-09-13T16:10:11.571487+00:00",
|
||||
"last_scanned": "2025-04-29T17:04:55.241504+00:00",
|
||||
"migrated": true,
|
||||
"device_id": "feec748cc156a1ca441d38caf620ecfe"
|
||||
},
|
||||
{
|
||||
"id": "1B9E6204",
|
||||
"name": "bin3b",
|
||||
"last_scanned": "2024-06-05T15:08:47.505079+00:00",
|
||||
"migrated": true
|
||||
},
|
||||
{
|
||||
"id": "1699DD04",
|
||||
"name": "bin4a",
|
||||
"last_scanned": "2024-09-30T18:48:52.498310+00:00",
|
||||
"last_scanned": "2025-04-30T18:26:05.528169+00:00",
|
||||
"migrated": true,
|
||||
"device_id": "feec748cc156a1ca441d38caf620ecfe"
|
||||
"device_id": "82395c211fed9b358b604c33ca0bea9e"
|
||||
},
|
||||
{
|
||||
"id": "1DD6A704",
|
||||
"name": "bin4b",
|
||||
"last_scanned": "2024-09-18T16:23:44.615496+00:00",
|
||||
"last_scanned": "2025-04-29T17:02:18.547200+00:00",
|
||||
"migrated": true,
|
||||
"device_id": "feec748cc156a1ca441d38caf620ecfe"
|
||||
},
|
||||
|
@ -85,120 +75,105 @@
|
|||
},
|
||||
{
|
||||
"id": "1A4E4B04",
|
||||
"last_scanned": "2024-05-09T15:03:42.985968+00:00",
|
||||
"migrated": true
|
||||
"last_scanned": "2025-04-17T20:43:29.034953+00:00",
|
||||
"migrated": true,
|
||||
"device_id": "a3919ba3219a48fe522181e5d2bab2b9"
|
||||
},
|
||||
{
|
||||
"id": "11275204",
|
||||
"last_scanned": "2024-09-30T18:45:50.522348+00:00",
|
||||
"name": "Cup 1",
|
||||
"last_scanned": "2025-04-30T16:14:14.886967+00:00",
|
||||
"migrated": true,
|
||||
"device_id": "c9fd6d12a759980d491da079f9e3a545"
|
||||
"device_id": "e6057a6d16d6d00efb7d6039743c2548"
|
||||
},
|
||||
{
|
||||
"id": "123E8904",
|
||||
"last_scanned": "2024-09-30T18:46:04.951017+00:00",
|
||||
"name": "Cup 2",
|
||||
"last_scanned": "2025-04-29T17:34:12.369552+00:00",
|
||||
"migrated": true,
|
||||
"device_id": "c9fd6d12a759980d491da079f9e3a545"
|
||||
"device_id": "3924458c8a1bec286d9eabdb1bea7bb5"
|
||||
},
|
||||
{
|
||||
"id": "1FD4D704",
|
||||
"last_scanned": "2024-09-30T18:46:11.337333+00:00",
|
||||
"name": "Cup 3",
|
||||
"last_scanned": "2025-04-30T18:30:48.283286+00:00",
|
||||
"migrated": true,
|
||||
"device_id": "c9fd6d12a759980d491da079f9e3a545"
|
||||
"device_id": "e6057a6d16d6d00efb7d6039743c2548"
|
||||
},
|
||||
{
|
||||
"id": "12619704",
|
||||
"last_scanned": "2024-09-30T18:46:40.483063+00:00",
|
||||
"name": "Cup 4",
|
||||
"last_scanned": "2025-04-30T16:20:51.084691+00:00",
|
||||
"migrated": true,
|
||||
"device_id": "c9fd6d12a759980d491da079f9e3a545"
|
||||
"device_id": "e6057a6d16d6d00efb7d6039743c2548"
|
||||
},
|
||||
{
|
||||
"id": "105B1904",
|
||||
"last_scanned": "2024-09-30T18:45:56.943239+00:00",
|
||||
"name": "Cup 5",
|
||||
"last_scanned": "2025-04-30T16:03:19.637582+00:00",
|
||||
"migrated": true,
|
||||
"device_id": "c9fd6d12a759980d491da079f9e3a545"
|
||||
"device_id": "e6057a6d16d6d00efb7d6039743c2548"
|
||||
},
|
||||
{
|
||||
"id": "10A49304",
|
||||
"last_scanned": "2024-09-30T18:45:01.668428+00:00",
|
||||
"name": "Cup 6",
|
||||
"last_scanned": "2025-04-29T17:34:55.364348+00:00",
|
||||
"migrated": true,
|
||||
"device_id": "c9fd6d12a759980d491da079f9e3a545"
|
||||
"device_id": "3924458c8a1bec286d9eabdb1bea7bb5"
|
||||
},
|
||||
{
|
||||
"id": "1145FB04",
|
||||
"last_scanned": "2024-09-30T18:46:26.264244+00:00",
|
||||
"name": "Cup 7",
|
||||
"last_scanned": "2025-04-29T17:33:13.903827+00:00",
|
||||
"migrated": true,
|
||||
"device_id": "c9fd6d12a759980d491da079f9e3a545"
|
||||
"device_id": "3924458c8a1bec286d9eabdb1bea7bb5"
|
||||
},
|
||||
{
|
||||
"id": "11866104",
|
||||
"last_scanned": "2024-09-30T18:46:19.747487+00:00",
|
||||
"name": "Cup 8",
|
||||
"last_scanned": "2025-04-29T17:33:46.197532+00:00",
|
||||
"migrated": true,
|
||||
"device_id": "c9fd6d12a759980d491da079f9e3a545"
|
||||
"device_id": "3924458c8a1bec286d9eabdb1bea7bb5"
|
||||
},
|
||||
{
|
||||
"id": "1FBCF604",
|
||||
"last_scanned": "2024-09-30T18:46:48.985172+00:00",
|
||||
"name": "Cup 9",
|
||||
"last_scanned": "2025-04-30T16:13:51.195542+00:00",
|
||||
"migrated": true,
|
||||
"device_id": "c9fd6d12a759980d491da079f9e3a545"
|
||||
"device_id": "e6057a6d16d6d00efb7d6039743c2548"
|
||||
},
|
||||
{
|
||||
"id": "11261C04",
|
||||
"last_scanned": "2024-09-30T18:45:09.658757+00:00",
|
||||
"name": "Cup 10",
|
||||
"last_scanned": "2025-04-30T16:03:59.977236+00:00",
|
||||
"migrated": true,
|
||||
"device_id": "c9fd6d12a759980d491da079f9e3a545"
|
||||
"device_id": "e6057a6d16d6d00efb7d6039743c2548"
|
||||
},
|
||||
{
|
||||
"id": "116CE504",
|
||||
"last_scanned": "2024-09-30T18:45:26.180399+00:00",
|
||||
"name": "Cup 11",
|
||||
"last_scanned": "2025-04-30T16:14:48.717617+00:00",
|
||||
"migrated": true,
|
||||
"device_id": "c9fd6d12a759980d491da079f9e3a545"
|
||||
"device_id": "e6057a6d16d6d00efb7d6039743c2548"
|
||||
},
|
||||
{
|
||||
"id": "10192204",
|
||||
"last_scanned": "2024-09-30T18:45:18.037085+00:00",
|
||||
"name": "Cup 12",
|
||||
"last_scanned": "2025-04-30T16:15:23.648372+00:00",
|
||||
"migrated": true,
|
||||
"device_id": "c9fd6d12a759980d491da079f9e3a545"
|
||||
"device_id": "e6057a6d16d6d00efb7d6039743c2548"
|
||||
},
|
||||
{
|
||||
"id": "1051FF04",
|
||||
"last_scanned": "2024-09-30T18:46:56.503381+00:00",
|
||||
"name": "Cup 13",
|
||||
"last_scanned": "2025-04-30T16:19:45.247773+00:00",
|
||||
"migrated": true,
|
||||
"device_id": "c9fd6d12a759980d491da079f9e3a545"
|
||||
"device_id": "e6057a6d16d6d00efb7d6039743c2548"
|
||||
},
|
||||
{
|
||||
"id": "10E1A404",
|
||||
"last_scanned": "2024-09-30T18:46:33.780378+00:00",
|
||||
"name": "Cup 14",
|
||||
"last_scanned": "2025-04-30T16:20:21.932297+00:00",
|
||||
"migrated": true,
|
||||
"device_id": "c9fd6d12a759980d491da079f9e3a545"
|
||||
"device_id": "e6057a6d16d6d00efb7d6039743c2548"
|
||||
},
|
||||
{
|
||||
"id": "1FB63C04",
|
||||
"last_scanned": "2024-09-30T18:45:42.002772+00:00",
|
||||
"name": "Cup 15",
|
||||
"last_scanned": "2025-04-30T16:04:37.390274+00:00",
|
||||
"migrated": true,
|
||||
"device_id": "c9fd6d12a759980d491da079f9e3a545"
|
||||
"device_id": "e6057a6d16d6d00efb7d6039743c2548"
|
||||
},
|
||||
{
|
||||
"id": "10F61E04",
|
||||
"last_scanned": "2024-09-30T18:45:33.579954+00:00",
|
||||
"name": "Cup 16",
|
||||
"last_scanned": "2025-04-30T16:05:19.458571+00:00",
|
||||
"migrated": true,
|
||||
"device_id": "c9fd6d12a759980d491da079f9e3a545"
|
||||
"device_id": "e6057a6d16d6d00efb7d6039743c2548"
|
||||
},
|
||||
{
|
||||
"id": "BCEA6208",
|
||||
|
@ -209,6 +184,336 @@
|
|||
"id": "5E7D5408",
|
||||
"last_scanned": "2024-08-05T17:07:58.912663+00:00",
|
||||
"device_id": "c9fd6d12a759980d491da079f9e3a545"
|
||||
},
|
||||
{
|
||||
"id": "126AC504",
|
||||
"last_scanned": "2024-10-16T20:06:21.273277+00:00",
|
||||
"device_id": "c9fd6d12a759980d491da079f9e3a545"
|
||||
},
|
||||
{
|
||||
"id": "1FD56B04",
|
||||
"last_scanned": "2024-10-16T20:08:46.232202+00:00",
|
||||
"device_id": "c9fd6d12a759980d491da079f9e3a545"
|
||||
},
|
||||
{
|
||||
"id": "12A61104",
|
||||
"last_scanned": "2024-10-16T20:09:58.680406+00:00",
|
||||
"device_id": "c9fd6d12a759980d491da079f9e3a545"
|
||||
},
|
||||
{
|
||||
"id": "12123E04",
|
||||
"last_scanned": "2024-10-16T20:05:12.492842+00:00",
|
||||
"device_id": "c9fd6d12a759980d491da079f9e3a545"
|
||||
},
|
||||
{
|
||||
"id": "1174DD04",
|
||||
"last_scanned": "2024-10-16T20:05:22.681966+00:00",
|
||||
"device_id": "c9fd6d12a759980d491da079f9e3a545"
|
||||
},
|
||||
{
|
||||
"id": "11A35B04",
|
||||
"last_scanned": "2024-10-16T20:11:38.427013+00:00",
|
||||
"device_id": "c9fd6d12a759980d491da079f9e3a545"
|
||||
},
|
||||
{
|
||||
"id": "10113604",
|
||||
"last_scanned": "2024-10-16T20:05:51.523423+00:00",
|
||||
"device_id": "c9fd6d12a759980d491da079f9e3a545"
|
||||
},
|
||||
{
|
||||
"id": "11397F04",
|
||||
"last_scanned": "2024-10-16T20:09:30.779940+00:00",
|
||||
"device_id": "c9fd6d12a759980d491da079f9e3a545"
|
||||
},
|
||||
{
|
||||
"id": "124B4E04",
|
||||
"last_scanned": "2024-10-16T20:10:54.345613+00:00",
|
||||
"device_id": "c9fd6d12a759980d491da079f9e3a545"
|
||||
},
|
||||
{
|
||||
"id": "11D64604",
|
||||
"last_scanned": "2024-10-16T20:10:58.045273+00:00",
|
||||
"device_id": "c9fd6d12a759980d491da079f9e3a545"
|
||||
},
|
||||
{
|
||||
"id": "10CC6C04",
|
||||
"last_scanned": "2024-10-16T20:09:41.630994+00:00",
|
||||
"device_id": "c9fd6d12a759980d491da079f9e3a545"
|
||||
},
|
||||
{
|
||||
"id": "112BB904",
|
||||
"last_scanned": "2024-10-16T20:05:41.715114+00:00",
|
||||
"device_id": "c9fd6d12a759980d491da079f9e3a545"
|
||||
},
|
||||
{
|
||||
"id": "1C997304",
|
||||
"last_scanned": "2025-04-30T16:01:32.847095+00:00",
|
||||
"device_id": "e6057a6d16d6d00efb7d6039743c2548"
|
||||
},
|
||||
{
|
||||
"id": "101BF204",
|
||||
"last_scanned": "2024-10-16T20:07:07.932103+00:00",
|
||||
"device_id": "c9fd6d12a759980d491da079f9e3a545"
|
||||
},
|
||||
{
|
||||
"id": "10C8FE04",
|
||||
"last_scanned": "2024-10-16T20:05:32.488955+00:00",
|
||||
"device_id": "c9fd6d12a759980d491da079f9e3a545"
|
||||
},
|
||||
{
|
||||
"id": "11AADF04",
|
||||
"last_scanned": "2024-10-16T20:08:13.854832+00:00",
|
||||
"device_id": "c9fd6d12a759980d491da079f9e3a545"
|
||||
},
|
||||
{
|
||||
"id": "12359604",
|
||||
"last_scanned": "2025-04-30T12:39:14.439061+00:00",
|
||||
"device_id": "e6057a6d16d6d00efb7d6039743c2548"
|
||||
},
|
||||
{
|
||||
"id": "1CF2EB04",
|
||||
"last_scanned": "2024-10-16T20:08:35.301584+00:00",
|
||||
"device_id": "c9fd6d12a759980d491da079f9e3a545"
|
||||
},
|
||||
{
|
||||
"id": "1252B404",
|
||||
"last_scanned": "2025-04-30T17:03:56.595065+00:00",
|
||||
"device_id": "e6057a6d16d6d00efb7d6039743c2548"
|
||||
},
|
||||
{
|
||||
"id": "128BC104",
|
||||
"last_scanned": "2024-10-16T20:11:27.597855+00:00",
|
||||
"device_id": "c9fd6d12a759980d491da079f9e3a545"
|
||||
},
|
||||
{
|
||||
"id": "12923C04",
|
||||
"last_scanned": "2024-10-16T20:08:03.605599+00:00",
|
||||
"device_id": "c9fd6d12a759980d491da079f9e3a545"
|
||||
},
|
||||
{
|
||||
"id": "111E5704",
|
||||
"last_scanned": "2024-10-16T20:11:14.641640+00:00",
|
||||
"device_id": "c9fd6d12a759980d491da079f9e3a545"
|
||||
},
|
||||
{
|
||||
"id": "107D4B04",
|
||||
"last_scanned": "2024-10-16T20:11:49.529152+00:00",
|
||||
"device_id": "c9fd6d12a759980d491da079f9e3a545"
|
||||
},
|
||||
{
|
||||
"id": "128DD104",
|
||||
"last_scanned": "2024-10-16T20:06:12.188014+00:00",
|
||||
"device_id": "c9fd6d12a759980d491da079f9e3a545"
|
||||
},
|
||||
{
|
||||
"id": "6ED8C60D",
|
||||
"last_scanned": "2025-02-18T16:04:11.262696+00:00",
|
||||
"device_id": "feec748cc156a1ca441d38caf620ecfe"
|
||||
},
|
||||
{
|
||||
"id": "6DEB99FD",
|
||||
"last_scanned": "2025-02-18T17:40:35.032864+00:00",
|
||||
"device_id": "feec748cc156a1ca441d38caf620ecfe"
|
||||
},
|
||||
{
|
||||
"id": "6ED8858D",
|
||||
"last_scanned": "2025-02-18T17:52:21.641988+00:00",
|
||||
"device_id": "feec748cc156a1ca441d38caf620ecfe"
|
||||
},
|
||||
{
|
||||
"id": "6E51E56D",
|
||||
"last_scanned": "2025-02-19T15:40:14.957160+00:00",
|
||||
"device_id": "feec748cc156a1ca441d38caf620ecfe"
|
||||
},
|
||||
{
|
||||
"id": "810112472622",
|
||||
"last_scanned": "2025-04-28T17:15:15.683878+00:00",
|
||||
"device_id": "da613b80ee3c2a0e321ba6cc1d18ef8f"
|
||||
},
|
||||
{
|
||||
"id": "500033",
|
||||
"last_scanned": "2025-04-17T20:59:48.751425+00:00",
|
||||
"device_id": "a3919ba3219a48fe522181e5d2bab2b9"
|
||||
},
|
||||
{
|
||||
"id": "500021",
|
||||
"last_scanned": "2025-04-17T20:21:13.366104+00:00",
|
||||
"device_id": "a3919ba3219a48fe522181e5d2bab2b9"
|
||||
},
|
||||
{
|
||||
"id": "043a981b6f6180",
|
||||
"last_scanned": "2025-04-17T20:33:33.558767+00:00",
|
||||
"device_id": "a3919ba3219a48fe522181e5d2bab2b9"
|
||||
},
|
||||
{
|
||||
"id": "0443de1a6f6180",
|
||||
"last_scanned": "2025-04-17T20:33:42.392952+00:00",
|
||||
"device_id": "a3919ba3219a48fe522181e5d2bab2b9"
|
||||
},
|
||||
{
|
||||
"id": "04e56c11bb2a81",
|
||||
"last_scanned": "2025-04-17T20:33:57.366177+00:00",
|
||||
"device_id": "a3919ba3219a48fe522181e5d2bab2b9"
|
||||
},
|
||||
{
|
||||
"id": "041ef610bb2a81",
|
||||
"last_scanned": "2025-04-17T20:35:21.147841+00:00",
|
||||
"device_id": "a3919ba3219a48fe522181e5d2bab2b9"
|
||||
},
|
||||
{
|
||||
"id": "10f61e04",
|
||||
"last_scanned": "2025-04-17T20:41:23.785347+00:00",
|
||||
"device_id": "a3919ba3219a48fe522181e5d2bab2b9"
|
||||
},
|
||||
{
|
||||
"id": "500032",
|
||||
"last_scanned": "2025-04-28T17:07:31.549866+00:00",
|
||||
"device_id": "3924458c8a1bec286d9eabdb1bea7bb5"
|
||||
},
|
||||
{
|
||||
"id": "500004",
|
||||
"last_scanned": "2025-04-17T20:44:27.861365+00:00",
|
||||
"device_id": "a3919ba3219a48fe522181e5d2bab2b9"
|
||||
},
|
||||
{
|
||||
"id": "500005",
|
||||
"last_scanned": "2025-04-17T20:44:28.469193+00:00",
|
||||
"device_id": "a3919ba3219a48fe522181e5d2bab2b9"
|
||||
},
|
||||
{
|
||||
"id": "500001",
|
||||
"last_scanned": "2025-04-17T20:44:28.957248+00:00",
|
||||
"device_id": "a3919ba3219a48fe522181e5d2bab2b9"
|
||||
},
|
||||
{
|
||||
"id": "500012",
|
||||
"last_scanned": "2025-04-17T20:44:29.469038+00:00",
|
||||
"device_id": "a3919ba3219a48fe522181e5d2bab2b9"
|
||||
},
|
||||
{
|
||||
"id": "500011",
|
||||
"last_scanned": "2025-04-17T20:44:34.681370+00:00",
|
||||
"device_id": "a3919ba3219a48fe522181e5d2bab2b9"
|
||||
},
|
||||
{
|
||||
"id": "500002",
|
||||
"last_scanned": "2025-04-17T20:44:35.459190+00:00",
|
||||
"device_id": "a3919ba3219a48fe522181e5d2bab2b9"
|
||||
},
|
||||
{
|
||||
"id": "730494",
|
||||
"last_scanned": "2025-04-17T21:05:17.116111+00:00",
|
||||
"device_id": "a3919ba3219a48fe522181e5d2bab2b9"
|
||||
},
|
||||
{
|
||||
"id": "732981",
|
||||
"last_scanned": "2025-04-17T20:59:18.951921+00:00",
|
||||
"device_id": "a3919ba3219a48fe522181e5d2bab2b9"
|
||||
},
|
||||
{
|
||||
"id": "740351",
|
||||
"last_scanned": "2025-04-17T21:05:43.742718+00:00",
|
||||
"device_id": "a3919ba3219a48fe522181e5d2bab2b9"
|
||||
},
|
||||
{
|
||||
"id": "739763",
|
||||
"last_scanned": "2025-04-17T21:01:17.839352+00:00",
|
||||
"device_id": "a3919ba3219a48fe522181e5d2bab2b9"
|
||||
},
|
||||
{
|
||||
"id": "732159",
|
||||
"last_scanned": "2025-04-17T21:01:32.957734+00:00",
|
||||
"device_id": "a3919ba3219a48fe522181e5d2bab2b9"
|
||||
},
|
||||
{
|
||||
"id": "723430",
|
||||
"last_scanned": "2025-04-17T21:04:13.084969+00:00",
|
||||
"device_id": "a3919ba3219a48fe522181e5d2bab2b9"
|
||||
},
|
||||
{
|
||||
"id": "88043A98",
|
||||
"last_scanned": "2025-04-28T17:07:14.582487+00:00",
|
||||
"device_id": "3924458c8a1bec286d9eabdb1bea7bb5"
|
||||
},
|
||||
{
|
||||
"id": "^J1B983A04",
|
||||
"last_scanned": "2025-04-29T19:51:53.295298+00:00",
|
||||
"device_id": "82395c211fed9b358b604c33ca0bea9e"
|
||||
},
|
||||
{
|
||||
"id": "^J^J1B983A04",
|
||||
"last_scanned": "2025-04-29T19:51:53.395591+00:00",
|
||||
"device_id": "82395c211fed9b358b604c33ca0bea9e"
|
||||
},
|
||||
{
|
||||
"id": "^J^J^J1B983A04",
|
||||
"last_scanned": "2025-04-29T19:51:53.593403+00:00",
|
||||
"device_id": "82395c211fed9b358b604c33ca0bea9e"
|
||||
},
|
||||
{
|
||||
"id": "^J^J^J^J1B983A04",
|
||||
"last_scanned": "2025-04-29T19:51:53.847634+00:00",
|
||||
"device_id": "82395c211fed9b358b604c33ca0bea9e"
|
||||
},
|
||||
{
|
||||
"id": "^J^J^J^J^J1B983A04",
|
||||
"last_scanned": "2025-04-29T19:51:54.117388+00:00",
|
||||
"device_id": "82395c211fed9b358b604c33ca0bea9e"
|
||||
},
|
||||
{
|
||||
"id": "^J^J^J^J^J^J1B983A04",
|
||||
"last_scanned": "2025-04-29T19:51:54.389359+00:00",
|
||||
"device_id": "82395c211fed9b358b604c33ca0bea9e"
|
||||
},
|
||||
{
|
||||
"id": "^J^J^J^J^J^J^J1B983A04",
|
||||
"last_scanned": "2025-04-29T19:51:54.633439+00:00",
|
||||
"device_id": "82395c211fed9b358b604c33ca0bea9e"
|
||||
},
|
||||
{
|
||||
"id": "^J^J^J^J^J^J^J^J1B983A04",
|
||||
"last_scanned": "2025-04-29T19:51:54.883523+00:00",
|
||||
"device_id": "82395c211fed9b358b604c33ca0bea9e"
|
||||
},
|
||||
{
|
||||
"id": "^J^J^J^J^J^J^J^J^J1B983A04",
|
||||
"last_scanned": "2025-04-29T19:51:55.207420+00:00",
|
||||
"device_id": "82395c211fed9b358b604c33ca0bea9e"
|
||||
},
|
||||
{
|
||||
"id": "^J^J^J^J^J^J^J^J^J^J1B983A04",
|
||||
"last_scanned": "2025-04-29T19:51:55.544369+00:00",
|
||||
"device_id": "82395c211fed9b358b604c33ca0bea9e"
|
||||
},
|
||||
{
|
||||
"id": "^J^J^J^J^J^J^J^J^J^J^J1B983A04",
|
||||
"last_scanned": "2025-04-29T19:51:55.804198+00:00",
|
||||
"device_id": "82395c211fed9b358b604c33ca0bea9e"
|
||||
},
|
||||
{
|
||||
"id": "^J^J^J^J^J^J^J^J^J^J^J^J1B983A04",
|
||||
"last_scanned": "2025-04-29T19:51:56.185549+00:00",
|
||||
"device_id": "82395c211fed9b358b604c33ca0bea9e"
|
||||
},
|
||||
{
|
||||
"id": "^J^J^J^J^J^J^J^J^J^J^J^J^J1B983A04",
|
||||
"last_scanned": "2025-04-29T19:51:56.566481+00:00",
|
||||
"device_id": "82395c211fed9b358b604c33ca0bea9e"
|
||||
},
|
||||
{
|
||||
"id": "^J^J^J^J^J^J^J^J^J^J^J^J^J^J1B983A04",
|
||||
"last_scanned": "2025-04-29T19:51:57.139310+00:00",
|
||||
"device_id": "82395c211fed9b358b604c33ca0bea9e"
|
||||
},
|
||||
{
|
||||
"id": "^J^J^J^J^J^J^J^J^J^J^J^J^J^J^J1B983A04",
|
||||
"last_scanned": "2025-04-29T19:51:57.962592+00:00",
|
||||
"device_id": "82395c211fed9b358b604c33ca0bea9e"
|
||||
},
|
||||
{
|
||||
"id": "^J^J^J^J^J^J^J^J^J^J^J^J^J^J^J^J1B983A04",
|
||||
"last_scanned": "2025-04-29T19:51:58.863292+00:00",
|
||||
"device_id": "82395c211fed9b358b604c33ca0bea9e"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1 +1 @@
|
|||
["1.21.2","2.1.1","3.0.26","4.0.21","2.1.2","2.7.33","2.9.42","1.17.3","1.16.9","2.2.6","1.19.2","2.0.0","1.13.4","2.2.9","1.14.11","5.0.34","5.0.33","1.20.1","4.1.53","3.0.22","1.13.7","1.15.0","1.20.2","1.21.3","1.17.4","2.8.28","2.6.17","1.17.1","1.21.0","1.17.2","1.21.5","2.10.10","1.18.1","1.18.0","1.21.4","2.7.34","1.20.0","1.19.1","1.20.3","1.21.6","2.8.35","2.0.1","1.19.0","2.2.2","2.10.11","2.7.18","2.11.21","2.2.11","4.0.33"]
|
||||
["2.11.21","2.10.10","1.13.7","1.18.1","5.3.45","2.2.11","1.21.2","1.21.3","1.19.0","5.1.85","4.0.21","5.1.78","3.0.22","2.10.11","5.2.46","1.13.4","1.17.3","2.1.2","2.2.6","1.20.0","5.0.45","5.0.34","5.0.33","1.15.0","1.20.3","1.17.4","1.19.2","1.16.9","1.21.0","1.21.6","2.2.2","1.20.1","2.7.18","2.9.42","2.0.1","2.8.28","1.17.2","1.21.5","1.20.2","5.2.62","4.0.33","1.19.1","2.1.1","2.6.17","4.1.53","2.2.9","5.3.41","1.18.0","5.0.51","5.0.47","2.0.0","5.1.57","1.17.1","2.7.33","2.7.34","5.1.87","1.21.4","3.0.26","5.2.42","5.3.38","1.14.11","5.2.49","5.2.61","2.8.35"]
|
|
@ -2,7 +2,7 @@
|
|||
"sessions": {
|
||||
"9b5ac4acac7240a8d95decfdf283d3d1f3a0c9191d156693302952edbc86dfc5": {
|
||||
"metadata": {
|
||||
"expires": "Thu, 03 Oct 2024 20:45:52 GMT",
|
||||
"expires": "Fri, 09 May 2025 22:10:33 GMT",
|
||||
"path": "/",
|
||||
"comment": "",
|
||||
"domain": "172.22.114.176",
|
||||
|
@ -14,8 +14,8 @@
|
|||
"partitioned": true
|
||||
},
|
||||
"cookiename": "TOKEN",
|
||||
"value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiI0NTJkOTgxOS0zZTVkLTQ4MzItYTVjMi1lNjA0Zjg5MTA1MmQiLCJwYXNzd29yZFJldmlzaW9uIjoxNzEzMTMxMjM1LCJpc1JlbWVtYmVyZWQiOnRydWUsImNzcmZUb2tlbiI6IjYxMDVkZDVmLWYzMWQtNDg5MS1iZGUxLWNkNjNmODAzNTZmZiIsImlhdCI6MTcyNTM5NjM1MiwiZXhwIjoxNzI3OTg4MzUyLCJqdGkiOiIxZDQ5Mjg3Yi1lNjdmLTRmYjEtYTEyOC05Mjg4MjZkYTE2MzIifQ._g2siuroWLCON2OPqI7Px9TDblt00NRK12XJXEIlpqk",
|
||||
"csrf": "6105dd5f-f31d-4891-bde1-cd63f80356ff"
|
||||
"value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiI0NTJkOTgxOS0zZTVkLTQ4MzItYTVjMi1lNjA0Zjg5MTA1MmQiLCJwYXNzd29yZFJldmlzaW9uIjoxNzMzODYxMzA0LCJpc1JlbWVtYmVyZWQiOnRydWUsImNzcmZUb2tlbiI6IjBlNGVmZWViLWE3Y2EtNGVjZi05NzQwLTk5NWExOTJjYzE0YyIsImlhdCI6MTc0NDIzNjYzMywiZXhwIjoxNzQ2ODI4NjMzLCJqdGkiOiJjM2VhNWYzMS0wMmQzLTQ0ZWMtODBlNC02ZWNkYTg0NWU2ZWYifQ.5zMbCTx0ll708zQkJKVmFX_lqKZi3m7xMNqQLQVdO14",
|
||||
"csrf": "0e4efeeb-a7ca-4ecf-9740-995a192cc14c"
|
||||
}
|
||||
}
|
||||
}
|
1668
automations.yaml
1668
automations.yaml
File diff suppressed because it is too large
Load Diff
|
@ -1,80 +1,58 @@
|
|||
"""
|
||||
HACS gives you a powerful UI to handle downloads of all your custom needs.
|
||||
"""HACS gives you a powerful UI to handle downloads of all your custom needs.
|
||||
|
||||
For more details about this integration, please refer to the documentation at
|
||||
https://hacs.xyz/
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from typing import Any
|
||||
from __future__ import annotations
|
||||
|
||||
from aiogithubapi import AIOGitHubAPIException, GitHub, GitHubAPI
|
||||
from aiogithubapi.const import ACCEPT_HEADERS
|
||||
from awesomeversion import AwesomeVersion
|
||||
from homeassistant.components.frontend import async_remove_panel
|
||||
from homeassistant.components.lovelace.system_health import system_health_info
|
||||
from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry
|
||||
from homeassistant.const import Platform, __version__ as HAVERSION
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
from homeassistant.helpers.discovery import async_load_platform
|
||||
from homeassistant.helpers.entity_registry import async_get as async_get_entity_registry
|
||||
from homeassistant.helpers.event import async_call_later
|
||||
from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue
|
||||
from homeassistant.helpers.start import async_at_start
|
||||
from homeassistant.loader import async_get_integration
|
||||
import voluptuous as vol
|
||||
|
||||
from .base import HacsBase
|
||||
from .const import DOMAIN, MINIMUM_HA_VERSION, STARTUP
|
||||
from .const import DOMAIN, HACS_SYSTEM_ID, MINIMUM_HA_VERSION, STARTUP
|
||||
from .data_client import HacsDataClient
|
||||
from .enums import ConfigurationType, HacsDisabledReason, HacsStage, LovelaceMode
|
||||
from .enums import HacsDisabledReason, HacsStage, LovelaceMode
|
||||
from .frontend import async_register_frontend
|
||||
from .utils.configuration_schema import hacs_config_combined
|
||||
from .utils.data import HacsData
|
||||
from .utils.logger import LOGGER
|
||||
from .utils.queue_manager import QueueManager
|
||||
from .utils.version import version_left_higher_or_equal_then_right
|
||||
from .websocket import async_register_websocket_commands
|
||||
|
||||
CONFIG_SCHEMA = vol.Schema({DOMAIN: hacs_config_combined()}, extra=vol.ALLOW_EXTRA)
|
||||
PLATFORMS = [Platform.SWITCH, Platform.UPDATE]
|
||||
|
||||
|
||||
async def async_initialize_integration(
|
||||
async def _async_initialize_integration(
|
||||
hass: HomeAssistant,
|
||||
*,
|
||||
config_entry: ConfigEntry | None = None,
|
||||
config: dict[str, Any] | None = None,
|
||||
config_entry: ConfigEntry,
|
||||
) -> bool:
|
||||
"""Initialize the integration"""
|
||||
hass.data[DOMAIN] = hacs = HacsBase()
|
||||
hacs.enable_hacs()
|
||||
|
||||
if config is not None:
|
||||
if DOMAIN not in config:
|
||||
return True
|
||||
if hacs.configuration.config_type == ConfigurationType.CONFIG_ENTRY:
|
||||
return True
|
||||
hacs.configuration.update_from_dict(
|
||||
{
|
||||
"config_type": ConfigurationType.YAML,
|
||||
**config[DOMAIN],
|
||||
"config": config[DOMAIN],
|
||||
}
|
||||
)
|
||||
if config_entry.source == SOURCE_IMPORT:
|
||||
# Import is not supported
|
||||
hass.async_create_task(hass.config_entries.async_remove(config_entry.entry_id))
|
||||
return False
|
||||
|
||||
if config_entry is not None:
|
||||
if config_entry.source == SOURCE_IMPORT:
|
||||
hass.async_create_task(hass.config_entries.async_remove(config_entry.entry_id))
|
||||
return False
|
||||
|
||||
hacs.configuration.update_from_dict(
|
||||
{
|
||||
"config_entry": config_entry,
|
||||
"config_type": ConfigurationType.CONFIG_ENTRY,
|
||||
**config_entry.data,
|
||||
**config_entry.options,
|
||||
}
|
||||
)
|
||||
hacs.configuration.update_from_dict(
|
||||
{
|
||||
"config_entry": config_entry,
|
||||
**config_entry.data,
|
||||
**config_entry.options,
|
||||
},
|
||||
)
|
||||
|
||||
integration = await async_get_integration(hass, DOMAIN)
|
||||
|
||||
|
@ -104,7 +82,6 @@ async def async_initialize_integration(
|
|||
except BaseException: # lgtm [py/catch-base-exception] pylint: disable=broad-except
|
||||
# If this happens, the users YAML is not valid, we assume YAML mode
|
||||
pass
|
||||
hacs.log.debug("Configuration type: %s", hacs.configuration.config_type)
|
||||
hacs.core.config_path = hacs.hass.config.path()
|
||||
|
||||
if hacs.core.ha_version is None:
|
||||
|
@ -131,19 +108,18 @@ async def async_initialize_integration(
|
|||
"""HACS startup tasks."""
|
||||
hacs.enable_hacs()
|
||||
|
||||
for location in (
|
||||
hass.config.path("custom_components/custom_updater.py"),
|
||||
hass.config.path("custom_components/custom_updater/__init__.py"),
|
||||
):
|
||||
if os.path.exists(location):
|
||||
hacs.log.critical(
|
||||
"This cannot be used with custom_updater. "
|
||||
"To use this you need to remove custom_updater form %s",
|
||||
location,
|
||||
)
|
||||
try:
|
||||
import custom_components.custom_updater
|
||||
except ImportError:
|
||||
pass
|
||||
else:
|
||||
hacs.log.critical(
|
||||
"HACS cannot be used with custom_updater. "
|
||||
"To use HACS you need to remove custom_updater from `custom_components`",
|
||||
)
|
||||
|
||||
hacs.disable_hacs(HacsDisabledReason.CONSTRAINS)
|
||||
return False
|
||||
hacs.disable_hacs(HacsDisabledReason.CONSTRAINS)
|
||||
return False
|
||||
|
||||
if not version_left_higher_or_equal_then_right(
|
||||
hacs.core.ha_version.string,
|
||||
|
@ -160,39 +136,23 @@ async def async_initialize_integration(
|
|||
hacs.disable_hacs(HacsDisabledReason.RESTORE)
|
||||
return False
|
||||
|
||||
if not hacs.configuration.experimental:
|
||||
can_update = await hacs.async_can_update()
|
||||
hacs.log.debug("Can update %s repositories", can_update)
|
||||
|
||||
hacs.set_active_categories()
|
||||
|
||||
async_register_websocket_commands(hass)
|
||||
async_register_frontend(hass, hacs)
|
||||
await async_register_frontend(hass, hacs)
|
||||
|
||||
if hacs.configuration.config_type == ConfigurationType.YAML:
|
||||
hass.async_create_task(
|
||||
async_load_platform(hass, Platform.SENSOR, DOMAIN, {}, hacs.configuration.config)
|
||||
)
|
||||
hacs.log.info("Update entities are only supported when using UI configuration")
|
||||
|
||||
else:
|
||||
await hass.config_entries.async_forward_entry_setups(
|
||||
config_entry,
|
||||
[Platform.SENSOR, Platform.UPDATE]
|
||||
if hacs.configuration.experimental
|
||||
else [Platform.SENSOR],
|
||||
)
|
||||
await hass.config_entries.async_forward_entry_setups(config_entry, PLATFORMS)
|
||||
|
||||
hacs.set_stage(HacsStage.SETUP)
|
||||
if hacs.system.disabled:
|
||||
return False
|
||||
|
||||
# Schedule startup tasks
|
||||
async_at_start(hass=hass, at_start_cb=hacs.startup_tasks)
|
||||
|
||||
hacs.set_stage(HacsStage.WAITING)
|
||||
hacs.log.info("Setup complete, waiting for Home Assistant before startup tasks starts")
|
||||
|
||||
# Schedule startup tasks
|
||||
async_at_start(hass=hass, at_start_cb=hacs.startup_tasks)
|
||||
|
||||
return not hacs.system.disabled
|
||||
|
||||
async def async_try_startup(_=None):
|
||||
|
@ -202,10 +162,7 @@ async def async_initialize_integration(
|
|||
except AIOGitHubAPIException:
|
||||
startup_result = False
|
||||
if not startup_result:
|
||||
if (
|
||||
hacs.configuration.config_type == ConfigurationType.YAML
|
||||
or hacs.system.disabled_reason != HacsDisabledReason.INVALID_TOKEN
|
||||
):
|
||||
if hacs.system.disabled_reason != HacsDisabledReason.INVALID_TOKEN:
|
||||
hacs.log.info("Could not setup HACS, trying again in 15 min")
|
||||
async_call_later(hass, 900, async_try_startup)
|
||||
return
|
||||
|
@ -213,37 +170,19 @@ async def async_initialize_integration(
|
|||
|
||||
await async_try_startup()
|
||||
|
||||
# Remove old (v0-v1) sensor if it exists, can be removed in v3
|
||||
er = async_get_entity_registry(hass)
|
||||
if old_sensor := er.async_get_entity_id("sensor", DOMAIN, HACS_SYSTEM_ID):
|
||||
er.async_remove(old_sensor)
|
||||
|
||||
# Mischief managed!
|
||||
return True
|
||||
|
||||
|
||||
async def async_setup(hass: HomeAssistant, config: dict[str, Any]) -> bool:
|
||||
"""Set up this integration using yaml."""
|
||||
if DOMAIN in config:
|
||||
async_create_issue(
|
||||
hass,
|
||||
DOMAIN,
|
||||
"deprecated_yaml_configuration",
|
||||
is_fixable=False,
|
||||
issue_domain=DOMAIN,
|
||||
severity=IssueSeverity.WARNING,
|
||||
translation_key="deprecated_yaml_configuration",
|
||||
learn_more_url="https://hacs.xyz/docs/configuration/options",
|
||||
)
|
||||
LOGGER.warning(
|
||||
"YAML configuration of HACS is deprecated and will be "
|
||||
"removed in version 2.0.0, there will be no automatic "
|
||||
"import of this. "
|
||||
"Please remove it from your configuration, "
|
||||
"restart Home Assistant and use the UI to configure it instead."
|
||||
)
|
||||
return await async_initialize_integration(hass=hass, config=config)
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
|
||||
"""Set up this integration using UI."""
|
||||
config_entry.async_on_unload(config_entry.add_update_listener(async_reload_entry))
|
||||
setup_result = await async_initialize_integration(hass=hass, config_entry=config_entry)
|
||||
setup_result = await _async_initialize_integration(hass=hass, config_entry=config_entry)
|
||||
hacs: HacsBase = hass.data[DOMAIN]
|
||||
return setup_result and not hacs.system.disabled
|
||||
|
||||
|
@ -259,7 +198,7 @@ async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry) ->
|
|||
# Clear out pending queue
|
||||
hacs.queue.clear()
|
||||
|
||||
for task in hacs.recuring_tasks:
|
||||
for task in hacs.recurring_tasks:
|
||||
# Cancel all pending tasks
|
||||
task()
|
||||
|
||||
|
@ -269,15 +208,11 @@ async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry) ->
|
|||
try:
|
||||
if hass.data.get("frontend_panels", {}).get("hacs"):
|
||||
hacs.log.info("Removing sidepanel")
|
||||
hass.components.frontend.async_remove_panel("hacs")
|
||||
async_remove_panel(hass, "hacs")
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
platforms = ["sensor"]
|
||||
if hacs.configuration.experimental:
|
||||
platforms.append("update")
|
||||
|
||||
unload_ok = await hass.config_entries.async_unload_platforms(config_entry, platforms)
|
||||
unload_ok = await hass.config_entries.async_unload_platforms(config_entry, PLATFORMS)
|
||||
|
||||
hacs.set_stage(None)
|
||||
hacs.disable_hacs(HacsDisabledReason.REMOVED)
|
||||
|
|
|
@ -1,16 +1,17 @@
|
|||
"""Base HACS class."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
from collections.abc import Awaitable, Callable
|
||||
from dataclasses import asdict, dataclass, field
|
||||
from datetime import timedelta
|
||||
import gzip
|
||||
import logging
|
||||
import math
|
||||
import os
|
||||
import pathlib
|
||||
import shutil
|
||||
from typing import TYPE_CHECKING, Any, Awaitable, Callable
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
from aiogithubapi import (
|
||||
AIOGitHubAPIException,
|
||||
|
@ -24,23 +25,22 @@ from aiogithubapi import (
|
|||
from aiogithubapi.objects.repository import AIOGitHubAPIRepository
|
||||
from aiohttp.client import ClientSession, ClientTimeout
|
||||
from awesomeversion import AwesomeVersion
|
||||
from homeassistant.config_entries import ConfigEntry, ConfigEntryState
|
||||
from homeassistant.components.persistent_notification import (
|
||||
async_create as async_create_persistent_notification,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import EVENT_HOMEASSISTANT_FINAL_WRITE, Platform
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_send
|
||||
from homeassistant.helpers.event import async_track_time_interval
|
||||
from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue
|
||||
from homeassistant.loader import Integration
|
||||
from homeassistant.util import dt
|
||||
|
||||
from custom_components.hacs.repositories.base import (
|
||||
HACS_MANIFEST_KEYS_TO_EXPORT,
|
||||
REPOSITORY_KEYS_TO_EXPORT,
|
||||
)
|
||||
|
||||
from .const import DOMAIN, TV, URL_BASE
|
||||
from .coordinator import HacsUpdateCoordinator
|
||||
from .data_client import HacsDataClient
|
||||
from .enums import (
|
||||
ConfigurationType,
|
||||
HacsCategory,
|
||||
HacsDisabledReason,
|
||||
HacsDispatchEvent,
|
||||
|
@ -58,12 +58,14 @@ from .exceptions import (
|
|||
HacsRepositoryExistException,
|
||||
HomeAssistantCoreRepositoryException,
|
||||
)
|
||||
from .repositories import RERPOSITORY_CLASSES
|
||||
from .utils.decode import decode_content
|
||||
from .repositories import REPOSITORY_CLASSES
|
||||
from .repositories.base import HACS_MANIFEST_KEYS_TO_EXPORT, REPOSITORY_KEYS_TO_EXPORT
|
||||
from .utils.file_system import async_exists
|
||||
from .utils.json import json_loads
|
||||
from .utils.logger import LOGGER
|
||||
from .utils.queue_manager import QueueManager
|
||||
from .utils.store import async_load_from_store, async_save_to_store
|
||||
from .utils.workarounds import async_register_static_path
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .repositories.base import HacsRepository
|
||||
|
@ -113,15 +115,11 @@ class HacsConfiguration:
|
|||
appdaemon: bool = False
|
||||
config: dict[str, Any] = field(default_factory=dict)
|
||||
config_entry: ConfigEntry | None = None
|
||||
config_type: ConfigurationType | None = None
|
||||
country: str = "ALL"
|
||||
debug: bool = False
|
||||
dev: bool = False
|
||||
experimental: bool = False
|
||||
frontend_repo_url: str = ""
|
||||
frontend_repo: str = ""
|
||||
netdaemon_path: str = "netdaemon/apps/"
|
||||
netdaemon: bool = False
|
||||
plugin_path: str = "www/community/"
|
||||
python_script_path: str = "python_scripts/"
|
||||
python_script: bool = False
|
||||
|
@ -142,6 +140,8 @@ class HacsConfiguration:
|
|||
raise HacsException("Configuration is not valid.")
|
||||
|
||||
for key in data:
|
||||
if key in {"experimental", "netdaemon", "release_limit", "debug"}:
|
||||
continue
|
||||
self.__setattr__(key, data[key])
|
||||
|
||||
|
||||
|
@ -217,6 +217,13 @@ class HacsRepositories:
|
|||
"""Return a list of downloaded repositories."""
|
||||
return [repo for repo in self._repositories if repo.data.installed]
|
||||
|
||||
def category_downloaded(self, category: HacsCategory) -> bool:
|
||||
"""Check if a given category has been downloaded."""
|
||||
for repository in self.list_downloaded:
|
||||
if repository.data.category == category:
|
||||
return True
|
||||
return False
|
||||
|
||||
def register(self, repository: HacsRepository, default: bool = False) -> None:
|
||||
"""Register a repository."""
|
||||
repo_id = str(repository.data.id)
|
||||
|
@ -348,9 +355,6 @@ class HacsRepositories:
|
|||
class HacsBase:
|
||||
"""Base HACS class."""
|
||||
|
||||
common = HacsCommon()
|
||||
configuration = HacsConfiguration()
|
||||
core = HacsCore()
|
||||
data: HacsData | None = None
|
||||
data_client: HacsDataClient | None = None
|
||||
frontend_version: str | None = None
|
||||
|
@ -358,17 +362,24 @@ class HacsBase:
|
|||
githubapi: GitHubAPI | None = None
|
||||
hass: HomeAssistant | None = None
|
||||
integration: Integration | None = None
|
||||
log: logging.Logger = LOGGER
|
||||
queue: QueueManager | None = None
|
||||
recuring_tasks = []
|
||||
repositories: HacsRepositories = HacsRepositories()
|
||||
repository: AIOGitHubAPIRepository | None = None
|
||||
session: ClientSession | None = None
|
||||
stage: HacsStage | None = None
|
||||
status = HacsStatus()
|
||||
system = HacsSystem()
|
||||
validation: ValidationManager | None = None
|
||||
version: str | None = None
|
||||
version: AwesomeVersion | None = None
|
||||
|
||||
def __init__(self) -> None:
|
||||
"""Initialize."""
|
||||
self.common = HacsCommon()
|
||||
self.configuration = HacsConfiguration()
|
||||
self.coordinators: dict[HacsCategory, HacsUpdateCoordinator] = {}
|
||||
self.core = HacsCore()
|
||||
self.log = LOGGER
|
||||
self.recurring_tasks: list[Callable[[], None]] = []
|
||||
self.repositories = HacsRepositories()
|
||||
self.status = HacsStatus()
|
||||
self.system = HacsSystem()
|
||||
|
||||
@property
|
||||
def integration_dir(self) -> pathlib.Path:
|
||||
|
@ -394,12 +405,7 @@ class HacsBase:
|
|||
if reason != HacsDisabledReason.REMOVED:
|
||||
self.log.error("HACS is disabled - %s", reason)
|
||||
|
||||
if (
|
||||
reason == HacsDisabledReason.INVALID_TOKEN
|
||||
and self.configuration.config_type == ConfigurationType.CONFIG_ENTRY
|
||||
):
|
||||
self.configuration.config_entry.state = ConfigEntryState.SETUP_ERROR
|
||||
self.configuration.config_entry.reason = "Authentication failed"
|
||||
if reason == HacsDisabledReason.INVALID_TOKEN:
|
||||
self.hass.add_job(self.configuration.config_entry.async_start_reauth, self.hass)
|
||||
|
||||
def enable_hacs(self) -> None:
|
||||
|
@ -413,12 +419,14 @@ class HacsBase:
|
|||
if category not in self.common.categories:
|
||||
self.log.info("Enable category: %s", category)
|
||||
self.common.categories.add(category)
|
||||
self.coordinators[category] = HacsUpdateCoordinator()
|
||||
|
||||
def disable_hacs_category(self, category: HacsCategory) -> None:
|
||||
"""Disable HACS category."""
|
||||
if category in self.common.categories:
|
||||
self.log.info("Disabling category: %s", category)
|
||||
self.common.categories.pop(category)
|
||||
self.coordinators.pop(category)
|
||||
|
||||
async def async_save_file(self, file_path: str, content: Any) -> bool:
|
||||
"""Save a file."""
|
||||
|
@ -451,12 +459,13 @@ class HacsBase:
|
|||
try:
|
||||
await self.hass.async_add_executor_job(_write_file)
|
||||
except (
|
||||
BaseException # lgtm [py/catch-base-exception] pylint: disable=broad-except
|
||||
# lgtm [py/catch-base-exception] pylint: disable=broad-except
|
||||
BaseException
|
||||
) as error:
|
||||
self.log.error("Could not write data to %s - %s", file_path, error)
|
||||
return False
|
||||
|
||||
return os.path.exists(file_path)
|
||||
return await async_exists(self.hass, file_path)
|
||||
|
||||
async def async_can_update(self) -> int:
|
||||
"""Helper to calculate the number of repositories we can fetch data for."""
|
||||
|
@ -472,24 +481,13 @@ class HacsBase:
|
|||
)
|
||||
self.disable_hacs(HacsDisabledReason.RATE_LIMIT)
|
||||
except (
|
||||
BaseException # lgtm [py/catch-base-exception] pylint: disable=broad-except
|
||||
# lgtm [py/catch-base-exception] pylint: disable=broad-except
|
||||
BaseException
|
||||
) as exception:
|
||||
self.log.exception(exception)
|
||||
|
||||
return 0
|
||||
|
||||
async def async_github_get_hacs_default_file(self, filename: str) -> list:
|
||||
"""Get the content of a default file."""
|
||||
response = await self.async_github_api_method(
|
||||
method=self.githubapi.repos.contents.get,
|
||||
repository=HacsGitHubRepo.DEFAULT,
|
||||
path=filename,
|
||||
)
|
||||
if response is None:
|
||||
return []
|
||||
|
||||
return json_loads(decode_content(response.data.content))
|
||||
|
||||
async def async_github_api_method(
|
||||
self,
|
||||
method: Callable[[], Awaitable[TV]],
|
||||
|
@ -513,7 +511,8 @@ class HacsBase:
|
|||
except GitHubException as exception:
|
||||
_exception = exception
|
||||
except (
|
||||
BaseException # lgtm [py/catch-base-exception] pylint: disable=broad-except
|
||||
# lgtm [py/catch-base-exception] pylint: disable=broad-except
|
||||
BaseException
|
||||
) as exception:
|
||||
self.log.exception(exception)
|
||||
_exception = exception
|
||||
|
@ -545,7 +544,7 @@ class HacsBase:
|
|||
):
|
||||
raise AddonRepositoryException()
|
||||
|
||||
if category not in RERPOSITORY_CLASSES:
|
||||
if category not in REPOSITORY_CLASSES:
|
||||
self.log.warning(
|
||||
"%s is not a valid repository category, %s will not be registered.",
|
||||
category,
|
||||
|
@ -556,7 +555,7 @@ class HacsBase:
|
|||
if (renamed := self.common.renamed_repositories.get(repository_full_name)) is not None:
|
||||
repository_full_name = renamed
|
||||
|
||||
repository: HacsRepository = RERPOSITORY_CLASSES[category](self, repository_full_name)
|
||||
repository: HacsRepository = REPOSITORY_CLASSES[category](self, repository_full_name)
|
||||
if check:
|
||||
try:
|
||||
await repository.async_registration(ref)
|
||||
|
@ -566,7 +565,8 @@ class HacsBase:
|
|||
self.log.error("Validation for %s failed.", repository_full_name)
|
||||
if self.system.action:
|
||||
raise HacsException(
|
||||
f"::error:: Validation for {repository_full_name} failed."
|
||||
f"::error:: Validation for {
|
||||
repository_full_name} failed."
|
||||
)
|
||||
return repository.validate.errors
|
||||
if self.system.action:
|
||||
|
@ -582,7 +582,8 @@ class HacsBase:
|
|||
except AIOGitHubAPIException as exception:
|
||||
self.common.skip.add(repository.data.full_name)
|
||||
raise HacsException(
|
||||
f"Validation for {repository_full_name} failed with {exception}."
|
||||
f"Validation for {
|
||||
repository_full_name} failed with {exception}."
|
||||
) from exception
|
||||
|
||||
if self.status.new:
|
||||
|
@ -592,7 +593,7 @@ class HacsBase:
|
|||
repository.data.id = repository_id
|
||||
|
||||
else:
|
||||
if self.hass is not None and ((check and repository.data.new) or self.status.new):
|
||||
if self.hass is not None and check and repository.data.new:
|
||||
self.async_dispatch(
|
||||
HacsDispatchEvent.REPOSITORY,
|
||||
{
|
||||
|
@ -613,91 +614,84 @@ class HacsBase:
|
|||
for repo in critical:
|
||||
if not repo["acknowledged"]:
|
||||
self.log.critical("URGENT!: Check the HACS panel!")
|
||||
self.hass.components.persistent_notification.create(
|
||||
title="URGENT!", message="**Check the HACS panel!**"
|
||||
async_create_persistent_notification(
|
||||
self.hass, title="URGENT!", message="**Check the HACS panel!**"
|
||||
)
|
||||
break
|
||||
|
||||
if not self.configuration.experimental:
|
||||
self.recuring_tasks.append(
|
||||
self.hass.helpers.event.async_track_time_interval(
|
||||
self.async_update_downloaded_repositories, timedelta(hours=48)
|
||||
)
|
||||
)
|
||||
self.recuring_tasks.append(
|
||||
self.hass.helpers.event.async_track_time_interval(
|
||||
self.async_update_all_repositories,
|
||||
timedelta(hours=96),
|
||||
)
|
||||
)
|
||||
else:
|
||||
self.recuring_tasks.append(
|
||||
self.hass.helpers.event.async_track_time_interval(
|
||||
self.async_load_hacs_from_github,
|
||||
timedelta(hours=48),
|
||||
)
|
||||
)
|
||||
|
||||
self.recuring_tasks.append(
|
||||
self.hass.helpers.event.async_track_time_interval(
|
||||
self.async_update_downloaded_custom_repositories, timedelta(hours=48)
|
||||
self.recurring_tasks.append(
|
||||
async_track_time_interval(
|
||||
self.hass,
|
||||
self.async_load_hacs_from_github,
|
||||
timedelta(hours=48),
|
||||
)
|
||||
)
|
||||
|
||||
self.recuring_tasks.append(
|
||||
self.hass.helpers.event.async_track_time_interval(
|
||||
self.async_get_all_category_repositories, timedelta(hours=6)
|
||||
self.recurring_tasks.append(
|
||||
async_track_time_interval(
|
||||
self.hass, self.async_update_downloaded_custom_repositories, timedelta(hours=48)
|
||||
)
|
||||
)
|
||||
|
||||
self.recuring_tasks.append(
|
||||
self.hass.helpers.event.async_track_time_interval(
|
||||
self.async_check_rate_limit, timedelta(minutes=5)
|
||||
)
|
||||
)
|
||||
self.recuring_tasks.append(
|
||||
self.hass.helpers.event.async_track_time_interval(
|
||||
self.async_prosess_queue, timedelta(minutes=10)
|
||||
self.recurring_tasks.append(
|
||||
async_track_time_interval(
|
||||
self.hass, self.async_get_all_category_repositories, timedelta(hours=6)
|
||||
)
|
||||
)
|
||||
|
||||
self.recuring_tasks.append(
|
||||
self.hass.helpers.event.async_track_time_interval(
|
||||
self.async_handle_critical_repositories, timedelta(hours=6)
|
||||
self.recurring_tasks.append(
|
||||
async_track_time_interval(self.hass, self.async_check_rate_limit, timedelta(minutes=5))
|
||||
)
|
||||
self.recurring_tasks.append(
|
||||
async_track_time_interval(self.hass, self.async_process_queue, timedelta(minutes=10))
|
||||
)
|
||||
|
||||
self.recurring_tasks.append(
|
||||
async_track_time_interval(
|
||||
self.hass, self.async_handle_critical_repositories, timedelta(hours=6)
|
||||
)
|
||||
)
|
||||
|
||||
self.hass.bus.async_listen_once(
|
||||
unsub = self.hass.bus.async_listen_once(
|
||||
EVENT_HOMEASSISTANT_FINAL_WRITE, self.data.async_force_write
|
||||
)
|
||||
if config_entry := self.configuration.config_entry:
|
||||
config_entry.async_on_unload(unsub)
|
||||
|
||||
self.log.debug("There are %s scheduled recurring tasks", len(self.recuring_tasks))
|
||||
self.log.debug("There are %s scheduled recurring tasks", len(self.recurring_tasks))
|
||||
|
||||
self.status.startup = False
|
||||
self.async_dispatch(HacsDispatchEvent.STATUS, {})
|
||||
|
||||
await self.async_handle_removed_repositories()
|
||||
await self.async_get_all_category_repositories()
|
||||
await self.async_update_downloaded_repositories()
|
||||
|
||||
self.set_stage(HacsStage.RUNNING)
|
||||
|
||||
self.async_dispatch(HacsDispatchEvent.RELOAD, {"force": True})
|
||||
|
||||
await self.async_handle_critical_repositories()
|
||||
await self.async_prosess_queue()
|
||||
await self.async_process_queue()
|
||||
|
||||
self.async_dispatch(HacsDispatchEvent.STATUS, {})
|
||||
|
||||
async def async_download_file(self, url: str, *, headers: dict | None = None) -> bytes | None:
|
||||
async def async_download_file(
|
||||
self,
|
||||
url: str,
|
||||
*,
|
||||
headers: dict | None = None,
|
||||
keep_url: bool = False,
|
||||
nolog: bool = False,
|
||||
**_,
|
||||
) -> bytes | None:
|
||||
"""Download files, and return the content."""
|
||||
if url is None:
|
||||
return None
|
||||
|
||||
if "tags/" in url:
|
||||
if not keep_url and "tags/" in url:
|
||||
url = url.replace("tags/", "")
|
||||
|
||||
self.log.debug("Downloading %s", url)
|
||||
self.log.debug("Trying to download %s", url)
|
||||
timeouts = 0
|
||||
|
||||
while timeouts < 5:
|
||||
|
@ -713,9 +707,10 @@ class HacsBase:
|
|||
return await request.read()
|
||||
|
||||
raise HacsException(
|
||||
f"Got status code {request.status} when trying to download {url}"
|
||||
f"Got status code {
|
||||
request.status} when trying to download {url}"
|
||||
)
|
||||
except asyncio.TimeoutError:
|
||||
except TimeoutError:
|
||||
self.log.warning(
|
||||
"A timeout of 60! seconds was encountered while downloading %s, "
|
||||
"using over 60 seconds to download a single file is not normal. "
|
||||
|
@ -731,23 +726,34 @@ class HacsBase:
|
|||
continue
|
||||
|
||||
except (
|
||||
BaseException # lgtm [py/catch-base-exception] pylint: disable=broad-except
|
||||
# lgtm [py/catch-base-exception] pylint: disable=broad-except
|
||||
BaseException
|
||||
) as exception:
|
||||
self.log.exception("Download failed - %s", exception)
|
||||
if not nolog:
|
||||
self.log.exception("Download failed - %s", exception)
|
||||
|
||||
return None
|
||||
|
||||
async def async_recreate_entities(self) -> None:
|
||||
"""Recreate entities."""
|
||||
if self.configuration == ConfigurationType.YAML or not self.configuration.experimental:
|
||||
return
|
||||
platforms = [Platform.UPDATE]
|
||||
|
||||
platforms = [Platform.SENSOR, Platform.UPDATE]
|
||||
|
||||
await self.hass.config_entries.async_unload_platforms(
|
||||
entry=self.configuration.config_entry,
|
||||
platforms=platforms,
|
||||
)
|
||||
# Workaround for core versions without https://github.com/home-assistant/core/pull/117084
|
||||
if self.core.ha_version < AwesomeVersion("2024.6.0"):
|
||||
unload_platforms_lock = asyncio.Lock()
|
||||
async with unload_platforms_lock:
|
||||
on_unload = self.configuration.config_entry._on_unload
|
||||
self.configuration.config_entry._on_unload = []
|
||||
await self.hass.config_entries.async_unload_platforms(
|
||||
entry=self.configuration.config_entry,
|
||||
platforms=platforms,
|
||||
)
|
||||
self.configuration.config_entry._on_unload = on_unload
|
||||
else:
|
||||
await self.hass.config_entries.async_unload_platforms(
|
||||
entry=self.configuration.config_entry,
|
||||
platforms=platforms,
|
||||
)
|
||||
await self.hass.config_entries.async_forward_entry_setups(
|
||||
self.configuration.config_entry, platforms
|
||||
)
|
||||
|
@ -760,49 +766,40 @@ class HacsBase:
|
|||
def set_active_categories(self) -> None:
|
||||
"""Set the active categories."""
|
||||
self.common.categories = set()
|
||||
for category in (HacsCategory.INTEGRATION, HacsCategory.PLUGIN):
|
||||
for category in (HacsCategory.INTEGRATION, HacsCategory.PLUGIN, HacsCategory.TEMPLATE):
|
||||
self.enable_hacs_category(HacsCategory(category))
|
||||
|
||||
if self.configuration.experimental and self.core.ha_version >= "2023.4.0b0":
|
||||
self.enable_hacs_category(HacsCategory.TEMPLATE)
|
||||
|
||||
if HacsCategory.PYTHON_SCRIPT in self.hass.config.components:
|
||||
if (
|
||||
HacsCategory.PYTHON_SCRIPT in self.hass.config.components
|
||||
or self.repositories.category_downloaded(HacsCategory.PYTHON_SCRIPT)
|
||||
):
|
||||
self.enable_hacs_category(HacsCategory.PYTHON_SCRIPT)
|
||||
|
||||
if self.hass.services.has_service("frontend", "reload_themes"):
|
||||
if self.hass.services.has_service(
|
||||
"frontend", "reload_themes"
|
||||
) or self.repositories.category_downloaded(HacsCategory.THEME):
|
||||
self.enable_hacs_category(HacsCategory.THEME)
|
||||
|
||||
if self.configuration.appdaemon:
|
||||
self.enable_hacs_category(HacsCategory.APPDAEMON)
|
||||
if self.configuration.netdaemon:
|
||||
downloaded_netdaemon = [
|
||||
x
|
||||
for x in self.repositories.list_downloaded
|
||||
if x.data.category == HacsCategory.NETDAEMON
|
||||
]
|
||||
if len(downloaded_netdaemon) != 0:
|
||||
self.log.warning(
|
||||
"NetDaemon in HACS is deprectaded. It will stop working in the future. "
|
||||
"Please remove all your current NetDaemon repositories from HACS "
|
||||
"and download them manually if you want to continue using them."
|
||||
)
|
||||
self.enable_hacs_category(HacsCategory.NETDAEMON)
|
||||
|
||||
async def async_load_hacs_from_github(self, _=None) -> None:
|
||||
"""Load HACS from GitHub."""
|
||||
if self.configuration.experimental and self.status.inital_fetch_done:
|
||||
if self.status.inital_fetch_done:
|
||||
return
|
||||
|
||||
try:
|
||||
repository = self.repositories.get_by_full_name(HacsGitHubRepo.INTEGRATION)
|
||||
should_recreate_entities = False
|
||||
if repository is None:
|
||||
should_recreate_entities = True
|
||||
await self.async_register_repository(
|
||||
repository_full_name=HacsGitHubRepo.INTEGRATION,
|
||||
category=HacsCategory.INTEGRATION,
|
||||
default=True,
|
||||
)
|
||||
repository = self.repositories.get_by_full_name(HacsGitHubRepo.INTEGRATION)
|
||||
elif self.configuration.experimental and not self.status.startup:
|
||||
elif not self.status.startup:
|
||||
self.log.error("Scheduling update of hacs/integration")
|
||||
self.queue.add(repository.common_update())
|
||||
if repository is None:
|
||||
|
@ -813,6 +810,9 @@ class HacsBase:
|
|||
repository.data.new = False
|
||||
repository.data.releases = True
|
||||
|
||||
if should_recreate_entities:
|
||||
await self.async_recreate_entities()
|
||||
|
||||
self.repository = repository.repository_object
|
||||
self.repositories.mark_default(repository)
|
||||
except HacsException as exception:
|
||||
|
@ -832,8 +832,6 @@ class HacsBase:
|
|||
await asyncio.gather(
|
||||
*[
|
||||
self.async_get_category_repositories_experimental(category)
|
||||
if self.configuration.experimental
|
||||
else self.async_get_category_repositories(HacsCategory(category))
|
||||
for category in self.common.categories or []
|
||||
]
|
||||
)
|
||||
|
@ -842,7 +840,7 @@ class HacsBase:
|
|||
"""Update all category repositories."""
|
||||
self.log.debug("Fetching updated content for %s", category)
|
||||
try:
|
||||
category_data = await self.data_client.get_data(category)
|
||||
category_data = await self.data_client.get_data(category, validate=True)
|
||||
except HacsNotModifiedException:
|
||||
self.log.debug("No updates for %s", category)
|
||||
return
|
||||
|
@ -853,14 +851,14 @@ class HacsBase:
|
|||
await self.data.register_unknown_repositories(category_data, category)
|
||||
|
||||
for repo_id, repo_data in category_data.items():
|
||||
repo = repo_data["full_name"]
|
||||
if self.common.renamed_repositories.get(repo):
|
||||
repo = self.common.renamed_repositories[repo]
|
||||
if self.repositories.is_removed(repo):
|
||||
repo_name = repo_data["full_name"]
|
||||
if self.common.renamed_repositories.get(repo_name):
|
||||
repo_name = self.common.renamed_repositories[repo_name]
|
||||
if self.repositories.is_removed(repo_name):
|
||||
continue
|
||||
if repo in self.common.archived_repositories:
|
||||
if repo_name in self.common.archived_repositories:
|
||||
continue
|
||||
if repository := self.repositories.get_by_full_name(repo):
|
||||
if repository := self.repositories.get_by_full_name(repo_name):
|
||||
self.repositories.set_repository_id(repository, repo_id)
|
||||
self.repositories.mark_default(repository)
|
||||
if repository.data.last_fetched is None or (
|
||||
|
@ -871,15 +869,6 @@ class HacsBase:
|
|||
repository.repository_manifest.update_data(
|
||||
{**dict(HACS_MANIFEST_KEYS_TO_EXPORT), **manifest}
|
||||
)
|
||||
self.async_dispatch(
|
||||
HacsDispatchEvent.REPOSITORY,
|
||||
{
|
||||
"id": 1337,
|
||||
"action": "update",
|
||||
"repository": repository.data.full_name,
|
||||
"repository_id": repository.data.id,
|
||||
},
|
||||
)
|
||||
|
||||
if category == "integration":
|
||||
self.status.inital_fetch_done = True
|
||||
|
@ -896,50 +885,8 @@ class HacsBase:
|
|||
)
|
||||
self.repositories.unregister(repository)
|
||||
|
||||
async def async_get_category_repositories(self, category: HacsCategory) -> None:
|
||||
"""Get repositories from category."""
|
||||
if self.system.disabled:
|
||||
return
|
||||
try:
|
||||
repositories = await self.async_github_get_hacs_default_file(category)
|
||||
except HacsException:
|
||||
return
|
||||
|
||||
for repo in repositories:
|
||||
if self.common.renamed_repositories.get(repo):
|
||||
repo = self.common.renamed_repositories[repo]
|
||||
if self.repositories.is_removed(repo):
|
||||
continue
|
||||
if repo in self.common.archived_repositories:
|
||||
continue
|
||||
repository = self.repositories.get_by_full_name(repo)
|
||||
if repository is not None:
|
||||
self.repositories.mark_default(repository)
|
||||
if self.status.new and self.configuration.dev:
|
||||
# Force update for new installations
|
||||
self.queue.add(repository.common_update())
|
||||
continue
|
||||
|
||||
self.queue.add(
|
||||
self.async_register_repository(
|
||||
repository_full_name=repo,
|
||||
category=category,
|
||||
default=True,
|
||||
)
|
||||
)
|
||||
|
||||
async def async_update_all_repositories(self, _=None) -> None:
|
||||
"""Update all repositories."""
|
||||
if self.system.disabled:
|
||||
return
|
||||
self.log.debug("Starting recurring background task for all repositories")
|
||||
|
||||
for repository in self.repositories.list_all:
|
||||
if repository.data.category in self.common.categories:
|
||||
self.queue.add(repository.common_update())
|
||||
|
||||
self.async_dispatch(HacsDispatchEvent.REPOSITORY, {"action": "reload"})
|
||||
self.log.debug("Recurring background task for all repositories done")
|
||||
self.async_dispatch(HacsDispatchEvent.REPOSITORY, {})
|
||||
self.coordinators[category].async_update_listeners()
|
||||
|
||||
async def async_check_rate_limit(self, _=None) -> None:
|
||||
"""Check rate limit."""
|
||||
|
@ -951,9 +898,9 @@ class HacsBase:
|
|||
self.log.debug("Ratelimit indicate we can update %s", can_update)
|
||||
if can_update > 0:
|
||||
self.enable_hacs()
|
||||
await self.async_prosess_queue()
|
||||
await self.async_process_queue()
|
||||
|
||||
async def async_prosess_queue(self, _=None) -> None:
|
||||
async def async_process_queue(self, _=None) -> None:
|
||||
"""Process the queue."""
|
||||
if self.system.disabled:
|
||||
self.log.debug("HACS is disabled")
|
||||
|
@ -993,12 +940,7 @@ class HacsBase:
|
|||
self.log.info("Loading removed repositories")
|
||||
|
||||
try:
|
||||
if self.configuration.experimental:
|
||||
removed_repositories = await self.data_client.get_data("removed")
|
||||
else:
|
||||
removed_repositories = await self.async_github_get_hacs_default_file(
|
||||
HacsCategory.REMOVED
|
||||
)
|
||||
removed_repositories = await self.data_client.get_data("removed", validate=True)
|
||||
except HacsException:
|
||||
return
|
||||
|
||||
|
@ -1013,21 +955,20 @@ class HacsBase:
|
|||
continue
|
||||
if repository.data.installed:
|
||||
if removed.removal_type != "critical":
|
||||
if self.configuration.experimental:
|
||||
async_create_issue(
|
||||
hass=self.hass,
|
||||
domain=DOMAIN,
|
||||
issue_id=f"removed_{repository.data.id}",
|
||||
is_fixable=False,
|
||||
issue_domain=DOMAIN,
|
||||
severity=IssueSeverity.WARNING,
|
||||
translation_key="removed",
|
||||
translation_placeholders={
|
||||
"name": repository.data.full_name,
|
||||
"reason": removed.reason,
|
||||
"repositry_id": repository.data.id,
|
||||
},
|
||||
)
|
||||
async_create_issue(
|
||||
hass=self.hass,
|
||||
domain=DOMAIN,
|
||||
issue_id=f"removed_{repository.data.id}",
|
||||
is_fixable=False,
|
||||
issue_domain=DOMAIN,
|
||||
severity=IssueSeverity.WARNING,
|
||||
translation_key="removed",
|
||||
translation_placeholders={
|
||||
"name": repository.data.full_name,
|
||||
"reason": removed.reason,
|
||||
"repositry_id": repository.data.id,
|
||||
},
|
||||
)
|
||||
self.log.warning(
|
||||
"You have '%s' installed with HACS "
|
||||
"this repository has been removed from HACS, please consider removing it. "
|
||||
|
@ -1042,30 +983,43 @@ class HacsBase:
|
|||
if need_to_save:
|
||||
await self.data.async_write()
|
||||
|
||||
async def async_update_downloaded_repositories(self, _=None) -> None:
|
||||
"""Execute the task."""
|
||||
if self.system.disabled or self.configuration.experimental:
|
||||
return
|
||||
self.log.info("Starting recurring background task for downloaded repositories")
|
||||
|
||||
for repository in self.repositories.list_downloaded:
|
||||
if repository.data.category in self.common.categories:
|
||||
self.queue.add(repository.update_repository(ignore_issues=True))
|
||||
|
||||
self.log.debug("Recurring background task for downloaded repositories done")
|
||||
|
||||
async def async_update_downloaded_custom_repositories(self, _=None) -> None:
|
||||
"""Execute the task."""
|
||||
if self.system.disabled or not self.configuration.experimental:
|
||||
if self.system.disabled:
|
||||
return
|
||||
self.log.info("Starting recurring background task for downloaded custom repositories")
|
||||
|
||||
repositories_to_update = 0
|
||||
repositories_updated = asyncio.Event()
|
||||
|
||||
async def update_repository(repository: HacsRepository) -> None:
|
||||
"""Update a repository"""
|
||||
nonlocal repositories_to_update
|
||||
await repository.update_repository(ignore_issues=True)
|
||||
repositories_to_update -= 1
|
||||
if not repositories_to_update:
|
||||
repositories_updated.set()
|
||||
|
||||
for repository in self.repositories.list_downloaded:
|
||||
if (
|
||||
repository.data.category in self.common.categories
|
||||
and not self.repositories.is_default(repository.data.id)
|
||||
):
|
||||
self.queue.add(repository.update_repository(ignore_issues=True))
|
||||
repositories_to_update += 1
|
||||
self.queue.add(update_repository(repository))
|
||||
|
||||
async def update_coordinators() -> None:
|
||||
"""Update all coordinators."""
|
||||
await repositories_updated.wait()
|
||||
for coordinator in self.coordinators.values():
|
||||
coordinator.async_update_listeners()
|
||||
|
||||
if config_entry := self.configuration.config_entry:
|
||||
config_entry.async_create_background_task(
|
||||
self.hass, update_coordinators(), "update_coordinators"
|
||||
)
|
||||
else:
|
||||
self.hass.async_create_background_task(update_coordinators(), "update_coordinators")
|
||||
|
||||
self.log.debug("Recurring background task for downloaded custom repositories done")
|
||||
|
||||
|
@ -1077,10 +1031,7 @@ class HacsBase:
|
|||
was_installed = False
|
||||
|
||||
try:
|
||||
if self.configuration.experimental:
|
||||
critical = await self.data_client.get_data("critical")
|
||||
else:
|
||||
critical = await self.async_github_get_hacs_default_file("critical")
|
||||
critical = await self.data_client.get_data("critical", validate=True)
|
||||
except (GitHubNotModifiedException, HacsNotModifiedException):
|
||||
return
|
||||
except HacsException:
|
||||
|
@ -1134,11 +1085,10 @@ class HacsBase:
|
|||
self.log.critical("Restarting Home Assistant")
|
||||
self.hass.async_create_task(self.hass.async_stop(100))
|
||||
|
||||
@callback
|
||||
def async_setup_frontend_endpoint_plugin(self) -> None:
|
||||
async def async_setup_frontend_endpoint_plugin(self) -> None:
|
||||
"""Setup the http endpoints for plugins if its not already handled."""
|
||||
if self.status.active_frontend_endpoint_plugin or not os.path.exists(
|
||||
self.hass.config.path("www/community")
|
||||
if self.status.active_frontend_endpoint_plugin or not await async_exists(
|
||||
self.hass, self.hass.config.path("www/community")
|
||||
):
|
||||
return
|
||||
|
||||
|
@ -1150,26 +1100,11 @@ class HacsBase:
|
|||
use_cache,
|
||||
)
|
||||
|
||||
self.hass.http.register_static_path(
|
||||
await async_register_static_path(
|
||||
self.hass,
|
||||
URL_BASE,
|
||||
self.hass.config.path("www/community"),
|
||||
cache_headers=use_cache,
|
||||
)
|
||||
|
||||
self.status.active_frontend_endpoint_plugin = True
|
||||
|
||||
@callback
|
||||
def async_setup_frontend_endpoint_themes(self) -> None:
|
||||
"""Setup the http endpoints for themes if its not already handled."""
|
||||
if (
|
||||
self.configuration.experimental
|
||||
or self.status.active_frontend_endpoint_theme
|
||||
or not os.path.exists(self.hass.config.path("themes"))
|
||||
):
|
||||
return
|
||||
|
||||
self.log.info("Setting up themes endpoint")
|
||||
# Register themes
|
||||
self.hass.http.register_static_path(f"{URL_BASE}/themes", self.hass.config.path("themes"))
|
||||
|
||||
self.status.active_frontend_endpoint_theme = True
|
||||
|
|
|
@ -1,29 +1,32 @@
|
|||
"""Adds config flow for HACS."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
from contextlib import suppress
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from aiogithubapi import GitHubDeviceAPI, GitHubException
|
||||
from aiogithubapi import (
|
||||
GitHubDeviceAPI,
|
||||
GitHubException,
|
||||
GitHubLoginDeviceModel,
|
||||
GitHubLoginOauthModel,
|
||||
)
|
||||
from aiogithubapi.common.const import OAUTH_USER_LOGIN
|
||||
from awesomeversion import AwesomeVersion
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.config_entries import ConfigFlow, OptionsFlow
|
||||
from homeassistant.const import __version__ as HAVERSION
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.data_entry_flow import UnknownFlow
|
||||
from homeassistant.helpers import aiohttp_client
|
||||
from homeassistant.helpers.event import async_call_later
|
||||
from homeassistant.loader import async_get_integration
|
||||
import voluptuous as vol
|
||||
|
||||
from .base import HacsBase
|
||||
from .const import CLIENT_ID, DOMAIN, LOCALE, MINIMUM_HA_VERSION
|
||||
from .enums import ConfigurationType
|
||||
from .utils.configuration_schema import (
|
||||
APPDAEMON,
|
||||
COUNTRY,
|
||||
DEBUG,
|
||||
EXPERIMENTAL,
|
||||
NETDAEMON,
|
||||
RELEASE_LIMIT,
|
||||
SIDEPANEL_ICON,
|
||||
SIDEPANEL_TITLE,
|
||||
)
|
||||
|
@ -33,23 +36,22 @@ if TYPE_CHECKING:
|
|||
from homeassistant.core import HomeAssistant
|
||||
|
||||
|
||||
class HacsFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
class HacsFlowHandler(ConfigFlow, domain=DOMAIN):
|
||||
"""Config flow for HACS."""
|
||||
|
||||
hass: HomeAssistant
|
||||
|
||||
VERSION = 1
|
||||
CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_POLL
|
||||
|
||||
def __init__(self):
|
||||
hass: HomeAssistant
|
||||
activation_task: asyncio.Task | None = None
|
||||
device: GitHubDeviceAPI | None = None
|
||||
|
||||
_registration: GitHubLoginDeviceModel | None = None
|
||||
_activation: GitHubLoginOauthModel | None = None
|
||||
_reauth: bool = False
|
||||
|
||||
def __init__(self) -> None:
|
||||
"""Initialize."""
|
||||
self._errors = {}
|
||||
self.device = None
|
||||
self.activation = None
|
||||
self.log = LOGGER
|
||||
self._progress_task = None
|
||||
self._login_device = None
|
||||
self._reauth = False
|
||||
self._user_input = {}
|
||||
|
||||
async def async_step_user(self, user_input):
|
||||
|
@ -69,48 +71,55 @@ class HacsFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
|||
|
||||
return await self.async_step_device(user_input)
|
||||
|
||||
## Initial form
|
||||
# Initial form
|
||||
return await self._show_config_form(user_input)
|
||||
|
||||
async def async_step_device(self, _user_input):
|
||||
"""Handle device steps"""
|
||||
"""Handle device steps."""
|
||||
|
||||
async def _wait_for_activation(_=None):
|
||||
if self._login_device is None or self._login_device.expires_in is None:
|
||||
async_call_later(self.hass, 1, _wait_for_activation)
|
||||
return
|
||||
async def _wait_for_activation() -> None:
|
||||
try:
|
||||
response = await self.device.activation(device_code=self._registration.device_code)
|
||||
self._activation = response.data
|
||||
finally:
|
||||
|
||||
response = await self.device.activation(device_code=self._login_device.device_code)
|
||||
self.activation = response.data
|
||||
self.hass.async_create_task(
|
||||
self.hass.config_entries.flow.async_configure(flow_id=self.flow_id)
|
||||
)
|
||||
async def _progress():
|
||||
with suppress(UnknownFlow):
|
||||
await self.hass.config_entries.flow.async_configure(flow_id=self.flow_id)
|
||||
|
||||
if not self.activation:
|
||||
if not self.device:
|
||||
integration = await async_get_integration(self.hass, DOMAIN)
|
||||
if not self.device:
|
||||
self.device = GitHubDeviceAPI(
|
||||
client_id=CLIENT_ID,
|
||||
session=aiohttp_client.async_get_clientsession(self.hass),
|
||||
**{"client_name": f"HACS/{integration.version}"},
|
||||
)
|
||||
async_call_later(self.hass, 1, _wait_for_activation)
|
||||
self.device = GitHubDeviceAPI(
|
||||
client_id=CLIENT_ID,
|
||||
session=aiohttp_client.async_get_clientsession(self.hass),
|
||||
**{"client_name": f"HACS/{integration.version}"},
|
||||
)
|
||||
try:
|
||||
response = await self.device.register()
|
||||
self._login_device = response.data
|
||||
return self.async_show_progress(
|
||||
step_id="device",
|
||||
progress_action="wait_for_device",
|
||||
description_placeholders={
|
||||
"url": OAUTH_USER_LOGIN,
|
||||
"code": self._login_device.user_code,
|
||||
},
|
||||
)
|
||||
self._registration = response.data
|
||||
except GitHubException as exception:
|
||||
self.log.error(exception)
|
||||
return self.async_abort(reason="github")
|
||||
LOGGER.exception(exception)
|
||||
return self.async_abort(reason="could_not_register")
|
||||
|
||||
return self.async_show_progress_done(next_step_id="device_done")
|
||||
if self.activation_task is None:
|
||||
self.activation_task = self.hass.async_create_task(_wait_for_activation())
|
||||
|
||||
if self.activation_task.done():
|
||||
if (exception := self.activation_task.exception()) is not None:
|
||||
LOGGER.exception(exception)
|
||||
return self.async_show_progress_done(next_step_id="could_not_register")
|
||||
return self.async_show_progress_done(next_step_id="device_done")
|
||||
|
||||
show_progress_kwargs = {
|
||||
"step_id": "device",
|
||||
"progress_action": "wait_for_device",
|
||||
"description_placeholders": {
|
||||
"url": OAUTH_USER_LOGIN,
|
||||
"code": self._registration.user_code,
|
||||
},
|
||||
"progress_task": self.activation_task,
|
||||
}
|
||||
return self.async_show_progress(**show_progress_kwargs)
|
||||
|
||||
async def _show_config_form(self, user_input):
|
||||
"""Show the configuration form to edit location data."""
|
||||
|
@ -133,9 +142,6 @@ class HacsFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
|||
"acc_untested", default=user_input.get("acc_untested", False)
|
||||
): bool,
|
||||
vol.Required("acc_disable", default=user_input.get("acc_disable", False)): bool,
|
||||
vol.Optional(
|
||||
"experimental", default=user_input.get("experimental", False)
|
||||
): bool,
|
||||
}
|
||||
),
|
||||
errors=self._errors,
|
||||
|
@ -146,7 +152,7 @@ class HacsFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
|||
if self._reauth:
|
||||
existing_entry = self.hass.config_entries.async_get_entry(self.context["entry_id"])
|
||||
self.hass.config_entries.async_update_entry(
|
||||
existing_entry, data={**existing_entry.data, "token": self.activation.access_token}
|
||||
existing_entry, data={**existing_entry.data, "token": self._activation.access_token}
|
||||
)
|
||||
await self.hass.config_entries.async_reload(existing_entry.entry_id)
|
||||
return self.async_abort(reason="reauth_successful")
|
||||
|
@ -154,13 +160,17 @@ class HacsFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
|||
return self.async_create_entry(
|
||||
title="",
|
||||
data={
|
||||
"token": self.activation.access_token,
|
||||
"token": self._activation.access_token,
|
||||
},
|
||||
options={
|
||||
"experimental": self._user_input.get("experimental", False),
|
||||
"experimental": True,
|
||||
},
|
||||
)
|
||||
|
||||
async def async_step_could_not_register(self, _user_input=None):
|
||||
"""Handle issues that need transition await from progress step."""
|
||||
return self.async_abort(reason="could_not_register")
|
||||
|
||||
async def async_step_reauth(self, _user_input=None):
|
||||
"""Perform reauth upon an API authentication error."""
|
||||
return await self.async_step_reauth_confirm()
|
||||
|
@ -181,12 +191,13 @@ class HacsFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
|||
return HacsOptionsFlowHandler(config_entry)
|
||||
|
||||
|
||||
class HacsOptionsFlowHandler(config_entries.OptionsFlow):
|
||||
class HacsOptionsFlowHandler(OptionsFlow):
|
||||
"""HACS config flow options handler."""
|
||||
|
||||
def __init__(self, config_entry):
|
||||
"""Initialize HACS options flow."""
|
||||
self.config_entry = config_entry
|
||||
if AwesomeVersion(HAVERSION) < "2024.11.99":
|
||||
self.config_entry = config_entry
|
||||
|
||||
async def async_step_init(self, _user_input=None):
|
||||
"""Manage the options."""
|
||||
|
@ -196,10 +207,7 @@ class HacsOptionsFlowHandler(config_entries.OptionsFlow):
|
|||
"""Handle a flow initialized by the user."""
|
||||
hacs: HacsBase = self.hass.data.get(DOMAIN)
|
||||
if user_input is not None:
|
||||
limit = int(user_input.get(RELEASE_LIMIT, 5))
|
||||
if limit <= 0 or limit > 100:
|
||||
return self.async_abort(reason="release_limit_value")
|
||||
return self.async_create_entry(title="", data=user_input)
|
||||
return self.async_create_entry(title="", data={**user_input, "experimental": True})
|
||||
|
||||
if hacs is None or hacs.configuration is None:
|
||||
return self.async_abort(reason="not_setup")
|
||||
|
@ -207,18 +215,11 @@ class HacsOptionsFlowHandler(config_entries.OptionsFlow):
|
|||
if hacs.queue.has_pending_tasks:
|
||||
return self.async_abort(reason="pending_tasks")
|
||||
|
||||
if hacs.configuration.config_type == ConfigurationType.YAML:
|
||||
schema = {vol.Optional("not_in_use", default=""): str}
|
||||
else:
|
||||
schema = {
|
||||
vol.Optional(SIDEPANEL_TITLE, default=hacs.configuration.sidepanel_title): str,
|
||||
vol.Optional(SIDEPANEL_ICON, default=hacs.configuration.sidepanel_icon): str,
|
||||
vol.Optional(RELEASE_LIMIT, default=hacs.configuration.release_limit): int,
|
||||
vol.Optional(COUNTRY, default=hacs.configuration.country): vol.In(LOCALE),
|
||||
vol.Optional(APPDAEMON, default=hacs.configuration.appdaemon): bool,
|
||||
vol.Optional(NETDAEMON, default=hacs.configuration.netdaemon): bool,
|
||||
vol.Optional(DEBUG, default=hacs.configuration.debug): bool,
|
||||
vol.Optional(EXPERIMENTAL, default=hacs.configuration.experimental): bool,
|
||||
}
|
||||
schema = {
|
||||
vol.Optional(SIDEPANEL_TITLE, default=hacs.configuration.sidepanel_title): str,
|
||||
vol.Optional(SIDEPANEL_ICON, default=hacs.configuration.sidepanel_icon): str,
|
||||
vol.Optional(COUNTRY, default=hacs.configuration.country): vol.In(LOCALE),
|
||||
vol.Optional(APPDAEMON, default=hacs.configuration.appdaemon): bool,
|
||||
}
|
||||
|
||||
return self.async_show_form(step_id="user", data_schema=vol.Schema(schema))
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
"""Constants for HACS"""
|
||||
|
||||
from typing import TypeVar
|
||||
|
||||
from aiogithubapi.common.const import ACCEPT_HEADERS
|
||||
|
@ -6,7 +7,7 @@ from aiogithubapi.common.const import ACCEPT_HEADERS
|
|||
NAME_SHORT = "HACS"
|
||||
DOMAIN = "hacs"
|
||||
CLIENT_ID = "395a8e669c5de9f7c6e8"
|
||||
MINIMUM_HA_VERSION = "2023.6.0"
|
||||
MINIMUM_HA_VERSION = "2024.4.1"
|
||||
|
||||
URL_BASE = "/hacsfiles"
|
||||
|
||||
|
|
|
@ -1,12 +1,25 @@
|
|||
"""HACS Data client."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
from typing import Any
|
||||
|
||||
from aiohttp import ClientSession, ClientTimeout
|
||||
import voluptuous as vol
|
||||
|
||||
from .exceptions import HacsException, HacsNotModifiedException
|
||||
from .utils.logger import LOGGER
|
||||
from .utils.validate import (
|
||||
VALIDATE_FETCHED_V2_CRITICAL_REPO_SCHEMA,
|
||||
VALIDATE_FETCHED_V2_REMOVED_REPO_SCHEMA,
|
||||
VALIDATE_FETCHED_V2_REPO_DATA,
|
||||
)
|
||||
|
||||
CRITICAL_REMOVED_VALIDATORS = {
|
||||
"critical": VALIDATE_FETCHED_V2_CRITICAL_REPO_SCHEMA,
|
||||
"removed": VALIDATE_FETCHED_V2_REMOVED_REPO_SCHEMA,
|
||||
}
|
||||
|
||||
|
||||
class HacsDataClient:
|
||||
|
@ -39,7 +52,7 @@ class HacsDataClient:
|
|||
response.raise_for_status()
|
||||
except HacsNotModifiedException:
|
||||
raise
|
||||
except asyncio.TimeoutError:
|
||||
except TimeoutError:
|
||||
raise HacsException("Timeout of 60s reached") from None
|
||||
except Exception as exception:
|
||||
raise HacsException(f"Error fetching data from HACS: {exception}") from exception
|
||||
|
@ -48,9 +61,37 @@ class HacsDataClient:
|
|||
|
||||
return await response.json()
|
||||
|
||||
async def get_data(self, section: str | None) -> dict[str, dict[str, Any]]:
|
||||
async def get_data(self, section: str | None, *, validate: bool) -> dict[str, dict[str, Any]]:
|
||||
"""Get data."""
|
||||
return await self._do_request(filename="data.json", section=section)
|
||||
data = await self._do_request(filename="data.json", section=section)
|
||||
if not validate:
|
||||
return data
|
||||
|
||||
if section in VALIDATE_FETCHED_V2_REPO_DATA:
|
||||
validated = {}
|
||||
for key, repo_data in data.items():
|
||||
try:
|
||||
validated[key] = VALIDATE_FETCHED_V2_REPO_DATA[section](repo_data)
|
||||
except vol.Invalid as exception:
|
||||
LOGGER.info(
|
||||
"Got invalid data for %s (%s)", repo_data.get("full_name", key), exception
|
||||
)
|
||||
continue
|
||||
|
||||
return validated
|
||||
|
||||
if not (validator := CRITICAL_REMOVED_VALIDATORS.get(section)):
|
||||
raise ValueError(f"Do not know how to validate {section}")
|
||||
|
||||
validated = []
|
||||
for repo_data in data:
|
||||
try:
|
||||
validated.append(validator(repo_data))
|
||||
except vol.Invalid as exception:
|
||||
LOGGER.info("Got invalid data for %s (%s)", section, exception)
|
||||
continue
|
||||
|
||||
return validated
|
||||
|
||||
async def get_repositories(self, section: str) -> list[str]:
|
||||
"""Get repositories."""
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
"""Diagnostics support for HACS."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
@ -10,7 +11,6 @@ from homeassistant.core import HomeAssistant
|
|||
|
||||
from .base import HacsBase
|
||||
from .const import DOMAIN
|
||||
from .utils.configuration_schema import TOKEN
|
||||
|
||||
|
||||
async def async_get_config_entry_diagnostics(
|
||||
|
@ -48,8 +48,6 @@ async def async_get_config_entry_diagnostics(
|
|||
"country",
|
||||
"debug",
|
||||
"dev",
|
||||
"experimental",
|
||||
"netdaemon",
|
||||
"python_script",
|
||||
"release_limit",
|
||||
"theme",
|
||||
|
@ -79,4 +77,4 @@ async def async_get_config_entry_diagnostics(
|
|||
except GitHubException as exception:
|
||||
data["rate_limit"] = str(exception)
|
||||
|
||||
return async_redact_data(data, (TOKEN,))
|
||||
return async_redact_data(data, ("token",))
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
"""HACS Base entities."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
@ -7,8 +8,10 @@ from homeassistant.core import callback
|
|||
from homeassistant.helpers.device_registry import DeviceEntryType
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
from homeassistant.helpers.entity import Entity
|
||||
from homeassistant.helpers.update_coordinator import BaseCoordinatorEntity
|
||||
|
||||
from .const import DOMAIN, HACS_SYSTEM_ID, NAME_SHORT
|
||||
from .coordinator import HacsUpdateCoordinator
|
||||
from .enums import HacsDispatchEvent, HacsGitHubRepo
|
||||
|
||||
if TYPE_CHECKING:
|
||||
|
@ -39,6 +42,10 @@ class HacsBaseEntity(Entity):
|
|||
"""Initialize."""
|
||||
self.hacs = hacs
|
||||
|
||||
|
||||
class HacsDispatcherEntity(HacsBaseEntity):
|
||||
"""Base HACS entity listening to dispatcher signals."""
|
||||
|
||||
async def async_added_to_hass(self) -> None:
|
||||
"""Register for status events."""
|
||||
self.async_on_remove(
|
||||
|
@ -64,7 +71,7 @@ class HacsBaseEntity(Entity):
|
|||
self.async_write_ha_state()
|
||||
|
||||
|
||||
class HacsSystemEntity(HacsBaseEntity):
|
||||
class HacsSystemEntity(HacsDispatcherEntity):
|
||||
"""Base system entity."""
|
||||
|
||||
_attr_icon = "hacs:hacs"
|
||||
|
@ -76,7 +83,7 @@ class HacsSystemEntity(HacsBaseEntity):
|
|||
return system_info(self.hacs)
|
||||
|
||||
|
||||
class HacsRepositoryEntity(HacsBaseEntity):
|
||||
class HacsRepositoryEntity(BaseCoordinatorEntity[HacsUpdateCoordinator], HacsBaseEntity):
|
||||
"""Base repository entity."""
|
||||
|
||||
def __init__(
|
||||
|
@ -85,9 +92,11 @@ class HacsRepositoryEntity(HacsBaseEntity):
|
|||
repository: HacsRepository,
|
||||
) -> None:
|
||||
"""Initialize."""
|
||||
super().__init__(hacs=hacs)
|
||||
BaseCoordinatorEntity.__init__(self, hacs.coordinators[repository.data.category])
|
||||
HacsBaseEntity.__init__(self, hacs=hacs)
|
||||
self.repository = repository
|
||||
self._attr_unique_id = str(repository.data.id)
|
||||
self._repo_last_fetched = repository.data.last_fetched
|
||||
|
||||
@property
|
||||
def available(self) -> bool:
|
||||
|
@ -100,20 +109,35 @@ class HacsRepositoryEntity(HacsBaseEntity):
|
|||
if self.repository.data.full_name == HacsGitHubRepo.INTEGRATION:
|
||||
return system_info(self.hacs)
|
||||
|
||||
def _manufacturer():
|
||||
if authors := self.repository.data.authors:
|
||||
return ", ".join(author.replace("@", "") for author in authors)
|
||||
return self.repository.data.full_name.split("/")[0]
|
||||
|
||||
return {
|
||||
"identifiers": {(DOMAIN, str(self.repository.data.id))},
|
||||
"name": self.repository.display_name,
|
||||
"model": self.repository.data.category,
|
||||
"manufacturer": ", ".join(
|
||||
author.replace("@", "") for author in self.repository.data.authors
|
||||
),
|
||||
"configuration_url": "homeassistant://hacs",
|
||||
"manufacturer": _manufacturer(),
|
||||
"configuration_url": f"homeassistant://hacs/repository/{self.repository.data.id}",
|
||||
"entry_type": DeviceEntryType.SERVICE,
|
||||
}
|
||||
|
||||
@callback
|
||||
def _update_and_write_state(self, data: dict) -> None:
|
||||
"""Update the entity and write state."""
|
||||
if data.get("repository_id") == self.repository.data.id:
|
||||
self._update()
|
||||
self.async_write_ha_state()
|
||||
def _handle_coordinator_update(self) -> None:
|
||||
"""Handle updated data from the coordinator."""
|
||||
if (
|
||||
self._repo_last_fetched is not None
|
||||
and self.repository.data.last_fetched is not None
|
||||
and self._repo_last_fetched >= self.repository.data.last_fetched
|
||||
):
|
||||
return
|
||||
|
||||
self._repo_last_fetched = self.repository.data.last_fetched
|
||||
self.async_write_ha_state()
|
||||
|
||||
async def async_update(self) -> None:
|
||||
"""Update the entity.
|
||||
|
||||
Only used by the generic entity update service.
|
||||
"""
|
||||
|
|
|
@ -1,20 +1,7 @@
|
|||
"""Helper constants."""
|
||||
|
||||
# pylint: disable=missing-class-docstring
|
||||
import sys
|
||||
|
||||
if sys.version_info.minor >= 11:
|
||||
# Needs Python 3.11
|
||||
from enum import StrEnum # # pylint: disable=no-name-in-module
|
||||
else:
|
||||
try:
|
||||
# https://github.com/home-assistant/core/blob/dev/homeassistant/backports/enum.py
|
||||
# Considered internal to Home Assistant, can be removed whenever.
|
||||
from homeassistant.backports.enum import StrEnum
|
||||
except ImportError:
|
||||
from enum import Enum
|
||||
|
||||
class StrEnum(str, Enum):
|
||||
pass
|
||||
from enum import StrEnum
|
||||
|
||||
|
||||
class HacsGitHubRepo(StrEnum):
|
||||
|
@ -29,7 +16,6 @@ class HacsCategory(StrEnum):
|
|||
INTEGRATION = "integration"
|
||||
LOVELACE = "lovelace"
|
||||
PLUGIN = "plugin" # Kept for legacy purposes
|
||||
NETDAEMON = "netdaemon"
|
||||
PYTHON_SCRIPT = "python_script"
|
||||
TEMPLATE = "template"
|
||||
THEME = "theme"
|
||||
|
@ -59,11 +45,6 @@ class RepositoryFile(StrEnum):
|
|||
MAINIFEST_JSON = "manifest.json"
|
||||
|
||||
|
||||
class ConfigurationType(StrEnum):
|
||||
YAML = "yaml"
|
||||
CONFIG_ENTRY = "config_entry"
|
||||
|
||||
|
||||
class LovelaceMode(StrEnum):
|
||||
"""Lovelace Modes."""
|
||||
|
||||
|
|
|
@ -1,61 +1,53 @@
|
|||
""""Starting setup task: Frontend"."""
|
||||
"""Starting setup task: Frontend."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.components.frontend import (
|
||||
add_extra_js_url,
|
||||
async_register_built_in_panel,
|
||||
)
|
||||
|
||||
from .const import DOMAIN, URL_BASE
|
||||
from .hacs_frontend import VERSION as FE_VERSION, locate_dir
|
||||
from .hacs_frontend_experimental import (
|
||||
VERSION as EXPERIMENTAL_FE_VERSION,
|
||||
locate_dir as experimental_locate_dir,
|
||||
)
|
||||
from .utils.workarounds import async_register_static_path
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from .base import HacsBase
|
||||
|
||||
|
||||
@callback
|
||||
def async_register_frontend(hass: HomeAssistant, hacs: HacsBase) -> None:
|
||||
async def async_register_frontend(hass: HomeAssistant, hacs: HacsBase) -> None:
|
||||
"""Register the frontend."""
|
||||
|
||||
# Setup themes endpoint if needed
|
||||
hacs.async_setup_frontend_endpoint_themes()
|
||||
|
||||
# Register frontend
|
||||
if hacs.configuration.dev and (frontend_path := os.getenv("HACS_FRONTEND_DIR")):
|
||||
hacs.log.warning(
|
||||
"<HacsFrontend> Frontend development mode enabled. Do not run in production!"
|
||||
)
|
||||
hass.http.register_static_path(
|
||||
f"{URL_BASE}/frontend", f"{frontend_path}/hacs_frontend", cache_headers=False
|
||||
)
|
||||
elif hacs.configuration.experimental:
|
||||
hacs.log.info("<HacsFrontend> Using experimental frontend")
|
||||
hass.http.register_static_path(
|
||||
f"{URL_BASE}/frontend", experimental_locate_dir(), cache_headers=False
|
||||
await async_register_static_path(
|
||||
hass, f"{URL_BASE}/frontend", f"{frontend_path}/hacs_frontend", cache_headers=False
|
||||
)
|
||||
hacs.frontend_version = "dev"
|
||||
else:
|
||||
#
|
||||
hass.http.register_static_path(f"{URL_BASE}/frontend", locate_dir(), cache_headers=False)
|
||||
await async_register_static_path(
|
||||
hass, f"{URL_BASE}/frontend", locate_dir(), cache_headers=False
|
||||
)
|
||||
hacs.frontend_version = FE_VERSION
|
||||
|
||||
# Custom iconset
|
||||
hass.http.register_static_path(
|
||||
f"{URL_BASE}/iconset.js", str(hacs.integration_dir / "iconset.js")
|
||||
)
|
||||
if "frontend_extra_module_url" not in hass.data:
|
||||
hass.data["frontend_extra_module_url"] = set()
|
||||
hass.data["frontend_extra_module_url"].add(f"{URL_BASE}/iconset.js")
|
||||
|
||||
hacs.frontend_version = (
|
||||
FE_VERSION if not hacs.configuration.experimental else EXPERIMENTAL_FE_VERSION
|
||||
await async_register_static_path(
|
||||
hass, f"{URL_BASE}/iconset.js", str(hacs.integration_dir / "iconset.js")
|
||||
)
|
||||
add_extra_js_url(hass, f"{URL_BASE}/iconset.js")
|
||||
|
||||
# Add to sidepanel if needed
|
||||
if DOMAIN not in hass.data.get("frontend_panels", {}):
|
||||
hass.components.frontend.async_register_built_in_panel(
|
||||
async_register_built_in_panel(
|
||||
hass,
|
||||
component_name="custom",
|
||||
sidebar_title=hacs.configuration.sidepanel_title,
|
||||
sidebar_icon=hacs.configuration.sidepanel_icon,
|
||||
|
@ -72,4 +64,4 @@ def async_register_frontend(hass: HomeAssistant, hacs: HacsBase) -> None:
|
|||
)
|
||||
|
||||
# Setup plugin endpoint if needed
|
||||
hacs.async_setup_frontend_endpoint_plugin()
|
||||
await hacs.async_setup_frontend_endpoint_plugin()
|
||||
|
|
|
@ -1,10 +1 @@
|
|||
|
||||
try {
|
||||
new Function("import('/hacsfiles/frontend/main-ad130be7.js')")();
|
||||
} catch (err) {
|
||||
var el = document.createElement('script');
|
||||
el.src = '/hacsfiles/frontend/main-ad130be7.js';
|
||||
el.type = 'module';
|
||||
document.body.appendChild(el);
|
||||
}
|
||||
|
||||
!function(){function n(n){var e=document.createElement("script");e.src=n,document.body.appendChild(e)}if(/.*Version\/(?:11|12)(?:\.\d+)*.*Safari\//.test(navigator.userAgent))n("/hacsfiles/frontend/frontend_es5/entrypoint.c180d0b256f9b6d0.js");else try{new Function("import('/hacsfiles/frontend/frontend_latest/entrypoint.bb9d28f38e9fba76.js')")()}catch(e){n("/hacsfiles/frontend/frontend_es5/entrypoint.c180d0b256f9b6d0.js")}}()
|
|
@ -1 +1 @@
|
|||
VERSION="20220906112053"
|
||||
VERSION="20250128065759"
|
|
@ -1,6 +1,9 @@
|
|||
{
|
||||
"domain": "hacs",
|
||||
"name": "HACS",
|
||||
"after_dependencies": [
|
||||
"python_script"
|
||||
],
|
||||
"codeowners": [
|
||||
"@ludeeus"
|
||||
],
|
||||
|
@ -13,11 +16,11 @@
|
|||
"lovelace",
|
||||
"repairs"
|
||||
],
|
||||
"documentation": "https://hacs.xyz/docs/configuration/start",
|
||||
"documentation": "https://hacs.xyz/docs/use/",
|
||||
"iot_class": "cloud_polling",
|
||||
"issue_tracker": "https://github.com/hacs/integration/issues",
|
||||
"requirements": [
|
||||
"aiogithubapi>=22.10.1"
|
||||
],
|
||||
"version": "1.33.0"
|
||||
"version": "2.0.5"
|
||||
}
|
|
@ -1,22 +1,21 @@
|
|||
"""Initialize repositories."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from ..enums import HacsCategory
|
||||
from .appdaemon import HacsAppdaemonRepository
|
||||
from .base import HacsRepository
|
||||
from .integration import HacsIntegrationRepository
|
||||
from .netdaemon import HacsNetdaemonRepository
|
||||
from .plugin import HacsPluginRepository
|
||||
from .python_script import HacsPythonScriptRepository
|
||||
from .template import HacsTemplateRepository
|
||||
from .theme import HacsThemeRepository
|
||||
|
||||
RERPOSITORY_CLASSES: dict[HacsCategory, HacsRepository] = {
|
||||
REPOSITORY_CLASSES: dict[HacsCategory, HacsRepository] = {
|
||||
HacsCategory.THEME: HacsThemeRepository,
|
||||
HacsCategory.INTEGRATION: HacsIntegrationRepository,
|
||||
HacsCategory.PYTHON_SCRIPT: HacsPythonScriptRepository,
|
||||
HacsCategory.APPDAEMON: HacsAppdaemonRepository,
|
||||
HacsCategory.NETDAEMON: HacsNetdaemonRepository,
|
||||
HacsCategory.PLUGIN: HacsPluginRepository,
|
||||
HacsCategory.TEMPLATE: HacsTemplateRepository,
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
"""Class for appdaemon apps in HACS."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
@ -79,7 +80,7 @@ class HacsAppdaemonRepository(HacsRepository):
|
|||
# Set local path
|
||||
self.content.path.local = self.localpath
|
||||
|
||||
# Signal entities to refresh
|
||||
# Signal frontend to refresh
|
||||
if self.data.installed:
|
||||
self.hacs.async_dispatch(
|
||||
HacsDispatchEvent.REPOSITORY,
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
"""Repository."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from asyncio import sleep
|
||||
from datetime import datetime
|
||||
from datetime import UTC, datetime
|
||||
import os
|
||||
import pathlib
|
||||
import shutil
|
||||
|
@ -15,29 +16,31 @@ from aiogithubapi import (
|
|||
AIOGitHubAPINotModifiedException,
|
||||
GitHubReleaseModel,
|
||||
)
|
||||
from aiogithubapi.const import BASE_API_URL
|
||||
from aiogithubapi.objects.repository import AIOGitHubAPIRepository
|
||||
import attr
|
||||
from homeassistant.helpers import device_registry as dr, issue_registry as ir
|
||||
|
||||
from ..const import DOMAIN
|
||||
from ..enums import ConfigurationType, HacsDispatchEvent, RepositoryFile
|
||||
from ..enums import HacsDispatchEvent, RepositoryFile
|
||||
from ..exceptions import (
|
||||
HacsException,
|
||||
HacsNotModifiedException,
|
||||
HacsRepositoryArchivedException,
|
||||
HacsRepositoryExistException,
|
||||
)
|
||||
from ..utils.backup import Backup, BackupNetDaemon
|
||||
from ..types import DownloadableContent
|
||||
from ..utils.backup import Backup
|
||||
from ..utils.decode import decode_content
|
||||
from ..utils.decorator import concurrent
|
||||
from ..utils.file_system import async_exists, async_remove, async_remove_directory
|
||||
from ..utils.filters import filter_content_return_one_of_type
|
||||
from ..utils.github_graphql_query import GET_REPOSITORY_RELEASES
|
||||
from ..utils.json import json_loads
|
||||
from ..utils.logger import LOGGER
|
||||
from ..utils.path import is_safe
|
||||
from ..utils.queue_manager import QueueManager
|
||||
from ..utils.store import async_remove_store
|
||||
from ..utils.template import render_template
|
||||
from ..utils.url import github_archive, github_release_asset
|
||||
from ..utils.validate import Validate
|
||||
from ..utils.version import (
|
||||
version_left_higher_or_equal_then_right,
|
||||
|
@ -83,7 +86,6 @@ TOPIC_FILTER = (
|
|||
"lovelace",
|
||||
"media-player",
|
||||
"mediaplayer",
|
||||
"netdaemon",
|
||||
"plugin",
|
||||
"python_script",
|
||||
"python-script",
|
||||
|
@ -112,6 +114,7 @@ REPOSITORY_KEYS_TO_EXPORT = (
|
|||
("last_version", None),
|
||||
("manifest_name", None),
|
||||
("open_issues", 0),
|
||||
("prerelease", None),
|
||||
("stargazers_count", 0),
|
||||
("topics", []),
|
||||
)
|
||||
|
@ -163,6 +166,7 @@ class RepositoryData:
|
|||
manifest_name: str = None
|
||||
new: bool = True
|
||||
open_issues: int = 0
|
||||
prerelease: str = None
|
||||
published_tags: list[str] = []
|
||||
releases: bool = False
|
||||
selected_tag: str = None
|
||||
|
@ -173,7 +177,7 @@ class RepositoryData:
|
|||
@property
|
||||
def name(self):
|
||||
"""Return the name."""
|
||||
if self.category in ["integration", "netdaemon"]:
|
||||
if self.category == "integration":
|
||||
return self.domain
|
||||
return self.full_name.split("/")[-1]
|
||||
|
||||
|
@ -195,7 +199,7 @@ class RepositoryData:
|
|||
continue
|
||||
|
||||
if key == "last_fetched" and isinstance(value, float):
|
||||
setattr(self, key, datetime.fromtimestamp(value))
|
||||
setattr(self, key, datetime.fromtimestamp(value, UTC))
|
||||
elif key == "id":
|
||||
setattr(self, key, str(value))
|
||||
elif key == "country":
|
||||
|
@ -383,7 +387,9 @@ class HacsRepository:
|
|||
@property
|
||||
def display_available_version(self) -> str:
|
||||
"""Return display_authors"""
|
||||
if self.data.last_version is not None:
|
||||
if self.data.show_beta and self.data.prerelease is not None:
|
||||
available = self.data.prerelease
|
||||
elif self.data.last_version is not None:
|
||||
available = self.data.last_version
|
||||
else:
|
||||
if self.data.last_commit is not None:
|
||||
|
@ -404,8 +410,6 @@ class HacsRepository:
|
|||
@property
|
||||
def pending_update(self) -> bool:
|
||||
"""Return True if pending update."""
|
||||
if not self.can_download:
|
||||
return False
|
||||
if self.data.installed:
|
||||
if self.data.selected_tag is not None:
|
||||
if self.data.selected_tag == self.data.default_branch:
|
||||
|
@ -500,13 +504,7 @@ class HacsRepository:
|
|||
|
||||
if self.repository_object:
|
||||
self.data.last_updated = self.repository_object.attributes.get("pushed_at", 0)
|
||||
self.data.last_fetched = datetime.utcnow()
|
||||
|
||||
# Set topics
|
||||
self.data.topics = self.data.topics
|
||||
|
||||
# Set description
|
||||
self.data.description = self.data.description
|
||||
self.data.last_fetched = datetime.now(UTC)
|
||||
|
||||
@concurrent(concurrenttasks=10, backoff_time=5)
|
||||
async def common_update(self, ignore_issues=False, force=False, skip_releases=False) -> bool:
|
||||
|
@ -554,52 +552,55 @@ class HacsRepository:
|
|||
self.additional_info = await self.async_get_info_file_contents()
|
||||
|
||||
# Set last fetch attribute
|
||||
self.data.last_fetched = datetime.utcnow()
|
||||
self.data.last_fetched = datetime.now(UTC)
|
||||
|
||||
return True
|
||||
|
||||
async def download_zip_files(self, validate) -> None:
|
||||
async def download_zip_files(self, validate: Validate) -> None:
|
||||
"""Download ZIP archive from repository release."""
|
||||
|
||||
try:
|
||||
await self.async_download_zip_file(
|
||||
DownloadableContent(
|
||||
name=self.repository_manifest.filename,
|
||||
url=github_release_asset(
|
||||
repository=self.data.full_name,
|
||||
version=self.ref,
|
||||
filename=self.repository_manifest.filename,
|
||||
),
|
||||
),
|
||||
validate,
|
||||
)
|
||||
# lgtm [py/catch-base-exception] pylint: disable=broad-except
|
||||
except BaseException:
|
||||
validate.errors.append(
|
||||
f"Download of {
|
||||
self.repository_manifest.filename} was not completed"
|
||||
)
|
||||
|
||||
async def async_download_zip_file(
|
||||
self,
|
||||
content: DownloadableContent,
|
||||
validate: Validate,
|
||||
) -> None:
|
||||
"""Download ZIP archive from repository release."""
|
||||
try:
|
||||
contents = None
|
||||
target_ref = self.ref.split("/")[1]
|
||||
|
||||
for release in self.releases.objects:
|
||||
self.logger.debug(
|
||||
"%s ref: %s --- tag: %s", self.string, target_ref, release.tag_name
|
||||
)
|
||||
if release.tag_name == target_ref:
|
||||
contents = release.assets
|
||||
break
|
||||
|
||||
if not contents:
|
||||
validate.errors.append(f"No assets found for release '{self.ref}'")
|
||||
return
|
||||
|
||||
download_queue = QueueManager(hass=self.hacs.hass)
|
||||
|
||||
for content in contents or []:
|
||||
download_queue.add(self.async_download_zip_file(content, validate))
|
||||
|
||||
await download_queue.execute()
|
||||
except BaseException: # lgtm [py/catch-base-exception] pylint: disable=broad-except
|
||||
validate.errors.append("Download was not completed")
|
||||
|
||||
async def async_download_zip_file(self, content, validate) -> None:
|
||||
"""Download ZIP archive from repository release."""
|
||||
try:
|
||||
filecontent = await self.hacs.async_download_file(content.browser_download_url)
|
||||
filecontent = await self.hacs.async_download_file(content["url"])
|
||||
|
||||
if filecontent is None:
|
||||
validate.errors.append(f"[{content.name}] was not downloaded")
|
||||
validate.errors.append(f"Failed to download {content['url']}")
|
||||
return
|
||||
|
||||
temp_dir = await self.hacs.hass.async_add_executor_job(tempfile.mkdtemp)
|
||||
temp_file = f"{temp_dir}/{self.repository_manifest.filename}"
|
||||
|
||||
result = await self.hacs.async_save_file(temp_file, filecontent)
|
||||
with zipfile.ZipFile(temp_file, "r") as zip_file:
|
||||
zip_file.extractall(self.content.path.local)
|
||||
|
||||
def _extract_zip_file():
|
||||
with zipfile.ZipFile(temp_file, "r") as zip_file:
|
||||
zip_file.extractall(self.content.path.local)
|
||||
|
||||
await self.hacs.hass.async_add_executor_job(_extract_zip_file)
|
||||
|
||||
def cleanup_temp_dir():
|
||||
"""Cleanup temp_dir."""
|
||||
|
@ -608,32 +609,39 @@ class HacsRepository:
|
|||
shutil.rmtree(temp_dir)
|
||||
|
||||
if result:
|
||||
self.logger.info("%s Download of %s completed", self.string, content.name)
|
||||
self.logger.info("%s Download of %s completed", self.string, content["name"])
|
||||
await self.hacs.hass.async_add_executor_job(cleanup_temp_dir)
|
||||
return
|
||||
|
||||
validate.errors.append(f"[{content.name}] was not downloaded")
|
||||
except BaseException: # lgtm [py/catch-base-exception] pylint: disable=broad-except
|
||||
validate.errors.append(f"[{content['name']}] was not downloaded")
|
||||
# lgtm [py/catch-base-exception] pylint: disable=broad-except
|
||||
except BaseException:
|
||||
validate.errors.append("Download was not completed")
|
||||
|
||||
async def download_content(self) -> None:
|
||||
async def download_content(self, version: string | None = None) -> None:
|
||||
"""Download the content of a directory."""
|
||||
if self.hacs.configuration.experimental:
|
||||
if (
|
||||
not self.repository_manifest.zip_release
|
||||
and not self.data.file_name
|
||||
and self.content.path.remote is not None
|
||||
):
|
||||
self.logger.info("%s Trying experimental download", self.string)
|
||||
try:
|
||||
await self.download_repository_zip()
|
||||
return
|
||||
except HacsException as exception:
|
||||
self.logger.exception(exception)
|
||||
contents: list[FileInformation] | None = None
|
||||
if (
|
||||
not self.repository_manifest.zip_release
|
||||
and not self.data.file_name
|
||||
and self.content.path.remote is not None
|
||||
):
|
||||
self.logger.info("%s Downloading repository archive", self.string)
|
||||
try:
|
||||
await self.download_repository_zip()
|
||||
return
|
||||
except HacsException as exception:
|
||||
self.logger.exception(exception)
|
||||
|
||||
contents = self.gather_files_to_download()
|
||||
if self.repository_manifest.filename:
|
||||
self.logger.debug("%s %s", self.string, self.repository_manifest.filename)
|
||||
|
||||
if self.content.path.remote == "release" and version is not None:
|
||||
contents = await self.release_contents(version)
|
||||
|
||||
if not contents:
|
||||
contents = self.gather_files_to_download()
|
||||
|
||||
if not contents:
|
||||
raise HacsException("No content to download")
|
||||
|
||||
|
@ -654,15 +662,17 @@ class HacsRepository:
|
|||
if not ref:
|
||||
raise HacsException("Missing required elements.")
|
||||
|
||||
url = f"{BASE_API_URL}/repos/{self.data.full_name}/zipball/{ref}"
|
||||
|
||||
filecontent = await self.hacs.async_download_file(
|
||||
url,
|
||||
headers={
|
||||
"Authorization": f"token {self.hacs.configuration.token}",
|
||||
"User-Agent": f"HACS/{self.hacs.version}",
|
||||
},
|
||||
github_archive(repository=self.data.full_name, version=ref, variant="tags"),
|
||||
keep_url=True,
|
||||
nolog=True,
|
||||
)
|
||||
|
||||
if filecontent is None:
|
||||
filecontent = await self.hacs.async_download_file(
|
||||
github_archive(repository=self.data.full_name, version=ref, variant="heads"),
|
||||
keep_url=True,
|
||||
)
|
||||
if filecontent is None:
|
||||
raise HacsException(f"[{self}] Failed to download zipball")
|
||||
|
||||
|
@ -672,18 +682,26 @@ class HacsRepository:
|
|||
if not result:
|
||||
raise HacsException("Could not save ZIP file")
|
||||
|
||||
with zipfile.ZipFile(temp_file, "r") as zip_file:
|
||||
extractable = []
|
||||
for path in zip_file.filelist:
|
||||
filename = "/".join(path.filename.split("/")[1:])
|
||||
if (
|
||||
filename.startswith(self.content.path.remote)
|
||||
and filename != self.content.path.remote
|
||||
):
|
||||
path.filename = filename.replace(self.content.path.remote, "")
|
||||
extractable.append(path)
|
||||
def _extract_zip_file():
|
||||
with zipfile.ZipFile(temp_file, "r") as zip_file:
|
||||
extractable = []
|
||||
for path in zip_file.filelist:
|
||||
filename = "/".join(path.filename.split("/")[1:])
|
||||
if (
|
||||
filename.startswith(self.content.path.remote)
|
||||
and filename != self.content.path.remote
|
||||
):
|
||||
path.filename = filename.replace(self.content.path.remote, "")
|
||||
if path.filename == "/":
|
||||
# Blank files is not valid, and will start to throw in Python 3.12
|
||||
continue
|
||||
extractable.append(path)
|
||||
|
||||
zip_file.extractall(self.content.path.local, extractable)
|
||||
if len(extractable) == 0:
|
||||
raise HacsException("No content to extract")
|
||||
zip_file.extractall(self.content.path.local, extractable)
|
||||
|
||||
await self.hacs.hass.async_add_executor_job(_extract_zip_file)
|
||||
|
||||
def cleanup_temp_dir():
|
||||
"""Cleanup temp_dir."""
|
||||
|
@ -706,18 +724,15 @@ class HacsRepository:
|
|||
)
|
||||
if response:
|
||||
return json_loads(decode_content(response.data.content))
|
||||
except BaseException: # lgtm [py/catch-base-exception] pylint: disable=broad-except
|
||||
# lgtm [py/catch-base-exception] pylint: disable=broad-except
|
||||
except BaseException:
|
||||
pass
|
||||
|
||||
async def async_get_info_file_contents(self) -> str:
|
||||
async def async_get_info_file_contents(self, *, version: str | None = None, **kwargs) -> str:
|
||||
"""Get the content of the info.md file."""
|
||||
|
||||
def _info_file_variants() -> tuple[str, ...]:
|
||||
name: str = (
|
||||
"readme"
|
||||
if self.repository_manifest.render_readme or self.hacs.configuration.experimental
|
||||
else "info"
|
||||
)
|
||||
name: str = "readme"
|
||||
return (
|
||||
f"{name.upper()}.md",
|
||||
f"{name}.md",
|
||||
|
@ -732,25 +747,7 @@ class HacsRepository:
|
|||
if not info_files:
|
||||
return ""
|
||||
|
||||
try:
|
||||
response = await self.hacs.async_github_api_method(
|
||||
method=self.hacs.githubapi.repos.contents.get,
|
||||
raise_exception=False,
|
||||
repository=self.data.full_name,
|
||||
path=info_files[0],
|
||||
)
|
||||
if response:
|
||||
return render_template(
|
||||
self.hacs,
|
||||
decode_content(response.data.content)
|
||||
.replace("<svg", "<disabled")
|
||||
.replace("</svg", "</disabled"),
|
||||
self,
|
||||
)
|
||||
except BaseException as exc: # lgtm [py/catch-base-exception] pylint: disable=broad-except
|
||||
self.logger.error("%s %s", self.string, exc)
|
||||
|
||||
return ""
|
||||
return await self.get_documentation(filename=info_files[0], version=version) or ""
|
||||
|
||||
def remove(self) -> None:
|
||||
"""Run remove tasks."""
|
||||
|
@ -764,19 +761,7 @@ class HacsRepository:
|
|||
if not await self.remove_local_directory():
|
||||
raise HacsException("Could not uninstall")
|
||||
self.data.installed = False
|
||||
if self.data.category == "integration":
|
||||
if self.data.config_flow:
|
||||
await self.reload_custom_components()
|
||||
else:
|
||||
self.pending_restart = True
|
||||
elif self.data.category == "theme":
|
||||
try:
|
||||
await self.hacs.hass.services.async_call("frontend", "reload_themes", {})
|
||||
except BaseException: # lgtm [py/catch-base-exception] pylint: disable=broad-except
|
||||
pass
|
||||
elif self.data.category == "template":
|
||||
await self.hacs.hass.services.async_call("homeassistant", "reload_custom_templates", {})
|
||||
|
||||
await self._async_post_uninstall()
|
||||
await async_remove_store(self.hacs.hass, f"hacs/{self.data.id}.hacs")
|
||||
|
||||
self.data.installed_version = None
|
||||
|
@ -799,7 +784,7 @@ class HacsRepository:
|
|||
|
||||
try:
|
||||
if self.data.category == "python_script":
|
||||
local_path = f"{self.content.path.local}/{self.data.name}.py"
|
||||
local_path = f"{self.content.path.local}/{self.data.file_name}"
|
||||
elif self.data.category == "template":
|
||||
local_path = f"{self.content.path.local}/{self.data.file_name}"
|
||||
elif self.data.category == "theme":
|
||||
|
@ -808,8 +793,7 @@ class HacsRepository:
|
|||
f"{self.hacs.configuration.theme_path}/"
|
||||
f"{self.data.name}.yaml"
|
||||
)
|
||||
if os.path.exists(path):
|
||||
os.remove(path)
|
||||
await async_remove(self.hacs.hass, path, missing_ok=True)
|
||||
local_path = self.content.path.local
|
||||
elif self.data.category == "integration":
|
||||
if not self.data.domain:
|
||||
|
@ -823,18 +807,18 @@ class HacsRepository:
|
|||
else:
|
||||
local_path = self.content.path.local
|
||||
|
||||
if os.path.exists(local_path):
|
||||
if await async_exists(self.hacs.hass, local_path):
|
||||
if not is_safe(self.hacs, local_path):
|
||||
self.logger.error("%s Path %s is blocked from removal", self.string, local_path)
|
||||
return False
|
||||
self.logger.debug("%s Removing %s", self.string, local_path)
|
||||
|
||||
if self.data.category in ["python_script", "template"]:
|
||||
os.remove(local_path)
|
||||
await async_remove(self.hacs.hass, local_path)
|
||||
else:
|
||||
shutil.rmtree(local_path)
|
||||
await async_remove_directory(self.hacs.hass, local_path)
|
||||
|
||||
while os.path.exists(local_path):
|
||||
while await async_exists(self.hacs.hass, local_path):
|
||||
await sleep(1)
|
||||
else:
|
||||
self.logger.debug(
|
||||
|
@ -842,7 +826,8 @@ class HacsRepository:
|
|||
)
|
||||
|
||||
except (
|
||||
BaseException # lgtm [py/catch-base-exception] pylint: disable=broad-except
|
||||
# lgtm [py/catch-base-exception] pylint: disable=broad-except
|
||||
BaseException
|
||||
) as exception:
|
||||
self.logger.debug("%s Removing %s failed with %s", self.string, local_path, exception)
|
||||
return False
|
||||
|
@ -888,7 +873,7 @@ class HacsRepository:
|
|||
await self.async_pre_install()
|
||||
self.logger.info("%s Pre installation steps completed", self.string)
|
||||
|
||||
async def async_install(self) -> None:
|
||||
async def async_install(self, *, version: str | None = None, **_) -> None:
|
||||
"""Run install steps."""
|
||||
await self._async_pre_install()
|
||||
self.hacs.async_dispatch(
|
||||
|
@ -896,7 +881,7 @@ class HacsRepository:
|
|||
{"repository": self.data.full_name, "progress": 30},
|
||||
)
|
||||
self.logger.info("%s Running installation steps", self.string)
|
||||
await self.async_install_repository()
|
||||
await self.async_install_repository(version=version)
|
||||
self.hacs.async_dispatch(
|
||||
HacsDispatchEvent.REPOSITORY_DOWNLOAD_PROGRESS,
|
||||
{"repository": self.data.full_name, "progress": 90},
|
||||
|
@ -911,6 +896,13 @@ class HacsRepository:
|
|||
async def async_post_installation(self) -> None:
|
||||
"""Run post install steps."""
|
||||
|
||||
async def async_post_uninstall(self):
|
||||
"""Run post uninstall steps."""
|
||||
|
||||
async def _async_post_uninstall(self):
|
||||
"""Run post uninstall steps."""
|
||||
await self.async_post_uninstall()
|
||||
|
||||
async def _async_post_install(self) -> None:
|
||||
"""Run post install steps."""
|
||||
self.logger.info("%s Running post installation steps", self.string)
|
||||
|
@ -927,39 +919,34 @@ class HacsRepository:
|
|||
)
|
||||
self.logger.info("%s Post installation steps completed", self.string)
|
||||
|
||||
async def async_install_repository(self) -> None:
|
||||
async def async_install_repository(self, *, version: str | None = None, **_) -> None:
|
||||
"""Common installation steps of the repository."""
|
||||
persistent_directory = None
|
||||
await self.update_repository(force=True)
|
||||
await self.update_repository(force=version is None)
|
||||
if self.content.path.local is None:
|
||||
raise HacsException("repository.content.path.local is None")
|
||||
self.validate.errors.clear()
|
||||
|
||||
if not self.can_download:
|
||||
raise HacsException("The version of Home Assistant is not compatible with this version")
|
||||
|
||||
version = self.version_to_download()
|
||||
if version == self.data.default_branch:
|
||||
self.ref = version
|
||||
version_to_install = version or self.version_to_download()
|
||||
if version_to_install == self.data.default_branch:
|
||||
self.ref = version_to_install
|
||||
else:
|
||||
self.ref = f"tags/{version}"
|
||||
self.ref = f"tags/{version_to_install}"
|
||||
|
||||
self.hacs.async_dispatch(
|
||||
HacsDispatchEvent.REPOSITORY_DOWNLOAD_PROGRESS,
|
||||
{"repository": self.data.full_name, "progress": 40},
|
||||
)
|
||||
|
||||
if self.data.installed and self.data.category == "netdaemon":
|
||||
persistent_directory = BackupNetDaemon(hacs=self.hacs, repository=self)
|
||||
await self.hacs.hass.async_add_executor_job(persistent_directory.create)
|
||||
|
||||
elif self.repository_manifest.persistent_directory:
|
||||
if os.path.exists(
|
||||
f"{self.content.path.local}/{self.repository_manifest.persistent_directory}"
|
||||
if self.repository_manifest.persistent_directory:
|
||||
if await async_exists(
|
||||
self.hacs.hass,
|
||||
f"{self.content.path.local}/{self.repository_manifest.persistent_directory}",
|
||||
):
|
||||
persistent_directory = Backup(
|
||||
hacs=self.hacs,
|
||||
local_path=f"{self.content.path.local}/{self.repository_manifest.persistent_directory}",
|
||||
local_path=f"{
|
||||
self.content.path.local}/{self.repository_manifest.persistent_directory}",
|
||||
backup_path=tempfile.gettempdir() + "/hacs_persistent_directory/",
|
||||
)
|
||||
await self.hacs.hass.async_add_executor_job(persistent_directory.create)
|
||||
|
@ -970,16 +957,17 @@ class HacsRepository:
|
|||
|
||||
self.hacs.log.debug("%s Local path is set to %s", self.string, self.content.path.local)
|
||||
self.hacs.log.debug("%s Remote path is set to %s", self.string, self.content.path.remote)
|
||||
self.hacs.log.debug("%s Version to install: %s", self.string, version_to_install)
|
||||
|
||||
self.hacs.async_dispatch(
|
||||
HacsDispatchEvent.REPOSITORY_DOWNLOAD_PROGRESS,
|
||||
{"repository": self.data.full_name, "progress": 50},
|
||||
)
|
||||
|
||||
if self.repository_manifest.zip_release and version != self.data.default_branch:
|
||||
if self.repository_manifest.zip_release and self.repository_manifest.filename:
|
||||
await self.download_zip_files(self.validate)
|
||||
else:
|
||||
await self.download_content()
|
||||
await self.download_content(version_to_install)
|
||||
|
||||
self.hacs.async_dispatch(
|
||||
HacsDispatchEvent.REPOSITORY_DOWNLOAD_PROGRESS,
|
||||
|
@ -1010,10 +998,10 @@ class HacsRepository:
|
|||
self.data.installed = True
|
||||
self.data.installed_commit = self.data.last_commit
|
||||
|
||||
if version == self.data.default_branch:
|
||||
if version_to_install == self.data.default_branch:
|
||||
self.data.installed_version = None
|
||||
else:
|
||||
self.data.installed_version = version
|
||||
self.data.installed_version = version_to_install
|
||||
|
||||
async def async_get_legacy_repository_object(
|
||||
self,
|
||||
|
@ -1071,9 +1059,9 @@ class HacsRepository:
|
|||
)
|
||||
self.repository_object = repository_object
|
||||
if self.data.full_name.lower() != repository_object.full_name.lower():
|
||||
self.hacs.common.renamed_repositories[
|
||||
self.data.full_name
|
||||
] = repository_object.full_name
|
||||
self.hacs.common.renamed_repositories[self.data.full_name] = (
|
||||
repository_object.full_name
|
||||
)
|
||||
if not self.hacs.system.generator:
|
||||
raise HacsRepositoryExistException
|
||||
self.logger.error(
|
||||
|
@ -1089,7 +1077,7 @@ class HacsRepository:
|
|||
except HacsRepositoryExistException:
|
||||
raise HacsRepositoryExistException from None
|
||||
except (AIOGitHubAPIException, HacsException) as exception:
|
||||
if not self.hacs.status.startup:
|
||||
if not self.hacs.status.startup or self.hacs.system.generator:
|
||||
self.logger.error("%s %s", self.string, exception)
|
||||
if not ignore_issues:
|
||||
self.validate.errors.append("Repository does not exist.")
|
||||
|
@ -1112,15 +1100,28 @@ class HacsRepository:
|
|||
# Get releases.
|
||||
if not skip_releases:
|
||||
try:
|
||||
releases = await self.get_releases(
|
||||
prerelease=self.data.show_beta,
|
||||
returnlimit=self.hacs.configuration.release_limit,
|
||||
)
|
||||
releases = await self.get_releases(prerelease=True, returnlimit=30)
|
||||
if releases:
|
||||
self.data.prerelease = None
|
||||
for release in releases:
|
||||
if release.draft:
|
||||
continue
|
||||
elif release.prerelease:
|
||||
if self.data.prerelease is None:
|
||||
self.data.prerelease = release.tag_name
|
||||
else:
|
||||
self.data.last_version = release.tag_name
|
||||
break
|
||||
|
||||
self.data.releases = True
|
||||
self.releases.objects = releases
|
||||
self.data.published_tags = [x.tag_name for x in self.releases.objects]
|
||||
self.data.last_version = next(iter(self.data.published_tags))
|
||||
|
||||
filtered_releases = [
|
||||
release
|
||||
for release in releases
|
||||
if not release.draft and (self.data.show_beta or not release.prerelease)
|
||||
]
|
||||
self.releases.objects = filtered_releases
|
||||
self.data.published_tags = [x.tag_name for x in filtered_releases]
|
||||
|
||||
except HacsException:
|
||||
self.data.releases = False
|
||||
|
@ -1228,6 +1229,25 @@ class HacsRepository:
|
|||
files.append(FileInformation(path.download_url, path.full_path, path.filename))
|
||||
return files
|
||||
|
||||
async def release_contents(self, version: str | None = None) -> list[FileInformation] | None:
|
||||
"""Gather the contents of a release."""
|
||||
release = await self.hacs.async_github_api_method(
|
||||
method=self.hacs.githubapi.generic,
|
||||
endpoint=f"/repos/{self.data.full_name}/releases/tags/{version}",
|
||||
raise_exception=False,
|
||||
)
|
||||
if release is None:
|
||||
return None
|
||||
|
||||
return [
|
||||
FileInformation(
|
||||
url=asset.get("browser_download_url"),
|
||||
path=asset.get("name"),
|
||||
name=asset.get("name"),
|
||||
)
|
||||
for asset in release.data.get("assets", [])
|
||||
]
|
||||
|
||||
@concurrent(concurrenttasks=10)
|
||||
async def dowload_repository_content(self, content: FileInformation) -> None:
|
||||
"""Download content."""
|
||||
|
@ -1266,18 +1286,13 @@ class HacsRepository:
|
|||
self.validate.errors.append(f"[{content.name}] was not downloaded.")
|
||||
|
||||
except (
|
||||
BaseException # lgtm [py/catch-base-exception] pylint: disable=broad-except
|
||||
# lgtm [py/catch-base-exception] pylint: disable=broad-except
|
||||
BaseException
|
||||
) as exception:
|
||||
self.validate.errors.append(f"Download was not completed [{exception}]")
|
||||
|
||||
async def async_remove_entity_device(self) -> None:
|
||||
"""Remove the entity device."""
|
||||
if (
|
||||
self.hacs.configuration == ConfigurationType.YAML
|
||||
or not self.hacs.configuration.experimental
|
||||
):
|
||||
return
|
||||
|
||||
device_registry: dr.DeviceRegistry = dr.async_get(hass=self.hacs.hass)
|
||||
device = device_registry.async_get_device(identifiers={(DOMAIN, str(self.data.id))})
|
||||
|
||||
|
@ -1303,3 +1318,137 @@ class HacsRepository:
|
|||
return self.data.selected_tag
|
||||
|
||||
return self.data.default_branch or "main"
|
||||
|
||||
async def get_documentation(
|
||||
self,
|
||||
*,
|
||||
filename: str | None = None,
|
||||
version: str | None = None,
|
||||
**kwargs,
|
||||
) -> str | None:
|
||||
"""Get the documentation of the repository."""
|
||||
if filename is None:
|
||||
return None
|
||||
|
||||
if version is not None:
|
||||
target_version = version
|
||||
elif self.data.installed:
|
||||
target_version = self.data.installed_version or self.data.installed_commit
|
||||
else:
|
||||
target_version = self.data.last_version or self.data.last_commit or self.ref
|
||||
|
||||
self.logger.debug(
|
||||
"%s Getting documentation for version=%s,filename=%s",
|
||||
self.string,
|
||||
target_version,
|
||||
filename,
|
||||
)
|
||||
if target_version is None:
|
||||
return None
|
||||
|
||||
result = await self.hacs.async_download_file(
|
||||
f"https://raw.githubusercontent.com/{
|
||||
self.data.full_name}/{target_version}/{filename}",
|
||||
nolog=True,
|
||||
)
|
||||
|
||||
return (
|
||||
result.decode(encoding="utf-8")
|
||||
.replace("<svg", "<disabled")
|
||||
.replace("</svg", "</disabled")
|
||||
if result
|
||||
else None
|
||||
)
|
||||
|
||||
async def get_hacs_json(self, *, version: str, **kwargs) -> HacsManifest | None:
|
||||
"""Get the hacs.json file of the repository."""
|
||||
self.logger.debug("%s Getting hacs.json for version=%s", self.string, version)
|
||||
try:
|
||||
result = await self.hacs.async_download_file(
|
||||
f"https://raw.githubusercontent.com/{
|
||||
self.data.full_name}/{version}/hacs.json",
|
||||
nolog=True,
|
||||
)
|
||||
if result is None:
|
||||
return None
|
||||
return HacsManifest.from_dict(json_loads(result))
|
||||
except Exception: # pylint: disable=broad-except
|
||||
return None
|
||||
|
||||
async def _ensure_download_capabilities(self, ref: str | None, **kwargs: Any) -> None:
|
||||
"""Ensure that the download can be handled."""
|
||||
target_manifest: HacsManifest | None = None
|
||||
if ref is None:
|
||||
if not self.can_download:
|
||||
raise HacsException(
|
||||
f"This {
|
||||
self.data.category.value} is not available for download."
|
||||
)
|
||||
return
|
||||
|
||||
if ref == self.data.last_version:
|
||||
target_manifest = self.repository_manifest
|
||||
else:
|
||||
target_manifest = await self.get_hacs_json(version=ref)
|
||||
|
||||
if target_manifest is None:
|
||||
raise HacsException(
|
||||
f"The version {ref} for this {
|
||||
self.data.category.value} can not be used with HACS."
|
||||
)
|
||||
|
||||
if (
|
||||
target_manifest.homeassistant is not None
|
||||
and self.hacs.core.ha_version < target_manifest.homeassistant
|
||||
):
|
||||
raise HacsException(
|
||||
f"This version requires Home Assistant {
|
||||
target_manifest.homeassistant} or newer."
|
||||
)
|
||||
if target_manifest.hacs is not None and self.hacs.version < target_manifest.hacs:
|
||||
raise HacsException(f"This version requires HACS {
|
||||
target_manifest.hacs} or newer.")
|
||||
|
||||
async def async_download_repository(self, *, ref: str | None = None, **_) -> None:
|
||||
"""Download the content of a repository."""
|
||||
await self._ensure_download_capabilities(ref)
|
||||
self.logger.info("Starting download, %s", ref)
|
||||
if self.display_version_or_commit == "version":
|
||||
self.hacs.async_dispatch(
|
||||
HacsDispatchEvent.REPOSITORY_DOWNLOAD_PROGRESS,
|
||||
{"repository": self.data.full_name, "progress": 10},
|
||||
)
|
||||
if not ref:
|
||||
await self.update_repository(force=True)
|
||||
else:
|
||||
self.ref = ref
|
||||
self.data.selected_tag = ref
|
||||
self.force_branch = ref is not None
|
||||
self.hacs.async_dispatch(
|
||||
HacsDispatchEvent.REPOSITORY_DOWNLOAD_PROGRESS,
|
||||
{"repository": self.data.full_name, "progress": 20},
|
||||
)
|
||||
|
||||
try:
|
||||
await self.async_install(version=ref)
|
||||
except HacsException as exception:
|
||||
raise HacsException(
|
||||
f"Downloading {self.data.full_name} with version {
|
||||
ref or self.data.last_version or self.data.last_commit} failed with ({exception})"
|
||||
) from exception
|
||||
finally:
|
||||
self.data.selected_tag = None
|
||||
self.force_branch = False
|
||||
self.hacs.async_dispatch(
|
||||
HacsDispatchEvent.REPOSITORY_DOWNLOAD_PROGRESS,
|
||||
{"repository": self.data.full_name, "progress": False},
|
||||
)
|
||||
|
||||
async def async_get_releases(self, *, first: int = 30) -> list[GitHubReleaseModel]:
|
||||
"""Get the last x releases of a repository."""
|
||||
response = await self.hacs.async_github_api_method(
|
||||
method=self.hacs.githubapi.repos.releases.list,
|
||||
repository=self.data.full_name,
|
||||
kwargs={"per_page": 30},
|
||||
)
|
||||
return response.data
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
"""Class for integrations in HACS."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
@ -45,7 +46,7 @@ class HacsIntegrationRepository(HacsRepository):
|
|||
if self.data.first_install:
|
||||
self.pending_restart = False
|
||||
|
||||
if self.pending_restart and self.hacs.configuration.experimental:
|
||||
if self.pending_restart:
|
||||
self.logger.debug("%s Creating restart_required issue", self.string)
|
||||
async_create_issue(
|
||||
hass=self.hacs.hass,
|
||||
|
@ -60,6 +61,13 @@ class HacsIntegrationRepository(HacsRepository):
|
|||
},
|
||||
)
|
||||
|
||||
async def async_post_uninstall(self) -> None:
|
||||
"""Run post uninstall steps."""
|
||||
if self.data.config_flow:
|
||||
await self.reload_custom_components()
|
||||
else:
|
||||
self.pending_restart = True
|
||||
|
||||
async def validate_repository(self):
|
||||
"""Validate."""
|
||||
await self.common_validate()
|
||||
|
@ -78,7 +86,8 @@ class HacsIntegrationRepository(HacsRepository):
|
|||
):
|
||||
raise AddonRepositoryException()
|
||||
raise HacsException(
|
||||
f"{self.string} Repository structure for {self.ref.replace('tags/','')} is not compliant"
|
||||
f"{self.string} Repository structure for {
|
||||
self.ref.replace('tags/', '')} is not compliant"
|
||||
)
|
||||
self.content.path.remote = f"custom_components/{name}"
|
||||
|
||||
|
@ -93,7 +102,8 @@ class HacsIntegrationRepository(HacsRepository):
|
|||
|
||||
except KeyError as exception:
|
||||
self.validate.errors.append(
|
||||
f"Missing expected key '{exception}' in { RepositoryFile.MAINIFEST_JSON}"
|
||||
f"Missing expected key '{exception}' in {
|
||||
RepositoryFile.MAINIFEST_JSON}"
|
||||
)
|
||||
self.hacs.log.error(
|
||||
"Missing expected key '%s' in '%s'", exception, RepositoryFile.MAINIFEST_JSON
|
||||
|
@ -133,7 +143,8 @@ class HacsIntegrationRepository(HacsRepository):
|
|||
|
||||
except KeyError as exception:
|
||||
self.validate.errors.append(
|
||||
f"Missing expected key '{exception}' in { RepositoryFile.MAINIFEST_JSON}"
|
||||
f"Missing expected key '{exception}' in {
|
||||
RepositoryFile.MAINIFEST_JSON}"
|
||||
)
|
||||
self.hacs.log.error(
|
||||
"Missing expected key '%s' in '%s'", exception, RepositoryFile.MAINIFEST_JSON
|
||||
|
@ -142,7 +153,7 @@ class HacsIntegrationRepository(HacsRepository):
|
|||
# Set local path
|
||||
self.content.path.local = self.localpath
|
||||
|
||||
# Signal entities to refresh
|
||||
# Signal frontend to refresh
|
||||
if self.data.installed:
|
||||
self.hacs.async_dispatch(
|
||||
HacsDispatchEvent.REPOSITORY,
|
||||
|
@ -180,3 +191,27 @@ class HacsIntegrationRepository(HacsRepository):
|
|||
)
|
||||
if response:
|
||||
return json_loads(decode_content(response.data.content))
|
||||
|
||||
async def get_integration_manifest(self, *, version: str, **kwargs) -> dict[str, Any] | None:
|
||||
"""Get the content of the manifest.json file."""
|
||||
manifest_path = (
|
||||
"manifest.json"
|
||||
if self.repository_manifest.content_in_root
|
||||
else f"{self.content.path.remote}/{RepositoryFile.MAINIFEST_JSON}"
|
||||
)
|
||||
|
||||
if manifest_path not in (x.full_path for x in self.tree):
|
||||
raise HacsException(f"No {RepositoryFile.MAINIFEST_JSON} file found '{manifest_path}'")
|
||||
|
||||
self.logger.debug("%s Getting manifest.json for version=%s", self.string, version)
|
||||
try:
|
||||
result = await self.hacs.async_download_file(
|
||||
f"https://raw.githubusercontent.com/{
|
||||
self.data.full_name}/{version}/{manifest_path}",
|
||||
nolog=True,
|
||||
)
|
||||
if result is None:
|
||||
return None
|
||||
return json_loads(result)
|
||||
except Exception: # pylint: disable=broad-except
|
||||
return None
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
"""Class for plugins in HACS."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from ..enums import HacsCategory, HacsDispatchEvent
|
||||
|
@ -9,7 +11,11 @@ from ..utils.decorator import concurrent
|
|||
from ..utils.json import json_loads
|
||||
from .base import HacsRepository
|
||||
|
||||
HACSTAG_REPLACER = re.compile(r"\D+")
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from homeassistant.components.lovelace.resources import ResourceStorageCollection
|
||||
|
||||
from ..base import HacsBase
|
||||
|
||||
|
||||
|
@ -55,7 +61,12 @@ class HacsPluginRepository(HacsRepository):
|
|||
|
||||
async def async_post_installation(self):
|
||||
"""Run post installation steps."""
|
||||
self.hacs.async_setup_frontend_endpoint_plugin()
|
||||
await self.hacs.async_setup_frontend_endpoint_plugin()
|
||||
await self.update_dashboard_resources()
|
||||
|
||||
async def async_post_uninstall(self):
|
||||
"""Run post uninstall steps."""
|
||||
await self.remove_dashboard_resources()
|
||||
|
||||
@concurrent(concurrenttasks=10, backoff_time=5)
|
||||
async def update_repository(self, ignore_issues=False, force=False):
|
||||
|
@ -74,7 +85,7 @@ class HacsPluginRepository(HacsRepository):
|
|||
if self.content.path.remote == "release":
|
||||
self.content.single = True
|
||||
|
||||
# Signal entities to refresh
|
||||
# Signal frontend to refresh
|
||||
if self.data.installed:
|
||||
self.hacs.async_dispatch(
|
||||
HacsDispatchEvent.REPOSITORY,
|
||||
|
@ -99,9 +110,9 @@ class HacsPluginRepository(HacsRepository):
|
|||
|
||||
def update_filenames(self) -> None:
|
||||
"""Get the filename to target."""
|
||||
# Handler for plug requirement 3
|
||||
if self.repository_manifest.filename:
|
||||
valid_filenames = (self.repository_manifest.filename,)
|
||||
content_in_root = self.repository_manifest.content_in_root
|
||||
if specific_filename := self.repository_manifest.filename:
|
||||
valid_filenames = (specific_filename,)
|
||||
else:
|
||||
valid_filenames = (
|
||||
f"{self.data.name.replace('lovelace-', '')}.js",
|
||||
|
@ -110,7 +121,7 @@ class HacsPluginRepository(HacsRepository):
|
|||
f"{self.data.name}-bundle.js",
|
||||
)
|
||||
|
||||
if not self.repository_manifest.content_in_root:
|
||||
if not content_in_root:
|
||||
if self.releases.objects:
|
||||
release = self.releases.objects[0]
|
||||
if release.assets:
|
||||
|
@ -124,11 +135,112 @@ class HacsPluginRepository(HacsRepository):
|
|||
self.content.path.remote = "release"
|
||||
return
|
||||
|
||||
for location in ("",) if self.repository_manifest.content_in_root else ("dist", ""):
|
||||
for filename in valid_filenames:
|
||||
if f"{location+'/' if location else ''}{filename}" in [
|
||||
x.full_path for x in self.tree
|
||||
]:
|
||||
self.data.file_name = filename.split("/")[-1]
|
||||
self.content.path.remote = location
|
||||
break
|
||||
all_paths = {x.full_path for x in self.tree}
|
||||
for filename in valid_filenames:
|
||||
if filename in all_paths:
|
||||
self.data.file_name = filename
|
||||
self.content.path.remote = ""
|
||||
return
|
||||
if not content_in_root and f"dist/{filename}" in all_paths:
|
||||
self.data.file_name = filename.split("/")[-1]
|
||||
self.content.path.remote = "dist"
|
||||
return
|
||||
|
||||
def generate_dashboard_resource_hacstag(self) -> str:
|
||||
"""Get the HACS tag used by dashboard resources."""
|
||||
version = (
|
||||
self.display_installed_version
|
||||
or self.data.selected_tag
|
||||
or self.display_available_version
|
||||
)
|
||||
return f"{self.data.id}{HACSTAG_REPLACER.sub('', version)}"
|
||||
|
||||
def generate_dashboard_resource_namespace(self) -> str:
|
||||
"""Get the dashboard resource namespace."""
|
||||
return f"/hacsfiles/{self.data.full_name.split("/")[1]}"
|
||||
|
||||
def generate_dashboard_resource_url(self) -> str:
|
||||
"""Get the dashboard resource namespace."""
|
||||
filename = self.data.file_name
|
||||
if "/" in filename:
|
||||
self.logger.warning("%s have defined an invalid file name %s", self.string, filename)
|
||||
filename = filename.split("/")[-1]
|
||||
return (
|
||||
f"{self.generate_dashboard_resource_namespace()}/{filename}"
|
||||
f"?hacstag={self.generate_dashboard_resource_hacstag()}"
|
||||
)
|
||||
|
||||
def _get_resource_handler(self) -> ResourceStorageCollection | None:
|
||||
"""Get the resource handler."""
|
||||
resources: ResourceStorageCollection | None
|
||||
if not (hass_data := self.hacs.hass.data):
|
||||
self.logger.error("%s Can not access the hass data", self.string)
|
||||
return
|
||||
|
||||
if (lovelace_data := hass_data.get("lovelace")) is None:
|
||||
self.logger.warning("%s Can not access the lovelace integration data", self.string)
|
||||
return
|
||||
|
||||
if self.hacs.core.ha_version > "2025.1.99":
|
||||
# Changed to 2025.2.0
|
||||
# Changed in https://github.com/home-assistant/core/pull/136313
|
||||
resources = lovelace_data.resources
|
||||
else:
|
||||
resources = lovelace_data.get("resources")
|
||||
|
||||
if resources is None:
|
||||
self.logger.warning("%s Can not access the dashboard resources", self.string)
|
||||
return
|
||||
|
||||
if not hasattr(resources, "store") or resources.store is None:
|
||||
self.logger.info("%s YAML mode detected, can not update resources", self.string)
|
||||
return
|
||||
|
||||
if resources.store.key != "lovelace_resources" or resources.store.version != 1:
|
||||
self.logger.warning("%s Can not use the dashboard resources", self.string)
|
||||
return
|
||||
|
||||
return resources
|
||||
|
||||
async def update_dashboard_resources(self) -> None:
|
||||
"""Update dashboard resources."""
|
||||
if not (resources := self._get_resource_handler()):
|
||||
return
|
||||
|
||||
if not resources.loaded:
|
||||
await resources.async_load()
|
||||
|
||||
namespace = self.generate_dashboard_resource_namespace()
|
||||
url = self.generate_dashboard_resource_url()
|
||||
|
||||
for entry in resources.async_items():
|
||||
if (entry_url := entry["url"]).startswith(namespace):
|
||||
if entry_url != url:
|
||||
self.logger.info(
|
||||
"%s Updating existing dashboard resource from %s to %s",
|
||||
self.string,
|
||||
entry_url,
|
||||
url,
|
||||
)
|
||||
await resources.async_update_item(entry["id"], {"url": url})
|
||||
return
|
||||
|
||||
# Nothing was updated, add the resource
|
||||
self.logger.info("%s Adding dashboard resource %s", self.string, url)
|
||||
await resources.async_create_item({"res_type": "module", "url": url})
|
||||
|
||||
async def remove_dashboard_resources(self) -> None:
|
||||
"""Remove dashboard resources."""
|
||||
if not (resources := self._get_resource_handler()):
|
||||
return
|
||||
|
||||
if not resources.loaded:
|
||||
await resources.async_load()
|
||||
|
||||
namespace = self.generate_dashboard_resource_namespace()
|
||||
|
||||
for entry in resources.async_items():
|
||||
if entry["url"].startswith(namespace):
|
||||
self.logger.info("%s Removing dashboard resource %s", self.string, entry["url"])
|
||||
await resources.async_delete_item(entry["id"])
|
||||
return
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
"""Class for python_scripts in HACS."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
@ -89,7 +90,7 @@ class HacsPythonScriptRepository(HacsRepository):
|
|||
# Update name
|
||||
self.update_filenames()
|
||||
|
||||
# Signal entities to refresh
|
||||
# Signal frontend to refresh
|
||||
if self.data.installed:
|
||||
self.hacs.async_dispatch(
|
||||
HacsDispatchEvent.REPOSITORY,
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
"""Class for themes in HACS."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
|
||||
from ..enums import HacsCategory, HacsDispatchEvent
|
||||
from ..exceptions import HacsException
|
||||
from ..utils.decorator import concurrent
|
||||
|
@ -32,7 +35,7 @@ class HacsTemplateRepository(HacsRepository):
|
|||
|
||||
async def async_post_installation(self):
|
||||
"""Run post installation steps."""
|
||||
await self.hacs.hass.services.async_call("homeassistant", "reload_custom_templates", {})
|
||||
await self._reload_custom_templates()
|
||||
|
||||
async def validate_repository(self):
|
||||
"""Validate."""
|
||||
|
@ -68,6 +71,18 @@ class HacsTemplateRepository(HacsRepository):
|
|||
if self.hacs.system.action:
|
||||
await self.hacs.validation.async_run_repository_checks(self)
|
||||
|
||||
async def async_post_uninstall(self) -> None:
|
||||
"""Run post uninstall steps."""
|
||||
await self._reload_custom_templates()
|
||||
|
||||
async def _reload_custom_templates(self) -> None:
|
||||
"""Reload custom templates."""
|
||||
self.logger.debug("%s Reloading custom templates", self.string)
|
||||
try:
|
||||
await self.hacs.hass.services.async_call("homeassistant", "reload_custom_templates", {})
|
||||
except HomeAssistantError as exception:
|
||||
self.logger.exception("%s %s", self.string, exception)
|
||||
|
||||
@concurrent(concurrenttasks=10, backoff_time=5)
|
||||
async def update_repository(self, ignore_issues=False, force=False):
|
||||
"""Update."""
|
||||
|
@ -78,7 +93,7 @@ class HacsTemplateRepository(HacsRepository):
|
|||
self.data.file_name = self.repository_manifest.filename
|
||||
self.content.path.local = self.localpath
|
||||
|
||||
# Signal entities to refresh
|
||||
# Signal frontend to refresh
|
||||
if self.data.installed:
|
||||
self.hacs.async_dispatch(
|
||||
HacsDispatchEvent.REPOSITORY,
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
"""Class for themes in HACS."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
|
||||
from ..enums import HacsCategory, HacsDispatchEvent
|
||||
from ..exceptions import HacsException
|
||||
from ..utils.decorator import concurrent
|
||||
|
@ -32,12 +35,7 @@ class HacsThemeRepository(HacsRepository):
|
|||
|
||||
async def async_post_installation(self):
|
||||
"""Run post installation steps."""
|
||||
try:
|
||||
await self.hacs.hass.services.async_call("frontend", "reload_themes", {})
|
||||
except BaseException: # lgtm [py/catch-base-exception] pylint: disable=broad-except
|
||||
pass
|
||||
|
||||
self.hacs.async_setup_frontend_endpoint_themes()
|
||||
await self._reload_frontend_themes()
|
||||
|
||||
async def validate_repository(self):
|
||||
"""Validate."""
|
||||
|
@ -74,6 +72,18 @@ class HacsThemeRepository(HacsRepository):
|
|||
if self.hacs.system.action:
|
||||
await self.hacs.validation.async_run_repository_checks(self)
|
||||
|
||||
async def _reload_frontend_themes(self) -> None:
|
||||
"""Reload frontend themes."""
|
||||
self.logger.debug("%s Reloading frontend themes", self.string)
|
||||
try:
|
||||
await self.hacs.hass.services.async_call("frontend", "reload_themes", {})
|
||||
except HomeAssistantError as exception:
|
||||
self.logger.exception("%s %s", self.string, exception)
|
||||
|
||||
async def async_post_uninstall(self) -> None:
|
||||
"""Run post uninstall steps."""
|
||||
await self._reload_frontend_themes()
|
||||
|
||||
@concurrent(concurrenttasks=10, backoff_time=5)
|
||||
async def update_repository(self, ignore_issues=False, force=False):
|
||||
"""Update."""
|
||||
|
@ -88,7 +98,7 @@ class HacsThemeRepository(HacsRepository):
|
|||
self.update_filenames()
|
||||
self.content.path.local = self.localpath
|
||||
|
||||
# Signal entities to refresh
|
||||
# Signal frontend to refresh
|
||||
if self.data.installed:
|
||||
self.hacs.async_dispatch(
|
||||
HacsDispatchEvent.REPOSITORY,
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
"""Provide info to system health."""
|
||||
|
||||
from typing import Any
|
||||
|
||||
from aiogithubapi.common.const import BASE_API_URL
|
||||
from homeassistant.components import system_health
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
|
@ -17,8 +20,11 @@ def async_register(hass: HomeAssistant, register: system_health.SystemHealthRegi
|
|||
register.async_register_info(system_health_info, "/hacs")
|
||||
|
||||
|
||||
async def system_health_info(hass):
|
||||
async def system_health_info(hass: HomeAssistant) -> dict[str, Any]:
|
||||
"""Get info for the info page."""
|
||||
if DOMAIN not in hass.data:
|
||||
return {"Disabled": "HACS is not loaded, but HA still requests this information..."}
|
||||
|
||||
hacs: HacsBase = hass.data[DOMAIN]
|
||||
response = await hacs.githubapi.rate_limit()
|
||||
|
||||
|
@ -30,6 +36,9 @@ async def system_health_info(hass):
|
|||
"GitHub Web": system_health.async_check_can_reach_url(
|
||||
hass, "https://github.com/", GITHUB_STATUS
|
||||
),
|
||||
"HACS Data": system_health.async_check_can_reach_url(
|
||||
hass, "https://data-v2.hacs.xyz/data.json", CLOUDFLARE_STATUS
|
||||
),
|
||||
"GitHub API Calls Remaining": response.data.resources.core.remaining,
|
||||
"Installed Version": hacs.version,
|
||||
"Stage": hacs.stage,
|
||||
|
@ -40,9 +49,4 @@ async def system_health_info(hass):
|
|||
if hacs.system.disabled:
|
||||
data["Disabled"] = hacs.system.disabled_reason
|
||||
|
||||
if hacs.configuration.experimental:
|
||||
data["HACS Data"] = system_health.async_check_can_reach_url(
|
||||
hass, "https://data-v2.hacs.xyz/data.json", CLOUDFLARE_STATUS
|
||||
)
|
||||
|
||||
return data
|
||||
|
|
|
@ -17,8 +17,7 @@
|
|||
"acc_logs": "I know how to access Home Assistant logs",
|
||||
"acc_addons": "I know that there are no add-ons in HACS",
|
||||
"acc_untested": "I know that everything inside HACS including HACS itself is custom and untested by Home Assistant",
|
||||
"acc_disable": "I know that if I get issues with Home Assistant I should disable all my custom_components",
|
||||
"experimental": "Enable experimental features, this is what eventually will become HACS 2.0.0, if you enable it now you do not need to do anything when 2.0.0 is released"
|
||||
"acc_disable": "I know that if I get issues with Home Assistant I should disable all my custom_components"
|
||||
},
|
||||
"description": "Before you can setup HACS you need to acknowledge the following"
|
||||
},
|
||||
|
@ -31,7 +30,7 @@
|
|||
}
|
||||
},
|
||||
"progress": {
|
||||
"wait_for_device": "1. Open {url} \n2. Paste the following key to authorize HACS: \n```\n{code}\n```\n"
|
||||
"wait_for_device": "1. Open {url} \n2. Paste the following key to authorize HACS: \n```\n{code}\n```"
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
|
@ -45,11 +44,9 @@
|
|||
"data": {
|
||||
"not_in_use": "Not in use with YAML",
|
||||
"country": "Filter with country code",
|
||||
"experimental": "Enable experimental features",
|
||||
"release_limit": "Number of releases to show",
|
||||
"debug": "Enable debug",
|
||||
"appdaemon": "Enable AppDaemon apps discovery & tracking",
|
||||
"netdaemon": "[DEPRECATED] Enable NetDaemon apps discovery & tracking",
|
||||
"sidepanel_icon": "Side panel icon",
|
||||
"sidepanel_title": "Side panel title"
|
||||
}
|
||||
|
@ -71,10 +68,17 @@
|
|||
"removed": {
|
||||
"title": "Repository removed from HACS",
|
||||
"description": "Because {reason}, `{name}` has been removed from HACS. Please visit the [HACS Panel](/hacs/repository/{repositry_id}) to remove it."
|
||||
},
|
||||
"deprecated_yaml_configuration": {
|
||||
"title": "YAML configuration is deprecated",
|
||||
"description": "YAML configuration of HACS is deprecated and will be removed in version 2.0.0, there will be no automatic import of this.\nPlease remove it from your configuration, restart Home Assistant and use the UI to configure it instead."
|
||||
}
|
||||
},
|
||||
"entity": {
|
||||
"switch": {
|
||||
"pre-release": {
|
||||
"name": "Pre-release",
|
||||
"state": {
|
||||
"off": "No pre-releases",
|
||||
"on": "Pre-releases preferred"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,22 +1,28 @@
|
|||
"""Update entities for HACS."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from homeassistant.components.update import UpdateEntity
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.components.update import UpdateEntity, UpdateEntityFeature
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant, HomeAssistantError, callback
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from .base import HacsBase
|
||||
from .const import DOMAIN
|
||||
from .entity import HacsRepositoryEntity
|
||||
from .enums import HacsCategory, HacsDispatchEvent
|
||||
from .exceptions import HacsException
|
||||
|
||||
|
||||
async def async_setup_entry(hass, _config_entry, async_add_devices):
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
|
||||
) -> None:
|
||||
"""Setup update platform."""
|
||||
hacs: HacsBase = hass.data.get(DOMAIN)
|
||||
async_add_devices(
|
||||
hacs: HacsBase = hass.data[DOMAIN]
|
||||
async_add_entities(
|
||||
HacsRepositoryUpdateEntity(hacs=hacs, repository=repository)
|
||||
for repository in hacs.repositories.list_downloaded
|
||||
)
|
||||
|
@ -25,13 +31,12 @@ async def async_setup_entry(hass, _config_entry, async_add_devices):
|
|||
class HacsRepositoryUpdateEntity(HacsRepositoryEntity, UpdateEntity):
|
||||
"""Update entities for repositories downloaded with HACS."""
|
||||
|
||||
@property
|
||||
def supported_features(self) -> int | None:
|
||||
"""Return the supported features of the entity."""
|
||||
features = 4 | 16
|
||||
if self.repository.can_download:
|
||||
features = features | 1
|
||||
return features
|
||||
_attr_supported_features = (
|
||||
UpdateEntityFeature.INSTALL
|
||||
| UpdateEntityFeature.SPECIFIC_VERSION
|
||||
| UpdateEntityFeature.PROGRESS
|
||||
| UpdateEntityFeature.RELEASE_NOTES
|
||||
)
|
||||
|
||||
@property
|
||||
def name(self) -> str | None:
|
||||
|
@ -58,8 +63,6 @@ class HacsRepositoryUpdateEntity(HacsRepositoryEntity, UpdateEntity):
|
|||
@property
|
||||
def release_summary(self) -> str | None:
|
||||
"""Return the release summary."""
|
||||
if not self.repository.can_download:
|
||||
return f"<ha-alert alert-type='warning'>Requires Home Assistant {self.repository.repository_manifest.homeassistant}</ha-alert>"
|
||||
if self.repository.pending_restart:
|
||||
return "<ha-alert alert-type='error'>Restart of Home Assistant required</ha-alert>"
|
||||
return None
|
||||
|
@ -77,17 +80,18 @@ class HacsRepositoryUpdateEntity(HacsRepositoryEntity, UpdateEntity):
|
|||
|
||||
async def async_install(self, version: str | None, backup: bool, **kwargs: Any) -> None:
|
||||
"""Install an update."""
|
||||
if self.repository.display_version_or_commit == "version":
|
||||
self._update_in_progress(progress=10)
|
||||
self.repository.data.selected_tag = self.latest_version
|
||||
await self.repository.update_repository(force=True)
|
||||
self._update_in_progress(progress=20)
|
||||
await self.repository.async_install()
|
||||
self._update_in_progress(progress=False)
|
||||
to_download = version or self.latest_version
|
||||
if to_download == self.installed_version:
|
||||
raise HomeAssistantError(f"Version {self.installed_version} of {
|
||||
self.repository.data.full_name} is already downloaded")
|
||||
try:
|
||||
await self.repository.async_download_repository(ref=version or self.latest_version)
|
||||
except HacsException as exception:
|
||||
raise HomeAssistantError(exception) from exception
|
||||
|
||||
async def async_release_notes(self) -> str | None:
|
||||
"""Return the release notes."""
|
||||
if self.repository.pending_restart or not self.repository.can_download:
|
||||
if self.repository.pending_restart:
|
||||
return None
|
||||
|
||||
if self.latest_version not in self.repository.data.published_tags:
|
||||
|
@ -102,9 +106,18 @@ class HacsRepositoryUpdateEntity(HacsRepositoryEntity, UpdateEntity):
|
|||
self.repository.data.last_version = next(iter(self.repository.data.published_tags))
|
||||
|
||||
release_notes = ""
|
||||
if len(self.repository.releases.objects) > 0:
|
||||
release = self.repository.releases.objects[0]
|
||||
release_notes += release.body
|
||||
# Compile release notes from installed version up to the latest
|
||||
if self.installed_version in self.repository.data.published_tags:
|
||||
for release in self.repository.releases.objects:
|
||||
if release.tag_name == self.installed_version:
|
||||
break
|
||||
release_notes += f"# {release.tag_name}"
|
||||
if release.tag_name != release.name:
|
||||
release_notes += f" - {release.name}"
|
||||
release_notes += f"\n\n{release.body}"
|
||||
release_notes += "\n\n---\n\n"
|
||||
elif any(self.repository.releases.objects):
|
||||
release_notes += self.repository.releases.objects[0].body
|
||||
|
||||
if self.repository.pending_update:
|
||||
if self.repository.data.category == HacsCategory.INTEGRATION:
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
"""Backup."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
|
@ -27,7 +28,7 @@ class Backup:
|
|||
backup_path: str = DEFAULT_BACKUP_PATH,
|
||||
repository: HacsRepository | None = None,
|
||||
) -> None:
|
||||
"""initialize."""
|
||||
"""Initialize."""
|
||||
self.hacs = hacs
|
||||
self.repository = repository
|
||||
self.local_path = local_path or repository.content.path.local
|
||||
|
@ -107,33 +108,3 @@ class Backup:
|
|||
while os.path.exists(self.backup_path):
|
||||
sleep(0.1)
|
||||
self.hacs.log.debug("Backup dir %s cleared", self.backup_path)
|
||||
|
||||
|
||||
class BackupNetDaemon(Backup):
|
||||
"""BackupNetDaemon."""
|
||||
|
||||
def create(self) -> None:
|
||||
"""Create a backup in /tmp"""
|
||||
if not self._init_backup_dir():
|
||||
return
|
||||
|
||||
for filename in os.listdir(self.repository.content.path.local):
|
||||
if not filename.endswith(".yaml"):
|
||||
continue
|
||||
|
||||
source_file_name = f"{self.repository.content.path.local}/{filename}"
|
||||
target_file_name = f"{self.backup_path}/{filename}"
|
||||
shutil.copyfile(source_file_name, target_file_name)
|
||||
|
||||
def restore(self) -> None:
|
||||
"""Create a backup in /tmp"""
|
||||
if not os.path.exists(self.backup_path):
|
||||
return
|
||||
|
||||
for filename in os.listdir(self.backup_path):
|
||||
if not filename.endswith(".yaml"):
|
||||
continue
|
||||
|
||||
source_file_name = f"{self.backup_path}/{filename}"
|
||||
target_file_name = f"{self.repository.content.path.local}/{filename}"
|
||||
shutil.copyfile(source_file_name, target_file_name)
|
||||
|
|
|
@ -1,74 +1,9 @@
|
|||
"""HACS Configuration Schemas."""
|
||||
# pylint: disable=dangerous-default-value
|
||||
import voluptuous as vol
|
||||
|
||||
from ..const import LOCALE
|
||||
|
||||
# Configuration:
|
||||
TOKEN = "token"
|
||||
SIDEPANEL_TITLE = "sidepanel_title"
|
||||
SIDEPANEL_ICON = "sidepanel_icon"
|
||||
FRONTEND_REPO = "frontend_repo"
|
||||
FRONTEND_REPO_URL = "frontend_repo_url"
|
||||
APPDAEMON = "appdaemon"
|
||||
NETDAEMON = "netdaemon"
|
||||
|
||||
# Options:
|
||||
COUNTRY = "country"
|
||||
DEBUG = "debug"
|
||||
RELEASE_LIMIT = "release_limit"
|
||||
EXPERIMENTAL = "experimental"
|
||||
|
||||
# Config group
|
||||
PATH_OR_URL = "frontend_repo_path_or_url"
|
||||
|
||||
|
||||
def hacs_base_config_schema(config: dict = {}) -> dict:
|
||||
"""Return a shcema configuration dict for HACS."""
|
||||
if not config:
|
||||
config = {
|
||||
TOKEN: "xxxxxxxxxxxxxxxxxxxxxxxxxxx",
|
||||
}
|
||||
return {
|
||||
vol.Required(TOKEN, default=config.get(TOKEN)): str,
|
||||
}
|
||||
|
||||
|
||||
def hacs_config_option_schema(options: dict = {}) -> dict:
|
||||
"""Return a shcema for HACS configuration options."""
|
||||
if not options:
|
||||
options = {
|
||||
APPDAEMON: False,
|
||||
COUNTRY: "ALL",
|
||||
DEBUG: False,
|
||||
EXPERIMENTAL: False,
|
||||
NETDAEMON: False,
|
||||
RELEASE_LIMIT: 5,
|
||||
SIDEPANEL_ICON: "hacs:hacs",
|
||||
SIDEPANEL_TITLE: "HACS",
|
||||
FRONTEND_REPO: "",
|
||||
FRONTEND_REPO_URL: "",
|
||||
}
|
||||
return {
|
||||
vol.Optional(SIDEPANEL_TITLE, default=options.get(SIDEPANEL_TITLE)): str,
|
||||
vol.Optional(SIDEPANEL_ICON, default=options.get(SIDEPANEL_ICON)): str,
|
||||
vol.Optional(RELEASE_LIMIT, default=options.get(RELEASE_LIMIT)): int,
|
||||
vol.Optional(COUNTRY, default=options.get(COUNTRY)): vol.In(LOCALE),
|
||||
vol.Optional(APPDAEMON, default=options.get(APPDAEMON)): bool,
|
||||
vol.Optional(NETDAEMON, default=options.get(NETDAEMON)): bool,
|
||||
vol.Optional(DEBUG, default=options.get(DEBUG)): bool,
|
||||
vol.Optional(EXPERIMENTAL, default=options.get(EXPERIMENTAL)): bool,
|
||||
vol.Exclusive(FRONTEND_REPO, PATH_OR_URL): str,
|
||||
vol.Exclusive(FRONTEND_REPO_URL, PATH_OR_URL): str,
|
||||
}
|
||||
|
||||
|
||||
def hacs_config_combined() -> dict:
|
||||
"""Combine the configuration options."""
|
||||
base = hacs_base_config_schema()
|
||||
options = hacs_config_option_schema()
|
||||
|
||||
for option in options:
|
||||
base[option] = options[option]
|
||||
|
||||
return base
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
"""Data handler for HACS."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
from datetime import datetime
|
||||
from datetime import UTC, datetime
|
||||
from typing import Any
|
||||
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.util import json as json_util
|
||||
|
||||
from ..base import HacsBase
|
||||
from ..const import HACS_REPOSITORY_ID
|
||||
|
@ -47,6 +47,7 @@ EXPORTED_DOWNLOADED_REPOSITORY_DATA = EXPORTED_REPOSITORY_DATA + (
|
|||
("last_version", None),
|
||||
("manifest_name", None),
|
||||
("open_issues", 0),
|
||||
("prerelease", None),
|
||||
("published_tags", []),
|
||||
("releases", False),
|
||||
("selected_tag", None),
|
||||
|
@ -84,8 +85,7 @@ class HacsData:
|
|||
"ignored_repositories": self.hacs.common.ignored_repositories,
|
||||
},
|
||||
)
|
||||
if self.hacs.configuration.experimental:
|
||||
await self._async_store_experimental_content_and_repos()
|
||||
await self._async_store_experimental_content_and_repos()
|
||||
await self._async_store_content_and_repos()
|
||||
|
||||
async def _async_store_content_and_repos(self, _=None): # bb: ignore
|
||||
|
@ -100,7 +100,7 @@ class HacsData:
|
|||
for event in (HacsDispatchEvent.REPOSITORY, HacsDispatchEvent.CONFIG):
|
||||
self.hacs.async_dispatch(event, {})
|
||||
|
||||
async def _async_store_experimental_content_and_repos(self, _=None): # bb: ignore
|
||||
async def _async_store_experimental_content_and_repos(self, _=None):
|
||||
"""Store the main repos file and each repo that is out of date."""
|
||||
# Repositories
|
||||
self.content = {}
|
||||
|
@ -165,29 +165,16 @@ class HacsData:
|
|||
pass
|
||||
|
||||
try:
|
||||
data = (
|
||||
await async_load_from_store(
|
||||
self.hacs.hass,
|
||||
"data" if self.hacs.configuration.experimental else "repositories",
|
||||
)
|
||||
or {}
|
||||
)
|
||||
if data and self.hacs.configuration.experimental:
|
||||
repositories = await async_load_from_store(self.hacs.hass, "repositories")
|
||||
if not repositories and (data := await async_load_from_store(self.hacs.hass, "data")):
|
||||
for category, entries in data.get("repositories", {}).items():
|
||||
for repository in entries:
|
||||
repositories[repository["id"]] = {"category": category, **repository}
|
||||
else:
|
||||
repositories = (
|
||||
data or await async_load_from_store(self.hacs.hass, "repositories") or {}
|
||||
)
|
||||
|
||||
except HomeAssistantError as exception:
|
||||
self.hacs.log.error(
|
||||
"Could not read %s, restore the file from a backup - %s",
|
||||
self.hacs.hass.config.path(
|
||||
".storage/hacs.data"
|
||||
if self.hacs.configuration.experimental
|
||||
else ".storage/hacs.repositories"
|
||||
),
|
||||
self.hacs.hass.config.path(".storage/hacs.data"),
|
||||
exception,
|
||||
)
|
||||
self.hacs.disable_hacs(HacsDisabledReason.RESTORE)
|
||||
|
@ -196,13 +183,7 @@ class HacsData:
|
|||
if not hacs and not repositories:
|
||||
# Assume new install
|
||||
self.hacs.status.new = True
|
||||
if self.hacs.configuration.experimental:
|
||||
return True
|
||||
self.logger.info("<HacsData restore> Loading base repository information")
|
||||
repositories = await self.hacs.hass.async_add_executor_job(
|
||||
json_util.load_json,
|
||||
f"{self.hacs.core.config_path}/custom_components/hacs/utils/default.repositories",
|
||||
)
|
||||
return True
|
||||
|
||||
self.logger.info("<HacsData restore> Restore started")
|
||||
|
||||
|
@ -242,7 +223,8 @@ class HacsData:
|
|||
|
||||
self.logger.info("<HacsData restore> Restore done")
|
||||
except (
|
||||
BaseException # lgtm [py/catch-base-exception] pylint: disable=broad-except
|
||||
# lgtm [py/catch-base-exception] pylint: disable=broad-except
|
||||
BaseException
|
||||
) as exception:
|
||||
self.logger.critical(
|
||||
"<HacsData restore> [%s] Restore Failed!", exception, exc_info=exception
|
||||
|
@ -250,22 +232,28 @@ class HacsData:
|
|||
return False
|
||||
return True
|
||||
|
||||
async def register_unknown_repositories(self, repositories, category: str | None = None):
|
||||
async def register_unknown_repositories(
|
||||
self, repositories: dict[str, dict[str, Any]], category: str | None = None
|
||||
):
|
||||
"""Registry any unknown repositories."""
|
||||
register_tasks = [
|
||||
self.hacs.async_register_repository(
|
||||
for repo_idx, (entry, repo_data) in enumerate(repositories.items()):
|
||||
# async_register_repository is awaited in a loop
|
||||
# since its unlikely to ever suspend at startup
|
||||
if (
|
||||
entry == "0"
|
||||
or repo_data.get("category", category) is None
|
||||
or self.hacs.repositories.is_registered(repository_id=entry)
|
||||
):
|
||||
continue
|
||||
await self.hacs.async_register_repository(
|
||||
repository_full_name=repo_data["full_name"],
|
||||
category=repo_data.get("category", category),
|
||||
check=False,
|
||||
repository_id=entry,
|
||||
)
|
||||
for entry, repo_data in repositories.items()
|
||||
if entry != "0"
|
||||
and not self.hacs.repositories.is_registered(repository_id=entry)
|
||||
and repo_data.get("category", category) is not None
|
||||
]
|
||||
if register_tasks:
|
||||
await asyncio.gather(*register_tasks)
|
||||
if repo_idx % 100 == 0:
|
||||
# yield to avoid blocking the event loop
|
||||
await asyncio.sleep(0)
|
||||
|
||||
@callback
|
||||
def async_restore_repository(self, entry: str, repository_data: dict[str, Any]):
|
||||
|
@ -278,8 +266,13 @@ class HacsData:
|
|||
if not repository:
|
||||
return
|
||||
|
||||
try:
|
||||
self.hacs.repositories.set_repository_id(repository, entry)
|
||||
except ValueError as exception:
|
||||
self.logger.warning("<HacsData async_restore_repository> duplicate IDs %s", exception)
|
||||
return
|
||||
|
||||
# Restore repository attributes
|
||||
self.hacs.repositories.set_repository_id(repository, entry)
|
||||
repository.data.authors = repository_data.get("authors", [])
|
||||
repository.data.description = repository_data.get("description", "")
|
||||
repository.data.downloads = repository_data.get("downloads", 0)
|
||||
|
@ -302,18 +295,22 @@ class HacsData:
|
|||
repository.data.selected_tag = repository_data.get("selected_tag")
|
||||
repository.data.show_beta = repository_data.get("show_beta", False)
|
||||
repository.data.last_version = repository_data.get("last_version")
|
||||
repository.data.prerelease = repository_data.get("prerelease")
|
||||
repository.data.last_commit = repository_data.get("last_commit")
|
||||
repository.data.installed_version = repository_data.get("version_installed")
|
||||
repository.data.installed_commit = repository_data.get("installed_commit")
|
||||
repository.data.manifest_name = repository_data.get("manifest_name")
|
||||
|
||||
if last_fetched := repository_data.get("last_fetched"):
|
||||
repository.data.last_fetched = datetime.fromtimestamp(last_fetched)
|
||||
repository.data.last_fetched = datetime.fromtimestamp(last_fetched, UTC)
|
||||
|
||||
repository.repository_manifest = HacsManifest.from_dict(
|
||||
repository_data.get("manifest") or repository_data.get("repository_manifest") or {}
|
||||
)
|
||||
|
||||
if repository.data.prerelease == repository.data.last_version:
|
||||
repository.data.prerelease = None
|
||||
|
||||
if repository.localpath is not None and is_safe(self.hacs, repository.localpath):
|
||||
# Set local path
|
||||
repository.content.path.local = repository.localpath
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
"""Util to decode content from the github API."""
|
||||
|
||||
from base64 import b64decode
|
||||
|
||||
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
"""HACS Decorators."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
from collections.abc import Coroutine
|
||||
from functools import wraps
|
||||
from typing import TYPE_CHECKING, Any, Coroutine
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
from ..const import DEFAULT_CONCURRENT_BACKOFF_TIME, DEFAULT_CONCURRENT_TASKS
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
"""Filter functions."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
|
|
@ -1,10 +1,5 @@
|
|||
"""JSON utils."""
|
||||
|
||||
try:
|
||||
# Could be removed after 2022.06 is the min version
|
||||
# But in case Home Assistant changes, keep this try/except here...
|
||||
from homeassistant.helpers.json import json_loads
|
||||
except ImportError:
|
||||
from json import loads as json_loads
|
||||
from homeassistant.util.json import json_loads
|
||||
|
||||
__all__ = ["json_loads"]
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
"""Custom logger for HACS."""
|
||||
|
||||
import logging
|
||||
|
||||
from ..const import PACKAGE_NAME
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
"""Path utils"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from functools import lru_cache
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
|
@ -8,14 +10,32 @@ if TYPE_CHECKING:
|
|||
from ..base import HacsBase
|
||||
|
||||
|
||||
@lru_cache(maxsize=1)
|
||||
def _get_safe_paths(
|
||||
config_path: str,
|
||||
appdaemon_path: str,
|
||||
plugin_path: str,
|
||||
python_script_path: str,
|
||||
theme_path: str,
|
||||
) -> set[str]:
|
||||
"""Get safe paths."""
|
||||
return {
|
||||
Path(f"{config_path}/{appdaemon_path}").as_posix(),
|
||||
Path(f"{config_path}/{plugin_path}").as_posix(),
|
||||
Path(f"{config_path}/{python_script_path}").as_posix(),
|
||||
Path(f"{config_path}/{theme_path}").as_posix(),
|
||||
Path(f"{config_path}/custom_components/").as_posix(),
|
||||
Path(f"{config_path}/custom_templates/").as_posix(),
|
||||
}
|
||||
|
||||
|
||||
def is_safe(hacs: HacsBase, path: str | Path) -> bool:
|
||||
"""Helper to check if path is safe to remove."""
|
||||
return Path(path).as_posix() not in (
|
||||
Path(f"{hacs.core.config_path}/{hacs.configuration.appdaemon_path}").as_posix(),
|
||||
Path(f"{hacs.core.config_path}/{hacs.configuration.netdaemon_path}").as_posix(),
|
||||
Path(f"{hacs.core.config_path}/{hacs.configuration.plugin_path}").as_posix(),
|
||||
Path(f"{hacs.core.config_path}/{hacs.configuration.python_script_path}").as_posix(),
|
||||
Path(f"{hacs.core.config_path}/{hacs.configuration.theme_path}").as_posix(),
|
||||
Path(f"{hacs.core.config_path}/custom_components/").as_posix(),
|
||||
Path(f"{hacs.core.config_path}/custom_templates/").as_posix(),
|
||||
configuration = hacs.configuration
|
||||
return Path(path).as_posix() not in _get_safe_paths(
|
||||
hacs.core.config_path,
|
||||
configuration.appdaemon_path,
|
||||
configuration.plugin_path,
|
||||
configuration.python_script_path,
|
||||
configuration.theme_path,
|
||||
)
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
"""The QueueManager class."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
from collections.abc import Coroutine
|
||||
import time
|
||||
from typing import Coroutine
|
||||
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
|
@ -60,9 +61,6 @@ class QueueManager:
|
|||
for task in self.queue:
|
||||
local_queue.append(task)
|
||||
|
||||
for task in local_queue:
|
||||
self.queue.remove(task)
|
||||
|
||||
_LOGGER.debug("<QueueManager> Starting queue execution for %s tasks", len(local_queue))
|
||||
start = time.time()
|
||||
result = await asyncio.gather(*local_queue, return_exceptions=True)
|
||||
|
@ -71,6 +69,9 @@ class QueueManager:
|
|||
_LOGGER.error("<QueueManager> %s", entry)
|
||||
end = time.time() - start
|
||||
|
||||
for task in local_queue:
|
||||
self.queue.remove(task)
|
||||
|
||||
_LOGGER.debug(
|
||||
"<QueueManager> Queue execution finished for %s tasks finished in %.2f seconds",
|
||||
len(local_queue),
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
"""Regex utils"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
"""Storage handers."""
|
||||
|
||||
from homeassistant.helpers.json import JSONEncoder
|
||||
from homeassistant.helpers.storage import Store
|
||||
from homeassistant.util import json as json_util
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
"""Validation utilities."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Callable
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Any
|
||||
|
||||
from awesomeversion import AwesomeVersion
|
||||
from homeassistant.helpers.config_validation import url as url_validator
|
||||
|
@ -45,9 +48,9 @@ HACS_MANIFEST_JSON_SCHEMA = vol.Schema(
|
|||
vol.Optional("content_in_root"): bool,
|
||||
vol.Optional("country"): _country_validator,
|
||||
vol.Optional("filename"): str,
|
||||
vol.Optional("hacs"): vol.Coerce(AwesomeVersion),
|
||||
vol.Optional("hacs"): str,
|
||||
vol.Optional("hide_default_branch"): bool,
|
||||
vol.Optional("homeassistant"): vol.Coerce(AwesomeVersion),
|
||||
vol.Optional("homeassistant"): str,
|
||||
vol.Optional("persistent_directory"): str,
|
||||
vol.Optional("render_readme"): bool,
|
||||
vol.Optional("zip_release"): bool,
|
||||
|
@ -67,3 +70,146 @@ INTEGRATION_MANIFEST_JSON_SCHEMA = vol.Schema(
|
|||
},
|
||||
extra=vol.ALLOW_EXTRA,
|
||||
)
|
||||
|
||||
|
||||
def validate_repo_data(schema: dict[str, Any], extra: int) -> Callable[[Any], Any]:
|
||||
"""Return a validator for repo data.
|
||||
|
||||
This is used instead of vol.All to always try both the repo schema and
|
||||
and the validate_version validator.
|
||||
"""
|
||||
_schema = vol.Schema(schema, extra=extra)
|
||||
|
||||
def validate_repo_data(data: Any) -> Any:
|
||||
"""Validate integration repo data."""
|
||||
schema_errors: vol.MultipleInvalid | None = None
|
||||
try:
|
||||
_schema(data)
|
||||
except vol.MultipleInvalid as err:
|
||||
schema_errors = err
|
||||
try:
|
||||
validate_version(data)
|
||||
except vol.Invalid as err:
|
||||
if schema_errors:
|
||||
schema_errors.add(err)
|
||||
else:
|
||||
raise
|
||||
if schema_errors:
|
||||
raise schema_errors
|
||||
return data
|
||||
|
||||
return validate_repo_data
|
||||
|
||||
|
||||
def validate_version(data: Any) -> Any:
|
||||
"""Ensure at least one of last_commit or last_version is present."""
|
||||
if "last_commit" not in data and "last_version" not in data:
|
||||
raise vol.Invalid("Expected at least one of [`last_commit`, `last_version`], got none")
|
||||
return data
|
||||
|
||||
|
||||
V2_COMMON_DATA_JSON_SCHEMA = {
|
||||
vol.Required("description"): vol.Any(str, None),
|
||||
vol.Optional("downloads"): int,
|
||||
vol.Optional("etag_releases"): str,
|
||||
vol.Required("etag_repository"): str,
|
||||
vol.Required("full_name"): str,
|
||||
vol.Optional("last_commit"): str,
|
||||
vol.Required("last_fetched"): vol.Any(int, float),
|
||||
vol.Required("last_updated"): str,
|
||||
vol.Optional("last_version"): str,
|
||||
vol.Optional("prerelease"): str,
|
||||
vol.Required("manifest"): {
|
||||
vol.Optional("country"): vol.Any([str], False),
|
||||
vol.Optional("name"): str,
|
||||
},
|
||||
vol.Optional("open_issues"): int,
|
||||
vol.Optional("stargazers_count"): int,
|
||||
vol.Optional("topics"): [str],
|
||||
}
|
||||
|
||||
V2_INTEGRATION_DATA_JSON_SCHEMA = {
|
||||
**V2_COMMON_DATA_JSON_SCHEMA,
|
||||
vol.Required("domain"): str,
|
||||
vol.Required("manifest_name"): str,
|
||||
}
|
||||
|
||||
_V2_REPO_SCHEMAS = {
|
||||
"appdaemon": V2_COMMON_DATA_JSON_SCHEMA,
|
||||
"integration": V2_INTEGRATION_DATA_JSON_SCHEMA,
|
||||
"plugin": V2_COMMON_DATA_JSON_SCHEMA,
|
||||
"python_script": V2_COMMON_DATA_JSON_SCHEMA,
|
||||
"template": V2_COMMON_DATA_JSON_SCHEMA,
|
||||
"theme": V2_COMMON_DATA_JSON_SCHEMA,
|
||||
}
|
||||
|
||||
# Used when validating repos in the hacs integration, discards extra keys
|
||||
VALIDATE_FETCHED_V2_REPO_DATA = {
|
||||
category: validate_repo_data(schema, vol.REMOVE_EXTRA)
|
||||
for category, schema in _V2_REPO_SCHEMAS.items()
|
||||
}
|
||||
|
||||
# Used when validating repos when generating data, fails on extra keys
|
||||
VALIDATE_GENERATED_V2_REPO_DATA = {
|
||||
category: vol.Schema({str: validate_repo_data(schema, vol.PREVENT_EXTRA)})
|
||||
for category, schema in _V2_REPO_SCHEMAS.items()
|
||||
}
|
||||
|
||||
V2_CRITICAL_REPO_DATA_SCHEMA = {
|
||||
vol.Required("link"): str,
|
||||
vol.Required("reason"): str,
|
||||
vol.Required("repository"): str,
|
||||
}
|
||||
|
||||
# Used when validating critical repos in the hacs integration, discards extra keys
|
||||
VALIDATE_FETCHED_V2_CRITICAL_REPO_SCHEMA = vol.Schema(
|
||||
V2_CRITICAL_REPO_DATA_SCHEMA,
|
||||
extra=vol.REMOVE_EXTRA,
|
||||
)
|
||||
|
||||
# Used when validating critical repos when generating data, fails on extra keys
|
||||
VALIDATE_GENERATED_V2_CRITICAL_REPO_SCHEMA = vol.Schema(
|
||||
[
|
||||
vol.Schema(
|
||||
V2_CRITICAL_REPO_DATA_SCHEMA,
|
||||
extra=vol.PREVENT_EXTRA,
|
||||
)
|
||||
]
|
||||
)
|
||||
|
||||
V2_REMOVED_REPO_DATA_SCHEMA = {
|
||||
vol.Optional("link"): str,
|
||||
vol.Optional("reason"): str,
|
||||
vol.Required("removal_type"): vol.In(
|
||||
[
|
||||
"Integration is missing a version, and is abandoned.",
|
||||
"Remove",
|
||||
"archived",
|
||||
"blacklist",
|
||||
"critical",
|
||||
"deprecated",
|
||||
"removal",
|
||||
"remove",
|
||||
"removed",
|
||||
"replaced",
|
||||
"repository",
|
||||
]
|
||||
),
|
||||
vol.Required("repository"): str,
|
||||
}
|
||||
|
||||
# Used when validating removed repos in the hacs integration, discards extra keys
|
||||
VALIDATE_FETCHED_V2_REMOVED_REPO_SCHEMA = vol.Schema(
|
||||
V2_REMOVED_REPO_DATA_SCHEMA,
|
||||
extra=vol.REMOVE_EXTRA,
|
||||
)
|
||||
|
||||
# Used when validating removed repos when generating data, fails on extra keys
|
||||
VALIDATE_GENERATED_V2_REMOVED_REPO_SCHEMA = vol.Schema(
|
||||
[
|
||||
vol.Schema(
|
||||
V2_REMOVED_REPO_DATA_SCHEMA,
|
||||
extra=vol.PREVENT_EXTRA,
|
||||
)
|
||||
]
|
||||
)
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
"""Version utils."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from functools import lru_cache
|
||||
|
|
|
@ -1,7 +1,37 @@
|
|||
"""Workarounds for issues that should not be fixed."""
|
||||
"""Workarounds."""
|
||||
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
DOMAIN_OVERRIDES = {
|
||||
# https://github.com/hacs/integration/issues/2465
|
||||
"custom-components/sensor.custom_aftership": "custom_aftership"
|
||||
}
|
||||
|
||||
|
||||
try:
|
||||
from homeassistant.components.http import StaticPathConfig
|
||||
|
||||
async def async_register_static_path(
|
||||
hass: HomeAssistant,
|
||||
url_path: str,
|
||||
path: str,
|
||||
cache_headers: bool = True,
|
||||
) -> None:
|
||||
"""Register a static path with the HTTP component."""
|
||||
await hass.http.async_register_static_paths(
|
||||
[StaticPathConfig(url_path, path, cache_headers)]
|
||||
)
|
||||
except ImportError:
|
||||
|
||||
async def async_register_static_path(
|
||||
hass: HomeAssistant,
|
||||
url_path: str,
|
||||
path: str,
|
||||
cache_headers: bool = True,
|
||||
) -> None:
|
||||
"""Register a static path with the HTTP component.
|
||||
|
||||
Legacy: Can be removed when min version is 2024.7
|
||||
https://developers.home-assistant.io/blog/2024/06/18/async_register_static_paths/
|
||||
"""
|
||||
hass.http.register_static_path(url_path, path, cache_headers)
|
||||
|
|
|
@ -1,8 +1,12 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from ..repositories.base import HacsRepository
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from .base import ActionValidationBase, ValidationException
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ..repositories.base import HacsRepository
|
||||
|
||||
|
||||
async def async_setup_validator(repository: HacsRepository) -> Validator:
|
||||
"""Set up this validator."""
|
||||
|
@ -15,7 +19,7 @@ class Validator(ActionValidationBase):
|
|||
more_info = "https://hacs.xyz/docs/publish/include#check-archived"
|
||||
allow_fork = False
|
||||
|
||||
async def async_validate(self):
|
||||
async def async_validate(self) -> None:
|
||||
"""Validate the repository."""
|
||||
if self.repository.data.archived:
|
||||
raise ValidationException("The repository is archived")
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
"""Base class for validation."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
from ..enums import HacsCategory
|
||||
from ..exceptions import HacsException
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ..enums import HacsCategory
|
||||
from ..repositories.base import HacsRepository
|
||||
|
||||
|
||||
|
@ -17,7 +18,7 @@ class ValidationException(HacsException):
|
|||
class ActionValidationBase:
|
||||
"""Base class for action validation."""
|
||||
|
||||
categories: list[HacsCategory] = []
|
||||
categories: tuple[HacsCategory, ...] = ()
|
||||
allow_fork: bool = True
|
||||
more_info: str = "https://hacs.xyz/docs/publish/action"
|
||||
|
||||
|
@ -34,7 +35,7 @@ class ActionValidationBase:
|
|||
async def async_validate(self) -> None:
|
||||
"""Validate the repository."""
|
||||
|
||||
async def execute_validation(self, *_, **__) -> None:
|
||||
async def execute_validation(self, *_: Any, **__: Any) -> None:
|
||||
"""Execute the task defined in subclass."""
|
||||
self.failed = False
|
||||
|
||||
|
|
|
@ -1,10 +1,14 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from custom_components.hacs.enums import HacsCategory
|
||||
|
||||
from ..repositories.base import HacsRepository
|
||||
from .base import ActionValidationBase, ValidationException
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ..repositories.base import HacsRepository
|
||||
|
||||
URL = "https://brands.home-assistant.io/domains.json"
|
||||
|
||||
|
||||
|
@ -17,9 +21,9 @@ class Validator(ActionValidationBase):
|
|||
"""Validate the repository."""
|
||||
|
||||
more_info = "https://hacs.xyz/docs/publish/include#check-brands"
|
||||
categories = [HacsCategory.INTEGRATION]
|
||||
categories = (HacsCategory.INTEGRATION,)
|
||||
|
||||
async def async_validate(self):
|
||||
async def async_validate(self) -> None:
|
||||
"""Validate the repository."""
|
||||
|
||||
response = await self.hacs.session.get(URL)
|
||||
|
|
|
@ -1,8 +1,12 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from ..repositories.base import HacsRepository
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from .base import ActionValidationBase, ValidationException
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ..repositories.base import HacsRepository
|
||||
|
||||
|
||||
async def async_setup_validator(repository: HacsRepository) -> Validator:
|
||||
"""Set up this validator."""
|
||||
|
@ -15,7 +19,7 @@ class Validator(ActionValidationBase):
|
|||
more_info = "https://hacs.xyz/docs/publish/include#check-repository"
|
||||
allow_fork = False
|
||||
|
||||
async def async_validate(self):
|
||||
async def async_validate(self) -> None:
|
||||
"""Validate the repository."""
|
||||
if not self.repository.data.description:
|
||||
raise ValidationException("The repository has no description")
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from voluptuous.error import Invalid
|
||||
from voluptuous.humanize import humanize_error
|
||||
|
||||
from ..enums import RepositoryFile
|
||||
from ..repositories.base import HacsRepository
|
||||
from ..enums import HacsCategory, RepositoryFile
|
||||
from ..repositories.base import HacsManifest, HacsRepository
|
||||
from ..utils.validate import HACS_MANIFEST_JSON_SCHEMA
|
||||
from .base import ActionValidationBase, ValidationException
|
||||
|
||||
|
@ -18,13 +19,17 @@ class Validator(ActionValidationBase):
|
|||
|
||||
more_info = "https://hacs.xyz/docs/publish/include#check-hacs-manifest"
|
||||
|
||||
async def async_validate(self):
|
||||
async def async_validate(self) -> None:
|
||||
"""Validate the repository."""
|
||||
if RepositoryFile.HACS_JSON not in [x.filename for x in self.repository.tree]:
|
||||
raise ValidationException(f"The repository has no '{RepositoryFile.HACS_JSON}' file")
|
||||
|
||||
content = await self.repository.async_get_hacs_json(self.repository.ref)
|
||||
try:
|
||||
HACS_MANIFEST_JSON_SCHEMA(content)
|
||||
hacsjson = HacsManifest.from_dict(HACS_MANIFEST_JSON_SCHEMA(content))
|
||||
except Invalid as exception:
|
||||
raise ValidationException(exception) from exception
|
||||
raise ValidationException(humanize_error(content, exception)) from exception
|
||||
|
||||
if self.repository.data.category == HacsCategory.INTEGRATION:
|
||||
if hacsjson.zip_release and not hacsjson.filename:
|
||||
raise ValidationException("zip_release is True, but filename is not set")
|
||||
|
|
|
@ -1,9 +1,13 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from ..enums import HacsCategory
|
||||
from ..repositories.base import HacsRepository
|
||||
from .base import ActionValidationBase, ValidationException
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ..repositories.base import HacsRepository
|
||||
|
||||
IGNORED = ["-shield", "img.shields.io", "buymeacoffee.com"]
|
||||
|
||||
|
||||
|
@ -15,12 +19,12 @@ async def async_setup_validator(repository: HacsRepository) -> Validator:
|
|||
class Validator(ActionValidationBase):
|
||||
"""Validate the repository."""
|
||||
|
||||
categories = [HacsCategory.PLUGIN, HacsCategory.THEME]
|
||||
categories = (HacsCategory.PLUGIN, HacsCategory.THEME)
|
||||
more_info = "https://hacs.xyz/docs/publish/include#check-images"
|
||||
|
||||
async def async_validate(self):
|
||||
async def async_validate(self) -> None:
|
||||
"""Validate the repository."""
|
||||
info = await self.repository.async_get_info_file_contents()
|
||||
info = await self.repository.async_get_info_file_contents(version=self.repository.ref)
|
||||
for line in info.split("\n"):
|
||||
if "<img" in line or "![" in line:
|
||||
if [ignore for ignore in IGNORED if ignore in line]:
|
||||
|
|
|
@ -1,8 +1,12 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from ..repositories.base import HacsRepository
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from .base import ActionValidationBase, ValidationException
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ..repositories.base import HacsRepository
|
||||
|
||||
|
||||
async def async_setup_validator(repository: HacsRepository) -> Validator:
|
||||
"""Set up this validator."""
|
||||
|
@ -14,7 +18,7 @@ class Validator(ActionValidationBase):
|
|||
|
||||
more_info = "https://hacs.xyz/docs/publish/include#check-info"
|
||||
|
||||
async def async_validate(self):
|
||||
async def async_validate(self) -> None:
|
||||
"""Validate the repository."""
|
||||
filenames = [x.filename.lower() for x in self.repository.tree]
|
||||
if "readme" in filenames:
|
||||
|
|
|
@ -1,13 +1,17 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from voluptuous.error import Invalid
|
||||
|
||||
from ..enums import HacsCategory, RepositoryFile
|
||||
from ..repositories.base import HacsRepository
|
||||
from ..repositories.integration import HacsIntegrationRepository
|
||||
from ..utils.validate import INTEGRATION_MANIFEST_JSON_SCHEMA
|
||||
from .base import ActionValidationBase, ValidationException
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ..repositories.base import HacsRepository
|
||||
from ..repositories.integration import HacsIntegrationRepository
|
||||
|
||||
|
||||
async def async_setup_validator(repository: HacsRepository) -> Validator:
|
||||
"""Set up this validator."""
|
||||
|
@ -19,16 +23,16 @@ class Validator(ActionValidationBase):
|
|||
|
||||
repository: HacsIntegrationRepository
|
||||
more_info = "https://hacs.xyz/docs/publish/include#check-manifest"
|
||||
categories = [HacsCategory.INTEGRATION]
|
||||
categories = (HacsCategory.INTEGRATION,)
|
||||
|
||||
async def async_validate(self):
|
||||
async def async_validate(self) -> None:
|
||||
"""Validate the repository."""
|
||||
if RepositoryFile.MAINIFEST_JSON not in [x.filename for x in self.repository.tree]:
|
||||
raise ValidationException(
|
||||
f"The repository has no '{RepositoryFile.MAINIFEST_JSON}' file"
|
||||
)
|
||||
|
||||
content = await self.repository.async_get_integration_manifest(self.repository.ref)
|
||||
content = await self.repository.get_integration_manifest(version=self.repository.ref)
|
||||
try:
|
||||
INTEGRATION_MANIFEST_JSON_SCHEMA(content)
|
||||
except Invalid as exception:
|
||||
|
|
|
@ -1,8 +1,12 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from ..repositories.base import HacsRepository
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from .base import ActionValidationBase, ValidationException
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ..repositories.base import HacsRepository
|
||||
|
||||
|
||||
async def async_setup_validator(repository: HacsRepository) -> Validator:
|
||||
"""Set up this validator."""
|
||||
|
@ -15,7 +19,7 @@ class Validator(ActionValidationBase):
|
|||
more_info = "https://hacs.xyz/docs/publish/include#check-repository"
|
||||
allow_fork = False
|
||||
|
||||
async def async_validate(self):
|
||||
async def async_validate(self) -> None:
|
||||
"""Validate the repository."""
|
||||
if not self.repository.data.has_issues:
|
||||
raise ValidationException("The repository does not have issues enabled")
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
"""Hacs validation manager."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
|
@ -7,13 +8,12 @@ import os
|
|||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from ..repositories.base import HacsRepository
|
||||
from .base import ActionValidationBase
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from ..base import HacsBase
|
||||
from ..repositories.base import HacsRepository
|
||||
from .base import ActionValidationBase
|
||||
|
||||
|
||||
class ValidationManager:
|
||||
|
@ -23,16 +23,16 @@ class ValidationManager:
|
|||
"""Initialize the setup manager class."""
|
||||
self.hacs = hacs
|
||||
self.hass = hass
|
||||
self._validatiors: dict[str, ActionValidationBase] = {}
|
||||
self._validators: dict[str, ActionValidationBase] = {}
|
||||
|
||||
@property
|
||||
def validatiors(self) -> list[ActionValidationBase]:
|
||||
def validators(self) -> list[ActionValidationBase]:
|
||||
"""Return all list of all tasks."""
|
||||
return list(self._validatiors.values())
|
||||
return list(self._validators.values())
|
||||
|
||||
async def async_load(self, repository: HacsRepository) -> None:
|
||||
"""Load all tasks."""
|
||||
self._validatiors = {}
|
||||
self._validators = {}
|
||||
validator_files = Path(__file__).parent
|
||||
validator_modules = (
|
||||
module.stem
|
||||
|
@ -40,10 +40,10 @@ class ValidationManager:
|
|||
if module.name not in ("base.py", "__init__.py", "manager.py")
|
||||
)
|
||||
|
||||
async def _load_module(module: str):
|
||||
async def _load_module(module: str) -> None:
|
||||
task_module = import_module(f"{__package__}.{module}")
|
||||
if task := await task_module.async_setup_validator(repository=repository):
|
||||
self._validatiors[task.slug] = task
|
||||
self._validators[task.slug] = task
|
||||
|
||||
await asyncio.gather(*[_load_module(task) for task in validator_modules])
|
||||
|
||||
|
@ -59,9 +59,9 @@ class ValidationManager:
|
|||
and os.getenv("GITHUB_REPOSITORY") != repository.data.full_name
|
||||
)
|
||||
|
||||
validatiors = [
|
||||
validators = [
|
||||
validator
|
||||
for validator in self.validatiors or []
|
||||
for validator in self.validators or []
|
||||
if (
|
||||
(not validator.categories or repository.data.category in validator.categories)
|
||||
and validator.slug not in os.getenv("INPUT_IGNORE", "").split(" ")
|
||||
|
@ -69,10 +69,10 @@ class ValidationManager:
|
|||
)
|
||||
]
|
||||
|
||||
await asyncio.gather(*[validator.execute_validation() for validator in validatiors])
|
||||
await asyncio.gather(*[validator.execute_validation() for validator in validators])
|
||||
|
||||
total = len(validatiors)
|
||||
failed = len([x for x in validatiors if x.failed])
|
||||
total = len(validators)
|
||||
failed = len([x for x in validators if x.failed])
|
||||
|
||||
if failed != 0:
|
||||
repository.logger.error("%s %s/%s checks failed", repository.string, failed, total)
|
||||
|
|
|
@ -1,8 +1,12 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from ..repositories.base import HacsRepository
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from .base import ActionValidationBase, ValidationException
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ..repositories.base import HacsRepository
|
||||
|
||||
|
||||
async def async_setup_validator(repository: HacsRepository) -> Validator:
|
||||
"""Set up this validator."""
|
||||
|
@ -15,7 +19,7 @@ class Validator(ActionValidationBase):
|
|||
more_info = "https://hacs.xyz/docs/publish/include#check-repository"
|
||||
allow_fork = False
|
||||
|
||||
async def async_validate(self):
|
||||
async def async_validate(self) -> None:
|
||||
"""Validate the repository."""
|
||||
if not self.repository.data.topics:
|
||||
raise ValidationException("The repository has no valid topics")
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
"""Register_commands."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
@ -24,6 +25,7 @@ from .repository import (
|
|||
hacs_repository_info,
|
||||
hacs_repository_refresh,
|
||||
hacs_repository_release_notes,
|
||||
hacs_repository_releases,
|
||||
hacs_repository_remove,
|
||||
hacs_repository_state,
|
||||
hacs_repository_version,
|
||||
|
@ -57,6 +59,7 @@ def async_register_websocket_commands(hass: HomeAssistant) -> None:
|
|||
websocket_api.async_register_command(hass, hacs_repositories_clear_new)
|
||||
websocket_api.async_register_command(hass, hacs_repositories_removed)
|
||||
websocket_api.async_register_command(hass, hacs_repositories_remove)
|
||||
websocket_api.async_register_command(hass, hacs_repository_releases)
|
||||
|
||||
|
||||
@websocket_api.websocket_command(
|
||||
|
@ -75,7 +78,7 @@ async def hacs_subscribe(
|
|||
"""Handle websocket subscriptions."""
|
||||
|
||||
@callback
|
||||
def forward_messages(data: dict | None = None):
|
||||
def forward_messages(data: dict | None = None) -> None:
|
||||
"""Forward events to websocket."""
|
||||
connection.send_message(websocket_api.event_message(msg["id"], data))
|
||||
|
||||
|
@ -110,7 +113,6 @@ async def hacs_info(
|
|||
"debug": hacs.configuration.debug,
|
||||
"dev": hacs.configuration.dev,
|
||||
"disabled_reason": hacs.system.disabled_reason,
|
||||
"experimental": hacs.configuration.experimental,
|
||||
"has_pending_tasks": hacs.queue.has_pending_tasks,
|
||||
"lovelace_mode": hacs.core.lovelace_mode,
|
||||
"stage": hacs.stage,
|
||||
|
|
|
@ -1,15 +1,18 @@
|
|||
"""Register info websocket commands."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
from homeassistant.components import websocket_api
|
||||
from homeassistant.core import HomeAssistant
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
import voluptuous as vol
|
||||
|
||||
from ..utils.store import async_load_from_store, async_save_to_store
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
|
||||
@websocket_api.websocket_command(
|
||||
{
|
||||
|
@ -22,7 +25,7 @@ async def hacs_critical_list(
|
|||
hass: HomeAssistant,
|
||||
connection: websocket_api.ActiveConnection,
|
||||
msg: dict[str, Any],
|
||||
):
|
||||
) -> None:
|
||||
"""List critical repositories."""
|
||||
connection.send_message(
|
||||
websocket_api.result_message(
|
||||
|
@ -44,7 +47,7 @@ async def hacs_critical_acknowledge(
|
|||
hass: HomeAssistant,
|
||||
connection: websocket_api.ActiveConnection,
|
||||
msg: dict[str, Any],
|
||||
):
|
||||
) -> None:
|
||||
"""Acknowledge critical repository."""
|
||||
repository = msg["repository"]
|
||||
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
"""Register info websocket commands."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
from homeassistant.components import websocket_api
|
||||
from homeassistant.core import HomeAssistant
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
import voluptuous as vol
|
||||
|
||||
|
@ -15,6 +15,8 @@ from ..const import DOMAIN
|
|||
from ..enums import HacsDispatchEvent
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from ..base import HacsBase
|
||||
|
||||
|
||||
|
@ -30,7 +32,7 @@ async def hacs_repositories_list(
|
|||
hass: HomeAssistant,
|
||||
connection: websocket_api.ActiveConnection,
|
||||
msg: dict[str, Any],
|
||||
):
|
||||
) -> None:
|
||||
"""List repositories."""
|
||||
hacs: HacsBase = hass.data.get(DOMAIN)
|
||||
connection.send_message(
|
||||
|
@ -68,7 +70,7 @@ async def hacs_repositories_list(
|
|||
for repo in hacs.repositories.list_all
|
||||
if repo.data.category in msg.get("categories", hacs.common.categories)
|
||||
and not repo.ignored_by_country_configuration
|
||||
and (not hacs.configuration.experimental or repo.data.last_fetched)
|
||||
and repo.data.last_fetched
|
||||
],
|
||||
)
|
||||
)
|
||||
|
@ -88,7 +90,7 @@ async def hacs_repositories_clear_new(
|
|||
connection: websocket_api.ActiveConnection,
|
||||
msg: dict[str, Any],
|
||||
) -> None:
|
||||
"""Clear new repositories for spesific categories."""
|
||||
"""Clear new repositories for specific categories."""
|
||||
hacs: HacsBase = hass.data.get(DOMAIN)
|
||||
|
||||
if repo := msg.get("repository"):
|
||||
|
@ -119,7 +121,7 @@ async def hacs_repositories_removed(
|
|||
hass: HomeAssistant,
|
||||
connection: websocket_api.ActiveConnection,
|
||||
msg: dict[str, Any],
|
||||
):
|
||||
) -> None:
|
||||
"""Get information about removed repositories."""
|
||||
hacs: HacsBase = hass.data.get(DOMAIN)
|
||||
content = []
|
||||
|
@ -142,7 +144,7 @@ async def hacs_repositories_add(
|
|||
hass: HomeAssistant,
|
||||
connection: websocket_api.ActiveConnection,
|
||||
msg: dict[str, Any],
|
||||
):
|
||||
) -> None:
|
||||
"""Add custom repositoriy."""
|
||||
hacs: HacsBase = hass.data.get(DOMAIN)
|
||||
repository = regex.extract_repository_from_url(msg["repository"])
|
||||
|
@ -203,7 +205,7 @@ async def hacs_repositories_remove(
|
|||
hass: HomeAssistant,
|
||||
connection: websocket_api.ActiveConnection,
|
||||
msg: dict[str, Any],
|
||||
):
|
||||
) -> None:
|
||||
"""Remove custom repositoriy."""
|
||||
hacs: HacsBase = hass.data.get(DOMAIN)
|
||||
repository = hacs.repositories.get_by_id(msg["repository"])
|
||||
|
|
|
@ -1,18 +1,21 @@
|
|||
"""Register info websocket commands."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
from homeassistant.components import websocket_api
|
||||
from homeassistant.core import HomeAssistant
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
import voluptuous as vol
|
||||
|
||||
from ..const import DOMAIN
|
||||
from ..enums import HacsDispatchEvent
|
||||
from ..exceptions import HacsException
|
||||
from ..utils.version import version_left_higher_then_right
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from ..base import HacsBase
|
||||
|
||||
|
||||
|
@ -107,7 +110,7 @@ async def hacs_repository_ignore(
|
|||
hass: HomeAssistant,
|
||||
connection: websocket_api.ActiveConnection,
|
||||
msg: dict[str, Any],
|
||||
):
|
||||
) -> None:
|
||||
"""Ignore a repository."""
|
||||
hacs: HacsBase = hass.data.get(DOMAIN)
|
||||
repository_id = msg["repository"]
|
||||
|
@ -140,7 +143,7 @@ async def hacs_repository_state(
|
|||
hass: HomeAssistant,
|
||||
connection: websocket_api.ActiveConnection,
|
||||
msg: dict[str, Any],
|
||||
):
|
||||
) -> None:
|
||||
"""Set the state of a repository"""
|
||||
hacs: HacsBase = hass.data.get(DOMAIN)
|
||||
repository = hacs.repositories.get_by_id(msg["repository"])
|
||||
|
@ -164,7 +167,7 @@ async def hacs_repository_version(
|
|||
hass: HomeAssistant,
|
||||
connection: websocket_api.ActiveConnection,
|
||||
msg: dict[str, Any],
|
||||
):
|
||||
) -> None:
|
||||
"""Set the version of a repository"""
|
||||
hacs: HacsBase = hass.data.get(DOMAIN)
|
||||
repository = hacs.repositories.get_by_id(msg["repository"])
|
||||
|
@ -194,7 +197,7 @@ async def hacs_repository_beta(
|
|||
hass: HomeAssistant,
|
||||
connection: websocket_api.ActiveConnection,
|
||||
msg: dict[str, Any],
|
||||
):
|
||||
) -> None:
|
||||
"""Show or hide beta versions of a repository"""
|
||||
hacs: HacsBase = hass.data.get(DOMAIN)
|
||||
repository = hacs.repositories.get_by_id(msg["repository"])
|
||||
|
@ -221,24 +224,23 @@ async def hacs_repository_download(
|
|||
hass: HomeAssistant,
|
||||
connection: websocket_api.ActiveConnection,
|
||||
msg: dict[str, Any],
|
||||
):
|
||||
) -> None:
|
||||
"""Set the version of a repository"""
|
||||
hacs: HacsBase = hass.data.get(DOMAIN)
|
||||
repository = hacs.repositories.get_by_id(msg["repository"])
|
||||
|
||||
was_installed = repository.data.installed
|
||||
if version := msg.get("version"):
|
||||
repository.data.selected_tag = version
|
||||
await repository.update_repository(force=True)
|
||||
try:
|
||||
was_installed = repository.data.installed
|
||||
await repository.async_download_repository(ref=msg.get("version"))
|
||||
if not was_installed:
|
||||
hacs.async_dispatch(HacsDispatchEvent.RELOAD, {"force": True})
|
||||
await hacs.async_recreate_entities()
|
||||
|
||||
await repository.async_install()
|
||||
repository.state = None
|
||||
if not was_installed:
|
||||
hacs.async_dispatch(HacsDispatchEvent.RELOAD, {"force": True})
|
||||
await hacs.async_recreate_entities()
|
||||
|
||||
await hacs.data.async_write()
|
||||
connection.send_message(websocket_api.result_message(msg["id"], {}))
|
||||
await hacs.data.async_write()
|
||||
connection.send_message(websocket_api.result_message(msg["id"], {}))
|
||||
except HacsException as exception:
|
||||
repository.logger.error("%s %s", repository.string, exception)
|
||||
connection.send_error(msg["id"], "error", str(exception))
|
||||
|
||||
|
||||
@websocket_api.websocket_command(
|
||||
|
@ -253,7 +255,7 @@ async def hacs_repository_remove(
|
|||
hass: HomeAssistant,
|
||||
connection: websocket_api.ActiveConnection,
|
||||
msg: dict[str, Any],
|
||||
):
|
||||
) -> None:
|
||||
"""Remove a repository."""
|
||||
hacs: HacsBase = hass.data.get(DOMAIN)
|
||||
repository = hacs.repositories.get_by_id(msg["repository"])
|
||||
|
@ -281,13 +283,15 @@ async def hacs_repository_refresh(
|
|||
hass: HomeAssistant,
|
||||
connection: websocket_api.ActiveConnection,
|
||||
msg: dict[str, Any],
|
||||
):
|
||||
) -> None:
|
||||
"""Refresh a repository."""
|
||||
hacs: HacsBase = hass.data.get(DOMAIN)
|
||||
repository = hacs.repositories.get_by_id(msg["repository"])
|
||||
|
||||
await repository.update_repository(ignore_issues=True, force=True)
|
||||
await hacs.data.async_write()
|
||||
# Update state of update entity
|
||||
hacs.coordinators[repository.data.category].async_update_listeners()
|
||||
|
||||
connection.send_message(websocket_api.result_message(msg["id"], {}))
|
||||
|
||||
|
@ -304,7 +308,7 @@ async def hacs_repository_release_notes(
|
|||
hass: HomeAssistant,
|
||||
connection: websocket_api.ActiveConnection,
|
||||
msg: dict[str, Any],
|
||||
):
|
||||
) -> None:
|
||||
"""Return release notes."""
|
||||
hacs: HacsBase = hass.data.get(DOMAIN)
|
||||
repository = hacs.repositories.get_by_id(msg["repository"])
|
||||
|
@ -324,3 +328,42 @@ async def hacs_repository_release_notes(
|
|||
],
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@websocket_api.websocket_command(
|
||||
{
|
||||
vol.Required("type"): "hacs/repository/releases",
|
||||
vol.Required("repository_id"): cv.string,
|
||||
}
|
||||
)
|
||||
@websocket_api.require_admin
|
||||
@websocket_api.async_response
|
||||
async def hacs_repository_releases(
|
||||
hass: HomeAssistant,
|
||||
connection: websocket_api.ActiveConnection,
|
||||
msg: dict[str, Any],
|
||||
) -> None:
|
||||
"""Return releases."""
|
||||
hacs: HacsBase = hass.data.get(DOMAIN)
|
||||
repository = hacs.repositories.get_by_id(msg["repository_id"])
|
||||
try:
|
||||
releases = await repository.async_get_releases()
|
||||
except Exception as exception:
|
||||
hacs.log.exception(exception)
|
||||
connection.send_error(msg["id"], "unknown", str(exception))
|
||||
return
|
||||
|
||||
connection.send_message(
|
||||
websocket_api.result_message(
|
||||
msg["id"],
|
||||
[
|
||||
{
|
||||
"name": release.name,
|
||||
"tag": release.tag_name,
|
||||
"published_at": release.published_at,
|
||||
"prerelease": release.prerelease,
|
||||
}
|
||||
for release in releases
|
||||
],
|
||||
)
|
||||
)
|
||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"domain": "pid_controller",
|
||||
"name": "PID Controller",
|
||||
"version": "v0.0.0",
|
||||
"version": "v1.1.5",
|
||||
"documentation": "https://github.com/soloam/ha-pid-controller/",
|
||||
"issue_tracker": "https://github.com/soloam/ha-pid-controller/issues",
|
||||
"dependencies": [],
|
||||
|
|
|
@ -18,7 +18,7 @@ class PIDController:
|
|||
|
||||
WARMUP_STAGE = 3
|
||||
|
||||
def __init__(self, P=0.2, I=0.0, D=0.0, logger=None):
|
||||
def __init__(self, P=2.7, I=37.6, D=0.0, logger=None):
|
||||
self._logger = logger
|
||||
|
||||
self._set_point = 0
|
||||
|
@ -92,7 +92,7 @@ class PIDController:
|
|||
self._i_term = self.clamp_value(self._i_term, self._windup)
|
||||
|
||||
# Calculate D
|
||||
self._d_term = self._kd * delta_error / delta_time
|
||||
self._d_term = self._kd * delta_error
|
||||
|
||||
# Compute final output
|
||||
self._output = self._p_term + self._i_term + self._d_term
|
||||
|
|
|
@ -4,7 +4,9 @@ Connect two Home Assistant instances via the Websocket API.
|
|||
For more details about this component, please refer to the documentation at
|
||||
https://home-assistant.io/components/remote_homeassistant/
|
||||
"""
|
||||
from __future__ import annotations
|
||||
import asyncio
|
||||
from typing import Optional
|
||||
import copy
|
||||
import fnmatch
|
||||
import inspect
|
||||
|
@ -13,10 +15,15 @@ import re
|
|||
from contextlib import suppress
|
||||
|
||||
import aiohttp
|
||||
from aiohttp import ClientWebSocketResponse
|
||||
import homeassistant.components.websocket_api.auth as api
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
import voluptuous as vol
|
||||
from homeassistant.config import DATA_CUSTOMIZE
|
||||
try:
|
||||
from homeassistant.core_config import DATA_CUSTOMIZE
|
||||
except (ModuleNotFoundError, ImportError):
|
||||
# hass 2024.10 or older
|
||||
from homeassistant.config import DATA_CUSTOMIZE
|
||||
from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry
|
||||
from homeassistant.const import (CONF_ABOVE, CONF_ACCESS_TOKEN, CONF_BELOW,
|
||||
CONF_DOMAINS, CONF_ENTITIES, CONF_ENTITY_ID,
|
||||
|
@ -28,10 +35,12 @@ from homeassistant.const import (CONF_ABOVE, CONF_ACCESS_TOKEN, CONF_BELOW,
|
|||
from homeassistant.core import (Context, EventOrigin, HomeAssistant, callback,
|
||||
split_entity_id)
|
||||
from homeassistant.helpers import device_registry as dr
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_send
|
||||
from homeassistant.helpers.reload import async_integration_yaml_config
|
||||
from homeassistant.helpers.typing import ConfigType, HomeAssistantType
|
||||
from homeassistant.helpers.service import async_register_admin_service
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
from homeassistant.setup import async_setup_component
|
||||
|
||||
from custom_components.remote_homeassistant.views import DiscoveryInfoView
|
||||
|
@ -52,6 +61,7 @@ CONF_INSTANCES = "instances"
|
|||
CONF_SECURE = "secure"
|
||||
CONF_SUBSCRIBE_EVENTS = "subscribe_events"
|
||||
CONF_ENTITY_PREFIX = "entity_prefix"
|
||||
CONF_ENTITY_FRIENDLY_NAME_PREFIX = "entity_friendly_name_prefix"
|
||||
CONF_FILTER = "filter"
|
||||
CONF_MAX_MSG_SIZE = "max_message_size"
|
||||
|
||||
|
@ -64,6 +74,7 @@ STATE_RECONNECTING = "reconnecting"
|
|||
STATE_DISCONNECTED = "disconnected"
|
||||
|
||||
DEFAULT_ENTITY_PREFIX = ""
|
||||
DEFAULT_ENTITY_FRIENDLY_NAME_PREFIX = ""
|
||||
|
||||
INSTANCES_SCHEMA = vol.Schema(
|
||||
{
|
||||
|
@ -103,7 +114,10 @@ INSTANCES_SCHEMA = vol.Schema(
|
|||
],
|
||||
),
|
||||
vol.Optional(CONF_SUBSCRIBE_EVENTS): cv.ensure_list,
|
||||
vol.Optional(CONF_ENTITY_PREFIX, default=DEFAULT_ENTITY_PREFIX): cv.string,
|
||||
vol.Optional(CONF_ENTITY_PREFIX,
|
||||
default=DEFAULT_ENTITY_PREFIX): cv.string,
|
||||
vol.Optional(CONF_ENTITY_FRIENDLY_NAME_PREFIX,
|
||||
default=DEFAULT_ENTITY_FRIENDLY_NAME_PREFIX): cv.string,
|
||||
vol.Optional(CONF_LOAD_COMPONENTS): cv.ensure_list,
|
||||
vol.Required(CONF_SERVICE_PREFIX, default="remote_"): cv.string,
|
||||
vol.Optional(CONF_SERVICES): cv.ensure_list,
|
||||
|
@ -152,6 +166,7 @@ def async_yaml_to_config_entry(instance_conf):
|
|||
CONF_FILTER,
|
||||
CONF_SUBSCRIBE_EVENTS,
|
||||
CONF_ENTITY_PREFIX,
|
||||
CONF_ENTITY_FRIENDLY_NAME_PREFIX,
|
||||
CONF_LOAD_COMPONENTS,
|
||||
CONF_SERVICE_PREFIX,
|
||||
CONF_SERVICES,
|
||||
|
@ -182,11 +197,11 @@ async def _async_update_config_entry_if_from_yaml(hass, entries_by_id, conf):
|
|||
hass.config_entries.async_update_entry(entry, data=data, options=options)
|
||||
|
||||
|
||||
async def setup_remote_instance(hass: HomeAssistantType):
|
||||
async def setup_remote_instance(hass: HomeAssistant.core.HomeAssistant):
|
||||
hass.http.register_view(DiscoveryInfoView())
|
||||
|
||||
|
||||
async def async_setup(hass: HomeAssistantType, config: ConfigType):
|
||||
async def async_setup(hass: HomeAssistant.core.HomeAssistant, config: ConfigType):
|
||||
"""Set up the remote_homeassistant component."""
|
||||
hass.data.setdefault(DOMAIN, {})
|
||||
|
||||
|
@ -210,7 +225,7 @@ async def async_setup(hass: HomeAssistantType, config: ConfigType):
|
|||
|
||||
hass.async_create_task(setup_remote_instance(hass))
|
||||
|
||||
hass.helpers.service.async_register_admin_service(
|
||||
async_register_admin_service(hass,
|
||||
DOMAIN,
|
||||
SERVICE_RELOAD,
|
||||
_handle_reload,
|
||||
|
@ -246,12 +261,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
|
|||
for domain in entry.options.get(CONF_LOAD_COMPONENTS, []):
|
||||
hass.async_create_task(async_setup_component(hass, domain, {}))
|
||||
|
||||
await asyncio.gather(
|
||||
*[
|
||||
hass.config_entries.async_forward_entry_setup(entry, platform)
|
||||
for platform in PLATFORMS
|
||||
]
|
||||
)
|
||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||
await remote.async_connect()
|
||||
|
||||
hass.async_create_task(setup_components_and_platforms())
|
||||
|
@ -292,7 +302,7 @@ async def _update_listener(hass, config_entry):
|
|||
await hass.config_entries.async_reload(config_entry.entry_id)
|
||||
|
||||
|
||||
class RemoteConnection(object):
|
||||
class RemoteConnection:
|
||||
"""A Websocket connection to a remote home-assistant instance."""
|
||||
|
||||
def __init__(self, hass, config_entry):
|
||||
|
@ -302,7 +312,7 @@ class RemoteConnection(object):
|
|||
self._secure = config_entry.data.get(CONF_SECURE, False)
|
||||
self._verify_ssl = config_entry.data.get(CONF_VERIFY_SSL, False)
|
||||
self._access_token = config_entry.data.get(CONF_ACCESS_TOKEN)
|
||||
self._max_msg_size = config_entry.data.get(CONF_MAX_MSG_SIZE)
|
||||
self._max_msg_size = config_entry.data.get(CONF_MAX_MSG_SIZE, DEFAULT_MAX_MSG_SIZE)
|
||||
|
||||
# see homeassistant/components/influxdb/__init__.py
|
||||
# for include/exclude logic
|
||||
|
@ -326,9 +336,12 @@ class RemoteConnection(object):
|
|||
self._subscribe_events = set(
|
||||
config_entry.options.get(CONF_SUBSCRIBE_EVENTS, []) + INTERNALLY_USED_EVENTS
|
||||
)
|
||||
self._entity_prefix = config_entry.options.get(CONF_ENTITY_PREFIX, "")
|
||||
self._entity_prefix = config_entry.options.get(
|
||||
CONF_ENTITY_PREFIX, "")
|
||||
self._entity_friendly_name_prefix = config_entry.options.get(
|
||||
CONF_ENTITY_FRIENDLY_NAME_PREFIX, "")
|
||||
|
||||
self._connection = None
|
||||
self._connection : Optional[ClientWebSocketResponse] = None
|
||||
self._heartbeat_task = None
|
||||
self._is_stopping = False
|
||||
self._entities = set()
|
||||
|
@ -349,6 +362,26 @@ class RemoteConnection(object):
|
|||
return entity_id
|
||||
return entity_id
|
||||
|
||||
def _prefixed_entity_friendly_name(self, entity_friendly_name):
|
||||
if (self._entity_friendly_name_prefix
|
||||
and entity_friendly_name.startswith(self._entity_friendly_name_prefix)
|
||||
== False):
|
||||
entity_friendly_name = (self._entity_friendly_name_prefix +
|
||||
entity_friendly_name)
|
||||
return entity_friendly_name
|
||||
return entity_friendly_name
|
||||
|
||||
def _full_picture_url(self, url):
|
||||
baseURL = "%s://%s:%s" % (
|
||||
"https" if self._secure else "http",
|
||||
self._entry.data[CONF_HOST],
|
||||
self._entry.data[CONF_PORT],
|
||||
)
|
||||
if url.startswith(baseURL) == False:
|
||||
url = baseURL + url
|
||||
return url
|
||||
return url
|
||||
|
||||
def set_connection_state(self, state):
|
||||
"""Change current connection state."""
|
||||
signal = f"remote_homeassistant_{self._entry.unique_id}"
|
||||
|
@ -445,7 +478,7 @@ class RemoteConnection(object):
|
|||
|
||||
async def _heartbeat_loop(self):
|
||||
"""Send periodic heartbeats to remote instance."""
|
||||
while not self._connection.closed:
|
||||
while self._connection is not None and not self._connection.closed:
|
||||
await asyncio.sleep(HEARTBEAT_INTERVAL)
|
||||
|
||||
_LOGGER.debug("Sending ping")
|
||||
|
@ -460,7 +493,7 @@ class RemoteConnection(object):
|
|||
try:
|
||||
await asyncio.wait_for(event.wait(), HEARTBEAT_TIMEOUT)
|
||||
except asyncio.TimeoutError:
|
||||
_LOGGER.error("heartbeat failed")
|
||||
_LOGGER.warning("heartbeat failed")
|
||||
|
||||
# Schedule closing on event loop to avoid deadlock
|
||||
asyncio.ensure_future(self._connection.close())
|
||||
|
@ -478,9 +511,13 @@ class RemoteConnection(object):
|
|||
self.__id += 1
|
||||
return _id
|
||||
|
||||
async def call(self, callback, message_type, **extra_args):
|
||||
async def call(self, handler, message_type, **extra_args) -> None:
|
||||
if self._connection is None:
|
||||
_LOGGER.error("No remote websocket connection")
|
||||
return
|
||||
|
||||
_id = self._next_id()
|
||||
self._handlers[_id] = callback
|
||||
self._handlers[_id] = handler
|
||||
try:
|
||||
await self._connection.send_json(
|
||||
{"id": _id, "type": message_type, **extra_args}
|
||||
|
@ -511,7 +548,7 @@ class RemoteConnection(object):
|
|||
asyncio.ensure_future(self.async_connect())
|
||||
|
||||
async def _recv(self):
|
||||
while not self._connection.closed:
|
||||
while self._connection is not None and not self._connection.closed:
|
||||
try:
|
||||
data = await self._connection.receive()
|
||||
except aiohttp.client_exceptions.ClientError as err:
|
||||
|
@ -552,13 +589,13 @@ class RemoteConnection(object):
|
|||
|
||||
elif message["type"] == api.TYPE_AUTH_REQUIRED:
|
||||
if self._access_token:
|
||||
data = {"type": api.TYPE_AUTH, "access_token": self._access_token}
|
||||
json_data = {"type": api.TYPE_AUTH, "access_token": self._access_token}
|
||||
else:
|
||||
_LOGGER.error("Access token required, but not provided")
|
||||
self.set_connection_state(STATE_AUTH_REQUIRED)
|
||||
return
|
||||
try:
|
||||
await self._connection.send_json(data)
|
||||
await self._connection.send_json(json_data)
|
||||
except Exception as err:
|
||||
_LOGGER.error("could not send data to remote connection: %s", err)
|
||||
break
|
||||
|
@ -570,12 +607,12 @@ class RemoteConnection(object):
|
|||
return
|
||||
|
||||
else:
|
||||
callback = self._handlers.get(message["id"])
|
||||
if callback is not None:
|
||||
if inspect.iscoroutinefunction(callback):
|
||||
await callback(message)
|
||||
handler = self._handlers.get(message["id"])
|
||||
if handler is not None:
|
||||
if inspect.iscoroutinefunction(handler):
|
||||
await handler(message)
|
||||
else:
|
||||
callback(message)
|
||||
handler(message)
|
||||
|
||||
await self._disconnected()
|
||||
|
||||
|
@ -583,8 +620,8 @@ class RemoteConnection(object):
|
|||
async def forward_event(event):
|
||||
"""Send local event to remote instance.
|
||||
|
||||
The affected entity_id has to origin from that remote instance,
|
||||
otherwise the event is dicarded.
|
||||
The affected entity_id has to originate from that remote instance,
|
||||
otherwise the event is discarded.
|
||||
"""
|
||||
event_data = event.data
|
||||
service_data = event_data["service_data"]
|
||||
|
@ -627,7 +664,10 @@ class RemoteConnection(object):
|
|||
data = {"id": _id, "type": event.event_type, **event_data}
|
||||
|
||||
_LOGGER.debug("forward event: %s", data)
|
||||
|
||||
|
||||
if self._connection is None:
|
||||
_LOGGER.error("There is no remote connecion to send send data to")
|
||||
return
|
||||
try:
|
||||
await self._connection.send_json(data)
|
||||
except Exception as err:
|
||||
|
@ -636,7 +676,7 @@ class RemoteConnection(object):
|
|||
|
||||
def state_changed(entity_id, state, attr):
|
||||
"""Publish remote state change on local instance."""
|
||||
domain, object_id = split_entity_id(entity_id)
|
||||
domain, _object_id = split_entity_id(entity_id)
|
||||
|
||||
self._all_entity_names.add(entity_id)
|
||||
|
||||
|
@ -661,7 +701,7 @@ class RemoteConnection(object):
|
|||
try:
|
||||
if f[CONF_BELOW] and float(state) < f[CONF_BELOW]:
|
||||
_LOGGER.info(
|
||||
"%s: ignoring state '%s', because " "below '%s'",
|
||||
"%s: ignoring state '%s', because below '%s'",
|
||||
entity_id,
|
||||
state,
|
||||
f[CONF_BELOW],
|
||||
|
@ -669,7 +709,7 @@ class RemoteConnection(object):
|
|||
return
|
||||
if f[CONF_ABOVE] and float(state) > f[CONF_ABOVE]:
|
||||
_LOGGER.info(
|
||||
"%s: ignoring state '%s', because " "above '%s'",
|
||||
"%s: ignoring state '%s', because above '%s'",
|
||||
entity_id,
|
||||
state,
|
||||
f[CONF_ABOVE],
|
||||
|
@ -680,15 +720,32 @@ class RemoteConnection(object):
|
|||
|
||||
entity_id = self._prefixed_entity_id(entity_id)
|
||||
|
||||
# Add local unique id
|
||||
domain, object_id = split_entity_id(entity_id)
|
||||
attr['unique_id'] = f"{self._entry.unique_id[:16]}_{entity_id}"
|
||||
entity_registry = er.async_get(self._hass)
|
||||
entity_registry.async_get_or_create(
|
||||
domain=domain,
|
||||
platform='remote_homeassistant',
|
||||
unique_id=attr['unique_id'],
|
||||
suggested_object_id=object_id,
|
||||
)
|
||||
|
||||
# Add local customization data
|
||||
if DATA_CUSTOMIZE in self._hass.data:
|
||||
attr.update(self._hass.data[DATA_CUSTOMIZE].get(entity_id))
|
||||
|
||||
for attrId, value in attr.items():
|
||||
if attrId == "friendly_name":
|
||||
attr[attrId] = self._prefixed_entity_friendly_name(value)
|
||||
if attrId == "entity_picture":
|
||||
attr[attrId] = self._full_picture_url(value)
|
||||
|
||||
self._entities.add(entity_id)
|
||||
self._hass.states.async_set(entity_id, state, attr)
|
||||
|
||||
def fire_event(message):
|
||||
"""Publish remove event on local instance."""
|
||||
"""Publish remote event on local instance."""
|
||||
if message["type"] == "result":
|
||||
return
|
||||
|
||||
|
@ -730,6 +787,11 @@ class RemoteConnection(object):
|
|||
entity_id = entity["entity_id"]
|
||||
state = entity["state"]
|
||||
attributes = entity["attributes"]
|
||||
for attr, value in attributes.items():
|
||||
if attr == "friendly_name":
|
||||
attributes[attr] = self._prefixed_entity_friendly_name(value)
|
||||
if attr == "entity_picture":
|
||||
attributes[attr] = self._full_picture_url(value)
|
||||
|
||||
state_changed(entity_id, state, attributes)
|
||||
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
"""Config flow for Remote Home-Assistant integration."""
|
||||
from __future__ import annotations
|
||||
import logging
|
||||
import enum
|
||||
from typing import Any, Mapping
|
||||
|
||||
from urllib.parse import urlparse
|
||||
|
||||
|
@ -16,6 +18,7 @@ from homeassistant.util import slugify
|
|||
|
||||
from . import async_yaml_to_config_entry
|
||||
from .const import (CONF_ENTITY_PREFIX, # pylint:disable=unused-import
|
||||
CONF_ENTITY_FRIENDLY_NAME_PREFIX,
|
||||
CONF_EXCLUDE_DOMAINS, CONF_EXCLUDE_ENTITIES, CONF_FILTER,
|
||||
CONF_INCLUDE_DOMAINS, CONF_INCLUDE_ENTITIES,
|
||||
CONF_LOAD_COMPONENTS, CONF_MAIN, CONF_OPTIONS, CONF_REMOTE, CONF_REMOTE_CONNECTION,
|
||||
|
@ -31,11 +34,11 @@ ADD_NEW_EVENT = "add_new_event"
|
|||
FILTER_OPTIONS = [CONF_ENTITY_ID, CONF_UNIT_OF_MEASUREMENT, CONF_ABOVE, CONF_BELOW]
|
||||
|
||||
|
||||
def _filter_str(index, filter):
|
||||
entity_id = filter[CONF_ENTITY_ID]
|
||||
unit = filter[CONF_UNIT_OF_MEASUREMENT]
|
||||
above = filter[CONF_ABOVE]
|
||||
below = filter[CONF_BELOW]
|
||||
def _filter_str(index, filter_conf: Mapping[str, str|float]):
|
||||
entity_id = filter_conf[CONF_ENTITY_ID]
|
||||
unit = filter_conf[CONF_UNIT_OF_MEASUREMENT]
|
||||
above = filter_conf[CONF_ABOVE]
|
||||
below = filter_conf[CONF_BELOW]
|
||||
return f"{index+1}. {entity_id}, unit: {unit}, above: {above}, below: {below}"
|
||||
|
||||
|
||||
|
@ -50,8 +53,8 @@ async def validate_input(hass: core.HomeAssistant, conf):
|
|||
conf[CONF_ACCESS_TOKEN],
|
||||
conf.get(CONF_VERIFY_SSL, False),
|
||||
)
|
||||
except OSError:
|
||||
raise CannotConnect()
|
||||
except OSError as exc:
|
||||
raise CannotConnect() from exc
|
||||
|
||||
return {"title": info["location_name"], "uuid": info["uuid"]}
|
||||
|
||||
|
@ -91,9 +94,9 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
|||
|
||||
elif user_input[CONF_TYPE] == CONF_MAIN:
|
||||
return await self.async_step_connection_details()
|
||||
|
||||
|
||||
errors["base"] = "unknown"
|
||||
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="user",
|
||||
data_schema=vol.Schema(
|
||||
|
@ -129,7 +132,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
|||
self._abort_if_unique_id_configured()
|
||||
return self.async_create_entry(title=info["title"], data=user_input)
|
||||
|
||||
user_input = user_input or dict()
|
||||
user_input = user_input or {}
|
||||
host = user_input.get(CONF_HOST, self.prefill.get(CONF_HOST) or vol.UNDEFINED)
|
||||
port = user_input.get(CONF_PORT, self.prefill.get(CONF_PORT) or vol.UNDEFINED)
|
||||
secure = user_input.get(CONF_SECURE, self.prefill.get(CONF_SECURE) or vol.UNDEFINED)
|
||||
|
@ -149,10 +152,10 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
|||
errors=errors,
|
||||
)
|
||||
|
||||
async def async_step_zeroconf(self, info):
|
||||
async def async_step_zeroconf(self, discovery_info):
|
||||
"""Handle instance discovered via zeroconf."""
|
||||
properties = info.properties
|
||||
port = info.port
|
||||
properties = discovery_info.properties
|
||||
port = discovery_info.port
|
||||
uuid = properties["uuid"]
|
||||
|
||||
await self.async_set_unique_id(uuid)
|
||||
|
@ -203,11 +206,11 @@ class OptionsFlowHandler(config_entries.OptionsFlow):
|
|||
def __init__(self, config_entry):
|
||||
"""Initialize remote_homeassistant options flow."""
|
||||
self.config_entry = config_entry
|
||||
self.filters = None
|
||||
self.events = None
|
||||
self.options = None
|
||||
self.filters : list[Any] | None = None
|
||||
self.events : set[Any] | None = None
|
||||
self.options : dict[str, Any] | None = None
|
||||
|
||||
async def async_step_init(self, user_input=None):
|
||||
async def async_step_init(self, user_input : dict[str, str] | None = None):
|
||||
"""Manage basic options."""
|
||||
if self.config_entry.unique_id == REMOTE_ID:
|
||||
return self.async_abort(reason="not_supported")
|
||||
|
@ -235,6 +238,14 @@ class OptionsFlowHandler(config_entries.OptionsFlow):
|
|||
)
|
||||
},
|
||||
): str,
|
||||
vol.Optional(
|
||||
CONF_ENTITY_FRIENDLY_NAME_PREFIX,
|
||||
description={
|
||||
"suggested_value": self.config_entry.options.get(
|
||||
CONF_ENTITY_FRIENDLY_NAME_PREFIX
|
||||
)
|
||||
},
|
||||
): str,
|
||||
vol.Optional(
|
||||
CONF_LOAD_COMPONENTS,
|
||||
default=self._default(CONF_LOAD_COMPONENTS),
|
||||
|
@ -252,7 +263,7 @@ class OptionsFlowHandler(config_entries.OptionsFlow):
|
|||
|
||||
async def async_step_domain_entity_filters(self, user_input=None):
|
||||
"""Manage domain and entity filters."""
|
||||
if user_input is not None:
|
||||
if self.options is not None and user_input is not None:
|
||||
self.options.update(user_input)
|
||||
return await self.async_step_general_filters()
|
||||
|
||||
|
@ -289,21 +300,25 @@ class OptionsFlowHandler(config_entries.OptionsFlow):
|
|||
# Each filter string is prefixed with a number (index in self.filter+1).
|
||||
# Extract all of them and build the final filter list.
|
||||
selected_indices = [
|
||||
int(filter.split(".")[0]) - 1
|
||||
for filter in user_input.get(CONF_FILTER, [])
|
||||
int(filterItem.split(".")[0]) - 1
|
||||
for filterItem in user_input.get(CONF_FILTER, [])
|
||||
]
|
||||
self.options[CONF_FILTER] = [self.filters[i] for i in selected_indices]
|
||||
if self.options is not None:
|
||||
self.options[CONF_FILTER] = [self.filters[i] for i in selected_indices] # type: ignore
|
||||
return await self.async_step_events()
|
||||
|
||||
selected = user_input.get(CONF_FILTER, [])
|
||||
new_filter = {conf: user_input.get(conf) for conf in FILTER_OPTIONS}
|
||||
selected.append(_filter_str(len(self.filters), new_filter))
|
||||
self.filters.append(new_filter)
|
||||
|
||||
selected.append(_filter_str(len(self.filters), new_filter)) # type: ignore
|
||||
self.filters.append(new_filter) # type: ignore
|
||||
else:
|
||||
self.filters = self.config_entry.options.get(CONF_FILTER, [])
|
||||
selected = [_filter_str(i, filter) for i, filter in enumerate(self.filters)]
|
||||
selected = [_filter_str(i, filterItem) for i, filterItem in enumerate(self.filters)] # type: ignore
|
||||
|
||||
strings = [_filter_str(i, filter) for i, filter in enumerate(self.filters)]
|
||||
if self.filters is None:
|
||||
self.filters = []
|
||||
strings = [_filter_str(i, filterItem) for i, filterItem in enumerate(self.filters)]
|
||||
return self.async_show_form(
|
||||
step_id="general_filters",
|
||||
data_schema=vol.Schema(
|
||||
|
@ -322,13 +337,15 @@ class OptionsFlowHandler(config_entries.OptionsFlow):
|
|||
async def async_step_events(self, user_input=None):
|
||||
"""Manage event options."""
|
||||
if user_input is not None:
|
||||
if ADD_NEW_EVENT not in user_input:
|
||||
if ADD_NEW_EVENT not in user_input and self.options is not None:
|
||||
self.options[CONF_SUBSCRIBE_EVENTS] = user_input.get(
|
||||
CONF_SUBSCRIBE_EVENTS, []
|
||||
)
|
||||
return self.async_create_entry(title="", data=self.options)
|
||||
|
||||
selected = user_input.get(CONF_SUBSCRIBE_EVENTS, [])
|
||||
if self.events is None:
|
||||
self.events = set()
|
||||
self.events.add(user_input[ADD_NEW_EVENT])
|
||||
selected.append(user_input[ADD_NEW_EVENT])
|
||||
else:
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue