<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Rootcastle Engineering Notes]]></title><description><![CDATA[Explore Rootcastle Engineering & Innovation's technical articles, research and open-source projects on embedded systems, IoT, GNSS, PCB design, AI and numerical optimization.]]></description><link>https://rootcastle.hashnode.dev</link><image><url>https://cdn.hashnode.com/uploads/logos/6af1e89e42b11713fd5cdfe8/57d15153-e9ba-4850-bf29-6ebaf8ef7891.png</url><title>Rootcastle Engineering Notes</title><link>https://rootcastle.hashnode.dev</link></image><generator>RSS for Node</generator><lastBuildDate>Fri, 13 Nov 2026 19:18:02 GMT</lastBuildDate><atom:link href="https://rootcastle.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Reliable Industrial IoT Telemetry: Data Integrity at Scale]]></title><description><![CDATA[How to design industrial IoT pipelines with durable acknowledgments, replay-safe processing, correct device state, and measurable fault tolerance.]]></description><link>https://rootcastle.hashnode.dev/reliable-industrial-iot-telemetry-data-integrity-at-scale</link><guid isPermaLink="true">https://rootcastle.hashnode.dev/reliable-industrial-iot-telemetry-data-integrity-at-scale</guid><category><![CDATA[iot]]></category><category><![CDATA[distributed system]]></category><category><![CDATA[kafka]]></category><dc:creator><![CDATA[Batuhan AYRIBAS]]></dc:creator><pubDate>Wed, 11 Nov 2026 05:19:28 GMT</pubDate><content:encoded><![CDATA[<p><strong>Batuhan Ayrıbaş</strong><br /><em>Rootcastle Engineering &amp; Innovation | Ankara, Türkiye</em><br /><a href="https://orcid.org/0009-0009-2807-3264">ORCID: 0009-0009-2807-3264</a></p>
<p>A device sends a telemetry packet. The server acknowledges it. A dashboard displays the device as online. Everything appears to be working.</p>
<p>Now suppose the gateway crashes between receiving the packet and writing it to durable storage. Or an older position arrives after a newer one and overwrites the latest state. Or a replay duplicates an event in the historical database.</p>
<p>These failures look different operationally, but they have a common source: <strong>message delivery, durable acceptance, historical persistence, and correct device state are not the same guarantee</strong>.</p>
<p>This article examines how to specify those guarantees separately and how to test them in an industrial IoT pipeline. The reference architecture comes from Sofia Parser, a Rootcastle engineering project built around a protocol-aware gateway, a Kafka-compatible event log, stream normalization, Redis, and ClickHouse. The proposed 10,000-to-50,000-device experiments are <strong>evaluation targets, not completed benchmark results</strong>.</p>
<h2>The problem with calling a pipeline "reliable"</h2>
<p>A telemetry event passes through several independently failing components:</p>
<pre><code class="language-text">Device / tracker
    |
    v
Protocol-aware TCP gateway
    |    Device ACK must follow durable publish
    v
Kafka-compatible durable event log (Redpanda)
    |
    v
At-least-once normalizer
    |---------------------------|
    v                           v
ClickHouse history           Redis latest state
    |                           |
    v                           v
Audit / reconciliation       Live dashboard
</code></pre>
<p>The gateway can accept a packet while the broker is unavailable. The broker can retain a packet while the normalizer is restarting. A normalizer can successfully update one sink and fail to update the other. A replay can be necessary for recovery but dangerous if consumers are not idempotent.</p>
<p>Consequently, I separate reliability into five testable properties:</p>
<ol>
<li><strong>Acknowledgment safety:</strong> an acknowledged event has crossed the declared durable-ingestion boundary.</li>
<li><strong>Accountable disposition:</strong> each accepted event is eventually persisted, explicitly quarantined, or still reported as unresolved; it must not disappear silently.</li>
<li><strong>Logical idempotence:</strong> processing the same event again does not create another logical history event or transition.</li>
<li><strong>Non-regressing state:</strong> delayed or replayed events cannot move a stored state backward under the selected ordering rule.</li>
<li><strong>Cross-sink convergence:</strong> after a recoverable fault, the history and live-state materializations can be checked and reconciled.</li>
</ol>
<p>None of these properties follows from throughput alone. A system processing thousands of packets per second may still produce an incorrect dashboard.</p>
<h2>1. A device ACK is a commitment, not a courtesy</h2>
<p>Consider this sequence:</p>
<pre><code class="language-text">T0  Device sends event E42
T1  Gateway receives E42
T2  Gateway replies ACK
T3  Gateway process crashes
T4  E42 was never written to the event log
</code></pre>
<p>The device may delete E42 from its local queue because it has already received the acknowledgment. From the device's perspective, delivery succeeded. From the server's perspective, there is no durable record.</p>
<p>The minimum useful safety condition is:</p>
<pre><code class="language-text">ACK(event) =&gt; DurableLogCommit(event)
</code></pre>
<p>For a native gateway, the publish operation should complete successfully under its configured broker acknowledgment and replication policy <strong>before</strong> the gateway sends the protocol ACK:</p>
<pre><code class="language-go">// Illustrative control flow; not a complete device protocol implementation.
if err := publisher.Publish(ctx, rawEnvelope); err != nil {
    // No successful broker publish: do not acknowledge the device.
    return err
}

return sendProtocolACK(conn, packetID)
</code></pre>
<p>Sofia Parser's documented native gateway follows this ordering and requests broker acknowledgment with <code>acks=all</code>. However, <code>acks=all</code> must be interpreted with the actual replication factor, in-sync replica configuration, persistence policy, and failure model. A single-broker development stack does not establish multi-node fault tolerance.</p>
<p>There is also a non-obvious boundary: a legacy device-protocol runtime may send its device ACK independently of a downstream forwarding bridge. Such a path <strong>does not inherit</strong> the native gateway's broker-before-ACK guarantee. Protocol coverage and acknowledgment safety must be certified independently.</p>
<h2>2. At-least-once processing requires stable event identity</h2>
<p>After an uncertain network failure, a device may retransmit a message even if its first copy was durably stored. A consumer may also replay records after a crash. Both behaviors are normal in an at-least-once pipeline.</p>
<p>The resulting question is not "How do we prevent every duplicate transmission?" It is "How do we prevent duplicate transmissions from changing the logical result?"</p>
<p>A stable event identifier should satisfy two different conditions:</p>
<ul>
<li>A retransmission of the <strong>same observation</strong> produces the <strong>same identity</strong>.</li>
<li>Two <strong>different observations</strong> do not collapse into one identity, even if their payload values are equal.</li>
</ul>
<p>A hash of raw packet bytes can identify byte-identical retries. But if a tracker emits the same coordinates, sensor reading, and timestamp for two legitimate measurements, raw-byte identity alone may merge them incorrectly. Where device firmware supports it, a device sequence and boot/session epoch provide a stronger identity contract.</p>
<p>Sofia Parser's existing deterministic packet and event IDs support replay-aware processing. A device-epoch-plus-sequence identity is a <strong>proposed research extension</strong>, not a production capability being claimed here.</p>
<p>Downstream processing can then use idempotent writes and duplicate-aware queries. A replacing-history table does not, by itself, guarantee that every read sees a deduplicated view immediately. Logical uniqueness must be checked against an independent event ledger.</p>
<h2>3. Monotonic state is not the same as physically correct state</h2>
<p>Suppose a tracker sends these events:</p>
<table>
<thead>
<tr>
<th>Event</th>
<th>Device event time</th>
<th>Arrival order</th>
<th>Position</th>
</tr>
</thead>
<tbody><tr>
<td>A</td>
<td>10:00:10</td>
<td>First</td>
<td>Point A</td>
</tr>
<tr>
<td>B</td>
<td>10:00:20</td>
<td>Second</td>
<td>Point B</td>
</tr>
<tr>
<td>A (replay)</td>
<td>10:00:10</td>
<td>Third</td>
<td>Point A</td>
</tr>
</tbody></table>
<p>A naive "last arrival wins" update overwrites Point B with Point A. The live map moves backward even though the replay contains no newer observation.</p>
<p>An atomic comparison of a declared event rank can prevent that kind of regression:</p>
<pre><code class="language-text">new_state = max(current_state, candidate_state, by = event_rank)
</code></pre>
<p>A rank may combine event timestamp, sequence presence/value, record index, receive timestamp, and deterministic event ID. The comparison must be atomic: concurrent consumers should not be able to overwrite a newer rank with an older one.</p>
<p>But there is an important counterexample. A device with a badly configured clock may send an event timestamped hours into the future. A monotonic ranking rule could accept that event and then reject legitimate subsequent observations for hours. The ranking invariant would hold, but the <strong>physical state would be stale</strong>.</p>
<p>Therefore, the experiment must measure two outcomes separately:</p>
<ul>
<li><strong>Rank regression:</strong> Did the stored rank decrease?</li>
<li><strong>Physical recency error:</strong> Does the displayed event represent the latest valid observation according to an independent ground-truth sequence?</li>
</ul>
<p>A system that passes the first test has not automatically passed the second. Clock skew, rebooted sequence counters, session epochs, and missing timestamps are part of the correctness contract rather than incidental data-quality problems.</p>
<h2>4. Protocol counts do not equal certified protocol support</h2>
<p>The GitHub-inspected Sofia Parser HW2 compatibility catalogue registers <strong>272 protocol names</strong>. The registration classification is:</p>
<table>
<thead>
<tr>
<th>Registration category</th>
<th align="right">Count</th>
</tr>
</thead>
<tbody><tr>
<td>Direct decoder registrations</td>
<td align="right">36</td>
</tr>
<tr>
<td>Family aliases</td>
<td align="right">14</td>
</tr>
<tr>
<td>Generic-parser aliases</td>
<td align="right">221</td>
</tr>
<tr>
<td>Generic fallback</td>
<td align="right">1</td>
</tr>
<tr>
<td><strong>Total</strong></td>
<td align="right"><strong>272</strong></td>
</tr>
</tbody></table>
<p>The 36 direct registrations correspond to <strong>29 distinct concrete implementation types</strong>. The remaining alias entries must not be presented as independently implemented and validated vendor decoders.</p>
<p>This distinction matters for reliability. Supporting a protocol name in a parser registry does not prove that a specific device firmware sends correct acknowledgments, handles fragmented TCP frames, or retries safely after broker failure.</p>
<p><strong>Figure to insert here:</strong> <em>Sofia Parser HW2 protocol-registration distribution (source-inspected repository data).</em> Upload the companion PNG in Hashnode after pasting this article.</p>
<p>The evidence needed for protocol certification includes captured and sanitized packet fixtures, CRC/checksum tests, frame fragmentation and coalescing, authentication where supported, specific ACK bytes, reconnect/replay behavior, and negative cases. An internal simulator can validate software contracts, but it cannot substitute for real-device protocol testing.</p>
<h2>5. Scaling begins with an offered-load equation</h2>
<p>Connection count alone is a misleading performance metric. A fleet of 50,000 connected devices reporting once a minute presents a different workload from the same fleet reporting every ten seconds.</p>
<p>For a simple periodic model:</p>
<pre><code class="language-text">events_per_second = active_devices / report_interval_seconds
</code></pre>
<table>
<thead>
<tr>
<th>Connected devices</th>
<th align="right">Every 60 s</th>
<th align="right">Every 10 s</th>
</tr>
</thead>
<tbody><tr>
<td>10,000</td>
<td align="right">166.7 events/s</td>
<td align="right">1,000 events/s</td>
</tr>
<tr>
<td>25,000</td>
<td align="right">416.7 events/s</td>
<td align="right">2,500 events/s</td>
</tr>
<tr>
<td>50,000</td>
<td align="right">833.3 events/s</td>
<td align="right">5,000 events/s</td>
</tr>
</tbody></table>
<p>These are <strong>arithmetic offered-load estimates</strong>, not measured Sofia throughput. Real fleets introduce bursty reconnects, synchronized reporting, differing payload sizes, TLS overhead, variable device behavior, retries, and uneven per-tenant traffic.</p>
<p><strong>Figure to insert here:</strong> <em>Calculated offered load under 10-second and 60-second reporting intervals.</em> Upload the companion PNG in Hashnode after pasting this article.</p>
<p>A benchmark should therefore report connection concurrency and event throughput separately. The Sofia operations plan identifies 10,000, 25,000, and 50,000 connections, a sustained workload of 5,000 events/s, a 25,000-events/s burst scenario, and a 72-hour soak as test targets. These targets have <strong>not</strong> been established as achieved results.</p>
<h2>6. Fault injection is the experiment, not an afterthought</h2>
<p>A baseline test with no failures reveals little about data integrity. The study should compare controlled architectures under the <em>same event ledger, hardware, payloads, and fault schedule</em>.</p>
<p>The minimal comparisons are:</p>
<table>
<thead>
<tr>
<th>Variant</th>
<th>ACK policy</th>
<th>Replay handling</th>
<th>Latest-state policy</th>
</tr>
</thead>
<tbody><tr>
<td>B0</td>
<td>ACK on receive</td>
<td>Non-idempotent</td>
<td>Arrival order</td>
</tr>
<tr>
<td>B1</td>
<td>ACK after durable broker publish</td>
<td>Non-idempotent</td>
<td>Arrival order</td>
</tr>
<tr>
<td>B2</td>
<td>ACK after durable broker publish</td>
<td>Stable IDs and idempotence</td>
<td>Arrival order</td>
</tr>
<tr>
<td>B3</td>
<td>ACK after durable broker publish</td>
<td>Stable IDs and idempotence</td>
<td>Atomic ordered update</td>
</tr>
</tbody></table>
<p>These variants isolate what each design mechanism contributes. They do not assume that the strongest design will be fastest.</p>
<p>Fault scenarios should include gateway death before/after publication, broker leader loss, insufficient in-sync replicas, timeout immediately before an ACK, consumer crash after updating one sink, Redis and ClickHouse outages, replay after offset rewind, clock skew, sequence reset, and identical-payload distinct observations.</p>
<p>Each run needs an independent oracle to compare four records: what the generator intended to send, what the device was told was accepted, what the raw log committed, and what the history and latest-state stores expose. Without this independent ground truth, the system can incorrectly grade its own results.</p>
<h2>7. Metrics that actually answer the question</h2>
<p>For each test condition, measure:</p>
<ul>
<li><strong>ACK safety violations:</strong> acknowledged logical events absent from the committed raw log within the declared observation window.</li>
<li><strong>Unresolved accepted events:</strong> events with no persisted or quarantined outcome after the allowed recovery window.</li>
<li><strong>Logical history duplicates:</strong> extra visible logical effects after replay, not just repeated physical insert attempts.</li>
<li><strong>State-rank regressions:</strong> transitions to a lower declared rank.</li>
<li><strong>Physical recency errors:</strong> differences from the independent valid device sequence.</li>
<li><strong>Tail latency:</strong> ACK and end-to-live p95/p99, with explicit measurement points.</li>
<li><strong>Recovery time:</strong> time to resume processing and reconcile history and live state.</li>
<li><strong>Cost under load:</strong> CPU, memory, network, broker lag, open connections, and backpressure.</li>
</ul>
<p>Zero observed violations would be meaningful only within the tested failure model and sample size. It would not prove that zero loss is possible under every real-world failure. Conversely, a counterexample would identify which assumption or component boundary must change.</p>
<p>The test report should preserve source revision, image digests, broker settings, workload seed, fault schedule, event IDs, and verification scripts. A run with missing evidence should be marked <strong>NOT VERIFIED</strong>, not PASS.</p>
<h2>8. What would count as a useful result?</h2>
<p>A positive finding would be evidence that postponing ACKs eliminates the tested ACK-before-commit failures, while stable identities and ordered updates reduce measured duplicate effects and state-rank regressions. A negative finding would be equally informative if it isolates a capacity bottleneck, an unfair retry policy, an incorrect clock assumption, or a compatibility protocol that acknowledges too early.</p>
<p>There are unavoidable trade-offs. Withholding acknowledgments during broker quorum loss protects an acceptance contract but reduces availability. Idempotent sinks may require extra state or reconciliation work. Atomic latest-state updates may increase latency under contention. These costs must be measured rather than hidden behind a single throughput figure.</p>
<p>The objective is not to claim "exactly once" for an entire heterogeneous pipeline. It is to specify <strong>which operation is performed once, which operation may be retried, what remains observable, and which failure assumptions are required</strong>.</p>
<h2>Conclusion</h2>
<p>Industrial IoT reliability is an end-to-end, cross-layer property. A broker acknowledgment does not prove that a database has persisted an event. A non-regressing state rank does not prove that a device's physical state is correct. A protocol catalogue does not prove that every listed tracker is production-ready.</p>
<p>The practical engineering approach is to make those distinctions explicit, introduce a durable acceptance boundary, preserve stable logical identities, prevent stale state transitions, and run controlled fault injection against independent event truth. Only then can connection count, event rate, and recovery time be interpreted as evidence of a system's reliability at scale.</p>
<p>For Rootcastle, the next step is a reproducible fault-injection study of Sofia Parser's native and compatibility ingestion paths, including multi-broker operation and real-device fixtures. The published design is a research protocol; <strong>system-wide 50,000-device capacity and zero-loss operation remain unverified until the planned tests are executed</strong>.</p>
<hr />
<h3>Further reading</h3>
<ul>
<li>Saltzer, Reed &amp; Clark (1984), <a href="https://doi.org/10.1145/357401.357402">End-to-End Arguments in System Design</a>.</li>
<li>Lamport (1978), <a href="https://doi.org/10.1145/359545.359563">Time, Clocks, and the Ordering of Events in a Distributed System</a>.</li>
<li>Akidau et al. (2015), <a href="https://doi.org/10.14778/2824032.2824076">The Dataflow Model</a>.</li>
<li>Fragkoulis et al. (2024), <a href="https://doi.org/10.1007/s00778-023-00819-8">A Survey on the Evolution of Stream Processing Systems</a>.</li>
<li><a href="https://cwiki.apache.org/confluence/spaces/KAFKA/pages/66854913/KIP-98+-+Exactly+Once+Delivery+and+Transactional+Messaging">Apache Kafka: KIP-98 Exactly Once Delivery and Transactional Messaging</a>.</li>
<li><a href="https://nightlies.apache.org/flink/flink-docs-stable/docs/learn-flink/fault_tolerance/">Apache Flink: Fault Tolerance</a>.</li>
</ul>
<p><em>Research context: <a href="https://rootcastle.com">Rootcastle Engineering &amp; Innovation</a>. Source-inspected code and engineering documents: Sofia Parser, revision 4e7e667 (11 November 2026); the repository is private.</em></p>
]]></content:encoded></item><item><title><![CDATA[Building Reliable GNSS Telemetry Ingestion Pipelines]]></title><description><![CDATA[Design GNSS event contracts, bounded framing, duplicate handling, timestamp quality and replay tests with a synthetic Python example.]]></description><link>https://rootcastle.hashnode.dev/building-reliable-gnss-telemetry-ingestion-pipelines</link><guid isPermaLink="true">https://rootcastle.hashnode.dev/building-reliable-gnss-telemetry-ingestion-pipelines</guid><dc:creator><![CDATA[Batuhan AYRIBAS]]></dc:creator><pubDate>Mon, 09 Nov 2026 16:43:12 GMT</pubDate><content:encoded><![CDATA[<h2>Start with an event contract</h2>
<p>A valid coordinate is only one part of a telemetry event. A useful ingestion contract also states the authenticated device identity, the protocol message identity, event time, receive time, position quality and the version of the parser that produced the record. These fields answer different questions. Receive time describes the server; event time describes a device observation. Neither timestamp should silently replace the other.</p>
<p>This walkthrough describes an architecture pattern. It does not disclose or claim to demonstrate a private REI Track deployment. The public <a href="https://rootcastle.com/en/projects/">Rootcastle engineering portfolio</a> and public protocol repositories provide separate entry points for inspecting available work.</p>
<h2>Frame before parsing a business record</h2>
<p>TCP is a byte stream. One socket read may contain half a frame, several frames or the end of one frame followed by another. Maintain a bounded receive buffer, extract complete protocol frames and validate their length and checksum before asking the domain parser to interpret coordinates. UDP preserves datagram boundaries, but a datagram still needs protocol validation.</p>
<p>Represent incomplete data separately from invalid data. An incomplete frame may become valid when more bytes arrive. An invalid frame needs a bounded resynchronization policy or rejection. A maximum frame length and an idle timeout keep a malformed connection from growing memory indefinitely. Use a synthetic frame corpus to test those paths without sending customer telemetry to a third-party service.</p>
<h2>Make duplicate policy a domain decision</h2>
<p>Retries and reconnects can repeat messages. A protocol may carry a sequence number, but counters can wrap or reset after a reboot. A deduplication key therefore needs documented scope: device, boot or session identity where available, message type and sequence. If the protocol lacks a stable message identity, describe the limitations of a time-and-payload fingerprint instead of promising exactly-once delivery.</p>
<p>The following toy example illustrates a device-scoped key. Its in-memory set has no durability or expiry and is not a production deduplication store.</p>
<pre><code class="language-python">seen = set()
def accept(event):
    key = (event['device'], event['session'], event['sequence'])
    if key in seen:
        return False
    seen.add(key)
    return True

a = {'device': 'synthetic-a', 'session': 'boot-1', 'sequence': 7}
b = {'device': 'synthetic-b', 'session': 'boot-1', 'sequence': 7}
assert accept(a)
assert not accept(a)
assert accept(b)
</code></pre>
<p>A real service must bound retention, handle concurrent workers and preserve the chosen identity across restarts. An acknowledgement should follow the protocol contract and the intended durability point. Acknowledging before durable acceptance can lose an event; acknowledging too late can provoke unnecessary retries.</p>
<h2>Preserve time and quality instead of repairing silently</h2>
<p>Keep event and receive timestamps, including an explicit UTC interpretation. Record invalid or absent GNSS fixes as quality states. Do not convert a missing fix into the coordinate zero, zero. Similarly, a late packet should not automatically move a live vehicle marker backward in time. Separate storage of late historical events from the policy that updates the latest-position view.</p>
<p>Validate latitude and longitude ranges, units, hemisphere signs, fix flags and speed semantics against the selected protocol. A parser version belongs with the normalized event so that an interpretation change can be traced later.</p>
<h2>Replay is an acceptance test</h2>
<p>A useful fixture set includes split frames, concatenated frames, invalid lengths, bad checksums, reconnect duplicates, sequence wrap, device reboot, late data and absent fixes. Save the expected normalized events and rejection reasons. Replay the same corpus through a parser change and compare semantic outputs, not only whether the process stayed alive.</p>
<p>Track bounded queue depth, validation failures, processing latency and duplicate rate without putting raw device identifiers or payloads into general application logs. Detailed evidence belongs in a permissioned, retention-controlled store.</p>
<h2>Checklist and public starting points</h2>
<ul>
<li>Define the event identity and the durability point before implementing acknowledgements.</li>
<li>Test stream fragmentation independently from domain parsing.</li>
<li>Keep event time, receive time and GNSS quality distinct.</li>
<li>Bound buffers, retries, retention and diagnostic payload storage.</li>
<li>Version fixtures and compare normalized outputs before rollout.</li>
</ul>
<p>For inspectable protocol work, see the public <a href="https://github.com/rootcastleco/jt808-parser">JT808 parser repository</a>. A repository's current README defines its actual scope; the presence of a parser does not establish a complete commercial fleet platform. For integration requirements, <a href="https://rootcastle.com/en/contact/">describe your system to Rootcastle</a>.</p>
<hr />
<p>Originally published at <a href="https://rootcastle.com/en/engineering-notes/reliable-gnss-telemetry/">Rootcastle Engineering &amp; Innovation</a>.</p>
<p><a href="https://rootcastle.com/en/projects/">Explore the engineering tools</a> · <a href="https://rootcastle.com/en/community/">Public profiles and publications</a></p>
]]></content:encoded></item></channel></rss>