Neuerstellung

This commit is contained in:
Stephan Maier
2023-08-29 11:37:30 +02:00
parent c703aea7bd
commit b9eb4cf019
58 changed files with 17442 additions and 0 deletions

View File

@@ -0,0 +1,12 @@
{
"version": 1,
"isRoot": true,
"tools": {
"joysoftware.netdaemon.hassmodel.codegen": {
"version": "23.26.0",
"commands": [
"nd-codegen"
]
}
}
}

View File

@@ -0,0 +1,4 @@
FROM mcr.microsoft.com/dotnet/sdk:6.0
ENV PATH="/root/.dotnet/tools:${PATH}"
RUN dotnet tool install -g JoySoftware.NetDaemon.HassModel.CodeGen

View File

@@ -0,0 +1,12 @@
{
"name": "NetDaemon App-development",
"dockerFile": "./Dockerfile",
"postCreateCommand": "dotnet restore *.csproj",
"settings": {
"terminal.integrated.shell.linux": "/bin/bash",
"terminal.integrated.fontSize": 10
},
"extensions": [
"ms-dotnettools.csharp"
]
}

View File

@@ -0,0 +1,8 @@
obj
bin
appsettings.Development.json
.vs
*.gen
.idea
#appsettings.json
#HomeAssistantGenerated.cs

View File

@@ -0,0 +1,28 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "Debug Apps",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build",
"program": "${workspaceFolder}/bin/Debug/net6.0/NetDaemonApps.dll",
"args": [],
"cwd": "${workspaceFolder}",
"env": {
"DOTNET_ENVIRONMENT": "Development",
"ASPNETCORE_ENVIRONMENT": "Development"
},
"stopAtEntry": false,
"logging": {
"moduleLoad": false
}
},
{
"name": ".NET Core Attach",
"type": "coreclr",
"request": "attach",
"processId": "${command:pickProcess}"
}
]
}

View File

@@ -0,0 +1,29 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "build",
"command": "dotnet",
"type": "process",
"args": [
"build",
"${workspaceFolder}/NetDaemonApps.csproj",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary"
],
"problemMatcher": "$msCompile"
},
{
"label": "publish",
"command": "dotnet",
"type": "process",
"args": [
"publish",
"${workspaceFolder}/NetDaemonApps.csproj",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary"
],
"problemMatcher": "$msCompile"
}
]
}

View File

@@ -0,0 +1,120 @@
using NetDaemonInterface;
using NetDaemonInterface;
using System.Threading;
using System.Threading.Tasks;
namespace NetDaemonApps.AreaControl
{
public class AreaControl : IAreaControl
{
internal CancellationTokenSource? CTSAfter;
internal CancellationTokenSource? CTSRun;
internal readonly IHaContext haContext;
internal readonly IScheduler scheduler;
internal readonly IEntities entities;
internal readonly IServices services;
public AreaControl(IHaContext haContext, IScheduler scheduler)
{
haContext = haContext;
scheduler = scheduler;
entities = new Entities(haContext);
services = new Services(haContext);
}
/// <summary>
/// The after task is designed for an action that needs to be executed after a delay.
/// Starting a new after task will stop the currently running task
/// </summary>
/// <param name="Delay">The delay</param>
/// <param name="action">The action</param>
internal void StartAfterTask(TimeSpan Delay, Action action)
{
CTSAfter?.Cancel();
CTSAfter = new CancellationTokenSource();
Task.Run(() =>
{
try
{
Task.Delay(Delay).Wait(CTSAfter.Token);
CTSAfter.Token.ThrowIfCancellationRequested();
action();
}
catch (OperationCanceledException)
{
// Ignore
}
});
}
/// <summary>
/// The RunTask is used to execute longer running tasks without blocking the return
/// Starting a new run task will stop the currently running task
/// </summary>
/// <param name="action"></param>
internal void StartRunTask(Action<CancellationToken> action)
{
CTSRun?.Cancel();
CTSRun = new CancellationTokenSource();
Task.Run(() =>
{
try
{
action(CTSRun.Token);
}
catch (OperationCanceledException)
{
// Ignore
}
});
}
/// <summary>
/// Helper to be used within a RunTask action, waits for a delay and checks for cancellation
/// </summary>
/// <param name="delay">The delay</param>
/// <param name="ct">The cancellation token</param>
internal void DelayRunTaskAndCheckCancellation(TimeSpan delay, CancellationToken ct)
{
Task.Delay(delay).Wait(ct);
ct.ThrowIfCancellationRequested();
}
/// <summary>
/// Stops the currently running RunTask
/// </summary>
internal void StopAfterTask()
{
CTSAfter?.Cancel();
}
/// <summary>
/// Stops the currently running RunTask
/// </summary>
internal void StopRunTask()
{
CTSRun?.Cancel();
}
public virtual void ButtonPressed(string entityId, CallServiceDataElement dataElement)
{
}
public virtual void SunDawn()
{
}
public virtual void SunRising()
{
}
public virtual void SunDusk()
{
}
public virtual void SunSetting()
{
}
}
}

View File

@@ -0,0 +1,16 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace NetDaemonApps.AreaControl.Areas
{
public class Bath : AreaControl
{
public Bath(IHaContext haContext, IScheduler scheduler)
: base(haContext, scheduler)
{
}
}
}

View File

@@ -0,0 +1,16 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace NetDaemonApps.AreaControl.Areas
{
public class Bedroom : AreaControl
{
public Bedroom(IHaContext haContext, IScheduler scheduler)
: base(haContext, scheduler)
{
}
}
}

View File

@@ -0,0 +1,16 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace NetDaemonApps.AreaControl.Areas
{
public class Corridor1stFloor : AreaControl
{
public Corridor1stFloor(IHaContext haContext, IScheduler scheduler)
: base(haContext, scheduler)
{
}
}
}

View File

@@ -0,0 +1,16 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace NetDaemonApps.AreaControl.Areas
{
public class Corridor2ndFloor : AreaControl
{
public Corridor2ndFloor(IHaContext haContext, IScheduler scheduler)
: base(haContext, scheduler)
{
}
}
}

View File

@@ -0,0 +1,16 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace NetDaemonApps.AreaControl.Areas
{
public class CorridorGroundFloor : AreaControl
{
public CorridorGroundFloor(IHaContext haContext, IScheduler scheduler)
: base(haContext, scheduler)
{
}
}
}

View File

@@ -0,0 +1,16 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace NetDaemonApps.AreaControl.Areas
{
public class EntranceArea : AreaControl
{
public EntranceArea(IHaContext haContext, IScheduler scheduler)
: base(haContext, scheduler)
{
}
}
}

View File

@@ -0,0 +1,16 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace NetDaemonApps.AreaControl.Areas
{
public class KidsRoom : AreaControl
{
public KidsRoom(IHaContext haContext, IScheduler scheduler)
: base(haContext, scheduler)
{
}
}
}

View File

@@ -0,0 +1,16 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace NetDaemonApps.AreaControl.Areas
{
public class Kitchen : AreaControl
{
public Kitchen(IHaContext haContext, IScheduler scheduler)
: base(haContext, scheduler)
{
}
}
}

View File

@@ -0,0 +1,16 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace NetDaemonApps.AreaControl.Areas
{
public class LivingRoom : AreaControl
{
public LivingRoom(IHaContext haContext, IScheduler scheduler)
: base(haContext, scheduler)
{
}
}
}

View File

@@ -0,0 +1,52 @@
using NetDaemonInterface;
using NetDaemonInterface;
namespace NetDaemonApps.AreaControl.Areas
{
public class Office : AreaControl
{
public Office(IHaContext haContext, IScheduler scheduler)
: base(haContext, scheduler)
{
}
public override void ButtonPressed(string ButtonSensor, CallServiceDataElement dataElement)
{
}
/// <summary>
/// Morgendämmerung
/// </summary>
public override void SunDawn()
{
}
/// <summary>
/// Sonnenaufgang
/// </summary>
public override void SunRising()
{
}
/// <summary>
/// Abenddämmerung
/// </summary>
public override void SunDusk()
{
}
/// <summary>
/// Sonnenuntergang
/// </summary>
public override void SunSetting()
{
}
}
}

View File

@@ -0,0 +1,16 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace NetDaemonApps.AreaControl.Areas
{
public class Studio : AreaControl
{
public Studio(IHaContext haContext, IScheduler scheduler)
: base(haContext, scheduler)
{
}
}
}

View File

@@ -0,0 +1,16 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace NetDaemonApps.AreaControl.Areas
{
public class TechnicalRoom : AreaControl
{
public TechnicalRoom(IHaContext haContext, IScheduler scheduler)
: base(haContext, scheduler)
{
}
}
}

View File

@@ -0,0 +1,125 @@
using NetDaemonApps.DeviceLib.UseeLink;
using NetDaemonInterface;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace NetDaemonApps.AreaControl.Areas
{
public class TmpArea : AreaControl
{
private EntityEvents events;
private NotifyServices notifyServices;
SmartSurgeProtectorSocket socketAllDevices;
Task socketAllDevicesTask;
SmartSurgeProtectorSocket socketTvDvd;
Task socketTvDvDTask;
public TmpArea(IHaContext haContext, IScheduler scheduler)
: base(haContext, scheduler)
{
events = new EntityEvents();
events.Click += Events_Click;
events.DoubleClick += Events_DoubleClick;
notifyServices = new NotifyServices(haContext);
socketAllDevices = new SmartSurgeProtectorSocket()
{
Input = entities.InputButton.Deengzb002dgStMxBtnonoff,
Outputs = new List<LightEntity>
{
entities.Light.Deengzb002dgStMxLight, // TV
entities.Light.Deengzb002dgStMxLight2, // PC
entities.Light.Deengzb002dgStMxLight3, // DVD
entities.Light.Deengzb002dgStMxLight4, // Wifi
//entities.Light.Deengzb002dgStMxLight5, // USB
},
SwitchOnDelayTime = 1000,
SwitchOffTime = 0,
};
socketTvDvd = new SmartSurgeProtectorSocket()
{
Input = entities.InputButton.Deengzb002dgStMxBtnonoff,
Outputs = new List<LightEntity>
{
entities.Light.Deengzb002dgStMxLight, // TV
entities.Light.Deengzb002dgStMxLight2, // PC
//entities.Light.Deengzb002dgStMxLight3, // DVD
//entities.Light.Deengzb002dgStMxLight4, // Wifi
//entities.Light.Deengzb002dgStMxLight5, // USB
},
SwitchOnDelayTime = 1000,
SwitchOffTime = 0,
};
}
private void Events_DoubleClick(object? sender, EventArgs e)
{
}
private void Events_Click(object? sender, EventArgs e)
{
}
public override void ButtonPressed(string ButtonSensor, CallServiceDataElement dataElement)
{
events.VarName = ButtonSensor;
events.Trigger = true;
// Alle Geräte außer die USB'S einschalten
if (entities.InputButton.Deengzb002dgStMxBtnonoff.EntityId.Equals(ButtonSensor))
{
socketAllDevicesTask = socketAllDevices.CheckStateAsync(dataElement);
}
// TV und PC einschalten - der Rest bleibt aus
if (entities.InputButton.Deengzb002dgStMxBtnonoffTvpc.EntityId.Equals(ButtonSensor))
{
socketTvDvDTask = socketTvDvd.CheckStateAsync(dataElement);
}
}
/// <summary>
/// Morgendämmerung
/// </summary>
public override void SunDawn()
{
notifyServices.Whatsapp("Morgendämmerung");
}
/// <summary>
/// Sonnenaufgang
/// </summary>
public override void SunRising()
{
notifyServices.Whatsapp("Sonnenaufgang");
}
/// <summary>
/// Abenddämmerung
/// </summary>
public override void SunDusk()
{
notifyServices.Whatsapp("Abenddämmerung");
}
/// <summary>
/// Sonnenuntergang
/// </summary>
public override void SunSetting()
{
notifyServices.Whatsapp("Sonnenuntergang");
}
}
}

View File

@@ -0,0 +1,19 @@
using NetDaemonInterface;
using System.Collections.Generic;
namespace NetDaemonApps
{
public class ButtonMapping
{
public List<Tuple<AreaControlEnum, string>> mapping;
public ButtonMapping(IEntities entities)
{
mapping = new()
{
new(AreaControlEnum.TmpArea, entities.InputButton.Deengzb002dgStMxBtnonoff.EntityId),
new(AreaControlEnum.TmpArea, entities.InputButton.Deengzb002dgStMxBtnonoffTvpc.EntityId),
};
}
}
}

View File

@@ -0,0 +1,6 @@
namespace NetDaemonApps;
public static class Constants
{
public static string dateTime_TimeFormat = "HH:mm:ss";
}

View File

@@ -0,0 +1,103 @@
using NetDaemonInterface;
using System.Threading;
using System.Threading.Tasks;
namespace NetDaemonApps.DeviceControl
{
public class DeviceControl : IDeviceControl
{
internal CancellationTokenSource? CTSAfter;
internal CancellationTokenSource? CTSRun;
internal readonly IHaContext haContext;
internal readonly IScheduler scheduler;
internal readonly IEntities entities;
internal readonly IServices services;
public DeviceControl(IHaContext haContext, IScheduler scheduler)
{
haContext = haContext;
scheduler = scheduler;
entities = new Entities(haContext);
services = new Services(haContext);
}
/// <summary>
/// The after task is designed for an action that needs to be executed after a delay.
/// Starting a new after task will stop the currently running task
/// </summary>
/// <param name="Delay">The delay</param>
/// <param name="action">The action</param>
internal void StartAfterTask(TimeSpan Delay, Action action)
{
CTSAfter?.Cancel();
CTSAfter = new CancellationTokenSource();
Task.Run(() =>
{
try
{
Task.Delay(Delay).Wait(CTSAfter.Token);
CTSAfter.Token.ThrowIfCancellationRequested();
action();
}
catch (OperationCanceledException)
{
// Ignore
}
});
}
/// <summary>
/// The RunTask is used to execute longer running tasks without blocking the return
/// Starting a new run task will stop the currently running task
/// </summary>
/// <param name="action"></param>
internal void StartRunTask(Action<CancellationToken> action)
{
CTSRun?.Cancel();
CTSRun = new CancellationTokenSource();
Task.Run(() =>
{
try
{
action(CTSRun.Token);
}
catch (OperationCanceledException)
{
// Ignore
}
});
}
/// <summary>
/// Helper to be used within a RunTask action, waits for a delay and checks for cancellation
/// </summary>
/// <param name="delay">The delay</param>
/// <param name="ct">The cancellation token</param>
internal void DelayRunTaskAndCheckCancellation(TimeSpan delay, CancellationToken ct)
{
Task.Delay(delay).Wait(ct);
ct.ThrowIfCancellationRequested();
}
/// <summary>
/// Stops the currently running RunTask
/// </summary>
internal void StopAfterTask()
{
CTSAfter?.Cancel();
}
/// <summary>
/// Stops the currently running RunTask
/// </summary>
internal void StopRunTask()
{
CTSRun?.Cancel();
}
public virtual void Idle(IEntities entities, IScheduler scheduler)
{
}
}
}

View File

@@ -0,0 +1,26 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace NetDaemonApps.DeviceControl.Devices
{
internal class Deengph001 : DeviceControl
{
public Deengph001(IHaContext haContext, IScheduler scheduler)
: base(haContext, scheduler)
{
}
public override void Idle(IEntities entities, IScheduler scheduler)
{
entities.Sensor.Deengph001BatteryLevel.StateChanges()
.Throttle(TimeSpan.FromSeconds(1), scheduler)
.Where(x => x.Old?.State >x.New?.State && x.New?.State <= 25)
.Subscribe(x => new NotifyServices(haContext)
.Whatsapp(x.Entity.EntityId.ToString() + Environment.NewLine + "Akku bei " + x.New.State + " %"));
}
}
}

View File

@@ -0,0 +1,16 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace NetDaemonApps.DeviceControl.Devices
{
internal class Deengph002 : DeviceControl
{
public Deengph002(IHaContext haContext, IScheduler scheduler)
: base(haContext, scheduler)
{
}
}
}

View File

@@ -0,0 +1,38 @@
using NetDaemonInterface;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
namespace NetDaemonApps.DeviceLib.UseeLink
{
public class SwitchedSocket : SocketBase
{
private BackgroundWorker worker;
public SwitchedSocket()
{
worker = new BackgroundWorker();
worker.DoWork += Worker_DoWork;
}
private int SwitchOnDelayTime
{
get { throw new NotImplementedException(); }
set { throw new NotImplementedException(); }
}
private void Worker_DoWork(object? sender, DoWorkEventArgs e)
{
Thread.Sleep(SwitchOffTime);
Outputs.ForEach(x => x.TurnOff());
}
/// <summary>
/// Steckdose nach Zeit x ms ausschalten.
/// </summary>
public int SwitchOffTime { get; set; } = 250;
}
}

View File

@@ -0,0 +1,111 @@
using NetDaemon.HassModel.Entities;
using NetDaemonInterface;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
namespace NetDaemonApps.DeviceLib
{
public class SocketBase
{
private Task switchOnSockets;
private CancellationTokenSource cts;
public SocketBase()
{
// worker.DoWork += Worker_DoWork;
cts = new CancellationTokenSource();
}
/// <summary>
///
/// </summary>
/// <param name="entities">Entitäten von HA</param>
/// <param name="dataElement">Daten aus dem CallService Event</param>
/// <returns></returns>
public Task CheckStateAsync(CallServiceDataElement dataElement)
{
return Task.Factory.StartNew(async () =>
{
if (Input != null && Outputs != null) // Aus- bzw Eingänge nicht null
{
if (Outputs.All(x => x.IsOff())) // alle Steckdosen ausgeschalten
{
cts = new CancellationTokenSource();
switchOnSockets = TurnOnSockets(cts.Token);
}
else // min. eine Steckdose eingeschalten
{
if (switchOnSockets != null && !switchOnSockets.IsCompleted && cts != null && !cts.IsCancellationRequested)
cts.Cancel();
if (switchOnSockets != null)
Task.WaitAll(switchOnSockets);
Outputs.ForEach(x => x.TurnOff()); // alle Steckdosen ausschalten
}
}
});
}
private Task TurnOnSockets(CancellationToken token = default(CancellationToken))
{
return Task.Factory.StartNew(() =>
{
// alle Steckdosen verzögert einschalten
foreach (var output in Outputs)
{
// Task abbrechen
if (token.IsCancellationRequested)
{
return;
}
// Steckdose einschalten
output.TurnOn();
if (!output.Equals(Outputs.LastOrDefault()))
Thread.Sleep(SwitchOnDelayTime); // Verzögerung
}
if (SwitchOffTime > 0)
{
//Thread.Sleep(SwitchOffTime);
Task.WaitAll(TurnOffSockets(token));
}
});
}
private Task TurnOffSockets(CancellationToken token = default(CancellationToken))
{
return Task.Factory.StartNew(() =>
{
Task.WaitAny(Task.Delay(SwitchOffTime, token));
Outputs.ForEach(x => x.TurnOff());
});
}
/// <summary>
/// Eingangs Entitäten, wobei Entitäten [0] die Master-Entitäten. Mit der Master-Entitäten werden alle Ausgänge verzögert geschalten.
/// </summary>
public InputButtonEntity Input { get; set; }
/// <summary>
/// Ausgangs Entitäten (die geschalten werden sollen)
/// </summary>
public List<LightEntity> Outputs { get; set; }
/// <summary>
/// Verzögerungszeit in ms zwischen den Out-Entitäten, wenn alle geschalten werden sollen
/// </summary>
public int SwitchOnDelayTime { get; set; } = 500;
/// <summary>
/// Steckdose nach Zeit x ms ausschalten.
/// </summary>
public int SwitchOffTime { get; set; } = 250;
}
}

View File

@@ -0,0 +1,14 @@
using NetDaemonInterface;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace NetDaemonApps.DeviceLib.UseeLink
{
public class SmartSurgeProtectorSocket : SocketBase
{
}
}

View File

@@ -0,0 +1,32 @@
using NetDaemon.HassModel.Entities;
namespace NetDaemonApps.Extensions
{
public static class EntityExtensions
{
public static void SetState(this Entity x, IServices services, string state)
{
services.Netdaemon.EntityUpdate(x.EntityId, state: state);
}
public static bool IsOn(this LightEntity x)
{
return x.State == "on";
}
public static bool IsOff(this LightEntity x)
{
return x.State == "off";
}
public static bool IsOn(this SwitchEntity x)
{
return x.State == "on";
}
public static bool IsOff(this SwitchEntity x)
{
return x.State == "off";
}
}
}

View File

@@ -0,0 +1,10 @@
// Common usings for NetDaemon apps
global using HomeAssistantGenerated;
global using Microsoft.Extensions.Logging;
global using NetDaemon.AppModel;
global using NetDaemon.HassModel;
global using NetDaemon.HassModel.Integration;
global using NetDaemonApps.Extensions;
global using System;
global using System.Reactive.Linq;
global using System.Reactive.Concurrency;

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,40 @@
using NetDaemonApps.AreaControl.Areas;
using NetDaemonInterface;
using System.Collections.Generic;
namespace NetDaemonApps.Modules
{
public class AreaCollection : IAreaCollection
{
private readonly Dictionary<AreaControlEnum, IAreaControl> collection = new();
public AreaCollection(IServiceProvider serviceProvider)
{
var haContext = DiHelper.GetHaContext(serviceProvider);
var scheduler = DiHelper.GetScheduler(serviceProvider);
collection.Add(AreaControlEnum.Bath, new Bath(haContext, scheduler));
collection.Add(AreaControlEnum.Bedroom, new Bedroom(haContext, scheduler));
collection.Add(AreaControlEnum.Corridor1stFloor, new Corridor1stFloor(haContext, scheduler));
collection.Add(AreaControlEnum.Corridor2ndFloor, new Corridor2ndFloor(haContext, scheduler));
collection.Add(AreaControlEnum.CorridorGroundFloor, new Corridor1stFloor(haContext, scheduler));
collection.Add(AreaControlEnum.EntranceArea, new EntranceArea(haContext, scheduler));
collection.Add(AreaControlEnum.KidsRoom, new KidsRoom(haContext, scheduler));
collection.Add(AreaControlEnum.Kitchen, new Kitchen(haContext, scheduler));
collection.Add(AreaControlEnum.LivingRoom, new LivingRoom(haContext, scheduler));
collection.Add(AreaControlEnum.Office, new Office(haContext, scheduler));
collection.Add(AreaControlEnum.Studio, new Studio(haContext, scheduler));
collection.Add(AreaControlEnum.TechnicalRoom, new TechnicalRoom(haContext, scheduler));
collection.Add(AreaControlEnum.TmpArea, new TmpArea(haContext, scheduler));
}
public IAreaControl GetArea(AreaControlEnum area)
{
if (!collection.ContainsKey(area))
{
throw new ArgumentException("unknown area");
}
return collection[area];
}
}
}

View File

@@ -0,0 +1,30 @@
using NetDaemonApps.DeviceControl.Devices;
using NetDaemonInterface;
using NetDaemonInterface;
using System.Collections.Generic;
namespace NetDaemonApps.Modules
{
public class DeviceCollection : IDeviceCollection
{
private readonly Dictionary<DeviceControlEnum, IDeviceControl> collection = new();
public DeviceCollection(IServiceProvider serviceProvider)
{
var haContext = DiHelper.GetHaContext(serviceProvider);
var scheduler = DiHelper.GetScheduler(serviceProvider);
collection.Add(DeviceControlEnum.deengph001, new Deengph001(haContext, scheduler));
collection.Add(DeviceControlEnum.deengph002, new Deengph002(haContext, scheduler));
}
public IDeviceControl GetDevice(DeviceControlEnum device)
{
if (!collection.ContainsKey(device))
{
throw new ArgumentException("unknown device");
}
return collection[device];
}
}
}

View File

@@ -0,0 +1,29 @@
using Microsoft.Extensions.DependencyInjection;
namespace NetDaemonApps.Modules
{
public static class DiHelper
{
public static IHaContext GetHaContext(IServiceProvider serviceProvider)
{
var scope = serviceProvider.CreateScope();
var haContext = scope.ServiceProvider.GetService<IHaContext>();
if (haContext == null)
{
throw new Exception("Unable to get correct HaContext");
}
return haContext;
}
public static IScheduler GetScheduler(IServiceProvider serviceProvider)
{
var scope = serviceProvider.CreateScope();
var scheduler = scope.ServiceProvider.GetService<IScheduler>();
if (scheduler == null)
{
throw new Exception("Unable to get correct HaContext");
}
return scheduler;
}
}
}

View File

@@ -0,0 +1,55 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<LangVersion>11.0</LangVersion>
<SatelliteResourceLanguages>de-DE</SatelliteResourceLanguages>
<Nullable>enable</Nullable>
<RootNamespace>NetDaemonApps</RootNamespace>
</PropertyGroup>
<ItemGroup>
<Compile Remove="HomeAssistantGenerated.cs" />
</ItemGroup>
<ItemGroup>
<None Update="$(MSBuildProjectDir)appsettings.Development.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
<CopyToPublishDirectory>Never</CopyToPublishDirectory>
</None>
<None Update="$(MSBuildProjectDir)appsettings.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="$(MSBuildProjectDir)**\*.yaml">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</None>
</ItemGroup>
<Target Name="AfterPublishMessage" AfterTargets="Publish">
<Message Text="Publish done! Copy all content in directory: $(PublishDir) to the `/config/netdaemon3' folder for add-on or your custom folder to deplpoy" Importance="high" />
</Target>
<ItemGroup>
<PackageReference Include="JoySoftware.NetDaemon.AppModel" Version="23.26.0" />
<PackageReference Include="JoySoftware.NetDaemon.Runtime" Version="23.26.0" />
<PackageReference Include="JoySoftware.NetDaemon.HassModel" Version="23.26.0" />
<PackageReference Include="JoySoftware.NetDaemon.Client" Version="23.26.0" />
<PackageReference Include="JoySoftware.NetDaemon.Extensions.Scheduling" Version="23.26.0" />
<PackageReference Include="JoySoftware.NetDaemon.Extensions.Logging" Version="23.26.0" />
<PackageReference Include="JoySoftware.NetDaemon.Extensions.Tts" Version="23.26.0" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="7.0.1" />
<PackageReference Include="Serilog.AspNetCore" Version="7.0.0" />
<PackageReference Include="SkiaSharp" Version="2.88.3" />
<PackageReference Include="SkiaSharp.NativeAssets.Linux.NoDependencies" Version="2.88.3" />
<PackageReference Include="System.Reactive" Version="6.0.0" />
</ItemGroup>
<ItemGroup>
<Folder Include="AreaControl\Areas\" />
<Folder Include="Properties\PublishProfiles\" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\NetDeamonInterface\NetDaemonInterface.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,31 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.6.33815.320
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NetDaemonApps", "NetDaemonApps.csproj", "{FD197591-A2E0-4C6E-A726-1E9C3427E97B}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NetDaemonInterface", "..\NetDeamonInterface\NetDaemonInterface.csproj", "{000DEEE4-DA5C-4B38-A0F5-050934EAAD66}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{FD197591-A2E0-4C6E-A726-1E9C3427E97B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{FD197591-A2E0-4C6E-A726-1E9C3427E97B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FD197591-A2E0-4C6E-A726-1E9C3427E97B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FD197591-A2E0-4C6E-A726-1E9C3427E97B}.Release|Any CPU.Build.0 = Release|Any CPU
{000DEEE4-DA5C-4B38-A0F5-050934EAAD66}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{000DEEE4-DA5C-4B38-A0F5-050934EAAD66}.Debug|Any CPU.Build.0 = Debug|Any CPU
{000DEEE4-DA5C-4B38-A0F5-050934EAAD66}.Release|Any CPU.ActiveCfg = Release|Any CPU
{000DEEE4-DA5C-4B38-A0F5-050934EAAD66}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {458B659E-D336-4D2A-B66C-04D33AD098BC}
EndGlobalSection
EndGlobal

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,12 @@
{
"profiles": {
"NetDaemonApps": {
"commandName": "Project",
"workingDirectory": "$(ProjectDir)",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development",
"DOTNET_ENVIRONMENT": "Development"
}
}
}
}

View File

@@ -0,0 +1,18 @@
# Project template for binary deploy
This is the project template for binary deploy. This allows you to build a binary package and deploy it to NetDaemon.
This is generated using NetDaemon runtime version 3.1 and .NET 7.
## Getting started
Please see [netdaemon.xyz](https://netdaemon.xyz/docs/v3) for more information about getting starting developing apps for Home Assistant using NetDaemon.
Please add code generation features in `program.cs` when using code generation features by removing comments!
## Issues
- If you have issues or suggestions of improvements to this template, please [add an issue](https://github.com/net-daemon/netdaemon-app-template)
- If you have issues or suggestions of improvements to NetDaemon, please [add an issue](https://github.com/net-daemon/netdaemon/issues)
## Discuss the NetDaemon
Please [join the Discord server](https://discord.gg/K3xwfcX) to get support or if you want to contribute and help others.

View File

@@ -0,0 +1,69 @@
using NetDaemon.HassModel.Entities;
using NetDaemonApps.DeviceLib.UseeLink;
using NetDaemonInterface;
using NetDaemonInterface;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace NetDaemonApps.apps;
[NetDaemonApp]
public class CallServiceEventHandlerApp : MyNetDaemonBaseApp
{
private ButtonMapping mapping;
private IAreaCollection AreaCollection { get; }
public CallServiceEventHandlerApp(IHaContext haContext, IScheduler scheduler, ILogger<CallServiceEventHandlerApp> logger, IAreaCollection areaCollection)
: base(haContext, scheduler, logger)
{
//{
//"event_type": "call_service",
//"data": {
// "domain": "light",
// "service": "turn_on",
// "service_data": {
// "color_temp": 370,
// "entity_id": ["light.woonkamer_kamer"]
// }
// }
//}
AreaCollection = areaCollection;
mapping = new(entities);
CallServiceDataElement dataElement = new CallServiceDataElement();
haContext.Events.Where(x => x.EventType == "call_service").Subscribe(x =>
{
try
{
if (x.DataElement.HasValue)
{
dataElement = System.Text.Json.JsonSerializer.Deserialize<CallServiceDataElement>(x.DataElement.Value);
dataElement.Entity = x.DataElement?.GetProperty("service_data").GetProperty("entity_id").ToString();
}
if (dataElement == null || dataElement.Entity == null || dataElement.Service == null)
{
logger.LogWarning($"Unable to parse deconz event: {x.DataElement.Value}");
return;
}
var map = mapping.mapping.SingleOrDefault(x => x.Item2 == dataElement.Entity);
if (map != null)
{
AreaCollection.GetArea(map.Item1).ButtonPressed(map.Item2, dataElement);
}
//else
//{
// logger.LogWarning($"Unmapped button pressed '{dataElement.Entity}'");
//}
}
catch
{
//ignore exceptions
}
});
}
}

View File

@@ -0,0 +1,29 @@
using NetDaemonApps.Modules;
using NetDaemonInterface;
using NetDaemonInterface;
using System.Linq;
namespace NetDaemonApps.apps;
[NetDaemonApp]
public class IdleSettingApp : MyNetDaemonBaseApp
{
private IDeviceCollection DeviceCollection { get; }
public IdleSettingApp(IHaContext haContext, IScheduler scheduler, ILogger<IdleSettingApp> logger, IDeviceCollection deviceCollection)
: base(haContext, scheduler, logger)
{
DeviceCollection = deviceCollection;
entities.Sensor.Deengph001BatteryLevel.StateChanges()
.Throttle(TimeSpan.FromSeconds(1), scheduler)
.Where(x => x.Old?.State > x.New?.State && x.New?.State <= 25)
.Subscribe(x => new NotifyServices(haContext)
.Whatsapp(x.Entity.EntityId.ToString() + Environment.NewLine + "Akku bei " + x.New.State + " %"));
foreach (var device in Enum.GetValues(typeof(DeviceControlEnum)))
{
DeviceCollection.GetDevice((DeviceControlEnum)device).Idle(entities, scheduler);
}
}
}

View File

@@ -0,0 +1,25 @@
using NetDaemonInterface;
namespace NetDaemonApps.apps;
/// <summary>
/// A base for the NetDaemon apps
/// It provides access to all essentials
/// </summary>
public class MyNetDaemonBaseApp
{
internal readonly IHaContext haContext;
internal readonly IScheduler scheduler;
internal readonly ILogger logger;
internal readonly IEntities entities;
internal readonly IServices services;
public MyNetDaemonBaseApp(IHaContext haContext, IScheduler scheduler, ILogger logger)
{
haContext = haContext;
scheduler = scheduler;
logger = logger;
entities = new Entities(haContext);
services = new Services(haContext);
}
}

View File

@@ -0,0 +1,23 @@
using NetDaemonInterface;
namespace NetDaemonApps.apps;
[NetDaemonApp]
public class PersistanceHandlerApp : MyNetDaemonBaseApp
{
public PersistanceHandlerApp(IHaContext haContext, IScheduler scheduler, ILogger<PersistanceHandlerApp> logger)
: base(haContext, scheduler, logger)
{
// Creation is only once but i keep them here just so i know what i created
//_services.Netdaemon.EntityCreate(entityId: "sensor.housestate");
//_services.Netdaemon.EntityCreate(entityId: "sensor.daynight", DayNightEnum.Day.ToString());
//_services.Netdaemon.EntityCreate(entityId: "sensor.daynight_lastdaytrigger", DateTime.Now.ToString(Statics.dateTime_TimeFormat));
//_services.Netdaemon.EntityCreate(entityId: "sensor.daynight_lastnighttrigger", DateTime.Now.ToString(Statics.dateTime_TimeFormat));
//_services.Netdaemon.EntityCreate(entityId: "switch.watchdog_buiten");
//_services.Netdaemon.EntityRemove(entityId: "switch.watchdog_wandlamp");
//_services.Netdaemon.EntityUpdate(entityId: "sensor.daynight_lastdaytrigger", "08:57:58");
//_services.Netdaemon.EntityUpdate(entityId: "sensor.daynight_lastnighttrigger", "16:39:39");
}
}

View File

@@ -0,0 +1,65 @@
using NetDaemon.Extensions.Scheduler;
using NetDaemonApps.Modules;
using NetDaemonInterface;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace NetDaemonApps.apps
{
[NetDaemonApp]
public class Scheduler : MyNetDaemonBaseApp
{
private IAreaCollection AreaCollection { get; }
private NotifyServices notifyServices;
public Scheduler(IHaContext haContext, IScheduler scheduler, ILogger<Scheduler> logger, IAreaCollection areaCollection)
: base(haContext, scheduler, logger)
{
AreaCollection = areaCollection;
notifyServices = new NotifyServices(haContext);
scheduler.Schedule(DateTime.Parse(entities.Sensor.SunNextDawn.State), () => SunDawn()); // Morgendämmerung
scheduler.Schedule(DateTime.Parse(entities.Sensor.SunNextRising.State), () => SunRising()); // Sonnenaufgang
scheduler.Schedule(DateTime.Parse(entities.Sensor.SunNextDusk.State), () => SunDusk()); // Abenddämmerung
scheduler.Schedule(DateTime.Parse(entities.Sensor.SunNextSetting.State), () => SunSetting()); // Sonnenuntergang
}
private void SunDawn()
{
foreach (var area in Enum.GetValues(typeof(AreaControlEnum)))
{
AreaCollection.GetArea((AreaControlEnum)area).SunDawn();
}
}
private async void SunRising()
{
foreach (var area in Enum.GetValues(typeof(AreaControlEnum)))
{
AreaCollection.GetArea((AreaControlEnum)area).SunRising();
}
}
private void SunDusk()
{
foreach (var area in Enum.GetValues(typeof(AreaControlEnum)))
{
AreaCollection.GetArea((AreaControlEnum)area).SunDusk();
}
}
private void SunSetting()
{
foreach (var area in Enum.GetValues(typeof(AreaControlEnum)))
{
AreaCollection.GetArea((AreaControlEnum)area).SunSetting();
}
}
}
}

View File

@@ -0,0 +1,23 @@
{
"Logging": {
"LogLevel": {
"Default": "Debug",
"Microsoft": "Warning"
},
"ConsoleThemeType": "Ansi"
},
"HomeAssistant": {
"Host": "deengha001.duckdns.org",
"Port": 8123,
"Ssl": true,
"Token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJiYWU3YTMwOWNjMzM0Y2VhYTI3MmUwZGIyZjBmYTY0MCIsImlhdCI6MTY4OTI2NzYyOSwiZXhwIjoyMDA0NjI3NjI5fQ.dCWiVbZ2Uhxym-WLFkGGfw5tFXP9My_S7VeXX-Gnwa4"
},
"NetDaemon": {
"ApplicationConfigurationFolder": "./apps"
},
"CodeGeneration": {
"Namespace": "HomeAssistantGenerated",
"OutputFile": "HomeAssistantGenerated.cs",
"UseAttributeBaseClasses" : "false"
}
}

View File

@@ -0,0 +1,39 @@
using System.Reflection;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using NetDaemon.Extensions.Logging;
using NetDaemon.Extensions.Scheduler;
using NetDaemon.Extensions.Tts;
using NetDaemon.Runtime;
using NetDaemonApps.Modules;
using NetDaemonInterface;
// Add next line if using code generator
//using HomeAssistantGenerated;
try
{
Console.WriteLine("Starting v0.0.0");
await Host.CreateDefaultBuilder(args)
.UseNetDaemonAppSettings()
.UseNetDaemonDefaultLogging()
.UseNetDaemonRuntime()
.UseNetDaemonTextToSpeech()
.ConfigureServices((_, services) =>
{
services
.AddAppsFromAssembly(Assembly.GetExecutingAssembly())
.AddNetDaemonStateManager()
.AddNetDaemonScheduler();
services.AddSingleton<IAreaCollection, AreaCollection>();
services.AddSingleton<IDeviceCollection, DeviceCollection>();
})
.Build()
.RunAsync()
.ConfigureAwait(false);
}
catch (Exception e)
{
Console.WriteLine($"Failed to start host... {e}");
throw;
}

View File

@@ -0,0 +1,17 @@
namespace NetDaemonInterface;
public enum AreaControlEnum
{
Bath,
Bedroom,
Corridor1stFloor,
Corridor2ndFloor,
CorridorGroundFloor,
EntranceArea,
KidsRoom,
Kitchen,
LivingRoom,
Office,
Studio,
TechnicalRoom,
TmpArea
}

View File

@@ -0,0 +1,22 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
namespace NetDaemonInterface;
public record CallServiceDataElement
{
[JsonPropertyName("domain")]
public string? Domain { get; init; }
[JsonPropertyName("service")]
public string? Service { get; init; }
[JsonPropertyName("entity_id")]
public string? Entity { get; set; }
}

View File

@@ -0,0 +1,7 @@
namespace NetDaemonInterface;
public enum DeviceControlEnum
{
deengph001,
deengph002,
}

View File

@@ -0,0 +1,104 @@
using NetDaemonInterface;
using System;
using System.ComponentModel;
namespace NetDaemonInterface;
public class EntityEvents : ViewModelBase
{
private int clickCnt;
private System.Timers.Timer timer;
public EntityEvents()
{
Init();
}
public EntityEvents(string varName)
{
Init();
VarName = varName;
}
private void Init()
{
PropertyChanged += CounterClass_PropertyChanged;
timer = new System.Timers.Timer(500);
timer.Elapsed += Timer_Elapsed;
timer.AutoReset = false;
}
private void CounterClass_PropertyChanged(object? sender, PropertyChangedEventArgs e)
{
if (clickCnt == 0)
{
timer.Start();
}
trigger = false;
clickCnt++;
}
private void Timer_Elapsed(object? sender, System.Timers.ElapsedEventArgs e)
{
timer.Stop();
switch (clickCnt)
{
case 1:
if (Click is not null)
Click(this, e);
break;
case 2:
if (DoubleClick is not null)
DoubleClick(this, e);
break;
case 3:
if (TribleClick is not null)
TribleClick(this, e);
break;
}
clickCnt = 0;
}
public event EventHandler? Click;
public event EventHandler? DoubleClick;
public event EventHandler? TribleClick;
private bool trigger;
public bool Trigger
{
get { return trigger; }
set
{
if (trigger != value)
{
trigger = value;
OnPropertyChanged();
}
}
}
private string varName;
public string VarName
{
get { return varName; }
set
{
if (varName != value)
{
varName = value;
OnPropertyChanged();
timer.Stop();
clickCnt = 0;
}
}
}
}

View File

@@ -0,0 +1,6 @@
namespace NetDaemonInterface;
public interface IAreaCollection
{
IAreaControl GetArea(AreaControlEnum area);
}

View File

@@ -0,0 +1,37 @@
using NetDaemonInterface;
namespace NetDaemonInterface;
/// <summary>
/// Interface used for providing inputs/events to area's
/// </summary>
public interface IAreaControl
{
/// <summary>
/// Ein Button wurde betätigt
/// </summary>
/// <param name="ButtonSensor">Sensor, der gedrückt wurde</param>
public void ButtonPressed(string entityId, CallServiceDataElement dataElement);
/// <summary>
/// Morgendämmerung
/// </summary>
public void SunDawn();
/// <summary>
/// Sonnenaufgang
/// </summary>
public void SunRising();
/// <summary>
/// Abenddämmerung
/// </summary>
public void SunDusk();
/// <summary>
/// Sonnenuntergang
/// </summary>
public void SunSetting();
}

View File

@@ -0,0 +1,8 @@
using NetDaemonInterface;
namespace NetDaemonInterface;
public interface IDeviceCollection
{
IDeviceControl GetDevice(DeviceControlEnum area);
}

View File

@@ -0,0 +1,18 @@
using HomeAssistantGenerated;
using NetDaemonInterface;
using System.Reactive.Concurrency;
namespace NetDaemonInterface;
/// <summary>
/// Interface used for providing inputs/events to area's
/// </summary>
public interface IDeviceControl
{
/// <summary>
/// Werteänderung
/// </summary>
public void Idle(IEntities entities, IScheduler scheduler);
}

View File

@@ -0,0 +1,27 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<LangVersion>11.0</LangVersion>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<Compile Include="..\NetDaemonApps\HomeAssistantGenerated.cs" Link="HomeAssistantGenerated.cs" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="JoySoftware.NetDaemon.AppModel" Version="23.26.0" />
<PackageReference Include="JoySoftware.NetDaemon.Client" Version="23.26.0" />
<PackageReference Include="JoySoftware.NetDaemon.Extensions.Logging" Version="23.26.0" />
<PackageReference Include="JoySoftware.NetDaemon.Extensions.Tts" Version="23.26.0" />
<PackageReference Include="JoySoftware.NetDaemon.HassModel" Version="23.26.0" />
<PackageReference Include="JoySoftware.NetDaemon.Extensions.Scheduling" Version="23.26.0" />
<PackageReference Include="JoySoftware.NetDaemon.HassModel.Integration" Version="23.26.0" />
<PackageReference Include="JoySoftware.NetDaemon.Runtime" Version="23.26.0" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="7.0.1" />
<PackageReference Include="Serilog.AspNetCore" Version="7.0.0" />
<PackageReference Include="System.Reactive" Version="6.0.0" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,16 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace NetDaemonInterface;
public static class StateEnum
{
public static readonly string Press = "press";
public static readonly string TurnOn = "turn_on";
public static readonly string TurnOff = "turn_off";
}

View File

@@ -0,0 +1,45 @@
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace NetDaemonInterface
{
public class ViewModelBase : INotifyPropertyChanged
{
/// <summary>
/// Multicast event for property change notifications.
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// Checks if a property already matches the desired value. Sets the property and
/// notifies listeners only when necessary.
/// </summary>
/// <typeparam name="T">Type of the property.</typeparam>
/// <param name="storage">Reference to a property with both getter and setter.</param>
/// <param name="value">Desired value for the property.</param>
/// <param name="propertyName">Name of the property used to notify listeners.This
/// value is optional and can be provided automatically when invoked from compilers that
/// support CallerMemberName.</param>
/// <returns>True if the value was changed, false if the existing value matched the
/// desired value.</returns>
protected virtual bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = null)
{
if (object.Equals(storage, value)) return false;
storage = value;
// Log.DebugFormat("{0}.{1} = {2}", this.GetType().Name, propertyName, storage);
this.OnPropertyChanged(propertyName);
return true;
}
/// <summary>
/// Notifies listeners that a property value has changed.
/// </summary>
/// <param name="propertyName">Name of the property used to notify listeners. This
/// value is optional and can be provided automatically when invoked from compilers
/// that support <see cref="CallerMemberNameAttribute"/>.</param>
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}