Skip to content

Note

When the document and the code disagree

A protocol document is a claim about behaviour, not evidence of it. The way to settle one is to read the implementation that is actually running and derive the same answer a second time by a route that could have disagreed.

  • Modbus
  • BACnet
  • Integration
  • Commissioning

Written

Three sources of truth, and they do not agree

On any integration there are at least three statements about what a device or a driver does. The equipment maker's protocol document. The framework's own help on the driver that talks to it. And the compiled code that is on the disk and running tonight. They are written at different times by different people, and where two of them disagree the one that decides the value on the operator's screen is always the third.

That is not a criticism of documentation. It is the reason a point can be wired correctly, configured exactly as specified, and still read a number that is wrong in a way nobody notices for a year. A discrepancy audit is the job of finding those before commissioning does.

Five discrepancies, and all five were ours

Every row below was found by the method described further down, in shipped Niagara 4.15 code — and every one of the five was a claim published on this site and corrected once it was checked properly. They are kept here on purpose. An audit method that has never caught its own author is a method nobody has tested, and a secondary source going wrong about a default is the same failure as a vendor document going wrong about one: a sentence written from a plausible reading instead of from the code.

The claimWhat the code does How it was settled
A Modbus 32-bit value defaults to byte order 1-0-3-2 The point property defaults to 3-2-1-0. Both defaults exist: the enum's own DEFAULT is its zero ordinal, which is 1-0-3-2, and the driver's float and long properties are declared with the other one. The enum's static initialiser, then the config class's static initialiser. Two classes, two answers, and only the second one reaches a new point.
Two byte orders are available for a two-register value Three. The enum has three ordinals — 1-0-3-2, 3-2-1-0 and 0-1-2-3. The fourth arrangement a reader expects, 2-3-0-1, is genuinely absent. Counting the ordinals in the enum rather than the options remembered from a dialog box.
A server register range starting at 250 with size 75 covers 40250 – 40325 40250 – 40324. The range loop runs size times from the starting address and stores each iteration minus one. Reading the address-array builder. The arithmetic error survived because both numbers look right and the difference is one register at the far end.
BACnet MS/TP Max Info Frames accepts 0 – 100 1 – 100, defaulting to 20. The property is declared with an integer facet whose minimum is 1, so 0 is not a low setting, it is refused. The link layer's property declaration: default and facet range are operands of the same call, so both come out of one read.
Each eight categories adds a byte to every component The mask is a hex string, one character per four categories, interned and shared between components. Length is paid on the wire when a component is encoded, not once per component in memory. size(), get() and the builder all divide by four; the component's slot map holds a reference, not a copy.

The method, in five steps

1. Write the claim down verbatim, with its source. Not the gist of it. A claim you have paraphrased is a claim you have already started to defend. The source matters as much as the words: a value in a table in a vendor PDF, a sentence in a help file and a number somebody remembers from a dialog box fail in different ways.

2. Get to the primitive. For a device, that is traffic: a capture of the real exchange, or a register dump read back with an independent tool. For a driver, it is the class that declares the property, not the documentation about it. Both are available without a licence, an NDA or pre-release access — reading a jar that is already on your own disk is not gated by anything.

3. Read the operand, not the prose. Niagara declares a property's default and its permitted range as arguments to one call, so both facts come out of a single line of bytecode. The MS/TP row above is literally newProperty(flags, 20, BFacets.makeInt(1, 100)): default 20, minimum 1, maximum 100. Nothing about that has to be inferred, and nothing about it depends on which help file is open.

4. Derive it a second time, by a route that could disagree. This is the step that earns the audit. The category-mask row was checked twice — the accessor that reads a single index, and the builder that sizes the buffer — because one of them arithmetically contradicting the other is exactly how a wrong answer announces itself. When two independent reads agree, the remaining risk is that the question was wrong, not the answer.

5. Record the command, not the conclusion. Every row above has a command behind it that can be re-run on a different version and produce either the same answer or a different one. A finding that cannot be re-derived next year is a rumour with a date on it.

Count nothing before the predicate is written down. One sweep of the 4.15 module set for a particular code idiom returned 31 modules; a second sweep, for the same idiom described more strictly, returned 29. Neither number was wrong. The question was — "carries that pattern" had never been defined tightly enough to have one answer. Any audit that reports a count should state the predicate it counted, and where a looser reading gives a different number, both.

What the deliverable looks like

A discrepancy audit is not a document of opinions. Each row is: the claim as written, where it came from, what was observed, the command that establishes it, and the consequence for the job. The consequence column is what makes it worth reading — a wrong default that nobody sets matters less than an off-by-one at the end of a register range that a master will poll on day one.

The same table serves two audiences without rewriting: the engineer who has to make the point read correctly, and the specifier who needs a reason to change a line in the document. Where the answer depends on the version, the version is part of the row; everything above was established against 4.15, and a mixed estate deserves the check repeating on its own floor.

None of this needs a client's name or their equipment schedule to be useful, which is why the method can be published and the jobs cannot. If the question is what your own estate does rather than what the document says, sending a capture and the protocol document is the whole first step.

Sources

Where this is written down

Related

Where this comes up in the work

More notes

Other things worth writing down

Next step

Tell us the version, the hardware, and what it has to do.

You will get a written scope and a fixed price against it. If the honest answer is that you do not need us, you will get that instead.