From 39a99a0ba83f96354ee7cca6a4edc1bb56f5875b Mon Sep 17 00:00:00 2001 From: Jasper Date: Thu, 1 Jan 2026 15:13:43 +0100 Subject: [PATCH 01/13] Initial refactor and new examples. --- .../BinarySensorAttributes.cs | 24 ++ .../BinarySensorEntities.cs | 20 ++ .../BinarySensorEntity.cs | 15 ++ .../Extensions/GeneratedExtensions.cs | 1 + src/CodeCasa.AutoGenerated/LightAttributes.cs | 38 +-- src/CodeCasa.AutoGenerated/LightEntities.cs | 6 + .../SensorAttributes.cs | 243 +++++++++++++++++ src/CodeCasa.AutoGenerated/SensorEntities.cs | 255 +----------------- src/CodeCasa.AutoGenerated/SensorEntity.cs | 15 ++ .../BackyardCoachLightsEnergySavingNode.cs | 8 - .../BackyardLights/BackyardLightsApp.cs | 64 +++++ .../BackyardStringLightsEnergySavingNode.cs | 7 - .../BackyardStringLightsPipeline.cs | 84 ------ .../BackyardLights/LightStringRoutineNode.cs | 21 -- .../BackyardLightsEnergySaving.cs} | 21 +- .../Observables/BackyardLightsRoutine.cs | 27 ++ .../BackyardLightsRoutineFactory.cs | 22 ++ .../OfficeLights/ColorTransitionNode.cs | 53 ++++ .../Lights/OfficeLights/OfficeLightsApp.cs | 65 +++++ .../Apps/Lights/OfficeLights/TestNode.cs | 18 ++ .../Apps/Notifications/AlarmLight.cs | 17 ++ .../DashboardDemoNotifications.cs | 2 +- .../OfficeLightsNotifications.cs | 6 +- .../Notifications/PhoneDemoNotifications.cs | 2 +- .../Apps/People/PeopleHomeStateSyncer.cs | 2 +- .../Extensions/ServiceCollectionExtensions.cs | 14 +- src/CodeCasa.Automations/Nodes/AlarmNode.cs | 28 ++ .../CodeCasa.CustomEntities.Automation.csproj | 1 + .../Extensions/ServiceCollectionExtensions.cs | 9 + .../People/CompositePersonEntity.cs | 14 +- .../People/PeopleEntities.cs | 10 +- .../Sensors/MotionSensor.cs | 43 +++ .../Sensors/OfficeMotionSensor.cs | 12 + .../Switches/OfficeDimmerSwitch.cs | 8 + .../Switches/OfficeWallModuleSingleRocker.cs | 8 + .../Switches/SwitchDeviceNames.cs | 11 + .../CodeCasa.CustomEntities.Core.csproj | 1 + .../Extensions/TriggerManagerExtensions.cs | 18 ++ .../Switches/HueDimmerSwitch.cs | 52 ++++ .../Switches/HueWallModuleDoubleRocker.cs | 26 ++ .../Switches/HueWallModuleSingleRocker.cs | 24 ++ .../Switches/IkeaStyrbarRemoteControl.cs | 89 ++++++ .../CodeCasa.NetDaemon.Utilities.csproj | 2 +- .../ColorModes.cs | 9 - .../Extensions/ColorExtensions.cs | 9 - .../Extensions/LightEntityExtensions.cs | 81 ------ .../Extensions/LightParametersExtensions.cs | 25 -- .../LightParameters.cs | 20 -- .../Config/LightNotificationConfig.cs | 24 ++ src/CodeCasa.Shared/CodeCasa.Shared.csproj | 4 + 50 files changed, 1017 insertions(+), 561 deletions(-) create mode 100644 src/CodeCasa.AutoGenerated/BinarySensorAttributes.cs create mode 100644 src/CodeCasa.AutoGenerated/BinarySensorEntities.cs create mode 100644 src/CodeCasa.AutoGenerated/BinarySensorEntity.cs create mode 100644 src/CodeCasa.AutoGenerated/SensorAttributes.cs create mode 100644 src/CodeCasa.AutoGenerated/SensorEntity.cs delete mode 100644 src/CodeCasa.Automations/Apps/Lights/BackyardLights/BackyardCoachLightsEnergySavingNode.cs create mode 100644 src/CodeCasa.Automations/Apps/Lights/BackyardLights/BackyardLightsApp.cs delete mode 100644 src/CodeCasa.Automations/Apps/Lights/BackyardLights/BackyardStringLightsEnergySavingNode.cs delete mode 100644 src/CodeCasa.Automations/Apps/Lights/BackyardLights/BackyardStringLightsPipeline.cs delete mode 100644 src/CodeCasa.Automations/Apps/Lights/BackyardLights/LightStringRoutineNode.cs rename src/CodeCasa.Automations/Apps/Lights/BackyardLights/{BackyardLightsEnergySavingNode.cs => Observables/BackyardLightsEnergySaving.cs} (61%) create mode 100644 src/CodeCasa.Automations/Apps/Lights/BackyardLights/Observables/BackyardLightsRoutine.cs create mode 100644 src/CodeCasa.Automations/Apps/Lights/BackyardLights/Observables/BackyardLightsRoutineFactory.cs create mode 100644 src/CodeCasa.Automations/Apps/Lights/OfficeLights/ColorTransitionNode.cs create mode 100644 src/CodeCasa.Automations/Apps/Lights/OfficeLights/OfficeLightsApp.cs create mode 100644 src/CodeCasa.Automations/Apps/Lights/OfficeLights/TestNode.cs create mode 100644 src/CodeCasa.Automations/Apps/Notifications/AlarmLight.cs create mode 100644 src/CodeCasa.Automations/Nodes/AlarmNode.cs create mode 100644 src/CodeCasa.CustomEntities.Automation/Sensors/MotionSensor.cs create mode 100644 src/CodeCasa.CustomEntities.Automation/Sensors/OfficeMotionSensor.cs create mode 100644 src/CodeCasa.CustomEntities.Automation/Switches/OfficeDimmerSwitch.cs create mode 100644 src/CodeCasa.CustomEntities.Automation/Switches/OfficeWallModuleSingleRocker.cs create mode 100644 src/CodeCasa.CustomEntities.Automation/Switches/SwitchDeviceNames.cs create mode 100644 src/CodeCasa.CustomEntities.Core/Extensions/TriggerManagerExtensions.cs create mode 100644 src/CodeCasa.CustomEntities.Core/Switches/HueDimmerSwitch.cs create mode 100644 src/CodeCasa.CustomEntities.Core/Switches/HueWallModuleDoubleRocker.cs create mode 100644 src/CodeCasa.CustomEntities.Core/Switches/HueWallModuleSingleRocker.cs create mode 100644 src/CodeCasa.CustomEntities.Core/Switches/IkeaStyrbarRemoteControl.cs delete mode 100644 src/CodeCasa.NetDaemon.Utilities/ColorModes.cs delete mode 100644 src/CodeCasa.NetDaemon.Utilities/Extensions/ColorExtensions.cs delete mode 100644 src/CodeCasa.NetDaemon.Utilities/Extensions/LightEntityExtensions.cs delete mode 100644 src/CodeCasa.NetDaemon.Utilities/Extensions/LightParametersExtensions.cs delete mode 100644 src/CodeCasa.NetDaemon.Utilities/LightParameters.cs create mode 100644 src/CodeCasa.Notifications.Light/Config/LightNotificationConfig.cs 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..cee6c9e --- /dev/null +++ b/src/CodeCasa.AutoGenerated/BinarySensorEntities.cs @@ -0,0 +1,20 @@ +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)); + + ///Office Motion Sensor Occupancy + public BinarySensorEntity OfficeMotionSensorOccupancy => new(_haContext, "binary_sensor.office_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/Extensions/GeneratedExtensions.cs b/src/CodeCasa.AutoGenerated/Extensions/GeneratedExtensions.cs index 57cb376..138afc8 100644 --- a/src/CodeCasa.AutoGenerated/Extensions/GeneratedExtensions.cs +++ b/src/CodeCasa.AutoGenerated/Extensions/GeneratedExtensions.cs @@ -10,6 +10,7 @@ public static IServiceCollection AddHomeAssistantGenerated(this IServiceCollecti return serviceCollection .AddTransient() .AddTransient() + .AddTransient() .AddTransient() .AddTransient() .AddTransient() 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..c73a112 100644 --- a/src/CodeCasa.AutoGenerated/LightEntities.cs +++ b/src/CodeCasa.AutoGenerated/LightEntities.cs @@ -12,6 +12,12 @@ public LightEntities(IHaContext haContext) /// Enumerates all light entities currently registered (at runtime) in Home Assistant as LightEntity public IEnumerable EnumerateAll() => _haContext.GetAllEntities().Where(e => e.EntityId.StartsWith("light.")).Select(e => new LightEntity(e)); + ///Office Light Color 1 + public LightEntity OfficeLightColor1 => new(_haContext, "light.office_light_color_1"); + ///Office Light Color 2 + public LightEntity OfficeLightColor2 => new(_haContext, "light.office_light_color_2"); + ///Office Light Color 3 + public LightEntity OfficeLightColor3 => new(_haContext, "light.office_light_color_3"); ///Office Lights public LightEntity OfficeLights => new(_haContext, "light.office_lights"); ///Backyard Door Light 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..8c7eb50 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; @@ -33,259 +31,10 @@ 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; } } \ 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..a8b4b5f --- /dev/null +++ b/src/CodeCasa.Automations/Apps/Lights/BackyardLights/BackyardLightsApp.cs @@ -0,0 +1,64 @@ +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.Light; +using CodeCasa.Notifications.Light.Extensions; +using NetDaemon.AppModel; + +namespace CodeCasa.Automations.Apps.Lights.BackyardLights; + +[NetDaemonApp] +internal class BackyardLightsApp +{ + public BackyardLightsApp( + LightPipelineFactory pipelineFactory, + LightEntities lightEntities, + LightNotificationManagerContext lightNotificationManager, + 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(); + }); + } + + // 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) + .TurnOffWhen() + .SupportNotifications(lightNotificationManager); + }); + } + } +} \ 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 61% rename from src/CodeCasa.Automations/Apps/Lights/BackyardLights/BackyardLightsEnergySavingNode.cs rename to src/CodeCasa.Automations/Apps/Lights/BackyardLights/Observables/BackyardLightsEnergySaving.cs index b59c37d..74856c8 100644 --- a/src/CodeCasa.Automations/Apps/Lights/BackyardLights/BackyardLightsEnergySavingNode.cs +++ b/src/CodeCasa.Automations/Apps/Lights/BackyardLights/Observables/BackyardLightsEnergySaving.cs @@ -1,17 +1,18 @@ using System.Reactive.Concurrency; -using AutomationPipelines; using CodeCasa.AutoGenerated; -using CodeCasa.NetDaemon.Extensions.Observables; +using NetDaemon.Extensions.Observables; using Occurify; using Occurify.Reactive.Extensions; 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/Lights/OfficeLights/ColorTransitionNode.cs b/src/CodeCasa.Automations/Apps/Lights/OfficeLights/ColorTransitionNode.cs new file mode 100644 index 0000000..e2d69fe --- /dev/null +++ b/src/CodeCasa.Automations/Apps/Lights/OfficeLights/ColorTransitionNode.cs @@ -0,0 +1,53 @@ +using System.Drawing; +using System.Reactive.Concurrency; +using CodeCasa.AutomationPipelines; +using CodeCasa.Lights; + +namespace CodeCasa.Automations.Apps.Lights.OfficeLights +{ + internal class ColorTransitionNode : PipelineNode + { + public ColorTransitionNode(IScheduler scheduler, TimeSpan transitionTime, params Color[] colors) + { + var defaultTransition = TimeSpan.FromMilliseconds(500); + if (transitionTime < defaultTransition) + { + throw new ArgumentException($"Transition time should be at least {defaultTransition}.", nameof(transitionTime)); + } + var colors1 = colors ?? throw new ArgumentNullException(nameof(colors)); + if (colors1.Length == 0) + { + throw new ArgumentException("At least one color must be provided.", nameof(colors)); + } + + var index = 0; + Output = new LightParameters + { + RgbColor = colors1[0], + Brightness = byte.MaxValue + }.AsTransition(); + + scheduler.Schedule(defaultTransition, () => + { + index = (index + 1) % colors1.Length; + Output = new LightParameters + { + RgbColor = colors1[index], + Brightness = byte.MaxValue + }.AsTransition(transitionTime - defaultTransition); + }); + + scheduler.SchedulePeriodic( + transitionTime, + () => + { + index = (index + 1) % colors1.Length; + Output = new LightParameters + { + RgbColor = colors1[index], + Brightness = byte.MaxValue + }.AsTransition(transitionTime); + }); + } + } +} diff --git a/src/CodeCasa.Automations/Apps/Lights/OfficeLights/OfficeLightsApp.cs b/src/CodeCasa.Automations/Apps/Lights/OfficeLights/OfficeLightsApp.cs new file mode 100644 index 0000000..78ef9ab --- /dev/null +++ b/src/CodeCasa.Automations/Apps/Lights/OfficeLights/OfficeLightsApp.cs @@ -0,0 +1,65 @@ +using CodeCasa.AutoGenerated; +using CodeCasa.AutomationPipelines.Lights.Pipeline; +using CodeCasa.CustomEntities.Automation.People; +using CodeCasa.CustomEntities.Automation.Sensors; +using CodeCasa.CustomEntities.Automation.Switches; +using CodeCasa.Lights; +using CodeCasa.Notifications.Light; +using Microsoft.Extensions.DependencyInjection; +using NetDaemon.AppModel; +using System.Drawing; +using System.Reactive.Concurrency; +using CodeCasa.AutomationPipelines.Lights.NetDaemon.Extensions; + +namespace CodeCasa.Automations.Apps.Lights.OfficeLights; + +[NetDaemonApp] +internal class OfficeLightsApp +{ + public OfficeLightsApp( + LightPipelineFactory lightPipelineFactory, + LightEntities lightEntities, + LightNotificationManagerContext lightNotificationManager, + OfficeMotionSensor officeMotionSensor, + OfficeWallModuleSingleRocker officeWallModuleSingleRocker, + OfficeDimmerSwitch officeDimmerSwitch, + PeopleEntities people) + { + lightPipelineFactory + .SetupLightPipeline(lightEntities.OfficeLights, builder => + { + builder + .AddReactiveNode(rc => + { + rc + //.AddCycle(officeDimmerSwitch.ScenePressed, LightScenes.Bright, LightScenes.Concentrate) + .AddToggle(officeDimmerSwitch.OnOffPressed, c => + { + c + .Add(LightParameters.Relax) + .ForLight(lightEntities.OfficeLightColor1.EntityId, c1 => + { + c1.Add(context => new ColorTransitionNode( + context.ServiceProvider.GetRequiredService(), + TimeSpan.FromSeconds(3), + Color.Red, Color.Blue, Color.LawnGreen)); + }) + .ForLight(lightEntities.OfficeLightColor2.EntityId, c2 => + { + c2.Add(context => new ColorTransitionNode( + context.ServiceProvider.GetRequiredService(), + TimeSpan.FromSeconds(3), + Color.Cyan, Color.Yellow, Color.Magenta)); + }); + }); + //.AddToggle(officeDimmerSwitch.ScenePressed, c => c.Add()) + //.AddToggle(officeDimmerSwitch.ScenePressed, c => c.Add(ctx => new TestNode(ctx, ctx.ServiceProvider.GetRequiredService()))) + //.AddToggle(officeDimmerSwitch.OnOffPressed, c => c.Add()) + //.TurnOffWhen(people.OnLastPersonToAsleepOrAwayObservable().Where(b => b) + // .Select(_ => Unit.Default)); + }); + //.AddNode(); + //.SupportNotifications(lightNotificationManager); + }); + } +} \ No newline at end of file diff --git a/src/CodeCasa.Automations/Apps/Lights/OfficeLights/TestNode.cs b/src/CodeCasa.Automations/Apps/Lights/OfficeLights/TestNode.cs new file mode 100644 index 0000000..f05933e --- /dev/null +++ b/src/CodeCasa.Automations/Apps/Lights/OfficeLights/TestNode.cs @@ -0,0 +1,18 @@ +using System.Reactive.Concurrency; +using CodeCasa.AutomationPipelines; +using CodeCasa.AutomationPipelines.Lights.Context; +using CodeCasa.Lights; + +namespace CodeCasa.Automations.Apps.Lights.OfficeLights +{ + internal class TestNode : PipelineNode + { + public TestNode(ILightPipelineContext context, IScheduler scheduler) + { + scheduler.SchedulePeriodic( + TimeSpan.FromSeconds(2), + () => Console.WriteLine($"Tick at {DateTime.Now} for {context.LightEntity.Id}") + ); + } + } +} diff --git a/src/CodeCasa.Automations/Apps/Notifications/AlarmLight.cs b/src/CodeCasa.Automations/Apps/Notifications/AlarmLight.cs new file mode 100644 index 0000000..0e55de1 --- /dev/null +++ b/src/CodeCasa.Automations/Apps/Notifications/AlarmLight.cs @@ -0,0 +1,17 @@ +using System.Reactive.Concurrency; +using CodeCasa.Automations.Nodes; +using CodeCasa.Notifications.Light; +using NetDaemon.AppModel; + +namespace CodeCasa.Automations.Apps.Notifications; + +[NetDaemonApp] +internal class AlarmLight +{ + public AlarmLight( + IScheduler scheduler, + LightNotificationContext lightNotification) + { + lightNotification.Notify("alarm"); + } +} \ No newline at end of file diff --git a/src/CodeCasa.Automations/Apps/Notifications/DashboardDemoNotifications.cs b/src/CodeCasa.Automations/Apps/Notifications/DashboardDemoNotifications.cs index 8f5d5d6..24d12e6 100644 --- a/src/CodeCasa.Automations/Apps/Notifications/DashboardDemoNotifications.cs +++ b/src/CodeCasa.Automations/Apps/Notifications/DashboardDemoNotifications.cs @@ -1,9 +1,9 @@ using CodeCasa.CustomEntities.Automation.Notifications.Dashboards; using CodeCasa.CustomEntities.Core.Events; using CodeCasa.NetDaemon.Utilities.Extensions; -using CodeCasa.Notifications.InputSelect.NetDaemon; using NetDaemon.AppModel; using NetDaemon.HassModel; +using NetDaemon.Notifications.InputSelect; namespace CodeCasa.Automations.Apps.Notifications; diff --git a/src/CodeCasa.Automations/Apps/Notifications/OfficeLightsNotifications.cs b/src/CodeCasa.Automations/Apps/Notifications/OfficeLightsNotifications.cs index a2327ac..1e7edf2 100644 --- a/src/CodeCasa.Automations/Apps/Notifications/OfficeLightsNotifications.cs +++ b/src/CodeCasa.Automations/Apps/Notifications/OfficeLightsNotifications.cs @@ -1,10 +1,10 @@ using CodeCasa.AutoGenerated; using CodeCasa.AutoGenerated.Extensions; using NetDaemon.AppModel; +using NetDaemon.Extensions.Observables; using CodeCasa.CustomEntities.Automation.Notifications.Dashboards; using CodeCasa.CustomEntities.Automation.People; -using CodeCasa.NetDaemon.Extensions.Observables; -using CodeCasa.Notifications.Phone.NetDaemon.Config; +using NetDaemon.Notifications.Phone.Config; using Reactive.Boolean; namespace CodeCasa.Automations.Apps.Notifications; @@ -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/Notifications/PhoneDemoNotifications.cs b/src/CodeCasa.Automations/Apps/Notifications/PhoneDemoNotifications.cs index 97b2146..740df49 100644 --- a/src/CodeCasa.Automations/Apps/Notifications/PhoneDemoNotifications.cs +++ b/src/CodeCasa.Automations/Apps/Notifications/PhoneDemoNotifications.cs @@ -1,10 +1,10 @@ using NetDaemon.AppModel; using NetDaemon.HassModel; +using NetDaemon.Notifications.Phone.Config; using System.Drawing; using CodeCasa.CustomEntities.Automation.Notifications.Phones; using CodeCasa.CustomEntities.Core.Events; using CodeCasa.NetDaemon.Utilities.Extensions; -using CodeCasa.Notifications.Phone.NetDaemon.Config; namespace CodeCasa.Automations.Apps.Notifications; 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/Extensions/ServiceCollectionExtensions.cs b/src/CodeCasa.Automations/Extensions/ServiceCollectionExtensions.cs index e2b05b8..0bf21bf 100644 --- a/src/CodeCasa.Automations/Extensions/ServiceCollectionExtensions.cs +++ b/src/CodeCasa.Automations/Extensions/ServiceCollectionExtensions.cs @@ -1,9 +1,11 @@ -using AutomationPipelines.Extensions; -using CodeCasa.AutoGenerated.Extensions; +using CodeCasa.AutoGenerated.Extensions; +using CodeCasa.AutomationPipelines.Extensions; +using CodeCasa.AutomationPipelines.Lights.Extensions; +using CodeCasa.Automations.Apps.Lights.BackyardLights.Observables; using CodeCasa.CustomEntities.Automation.Extensions; -using CodeCasa.Notifications.InputSelect.NetDaemon.Extensions; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; +using NetDaemon.Notifications.InputSelect.Extensions; using Occurify.Astro; namespace CodeCasa.Automations.Extensions; @@ -19,6 +21,10 @@ public static IServiceCollection AddCodeCasa(this IServiceCollection serviceColl .AddAutomationPipelines() .AddHomeAssistantGenerated() .AddCodeCasaCustomAutomationEntities() - .AddInputSelectNotifications(configuration); + .AddLightPipelines() + .AddInputSelectNotifications(configuration) + + .AddTransient() + .AddTransient(); } } \ 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..94d4413 --- /dev/null +++ b/src/CodeCasa.Automations/Nodes/AlarmNode.cs @@ -0,0 +1,28 @@ +using System.Reactive.Concurrency; +using System.Reactive.Linq; +using CodeCasa.AutomationPipelines.Lights.Context; +using CodeCasa.AutomationPipelines.Lights.Nodes; + +namespace CodeCasa.Automations.Nodes +{ + public class AlarmNode : LightTransitionNode, IDisposable + { + private readonly IDisposable _subscription; + + public AlarmNode(IScheduler scheduler, ILightPipelineContext lightPipelineContext) : base(scheduler) + { + var high = false; + _subscription = Observable.Interval(TimeSpan.FromSeconds(2), scheduler) + .Subscribe(_ => + { + high = !high; + //Output = lightPipelineContext.LightEntity.GetWarningSceneParameters(high).AsTransitionInSeconds(0); + }); + } + + public void Dispose() + {// todo: test + _subscription.Dispose(); + } + } +} diff --git a/src/CodeCasa.CustomEntities.Automation/CodeCasa.CustomEntities.Automation.csproj b/src/CodeCasa.CustomEntities.Automation/CodeCasa.CustomEntities.Automation.csproj index d9afc1f..9ac2e82 100644 --- a/src/CodeCasa.CustomEntities.Automation/CodeCasa.CustomEntities.Automation.csproj +++ b/src/CodeCasa.CustomEntities.Automation/CodeCasa.CustomEntities.Automation.csproj @@ -16,6 +16,7 @@ + diff --git a/src/CodeCasa.CustomEntities.Automation/Extensions/ServiceCollectionExtensions.cs b/src/CodeCasa.CustomEntities.Automation/Extensions/ServiceCollectionExtensions.cs index 3f6994d..f237a1e 100644 --- a/src/CodeCasa.CustomEntities.Automation/Extensions/ServiceCollectionExtensions.cs +++ b/src/CodeCasa.CustomEntities.Automation/Extensions/ServiceCollectionExtensions.cs @@ -1,7 +1,10 @@ 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.Notifications.Light.Extensions; using Microsoft.Extensions.DependencyInjection; namespace CodeCasa.CustomEntities.Automation.Extensions; @@ -13,6 +16,12 @@ public static IServiceCollection AddCodeCasaCustomAutomationEntities(this IServi return serviceCollection .AddCodeCasaCustomCoreEntities() + .AddLightNotifications() + + .AddTransient() + .AddTransient() + + .AddTransient() // Dashboard Notifications .AddTransient() diff --git a/src/CodeCasa.CustomEntities.Automation/People/CompositePersonEntity.cs b/src/CodeCasa.CustomEntities.Automation/People/CompositePersonEntity.cs index a8640dc..6df8faf 100644 --- a/src/CodeCasa.CustomEntities.Automation/People/CompositePersonEntity.cs +++ b/src/CodeCasa.CustomEntities.Automation/People/CompositePersonEntity.cs @@ -4,6 +4,10 @@ using CodeCasa.NetDaemon.Extensions.Observables; using CodeCasa.Notifications.InputSelect.NetDaemon.Interact; using CodeCasa.Notifications.Phone.NetDaemon; +using System.Reactive.Linq; +using NetDaemon.HassModel.Entities; +using CodeCasa.CustomEntities.Core.InputSelect; + namespace CodeCasa.CustomEntities.Automation.People; @@ -39,19 +43,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..829639d 100644 --- a/src/CodeCasa.CustomEntities.Automation/People/PeopleEntities.cs +++ b/src/CodeCasa.CustomEntities.Automation/People/PeopleEntities.cs @@ -10,9 +10,17 @@ public class PeopleEntities(Jane jane, Jasper jasper) public IEnumerable All { get; } = [jane, jasper]; + public IObservable AnyAwakeWithCurrent() => + All.Select(e => e.PersonStateEqualsWithCurrent(PersonStates.Awake)) + .CombineLatest(x => x.Any()); + + public IObservable NoOneAsleepWithCurrent() => + All.Select(e => e.PersonStateEqualsWithCurrent(PersonStates.Asleep)) + .CombineLatest(x => !x.Any()); + public IObservable OnLastPersonToAsleepOrAwayObservable() { - return All.Select(p => p.CreateStateChangeObservable()).CombineLatest() + return All.Select(p => p.PersonStateChangeWithCurrent()).CombineLatest() .Select(tuple => { var prevAnyAwake = tuple.Any(change => change.Old == PersonStates.Awake); diff --git a/src/CodeCasa.CustomEntities.Automation/Sensors/MotionSensor.cs b/src/CodeCasa.CustomEntities.Automation/Sensors/MotionSensor.cs new file mode 100644 index 0000000..8439e03 --- /dev/null +++ b/src/CodeCasa.CustomEntities.Automation/Sensors/MotionSensor.cs @@ -0,0 +1,43 @@ +using CodeCasa.AutoGenerated; +using System.Reactive.Concurrency; +using System.Reactive.Linq; +using CodeCasa.NetDaemon.Extensions.Observables; + +namespace CodeCasa.CustomEntities.Automation.Sensors +{ + // todo: move to a common library + public abstract class MotionSensor( + IScheduler scheduler, + BinarySensorEntity motionOccupancySensor, + NumericSensorEntity motionIlluminanceLuxSensor) + { + public BinarySensorEntity MotionOccupancySensor { get; } = motionOccupancySensor; + public NumericSensorEntity MotionIlluminanceLuxSensor { get; } = motionIlluminanceLuxSensor; + + public IObservable CreateMotionObservable(double brightnessThreshold = 5, TimeSpan? offDelay = null) + { + offDelay ??= TimeSpan.FromSeconds(25); + + var motionLastXTime = + MotionOccupancySensor.PersistOnFor(offDelay.Value, scheduler); + + var brightnessLessThanX = MotionIlluminanceLuxSensor + .ToBooleanObservable(s => s.State <= brightnessThreshold); + + var triggered = false; + return motionLastXTime.CombineLatest(brightnessLessThanX, (motionTriggered, brightnessTriggered) => + { + if (motionTriggered && brightnessTriggered) + { + triggered = true; + } + else if (!motionTriggered) + { + triggered = false; + } + + return triggered; + }).DistinctUntilChanged(); + } + } +} diff --git a/src/CodeCasa.CustomEntities.Automation/Sensors/OfficeMotionSensor.cs b/src/CodeCasa.CustomEntities.Automation/Sensors/OfficeMotionSensor.cs new file mode 100644 index 0000000..f8008a1 --- /dev/null +++ b/src/CodeCasa.CustomEntities.Automation/Sensors/OfficeMotionSensor.cs @@ -0,0 +1,12 @@ +using System.Reactive.Concurrency; +using CodeCasa.AutoGenerated; + +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/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/OfficeWallModuleSingleRocker.cs b/src/CodeCasa.CustomEntities.Automation/Switches/OfficeWallModuleSingleRocker.cs new file mode 100644 index 0000000..43244de --- /dev/null +++ b/src/CodeCasa.CustomEntities.Automation/Switches/OfficeWallModuleSingleRocker.cs @@ -0,0 +1,8 @@ +using CodeCasa.CustomEntities.Core.Switches; +using NetDaemon.HassModel; + +namespace CodeCasa.CustomEntities.Automation.Switches +{ + public class OfficeWallModuleSingleRocker(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..7026862 --- /dev/null +++ b/src/CodeCasa.CustomEntities.Automation/Switches/SwitchDeviceNames.cs @@ -0,0 +1,11 @@ +namespace CodeCasa.CustomEntities.Automation.Switches +{ + internal class SwitchDeviceNames + { + // Hue wall switches + public const string OfficeWallSwitch = "Office Wall Switch"; + + // Hue dimmer switches + public const string OfficeDeskDimmerSwitch = "Office Desk Dimmer Switch"; + } +} diff --git a/src/CodeCasa.CustomEntities.Core/CodeCasa.CustomEntities.Core.csproj b/src/CodeCasa.CustomEntities.Core/CodeCasa.CustomEntities.Core.csproj index 9387a92..54708c9 100644 --- a/src/CodeCasa.CustomEntities.Core/CodeCasa.CustomEntities.Core.csproj +++ b/src/CodeCasa.CustomEntities.Core/CodeCasa.CustomEntities.Core.csproj @@ -7,6 +7,7 @@ + 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/Switches/HueDimmerSwitch.cs b/src/CodeCasa.CustomEntities.Core/Switches/HueDimmerSwitch.cs new file mode 100644 index 0000000..2dff813 --- /dev/null +++ b/src/CodeCasa.CustomEntities.Core/Switches/HueDimmerSwitch.cs @@ -0,0 +1,52 @@ +using System.Reactive; +using System.Reactive.Linq; +using CodeCasa.CustomEntities.Core.Extensions; +using NetDaemon.Devices.Abstractions; +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/IkeaStyrbarRemoteControl.cs b/src/CodeCasa.CustomEntities.Core/Switches/IkeaStyrbarRemoteControl.cs new file mode 100644 index 0000000..2e0b8fb --- /dev/null +++ b/src/CodeCasa.CustomEntities.Core/Switches/IkeaStyrbarRemoteControl.cs @@ -0,0 +1,89 @@ +using System.Reactive; +using System.Reactive.Linq; +using CodeCasa.CustomEntities.Core.Extensions; +using NetDaemon.Devices.Abstractions; +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.NetDaemon.Utilities/CodeCasa.NetDaemon.Utilities.csproj b/src/CodeCasa.NetDaemon.Utilities/CodeCasa.NetDaemon.Utilities.csproj index e770439..f10839a 100644 --- a/src/CodeCasa.NetDaemon.Utilities/CodeCasa.NetDaemon.Utilities.csproj +++ b/src/CodeCasa.NetDaemon.Utilities/CodeCasa.NetDaemon.Utilities.csproj @@ -7,7 +7,7 @@ - + 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.Notifications.Light/Config/LightNotificationConfig.cs b/src/CodeCasa.Notifications.Light/Config/LightNotificationConfig.cs new file mode 100644 index 0000000..ebcff6f --- /dev/null +++ b/src/CodeCasa.Notifications.Light/Config/LightNotificationConfig.cs @@ -0,0 +1,24 @@ +using CodeCasa.AutomationPipelines; +using CodeCasa.AutomationPipelines.Lights.Context; +using CodeCasa.Lights; + +namespace CodeCasa.Notifications.Light.Config; + +public class LightNotificationConfig +{ + public LightNotificationOptions Options { get; } + public Type? NodeType { get; } + public Func>? NodeFactory { get; } + + internal LightNotificationConfig(LightNotificationOptions options, Type nodeType) + { + Options = options; + NodeType = nodeType; + } + + internal LightNotificationConfig(LightNotificationOptions options, Func> nodeFactory) + { + Options = options; + NodeFactory = nodeFactory; + } +} \ 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 + + + + From bf3428147caac17969b47e810e1888afa85492bc Mon Sep 17 00:00:00 2001 From: Jasper Date: Thu, 1 Jan 2026 15:16:31 +0100 Subject: [PATCH 02/13] Fixed build. --- .../Apps/Lights/BackyardLights/BackyardLightsApp.cs | 6 +++--- .../Observables/BackyardLightsEnergySaving.cs | 2 +- .../Apps/Lights/OfficeLights/OfficeLightsApp.cs | 2 +- .../Apps/Lights/OfficeLights/TestNode.cs | 2 +- src/CodeCasa.Automations/Apps/Notifications/AlarmLight.cs | 2 +- .../Apps/Notifications/DashboardDemoNotifications.cs | 2 +- .../Apps/Notifications/OfficeLightsNotifications.cs | 4 ++-- .../Apps/Notifications/PhoneDemoNotifications.cs | 2 +- .../Extensions/ServiceCollectionExtensions.cs | 2 +- .../CodeCasa.CustomEntities.Automation.csproj | 2 ++ .../Extensions/ServiceCollectionExtensions.cs | 2 +- .../People/CompositePersonEntity.cs | 3 --- .../TypeSafeInputSelectEntityExtensionMethods.cs | 4 +--- 13 files changed, 16 insertions(+), 19 deletions(-) diff --git a/src/CodeCasa.Automations/Apps/Lights/BackyardLights/BackyardLightsApp.cs b/src/CodeCasa.Automations/Apps/Lights/BackyardLights/BackyardLightsApp.cs index a8b4b5f..d2f7197 100644 --- a/src/CodeCasa.Automations/Apps/Lights/BackyardLights/BackyardLightsApp.cs +++ b/src/CodeCasa.Automations/Apps/Lights/BackyardLights/BackyardLightsApp.cs @@ -3,8 +3,8 @@ using CodeCasa.AutomationPipelines.Lights.Pipeline; using CodeCasa.Automations.Apps.Lights.BackyardLights.Observables; using CodeCasa.Lights; -using CodeCasa.Notifications.Light; -using CodeCasa.Notifications.Light.Extensions; +using CodeCasa.Notifications.Lights; +using CodeCasa.Notifications.Lights.Extensions; using NetDaemon.AppModel; namespace CodeCasa.Automations.Apps.Lights.BackyardLights; @@ -57,7 +57,7 @@ public BackyardLightsApp( builder .When(backyardLightsRoutineFactory.Create(offsetSeconds), LightParameters.Relax) .TurnOffWhen() - .SupportNotifications(lightNotificationManager); + .AddNotifications(lightNotificationManager); }); } } diff --git a/src/CodeCasa.Automations/Apps/Lights/BackyardLights/Observables/BackyardLightsEnergySaving.cs b/src/CodeCasa.Automations/Apps/Lights/BackyardLights/Observables/BackyardLightsEnergySaving.cs index 74856c8..579380e 100644 --- a/src/CodeCasa.Automations/Apps/Lights/BackyardLights/Observables/BackyardLightsEnergySaving.cs +++ b/src/CodeCasa.Automations/Apps/Lights/BackyardLights/Observables/BackyardLightsEnergySaving.cs @@ -1,6 +1,6 @@ using System.Reactive.Concurrency; using CodeCasa.AutoGenerated; -using NetDaemon.Extensions.Observables; +using CodeCasa.NetDaemon.Extensions.Observables; using Occurify; using Occurify.Reactive.Extensions; using Occurify.TimeZones; diff --git a/src/CodeCasa.Automations/Apps/Lights/OfficeLights/OfficeLightsApp.cs b/src/CodeCasa.Automations/Apps/Lights/OfficeLights/OfficeLightsApp.cs index 78ef9ab..a27d7af 100644 --- a/src/CodeCasa.Automations/Apps/Lights/OfficeLights/OfficeLightsApp.cs +++ b/src/CodeCasa.Automations/Apps/Lights/OfficeLights/OfficeLightsApp.cs @@ -4,12 +4,12 @@ using CodeCasa.CustomEntities.Automation.Sensors; using CodeCasa.CustomEntities.Automation.Switches; using CodeCasa.Lights; -using CodeCasa.Notifications.Light; using Microsoft.Extensions.DependencyInjection; using NetDaemon.AppModel; using System.Drawing; using System.Reactive.Concurrency; using CodeCasa.AutomationPipelines.Lights.NetDaemon.Extensions; +using CodeCasa.Notifications.Lights; namespace CodeCasa.Automations.Apps.Lights.OfficeLights; diff --git a/src/CodeCasa.Automations/Apps/Lights/OfficeLights/TestNode.cs b/src/CodeCasa.Automations/Apps/Lights/OfficeLights/TestNode.cs index f05933e..75131c4 100644 --- a/src/CodeCasa.Automations/Apps/Lights/OfficeLights/TestNode.cs +++ b/src/CodeCasa.Automations/Apps/Lights/OfficeLights/TestNode.cs @@ -11,7 +11,7 @@ public TestNode(ILightPipelineContext context, IScheduler scheduler) { scheduler.SchedulePeriodic( TimeSpan.FromSeconds(2), - () => Console.WriteLine($"Tick at {DateTime.Now} for {context.LightEntity.Id}") + () => Console.WriteLine($"Tick at {DateTime.Now} for {context.Light.Id}") ); } } diff --git a/src/CodeCasa.Automations/Apps/Notifications/AlarmLight.cs b/src/CodeCasa.Automations/Apps/Notifications/AlarmLight.cs index 0e55de1..142c929 100644 --- a/src/CodeCasa.Automations/Apps/Notifications/AlarmLight.cs +++ b/src/CodeCasa.Automations/Apps/Notifications/AlarmLight.cs @@ -1,6 +1,6 @@ using System.Reactive.Concurrency; using CodeCasa.Automations.Nodes; -using CodeCasa.Notifications.Light; +using CodeCasa.Notifications.Lights; using NetDaemon.AppModel; namespace CodeCasa.Automations.Apps.Notifications; diff --git a/src/CodeCasa.Automations/Apps/Notifications/DashboardDemoNotifications.cs b/src/CodeCasa.Automations/Apps/Notifications/DashboardDemoNotifications.cs index 24d12e6..8f5d5d6 100644 --- a/src/CodeCasa.Automations/Apps/Notifications/DashboardDemoNotifications.cs +++ b/src/CodeCasa.Automations/Apps/Notifications/DashboardDemoNotifications.cs @@ -1,9 +1,9 @@ using CodeCasa.CustomEntities.Automation.Notifications.Dashboards; using CodeCasa.CustomEntities.Core.Events; using CodeCasa.NetDaemon.Utilities.Extensions; +using CodeCasa.Notifications.InputSelect.NetDaemon; using NetDaemon.AppModel; using NetDaemon.HassModel; -using NetDaemon.Notifications.InputSelect; namespace CodeCasa.Automations.Apps.Notifications; diff --git a/src/CodeCasa.Automations/Apps/Notifications/OfficeLightsNotifications.cs b/src/CodeCasa.Automations/Apps/Notifications/OfficeLightsNotifications.cs index 1e7edf2..0c6de29 100644 --- a/src/CodeCasa.Automations/Apps/Notifications/OfficeLightsNotifications.cs +++ b/src/CodeCasa.Automations/Apps/Notifications/OfficeLightsNotifications.cs @@ -1,10 +1,10 @@ using CodeCasa.AutoGenerated; using CodeCasa.AutoGenerated.Extensions; using NetDaemon.AppModel; -using NetDaemon.Extensions.Observables; using CodeCasa.CustomEntities.Automation.Notifications.Dashboards; using CodeCasa.CustomEntities.Automation.People; -using NetDaemon.Notifications.Phone.Config; +using CodeCasa.NetDaemon.Extensions.Observables; +using CodeCasa.Notifications.Phone.NetDaemon.Config; using Reactive.Boolean; namespace CodeCasa.Automations.Apps.Notifications; diff --git a/src/CodeCasa.Automations/Apps/Notifications/PhoneDemoNotifications.cs b/src/CodeCasa.Automations/Apps/Notifications/PhoneDemoNotifications.cs index 740df49..97b2146 100644 --- a/src/CodeCasa.Automations/Apps/Notifications/PhoneDemoNotifications.cs +++ b/src/CodeCasa.Automations/Apps/Notifications/PhoneDemoNotifications.cs @@ -1,10 +1,10 @@ using NetDaemon.AppModel; using NetDaemon.HassModel; -using NetDaemon.Notifications.Phone.Config; using System.Drawing; using CodeCasa.CustomEntities.Automation.Notifications.Phones; using CodeCasa.CustomEntities.Core.Events; using CodeCasa.NetDaemon.Utilities.Extensions; +using CodeCasa.Notifications.Phone.NetDaemon.Config; namespace CodeCasa.Automations.Apps.Notifications; diff --git a/src/CodeCasa.Automations/Extensions/ServiceCollectionExtensions.cs b/src/CodeCasa.Automations/Extensions/ServiceCollectionExtensions.cs index 0bf21bf..9201156 100644 --- a/src/CodeCasa.Automations/Extensions/ServiceCollectionExtensions.cs +++ b/src/CodeCasa.Automations/Extensions/ServiceCollectionExtensions.cs @@ -3,9 +3,9 @@ using CodeCasa.AutomationPipelines.Lights.Extensions; using CodeCasa.Automations.Apps.Lights.BackyardLights.Observables; using CodeCasa.CustomEntities.Automation.Extensions; +using CodeCasa.Notifications.InputSelect.NetDaemon.Extensions; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; -using NetDaemon.Notifications.InputSelect.Extensions; using Occurify.Astro; namespace CodeCasa.Automations.Extensions; diff --git a/src/CodeCasa.CustomEntities.Automation/CodeCasa.CustomEntities.Automation.csproj b/src/CodeCasa.CustomEntities.Automation/CodeCasa.CustomEntities.Automation.csproj index 9ac2e82..9d3d107 100644 --- a/src/CodeCasa.CustomEntities.Automation/CodeCasa.CustomEntities.Automation.csproj +++ b/src/CodeCasa.CustomEntities.Automation/CodeCasa.CustomEntities.Automation.csproj @@ -7,8 +7,10 @@ + + diff --git a/src/CodeCasa.CustomEntities.Automation/Extensions/ServiceCollectionExtensions.cs b/src/CodeCasa.CustomEntities.Automation/Extensions/ServiceCollectionExtensions.cs index f237a1e..316308e 100644 --- a/src/CodeCasa.CustomEntities.Automation/Extensions/ServiceCollectionExtensions.cs +++ b/src/CodeCasa.CustomEntities.Automation/Extensions/ServiceCollectionExtensions.cs @@ -4,7 +4,7 @@ using CodeCasa.CustomEntities.Automation.Sensors; using CodeCasa.CustomEntities.Automation.Switches; using CodeCasa.CustomEntities.Core.Extensions; -using CodeCasa.Notifications.Light.Extensions; +using CodeCasa.Notifications.Lights.Extensions; using Microsoft.Extensions.DependencyInjection; namespace CodeCasa.CustomEntities.Automation.Extensions; diff --git a/src/CodeCasa.CustomEntities.Automation/People/CompositePersonEntity.cs b/src/CodeCasa.CustomEntities.Automation/People/CompositePersonEntity.cs index 6df8faf..d5f260d 100644 --- a/src/CodeCasa.CustomEntities.Automation/People/CompositePersonEntity.cs +++ b/src/CodeCasa.CustomEntities.Automation/People/CompositePersonEntity.cs @@ -4,9 +4,6 @@ using CodeCasa.NetDaemon.Extensions.Observables; using CodeCasa.Notifications.InputSelect.NetDaemon.Interact; using CodeCasa.Notifications.Phone.NetDaemon; -using System.Reactive.Linq; -using NetDaemon.HassModel.Entities; -using CodeCasa.CustomEntities.Core.InputSelect; namespace CodeCasa.CustomEntities.Automation.People; 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 { From ce6811c83371765697813d59bd0f02348de9f371 Mon Sep 17 00:00:00 2001 From: Jasper Date: Thu, 1 Jan 2026 20:28:07 +0100 Subject: [PATCH 03/13] General cleanup --- .../BackyardLights/BackyardLightsApp.cs | 5 ++-- .../Lights/OfficeLights/OfficeLightsApp.cs | 24 ++++++++++------- .../Apps/Lights/OfficeLights/TestNode.cs | 18 ------------- .../People/TurnOffLightsWhenNoOneAwake.cs | 26 ------------------- .../People/PeopleEntities.cs | 7 ++--- .../Sensors/OfficeMotionSensor.cs | 1 + .../CodeCasa.CustomEntities.Core.csproj | 3 ++- .../Sensors/MotionSensor.cs | 6 ++--- .../Switches/HueDimmerSwitch.cs | 2 +- .../Switches/IkeaStyrbarRemoteControl.cs | 2 +- .../CodeCasa.NetDaemon.Utilities.csproj | 2 +- 11 files changed, 30 insertions(+), 66 deletions(-) delete mode 100644 src/CodeCasa.Automations/Apps/Lights/OfficeLights/TestNode.cs delete mode 100644 src/CodeCasa.Automations/Apps/People/TurnOffLightsWhenNoOneAwake.cs rename src/{CodeCasa.CustomEntities.Automation => CodeCasa.CustomEntities.Core}/Sensors/MotionSensor.cs (92%) diff --git a/src/CodeCasa.Automations/Apps/Lights/BackyardLights/BackyardLightsApp.cs b/src/CodeCasa.Automations/Apps/Lights/BackyardLights/BackyardLightsApp.cs index d2f7197..f72d6e5 100644 --- a/src/CodeCasa.Automations/Apps/Lights/BackyardLights/BackyardLightsApp.cs +++ b/src/CodeCasa.Automations/Apps/Lights/BackyardLights/BackyardLightsApp.cs @@ -35,7 +35,8 @@ public BackyardLightsApp( { b .TurnOnWhen(backyardLightsRoutineFactory.Create(TimeSpan.FromSeconds(index))) - .TurnOffWhen(); + .TurnOffWhen() + .AddNotifications(lightNotificationManager); }); } @@ -55,7 +56,7 @@ public BackyardLightsApp( .SetupLightPipeline(light, builder => { builder - .When(backyardLightsRoutineFactory.Create(offsetSeconds), LightParameters.Relax) + .When(backyardLightsRoutineFactory.Create(offsetSeconds), LightParameters.Relax.AsTransitionInSeconds(transitionSeconds)) .TurnOffWhen() .AddNotifications(lightNotificationManager); }); diff --git a/src/CodeCasa.Automations/Apps/Lights/OfficeLights/OfficeLightsApp.cs b/src/CodeCasa.Automations/Apps/Lights/OfficeLights/OfficeLightsApp.cs index a27d7af..8b3f8f0 100644 --- a/src/CodeCasa.Automations/Apps/Lights/OfficeLights/OfficeLightsApp.cs +++ b/src/CodeCasa.Automations/Apps/Lights/OfficeLights/OfficeLightsApp.cs @@ -10,6 +10,7 @@ using System.Reactive.Concurrency; using CodeCasa.AutomationPipelines.Lights.NetDaemon.Extensions; using CodeCasa.Notifications.Lights; +using CodeCasa.Notifications.Lights.Extensions; namespace CodeCasa.Automations.Apps.Lights.OfficeLights; @@ -49,17 +50,20 @@ public OfficeLightsApp( c2.Add(context => new ColorTransitionNode( context.ServiceProvider.GetRequiredService(), TimeSpan.FromSeconds(3), - Color.Cyan, Color.Yellow, Color.Magenta)); + Color.LawnGreen, Color.Red, Color.Blue)); + }) + .ForLight(lightEntities.OfficeLightColor3.EntityId, c2 => + { + c2.Add(context => new ColorTransitionNode( + context.ServiceProvider.GetRequiredService(), + TimeSpan.FromSeconds(3), + Color.Blue, Color.LawnGreen, Color.Red)); }); - }); - //.AddToggle(officeDimmerSwitch.ScenePressed, c => c.Add()) - //.AddToggle(officeDimmerSwitch.ScenePressed, c => c.Add(ctx => new TestNode(ctx, ctx.ServiceProvider.GetRequiredService()))) - //.AddToggle(officeDimmerSwitch.OnOffPressed, c => c.Add()) - //.TurnOffWhen(people.OnLastPersonToAsleepOrAwayObservable().Where(b => b) - // .Select(_ => Unit.Default)); - }); - //.AddNode(); - //.SupportNotifications(lightNotificationManager); + }) + .TurnOffWhen(people.OnLastPersonToAsleepOrAwayObservable()) + .AddReactiveDimmer(officeDimmerSwitch); + }) + .AddNotifications(lightNotificationManager); }); } } \ No newline at end of file diff --git a/src/CodeCasa.Automations/Apps/Lights/OfficeLights/TestNode.cs b/src/CodeCasa.Automations/Apps/Lights/OfficeLights/TestNode.cs deleted file mode 100644 index 75131c4..0000000 --- a/src/CodeCasa.Automations/Apps/Lights/OfficeLights/TestNode.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System.Reactive.Concurrency; -using CodeCasa.AutomationPipelines; -using CodeCasa.AutomationPipelines.Lights.Context; -using CodeCasa.Lights; - -namespace CodeCasa.Automations.Apps.Lights.OfficeLights -{ - internal class TestNode : PipelineNode - { - public TestNode(ILightPipelineContext context, IScheduler scheduler) - { - scheduler.SchedulePeriodic( - TimeSpan.FromSeconds(2), - () => Console.WriteLine($"Tick at {DateTime.Now} for {context.Light.Id}") - ); - } - } -} 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.CustomEntities.Automation/People/PeopleEntities.cs b/src/CodeCasa.CustomEntities.Automation/People/PeopleEntities.cs index 829639d..948c8fd 100644 --- a/src/CodeCasa.CustomEntities.Automation/People/PeopleEntities.cs +++ b/src/CodeCasa.CustomEntities.Automation/People/PeopleEntities.cs @@ -1,4 +1,5 @@  +using System.Reactive; using System.Reactive.Linq; namespace CodeCasa.CustomEntities.Automation.People; @@ -18,14 +19,14 @@ public IObservable NoOneAsleepWithCurrent() => All.Select(e => e.PersonStateEqualsWithCurrent(PersonStates.Asleep)) .CombineLatest(x => !x.Any()); - public IObservable OnLastPersonToAsleepOrAwayObservable() + public IObservable OnLastPersonToAsleepOrAwayObservable() { return All.Select(p => p.PersonStateChangeWithCurrent()).CombineLatest() - .Select(tuple => + .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/OfficeMotionSensor.cs b/src/CodeCasa.CustomEntities.Automation/Sensors/OfficeMotionSensor.cs index f8008a1..a347060 100644 --- a/src/CodeCasa.CustomEntities.Automation/Sensors/OfficeMotionSensor.cs +++ b/src/CodeCasa.CustomEntities.Automation/Sensors/OfficeMotionSensor.cs @@ -1,5 +1,6 @@ using System.Reactive.Concurrency; using CodeCasa.AutoGenerated; +using CodeCasa.CustomEntities.Core.Sensors; namespace CodeCasa.CustomEntities.Automation.Sensors { diff --git a/src/CodeCasa.CustomEntities.Core/CodeCasa.CustomEntities.Core.csproj b/src/CodeCasa.CustomEntities.Core/CodeCasa.CustomEntities.Core.csproj index 54708c9..a2571f9 100644 --- a/src/CodeCasa.CustomEntities.Core/CodeCasa.CustomEntities.Core.csproj +++ b/src/CodeCasa.CustomEntities.Core/CodeCasa.CustomEntities.Core.csproj @@ -7,7 +7,8 @@ - + + diff --git a/src/CodeCasa.CustomEntities.Automation/Sensors/MotionSensor.cs b/src/CodeCasa.CustomEntities.Core/Sensors/MotionSensor.cs similarity index 92% rename from src/CodeCasa.CustomEntities.Automation/Sensors/MotionSensor.cs rename to src/CodeCasa.CustomEntities.Core/Sensors/MotionSensor.cs index 8439e03..633a92c 100644 --- a/src/CodeCasa.CustomEntities.Automation/Sensors/MotionSensor.cs +++ b/src/CodeCasa.CustomEntities.Core/Sensors/MotionSensor.cs @@ -1,9 +1,9 @@ -using CodeCasa.AutoGenerated; -using System.Reactive.Concurrency; +using System.Reactive.Concurrency; using System.Reactive.Linq; +using CodeCasa.AutoGenerated; using CodeCasa.NetDaemon.Extensions.Observables; -namespace CodeCasa.CustomEntities.Automation.Sensors +namespace CodeCasa.CustomEntities.Core.Sensors { // todo: move to a common library public abstract class MotionSensor( diff --git a/src/CodeCasa.CustomEntities.Core/Switches/HueDimmerSwitch.cs b/src/CodeCasa.CustomEntities.Core/Switches/HueDimmerSwitch.cs index 2dff813..1aaca2e 100644 --- a/src/CodeCasa.CustomEntities.Core/Switches/HueDimmerSwitch.cs +++ b/src/CodeCasa.CustomEntities.Core/Switches/HueDimmerSwitch.cs @@ -1,7 +1,7 @@ using System.Reactive; using System.Reactive.Linq; +using CodeCasa.Abstractions; using CodeCasa.CustomEntities.Core.Extensions; -using NetDaemon.Devices.Abstractions; using NetDaemon.HassModel; namespace CodeCasa.CustomEntities.Core.Switches diff --git a/src/CodeCasa.CustomEntities.Core/Switches/IkeaStyrbarRemoteControl.cs b/src/CodeCasa.CustomEntities.Core/Switches/IkeaStyrbarRemoteControl.cs index 2e0b8fb..d3d7d12 100644 --- a/src/CodeCasa.CustomEntities.Core/Switches/IkeaStyrbarRemoteControl.cs +++ b/src/CodeCasa.CustomEntities.Core/Switches/IkeaStyrbarRemoteControl.cs @@ -1,7 +1,7 @@ using System.Reactive; using System.Reactive.Linq; +using CodeCasa.Abstractions; using CodeCasa.CustomEntities.Core.Extensions; -using NetDaemon.Devices.Abstractions; using NetDaemon.HassModel; namespace CodeCasa.CustomEntities.Core.Switches diff --git a/src/CodeCasa.NetDaemon.Utilities/CodeCasa.NetDaemon.Utilities.csproj b/src/CodeCasa.NetDaemon.Utilities/CodeCasa.NetDaemon.Utilities.csproj index f10839a..e770439 100644 --- a/src/CodeCasa.NetDaemon.Utilities/CodeCasa.NetDaemon.Utilities.csproj +++ b/src/CodeCasa.NetDaemon.Utilities/CodeCasa.NetDaemon.Utilities.csproj @@ -7,7 +7,7 @@ - + From b3ea628af5cd4c9635a7bc34f8b7e34203a72b56 Mon Sep 17 00:00:00 2001 From: Jasper Date: Fri, 2 Jan 2026 14:15:31 +0100 Subject: [PATCH 04/13] Updated references. --- src/CodeCasa.Automations/CodeCasa.Automations.csproj | 7 +++---- .../CodeCasa.CustomEntities.Automation.csproj | 10 +++++----- .../CodeCasa.CustomEntities.Core.csproj | 4 ++-- src/CodeCasa.Dashboard/CodeCasa.Dashboard.csproj | 6 +++--- .../CodeCasa.NetDaemon.Utilities.csproj | 2 +- 5 files changed, 14 insertions(+), 15 deletions(-) diff --git a/src/CodeCasa.Automations/CodeCasa.Automations.csproj b/src/CodeCasa.Automations/CodeCasa.Automations.csproj index 35dc51c..70d0955 100644 --- a/src/CodeCasa.Automations/CodeCasa.Automations.csproj +++ b/src/CodeCasa.Automations/CodeCasa.Automations.csproj @@ -23,10 +23,9 @@ - - - - + + + diff --git a/src/CodeCasa.CustomEntities.Automation/CodeCasa.CustomEntities.Automation.csproj b/src/CodeCasa.CustomEntities.Automation/CodeCasa.CustomEntities.Automation.csproj index 9d3d107..5341237 100644 --- a/src/CodeCasa.CustomEntities.Automation/CodeCasa.CustomEntities.Automation.csproj +++ b/src/CodeCasa.CustomEntities.Automation/CodeCasa.CustomEntities.Automation.csproj @@ -7,11 +7,11 @@ - - - - - + + + + + diff --git a/src/CodeCasa.CustomEntities.Core/CodeCasa.CustomEntities.Core.csproj b/src/CodeCasa.CustomEntities.Core/CodeCasa.CustomEntities.Core.csproj index a2571f9..67fb567 100644 --- a/src/CodeCasa.CustomEntities.Core/CodeCasa.CustomEntities.Core.csproj +++ b/src/CodeCasa.CustomEntities.Core/CodeCasa.CustomEntities.Core.csproj @@ -7,8 +7,8 @@ - - + + diff --git a/src/CodeCasa.Dashboard/CodeCasa.Dashboard.csproj b/src/CodeCasa.Dashboard/CodeCasa.Dashboard.csproj index 8d382f1..72d44d3 100644 --- a/src/CodeCasa.Dashboard/CodeCasa.Dashboard.csproj +++ b/src/CodeCasa.Dashboard/CodeCasa.Dashboard.csproj @@ -7,9 +7,9 @@ - - - + + + diff --git a/src/CodeCasa.NetDaemon.Utilities/CodeCasa.NetDaemon.Utilities.csproj b/src/CodeCasa.NetDaemon.Utilities/CodeCasa.NetDaemon.Utilities.csproj index e770439..55c8073 100644 --- a/src/CodeCasa.NetDaemon.Utilities/CodeCasa.NetDaemon.Utilities.csproj +++ b/src/CodeCasa.NetDaemon.Utilities/CodeCasa.NetDaemon.Utilities.csproj @@ -7,7 +7,7 @@ - + From e9f7089432cb132635f5a46361b9b59a9f05e528 Mon Sep 17 00:00:00 2001 From: Jasper Date: Fri, 2 Jan 2026 14:47:57 +0100 Subject: [PATCH 05/13] Allowing local debug support. --- Directory.Build.props | 11 +++++++++++ Directory.Build.targets | 31 +++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+) create mode 100644 Directory.Build.props create mode 100644 Directory.Build.targets diff --git a/Directory.Build.props b/Directory.Build.props new file mode 100644 index 0000000..d51d638 --- /dev/null +++ b/Directory.Build.props @@ -0,0 +1,11 @@ + + + + false + + \ No newline at end of file diff --git a/Directory.Build.targets b/Directory.Build.targets new file mode 100644 index 0000000..53051c0 --- /dev/null +++ b/Directory.Build.targets @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file From c61d924ea80e7bdc639d8ee8f52aab016741548a Mon Sep 17 00:00:00 2001 From: Jasper Date: Fri, 2 Jan 2026 15:08:51 +0100 Subject: [PATCH 06/13] Included CodeCasa.Showcase.withLocalCodeCasa.sln --- CodeCasa.Showcase.withLocalCodeCasa.sln | 161 ++++++++++++++++++++++++ Directory.Build.props | 11 -- Directory.Build.targets | 4 + 3 files changed, 165 insertions(+), 11 deletions(-) create mode 100644 CodeCasa.Showcase.withLocalCodeCasa.sln delete mode 100644 Directory.Build.props diff --git a/CodeCasa.Showcase.withLocalCodeCasa.sln b/CodeCasa.Showcase.withLocalCodeCasa.sln new file mode 100644 index 0000000..2aac5dd --- /dev/null +++ b/CodeCasa.Showcase.withLocalCodeCasa.sln @@ -0,0 +1,161 @@ + +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 +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 + 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} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {C77A270A-D381-419D-AEA1-1592AA2B4B6E} + EndGlobalSection +EndGlobal diff --git a/Directory.Build.props b/Directory.Build.props deleted file mode 100644 index d51d638..0000000 --- a/Directory.Build.props +++ /dev/null @@ -1,11 +0,0 @@ - - - - false - - \ No newline at end of file diff --git a/Directory.Build.targets b/Directory.Build.targets index 53051c0..fdfae41 100644 --- a/Directory.Build.targets +++ b/Directory.Build.targets @@ -1,4 +1,8 @@ + + true + + From 42fb7dc6c03381741a1e69638672132e67f67dea Mon Sep 17 00:00:00 2001 From: Jasper Date: Fri, 2 Jan 2026 15:19:39 +0100 Subject: [PATCH 07/13] Updated readme for local debugging --- README.md | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) 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 From 81c7ae954d543122d868dc10631e215e36c602e6 Mon Sep 17 00:00:00 2001 From: Jasper Date: Mon, 5 Jan 2026 17:17:01 +0100 Subject: [PATCH 08/13] Using latest packages. --- src/CodeCasa.Automations/CodeCasa.Automations.csproj | 6 +++--- .../CodeCasa.CustomEntities.Automation.csproj | 10 +++++----- .../CodeCasa.CustomEntities.Core.csproj | 4 ++-- src/CodeCasa.Dashboard/CodeCasa.Dashboard.csproj | 6 +++--- .../CodeCasa.NetDaemon.Utilities.csproj | 2 +- 5 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/CodeCasa.Automations/CodeCasa.Automations.csproj b/src/CodeCasa.Automations/CodeCasa.Automations.csproj index 70d0955..253e736 100644 --- a/src/CodeCasa.Automations/CodeCasa.Automations.csproj +++ b/src/CodeCasa.Automations/CodeCasa.Automations.csproj @@ -23,9 +23,9 @@ - - - + + + diff --git a/src/CodeCasa.CustomEntities.Automation/CodeCasa.CustomEntities.Automation.csproj b/src/CodeCasa.CustomEntities.Automation/CodeCasa.CustomEntities.Automation.csproj index 5341237..76b398b 100644 --- a/src/CodeCasa.CustomEntities.Automation/CodeCasa.CustomEntities.Automation.csproj +++ b/src/CodeCasa.CustomEntities.Automation/CodeCasa.CustomEntities.Automation.csproj @@ -7,11 +7,11 @@ - - - - - + + + + + diff --git a/src/CodeCasa.CustomEntities.Core/CodeCasa.CustomEntities.Core.csproj b/src/CodeCasa.CustomEntities.Core/CodeCasa.CustomEntities.Core.csproj index 67fb567..e4974b3 100644 --- a/src/CodeCasa.CustomEntities.Core/CodeCasa.CustomEntities.Core.csproj +++ b/src/CodeCasa.CustomEntities.Core/CodeCasa.CustomEntities.Core.csproj @@ -7,8 +7,8 @@ - - + + diff --git a/src/CodeCasa.Dashboard/CodeCasa.Dashboard.csproj b/src/CodeCasa.Dashboard/CodeCasa.Dashboard.csproj index 72d44d3..6d7cf04 100644 --- a/src/CodeCasa.Dashboard/CodeCasa.Dashboard.csproj +++ b/src/CodeCasa.Dashboard/CodeCasa.Dashboard.csproj @@ -7,9 +7,9 @@ - - - + + + diff --git a/src/CodeCasa.NetDaemon.Utilities/CodeCasa.NetDaemon.Utilities.csproj b/src/CodeCasa.NetDaemon.Utilities/CodeCasa.NetDaemon.Utilities.csproj index 55c8073..e49d458 100644 --- a/src/CodeCasa.NetDaemon.Utilities/CodeCasa.NetDaemon.Utilities.csproj +++ b/src/CodeCasa.NetDaemon.Utilities/CodeCasa.NetDaemon.Utilities.csproj @@ -7,7 +7,7 @@ - + From 7f8da95ac74f20890a74ab905b5e9cf62fa65cbd Mon Sep 17 00:00:00 2001 From: Jasper Date: Fri, 20 Mar 2026 15:43:26 +0100 Subject: [PATCH 09/13] Continue work on automation pipeline examples. --- CodeCasa.Showcase.withLocalCodeCasa.sln | 14 +++ Directory.Build.targets | 4 + .../BinarySensorEntities.cs | 4 + .../CodeCasa.AutoGenerated.csproj | 4 +- src/CodeCasa.AutoGenerated/LightEntities.cs | 10 ++- src/CodeCasa.AutoGenerated/SensorEntities.cs | 5 ++ .../Apps/Lights/AtticLights/AtticLightsApp.cs | 35 ++++++++ .../BackyardLights/BackyardLightsApp.cs | 3 +- .../BathroomLightTimelineNode.cs | 29 ++++++ .../BathroomLights/BathroomLightsApp.cs | 88 +++++++++++++++++++ .../Lights/OfficeLights/OfficeLightsApp.cs | 28 +++--- .../Apps/Notifications/AlarmLight.cs | 40 ++++++++- .../CodeCasa.Automations.csproj | 16 ++-- src/CodeCasa.Automations/Nodes/AlarmNode.cs | 10 ++- src/CodeCasa.Automations/appsettings.json | 2 +- .../CodeCasa.CustomEntities.Automation.csproj | 13 +-- ...itionReactiveNodeConfiguratorExtensions.cs | 26 ++++++ .../Extensions/ServiceCollectionExtensions.cs | 7 +- .../People/PeopleEntities.cs | 9 +- .../Sensors/BathroomMotionSensor.cs | 13 +++ .../Sensors/OfficeMotionSensor.cs | 2 +- .../UpstairsHallwayAtticMotionSensor.cs | 13 +++ .../Switches/AtticDimmerSwitch.cs | 8 ++ .../Switches/AtticWallSwitch.cs | 8 ++ .../Switches/BathroomWallSwitch.cs | 8 ++ ...uleSingleRocker.cs => OfficeWallSwitch.cs} | 2 +- .../Switches/SwitchDeviceNames.cs | 3 + .../CodeCasa.CustomEntities.Core.csproj | 6 +- .../Sensors/MotionSensor.cs | 43 --------- .../CodeCasa.Dashboard.csproj | 11 +-- src/CodeCasa.Dashboard/Components/App.razor | 2 + .../CodeCasa.NetDaemon.Utilities.csproj | 4 +- 32 files changed, 377 insertions(+), 93 deletions(-) create mode 100644 src/CodeCasa.Automations/Apps/Lights/AtticLights/AtticLightsApp.cs create mode 100644 src/CodeCasa.Automations/Apps/Lights/BathroomLights/BathroomLightTimelineNode.cs create mode 100644 src/CodeCasa.Automations/Apps/Lights/BathroomLights/BathroomLightsApp.cs create mode 100644 src/CodeCasa.CustomEntities.Automation/Extensions/LightTransitionReactiveNodeConfiguratorExtensions.cs create mode 100644 src/CodeCasa.CustomEntities.Automation/Sensors/BathroomMotionSensor.cs create mode 100644 src/CodeCasa.CustomEntities.Automation/Sensors/UpstairsHallwayAtticMotionSensor.cs create mode 100644 src/CodeCasa.CustomEntities.Automation/Switches/AtticDimmerSwitch.cs create mode 100644 src/CodeCasa.CustomEntities.Automation/Switches/AtticWallSwitch.cs create mode 100644 src/CodeCasa.CustomEntities.Automation/Switches/BathroomWallSwitch.cs rename src/CodeCasa.CustomEntities.Automation/Switches/{OfficeWallModuleSingleRocker.cs => OfficeWallSwitch.cs} (74%) delete mode 100644 src/CodeCasa.CustomEntities.Core/Sensors/MotionSensor.cs diff --git a/CodeCasa.Showcase.withLocalCodeCasa.sln b/CodeCasa.Showcase.withLocalCodeCasa.sln index 2aac5dd..d752474 100644 --- a/CodeCasa.Showcase.withLocalCodeCasa.sln +++ b/CodeCasa.Showcase.withLocalCodeCasa.sln @@ -47,6 +47,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CodeCasa.Notifications.Ligh 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 Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -133,6 +137,14 @@ Global {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 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -154,6 +166,8 @@ Global {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} diff --git a/Directory.Build.targets b/Directory.Build.targets index fdfae41..6c48631 100644 --- a/Directory.Build.targets +++ b/Directory.Build.targets @@ -9,11 +9,13 @@ + + @@ -23,11 +25,13 @@ + + diff --git a/src/CodeCasa.AutoGenerated/BinarySensorEntities.cs b/src/CodeCasa.AutoGenerated/BinarySensorEntities.cs index cee6c9e..6e8ec33 100644 --- a/src/CodeCasa.AutoGenerated/BinarySensorEntities.cs +++ b/src/CodeCasa.AutoGenerated/BinarySensorEntities.cs @@ -15,6 +15,10 @@ public BinarySensorEntities(IHaContext haContext) 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/CodeCasa.AutoGenerated.csproj b/src/CodeCasa.AutoGenerated/CodeCasa.AutoGenerated.csproj index a773f5b..8ab0e63 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/LightEntities.cs b/src/CodeCasa.AutoGenerated/LightEntities.cs index c73a112..54d18e3 100644 --- a/src/CodeCasa.AutoGenerated/LightEntities.cs +++ b/src/CodeCasa.AutoGenerated/LightEntities.cs @@ -12,6 +12,10 @@ public LightEntities(IHaContext haContext) /// Enumerates all light entities currently registered (at runtime) in Home Assistant as LightEntity public IEnumerable EnumerateAll() => _haContext.GetAllEntities().Where(e => e.EntityId.StartsWith("light.")).Select(e => new LightEntity(e)); + ///Attic Lights + public LightEntity AtticLights => new(_haContext, "light.attic_lights"); + ///Attic Spot Middle Door + public LightEntity AtticSpotMiddleDoor => new(_haContext, "light.attic_spot_middle_door"); ///Office Light Color 1 public LightEntity OfficeLightColor1 => new(_haContext, "light.office_light_color_1"); ///Office Light Color 2 @@ -32,5 +36,9 @@ 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 + ///Bathroom Lights + public LightEntity BathroomLights => new(_haContext, "light.bathroom_lights"); + + + public LightEntity BathroomSpotWindow => new(_haContext, "light.bathroom_spot_window"); } \ No newline at end of file diff --git a/src/CodeCasa.AutoGenerated/SensorEntities.cs b/src/CodeCasa.AutoGenerated/SensorEntities.cs index 8c7eb50..440ef85 100644 --- a/src/CodeCasa.AutoGenerated/SensorEntities.cs +++ b/src/CodeCasa.AutoGenerated/SensorEntities.cs @@ -18,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 @@ -37,4 +40,6 @@ public SensorEntities(IHaContext haContext) public SensorEntity OfficeSpeakerAlarms => new(_haContext, "sensor.office_speaker_alarms"); ///Office speaker timers public SensorEntity OfficeSpeakerTimers => new(_haContext, "sensor.office_speaker_timers"); + ///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.Automations/Apps/Lights/AtticLights/AtticLightsApp.cs b/src/CodeCasa.Automations/Apps/Lights/AtticLights/AtticLightsApp.cs new file mode 100644 index 0000000..b6ea5a4 --- /dev/null +++ b/src/CodeCasa.Automations/Apps/Lights/AtticLights/AtticLightsApp.cs @@ -0,0 +1,35 @@ +using CodeCasa.AutoGenerated; +using CodeCasa.AutomationPipelines.Lights.Extensions; +using CodeCasa.AutomationPipelines.Lights.Pipeline; +using CodeCasa.CustomEntities.Automation.Sensors; +using CodeCasa.CustomEntities.Automation.Switches; +using CodeCasa.Lights; +using NetDaemon.AppModel; +using System.Reactive.Concurrency; +using CodeCasa.AutomationPipelines.Lights.NetDaemon.Extensions; + +namespace CodeCasa.Automations.Apps.Lights.AtticLights +{ + [Focus] + [NetDaemonApp] + internal class AtticLightApp + { + public AtticLightApp( + LightPipelineFactory lightPipelineFactory, + LightEntities lightEntities, + UpstairsHallwayAtticMotionSensor motionSensor, + AtticWallSwitch atticWallSwitch, + IScheduler scheduler) + { + lightPipelineFactory.SetupLightPipeline(lightEntities.AtticSpotMiddleDoor, b => + { + b.EnableLogging("Attic Pipeline") + .When(LightParameters.Bright) + .AddReactiveNode(c => + c.AddToggle(atticWallSwitch, + sp => + sp.CreateAutoOffLightNode(LightParameters.Bright, TimeSpan.FromSeconds(60), motionSensor.Motion))); + }); + } + } +} diff --git a/src/CodeCasa.Automations/Apps/Lights/BackyardLights/BackyardLightsApp.cs b/src/CodeCasa.Automations/Apps/Lights/BackyardLights/BackyardLightsApp.cs index f72d6e5..116e4d6 100644 --- a/src/CodeCasa.Automations/Apps/Lights/BackyardLights/BackyardLightsApp.cs +++ b/src/CodeCasa.Automations/Apps/Lights/BackyardLights/BackyardLightsApp.cs @@ -5,11 +5,10 @@ using CodeCasa.Lights; using CodeCasa.Notifications.Lights; using CodeCasa.Notifications.Lights.Extensions; -using NetDaemon.AppModel; namespace CodeCasa.Automations.Apps.Lights.BackyardLights; -[NetDaemonApp] +//[NetDaemonApp] internal class BackyardLightsApp { public BackyardLightsApp( diff --git a/src/CodeCasa.Automations/Apps/Lights/BathroomLights/BathroomLightTimelineNode.cs b/src/CodeCasa.Automations/Apps/Lights/BathroomLights/BathroomLightTimelineNode.cs new file mode 100644 index 0000000..cf4f9c7 --- /dev/null +++ b/src/CodeCasa.Automations/Apps/Lights/BathroomLights/BathroomLightTimelineNode.cs @@ -0,0 +1,29 @@ +using CodeCasa.AutomationPipelines.Lights.Nodes; +using CodeCasa.Lights; +using Occurify; +using Occurify.TimeZones; +using System.Reactive.Concurrency; +using CodeCasa.Lights.Timelines.Extensions; + +namespace CodeCasa.Automations.Apps.Lights.BathroomLights +{ + internal class BathroomLightTimelineNode : LightTransitionNode + { + public BathroomLightTimelineNode(IScheduler scheduler) : base(scheduler) + { + var timeline = new Dictionary + { + { TimeZoneInstants.DailyAt(4), LightParameters.Dimmed }, + { TimeZoneInstants.DailyAt(5), LightParameters.Bright }, + { TimeZoneInstants.DailyAt(16), LightParameters.Bright }, + { TimeZoneInstants.DailyAt(23), LightParameters.Dimmed } + }; + + timeline.ToLightTransitionObservableIncludingCurrent(scheduler) + .Subscribe(transition => + { + Output = transition; + }); + } + } +} diff --git a/src/CodeCasa.Automations/Apps/Lights/BathroomLights/BathroomLightsApp.cs b/src/CodeCasa.Automations/Apps/Lights/BathroomLights/BathroomLightsApp.cs new file mode 100644 index 0000000..fc0cbbf --- /dev/null +++ b/src/CodeCasa.Automations/Apps/Lights/BathroomLights/BathroomLightsApp.cs @@ -0,0 +1,88 @@ +using CodeCasa.AutoGenerated; +using CodeCasa.AutomationPipelines.Lights.Extensions; +using CodeCasa.AutomationPipelines.Lights.NetDaemon.Extensions; +using CodeCasa.AutomationPipelines.Lights.Pipeline; +using CodeCasa.CustomEntities.Automation.People; +using CodeCasa.CustomEntities.Automation.Sensors; +using CodeCasa.CustomEntities.Automation.Switches; +using CodeCasa.Lights; +using CodeCasa.Lights.NetDaemon.Extensions; +using Microsoft.Extensions.DependencyInjection; +using NetDaemon.AppModel; +using Newtonsoft.Json; +using Newtonsoft.Json.Serialization; +using Reactive.Boolean; +using System.Reactive.Concurrency; +using System.Reactive.Linq; + +namespace CodeCasa.Automations.Apps.Lights.BathroomLights +{ + [Focus] + [NetDaemonApp] + internal class BathroomLightsApp + { + public BathroomLightsApp( + LightPipelineFactory lightPipelineFactory, + LightEntities lightEntities, + BathroomMotionSensor bathroomMotionSensor, + BathroomWallSwitch bathroomWallSwitch, + PeopleEntities people, + IScheduler scheduler) + { + var motionDetected = bathroomMotionSensor.Motion.Where(occupied => occupied); + var pipeline = lightPipelineFactory.CreateLightPipeline(lightEntities.BathroomSpotWindow.AsLight(), b => + { + b + .EnableLogging() + .AddPipelineWhen(pc => + { + pc.When(people.AnyAsleepWithCurrent(), LightParameters.Bright with { Brightness = 30 }) + .When(people.AnyAsleepWithCurrent().Not()); + }) + .AddPipeline(pc => + { + pc.When(people.AnyAsleepWithCurrent(), LightParameters.Bright with { Brightness = 30 }) + .When(people.AnyAsleepWithCurrent().Not()); + }) + .AddReactiveNode(c => + { + c.AddToggle(bathroomWallSwitch, + sp => new BathroomLightTimelineNode(sp.GetRequiredService()).TurnOffAfter(TimeSpan.FromMinutes(15), motionDetected, sp.GetRequiredService()), + context => context.CreateAutoOffLightNode(LightParameters.Relax, TimeSpan.FromMinutes(15), motionDetected), + context => context.CreateAutoOffLightNode(LightParameters.NightLight, TimeSpan.FromMinutes(15), motionDetected)); + }); + }); + + var settings = new JsonSerializerSettings + { + Formatting = Formatting.Indented, + ContractResolver = new IgnoreObservablesContractResolver(), + ReferenceLoopHandling = ReferenceLoopHandling.Ignore + }; + + string jsonString = JsonConvert.SerializeObject(pipeline, settings); + + bathroomMotionSensor.Subscribe(x => + { + Console.WriteLine("Test"); + }); + } + + private class IgnoreObservablesContractResolver : DefaultContractResolver + { + protected override JsonProperty CreateProperty(System.Reflection.MemberInfo member, MemberSerialization memberSerialization) + { + var property = base.CreateProperty(member, memberSerialization); + + if (property.PropertyType is not null && + property.PropertyType.IsGenericType && + property.PropertyType.GetGenericTypeDefinition() == typeof(IObservable<>)) + { + property.ShouldSerialize = _ => false; + } + + return property; + } + } + } +} diff --git a/src/CodeCasa.Automations/Apps/Lights/OfficeLights/OfficeLightsApp.cs b/src/CodeCasa.Automations/Apps/Lights/OfficeLights/OfficeLightsApp.cs index 8b3f8f0..9679d1e 100644 --- a/src/CodeCasa.Automations/Apps/Lights/OfficeLights/OfficeLightsApp.cs +++ b/src/CodeCasa.Automations/Apps/Lights/OfficeLights/OfficeLightsApp.cs @@ -3,12 +3,12 @@ using CodeCasa.CustomEntities.Automation.People; using CodeCasa.CustomEntities.Automation.Sensors; using CodeCasa.CustomEntities.Automation.Switches; -using CodeCasa.Lights; using Microsoft.Extensions.DependencyInjection; using NetDaemon.AppModel; using System.Drawing; using System.Reactive.Concurrency; using CodeCasa.AutomationPipelines.Lights.NetDaemon.Extensions; +using CodeCasa.CustomEntities.Automation.Extensions; using CodeCasa.Notifications.Lights; using CodeCasa.Notifications.Lights.Extensions; @@ -22,10 +22,17 @@ public OfficeLightsApp( LightEntities lightEntities, LightNotificationManagerContext lightNotificationManager, OfficeMotionSensor officeMotionSensor, - OfficeWallModuleSingleRocker officeWallModuleSingleRocker, + OfficeWallSwitch officeWallSwitch, OfficeDimmerSwitch officeDimmerSwitch, PeopleEntities people) { + people.OnLastPersonToAsleepOrAwayObservable().Subscribe(_ => + { + int i = 0; + i++; + }); + + lightPipelineFactory .SetupLightPipeline(lightEntities.OfficeLights, builder => { @@ -37,30 +44,31 @@ public OfficeLightsApp( .AddToggle(officeDimmerSwitch.OnOffPressed, c => { c - .Add(LightParameters.Relax) + //.Add(LightParameters.Relax) + .Add() .ForLight(lightEntities.OfficeLightColor1.EntityId, c1 => { - c1.Add(context => new ColorTransitionNode( - context.ServiceProvider.GetRequiredService(), + c1.Add(sp => new ColorTransitionNode( + sp.GetRequiredService(), TimeSpan.FromSeconds(3), Color.Red, Color.Blue, Color.LawnGreen)); }) .ForLight(lightEntities.OfficeLightColor2.EntityId, c2 => { - c2.Add(context => new ColorTransitionNode( - context.ServiceProvider.GetRequiredService(), + c2.Add(sp => new ColorTransitionNode( + sp.GetRequiredService(), TimeSpan.FromSeconds(3), Color.LawnGreen, Color.Red, Color.Blue)); }) .ForLight(lightEntities.OfficeLightColor3.EntityId, c2 => { - c2.Add(context => new ColorTransitionNode( - context.ServiceProvider.GetRequiredService(), + c2.Add(sp => new ColorTransitionNode( + sp.GetRequiredService(), TimeSpan.FromSeconds(3), Color.Blue, Color.LawnGreen, Color.Red)); }); }) - .TurnOffWhen(people.OnLastPersonToAsleepOrAwayObservable()) + .TurnOffWhenLastPersonToAsleepOrAway() .AddReactiveDimmer(officeDimmerSwitch); }) .AddNotifications(lightNotificationManager); diff --git a/src/CodeCasa.Automations/Apps/Notifications/AlarmLight.cs b/src/CodeCasa.Automations/Apps/Notifications/AlarmLight.cs index 142c929..3fc16d3 100644 --- a/src/CodeCasa.Automations/Apps/Notifications/AlarmLight.cs +++ b/src/CodeCasa.Automations/Apps/Notifications/AlarmLight.cs @@ -1,6 +1,12 @@ using System.Reactive.Concurrency; +using CodeCasa.Abstractions; +using CodeCasa.AutoGenerated; using CodeCasa.Automations.Nodes; +using CodeCasa.Lights; +using CodeCasa.Lights.NetDaemon; +using CodeCasa.NetDaemon.Extensions.Observables; using CodeCasa.Notifications.Lights; +using Microsoft.Extensions.DependencyInjection; using NetDaemon.AppModel; namespace CodeCasa.Automations.Apps.Notifications; @@ -10,8 +16,40 @@ internal class AlarmLight { public AlarmLight( IScheduler scheduler, + LightEntities lightEntities, LightNotificationContext lightNotification) { - lightNotification.Notify("alarm"); + lightEntities.BackyardPergolaStringLights.SubscribeOnOff( + () => lightNotification.Notify("alarm", sp => new AlarmNode(sp.GetRequiredService(), sp.GetRequiredService())), + () => lightNotification.RemoveNotification("alarm")); + } +} + +public class TestLight : ILight +{ + public string Id { get; } + public LightParameters GetParameters() + { + throw new NotImplementedException(); + } + + public void ApplyTransition(LightTransition transition) + { + throw new NotImplementedException(); + } + + public ILight[] GetChildren() + { + throw new NotImplementedException(); + } + + public IObservable> StateChanges() + { + throw new NotImplementedException(); + } + + public IObservable> StateChangesWithCurrent() + { + throw new NotImplementedException(); } } \ No newline at end of file diff --git a/src/CodeCasa.Automations/CodeCasa.Automations.csproj b/src/CodeCasa.Automations/CodeCasa.Automations.csproj index 253e736..450b530 100644 --- a/src/CodeCasa.Automations/CodeCasa.Automations.csproj +++ b/src/CodeCasa.Automations/CodeCasa.Automations.csproj @@ -23,13 +23,15 @@ - - - - - - - + + + + + + + + + diff --git a/src/CodeCasa.Automations/Nodes/AlarmNode.cs b/src/CodeCasa.Automations/Nodes/AlarmNode.cs index 94d4413..1f7fc64 100644 --- a/src/CodeCasa.Automations/Nodes/AlarmNode.cs +++ b/src/CodeCasa.Automations/Nodes/AlarmNode.cs @@ -1,7 +1,9 @@ -using System.Reactive.Concurrency; +using System.Drawing; +using System.Reactive.Concurrency; using System.Reactive.Linq; -using CodeCasa.AutomationPipelines.Lights.Context; using CodeCasa.AutomationPipelines.Lights.Nodes; +using CodeCasa.Lights; +using CodeCasa.Lights.NetDaemon; namespace CodeCasa.Automations.Nodes { @@ -9,14 +11,14 @@ public class AlarmNode : LightTransitionNode, IDisposable { private readonly IDisposable _subscription; - public AlarmNode(IScheduler scheduler, ILightPipelineContext lightPipelineContext) : base(scheduler) + public AlarmNode(IScheduler scheduler, NetDaemonLight light) : base(scheduler) { var high = false; _subscription = Observable.Interval(TimeSpan.FromSeconds(2), scheduler) .Subscribe(_ => { high = !high; - //Output = lightPipelineContext.LightEntity.GetWarningSceneParameters(high).AsTransitionInSeconds(0); + Output = new LightParameters{RgbColor = high ? Color.Red : Color.Yellow}.AsTransitionInSeconds(0); }); } 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 76b398b..0918171 100644 --- a/src/CodeCasa.CustomEntities.Automation/CodeCasa.CustomEntities.Automation.csproj +++ b/src/CodeCasa.CustomEntities.Automation/CodeCasa.CustomEntities.Automation.csproj @@ -7,12 +7,13 @@ - - - - - - + + + + + + + diff --git a/src/CodeCasa.CustomEntities.Automation/Extensions/LightTransitionReactiveNodeConfiguratorExtensions.cs b/src/CodeCasa.CustomEntities.Automation/Extensions/LightTransitionReactiveNodeConfiguratorExtensions.cs new file mode 100644 index 0000000..bf66053 --- /dev/null +++ b/src/CodeCasa.CustomEntities.Automation/Extensions/LightTransitionReactiveNodeConfiguratorExtensions.cs @@ -0,0 +1,26 @@ +using CodeCasa.AutomationPipelines; +using CodeCasa.AutomationPipelines.Lights.Nodes; +using CodeCasa.AutomationPipelines.Lights.ReactiveNode; +using CodeCasa.CustomEntities.Automation.People; +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; + } + } +} diff --git a/src/CodeCasa.CustomEntities.Automation/Extensions/ServiceCollectionExtensions.cs b/src/CodeCasa.CustomEntities.Automation/Extensions/ServiceCollectionExtensions.cs index 316308e..cafa6ce 100644 --- a/src/CodeCasa.CustomEntities.Automation/Extensions/ServiceCollectionExtensions.cs +++ b/src/CodeCasa.CustomEntities.Automation/Extensions/ServiceCollectionExtensions.cs @@ -18,10 +18,15 @@ public static IServiceCollection AddCodeCasaCustomAutomationEntities(this IServi .AddCodeCasaCustomCoreEntities() .AddLightNotifications() - .AddTransient() + .AddTransient() + .AddTransient() + .AddTransient() + .AddTransient() .AddTransient() + .AddTransient() .AddTransient() + .AddTransient() // Dashboard Notifications .AddTransient() diff --git a/src/CodeCasa.CustomEntities.Automation/People/PeopleEntities.cs b/src/CodeCasa.CustomEntities.Automation/People/PeopleEntities.cs index 948c8fd..3a681e0 100644 --- a/src/CodeCasa.CustomEntities.Automation/People/PeopleEntities.cs +++ b/src/CodeCasa.CustomEntities.Automation/People/PeopleEntities.cs @@ -1,6 +1,7 @@  using System.Reactive; using System.Reactive.Linq; +using Reactive.Boolean; namespace CodeCasa.CustomEntities.Automation.People; @@ -9,15 +10,17 @@ 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 AnyAwakeWithCurrent() => All.Select(e => e.PersonStateEqualsWithCurrent(PersonStates.Awake)) .CombineLatest(x => x.Any()); - public IObservable NoOneAsleepWithCurrent() => + public IObservable AnyAsleepWithCurrent() => All.Select(e => e.PersonStateEqualsWithCurrent(PersonStates.Asleep)) - .CombineLatest(x => !x.Any()); + .CombineLatest(x => x.Any()); + + public IObservable NoOneAsleepWithCurrent() => AnyAsleepWithCurrent().Not(); public IObservable OnLastPersonToAsleepOrAwayObservable() { 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 index a347060..996237f 100644 --- a/src/CodeCasa.CustomEntities.Automation/Sensors/OfficeMotionSensor.cs +++ b/src/CodeCasa.CustomEntities.Automation/Sensors/OfficeMotionSensor.cs @@ -1,6 +1,6 @@ using System.Reactive.Concurrency; using CodeCasa.AutoGenerated; -using CodeCasa.CustomEntities.Core.Sensors; +using CodeCasa.NetDaemon.Sensors.Composite; namespace CodeCasa.CustomEntities.Automation.Sensors { 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/OfficeWallModuleSingleRocker.cs b/src/CodeCasa.CustomEntities.Automation/Switches/OfficeWallSwitch.cs similarity index 74% rename from src/CodeCasa.CustomEntities.Automation/Switches/OfficeWallModuleSingleRocker.cs rename to src/CodeCasa.CustomEntities.Automation/Switches/OfficeWallSwitch.cs index 43244de..d87ead5 100644 --- a/src/CodeCasa.CustomEntities.Automation/Switches/OfficeWallModuleSingleRocker.cs +++ b/src/CodeCasa.CustomEntities.Automation/Switches/OfficeWallSwitch.cs @@ -3,6 +3,6 @@ namespace CodeCasa.CustomEntities.Automation.Switches { - public class OfficeWallModuleSingleRocker(ITriggerManager triggerManager) + 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 index 7026862..e6dcb00 100644 --- a/src/CodeCasa.CustomEntities.Automation/Switches/SwitchDeviceNames.cs +++ b/src/CodeCasa.CustomEntities.Automation/Switches/SwitchDeviceNames.cs @@ -3,9 +3,12 @@ internal class SwitchDeviceNames { // Hue wall switches + public const string AtticHallwayWallSwitch = "Attic Hallway Wall Switch"; + public const string BathroomWallSwitch = "Bathroom Wall Switch"; public const string OfficeWallSwitch = "Office Wall Switch"; // Hue dimmer switches + public const string AtticDimmerSwitch = "Attic Dimmer Switch"; public const string OfficeDeskDimmerSwitch = "Office Desk Dimmer Switch"; } } diff --git a/src/CodeCasa.CustomEntities.Core/CodeCasa.CustomEntities.Core.csproj b/src/CodeCasa.CustomEntities.Core/CodeCasa.CustomEntities.Core.csproj index e4974b3..d150e7d 100644 --- a/src/CodeCasa.CustomEntities.Core/CodeCasa.CustomEntities.Core.csproj +++ b/src/CodeCasa.CustomEntities.Core/CodeCasa.CustomEntities.Core.csproj @@ -7,9 +7,9 @@ - - - + + + diff --git a/src/CodeCasa.CustomEntities.Core/Sensors/MotionSensor.cs b/src/CodeCasa.CustomEntities.Core/Sensors/MotionSensor.cs deleted file mode 100644 index 633a92c..0000000 --- a/src/CodeCasa.CustomEntities.Core/Sensors/MotionSensor.cs +++ /dev/null @@ -1,43 +0,0 @@ -using System.Reactive.Concurrency; -using System.Reactive.Linq; -using CodeCasa.AutoGenerated; -using CodeCasa.NetDaemon.Extensions.Observables; - -namespace CodeCasa.CustomEntities.Core.Sensors -{ - // todo: move to a common library - public abstract class MotionSensor( - IScheduler scheduler, - BinarySensorEntity motionOccupancySensor, - NumericSensorEntity motionIlluminanceLuxSensor) - { - public BinarySensorEntity MotionOccupancySensor { get; } = motionOccupancySensor; - public NumericSensorEntity MotionIlluminanceLuxSensor { get; } = motionIlluminanceLuxSensor; - - public IObservable CreateMotionObservable(double brightnessThreshold = 5, TimeSpan? offDelay = null) - { - offDelay ??= TimeSpan.FromSeconds(25); - - var motionLastXTime = - MotionOccupancySensor.PersistOnFor(offDelay.Value, scheduler); - - var brightnessLessThanX = MotionIlluminanceLuxSensor - .ToBooleanObservable(s => s.State <= brightnessThreshold); - - var triggered = false; - return motionLastXTime.CombineLatest(brightnessLessThanX, (motionTriggered, brightnessTriggered) => - { - if (motionTriggered && brightnessTriggered) - { - triggered = true; - } - else if (!motionTriggered) - { - triggered = false; - } - - return triggered; - }).DistinctUntilChanged(); - } - } -} diff --git a/src/CodeCasa.Dashboard/CodeCasa.Dashboard.csproj b/src/CodeCasa.Dashboard/CodeCasa.Dashboard.csproj index 6d7cf04..f0630e8 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 e49d458..e9e58d3 100644 --- a/src/CodeCasa.NetDaemon.Utilities/CodeCasa.NetDaemon.Utilities.csproj +++ b/src/CodeCasa.NetDaemon.Utilities/CodeCasa.NetDaemon.Utilities.csproj @@ -7,8 +7,8 @@ - - + + From 78719c8a86a40ca8d5603b8c130796b6caa65e2e Mon Sep 17 00:00:00 2001 From: Jasper Date: Sun, 22 Mar 2026 15:50:56 +0100 Subject: [PATCH 10/13] Publishing pipeline state to mqtt. --- CodeCasa.Showcase.withLocalCodeCasa.sln | 6 ++ .../Apps/Lights/AtticLights/AtticLightsApp.cs | 1 - .../BathroomLights/BathroomLightsApp.cs | 42 ++------------ .../Lights/OfficeLights/OfficeLightsApp.cs | 8 --- .../CodeCasa.Automations.csproj | 2 +- src/CodeCasa.Automations/Program.cs | 2 + .../BackgroundService/MqttWorker.cs | 43 +++++++++++++++ src/CodeCasa.Mqtt/CodeCasa.Mqtt.csproj | 14 +++++ ...ransitionPipelineConfiguratorExtensions.cs | 55 +++++++++++++++++++ .../Extensions/ServiceCollectionExtensions.cs | 21 +++++++ src/CodeCasa.Mqtt/MqttOptions.cs | 12 ++++ src/CodeCasa.Mqtt/MqttPublisher.cs | 25 +++++++++ 12 files changed, 185 insertions(+), 46 deletions(-) create mode 100644 src/CodeCasa.Mqtt/BackgroundService/MqttWorker.cs create mode 100644 src/CodeCasa.Mqtt/CodeCasa.Mqtt.csproj create mode 100644 src/CodeCasa.Mqtt/Extensions/ILightTransitionPipelineConfiguratorExtensions.cs create mode 100644 src/CodeCasa.Mqtt/Extensions/ServiceCollectionExtensions.cs create mode 100644 src/CodeCasa.Mqtt/MqttOptions.cs create mode 100644 src/CodeCasa.Mqtt/MqttPublisher.cs diff --git a/CodeCasa.Showcase.withLocalCodeCasa.sln b/CodeCasa.Showcase.withLocalCodeCasa.sln index d752474..4cb1045 100644 --- a/CodeCasa.Showcase.withLocalCodeCasa.sln +++ b/CodeCasa.Showcase.withLocalCodeCasa.sln @@ -51,6 +51,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CodeCasa.NetDaemon.Sensors. 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 @@ -145,6 +147,10 @@ Global {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 diff --git a/src/CodeCasa.Automations/Apps/Lights/AtticLights/AtticLightsApp.cs b/src/CodeCasa.Automations/Apps/Lights/AtticLights/AtticLightsApp.cs index b6ea5a4..10a6043 100644 --- a/src/CodeCasa.Automations/Apps/Lights/AtticLights/AtticLightsApp.cs +++ b/src/CodeCasa.Automations/Apps/Lights/AtticLights/AtticLightsApp.cs @@ -10,7 +10,6 @@ namespace CodeCasa.Automations.Apps.Lights.AtticLights { - [Focus] [NetDaemonApp] internal class AtticLightApp { diff --git a/src/CodeCasa.Automations/Apps/Lights/BathroomLights/BathroomLightsApp.cs b/src/CodeCasa.Automations/Apps/Lights/BathroomLights/BathroomLightsApp.cs index fc0cbbf..3f612ee 100644 --- a/src/CodeCasa.Automations/Apps/Lights/BathroomLights/BathroomLightsApp.cs +++ b/src/CodeCasa.Automations/Apps/Lights/BathroomLights/BathroomLightsApp.cs @@ -1,6 +1,5 @@ using CodeCasa.AutoGenerated; using CodeCasa.AutomationPipelines.Lights.Extensions; -using CodeCasa.AutomationPipelines.Lights.NetDaemon.Extensions; using CodeCasa.AutomationPipelines.Lights.Pipeline; using CodeCasa.CustomEntities.Automation.People; using CodeCasa.CustomEntities.Automation.Sensors; @@ -9,11 +8,11 @@ using CodeCasa.Lights.NetDaemon.Extensions; using Microsoft.Extensions.DependencyInjection; using NetDaemon.AppModel; -using Newtonsoft.Json; -using Newtonsoft.Json.Serialization; using Reactive.Boolean; using System.Reactive.Concurrency; using System.Reactive.Linq; +using CodeCasa.Mqtt; +using CodeCasa.Mqtt.Extensions; namespace CodeCasa.Automations.Apps.Lights.BathroomLights { @@ -27,12 +26,14 @@ public BathroomLightsApp( BathroomMotionSensor bathroomMotionSensor, BathroomWallSwitch bathroomWallSwitch, PeopleEntities people, - IScheduler scheduler) + IScheduler scheduler, + MqttPublisher mqttPublisher) { var motionDetected = bathroomMotionSensor.Motion.Where(occupied => occupied); - var pipeline = lightPipelineFactory.CreateLightPipeline(lightEntities.BathroomSpotWindow.AsLight(), b => + lightPipelineFactory.CreateLightPipeline(lightEntities.BathroomSpotWindow.AsLight(), b => { b + .PublishTelemetryToMqtt(mqttPublisher) .EnableLogging() .AddPipelineWhen(pc => { @@ -52,37 +53,6 @@ public BathroomLightsApp( context => context.CreateAutoOffLightNode(LightParameters.NightLight, TimeSpan.FromMinutes(15), motionDetected)); }); }); - - var settings = new JsonSerializerSettings - { - Formatting = Formatting.Indented, - ContractResolver = new IgnoreObservablesContractResolver(), - ReferenceLoopHandling = ReferenceLoopHandling.Ignore - }; - - string jsonString = JsonConvert.SerializeObject(pipeline, settings); - - bathroomMotionSensor.Subscribe(x => - { - Console.WriteLine("Test"); - }); - } - - private class IgnoreObservablesContractResolver : DefaultContractResolver - { - protected override JsonProperty CreateProperty(System.Reflection.MemberInfo member, MemberSerialization memberSerialization) - { - var property = base.CreateProperty(member, memberSerialization); - - if (property.PropertyType is not null && - property.PropertyType.IsGenericType && - property.PropertyType.GetGenericTypeDefinition() == typeof(IObservable<>)) - { - property.ShouldSerialize = _ => false; - } - - return property; - } } } } diff --git a/src/CodeCasa.Automations/Apps/Lights/OfficeLights/OfficeLightsApp.cs b/src/CodeCasa.Automations/Apps/Lights/OfficeLights/OfficeLightsApp.cs index 9679d1e..8233143 100644 --- a/src/CodeCasa.Automations/Apps/Lights/OfficeLights/OfficeLightsApp.cs +++ b/src/CodeCasa.Automations/Apps/Lights/OfficeLights/OfficeLightsApp.cs @@ -26,13 +26,6 @@ public OfficeLightsApp( OfficeDimmerSwitch officeDimmerSwitch, PeopleEntities people) { - people.OnLastPersonToAsleepOrAwayObservable().Subscribe(_ => - { - int i = 0; - i++; - }); - - lightPipelineFactory .SetupLightPipeline(lightEntities.OfficeLights, builder => { @@ -45,7 +38,6 @@ public OfficeLightsApp( { c //.Add(LightParameters.Relax) - .Add() .ForLight(lightEntities.OfficeLightColor1.EntityId, c1 => { c1.Add(sp => new ColorTransitionNode( diff --git a/src/CodeCasa.Automations/CodeCasa.Automations.csproj b/src/CodeCasa.Automations/CodeCasa.Automations.csproj index 450b530..52fe70c 100644 --- a/src/CodeCasa.Automations/CodeCasa.Automations.csproj +++ b/src/CodeCasa.Automations/CodeCasa.Automations.csproj @@ -31,7 +31,6 @@ - @@ -41,6 +40,7 @@ + diff --git a/src/CodeCasa.Automations/Program.cs b/src/CodeCasa.Automations/Program.cs index ea0c04b..5cfb856 100644 --- a/src/CodeCasa.Automations/Program.cs +++ b/src/CodeCasa.Automations/Program.cs @@ -1,5 +1,6 @@ using System.Reflection; using CodeCasa.Automations.Extensions; +using CodeCasa.Mqtt.Extensions; using Microsoft.Extensions.Hosting; using NetDaemon.AppModel; using NetDaemon.Extensions.Scheduler; @@ -21,6 +22,7 @@ await Host.CreateDefaultBuilder(args) .AddAppsFromAssembly(Assembly.GetExecutingAssembly()) .AddNetDaemonScheduler() .AddCodeCasa(context.Configuration) + .AddCodeCasaMqtt(context.Configuration) ) .Build() .RunAsync() diff --git a/src/CodeCasa.Mqtt/BackgroundService/MqttWorker.cs b/src/CodeCasa.Mqtt/BackgroundService/MqttWorker.cs new file mode 100644 index 0000000..d3bfbef --- /dev/null +++ b/src/CodeCasa.Mqtt/BackgroundService/MqttWorker.cs @@ -0,0 +1,43 @@ +using Microsoft.Extensions.Options; +using MQTTnet; +using MQTTnet.Formatter; + +namespace CodeCasa.Mqtt.BackgroundService +{ + public class MqttWorker(IMqttClient client, IOptions options) + : Microsoft.Extensions.Hosting.BackgroundService + { + private readonly MqttOptions _options = options.Value; + + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + var connectionBuilder = new MqttClientOptionsBuilder() + .WithTcpServer(_options.Host, _options.Port) + .WithProtocolVersion(MqttProtocolVersion.V500); + if (_options.User != null) + { + connectionBuilder.WithCredentials(_options.User, _options.Password!); + } + var connectOptions = connectionBuilder.Build(); + + while (!stoppingToken.IsCancellationRequested) + { + try + { + if (!client.IsConnected) + { + await client.ConnectAsync(connectOptions, stoppingToken); + Console.WriteLine("MQTT Connected."); + } + } + catch (Exception ex) + { + Console.WriteLine($"Connection failed: {ex.Message}. Retrying..."); + } + + // Wait before checking connection status again + await Task.Delay(TimeSpan.FromSeconds(5), stoppingToken); + } + } + } +} diff --git a/src/CodeCasa.Mqtt/CodeCasa.Mqtt.csproj b/src/CodeCasa.Mqtt/CodeCasa.Mqtt.csproj new file mode 100644 index 0000000..6a3770b --- /dev/null +++ b/src/CodeCasa.Mqtt/CodeCasa.Mqtt.csproj @@ -0,0 +1,14 @@ + + + + net10.0 + enable + enable + + + + + + + + diff --git a/src/CodeCasa.Mqtt/Extensions/ILightTransitionPipelineConfiguratorExtensions.cs b/src/CodeCasa.Mqtt/Extensions/ILightTransitionPipelineConfiguratorExtensions.cs new file mode 100644 index 0000000..4fde205 --- /dev/null +++ b/src/CodeCasa.Mqtt/Extensions/ILightTransitionPipelineConfiguratorExtensions.cs @@ -0,0 +1,55 @@ +using System.Reactive; +using System.Reactive.Linq; +using CodeCasa.AutomationPipelines; +using CodeCasa.AutomationPipelines.Lights.Pipeline; +using CodeCasa.Lights; +using Newtonsoft.Json; +using Newtonsoft.Json.Serialization; + +namespace CodeCasa.Mqtt.Extensions +{ + public static class ILightTransitionPipelineConfiguratorExtensions + { + public static ILightTransitionPipelineConfigurator PublishTelemetryToMqtt(this ILightTransitionPipelineConfigurator configurator, MqttPublisher publisher) where TLight : ILight + { + configurator.OnCompleted(e => + { + PublishPipelineState(publisher, e.Pipeline, e.Light.Id).GetAwaiter().GetResult(); + }); + configurator.ConfigureTelemetrySubscriber(stream => + { + return stream.SelectMany(async t => + { + await PublishPipelineState(publisher, t.Pipeline, t.Light.Id); + return Unit.Default; + }).Subscribe(); + }); + return configurator; + } + + private static async Task PublishPipelineState(MqttPublisher publisher, IPipeline pipeline, string lightEntityId) + { + var settings = new JsonSerializerSettings { Formatting = Formatting.Indented, ContractResolver = new IgnoreObservablesContractResolver(), ReferenceLoopHandling = ReferenceLoopHandling.Ignore }; + + var jsonString = JsonConvert.SerializeObject(pipeline, settings); + await publisher.PublishAsync(lightEntityId, jsonString); + } + + private class IgnoreObservablesContractResolver : DefaultContractResolver + { + protected override JsonProperty CreateProperty(System.Reflection.MemberInfo member, MemberSerialization memberSerialization) + { + var property = base.CreateProperty(member, memberSerialization); + + if (property.PropertyType is not null && + property.PropertyType.IsGenericType && + property.PropertyType.GetGenericTypeDefinition() == typeof(IObservable<>)) + { + property.ShouldSerialize = _ => false; + } + + return property; + } + } + } +} diff --git a/src/CodeCasa.Mqtt/Extensions/ServiceCollectionExtensions.cs b/src/CodeCasa.Mqtt/Extensions/ServiceCollectionExtensions.cs new file mode 100644 index 0000000..bf82d61 --- /dev/null +++ b/src/CodeCasa.Mqtt/Extensions/ServiceCollectionExtensions.cs @@ -0,0 +1,21 @@ +using CodeCasa.Mqtt.BackgroundService; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using MQTTnet; + +namespace CodeCasa.Mqtt.Extensions; + +public static class ServiceCollectionExtensions +{ + public static IServiceCollection AddCodeCasaMqtt(this IServiceCollection serviceCollection, IConfiguration configuration) + { + serviceCollection.Configure(configuration.GetSection("MqttOptions")); + + serviceCollection + .AddSingleton(new MqttClientFactory().CreateMqttClient()) + .AddSingleton(); + serviceCollection.AddHostedService(); + + return serviceCollection; + } +} \ No newline at end of file diff --git a/src/CodeCasa.Mqtt/MqttOptions.cs b/src/CodeCasa.Mqtt/MqttOptions.cs new file mode 100644 index 0000000..ea54ea5 --- /dev/null +++ b/src/CodeCasa.Mqtt/MqttOptions.cs @@ -0,0 +1,12 @@ + +namespace CodeCasa.Mqtt +{ + public class MqttOptions + { + public string Host { get; set; } = null!; + public int Port { get; set; } = 1883; + public string? User { get; set; } + public string? Password { get; set; } + public string BaseTopic { get; set; } = "code-casa"; + } +} diff --git a/src/CodeCasa.Mqtt/MqttPublisher.cs b/src/CodeCasa.Mqtt/MqttPublisher.cs new file mode 100644 index 0000000..000fe35 --- /dev/null +++ b/src/CodeCasa.Mqtt/MqttPublisher.cs @@ -0,0 +1,25 @@ +using Microsoft.Extensions.Options; +using MQTTnet; + +namespace CodeCasa.Mqtt +{ + public class MqttPublisher(IMqttClient mqttClient, IOptions options) + { + private readonly MqttOptions _options = options.Value; + + public async Task PublishAsync(string entityId, string message) + { + if (!mqttClient.IsConnected) + { + return null; + } + + var mqttMessage = new MqttApplicationMessageBuilder() + .WithTopic($"{_options.BaseTopic}/{entityId}") + .WithPayload(message) + .Build(); + + return await mqttClient.PublishAsync(mqttMessage); + } + } +} From 0d9e81b1f5b183d8202cf89d2fc0e663eebcbef6 Mon Sep 17 00:00:00 2001 From: Jasper Date: Fri, 10 Jul 2026 11:14:00 +0200 Subject: [PATCH 11/13] Branch cleanup. --- .../CodeCasa.AutoGenerated.csproj | 4 +- .../Extensions/GeneratedExtensions.cs | 1 + src/CodeCasa.AutoGenerated/LightEntities.cs | 15 ---- src/CodeCasa.AutoGenerated/SceneEntities.cs | 46 +++++++++++++ .../Apps/Lights/AtticLights/AtticLightsApp.cs | 34 --------- .../BackyardLights/BackyardLightsApp.cs | 5 +- .../BathroomLightTimelineNode.cs | 29 -------- .../BathroomLights/BathroomLightsApp.cs | 58 ---------------- .../OfficeLights/ColorTransitionNode.cs | 53 -------------- .../Lights/OfficeLights/OfficeLightsApp.cs | 69 ------------------- .../Apps/Notifications/AlarmLight.cs | 55 --------------- .../CodeCasa.Automations.csproj | 15 ++-- .../Extensions/ServiceCollectionExtensions.cs | 8 ++- src/CodeCasa.Automations/Program.cs | 1 - .../CodeCasa.CustomEntities.Automation.csproj | 14 ++-- ...itionReactiveNodeConfiguratorExtensions.cs | 24 +++++++ .../Extensions/ServiceCollectionExtensions.cs | 43 ++++++++---- .../Switches/BedroomNightstandLeftSwitch.cs | 8 +++ .../Switches/BedroomNightstandRightSwitch.cs | 8 +++ .../Switches/BedroomWallSwitch.cs | 8 +++ .../Switches/LivingRoomWallSwitch.cs | 8 +++ .../Switches/SwitchDeviceNames.cs | 5 ++ .../CodeCasa.CustomEntities.Core.csproj | 6 +- .../Switches/IkeaRodretDimmer.cs | 43 ++++++++++++ .../CodeCasa.Dashboard.csproj | 10 +-- .../BackgroundService/MqttWorker.cs | 43 ------------ src/CodeCasa.Mqtt/CodeCasa.Mqtt.csproj | 14 ---- ...ransitionPipelineConfiguratorExtensions.cs | 55 --------------- .../Extensions/ServiceCollectionExtensions.cs | 21 ------ src/CodeCasa.Mqtt/MqttOptions.cs | 12 ---- src/CodeCasa.Mqtt/MqttPublisher.cs | 25 ------- .../CodeCasa.NetDaemon.Utilities.csproj | 4 +- 32 files changed, 218 insertions(+), 526 deletions(-) create mode 100644 src/CodeCasa.AutoGenerated/SceneEntities.cs delete mode 100644 src/CodeCasa.Automations/Apps/Lights/AtticLights/AtticLightsApp.cs delete mode 100644 src/CodeCasa.Automations/Apps/Lights/BathroomLights/BathroomLightTimelineNode.cs delete mode 100644 src/CodeCasa.Automations/Apps/Lights/BathroomLights/BathroomLightsApp.cs delete mode 100644 src/CodeCasa.Automations/Apps/Lights/OfficeLights/ColorTransitionNode.cs delete mode 100644 src/CodeCasa.Automations/Apps/Lights/OfficeLights/OfficeLightsApp.cs delete mode 100644 src/CodeCasa.Automations/Apps/Notifications/AlarmLight.cs create mode 100644 src/CodeCasa.CustomEntities.Automation/Switches/BedroomNightstandLeftSwitch.cs create mode 100644 src/CodeCasa.CustomEntities.Automation/Switches/BedroomNightstandRightSwitch.cs create mode 100644 src/CodeCasa.CustomEntities.Automation/Switches/BedroomWallSwitch.cs create mode 100644 src/CodeCasa.CustomEntities.Automation/Switches/LivingRoomWallSwitch.cs create mode 100644 src/CodeCasa.CustomEntities.Core/Switches/IkeaRodretDimmer.cs delete mode 100644 src/CodeCasa.Mqtt/BackgroundService/MqttWorker.cs delete mode 100644 src/CodeCasa.Mqtt/CodeCasa.Mqtt.csproj delete mode 100644 src/CodeCasa.Mqtt/Extensions/ILightTransitionPipelineConfiguratorExtensions.cs delete mode 100644 src/CodeCasa.Mqtt/Extensions/ServiceCollectionExtensions.cs delete mode 100644 src/CodeCasa.Mqtt/MqttOptions.cs delete mode 100644 src/CodeCasa.Mqtt/MqttPublisher.cs diff --git a/src/CodeCasa.AutoGenerated/CodeCasa.AutoGenerated.csproj b/src/CodeCasa.AutoGenerated/CodeCasa.AutoGenerated.csproj index 8ab0e63..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 138afc8..7d72c44 100644 --- a/src/CodeCasa.AutoGenerated/Extensions/GeneratedExtensions.cs +++ b/src/CodeCasa.AutoGenerated/Extensions/GeneratedExtensions.cs @@ -17,6 +17,7 @@ public static IServiceCollection AddHomeAssistantGenerated(this IServiceCollecti .AddTransient() .AddTransient() .AddTransient() + .AddTransient() .AddTransient(); } } \ No newline at end of file diff --git a/src/CodeCasa.AutoGenerated/LightEntities.cs b/src/CodeCasa.AutoGenerated/LightEntities.cs index 54d18e3..dbdd1a3 100644 --- a/src/CodeCasa.AutoGenerated/LightEntities.cs +++ b/src/CodeCasa.AutoGenerated/LightEntities.cs @@ -12,16 +12,6 @@ public LightEntities(IHaContext haContext) /// Enumerates all light entities currently registered (at runtime) in Home Assistant as LightEntity public IEnumerable EnumerateAll() => _haContext.GetAllEntities().Where(e => e.EntityId.StartsWith("light.")).Select(e => new LightEntity(e)); - ///Attic Lights - public LightEntity AtticLights => new(_haContext, "light.attic_lights"); - ///Attic Spot Middle Door - public LightEntity AtticSpotMiddleDoor => new(_haContext, "light.attic_spot_middle_door"); - ///Office Light Color 1 - public LightEntity OfficeLightColor1 => new(_haContext, "light.office_light_color_1"); - ///Office Light Color 2 - public LightEntity OfficeLightColor2 => new(_haContext, "light.office_light_color_2"); - ///Office Light Color 3 - public LightEntity OfficeLightColor3 => new(_haContext, "light.office_light_color_3"); ///Office Lights public LightEntity OfficeLights => new(_haContext, "light.office_lights"); ///Backyard Door Light @@ -36,9 +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"); - ///Bathroom Lights - public LightEntity BathroomLights => new(_haContext, "light.bathroom_lights"); - - - public LightEntity BathroomSpotWindow => new(_haContext, "light.bathroom_spot_window"); } \ 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.Automations/Apps/Lights/AtticLights/AtticLightsApp.cs b/src/CodeCasa.Automations/Apps/Lights/AtticLights/AtticLightsApp.cs deleted file mode 100644 index 10a6043..0000000 --- a/src/CodeCasa.Automations/Apps/Lights/AtticLights/AtticLightsApp.cs +++ /dev/null @@ -1,34 +0,0 @@ -using CodeCasa.AutoGenerated; -using CodeCasa.AutomationPipelines.Lights.Extensions; -using CodeCasa.AutomationPipelines.Lights.Pipeline; -using CodeCasa.CustomEntities.Automation.Sensors; -using CodeCasa.CustomEntities.Automation.Switches; -using CodeCasa.Lights; -using NetDaemon.AppModel; -using System.Reactive.Concurrency; -using CodeCasa.AutomationPipelines.Lights.NetDaemon.Extensions; - -namespace CodeCasa.Automations.Apps.Lights.AtticLights -{ - [NetDaemonApp] - internal class AtticLightApp - { - public AtticLightApp( - LightPipelineFactory lightPipelineFactory, - LightEntities lightEntities, - UpstairsHallwayAtticMotionSensor motionSensor, - AtticWallSwitch atticWallSwitch, - IScheduler scheduler) - { - lightPipelineFactory.SetupLightPipeline(lightEntities.AtticSpotMiddleDoor, b => - { - b.EnableLogging("Attic Pipeline") - .When(LightParameters.Bright) - .AddReactiveNode(c => - c.AddToggle(atticWallSwitch, - sp => - sp.CreateAutoOffLightNode(LightParameters.Bright, TimeSpan.FromSeconds(60), motionSensor.Motion))); - }); - } - } -} diff --git a/src/CodeCasa.Automations/Apps/Lights/BackyardLights/BackyardLightsApp.cs b/src/CodeCasa.Automations/Apps/Lights/BackyardLights/BackyardLightsApp.cs index 116e4d6..0c252bc 100644 --- a/src/CodeCasa.Automations/Apps/Lights/BackyardLights/BackyardLightsApp.cs +++ b/src/CodeCasa.Automations/Apps/Lights/BackyardLights/BackyardLightsApp.cs @@ -14,7 +14,6 @@ internal class BackyardLightsApp public BackyardLightsApp( LightPipelineFactory pipelineFactory, LightEntities lightEntities, - LightNotificationManagerContext lightNotificationManager, BackyardLightsRoutineFactory backyardLightsRoutineFactory) { // We use a separate pipeline for each light string to allow for different timing on the startup routine. @@ -35,7 +34,7 @@ public BackyardLightsApp( b .TurnOnWhen(backyardLightsRoutineFactory.Create(TimeSpan.FromSeconds(index))) .TurnOffWhen() - .AddNotifications(lightNotificationManager); + .AddNotifications(); }); } @@ -57,7 +56,7 @@ public BackyardLightsApp( builder .When(backyardLightsRoutineFactory.Create(offsetSeconds), LightParameters.Relax.AsTransitionInSeconds(transitionSeconds)) .TurnOffWhen() - .AddNotifications(lightNotificationManager); + .AddNotifications(); }); } } diff --git a/src/CodeCasa.Automations/Apps/Lights/BathroomLights/BathroomLightTimelineNode.cs b/src/CodeCasa.Automations/Apps/Lights/BathroomLights/BathroomLightTimelineNode.cs deleted file mode 100644 index cf4f9c7..0000000 --- a/src/CodeCasa.Automations/Apps/Lights/BathroomLights/BathroomLightTimelineNode.cs +++ /dev/null @@ -1,29 +0,0 @@ -using CodeCasa.AutomationPipelines.Lights.Nodes; -using CodeCasa.Lights; -using Occurify; -using Occurify.TimeZones; -using System.Reactive.Concurrency; -using CodeCasa.Lights.Timelines.Extensions; - -namespace CodeCasa.Automations.Apps.Lights.BathroomLights -{ - internal class BathroomLightTimelineNode : LightTransitionNode - { - public BathroomLightTimelineNode(IScheduler scheduler) : base(scheduler) - { - var timeline = new Dictionary - { - { TimeZoneInstants.DailyAt(4), LightParameters.Dimmed }, - { TimeZoneInstants.DailyAt(5), LightParameters.Bright }, - { TimeZoneInstants.DailyAt(16), LightParameters.Bright }, - { TimeZoneInstants.DailyAt(23), LightParameters.Dimmed } - }; - - timeline.ToLightTransitionObservableIncludingCurrent(scheduler) - .Subscribe(transition => - { - Output = transition; - }); - } - } -} diff --git a/src/CodeCasa.Automations/Apps/Lights/BathroomLights/BathroomLightsApp.cs b/src/CodeCasa.Automations/Apps/Lights/BathroomLights/BathroomLightsApp.cs deleted file mode 100644 index 3f612ee..0000000 --- a/src/CodeCasa.Automations/Apps/Lights/BathroomLights/BathroomLightsApp.cs +++ /dev/null @@ -1,58 +0,0 @@ -using CodeCasa.AutoGenerated; -using CodeCasa.AutomationPipelines.Lights.Extensions; -using CodeCasa.AutomationPipelines.Lights.Pipeline; -using CodeCasa.CustomEntities.Automation.People; -using CodeCasa.CustomEntities.Automation.Sensors; -using CodeCasa.CustomEntities.Automation.Switches; -using CodeCasa.Lights; -using CodeCasa.Lights.NetDaemon.Extensions; -using Microsoft.Extensions.DependencyInjection; -using NetDaemon.AppModel; -using Reactive.Boolean; -using System.Reactive.Concurrency; -using System.Reactive.Linq; -using CodeCasa.Mqtt; -using CodeCasa.Mqtt.Extensions; - -namespace CodeCasa.Automations.Apps.Lights.BathroomLights -{ - [Focus] - [NetDaemonApp] - internal class BathroomLightsApp - { - public BathroomLightsApp( - LightPipelineFactory lightPipelineFactory, - LightEntities lightEntities, - BathroomMotionSensor bathroomMotionSensor, - BathroomWallSwitch bathroomWallSwitch, - PeopleEntities people, - IScheduler scheduler, - MqttPublisher mqttPublisher) - { - var motionDetected = bathroomMotionSensor.Motion.Where(occupied => occupied); - lightPipelineFactory.CreateLightPipeline(lightEntities.BathroomSpotWindow.AsLight(), b => - { - b - .PublishTelemetryToMqtt(mqttPublisher) - .EnableLogging() - .AddPipelineWhen(pc => - { - pc.When(people.AnyAsleepWithCurrent(), LightParameters.Bright with { Brightness = 30 }) - .When(people.AnyAsleepWithCurrent().Not()); - }) - .AddPipeline(pc => - { - pc.When(people.AnyAsleepWithCurrent(), LightParameters.Bright with { Brightness = 30 }) - .When(people.AnyAsleepWithCurrent().Not()); - }) - .AddReactiveNode(c => - { - c.AddToggle(bathroomWallSwitch, - sp => new BathroomLightTimelineNode(sp.GetRequiredService()).TurnOffAfter(TimeSpan.FromMinutes(15), motionDetected, sp.GetRequiredService()), - context => context.CreateAutoOffLightNode(LightParameters.Relax, TimeSpan.FromMinutes(15), motionDetected), - context => context.CreateAutoOffLightNode(LightParameters.NightLight, TimeSpan.FromMinutes(15), motionDetected)); - }); - }); - } - } -} diff --git a/src/CodeCasa.Automations/Apps/Lights/OfficeLights/ColorTransitionNode.cs b/src/CodeCasa.Automations/Apps/Lights/OfficeLights/ColorTransitionNode.cs deleted file mode 100644 index e2d69fe..0000000 --- a/src/CodeCasa.Automations/Apps/Lights/OfficeLights/ColorTransitionNode.cs +++ /dev/null @@ -1,53 +0,0 @@ -using System.Drawing; -using System.Reactive.Concurrency; -using CodeCasa.AutomationPipelines; -using CodeCasa.Lights; - -namespace CodeCasa.Automations.Apps.Lights.OfficeLights -{ - internal class ColorTransitionNode : PipelineNode - { - public ColorTransitionNode(IScheduler scheduler, TimeSpan transitionTime, params Color[] colors) - { - var defaultTransition = TimeSpan.FromMilliseconds(500); - if (transitionTime < defaultTransition) - { - throw new ArgumentException($"Transition time should be at least {defaultTransition}.", nameof(transitionTime)); - } - var colors1 = colors ?? throw new ArgumentNullException(nameof(colors)); - if (colors1.Length == 0) - { - throw new ArgumentException("At least one color must be provided.", nameof(colors)); - } - - var index = 0; - Output = new LightParameters - { - RgbColor = colors1[0], - Brightness = byte.MaxValue - }.AsTransition(); - - scheduler.Schedule(defaultTransition, () => - { - index = (index + 1) % colors1.Length; - Output = new LightParameters - { - RgbColor = colors1[index], - Brightness = byte.MaxValue - }.AsTransition(transitionTime - defaultTransition); - }); - - scheduler.SchedulePeriodic( - transitionTime, - () => - { - index = (index + 1) % colors1.Length; - Output = new LightParameters - { - RgbColor = colors1[index], - Brightness = byte.MaxValue - }.AsTransition(transitionTime); - }); - } - } -} diff --git a/src/CodeCasa.Automations/Apps/Lights/OfficeLights/OfficeLightsApp.cs b/src/CodeCasa.Automations/Apps/Lights/OfficeLights/OfficeLightsApp.cs deleted file mode 100644 index 8233143..0000000 --- a/src/CodeCasa.Automations/Apps/Lights/OfficeLights/OfficeLightsApp.cs +++ /dev/null @@ -1,69 +0,0 @@ -using CodeCasa.AutoGenerated; -using CodeCasa.AutomationPipelines.Lights.Pipeline; -using CodeCasa.CustomEntities.Automation.People; -using CodeCasa.CustomEntities.Automation.Sensors; -using CodeCasa.CustomEntities.Automation.Switches; -using Microsoft.Extensions.DependencyInjection; -using NetDaemon.AppModel; -using System.Drawing; -using System.Reactive.Concurrency; -using CodeCasa.AutomationPipelines.Lights.NetDaemon.Extensions; -using CodeCasa.CustomEntities.Automation.Extensions; -using CodeCasa.Notifications.Lights; -using CodeCasa.Notifications.Lights.Extensions; - -namespace CodeCasa.Automations.Apps.Lights.OfficeLights; - -[NetDaemonApp] -internal class OfficeLightsApp -{ - public OfficeLightsApp( - LightPipelineFactory lightPipelineFactory, - LightEntities lightEntities, - LightNotificationManagerContext lightNotificationManager, - OfficeMotionSensor officeMotionSensor, - OfficeWallSwitch officeWallSwitch, - OfficeDimmerSwitch officeDimmerSwitch, - PeopleEntities people) - { - lightPipelineFactory - .SetupLightPipeline(lightEntities.OfficeLights, builder => - { - builder - .AddReactiveNode(rc => - { - rc - //.AddCycle(officeDimmerSwitch.ScenePressed, LightScenes.Bright, LightScenes.Concentrate) - .AddToggle(officeDimmerSwitch.OnOffPressed, c => - { - c - //.Add(LightParameters.Relax) - .ForLight(lightEntities.OfficeLightColor1.EntityId, c1 => - { - c1.Add(sp => new ColorTransitionNode( - sp.GetRequiredService(), - TimeSpan.FromSeconds(3), - Color.Red, Color.Blue, Color.LawnGreen)); - }) - .ForLight(lightEntities.OfficeLightColor2.EntityId, c2 => - { - c2.Add(sp => new ColorTransitionNode( - sp.GetRequiredService(), - TimeSpan.FromSeconds(3), - Color.LawnGreen, Color.Red, Color.Blue)); - }) - .ForLight(lightEntities.OfficeLightColor3.EntityId, c2 => - { - c2.Add(sp => new ColorTransitionNode( - sp.GetRequiredService(), - TimeSpan.FromSeconds(3), - Color.Blue, Color.LawnGreen, Color.Red)); - }); - }) - .TurnOffWhenLastPersonToAsleepOrAway() - .AddReactiveDimmer(officeDimmerSwitch); - }) - .AddNotifications(lightNotificationManager); - }); - } -} \ No newline at end of file diff --git a/src/CodeCasa.Automations/Apps/Notifications/AlarmLight.cs b/src/CodeCasa.Automations/Apps/Notifications/AlarmLight.cs deleted file mode 100644 index 3fc16d3..0000000 --- a/src/CodeCasa.Automations/Apps/Notifications/AlarmLight.cs +++ /dev/null @@ -1,55 +0,0 @@ -using System.Reactive.Concurrency; -using CodeCasa.Abstractions; -using CodeCasa.AutoGenerated; -using CodeCasa.Automations.Nodes; -using CodeCasa.Lights; -using CodeCasa.Lights.NetDaemon; -using CodeCasa.NetDaemon.Extensions.Observables; -using CodeCasa.Notifications.Lights; -using Microsoft.Extensions.DependencyInjection; -using NetDaemon.AppModel; - -namespace CodeCasa.Automations.Apps.Notifications; - -[NetDaemonApp] -internal class AlarmLight -{ - public AlarmLight( - IScheduler scheduler, - LightEntities lightEntities, - LightNotificationContext lightNotification) - { - lightEntities.BackyardPergolaStringLights.SubscribeOnOff( - () => lightNotification.Notify("alarm", sp => new AlarmNode(sp.GetRequiredService(), sp.GetRequiredService())), - () => lightNotification.RemoveNotification("alarm")); - } -} - -public class TestLight : ILight -{ - public string Id { get; } - public LightParameters GetParameters() - { - throw new NotImplementedException(); - } - - public void ApplyTransition(LightTransition transition) - { - throw new NotImplementedException(); - } - - public ILight[] GetChildren() - { - throw new NotImplementedException(); - } - - public IObservable> StateChanges() - { - throw new NotImplementedException(); - } - - public IObservable> StateChangesWithCurrent() - { - throw new NotImplementedException(); - } -} \ No newline at end of file diff --git a/src/CodeCasa.Automations/CodeCasa.Automations.csproj b/src/CodeCasa.Automations/CodeCasa.Automations.csproj index 52fe70c..07d0d50 100644 --- a/src/CodeCasa.Automations/CodeCasa.Automations.csproj +++ b/src/CodeCasa.Automations/CodeCasa.Automations.csproj @@ -23,14 +23,15 @@ - - - - - + + + + + + - - + + diff --git a/src/CodeCasa.Automations/Extensions/ServiceCollectionExtensions.cs b/src/CodeCasa.Automations/Extensions/ServiceCollectionExtensions.cs index 9201156..abc49f3 100644 --- a/src/CodeCasa.Automations/Extensions/ServiceCollectionExtensions.cs +++ b/src/CodeCasa.Automations/Extensions/ServiceCollectionExtensions.cs @@ -1,8 +1,11 @@ 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.Mqtt.Extensions; using CodeCasa.Notifications.InputSelect.NetDaemon.Extensions; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; @@ -18,6 +21,7 @@ public static IServiceCollection AddCodeCasa(this IServiceCollection serviceColl Coordinates.Local = new Coordinates(52.374191, 4.912269, 2.5); return serviceCollection + .AddLightScenes() .AddAutomationPipelines() .AddHomeAssistantGenerated() .AddCodeCasaCustomAutomationEntities() @@ -25,6 +29,8 @@ public static IServiceCollection AddCodeCasa(this IServiceCollection serviceColl .AddInputSelectNotifications(configuration) .AddTransient() - .AddTransient(); + .AddTransient() + + .AddCodeCasaMqtt(configuration); } } \ No newline at end of file diff --git a/src/CodeCasa.Automations/Program.cs b/src/CodeCasa.Automations/Program.cs index 5cfb856..957a515 100644 --- a/src/CodeCasa.Automations/Program.cs +++ b/src/CodeCasa.Automations/Program.cs @@ -22,7 +22,6 @@ await Host.CreateDefaultBuilder(args) .AddAppsFromAssembly(Assembly.GetExecutingAssembly()) .AddNetDaemonScheduler() .AddCodeCasa(context.Configuration) - .AddCodeCasaMqtt(context.Configuration) ) .Build() .RunAsync() diff --git a/src/CodeCasa.CustomEntities.Automation/CodeCasa.CustomEntities.Automation.csproj b/src/CodeCasa.CustomEntities.Automation/CodeCasa.CustomEntities.Automation.csproj index 0918171..ed57223 100644 --- a/src/CodeCasa.CustomEntities.Automation/CodeCasa.CustomEntities.Automation.csproj +++ b/src/CodeCasa.CustomEntities.Automation/CodeCasa.CustomEntities.Automation.csproj @@ -7,13 +7,13 @@ - - - - - - - + + + + + + + diff --git a/src/CodeCasa.CustomEntities.Automation/Extensions/LightTransitionReactiveNodeConfiguratorExtensions.cs b/src/CodeCasa.CustomEntities.Automation/Extensions/LightTransitionReactiveNodeConfiguratorExtensions.cs index bf66053..5f11e88 100644 --- a/src/CodeCasa.CustomEntities.Automation/Extensions/LightTransitionReactiveNodeConfiguratorExtensions.cs +++ b/src/CodeCasa.CustomEntities.Automation/Extensions/LightTransitionReactiveNodeConfiguratorExtensions.cs @@ -1,7 +1,9 @@ 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; @@ -22,5 +24,27 @@ public static ILightTransitionReactiveNodeConfigurator TurnOffWhenLastPe }); 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 cafa6ce..57b8ec3 100644 --- a/src/CodeCasa.CustomEntities.Automation/Extensions/ServiceCollectionExtensions.cs +++ b/src/CodeCasa.CustomEntities.Automation/Extensions/ServiceCollectionExtensions.cs @@ -4,6 +4,8 @@ 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; @@ -13,23 +15,22 @@ public static class ServiceCollectionExtensions { public static IServiceCollection AddCodeCasaCustomAutomationEntities(this IServiceCollection serviceCollection) { - return serviceCollection + serviceCollection .AddCodeCasaCustomCoreEntities() - .AddLightNotifications() + .AddLightNotifications(); - .AddTransient() - .AddTransient() - .AddTransient() - .AddTransient() - .AddTransient() + serviceCollection + .AddTransientImplementationsOf() + .AddTransientImplementationsOf() + .AddTransientImplementationsOf() + .AddTransientImplementationsOf() + .AddTransientImplementationsOf(); - .AddTransient() - .AddTransient() - .AddTransient() + serviceCollection.AddTransientImplementationsOf(); - // Dashboard Notifications - .AddTransient() + // Dashboard Notifications + serviceCollection.AddTransient() .AddTransient() .AddTransient() @@ -41,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/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/SwitchDeviceNames.cs b/src/CodeCasa.CustomEntities.Automation/Switches/SwitchDeviceNames.cs index e6dcb00..2f41e6d 100644 --- a/src/CodeCasa.CustomEntities.Automation/Switches/SwitchDeviceNames.cs +++ b/src/CodeCasa.CustomEntities.Automation/Switches/SwitchDeviceNames.cs @@ -5,10 +5,15 @@ 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 d150e7d..f1d83eb 100644 --- a/src/CodeCasa.CustomEntities.Core/CodeCasa.CustomEntities.Core.csproj +++ b/src/CodeCasa.CustomEntities.Core/CodeCasa.CustomEntities.Core.csproj @@ -7,9 +7,9 @@ - - - + + + 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.Dashboard/CodeCasa.Dashboard.csproj b/src/CodeCasa.Dashboard/CodeCasa.Dashboard.csproj index f0630e8..38e2e39 100644 --- a/src/CodeCasa.Dashboard/CodeCasa.Dashboard.csproj +++ b/src/CodeCasa.Dashboard/CodeCasa.Dashboard.csproj @@ -7,11 +7,11 @@ - - - - - + + + + + diff --git a/src/CodeCasa.Mqtt/BackgroundService/MqttWorker.cs b/src/CodeCasa.Mqtt/BackgroundService/MqttWorker.cs deleted file mode 100644 index d3bfbef..0000000 --- a/src/CodeCasa.Mqtt/BackgroundService/MqttWorker.cs +++ /dev/null @@ -1,43 +0,0 @@ -using Microsoft.Extensions.Options; -using MQTTnet; -using MQTTnet.Formatter; - -namespace CodeCasa.Mqtt.BackgroundService -{ - public class MqttWorker(IMqttClient client, IOptions options) - : Microsoft.Extensions.Hosting.BackgroundService - { - private readonly MqttOptions _options = options.Value; - - protected override async Task ExecuteAsync(CancellationToken stoppingToken) - { - var connectionBuilder = new MqttClientOptionsBuilder() - .WithTcpServer(_options.Host, _options.Port) - .WithProtocolVersion(MqttProtocolVersion.V500); - if (_options.User != null) - { - connectionBuilder.WithCredentials(_options.User, _options.Password!); - } - var connectOptions = connectionBuilder.Build(); - - while (!stoppingToken.IsCancellationRequested) - { - try - { - if (!client.IsConnected) - { - await client.ConnectAsync(connectOptions, stoppingToken); - Console.WriteLine("MQTT Connected."); - } - } - catch (Exception ex) - { - Console.WriteLine($"Connection failed: {ex.Message}. Retrying..."); - } - - // Wait before checking connection status again - await Task.Delay(TimeSpan.FromSeconds(5), stoppingToken); - } - } - } -} diff --git a/src/CodeCasa.Mqtt/CodeCasa.Mqtt.csproj b/src/CodeCasa.Mqtt/CodeCasa.Mqtt.csproj deleted file mode 100644 index 6a3770b..0000000 --- a/src/CodeCasa.Mqtt/CodeCasa.Mqtt.csproj +++ /dev/null @@ -1,14 +0,0 @@ - - - - net10.0 - enable - enable - - - - - - - - diff --git a/src/CodeCasa.Mqtt/Extensions/ILightTransitionPipelineConfiguratorExtensions.cs b/src/CodeCasa.Mqtt/Extensions/ILightTransitionPipelineConfiguratorExtensions.cs deleted file mode 100644 index 4fde205..0000000 --- a/src/CodeCasa.Mqtt/Extensions/ILightTransitionPipelineConfiguratorExtensions.cs +++ /dev/null @@ -1,55 +0,0 @@ -using System.Reactive; -using System.Reactive.Linq; -using CodeCasa.AutomationPipelines; -using CodeCasa.AutomationPipelines.Lights.Pipeline; -using CodeCasa.Lights; -using Newtonsoft.Json; -using Newtonsoft.Json.Serialization; - -namespace CodeCasa.Mqtt.Extensions -{ - public static class ILightTransitionPipelineConfiguratorExtensions - { - public static ILightTransitionPipelineConfigurator PublishTelemetryToMqtt(this ILightTransitionPipelineConfigurator configurator, MqttPublisher publisher) where TLight : ILight - { - configurator.OnCompleted(e => - { - PublishPipelineState(publisher, e.Pipeline, e.Light.Id).GetAwaiter().GetResult(); - }); - configurator.ConfigureTelemetrySubscriber(stream => - { - return stream.SelectMany(async t => - { - await PublishPipelineState(publisher, t.Pipeline, t.Light.Id); - return Unit.Default; - }).Subscribe(); - }); - return configurator; - } - - private static async Task PublishPipelineState(MqttPublisher publisher, IPipeline pipeline, string lightEntityId) - { - var settings = new JsonSerializerSettings { Formatting = Formatting.Indented, ContractResolver = new IgnoreObservablesContractResolver(), ReferenceLoopHandling = ReferenceLoopHandling.Ignore }; - - var jsonString = JsonConvert.SerializeObject(pipeline, settings); - await publisher.PublishAsync(lightEntityId, jsonString); - } - - private class IgnoreObservablesContractResolver : DefaultContractResolver - { - protected override JsonProperty CreateProperty(System.Reflection.MemberInfo member, MemberSerialization memberSerialization) - { - var property = base.CreateProperty(member, memberSerialization); - - if (property.PropertyType is not null && - property.PropertyType.IsGenericType && - property.PropertyType.GetGenericTypeDefinition() == typeof(IObservable<>)) - { - property.ShouldSerialize = _ => false; - } - - return property; - } - } - } -} diff --git a/src/CodeCasa.Mqtt/Extensions/ServiceCollectionExtensions.cs b/src/CodeCasa.Mqtt/Extensions/ServiceCollectionExtensions.cs deleted file mode 100644 index bf82d61..0000000 --- a/src/CodeCasa.Mqtt/Extensions/ServiceCollectionExtensions.cs +++ /dev/null @@ -1,21 +0,0 @@ -using CodeCasa.Mqtt.BackgroundService; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using MQTTnet; - -namespace CodeCasa.Mqtt.Extensions; - -public static class ServiceCollectionExtensions -{ - public static IServiceCollection AddCodeCasaMqtt(this IServiceCollection serviceCollection, IConfiguration configuration) - { - serviceCollection.Configure(configuration.GetSection("MqttOptions")); - - serviceCollection - .AddSingleton(new MqttClientFactory().CreateMqttClient()) - .AddSingleton(); - serviceCollection.AddHostedService(); - - return serviceCollection; - } -} \ No newline at end of file diff --git a/src/CodeCasa.Mqtt/MqttOptions.cs b/src/CodeCasa.Mqtt/MqttOptions.cs deleted file mode 100644 index ea54ea5..0000000 --- a/src/CodeCasa.Mqtt/MqttOptions.cs +++ /dev/null @@ -1,12 +0,0 @@ - -namespace CodeCasa.Mqtt -{ - public class MqttOptions - { - public string Host { get; set; } = null!; - public int Port { get; set; } = 1883; - public string? User { get; set; } - public string? Password { get; set; } - public string BaseTopic { get; set; } = "code-casa"; - } -} diff --git a/src/CodeCasa.Mqtt/MqttPublisher.cs b/src/CodeCasa.Mqtt/MqttPublisher.cs deleted file mode 100644 index 000fe35..0000000 --- a/src/CodeCasa.Mqtt/MqttPublisher.cs +++ /dev/null @@ -1,25 +0,0 @@ -using Microsoft.Extensions.Options; -using MQTTnet; - -namespace CodeCasa.Mqtt -{ - public class MqttPublisher(IMqttClient mqttClient, IOptions options) - { - private readonly MqttOptions _options = options.Value; - - public async Task PublishAsync(string entityId, string message) - { - if (!mqttClient.IsConnected) - { - return null; - } - - var mqttMessage = new MqttApplicationMessageBuilder() - .WithTopic($"{_options.BaseTopic}/{entityId}") - .WithPayload(message) - .Build(); - - return await mqttClient.PublishAsync(mqttMessage); - } - } -} diff --git a/src/CodeCasa.NetDaemon.Utilities/CodeCasa.NetDaemon.Utilities.csproj b/src/CodeCasa.NetDaemon.Utilities/CodeCasa.NetDaemon.Utilities.csproj index e9e58d3..f890cf4 100644 --- a/src/CodeCasa.NetDaemon.Utilities/CodeCasa.NetDaemon.Utilities.csproj +++ b/src/CodeCasa.NetDaemon.Utilities/CodeCasa.NetDaemon.Utilities.csproj @@ -7,8 +7,8 @@ - - + + From 616e88b2480a20bab546aa775193255701340414 Mon Sep 17 00:00:00 2001 From: Jasper Date: Fri, 10 Jul 2026 11:18:41 +0200 Subject: [PATCH 12/13] Cleanup of namespaces. --- .../Extensions/ServiceCollectionExtensions.cs | 1 - src/CodeCasa.Automations/Program.cs | 1 - 2 files changed, 2 deletions(-) diff --git a/src/CodeCasa.Automations/Extensions/ServiceCollectionExtensions.cs b/src/CodeCasa.Automations/Extensions/ServiceCollectionExtensions.cs index abc49f3..603836d 100644 --- a/src/CodeCasa.Automations/Extensions/ServiceCollectionExtensions.cs +++ b/src/CodeCasa.Automations/Extensions/ServiceCollectionExtensions.cs @@ -5,7 +5,6 @@ using CodeCasa.Automations.Apps.Lights.BackyardLights.Observables; using CodeCasa.CustomEntities.Automation.Extensions; using CodeCasa.Lights.NetDaemon.Scenes.Extensions; -using CodeCasa.Mqtt.Extensions; using CodeCasa.Notifications.InputSelect.NetDaemon.Extensions; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; diff --git a/src/CodeCasa.Automations/Program.cs b/src/CodeCasa.Automations/Program.cs index 957a515..ea0c04b 100644 --- a/src/CodeCasa.Automations/Program.cs +++ b/src/CodeCasa.Automations/Program.cs @@ -1,6 +1,5 @@ using System.Reflection; using CodeCasa.Automations.Extensions; -using CodeCasa.Mqtt.Extensions; using Microsoft.Extensions.Hosting; using NetDaemon.AppModel; using NetDaemon.Extensions.Scheduler; From e3346f00c4e3ff434cc40733fc1bd9ddc56f45bd Mon Sep 17 00:00:00 2001 From: Jasper Date: Fri, 10 Jul 2026 11:25:00 +0200 Subject: [PATCH 13/13] Updating build script. --- .github/workflows/ci-build-and-test.yml | 2 +- .../Config/LightNotificationConfig.cs | 24 ------------------- 2 files changed, 1 insertion(+), 25 deletions(-) delete mode 100644 src/CodeCasa.Notifications.Light/Config/LightNotificationConfig.cs 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/src/CodeCasa.Notifications.Light/Config/LightNotificationConfig.cs b/src/CodeCasa.Notifications.Light/Config/LightNotificationConfig.cs deleted file mode 100644 index ebcff6f..0000000 --- a/src/CodeCasa.Notifications.Light/Config/LightNotificationConfig.cs +++ /dev/null @@ -1,24 +0,0 @@ -using CodeCasa.AutomationPipelines; -using CodeCasa.AutomationPipelines.Lights.Context; -using CodeCasa.Lights; - -namespace CodeCasa.Notifications.Light.Config; - -public class LightNotificationConfig -{ - public LightNotificationOptions Options { get; } - public Type? NodeType { get; } - public Func>? NodeFactory { get; } - - internal LightNotificationConfig(LightNotificationOptions options, Type nodeType) - { - Options = options; - NodeType = nodeType; - } - - internal LightNotificationConfig(LightNotificationOptions options, Func> nodeFactory) - { - Options = options; - NodeFactory = nodeFactory; - } -} \ No newline at end of file