/**
* Weigh Scales Driver
* @module weigh
* @desc This driver implements the protocol to retreive samples from the ADC in the weigh scales.
* The weight is periodically sampled and an event fired to all listeners when the weight changes.
*/
const brewdefs = require('../../../common/brewdefs.js');
const brewlog = require('../../../common/brewlog.js');
const broker = require('../../../common/broker.js');
const calibrationFile = require('json-fs-store')(`${brewdefs.installDir}/brewstack/equipmentDrivers/weight`);
const mraa = require('../../nodeDrivers/i2c/i2c_mraa.js');
const temp = require('../../nodeDrivers/therm/temp.js');
let calibration;
let LOG_RAW_SAMPLES; //"./raw_weight.log"; //undefine to turn off
const EVENT_INTERVAL_SECS = 10 * 60;
const NAME = "Fermenter Weight";
const MAX_DELTA = 256;
const RESOLUTION = 10;//round to the nearest grams
const SENSOR_NAME = "Kg";
const MAX_KG = 50;
const current = {temp:undefined, kg:undefined};
let timer;
let prevKg;
const ADC_DATA_HIGH = 1;
const ADC_CLK_HIGH = 1;
const ADC_CLK_LOW = 0;
let publishWeight;
let ADC_clk;
let ADC_data;
const i2c_mraa = require('../../nodeDrivers/i2c/i2c_mraa.js');
/**
* @desc Take a sample by driving the GPIO pins connected to the ADC.
* The protocol is:
* 0 - Drive CLK low to leave low power mode.
* 1 - Wait until the ADO pin goes low.
* 2 - Drive CLK high.
* 3 - Drive CLK low.
* 4 - Read ADO
* 5 - Repeat 2,3,4 for each of the 24 bits.
* 7 - Drive CLK high
* 8 - Drive CLK low
* 9 - Drive CLK high to enter low power mode.
*/
function sample(){
const TIMEOUT_ITERATIONS = 100000;
let i;
let count = -1;
let t = 0;
ADC_clk.write(ADC_CLK_LOW);//Active
//console.log("waiting for DO ("+brewdefs.GPIO_ADC_DATA+ ") to go low");
while ((ADC_data.read() == ADC_DATA_HIGH) && (t++ < TIMEOUT_ITERATIONS)){
//wait for DO to go low
}
if (t < TIMEOUT_ITERATIONS) {
count=0;
for(i=0;i<24;i++){
ADC_clk.write(ADC_CLK_HIGH);
ADC_clk.write(ADC_CLK_LOW);
count=count<<1;
if (ADC_data.read() == ADC_DATA_HIGH){
count++;
}
}
}
else{
brewlog.error("Timeout waiting upon ADDO.");
return -1;
}
ADC_clk.write(ADC_CLK_HIGH);
ADC_clk.write(ADC_CLK_LOW);
ADC_clk.write(ADC_CLK_HIGH);//Low power
return count;
}
//Take n samples
function samples(n){
let i = 0;
const s = [];
let w;
let w1;
while (i++ < n){
w = sample();
if (w !== -1) {
if ((w & 0x0800000) !== 0x0800000){
//skip negative values
w1 = (w & 0x00FFFFFF) >> 0;
s.push(w1);
if (LOG_RAW_SAMPLES){
let Kg = (w1 - calibration.c)/ calibration.m;
brewlog.info("Raw Weight=", Kg);
}
}
}else{
return -1;
}
}
return s;
}
//average and filter a number of samples
function average(){
const AVG = 10;//average over this number of samples
const s = samples(AVG);
if (s === -1) {
return -1;
}
//filter by limiting the difference between samples.
let total = 0;
let n = 0;
let prev = 0;
s.forEach(weight => {
if (prev !== 0){
if (Math.abs(weight-prev) <= MAX_DELTA){
n++;
total += weight;
}
}else{
n = 1;
total = weight;
}
prev = weight;
});
if (n === 0){
return -1;
}else {
return total/n;
}
}
/*
Measurements showed that as
temp varied from 19.6 to 7.3 (12.3DegC) , the
weight varied from 20.6 to 22.0 (+6.8%)
i.e. 114g/C
*/
function tempAdjust(kg, calTemp, temp){
//console.log(kg, calTemp, temp)
let offset = (temp - calTemp) * 0.114;
//console.log("offset=",offset)
const result = Math.ceil((kg+offset)*1000/RESOLUTION) * RESOLUTION / 1000;
return result < 0 ? 0 : result;
}
function weigh() {
if (calibration === undefined){
brewlog.error("Attempted to weight prior to calibration data being loaded");
return;
}
const x = average();
if (x === -1){
brewlog.error("Error taking weight");
return;
}else{
if (x >= 0){
const g = (1000*(x - calibration.c))/ calibration.m;
var kg;
if (g < 0){
kg = 0;
}else{
kg = (Math.ceil(g/RESOLUTION)*RESOLUTION)/1000;
}
}
if (kg < MAX_KG){
//console.log("Raw weight =",kg)
kg = tempAdjust(kg, calibration.temp, current.temp);
if (kg !== prevKg)
{
current.kg = kg;
publishWeight(kg);
prevKg = kg;
}
return current.kg;
}
return undefined;
}
}
function currentWeight() {
return new Promise((resolve, reject) => {
const kg = weigh();
if (kg === -1){
reject("Failed to measure weight");
}else{
resolve(kg);
}
});
}
function tempChange({value}) {
current.temp = value;
current.kg = tempAdjust(current.kg, calibration.temp, current.temp);
}
module.exports = {
sensorName: SENSOR_NAME,
start(opt) {
return new Promise((resolve, reject) => {
i2c_mraa.start(opt);
ADC_clk = new mraa.Gpio(brewdefs.GPIO_ADC_CLK);
ADC_data = new mraa.Gpio(brewdefs.GPIO_ADC_DATA);
ADC_clk.dir(mraa.DIR_OUTPUT);
ADC_data.dir(mraa.DIR_INPUT);
temp.start(opt)
.then((opt) => {
temp.getTemp("TempGlycol")
.then(t => {
current.temp = t;
calibrationFile.load('cal', (err, cal) => {
if (err){
console.log("err=",err)
reject(err);
}
calibration = cal;
publishWeight = broker.create(SENSOR_NAME);
if (timer){
clearInterval(timer);
}
timer = setInterval(weigh, EVENT_INTERVAL_SECS * 1000);
//on temp change update weight
broker.subscribe("TempGlycol", tempChange);
resolve(opt);
});
})
.catch(err => {console.log(err)});
});
});
},
/* Free memory associated with the GPIO pins.
*/
stop() {
return new Promise((resolve, reject) => {
temp.stop()
.then(function(){
if (timer){
clearInterval(timer);
timer = null;
}
broker.unSubscribe(tempChange);
broker.destroy(SENSOR_NAME);
})
.then(resolve);
});
},
/**
* @desc Assume that the weight is zero. Take a measurement and adjust the calibration data (offset).
* @param function Callback
*/
tare() {
return new Promise((resolve, reject) => {
const kg = average();
if (kg === undefined){
reject("tare is undefined");
return;
}
calibrationFile.load('cal', (err, cal) => {
const kg = average();
if (kg !== undefined){
cal.c = kg;
cal.temp = current.temp;
calibrationFile.add(cal, err => {
if (err){
reject(err);
}else{
// called when the file has been written
calibration = cal;
resolve(cal);
}
});
}else{
reject("Undefined measurement");
}
});
});
},
/**
* @desc For a specific weight, take a measurement and adjust the calibration data (slope).
* @param {number} refKg - Reference weight
* @param {function} Callback
*/
calibrate(refKg) {
return new Promise((resolve, reject) => {
//y=mx+c
const kg = refKg;
calibrationFile.load('cal', (err, cal) => {
const meanX = average();
if (meanX === -1) {
reject();
}
if (kg == 0){
cal.m = 1;
}else{
cal.m = (meanX - cal.c) / kg;
}
cal.temp = current.temp;
calibrationFile.add(cal, err => {
calibration = cal;
resolve(cal);
});
});
});
},
currentWeight
}