Livre de recettes
Recettes compactes, prêtes à copier, pour la bibliothèque Java. Toutes
supposent le module JPMS com.optimeas.osf sur le module path (Java 21) et
importent les types publics du paquet com.optimeas.osf :
import com.optimeas.osf.*;
import java.nio.file.Path;
La gestion des erreurs est réduite au minimum. Les API de lecture et
d'écriture signalent les erreurs par l'exception non contrôlée OsfException
(voir Gestion des erreurs) ; pour une vue d'ensemble
des classes, voir Architecture, Lecture
et Écriture.
Inspecter un fichier (métadonnées + liste des canaux)
loadFromFile reconnaît de manière transparente OSF4, OSF5 et OSFZ
compressé (gzip/zlib) d'après les premiers octets — le même appel pour
.osf et .osfz.
DataManager mgr = DataManager.loadFromFile(Path.of(pfad)); // .osf oder .osfz
System.out.println("version: " + mgr.version()); // OSF4 / OSF5
System.out.println("creator: " + mgr.metadata().getOrDefault("creator", "-"));
System.out.println("created: " + mgr.metadata().getOrDefault("created_utc", "-"));
System.out.println("channels: " + mgr.channels().size());
for (DataChannel ch : mgr.channels()) {
System.out.printf(" [%d] %s (%d Samples, Einheit: %s)%n",
ch.index(), ch.name(), ch.sampleCount(),
ch.physicalUnit() == null ? "-" : ch.physicalUnit());
}
Récupérer un canal sous forme de valeurs double avec horodatages
asDoubles() élargit tout type de données numérique en double ;
timestampsNs() fournit le tableau d'horodatages parallèle — reconstruit à
partir de l'heure de départ et de la fréquence d'échantillonnage pour les
canaux équidistants, explicite pour les canaux horodatés. Les deux tableaux
ont la même longueur.
DataChannel ch = mgr.channelByName("Motor.Drehzahl").orElseThrow();
long[] ts = ch.timestampsNs();
double[] v = ch.asDoubles(); // wirft OsfException.UnsupportedType, wenn nicht numerisch
for (int i = 0; i < v.length; i++) {
long tNs = ts[i];
double wert = v[i];
// … tNs, wert verarbeiten
}
channelByName renvoie un Optional<DataChannel> ; à l'inverse,
channelByIndex(int) accède au canal via l'index du métabloc.
Itérer de façon générique sur des types de données mixtes
Un switch sur dataType() rend les outils d'export indépendants du type de
données. Chaque accès as…() lève OsfException.UnsupportedType s'il ne
correspond pas au canal — le switch l'évite.
long[] ts = ch.timestampsNs();
switch (ch.dataType()) {
case DOUBLE, FLOAT, INT8, INT16, INT32, INT64,
UINT8, UINT16, UINT32, UINT64, BOOL -> {
double[] v = ch.asDoubles();
for (int i = 0; i < v.length; i++) row(ts[i], v[i]);
}
case GPS_LOCATION -> {
GpsLocation[] g = ch.asGps();
for (int i = 0; i < g.length; i++) row(ts[i], g[i].latitude(), g[i].longitude());
}
case STRING -> {
String[] s = ch.asStrings();
for (int i = 0; i < s.length; i++) row(ts[i], s[i]);
}
case BINARY -> {
byte[][] b = ch.asBinaries();
for (int i = 0; i < b.length; i++) row(ts[i], b[i].length + " Bytes");
}
default -> { /* UNSUPPORTED: Kanal geladen, aber Werte nicht projizierbar */ }
}
Export CSV minimal
import java.io.BufferedWriter;
import java.nio.file.Files;
DataChannel ch = mgr.channelByName(name).orElseThrow();
long[] ts = ch.timestampsNs();
double[] v = ch.asDoubles();
try (BufferedWriter w = Files.newBufferedWriter(Path.of("kanal.csv"))) {
w.write("timestamp_ns,value\n");
for (int i = 0; i < v.length; i++) {
w.write(ts[i] + "," + v[i] + "\n");
}
}
Un tableau large (une colonne par canal, une ligne par horodatage) est
produit par l'outil osf-cli avec osf convert --to csv — voir
Outils.
Convertir OSF4 → OSF5 (y compris en entrée OSFZ)
BlockWriter.fromManager reconstruit un writer à partir d'un fichier chargé
— métadonnées, définitions de canaux et tous les échantillons. La sortie est
toujours en OSF5, quel que soit le format source ; les échantillons
restent identiques à l'octet près.
DataManager mgr = DataManager.loadFromFile(Path.of("alt_osf4.osf")); // oder .osfz
BlockWriter.fromManager(mgr).writeToFile(Path.of("neu_osf5.osf"));
Pour une sortie OSFZ, écrire le BlockWriter dans un GZIPOutputStream :
import java.util.zip.GZIPOutputStream;
try (var os = new GZIPOutputStream(Files.newOutputStream(Path.of("neu.osfz")))) {
BlockWriter.fromManager(mgr).writeTo(os);
}
Écrire un nouveau fichier avec des données d'analyse
Le BlockWriter collecte tous les échantillons en mémoire et écrit le
fichier en une seule passe. Les canaux sont d'abord déclarés (la valeur de
retour est l'index du canal), puis remplis.
BlockWriter w = new BlockWriter();
w.setMetadata("creator", "mein-tool/1.0");
int fftPeak = w.addTimestampedChannel("ergebnis.fft_peak", DataType.DOUBLE);
for (var e : ergebnisse) {
w.writeSample(fftPeak, e.timestampNs(), e.peakHz());
}
w.writeToFile(Path.of("ergebnis.osf")); // created_utc wird automatisch gesetzt
Pour des séries d'analyse équidistantes (fréquence fixe, uniquement
float/double) au lieu de canaux horodatés :
int spektrum = w.addEquidistantChannel("ergebnis.psd", DataType.DOUBLE, 2, 1000.0); // 1 kHz
w.startEquidistantSegment(spektrum, startNs, block1); // double[]
w.appendEquidistantSamples(spektrum, block2); // hängt an dasselbe Segment an
Boucle d'enregistrement embarquée tolérante aux pannes
Le StreamingWriter écrit immédiatement le préambule et chaque bloc terminé,
et appelle force(true) (fsync) après chaque bloc. La durabilité est donc
assurée par bloc : après une coupure de courant, le DataManager lit le
fichier jusqu'au dernier bloc complet et met stats().truncationSeen() pour
les derniers octets tronqués. Il implémente Closeable — un
try-with-resources écrit les blocs restants, force et ferme.
try (StreamingWriter w = StreamingWriter.create(Path.of("/data/rec_0001.osf"))) {
w.setMetadata("creator", "logger-fw/3.2");
int temp = w.addTimestampedChannel("temp", DataType.DOUBLE, 2, "degC", null);
int tuer = w.addTimestampedChannel("tuer", DataType.BOOL, 2); // Event-Kanal
w.begin(); // Präambel früh festschreiben (sonst lazy beim ersten Sample)
while (laeuft) {
long now = jetztNs();
if (neuerMesswert) w.writeSample(temp, now, wert);
if (tuerGeaendert) w.writeSample(tuer, now, offen);
// Optional: w.flush() erzwingt die noch offenen Teilblöcke sofort.
warteAufNaechstenTick();
}
} // close(): Restblöcke schreiben, force, schließen
Le StreamingWriter fixe sizeoflengthvalue par canal et ne peut pas le
relever après coup — les canaux contenant de grands échantillons variables
(voir ci-dessous) doivent donc être déclarés d'emblée avec 4.
Images/blobs comme canal Binary
Les grands échantillons variables (JPEG, tampons bruts) dépassent facilement
le champ de longueur de 2 octets ; il faut donc créer le canal avec
sizeoflengthvalue = 4. Via la map d'attributs, un mimetype peut être
inscrit dans le métabloc. Les échantillons variables ne sont jamais regroupés
— un bloc par échantillon.
// Schreiben (StreamingWriter — sov=4 wegen Sample-Größe):
int kamera = w.addTimestampedChannel(
"kamera.snapshots", DataType.BINARY, 4,
null, java.util.Map.of("mimetype", "image/jpeg"));
w.writeSample(kamera, tsNs, jpegBytes); // byte[]
// Lesen:
DataChannel ch = mgr.channelByName("kamera.snapshots").orElseThrow();
long[] ts = ch.timestampsNs();
byte[][] blobs = ch.asBinaries();
for (int i = 0; i < blobs.length; i++) {
long t = ts[i];
byte[] jpeg = blobs[i];
// … jpeg speichern/decodieren
}
Le BlockWriter connaît à l'avance la taille maximale des échantillons : la
forme abrégée addTimestampedChannel(name, DataType.BINARY) y suffit et
relève automatiquement sizeoflengthvalue de 2 à 4 si nécessaire.
Écrire et vérifier le profil d'intégrité crc
Les deux writers peuvent produire le niveau crc : un CRC32C sur le métabloc
dans l'en-tête magique, plus un CRC32C de trame par bloc.
BlockWriter w = new BlockWriter();
w.setIntegrity(IntegrityProfile.CRC32C);
// … Kanäle + Samples …
w.writeToFile(Path.of("gesichert.osf"));
À la lecture, le DataManager vérifie en mode fail-closed : un CRC de
métabloc erroné lève OsfException.MetablockCrcMismatch, les blocs
défectueux sont ignorés et comptés.
DataManager mgr = DataManager.loadFromFile(Path.of("gesichert.osf"));
ReaderStats s = mgr.stats();
System.out.println(s.verificationStatus()); // "crc_valid" oder "invalid"
if (s.blocksCrcFailed() > 0) {
System.out.println("ACHTUNG: " + s.blocksCrcFailed() + " Blöcke mit CRC-Fehler");
}
Afficher les statistiques de lecture
ReaderStats s = mgr.stats();
System.out.printf("Blöcke gelesen: %d%n", s.blocksRead());
if (s.truncationSeen()) {
System.out.println("ACHTUNG: Datei war abgeschnitten");
}
if (s.compressed()) {
System.out.println("Quelle war OSFZ (" + s.compressionFormat() + ")");
}
Les détails sur les éléments internes (flux de blocs, assembleur, chunking) se trouvent sous Éléments internes ; le build et les coordonnées Maven sous Build. La vue d'ensemble de tous les outils est fournie par la page d'accueil Java.
Ce document est publié sous licence CC BY 4.0. Attribution : optiMEAS GmbH et optiMEAS Switzerland GmbH.