====== std.hardware.usb ======
Der USB-Stack implementiert alle USB-Host-Operationen direkt via Linux ''usbdevfs''-ioctls — kein libusb, keine externen Abhängigkeiten. Alle Transfers (Control, Bulk, Interrupt, Isochronous) sind vollständig in Lyx implementiert.
Import-Muster: Jede Sub-Unit wird einzeln importiert, je nach benötigtem Funktionsumfang:
import std.hardware.usb_discovery;
import std.hardware.usb_control;
import std.hardware.usb_bulk;
→ [[lyx_-_programmiersprache:units:hardware|std.hardware]] · [[lyx_-_programmiersprache:units|Standard Library]]
----
===== Architektur =====
Der Stack ist in 13 Sub-Units gegliedert, die aufeinander aufbauen:
^ Schicht ^ Sub-Unit ^ Beschreibung ^
| High-Level / Sicherheit | ''usb_endpoint_types'' | Typsichere Endpoint-Wrapper mit Richtungsvalidierung (Bulk OUT/IN, Interrupt IN) |
| High-Level / Sicherheit | ''usb_endpoint_bind'' | Endpoint-Registry: Adressen einmalig binden, danach nur per Schlüssel nutzen |
| Async-Infrastruktur | ''usb_urb_pool'' | URB-Pool (4 Slots) für überlappende Interrupt-Transfers |
| Async-Infrastruktur | ''usb_ifc_mgr'' | Idempotenter Interface-Claim/Release-Manager |
| Transfer-Schicht | ''usb_interrupt'' | Asynchrone Interrupt-Transfers: URB allokieren, einreichen, warten |
| Transfer-Schicht | ''usb_iso'' | Isochronous Transfers (Audio/Video): ISO-URB mit Paket-Deskriptoren |
| Transfer-Schicht | ''usb_bulk'' | Synchrone Bulk-Transfers (OUT/IN) |
| Transfer-Schicht | ''usb_control'' | Control-Transfers + Interface-Claim/Release-IOCTLs |
| Erkennung | ''usb_discovery'' | Geräteerkennung via ''/dev/bus/usb/'': VID/PID-Scan |
| Descriptor-Parsing | ''usb_parse'' | Configuration-Descriptor-Strom → Config/Interface/Endpoint-Baum |
| Typen & Accessoren | ''usb_types'' | Descriptor-Konstanten, Lyx-Structs, Accessor-Funktionen |
| Hilfstypen | ''usb_util'' | Dirent64-Parser, String-Utilities, Pfad-Builder |
| Grundschicht | ''usb_syscalls'' | open/read/write/ioctl/close/lseek/getdents64/poll-Wrapper |
----
===== usb_syscalls =====
Rohe Linux-Syscall-Wrapper. Alle anderen Sub-Units bauen darauf auf.
**Konstanten:**
^ Konstante ^ Wert ^ Bedeutung ^
| ''USB_O_RDONLY'' / ''USB_O_RDWR'' | 0 / 2 | open()-Flags |
| ''USB_O_DIRECTORY'' | 65536 | Verzeichnis öffnen |
| ''USB_SEEK_SET/CUR/END'' | 0 / 1 / 2 | lseek()-Whence |
| ''USB_DT_DIR'' / ''USB_DT_REG'' | 4 / 8 | dirent-Typ |
| ''USB_POLLIN'' | 1 | poll()-Event: Daten verfügbar |
**Funktionen:**
^ Funktion ^ Parameter ^ Beschreibung ^
| ''usb_open(path, flags)'' | pchar, int64 | Datei oder Verzeichnis öffnen |
| ''usb_read(fd, buf, len)'' | int64 × 3 | Bytes lesen |
| ''usb_write(fd, buf, len)'' | int64 × 3 | Bytes schreiben |
| ''usb_ioctl(fd, request, arg)'' | int64 × 3 | ioctl-Aufruf (usbdevfs) |
| ''usb_close(fd)'' | int64 | fd schließen |
| ''usb_lseek(fd, offset, whence)'' | int64 × 3 | Leseposition setzen |
| ''usb_getdents64(fd, buf, count)'' | int64 × 3 | Verzeichniseinträge lesen |
| ''usb_poll(fds_ptr, nfds, timeout_ms)'' | int64 × 3 | Auf fd-Ereignisse warten |
| ''usb_pollfd_set(ptr, fd, events)'' | int64 × 3 | pollfd-Struct befüllen |
| ''usb_pollfd_revents(ptr)'' | int64 | revents aus pollfd lesen |
----
===== usb_util =====
Hilfstypen für Verzeichnis-Parsing und Pfad-Operationen. Wird intern von ''usb_discovery'' genutzt.
^ Funktion ^ Beschreibung ^
| ''Dirent64RecLen(entry)'' | Gesamtlänge des Dirent64-Eintrags (für Iteration) |
| ''Dirent64Type(entry)'' | Dateityp (USB_DT_REG / USB_DT_DIR) |
| ''Dirent64Name(entry)'' | Zeiger auf null-terminierten Dateinamen |
| ''Dirent64Ino(entry)'' | Inode-Nummer |
| ''UsbIsNumeric(ptr)'' | Gibt 1 wenn String nur Dezimalziffern enthält |
| ''UsbParseNum(ptr)'' | Dekodiert Dezimal-String → int64 |
| ''UsbBuildPath(base, segment, suffix, buf, buf_size)'' | Pfadsegmente zusammensetzen |
| ''UsbStrLen(ptr)'' | Länge eines null-terminierten Strings (int64-Zeiger) |
----
===== usb_types =====
Alle USB-Descriptor-Typen, Struct-Definitionen und Accessor-Funktionen.
**Descriptor-Typ-Konstanten:**
^ Konstante ^ Wert ^ Bedeutung ^
| ''USB_DT_DEVICE'' | 1 | Device Descriptor |
| ''USB_DT_CONFIG'' | 2 | Configuration Descriptor |
| ''USB_DT_STRING'' | 3 | String Descriptor |
| ''USB_DT_INTERFACE'' | 4 | Interface Descriptor |
| ''USB_DT_ENDPOINT'' | 5 | Endpoint Descriptor |
**Transfer-Typ und Richtung:**
^ Konstante ^ Wert ^
| ''USB_TRANS_CONTROL / ISO / BULK / INTERRUPT'' | 0 / 1 / 2 / 3 |
| ''USB_EP_DIR_OUT / USB_EP_DIR_IN'' | 0 / 1 |
**Struct-Größen:**
^ Konstante ^ Größe ^
| ''USB_SIZEOF_DEVICE_DESC'' | 18 Bytes (USB 2.0 §9.6.1) |
| ''USB_SIZEOF_CONFIG_DESC'' | 9 Bytes |
| ''USB_SIZEOF_INTERFACE_DESC'' | 9 Bytes |
| ''USB_SIZEOF_ENDPOINT_DESC'' | 7 Bytes |
| ''USB_SIZEOF_ENDPOINT'' | 48 Bytes (Lyx-Struct, 6 × int64) |
| ''USB_SIZEOF_INTERFACE'' | 56 Bytes (7 × int64) |
| ''USB_SIZEOF_DEVICE'' | 72 Bytes (9 × int64) |
**Lyx-Typen:**
type UsbEndpoint = struct {
address: int64; direction: int64; number: int64;
transfer_type: int64; max_packet: int64; interval: int64;
};
type UsbInterface = struct {
interface_number: int64; alternate_setting: int64;
class_code: int64; subclass_code: int64; protocol_code: int64;
num_endpoints: int64; endpoints: int64; // Zeiger auf alloc'd Array
};
type UsbDevice = struct {
vendor_id: int64; product_id: int64; device_class: int64;
usb_version: int64; bus_num: int64; dev_num: int64;
handle: int64; // fd — bleibt offen
num_configs: int64; configs: int64;
};
**Accessor-Funktionen** (Auswahl):
^ Funktion ^ Beschreibung ^
| ''UsbDevDesc_idVendor(buf)'' | Vendor-ID aus Raw-Buffer |
| ''UsbDevDesc_idProduct(buf)'' | Product-ID |
| ''UsbDevDesc_bNumConfigurations(buf)'' | Anzahl Konfigurationen |
| ''UsbEpDesc_bEndpointAddress(buf)'' | Endpoint-Adresse (Bit 7 = Richtung) |
| ''UsbEpDesc_bmAttributes(buf)'' | Transfer-Typ (Bits 1-0) |
| ''UsbEpDirection(epAddr)'' | USB_EP_DIR_OUT oder USB_EP_DIR_IN |
| ''UsbEpNumber(epAddr)'' | Endpoint-Nummer (Bits 0-3) |
| ''UsbEndpointAt(base, i)'' | Zeiger auf UsbEndpoint[i] im Array |
| ''UsbInterfaceAt(base, i)'' | Zeiger auf UsbInterface[i] |
| ''UsbEndpointFromDesc(ep, epDescBuf)'' | Befüllt UsbEndpoint-Struct aus Raw-Descriptor |
----
===== usb_parse =====
Liest den vollständigen Descriptor-Strom von einem geöffneten USB-fd und baut daraus einen vollständigen Config/Interface/Endpoint-Baum.
^ Funktion ^ Beschreibung ^
| ''UsbAllocDevice()'' | Allokiert und nullt UsbDevice-Struct (72 Bytes) |
| ''UsbParseConfiguration(dev_fd, devicePtr)'' | Parst Descriptor-Strom → Config/Iface/EP-Baum. Rückgabe: 1=Erfolg, 0=Fehler |
Hinweis: ''UsbParseConfiguration'' setzt den Dateizeiger via ''lseek'' auf 0 zurück (Kernel-fd ist seekbar). Class-spezifische Descriptoren (HID-Report etc.) werden stillschweigend übersprungen.
----
===== usb_discovery =====
Scannt ''/dev/bus/usb/'' nach einem Gerät mit gegebener VID/PID. Öffnet das gefundene Gerät und parst automatisch seine Konfiguration.
^ Konstante ^ Bedeutung ^
| ''USB_FIND_OK'' (0) | Gerät gefunden |
| ''USB_FIND_NOTFOUND'' (-1) | Nicht gefunden |
^ Funktion ^ Beschreibung ^
| ''UsbFindDevice(vendor, product, devicePtr)'' | Sucht in ''/dev/bus/usb/'' |
| ''UsbFindDevicePath(busRoot, vendor, product, devicePtr)'' | Sucht mit konfigurierbarem Bus-Root |
Verwendung:
import std.hardware.usb_discovery;
import std.hardware.usb_parse;
var dev: int64 := UsbAllocDevice();
if (UsbFindDevice(0x046D, 0xC52B, dev) == USB_FIND_OK) {
// dev.handle ist geöffnet, Konfiguration geparst
}
----
===== usb_control =====
Control-Transfers und Interface-Claim/Release via usbdevfs-ioctls.
**IOCTL-Codes:**
^ Konstante ^ Wert ^ Bedeutung ^
| ''USBDEVFS_CONTROL'' | 0xC0185500 | Control-Transfer |
| ''USBDEVFS_CLAIMINTERFACE'' | 0x4004550F | Interface beanspruchen |
| ''USBDEVFS_RELEASEINTERFACE'' | 0x40045510 | Interface freigeben |
**Request-Typen (USB 2.0 §9.4):**
^ Konstante ^ Bedeutung ^
| ''USB_DIR_IN'' / ''USB_DIR_OUT'' | 0x80 / 0x00 — Transfer-Richtung |
| ''USB_REQ_GET_DESCRIPTOR'' | 6 |
| ''USB_REQ_SET_CONFIGURATION'' | 9 |
| ''USB_TIMEOUT_DEFAULT'' / ''USB_TIMEOUT_SHORT'' | 5000 / 1000 ms |
**Funktionen:**
^ Funktion ^ Beschreibung ^
| ''UsbCtrlSetRequest(buf, reqType, req, wValue, wIndex)'' | Request-Header (Bytes 0–7) befüllen |
| ''UsbCtrlSetTransfer(buf, data, length, timeout)'' | Länge/Timeout/Datenzeiger (Bytes 6–23) |
| ''UsbControlTransferRaw(dev_fd, ctrlBuf)'' | Fertigen ctrlBuf via ioctl senden |
| ''UsbGetDeviceDescriptor(dev_fd, descBuf)'' | Device Descriptor lesen (18 Bytes) |
| ''UsbClaimInterface(dev_fd, interface_num)'' | Interface für exklusiven Zugriff beanspruchen |
| ''UsbReleaseInterface(dev_fd, interface_num)'' | Interface freigeben |
----
===== usb_bulk =====
Synchrone Bulk-Transfers via ''USBDEVFS_BULK'' (0x40185502).
^ Funktion ^ Beschreibung ^
| ''UsbBulkWrite(dev_fd, endpoint, data, length, timeout)'' | Daten an OUT-Endpoint senden (Bit 7 = 0) |
| ''UsbBulkRead(dev_fd, endpoint, buffer, length, timeout)'' | Daten von IN-Endpoint empfangen (Bit 7 = 1) |
| ''UsbBulkTransfer(dev_fd, ep, data, length, timeout)'' | Niedrig-Level (OUT und IN) |
| ''UsbBuildBulkTransfer(buf, ep, data, length, timeout)'' | usbdevfs_bulktransfer-Struct befüllen |
Rückgabe: Übertragene Bytes (≥ 0) oder negativer Fehlercode (z.B. ''-ETIMEDOUT'', ''-ENODEV'').
----
===== usb_interrupt =====
Asynchrone Interrupt-Transfers via URBs (Kernel-interne Puffer). Ablauf: allokieren → befüllen → einreichen → warten → auswerten → erneut einreichen.
**IOCTL-Codes:**
^ Konstante ^ Wert ^ Bedeutung ^
| ''USBDEVFS_SUBMITURB'' | 0x4038550A | URB einreichen |
| ''USBDEVFS_DISCARDURB'' | 0x0000550B | URB abbrechen |
| ''USBDEVFS_REAPURB'' | 0x4008550C | Blockierend warten |
| ''USBDEVFS_REAPURBNDELAY'' | 0x4008550D | Nicht-blockierend abholen |
**URB-Struct** (56 Bytes, Feld-Offsets als Konstanten ''URB_OFF_*''):
^ Offset ^ Feld ^ Größe ^
| +0 | type (USBDEVFS_URB_TYPE_*) | uint8 |
| +1 | endpoint (Bit 7: Richtung) | uint8 |
| +4 | status (0 = OK, vom Kernel gesetzt) | int32 |
| +16 | buffer | void * (8 Bytes) |
| +24 / +28 | buffer_length / actual_length | int32 |
| +44 | signr (uint32, 0 = kein Signal) | uint32 |
| +48 | usercontext | void * (8 Bytes) |
**Funktionen:**
^ Funktion ^ Beschreibung ^
| ''UsbAllocUrb()'' | Allokiert + nullt URB-Struct (Heap, 56 Bytes) |
| ''UsbFreeUrb(urbPtr)'' | Gibt URB-Heap frei |
| ''UsbInitInterruptRead(urbPtr, endpoint, buffer, length, context)'' | Interrupt-IN-URB befüllen |
| ''UsbSubmitUrb(dev_fd, urbPtr)'' | URB beim Kernel einreichen (kehrt sofort zurück) |
| ''UsbSubmitInterruptRead(dev_fd, endpoint, buffer, length, context)'' | Allokiert + befüllt + reicht ein |
| ''UsbWaitForUrb(dev_fd, timeout_ms)'' | poll + REAPURBNDELAY — gibt URB-Zeiger zurück |
| ''UsbDiscardUrb(dev_fd, urbPtr)'' | Laufenden URB abbrechen |
| ''UsbUrbStatus(urbPtr)'' | Fehlercode nach Abschluss (0 = OK) |
| ''UsbUrbActualLength(urbPtr)'' | Tatsächlich empfangene Bytes |
| ''UsbUrbContext(urbPtr)'' | Usercontext-Zeiger (wie beim Submit übergeben) |
Verwendung (einfacher HID-Empfang):
import std.hardware.usb_interrupt;
var buf: int64 := alloc(64);
var urb: int64 := UsbSubmitInterruptRead(dev_fd, 0x81, buf, 64, 0);
var done: int64 := UsbWaitForUrb(dev_fd, 1000);
if (done != 0 && UsbUrbStatus(done) == 0) {
var n: int64 := UsbUrbActualLength(done);
// buf enthält n Bytes HID-Daten
UsbFreeUrb(urb);
}
----
===== usb_endpoint_types =====
Typsichere Endpoint-Wrapper: ''UsbBulkOutEndpoint'' hat **nur** Write-Operationen, ''UsbBulkInEndpoint'' **nur** Read. Falsche Richtung erzeugt einen Sentinel-fd (dev_fd=-1) → alle Ops geben -1.
**Typen:** ''UsbBulkOutEndpoint'', ''UsbBulkInEndpoint'', ''UsbInterruptInEndpoint''
^ Funktion ^ Beschreibung ^
| ''UsbMakeBulkOut(dev_fd, ep, timeout)'' | Erstellt OUT-Wrapper (ep muss Bit 7 = 0 haben) |
| ''UsbMakeBulkIn(dev_fd, ep, timeout)'' | Erstellt IN-Wrapper (ep muss Bit 7 = 1 haben) |
| ''UsbMakeInterruptIn(dev_fd, ep, bufSize, timeout)'' | Interrupt-IN-Wrapper |
| ''UsbBulkOutWrite(ep, data, length)'' | Sendet via OUT-Endpoint |
| ''UsbBulkInRead(ep, buffer, length)'' | Empfängt vom IN-Endpoint |
| ''UsbInterruptInSubmit(ep, buffer, context)'' | URB einreichen |
| ''UsbInterruptInWait(ep, timeout_ms)'' | Auf URB-Abschluss warten |
| ''UsbBulkOutValid(ep)'' / ''UsbBulkInValid(ep)'' | 1 wenn Endpoint gültig (kein Sentinel) |
----
===== usb_endpoint_bind =====
Endpoint-Registry: Adressen einmalig unter einem Schlüssel registrieren, danach nur noch per Schlüssel nutzen — keine Adresse beim Aufruf nötig.
^ Funktion ^ Beschreibung ^
| ''UsbEpRegistryAlloc()'' | Neue Registry anlegen (max. 8 Slots) |
| ''UsbEpRegistryFree(reg)'' | Registry freigeben |
| ''UsbDefOutEndpoint(reg, key, fd, ep, timeout)'' | OUT-Endpoint unter Schlüssel registrieren |
| ''UsbDefInEndpoint(reg, key, fd, ep, timeout)'' | IN-Endpoint registrieren |
| ''UsbEpWrite(reg, key, data, len)'' | Senden über registrierten OUT-Endpoint |
| ''UsbEpRead(reg, key, buf, len)'' | Empfangen vom registrierten IN-Endpoint |
| ''UsbEpAddr(reg, key)'' | EP-Adresse eines Slots lesen |
| ''UsbEpIsOut(reg, key)'' | 1 = OUT, 0 = IN, -1 = nicht gefunden |
import std.hardware.usb_endpoint_bind;
con EP_DATA_OUT: int64 := 1;
con EP_DATA_IN: int64 := 2;
var reg: int64 := UsbEpRegistryAlloc();
UsbDefOutEndpoint(reg, EP_DATA_OUT, dev_fd, 0x01, 1000);
UsbDefInEndpoint(reg, EP_DATA_IN, dev_fd, 0x81, 1000);
UsbEpWrite(reg, EP_DATA_OUT, dataPtr, 64);
UsbEpRead(reg, EP_DATA_IN, recvBuf, 64);
----
===== usb_iso =====
Isochronous Transfers für Audio/Video. ISO-URBs enthalten N angehängte Paket-Deskriptoren (''usbdevfs_iso_packet_desc'', je 12 Bytes) direkt hinter dem Basis-URB (Offset 56).
^ Funktion ^ Beschreibung ^
| ''UsbAllocIsoUrb(num_packets)'' | ISO-URB allokieren (56 + n × 12 Bytes) |
| ''UsbFreeIsoUrb(urbPtr, num_packets)'' | Freigeben (Größe muss stimmen) |
| ''UsbInitIsoRead(urbPtr, endpoint, buffer, packet_size, num_packets)'' | ISO-URB befüllen |
| ''UsbSubmitIsoUrb(dev_fd, urbPtr)'' | ISO-URB einreichen |
| ''UsbSubmitIsoRead(dev_fd, ep, buf, pkt_size, num_pkts)'' | Allokiert + init + submit |
| ''UsbReapIsoUrb(dev_fd)'' | Blockierend warten (USBDEVFS_REAPURB) |
| ''UsbReapIsoUrbNd(dev_fd)'' | Nicht-blockierend (USBDEVFS_REAPURBNDELAY) |
| ''UsbIsoPacketActualLen(urbPtr, i)'' | Tatsächliche Bytes von Paket i |
| ''UsbIsoPacketStatus(urbPtr, i)'' | Status von Paket i (0 = OK) |
| ''UsbIsoErrorCount(urbPtr)'' | Gesamt-Fehleranzahl aller Pakete |
----
===== usb_ifc_mgr =====
Idempotenter Claim/Release-Zustandsautomat: doppeltes Claim ist No-op, Release ohne vorheriges Claim ebenfalls.
^ Funktion ^ Beschreibung ^
| ''UsbIfcMgrAlloc(dev_fd, ifc_num)'' | Neuen Manager anlegen (24 Bytes, unclaimed) |
| ''UsbIfcMgrFree(mgrPtr)'' | Manager freigeben |
| ''UsbIfcAcquire(mgrPtr)'' | Interface beanspruchen (1 = Erfolg, 0 = Fehler) |
| ''UsbIfcRelease(mgrPtr)'' | Interface freigeben (No-op wenn nicht geclaimt) |
| ''UsbIfcIsClaimed(mgrPtr)'' | Aktueller Claim-Status (1 oder 0) |
----
===== usb_urb_pool =====
Pool mit 4 vorbelegten URBs für überlappende Interrupt-Transfers. Während ein URB verarbeitet wird, warten die anderen bereits im Kernel. ''UsbUrbPoolPoll'' gibt den Entry-Index zurück — kein Callback-Mechanismus nötig.
**Wichtig:** Bei ''UsbUrbPoolGetStatus(urbp, idx) == -ENODEV'' (Gerät abgesteckt) nicht erneut einreichen.
^ Funktion ^ Beschreibung ^
| ''UsbUrbPoolAlloc(dev_fd, endpoint, bufSize)'' | Pool anlegen (4 Slots, max. 64 Bytes/Slot) |
| ''UsbUrbPoolFree(urbp)'' | Pool freigeben |
| ''UsbUrbPoolInit(urbp)'' | Alle 4 URBs einreichen, Rückgabe: Anzahl eingereicht |
| ''UsbUrbPoolPoll(urbp, timeout_ms)'' | Warten — gibt Entry-Index (0..3) oder -1 zurück |
| ''UsbUrbPoolResubmit(urbp, idx)'' | Eintrag erneut einreichen |
| ''UsbUrbPoolGetBuffer(urbp, idx)'' | Zeiger auf Datenpuffer von Slot idx |
| ''UsbUrbPoolGetActualLen(urbp, idx)'' | Empfangene Bytes nach Poll |
| ''UsbUrbPoolGetStatus(urbp, idx)'' | URB-Status (0=OK, -ENODEV=Disconnect) |
import std.hardware.usb_urb_pool;
var pool: int64 := UsbUrbPoolAlloc(dev_fd, 0x81, 64);
UsbUrbPoolInit(pool);
while (true) {
var idx: int64 := UsbUrbPoolPoll(pool, 2000);
if (idx < 0) { break; }
if (UsbUrbPoolGetStatus(pool, idx) == 0) {
var data: int64 := UsbUrbPoolGetBuffer(pool, idx);
var len: int64 := UsbUrbPoolGetActualLen(pool, idx);
// data enthält len Bytes
UsbUrbPoolResubmit(pool, idx);
}
}
UsbUrbPoolFree(pool);
----
===== Vollständiges Beispiel: USB-Gerät öffnen und Bulk-Daten übertragen =====
import std.hardware.usb_parse;
import std.hardware.usb_discovery;
import std.hardware.usb_control;
import std.hardware.usb_bulk;
fn main(): int64 {
var dev: int64 := UsbAllocDevice();
if (UsbFindDevice(0x1234, 0x5678, dev) != USB_FIND_OK) {
return -1;
}
var fd: int64 := peek64(dev + 48); // dev.handle
if (UsbClaimInterface(fd, 0) < 0) { return -2; }
var txBuf: int64 := alloc(64);
poke8(txBuf, 0x01);
var sent: int64 := UsbBulkWrite(fd, 0x01, txBuf, 64, 1000);
var rxBuf: int64 := alloc(64);
var recv: int64 := UsbBulkRead(fd, 0x81, rxBuf, 64, 1000);
UsbReleaseInterface(fd, 0);
usb_close(fd);
free(txBuf, 64); free(rxBuf, 64);
return 0;
}
Letzte Aktualisierung: 2026-06-07