feat: publish NIRO Gen5W research overview and reviewed source examples

This commit is contained in:
seo committed 2026-11-02 09:59:38 +09:00
commit ccad0ed9f3
23 files changed
+1279

No files matched your search

+96
View File
@@ -0,0 +1,96 @@
package com.chaegeon.niro;
import java.io.FileInputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public final class HscanRpmDecoderTest {
private static int assertions;
private static final class Sink implements HscanRpmDecoder.Listener {
final List<Integer> values = new ArrayList<Integer>();
public void onRpm(int rpm) { values.add(rpm); }
}
public static void main(String[] args) throws Exception {
Sink sink = new Sink();
HscanRpmDecoder decoder = new HscanRpmDecoder(sink);
byte[] zero = packet(0), rev = packet(8403);
decoder.accept(zero, 0, zero.length);
check(sink.values.equals(Arrays.asList(0)), "valid stopped engine");
for (byte b : rev) decoder.accept(new byte[] {b}, 0, 1);
check(sink.values.equals(Arrays.asList(0, 2100)), "single-byte fragmentation + stock truncation");
byte[] burst = new byte[528];
System.arraycopy(packet(9200), 0, burst, 0, 264);
System.arraycopy(packet(6400), 0, burst, 264, 264);
decoder.accept(burst, 0, burst.length);
check(sink.values.equals(Arrays.asList(0, 2100, 2300, 1600)), "coalesced changes emitted before accept returns");
int before = sink.values.size();
byte[] bad = packet(4000); bad[30] ^= 1;
decoder.accept(bad, 0, bad.length);
check(sink.values.size() == before, "bad XOR never emitted");
bad = packet(4000); bad[9] = 2; bad[22] = 9; checksum(bad);
decoder.accept(bad, 0, bad.length);
check(sink.values.size() == before, "validate entire frame before any callback");
bad = packet(4000); bad[12] = 7; checksum(bad);
decoder.accept(bad, 0, bad.length);
check(sink.values.size() == before, "wrong EMS1 length");
bad = packet(4000); bad[200] = 1; checksum(bad);
decoder.accept(bad, 0, bad.length);
check(sink.values.size() == before, "nonzero tail rejected");
bad = packet(4000); bad[9] = (byte) 255; checksum(bad);
decoder.accept(bad, 0, bad.length);
check(sink.values.size() == before, "excess count bounded");
bad = packet(4000); bad[11] = 0x17; checksum(bad);
decoder.accept(bad, 0, bad.length);
check(sink.values.size() == before, "unrelated CAN not RPM");
bad = packet(4000); bad[2] = 0; checksum(bad);
decoder.accept(bad, 0, bad.length);
check(sink.values.size() == before, "other envelope not accepted");
for (int sentinel : new int[] {65534, 65535}) {
byte[] bytes = packet(sentinel); decoder.accept(bytes, 0, bytes.length);
}
check(sink.values.size() == before, "reserved raw values ignored");
decoder.accept(new byte[] {1, 2, (byte) 255, 4, 5}, 0, 5);
decoder.accept(rev, 0, rev.length);
check(sink.values.size() == before + 1, "recovery after corrupt frames and garbage");
for (int split = 1; split < 264; split++) {
Sink s = new Sink(); HscanRpmDecoder d = new HscanRpmDecoder(s);
d.accept(rev, 0, split);
check(s.values.isEmpty(), "partial frame held");
d.accept(rev, split, rev.length - split);
check(s.values.equals(Arrays.asList(2100)), "every split decodes once");
}
if (args.length > 0) {
Sink live = new Sink(); HscanRpmDecoder d = new HscanRpmDecoder(live);
long bytes = 0;
for (String file : args) {
FileInputStream in = new FileInputStream(file);
try {
byte[] buffer = new byte[137]; int n;
while ((n = in.read(buffer)) >= 0) { d.accept(buffer, 0, n); bytes += n; }
} finally { in.close(); }
}
check(d.validFrames * 264 == bytes, "all private capture frames validated");
check(d.rejectedFrames == 0 && d.samples > 500, "real EMS1 samples recovered");
System.out.println("private_fixture_frames=" + d.validFrames + " rpm_samples=" + d.samples);
}
System.out.println("HscanRpmDecoderTest OK assertions=" + assertions);
}
private static byte[] packet(int raw) {
byte[] b = new byte[264];
int[] header = {255, 11, 142, 1, 139, 199, 1, 0, 199, 1, 0, 22, 8};
for (int i = 0; i < header.length; i++) b[i] = (byte) header[i];
b[15] = (byte) raw; b[16] = (byte) (raw >> 8);
checksum(b); return b;
}
private static void checksum(byte[] b) {
b[263] = 0;
for (int i = 0; i < 263; i++) b[263] ^= b[i];
}
private static void check(boolean good, String message) {
assertions++; if (!good) throw new AssertionError(message);
}
}
@@ -0,0 +1,65 @@
package com.chaegeon.niro;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
public final class WeatherRefreshCountersTest {
private static int checks;
private static void ok(boolean actual) {
++checks;
if (!actual) { throw new AssertionError("check " + checks); }
}
private static String read(File file) throws Exception {
FileInputStream input = new FileInputStream(file);
try {
byte[] data = new byte[32];
int length = input.read(data);
ok(length > 0 && input.read() == -1);
return new String(data, 0, length, "US-ASCII");
} finally {
input.close();
}
}
public static void main(String[] args) throws Exception {
File directory = File.createTempFile("weather-refresh-counters-", ".dir");
if (!directory.delete() || !directory.mkdir()) {
throw new IOException("test directory setup failed");
}
File signal = new File(directory, "signal.counter");
File callback = new File(directory, "callback.counter");
try {
WeatherRefreshCounters counters = new WeatherRefreshCounters(directory);
ok("0\n".equals(read(signal)));
ok("0\n".equals(read(callback)));
counters.signalAccepted();
counters.signalAccepted();
ok("2\n".equals(read(signal)));
ok("0\n".equals(read(callback)));
counters.freshCallbackCompleted();
ok("2\n".equals(read(signal)));
ok("1\n".equals(read(callback)));
ok(!new File(directory, "signal.counter.new").exists());
ok(!new File(directory, "callback.counter.new").exists());
File stale = new File(directory, "signal.counter.new");
ok(stale.createNewFile());
boolean failedClosed = false;
try {
counters.signalAccepted();
} catch (IOException expected) {
failedClosed = true;
}
ok(failedClosed);
ok("2\n".equals(read(signal)));
ok(stale.delete());
} finally {
if (signal.exists() && !signal.delete()) { throw new IOException("signal cleanup"); }
if (callback.exists() && !callback.delete()) { throw new IOException("callback cleanup"); }
if (!directory.delete()) { throw new IOException("directory cleanup"); }
}
System.out.println("result=PASS_WEATHER_REFRESH_COUNTERS checks=" + checks);
}
}
+59
View File
@@ -0,0 +1,59 @@
import com.chaegeon.niro.WeatherRefreshGate;
public final class WeatherRefreshGateTest {
private static void check(boolean condition, String name) {
if (!condition) {
throw new AssertionError(name);
}
}
public static void main(String[] args) {
WeatherRefreshGate gate = new WeatherRefreshGate(60000L, 10000L, 2000L);
check(gate.admit(100L) == WeatherRefreshGate.Admission.FETCH,
"first fetch accepted");
check(gate.isRunning(), "running");
check(gate.admit(101L) == WeatherRefreshGate.Admission.COALESCE,
"in-flight request coalesced");
gate.finish(200L, WeatherRefreshGate.Admission.FETCH, true);
check(!gate.isRunning(), "finished");
check(gate.admit(300L) == WeatherRefreshGate.Admission.REPLAY,
"recent successful response replayed");
check(gate.replayDelayMillis(300L) == 1900L,
"first replay waits until two seconds after fetch callback");
check(gate.admit(301L) == WeatherRefreshGate.Admission.COALESCE,
"in-flight replay coalesced");
gate.finish(400L, WeatherRefreshGate.Admission.REPLAY, true);
check(gate.admit(500L) == WeatherRefreshGate.Admission.REPLAY,
"second replay admitted without new fetch");
check(gate.replayDelayMillis(500L) == 1900L,
"subsequent replay is paced from previous callback");
gate.finish(501L, WeatherRefreshGate.Admission.REPLAY, true);
check(gate.admit(60199L) == WeatherRefreshGate.Admission.REPLAY,
"replay does not extend fetch TTL");
check(gate.replayDelayMillis(60199L) == 0L,
"replay runs immediately after spacing elapsed");
gate.finish(60200L, WeatherRefreshGate.Admission.REPLAY, true);
check(gate.admit(60200L) == WeatherRefreshGate.Admission.FETCH,
"new fetch accepted at original TTL boundary");
gate.finish(60300L, WeatherRefreshGate.Admission.FETCH, false);
check(gate.admit(60301L) == WeatherRefreshGate.Admission.BACKOFF,
"fast failed-fetch retry is throttled");
check(!gate.isRunning(), "backoff does not start worker");
check(gate.admit(70299L) == WeatherRefreshGate.Admission.BACKOFF,
"retry stays throttled until boundary");
check(gate.admit(70300L) == WeatherRefreshGate.Admission.FETCH,
"failed fetch may retry after ten seconds");
gate.finish(70302L, WeatherRefreshGate.Admission.FETCH, true);
check(gate.admit(70303L) == WeatherRefreshGate.Admission.REPLAY,
"successful response becomes replayable");
gate.finish(70304L, WeatherRefreshGate.Admission.REPLAY, false);
check(gate.admit(70305L) == WeatherRefreshGate.Admission.BACKOFF,
"failed replay invalidates cache and throttles retry");
check(gate.admit(80303L) == WeatherRefreshGate.Admission.BACKOFF,
"failed replay still throttled before retry boundary");
check(gate.admit(80304L) == WeatherRefreshGate.Admission.FETCH,
"failed replay retries with fresh fetch after ten seconds");
gate.finish(80306L, WeatherRefreshGate.Admission.FETCH, false);
System.out.println("result=PASS_WEATHER_REFRESH_GATE");
}
}