diff --git a/.github/workflows/ci-build-and-test.yml b/.github/workflows/ci-build-and-test.yml index e0f05b3..b8594f6 100644 --- a/.github/workflows/ci-build-and-test.yml +++ b/.github/workflows/ci-build-and-test.yml @@ -22,4 +22,4 @@ jobs: dotnet-version: '9.0.x' - name: 🛠️ Build code - run: dotnet build --configuration Release -p:TreatWarningsAsErrors=true \ No newline at end of file + run: dotnet build CodeCasa.Showcase.sln --configuration Release -p:TreatWarningsAsErrors=true \ No newline at end of file diff --git a/CodeCasa.Showcase.withLocalCodeCasa.sln b/CodeCasa.Showcase.withLocalCodeCasa.sln new file mode 100644 index 0000000..4cb1045 --- /dev/null +++ b/CodeCasa.Showcase.withLocalCodeCasa.sln @@ -0,0 +1,181 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 18 +VisualStudioVersion = 18.1.11312.151 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CodeCasa.Dashboard", "src\CodeCasa.Dashboard\CodeCasa.Dashboard.csproj", "{D734015F-45B7-4689-A151-C279A5F6A61A}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CodeCasa.AutoGenerated", "src\CodeCasa.AutoGenerated\CodeCasa.AutoGenerated.csproj", "{338F0D10-B628-451B-86D5-B22393778D7C}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CodeCasa.Automations", "src\CodeCasa.Automations\CodeCasa.Automations.csproj", "{69E23803-F031-4294-8C32-3DBA59A3A08E}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CodeCasa.NetDaemon.Utilities", "src\CodeCasa.NetDaemon.Utilities\CodeCasa.NetDaemon.Utilities.csproj", "{AF4EB14B-3F32-4DBD-8552-56CE2A470E48}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Entities", "Entities", "{B8417343-120D-42CE-9802-F9584E63E3AF}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CodeCasa.Shared", "src\CodeCasa.Shared\CodeCasa.Shared.csproj", "{2497FDAA-46A9-4D2F-8411-98EE08FA2D23}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CodeCasa.CustomEntities.Core", "src\CodeCasa.CustomEntities.Core\CodeCasa.CustomEntities.Core.csproj", "{601FA957-4116-4DB6-AA17-DF358DB4D149}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CodeCasa.CustomEntities.Automation", "src\CodeCasa.CustomEntities.Automation\CodeCasa.CustomEntities.Automation.csproj", "{F91714FE-A475-4680-BF70-7B50E163DA3C}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "CodeCasa", "CodeCasa", "{69FE0431-17B3-4A14-ABAE-BA106D3BF6DF}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CodeCasa.Abstractions", "..\CodeCasa\src\CodeCasa.Abstractions\CodeCasa.Abstractions.csproj", "{EA05C3BC-A411-AFC7-0820-739AC4800BAA}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CodeCasa.AutomationPipelines", "..\CodeCasa\src\CodeCasa.AutomationPipelines\CodeCasa.AutomationPipelines.csproj", "{DA77B96C-7E87-8A51-0A48-F818D85AA7BD}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CodeCasa.AutomationPipelines.Lights", "..\CodeCasa\src\CodeCasa.AutomationPipelines.Lights\CodeCasa.AutomationPipelines.Lights.csproj", "{9D3D66D6-1263-F2D8-3252-56CD60CEAE9B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CodeCasa.AutomationPipelines.Lights.NetDaemon", "..\CodeCasa\src\CodeCasa.AutomationPipelines.Lights.NetDaemon\CodeCasa.AutomationPipelines.Lights.NetDaemon.csproj", "{D1431BA6-C9FA-D3F9-E9F4-3FDE8A2AEB9E}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CodeCasa.Lights", "..\CodeCasa\src\CodeCasa.Lights\CodeCasa.Lights.csproj", "{946F6E93-4745-4E88-1EB2-D412F7FA4069}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CodeCasa.Lights.NetDaemon", "..\CodeCasa\src\CodeCasa.Lights.NetDaemon\CodeCasa.Lights.NetDaemon.csproj", "{DF7DB0BF-3AEE-805F-16EE-74237FDC24FB}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CodeCasa.Lights.NetDaemon.Scenes", "..\CodeCasa\src\CodeCasa.Lights.NetDaemon.Scenes\CodeCasa.Lights.NetDaemon.Scenes.csproj", "{01488D66-79A9-E199-2032-6E5108834C0D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CodeCasa.NetDaemon.Extensions.Observables", "..\CodeCasa\src\CodeCasa.NetDaemon.Extensions.Observables\CodeCasa.NetDaemon.Extensions.Observables.csproj", "{770317DB-B681-DE60-54DF-E221B3F39C48}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CodeCasa.NetDaemon.RuntimeState", "..\CodeCasa\src\CodeCasa.NetDaemon.RuntimeState\CodeCasa.NetDaemon.RuntimeState.csproj", "{D72EED6A-8CD3-CDB6-7E7E-1781005CD6D8}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CodeCasa.NetDaemon.TypedEntities", "..\CodeCasa\src\CodeCasa.NetDaemon.TypedEntities\CodeCasa.NetDaemon.TypedEntities.csproj", "{781677A8-D8B6-EBE7-6C4D-08961A68CBCD}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CodeCasa.Notifications.InputSelect.NetDaemon", "..\CodeCasa\src\CodeCasa.Notifications.InputSelect.NetDaemon\CodeCasa.Notifications.InputSelect.NetDaemon.csproj", "{A48B5B2A-216A-A78B-C449-66372044C6F1}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CodeCasa.Notifications.Lights", "..\CodeCasa\src\CodeCasa.Notifications.Lights\CodeCasa.Notifications.Lights.csproj", "{0F0DD0FE-7475-D32D-FF81-79D9E837CD21}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CodeCasa.Notifications.Phone.NetDaemon", "..\CodeCasa\src\CodeCasa.Notifications.Phone.NetDaemon\CodeCasa.Notifications.Phone.NetDaemon.csproj", "{5E27A3C9-DEC8-EE85-3AD0-2AC35879186F}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CodeCasa.NetDaemon.Sensors.Composite", "..\CodeCasa\src\CodeCasa.NetDaemon.Sensors.Composite\CodeCasa.NetDaemon.Sensors.Composite.csproj", "{C52ABD0A-8097-D982-629D-F57CCD5A4AA8}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CodeCasa.Lights.Timelines", "..\CodeCasa\src\CodeCasa.Lights.Timelines\CodeCasa.Lights.Timelines.csproj", "{F0A38131-55E6-85EB-84A7-43B29068C457}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CodeCasa.Mqtt", "src\CodeCasa.Mqtt\CodeCasa.Mqtt.csproj", "{E67C8206-7782-4548-9DB8-CB0DA6B330AC}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {D734015F-45B7-4689-A151-C279A5F6A61A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D734015F-45B7-4689-A151-C279A5F6A61A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D734015F-45B7-4689-A151-C279A5F6A61A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D734015F-45B7-4689-A151-C279A5F6A61A}.Release|Any CPU.Build.0 = Release|Any CPU + {338F0D10-B628-451B-86D5-B22393778D7C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {338F0D10-B628-451B-86D5-B22393778D7C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {338F0D10-B628-451B-86D5-B22393778D7C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {338F0D10-B628-451B-86D5-B22393778D7C}.Release|Any CPU.Build.0 = Release|Any CPU + {69E23803-F031-4294-8C32-3DBA59A3A08E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {69E23803-F031-4294-8C32-3DBA59A3A08E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {69E23803-F031-4294-8C32-3DBA59A3A08E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {69E23803-F031-4294-8C32-3DBA59A3A08E}.Release|Any CPU.Build.0 = Release|Any CPU + {AF4EB14B-3F32-4DBD-8552-56CE2A470E48}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AF4EB14B-3F32-4DBD-8552-56CE2A470E48}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AF4EB14B-3F32-4DBD-8552-56CE2A470E48}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AF4EB14B-3F32-4DBD-8552-56CE2A470E48}.Release|Any CPU.Build.0 = Release|Any CPU + {2497FDAA-46A9-4D2F-8411-98EE08FA2D23}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2497FDAA-46A9-4D2F-8411-98EE08FA2D23}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2497FDAA-46A9-4D2F-8411-98EE08FA2D23}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2497FDAA-46A9-4D2F-8411-98EE08FA2D23}.Release|Any CPU.Build.0 = Release|Any CPU + {601FA957-4116-4DB6-AA17-DF358DB4D149}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {601FA957-4116-4DB6-AA17-DF358DB4D149}.Debug|Any CPU.Build.0 = Debug|Any CPU + {601FA957-4116-4DB6-AA17-DF358DB4D149}.Release|Any CPU.ActiveCfg = Release|Any CPU + {601FA957-4116-4DB6-AA17-DF358DB4D149}.Release|Any CPU.Build.0 = Release|Any CPU + {F91714FE-A475-4680-BF70-7B50E163DA3C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F91714FE-A475-4680-BF70-7B50E163DA3C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F91714FE-A475-4680-BF70-7B50E163DA3C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F91714FE-A475-4680-BF70-7B50E163DA3C}.Release|Any CPU.Build.0 = Release|Any CPU + {EA05C3BC-A411-AFC7-0820-739AC4800BAA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EA05C3BC-A411-AFC7-0820-739AC4800BAA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EA05C3BC-A411-AFC7-0820-739AC4800BAA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EA05C3BC-A411-AFC7-0820-739AC4800BAA}.Release|Any CPU.Build.0 = Release|Any CPU + {DA77B96C-7E87-8A51-0A48-F818D85AA7BD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DA77B96C-7E87-8A51-0A48-F818D85AA7BD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DA77B96C-7E87-8A51-0A48-F818D85AA7BD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DA77B96C-7E87-8A51-0A48-F818D85AA7BD}.Release|Any CPU.Build.0 = Release|Any CPU + {9D3D66D6-1263-F2D8-3252-56CD60CEAE9B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9D3D66D6-1263-F2D8-3252-56CD60CEAE9B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9D3D66D6-1263-F2D8-3252-56CD60CEAE9B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9D3D66D6-1263-F2D8-3252-56CD60CEAE9B}.Release|Any CPU.Build.0 = Release|Any CPU + {D1431BA6-C9FA-D3F9-E9F4-3FDE8A2AEB9E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D1431BA6-C9FA-D3F9-E9F4-3FDE8A2AEB9E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D1431BA6-C9FA-D3F9-E9F4-3FDE8A2AEB9E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D1431BA6-C9FA-D3F9-E9F4-3FDE8A2AEB9E}.Release|Any CPU.Build.0 = Release|Any CPU + {946F6E93-4745-4E88-1EB2-D412F7FA4069}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {946F6E93-4745-4E88-1EB2-D412F7FA4069}.Debug|Any CPU.Build.0 = Debug|Any CPU + {946F6E93-4745-4E88-1EB2-D412F7FA4069}.Release|Any CPU.ActiveCfg = Release|Any CPU + {946F6E93-4745-4E88-1EB2-D412F7FA4069}.Release|Any CPU.Build.0 = Release|Any CPU + {DF7DB0BF-3AEE-805F-16EE-74237FDC24FB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DF7DB0BF-3AEE-805F-16EE-74237FDC24FB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DF7DB0BF-3AEE-805F-16EE-74237FDC24FB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DF7DB0BF-3AEE-805F-16EE-74237FDC24FB}.Release|Any CPU.Build.0 = Release|Any CPU + {01488D66-79A9-E199-2032-6E5108834C0D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {01488D66-79A9-E199-2032-6E5108834C0D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {01488D66-79A9-E199-2032-6E5108834C0D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {01488D66-79A9-E199-2032-6E5108834C0D}.Release|Any CPU.Build.0 = Release|Any CPU + {770317DB-B681-DE60-54DF-E221B3F39C48}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {770317DB-B681-DE60-54DF-E221B3F39C48}.Debug|Any CPU.Build.0 = Debug|Any CPU + {770317DB-B681-DE60-54DF-E221B3F39C48}.Release|Any CPU.ActiveCfg = Release|Any CPU + {770317DB-B681-DE60-54DF-E221B3F39C48}.Release|Any CPU.Build.0 = Release|Any CPU + {D72EED6A-8CD3-CDB6-7E7E-1781005CD6D8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D72EED6A-8CD3-CDB6-7E7E-1781005CD6D8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D72EED6A-8CD3-CDB6-7E7E-1781005CD6D8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D72EED6A-8CD3-CDB6-7E7E-1781005CD6D8}.Release|Any CPU.Build.0 = Release|Any CPU + {781677A8-D8B6-EBE7-6C4D-08961A68CBCD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {781677A8-D8B6-EBE7-6C4D-08961A68CBCD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {781677A8-D8B6-EBE7-6C4D-08961A68CBCD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {781677A8-D8B6-EBE7-6C4D-08961A68CBCD}.Release|Any CPU.Build.0 = Release|Any CPU + {A48B5B2A-216A-A78B-C449-66372044C6F1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A48B5B2A-216A-A78B-C449-66372044C6F1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A48B5B2A-216A-A78B-C449-66372044C6F1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A48B5B2A-216A-A78B-C449-66372044C6F1}.Release|Any CPU.Build.0 = Release|Any CPU + {0F0DD0FE-7475-D32D-FF81-79D9E837CD21}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0F0DD0FE-7475-D32D-FF81-79D9E837CD21}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0F0DD0FE-7475-D32D-FF81-79D9E837CD21}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0F0DD0FE-7475-D32D-FF81-79D9E837CD21}.Release|Any CPU.Build.0 = Release|Any CPU + {5E27A3C9-DEC8-EE85-3AD0-2AC35879186F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5E27A3C9-DEC8-EE85-3AD0-2AC35879186F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5E27A3C9-DEC8-EE85-3AD0-2AC35879186F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5E27A3C9-DEC8-EE85-3AD0-2AC35879186F}.Release|Any CPU.Build.0 = Release|Any CPU + {C52ABD0A-8097-D982-629D-F57CCD5A4AA8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C52ABD0A-8097-D982-629D-F57CCD5A4AA8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C52ABD0A-8097-D982-629D-F57CCD5A4AA8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C52ABD0A-8097-D982-629D-F57CCD5A4AA8}.Release|Any CPU.Build.0 = Release|Any CPU + {F0A38131-55E6-85EB-84A7-43B29068C457}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F0A38131-55E6-85EB-84A7-43B29068C457}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F0A38131-55E6-85EB-84A7-43B29068C457}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F0A38131-55E6-85EB-84A7-43B29068C457}.Release|Any CPU.Build.0 = Release|Any CPU + {E67C8206-7782-4548-9DB8-CB0DA6B330AC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E67C8206-7782-4548-9DB8-CB0DA6B330AC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E67C8206-7782-4548-9DB8-CB0DA6B330AC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E67C8206-7782-4548-9DB8-CB0DA6B330AC}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {338F0D10-B628-451B-86D5-B22393778D7C} = {B8417343-120D-42CE-9802-F9584E63E3AF} + {601FA957-4116-4DB6-AA17-DF358DB4D149} = {B8417343-120D-42CE-9802-F9584E63E3AF} + {F91714FE-A475-4680-BF70-7B50E163DA3C} = {B8417343-120D-42CE-9802-F9584E63E3AF} + {EA05C3BC-A411-AFC7-0820-739AC4800BAA} = {69FE0431-17B3-4A14-ABAE-BA106D3BF6DF} + {DA77B96C-7E87-8A51-0A48-F818D85AA7BD} = {69FE0431-17B3-4A14-ABAE-BA106D3BF6DF} + {9D3D66D6-1263-F2D8-3252-56CD60CEAE9B} = {69FE0431-17B3-4A14-ABAE-BA106D3BF6DF} + {D1431BA6-C9FA-D3F9-E9F4-3FDE8A2AEB9E} = {69FE0431-17B3-4A14-ABAE-BA106D3BF6DF} + {946F6E93-4745-4E88-1EB2-D412F7FA4069} = {69FE0431-17B3-4A14-ABAE-BA106D3BF6DF} + {DF7DB0BF-3AEE-805F-16EE-74237FDC24FB} = {69FE0431-17B3-4A14-ABAE-BA106D3BF6DF} + {01488D66-79A9-E199-2032-6E5108834C0D} = {69FE0431-17B3-4A14-ABAE-BA106D3BF6DF} + {770317DB-B681-DE60-54DF-E221B3F39C48} = {69FE0431-17B3-4A14-ABAE-BA106D3BF6DF} + {D72EED6A-8CD3-CDB6-7E7E-1781005CD6D8} = {69FE0431-17B3-4A14-ABAE-BA106D3BF6DF} + {781677A8-D8B6-EBE7-6C4D-08961A68CBCD} = {69FE0431-17B3-4A14-ABAE-BA106D3BF6DF} + {A48B5B2A-216A-A78B-C449-66372044C6F1} = {69FE0431-17B3-4A14-ABAE-BA106D3BF6DF} + {0F0DD0FE-7475-D32D-FF81-79D9E837CD21} = {69FE0431-17B3-4A14-ABAE-BA106D3BF6DF} + {5E27A3C9-DEC8-EE85-3AD0-2AC35879186F} = {69FE0431-17B3-4A14-ABAE-BA106D3BF6DF} + {C52ABD0A-8097-D982-629D-F57CCD5A4AA8} = {69FE0431-17B3-4A14-ABAE-BA106D3BF6DF} + {F0A38131-55E6-85EB-84A7-43B29068C457} = {69FE0431-17B3-4A14-ABAE-BA106D3BF6DF} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {C77A270A-D381-419D-AEA1-1592AA2B4B6E} + EndGlobalSection +EndGlobal diff --git a/Directory.Build.targets b/Directory.Build.targets new file mode 100644 index 0000000..6c48631 --- /dev/null +++ b/Directory.Build.targets @@ -0,0 +1,39 @@ + + + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/README.md b/README.md index 458687a..5b70467 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ A smart home implementation example using C# and NetDaemon. This repository explores creative and powerful ways to use a rich programming language like C# for home automation. From custom logic to seamless integrations, you'll find practical examples and unique ideas to elevate your smart home setup. Stay tuned for ongoing updates and new features! -## 📖 Table of Contents +## 📖 Table of Contents - [Architectures & Implementations](#🛠️-architectures--implementations) - [Blazor Frontend (NSPanel Pro)](#blazor-frontend-nspanel-pro) - [People](#people) @@ -14,6 +14,9 @@ This repository explores creative and powerful ways to use a rich programming la - [Input Select Notifications](#input-select-notifications) - [Automation Pipelines](#automation-pipelines) - [Projects Overview](#🔧-projects-overview) +- [Local Debugging with CodeCasa Projects](#local-debugging-with-codecasa-projects) + - [CodeCasa.Showcase.withLocalCodeCasa.sln](#codecasashowcasewithlocalcodecasasln) + - [Requirements](#requirements) ## 🛠️ Architectures & Implementations @@ -146,3 +149,17 @@ These projects combine existing entities into compound entities or creates entir ### 🛠️ NetDaemon Utilities (`CodeCasa.NetDaemon.Utilities`) A collection of utility classes for working with NetDaemon entities. These utilities are tailored to the use-cases of this project but may be useful for similar implementations in other projects. + +## Local Debugging with CodeCasa Projects + +This repository can optionally use **local versions of the CodeCasa libraries** for debugging instead of the NuGet packages. + +### `CodeCasa.Showcase.withLocalCodeCasa.sln` + +- A local solution file that **includes all showcase projects and any local CodeCasa projects**. +- Opening this solution **automatically activates the project reference replacement**, so NuGet packages are replaced with local projects. +- Breakpoints in CodeCasa projects work normally, and projects appear in Solution Explorer. + +### Requirements + +- You must **checkout the [CodeCasa repository](https://github.com/DevJasperNL/CodeCasa)** as a sibling of this repo (same parent directory as this repo). \ No newline at end of file diff --git a/src/CodeCasa.AutoGenerated/BinarySensorAttributes.cs b/src/CodeCasa.AutoGenerated/BinarySensorAttributes.cs new file mode 100644 index 0000000..8d2a64e --- /dev/null +++ b/src/CodeCasa.AutoGenerated/BinarySensorAttributes.cs @@ -0,0 +1,24 @@ +using System.Text.Json.Serialization; + +namespace CodeCasa.AutoGenerated; + +public partial record BinarySensorAttributes +{ + [JsonPropertyName("device_class")] + public string? DeviceClass { get; init; } + + [JsonPropertyName("friendly_name")] + public string? FriendlyName { get; init; } + + [JsonPropertyName("entity_id")] + public IReadOnlyList? EntityId { get; init; } + + [JsonPropertyName("icon")] + public string? Icon { get; init; } + + [JsonPropertyName("restored")] + public bool? Restored { get; init; } + + [JsonPropertyName("supported_features")] + public double? SupportedFeatures { get; init; } +} \ No newline at end of file diff --git a/src/CodeCasa.AutoGenerated/BinarySensorEntities.cs b/src/CodeCasa.AutoGenerated/BinarySensorEntities.cs new file mode 100644 index 0000000..6e8ec33 --- /dev/null +++ b/src/CodeCasa.AutoGenerated/BinarySensorEntities.cs @@ -0,0 +1,24 @@ +using NetDaemon.HassModel; + +namespace CodeCasa.AutoGenerated; + +public partial class BinarySensorEntities +{ + private readonly IHaContext _haContext; + + public BinarySensorEntities(IHaContext haContext) + { + _haContext = haContext; + } + + /// Enumerates all binary_sensor entities currently registered (at runtime) in Home Assistant as BinarySensorEntity + public IEnumerable EnumerateAll() => _haContext.GetAllEntities() + .Where(e => e.EntityId.StartsWith("binary_sensor.")).Select(e => new BinarySensorEntity(e)); + + ///Bathroom Motion Sensor Occupancy + public BinarySensorEntity BathroomMotionSensorOccupancy => new(_haContext, "binary_sensor.bathroom_motion_sensor_occupancy"); + ///Office Motion Sensor Occupancy + public BinarySensorEntity OfficeMotionSensorOccupancy => new(_haContext, "binary_sensor.office_motion_sensor_occupancy"); + ///Upstairs Hallway Attic Motion Sensor Occupancy + public BinarySensorEntity UpstairsHallwayAtticMotionSensorOccupancy => new(_haContext, "binary_sensor.upstairs_hallway_attic_motion_sensor_occupancy"); +} \ No newline at end of file diff --git a/src/CodeCasa.AutoGenerated/BinarySensorEntity.cs b/src/CodeCasa.AutoGenerated/BinarySensorEntity.cs new file mode 100644 index 0000000..03b9b6d --- /dev/null +++ b/src/CodeCasa.AutoGenerated/BinarySensorEntity.cs @@ -0,0 +1,15 @@ +using NetDaemon.HassModel; +using NetDaemon.HassModel.Entities; + +namespace CodeCasa.AutoGenerated; + +public partial record BinarySensorEntity : Entity, BinarySensorAttributes>, IBinarySensorEntityCore +{ + public BinarySensorEntity(IHaContext haContext, string entityId) : base(haContext, entityId) + { + } + + public BinarySensorEntity(IEntityCore entity) : base(entity) + { + } +} \ No newline at end of file diff --git a/src/CodeCasa.AutoGenerated/CodeCasa.AutoGenerated.csproj b/src/CodeCasa.AutoGenerated/CodeCasa.AutoGenerated.csproj index a773f5b..39289d3 100644 --- a/src/CodeCasa.AutoGenerated/CodeCasa.AutoGenerated.csproj +++ b/src/CodeCasa.AutoGenerated/CodeCasa.AutoGenerated.csproj @@ -7,8 +7,8 @@ - - + + diff --git a/src/CodeCasa.AutoGenerated/Extensions/GeneratedExtensions.cs b/src/CodeCasa.AutoGenerated/Extensions/GeneratedExtensions.cs index 57cb376..7d72c44 100644 --- a/src/CodeCasa.AutoGenerated/Extensions/GeneratedExtensions.cs +++ b/src/CodeCasa.AutoGenerated/Extensions/GeneratedExtensions.cs @@ -10,12 +10,14 @@ public static IServiceCollection AddHomeAssistantGenerated(this IServiceCollecti return serviceCollection .AddTransient() .AddTransient() + .AddTransient() .AddTransient() .AddTransient() .AddTransient() .AddTransient() .AddTransient() .AddTransient() + .AddTransient() .AddTransient(); } } \ No newline at end of file diff --git a/src/CodeCasa.AutoGenerated/LightAttributes.cs b/src/CodeCasa.AutoGenerated/LightAttributes.cs index b272a2c..52da3cc 100644 --- a/src/CodeCasa.AutoGenerated/LightAttributes.cs +++ b/src/CodeCasa.AutoGenerated/LightAttributes.cs @@ -7,12 +7,12 @@ public partial record LightAttributes [JsonPropertyName("supported_color_modes")] public IReadOnlyList? SupportedColorModes { get; init; } - [JsonPropertyName("friendly_name")] - public string? FriendlyName { get; init; } - [JsonPropertyName("supported_features")] public double? SupportedFeatures { get; init; } + [JsonPropertyName("color_mode")] + public string? ColorMode { get; init; } + [JsonPropertyName("min_color_temp_kelvin")] public double? MinColorTempKelvin { get; init; } @@ -25,39 +25,39 @@ public partial record LightAttributes [JsonPropertyName("max_mireds")] public double? MaxMireds { get; init; } - [JsonPropertyName("effect_list")] - public IReadOnlyList? EffectList { get; init; } - - [JsonPropertyName("color_mode")] - public string? ColorMode { get; init; } - [JsonPropertyName("brightness")] - public object? Brightness { get; init; } + public double? Brightness { get; init; } [JsonPropertyName("color_temp_kelvin")] - public object? ColorTempKelvin { get; init; } + public double? ColorTempKelvin { get; init; } [JsonPropertyName("color_temp")] - public object? ColorTemp { get; init; } + public double? ColorTemp { get; init; } [JsonPropertyName("hs_color")] - public object? HsColor { get; init; } + public IReadOnlyList? HsColor { get; init; } [JsonPropertyName("rgb_color")] - public object? RgbColor { get; init; } + public IReadOnlyList? RgbColor { get; init; } [JsonPropertyName("xy_color")] - public object? XyColor { get; init; } + public IReadOnlyList? XyColor { get; init; } [JsonPropertyName("entity_id")] public IReadOnlyList? EntityId { get; init; } - [JsonPropertyName("icon")] - public string? Icon { get; init; } + [JsonPropertyName("effect_list")] + public IReadOnlyList? EffectList { get; init; } - [JsonPropertyName("restored")] - public bool? Restored { get; init; } + [JsonPropertyName("color")] + public object? Color { get; init; } [JsonPropertyName("effect")] public object? Effect { get; init; } + + [JsonPropertyName("icon")] + public string? Icon { get; init; } + + [JsonPropertyName("friendly_name")] + public string? FriendlyName { get; init; } } \ No newline at end of file diff --git a/src/CodeCasa.AutoGenerated/LightEntities.cs b/src/CodeCasa.AutoGenerated/LightEntities.cs index 2e1c67b..dbdd1a3 100644 --- a/src/CodeCasa.AutoGenerated/LightEntities.cs +++ b/src/CodeCasa.AutoGenerated/LightEntities.cs @@ -26,5 +26,4 @@ public LightEntities(IHaContext haContext) public LightEntity BackyardPergolaStringLights => new(_haContext, "light.backyard_pergola_string_lights"); ///Backyard Porch Kerosene Light public LightEntity BackyardPorchStringLights => new(_haContext, "light.backyard_porch_string_lights"); - ///Backyard String Lights } \ No newline at end of file diff --git a/src/CodeCasa.AutoGenerated/SceneEntities.cs b/src/CodeCasa.AutoGenerated/SceneEntities.cs new file mode 100644 index 0000000..2f06f77 --- /dev/null +++ b/src/CodeCasa.AutoGenerated/SceneEntities.cs @@ -0,0 +1,46 @@ +using NetDaemon.HassModel; +using NetDaemon.HassModel.Entities; +using System.Text.Json.Serialization; + +namespace CodeCasa.AutoGenerated; + +public partial class SceneEntities +{ + private readonly IHaContext _haContext; + public SceneEntities(IHaContext haContext) + { + _haContext = haContext; + } + + /// Enumerates all scene entities currently registered (at runtime) in Home Assistant as SceneEntity + public IEnumerable EnumerateAll() => _haContext.GetAllEntities().Where(e => e.EntityId.StartsWith("scene.")).Select(e => new SceneEntity(e)); + + ///Living Room Ambiance + public SceneEntity LivingRoomAmbiance => new(_haContext, "scene.living_room_ambiance"); +} + +public partial record SceneEntity : Entity, SceneAttributes>, ISceneEntityCore +{ + public SceneEntity(IHaContext haContext, string entityId) : base(haContext, entityId) + { + } + + public SceneEntity(IEntityCore entity) : base(entity) + { + } +} + +public partial record SceneAttributes +{ + [JsonPropertyName("entity_id")] + public IReadOnlyList? EntityId { get; init; } + + [JsonPropertyName("id")] + public string? Id { get; init; } + + [JsonPropertyName("icon")] + public string? Icon { get; init; } + + [JsonPropertyName("friendly_name")] + public string? FriendlyName { get; init; } +} \ No newline at end of file diff --git a/src/CodeCasa.AutoGenerated/SensorAttributes.cs b/src/CodeCasa.AutoGenerated/SensorAttributes.cs new file mode 100644 index 0000000..e814148 --- /dev/null +++ b/src/CodeCasa.AutoGenerated/SensorAttributes.cs @@ -0,0 +1,243 @@ +using System.Text.Json.Serialization; + +namespace CodeCasa.AutoGenerated; + +public partial record SensorAttributes +{ + [JsonPropertyName("icon")] + public string? Icon { get; init; } + + [JsonPropertyName("friendly_name")] + public string? FriendlyName { get; init; } + + [JsonPropertyName("assumed_state")] + public bool? AssumedState { get; init; } + + [JsonPropertyName("time")] + public string? Time { get; init; } + + [JsonPropertyName("device_class")] + public string? DeviceClass { get; init; } + + [JsonPropertyName("restored")] + public bool? Restored { get; init; } + + [JsonPropertyName("supported_features")] + public double? SupportedFeatures { get; init; } + + [JsonPropertyName("attribution")] + public string? Attribution { get; init; } + + [JsonPropertyName("Local Time")] + public string? LocalTime { get; init; } + + [JsonPropertyName("Package")] + public string? Package { get; init; } + + [JsonPropertyName("Time in Milliseconds")] + public double? TimeinMilliseconds { get; init; } + + [JsonPropertyName("device_id")] + public string? DeviceId { get; init; } + + [JsonPropertyName("device_name")] + public string? DeviceName { get; init; } + + [JsonPropertyName("auth_token")] + public string? AuthToken { get; init; } + + [JsonPropertyName("ip_address")] + public string? IpAddress { get; init; } + + [JsonPropertyName("available")] + public bool? Available { get; init; } + + [JsonPropertyName("next_alarm_status")] + public string? NextAlarmStatus { get; init; } + + [JsonPropertyName("alarm_volume")] + public double? AlarmVolume { get; init; } + + [JsonPropertyName("alarms")] + public IReadOnlyList? Alarms { get; init; } + + [JsonPropertyName("next_timer_status")] + public string? NextTimerStatus { get; init; } + + [JsonPropertyName("timers")] + public IReadOnlyList? Timers { get; init; } + + [JsonPropertyName("options")] + public IReadOnlyList? Options { get; init; } + + [JsonPropertyName("info")] + public string? Info { get; init; } + + [JsonPropertyName("serial")] + public string? Serial { get; init; } + + [JsonPropertyName("location")] + public string? Location_0 { get; init; } + + [JsonPropertyName("Location")] + public IReadOnlyList? Location_1 { get; init; } + + [JsonPropertyName("state_message")] + public string? StateMessage { get; init; } + + [JsonPropertyName("state_reason")] + public object? StateReason { get; init; } + + [JsonPropertyName("command_set")] + public string? CommandSet { get; init; } + + [JsonPropertyName("uri_supported")] + public string? UriSupported { get; init; } + + [JsonPropertyName("Wastecollector")] + public string? Wastecollector { get; init; } + + [JsonPropertyName("Hidden")] + public bool? Hidden { get; init; } + + [JsonPropertyName("Sort_date")] + public double? SortDate { get; init; } + + [JsonPropertyName("Days_until")] + public double? DaysUntil { get; init; } + + [JsonPropertyName("Cellular Technology")] + public string? CellularTechnology { get; init; } + + [JsonPropertyName("Allows VoIP")] + public bool? AllowsVoIP { get; init; } + + [JsonPropertyName("Carrier ID")] + public string? CarrierID { get; init; } + + [JsonPropertyName("Carrier Name")] + public string? CarrierName { get; init; } + + [JsonPropertyName("ISO Country Code")] + public string? ISOCountryCode { get; init; } + + [JsonPropertyName("Mobile Country Code")] + public string? MobileCountryCode { get; init; } + + [JsonPropertyName("Mobile Network Code")] + public string? MobileNetworkCode { get; init; } + + [JsonPropertyName("Current Radio Technology")] + public string? CurrentRadioTechnology { get; init; } + + [JsonPropertyName("Administrative Area")] + public string? AdministrativeArea { get; init; } + + [JsonPropertyName("Areas Of Interest")] + public object? AreasOfInterest { get; init; } + + [JsonPropertyName("Country")] + public string? Country { get; init; } + + [JsonPropertyName("Inland Water")] + public string? InlandWater { get; init; } + + [JsonPropertyName("Locality")] + public string? Locality { get; init; } + + [JsonPropertyName("Name")] + public string? Name { get; init; } + + [JsonPropertyName("Ocean")] + public string? Ocean { get; init; } + + [JsonPropertyName("Postal Code")] + public string? PostalCode { get; init; } + + [JsonPropertyName("Sub Administrative Area")] + public string? SubAdministrativeArea { get; init; } + + [JsonPropertyName("Sub Locality")] + public string? SubLocality { get; init; } + + [JsonPropertyName("Sub Thoroughfare")] + public string? SubThoroughfare { get; init; } + + [JsonPropertyName("Thoroughfare")] + public string? Thoroughfare { get; init; } + + [JsonPropertyName("Time Zone")] + public string? TimeZone { get; init; } + + [JsonPropertyName("Zones")] + public object? Zones { get; init; } + + [JsonPropertyName("Low Power Mode")] + public bool? LowPowerMode { get; init; } + + [JsonPropertyName("state_class")] + public string? StateClass { get; init; } + + [JsonPropertyName("run_completed")] + public string? RunCompleted { get; init; } + + [JsonPropertyName("error_state")] + public string? ErrorState { get; init; } + + [JsonPropertyName("start_time")] + public object? StartTime { get; init; } + + [JsonPropertyName("end_time")] + public object? EndTime { get; init; } + + [JsonPropertyName("initial_time")] + public string? InitialTime { get; init; } + + [JsonPropertyName("remain_time")] + public string? RemainTime { get; init; } + + [JsonPropertyName("reserve_time")] + public string? ReserveTime { get; init; } + + [JsonPropertyName("current_course")] + public string? CurrentCourse { get; init; } + + [JsonPropertyName("run_state")] + public string? RunState { get; init; } + + [JsonPropertyName("process_state")] + public string? ProcessState { get; init; } + + [JsonPropertyName("error_message")] + public string? ErrorMessage { get; init; } + + [JsonPropertyName("dry_level")] + public string? DryLevel { get; init; } + + [JsonPropertyName("eco_hybrid")] + public string? EcoHybrid { get; init; } + + [JsonPropertyName("standby")] + public string? Standby { get; init; } + + [JsonPropertyName("anti_crease")] + public string? AntiCrease { get; init; } + + [JsonPropertyName("child_lock")] + public string? ChildLock { get; init; } + + [JsonPropertyName("damp_dry_beep")] + public string? DampDryBeep { get; init; } + + [JsonPropertyName("hand_iron")] + public string? HandIron { get; init; } + + [JsonPropertyName("remote_start")] + public string? RemoteStart { get; init; } + + [JsonPropertyName("reservation")] + public string? Reservation { get; init; } + + [JsonPropertyName("self_clean")] + public string? SelfClean { get; init; } +} \ No newline at end of file diff --git a/src/CodeCasa.AutoGenerated/SensorEntities.cs b/src/CodeCasa.AutoGenerated/SensorEntities.cs index 73e9ae9..440ef85 100644 --- a/src/CodeCasa.AutoGenerated/SensorEntities.cs +++ b/src/CodeCasa.AutoGenerated/SensorEntities.cs @@ -1,6 +1,4 @@ using NetDaemon.HassModel; -using NetDaemon.HassModel.Entities; -using System.Text.Json.Serialization; namespace CodeCasa.AutoGenerated; @@ -20,6 +18,9 @@ public SensorEntities(IHaContext haContext) /// Enumerates all numeric sensor entities currently registered (at runtime) in Home Assistant as NumericSensorEntity public IEnumerable EnumerateAllNumeric() => _haContext.GetAllEntities().Where(e => e.EntityId.StartsWith("sensor.") && (e.EntityState?.AttributesJson?.TryGetProperty("unit_of_measurement", out _) ?? false)).Select(e => new NumericSensorEntity(e)); + ///Bathroom Motion Sensor Illuminance + public NumericSensorEntity BathroomMotionSensorIlluminance => new(_haContext, "sensor.bathroom_motion_sensor_illuminance"); + ///Living Room Temperature public NumericSensorEntity LivingRoomTemperature => new(_haContext, "sensor.living_room_temperature"); ///Meteorologisk Temperature @@ -33,259 +34,12 @@ public SensorEntities(IHaContext haContext) public SensorEntity LivingRoomSpeakerAlarms => new(_haContext, "sensor.living_room_speaker_alarms"); ///Living Room speaker timers public SensorEntity LivingRoomSpeakerTimers => new(_haContext, "sensor.living_room_speaker_timers"); + ///Office Motion Sensor Illuminance + public NumericSensorEntity OfficeMotionSensorIlluminance => new(_haContext, "sensor.office_motion_sensor_illuminance"); ///Office speaker alarms public SensorEntity OfficeSpeakerAlarms => new(_haContext, "sensor.office_speaker_alarms"); ///Office speaker timers public SensorEntity OfficeSpeakerTimers => new(_haContext, "sensor.office_speaker_timers"); -} - -public partial record SensorEntity : Entity, SensorAttributes>, ISensorEntityCore -{ - public SensorEntity(IHaContext haContext, string entityId) : base(haContext, entityId) - { - } - - public SensorEntity(IEntityCore entity) : base(entity) - { - } -} - -public partial record SensorAttributes -{ - [JsonPropertyName("icon")] - public string? Icon { get; init; } - - [JsonPropertyName("friendly_name")] - public string? FriendlyName { get; init; } - - [JsonPropertyName("assumed_state")] - public bool? AssumedState { get; init; } - - [JsonPropertyName("time")] - public string? Time { get; init; } - - [JsonPropertyName("device_class")] - public string? DeviceClass { get; init; } - - [JsonPropertyName("restored")] - public bool? Restored { get; init; } - - [JsonPropertyName("supported_features")] - public double? SupportedFeatures { get; init; } - - [JsonPropertyName("attribution")] - public string? Attribution { get; init; } - - [JsonPropertyName("Local Time")] - public string? LocalTime { get; init; } - - [JsonPropertyName("Package")] - public string? Package { get; init; } - - [JsonPropertyName("Time in Milliseconds")] - public double? TimeinMilliseconds { get; init; } - - [JsonPropertyName("device_id")] - public string? DeviceId { get; init; } - - [JsonPropertyName("device_name")] - public string? DeviceName { get; init; } - - [JsonPropertyName("auth_token")] - public string? AuthToken { get; init; } - - [JsonPropertyName("ip_address")] - public string? IpAddress { get; init; } - - [JsonPropertyName("available")] - public bool? Available { get; init; } - - [JsonPropertyName("next_alarm_status")] - public string? NextAlarmStatus { get; init; } - - [JsonPropertyName("alarm_volume")] - public double? AlarmVolume { get; init; } - - [JsonPropertyName("alarms")] - public IReadOnlyList? Alarms { get; init; } - - [JsonPropertyName("next_timer_status")] - public string? NextTimerStatus { get; init; } - - [JsonPropertyName("timers")] - public IReadOnlyList? Timers { get; init; } - - [JsonPropertyName("options")] - public IReadOnlyList? Options { get; init; } - - [JsonPropertyName("info")] - public string? Info { get; init; } - - [JsonPropertyName("serial")] - public string? Serial { get; init; } - - [JsonPropertyName("location")] - public string? Location_0 { get; init; } - - [JsonPropertyName("Location")] - public IReadOnlyList? Location_1 { get; init; } - - [JsonPropertyName("state_message")] - public string? StateMessage { get; init; } - - [JsonPropertyName("state_reason")] - public object? StateReason { get; init; } - - [JsonPropertyName("command_set")] - public string? CommandSet { get; init; } - - [JsonPropertyName("uri_supported")] - public string? UriSupported { get; init; } - - [JsonPropertyName("Wastecollector")] - public string? Wastecollector { get; init; } - - [JsonPropertyName("Hidden")] - public bool? Hidden { get; init; } - - [JsonPropertyName("Sort_date")] - public double? SortDate { get; init; } - - [JsonPropertyName("Days_until")] - public double? DaysUntil { get; init; } - - [JsonPropertyName("Cellular Technology")] - public string? CellularTechnology { get; init; } - - [JsonPropertyName("Allows VoIP")] - public bool? AllowsVoIP { get; init; } - - [JsonPropertyName("Carrier ID")] - public string? CarrierID { get; init; } - - [JsonPropertyName("Carrier Name")] - public string? CarrierName { get; init; } - - [JsonPropertyName("ISO Country Code")] - public string? ISOCountryCode { get; init; } - - [JsonPropertyName("Mobile Country Code")] - public string? MobileCountryCode { get; init; } - - [JsonPropertyName("Mobile Network Code")] - public string? MobileNetworkCode { get; init; } - - [JsonPropertyName("Current Radio Technology")] - public string? CurrentRadioTechnology { get; init; } - - [JsonPropertyName("Administrative Area")] - public string? AdministrativeArea { get; init; } - - [JsonPropertyName("Areas Of Interest")] - public object? AreasOfInterest { get; init; } - - [JsonPropertyName("Country")] - public string? Country { get; init; } - - [JsonPropertyName("Inland Water")] - public string? InlandWater { get; init; } - - [JsonPropertyName("Locality")] - public string? Locality { get; init; } - - [JsonPropertyName("Name")] - public string? Name { get; init; } - - [JsonPropertyName("Ocean")] - public string? Ocean { get; init; } - - [JsonPropertyName("Postal Code")] - public string? PostalCode { get; init; } - - [JsonPropertyName("Sub Administrative Area")] - public string? SubAdministrativeArea { get; init; } - - [JsonPropertyName("Sub Locality")] - public string? SubLocality { get; init; } - - [JsonPropertyName("Sub Thoroughfare")] - public string? SubThoroughfare { get; init; } - - [JsonPropertyName("Thoroughfare")] - public string? Thoroughfare { get; init; } - - [JsonPropertyName("Time Zone")] - public string? TimeZone { get; init; } - - [JsonPropertyName("Zones")] - public object? Zones { get; init; } - - [JsonPropertyName("Low Power Mode")] - public bool? LowPowerMode { get; init; } - - [JsonPropertyName("state_class")] - public string? StateClass { get; init; } - - [JsonPropertyName("run_completed")] - public string? RunCompleted { get; init; } - - [JsonPropertyName("error_state")] - public string? ErrorState { get; init; } - - [JsonPropertyName("start_time")] - public object? StartTime { get; init; } - - [JsonPropertyName("end_time")] - public object? EndTime { get; init; } - - [JsonPropertyName("initial_time")] - public string? InitialTime { get; init; } - - [JsonPropertyName("remain_time")] - public string? RemainTime { get; init; } - - [JsonPropertyName("reserve_time")] - public string? ReserveTime { get; init; } - - [JsonPropertyName("current_course")] - public string? CurrentCourse { get; init; } - - [JsonPropertyName("run_state")] - public string? RunState { get; init; } - - [JsonPropertyName("process_state")] - public string? ProcessState { get; init; } - - [JsonPropertyName("error_message")] - public string? ErrorMessage { get; init; } - - [JsonPropertyName("dry_level")] - public string? DryLevel { get; init; } - - [JsonPropertyName("eco_hybrid")] - public string? EcoHybrid { get; init; } - - [JsonPropertyName("standby")] - public string? Standby { get; init; } - - [JsonPropertyName("anti_crease")] - public string? AntiCrease { get; init; } - - [JsonPropertyName("child_lock")] - public string? ChildLock { get; init; } - - [JsonPropertyName("damp_dry_beep")] - public string? DampDryBeep { get; init; } - - [JsonPropertyName("hand_iron")] - public string? HandIron { get; init; } - - [JsonPropertyName("remote_start")] - public string? RemoteStart { get; init; } - - [JsonPropertyName("reservation")] - public string? Reservation { get; init; } - - [JsonPropertyName("self_clean")] - public string? SelfClean { get; init; } + ///Upstairs Hallway Attic Motion Sensor Illuminance + public NumericSensorEntity UpstairsHallwayAtticMotionSensorIlluminance => new(_haContext, "sensor.upstairs_hallway_attic_motion_sensor_illuminance"); } \ No newline at end of file diff --git a/src/CodeCasa.AutoGenerated/SensorEntity.cs b/src/CodeCasa.AutoGenerated/SensorEntity.cs new file mode 100644 index 0000000..1918110 --- /dev/null +++ b/src/CodeCasa.AutoGenerated/SensorEntity.cs @@ -0,0 +1,15 @@ +using NetDaemon.HassModel; +using NetDaemon.HassModel.Entities; + +namespace CodeCasa.AutoGenerated; + +public partial record SensorEntity : Entity, SensorAttributes>, ISensorEntityCore +{ + public SensorEntity(IHaContext haContext, string entityId) : base(haContext, entityId) + { + } + + public SensorEntity(IEntityCore entity) : base(entity) + { + } +} \ No newline at end of file diff --git a/src/CodeCasa.Automations/Apps/Lights/BackyardLights/BackyardCoachLightsEnergySavingNode.cs b/src/CodeCasa.Automations/Apps/Lights/BackyardLights/BackyardCoachLightsEnergySavingNode.cs deleted file mode 100644 index cf3ffa9..0000000 --- a/src/CodeCasa.Automations/Apps/Lights/BackyardLights/BackyardCoachLightsEnergySavingNode.cs +++ /dev/null @@ -1,8 +0,0 @@ -using System.Reactive.Concurrency; -using CodeCasa.AutoGenerated; -using CodeCasa.NetDaemon.Utilities; - -namespace CodeCasa.Automations.Apps.Lights.BackyardLights; - -public class BackyardCoachLightsEnergySavingNode(CoverEntities coverEntities, IScheduler scheduler) - : BackyardLightsEnergySavingNode(coverEntities, LightParameters.Off(), scheduler); \ No newline at end of file diff --git a/src/CodeCasa.Automations/Apps/Lights/BackyardLights/BackyardLightsApp.cs b/src/CodeCasa.Automations/Apps/Lights/BackyardLights/BackyardLightsApp.cs new file mode 100644 index 0000000..0c252bc --- /dev/null +++ b/src/CodeCasa.Automations/Apps/Lights/BackyardLights/BackyardLightsApp.cs @@ -0,0 +1,63 @@ +using CodeCasa.AutoGenerated; +using CodeCasa.AutomationPipelines.Lights.NetDaemon.Extensions; +using CodeCasa.AutomationPipelines.Lights.Pipeline; +using CodeCasa.Automations.Apps.Lights.BackyardLights.Observables; +using CodeCasa.Lights; +using CodeCasa.Notifications.Lights; +using CodeCasa.Notifications.Lights.Extensions; + +namespace CodeCasa.Automations.Apps.Lights.BackyardLights; + +//[NetDaemonApp] +internal class BackyardLightsApp +{ + public BackyardLightsApp( + LightPipelineFactory pipelineFactory, + LightEntities lightEntities, + BackyardLightsRoutineFactory backyardLightsRoutineFactory) + { + // We use a separate pipeline for each light string to allow for different timing on the startup routine. + // This results in a nice animation, in which lights are turned on one by one. + var stringLights = new[] + { + lightEntities.BackyardPorchStringLights, + lightEntities.BackyardPergolaStringLights, + lightEntities.BackyardFenceStringLights + }; + for (var i = 0; i < stringLights.Length; i++) + { + var light = stringLights[i]; + var index = i; + pipelineFactory + .SetupLightPipeline(light, b => + { + b + .TurnOnWhen(backyardLightsRoutineFactory.Create(TimeSpan.FromSeconds(index))) + .TurnOffWhen() + .AddNotifications(); + }); + } + + // All other lights should turn on slowly at the same time. + // Note: We could actually use a single pipeline for this. But as we generate parameters based on light entity, I split them up. This way, even if the light type changes, this implementation will still work. + const int transitionSeconds = 4; + const int offsetSeconds = 10; + var wallLights = new[] + { + lightEntities.BackyardGarageLight, + lightEntities.BackyardEntranceLight, + lightEntities.BackyardDoorLight + }; + foreach (var light in wallLights) + { + pipelineFactory + .SetupLightPipeline(light, builder => + { + builder + .When(backyardLightsRoutineFactory.Create(offsetSeconds), LightParameters.Relax.AsTransitionInSeconds(transitionSeconds)) + .TurnOffWhen() + .AddNotifications(); + }); + } + } +} \ No newline at end of file diff --git a/src/CodeCasa.Automations/Apps/Lights/BackyardLights/BackyardStringLightsEnergySavingNode.cs b/src/CodeCasa.Automations/Apps/Lights/BackyardLights/BackyardStringLightsEnergySavingNode.cs deleted file mode 100644 index 8da220d..0000000 --- a/src/CodeCasa.Automations/Apps/Lights/BackyardLights/BackyardStringLightsEnergySavingNode.cs +++ /dev/null @@ -1,7 +0,0 @@ -using System.Reactive.Concurrency; -using CodeCasa.AutoGenerated; - -namespace CodeCasa.Automations.Apps.Lights.BackyardLights; - -public class BackyardStringLightsEnergySavingNode(CoverEntities coverEntities, IScheduler scheduler) - : BackyardLightsEnergySavingNode(coverEntities, false, scheduler); \ No newline at end of file diff --git a/src/CodeCasa.Automations/Apps/Lights/BackyardLights/BackyardStringLightsPipeline.cs b/src/CodeCasa.Automations/Apps/Lights/BackyardLights/BackyardStringLightsPipeline.cs deleted file mode 100644 index 25bfe87..0000000 --- a/src/CodeCasa.Automations/Apps/Lights/BackyardLights/BackyardStringLightsPipeline.cs +++ /dev/null @@ -1,84 +0,0 @@ -using System.Reactive.Concurrency; -using AutomationPipelines; -using CodeCasa.AutoGenerated; -using CodeCasa.AutoGenerated.Extensions; -using CodeCasa.NetDaemon.Utilities; -using CodeCasa.NetDaemon.Utilities.Extensions; -using NetDaemon.AppModel; - -namespace CodeCasa.Automations.Apps.Lights.BackyardLights; - -[NetDaemonApp] -internal class BackyardStringLightsPipeline -{ - public BackyardStringLightsPipeline( - IPipeline backyardPorchStringLightsPipeline, - IPipeline backyardPergolaStringLightsPipeline, - IPipeline backyardFenceStringLightsPipeline, - IPipeline backyardGarageLightPipeline, - IPipeline backyardEntranceLightPipeline, - IPipeline backyardDoorLightPipeline, - LightEntities lightEntities, - IScheduler scheduler) - { - // We use a separate pipeline for each light string to allow for different timing on the startup routine. - // This results in a nice animation, in which lights are turned on one by one. - backyardPorchStringLightsPipeline - .SetDefault(false) - .RegisterNode(new LightStringRoutineNode(scheduler, true, TimeSpan.Zero)) - .RegisterNode() - .SetOutputHandler(b => UpdateLightState(lightEntities.BackyardPorchStringLights, b)); - - backyardPergolaStringLightsPipeline - .SetDefault(false) - .RegisterNode(new LightStringRoutineNode(scheduler, true, TimeSpan.FromSeconds(1))) - .RegisterNode() - .SetOutputHandler(b => UpdateLightState(lightEntities.BackyardPergolaStringLights, b)); - - backyardFenceStringLightsPipeline - .SetDefault(false) - .RegisterNode(new LightStringRoutineNode(scheduler, true, TimeSpan.FromSeconds(2))) - .RegisterNode() - .SetOutputHandler(b => UpdateLightState(lightEntities.BackyardFenceStringLights, b)); - - // All other lights should turn on slowly at the same time. - // Note: We could actually use a single pipeline for this. But as we generate parameters based on light entity, I split them up. This way, even if the light type changes, this implementation will still work. - const int transitionSeconds = 4; - const int offsetSeconds = 10; - backyardGarageLightPipeline - .SetDefault(LightParameters.Off()) - .RegisterNode(new LightStringRoutineNode(scheduler, - lightEntities.BackyardGarageLight.GetRelaxSceneParameters() with { Transition = TimeSpan.FromSeconds(transitionSeconds) }, - TimeSpan.FromSeconds(offsetSeconds))) - .RegisterNode() - .SetOutputHandler(lightEntities.BackyardGarageLight.ExecuteLightParameters); - - backyardEntranceLightPipeline - .SetDefault(LightParameters.Off()) - .RegisterNode(new LightStringRoutineNode(scheduler, - lightEntities.BackyardEntranceLight.GetRelaxSceneParameters() with { Transition = TimeSpan.FromSeconds(transitionSeconds) }, - TimeSpan.FromSeconds(offsetSeconds))) - .RegisterNode() - .SetOutputHandler(lightEntities.BackyardEntranceLight.ExecuteLightParameters); - - backyardDoorLightPipeline - .SetDefault(LightParameters.Off()) - .RegisterNode(new LightStringRoutineNode(scheduler, - lightEntities.BackyardDoorLight.GetRelaxSceneParameters() with { Transition = TimeSpan.FromSeconds(transitionSeconds) }, - TimeSpan.FromSeconds(offsetSeconds))) - .RegisterNode() - .SetOutputHandler(lightEntities.BackyardDoorLight.ExecuteLightParameters); - } - - private static void UpdateLightState(LightEntity lightEntity, bool state) - { - if (state) - { - lightEntity.TurnOn(); - } - else - { - lightEntity.TurnOff(); - } - } -} \ No newline at end of file diff --git a/src/CodeCasa.Automations/Apps/Lights/BackyardLights/LightStringRoutineNode.cs b/src/CodeCasa.Automations/Apps/Lights/BackyardLights/LightStringRoutineNode.cs deleted file mode 100644 index a8da74d..0000000 --- a/src/CodeCasa.Automations/Apps/Lights/BackyardLights/LightStringRoutineNode.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System.Reactive.Concurrency; -using AutomationPipelines; -using Occurify; -using Occurify.Astro; -using Occurify.Extensions; -using Occurify.Reactive.Extensions; -using Occurify.TimeZones; -using Reactive.Boolean; - -namespace CodeCasa.Automations.Apps.Lights.BackyardLights; - -public class LightStringRoutineNode : PipelineNode -{ - public LightStringRoutineNode(IScheduler scheduler, TState onState, TimeSpan turnOnOffset) - { - var eveningAndNight = PeriodTimeline.Between(AstroInstants.LocalSunsets.Offset(turnOnOffset), TimeZoneInstants.DailyAt(2)).ToBooleanObservable(scheduler); - var morning = TimeZonePeriods.DailyBetween(TimeZoneInstants.DailyAt(6).Offset(turnOnOffset), AstroInstants.LocalSunrises).ToBooleanObservable(scheduler); - - eveningAndNight.Or(morning).SubscribeTrueFalse(() => Output = onState, PassInputThrough); - } -} \ No newline at end of file diff --git a/src/CodeCasa.Automations/Apps/Lights/BackyardLights/BackyardLightsEnergySavingNode.cs b/src/CodeCasa.Automations/Apps/Lights/BackyardLights/Observables/BackyardLightsEnergySaving.cs similarity index 64% rename from src/CodeCasa.Automations/Apps/Lights/BackyardLights/BackyardLightsEnergySavingNode.cs rename to src/CodeCasa.Automations/Apps/Lights/BackyardLights/Observables/BackyardLightsEnergySaving.cs index b59c37d..579380e 100644 --- a/src/CodeCasa.Automations/Apps/Lights/BackyardLights/BackyardLightsEnergySavingNode.cs +++ b/src/CodeCasa.Automations/Apps/Lights/BackyardLights/Observables/BackyardLightsEnergySaving.cs @@ -1,5 +1,4 @@ using System.Reactive.Concurrency; -using AutomationPipelines; using CodeCasa.AutoGenerated; using CodeCasa.NetDaemon.Extensions.Observables; using Occurify; @@ -7,11 +6,13 @@ using Occurify.TimeZones; using Reactive.Boolean; -namespace CodeCasa.Automations.Apps.Lights.BackyardLights; +namespace CodeCasa.Automations.Apps.Lights.BackyardLights.Observables; -public class BackyardLightsEnergySavingNode : PipelineNode +public class BackyardLightsEnergySaving : IObservable { - public BackyardLightsEnergySavingNode(CoverEntities coverEntities, TState energySavingState, IScheduler scheduler) + private readonly IObservable _observable; + + public BackyardLightsEnergySaving(CoverEntities coverEntities, IScheduler scheduler) { var timeWindow = PeriodTimeline.Between(TimeZoneInstants.DailyAt(23), TimeZoneInstants.DailyAt(6)).ToBooleanObservable(scheduler); @@ -21,9 +22,13 @@ public BackyardLightsEnergySavingNode(CoverEntities coverEntities, TState energy var kitchenRollerShutterClosed = coverEntities.KitchenRollerShutter.ToOpenClosedObservable().Not(); - timeWindow + _observable = timeWindow .And(anyLivingRoomShutterClosed) - .And(kitchenRollerShutterClosed) - .SubscribeTrueFalse(() => Output = energySavingState, PassInputThrough); + .And(kitchenRollerShutterClosed); + } + + public IDisposable Subscribe(IObserver observer) + { + return _observable.Subscribe(observer); } } \ No newline at end of file diff --git a/src/CodeCasa.Automations/Apps/Lights/BackyardLights/Observables/BackyardLightsRoutine.cs b/src/CodeCasa.Automations/Apps/Lights/BackyardLights/Observables/BackyardLightsRoutine.cs new file mode 100644 index 0000000..bbe7efb --- /dev/null +++ b/src/CodeCasa.Automations/Apps/Lights/BackyardLights/Observables/BackyardLightsRoutine.cs @@ -0,0 +1,27 @@ +using System.Reactive.Concurrency; +using Occurify; +using Occurify.Astro; +using Occurify.Extensions; +using Occurify.Reactive.Extensions; +using Occurify.TimeZones; +using Reactive.Boolean; + +namespace CodeCasa.Automations.Apps.Lights.BackyardLights.Observables; + +public class BackyardLightsRoutine : IObservable +{ + private readonly IObservable _observable; + + public BackyardLightsRoutine(IScheduler scheduler, TimeSpan offset) + { + var eveningAndNight = PeriodTimeline.Between(AstroInstants.LocalSunsets.Offset(offset), TimeZoneInstants.DailyAt(2)).ToBooleanObservable(scheduler); + var morning = TimeZonePeriods.DailyBetween(TimeZoneInstants.DailyAt(6).Offset(offset), AstroInstants.LocalSunrises).ToBooleanObservable(scheduler); + + _observable = eveningAndNight.Or(morning); + } + + public IDisposable Subscribe(IObserver observer) + { + return _observable.Subscribe(observer); + } +} \ No newline at end of file diff --git a/src/CodeCasa.Automations/Apps/Lights/BackyardLights/Observables/BackyardLightsRoutineFactory.cs b/src/CodeCasa.Automations/Apps/Lights/BackyardLights/Observables/BackyardLightsRoutineFactory.cs new file mode 100644 index 0000000..6609a77 --- /dev/null +++ b/src/CodeCasa.Automations/Apps/Lights/BackyardLights/Observables/BackyardLightsRoutineFactory.cs @@ -0,0 +1,22 @@ +using System.Reactive.Concurrency; + +namespace CodeCasa.Automations.Apps.Lights.BackyardLights.Observables +{ + internal class BackyardLightsRoutineFactory(IScheduler scheduler) + { + public BackyardLightsRoutine Create() + { + return new BackyardLightsRoutine(scheduler, TimeSpan.Zero); + } + + public BackyardLightsRoutine Create(TimeSpan offset) + { + return new BackyardLightsRoutine(scheduler, offset); + } + + public BackyardLightsRoutine Create(int offsetInSeconds) + { + return new BackyardLightsRoutine(scheduler, TimeSpan.FromSeconds(offsetInSeconds)); + } + } +} diff --git a/src/CodeCasa.Automations/Apps/Notifications/OfficeLightsNotifications.cs b/src/CodeCasa.Automations/Apps/Notifications/OfficeLightsNotifications.cs index a2327ac..0c6de29 100644 --- a/src/CodeCasa.Automations/Apps/Notifications/OfficeLightsNotifications.cs +++ b/src/CodeCasa.Automations/Apps/Notifications/OfficeLightsNotifications.cs @@ -29,7 +29,7 @@ public OfficeLightsNotifications( var notificationId = $"{nameof(OfficeLightsNotifications)}_Notification"; var officeLights = lightEntities.OfficeLights.ToOnOffObservable(); - var jasperHome = jasper.CreateHomeObservable(); + var jasperHome = jasper.HomeWithCurrent(); officeLights.SubscribeOnOff( () => diff --git a/src/CodeCasa.Automations/Apps/People/PeopleHomeStateSyncer.cs b/src/CodeCasa.Automations/Apps/People/PeopleHomeStateSyncer.cs index f546d11..4f02525 100644 --- a/src/CodeCasa.Automations/Apps/People/PeopleHomeStateSyncer.cs +++ b/src/CodeCasa.Automations/Apps/People/PeopleHomeStateSyncer.cs @@ -14,7 +14,7 @@ public PeopleHomeStateSyncer(PeopleEntities people) { foreach (var person in people.All) { - person.CreateHomeObservable() + person.HomeWithCurrent() .SubscribeTrueFalse( () => { diff --git a/src/CodeCasa.Automations/Apps/People/TurnOffLightsWhenNoOneAwake.cs b/src/CodeCasa.Automations/Apps/People/TurnOffLightsWhenNoOneAwake.cs deleted file mode 100644 index 26c44a3..0000000 --- a/src/CodeCasa.Automations/Apps/People/TurnOffLightsWhenNoOneAwake.cs +++ /dev/null @@ -1,26 +0,0 @@ -using CodeCasa.AutoGenerated; -using CodeCasa.AutoGenerated.Extensions; -using CodeCasa.CustomEntities.Automation.People; -using NetDaemon.AppModel; -using Reactive.Boolean; - -namespace CodeCasa.Automations.Apps.People; - -/// -/// Will turn off all lights when the last person leaves the house or goes to sleep. -/// -[NetDaemonApp] -internal class TurnOffLightsWhenNoOneAwake -{ - public TurnOffLightsWhenNoOneAwake(PeopleEntities people, LightEntities lights) - { - people.OnLastPersonToAsleepOrAwayObservable() - .SubscribeTrue(() => - { - foreach (var light in lights.EnumerateAll()) - { - light.TurnOff(); - } - }); - } -} \ No newline at end of file diff --git a/src/CodeCasa.Automations/CodeCasa.Automations.csproj b/src/CodeCasa.Automations/CodeCasa.Automations.csproj index 35dc51c..07d0d50 100644 --- a/src/CodeCasa.Automations/CodeCasa.Automations.csproj +++ b/src/CodeCasa.Automations/CodeCasa.Automations.csproj @@ -23,14 +23,15 @@ - - - - - - - - + + + + + + + + + @@ -40,6 +41,7 @@ + diff --git a/src/CodeCasa.Automations/Extensions/ServiceCollectionExtensions.cs b/src/CodeCasa.Automations/Extensions/ServiceCollectionExtensions.cs index e2b05b8..603836d 100644 --- a/src/CodeCasa.Automations/Extensions/ServiceCollectionExtensions.cs +++ b/src/CodeCasa.Automations/Extensions/ServiceCollectionExtensions.cs @@ -1,6 +1,10 @@ -using AutomationPipelines.Extensions; -using CodeCasa.AutoGenerated.Extensions; +using CodeCasa.AutoGenerated.Extensions; +using CodeCasa.AutomationPipelines.Extensions; +using CodeCasa.AutomationPipelines.Lights.Extensions; +using CodeCasa.AutomationPipelines.Lights.Mqtt.Extensions; +using CodeCasa.Automations.Apps.Lights.BackyardLights.Observables; using CodeCasa.CustomEntities.Automation.Extensions; +using CodeCasa.Lights.NetDaemon.Scenes.Extensions; using CodeCasa.Notifications.InputSelect.NetDaemon.Extensions; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; @@ -16,9 +20,16 @@ public static IServiceCollection AddCodeCasa(this IServiceCollection serviceColl Coordinates.Local = new Coordinates(52.374191, 4.912269, 2.5); return serviceCollection + .AddLightScenes() .AddAutomationPipelines() .AddHomeAssistantGenerated() .AddCodeCasaCustomAutomationEntities() - .AddInputSelectNotifications(configuration); + .AddLightPipelines() + .AddInputSelectNotifications(configuration) + + .AddTransient() + .AddTransient() + + .AddCodeCasaMqtt(configuration); } } \ No newline at end of file diff --git a/src/CodeCasa.Automations/Nodes/AlarmNode.cs b/src/CodeCasa.Automations/Nodes/AlarmNode.cs new file mode 100644 index 0000000..1f7fc64 --- /dev/null +++ b/src/CodeCasa.Automations/Nodes/AlarmNode.cs @@ -0,0 +1,30 @@ +using System.Drawing; +using System.Reactive.Concurrency; +using System.Reactive.Linq; +using CodeCasa.AutomationPipelines.Lights.Nodes; +using CodeCasa.Lights; +using CodeCasa.Lights.NetDaemon; + +namespace CodeCasa.Automations.Nodes +{ + public class AlarmNode : LightTransitionNode, IDisposable + { + private readonly IDisposable _subscription; + + public AlarmNode(IScheduler scheduler, NetDaemonLight light) : base(scheduler) + { + var high = false; + _subscription = Observable.Interval(TimeSpan.FromSeconds(2), scheduler) + .Subscribe(_ => + { + high = !high; + Output = new LightParameters{RgbColor = high ? Color.Red : Color.Yellow}.AsTransitionInSeconds(0); + }); + } + + public void Dispose() + {// todo: test + _subscription.Dispose(); + } + } +} diff --git a/src/CodeCasa.Automations/appsettings.json b/src/CodeCasa.Automations/appsettings.json index e7fdbcb..79ee54c 100644 --- a/src/CodeCasa.Automations/appsettings.json +++ b/src/CodeCasa.Automations/appsettings.json @@ -1,7 +1,7 @@ { "Serilog": { "MinimumLevel": { - "Default": "Debug", + "Default": "Verbose", "Override": { "Microsoft": "Warning", "System.Net.Http.HttpClient": "Warning" diff --git a/src/CodeCasa.CustomEntities.Automation/CodeCasa.CustomEntities.Automation.csproj b/src/CodeCasa.CustomEntities.Automation/CodeCasa.CustomEntities.Automation.csproj index d9afc1f..ed57223 100644 --- a/src/CodeCasa.CustomEntities.Automation/CodeCasa.CustomEntities.Automation.csproj +++ b/src/CodeCasa.CustomEntities.Automation/CodeCasa.CustomEntities.Automation.csproj @@ -7,15 +7,19 @@ - - - - + + + + + + + + diff --git a/src/CodeCasa.CustomEntities.Automation/Extensions/LightTransitionReactiveNodeConfiguratorExtensions.cs b/src/CodeCasa.CustomEntities.Automation/Extensions/LightTransitionReactiveNodeConfiguratorExtensions.cs new file mode 100644 index 0000000..5f11e88 --- /dev/null +++ b/src/CodeCasa.CustomEntities.Automation/Extensions/LightTransitionReactiveNodeConfiguratorExtensions.cs @@ -0,0 +1,50 @@ +using CodeCasa.AutomationPipelines; +using CodeCasa.AutomationPipelines.Lights.Nodes; +using CodeCasa.AutomationPipelines.Lights.ReactiveNode; +using CodeCasa.AutomationPipelines.Lights.Timeline; +using CodeCasa.CustomEntities.Automation.People; +using CodeCasa.CustomEntities.Core.Switches; +using CodeCasa.Lights; +using Microsoft.Extensions.DependencyInjection; +using System.Reactive.Linq; + +namespace CodeCasa.CustomEntities.Automation.Extensions +{ + public static class LightTransitionReactiveNodeConfiguratorExtensions + { + public static ILightTransitionReactiveNodeConfigurator TurnOffWhenLastPersonToAsleepOrAway(this ILightTransitionReactiveNodeConfigurator configurator) where TLight : ILight + { + configurator.AddNodeSource(sp => + { + var people = sp.GetRequiredService(); + return people.OnLastPersonToAsleepOrAwayObservable() + .Select(_ => + new Func?>( + _ => new TurnOffThenPassThroughNode())); + }); + return configurator; + } + + public static ILightTransitionReactiveNodeConfigurator AddHueDimmerSwitch(this ILightTransitionReactiveNodeConfigurator configurator, + HueDimmerSwitch dimmerSwitch, + LightParameters onPressParameters, + params LightParameters[] scenes) where TLight : ILight + { + return configurator + .AddToggle(dimmerSwitch.OnOffPressed, onPressParameters) + .AddCycle(dimmerSwitch.ScenePressed, scenes) + .AddReactiveDimmer(dimmerSwitch); + } + + public static ILightTransitionReactiveNodeConfigurator AddHueDimmerSwitch(this ILightTransitionReactiveNodeConfigurator configurator, + HueDimmerSwitch dimmerSwitch, + Action timelineConfigure, + params LightParameters[] scenes) where TLight : ILight + { + return configurator + .AddToggle(dimmerSwitch.OnOffPressed, timelineConfigure) + .AddCycle(dimmerSwitch.ScenePressed, scenes) + .AddReactiveDimmer(dimmerSwitch); + } + } +} diff --git a/src/CodeCasa.CustomEntities.Automation/Extensions/ServiceCollectionExtensions.cs b/src/CodeCasa.CustomEntities.Automation/Extensions/ServiceCollectionExtensions.cs index 3f6994d..57b8ec3 100644 --- a/src/CodeCasa.CustomEntities.Automation/Extensions/ServiceCollectionExtensions.cs +++ b/src/CodeCasa.CustomEntities.Automation/Extensions/ServiceCollectionExtensions.cs @@ -1,7 +1,12 @@ using CodeCasa.CustomEntities.Automation.Notifications.Dashboards; using CodeCasa.CustomEntities.Automation.Notifications.Phones; using CodeCasa.CustomEntities.Automation.People; +using CodeCasa.CustomEntities.Automation.Sensors; +using CodeCasa.CustomEntities.Automation.Switches; using CodeCasa.CustomEntities.Core.Extensions; +using CodeCasa.CustomEntities.Core.Switches; +using CodeCasa.NetDaemon.Sensors.Composite; +using CodeCasa.Notifications.Lights.Extensions; using Microsoft.Extensions.DependencyInjection; namespace CodeCasa.CustomEntities.Automation.Extensions; @@ -10,12 +15,22 @@ public static class ServiceCollectionExtensions { public static IServiceCollection AddCodeCasaCustomAutomationEntities(this IServiceCollection serviceCollection) { - return serviceCollection + serviceCollection .AddCodeCasaCustomCoreEntities() + .AddLightNotifications(); - // Dashboard Notifications - .AddTransient() + serviceCollection + .AddTransientImplementationsOf() + .AddTransientImplementationsOf() + .AddTransientImplementationsOf() + .AddTransientImplementationsOf() + .AddTransientImplementationsOf(); + + serviceCollection.AddTransientImplementationsOf(); + + // Dashboard Notifications + serviceCollection.AddTransient() .AddTransient() .AddTransient() @@ -27,5 +42,23 @@ public static IServiceCollection AddCodeCasaCustomAutomationEntities(this IServi .AddTransient() .AddTransient() .AddTransient(); + + return serviceCollection; + } + + private static IServiceCollection AddTransientImplementationsOf(this IServiceCollection services) + { + var baseType = typeof(TBase); + + var types = AppDomain.CurrentDomain.GetAssemblies() + .SelectMany(assembly => assembly.GetTypes()) + .Where(type => baseType.IsAssignableFrom(type) && !type.IsInterface && !type.IsAbstract).ToArray(); + + foreach (var type in types) + { + services.AddTransient(type); + } + + return services; } } \ No newline at end of file diff --git a/src/CodeCasa.CustomEntities.Automation/People/CompositePersonEntity.cs b/src/CodeCasa.CustomEntities.Automation/People/CompositePersonEntity.cs index a8640dc..d5f260d 100644 --- a/src/CodeCasa.CustomEntities.Automation/People/CompositePersonEntity.cs +++ b/src/CodeCasa.CustomEntities.Automation/People/CompositePersonEntity.cs @@ -5,6 +5,7 @@ using CodeCasa.Notifications.InputSelect.NetDaemon.Interact; using CodeCasa.Notifications.Phone.NetDaemon; + namespace CodeCasa.CustomEntities.Automation.People; public abstract class CompositePersonEntity( @@ -39,19 +40,19 @@ public PersonStates? State public bool Home => State != PersonStates.Away; - public IObservable CreateStateObservable() => + public IObservable PersonStateWithCurrent() => PersonStateEntity.StateChangesWithCurrent() - .Select(state => state.New!.State) + .Select(state => state.New?.State) .Where(s => s != null) .Select(s => s!.Value); - public IObservable> CreateStateChangeObservable() => + public IObservable> PersonStateChangeWithCurrent() => PersonStateEntity.StateChangesWithCurrent() .Select(state => new StateChange(state.Old?.State, state.New?.State)); - public IObservable CreateStateEqualsObservable(PersonStates personState) => + public IObservable PersonStateEqualsWithCurrent(PersonStates personState) => PersonStateEntity.ToBooleanObservable(state => state.State == personState); - public IObservable CreateHomeObservable() => + public IObservable HomeWithCurrent() => PersonEntity.ToBooleanObservable(state => state.State == PersonEntityStates.Home); } \ No newline at end of file diff --git a/src/CodeCasa.CustomEntities.Automation/People/PeopleEntities.cs b/src/CodeCasa.CustomEntities.Automation/People/PeopleEntities.cs index b870e04..3a681e0 100644 --- a/src/CodeCasa.CustomEntities.Automation/People/PeopleEntities.cs +++ b/src/CodeCasa.CustomEntities.Automation/People/PeopleEntities.cs @@ -1,5 +1,7 @@  +using System.Reactive; using System.Reactive.Linq; +using Reactive.Boolean; namespace CodeCasa.CustomEntities.Automation.People; @@ -8,16 +10,26 @@ public class PeopleEntities(Jane jane, Jasper jasper) public Jane Jane { get; } = jane; public Jasper Jasper { get; } = jasper; - public IEnumerable All { get; } = [jane, jasper]; + public IEnumerable All { get; } = [jasper]; - public IObservable OnLastPersonToAsleepOrAwayObservable() + public IObservable AnyAwakeWithCurrent() => + All.Select(e => e.PersonStateEqualsWithCurrent(PersonStates.Awake)) + .CombineLatest(x => x.Any()); + + public IObservable AnyAsleepWithCurrent() => + All.Select(e => e.PersonStateEqualsWithCurrent(PersonStates.Asleep)) + .CombineLatest(x => x.Any()); + + public IObservable NoOneAsleepWithCurrent() => AnyAsleepWithCurrent().Not(); + + public IObservable OnLastPersonToAsleepOrAwayObservable() { - return All.Select(p => p.CreateStateChangeObservable()).CombineLatest() - .Select(tuple => + return All.Select(p => p.PersonStateChangeWithCurrent()).CombineLatest() + .Where(tuple => { var prevAnyAwake = tuple.Any(change => change.Old == PersonStates.Awake); var currentAnyAwake = tuple.Any(change => change.New == PersonStates.Awake); return prevAnyAwake && !currentAnyAwake; - }).DistinctUntilChanged(); + }).DistinctUntilChanged().Select(_ => Unit.Default); } } \ No newline at end of file diff --git a/src/CodeCasa.CustomEntities.Automation/Sensors/BathroomMotionSensor.cs b/src/CodeCasa.CustomEntities.Automation/Sensors/BathroomMotionSensor.cs new file mode 100644 index 0000000..e1cf283 --- /dev/null +++ b/src/CodeCasa.CustomEntities.Automation/Sensors/BathroomMotionSensor.cs @@ -0,0 +1,13 @@ +using System.Reactive.Concurrency; +using CodeCasa.AutoGenerated; +using CodeCasa.NetDaemon.Sensors.Composite; + +namespace CodeCasa.CustomEntities.Automation.Sensors +{ + public class BathroomMotionSensor( + IScheduler scheduler, + BinarySensorEntities binarySensorEntities, + SensorEntities sensorEntities) + : MotionSensor(scheduler, binarySensorEntities.BathroomMotionSensorOccupancy, + sensorEntities.BathroomMotionSensorIlluminance); +} diff --git a/src/CodeCasa.CustomEntities.Automation/Sensors/OfficeMotionSensor.cs b/src/CodeCasa.CustomEntities.Automation/Sensors/OfficeMotionSensor.cs new file mode 100644 index 0000000..996237f --- /dev/null +++ b/src/CodeCasa.CustomEntities.Automation/Sensors/OfficeMotionSensor.cs @@ -0,0 +1,13 @@ +using System.Reactive.Concurrency; +using CodeCasa.AutoGenerated; +using CodeCasa.NetDaemon.Sensors.Composite; + +namespace CodeCasa.CustomEntities.Automation.Sensors +{ + public class OfficeMotionSensor( + IScheduler scheduler, + BinarySensorEntities binarySensorEntities, + SensorEntities sensorEntities) + : MotionSensor(scheduler, binarySensorEntities.OfficeMotionSensorOccupancy, + sensorEntities.OfficeMotionSensorIlluminance); +} diff --git a/src/CodeCasa.CustomEntities.Automation/Sensors/UpstairsHallwayAtticMotionSensor.cs b/src/CodeCasa.CustomEntities.Automation/Sensors/UpstairsHallwayAtticMotionSensor.cs new file mode 100644 index 0000000..2336d76 --- /dev/null +++ b/src/CodeCasa.CustomEntities.Automation/Sensors/UpstairsHallwayAtticMotionSensor.cs @@ -0,0 +1,13 @@ +using System.Reactive.Concurrency; +using CodeCasa.AutoGenerated; +using CodeCasa.NetDaemon.Sensors.Composite; + +namespace CodeCasa.CustomEntities.Automation.Sensors +{ + public class UpstairsHallwayAtticMotionSensor( + IScheduler scheduler, + BinarySensorEntities binarySensorEntities, + SensorEntities sensorEntities) + : MotionSensor(scheduler, binarySensorEntities.UpstairsHallwayAtticMotionSensorOccupancy, + sensorEntities.UpstairsHallwayAtticMotionSensorIlluminance); +} diff --git a/src/CodeCasa.CustomEntities.Automation/Switches/AtticDimmerSwitch.cs b/src/CodeCasa.CustomEntities.Automation/Switches/AtticDimmerSwitch.cs new file mode 100644 index 0000000..be78fa0 --- /dev/null +++ b/src/CodeCasa.CustomEntities.Automation/Switches/AtticDimmerSwitch.cs @@ -0,0 +1,8 @@ +using CodeCasa.CustomEntities.Core.Switches; +using NetDaemon.HassModel; + +namespace CodeCasa.CustomEntities.Automation.Switches +{ + public class AtticDimmerSwitch(ITriggerManager triggerManager) + : HueDimmerSwitch(triggerManager, SwitchDeviceNames.AtticDimmerSwitch); +} diff --git a/src/CodeCasa.CustomEntities.Automation/Switches/AtticWallSwitch.cs b/src/CodeCasa.CustomEntities.Automation/Switches/AtticWallSwitch.cs new file mode 100644 index 0000000..88bee6c --- /dev/null +++ b/src/CodeCasa.CustomEntities.Automation/Switches/AtticWallSwitch.cs @@ -0,0 +1,8 @@ +using CodeCasa.CustomEntities.Core.Switches; +using NetDaemon.HassModel; + +namespace CodeCasa.CustomEntities.Automation.Switches +{ + public class AtticWallSwitch(ITriggerManager triggerManager) + : HueWallModuleSingleRocker(triggerManager, SwitchDeviceNames.AtticHallwayWallSwitch); +} diff --git a/src/CodeCasa.CustomEntities.Automation/Switches/BathroomWallSwitch.cs b/src/CodeCasa.CustomEntities.Automation/Switches/BathroomWallSwitch.cs new file mode 100644 index 0000000..b454248 --- /dev/null +++ b/src/CodeCasa.CustomEntities.Automation/Switches/BathroomWallSwitch.cs @@ -0,0 +1,8 @@ +using CodeCasa.CustomEntities.Core.Switches; +using NetDaemon.HassModel; + +namespace CodeCasa.CustomEntities.Automation.Switches +{ + public class BathroomWallSwitch(ITriggerManager triggerManager) + : HueWallModuleSingleRocker(triggerManager, SwitchDeviceNames.BathroomWallSwitch); +} diff --git a/src/CodeCasa.CustomEntities.Automation/Switches/BedroomNightstandLeftSwitch.cs b/src/CodeCasa.CustomEntities.Automation/Switches/BedroomNightstandLeftSwitch.cs new file mode 100644 index 0000000..13a1e84 --- /dev/null +++ b/src/CodeCasa.CustomEntities.Automation/Switches/BedroomNightstandLeftSwitch.cs @@ -0,0 +1,8 @@ +using CodeCasa.CustomEntities.Core.Switches; +using NetDaemon.HassModel; + +namespace CodeCasa.CustomEntities.Automation.Switches +{ + public class BedroomNightstandLeftSwitch(ITriggerManager triggerManager) + : IkeaRodretDimmer(triggerManager, SwitchDeviceNames.BedroomNightstandLeftSwitch); +} diff --git a/src/CodeCasa.CustomEntities.Automation/Switches/BedroomNightstandRightSwitch.cs b/src/CodeCasa.CustomEntities.Automation/Switches/BedroomNightstandRightSwitch.cs new file mode 100644 index 0000000..6ab72d4 --- /dev/null +++ b/src/CodeCasa.CustomEntities.Automation/Switches/BedroomNightstandRightSwitch.cs @@ -0,0 +1,8 @@ +using CodeCasa.CustomEntities.Core.Switches; +using NetDaemon.HassModel; + +namespace CodeCasa.CustomEntities.Automation.Switches +{ + public class BedroomNightstandRightSwitch(ITriggerManager triggerManager) + : IkeaRodretDimmer(triggerManager, SwitchDeviceNames.BedroomNightstandRightSwitch); +} diff --git a/src/CodeCasa.CustomEntities.Automation/Switches/BedroomWallSwitch.cs b/src/CodeCasa.CustomEntities.Automation/Switches/BedroomWallSwitch.cs new file mode 100644 index 0000000..31e87b8 --- /dev/null +++ b/src/CodeCasa.CustomEntities.Automation/Switches/BedroomWallSwitch.cs @@ -0,0 +1,8 @@ +using CodeCasa.CustomEntities.Core.Switches; +using NetDaemon.HassModel; + +namespace CodeCasa.CustomEntities.Automation.Switches +{ + public class BedroomWallSwitch(ITriggerManager triggerManager) + : HueWallModuleSingleRocker(triggerManager, SwitchDeviceNames.BedroomWallSwitch); +} diff --git a/src/CodeCasa.CustomEntities.Automation/Switches/LivingRoomWallSwitch.cs b/src/CodeCasa.CustomEntities.Automation/Switches/LivingRoomWallSwitch.cs new file mode 100644 index 0000000..0f56bd5 --- /dev/null +++ b/src/CodeCasa.CustomEntities.Automation/Switches/LivingRoomWallSwitch.cs @@ -0,0 +1,8 @@ +using CodeCasa.CustomEntities.Core.Switches; +using NetDaemon.HassModel; + +namespace CodeCasa.CustomEntities.Automation.Switches +{ + public class LivingRoomWallSwitch(ITriggerManager triggerManager) + : HueWallModuleSingleRocker(triggerManager, SwitchDeviceNames.LivingRoomWallSwitch); +} diff --git a/src/CodeCasa.CustomEntities.Automation/Switches/OfficeDimmerSwitch.cs b/src/CodeCasa.CustomEntities.Automation/Switches/OfficeDimmerSwitch.cs new file mode 100644 index 0000000..51fab70 --- /dev/null +++ b/src/CodeCasa.CustomEntities.Automation/Switches/OfficeDimmerSwitch.cs @@ -0,0 +1,8 @@ +using CodeCasa.CustomEntities.Core.Switches; +using NetDaemon.HassModel; + +namespace CodeCasa.CustomEntities.Automation.Switches +{ + public class OfficeDimmerSwitch(ITriggerManager triggerManager) + : HueDimmerSwitch(triggerManager, SwitchDeviceNames.OfficeDeskDimmerSwitch); +} diff --git a/src/CodeCasa.CustomEntities.Automation/Switches/OfficeWallSwitch.cs b/src/CodeCasa.CustomEntities.Automation/Switches/OfficeWallSwitch.cs new file mode 100644 index 0000000..d87ead5 --- /dev/null +++ b/src/CodeCasa.CustomEntities.Automation/Switches/OfficeWallSwitch.cs @@ -0,0 +1,8 @@ +using CodeCasa.CustomEntities.Core.Switches; +using NetDaemon.HassModel; + +namespace CodeCasa.CustomEntities.Automation.Switches +{ + public class OfficeWallSwitch(ITriggerManager triggerManager) + : HueWallModuleSingleRocker(triggerManager, SwitchDeviceNames.OfficeWallSwitch); +} diff --git a/src/CodeCasa.CustomEntities.Automation/Switches/SwitchDeviceNames.cs b/src/CodeCasa.CustomEntities.Automation/Switches/SwitchDeviceNames.cs new file mode 100644 index 0000000..2f41e6d --- /dev/null +++ b/src/CodeCasa.CustomEntities.Automation/Switches/SwitchDeviceNames.cs @@ -0,0 +1,19 @@ +namespace CodeCasa.CustomEntities.Automation.Switches +{ + internal class SwitchDeviceNames + { + // Hue wall switches + public const string AtticHallwayWallSwitch = "Attic Hallway Wall Switch"; + public const string BathroomWallSwitch = "Bathroom Wall Switch"; + public const string BedroomWallSwitch = "Bedroom Wall Switch"; + public const string OfficeWallSwitch = "Office Wall Switch"; + public const string LivingRoomWallSwitch = "Living Room Wall Switch"; + + // Hue dimmer switches + public const string AtticDimmerSwitch = "Attic Dimmer Switch"; + public const string OfficeDeskDimmerSwitch = "Office Desk Dimmer Switch"; + + public const string BedroomNightstandRightSwitch = "Bedroom Nightstand Right Switch"; + public const string BedroomNightstandLeftSwitch = "Bedroom Nightstand Left Switch"; + } +} diff --git a/src/CodeCasa.CustomEntities.Core/CodeCasa.CustomEntities.Core.csproj b/src/CodeCasa.CustomEntities.Core/CodeCasa.CustomEntities.Core.csproj index 9387a92..f1d83eb 100644 --- a/src/CodeCasa.CustomEntities.Core/CodeCasa.CustomEntities.Core.csproj +++ b/src/CodeCasa.CustomEntities.Core/CodeCasa.CustomEntities.Core.csproj @@ -7,7 +7,9 @@ - + + + diff --git a/src/CodeCasa.CustomEntities.Core/Extensions/TriggerManagerExtensions.cs b/src/CodeCasa.CustomEntities.Core/Extensions/TriggerManagerExtensions.cs new file mode 100644 index 0000000..044c49f --- /dev/null +++ b/src/CodeCasa.CustomEntities.Core/Extensions/TriggerManagerExtensions.cs @@ -0,0 +1,18 @@ +using System.Reactive.Linq; +using NetDaemon.HassModel; + +namespace CodeCasa.CustomEntities.Core.Extensions +{ + public static class TriggerManagerExtensions + { + public static IObservable Zigbee2MqttActions(this ITriggerManager triggerManager, string mqttDeviceName) + { + var triggerTopic = triggerManager.RegisterTrigger(new + { + platform = "mqtt", + topic = $"zigbee2mqtt/{mqttDeviceName}/action" + }); + return triggerTopic.Select(e => e.GetProperty("payload").GetString()).Where(s => s != null)!; + } + } +} diff --git a/src/CodeCasa.CustomEntities.Core/InputSelect/TypeSafeInputSelectEntityExtensionMethods.cs b/src/CodeCasa.CustomEntities.Core/InputSelect/TypeSafeInputSelectEntityExtensionMethods.cs index 367c972..abf0fbd 100644 --- a/src/CodeCasa.CustomEntities.Core/InputSelect/TypeSafeInputSelectEntityExtensionMethods.cs +++ b/src/CodeCasa.CustomEntities.Core/InputSelect/TypeSafeInputSelectEntityExtensionMethods.cs @@ -1,6 +1,4 @@ -using NetDaemon.HassModel.Entities; - -namespace CodeCasa.CustomEntities.Core.InputSelect; +namespace CodeCasa.CustomEntities.Core.InputSelect; public static class TypeSafeInputSelectEntityExtensionMethods { diff --git a/src/CodeCasa.CustomEntities.Core/Switches/HueDimmerSwitch.cs b/src/CodeCasa.CustomEntities.Core/Switches/HueDimmerSwitch.cs new file mode 100644 index 0000000..1aaca2e --- /dev/null +++ b/src/CodeCasa.CustomEntities.Core/Switches/HueDimmerSwitch.cs @@ -0,0 +1,52 @@ +using System.Reactive; +using System.Reactive.Linq; +using CodeCasa.Abstractions; +using CodeCasa.CustomEntities.Core.Extensions; +using NetDaemon.HassModel; + +namespace CodeCasa.CustomEntities.Core.Switches +{ + public abstract class HueDimmerSwitch : IDimmer + { + // todo: have a look in zigbee2mqtt if this is all + private const string OnPress = "on_press"; + private const string OffPress = "off_press"; // scene button + // Dimming + private const string DownPress = "down_press"; + private const string DownPressRelease = "down_press_release"; + private const string DownHoldRelease = "down_hold_release"; + // Brightening + private const string UpPress = "up_press"; + private const string UpPressRelease = "up_press_release"; + private const string UpHoldRelease = "up_hold_release"; + + protected HueDimmerSwitch(ITriggerManager triggerManager, string mqttDeviceName) + { + OnOffPressed = triggerManager.Zigbee2MqttActions(mqttDeviceName) + .Where(x => x == OnPress) + .Select(_ => Unit.Default); + ScenePressed = triggerManager.Zigbee2MqttActions(mqttDeviceName) + .Where(x => x == OffPress) + .Select(_ => Unit.Default); + var dimStart = triggerManager.Zigbee2MqttActions(mqttDeviceName) + .Where(x => x == DownPress) + .Select(_ => true); + var dimStop = triggerManager.Zigbee2MqttActions(mqttDeviceName) + .Where(x => x is DownPressRelease or DownHoldRelease) + .Select(_ => false); + Dimming = dimStart.Merge(dimStop).DistinctUntilChanged().Prepend(false); + var brightenStart = triggerManager.Zigbee2MqttActions(mqttDeviceName) + .Where(x => x == UpPress) + .Select(_ => true); + var brightenStop = triggerManager.Zigbee2MqttActions(mqttDeviceName) + .Where(x => x is UpPressRelease or UpHoldRelease) + .Select(_ => false); + Brightening = brightenStart.Merge(brightenStop).DistinctUntilChanged().Prepend(false); + } + + public IObservable OnOffPressed { get; } + public IObservable ScenePressed { get; } + public IObservable Dimming { get; } + public IObservable Brightening { get; } + } +} diff --git a/src/CodeCasa.CustomEntities.Core/Switches/HueWallModuleDoubleRocker.cs b/src/CodeCasa.CustomEntities.Core/Switches/HueWallModuleDoubleRocker.cs new file mode 100644 index 0000000..ea92343 --- /dev/null +++ b/src/CodeCasa.CustomEntities.Core/Switches/HueWallModuleDoubleRocker.cs @@ -0,0 +1,26 @@ +using System.Reactive; +using System.Reactive.Linq; +using CodeCasa.CustomEntities.Core.Extensions; +using NetDaemon.HassModel; + +namespace CodeCasa.CustomEntities.Core.Switches +{ + public abstract class HueWallModuleDoubleRocker + { + private const string LeftPress = "left_press"; + private const string RightPress = "right_press"; + + protected HueWallModuleDoubleRocker(ITriggerManager triggerManager, string mqttDeviceName, bool invertButtons = false) + { + LeftPressed = triggerManager.Zigbee2MqttActions(mqttDeviceName) + .Where(x => x == (invertButtons ? RightPress : LeftPress)) + .Select(_ => Unit.Default); + RightPressed = triggerManager.Zigbee2MqttActions(mqttDeviceName) + .Where(x => x == (invertButtons ? LeftPress : RightPress)) + .Select(_ => Unit.Default); + } + + public IObservable LeftPressed { get; } + public IObservable RightPressed { get; } + } +} diff --git a/src/CodeCasa.CustomEntities.Core/Switches/HueWallModuleSingleRocker.cs b/src/CodeCasa.CustomEntities.Core/Switches/HueWallModuleSingleRocker.cs new file mode 100644 index 0000000..a51e16d --- /dev/null +++ b/src/CodeCasa.CustomEntities.Core/Switches/HueWallModuleSingleRocker.cs @@ -0,0 +1,24 @@ +using System.Reactive; +using System.Reactive.Linq; +using CodeCasa.CustomEntities.Core.Extensions; +using NetDaemon.HassModel; + +namespace CodeCasa.CustomEntities.Core.Switches +{ + public abstract class HueWallModuleSingleRocker : IObservable + { + private const string LeftPress = "left_press"; + private const string RightPress = "right_press"; + + private readonly IObservable _observable; + + protected HueWallModuleSingleRocker(ITriggerManager triggerManager, string mqttDeviceName, bool rightSwitch = false) + { + _observable = triggerManager.Zigbee2MqttActions(mqttDeviceName) + .Where(x => x == (rightSwitch ? RightPress : LeftPress)) + .Select(_ => Unit.Default); + } + + public IDisposable Subscribe(IObserver observer) => _observable.Subscribe(observer); + } +} diff --git a/src/CodeCasa.CustomEntities.Core/Switches/IkeaRodretDimmer.cs b/src/CodeCasa.CustomEntities.Core/Switches/IkeaRodretDimmer.cs new file mode 100644 index 0000000..662edb9 --- /dev/null +++ b/src/CodeCasa.CustomEntities.Core/Switches/IkeaRodretDimmer.cs @@ -0,0 +1,43 @@ +using System.Reactive; +using System.Reactive.Linq; +using CodeCasa.Abstractions; +using CodeCasa.CustomEntities.Core.Extensions; +using NetDaemon.HassModel; + +namespace CodeCasa.CustomEntities.Core.Switches +{ + public abstract class IkeaRodretDimmer : IDimmer + { + private const string On = "on"; + private const string Off = "off"; + private const string BrightnessMoveUp = "brightness_move_up"; + private const string BrightnessMoveDown = "brightness_move_down"; + private const string BrightnessStop = "brightness_stop"; + + protected IkeaRodretDimmer(ITriggerManager triggerManager, string mqttDeviceName) + { + OnPressed = triggerManager.Zigbee2MqttActions(mqttDeviceName) + .Where(x => x == On) + .Select(_ => Unit.Default); + OffPressed = triggerManager.Zigbee2MqttActions(mqttDeviceName) + .Where(x => x == Off) + .Select(_ => Unit.Default); + var dimStart = triggerManager.Zigbee2MqttActions(mqttDeviceName) + .Where(x => x == BrightnessMoveDown) + .Select(_ => true); + var brightenStart = triggerManager.Zigbee2MqttActions(mqttDeviceName) + .Where(x => x == BrightnessMoveUp) + .Select(_ => true); + var dimOrbrightenStop = triggerManager.Zigbee2MqttActions(mqttDeviceName) + .Where(x => x == BrightnessStop) + .Select(_ => false); + Dimming = dimStart.Merge(dimOrbrightenStop).DistinctUntilChanged().Prepend(false); + Brightening = brightenStart.Merge(dimOrbrightenStop).DistinctUntilChanged().Prepend(false); + } + + public IObservable OnPressed { get; } + public IObservable OffPressed { get; } + public IObservable Dimming { get; } + public IObservable Brightening { get; } + } +} diff --git a/src/CodeCasa.CustomEntities.Core/Switches/IkeaStyrbarRemoteControl.cs b/src/CodeCasa.CustomEntities.Core/Switches/IkeaStyrbarRemoteControl.cs new file mode 100644 index 0000000..d3d7d12 --- /dev/null +++ b/src/CodeCasa.CustomEntities.Core/Switches/IkeaStyrbarRemoteControl.cs @@ -0,0 +1,89 @@ +using System.Reactive; +using System.Reactive.Linq; +using CodeCasa.Abstractions; +using CodeCasa.CustomEntities.Core.Extensions; +using NetDaemon.HassModel; + +namespace CodeCasa.CustomEntities.Core.Switches +{ + public abstract class IkeaStyrbarRemoteControl : IDimmer + { + // Light large (top) + private const string On = "on"; + private const string BrightnessMoveUp = "brightness_move_up"; + private const string BrightnessStop = "brightness_stop"; + + // Light small (bottom) + private const string Off = "off"; + private const string BrightnessMoveDown = "brightness_move_down"; + // BrightnessStop already defined above + + // Arrow left + private const string ArrowLeftClick = "arrow_left_click"; + private const string ArrowLeftHold = "arrow_left_hold"; + private const string ArrowLeftRelease = "arrow_left_release"; + + // Arrow right + private const string ArrowRightClick = "arrow_right_click"; + private const string ArrowRightHold = "arrow_right_hold"; + private const string ArrowRightRelease = "arrow_right_release"; + + protected IkeaStyrbarRemoteControl(ITriggerManager triggerManager, string mqttDeviceName) + { + LeftPressed = triggerManager.Zigbee2MqttActions(mqttDeviceName) + .Where(x => x == ArrowLeftClick) + .Select(_ => Unit.Default); + RightPressed = triggerManager.Zigbee2MqttActions(mqttDeviceName) + .Where(x => x == ArrowRightClick) + .Select(_ => Unit.Default); + + SmallLightPressed = triggerManager.Zigbee2MqttActions(mqttDeviceName) + .Where(x => x == Off) + .Select(_ => Unit.Default); + LargeLightPressed = triggerManager.Zigbee2MqttActions(mqttDeviceName) + .Where(x => x == On) + .Select(_ => Unit.Default); + + var leftHoldStart = triggerManager.Zigbee2MqttActions(mqttDeviceName) + .Where(x => x is ArrowLeftClick or ArrowLeftHold) + .Select(_ => true); + var leftHoldStop = triggerManager.Zigbee2MqttActions(mqttDeviceName) + .Where(x => x == ArrowLeftRelease) + .Select(_ => false); + LeftHold = leftHoldStart.Merge(leftHoldStop).DistinctUntilChanged().Prepend(false); + + var rightHoldStart = triggerManager.Zigbee2MqttActions(mqttDeviceName) + .Where(x => x is ArrowRightClick or ArrowRightHold) + .Select(_ => true); + var rightHoldStop = triggerManager.Zigbee2MqttActions(mqttDeviceName) + .Where(x => x == ArrowRightRelease) + .Select(_ => false); + RightHold = rightHoldStart.Merge(rightHoldStop).DistinctUntilChanged().Prepend(false); + + var dimStart = triggerManager.Zigbee2MqttActions(mqttDeviceName) + .Where(x => x is Off or BrightnessMoveDown) + .Select(_ => true); + var dimStop = triggerManager.Zigbee2MqttActions(mqttDeviceName) + .Where(x => x == BrightnessStop) + .Select(_ => false); + Dimming = dimStart.Merge(dimStop).DistinctUntilChanged().Prepend(false); + + var brightenStart = triggerManager.Zigbee2MqttActions(mqttDeviceName) + .Where(x => x is On or BrightnessMoveUp) + .Select(_ => true); + var brightenStop = triggerManager.Zigbee2MqttActions(mqttDeviceName) + .Where(x => x == BrightnessStop) + .Select(_ => false); + Brightening = brightenStart.Merge(brightenStop).DistinctUntilChanged().Prepend(false); + } + + public IObservable LeftPressed { get; } + public IObservable RightPressed { get; } + public IObservable SmallLightPressed { get; } + public IObservable LargeLightPressed { get; } + public IObservable LeftHold { get; } + public IObservable RightHold { get; } + public IObservable Dimming { get; } + public IObservable Brightening { get; } + } +} diff --git a/src/CodeCasa.Dashboard/CodeCasa.Dashboard.csproj b/src/CodeCasa.Dashboard/CodeCasa.Dashboard.csproj index 8d382f1..38e2e39 100644 --- a/src/CodeCasa.Dashboard/CodeCasa.Dashboard.csproj +++ b/src/CodeCasa.Dashboard/CodeCasa.Dashboard.csproj @@ -7,13 +7,14 @@ - - - - - + + + + + + diff --git a/src/CodeCasa.Dashboard/Components/App.razor b/src/CodeCasa.Dashboard/Components/App.razor index aafb3e9..8d1b601 100644 --- a/src/CodeCasa.Dashboard/Components/App.razor +++ b/src/CodeCasa.Dashboard/Components/App.razor @@ -9,6 +9,7 @@ + @@ -19,6 +20,7 @@ + diff --git a/src/CodeCasa.NetDaemon.Utilities/CodeCasa.NetDaemon.Utilities.csproj b/src/CodeCasa.NetDaemon.Utilities/CodeCasa.NetDaemon.Utilities.csproj index e770439..f890cf4 100644 --- a/src/CodeCasa.NetDaemon.Utilities/CodeCasa.NetDaemon.Utilities.csproj +++ b/src/CodeCasa.NetDaemon.Utilities/CodeCasa.NetDaemon.Utilities.csproj @@ -7,8 +7,8 @@ - - + + diff --git a/src/CodeCasa.NetDaemon.Utilities/ColorModes.cs b/src/CodeCasa.NetDaemon.Utilities/ColorModes.cs deleted file mode 100644 index 6766e7e..0000000 --- a/src/CodeCasa.NetDaemon.Utilities/ColorModes.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace CodeCasa.NetDaemon.Utilities; - -internal static class ColorModes -{ - public const string ColorTemp = "color_temp"; - public const string Brightness = "brightness"; - public const string OnOff = "onoff"; - public const string Xy = "xy"; -} \ No newline at end of file diff --git a/src/CodeCasa.NetDaemon.Utilities/Extensions/ColorExtensions.cs b/src/CodeCasa.NetDaemon.Utilities/Extensions/ColorExtensions.cs deleted file mode 100644 index a4c2e66..0000000 --- a/src/CodeCasa.NetDaemon.Utilities/Extensions/ColorExtensions.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System.Drawing; - -namespace CodeCasa.NetDaemon.Utilities.Extensions; - -internal static class ColorExtensions -{ - public static IReadOnlyCollection ToRgbCollection(this Color color) - => new int[] { color.R, color.G, color.B }.AsReadOnly(); -} \ No newline at end of file diff --git a/src/CodeCasa.NetDaemon.Utilities/Extensions/LightEntityExtensions.cs b/src/CodeCasa.NetDaemon.Utilities/Extensions/LightEntityExtensions.cs deleted file mode 100644 index aacc51f..0000000 --- a/src/CodeCasa.NetDaemon.Utilities/Extensions/LightEntityExtensions.cs +++ /dev/null @@ -1,81 +0,0 @@ -using CodeCasa.AutoGenerated; -using CodeCasa.AutoGenerated.Extensions; - -namespace CodeCasa.NetDaemon.Utilities.Extensions; - -public static class LightEntityExtensions -{ - private const int RelaxTemp = 500; - - public static void ExecuteLightParameters(this LightEntity lightEntity, LightParameters lightParameters) - { - if (lightParameters.Brightness == 0) - { - lightEntity.TurnOff(lightParameters.ToLightTurnOffParameters()); - return; - } - - lightEntity.TurnOn(lightParameters.ToLightTurnOnParameters()); - } - - /// - /// Will attempt to autogenerate the parameters for the Relax scene of any given light entity. - /// - public static LightParameters GetRelaxSceneParameters(this LightEntity lightEntity) - { - return lightEntity.TryGetColorTempParameters(RelaxTemp, 142) ?? - lightEntity.TryGetBrightnessParameters(142) ?? // Note: This will also turn on white lights that only support brightness, but we don't have those. - LightParameters.Off(); - } - - private static LightParameters? TryGetColorTempParameters(this LightEntity lightEntity, int desiredTemp, int brightness) - { - if (!lightEntity.SupportsColorMode(ColorModes.ColorTemp)) - { - return null; - } - var colorTemp = lightEntity.GetColorTempWithinRange(desiredTemp); - if (colorTemp == null) - { - return null; - } - - return new() { ColorTemp = colorTemp, Brightness = brightness }; - } - - public static bool SupportsColorMode(this LightEntity lightEntity, string colorMode) - { - var attributes = lightEntity.Attributes; - if (attributes?.SupportedColorModes == null) - { - return false; - } - - return attributes.SupportedColorModes.Any(mode => - string.Equals(mode, colorMode, StringComparison.OrdinalIgnoreCase)); - } - - private static LightParameters? TryGetBrightnessParameters(this LightEntity lightEntity, int brightness) - { - if (!lightEntity.SupportsColorMode(ColorModes.Brightness)) - { - return null; - } - return new() { Brightness = brightness }; - } - - public static int? GetColorTempWithinRange(this LightEntity lightEntity, int desiredMireds) - { - var attributes = lightEntity.Attributes; - if (attributes == null) - { - return null; - } - if (attributes.MinMireds == null || attributes.MaxMireds == null) - { - return null; - } - - return Math.Min((int)attributes.MaxMireds, Math.Max((int)attributes.MinMireds, desiredMireds)); - } -} \ No newline at end of file diff --git a/src/CodeCasa.NetDaemon.Utilities/Extensions/LightParametersExtensions.cs b/src/CodeCasa.NetDaemon.Utilities/Extensions/LightParametersExtensions.cs deleted file mode 100644 index ac50a1c..0000000 --- a/src/CodeCasa.NetDaemon.Utilities/Extensions/LightParametersExtensions.cs +++ /dev/null @@ -1,25 +0,0 @@ -using CodeCasa.AutoGenerated.Parameters; - -namespace CodeCasa.NetDaemon.Utilities.Extensions; - -internal static class LightParametersExtensions -{ - public static LightTurnOnParameters ToLightTurnOnParameters(this LightParameters lightParameters) - { - return new LightTurnOnParameters - { - Transition = lightParameters.Transition?.TotalSeconds, - Brightness = lightParameters.Brightness, - RgbColor = lightParameters.RgbColor?.ToRgbCollection(), - ColorTemp = lightParameters.ColorTemp - }; - } - - public static LightTurnOffParameters ToLightTurnOffParameters(this LightParameters lightParameters) - { - return new LightTurnOffParameters - { - Transition = lightParameters.Transition?.TotalSeconds - }; - } -} \ No newline at end of file diff --git a/src/CodeCasa.NetDaemon.Utilities/LightParameters.cs b/src/CodeCasa.NetDaemon.Utilities/LightParameters.cs deleted file mode 100644 index 24e6574..0000000 --- a/src/CodeCasa.NetDaemon.Utilities/LightParameters.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System.Drawing; - -namespace CodeCasa.NetDaemon.Utilities; - -/// -/// This record represents the state of a light entity. -/// Using a separate record rather than LightTurnOnParameters makes it easier to see what properties are being used. Especially as this record will also be used to store current state in later automations. -/// -public record LightParameters -{ - public double? Brightness { get; init; } - public Color? RgbColor { get; init; } - public int? ColorTemp { get; init; } - public TimeSpan? Transition { get; init; } - - public static LightParameters Off() - { - return new LightParameters { Brightness = 0 }; - } -} \ No newline at end of file diff --git a/src/CodeCasa.Shared/CodeCasa.Shared.csproj b/src/CodeCasa.Shared/CodeCasa.Shared.csproj index b760144..7cee667 100644 --- a/src/CodeCasa.Shared/CodeCasa.Shared.csproj +++ b/src/CodeCasa.Shared/CodeCasa.Shared.csproj @@ -6,4 +6,8 @@ enable + + + +