monteslu / node-red-contrib-gpio

A set of node-red nodes for connecting to johnny-five IO Plugins
MIT License
41 stars 31 forks source link

Uncaught Exception causing Node-red to exit #28

Open kjetilei opened 6 years ago

kjetilei commented 6 years ago

I have encountered an issue where Johnny-five is causing Node-red to exit due to an uncaught exception.

I'm using the Node-red Johnny-five node to control a stepper, checking for the status of the end stop and stepper alarm and triggering a LED strip.

System setup Node-red 0.15.2 running on a RPi3 node-red-contrib-gpio node: 0.10.0 Nodebot: Arduino/Firmata Connection: Local Serial Port Port: /dev/ttyUSB0

I'm using a version of AdvancedFirmata on an Arduino Nano, that I built in January 2017 with the following core features enabled: firmataExt.addFeature(digitalInput); firmataExt.addFeature(digitalOutput); firmataExt.addFeature(analogInput); firmataExt.addFeature(analogOutput); firmataExt.addFeature(servo); firmataExt.addFeature(i2c); firmataExt.addFeature(stepper); firmataExt.addFeature(reporting);

The issue happens randomly, but since my code within the Node-red Johnny-five node spends most of the time idling in a setTimeout loop, the issue has only happened when no gpio pin was being actively set, but some pins where being listened to.

It could very well be an issue with the Firmata code/Arduino Nano used and not the Johnny-five node, but the errors listed are too vaguely detailed for me to understand what is the root cause.

Excerpt from the log file:

  1. Oct 29 19:08:19 master01 Node-RED[15825]: 29 Oct 19:08:19 - [red] Uncaught Exception:
  2. Oct 29 19:08:20 master01 Node-RED[15825]: 29 Oct 19:08:20 - TypeError: Cannot read property 'pinMode' of null
  3. Oct 29 19:08:20 master01 Node-RED[15825]: at Pin.set (/home/pi/.node-red/node_modules/johnny-five/lib/pin.js:82:16)
  4. Oct 29 19:08:20 master01 Node-RED[15825]: at Function.Pin.write (/home/pi/.node-red/node_modules/johnny-five/lib/pin.js:193:12)
  5. Oct 29 19:08:20 master01 Node-RED[15825]: at Pin.high (/home/pi/.node-red/node_modules/johnny-five/lib/pin.js:277:7)
  6. Oct 29 19:08:20 master01 Node-RED[15825]: at stepperEnable (evalmachine.:80:26)
  7. Oct 29 19:08:20 master01 Node-RED[15825]: at home (evalmachine.:112:5)
  8. Oct 29 19:08:20 master01 Node-RED[15825]: at experimentStopped (evalmachine.:187:5)
  9. Oct 29 19:08:20 master01 Node-RED[15825]: at loop (evalmachine.:213:13)
  10. Oct 29 19:08:20 master01 Node-RED[15825]: at ontimeout (timers.js:365:14)
  11. Oct 29 19:08:20 master01 Node-RED[15825]: at tryOnTimeout (timers.js:237:5)
  12. Oct 29 19:08:20 master01 Node-RED[15825]: at Timer.listOnTimeout (timers.js:207:5)
  13. Oct 29 19:08:20 master01 systemd[1]: nodered.service: main process exited, code=exited, status=1/FAILURE
  14. Oct 29 19:08:20 master01 systemd[1]: Unit nodered.service entered failed state.

My Johnny-five code:

//Initializing GPIO
//------------------------------------------------------------------------------

//Initialize LED strip
var LEDEnablePin = new five.Pin(7);

//Initialize endstop
var endstop = new five.Button({
  pin: 8, 
  invert: true,
  isPullup: true
});

//Initialize stepper alram
var stepperAlarm = new five.Button({
  pin: 9, 
  invert: true,
  isPullup: true
});

//Initialize stepper
var stepper = new five.Stepper({
    type: five.Stepper.TYPE.DRIVER,
    stepsPerRev: 200,
    pins: {
        step: 10,
        dir: 11
    }
});

//Initialize stepper enable
var stepperEnablePin = new five.Pin(12);

//Initialize Arduino LED
var led = new five.Led(13);

//Define variables
//------------------------------------------------------------------------------

var experimentActiveStatus = false;
var stepperActive = false;
var rpm = 200;
var accel = 2000;
var decel = 2000;
var stepsPerCM = 111;

// Variables later fetched from Dashboard
var duration = 1;
var photoEveryXMin = 60;
var noOfPlants = 12;
var distanceToFirstPlantCM = 1;
var plantDistanceCM = 24;
var stepsPerPosition = stepsPerCM * plantDistanceCM;
var maxTravelCM = 289;
var maxSteps = maxTravelCM * stepsPerCM;
var currentTime = 1;
var nextLoop = 1;
var unixStartTime = 1;
var unixStopTime = 1;
var startTime = "Start date";
var stopTime = "End date";

var camerasEnabled = true;
var cameraPosition = 0;
var currentStep = 0; //consider adding logic to tie steps to camera position

var LEDEnableState = false;
var endstopTriggered = false;
var stepperAlarmTriggered = false;

//FUNCTIONS
//------------------------------------------------------------------------------

function stepperEnable(state) {
    if (state === true) {
        stepperActive = true;
        stepperEnablePin.high();
    } 
    if (state === false || state === undefined) {
        stepperActive = false;
        stepperEnablePin.low();
    }
    var msg = {payload: {stepperEnable: stepperActive}};
    node.send(msg);
}

function LEDEnable(state) {
//    var LEDEnableState = false;
    if (state === true) {
        LEDEnableState = true;
        LEDEnablePin.high();
    } 
    if (state === false || state === undefined) {
        LEDEnableState = false;
        LEDEnablePin.low();
    }
    var msg = {payload: {LEDEnable: LEDEnableState}};
    node.send(msg);
}

function LEDFlash(leaveOnTime){
    LEDEnable(true);
    setTimeout(function(){
        LEDEnable(false)}, 1000 * leaveOnTime);    
}

function home() {
    var home_steps = noOfPlants * stepsPerPosition;
    stepperEnable(true);
    stepper.rpm(rpm).step({steps: home_steps, direction: 0, accel: accel, decel: decel}, function() {
        console.log("Homeing");
        stepperEnable(false);
        cameraPosition = 0;
        currentStep = 0;
        msg = {payload: {cameraPosition: cameraPosition}};
        node.send(msg);
    });
}

function forward() {
    cameraPosition++;
    led.blink(100);
    stepperEnable(true);
    stepper.rpm(rpm).step({steps: stepsPerPosition, direction: 1, accel: accel, decel: decel}, function() {
        console.log("Moving forward");
        LEDFlash(2);
        stepperEnable(false);
        msg = {payload: {cameraPosition: cameraPosition, takePhoto: camerasEnabled}};
        node.send(msg);
    });
}

function backward() {
    cameraPosition--;
    led.blink(200);
    stepperEnable(true);
    stepper.rpm(rpm).step({steps: stepsPerPosition, direction: 0, accel: accel, decel: decel}, function() {
        console.log("Moving backward");
        stepperEnable(false);
        msg = {payload: {cameraPosition: cameraPosition}};
        node.send(msg);
    });    
}

function experimentActive() {
    experimentActiveStatus = true;
    var msg = {
        payload: {
            overview: {
                experimentStatus: "Active", 
                photoEveryXMin: photoEveryXMin, 
                noOfPlants: noOfPlants, 
                startTime: startTime, 
                stopTime: stopTime,
                nextLoop: "Preparing"
            }
        }
    };
    node.send(msg); 

    home();
    setTimeout(function () {
        // run inner loop
        mainLoop();
        //setNextMinute();
    }, 1000*4); //wait 4 seconds for camera rig to home before starting
}

function experimentStopped() {
    experimentActiveStatus = false;
    var msg = {
        payload: {
            overview: {
                experimentStatus: "Finished", 
                photoEveryXMin: undefined, 
                noOfPlants: undefined, 
                startTime: undefined, 
                stopTime: undefined,
                nextLoop: undefined
            }
        }
    };
    node.send(msg);
    home();
}

function setNextMinute() {

    // figure out how much time remains before the end of the current minute
    //var d = new Date().getTime()%60000;
    var d = 50000;
    //set a timeout to occur when that expires.
    setTimeout(function () {
    // recalculate a new timeout so that your timer doesn't lag over time.
        innerloop();
        // note that calling a function() here will 
        // not offset the next minute, since it is recalculated in setNextMinute()
        setNextMinute();
    },60000-d);
}

function mainLoop() {
//    var loops = 2;
//    var delay = 12000*noOfPlants; //base wait on no of active plants
    var loops = Math.floor((duration*24*60)/photoEveryXMin);
    var delay = 1000*60*photoEveryXMin; //wait X minutes between each main pass
    // Iterate sequence asynchronously
    (function loop(main) {
        if (main >= loops || experimentActiveStatus === false){
            experimentStopped();
            return;
        } // all done
        if(main == (loops-1)){
            nextLoop = unixStartTime + ((main)*photoEveryXMin*60*1000);
            var msg = {
                payload: {
                    nextLoop: nextLoop
                }
            };
            node.send(msg);
        } else{
            nextLoop = unixStartTime + ((main+1)*photoEveryXMin*60*1000);
            var msg = {
                payload: {
                    nextLoop: nextLoop
                }
            };
            node.send(msg);
        }
        innerLoop();
        setTimeout(loop.bind(null, main+1), delay);
    })(0);
}

function innerLoop() {
    var delay = 1000*7; //wait 7 seconds before continuing to move
    // Iterate sequence asynchronously
    (function loop(inner) {
        if (inner >= noOfPlants && experimentActiveStatus == true){
            home();
        } else if (inner < noOfPlants && experimentActiveStatus != false){
            forward();
        }
        if (inner >= noOfPlants || experimentActiveStatus === false){
            return;
        } // all done
        setTimeout(loop.bind(null, inner+1), delay); 
    })(0);
}

//EVENT LISTENERS
//------------------------------------------------------------------------------

endstop.on("press", function() {
    endstopTriggered = true;
    //small hack to stop stepper if endstop triggered. 
    stepper.rpm(rpm).step({steps: stepsPerCM, direction: 1, accel: accel, decel: decel}, function() {
        console.log("Endstop triggered");
        stepperEnable(false);
        cameraPosition = 0;
    });   
    var msg = {payload: {endstopTriggered:true, cameraPosition: cameraPosition}};
    node.send(msg);
});

endstop.on("release", function() {
    endstopTriggered = false;
    var msg = {payload: {endstopTriggered:false}};
    node.send(msg);
});

stepperAlarm.on("press", function() {
    stepperAlarmTriggered = true;
    var msg = {payload: {stepperAlarm:true}};
    node.send(msg);
});

stepperAlarm.on("release", function() {
    stepperAlarmTriggered = false;
    var msg = {payload: {stepperAlarm:false}};
    node.send(msg);
});

//"Setup"
//------------------------------------------------------------------------------

// "blink" the Arduino led in 1000ms on-off phase periods as a visual feedback
//led.blink(1000);

//Energize stepper to hold position
stepperEnable(true);

//Ensure LEDs are off
LEDEnable(false);

//Home rig on startup
home();

//"LOOP"
//------------------------------------------------------------------------------
//direction: null, movePosition: null, gotoPosition: null, runExperiment: null

node.on('input', function(msg){
    console.log("J5 input");
    if(msg.payload.currentTime !== undefined){
        currentTime = msg.payload.currentTime;
    }
    if(msg.payload.config !== undefined){
        noOfCams = msg.payload.config.noOfCams;
        distanceToFirstPlantCM = msg.payload.config.distanceToFirstPlantCM;
        plantDistanceCM = msg.payload.config.plantDistanceCM;
        maxTravelCM = msg.payload.config.maxTravelCM;

        stepsPerPosition = stepsPerCM * plantDistanceCM;
    }

    if(msg.payload.experiment !== undefined){
        duration = msg.payload.experiment.duration;
        photoEveryXMin = msg.payload.experiment.photoEveryXMin;
        noOfPlants = msg.payload.experiment.noOfPlants;
        startTime = msg.payload.experiment.startTime;
        stopTime = msg.payload.experiment.stopTime;
        unixStartTime = msg.payload.experiment.unixStartTime;
        unixStopTime = msg.payload.experiment.unixStopTime;
    }

    if(msg.payload.enableCameras === true){
        camerasEnabled = true;
    } else if(msg.payload.enableCameras === false){
        camerasEnabled = false;
    }

    if(msg.payload.enableStepper === true){
        stepperEnable(true);
    } else if(msg.payload.enableStepper === false){
        stepperEnable(false);
    }

    if(msg.payload.enableLED === true){
        LEDEnable(true);
    } else if(msg.payload.enableLED === false){
        LEDEnable(false);
    }

    if(msg.payload.takePhoto === true && cameraPosition > 0){
        msg = {payload: {takePhoto: camerasEnabled}};
        node.send(msg);
        LEDFlash(2);
    }

    //Next position
    if(msg.payload.direction === 1 && stepperActive === false && cameraPosition < noOfPlants){
        forward();
    }

    //Start experiment
    else if (msg.payload.direction === 0 && msg.payload.gotoPosition === 0 && msg.payload.experimentActive === true && stepperActive === false){
        experimentActive();
    }
    //Stop experiment
    else if (msg.payload.direction === null && msg.payload.movePosition === null && msg.payload.gotoPosition === null && msg.payload.experimentActive === false){
        experimentStopped();
    }
    //Home
    else if(msg.payload.direction === 0 && msg.payload.gotoPosition === 0 && stepperActive === false /* && cameraPosition > 0 */){
        home();
    }

    //Previous position
    else if(msg.payload.direction === 0 && stepperActive === false && cameraPosition >= 1){
        backward();
    }

    // "blink" the Arduino led in 500ms on-off phase periods as a visual feedback
    //led.blink(500);
    msg = {payload: {cameraPosition: cameraPosition}};
    node.send(msg);
});