Cookbook
Ricette compatte e pronte per la copia per la libreria Java. Tutte
presuppongono il modulo JPMS com.optimeas.osf sul module path
(Java 21) e importano i tipi pubblici dal package com.optimeas.osf:
import com.optimeas.osf.*;
import java.nio.file.Path;
La gestione degli errori è ridotta al minimo. Le API di lettura e
scrittura segnalano gli errori tramite l'eccezione non controllata
OsfException (vedere Gestione degli errori);
per la panoramica complessiva delle classi vedere
Architettura, Lettura e
Scrittura.
Ispezionare un file (metadati + elenco dei canali)
loadFromFile riconosce in modo trasparente OSF4, OSF5 e OSFZ
compresso (gzip/zlib) dai primi byte — la stessa chiamata per .osf e
.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());
}
Ottenere un canale come valori double con timestamp
asDoubles() estende ogni tipo di dati numerico a double;
timestampsNs() restituisce l'array parallelo dei timestamp —
ricostruito da ora di inizio e frequenza di campionamento nei canali
equidistanti, esplicito nei canali timestamped. Entrambi gli array hanno
la stessa lunghezza.
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 restituisce un Optional<DataChannel>; in alternativa
channelByIndex(int) accede tramite l'indice del metablock.
Iterare in modo generico su tipi di dati misti
Uno switch su dataType() rende gli strumenti di esportazione
indipendenti dal tipo di dati. Ogni accesso as…() genera
OsfException.UnsupportedType se non è adatto al canale — lo switch
lo evita.
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 */ }
}
Esportazione CSV minimale
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");
}
}
Una tabella larga (una colonna per canale, una riga per timestamp) viene
generata dallo strumento osf-cli con osf convert --to csv — vedere
Strumenti.
Convertire OSF4 → OSF5 (anche con input OSFZ)
BlockWriter.fromManager ricostruisce un writer da un file caricato —
metadati, definizioni dei canali e tutti i campioni. L'output è
sempre OSF5, indipendentemente dal formato di origine; i campioni
restano identici bit per bit.
DataManager mgr = DataManager.loadFromFile(Path.of("alt_osf4.osf")); // oder .osfz
BlockWriter.fromManager(mgr).writeToFile(Path.of("neu_osf5.osf"));
Per un output OSFZ scrivere il BlockWriter in un
GZIPOutputStream:
import java.util.zip.GZIPOutputStream;
try (var os = new GZIPOutputStream(Files.newOutputStream(Path.of("neu.osfz")))) {
BlockWriter.fromManager(mgr).writeTo(os);
}
Scrivere un nuovo file con dati di analisi
Il BlockWriter accumula tutti i campioni in memoria e scrive il file
in un unico passaggio. I canali vengono prima dichiarati (il valore di
ritorno è l'indice del canale) e poi riempiti.
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
Per serie di analisi equidistanti (frequenza fissa, solo
float/double) anziché timestamped:
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
Ciclo di registrazione embedded a prova di interruzione
Lo StreamingWriter scrive immediatamente il preambolo e ogni blocco
completato e dopo ogni blocco chiama force(true) (fsync). La
durabilità è quindi per blocco: dopo un'interruzione
dell'alimentazione il DataManager legge il file fino all'ultimo
blocco completo e imposta stats().truncationSeen() per i byte residui
troncati. Implementa Closeable — il try-with-resources scrive i
blocchi residui, esegue il force e chiude.
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
Lo StreamingWriter fissa sizeoflengthvalue per canale e non può
aumentarlo in un secondo momento — i canali con grandi campioni
variabili (vedere sotto) vanno quindi dichiarati subito con 4.
Immagini/blob come canale Binary
I grandi campioni variabili (JPEG, buffer grezzi) superano facilmente il
campo di lunghezza di 2 byte; per questo il canale va creato con
sizeoflengthvalue = 4. Tramite la mappa degli attributi è possibile
scrivere un mimetype nel metablock. I campioni variabili non vengono
mai raggruppati — un blocco per campione.
// 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
}
Il BlockWriter conosce in anticipo la dimensione massima dei campioni:
qui è sufficiente la forma abbreviata
addTimestampedChannel(name, DataType.BINARY), che all'occorrenza
porta automaticamente sizeoflengthvalue da 2 a 4.
Scrivere e verificare il profilo di integrità crc
Entrambi i writer possono generare il livello crc: un CRC32C sul
metablock nel magic header più un CRC32C di frame per ogni blocco.
BlockWriter w = new BlockWriter();
w.setIntegrity(IntegrityProfile.CRC32C);
// … Kanäle + Samples …
w.writeToFile(Path.of("gesichert.osf"));
In lettura il DataManager verifica in modalità fail-closed: un CRC del
metablock errato genera OsfException.MetablockCrcMismatch, mentre i
blocchi difettosi vengono saltati e conteggiati.
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");
}
Visualizzare le statistiche di lettura
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() + ")");
}
I dettagli sugli aspetti interni (flusso di blocchi, assembler, chunking) sono in Interni; build e coordinate Maven in Build. La panoramica di tutti gli strumenti è disponibile nella pagina iniziale Java.
Questo documento è distribuito con licenza CC BY 4.0. Attribuzione: optiMEAS GmbH e optiMEAS Switzerland GmbH.