Merge branch 'dev' into web

This commit is contained in:
Skylar Grant 2022-12-20 15:19:38 -05:00
commit b758529d79
3 changed files with 189 additions and 118 deletions

View File

@ -6,10 +6,13 @@
"auger": 0, "auger": 0,
"igniterFinished": false, "igniterFinished": false,
"seenFire": false, "seenFire": false,
"shutdown": 0 "shutdown": 0,
"vacuum": 1,
"pof": 0
}, },
"timestamps": { "timestamps": {
"procStart": 0, "procStart": 0,
"blowerOn": 0,
"blowerOff": 0, "blowerOff": 0,
"igniterOn": 0, "igniterOn": 0,
"igniterOff": 0 "igniterOff": 0
@ -17,7 +20,7 @@
"intervals": { "intervals": {
"augerOn": 500, "augerOn": 500,
"augerOff": 1500, "augerOff": 1500,
"pause": 10000, "pause": 3000,
"igniterStart": 30000, "igniterStart": 30000,
"blowerStop": 10000 "blowerStop": 10000
}, },

View File

@ -1,5 +1,6 @@
// TODOs: Add tests for PoF and Vacuum switches, add delays for shutting down blower, test logic for igniter // TODOs: Add tests for PoF and Vacuum switches, add delays for shutting down blower, test logic for igniter
// TODO: Move these to config
// Physical Pin numbers for GPIO // Physical Pin numbers for GPIO
const augerPin = 26; // Pin for controlling the relay for the pellet auger motor. const augerPin = 26; // Pin for controlling the relay for the pellet auger motor.
const igniterPin = 13; // Pin for controlling the relay for the igniter. const igniterPin = 13; // Pin for controlling the relay for the igniter.
@ -17,8 +18,7 @@ const config = require('./config.json');
const dotenv = require('dotenv').config(); const dotenv = require('dotenv').config();
// Module for working with files // Module for working with files
const fs = require('fs'); const fs = require('fs');
// Promises I think? const { time } = require('console');
const { resolve } = require('path');
// The functions we'll export to be used in other files // The functions we'll export to be used in other files
@ -66,21 +66,21 @@ const functions = {
// Turn the auger on // Turn the auger on
this.on(gpio).then((res) => { this.on(gpio).then((res) => {
// Log action if in debug mode // Log action if in debug mode
if (config.debugMode) console.log(`[${(Date.now() - config.timestamps.procStart)/1000}] I: ${res}`); // if (config.debugMode) console.log(`[${(Date.now() - config.timestamps.procStart)/1000}] I: ${res}`);
// Sleep for the time set in env variables // Sleep for the time set in env variables
functions.sleep(config.intervals.augerOn).then((res) => { functions.sleep(config.intervals.augerOn).then((res) => {
// Log action if in debug mode // Log action if in debug mode
if (config.debugMode) console.log(`[${(Date.now() - config.timestamps.procStart)/1000}] I: ${res}`); // if (config.debugMode) console.log(`[${(Date.now() - config.timestamps.procStart)/1000}] I: ${res}`);
// Turn the auger off // Turn the auger off
this.off(gpio).then((res) => { this.off(gpio).then((res) => {
// Log action if in debug mode // Log action if in debug mode
if (config.debugMode) console.log(`[${(Date.now() - config.timestamps.procStart)/1000}] I: ${res}`); // if (config.debugMode) console.log(`[${(Date.now() - config.timestamps.procStart)/1000}] I: ${res}`);
// Sleep for the time set in env variables // Sleep for the time set in env variables
functions.sleep(config.intervals.augerOff).then((res) => { functions.sleep(config.intervals.augerOff).then((res) => {
// Log action if in debug mode // Log action if in debug mode
if (config.debugMode) console.log(`[${(Date.now() - config.timestamps.procStart)/1000}] I: ${res}`); // if (config.debugMode) console.log(`[${(Date.now() - config.timestamps.procStart)/1000}] I: ${res}`);
// Resolve the promise, letting the main script know the cycle is complete // Resolve the promise, letting the main script know the cycle is complete
resolve("Cycle complete."); resolve("Auger cycled.");
}); });
}); });
}); });
@ -89,15 +89,13 @@ const functions = {
}, },
}, },
blower: { blower: {
canShutdown() { blocksShutdown() {
// If the blowerOff timestamp hasn't been set, return false as the blower hasn't been asked to turn off yet // If the current time is past the blowerOff timestamp, we can turn finish shutting down the stove
if (config.timestamps.blowerOff == 0) return false; if ((config.timestamps.blowerOff > 0) && (Date.now() > config.timestamps.blowerOff)) {
// If the current time is past the blowerOff timestamp, we can turn off the blower
if (Date.now() > config.timestamps.blowerOff) {
return true;
// Otherwise, return false because we're not ready to
} else {
return false; return false;
// Otherwise, return true because we're not ready to shutdown yet
} else {
return true;
} }
} }
}, },
@ -197,29 +195,23 @@ const functions = {
process.exit(); process.exit();
}, },
ignite(gpio) { ignite(gpio) {
// Enable the auger
config.status.auger = 1;
// Set the timestamp when the igniter turned on
config.timestamps.igniterOn = Date.now();
// Set the timestamp for when the igniter will turn off
config.timestamps.igniterOff = config.timestamps.igniterOn + config.intervals.igniterStart; // 7 Minutes, 420,000ms
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
// Check if we got here from a file, then delete it. // Check if we got here from a file, then delete it.
if (fs.existsSync('./ignite')) fs.unlink('./ignite', (err) => { if (err) throw err; }); if (fs.existsSync('./ignite')) fs.unlink('./ignite', (err) => { if (err) throw err; });
// Run the first block if this is being run on a Raspberry Pi
if (process.env.ONPI == 'true') {
// Power the blower on
functions.power.blower.on(gpio).then(res => { functions.power.blower.on(gpio).then(res => {
if (config.debugMode) console.log(`[${(Date.now() - config.timestamps.procStart)/1000}] I: ${res}`);
// Turn on the igniter // Turn on the igniter
functions.power.igniter.on(gpio).then(res => { functions.power.igniter.on(gpio).then(res => {
resolve('Auger enabled, combustion blower and igniter turned on.'); if (config.debugMode) console.log(`[${(Date.now() - config.timestamps.procStart)/1000}] I: ${res}`);
// Enable the auger
config.status.auger = 1;
if (config.debugMode) console.log(`[${(Date.now() - config.timestamps.procStart)/1000}] I: Auger enabled.`);
resolve('Ignition sequence started successfully.');
}).catch(err => { }).catch(err => {
reject(err); reject(err);
}); });
}); });
} else {
resolve('Simulated igniter turned on.');
}
}); });
}, },
shutdown(gpio) { shutdown(gpio) {
@ -232,11 +224,12 @@ const functions = {
// If the auger is enabled, disable it // If the auger is enabled, disable it
if (config.status.auger == 1) { if (config.status.auger == 1) {
config.status.auger = 0; config.status.auger = 0;
if (config.debugMode) console.log(`[${(Date.now() - config.timestamps.procStart)/1000}] I: Auger disabled.`);
} }
// If the igniter is on, shut it off. // If the igniter is on, shut it off.
if (config.status.igniter == 1) { if (config.status.igniter == 1) {
functions.power.igniter.off(gpio).then(res => { functions.power.igniter.off(gpio).then(res => {
if (config.debugMode) console.log(`[${(Date.now() - config.timestamps.procStart)/1000}] I: Shut off igniter.`); if (config.debugMode) console.log(`[${(Date.now() - config.timestamps.procStart)/1000}] I: ${res}`);
}); // TODO catch an error here }); // TODO catch an error here
} }
// TODO Change this so it gives a delay after shutting down so smoke doesn't enter the house // TODO Change this so it gives a delay after shutting down so smoke doesn't enter the house
@ -246,65 +239,110 @@ const functions = {
} }
return "Shutdown has been initiated."; return "Shutdown has been initiated.";
} else { } else {
return "A shutdown has already been initiated."; // blower.blocksShutdown() returns false only if the blower shutdown has
// been initiated AND the specified cooldown time has passed
if(!(functions.blower.blocksShutdown())) {
if (config.debugMode) console.log(`[${(Date.now() - config.timestamps.procStart)/1000}] I: Blower can be turned off.`);
functions.power.blower.off(gpio).then(res => {
// Since the blower shutting off is the last step in the shutdown, we can quit.
// TODO eventually we don't want to ever quit the program, so it can be restarted remotely
functions.commands.quit();
});
} else {
return "A shutdown has already been initiated and the blower is preventing shutdown.";
}
} }
}, },
}, },
tests: { tests: {
vacuum(gpio) { vacuum(gpio) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if (process.env.ONPI == 'true') {
gpio.read(vacuumPin, (err, status) => { gpio.read(vacuumPin, (err, status) => {
if (err) reject(err); if (err) reject(err);
resolve(status); resolve(status);
}); });
} else {
switch (config.status.vacuum) {
case 0:
resolve(false);
break;
case 1:
resolve(true);
break;
default:
reject('Unable to determine vacuum status.');
break;
}
}
}); });
}, },
pof(gpio) { pof(gpio) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if (process.env.ONPI == 'true') {
gpio.read(pofPin, (err, status) => { gpio.read(pofPin, (err, status) => {
if (err) reject(err); if (err) reject(err);
resolve(status); resolve(status);
}); });
} else {
switch (config.status.pof) {
case 0:
resolve(false);
break;
case 1:
resolve(true);
break;
default:
reject('Unable to determine proof of fire status.');
break;
}
}
}); });
}, },
igniter(gpio) { igniter(gpio) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
// Create a blank string to store the status message in as we build it
var statusMsg = ""; var statusMsg = "";
// Determine if the igniter is on
if (config.status.igniter == 1) { if (config.status.igniter == 1) {
statusMsg += "The igniter is on.\n"; statusMsg += "The igniter is on. ";
} else if (config.status.igniter == 0) { } else if (config.status.igniter == 0) {
statusMsg += "The igniter is off.\n"; statusMsg += "The igniter is off. ";
} else { } else {
reject("E: Unable to determine igniter status."); reject("Unable to determine igniter status.");
} }
// Run this if the igniter has been turned on
if (config.timestamps.igniterOn > 0) { if (config.timestamps.igniterOn > 0) {
const humanStartTime = new Date(config.timestamps.igniterOn).toISOString();
const humanEndTime = new Date(config.timestamps.igniterOff).toISOString();
if (Date.now() < config.timestamps.igniterOff && config.status.igniter == 1) { if (Date.now() < config.timestamps.igniterOff && config.status.igniter == 1) {
statusMsg += `Igniter started: ${humanStartTime}.\n`; statusMsg += `Started: ${functions.time(config.timestamps.igniterOn)}. `;
statusMsg += `Igniter scheduled to stop: ${humanEndTime}.\n`; statusMsg += `Stopping: ${functions.time(config.timestamps.igniterOff)}. `;
} }
// Shut the igniter off if it's past the waiting period // Shut the igniter off if it's past the waiting period
if ((Date.now() > config.timestamps.igniterOff) && (config.status.igniter == 1)) { if ((Date.now() > config.timestamps.igniterOff) && (config.status.igniter == 1)) {
if (process.env.ONPI == 'true') { // if (process.env.ONPI == 'true') {
gpio.write(igniterPin, false, (err) => { // gpio.write(igniterPin, false, (err) => {
if (err) throw(err); // if (err) throw(err);
config.status.igniter = 0; // config.status.igniter = 0;
statusMsg += `${new Date().toISOString()} I: Turned off igniter.`; // statusMsg += `${new Date().toISOString()} I: Turned off igniter.`;
functions.tests.pof(gpio).then(res => { // functions.tests.pof(gpio).then(res => {
if (res) { // if (res) {
config.status.seenFire = true; // config.status.seenFire = true;
} else { // } else {
reject(`E: No Proof of Fire after igniter shut off.`); // reject(`E: No Proof of Fire after igniter shut off.`);
} // }
}).catch(rej => { // }).catch(rej => {
// });
// });
// } else {
// config.status.igniter = 0;
// statusMsg += `${new Date().toISOString()} I: Simulated igniter turned off.`;
// }
// TODO I think this needs to be moved elsewhere, it doesn't finish resolving before the resolve call on line 354 is called (344+10=354)
functions.power.igniter.off(gpio).then(res => {
statusMsg += res;
}); });
});
} else {
config.status.igniter = 0;
statusMsg += `${new Date().toISOString()} I: Simulated igniter turned off.`;
}
} else if ((Date.now() > config.timestamps.igniterOff) && (config.status.igniter == 0)) { } else if ((Date.now() > config.timestamps.igniterOff) && (config.status.igniter == 0)) {
statusMsg += `The igniter was turned off at ${new Date(config.timestamps.igniterOff).toISOString()}.`; statusMsg += `The igniter was turned off at ${new Date(config.timestamps.igniterOff).toISOString()}.`;
} }
@ -321,45 +359,67 @@ const functions = {
power: { power: {
igniter: { igniter: {
on(gpio) { on(gpio) {
// TODO
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
config.timestamps.igniterOn = Date.now();
config.timestamps.igniterOff = Date.now() + config.intervals.igniterStart;
if (process.env.ONPI == 'true') {
gpio.write(igniterPin, true, (err) => { gpio.write(igniterPin, true, (err) => {
if (err) reject(err); if (err) reject(err);
config.status.igniter = 1; config.status.igniter = 1;
resolve('Igniter turned on.'); resolve('Igniter turned on.');
}); });
} else {
config.status.igniter = 1;
resolve('Igniter turned on.');
}
}); });
}, },
off(gpio) { off(gpio) {
// TODO
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
config.timestamps.igniterOff = Date.now();
config.status.igniterFinished = true;
if (process.env.ONPI == 'true') {
gpio.write(igniterPin, false, (err) => { gpio.write(igniterPin, false, (err) => {
if (err) reject(err); if (err) reject(err);
config.status.igniter = 0; config.status.igniter = 0;
resolve('Igniter turned off.'); resolve('Igniter turned off.');
}); });
} else {
config.status.igniter = 0;
resolve('Igniter turned off.');
}
}); });
}, },
}, },
blower: { blower: {
on(gpio) { on(gpio) {
// TODO
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
config.timestamps.blowerOn = Date.now();
if (process.env.ONPI == 'true') {
gpio.write(blowerPin, true, (err) => { gpio.write(blowerPin, true, (err) => {
if (err) reject(err); if (err) reject(err);
config.status.blower = 1; config.status.blower = 1;
resolve('Blower turned on.'); resolve('Blower turned on.');
}); });
} else {
config.status.blower = 1;
resolve('Blower turned on.');
}
}); });
}, },
off(gpio) { off(gpio) {
// TODO config.timestamps.blowerOff = Date.now();
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if (process.env.ONPI == 'true') {
gpio.write(blowerPin, false, (err) => { gpio.write(blowerPin, false, (err) => {
if (err) reject(err); if (err) reject(err);
config.status.blower = 0; config.status.blower = 0;
resolve('Blower turned off.'); resolve('Blower turned off.');
}); });
} else {
config.status.blower = 0;
resolve('Blower turned off.');
}
}); });
}, },
}, },
@ -379,6 +439,7 @@ const functions = {
}, },
// Initializes rpi-gpio, or resolves if not on a raspberry pi // Initializes rpi-gpio, or resolves if not on a raspberry pi
init(gpio) { init(gpio) {
// TODO this boot splash needs updating
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
// Boot/About/Info // Boot/About/Info
console.log(`== Lennox Winslow PS40 console.log(`== Lennox Winslow PS40
@ -435,6 +496,10 @@ const functions = {
} }
}); });
}, },
time(stamp) {
const time = new Date(stamp);
return `${time.getHours()}:${time.getMinutes()}:${time.getSeconds()}`;
}
} }
// Export the above object, functions, as a module // Export the above object, functions, as a module

57
main.js
View File

@ -49,7 +49,7 @@ async function main(fn, gpio) {
// Check for the existence of certain files // Check for the existence of certain files
fn.files.check().then((res,rej) => { fn.files.check().then((res,rej) => {
// Log the result of the check if in debug mode // Log the result of the check if in debug mode
if (config.debugMode) console.log(`[${(Date.now() - config.timestamps.procStart)/1000}] I: File Check: ${res}`); // if (config.debugMode) console.log(`[${(Date.now() - config.timestamps.procStart)/1000}] I: File Check: ${res}`);
// Choose what to do depending on the result of the check // Choose what to do depending on the result of the check
switch (res) { switch (res) {
case "pause": case "pause":
@ -70,21 +70,18 @@ async function main(fn, gpio) {
break; break;
case "quit": case "quit":
// Quit the script // Quit the script
fn.commands.shutdown(gpio); console.log(fn.commands.shutdown(gpio));
statusCheck(fn, gpio);
break; break;
case "ignite": case "ignite":
// Start the ignite sequence // Start the ignite sequence
fn.commands.ignite(gpio).then(res => { fn.commands.ignite(gpio).then(res => {
if (config.debugMode) console.log(res); if (config.debugMode) console.log(`[${(Date.now() - config.timestamps.procStart)/1000}] I: ${res}`);
statusCheck(fn, gpio); statusCheck(fn, gpio);
}).catch(rej => { }).catch(rej => {
console.error(`[${(Date.now() - config.timestamps.procStart)/1000}] E: ${rej}`); console.error(`[${(Date.now() - config.timestamps.procStart)/1000}] E: ${rej}`);
fn.commands.shutdown(gpio).then(res => { fn.commands.shutdown(gpio);
fn.commands.quit();
}).catch(rej => {
console.error(rej);
fn.commands.quit();
});
}); });
break; break;
case "start": case "start":
@ -125,32 +122,38 @@ async function main(fn, gpio) {
} }
function statusCheck(fn, gpio) { function statusCheck(fn, gpio) {
if (config.status.shutdown == 1) {
console.log(fn.commands.shutdown(gpio) || 'Shutting down...');
main(fn, gpio);
return;
}
if (config.status.igniter == 1) {
fn.tests.igniter(gpio).then((res) => { fn.tests.igniter(gpio).then((res) => {
if (config.debugMode) console.log(`[${(Date.now() - config.timestamps.procStart)/1000}] I: ${res}`); if (config.debugMode) console.log(`[${(Date.now() - config.timestamps.procStart)/1000}] I: ${res}`);
main(fn, gpio);
}); });
}
// Check the vacuum switch, if the test returns true, the vacuum is sensed // Check the vacuum switch, if the test returns true, the vacuum is sensed
// if it returns false, we will initiate a shutdown // if it returns false, we will initiate a shutdown
fn.tests.vacuum(gpio).then(status => { // TODO this is messed up
if (!status) { fn.tests.vacuum(gpio).then(vacStatus => {
fn.commands.shutdown(gpio); if (!vacStatus) {
} console.error('No vacuum detected, beginning shutdown procedure.');
}); console.log(fn.commands.shutdown(gpio));
main(fn, gpio);
} else {
// Check the Proof of Fire Switch // Check the Proof of Fire Switch
fn.tests.pof(gpio).then(status => { fn.tests.pof(gpio).then(pofStatus => {
// If the igniter has finished running and no proof of fire is seen, shutdown the stove // If the igniter has finished running and no proof of fire is seen, shutdown the stove
if (config.status.igniterFinished && (!status)) fn.commands.shutdown(gpio); if (config.status.igniterFinished && (!pofStatus)) {
}); console.error('No Proof of Fire after the igniter shut off, beginning shutdown procedure.');
console.log(fn.commands.shutdown(gpio));
// blower.canShutdown() returns true only if the blower shutdown has main(fn, gpio);
// been initiated AND the specified cooldown time has passed } else {
if(fn.blower.canShutdown()) { if (config.debugMode) console.log(`[${(Date.now() - config.timestamps.procStart)/1000}] I: Vacuum and Proof of Fire OK.`);
fn.power.blower.off(gpio).then(res => { main(fn, gpio);
// Since the blower shutting off is the last step in the shutdown, we can quit. }
// TODO eventually we don't want to ever quit the program, so it can be restarted remotely
fn.commands.quit();
}); });
} }
});
} }