AGB  ·  Datenschutz  ·  Impressum  







Anmelden
Nützliche Links
Registrieren
Zurück Delphi-PRAXiS Programmierung allgemein Cross-Platform-Entwicklung Android AlarmManager periodisch einen Event bekommen
Thema durchsuchen
Ansicht
Themen-Optionen

Android AlarmManager periodisch einen Event bekommen

Ein Thema von Kostas · begonnen am 23. Jul 2018 · letzter Beitrag vom 2. Aug 2018
Antwort Antwort
Kostas

Registriert seit: 14. Mai 2003
Ort: Gerstrhofen
1.095 Beiträge
 
Delphi 10 Seattle Enterprise
 
#1

Android AlarmManager periodisch einen Event bekommen

  Alt 23. Jul 2018, 11:39
Hallo Zusammen,

es gibt relativ wenig Informationen darüber wie der AlarmManager genutzt werden kann.
Ich habe zwei russische Berichte darüber gefunden, allerdings für XE5 und die passen auch nicht zu mein Vorhaben.
Ich möchte das der AlarmManager alle 30 Sekunden ein Event feuert. Über den AlarmManager deshalb weil wen die App beendet
wird würde der AlarmManager die App starten und den event feuern. Genau darum geht es.

Dazu muss man ein BroadcastReceiver registrieren der auf den Event reagiert. Doch wie registriert man so ein BroadcastReceiver und wie setzt man die Filter damit ein Event gefeuert wird?


Das sind meine Code schnipsel bis jetzt.
Delphi-Quellcode:

const intentAction = 'de.firma.action.CHECK_ACTIVE';


function TFormMain.getPendingIntent:JPendingIntent;
var intent : JIntent;
begin

  intent := TJIntent.Create;
  intent.setAction( StringToJString( intentAction ) );

  Result := TJPendingIntent.JavaClass.getBroadcast( TAndroidHelper.Context, 0, intent, 0 );
end;




function TFormMain.DateTimeLocalToUnixMSecGMT(const ADateTime: TDateTime): Int64;
begin
  Result := DateTimeToUnix(ADateTime) * MSecsPerSec - Round(TTimeZone.Local.UtcOffset.TotalMilliseconds);
end;



procedure TFormMain.setAlarm;
var penIntent : JPendingIntent;
begin
  penIntent := getPendingIntent;

  TAndroidHelper.AlarmManager.cancel( penIntent );
  TAndroidHelper.AlarmManager.&set(TJAlarmManager.JavaClass.RTC_WAKEUP, DateTimeLocalToUnixMSecGMT(IncSecond(Now,30)), penIntent );

end;

Gruß Kostas
  Mit Zitat antworten Zitat
knaeuel

Registriert seit: 2. Jul 2007
110 Beiträge
 
Delphi 10.3 Rio
 
#2

AW: Android AlarmManager periodisch einen Event bekommen

  Alt 26. Jul 2018, 11:40
Hi Kostas,

ich beschäftige mich zur Zeit auch mit dem Alarm Manager.
Bei meinem Projekt geht es darum, quasi eine Weckfunktion mit einzubauen.

Das Weckerklingeln an sich ist kein Problem, aber dabei auch den Bildschirm einzuschalten und mal eben am Lockscreen vorbei das Formular mit dem aus-Knopf anzuzeigen, ist problematisch.

Ein Ansatz wäre für mich, mit dem Alarmmanager einen System-Wecker einzutragen. Dann kümmert sich das System mit dem normalen Wecker um den Lockscreen und alles ist gut.

Ich werte aktuell den Code von 2 Beispiel-Apps aus dem Android Studio aus.

Ich habe schonmal folgende Frage(n):
  1. Die Action "const intentAction = 'de.firma.action.CHECK_ACTIVE';" - geht das so? Oder muss immer eine Action aus der Liste der Action-Konstanten vom Intent-Interface genutzt werden? (siehe Link zu Android Developers - Intent - Constants)
  2. Ich verstehe die Zeitangaben nicht so ganz. Zunächst dachte ich, es handelt sich einfach um UNIX-Zeitangaben. Aber wenn ich manuell einen Wecker eintrage (mit der Uhr-App vom System) und den dann mit dem folgenden Code einlese (das hat schon geklappt), dann rechnet Delphi die Zeitangabe mit UnixToDateTime in ein Datum im Jahr 2035 um, obwohl der Wecker auf "morgen früh um 7 Uhr" gestellt war.

Code zum Einlesen eines gestellten Weckers:
Delphi-Quellcode:
procedure Wecker_Einlesen;
var alarmman1:JAlarmManager;
    alarmclockinfo1:JAlarmManager_AlarmClockInfo;
    pendingintent1:JPendingIntent;
    triggertime1:int64;
begin
  alarmman1:=TAndroidhelper.AlarmManager;

  //Daten eines gestellten Weckers einlesen: (funzt, aber der Weckzeitpunkt liegt scheinbar im Jahr 2035, obwohl eigentlich morgen)
  alarmclockinfo1:=alarmman1.getNextAlarmClock;
  if alarmclockinfo1<>nil then
  begin
    pendingintent1:=alarmclockinfo1.getShowIntent;
    triggertime1:=alarmclockinfo1.getTriggerTime;
    ShowMessage('Der Wecker: '+DateTimeToStr(UnixToDateTime(triggertime1))); //2035?? dann komm ich zu spät
  end;
end;
Nebenbei: hier habe ich, genau wie du, einfach über "TAndroidhelper.AlarmManager" auf den AlarmManager zugegriffen. In der einen Java-App hieß es, man müsse den System-Service AlarmManager vom System abholen, also so (bzw. so wie in der folgenden Procedure):
Delphi-Quellcode:
java_obj:=TAndroidHelper.Context.getSystemService(TJContext.JavaClass.ALARM_SERVICE);
alarmman:=TJAlarmManager.Wrap((java_obj as ILocalObject).GetObjectID);
jetzt kommt der Delphi Code, den ich aus der Java-App portiert habe. Die Original-Java-Zeilen sind alle noch mit drin. Der englische Kommentar stammt komplett aus der Java-App. Netterweise habe ich die eingebundenen Units mitangegeben (blöde Sucherei ersparend):
(leider noch nicht kompilierbar)

Delphi-Quellcode:
uses System.DateUtils, Androidapi.JNI.GraphicsContentViewText, Androidapi.JNI.App,
     Androidapi.JNI.JavaTypes, Androidapi.JNIBridge, Androidapi.JNI.PlayServices, Androidapi.Helpers;


procedure TFormWecker.SetAlarm;
var intent : JIntent;
    fragment : JFragment;
    alarmType : integer;
    alarmman : JAlarmManager;
    java_obj : JObject;
    pendingIntent : JPendingIntent;
    ar_result : JActivityRecognitionResult;
    elapsedmillis : int64;
const ACTION_MAIN = 'android.intent.action.MAIN'; //Action ohne weitere Auswirkung
      FLAG_ACTIVITY_REORDER_TO_FRONT = 131072;
      REQUEST_CODE = 0; //jeder andere Wert wäre ebenso ok
      FIFTEEN_SEC_MILLIS = 15000;
      ELAPSED_REALTIME_WAKEUP = $00000002; //Gerät dabei aufwecken
      ELAPSED_REALTIME = $00000003; //Gerät nicht aufwecken
begin
  //Java Code und Kommentare aus Android-Studio Beispiel Projekt
  // BEGIN_INCLUDE (intent_fired_by_alarm)
  // First create an intent for the alarm to activate.
  // This code simply starts an Activity, or brings it [die App?] to the front if it has already
  // been created.
// Intent intent = new Intent(getActivity(), MainActivity.class);
// intent.setAction(Intent.ACTION_MAIN);
// intent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
  // END_INCLUDE (intent_fired_by_alarm)

intent := TJIntent.Create;
intent.setAction(StringToJString(ACTION_MAIN));
intent.setFlags(FLAG_ACTIVITY_REORDER_TO_FRONT);

  // BEGIN_INCLUDE (pending_intent_for_alarm)
  // Because the intent must be fired by a system service from outside the application,
  // it's necessary to wrap it in a PendingIntent. Providing a different process with
  // a PendingIntent gives that other process permission to fire the intent that this
  // application has created.
  // Also, this code creates a PendingIntent to start an Activity. To create a
  // BroadcastIntent instead, simply call getBroadcast instead of getIntent.
// PendingIntent pendingIntent=PendingIntent.getActivity(getActivity(),REQUEST_CODE,intent,0);
pendingIntent:=TJPendingIntent.JavaClass.getActivity(fragment.getActivity,REQUEST_CODE,intent,0);

  // END_INCLUDE (pending_intent_for_alarm)

  // BEGIN_INCLUDE (configure_alarm_manager)
  // There are two clock types for alarms, ELAPSED_REALTIME and RTC.
  // ELAPSED_REALTIME uses time since system boot as a reference, and RTC uses UTC (wall
  // clock) time. This means ELAPSED_REALTIME is suited to setting an alarm according to
  // passage of time (every 15 seconds, 15 minutes, etc), since it isn't affected by
  // timezone/locale. RTC is better suited for alarms that should be dependant on current
  // locale.

  // Both types have a WAKEUP version, which says to wake up the device if the screen is
  // off. This is useful for situations such as alarm clocks. Abuse of this flag is an
  // efficient way to skyrocket the uninstall rate of an application, so use with care.
  // For most situations, ELAPSED_REALTIME will suffice.
// int alarmType = AlarmManager.ELAPSED_REALTIME;
// final int FIFTEEN_SEC_MILLIS = 15000;
alarmType:=ELAPSED_REALTIME_WAKEUP;

  // The AlarmManager, like most system services, isn't created by application code, but
  // requested from the system.
// AlarmManager alarmManager = (AlarmManager)
// getActivity().getSystemService(getActivity().ALARM_SERVICE);
java_obj:=TAndroidHelper.Context.getSystemService(TJContext.JavaClass.ALARM_SERVICE);
alarmman:=TJAlarmManager.Wrap((java_obj as ILocalObject).GetObjectID);

  // setRepeating takes a start delay and period between alarms as arguments.
  // The below code fires after 15 seconds, and repeats every 15 seconds. This is very
  // useful for demonstration purposes, but horrendous for production. Don't be that dev.
// alarmManager.setRepeating(alarmType, SystemClock.elapsedRealtime() + FIFTEEN_SEC_MILLIS,
// FIFTEEN_SEC_MILLIS, pendingIntent);
//alarmman.setRepeating(alarmType, JActivityRecognitionResult.getElapsedRealtimeMillis + FIFTEEN_SEC_MILLIS,
// FIFTEEN_SEC_MILLIS, pendingIntent);
ar_result:=TJActivityRecognitionResult.Create;
elapsedmillis:=ar_result.getElapsedRealtimeMillis;
alarmman.setRepeating(alarmType, elapsedmillis + FIFTEEN_SEC_MILLIS,
                      FIFTEEN_SEC_MILLIS, pendingIntent);

  // END_INCLUDE (configure_alarm_manager);
end;
(leider noch nicht kompilierbar)
Die Procedure soll einen Alarm erzeugen, der sich alle 15 Sekunden wiederholt.

Man beachte hier die Action "ACTION_MAIN" - diese Action hat keine zusätzliche Auswirkung.
Das Flag "FLAG_ACTIVITY_REORDER_TO_FRONT" soll wohl dafür sorgen, dass die App bei jedem Alarm in den Vordergrund gebracht wird.

Ansonsten passiert gar nichts

Dieser Alarm soll sich an der "ELAPSED_REALTIME" orientieren. Das ist die vergangene Zeit seit Systemstart. Die Konstantenwerte habe ich ebenfalls von Android Developers.

Das Programm stürzt bisher bei allen meinen Versuchen, die Elapsed_Systemtime einzulesen, ab:
Delphi-Quellcode:
//absturz:
//ar_result:=TJActivityRecognitionResult.Create;
//elapsedmillis:=ar_result.getElapsedRealtimeMillis;

//absturz
//java_obj:=TAndroidHelper.Context.getSystemService(TJContext.JavaClass.ACTIVITY_SERVICE);
//ar_result:=TJActivityRecognitionResult.Wrap((java_obj as ILocalObject).GetObjectID);
//elapsedmillis:=ar_result.getElapsedRealtimeMillis;

//absturz:
//java_obj:=TAndroidHelper.Context.getSystemService(TJContext.JavaClass.LOCATION_SERVICE);
//location:=TJLocation.Wrap((java_obj as ILocalObject).GetObjectID);
//elapsedmillis:=location.getElapsedRealtimeNanos * 1000000;
Hast du inzwischen mehr herausgefunden?

Gruß,
knaeuel/Wolfgang
Wolfgang

Geändert von knaeuel (26. Jul 2018 um 12:50 Uhr) Grund: fehlende Portierung ergänzt
  Mit Zitat antworten Zitat
Kostas

Registriert seit: 14. Mai 2003
Ort: Gerstrhofen
1.095 Beiträge
 
Delphi 10 Seattle Enterprise
 
#3

AW: Android AlarmManager periodisch einen Event bekommen

  Alt 26. Jul 2018, 17:32
Für deinen Fall habe in in meinem Sammelsurium ein Beispiel.

http://yaroslavbrovin.ru/using-alarm...on-android-ru/
https://forums.embarcadero.com/messa...ssageID=772040

Für meinen Fall haben fast eine Lösung. Ich melde mich....
Gruß Kostas
  Mit Zitat antworten Zitat
knaeuel

Registriert seit: 2. Jul 2007
110 Beiträge
 
Delphi 10.3 Rio
 
#4

AW: Android AlarmManager periodisch einen Event bekommen

  Alt 27. Jul 2018, 10:11
supi, danke, sieht vielversprechend aus!
Wolfgang
  Mit Zitat antworten Zitat
knaeuel

Registriert seit: 2. Jul 2007
110 Beiträge
 
Delphi 10.3 Rio
 
#5

AW: Android AlarmManager periodisch einen Event bekommen

  Alt 27. Jul 2018, 14:03
Mit dem Tipp (russische Anleitung "Erstellen einer Java-BroadcastReceiver-Klasse für Delphi") habe ich leider leichte Probleme. Ich habe die dort abgedruckte batch-Datei fast in jeder zeile angepasst, damit es lauffähig ist.

Probleme habe ich nun bei Schritt 3 in der Batch-Datei:
Code:
REM @echo off
 
setlocal
 
REM Pfad zum Android SDK
if x%ANDROID% == x set ANDROID=C:\Users\Public\Documents\Embarcadero\Studio\19.0\CatalogRepository\AndroidSDK-2433_19.0.29899.2631

REM Pfad zum Java-Compiler
set JAVAC_DIR="C:\Program Files\Java\jdk1.8.0_60\bin"

REM Pfad zur Android-Plattform
set ANDROID_PLATFORM=%ANDROID%\platforms\android-22

set DX_LIB=%ANDROID%\build-tools\22.0.1\lib

set EMBO_DEX="C:\Program Files (x86)\Embarcadero\Studio\19.0\lib\android\debug\classes.dex"

set PROJ_DIR=%CD%

echo ==========================================================================================
echo.
echo  1. Compiling the Java service activity source files
echo.
mkdir output 2> nul
mkdir output\classes 2> nul
%JAVAC_DIR%\javac -verbose -Xlint:deprecation -cp %ANDROID_PLATFORM%\android.jar -d "output\classes" "src\com\MNCClasses\AlarmReceiver.java"
 
echo ==========================================================================================
echo.
echo  2. Creating jar containing the new classes
echo.
mkdir output\jar 2> nul
%JAVAC_DIR%\jar cvf %PROJ_DIR%\output\jar\MNCClasses_classes.jar -C output\classes com\MNCClasses\AlarmReceiver.class
 
echo ==========================================================================================
echo.
echo  3. Converting from jar to dex...
echo.
mkdir output\dex 2> nul
java -jar %DX_LIB%\dx.jar --dex --verbose --output=output\dex\MNCClasses_classes.dex --positions=lines output\jar\MNCClasses_classes.jar
REM erzeugt Fehler: com.android.dx.cf.iface.ParseException: bad class file magic (cafebabe) or version (0034.0000)
 
echo ==========================================================================================
echo.
echo Merging dex files
echo.com.android.dx.merge.DexMerger
java -cp %DX_LIB%\dx.jar com.android.dx.merge.DexMerger %PROJ_DIR%\output\dex\classes.dex %PROJ_DIR%\output\dex\MNCClasses_classes.dex %EMBO_DEX%
 
echo Tidying up
echo.
rmdir /s /q output\classes
del output\dex\test_classes.dex
rmdir /s /q output\jar
 
echo ==========================================================================================
echo Now we have the end result, which is output\dex\classes.dex
 
:Exit
 
endlocal
pause
die aktuelle Zeile (unter Schritt 3)
Code:
java -jar %DX_LIB%\dx.jar --dex --verbose --output=output\dex\MNCClasses_classes.dex --positions=lines output\jar\MNCClasses_classes.jar
lautete ursprünglich so:
Code:
call %DX_LIB%\dx.jar --dex --verbose --output=output\dex\MNCClasses_classes.dex output\jar\MNCClasses_classes.jar
mit call kann das doch nicht funzen, oder? damit führt man doch batch-Dateien oder exe oder vergleichbares von Windows aus. Oder gibt es in Java eine call.exe? wenn ja, dann nicht in meiner installation. Egal. Ich vermute, dass der Aufruf mit "java -jar ..." korrekt ist.

Nichtsdestotrotz erzeugt dieser Aufruf immer nur eine Fehlermeldung, die auch schon oben in der Batch-Datei angerissen ist. Die vollständige Meldung lautet:
Code:
processing archive output\jar\MNCClasses_classes.jar...
ignored resource META-INF/MANIFEST.MF
processing com/MNCClasses/AlarmReceiver.class...

UNEXPECTED TOP-LEVEL EXCEPTION:
com.android.dx.cf.iface.ParseException: bad class file magic (cafebabe) or version (0034.0000)
        at com.android.dx.cf.direct.DirectClassFile.parse0(DirectClassFile.java:472)
        at com.android.dx.cf.direct.DirectClassFile.parse(DirectClassFile.java:406)
        at com.android.dx.cf.direct.DirectClassFile.parseToInterfacesIfNecessary(DirectClassFile.java:388)
        at com.android.dx.cf.direct.DirectClassFile.getMagic(DirectClassFile.java:251)
        at com.android.dx.command.dexer.Main.processClass(Main.java:704)
        at com.android.dx.command.dexer.Main.processFileBytes(Main.java:673)
        at com.android.dx.command.dexer.Main.access$300(Main.java:83)
        at com.android.dx.command.dexer.Main$1.processFileBytes(Main.java:602)
        at com.android.dx.cf.direct.ClassPathOpener.processArchive(ClassPathOpener.java:284)
        at com.android.dx.cf.direct.ClassPathOpener.processOne(ClassPathOpener.java:166)
        at com.android.dx.cf.direct.ClassPathOpener.process(ClassPathOpener.java:144)
        at com.android.dx.command.dexer.Main.processOne(Main.java:632)
        at com.android.dx.command.dexer.Main.processAllFiles(Main.java:510)
        at com.android.dx.command.dexer.Main.runMonoDex(Main.java:280)
        at com.android.dx.command.dexer.Main.run(Main.java:246)
        at com.android.dx.command.dexer.Main.main(Main.java:215)
        at com.android.dx.command.Main.main(Main.java:106)
...while parsing com/MNCClasses/AlarmReceiver.class

1 error; aborting
Google hat mich bereits darauf gebracht, dass dies in irgendeiner Form ein Versionskonflikt im Java oder mit Android Versionen sein kann. Aber das hilft mir im Moment wenig. Hat irgendein Java-Profi eine Ahnung, was ich tun kann?

Aktuell scheint Delphi die Android-Platform in der Version 22 zu nutzen und das SDK in Version 2433_19.0.29899.2631, sofern das nicht eine von Delphi erzeugte Nummer ist. Das Java Development Kit trägt die Versionnummer 1.8.0_60

Falls jemand helfen kann und noch weitere Infos erforderlich sind, bitte einfach fragen!
Wolfgang
  Mit Zitat antworten Zitat
Kostas

Registriert seit: 14. Mai 2003
Ort: Gerstrhofen
1.095 Beiträge
 
Delphi 10 Seattle Enterprise
 
#6

AW: Android AlarmManager periodisch einen Event bekommen

  Alt 27. Jul 2018, 15:51
Hi,

brauchst du alles nicht.
In der Demo siehst du unsere Lösung.

-Die App hat zwei aufgaben:
a) nach dem Beenden soll sie sich selbst neu starten.
b) erzeuge alle 10 Sekunden einen Event über den AlarmManager.

Gruß Kostas
Angehängte Dateien
Dateityp: zip SelfStarter.zip (3,3 KB, 76x aufgerufen)
  Mit Zitat antworten Zitat
knaeuel

Registriert seit: 2. Jul 2007
110 Beiträge
 
Delphi 10.3 Rio
 
#7

AW: Android AlarmManager periodisch einen Event bekommen

  Alt 2. Aug 2018, 10:01
heißen Dank! ich schaus mir direkt mal an

edit: habs mir gerade angesehen. Ich hab ein neues Projet erstellt und die miterzeugte Haupt-Unit einfach durch dein Main.pas/Main.fmx ersetzt.

Ein paar Dinge sind mir aber noch unklar. Wie ist das geplante Verhalten der App?

Beispiele:

Ich starte die App, nehme beide Häkchen aus den Checkboxes weg. Dann warte ich den letzten noch geplanten Alarm ab. Jetzt klicke ich auf Alarm. Nach 10 Sekunden taucht der Alarm im Log auf.

Jetzt klicke ich wieder auf Alarm und minimiere die App. Nach 10 Sekunden tut sich erstmal nichts. Bringe ich die App wieder in den Vordergrund, sehe ich im Log, dass die App das Event empfangen hat. Allerdings wurde sie nicht in den Vordergrund gebracht.
Frage 1: Sollte das so sein?

Jetzt klicke ich wieder auf Alarm und beende die App diesmal. Nach 10 Sekunden tut sich nichts. Starte ich die App wieder, sehe ich nur die mitgeloggten Ereignisse (willTerminate fürs beenden und FinishedLaunching für das manuelle neustarten).
Frage 2: Sollte die App vom System gestartet werden, sobald der Alarm ausgelöst wird oder nicht?

In jedem Fall soll die App nach 30 Sekunden neu starten und das klappt bei mir auch nicht.


Ich schaue gerade, ob es an Berechtigungen (Projektoptionen Delhpi) liegen könnte.
Welche Berechtigungen werden benötigt?

"Alarm festlegen" - war bisher aus und trotzdem hat es ja zumindest teilweise funktioniert. ich schalts an.
"Kalender lesen"/"Kalender schreiben" - sind standardmäßig an. ich lasse es an. (ka, obs was damit zu tun hat)
"Sticky Intents als Broadcast senden" - ist standardmäßig aus. Ich schalte es mal ein.
"Tastensperre deaktivieren" - Standard aus, ich schalts ein.
"Wakelock" - standard aus, ich schalts ein.
Frage 3: Hast du noch irgendwelche anderen Dinge eingestellt oder Berechtigungen gesetzt oder sonst irgendwas?

Vielen Dank nochmal!
Wolfgang

Geändert von knaeuel ( 2. Aug 2018 um 14:15 Uhr)
  Mit Zitat antworten Zitat
knaeuel

Registriert seit: 2. Jul 2007
110 Beiträge
 
Delphi 10.3 Rio
 
#8

AW: Android AlarmManager periodisch einen Event bekommen

  Alt 2. Aug 2018, 14:41
kleiner Nachtrag: soeben habe ich festgestellt, dass die App doch neu startet - aber erst nach 3 Minuten und nicht nach 30 Sekunden. Und siehe da, der Aufruf lautet ja auch "AppRestartIncSecondes(180);", wobei dann natürlich nicht der Defaultwert von 30 Sekunden genutzt wird
Wolfgang
  Mit Zitat antworten Zitat
Antwort Antwort


Forumregeln

Es ist dir nicht erlaubt, neue Themen zu verfassen.
Es ist dir nicht erlaubt, auf Beiträge zu antworten.
Es ist dir nicht erlaubt, Anhänge hochzuladen.
Es ist dir nicht erlaubt, deine Beiträge zu bearbeiten.

BB-Code ist an.
Smileys sind an.
[IMG] Code ist an.
HTML-Code ist aus.
Trackbacks are an
Pingbacks are an
Refbacks are aus

Gehe zu:

Impressum · AGB · Datenschutz · Nach oben
Alle Zeitangaben in WEZ +1. Es ist jetzt 07:09 Uhr.
Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz