When reading a pressure transducer, I’m currently using the following code to apply the “device under test data” from the calibration report. This significantly improves any dead band across the whole scope; vs just using the entire pressure range. What I’m looking to understand is there a better way to optimize this vs a for loop with javascript? Is there a better way to determine which range segment of calibration data the output voltage is with in?
The application is using a web worker to read the values from the transducer every 250ms, pushes them into an array with a set length, to be averaged every 1000ms and then returned to application and plotted to a graph.
Current code to process readings (code works, just looking for input & feedback):
/**
* Example device under test calibration data
* 4-20ma 0-10,000psi Transducer
* Column 0: Voltage in volts
* Column 1: Pressure in psi
*/
const CALIBRATION_DATA = [
[4.01, 0.0],
[7.20, 2000.1],
[10.41, 4000.0],
[13.62, 6000.2],
[16.81, 8000.4],
[20.02, 9999.0],
];
let activeSegmentIndex = 0;
function getPsi(volts: number, CALIBRATION_DATA: number[][]): number {
let lower = CALIBRATION_DATA[activeSegmentIndex];
let upper = CALIBRATION_DATA[activeSegmentIndex + 1];
if (!(volts >= lower[0] && volts <= upper[0])) {
for (let i = 0; i < CALIBRATION_DATA.length - 1; i++) {
if (volts >= CALIBRATION_DATA[i][0] && volts <= CALIBRATION_DATA[i + 1][0]) {
// Found the segment for the current voltage
activeSegmentIndex = i;
lower = CALIBRATION_DATA[i];
upper = CALIBRATION_DATA[i + 1];
break;
}
}
}
// Calculate slope and offset for the segment
const slope = (upper[1] - lower[1]) / (upper[0] - lower[0]);
const offset = lower[1] - (slope * lower[0]);
const calculatedValue = volts * slope + offset;
return Number(calculatedValue.toFixed(0));
}
Most common formula using entire pressure range but introduces dead band.
- P=PR∗(Vr−VL)/(Vu−VL)
- P = Pressure
- PR = Pressure Range (Transducer lowestValue to HighestValue i.e 0-10k)
- Vr = Volts reading
- VL = Volts lower limit
- Vu = Volts upper limit
And for sake of entirety here is the entire WebWorker
/// <reference lib="webworker" />
import { DoWork, runWorker } from 'observable-webworker';
import { fromFetch } from 'rxjs/fetch';
import { Observable, of } from "rxjs";
import { catchError, delay, map, repeat, scan, switchMap, tap } from "rxjs/operators";
import { WebRelayStateModel } from '@dis/webrelay';
import { PreferencesStateModel } from '../../pattern/state/preferences/preferences.state';
export class WebRelayWorker implements DoWork<any, any> {
public work(input$: Observable<PreferencesStateModel>) {
return input$.pipe(
switchMap(input => {
const { webRelayIp, pollInterval, observedInput, inputs } = input;
const webRelayState$ = getRelayState(webRelayIp, pollInterval);
let webRelayState: WebRelayStateModel;
return webRelayState$.pipe(
tap(_webRelayState => webRelayState = _webRelayState),
// Map the web relay state to the input being monitored;
map((webRelayState: WebRelayStateModel) => Number(webRelayState[`analogInput${observedInput + 1}`])),
// tap(console.log),
// Calculate to psi apply offset and drop any negative numbers
map(value => getPsi(value, inputs[observedInput].dutData)),
// Calculate offset from zeroing data
map(value => value - Number(inputs[observedInput].offset)),
//tap(console.log),
map(value => value > 0 ? value : 0),
//tap(console.log),
// Buffer the amount of readings that is set in preferences
scan((acc, curr) => {
acc.push(curr);
if (acc.length > Number(inputs[observedInput].bufferArrayLength)) {
acc.shift();
}
return acc;
}, []),
// tap(console.log),
// Avervage the collected readings into current pressure;
map(arr => Number((arr.reduce((acc, current) => acc + current, 0) / arr.length).toFixed(0))),
// tap(console.log),
// Return the web relay state and the calculated value
map(value => [webRelayState, value]),
)
})
)
}
}
runWorker(WebRelayWorker);
/**
* Example device under test calibration data
* Column 0: Voltage in volts
* Column 1: Pressure in psi
*/
const CALIBRATION_DATA = [
[4.01, 0.0],
[7.20, 2000.1],
[10.41, 4000.0],
[13.62, 6000.2],
[16.81, 8000.4],
[20.02, 9999.0],
];
let activeSegmentIndex = 0;
function getPsi(volts: number, CALIBRATION_DATA: number[][]): number {
let lower = CALIBRATION_DATA[activeSegmentIndex];
let upper = CALIBRATION_DATA[activeSegmentIndex + 1];
if (!(volts >= lower[0] && volts <= upper[0])) {
for (let i = 0; i < CALIBRATION_DATA.length - 1; i++) {
if (volts >= CALIBRATION_DATA[i][0] && volts <= CALIBRATION_DATA[i + 1][0]) {
// Found the segment for the current voltage
activeSegmentIndex = i;
lower = CALIBRATION_DATA[i];
upper = CALIBRATION_DATA[i + 1];
break;
}
}
}
// Calculate slope and offset for the segment
const slope = (upper[1] - lower[1]) / (upper[0] - lower[0]);
const offset = lower[1] - (slope * lower[0]);
const calculatedValue = volts * slope + offset;
return Number(calculatedValue.toFixed(0));
}
function getRelayState(webRelayIp: string, pollInterval: number, simulateData?: boolean) {
const webRelayUrl = `${webRelayIp}/state.json`;
return fromFetch(webRelayUrl)
.pipe(
switchMap(response => {
if (response.ok) {
// OK return data
return response.json();
} else {
// Server is returning a status requiring the client to try something else.
return of({ error: true, message: `Error ${response.status}` });
}
}),
catchError(err => {
// Network or other error, handle appropriately
console.error(err);
return of({ error: true, message: err.message })
}),
delay(pollInterval),
repeat(),
);
}
