Overview

A historian stores a flow in m³/h. A site in the United States reads it in gpm, a site in Germany reads it in m³/h, and the same screen has to serve both. The conversion factors are simple; the difficult cases are around them: a gauge pressure converted to an absolute pressure without the atmospheric offset, a temperature difference converted like a temperature, a value stored with two decimals in psi and shown with two decimals in bar, which loses most of its resolution, and unit strings that were never normalised, so that m3/h, m³/h and M3/H are treated as three different units.

Smart.Industrial.Units provides the conversion factors and handles these cases. It is available in smart.industrial.units.js and in the bundle.

Converting

const Units = Smart.Industrial.Units;

Units.convert(78.4, 'm³/h', 'gal/min');            //345.18...
Units.convert(2.5, 'barg', 'psia');                 //50.96... - gauge to absolute, with the atmosphere
Units.convert(10, 'ΔK', 'Δ°F');                   //18 - a rise, not a temperature
Units.format(Units.convert(25, '°C', '°F'), '°F');   //"77 °F"

Units.quantityOf('psig');                           //'pressure'
Units.compatible('bar', 'kPa');                     //true
Units.normalize('degC');                            //'°C'
Units.parse('78.4 m3/h');                           //{ value: 78.4, unit: 'm³/h' }
Units.unitsOf('volumetricFlow');                    //['m³/s', 'm³/h', 'm³/min', 'm³/d', 'L/s', ...]
Units.quantities();                                 //['temperature', 'temperatureDifference', 'pressure', ...]

The table contains 25 quantities: temperature and temperature difference, pressure (gauge and absolute), volumetric and mass flow, length, mass, volume, time, velocity, rotational speed, frequency, acceleration, power, energy, voltage, current, resistance, ratio, density, conductivity, torque, force, area and viscosity. Unit strings are normalised through aliases, so a tag configured as degC or psig is recognised. A converted value is rounded to 15 significant digits, the most a double carries through decimal, so the arithmetic through the base unit leaves no visible error: Units.convert(0, '°C', '°F') returns 32, not 31.999999999999986. The number of decimals for display is set by the caller: Units.format() prints at most six decimals unless { decimals } is given, and a component that has a precisionDigits property displays that many.

Resolution

Two decimals in psi is a step of 0.01 psi, which is 0.0007 bar. Shown with two decimals in bar, fourteen consecutive readings would print the same number. The decimals() function carries the resolution across a conversion in either direction:

Units.decimals(2, 'psi', 'bar');       //4
Units.decimals(2, 'bar', 'psi');       //1
Units.decimals(1, 'µm', 'mil');       //3

tile.precisionDigits = Units.decimals(2, 'barg', displayUnit);
tile.value = Units.convert(reading, 'barg', displayUnit);

Unit systems

A unit system names the preferred unit of each quantity, so a screen converts all of its values with one setting. The si and us systems are built in, and a site can define its own.

Units.preferred('bar', 'us');                       //'psi'
Units.toSystem(3.2, 'bar', 'us');                   //{ value: 46.41..., unit: 'psi' }

Units.defineSystem('site', { pressure: 'kPag', temperature: '°C', volumetricFlow: 'L/min', length: 'µm' });
Units.systems();                                    //['si', 'us', 'site']

Units.system = 'site';                              //what bindings convert to from now on

Through Connect

A binding that names the unit of its tag converts every sample before it reaches the property, and converts the component's unit, its scale (min and max) and its alarm limits from the values they had when the binding was created. This keeps the reading and the scale in the same unit.

Connect.bind(faceplate, {
    processValue: { tag: 'PIC-102.PV', unit: 'barg', displayUnit: 'psig' },
    setpoint: { tag: 'PIC-102.SP', unit: 'barg', displayUnit: 'psig' }
});

//Or leave displayUnit out and let the system decide, per sample, so that changing
//Units.system switches a whole screen from bar to psi between two readings.
Connect.bind(flowTile, {
    value: { tag: 'FIC-101.PV', unit: 'm³/h' }
});
Units.system = 'us';

//scale: false converts the value and leaves the element's scale alone.
Connect.bind(gauge, {
    value: { tag: 'TIC-101.PV', unit: '°C', displayUnit: '°F', scale: false }
});

The scale properties that move with the value are min, max, hiHi, hi, lo, loLo, target, alarmHigh, alarmLow, warningHigh, warningLow, and the faceplate's alarmLimits.

Adding a unit

//A unit in a known quantity: value_in_base = value * factor + offset.
Units.define('pressure', 'ftH2O', { factor: 2989.06692, aliases: ['ftWC', 'ft H2O'] });

//A new quantity starts with its base.
Units.define('illuminance', 'lx', { base: true });
Units.define('illuminance', 'fc', { factor: 10.7639 });

Defining a symbol that already exists replaces it, so check Units.normalize() before adding a unit: inH2O, mmH2O, inHg, mmHg and kgf/cm², for example, are built in. Currencies, dates and unit algebra are not supported. The unit systems screen switches a live screen between SI, US customary and a site system, and shows the gauge pressure, temperature difference and resolution cases next to the result of a plain conversion.