Skip to content

Free tool

tuning-stale-scan — what notices a device that stopped talking

On a default Niagara station, nothing does. staleTime ships at zero, and the single branch in the framework that would mark a point stale returns immediately when it is zero. tuning-stale-scan reads that out of the compiled classes with javap and prints the policy defaults, the branch, the clock it compares against, and the four places the machinery throws its errors away. One Python file, read-only, and no station is contacted.

  • Niagara 4
  • 2 jars
  • javap
  • staleTime
  • Read-only
  • Python 3

Install and run

There is no install. One file, standard library only, and the only thing it needs besides Python is a javap — which it looks for in $JAVAP, on PATH, under $JAVA_HOME, in the JDK Niagara ships, and only then at a Debian path. It reads four compiled classes out of two shipped jars and contacts nothing.

# download it
curl -O https://plantroomlabs.com/tools/tuning-stale-scan.py

# read the tuning machinery in the default install
python3 tuning-stale-scan.py

# or name the installation to read
python3 tuning-stale-scan.py /opt/Niagara/Niagara-4.15.5.22

The default policy, and the branch that reads it

The default tuning policy, read out of the static initialiser:
  minWriteTime   0 ms     (min facet 0 ms)
  maxWriteTime   0 ms     (min facet 0 ms)
  staleTime      0 ms     (min facet 0 ms)
  writeOnStart   true
  writeOnUp      true
  writeOnEnabled true

Tuning.process(), the only code in the framework that notices:
  at offset 160 it loads staleTime, compares it with zero, and on
  less-or-equal jumps to 212 - which is the method's own return.
  The default is 0, so by default this branch is never taken and no
  point in any driver on the station ever goes stale.

  With staleTime set, the rest reads:
    elapsed = now - (mode.isWriteonly() ? writeTicks : readTicks)
    if elapsed > staleTime, call stale()   (the branch is ifle)
  so the threshold is strictly greater than staleTime, and the clock is
  readTicks - which only readOk() ever sets.

stale() is three instructions: get the tunable, call setStale(true, null).
  It is the driver's own BITunable implementation that decides what
  that means for the point's status and value. The framework sets a
  flag and stops.

Zero means the feature is off, not instant

staleTime defaults to 0 ms. The method that would mark a point stale loads it, compares it with zero, and on less-or-equal jumps straight to its own return. So on a station nobody has configured, no point in any driver ever goes stale.

The clock is readTicks, and only one call sets it

The elapsed time is measured against readTicks, which only readOk() ever sets. If a driver calls readOk() because a subscription or a link is healthy rather than because a value arrived, a configured staleTime still never fires. That is one line in a proxy extension and it decides whether the feature works at all.

The framework sets a flag and stops

stale() is three instructions: get the tunable, call setStale(true, null). What that means for the point's status and value is the driver's own BITunable implementation. Nothing else in the framework decides it.

Where the errors go

Where the errors go. Tuning holds a logger named 'driver.tuning' with
  1 warning call, and 4 printStackTrace() sites.
  The warning says: TuningPolicy not found:
  The stack traces are in:
    void write(javax.baja.sys.Context)
    void readSubscribed()
    void readUnsubscribed()
    void stale()
  Those are the four things the tuning machinery does. Every one of
  them swallows its exception to stderr, where a station's log export
  will not have it.
  The background thread does the same with anything process() throws,
  and nulls a tuning out of its array when process() returns false.

Each of those sites prints to stderr, where a station's log export will not have it. That is worth knowing before spending an afternoon on why a point never went stale: the reason may have been printed somewhere nobody collects.

What follows from it

Two consequences worth designing for
----------------------------------------------------------------------
  1. If your device reports unsolicited rather than being polled, a
     dead device is invisible by default. No poll fails, so nothing
     goes fault; staleTime is 0, so nothing goes stale; the last
     value sits there with an ok status for as long as the station runs.
  2. staleTime measures readTicks, and readTicks is set by readOk().
     If a driver calls readOk() when a link or a subscription is
     healthy rather than when a value actually arrives, then even a
     configured staleTime never fires. That is one line in a proxy
     extension and it decides whether the feature works at all.

Three checks one station settles in an afternoon
----------------------------------------------------------------------
  1. Read the default tuning policy on a live network and confirm
     staleTime is 0. If it is, no point under it can go stale.
  2. Set staleTime to a minute on one point, pull the device, and watch
     whether the point goes stale within 20000 ms of the minute.
  3. Set staleTime, leave the device alive but stop its reporting at
     the source, and see whether the point still goes stale. If it does
     not, readOk() is being called for the wrong event.

What it reads, and the limit of what the output is worth

It reads the shipped jars with javap and nothing else. No station is contacted, no point is read and nothing is written — which is also the limit of what the output is worth. It is a reading of compiled code, so it tells you what the tuning machinery is built to do rather than what a particular driver did yesterday.

The numbers above came from one installation, 4.15.5.22, and the output names it. The controller this work usually targets runs 4.14.0.162. A different Niagara version is a different answer, so run it against yours rather than trusting this page's figures.

The engineering reasoning this belongs beside is why a station polls too slowly: what to measure on a live station before changing a rate, and the order to change things in.

The file you are downloading

Published here so the download is checkable rather than trusted. Both figures are read off the file served at /tools/tuning-stale-scan.py when this page is built, so they cannot disagree with it.

PropertyValue
Filetuning-stale-scan.py
Size12,917 bytes
SHA-256 96ab5717a01582e5455a557115d8089343ac155cb176214d8ee3e08875c7d1c4
Licence MIT — LICENSE.txt
Source github.com/UsamaIqbal0304/tuning-stale-scan

To check it, on Linux sha256sum tuning-stale-scan.py, on macOS shasum -a 256 tuning-stale-scan.py, on Windows certutil -hashfile tuning-stale-scan.py SHA256. A different digest means a different file — not necessarily a hostile one, but not this one.

The repository holds the same file, byte for byte, together with everything needed to re-run the checks this page's claims rest on — so they can be run rather than read about. Issues and pull requests there are read.

Also free

The others

Same idea, a different protocol or a different file. Every free tool.

Next step

Send the driver and the tuning policy you ship.

Whether a dead device is visible depends on one property default and on which event your proxy extension calls readOk() for. Both are readable, so the answer comes back in writing with the bytes it was read from.