diff --git a/Pilz.Net/Api/ApiServer.cs b/Pilz.Net/Api/ApiServer.cs
index a7681c0..09bb9c6 100644
--- a/Pilz.Net/Api/ApiServer.cs
+++ b/Pilz.Net/Api/ApiServer.cs
@@ -1,6 +1,7 @@
using Castle.Core.Logging;
using Pilz.Data;
using Pilz.Extensions.Reflection;
+using Pilz.Jobs;
using System.Diagnostics.CodeAnalysis;
using System.Net;
using System.Reflection;
@@ -55,6 +56,7 @@ public class ApiServer : IApiServer
public int MaxConcurentConnections { get; set; } = 5;
public IDataManager Manager => GetManager();
public bool ThreadedDataManager { get; set; }
+ public JobCenter Jobs { get; } = new();
public ApiServer(string apiUrl) : this(apiUrl, null)
{
diff --git a/Pilz.Net/Api/IApiServer.cs b/Pilz.Net/Api/IApiServer.cs
index f9634d3..bd87a39 100644
--- a/Pilz.Net/Api/IApiServer.cs
+++ b/Pilz.Net/Api/IApiServer.cs
@@ -1,5 +1,6 @@
using Castle.Core.Logging;
using Pilz.Data;
+using Pilz.Jobs;
namespace Pilz.Net.Api;
@@ -18,6 +19,7 @@ public interface IApiServer
IDataManager Manager { get; }
string ApiUrl { get; }
bool EnableAuth { get; set; }
+ public JobCenter Jobs { get; }
IApiMessageSerializer Serializer { get; }
ILogger Log { get; set; }
diff --git a/Pilz.Net/Pilz.Net.csproj b/Pilz.Net/Pilz.Net.csproj
index c46854a..43d32dd 100644
--- a/Pilz.Net/Pilz.Net.csproj
+++ b/Pilz.Net/Pilz.Net.csproj
@@ -8,7 +8,7 @@
- 2.7.0
+ 2.8.0
diff --git a/Pilz/Jobs/Job.cs b/Pilz/Jobs/Job.cs
new file mode 100644
index 0000000..6be1da7
--- /dev/null
+++ b/Pilz/Jobs/Job.cs
@@ -0,0 +1,34 @@
+namespace Pilz.Jobs;
+
+public class Job
+{
+ private readonly Action? handler;
+
+ public virtual string Name { get; }
+ public virtual DateTime LastExecution { get; internal set; }
+ public virtual TimeSpan Interval { get; }
+
+ public Job()
+ {
+ }
+
+ public Job(string name) : this()
+ {
+ Name = name;
+ }
+
+ public Job(string name, TimeSpan interval) : this(name)
+ {
+ Interval = interval;
+ }
+
+ public Job(string name, TimeSpan interval, Action handler) : this(name, interval)
+ {
+ this.handler = handler;
+ }
+
+ public virtual void Execute(JobContext context)
+ {
+ handler?.Invoke(context);
+ }
+}
diff --git a/Pilz/Jobs/JobCenter.cs b/Pilz/Jobs/JobCenter.cs
new file mode 100644
index 0000000..500d0e0
--- /dev/null
+++ b/Pilz/Jobs/JobCenter.cs
@@ -0,0 +1,55 @@
+namespace Pilz.Jobs;
+
+public class JobCenter
+{
+ private readonly HashSet jobs = [];
+ private readonly System.Timers.Timer timerRepeat = new()
+ {
+ AutoReset = false,
+ Interval = 5000,
+ };
+
+ public bool Enabled { get; protected set; }
+
+ public JobCenter()
+ {
+ timerRepeat.Elapsed += TimerRepeat_Elapsed;
+ }
+
+ private void TimerRepeat_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
+ {
+ var now = DateTime.Now;
+ var jobs = this.jobs.Where(n => n.LastExecution + n.Interval > now);
+
+ foreach (var job in jobs)
+ {
+ job.LastExecution = now;
+ job.Execute(new(this));
+ }
+
+ if (Enabled)
+ timerRepeat.Start();
+ }
+
+ public virtual void AddJob(Job job)
+ {
+ jobs.Add(job);
+ }
+
+ public virtual void RemoveJob(Job job)
+ {
+ jobs.Remove(job);
+ }
+
+ public virtual void Start()
+ {
+ Enabled = true;
+ timerRepeat.Start();
+ }
+
+ public virtual void Stop()
+ {
+ Enabled = false;
+ timerRepeat.Stop();
+ }
+}
diff --git a/Pilz/Jobs/JobContext.cs b/Pilz/Jobs/JobContext.cs
new file mode 100644
index 0000000..4f1ba49
--- /dev/null
+++ b/Pilz/Jobs/JobContext.cs
@@ -0,0 +1,7 @@
+namespace Pilz.Jobs;
+
+public class JobContext(JobCenter jobCenter)
+{
+ public JobCenter JobCenter { get; } = jobCenter;
+ public bool HasError { get; set; }
+}
diff --git a/Pilz/Pilz.csproj b/Pilz/Pilz.csproj
index 7fd3e61..ed00c03 100644
--- a/Pilz/Pilz.csproj
+++ b/Pilz/Pilz.csproj
@@ -5,7 +5,7 @@
latest
enable
annotations
- 2.4.7
+ 2.5.0