Archiv verlassen und diese Seite im Standarddesign anzeigen : Systen zur verwaltung und kontrolle von Sensoren und Aktoren
Nagut, der Titel ist villeicht nur ein teil davon was ich mir überlegt habe, aber ich will hier mal ideen zu diesem Thema sammeln, und dies später dann auch programmtechnisch umsetzen.
Kurz zu dem Hintergrund dazu:
Ich hab dieses Jahr für den RCJ einen Roboter auf Basis des Raspberry Pi entwickelt. Wir haben nur mittelfeld erreicht, aber ich hab bei diesesm Projekt zum ersten mal C++ auch für Roboter verwendet, und hab es allgemein gesehen als sehr hilfreich angesehen ein stabiles System zu bauen. Unter anderem hatte ich z.B. eine Klasse die die Kommunikation mittels I2C geleitet hat und eine andere die einen Roboter im Programm abbildete (alle Sensoren und Aktoren,...). Das Ganze war relativ Demomäßig und noch nicht so umgesetzt dass man von einem wirklich schönem OOP-Entwurf sprechen kann. Auch gab es probleme, weil ich für Änderungen an den Sensoren,... jedes mal neu kompilieren musste. Ich hab also durch diesesen Roboter ein allgmeines Bild was eine einfache Steuerung über einen kleinen Computer benötigen würde.
Meine Idee wäre dabei folgende:
Eine hochdynamische Bibliotek die das programmieren von Robotern wesentlich erleichtern soll (besonders die Sensorkommunikation). Ein paar Punkte die dabei meiner meinung wichtig sind:
Definieren der Sensoren und Bus-Slaves in einem Config-File (z.B.: XML)
Fehlererkennung, ob ein Sensor korrekte Werte liefert und ob er überhaupt verbunden ist
Einbindung von Skriptsprachen wie Lua um den Roboter zu steuern (kein neucompilieren nötig, schnelle änderungen)
Objektorentiert
Es wird vom steuerprogramm nicht der Sensor selber sondern ein Verknüpfter Wert genutzt.
Sensoren & Aktuatoren werden nur dann angesprochen wenn es benötigt wird, oder in einen bestimmten Interval. (Verbesserte Bußnutzung, einfache mischung von Echtzeitnotwendige und unwichtige Systeme)
Einfache möglichkeit aufzeichnungen von den Sensoren zu bekommen
...
Wer sich da nichts vorstellen kann hab ich mir mal ein Beispiel überlegt:
Wir haben eine kleine Drohne mit dem Raspberry Pi gebaut (Mikrokopter oder Modellflieger ist egal), der hat diverse Sensore die angeschlossen sind. Daber werden verschiedene Bussysteme verwendet:
I2C
RS232
CAN
SPI
...
Diese Drohne hat natürlich Sensoren die an den unterschiedlichen Bussystemen angeschlossen sind wie:
GPS
Luftdruck
Kompass
Gyrometer
Beschleunigungssensor
Neigungssensor (z.B. Infrarotsensoren)
...
Durch das System sollten sich dann unter anderem solche Dinge ergeben:
Wer sich ein wenig auskennt weiß dass GPS auch Höheninformationen, Richtung, Geschwindikeit übertragen kann, aber teilweise wesentlich ungenauer als ein spezieller Sensor ist. Man kann jetzt natürlich z.B. nur den Luftdrucksensor nehmen, oder wie ich diese miteinander verknüpfen (mit berücksichtigung deren Genauigkeit und zuverlässigkeit). Wenn jetzt 1. Sensor falsche Werte ausgiebt oder nicht mehr funktioniert kann einfach der andere benutzt werden.
Dadurch hat man jetzt eine gewisse redundanz, die z.B: durch den Gyrometer und Beschleunigungssensor noch erhöht werden könnte. Das kann jeder für sein projekt natürlich selber proggen, aber eine Fertige lösung wäre natürlich wesentlich vorteilhafter.
Wenn man das jetzt so weiterspielt sieht man dass die Steuerungssoftware auf wesentlich bessere Informationen zugreifen kann als wenn sie nur die Sensoren auswerten. Auch ist die Auswertung durch ein definiertes Modell wesentlich einfacher und man kann schneller und sicher andere Sensoren implementieren.
Wenn man neue Sensoren hinzufügen will oder sie mit einer anderen Adresse, etc. anschließt ändert man einfach ein config-file und das Programm arbeitet danach damit. Man muss keine umfangreichen Änderungen machen, und auch nicht das Programm neu compilieren.
Nicht zeitkritische Funktionen werden/können auf skripsprachen ausgelagert werden. Dadurch sind änderungen einfach und schneller vorzunehmen.
Wenn jetzt ein Sensor ausfällt oder nicht eingebaut ist wird wenn möglich auf eine alternative oder eine interpolation mit anderen Sensoren umgeschaltet. Das Steuerprogramm kann dies auch erkennen und bekommt auch die neuen Toleranzen der Daten, etc. wenn benötigt mitgeteilt.
Da die config-Files sehr umfangreich werden können (z.B: einbauposition, technische Daten und Rahmenbedingunge,...) könnte das ganze auch dazu genutzt werden den Roboter in einem Simulator mit realistischen Bedingungen zu konfrontieren (incl. toleranzabweichungen, störungen,...)
Ich hoffe ihr versteht was ich mit so einen System gerne bezwecken will. Ich würde gerne eure ideen und vorschläge dazu hören, und ob sowas eurer meinung eigentlich sinvoll ist.
mfg, pointhi
NACHTRAG:
Ich hab gerade ROS wiederentdeckt, leider bin ich relativ unschlüssig was ROS jetzt im allgemeinen alles beherscht und wie es aufgebaut ist. Es schaut aber schon sehr nach dem aus was ich gerne schaffen möchte.
NACHTRAG
Das System ist bereits aktiv in enwicklung! Zumindestens der allgemeine Hardwareabstraktionsteil.
https://github.com/pointhi/OpenSensorSystem (https://github.com/pointhi/OpenSensorSystem)
Schade das sich sich dazu noch niemand geäußert hat.
Ich hab ein Demodatei erstellt, die eine erste idee darstellt, wie man die sensoren und systeme konfigurieren können soll. Vom XML-Schema hängt viel ab, wie allgemein so ein programm sein wird. Jedenfalls hab ich heute das erste mal ein LUA-Programm mit C++ Interpretiert, und bin davon überzeugt dass sich Skriptsprachen für die Sensorauswertung von komplexeren Protokollen eignet. Immerhin ist es nicht sehr dynamisch wenn man für jedes kleine Protokoll den Sourcecode erweitern und neu compilieren muss.
Ich bin mir so gut wie sicher dass diese version von XML-Schema sicher noch ein paar große lücken aufweist. Besonders in der Art wie die Daten ausgelesen werden, und mit welchen Timing (z.b. muss ich in ein kompassmodul von mir zuerst 3 Befehle im 1ms takt schreiben, bevor ich davon lesen kann). Der Zugriff auf die Daten würde warscheinlich über std::set mit den Namen im XML-File geschehen. z.B. wie
double Altitude = Sensor["GPS_Altitude"].GetValue();
<?xml version="1.0"?>
<bus-controller bussystem="i2c">
<name>I2C</name>
<device>/dev/i2c-1</device>
<interrupt-pin>18</interrupt-pin>
<clock>100kHz</clock>
<enabled>yes</enabled>
</bus-controller>
<bus-controller bussystem="rs232">
<name>COM0</name>
<device>/dev/ttyAMA0</device>
<baudrate>9600</baudrate>
<parity-bit>no</parity-bit>
<stop-bits>1</stop-bits>
</bus-controller>
<i2c-slave bus-controller="I2C">
<name>PCF8574A</name>
<adress>0x38</adress>
<generate-interrupt>yes</generate-interrupt>
<maxclock>100kHz</maxclock>
<rw-instructions>
<read bytes="1" position="0"/>
</rw-instructions>
<sensor datatype="boolean">
<name>Sensor1</name>
<data>0.0</data> <!-- Byte 0, Bit 0-->
<invert>yes</invert>
<refresh-time>100ms</refresh-time>
</sensor>
</i2c-slave>
<sensor i2c-slave="PCF8574A" datatype="boolean">
<name>Sensor1</name>
<data>
<databit>0.1</databit> <!-- Byte 0, Bit 1-->
</data>
<refresh-time>50Hz</refresh-time>
</sensor>
<rs232-slave bus-controller="COM0">
<name>GPS_0</name>
<rw-instructions>
<read savetype="ringbuffer" bytes="100" startbyte="$" stopbyte="\r\n"/>
</rw-instructions>
</rs232-slave>
<sensor rs232-slave="GPS_0" datatype="float">
<name>GPS_Altitude</name>
<data>
<script language="lua" file="gps.lua" function="gps_height"/>
</data>
</sensor>
<sensor rs232-slave="GPS_0" datatype="float">
<name>GPS_Speed</name>
<data>
<script language="lua" file="gps.lua" function="gps_speed"/>
</data>
</sensor>
Was sagt ihr dazu? Ich finde es ist sicher nicht die einfachste möglichkeit, aber es geht mir besonders auch um leistungsfähigkeit. Es soll später dann so sein, dass man einfach den sensor definiert, und er danach im Programm verwendet werden kann.
mfg, pointhi
TheDarkRose
26.04.2013, 11:35
Die Idee ist cool, auch wenn es ziemlich umfangreich sein wird.
Aber ein Punkt. Kein XML!!!!11elf. So viel Overhead und doch nicht wirklich lesbar. Wie wärs mit YAML? Ist ganz gemütlich und super lesbar was Konfiguration anbetrifft.
Ich hab noch mit ein paar Leuten deswegen geredet, und auch ich denke dass XML für den Anfang das beste ist. Auserdem wird die Bibliotek so gestaltet sein dass jeder eigene Auswertfunktionen hinzufügen kann.
Ich hab heute ein System entwickelt, wie das Klassensystem für diese Aufgaben aufgebaut werden sein muss. Erste tests mit der XML und Lua Bibliotek waren auch erfolgreich. Das ganze wird standardmäßig als DLL compiliert werden und kann dann eigebunden werden. Unter Linux sollte es auch funktionieren, hab ich aber noch nicht getestet. Ein Compilieren direkt in den Quellcode oder als Statische Library sollte meines Wissens auch möglich sein.
Wenn das Klassendesign feststeht und erste ergebnisse da sind werde ich das Projekt auf GitHub veröffentlichen.
mfg, pointhi
TheDarkRose
26.04.2013, 21:51
Gegen den Rest sagte ich ja nix. Aber ehrlich, ich hab schon mit einigen Frameworks gearbeitet und ich sage dir, XML Konfigurationen sind für viele ein Fluch, da diese ziemlich schnell aufblähen. Yaml ist einfach, genial, schlank und leicht. Schau es dir mal an.
Oder bau deinen Konfigurationsloader abstrakt auf. Also das es der restlichen Anwendung egal ist, wie die Konfiguration geladen wird. Dann kannst du gegen diese Schnittstelle deinen XML-Loader bauen, und wenn's was wird, können ja bei Bedarf anderen einen Yaml-Loader hinzufügen. Symfony z.B. macht das, und ich find das super.
Genau so habe ich das geplant. Ich übergebe der Hauptobjekt das XML-File, und es gibt knoten auf untergeordnete Klassen weiter die dies wiederum können. Ich werde das ganze vermutlich in eine spezielle klasse packen. Eigentlich sollte die implementation von beliebigen interpretern für dom modelle so einfach möglich sein.
Als info, ich werde vermutlich pugixml (http://pugixml.org/) nutzen um die XML-Dateien auszuwerten. Wenn der Interpreter ähnlich wie dieser aufgebaut ist kann jedes beliebige DOM-Basierendes Dateiformat implementiert werden.
Auch will ich es so machen dass nicht 1. einziges XML-File alles laden muss, sondern dass einzelne Files bestimmte Bereiche hinzufügen können. So wird das ganze auch nicht so lange, sondern kann in kleine happen zerteilt werden.
mfg, pointhi
TheDarkRose
26.04.2013, 22:39
Hmm... Also die hauptapplikation(klasse) sollte gar nichts von einer XML-Datei oder pugixml wissen. Dafür sollte es eine extrige Schicht geben. Als Schnittstelle zur Hauptapplikation gibt diese Schicht wohldefinierte structs oder Klasseninstanzen zurück.
Die Loaderschicht definiert selbst ein Interface nach unten, welches die verschieden Loader zu implementieren haben. Erst hier darunter siedelt sich der XML-Loader an, welcher das Interface implementiert und selbst mit pugixml kommuniziert.
Die Hauptapplikation hat es u. A. nicht zu interessieren wie die Daten gehalten werden, ob diese sofort alle im Speicher sind, oder nachgeladen, bla bla. Alles Verantwortlichkeiten der Loaderschicht ^^
Wenn du willst, können wir mal einen nette Architektur ausarbeiten. Da ich ja beruflich im Bereich Embedded Systems tätig bin, wäre das schon interessant. Muss zugeben, falls es ähnliches schon gibt, ich hab danach noch nicht gesucht, da noch nicht der Bedarf da war. Im Moment alles noch Embedded Qt und ein CoController der die I/Os macht. Aber der Bedarf kommt sicher zukünftig. Voraussetzungen wäre aber eine kommerziell kompatible Open Source Lizenz, wie die MIT oder Apache ^^
Und ein Blick in die gerade von Elektor veröffenlichte EFL wäre sicher auch interessant. Diese ist mehr auf Bare-Metal Controller (also uC ohne Betriebssystem) ausgelegt. Deine Idee geht ja eher zur Integration in Embedded Linux, wenn ich richtig liege?
Ich werd morgen die klasse noch ein wenig überarbeiten und dann eine erste idee hier veröffentlichen.
MIT oder so wäre kein problem, auch wenn ich normalerweise unter der GPL veröffentliche. Ich würde die LGPL nehmen, da dadurch auch kommerzieller nutzen erlaubt ist, und ich das ganze sowieso als dynamische Bibliotek auslegen will.
mfg, pointhi
TheDarkRose
26.04.2013, 23:02
Hmm, hättest mal Zeit eine Architektur auszuarbeiten? Du hast ja schon größere Ideen, und einfach drauf los, könnte etwas schiefgehen ;)
Naja ich finde es schade, wenn interessante Projekte mit der Viralität der GPL das verwenden in kommerziellen Projekten so erschweren. Oft wird darauf vergessen oder unterschätzt, dass halt doch von entsprechenden Firmen Know-How in das Projekt zurück fließen kann. Und LGPL ist auch nicht gerade besser. Besonders da ich bei embedded Projekten statisches Linken sogar bevorzuge. Auch wenn die Binary größer wird und bei Updates alles neu ausgeliefert wird, ist es bei gewissen Projekten einfacher nur ein File auszuliefern.
Das mit der Lizenz ist ja noch nicht fix, ich muss mir die MIT oä. halt mal genau anschauen. Durch OSM bin ich ja auch bei einem Projekt das dabei auch besonders auch komerziell genutzt wird.
Unter der Architektur verstehe ich besonders das System wie die Klassen miteinander kommunizieren und interagieren und deren Schnittstellen. Und das ist meiner meinung auch die Herausforderung dabei. Wenn ich mich irre lasse ich mich gerne belehren.
mfg, pointhi
TheDarkRose
26.04.2013, 23:45
Ja genau das meine ich. Und das die einzelnen Komponenten nicht zu stark untereinander gekoppelt sind.
Ich veröffentliche hier mal mein derzeitiges experimentiermodell:
/*
* File: SensorHandler.hpp
* Author: pointhi
*
* Created on 26. April 2013, 14:13
*/
#ifndef SENSORHANDLER_HPP
#define SENSORHANDLER_HPP
#include <string>
#include <set>
#include <iostream>
// Achtung, Machbarkeitsanalyse, aus einfachheit mit public-variablen gemacht.
namespace SensorHandler {
class Baseclass { // Basisklasse um gennerelle Variablen und virtuelle Funktionen zu deklarieren
public:
std::string Name; // Variable, die jedes Objekt besitzt
Baseclass() {this->Name = "1234";}
void Output() {std::cout << "Hello Name: " << this->Name << std::endl;}
virtual Baseclass* GetItem(std::string Name) = 0;
virtual Baseclass* GetItem(signed int Id) = 0;
virtual signed int GetItemCount( void ) = 0;
// virtual void NewItem(std::string Name, std::string Type) = 0;
};
namespace Hardware {
class I2c : public SensorHandler::Baseclass {
public:
class Slave : public SensorHandler::Baseclass {
public:
Slave() {this->Name = "testclass";}
Baseclass* GetItem(std::string Name) {return NULL;}
I2c::Slave* GetItem(signed int Id) {return NULL;}
signed int GetItemCount( void ) {return 0;}
// void NewItem(std::string Name, std::string Type=NULL);
};
private:
// std::set<I2c::Slave> SlaveList;
I2c::Slave test; // Kindklasse
public:
I2c() {this->Name = "testtest";}
I2c::Slave* GetItem(std::string Name) {return &this->test;}
I2c::Slave* GetItem(signed int Id) {return &this->test;}
signed int GetItemCount( void ) {return 1;}
// void NewItem(std::string Name, std::string Type=NULL);
};
}
}
#endif /* SENSORHANDLER_HPP */
Es ist wirklich quick and dirty, aber es funktioniert derzeit wie gewollt. Aus einfachheit habe ich alles public gemacht, was im späteren Projekt natürlich nicht mehr sein darf. Auch fehlen noch ein paar essentielle klassen. Mir ist es derzeit nur darum gegangen die kommunikation in die subklassen zu testen, und eine sichere Objektverwaltung zu gewährleisten.
Als info, damit ihr euch auskennt wie ich mir das derzeit vorstelle:
virtual Baseclass* GetItem(std::string Name) = 0;
virtual Baseclass* GetItem(signed int Id) = 0;
Gibt einen Pointer auf die nächstfolgede Klasse. So können auch Standardvariablen wie der Name von jeder untergeordneten Klasse aufgerufen werden. In der klasse selber wird nicht die Klasse Baseclass sondern ein darauf aufbauender Typ angegeben. Mit diesem Typ werden auch die Kindelemente in der Klasse gespeichert. So kann diese Klasse auf alle Funktionen der Kindklasse zugreifen, eine allgemeine Funktion die nur durchnavigieren kann und nur auf Standardparameter und funktionen die jede klasse besitzt zugreifen kann. Durch die Spezialisierung wird auch vermieden dass falsche Kindklassen eingebunden werden können. (Z.b. das das I2C-System eine kindklasse von einem I2C-Slave wird.)
Es wird 2. Zugriffsmethoden geben, eine auf Namen bassierte und eine mit einer Id.
virtual signed int GetItemCount( void ) = 0;
Gibt die Anzahl der Kindklasse zurück, um sauber durchnavigieren zu können
virtual void NewItem(std::string Name, std::string Type) = 0;
Erstellt eine neue Kindklasse. Ich dachte vorher dass ich den Typ der Klasse übergebe, hab aber noch keine saubere Funktion dafür gefunden. Ich hab mir derzeit überlegt dass man einen Namen übergibt der den Klassentyp der Kindklasse repräsentiert, wenn es nur 1. Kindklassentyp gibt wird der Wert ignoriert und standardmäßig eine 0 übergeben.
Ich hoffe ihr kennt euch aus. Besonders das mit NewItem würde ich gerne noch überarbeiten. Was haltet ihr von der allgemeinen Idee, hier geht es derzeit nur darum in der Klasse zu navigieren. Das Dynamische Anlegen von Kindelementen ist z.b. noch nicht ausprobiert, wobei da villeicht eine Templateklasse für die Elementeverarbeitung ins spiel kommt, die von allen anderen Klassen die diese funktion benötigen geerbt wird.
Hier noch ein kleines Testprogramm von mir bezüglich der Navigation:
/*
* File: main.cpp
* Author: pointhi
*
* Created on 26. April 2013, 14:20
*/
#include <cstdlib>
#include <tr1/memory>
#include "SensorHandler.hpp"
using namespace std;
int main(int argc, char** argv) {
std::tr1::shared_ptr<SensorHandler::Baseclass> testClass(new SensorHandler::Hardware::I2c);
testClass->Output();
testClass->GetItem("123")->Output();
return 0;
}
Es schaut einfach nur ob ich durchnavigieren kann. Die Ausgabe ist die folgende:
Hello Name: testtest
Hello Name: testclass
Es zeigt dass beim ersten ->Output die erstellte Klasse angesprochen wird, beim 2. Output aber die aufgerufene kindklasse davon. Ich bin auch noch nicht sicher ob ich villeicht nicht shared_ptr als rückgabetyp geben soll, um speicherlecks und fehlzugriffe zu vermeiden. Ich hab mit diesen Klassen aber noch sehr wenig erfahrung.
mfg, pointhi
Das experimentiermodell entwickelt sich langsam weiter, erzeugt aber wie erwartet immer neue hürden da ich in einem Bereich programmiere den ich noch nie genutzt habe.
Ich hab mal eine Schematik erstellt die zeigen soll wie ich mir es in etwa vorstelle, wie die abhängikeitsstrukur ausschauen soll:
25360
Dabei gibt es eine Basisklasse, die andere Klassen erstellen kann, die wiederum andere Klassen instanzieren können. Ich hab die Farben so gewählt, dass 1. Farbei eine Hauptgruppe ist, wobei nur diese Hauptgruppe von der darunterliegenden Klasse instanziert werden kann.
Dann wie die erzeugte Struktur z.B. so ausschauen könnte:
25361
Hier hab ich sozusagen 2. unabhängige I2C-Stränge an denen ein paar I2C-Slaves hängen, und auch eine RS232-Schnittstelle mit Buspartner.
Ich hab mir auch gedanken über die implementierung dieser Struktur in C++ gemacht:
25362
Hier hab ich die Implementierung in richtung I2C-Slave gezeichnet. Dieses Diagramm ist nicht mehr ganz simpel, es sollte aber bei richtiger Implementierungen alle benötigten Eigenschaften aufweisen (besonders auch die Typensicherheit). Derzeit hänge ich bei der Implementierung einer Klasse mit dem Namen TreeHandler fest, der die abgehenden Objekte speichern und verwalten soll. Die Klassen wir Hardware werden von der Baseclass abgeleitet, und sorgen dafür dass alle Hardwarerepräsentierend Klassen typenmäßig zusammengefasst werden. Diese Klassen sind rein Virtuell deklariert.
Ich hab auch kurz bei den DesignPatterns gesucht um meinen Entwurf darin zu finden. Ich glaube man kann es unter Abstrakte Fabrik (http://de.wikipedia.org/wiki/Abstrakte_Fabrik) einordnen, bzw. ist es genau so eine.
Wie findet ihr die idee?
Bezügl. der Lizenz werde ich die LGPL nehmen, da ich mit den freizügigeren Lizenzen die ich gefunden habe nicht zufrieden bin. Der Vorteil dafür ist aber auch, dass auch bei kommerziellen Produkten ein neues Datenformat oä. hinzugefügt werden kann.
Es gibt noch mehr gründe, die ich jetzt aber nicht aufliste. Unter anderem muss ein Compiler den C++ Standard unterstützen, und vermutlich auch ein minimal-OS eingesetzt werden (z.B. benötigt man eine Speicherverwaltung und ein Filesystem wenn die hardwarestruktur nicht hardcodiert wird.), wodurch die Bibliotek in den meisten fällen auch dynamisch gelinkt werden kann.
mfg, pointhi
Ich hab heute wieder begonnen weiterzuarbeiten. Vor einiger zeit habe ich das ganze noch einmal neu geschrieben, und jetzt funktioniert auch das ganze arbeiten im Baum fehlerfrei. XML-Dateien werden schon teilweise geparst, und sobald die Daten korrekt geladen sind, und auch I2C angesprochen werden kann, werde ich den neuen Code auf GitHub stellen (ich hab das alte gelöscht, weil es wirklich nicht gut geproggt wurde).
mfg, pointhi
Die erste Alpha-Version rückt immer näher (Lua funktioniert bereits, I2C wird in den nächsten Tagen implementiert).
Code ist hier zu finden: https://github.com/pointhi/OpenSensorSystem, ich hab auch ein kleines Wiki (https://github.com/pointhi/OpenSensorSystem/wiki) geschrieben, in dem der grundlegende Aufbau der XML und Lua-Dateien beschrieben ist.
Ich würde mich noch immer auf mitentwickler freuen. Sobald das Lua-System ausgereift ist ist es dann besonders einfach mitzuhelfen, indem man Lua-Plugins für bekannte Hardware schreibt (z.B. RN-Hardware, bekannte Portexpander, Sensoren,...). Die Syntax ist nicht sehr schwer.
Ein Ziel wäre es, die Bibliotek + den Lua-Plugins als DebianPaket zur Verfügung zu stellen, um die Nutzung zu vereinfachen. Leider ist mein Wissen im paketbau mehr als mangelhaft.
mfg, pointhi
Powered by vBulletin® Version 4.2.5 Copyright ©2024 Adduco Digital e.K. und vBulletin Solutions, Inc. Alle Rechte vorbehalten.