From 289546ef6d1893827b5e2a7ad00f49cdfc32ee42 Mon Sep 17 00:00:00 2001 From: Andrew Jackson Date: Mon, 29 Sep 2025 10:58:40 +0100 Subject: [PATCH 01/13] Bump aiomealie to 0.11.0 adding times to recipes (#153183) --- homeassistant/components/mealie/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- .../mealie/snapshots/test_diagnostics.ambr | 42 ++ .../mealie/snapshots/test_services.ambr | 360 ++++++++++++++++++ 5 files changed, 405 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/mealie/manifest.json b/homeassistant/components/mealie/manifest.json index dba018349eb851..b768cc92ccd650 100644 --- a/homeassistant/components/mealie/manifest.json +++ b/homeassistant/components/mealie/manifest.json @@ -7,5 +7,5 @@ "integration_type": "service", "iot_class": "local_polling", "quality_scale": "silver", - "requirements": ["aiomealie==0.10.2"] + "requirements": ["aiomealie==0.11.0"] } diff --git a/requirements_all.txt b/requirements_all.txt index 963b249ee34c1c..7c2870bec6256e 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -310,7 +310,7 @@ aiolookin==1.0.0 aiolyric==2.0.2 # homeassistant.components.mealie -aiomealie==0.10.2 +aiomealie==0.11.0 # homeassistant.components.modern_forms aiomodernforms==0.1.8 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index fdd72ea16b9465..8800debebaf43e 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -292,7 +292,7 @@ aiolookin==1.0.0 aiolyric==2.0.2 # homeassistant.components.mealie -aiomealie==0.10.2 +aiomealie==0.11.0 # homeassistant.components.modern_forms aiomodernforms==0.1.8 diff --git a/tests/components/mealie/snapshots/test_diagnostics.ambr b/tests/components/mealie/snapshots/test_diagnostics.ambr index c4d649fcec6749..c569ad8e5892fd 100644 --- a/tests/components/mealie/snapshots/test_diagnostics.ambr +++ b/tests/components/mealie/snapshots/test_diagnostics.ambr @@ -23,9 +23,12 @@ 'image': 'JeQ2', 'name': 'Roast Chicken', 'original_url': 'https://tastesbetterfromscratch.com/roast-chicken/', + 'perform_time': '1 Hour 20 Minutes', + 'prep_time': '15 Minutes', 'recipe_id': '5b055066-d57d-4fd0-8dfd-a2c2f07b36f1', 'recipe_yield': '6 servings', 'slug': 'roast-chicken', + 'total_time': '1 Hour 35 Minutes', 'user_id': '1ce8b5fe-04e8-4b80-aab1-d92c94685c6d', }), 'title': None, @@ -50,9 +53,12 @@ 'image': 'AiIo', 'name': 'Zoete aardappel curry traybake', 'original_url': 'https://chickslovefood.com/recept/zoete-aardappel-curry-traybake/', + 'perform_time': None, + 'prep_time': None, 'recipe_id': 'c5f00a93-71a2-4e48-900f-d9ad0bb9de93', 'recipe_yield': '2 servings', 'slug': 'zoete-aardappel-curry-traybake', + 'total_time': '40 Minutes', 'user_id': '1ce8b5fe-04e8-4b80-aab1-d92c94685c6d', }), 'title': None, @@ -75,9 +81,12 @@ 'image': 'En9o', 'name': 'Εύκολη μακαρονάδα με κεφτεδάκια στον φούρνο (1)', 'original_url': 'https://akispetretzikis.com/recipe/7959/efkolh-makaronada-me-keftedakia-ston-fourno', + 'perform_time': '50 Minutes', + 'prep_time': '15 Minutes', 'recipe_id': 'f79f7e9d-4b58-4930-a586-2b127f16ee34', 'recipe_yield': '6 servings', 'slug': 'eukole-makaronada-me-kephtedakia-ston-phourno-1', + 'total_time': None, 'user_id': '1ce8b5fe-04e8-4b80-aab1-d92c94685c6d', }), 'title': None, @@ -100,9 +109,12 @@ 'image': 'Kn62', 'name': 'Greek Turkey Meatballs with Lemon Orzo & Creamy Feta Yogurt Sauce', 'original_url': 'https://www.ambitiouskitchen.com/greek-turkey-meatballs/', + 'perform_time': '20 Minutes', + 'prep_time': '40 Minutes', 'recipe_id': '47595e4c-52bc-441d-b273-3edf4258806d', 'recipe_yield': '4 servings', 'slug': 'greek-turkey-meatballs-with-lemon-orzo-creamy-feta-yogurt-sauce', + 'total_time': '1 Hour', 'user_id': '1ce8b5fe-04e8-4b80-aab1-d92c94685c6d', }), 'title': None, @@ -125,9 +137,12 @@ 'image': 'ibL6', 'name': 'Pampered Chef Double Chocolate Mocha Trifle', 'original_url': 'https://www.food.com/recipe/pampered-chef-double-chocolate-mocha-trifle-74963', + 'perform_time': '1 Hour', + 'prep_time': '15 Minutes', 'recipe_id': '92635fd0-f2dc-4e78-a6e4-ecd556ad361f', 'recipe_yield': '12 servings', 'slug': 'pampered-chef-double-chocolate-mocha-trifle', + 'total_time': '1 Hour 15 Minutes', 'user_id': '1ce8b5fe-04e8-4b80-aab1-d92c94685c6d', }), 'title': None, @@ -150,9 +165,12 @@ 'image': 'beGq', 'name': 'Cheeseburger Sliders (Easy, 30-min Recipe)', 'original_url': 'https://natashaskitchen.com/cheeseburger-sliders/', + 'perform_time': '22 Minutes', + 'prep_time': '8 Minutes', 'recipe_id': '8bdd3656-5e7e-45d3-a3c4-557390846a22', 'recipe_yield': '24 servings', 'slug': 'cheeseburger-sliders-easy-30-min-recipe', + 'total_time': '30 Minutes', 'user_id': '1ce8b5fe-04e8-4b80-aab1-d92c94685c6d', }), 'title': None, @@ -175,9 +193,12 @@ 'image': '356X', 'name': 'All-American Beef Stew Recipe', 'original_url': 'https://www.seriouseats.com/all-american-beef-stew-recipe', + 'perform_time': '3 Hours 10 Minutes', + 'prep_time': '5 Minutes', 'recipe_id': '48f39d27-4b8e-4c14-bf36-4e1e6497e75e', 'recipe_yield': '6 servings', 'slug': 'all-american-beef-stew-recipe', + 'total_time': '3 Hours 15 Minutes', 'user_id': '1ce8b5fe-04e8-4b80-aab1-d92c94685c6d', }), 'title': None, @@ -200,9 +221,12 @@ 'image': 'nOPT', 'name': 'Einfacher Nudelauflauf mit Brokkoli', 'original_url': 'https://kochkarussell.com/einfacher-nudelauflauf-brokkoli/', + 'perform_time': '20 Minutes', + 'prep_time': '15 Minutes', 'recipe_id': '9d553779-607e-471b-acf3-84e6be27b159', 'recipe_yield': '4 servings', 'slug': 'einfacher-nudelauflauf-mit-brokkoli', + 'total_time': '35 Minutes', 'user_id': '1ce8b5fe-04e8-4b80-aab1-d92c94685c6d', }), 'title': None, @@ -225,9 +249,12 @@ 'image': '5G1v', 'name': 'Miso Udon Noodles with Spinach and Tofu', 'original_url': 'https://www.allrecipes.com/recipe/284039/miso-udon-noodles-with-spinach-and-tofu/', + 'perform_time': '15 Minutes', + 'prep_time': '10 Minutes', 'recipe_id': '25b814f2-d9bf-4df0-b40d-d2f2457b4317', 'recipe_yield': '2 servings', 'slug': 'miso-udon-noodles-with-spinach-and-tofu', + 'total_time': '25 Minutes', 'user_id': '1ce8b5fe-04e8-4b80-aab1-d92c94685c6d', }), 'title': None, @@ -250,9 +277,12 @@ 'image': 'rrNL', 'name': 'Mousse de saumon', 'original_url': 'https://www.ricardocuisine.com/recettes/8919-mousse-de-saumon', + 'perform_time': '2 Minutes', + 'prep_time': '15 Minutes', 'recipe_id': '55c88810-4cf1-4d86-ae50-63b15fd173fb', 'recipe_yield': '12 servings', 'slug': 'mousse-de-saumon', + 'total_time': '17 Minutes', 'user_id': '1ce8b5fe-04e8-4b80-aab1-d92c94685c6d', }), 'title': None, @@ -291,9 +321,12 @@ 'image': 'INQz', 'name': 'Receta de pollo al curry en 10 minutos (con vídeo incluido)', 'original_url': 'https://www.directoalpaladar.com/recetas-de-carnes-y-aves/receta-de-pollo-al-curry-en-10-minutos', + 'perform_time': '7 Minutes', + 'prep_time': '3 Minutes', 'recipe_id': 'e360a0cc-18b0-4a84-a91b-8aa59e2451c9', 'recipe_yield': '2 servings', 'slug': 'receta-de-pollo-al-curry-en-10-minutos-con-video-incluido', + 'total_time': '10 Minutes', 'user_id': '1ce8b5fe-04e8-4b80-aab1-d92c94685c6d', }), 'title': None, @@ -316,9 +349,12 @@ 'image': 'nj5M', 'name': 'Boeuf bourguignon : la vraie recette (2)', 'original_url': 'https://www.marmiton.org/recettes/recette_boeuf-bourguignon_18889.aspx', + 'perform_time': '4 Hours', + 'prep_time': '1 Hour', 'recipe_id': '9c7b8aee-c93c-4b1b-ab48-2625d444743a', 'recipe_yield': '4 servings', 'slug': 'boeuf-bourguignon-la-vraie-recette-2', + 'total_time': '5 Hours', 'user_id': '1ce8b5fe-04e8-4b80-aab1-d92c94685c6d', }), 'title': None, @@ -341,9 +377,12 @@ 'image': '356X', 'name': 'All-American Beef Stew Recipe', 'original_url': 'https://www.seriouseats.com/all-american-beef-stew-recipe', + 'perform_time': '3 Hours 10 Minutes', + 'prep_time': '5 Minutes', 'recipe_id': '48f39d27-4b8e-4c14-bf36-4e1e6497e75e', 'recipe_yield': '6 servings', 'slug': 'all-american-beef-stew-recipe', + 'total_time': '3 Hours 15 Minutes', 'user_id': '1ce8b5fe-04e8-4b80-aab1-d92c94685c6d', }), 'title': None, @@ -368,9 +407,12 @@ 'image': 'nOPT', 'name': 'Einfacher Nudelauflauf mit Brokkoli', 'original_url': 'https://kochkarussell.com/einfacher-nudelauflauf-brokkoli/', + 'perform_time': '20 Minutes', + 'prep_time': '15 Minutes', 'recipe_id': '9d553779-607e-471b-acf3-84e6be27b159', 'recipe_yield': '4 servings', 'slug': 'einfacher-nudelauflauf-mit-brokkoli', + 'total_time': '35 Minutes', 'user_id': '1ce8b5fe-04e8-4b80-aab1-d92c94685c6d', }), 'title': None, diff --git a/tests/components/mealie/snapshots/test_services.ambr b/tests/components/mealie/snapshots/test_services.ambr index a1cb758098e573..b8afee7c9d5289 100644 --- a/tests/components/mealie/snapshots/test_services.ambr +++ b/tests/components/mealie/snapshots/test_services.ambr @@ -10,9 +10,12 @@ 'image': None, 'name': 'tu6y', 'original_url': None, + 'perform_time': None, + 'prep_time': None, 'recipe_id': 'e82f5449-c33b-437c-b712-337587199264', 'recipe_yield': None, 'slug': 'tu6y', + 'total_time': None, 'user_id': '1ce8b5fe-04e8-4b80-aab1-d92c94685c6d', }), dict({ @@ -22,9 +25,12 @@ 'image': 'En9o', 'name': 'Εύκολη μακαρονάδα με κεφτεδάκια στον φούρνο (1)', 'original_url': 'https://akispetretzikis.com/recipe/7959/efkolh-makaronada-me-keftedakia-ston-fourno', + 'perform_time': '50 Minutes', + 'prep_time': '15 Minutes', 'recipe_id': 'f79f7e9d-4b58-4930-a586-2b127f16ee34', 'recipe_yield': '6 servings', 'slug': 'eukole-makaronada-me-kephtedakia-ston-phourno-1', + 'total_time': None, 'user_id': '1ce8b5fe-04e8-4b80-aab1-d92c94685c6d', }), dict({ @@ -34,9 +40,12 @@ 'image': 'aAhk', 'name': 'Patates douces au four (1)', 'original_url': 'https://www.papillesetpupilles.fr/2018/10/patates-douces-au-four.html/', + 'perform_time': None, + 'prep_time': None, 'recipe_id': '90097c8b-9d80-468a-b497-73957ac0cd8b', 'recipe_yield': '', 'slug': 'patates-douces-au-four-1', + 'total_time': None, 'user_id': '1ce8b5fe-04e8-4b80-aab1-d92c94685c6d', }), dict({ @@ -46,9 +55,12 @@ 'image': 'kdhm', 'name': 'Sweet potatoes', 'original_url': 'https://www.papillesetpupilles.fr/2018/10/patates-douces-au-four.html/', + 'perform_time': None, + 'prep_time': None, 'recipe_id': '98845807-9365-41fd-acd1-35630b468c27', 'recipe_yield': '', 'slug': 'sweet-potatoes', + 'total_time': None, 'user_id': '1ce8b5fe-04e8-4b80-aab1-d92c94685c6d', }), dict({ @@ -58,9 +70,12 @@ 'image': 'tNbG', 'name': 'Εύκολη μακαρονάδα με κεφτεδάκια στον φούρνο', 'original_url': 'https://akispetretzikis.com/recipe/7959/efkolh-makaronada-me-keftedakia-ston-fourno', + 'perform_time': '50 Minutes', + 'prep_time': '15 Minutes', 'recipe_id': '40c227e0-3c7e-41f7-866d-5de04eaecdd7', 'recipe_yield': '6 servings', 'slug': 'eukole-makaronada-me-kephtedakia-ston-phourno', + 'total_time': None, 'user_id': '1ce8b5fe-04e8-4b80-aab1-d92c94685c6d', }), dict({ @@ -70,9 +85,12 @@ 'image': 'nj5M', 'name': 'Boeuf bourguignon : la vraie recette (2)', 'original_url': 'https://www.marmiton.org/recettes/recette_boeuf-bourguignon_18889.aspx', + 'perform_time': '4 Hours', + 'prep_time': '1 Hour', 'recipe_id': '9c7b8aee-c93c-4b1b-ab48-2625d444743a', 'recipe_yield': '4 servings', 'slug': 'boeuf-bourguignon-la-vraie-recette-2', + 'total_time': '5 Hours', 'user_id': '1ce8b5fe-04e8-4b80-aab1-d92c94685c6d', }), dict({ @@ -82,9 +100,12 @@ 'image': 'rbU7', 'name': 'Boeuf bourguignon : la vraie recette (1)', 'original_url': 'https://www.marmiton.org/recettes/recette_boeuf-bourguignon_18889.aspx', + 'perform_time': '4 Hours', + 'prep_time': '1 Hour', 'recipe_id': 'fc42c7d1-7b0f-4e04-b88a-dbd80b81540b', 'recipe_yield': '4 servings', 'slug': 'boeuf-bourguignon-la-vraie-recette-1', + 'total_time': '5 Hours', 'user_id': '1ce8b5fe-04e8-4b80-aab1-d92c94685c6d', }), dict({ @@ -94,9 +115,12 @@ 'image': 'JSp3', 'name': 'Veganes Marmor-Bananenbrot mit Erdnussbutter', 'original_url': 'https://biancazapatka.com/de/erdnussbutter-schoko-bananenbrot/', + 'perform_time': '55 Minutes', + 'prep_time': '15 Minutes', 'recipe_id': '89e63d72-7a51-4cef-b162-2e45035d0a91', 'recipe_yield': '14 servings', 'slug': 'veganes-marmor-bananenbrot-mit-erdnussbutter', + 'total_time': None, 'user_id': '1ce8b5fe-04e8-4b80-aab1-d92c94685c6d', }), dict({ @@ -106,9 +130,12 @@ 'image': '9QMh', 'name': 'Pasta mit Tomaten, Knoblauch und Basilikum - einfach (und) genial! - Kuechenchaotin', 'original_url': 'https://kuechenchaotin.de/pasta-mit-tomaten-knoblauch-basilikum/', + 'perform_time': None, + 'prep_time': None, 'recipe_id': 'eab64457-97ba-4d6c-871c-cb1c724ccb51', 'recipe_yield': '', 'slug': 'pasta-mit-tomaten-knoblauch-und-basilikum-einfach-und-genial-kuechenchaotin', + 'total_time': None, 'user_id': '1ce8b5fe-04e8-4b80-aab1-d92c94685c6d', }), dict({ @@ -118,9 +145,12 @@ 'image': None, 'name': 'test123', 'original_url': None, + 'perform_time': None, + 'prep_time': None, 'recipe_id': '12439e3d-3c1c-4dcc-9c6e-4afcea2a0542', 'recipe_yield': None, 'slug': 'test123', + 'total_time': None, 'user_id': '1ce8b5fe-04e8-4b80-aab1-d92c94685c6d', }), dict({ @@ -130,9 +160,12 @@ 'image': None, 'name': 'Bureeto', 'original_url': None, + 'perform_time': None, + 'prep_time': None, 'recipe_id': '6567f6ec-e410-49cb-a1a5-d08517184e78', 'recipe_yield': None, 'slug': 'bureeto', + 'total_time': None, 'user_id': '1ce8b5fe-04e8-4b80-aab1-d92c94685c6d', }), dict({ @@ -142,9 +175,12 @@ 'image': None, 'name': 'Subway Double Cookies', 'original_url': None, + 'perform_time': None, + 'prep_time': None, 'recipe_id': 'f7737d17-161c-4008-88d4-dd2616778cd0', 'recipe_yield': None, 'slug': 'subway-double-cookies', + 'total_time': None, 'user_id': '1ce8b5fe-04e8-4b80-aab1-d92c94685c6d', }), dict({ @@ -154,9 +190,12 @@ 'image': None, 'name': 'qwerty12345', 'original_url': None, + 'perform_time': None, + 'prep_time': None, 'recipe_id': '1904b717-4a8b-4de9-8909-56958875b5f4', 'recipe_yield': None, 'slug': 'qwerty12345', + 'total_time': None, 'user_id': '1ce8b5fe-04e8-4b80-aab1-d92c94685c6d', }), dict({ @@ -166,9 +205,12 @@ 'image': 'beGq', 'name': 'Cheeseburger Sliders (Easy, 30-min Recipe)', 'original_url': 'https://natashaskitchen.com/cheeseburger-sliders/', + 'perform_time': '22 Minutes', + 'prep_time': '8 Minutes', 'recipe_id': '8bdd3656-5e7e-45d3-a3c4-557390846a22', 'recipe_yield': '24 servings', 'slug': 'cheeseburger-sliders-easy-30-min-recipe', + 'total_time': '30 Minutes', 'user_id': '1ce8b5fe-04e8-4b80-aab1-d92c94685c6d', }), dict({ @@ -178,9 +220,12 @@ 'image': None, 'name': 'meatloaf', 'original_url': None, + 'perform_time': None, + 'prep_time': None, 'recipe_id': '8a30d31d-aa14-411e-af0c-6b61a94f5291', 'recipe_yield': '4', 'slug': 'meatloaf', + 'total_time': None, 'user_id': '1ce8b5fe-04e8-4b80-aab1-d92c94685c6d', }), dict({ @@ -190,9 +235,12 @@ 'image': 'kCBh', 'name': 'Richtig rheinischer Sauerbraten', 'original_url': 'https://www.chefkoch.de/rezepte/937641199437984/Richtig-rheinischer-Sauerbraten.html', + 'perform_time': '2 Hours 20 Minutes', + 'prep_time': '1 Hour', 'recipe_id': 'f2f7880b-1136-436f-91b7-129788d8c117', 'recipe_yield': '4 servings', 'slug': 'richtig-rheinischer-sauerbraten', + 'total_time': '3 Hours 20 Minutes', 'user_id': '1ce8b5fe-04e8-4b80-aab1-d92c94685c6d', }), dict({ @@ -202,9 +250,12 @@ 'image': 'kpBx', 'name': 'Orientalischer Gemüse-Hähnchen Eintopf', 'original_url': 'https://www.chefkoch.de/rezepte/2307761368177614/Orientalischer-Gemuese-Haehnchen-Eintopf.html', + 'perform_time': '20 Minutes', + 'prep_time': '15 Minutes', 'recipe_id': 'cf634591-0f82-4254-8e00-2f7e8b0c9022', 'recipe_yield': '6 servings', 'slug': 'orientalischer-gemuse-hahnchen-eintopf', + 'total_time': '35 Minutes', 'user_id': '1ce8b5fe-04e8-4b80-aab1-d92c94685c6d', }), dict({ @@ -214,9 +265,12 @@ 'image': None, 'name': 'test 20240121', 'original_url': None, + 'perform_time': None, + 'prep_time': None, 'recipe_id': '05208856-d273-4cc9-bcfa-e0215d57108d', 'recipe_yield': '4', 'slug': 'test-20240121', + 'total_time': None, 'user_id': '1ce8b5fe-04e8-4b80-aab1-d92c94685c6d', }), dict({ @@ -226,9 +280,12 @@ 'image': 'McEx', 'name': 'Loempia bowl', 'original_url': 'https://www.lekkerensimpel.com/loempia-bowl/', + 'perform_time': None, + 'prep_time': None, 'recipe_id': '145eeb05-781a-4eb0-a656-afa8bc8c0164', 'recipe_yield': '', 'slug': 'loempia-bowl', + 'total_time': None, 'user_id': '1ce8b5fe-04e8-4b80-aab1-d92c94685c6d', }), dict({ @@ -238,9 +295,12 @@ 'image': 'bzqo', 'name': '5 Ingredient Chocolate Mousse', 'original_url': 'https://thehappypear.ie/aquafaba-chocolate-mousse/', + 'perform_time': None, + 'prep_time': '10 Minutes', 'recipe_id': '5c6532aa-ad84-424c-bc05-c32d50430fe4', 'recipe_yield': '6 servings', 'slug': '5-ingredient-chocolate-mousse', + 'total_time': None, 'user_id': '1ce8b5fe-04e8-4b80-aab1-d92c94685c6d', }), dict({ @@ -250,9 +310,12 @@ 'image': 'KGK6', 'name': 'Der perfekte Pfannkuchen - gelingt einfach immer', 'original_url': 'https://www.chefkoch.de/rezepte/1208161226570428/Der-perfekte-Pfannkuchen-gelingt-einfach-immer.html', + 'perform_time': '10 Minutes', + 'prep_time': '5 Minutes', 'recipe_id': 'f2e684f2-49e0-45ee-90de-951344472f1c', 'recipe_yield': '4 servings', 'slug': 'der-perfekte-pfannkuchen-gelingt-einfach-immer', + 'total_time': '15 Minutes', 'user_id': '1ce8b5fe-04e8-4b80-aab1-d92c94685c6d', }), dict({ @@ -262,9 +325,12 @@ 'image': 'yNDq', 'name': 'Dinkel-Sauerteigbrot', 'original_url': 'https://www.besondersgut.ch/dinkel-sauerteigbrot/', + 'perform_time': '35min', + 'prep_time': '1h', 'recipe_id': 'cf239441-b75d-4dea-a48e-9d99b7cb5842', 'recipe_yield': '1', 'slug': 'dinkel-sauerteigbrot', + 'total_time': '24h', 'user_id': '1ce8b5fe-04e8-4b80-aab1-d92c94685c6d', }), dict({ @@ -274,9 +340,12 @@ 'image': None, 'name': 'test 234234', 'original_url': None, + 'perform_time': None, + 'prep_time': None, 'recipe_id': '2673eb90-6d78-4b95-af36-5db8c8a6da37', 'recipe_yield': None, 'slug': 'test-234234', + 'total_time': None, 'user_id': '1ce8b5fe-04e8-4b80-aab1-d92c94685c6d', }), dict({ @@ -286,9 +355,12 @@ 'image': None, 'name': 'test 243', 'original_url': None, + 'perform_time': None, + 'prep_time': None, 'recipe_id': '0a723c54-af53-40e9-a15f-c87aae5ac688', 'recipe_yield': None, 'slug': 'test-243', + 'total_time': None, 'user_id': '1ce8b5fe-04e8-4b80-aab1-d92c94685c6d', }), dict({ @@ -298,9 +370,12 @@ 'image': 'nOPT', 'name': 'Einfacher Nudelauflauf mit Brokkoli', 'original_url': 'https://kochkarussell.com/einfacher-nudelauflauf-brokkoli/', + 'perform_time': '20 Minutes', + 'prep_time': '15 Minutes', 'recipe_id': '9d553779-607e-471b-acf3-84e6be27b159', 'recipe_yield': '4 servings', 'slug': 'einfacher-nudelauflauf-mit-brokkoli', + 'total_time': '35 Minutes', 'user_id': '1ce8b5fe-04e8-4b80-aab1-d92c94685c6d', }), dict({ @@ -321,9 +396,12 @@ 'image': 'vxuL', 'name': 'Tarta cytrynowa z bezą', 'original_url': 'https://www.przepisy.pl/przepis/tarta-cytrynowa-z-beza', + 'perform_time': None, + 'prep_time': '1 Hour', 'recipe_id': '9d3cb303-a996-4144-948a-36afaeeef554', 'recipe_yield': '8 servings', 'slug': 'tarta-cytrynowa-z-beza', + 'total_time': '1 Hour', 'user_id': '1ce8b5fe-04e8-4b80-aab1-d92c94685c6d', }), dict({ @@ -333,9 +411,12 @@ 'image': None, 'name': 'Martins test Recipe', 'original_url': None, + 'perform_time': None, + 'prep_time': None, 'recipe_id': '77f05a49-e869-4048-aa62-0d8a1f5a8f1c', 'recipe_yield': None, 'slug': 'martins-test-recipe', + 'total_time': None, 'user_id': '1ce8b5fe-04e8-4b80-aab1-d92c94685c6d', }), dict({ @@ -345,9 +426,12 @@ 'image': 'xP1Q', 'name': 'Muffinki czekoladowe', 'original_url': 'https://aniagotuje.pl/przepis/muffinki-czekoladowe', + 'perform_time': '30 Minutes', + 'prep_time': '25 Minutes', 'recipe_id': '75a90207-9c10-4390-a265-c47a4b67fd69', 'recipe_yield': '12', 'slug': 'muffinki-czekoladowe', + 'total_time': None, 'user_id': '1ce8b5fe-04e8-4b80-aab1-d92c94685c6d', }), dict({ @@ -357,9 +441,12 @@ 'image': None, 'name': 'My Test Recipe', 'original_url': None, + 'perform_time': None, + 'prep_time': None, 'recipe_id': '4320ba72-377b-4657-8297-dce198f24cdf', 'recipe_yield': None, 'slug': 'my-test-recipe', + 'total_time': None, 'user_id': '1ce8b5fe-04e8-4b80-aab1-d92c94685c6d', }), dict({ @@ -369,9 +456,12 @@ 'image': None, 'name': 'My Test Receipe', 'original_url': None, + 'perform_time': None, + 'prep_time': None, 'recipe_id': '98dac844-31ee-426a-b16c-fb62a5dd2816', 'recipe_yield': None, 'slug': 'my-test-receipe', + 'total_time': None, 'user_id': '1ce8b5fe-04e8-4b80-aab1-d92c94685c6d', }), dict({ @@ -381,9 +471,12 @@ 'image': 'r1ck', 'name': 'Patates douces au four', 'original_url': 'https://www.papillesetpupilles.fr/2018/10/patates-douces-au-four.html/', + 'perform_time': None, + 'prep_time': None, 'recipe_id': 'c3c8f207-c704-415d-81b1-da9f032cf52f', 'recipe_yield': '', 'slug': 'patates-douces-au-four', + 'total_time': None, 'user_id': '1ce8b5fe-04e8-4b80-aab1-d92c94685c6d', }), dict({ @@ -393,9 +486,12 @@ 'image': 'gD94', 'name': 'Easy Homemade Pizza Dough', 'original_url': 'https://sallysbakingaddiction.com/homemade-pizza-crust-recipe/', + 'perform_time': '15 Minutes', + 'prep_time': '2 Hours 15 Minutes', 'recipe_id': '1edb2f6e-133c-4be0-b516-3c23625a97ec', 'recipe_yield': '2 servings', 'slug': 'easy-homemade-pizza-dough', + 'total_time': '2 Hours 30 Minutes', 'user_id': '1ce8b5fe-04e8-4b80-aab1-d92c94685c6d', }), dict({ @@ -405,9 +501,12 @@ 'image': '356X', 'name': 'All-American Beef Stew Recipe', 'original_url': 'https://www.seriouseats.com/all-american-beef-stew-recipe', + 'perform_time': '3 Hours 10 Minutes', + 'prep_time': '5 Minutes', 'recipe_id': '48f39d27-4b8e-4c14-bf36-4e1e6497e75e', 'recipe_yield': '6 servings', 'slug': 'all-american-beef-stew-recipe', + 'total_time': '3 Hours 15 Minutes', 'user_id': '1ce8b5fe-04e8-4b80-aab1-d92c94685c6d', }), dict({ @@ -417,9 +516,12 @@ 'image': '4Sys', 'name': "Serious Eats' Halal Cart-Style Chicken and Rice With White Sauce", 'original_url': 'https://www.seriouseats.com/serious-eats-halal-cart-style-chicken-and-rice-white-sauce-recipe', + 'perform_time': '55 Minutes', + 'prep_time': '20 Minutes', 'recipe_id': '6530ea6e-401e-4304-8a7a-12162ddf5b9c', 'recipe_yield': '4 servings', 'slug': 'serious-eats-halal-cart-style-chicken-and-rice-with-white-sauce', + 'total_time': '2 Hours 15 Minutes', 'user_id': '1ce8b5fe-04e8-4b80-aab1-d92c94685c6d', }), dict({ @@ -429,9 +531,12 @@ 'image': '8goY', 'name': 'Schnelle Käsespätzle', 'original_url': 'https://www.chefkoch.de/rezepte/1062121211526182/Schnelle-Kaesespaetzle.html', + 'perform_time': '30 Minutes', + 'prep_time': '10 Minutes', 'recipe_id': 'c496cf9c-1ece-448a-9d3f-ef772f078a4e', 'recipe_yield': '4 servings', 'slug': 'schnelle-kasespatzle', + 'total_time': '40 Minutes', 'user_id': '1ce8b5fe-04e8-4b80-aab1-d92c94685c6d', }), dict({ @@ -441,9 +546,12 @@ 'image': None, 'name': 'taco', 'original_url': None, + 'perform_time': None, + 'prep_time': None, 'recipe_id': '49aa6f42-6760-4adf-b6cd-59592da485c3', 'recipe_yield': None, 'slug': 'taco', + 'total_time': None, 'user_id': '1ce8b5fe-04e8-4b80-aab1-d92c94685c6d', }), dict({ @@ -453,9 +561,12 @@ 'image': 'z8BB', 'name': 'Vodkapasta', 'original_url': 'https://www.ica.se/recept/vodkapasta-729011/', + 'perform_time': None, + 'prep_time': None, 'recipe_id': '6402a253-2baa-460d-bf4f-b759bb655588', 'recipe_yield': '4 servings', 'slug': 'vodkapasta', + 'total_time': '30 Minutes', 'user_id': '1ce8b5fe-04e8-4b80-aab1-d92c94685c6d', }), dict({ @@ -465,9 +576,12 @@ 'image': 'Nqpz', 'name': 'Vodkapasta2', 'original_url': 'https://www.ica.se/recept/vodkapasta-729011/', + 'perform_time': None, + 'prep_time': None, 'recipe_id': '4f54e9e1-f21d-40ec-a135-91e633dfb733', 'recipe_yield': '4 servings', 'slug': 'vodkapasta2', + 'total_time': '30 Minutes', 'user_id': '1ce8b5fe-04e8-4b80-aab1-d92c94685c6d', }), dict({ @@ -477,9 +591,12 @@ 'image': None, 'name': 'Rub', 'original_url': None, + 'perform_time': None, + 'prep_time': None, 'recipe_id': 'e1a3edb0-49a0-49a3-83e3-95554e932670', 'recipe_yield': '1', 'slug': 'rub', + 'total_time': None, 'user_id': '1ce8b5fe-04e8-4b80-aab1-d92c94685c6d', }), dict({ @@ -489,9 +606,12 @@ 'image': '03XS', 'name': 'Banana Bread Chocolate Chip Cookies', 'original_url': 'https://www.justapinch.com/recipes/dessert/cookies/banana-bread-chocolate-chip-cookies.html', + 'perform_time': '15 Minutes', + 'prep_time': '10 Minutes', 'recipe_id': '1a0f4e54-db5b-40f1-ab7e-166dab5f6523', 'recipe_yield': '', 'slug': 'banana-bread-chocolate-chip-cookies', + 'total_time': None, 'user_id': '1ce8b5fe-04e8-4b80-aab1-d92c94685c6d', }), dict({ @@ -501,9 +621,12 @@ 'image': 'KuXV', 'name': 'Cauliflower Bisque Recipe with Cheddar Cheese', 'original_url': 'https://chefjeanpierre.com/recipes/soups/creamy-cauliflower-bisque/', + 'perform_time': None, + 'prep_time': None, 'recipe_id': '447acae6-3424-4c16-8c26-c09040ad8041', 'recipe_yield': '', 'slug': 'cauliflower-bisque-recipe-with-cheddar-cheese', + 'total_time': None, 'user_id': '1ce8b5fe-04e8-4b80-aab1-d92c94685c6d', }), dict({ @@ -513,9 +636,12 @@ 'image': None, 'name': 'Prova ', 'original_url': None, + 'perform_time': None, + 'prep_time': None, 'recipe_id': '864136a3-27b0-4f3b-a90f-486f42d6df7a', 'recipe_yield': '', 'slug': 'prova', + 'total_time': None, 'user_id': '1ce8b5fe-04e8-4b80-aab1-d92c94685c6d', }), dict({ @@ -525,9 +651,12 @@ 'image': None, 'name': 'pate au beurre (1)', 'original_url': None, + 'perform_time': None, + 'prep_time': None, 'recipe_id': 'c7ccf4c7-c5f4-4191-a79b-1a49d068f6a4', 'recipe_yield': None, 'slug': 'pate-au-beurre-1', + 'total_time': None, 'user_id': '1ce8b5fe-04e8-4b80-aab1-d92c94685c6d', }), dict({ @@ -537,9 +666,12 @@ 'image': None, 'name': 'pate au beurre', 'original_url': None, + 'perform_time': None, + 'prep_time': None, 'recipe_id': 'd01865c3-0f18-4e8d-84c0-c14c345fdf9c', 'recipe_yield': None, 'slug': 'pate-au-beurre', + 'total_time': None, 'user_id': '1ce8b5fe-04e8-4b80-aab1-d92c94685c6d', }), dict({ @@ -549,9 +681,12 @@ 'image': 'tmwm', 'name': 'Sous Vide Cheesecake Recipe', 'original_url': 'https://saltpepperskillet.com/recipes/sous-vide-cheesecake/', + 'perform_time': '1 Hour 30 Minutes', + 'prep_time': '10 Minutes', 'recipe_id': '2cec2bb2-19b6-40b8-a36c-1a76ea29c517', 'recipe_yield': '4 servings', 'slug': 'sous-vide-cheesecake-recipe', + 'total_time': '2 Hours 10 Minutes', 'user_id': '1ce8b5fe-04e8-4b80-aab1-d92c94685c6d', }), dict({ @@ -561,9 +696,12 @@ 'image': 'xCYc', 'name': 'The Bomb Mini Cheesecakes', 'original_url': 'https://recipes.anovaculinary.com/recipe/the-bomb-cheesecakes', + 'perform_time': None, + 'prep_time': '30 Minutes', 'recipe_id': '8e0e4566-9caf-4c2e-a01c-dcead23db86b', 'recipe_yield': '10 servings', 'slug': 'the-bomb-mini-cheesecakes', + 'total_time': '1 Hour 30 Minutes', 'user_id': '1ce8b5fe-04e8-4b80-aab1-d92c94685c6d', }), dict({ @@ -573,9 +711,12 @@ 'image': 'qzaN', 'name': 'Tagliatelle al Salmone', 'original_url': 'https://www.chefkoch.de/rezepte/2109501340136606/Tagliatelle-al-Salmone.html', + 'perform_time': '15 Minutes', + 'prep_time': '10 Minutes', 'recipe_id': 'a051eafd-9712-4aee-a8e5-0cd10a6772ee', 'recipe_yield': '4 servings', 'slug': 'tagliatelle-al-salmone', + 'total_time': '25 Minutes', 'user_id': '1ce8b5fe-04e8-4b80-aab1-d92c94685c6d', }), dict({ @@ -585,9 +726,12 @@ 'image': 'K9qP', 'name': 'Death by Chocolate', 'original_url': 'https://www.backenmachtgluecklich.de/rezepte/death-by-chocolate-kuchen.html', + 'perform_time': '25 Minutes', + 'prep_time': '25 Minutes', 'recipe_id': '093d51e9-0823-40ad-8e0e-a1d5790dd627', 'recipe_yield': '1 serving', 'slug': 'death-by-chocolate', + 'total_time': None, 'user_id': '1ce8b5fe-04e8-4b80-aab1-d92c94685c6d', }), dict({ @@ -597,9 +741,12 @@ 'image': 'jKQ3', 'name': 'Palak Dal Rezept aus Indien', 'original_url': 'https://www.fernweh-koch.de/palak-dal-indischer-spinat-linsen-rezept/', + 'perform_time': '20 Minutes', + 'prep_time': '10 Minutes', 'recipe_id': '2d1f62ec-4200-4cfd-987e-c75755d7607c', 'recipe_yield': '4 servings', 'slug': 'palak-dal-rezept-aus-indien', + 'total_time': '30 Minutes', 'user_id': '1ce8b5fe-04e8-4b80-aab1-d92c94685c6d', }), dict({ @@ -609,9 +756,12 @@ 'image': 'rkSn', 'name': 'Tortelline - á la Romana', 'original_url': 'https://www.chefkoch.de/rezepte/74441028021809/Tortelline-a-la-Romana.html', + 'perform_time': None, + 'prep_time': '30 Minutes', 'recipe_id': '973dc36d-1661-49b4-ad2d-0b7191034fb3', 'recipe_yield': '4 servings', 'slug': 'tortelline-a-la-romana', + 'total_time': '30 Minutes', 'user_id': '1ce8b5fe-04e8-4b80-aab1-d92c94685c6d', }), ]), @@ -629,9 +779,12 @@ 'image': None, 'name': 'tu6y', 'original_url': None, + 'perform_time': None, + 'prep_time': None, 'recipe_id': 'e82f5449-c33b-437c-b712-337587199264', 'recipe_yield': None, 'slug': 'tu6y', + 'total_time': None, 'user_id': '1ce8b5fe-04e8-4b80-aab1-d92c94685c6d', }), dict({ @@ -641,9 +794,12 @@ 'image': 'En9o', 'name': 'Εύκολη μακαρονάδα με κεφτεδάκια στον φούρνο (1)', 'original_url': 'https://akispetretzikis.com/recipe/7959/efkolh-makaronada-me-keftedakia-ston-fourno', + 'perform_time': '50 Minutes', + 'prep_time': '15 Minutes', 'recipe_id': 'f79f7e9d-4b58-4930-a586-2b127f16ee34', 'recipe_yield': '6 servings', 'slug': 'eukole-makaronada-me-kephtedakia-ston-phourno-1', + 'total_time': None, 'user_id': '1ce8b5fe-04e8-4b80-aab1-d92c94685c6d', }), dict({ @@ -653,9 +809,12 @@ 'image': 'aAhk', 'name': 'Patates douces au four (1)', 'original_url': 'https://www.papillesetpupilles.fr/2018/10/patates-douces-au-four.html/', + 'perform_time': None, + 'prep_time': None, 'recipe_id': '90097c8b-9d80-468a-b497-73957ac0cd8b', 'recipe_yield': '', 'slug': 'patates-douces-au-four-1', + 'total_time': None, 'user_id': '1ce8b5fe-04e8-4b80-aab1-d92c94685c6d', }), dict({ @@ -665,9 +824,12 @@ 'image': 'kdhm', 'name': 'Sweet potatoes', 'original_url': 'https://www.papillesetpupilles.fr/2018/10/patates-douces-au-four.html/', + 'perform_time': None, + 'prep_time': None, 'recipe_id': '98845807-9365-41fd-acd1-35630b468c27', 'recipe_yield': '', 'slug': 'sweet-potatoes', + 'total_time': None, 'user_id': '1ce8b5fe-04e8-4b80-aab1-d92c94685c6d', }), dict({ @@ -677,9 +839,12 @@ 'image': 'tNbG', 'name': 'Εύκολη μακαρονάδα με κεφτεδάκια στον φούρνο', 'original_url': 'https://akispetretzikis.com/recipe/7959/efkolh-makaronada-me-keftedakia-ston-fourno', + 'perform_time': '50 Minutes', + 'prep_time': '15 Minutes', 'recipe_id': '40c227e0-3c7e-41f7-866d-5de04eaecdd7', 'recipe_yield': '6 servings', 'slug': 'eukole-makaronada-me-kephtedakia-ston-phourno', + 'total_time': None, 'user_id': '1ce8b5fe-04e8-4b80-aab1-d92c94685c6d', }), dict({ @@ -689,9 +854,12 @@ 'image': 'nj5M', 'name': 'Boeuf bourguignon : la vraie recette (2)', 'original_url': 'https://www.marmiton.org/recettes/recette_boeuf-bourguignon_18889.aspx', + 'perform_time': '4 Hours', + 'prep_time': '1 Hour', 'recipe_id': '9c7b8aee-c93c-4b1b-ab48-2625d444743a', 'recipe_yield': '4 servings', 'slug': 'boeuf-bourguignon-la-vraie-recette-2', + 'total_time': '5 Hours', 'user_id': '1ce8b5fe-04e8-4b80-aab1-d92c94685c6d', }), dict({ @@ -701,9 +869,12 @@ 'image': 'rbU7', 'name': 'Boeuf bourguignon : la vraie recette (1)', 'original_url': 'https://www.marmiton.org/recettes/recette_boeuf-bourguignon_18889.aspx', + 'perform_time': '4 Hours', + 'prep_time': '1 Hour', 'recipe_id': 'fc42c7d1-7b0f-4e04-b88a-dbd80b81540b', 'recipe_yield': '4 servings', 'slug': 'boeuf-bourguignon-la-vraie-recette-1', + 'total_time': '5 Hours', 'user_id': '1ce8b5fe-04e8-4b80-aab1-d92c94685c6d', }), dict({ @@ -713,9 +884,12 @@ 'image': 'JSp3', 'name': 'Veganes Marmor-Bananenbrot mit Erdnussbutter', 'original_url': 'https://biancazapatka.com/de/erdnussbutter-schoko-bananenbrot/', + 'perform_time': '55 Minutes', + 'prep_time': '15 Minutes', 'recipe_id': '89e63d72-7a51-4cef-b162-2e45035d0a91', 'recipe_yield': '14 servings', 'slug': 'veganes-marmor-bananenbrot-mit-erdnussbutter', + 'total_time': None, 'user_id': '1ce8b5fe-04e8-4b80-aab1-d92c94685c6d', }), dict({ @@ -725,9 +899,12 @@ 'image': '9QMh', 'name': 'Pasta mit Tomaten, Knoblauch und Basilikum - einfach (und) genial! - Kuechenchaotin', 'original_url': 'https://kuechenchaotin.de/pasta-mit-tomaten-knoblauch-basilikum/', + 'perform_time': None, + 'prep_time': None, 'recipe_id': 'eab64457-97ba-4d6c-871c-cb1c724ccb51', 'recipe_yield': '', 'slug': 'pasta-mit-tomaten-knoblauch-und-basilikum-einfach-und-genial-kuechenchaotin', + 'total_time': None, 'user_id': '1ce8b5fe-04e8-4b80-aab1-d92c94685c6d', }), dict({ @@ -737,9 +914,12 @@ 'image': None, 'name': 'test123', 'original_url': None, + 'perform_time': None, + 'prep_time': None, 'recipe_id': '12439e3d-3c1c-4dcc-9c6e-4afcea2a0542', 'recipe_yield': None, 'slug': 'test123', + 'total_time': None, 'user_id': '1ce8b5fe-04e8-4b80-aab1-d92c94685c6d', }), dict({ @@ -749,9 +929,12 @@ 'image': None, 'name': 'Bureeto', 'original_url': None, + 'perform_time': None, + 'prep_time': None, 'recipe_id': '6567f6ec-e410-49cb-a1a5-d08517184e78', 'recipe_yield': None, 'slug': 'bureeto', + 'total_time': None, 'user_id': '1ce8b5fe-04e8-4b80-aab1-d92c94685c6d', }), dict({ @@ -761,9 +944,12 @@ 'image': None, 'name': 'Subway Double Cookies', 'original_url': None, + 'perform_time': None, + 'prep_time': None, 'recipe_id': 'f7737d17-161c-4008-88d4-dd2616778cd0', 'recipe_yield': None, 'slug': 'subway-double-cookies', + 'total_time': None, 'user_id': '1ce8b5fe-04e8-4b80-aab1-d92c94685c6d', }), dict({ @@ -773,9 +959,12 @@ 'image': None, 'name': 'qwerty12345', 'original_url': None, + 'perform_time': None, + 'prep_time': None, 'recipe_id': '1904b717-4a8b-4de9-8909-56958875b5f4', 'recipe_yield': None, 'slug': 'qwerty12345', + 'total_time': None, 'user_id': '1ce8b5fe-04e8-4b80-aab1-d92c94685c6d', }), dict({ @@ -785,9 +974,12 @@ 'image': 'beGq', 'name': 'Cheeseburger Sliders (Easy, 30-min Recipe)', 'original_url': 'https://natashaskitchen.com/cheeseburger-sliders/', + 'perform_time': '22 Minutes', + 'prep_time': '8 Minutes', 'recipe_id': '8bdd3656-5e7e-45d3-a3c4-557390846a22', 'recipe_yield': '24 servings', 'slug': 'cheeseburger-sliders-easy-30-min-recipe', + 'total_time': '30 Minutes', 'user_id': '1ce8b5fe-04e8-4b80-aab1-d92c94685c6d', }), dict({ @@ -797,9 +989,12 @@ 'image': None, 'name': 'meatloaf', 'original_url': None, + 'perform_time': None, + 'prep_time': None, 'recipe_id': '8a30d31d-aa14-411e-af0c-6b61a94f5291', 'recipe_yield': '4', 'slug': 'meatloaf', + 'total_time': None, 'user_id': '1ce8b5fe-04e8-4b80-aab1-d92c94685c6d', }), dict({ @@ -809,9 +1004,12 @@ 'image': 'kCBh', 'name': 'Richtig rheinischer Sauerbraten', 'original_url': 'https://www.chefkoch.de/rezepte/937641199437984/Richtig-rheinischer-Sauerbraten.html', + 'perform_time': '2 Hours 20 Minutes', + 'prep_time': '1 Hour', 'recipe_id': 'f2f7880b-1136-436f-91b7-129788d8c117', 'recipe_yield': '4 servings', 'slug': 'richtig-rheinischer-sauerbraten', + 'total_time': '3 Hours 20 Minutes', 'user_id': '1ce8b5fe-04e8-4b80-aab1-d92c94685c6d', }), dict({ @@ -821,9 +1019,12 @@ 'image': 'kpBx', 'name': 'Orientalischer Gemüse-Hähnchen Eintopf', 'original_url': 'https://www.chefkoch.de/rezepte/2307761368177614/Orientalischer-Gemuese-Haehnchen-Eintopf.html', + 'perform_time': '20 Minutes', + 'prep_time': '15 Minutes', 'recipe_id': 'cf634591-0f82-4254-8e00-2f7e8b0c9022', 'recipe_yield': '6 servings', 'slug': 'orientalischer-gemuse-hahnchen-eintopf', + 'total_time': '35 Minutes', 'user_id': '1ce8b5fe-04e8-4b80-aab1-d92c94685c6d', }), dict({ @@ -833,9 +1034,12 @@ 'image': None, 'name': 'test 20240121', 'original_url': None, + 'perform_time': None, + 'prep_time': None, 'recipe_id': '05208856-d273-4cc9-bcfa-e0215d57108d', 'recipe_yield': '4', 'slug': 'test-20240121', + 'total_time': None, 'user_id': '1ce8b5fe-04e8-4b80-aab1-d92c94685c6d', }), dict({ @@ -845,9 +1049,12 @@ 'image': 'McEx', 'name': 'Loempia bowl', 'original_url': 'https://www.lekkerensimpel.com/loempia-bowl/', + 'perform_time': None, + 'prep_time': None, 'recipe_id': '145eeb05-781a-4eb0-a656-afa8bc8c0164', 'recipe_yield': '', 'slug': 'loempia-bowl', + 'total_time': None, 'user_id': '1ce8b5fe-04e8-4b80-aab1-d92c94685c6d', }), dict({ @@ -857,9 +1064,12 @@ 'image': 'bzqo', 'name': '5 Ingredient Chocolate Mousse', 'original_url': 'https://thehappypear.ie/aquafaba-chocolate-mousse/', + 'perform_time': None, + 'prep_time': '10 Minutes', 'recipe_id': '5c6532aa-ad84-424c-bc05-c32d50430fe4', 'recipe_yield': '6 servings', 'slug': '5-ingredient-chocolate-mousse', + 'total_time': None, 'user_id': '1ce8b5fe-04e8-4b80-aab1-d92c94685c6d', }), dict({ @@ -869,9 +1079,12 @@ 'image': 'KGK6', 'name': 'Der perfekte Pfannkuchen - gelingt einfach immer', 'original_url': 'https://www.chefkoch.de/rezepte/1208161226570428/Der-perfekte-Pfannkuchen-gelingt-einfach-immer.html', + 'perform_time': '10 Minutes', + 'prep_time': '5 Minutes', 'recipe_id': 'f2e684f2-49e0-45ee-90de-951344472f1c', 'recipe_yield': '4 servings', 'slug': 'der-perfekte-pfannkuchen-gelingt-einfach-immer', + 'total_time': '15 Minutes', 'user_id': '1ce8b5fe-04e8-4b80-aab1-d92c94685c6d', }), dict({ @@ -881,9 +1094,12 @@ 'image': 'yNDq', 'name': 'Dinkel-Sauerteigbrot', 'original_url': 'https://www.besondersgut.ch/dinkel-sauerteigbrot/', + 'perform_time': '35min', + 'prep_time': '1h', 'recipe_id': 'cf239441-b75d-4dea-a48e-9d99b7cb5842', 'recipe_yield': '1', 'slug': 'dinkel-sauerteigbrot', + 'total_time': '24h', 'user_id': '1ce8b5fe-04e8-4b80-aab1-d92c94685c6d', }), dict({ @@ -893,9 +1109,12 @@ 'image': None, 'name': 'test 234234', 'original_url': None, + 'perform_time': None, + 'prep_time': None, 'recipe_id': '2673eb90-6d78-4b95-af36-5db8c8a6da37', 'recipe_yield': None, 'slug': 'test-234234', + 'total_time': None, 'user_id': '1ce8b5fe-04e8-4b80-aab1-d92c94685c6d', }), dict({ @@ -905,9 +1124,12 @@ 'image': None, 'name': 'test 243', 'original_url': None, + 'perform_time': None, + 'prep_time': None, 'recipe_id': '0a723c54-af53-40e9-a15f-c87aae5ac688', 'recipe_yield': None, 'slug': 'test-243', + 'total_time': None, 'user_id': '1ce8b5fe-04e8-4b80-aab1-d92c94685c6d', }), dict({ @@ -917,9 +1139,12 @@ 'image': 'nOPT', 'name': 'Einfacher Nudelauflauf mit Brokkoli', 'original_url': 'https://kochkarussell.com/einfacher-nudelauflauf-brokkoli/', + 'perform_time': '20 Minutes', + 'prep_time': '15 Minutes', 'recipe_id': '9d553779-607e-471b-acf3-84e6be27b159', 'recipe_yield': '4 servings', 'slug': 'einfacher-nudelauflauf-mit-brokkoli', + 'total_time': '35 Minutes', 'user_id': '1ce8b5fe-04e8-4b80-aab1-d92c94685c6d', }), dict({ @@ -940,9 +1165,12 @@ 'image': 'vxuL', 'name': 'Tarta cytrynowa z bezą', 'original_url': 'https://www.przepisy.pl/przepis/tarta-cytrynowa-z-beza', + 'perform_time': None, + 'prep_time': '1 Hour', 'recipe_id': '9d3cb303-a996-4144-948a-36afaeeef554', 'recipe_yield': '8 servings', 'slug': 'tarta-cytrynowa-z-beza', + 'total_time': '1 Hour', 'user_id': '1ce8b5fe-04e8-4b80-aab1-d92c94685c6d', }), dict({ @@ -952,9 +1180,12 @@ 'image': None, 'name': 'Martins test Recipe', 'original_url': None, + 'perform_time': None, + 'prep_time': None, 'recipe_id': '77f05a49-e869-4048-aa62-0d8a1f5a8f1c', 'recipe_yield': None, 'slug': 'martins-test-recipe', + 'total_time': None, 'user_id': '1ce8b5fe-04e8-4b80-aab1-d92c94685c6d', }), dict({ @@ -964,9 +1195,12 @@ 'image': 'xP1Q', 'name': 'Muffinki czekoladowe', 'original_url': 'https://aniagotuje.pl/przepis/muffinki-czekoladowe', + 'perform_time': '30 Minutes', + 'prep_time': '25 Minutes', 'recipe_id': '75a90207-9c10-4390-a265-c47a4b67fd69', 'recipe_yield': '12', 'slug': 'muffinki-czekoladowe', + 'total_time': None, 'user_id': '1ce8b5fe-04e8-4b80-aab1-d92c94685c6d', }), dict({ @@ -976,9 +1210,12 @@ 'image': None, 'name': 'My Test Recipe', 'original_url': None, + 'perform_time': None, + 'prep_time': None, 'recipe_id': '4320ba72-377b-4657-8297-dce198f24cdf', 'recipe_yield': None, 'slug': 'my-test-recipe', + 'total_time': None, 'user_id': '1ce8b5fe-04e8-4b80-aab1-d92c94685c6d', }), dict({ @@ -988,9 +1225,12 @@ 'image': None, 'name': 'My Test Receipe', 'original_url': None, + 'perform_time': None, + 'prep_time': None, 'recipe_id': '98dac844-31ee-426a-b16c-fb62a5dd2816', 'recipe_yield': None, 'slug': 'my-test-receipe', + 'total_time': None, 'user_id': '1ce8b5fe-04e8-4b80-aab1-d92c94685c6d', }), dict({ @@ -1000,9 +1240,12 @@ 'image': 'r1ck', 'name': 'Patates douces au four', 'original_url': 'https://www.papillesetpupilles.fr/2018/10/patates-douces-au-four.html/', + 'perform_time': None, + 'prep_time': None, 'recipe_id': 'c3c8f207-c704-415d-81b1-da9f032cf52f', 'recipe_yield': '', 'slug': 'patates-douces-au-four', + 'total_time': None, 'user_id': '1ce8b5fe-04e8-4b80-aab1-d92c94685c6d', }), dict({ @@ -1012,9 +1255,12 @@ 'image': 'gD94', 'name': 'Easy Homemade Pizza Dough', 'original_url': 'https://sallysbakingaddiction.com/homemade-pizza-crust-recipe/', + 'perform_time': '15 Minutes', + 'prep_time': '2 Hours 15 Minutes', 'recipe_id': '1edb2f6e-133c-4be0-b516-3c23625a97ec', 'recipe_yield': '2 servings', 'slug': 'easy-homemade-pizza-dough', + 'total_time': '2 Hours 30 Minutes', 'user_id': '1ce8b5fe-04e8-4b80-aab1-d92c94685c6d', }), dict({ @@ -1024,9 +1270,12 @@ 'image': '356X', 'name': 'All-American Beef Stew Recipe', 'original_url': 'https://www.seriouseats.com/all-american-beef-stew-recipe', + 'perform_time': '3 Hours 10 Minutes', + 'prep_time': '5 Minutes', 'recipe_id': '48f39d27-4b8e-4c14-bf36-4e1e6497e75e', 'recipe_yield': '6 servings', 'slug': 'all-american-beef-stew-recipe', + 'total_time': '3 Hours 15 Minutes', 'user_id': '1ce8b5fe-04e8-4b80-aab1-d92c94685c6d', }), dict({ @@ -1036,9 +1285,12 @@ 'image': '4Sys', 'name': "Serious Eats' Halal Cart-Style Chicken and Rice With White Sauce", 'original_url': 'https://www.seriouseats.com/serious-eats-halal-cart-style-chicken-and-rice-white-sauce-recipe', + 'perform_time': '55 Minutes', + 'prep_time': '20 Minutes', 'recipe_id': '6530ea6e-401e-4304-8a7a-12162ddf5b9c', 'recipe_yield': '4 servings', 'slug': 'serious-eats-halal-cart-style-chicken-and-rice-with-white-sauce', + 'total_time': '2 Hours 15 Minutes', 'user_id': '1ce8b5fe-04e8-4b80-aab1-d92c94685c6d', }), dict({ @@ -1048,9 +1300,12 @@ 'image': '8goY', 'name': 'Schnelle Käsespätzle', 'original_url': 'https://www.chefkoch.de/rezepte/1062121211526182/Schnelle-Kaesespaetzle.html', + 'perform_time': '30 Minutes', + 'prep_time': '10 Minutes', 'recipe_id': 'c496cf9c-1ece-448a-9d3f-ef772f078a4e', 'recipe_yield': '4 servings', 'slug': 'schnelle-kasespatzle', + 'total_time': '40 Minutes', 'user_id': '1ce8b5fe-04e8-4b80-aab1-d92c94685c6d', }), dict({ @@ -1060,9 +1315,12 @@ 'image': None, 'name': 'taco', 'original_url': None, + 'perform_time': None, + 'prep_time': None, 'recipe_id': '49aa6f42-6760-4adf-b6cd-59592da485c3', 'recipe_yield': None, 'slug': 'taco', + 'total_time': None, 'user_id': '1ce8b5fe-04e8-4b80-aab1-d92c94685c6d', }), dict({ @@ -1072,9 +1330,12 @@ 'image': 'z8BB', 'name': 'Vodkapasta', 'original_url': 'https://www.ica.se/recept/vodkapasta-729011/', + 'perform_time': None, + 'prep_time': None, 'recipe_id': '6402a253-2baa-460d-bf4f-b759bb655588', 'recipe_yield': '4 servings', 'slug': 'vodkapasta', + 'total_time': '30 Minutes', 'user_id': '1ce8b5fe-04e8-4b80-aab1-d92c94685c6d', }), dict({ @@ -1084,9 +1345,12 @@ 'image': 'Nqpz', 'name': 'Vodkapasta2', 'original_url': 'https://www.ica.se/recept/vodkapasta-729011/', + 'perform_time': None, + 'prep_time': None, 'recipe_id': '4f54e9e1-f21d-40ec-a135-91e633dfb733', 'recipe_yield': '4 servings', 'slug': 'vodkapasta2', + 'total_time': '30 Minutes', 'user_id': '1ce8b5fe-04e8-4b80-aab1-d92c94685c6d', }), dict({ @@ -1096,9 +1360,12 @@ 'image': None, 'name': 'Rub', 'original_url': None, + 'perform_time': None, + 'prep_time': None, 'recipe_id': 'e1a3edb0-49a0-49a3-83e3-95554e932670', 'recipe_yield': '1', 'slug': 'rub', + 'total_time': None, 'user_id': '1ce8b5fe-04e8-4b80-aab1-d92c94685c6d', }), dict({ @@ -1108,9 +1375,12 @@ 'image': '03XS', 'name': 'Banana Bread Chocolate Chip Cookies', 'original_url': 'https://www.justapinch.com/recipes/dessert/cookies/banana-bread-chocolate-chip-cookies.html', + 'perform_time': '15 Minutes', + 'prep_time': '10 Minutes', 'recipe_id': '1a0f4e54-db5b-40f1-ab7e-166dab5f6523', 'recipe_yield': '', 'slug': 'banana-bread-chocolate-chip-cookies', + 'total_time': None, 'user_id': '1ce8b5fe-04e8-4b80-aab1-d92c94685c6d', }), dict({ @@ -1120,9 +1390,12 @@ 'image': 'KuXV', 'name': 'Cauliflower Bisque Recipe with Cheddar Cheese', 'original_url': 'https://chefjeanpierre.com/recipes/soups/creamy-cauliflower-bisque/', + 'perform_time': None, + 'prep_time': None, 'recipe_id': '447acae6-3424-4c16-8c26-c09040ad8041', 'recipe_yield': '', 'slug': 'cauliflower-bisque-recipe-with-cheddar-cheese', + 'total_time': None, 'user_id': '1ce8b5fe-04e8-4b80-aab1-d92c94685c6d', }), dict({ @@ -1132,9 +1405,12 @@ 'image': None, 'name': 'Prova ', 'original_url': None, + 'perform_time': None, + 'prep_time': None, 'recipe_id': '864136a3-27b0-4f3b-a90f-486f42d6df7a', 'recipe_yield': '', 'slug': 'prova', + 'total_time': None, 'user_id': '1ce8b5fe-04e8-4b80-aab1-d92c94685c6d', }), dict({ @@ -1144,9 +1420,12 @@ 'image': None, 'name': 'pate au beurre (1)', 'original_url': None, + 'perform_time': None, + 'prep_time': None, 'recipe_id': 'c7ccf4c7-c5f4-4191-a79b-1a49d068f6a4', 'recipe_yield': None, 'slug': 'pate-au-beurre-1', + 'total_time': None, 'user_id': '1ce8b5fe-04e8-4b80-aab1-d92c94685c6d', }), dict({ @@ -1156,9 +1435,12 @@ 'image': None, 'name': 'pate au beurre', 'original_url': None, + 'perform_time': None, + 'prep_time': None, 'recipe_id': 'd01865c3-0f18-4e8d-84c0-c14c345fdf9c', 'recipe_yield': None, 'slug': 'pate-au-beurre', + 'total_time': None, 'user_id': '1ce8b5fe-04e8-4b80-aab1-d92c94685c6d', }), dict({ @@ -1168,9 +1450,12 @@ 'image': 'tmwm', 'name': 'Sous Vide Cheesecake Recipe', 'original_url': 'https://saltpepperskillet.com/recipes/sous-vide-cheesecake/', + 'perform_time': '1 Hour 30 Minutes', + 'prep_time': '10 Minutes', 'recipe_id': '2cec2bb2-19b6-40b8-a36c-1a76ea29c517', 'recipe_yield': '4 servings', 'slug': 'sous-vide-cheesecake-recipe', + 'total_time': '2 Hours 10 Minutes', 'user_id': '1ce8b5fe-04e8-4b80-aab1-d92c94685c6d', }), dict({ @@ -1180,9 +1465,12 @@ 'image': 'xCYc', 'name': 'The Bomb Mini Cheesecakes', 'original_url': 'https://recipes.anovaculinary.com/recipe/the-bomb-cheesecakes', + 'perform_time': None, + 'prep_time': '30 Minutes', 'recipe_id': '8e0e4566-9caf-4c2e-a01c-dcead23db86b', 'recipe_yield': '10 servings', 'slug': 'the-bomb-mini-cheesecakes', + 'total_time': '1 Hour 30 Minutes', 'user_id': '1ce8b5fe-04e8-4b80-aab1-d92c94685c6d', }), dict({ @@ -1192,9 +1480,12 @@ 'image': 'qzaN', 'name': 'Tagliatelle al Salmone', 'original_url': 'https://www.chefkoch.de/rezepte/2109501340136606/Tagliatelle-al-Salmone.html', + 'perform_time': '15 Minutes', + 'prep_time': '10 Minutes', 'recipe_id': 'a051eafd-9712-4aee-a8e5-0cd10a6772ee', 'recipe_yield': '4 servings', 'slug': 'tagliatelle-al-salmone', + 'total_time': '25 Minutes', 'user_id': '1ce8b5fe-04e8-4b80-aab1-d92c94685c6d', }), dict({ @@ -1204,9 +1495,12 @@ 'image': 'K9qP', 'name': 'Death by Chocolate', 'original_url': 'https://www.backenmachtgluecklich.de/rezepte/death-by-chocolate-kuchen.html', + 'perform_time': '25 Minutes', + 'prep_time': '25 Minutes', 'recipe_id': '093d51e9-0823-40ad-8e0e-a1d5790dd627', 'recipe_yield': '1 serving', 'slug': 'death-by-chocolate', + 'total_time': None, 'user_id': '1ce8b5fe-04e8-4b80-aab1-d92c94685c6d', }), dict({ @@ -1216,9 +1510,12 @@ 'image': 'jKQ3', 'name': 'Palak Dal Rezept aus Indien', 'original_url': 'https://www.fernweh-koch.de/palak-dal-indischer-spinat-linsen-rezept/', + 'perform_time': '20 Minutes', + 'prep_time': '10 Minutes', 'recipe_id': '2d1f62ec-4200-4cfd-987e-c75755d7607c', 'recipe_yield': '4 servings', 'slug': 'palak-dal-rezept-aus-indien', + 'total_time': '30 Minutes', 'user_id': '1ce8b5fe-04e8-4b80-aab1-d92c94685c6d', }), dict({ @@ -1228,9 +1525,12 @@ 'image': 'rkSn', 'name': 'Tortelline - á la Romana', 'original_url': 'https://www.chefkoch.de/rezepte/74441028021809/Tortelline-a-la-Romana.html', + 'perform_time': None, + 'prep_time': '30 Minutes', 'recipe_id': '973dc36d-1661-49b4-ad2d-0b7191034fb3', 'recipe_yield': '4 servings', 'slug': 'tortelline-a-la-romana', + 'total_time': '30 Minutes', 'user_id': '1ce8b5fe-04e8-4b80-aab1-d92c94685c6d', }), ]), @@ -1384,6 +1684,8 @@ ]), 'name': 'Original Sacher-Torte (2)', 'original_url': 'https://www.sacher.com/en/original-sacher-torte/recipe/', + 'perform_time': '1 hour', + 'prep_time': '1 hour 30 minutes', 'recipe_id': 'fada9582-709b-46aa-b384-d5952123ad93', 'recipe_yield': '4 servings', 'slug': 'original-sacher-torte-2', @@ -1424,6 +1726,7 @@ 'tag_id': 'd530b8e4-275a-4093-804b-6d0de154c206', }), ]), + 'total_time': '2 hours 30 minutes', 'user_id': 'bf1c62fe-4941-4332-9886-e54e88dbdba0', }), }) @@ -1445,9 +1748,12 @@ 'image': 'AiIo', 'name': 'Zoete aardappel curry traybake', 'original_url': 'https://chickslovefood.com/recept/zoete-aardappel-curry-traybake/', + 'perform_time': None, + 'prep_time': None, 'recipe_id': 'c5f00a93-71a2-4e48-900f-d9ad0bb9de93', 'recipe_yield': '2 servings', 'slug': 'zoete-aardappel-curry-traybake', + 'total_time': '40 Minutes', 'user_id': '1ce8b5fe-04e8-4b80-aab1-d92c94685c6d', }), 'title': None, @@ -1467,9 +1773,12 @@ 'image': 'JeQ2', 'name': 'Roast Chicken', 'original_url': 'https://tastesbetterfromscratch.com/roast-chicken/', + 'perform_time': '1 Hour 20 Minutes', + 'prep_time': '15 Minutes', 'recipe_id': '5b055066-d57d-4fd0-8dfd-a2c2f07b36f1', 'recipe_yield': '6 servings', 'slug': 'roast-chicken', + 'total_time': '1 Hour 35 Minutes', 'user_id': '1ce8b5fe-04e8-4b80-aab1-d92c94685c6d', }), 'title': None, @@ -1489,9 +1798,12 @@ 'image': 'INQz', 'name': 'Receta de pollo al curry en 10 minutos (con vídeo incluido)', 'original_url': 'https://www.directoalpaladar.com/recetas-de-carnes-y-aves/receta-de-pollo-al-curry-en-10-minutos', + 'perform_time': '7 Minutes', + 'prep_time': '3 Minutes', 'recipe_id': 'e360a0cc-18b0-4a84-a91b-8aa59e2451c9', 'recipe_yield': '2 servings', 'slug': 'receta-de-pollo-al-curry-en-10-minutos-con-video-incluido', + 'total_time': '10 Minutes', 'user_id': '1ce8b5fe-04e8-4b80-aab1-d92c94685c6d', }), 'title': None, @@ -1511,9 +1823,12 @@ 'image': 'nj5M', 'name': 'Boeuf bourguignon : la vraie recette (2)', 'original_url': 'https://www.marmiton.org/recettes/recette_boeuf-bourguignon_18889.aspx', + 'perform_time': '4 Hours', + 'prep_time': '1 Hour', 'recipe_id': '9c7b8aee-c93c-4b1b-ab48-2625d444743a', 'recipe_yield': '4 servings', 'slug': 'boeuf-bourguignon-la-vraie-recette-2', + 'total_time': '5 Hours', 'user_id': '1ce8b5fe-04e8-4b80-aab1-d92c94685c6d', }), 'title': None, @@ -1533,9 +1848,12 @@ 'image': 'En9o', 'name': 'Εύκολη μακαρονάδα με κεφτεδάκια στον φούρνο (1)', 'original_url': 'https://akispetretzikis.com/recipe/7959/efkolh-makaronada-me-keftedakia-ston-fourno', + 'perform_time': '50 Minutes', + 'prep_time': '15 Minutes', 'recipe_id': 'f79f7e9d-4b58-4930-a586-2b127f16ee34', 'recipe_yield': '6 servings', 'slug': 'eukole-makaronada-me-kephtedakia-ston-phourno-1', + 'total_time': None, 'user_id': '1ce8b5fe-04e8-4b80-aab1-d92c94685c6d', }), 'title': None, @@ -1555,9 +1873,12 @@ 'image': 'Kn62', 'name': 'Greek Turkey Meatballs with Lemon Orzo & Creamy Feta Yogurt Sauce', 'original_url': 'https://www.ambitiouskitchen.com/greek-turkey-meatballs/', + 'perform_time': '20 Minutes', + 'prep_time': '40 Minutes', 'recipe_id': '47595e4c-52bc-441d-b273-3edf4258806d', 'recipe_yield': '4 servings', 'slug': 'greek-turkey-meatballs-with-lemon-orzo-creamy-feta-yogurt-sauce', + 'total_time': '1 Hour', 'user_id': '1ce8b5fe-04e8-4b80-aab1-d92c94685c6d', }), 'title': None, @@ -1577,9 +1898,12 @@ 'image': 'nOPT', 'name': 'Einfacher Nudelauflauf mit Brokkoli', 'original_url': 'https://kochkarussell.com/einfacher-nudelauflauf-brokkoli/', + 'perform_time': '20 Minutes', + 'prep_time': '15 Minutes', 'recipe_id': '9d553779-607e-471b-acf3-84e6be27b159', 'recipe_yield': '4 servings', 'slug': 'einfacher-nudelauflauf-mit-brokkoli', + 'total_time': '35 Minutes', 'user_id': '1ce8b5fe-04e8-4b80-aab1-d92c94685c6d', }), 'title': None, @@ -1599,9 +1923,12 @@ 'image': 'ibL6', 'name': 'Pampered Chef Double Chocolate Mocha Trifle', 'original_url': 'https://www.food.com/recipe/pampered-chef-double-chocolate-mocha-trifle-74963', + 'perform_time': '1 Hour', + 'prep_time': '15 Minutes', 'recipe_id': '92635fd0-f2dc-4e78-a6e4-ecd556ad361f', 'recipe_yield': '12 servings', 'slug': 'pampered-chef-double-chocolate-mocha-trifle', + 'total_time': '1 Hour 15 Minutes', 'user_id': '1ce8b5fe-04e8-4b80-aab1-d92c94685c6d', }), 'title': None, @@ -1621,9 +1948,12 @@ 'image': 'beGq', 'name': 'Cheeseburger Sliders (Easy, 30-min Recipe)', 'original_url': 'https://natashaskitchen.com/cheeseburger-sliders/', + 'perform_time': '22 Minutes', + 'prep_time': '8 Minutes', 'recipe_id': '8bdd3656-5e7e-45d3-a3c4-557390846a22', 'recipe_yield': '24 servings', 'slug': 'cheeseburger-sliders-easy-30-min-recipe', + 'total_time': '30 Minutes', 'user_id': '1ce8b5fe-04e8-4b80-aab1-d92c94685c6d', }), 'title': None, @@ -1643,9 +1973,12 @@ 'image': '356X', 'name': 'All-American Beef Stew Recipe', 'original_url': 'https://www.seriouseats.com/all-american-beef-stew-recipe', + 'perform_time': '3 Hours 10 Minutes', + 'prep_time': '5 Minutes', 'recipe_id': '48f39d27-4b8e-4c14-bf36-4e1e6497e75e', 'recipe_yield': '6 servings', 'slug': 'all-american-beef-stew-recipe', + 'total_time': '3 Hours 15 Minutes', 'user_id': '1ce8b5fe-04e8-4b80-aab1-d92c94685c6d', }), 'title': None, @@ -1665,9 +1998,12 @@ 'image': '356X', 'name': 'All-American Beef Stew Recipe', 'original_url': 'https://www.seriouseats.com/all-american-beef-stew-recipe', + 'perform_time': '3 Hours 10 Minutes', + 'prep_time': '5 Minutes', 'recipe_id': '48f39d27-4b8e-4c14-bf36-4e1e6497e75e', 'recipe_yield': '6 servings', 'slug': 'all-american-beef-stew-recipe', + 'total_time': '3 Hours 15 Minutes', 'user_id': '1ce8b5fe-04e8-4b80-aab1-d92c94685c6d', }), 'title': None, @@ -1687,9 +2023,12 @@ 'image': 'nOPT', 'name': 'Einfacher Nudelauflauf mit Brokkoli', 'original_url': 'https://kochkarussell.com/einfacher-nudelauflauf-brokkoli/', + 'perform_time': '20 Minutes', + 'prep_time': '15 Minutes', 'recipe_id': '9d553779-607e-471b-acf3-84e6be27b159', 'recipe_yield': '4 servings', 'slug': 'einfacher-nudelauflauf-mit-brokkoli', + 'total_time': '35 Minutes', 'user_id': '1ce8b5fe-04e8-4b80-aab1-d92c94685c6d', }), 'title': None, @@ -1709,9 +2048,12 @@ 'image': '5G1v', 'name': 'Miso Udon Noodles with Spinach and Tofu', 'original_url': 'https://www.allrecipes.com/recipe/284039/miso-udon-noodles-with-spinach-and-tofu/', + 'perform_time': '15 Minutes', + 'prep_time': '10 Minutes', 'recipe_id': '25b814f2-d9bf-4df0-b40d-d2f2457b4317', 'recipe_yield': '2 servings', 'slug': 'miso-udon-noodles-with-spinach-and-tofu', + 'total_time': '25 Minutes', 'user_id': '1ce8b5fe-04e8-4b80-aab1-d92c94685c6d', }), 'title': None, @@ -1731,9 +2073,12 @@ 'image': 'rrNL', 'name': 'Mousse de saumon', 'original_url': 'https://www.ricardocuisine.com/recettes/8919-mousse-de-saumon', + 'perform_time': '2 Minutes', + 'prep_time': '15 Minutes', 'recipe_id': '55c88810-4cf1-4d86-ae50-63b15fd173fb', 'recipe_yield': '12 servings', 'slug': 'mousse-de-saumon', + 'total_time': '17 Minutes', 'user_id': '1ce8b5fe-04e8-4b80-aab1-d92c94685c6d', }), 'title': None, @@ -1900,6 +2245,8 @@ ]), 'name': 'Original Sacher-Torte (2)', 'original_url': 'https://www.sacher.com/en/original-sacher-torte/recipe/', + 'perform_time': '1 hour', + 'prep_time': '1 hour 30 minutes', 'recipe_id': 'fada9582-709b-46aa-b384-d5952123ad93', 'recipe_yield': '4 servings', 'slug': 'original-sacher-torte-2', @@ -1940,6 +2287,7 @@ 'tag_id': 'd530b8e4-275a-4093-804b-6d0de154c206', }), ]), + 'total_time': '2 hours 30 minutes', 'user_id': 'bf1c62fe-4941-4332-9886-e54e88dbdba0', }), }) @@ -1960,9 +2308,12 @@ 'image': 'AiIo', 'name': 'Zoete aardappel curry traybake', 'original_url': 'https://chickslovefood.com/recept/zoete-aardappel-curry-traybake/', + 'perform_time': None, + 'prep_time': None, 'recipe_id': 'c5f00a93-71a2-4e48-900f-d9ad0bb9de93', 'recipe_yield': '2 servings', 'slug': 'zoete-aardappel-curry-traybake', + 'total_time': '40 Minutes', 'user_id': '1ce8b5fe-04e8-4b80-aab1-d92c94685c6d', }), 'title': None, @@ -1986,9 +2337,12 @@ 'image': 'AiIo', 'name': 'Zoete aardappel curry traybake', 'original_url': 'https://chickslovefood.com/recept/zoete-aardappel-curry-traybake/', + 'perform_time': None, + 'prep_time': None, 'recipe_id': 'c5f00a93-71a2-4e48-900f-d9ad0bb9de93', 'recipe_yield': '2 servings', 'slug': 'zoete-aardappel-curry-traybake', + 'total_time': '40 Minutes', 'user_id': '1ce8b5fe-04e8-4b80-aab1-d92c94685c6d', }), 'title': None, @@ -2012,9 +2366,12 @@ 'image': 'AiIo', 'name': 'Zoete aardappel curry traybake', 'original_url': 'https://chickslovefood.com/recept/zoete-aardappel-curry-traybake/', + 'perform_time': None, + 'prep_time': None, 'recipe_id': 'c5f00a93-71a2-4e48-900f-d9ad0bb9de93', 'recipe_yield': '2 servings', 'slug': 'zoete-aardappel-curry-traybake', + 'total_time': '40 Minutes', 'user_id': '1ce8b5fe-04e8-4b80-aab1-d92c94685c6d', }), 'title': None, @@ -2038,9 +2395,12 @@ 'image': 'AiIo', 'name': 'Zoete aardappel curry traybake', 'original_url': 'https://chickslovefood.com/recept/zoete-aardappel-curry-traybake/', + 'perform_time': None, + 'prep_time': None, 'recipe_id': 'c5f00a93-71a2-4e48-900f-d9ad0bb9de93', 'recipe_yield': '2 servings', 'slug': 'zoete-aardappel-curry-traybake', + 'total_time': '40 Minutes', 'user_id': '1ce8b5fe-04e8-4b80-aab1-d92c94685c6d', }), 'title': None, From 1289a031abb8de7541e9caf52be322ca2a3493a7 Mon Sep 17 00:00:00 2001 From: Maciej Bieniek Date: Mon, 29 Sep 2025 12:06:07 +0200 Subject: [PATCH 02/13] Add `consumed energy` sensor for Shelly `pm1` and `switch` components (#153053) --- homeassistant/components/shelly/sensor.py | 50 ++- .../shelly/snapshots/test_devices.ambr | 352 ++++++++++++------ .../shelly/snapshots/test_sensor.ambr | 75 +++- tests/components/shelly/test_sensor.py | 78 +++- 4 files changed, 426 insertions(+), 129 deletions(-) diff --git a/homeassistant/components/shelly/sensor.py b/homeassistant/components/shelly/sensor.py index 08a527591e01c3..ced5f46be3a83f 100644 --- a/homeassistant/components/shelly/sensor.py +++ b/homeassistant/components/shelly/sensor.py @@ -122,6 +122,23 @@ def native_value(self) -> StateType: return self.option_map[attribute_value] +class RpcConsumedEnergySensor(RpcSensor): + """Represent a RPC sensor.""" + + @property + def native_value(self) -> StateType: + """Return value of sensor.""" + total_energy = self.status["aenergy"]["total"] + + if not isinstance(total_energy, float): + return None + + if not isinstance(self.attribute_value, float): + return None + + return total_energy - self.attribute_value + + class RpcPresenceSensor(RpcSensor): """Represent a RPC presence sensor.""" @@ -885,7 +902,7 @@ def __init__( "energy": RpcSensorDescription( key="switch", sub_key="aenergy", - name="Energy", + name="Total energy", native_unit_of_measurement=UnitOfEnergy.WATT_HOUR, suggested_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR, value=lambda status, _: status["total"], @@ -903,7 +920,22 @@ def __init__( suggested_display_precision=2, device_class=SensorDeviceClass.ENERGY, state_class=SensorStateClass.TOTAL_INCREASING, + removal_condition=lambda _config, status, key: ( + status[key].get("ret_aenergy") is None + ), + ), + "consumed_energy_switch": RpcSensorDescription( + key="switch", + sub_key="ret_aenergy", + name="Consumed energy", + native_unit_of_measurement=UnitOfEnergy.WATT_HOUR, + suggested_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR, + value=lambda status, _: status["total"], + suggested_display_precision=2, + device_class=SensorDeviceClass.ENERGY, + state_class=SensorStateClass.TOTAL_INCREASING, entity_registry_enabled_default=False, + entity_class=RpcConsumedEnergySensor, removal_condition=lambda _config, status, key: ( status[key].get("ret_aenergy") is None ), @@ -922,7 +954,7 @@ def __init__( "energy_pm1": RpcSensorDescription( key="pm1", sub_key="aenergy", - name="Energy", + name="Total energy", native_unit_of_measurement=UnitOfEnergy.WATT_HOUR, suggested_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR, value=lambda status, _: status["total"], @@ -933,7 +965,18 @@ def __init__( "ret_energy_pm1": RpcSensorDescription( key="pm1", sub_key="ret_aenergy", - name="Total active returned energy", + name="Returned energy", + native_unit_of_measurement=UnitOfEnergy.WATT_HOUR, + suggested_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR, + value=lambda status, _: status["total"], + suggested_display_precision=2, + device_class=SensorDeviceClass.ENERGY, + state_class=SensorStateClass.TOTAL_INCREASING, + ), + "consumed_energy_pm1": RpcSensorDescription( + key="pm1", + sub_key="ret_aenergy", + name="Consumed energy", native_unit_of_measurement=UnitOfEnergy.WATT_HOUR, suggested_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR, value=lambda status, _: status["total"], @@ -941,6 +984,7 @@ def __init__( device_class=SensorDeviceClass.ENERGY, state_class=SensorStateClass.TOTAL_INCREASING, entity_registry_enabled_default=False, + entity_class=RpcConsumedEnergySensor, ), "energy_cct": RpcSensorDescription( key="cct", diff --git a/tests/components/shelly/snapshots/test_devices.ambr b/tests/components/shelly/snapshots/test_devices.ambr index 74c50691ce815f..47c952258d5100 100644 --- a/tests/components/shelly/snapshots/test_devices.ambr +++ b/tests/components/shelly/snapshots/test_devices.ambr @@ -546,65 +546,6 @@ 'state': '0.0', }) # --- -# name: test_shelly_2pm_gen3_cover[sensor.test_name_energy-entry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), - 'config_entry_id': , - 'config_subentry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.test_name_energy', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'labels': set({ - }), - 'name': None, - 'options': dict({ - 'sensor': dict({ - 'suggested_display_precision': 2, - }), - 'sensor.private': dict({ - 'suggested_unit_of_measurement': , - }), - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Energy', - 'platform': 'shelly', - 'previous_unique_id': None, - 'suggested_object_id': None, - 'supported_features': 0, - 'translation_key': None, - 'unique_id': '123456789ABC-cover:0-energy', - 'unit_of_measurement': , - }) -# --- -# name: test_shelly_2pm_gen3_cover[sensor.test_name_energy-state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'energy', - 'friendly_name': 'Test name Energy', - 'state_class': , - 'unit_of_measurement': , - }), - 'context': , - 'entity_id': 'sensor.test_name_energy', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': '0.0', - }) -# --- # name: test_shelly_2pm_gen3_cover[sensor.test_name_frequency-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ @@ -826,6 +767,65 @@ 'state': '36.4', }) # --- +# name: test_shelly_2pm_gen3_cover[sensor.test_name_total_energy-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.test_name_total_energy', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 2, + }), + 'sensor.private': dict({ + 'suggested_unit_of_measurement': , + }), + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Total energy', + 'platform': 'shelly', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': None, + 'unique_id': '123456789ABC-cover:0-energy', + 'unit_of_measurement': , + }) +# --- +# name: test_shelly_2pm_gen3_cover[sensor.test_name_total_energy-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'energy', + 'friendly_name': 'Test name Total energy', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.test_name_total_energy', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '0.0', + }) +# --- # name: test_shelly_2pm_gen3_cover[sensor.test_name_uptime-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ @@ -1743,13 +1743,13 @@ 'state': '-52', }) # --- -# name: test_shelly_2pm_gen3_no_relay_names[sensor.test_name_switch_0_current-entry] +# name: test_shelly_2pm_gen3_no_relay_names[sensor.test_name_switch_0_consumed_energy-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), 'area_id': None, 'capabilities': dict({ - 'state_class': , + 'state_class': , }), 'config_entry_id': , 'config_subentry_id': , @@ -1758,7 +1758,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': None, - 'entity_id': 'sensor.test_name_switch_0_current', + 'entity_id': 'sensor.test_name_switch_0_consumed_energy', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -1770,42 +1770,45 @@ 'sensor': dict({ 'suggested_display_precision': 2, }), + 'sensor.private': dict({ + 'suggested_unit_of_measurement': , + }), }), - 'original_device_class': , + 'original_device_class': , 'original_icon': None, - 'original_name': 'Current', + 'original_name': 'Consumed energy', 'platform': 'shelly', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, 'translation_key': None, - 'unique_id': '123456789ABC-switch:0-current', - 'unit_of_measurement': , + 'unique_id': '123456789ABC-switch:0-consumed_energy_switch', + 'unit_of_measurement': , }) # --- -# name: test_shelly_2pm_gen3_no_relay_names[sensor.test_name_switch_0_current-state] +# name: test_shelly_2pm_gen3_no_relay_names[sensor.test_name_switch_0_consumed_energy-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - 'device_class': 'current', - 'friendly_name': 'Test name Switch 0 Current', - 'state_class': , - 'unit_of_measurement': , + 'device_class': 'energy', + 'friendly_name': 'Test name Switch 0 Consumed energy', + 'state_class': , + 'unit_of_measurement': , }), 'context': , - 'entity_id': 'sensor.test_name_switch_0_current', + 'entity_id': 'sensor.test_name_switch_0_consumed_energy', 'last_changed': , 'last_reported': , 'last_updated': , 'state': '0.0', }) # --- -# name: test_shelly_2pm_gen3_no_relay_names[sensor.test_name_switch_0_energy-entry] +# name: test_shelly_2pm_gen3_no_relay_names[sensor.test_name_switch_0_current-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), 'area_id': None, 'capabilities': dict({ - 'state_class': , + 'state_class': , }), 'config_entry_id': , 'config_subentry_id': , @@ -1814,7 +1817,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': None, - 'entity_id': 'sensor.test_name_switch_0_energy', + 'entity_id': 'sensor.test_name_switch_0_current', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -1826,32 +1829,29 @@ 'sensor': dict({ 'suggested_display_precision': 2, }), - 'sensor.private': dict({ - 'suggested_unit_of_measurement': , - }), }), - 'original_device_class': , + 'original_device_class': , 'original_icon': None, - 'original_name': 'Energy', + 'original_name': 'Current', 'platform': 'shelly', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, 'translation_key': None, - 'unique_id': '123456789ABC-switch:0-energy', - 'unit_of_measurement': , + 'unique_id': '123456789ABC-switch:0-current', + 'unit_of_measurement': , }) # --- -# name: test_shelly_2pm_gen3_no_relay_names[sensor.test_name_switch_0_energy-state] +# name: test_shelly_2pm_gen3_no_relay_names[sensor.test_name_switch_0_current-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - 'device_class': 'energy', - 'friendly_name': 'Test name Switch 0 Energy', - 'state_class': , - 'unit_of_measurement': , + 'device_class': 'current', + 'friendly_name': 'Test name Switch 0 Current', + 'state_class': , + 'unit_of_measurement': , }), 'context': , - 'entity_id': 'sensor.test_name_switch_0_energy', + 'entity_id': 'sensor.test_name_switch_0_current', 'last_changed': , 'last_reported': , 'last_updated': , @@ -2085,6 +2085,65 @@ 'state': '40.6', }) # --- +# name: test_shelly_2pm_gen3_no_relay_names[sensor.test_name_switch_0_total_energy-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.test_name_switch_0_total_energy', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 2, + }), + 'sensor.private': dict({ + 'suggested_unit_of_measurement': , + }), + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Total energy', + 'platform': 'shelly', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': None, + 'unique_id': '123456789ABC-switch:0-energy', + 'unit_of_measurement': , + }) +# --- +# name: test_shelly_2pm_gen3_no_relay_names[sensor.test_name_switch_0_total_energy-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'energy', + 'friendly_name': 'Test name Switch 0 Total energy', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.test_name_switch_0_total_energy', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '0.0', + }) +# --- # name: test_shelly_2pm_gen3_no_relay_names[sensor.test_name_switch_0_voltage-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ @@ -2141,13 +2200,13 @@ 'state': '216.2', }) # --- -# name: test_shelly_2pm_gen3_no_relay_names[sensor.test_name_switch_1_current-entry] +# name: test_shelly_2pm_gen3_no_relay_names[sensor.test_name_switch_1_consumed_energy-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), 'area_id': None, 'capabilities': dict({ - 'state_class': , + 'state_class': , }), 'config_entry_id': , 'config_subentry_id': , @@ -2156,7 +2215,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': None, - 'entity_id': 'sensor.test_name_switch_1_current', + 'entity_id': 'sensor.test_name_switch_1_consumed_energy', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -2168,42 +2227,45 @@ 'sensor': dict({ 'suggested_display_precision': 2, }), + 'sensor.private': dict({ + 'suggested_unit_of_measurement': , + }), }), - 'original_device_class': , + 'original_device_class': , 'original_icon': None, - 'original_name': 'Current', + 'original_name': 'Consumed energy', 'platform': 'shelly', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, 'translation_key': None, - 'unique_id': '123456789ABC-switch:1-current', - 'unit_of_measurement': , + 'unique_id': '123456789ABC-switch:1-consumed_energy_switch', + 'unit_of_measurement': , }) # --- -# name: test_shelly_2pm_gen3_no_relay_names[sensor.test_name_switch_1_current-state] +# name: test_shelly_2pm_gen3_no_relay_names[sensor.test_name_switch_1_consumed_energy-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - 'device_class': 'current', - 'friendly_name': 'Test name Switch 1 Current', - 'state_class': , - 'unit_of_measurement': , + 'device_class': 'energy', + 'friendly_name': 'Test name Switch 1 Consumed energy', + 'state_class': , + 'unit_of_measurement': , }), 'context': , - 'entity_id': 'sensor.test_name_switch_1_current', + 'entity_id': 'sensor.test_name_switch_1_consumed_energy', 'last_changed': , 'last_reported': , 'last_updated': , 'state': '0.0', }) # --- -# name: test_shelly_2pm_gen3_no_relay_names[sensor.test_name_switch_1_energy-entry] +# name: test_shelly_2pm_gen3_no_relay_names[sensor.test_name_switch_1_current-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), 'area_id': None, 'capabilities': dict({ - 'state_class': , + 'state_class': , }), 'config_entry_id': , 'config_subentry_id': , @@ -2212,7 +2274,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': None, - 'entity_id': 'sensor.test_name_switch_1_energy', + 'entity_id': 'sensor.test_name_switch_1_current', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -2224,32 +2286,29 @@ 'sensor': dict({ 'suggested_display_precision': 2, }), - 'sensor.private': dict({ - 'suggested_unit_of_measurement': , - }), }), - 'original_device_class': , + 'original_device_class': , 'original_icon': None, - 'original_name': 'Energy', + 'original_name': 'Current', 'platform': 'shelly', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, 'translation_key': None, - 'unique_id': '123456789ABC-switch:1-energy', - 'unit_of_measurement': , + 'unique_id': '123456789ABC-switch:1-current', + 'unit_of_measurement': , }) # --- -# name: test_shelly_2pm_gen3_no_relay_names[sensor.test_name_switch_1_energy-state] +# name: test_shelly_2pm_gen3_no_relay_names[sensor.test_name_switch_1_current-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - 'device_class': 'energy', - 'friendly_name': 'Test name Switch 1 Energy', - 'state_class': , - 'unit_of_measurement': , + 'device_class': 'current', + 'friendly_name': 'Test name Switch 1 Current', + 'state_class': , + 'unit_of_measurement': , }), 'context': , - 'entity_id': 'sensor.test_name_switch_1_energy', + 'entity_id': 'sensor.test_name_switch_1_current', 'last_changed': , 'last_reported': , 'last_updated': , @@ -2483,6 +2542,65 @@ 'state': '40.6', }) # --- +# name: test_shelly_2pm_gen3_no_relay_names[sensor.test_name_switch_1_total_energy-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.test_name_switch_1_total_energy', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 2, + }), + 'sensor.private': dict({ + 'suggested_unit_of_measurement': , + }), + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Total energy', + 'platform': 'shelly', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': None, + 'unique_id': '123456789ABC-switch:1-energy', + 'unit_of_measurement': , + }) +# --- +# name: test_shelly_2pm_gen3_no_relay_names[sensor.test_name_switch_1_total_energy-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'energy', + 'friendly_name': 'Test name Switch 1 Total energy', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.test_name_switch_1_total_energy', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '0.0', + }) +# --- # name: test_shelly_2pm_gen3_no_relay_names[sensor.test_name_switch_1_voltage-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ diff --git a/tests/components/shelly/snapshots/test_sensor.ambr b/tests/components/shelly/snapshots/test_sensor.ambr index 6188d44922c0a7..3e849287bd73e3 100644 --- a/tests/components/shelly/snapshots/test_sensor.ambr +++ b/tests/components/shelly/snapshots/test_sensor.ambr @@ -339,7 +339,7 @@ 'state': '5.0', }) # --- -# name: test_rpc_switch_energy_sensors[sensor.test_name_test_switch_0_energy-entry] +# name: test_rpc_switch_energy_sensors[sensor.test_name_test_switch_0_consumed_energy-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -354,7 +354,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': None, - 'entity_id': 'sensor.test_name_test_switch_0_energy', + 'entity_id': 'sensor.test_name_test_switch_0_consumed_energy', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -372,30 +372,30 @@ }), 'original_device_class': , 'original_icon': None, - 'original_name': 'test switch_0 energy', + 'original_name': 'test switch_0 consumed energy', 'platform': 'shelly', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, 'translation_key': None, - 'unique_id': '123456789ABC-switch:0-energy', + 'unique_id': '123456789ABC-switch:0-consumed_energy_switch', 'unit_of_measurement': , }) # --- -# name: test_rpc_switch_energy_sensors[sensor.test_name_test_switch_0_energy-state] +# name: test_rpc_switch_energy_sensors[sensor.test_name_test_switch_0_consumed_energy-state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'device_class': 'energy', - 'friendly_name': 'Test name test switch_0 energy', + 'friendly_name': 'Test name test switch_0 consumed energy', 'state_class': , 'unit_of_measurement': , }), 'context': , - 'entity_id': 'sensor.test_name_test_switch_0_energy', + 'entity_id': 'sensor.test_name_test_switch_0_consumed_energy', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '1234.56789', + 'state': '1135.80246', }) # --- # name: test_rpc_switch_energy_sensors[sensor.test_name_test_switch_0_returned_energy-entry] @@ -457,3 +457,62 @@ 'state': '98.76543', }) # --- +# name: test_rpc_switch_energy_sensors[sensor.test_name_test_switch_0_total_energy-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.test_name_test_switch_0_total_energy', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 2, + }), + 'sensor.private': dict({ + 'suggested_unit_of_measurement': , + }), + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'test switch_0 total energy', + 'platform': 'shelly', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': None, + 'unique_id': '123456789ABC-switch:0-energy', + 'unit_of_measurement': , + }) +# --- +# name: test_rpc_switch_energy_sensors[sensor.test_name_test_switch_0_total_energy-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'energy', + 'friendly_name': 'Test name test switch_0 total energy', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.test_name_test_switch_0_total_energy', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '1234.56789', + }) +# --- diff --git a/tests/components/shelly/test_sensor.py b/tests/components/shelly/test_sensor.py index 015afdd36611fc..8bca4ce38ab62c 100644 --- a/tests/components/shelly/test_sensor.py +++ b/tests/components/shelly/test_sensor.py @@ -1640,7 +1640,7 @@ async def test_rpc_switch_energy_sensors( monkeypatch.setattr(mock_rpc_device, "status", status) await init_integration(hass, 3) - for entity in ("energy", "returned_energy"): + for entity in ("total_energy", "returned_energy", "consumed_energy"): entity_id = f"{SENSOR_DOMAIN}.test_name_test_switch_0_{entity}" state = hass.states.get(entity_id) @@ -1670,6 +1670,7 @@ async def test_rpc_switch_no_returned_energy_sensor( await init_integration(hass, 3) assert hass.states.get("sensor.test_name_test_switch_0_returned_energy") is None + assert hass.states.get("sensor.test_name_test_switch_0_consumed_energy") is None async def test_rpc_shelly_ev_sensors( @@ -1864,3 +1865,78 @@ async def test_rpc_presencezone_component( assert (state := hass.states.get(entity_id)) assert state.state == STATE_UNAVAILABLE + + +@pytest.mark.usefixtures("entity_registry_enabled_by_default") +async def test_rpc_pm1_consumed_energy_sensor( + hass: HomeAssistant, + mock_rpc_device: Mock, + entity_registry: EntityRegistry, + monkeypatch: pytest.MonkeyPatch, +) -> None: + """Test energy sensors for switch component.""" + status = { + "sys": {}, + "pm1:0": { + "id": 0, + "voltage": 235.0, + "current": 0.957, + "apower": -220.3, + "freq": 50.0, + "aenergy": {"total": 3000.000}, + "ret_aenergy": {"total": 1000.000}, + }, + } + monkeypatch.setattr(mock_rpc_device, "status", status) + await init_integration(hass, 3) + + assert (state := hass.states.get(f"{SENSOR_DOMAIN}.test_name_total_energy")) + assert state.state == "3.0" + + assert (state := hass.states.get(f"{SENSOR_DOMAIN}.test_name_returned_energy")) + assert state.state == "1.0" + + entity_id = f"{SENSOR_DOMAIN}.test_name_consumed_energy" + # consumed energy = total energy - returned energy + assert (state := hass.states.get(entity_id)) + assert state.state == "2.0" + + assert (entry := entity_registry.async_get(entity_id)) + assert entry.unique_id == "123456789ABC-pm1:0-consumed_energy_pm1" + + +@pytest.mark.parametrize(("key"), ["aenergy", "ret_aenergy"]) +@pytest.mark.usefixtures("entity_registry_enabled_by_default") +async def test_rpc_pm1_consumed_energy_sensor_non_float_value( + hass: HomeAssistant, + mock_rpc_device: Mock, + monkeypatch: pytest.MonkeyPatch, + key: str, +) -> None: + """Test energy sensors for switch component.""" + entity_id = f"{SENSOR_DOMAIN}.test_name_consumed_energy" + status = { + "sys": {}, + "pm1:0": { + "id": 0, + "voltage": 235.0, + "current": 0.957, + "apower": -220.3, + "freq": 50.0, + "aenergy": {"total": 3000.000}, + "ret_aenergy": {"total": 1000.000}, + }, + } + monkeypatch.setattr(mock_rpc_device, "status", status) + await init_integration(hass, 3) + + assert (state := hass.states.get(entity_id)) + assert state.state == "2.0" + + mutate_rpc_device_status( + monkeypatch, mock_rpc_device, "pm1:0", key, {"total": None} + ) + mock_rpc_device.mock_update() + + assert (state := hass.states.get(entity_id)) + assert state.state == STATE_UNKNOWN From b9f76135672fbe5be295c454beab3be69989b165 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 29 Sep 2025 13:15:53 +0200 Subject: [PATCH 03/13] Bump github/codeql-action from 3.30.4 to 3.30.5 (#153179) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/codeql.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index e1f6061ca5652f..a8081884de122d 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -24,11 +24,11 @@ jobs: uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - name: Initialize CodeQL - uses: github/codeql-action/init@303c0aef88fc2fe5ff6d63d3b1596bfd83dfa1f9 # v3.30.4 + uses: github/codeql-action/init@3599b3baa15b485a2e49ef411a7a4bb2452e7f93 # v3.30.5 with: languages: python - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@303c0aef88fc2fe5ff6d63d3b1596bfd83dfa1f9 # v3.30.4 + uses: github/codeql-action/analyze@3599b3baa15b485a2e49ef411a7a4bb2452e7f93 # v3.30.5 with: category: "/language:python" From b935231e470ccf098b43c6711049cc48fa89e1a8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 29 Sep 2025 13:17:20 +0200 Subject: [PATCH 04/13] Bump actions/dependency-review-action from 4.7.3 to 4.8.0 (#153180) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index e5b4a6614e6f8c..1bfa93eed5d7c9 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -711,7 +711,7 @@ jobs: - name: Check out code from GitHub uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - name: Dependency review - uses: actions/dependency-review-action@595b5aeba73380359d98a5e087f648dbb0edce1b # v4.7.3 + uses: actions/dependency-review-action@56339e523c0409420f6c2c9a2f4292bbb3c07dd3 # v4.8.0 with: license-check: false # We use our own license audit checks From f071a3f38b8f2df83f59ae8badf866310237729c Mon Sep 17 00:00:00 2001 From: cdnninja Date: Mon, 29 Sep 2025 06:29:25 -0600 Subject: [PATCH 05/13] Correct vesync water tank lifted key (#153173) --- .../components/vesync/binary_sensor.py | 2 +- .../vesync/snapshots/test_binary_sensor.ambr | 633 ++++++++++++++++++ tests/components/vesync/test_binary_sensor.py | 51 ++ 3 files changed, 685 insertions(+), 1 deletion(-) create mode 100644 tests/components/vesync/snapshots/test_binary_sensor.ambr create mode 100644 tests/components/vesync/test_binary_sensor.py diff --git a/homeassistant/components/vesync/binary_sensor.py b/homeassistant/components/vesync/binary_sensor.py index 933d2f2599d479..7b72c80ff85e50 100644 --- a/homeassistant/components/vesync/binary_sensor.py +++ b/homeassistant/components/vesync/binary_sensor.py @@ -43,7 +43,7 @@ class VeSyncBinarySensorEntityDescription(BinarySensorEntityDescription): exists_fn=lambda device: rgetattr(device, "state.water_lacks") is not None, ), VeSyncBinarySensorEntityDescription( - key="water_tank_lifted", + key="details.water_tank_lifted", translation_key="water_tank_lifted", is_on=lambda device: device.state.water_tank_lifted, device_class=BinarySensorDeviceClass.PROBLEM, diff --git a/tests/components/vesync/snapshots/test_binary_sensor.ambr b/tests/components/vesync/snapshots/test_binary_sensor.ambr new file mode 100644 index 00000000000000..3b851ea989c2de --- /dev/null +++ b/tests/components/vesync/snapshots/test_binary_sensor.ambr @@ -0,0 +1,633 @@ +# serializer version: 1 +# name: test_sensor_state[Air Purifier 131s][devices] + list([ + DeviceRegistryEntrySnapshot({ + 'area_id': None, + 'config_entries': , + 'config_entries_subentries': , + 'configuration_url': None, + 'connections': set({ + }), + 'disabled_by': None, + 'entry_type': None, + 'hw_version': None, + 'id': , + 'identifiers': set({ + tuple( + 'vesync', + 'air-purifier', + ), + }), + 'labels': set({ + }), + 'manufacturer': 'VeSync', + 'model': 'LV-PUR131S', + 'model_id': None, + 'name': 'Air Purifier 131s', + 'name_by_user': None, + 'primary_config_entry': , + 'serial_number': None, + 'sw_version': None, + 'via_device_id': None, + }), + ]) +# --- +# name: test_sensor_state[Air Purifier 131s][entities] + list([ + ]) +# --- +# name: test_sensor_state[Air Purifier 200s][devices] + list([ + DeviceRegistryEntrySnapshot({ + 'area_id': None, + 'config_entries': , + 'config_entries_subentries': , + 'configuration_url': None, + 'connections': set({ + }), + 'disabled_by': None, + 'entry_type': None, + 'hw_version': None, + 'id': , + 'identifiers': set({ + tuple( + 'vesync', + 'asd_sdfKIHG7IJHGwJGJ7GJ_ag5h3G55', + ), + }), + 'labels': set({ + }), + 'manufacturer': 'VeSync', + 'model': 'Core200S', + 'model_id': None, + 'name': 'Air Purifier 200s', + 'name_by_user': None, + 'primary_config_entry': , + 'serial_number': None, + 'sw_version': None, + 'via_device_id': None, + }), + ]) +# --- +# name: test_sensor_state[Air Purifier 200s][entities] + list([ + ]) +# --- +# name: test_sensor_state[Air Purifier 400s][devices] + list([ + DeviceRegistryEntrySnapshot({ + 'area_id': None, + 'config_entries': , + 'config_entries_subentries': , + 'configuration_url': None, + 'connections': set({ + }), + 'disabled_by': None, + 'entry_type': None, + 'hw_version': None, + 'id': , + 'identifiers': set({ + tuple( + 'vesync', + '400s-purifier', + ), + }), + 'labels': set({ + }), + 'manufacturer': 'VeSync', + 'model': 'LAP-C401S-WJP', + 'model_id': None, + 'name': 'Air Purifier 400s', + 'name_by_user': None, + 'primary_config_entry': , + 'serial_number': None, + 'sw_version': None, + 'via_device_id': None, + }), + ]) +# --- +# name: test_sensor_state[Air Purifier 400s][entities] + list([ + ]) +# --- +# name: test_sensor_state[Air Purifier 600s][devices] + list([ + DeviceRegistryEntrySnapshot({ + 'area_id': None, + 'config_entries': , + 'config_entries_subentries': , + 'configuration_url': None, + 'connections': set({ + }), + 'disabled_by': None, + 'entry_type': None, + 'hw_version': None, + 'id': , + 'identifiers': set({ + tuple( + 'vesync', + '600s-purifier', + ), + }), + 'labels': set({ + }), + 'manufacturer': 'VeSync', + 'model': 'LAP-C601S-WUS', + 'model_id': None, + 'name': 'Air Purifier 600s', + 'name_by_user': None, + 'primary_config_entry': , + 'serial_number': None, + 'sw_version': None, + 'via_device_id': None, + }), + ]) +# --- +# name: test_sensor_state[Air Purifier 600s][entities] + list([ + ]) +# --- +# name: test_sensor_state[Dimmable Light][devices] + list([ + DeviceRegistryEntrySnapshot({ + 'area_id': None, + 'config_entries': , + 'config_entries_subentries': , + 'configuration_url': None, + 'connections': set({ + }), + 'disabled_by': None, + 'entry_type': None, + 'hw_version': None, + 'id': , + 'identifiers': set({ + tuple( + 'vesync', + 'dimmable-bulb', + ), + }), + 'labels': set({ + }), + 'manufacturer': 'VeSync', + 'model': 'ESL100', + 'model_id': None, + 'name': 'Dimmable Light', + 'name_by_user': None, + 'primary_config_entry': , + 'serial_number': None, + 'sw_version': None, + 'via_device_id': None, + }), + ]) +# --- +# name: test_sensor_state[Dimmable Light][entities] + list([ + ]) +# --- +# name: test_sensor_state[Dimmer Switch][devices] + list([ + DeviceRegistryEntrySnapshot({ + 'area_id': None, + 'config_entries': , + 'config_entries_subentries': , + 'configuration_url': None, + 'connections': set({ + }), + 'disabled_by': None, + 'entry_type': None, + 'hw_version': None, + 'id': , + 'identifiers': set({ + tuple( + 'vesync', + 'dimmable-switch', + ), + }), + 'labels': set({ + }), + 'manufacturer': 'VeSync', + 'model': 'ESWD16', + 'model_id': None, + 'name': 'Dimmer Switch', + 'name_by_user': None, + 'primary_config_entry': , + 'serial_number': None, + 'sw_version': None, + 'via_device_id': None, + }), + ]) +# --- +# name: test_sensor_state[Dimmer Switch][entities] + list([ + ]) +# --- +# name: test_sensor_state[Humidifier 200s][binary_sensor.humidifier_200s_low_water] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'problem', + 'friendly_name': 'Humidifier 200s Low water', + }), + 'context': , + 'entity_id': 'binary_sensor.humidifier_200s_low_water', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'on', + }) +# --- +# name: test_sensor_state[Humidifier 200s][binary_sensor.humidifier_200s_water_tank_lifted] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'problem', + 'friendly_name': 'Humidifier 200s Water tank lifted', + }), + 'context': , + 'entity_id': 'binary_sensor.humidifier_200s_water_tank_lifted', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'on', + }) +# --- +# name: test_sensor_state[Humidifier 200s][devices] + list([ + DeviceRegistryEntrySnapshot({ + 'area_id': None, + 'config_entries': , + 'config_entries_subentries': , + 'configuration_url': None, + 'connections': set({ + }), + 'disabled_by': None, + 'entry_type': None, + 'hw_version': None, + 'id': , + 'identifiers': set({ + tuple( + 'vesync', + '200s-humidifier4321', + ), + }), + 'labels': set({ + }), + 'manufacturer': 'VeSync', + 'model': 'Classic200S', + 'model_id': None, + 'name': 'Humidifier 200s', + 'name_by_user': None, + 'primary_config_entry': , + 'serial_number': None, + 'sw_version': None, + 'via_device_id': None, + }), + ]) +# --- +# name: test_sensor_state[Humidifier 200s][entities] + list([ + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'binary_sensor', + 'entity_category': None, + 'entity_id': 'binary_sensor.humidifier_200s_low_water', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Low water', + 'platform': 'vesync', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'water_lacks', + 'unique_id': '200s-humidifier4321-water_lacks', + 'unit_of_measurement': None, + }), + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'binary_sensor', + 'entity_category': None, + 'entity_id': 'binary_sensor.humidifier_200s_water_tank_lifted', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Water tank lifted', + 'platform': 'vesync', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'water_tank_lifted', + 'unique_id': '200s-humidifier4321-details.water_tank_lifted', + 'unit_of_measurement': None, + }), + ]) +# --- +# name: test_sensor_state[Humidifier 600S][binary_sensor.humidifier_600s_low_water] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'problem', + 'friendly_name': 'Humidifier 600S Low water', + }), + 'context': , + 'entity_id': 'binary_sensor.humidifier_600s_low_water', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'on', + }) +# --- +# name: test_sensor_state[Humidifier 600S][binary_sensor.humidifier_600s_water_tank_lifted] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'problem', + 'friendly_name': 'Humidifier 600S Water tank lifted', + }), + 'context': , + 'entity_id': 'binary_sensor.humidifier_600s_water_tank_lifted', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'on', + }) +# --- +# name: test_sensor_state[Humidifier 600S][devices] + list([ + DeviceRegistryEntrySnapshot({ + 'area_id': None, + 'config_entries': , + 'config_entries_subentries': , + 'configuration_url': None, + 'connections': set({ + }), + 'disabled_by': None, + 'entry_type': None, + 'hw_version': None, + 'id': , + 'identifiers': set({ + tuple( + 'vesync', + '600s-humidifier', + ), + }), + 'labels': set({ + }), + 'manufacturer': 'VeSync', + 'model': 'LUH-A602S-WUS', + 'model_id': None, + 'name': 'Humidifier 600S', + 'name_by_user': None, + 'primary_config_entry': , + 'serial_number': None, + 'sw_version': None, + 'via_device_id': None, + }), + ]) +# --- +# name: test_sensor_state[Humidifier 600S][entities] + list([ + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'binary_sensor', + 'entity_category': None, + 'entity_id': 'binary_sensor.humidifier_600s_low_water', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Low water', + 'platform': 'vesync', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'water_lacks', + 'unique_id': '600s-humidifier-water_lacks', + 'unit_of_measurement': None, + }), + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'binary_sensor', + 'entity_category': None, + 'entity_id': 'binary_sensor.humidifier_600s_water_tank_lifted', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Water tank lifted', + 'platform': 'vesync', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'water_tank_lifted', + 'unique_id': '600s-humidifier-details.water_tank_lifted', + 'unit_of_measurement': None, + }), + ]) +# --- +# name: test_sensor_state[Outlet][devices] + list([ + DeviceRegistryEntrySnapshot({ + 'area_id': None, + 'config_entries': , + 'config_entries_subentries': , + 'configuration_url': None, + 'connections': set({ + }), + 'disabled_by': None, + 'entry_type': None, + 'hw_version': None, + 'id': , + 'identifiers': set({ + tuple( + 'vesync', + 'outlet', + ), + }), + 'labels': set({ + }), + 'manufacturer': 'VeSync', + 'model': 'wifi-switch-1.3', + 'model_id': None, + 'name': 'Outlet', + 'name_by_user': None, + 'primary_config_entry': , + 'serial_number': None, + 'sw_version': None, + 'via_device_id': None, + }), + ]) +# --- +# name: test_sensor_state[Outlet][entities] + list([ + ]) +# --- +# name: test_sensor_state[SmartTowerFan][devices] + list([ + DeviceRegistryEntrySnapshot({ + 'area_id': None, + 'config_entries': , + 'config_entries_subentries': , + 'configuration_url': None, + 'connections': set({ + }), + 'disabled_by': None, + 'entry_type': None, + 'hw_version': None, + 'id': , + 'identifiers': set({ + tuple( + 'vesync', + 'smarttowerfan', + ), + }), + 'labels': set({ + }), + 'manufacturer': 'VeSync', + 'model': 'LTF-F422S-KEU', + 'model_id': None, + 'name': 'SmartTowerFan', + 'name_by_user': None, + 'primary_config_entry': , + 'serial_number': None, + 'sw_version': None, + 'via_device_id': None, + }), + ]) +# --- +# name: test_sensor_state[SmartTowerFan][entities] + list([ + ]) +# --- +# name: test_sensor_state[Temperature Light][devices] + list([ + DeviceRegistryEntrySnapshot({ + 'area_id': None, + 'config_entries': , + 'config_entries_subentries': , + 'configuration_url': None, + 'connections': set({ + }), + 'disabled_by': None, + 'entry_type': None, + 'hw_version': None, + 'id': , + 'identifiers': set({ + tuple( + 'vesync', + 'tunable-bulb', + ), + }), + 'labels': set({ + }), + 'manufacturer': 'VeSync', + 'model': 'ESL100CW', + 'model_id': None, + 'name': 'Temperature Light', + 'name_by_user': None, + 'primary_config_entry': , + 'serial_number': None, + 'sw_version': None, + 'via_device_id': None, + }), + ]) +# --- +# name: test_sensor_state[Temperature Light][entities] + list([ + ]) +# --- +# name: test_sensor_state[Wall Switch][devices] + list([ + DeviceRegistryEntrySnapshot({ + 'area_id': None, + 'config_entries': , + 'config_entries_subentries': , + 'configuration_url': None, + 'connections': set({ + }), + 'disabled_by': None, + 'entry_type': None, + 'hw_version': None, + 'id': , + 'identifiers': set({ + tuple( + 'vesync', + 'switch', + ), + }), + 'labels': set({ + }), + 'manufacturer': 'VeSync', + 'model': 'ESWL01', + 'model_id': None, + 'name': 'Wall Switch', + 'name_by_user': None, + 'primary_config_entry': , + 'serial_number': None, + 'sw_version': None, + 'via_device_id': None, + }), + ]) +# --- +# name: test_sensor_state[Wall Switch][entities] + list([ + ]) +# --- diff --git a/tests/components/vesync/test_binary_sensor.py b/tests/components/vesync/test_binary_sensor.py new file mode 100644 index 00000000000000..5863270f7f5341 --- /dev/null +++ b/tests/components/vesync/test_binary_sensor.py @@ -0,0 +1,51 @@ +"""Tests for the binary sensor module.""" + +import pytest +from syrupy.assertion import SnapshotAssertion + +from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR_DOMAIN +from homeassistant.core import HomeAssistant +from homeassistant.helpers import device_registry as dr, entity_registry as er + +from .common import ALL_DEVICE_NAMES, mock_devices_response + +from tests.common import MockConfigEntry +from tests.test_util.aiohttp import AiohttpClientMocker + + +@pytest.mark.parametrize("device_name", ALL_DEVICE_NAMES) +async def test_sensor_state( + hass: HomeAssistant, + snapshot: SnapshotAssertion, + config_entry: MockConfigEntry, + device_registry: dr.DeviceRegistry, + entity_registry: er.EntityRegistry, + aioclient_mock: AiohttpClientMocker, + device_name: str, +) -> None: + """Test the resulting setup state is as expected for the platform.""" + + # Configure the API devices call for device_name + mock_devices_response(aioclient_mock, device_name) + + # setup platform - only including the named device + await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + + # Check device registry + devices = dr.async_entries_for_config_entry(device_registry, config_entry.entry_id) + assert devices == snapshot(name="devices") + + # Check entity registry + entities = [ + entity + for entity in er.async_entries_for_config_entry( + entity_registry, config_entry.entry_id + ) + if entity.domain == BINARY_SENSOR_DOMAIN + ] + assert entities == snapshot(name="entities") + + # Check states + for entity in entities: + assert hass.states.get(entity.entity_id) == snapshot(name=entity.entity_id) From dc02002b9d4ae9a147e94330f6e558e7fb2f6b7c Mon Sep 17 00:00:00 2001 From: Simone Chemelli Date: Mon, 29 Sep 2025 14:30:42 +0200 Subject: [PATCH 06/13] Bump aioamazondevices to 6.2.7 (#153185) --- homeassistant/components/alexa_devices/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/alexa_devices/const.py | 2 ++ tests/components/alexa_devices/snapshots/test_services.ambr | 2 ++ 5 files changed, 7 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/alexa_devices/manifest.json b/homeassistant/components/alexa_devices/manifest.json index 14b2ddf90d969a..fa5fb5531cc9e2 100644 --- a/homeassistant/components/alexa_devices/manifest.json +++ b/homeassistant/components/alexa_devices/manifest.json @@ -8,5 +8,5 @@ "iot_class": "cloud_polling", "loggers": ["aioamazondevices"], "quality_scale": "platinum", - "requirements": ["aioamazondevices==6.2.6"] + "requirements": ["aioamazondevices==6.2.7"] } diff --git a/requirements_all.txt b/requirements_all.txt index 7c2870bec6256e..1b8e5298621222 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -185,7 +185,7 @@ aioairzone-cloud==0.7.2 aioairzone==1.0.1 # homeassistant.components.alexa_devices -aioamazondevices==6.2.6 +aioamazondevices==6.2.7 # homeassistant.components.ambient_network # homeassistant.components.ambient_station diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 8800debebaf43e..68b31c316bed87 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -173,7 +173,7 @@ aioairzone-cloud==0.7.2 aioairzone==1.0.1 # homeassistant.components.alexa_devices -aioamazondevices==6.2.6 +aioamazondevices==6.2.7 # homeassistant.components.ambient_network # homeassistant.components.ambient_station diff --git a/tests/components/alexa_devices/const.py b/tests/components/alexa_devices/const.py index 05a6ff58719647..8fe407bd1c7358 100644 --- a/tests/components/alexa_devices/const.py +++ b/tests/components/alexa_devices/const.py @@ -13,6 +13,7 @@ capabilities=["AUDIO_PLAYER", "MICROPHONE"], device_family="mine", device_type="echo", + household_device=False, device_owner_customer_id="amazon_ower_id", device_cluster_members=[TEST_DEVICE_1_SN], online=True, @@ -35,6 +36,7 @@ capabilities=["AUDIO_PLAYER", "MICROPHONE"], device_family="mine", device_type="echo", + household_device=True, device_owner_customer_id="amazon_ower_id", device_cluster_members=[TEST_DEVICE_2_SN], online=True, diff --git a/tests/components/alexa_devices/snapshots/test_services.ambr b/tests/components/alexa_devices/snapshots/test_services.ambr index dc15796c32c63c..2f6576adb35e16 100644 --- a/tests/components/alexa_devices/snapshots/test_services.ambr +++ b/tests/components/alexa_devices/snapshots/test_services.ambr @@ -16,6 +16,7 @@ 'device_type': 'echo', 'endpoint_id': 'G1234567890123456789012345678A', 'entity_id': '11111111-2222-3333-4444-555555555555', + 'household_device': False, 'online': True, 'sensors': dict({ 'dnd': dict({ @@ -57,6 +58,7 @@ 'device_type': 'echo', 'endpoint_id': 'G1234567890123456789012345678A', 'entity_id': '11111111-2222-3333-4444-555555555555', + 'household_device': False, 'online': True, 'sensors': dict({ 'dnd': dict({ From 0a6fa978fa34562fdcfecfbb03f32032577b4441 Mon Sep 17 00:00:00 2001 From: G Johansson Date: Mon, 29 Sep 2025 14:49:38 +0200 Subject: [PATCH 07/13] Add timeout to dnsip (to handle stale connections) (#153086) --- homeassistant/components/dnsip/sensor.py | 21 ++++++-- tests/components/dnsip/__init__.py | 5 ++ tests/components/dnsip/test_sensor.py | 67 ++++++++++++++++++++++++ 3 files changed, 89 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/dnsip/sensor.py b/homeassistant/components/dnsip/sensor.py index d093698e26b6f5..e22155a24e8bcd 100644 --- a/homeassistant/components/dnsip/sensor.py +++ b/homeassistant/components/dnsip/sensor.py @@ -2,6 +2,7 @@ from __future__ import annotations +import asyncio from datetime import timedelta from ipaddress import IPv4Address, IPv6Address import logging @@ -88,8 +89,8 @@ def __init__( self._attr_name = "IPv6" if ipv6 else None self._attr_unique_id = f"{hostname}_{ipv6}" self.hostname = hostname - self.resolver = aiodns.DNSResolver(tcp_port=port, udp_port=port) - self.resolver.nameservers = [resolver] + self.port = port + self._resolver = resolver self.querytype: Literal["A", "AAAA"] = "AAAA" if ipv6 else "A" self._retries = DEFAULT_RETRIES self._attr_extra_state_attributes = { @@ -103,14 +104,26 @@ def __init__( model=aiodns.__version__, name=name, ) + self.resolver: aiodns.DNSResolver + self.create_dns_resolver() + + def create_dns_resolver(self) -> None: + """Create the DNS resolver.""" + self.resolver = aiodns.DNSResolver(tcp_port=self.port, udp_port=self.port) + self.resolver.nameservers = [self._resolver] async def async_update(self) -> None: """Get the current DNS IP address for hostname.""" + if self.resolver._closed: # noqa: SLF001 + self.create_dns_resolver() + response = None try: - response = await self.resolver.query(self.hostname, self.querytype) + async with asyncio.timeout(10): + response = await self.resolver.query(self.hostname, self.querytype) + except TimeoutError: + await self.resolver.close() except DNSError as err: _LOGGER.warning("Exception while resolving host: %s", err) - response = None if response: sorted_ips = sort_ips( diff --git a/tests/components/dnsip/__init__.py b/tests/components/dnsip/__init__.py index a0e6b7c81b85b1..254aad8f1da148 100644 --- a/tests/components/dnsip/__init__.py +++ b/tests/components/dnsip/__init__.py @@ -23,6 +23,7 @@ def __init__( self.nameservers = nameservers self._nameservers = ["1.2.3.4"] self.error = error + self._closed = False async def query(self, hostname, qtype) -> list[QueryResult]: """Return information.""" @@ -47,3 +48,7 @@ def nameservers(self) -> list[str]: @nameservers.setter def nameservers(self, value: list[str]) -> None: self._nameservers = value + + async def close(self) -> None: + """Close the resolver.""" + self._closed = True diff --git a/tests/components/dnsip/test_sensor.py b/tests/components/dnsip/test_sensor.py index 66cb5cc6ad99cb..87e03ebceb81cb 100644 --- a/tests/components/dnsip/test_sensor.py +++ b/tests/components/dnsip/test_sensor.py @@ -171,3 +171,70 @@ async def test_sensor_no_response( state = hass.states.get("sensor.home_assistant_io") assert state.state == STATE_UNAVAILABLE + + +async def test_sensor_timeout( + hass: HomeAssistant, freezer: FrozenDateTimeFactory +) -> None: + """Test the DNS IP sensor with timeout.""" + entry = MockConfigEntry( + domain=DOMAIN, + source=SOURCE_USER, + data={ + CONF_HOSTNAME: "home-assistant.io", + CONF_NAME: "home-assistant.io", + CONF_IPV4: True, + CONF_IPV6: False, + }, + options={ + CONF_RESOLVER: "208.67.222.222", + CONF_RESOLVER_IPV6: "2620:119:53::53", + CONF_PORT: 53, + CONF_PORT_IPV6: 53, + }, + entry_id="1", + unique_id="home-assistant.io", + ) + entry.add_to_hass(hass) + + dns_mock = RetrieveDNS() + with patch( + "homeassistant.components.dnsip.sensor.aiodns.DNSResolver", + return_value=dns_mock, + ): + await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + state = hass.states.get("sensor.home_assistant_io") + + assert state.state == "1.1.1.1" + + with ( + patch( + "homeassistant.components.dnsip.sensor.aiodns.DNSResolver", + return_value=dns_mock, + ), + patch( + "homeassistant.components.dnsip.sensor.asyncio.timeout", + side_effect=TimeoutError(), + ), + ): + freezer.tick(timedelta(seconds=SCAN_INTERVAL.seconds)) + async_fire_time_changed(hass) + await hass.async_block_till_done() + + # Allows 2 retries before going unavailable + state = hass.states.get("sensor.home_assistant_io") + assert state.state == "1.1.1.1" + assert state.attributes["ip_addresses"] == ["1.1.1.1", "1.2.3.4"] + + freezer.tick(timedelta(seconds=SCAN_INTERVAL.seconds)) + async_fire_time_changed(hass) + await hass.async_block_till_done() + + freezer.tick(timedelta(seconds=SCAN_INTERVAL.seconds)) + async_fire_time_changed(hass) + await hass.async_block_till_done() + + state = hass.states.get("sensor.home_assistant_io") + assert state.state == STATE_UNAVAILABLE From aa4151ced78ea73583f98be2e2fee78ead8492cf Mon Sep 17 00:00:00 2001 From: Kyle Worrall <65330257+kylewhirl@users.noreply.github.com> Date: Mon, 29 Sep 2025 05:50:36 -0700 Subject: [PATCH 08/13] Fix for Hue Integration motion aware areas (#153079) Co-authored-by: Marcel van der Veldt Co-authored-by: Joost Lekkerkerker --- .../components/hue/v2/binary_sensor.py | 6 ++++- tests/components/hue/test_binary_sensor.py | 23 +++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/hue/v2/binary_sensor.py b/homeassistant/components/hue/v2/binary_sensor.py index da28fd1f6a94ef..7b7717cbf76bce 100644 --- a/homeassistant/components/hue/v2/binary_sensor.py +++ b/homeassistant/components/hue/v2/binary_sensor.py @@ -145,7 +145,11 @@ def is_on(self) -> bool | None: if not self.resource.enabled: # Force None (unknown) if the sensor is set to disabled in Hue return None - return self.resource.motion.value + if not (motion_feature := self.resource.motion): + return None + if motion_feature.motion_report is not None: + return motion_feature.motion_report.motion + return motion_feature.motion # pylint: disable-next=hass-enforce-class-module diff --git a/tests/components/hue/test_binary_sensor.py b/tests/components/hue/test_binary_sensor.py index 02b4d93acfede5..8fc2043d45aa84 100644 --- a/tests/components/hue/test_binary_sensor.py +++ b/tests/components/hue/test_binary_sensor.py @@ -123,6 +123,29 @@ async def test_binary_sensor_add_update( test_entity = hass.states.get(test_entity_id) assert test_entity is not None assert test_entity.state == "on" + # NEW: prefer motion_report.motion when present (should turn on even if plain motion is False) + updated_sensor = { + **FAKE_BINARY_SENSOR, + "motion": { + "motion": False, + "motion_report": {"changed": "2025-01-01T00:00:00Z", "motion": True}, + }, + } + mock_bridge_v2.api.emit_event("update", updated_sensor) + await hass.async_block_till_done() + assert hass.states.get(test_entity_id).state == "on" + + # NEW: motion_report False should turn it off (even if plain motion is True) + updated_sensor = { + **FAKE_BINARY_SENSOR, + "motion": { + "motion": True, + "motion_report": {"changed": "2025-01-01T00:00:01Z", "motion": False}, + }, + } + mock_bridge_v2.api.emit_event("update", updated_sensor) + await hass.async_block_till_done() + assert hass.states.get(test_entity_id).state == "off" async def test_grouped_motion_sensor( From f0c29c7699f1ee5f52611376d1aa5fb4fe918cc2 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Mon, 29 Sep 2025 14:56:42 +0200 Subject: [PATCH 09/13] Revert "Add comment on conversion factor for Carbon monoxide on dependency molecular weight" (#153195) --- homeassistant/util/unit_conversion.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/homeassistant/util/unit_conversion.py b/homeassistant/util/unit_conversion.py index b2938b249b87d2..0483878f547c5e 100644 --- a/homeassistant/util/unit_conversion.py +++ b/homeassistant/util/unit_conversion.py @@ -174,9 +174,7 @@ class CarbonMonoxideConcentrationConverter(BaseUnitConverter): UNIT_CLASS = "carbon_monoxide" _UNIT_CONVERSION: dict[str | None, float] = { CONCENTRATION_PARTS_PER_MILLION: 1, - # concentration (mg/m3) = 0.0409 x concentration (ppm) x molecular weight - # Carbon monoxide molecular weight: 28.01 g/mol - CONCENTRATION_MILLIGRAMS_PER_CUBIC_METER: 0.0409 * 28.01, + CONCENTRATION_MILLIGRAMS_PER_CUBIC_METER: 1.145609, } VALID_UNITS = { CONCENTRATION_PARTS_PER_MILLION, From 76cb4d123a5a93422a4d12c8f4da9c6554502f77 Mon Sep 17 00:00:00 2001 From: Artur Pragacz <49985303+arturpragacz@users.noreply.github.com> Date: Mon, 29 Sep 2025 15:18:15 +0200 Subject: [PATCH 10/13] Filter out empty integration type in extended analytics (#153188) --- homeassistant/components/analytics/analytics.py | 2 +- tests/common.py | 1 + tests/components/analytics/test_analytics.py | 4 ++-- tests/components/diagnostics/test_init.py | 2 ++ 4 files changed, 6 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/analytics/analytics.py b/homeassistant/components/analytics/analytics.py index 5795be4e027999..2b67592e2f9224 100644 --- a/homeassistant/components/analytics/analytics.py +++ b/homeassistant/components/analytics/analytics.py @@ -551,7 +551,7 @@ async def async_devices_payload(hass: HomeAssistant) -> dict: for domain, integration_info in integration_inputs.items() if (integration := integrations.get(domain)) is not None and integration.is_built_in - and integration.integration_type in ("device", "hub") + and integration.manifest.get("integration_type") in ("device", "hub") } # Call integrations that implement the analytics platform diff --git a/tests/common.py b/tests/common.py index e43e4bf5fee5ce..419ba0ad4666ad 100644 --- a/tests/common.py +++ b/tests/common.py @@ -934,6 +934,7 @@ def __init__( def mock_manifest(self): """Generate a mock manifest to represent this module.""" return { + "integration_type": "hub", **loader.manifest_from_legacy_module(self.DOMAIN, self), **(self._partial_manifest or {}), } diff --git a/tests/components/analytics/test_analytics.py b/tests/components/analytics/test_analytics.py index 876e34dae75e80..be8f38901ee442 100644 --- a/tests/components/analytics/test_analytics.py +++ b/tests/components/analytics/test_analytics.py @@ -1195,7 +1195,7 @@ async def test_devices_payload_with_entities( # Entity from a different integration entity_registry.async_get_or_create( domain="light", - platform="roomba", + platform="shelly", unique_id="1", device_id=device_entry.id, has_entity_name=True, @@ -1296,7 +1296,7 @@ async def test_devices_payload_with_entities( }, ], }, - "roomba": { + "shelly": { "devices": [], "entities": [ { diff --git a/tests/components/diagnostics/test_init.py b/tests/components/diagnostics/test_init.py index fe62efeebacd29..e27331811e6353 100644 --- a/tests/components/diagnostics/test_init.py +++ b/tests/components/diagnostics/test_init.py @@ -197,6 +197,7 @@ async def test_download_diagnostics( "codeowners": ["test"], "dependencies": [], "domain": "fake_integration", + "integration_type": "hub", "is_built_in": True, "overwrites_built_in": False, "name": "fake_integration", @@ -301,6 +302,7 @@ async def test_download_diagnostics( "codeowners": [], "dependencies": [], "domain": "fake_integration", + "integration_type": "hub", "is_built_in": True, "overwrites_built_in": False, "name": "fake_integration", From 89c5d498a409db1bb29a48338dc633e4306afea5 Mon Sep 17 00:00:00 2001 From: starkillerOG Date: Mon, 29 Sep 2025 15:39:29 +0200 Subject: [PATCH 11/13] Add Reolink Ai person type, vehicle type and animal type (#153170) --- homeassistant/components/reolink/icons.json | 9 +++++ homeassistant/components/reolink/sensor.py | 34 +++++++++++++++++++ homeassistant/components/reolink/strings.json | 23 +++++++++++++ tests/components/reolink/conftest.py | 11 ++++++ 4 files changed, 77 insertions(+) diff --git a/homeassistant/components/reolink/icons.json b/homeassistant/components/reolink/icons.json index 736f8c947b2eea..dfc7ca43608d8a 100644 --- a/homeassistant/components/reolink/icons.json +++ b/homeassistant/components/reolink/icons.json @@ -462,6 +462,15 @@ }, "sd_storage": { "default": "mdi:micro-sd" + }, + "person_type": { + "default": "mdi:account" + }, + "vehicle_type": { + "default": "mdi:car" + }, + "animal_type": { + "default": "mdi:paw" } }, "siren": { diff --git a/homeassistant/components/reolink/sensor.py b/homeassistant/components/reolink/sensor.py index d832bf10e28e90..a0939046a1728b 100644 --- a/homeassistant/components/reolink/sensor.py +++ b/homeassistant/components/reolink/sensor.py @@ -8,6 +8,7 @@ from decimal import Decimal from reolink_aio.api import Host +from reolink_aio.const import YOLO_DETECT_TYPES from reolink_aio.enums import BatteryEnum from homeassistant.components.sensor import ( @@ -135,6 +136,39 @@ class ReolinkHostSensorEntityDescription( value=lambda api, ch: api.wifi_signal(ch), supported=lambda api, ch: api.supported(ch, "wifi"), ), + ReolinkSensorEntityDescription( + key="person_type", + cmd_id=696, + translation_key="person_type", + device_class=SensorDeviceClass.ENUM, + options=YOLO_DETECT_TYPES["people"], + value=lambda api, ch: api.baichuan.ai_detect_type(ch, "person"), + supported=lambda api, ch: ( + api.supported(ch, "ai_yolo_type") and api.supported(ch, "ai_people") + ), + ), + ReolinkSensorEntityDescription( + key="vehicle_type", + cmd_id=696, + translation_key="vehicle_type", + device_class=SensorDeviceClass.ENUM, + options=YOLO_DETECT_TYPES["vehicle"], + value=lambda api, ch: api.baichuan.ai_detect_type(ch, "vehicle"), + supported=lambda api, ch: ( + api.supported(ch, "ai_yolo_type") and api.supported(ch, "ai_vehicle") + ), + ), + ReolinkSensorEntityDescription( + key="animal_type", + cmd_id=696, + translation_key="animal_type", + device_class=SensorDeviceClass.ENUM, + options=YOLO_DETECT_TYPES["dog_cat"], + value=lambda api, ch: api.baichuan.ai_detect_type(ch, "dog_cat"), + supported=lambda api, ch: ( + api.supported(ch, "ai_yolo_type") and api.supported(ch, "ai_dog_cat") + ), + ), ) HOST_SENSORS = ( diff --git a/homeassistant/components/reolink/strings.json b/homeassistant/components/reolink/strings.json index 8afb9188e57c0b..1449477716b0cb 100644 --- a/homeassistant/components/reolink/strings.json +++ b/homeassistant/components/reolink/strings.json @@ -930,6 +930,29 @@ }, "sd_storage": { "name": "SD {hdd_index} storage" + }, + "person_type": { + "name": "Person type", + "state": { + "man": "Man", + "woman": "Woman" + } + }, + "vehicle_type": { + "name": "Vehicle type", + "state": { + "sedan": "Sedan", + "suv": "SUV", + "pickup_truck": "Pickup truck", + "motorcycle": "Motorcycle" + } + }, + "animal_type": { + "name": "Animal type", + "state": { + "dog": "Dog", + "cat": "Cat" + } } }, "siren": { diff --git a/tests/components/reolink/conftest.py b/tests/components/reolink/conftest.py index d501b146b7d9dc..82d96d32622a66 100644 --- a/tests/components/reolink/conftest.py +++ b/tests/components/reolink/conftest.py @@ -185,6 +185,17 @@ def _init_host_mock(host_mock: MagicMock) -> None: host_mock.baichuan.smart_ai_index.return_value = 1 host_mock.baichuan.smart_ai_name.return_value = "zone1" + def ai_detect_type(channel: int, object_type: str) -> str | None: + if object_type == "people": + return "man" + if object_type == "dog_cat": + return "dog" + if object_type == "vehicle": + return "motorcycle" + return None + + host_mock.baichuan.ai_detect_type = ai_detect_type + @pytest.fixture def reolink_host_class() -> Generator[MagicMock]: From 258c9ff52b453458bc2b22619f67b7333330c1d6 Mon Sep 17 00:00:00 2001 From: RogerSelwyn Date: Mon, 29 Sep 2025 15:08:42 +0100 Subject: [PATCH 12/13] Handle return result from ebusd being "empty" (#153199) --- homeassistant/components/ebusd/__init__.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/ebusd/__init__.py b/homeassistant/components/ebusd/__init__.py index 4cb8d92c391767..5c36c311bffe95 100644 --- a/homeassistant/components/ebusd/__init__.py +++ b/homeassistant/components/ebusd/__init__.py @@ -116,7 +116,11 @@ def write(self, call: ServiceCall) -> None: try: _LOGGER.debug("Opening socket to ebusd %s", name) command_result = ebusdpy.write(self._address, self._circuit, name, value) - if command_result is not None and "done" not in command_result: + if ( + command_result is not None + and "done" not in command_result + and "empty" not in command_result + ): _LOGGER.warning("Write command failed: %s", name) except RuntimeError as err: _LOGGER.error(err) From 5975cd6e09bf0ae679c48cd50a325989cd159101 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Mon, 29 Sep 2025 16:43:13 +0200 Subject: [PATCH 13/13] =?UTF-8?q?Revert=20"Add=20mg/m=C2=B3=20as=20a=20val?= =?UTF-8?q?id=20UOM=20for=20sensor/number=20Carbon=20Monoxide=20device=20c?= =?UTF-8?q?lass"=20(#153196)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- homeassistant/components/number/const.py | 7 ++----- .../components/recorder/statistics.py | 5 ----- .../components/recorder/websocket_api.py | 4 ---- homeassistant/components/sensor/const.py | 9 ++------ homeassistant/util/unit_conversion.py | 14 ------------- tests/components/sensor/test_init.py | 1 + tests/util/test_unit_conversion.py | 21 ------------------- 7 files changed, 5 insertions(+), 56 deletions(-) diff --git a/homeassistant/components/number/const.py b/homeassistant/components/number/const.py index 07a53c9cb61ee1..fab3d6f4276a8b 100644 --- a/homeassistant/components/number/const.py +++ b/homeassistant/components/number/const.py @@ -124,7 +124,7 @@ class NumberDeviceClass(StrEnum): CO = "carbon_monoxide" """Carbon Monoxide gas concentration. - Unit of measurement: `ppm` (parts per million), mg/m³ + Unit of measurement: `ppm` (parts per million) """ CO2 = "carbon_dioxide" @@ -475,10 +475,7 @@ class NumberDeviceClass(StrEnum): NumberDeviceClass.ATMOSPHERIC_PRESSURE: set(UnitOfPressure), NumberDeviceClass.BATTERY: {PERCENTAGE}, NumberDeviceClass.BLOOD_GLUCOSE_CONCENTRATION: set(UnitOfBloodGlucoseConcentration), - NumberDeviceClass.CO: { - CONCENTRATION_PARTS_PER_MILLION, - CONCENTRATION_MILLIGRAMS_PER_CUBIC_METER, - }, + NumberDeviceClass.CO: {CONCENTRATION_PARTS_PER_MILLION}, NumberDeviceClass.CO2: {CONCENTRATION_PARTS_PER_MILLION}, NumberDeviceClass.CONDUCTIVITY: set(UnitOfConductivity), NumberDeviceClass.CURRENT: set(UnitOfElectricCurrent), diff --git a/homeassistant/components/recorder/statistics.py b/homeassistant/components/recorder/statistics.py index c2a8a6c7607c5e..2321da45bb9a72 100644 --- a/homeassistant/components/recorder/statistics.py +++ b/homeassistant/components/recorder/statistics.py @@ -46,7 +46,6 @@ AreaConverter, BaseUnitConverter, BloodGlucoseConcentrationConverter, - CarbonMonoxideConcentrationConverter, ConductivityConverter, DataRateConverter, DistanceConverter, @@ -205,10 +204,6 @@ def query_circular_mean(table: type[StatisticsBase]) -> tuple[Label, Label]: **dict.fromkeys( MassVolumeConcentrationConverter.VALID_UNITS, MassVolumeConcentrationConverter ), - **dict.fromkeys( - CarbonMonoxideConcentrationConverter.VALID_UNITS, - CarbonMonoxideConcentrationConverter, - ), **dict.fromkeys(ConductivityConverter.VALID_UNITS, ConductivityConverter), **dict.fromkeys(DataRateConverter.VALID_UNITS, DataRateConverter), **dict.fromkeys(DistanceConverter.VALID_UNITS, DistanceConverter), diff --git a/homeassistant/components/recorder/websocket_api.py b/homeassistant/components/recorder/websocket_api.py index c65a11cee2ae58..4f798fb86d01e7 100644 --- a/homeassistant/components/recorder/websocket_api.py +++ b/homeassistant/components/recorder/websocket_api.py @@ -19,7 +19,6 @@ ApparentPowerConverter, AreaConverter, BloodGlucoseConcentrationConverter, - CarbonMonoxideConcentrationConverter, ConductivityConverter, DataRateConverter, DistanceConverter, @@ -67,9 +66,6 @@ vol.Optional("blood_glucose_concentration"): vol.In( BloodGlucoseConcentrationConverter.VALID_UNITS ), - vol.Optional("carbon_monoxide"): vol.In( - CarbonMonoxideConcentrationConverter.VALID_UNITS - ), vol.Optional("concentration"): vol.In( MassVolumeConcentrationConverter.VALID_UNITS ), diff --git a/homeassistant/components/sensor/const.py b/homeassistant/components/sensor/const.py index b91bd26d410cfc..87ddf4445a015c 100644 --- a/homeassistant/components/sensor/const.py +++ b/homeassistant/components/sensor/const.py @@ -51,7 +51,6 @@ AreaConverter, BaseUnitConverter, BloodGlucoseConcentrationConverter, - CarbonMonoxideConcentrationConverter, ConductivityConverter, DataRateConverter, DistanceConverter, @@ -157,7 +156,7 @@ class SensorDeviceClass(StrEnum): CO = "carbon_monoxide" """Carbon Monoxide gas concentration. - Unit of measurement: `ppm` (parts per million), `mg/m³` + Unit of measurement: `ppm` (parts per million) """ CO2 = "carbon_dioxide" @@ -544,7 +543,6 @@ class SensorStateClass(StrEnum): SensorDeviceClass.AREA: AreaConverter, SensorDeviceClass.ATMOSPHERIC_PRESSURE: PressureConverter, SensorDeviceClass.BLOOD_GLUCOSE_CONCENTRATION: BloodGlucoseConcentrationConverter, - SensorDeviceClass.CO: CarbonMonoxideConcentrationConverter, SensorDeviceClass.CONDUCTIVITY: ConductivityConverter, SensorDeviceClass.CURRENT: ElectricCurrentConverter, SensorDeviceClass.DATA_RATE: DataRateConverter, @@ -586,10 +584,7 @@ class SensorStateClass(StrEnum): SensorDeviceClass.ATMOSPHERIC_PRESSURE: set(UnitOfPressure), SensorDeviceClass.BATTERY: {PERCENTAGE}, SensorDeviceClass.BLOOD_GLUCOSE_CONCENTRATION: set(UnitOfBloodGlucoseConcentration), - SensorDeviceClass.CO: { - CONCENTRATION_PARTS_PER_MILLION, - CONCENTRATION_MILLIGRAMS_PER_CUBIC_METER, - }, + SensorDeviceClass.CO: {CONCENTRATION_PARTS_PER_MILLION}, SensorDeviceClass.CO2: {CONCENTRATION_PARTS_PER_MILLION}, SensorDeviceClass.CONDUCTIVITY: set(UnitOfConductivity), SensorDeviceClass.CURRENT: set(UnitOfElectricCurrent), diff --git a/homeassistant/util/unit_conversion.py b/homeassistant/util/unit_conversion.py index 0483878f547c5e..dba858c07bffbd 100644 --- a/homeassistant/util/unit_conversion.py +++ b/homeassistant/util/unit_conversion.py @@ -168,20 +168,6 @@ def _are_unit_inverses(cls, from_unit: str | None, to_unit: str | None) -> bool: return (from_unit in cls._UNIT_INVERSES) != (to_unit in cls._UNIT_INVERSES) -class CarbonMonoxideConcentrationConverter(BaseUnitConverter): - """Convert carbon monoxide ratio to mass per volume.""" - - UNIT_CLASS = "carbon_monoxide" - _UNIT_CONVERSION: dict[str | None, float] = { - CONCENTRATION_PARTS_PER_MILLION: 1, - CONCENTRATION_MILLIGRAMS_PER_CUBIC_METER: 1.145609, - } - VALID_UNITS = { - CONCENTRATION_PARTS_PER_MILLION, - CONCENTRATION_MILLIGRAMS_PER_CUBIC_METER, - } - - class DataRateConverter(BaseUnitConverter): """Utility to convert data rate values.""" diff --git a/tests/components/sensor/test_init.py b/tests/components/sensor/test_init.py index 5d53cfe6d53a7a..36e8ab4576f771 100644 --- a/tests/components/sensor/test_init.py +++ b/tests/components/sensor/test_init.py @@ -3008,6 +3008,7 @@ def test_device_class_converters_are_complete() -> None: no_converter_device_classes = { SensorDeviceClass.AQI, SensorDeviceClass.BATTERY, + SensorDeviceClass.CO, SensorDeviceClass.CO2, SensorDeviceClass.DATE, SensorDeviceClass.ENUM, diff --git a/tests/util/test_unit_conversion.py b/tests/util/test_unit_conversion.py index 0d14a30a1b87ba..d9377779b68e83 100644 --- a/tests/util/test_unit_conversion.py +++ b/tests/util/test_unit_conversion.py @@ -44,7 +44,6 @@ AreaConverter, BaseUnitConverter, BloodGlucoseConcentrationConverter, - CarbonMonoxideConcentrationConverter, ConductivityConverter, DataRateConverter, DistanceConverter, @@ -79,7 +78,6 @@ AreaConverter, BloodGlucoseConcentrationConverter, MassVolumeConcentrationConverter, - CarbonMonoxideConcentrationConverter, ConductivityConverter, DataRateConverter, DistanceConverter, @@ -116,11 +114,6 @@ UnitOfBloodGlucoseConcentration.MILLIMOLE_PER_LITER, 18, ), - CarbonMonoxideConcentrationConverter: ( - CONCENTRATION_MILLIGRAMS_PER_CUBIC_METER, - CONCENTRATION_PARTS_PER_MILLION, - 1.145609, - ), ConductivityConverter: ( UnitOfConductivity.MICROSIEMENS_PER_CM, UnitOfConductivity.MILLISIEMENS_PER_CM, @@ -287,20 +280,6 @@ UnitOfBloodGlucoseConcentration.MILLIGRAMS_PER_DECILITER, ), ], - CarbonMonoxideConcentrationConverter: [ - ( - 1, - CONCENTRATION_PARTS_PER_MILLION, - 1.145609, - CONCENTRATION_MILLIGRAMS_PER_CUBIC_METER, - ), - ( - 120, - CONCENTRATION_MILLIGRAMS_PER_CUBIC_METER, - 104.74778, - CONCENTRATION_PARTS_PER_MILLION, - ), - ], ConductivityConverter: [ # Deprecated to deprecated (5, UnitOfConductivity.SIEMENS, 5e3, UnitOfConductivity.MILLISIEMENS),