struggles

This commit is contained in:
Skylar Grant 2022-12-19 21:25:17 -05:00
parent 2a19a8499d
commit 096d1739ac
5 changed files with 193 additions and 114 deletions

BIN
.DS_Store vendored Normal file

Binary file not shown.

17
.vscode/launch.json vendored Normal file
View File

@ -0,0 +1,17 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Launch Program",
"skipFiles": [
"<node_internals>/**"
],
"program": "${workspaceFolder}/main.js"
}
]
}

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
@ -18,7 +21,7 @@
"augerOn": 500, "augerOn": 500,
"augerOff": 1500, "augerOff": 1500,
"pause": 10000, "pause": 10000,
"igniterStart": 30000, "igniterStart": 10000,
"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 functions.power.blower.on(gpio).then(res => {
if (process.env.ONPI == 'true') { if (config.debugMode) console.log(`[${(Date.now() - config.timestamps.procStart)/1000}] I: ${res}`);
// Power the blower on // Turn on the igniter
functions.power.blower.on(gpio).then(res => { functions.power.igniter.on(gpio).then(res => {
// Turn on the igniter if (config.debugMode) console.log(`[${(Date.now() - config.timestamps.procStart)/1000}] I: ${res}`);
functions.power.igniter.on(gpio).then(res => { // Enable the auger
resolve('Auger enabled, combustion blower and igniter turned on.'); config.status.auger = 1;
}).catch(err => { if (config.debugMode) console.log(`[${(Date.now() - config.timestamps.procStart)/1000}] I: Auger enabled.`);
reject(err);
}); resolve('Ignition sequence started successfully.');
}).catch(err => {
reject(err);
}); });
} else { });
resolve('Simulated igniter turned on.');
}
}); });
}, },
shutdown(gpio) { shutdown(gpio) {
@ -236,75 +228,123 @@ const functions = {
// 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
if (config.status.blower == 1) { if (config.status.blower == 1) {
// Set the timestamp to turn the blower off at // Set the timestamp to turn the blower off at
config.timestamps.blowerOff = Date.now() + config.intervals.blowerStop; config.timestamps.blowerOff = Date.now() + config.intervals.blowerStop;
functions.power.blower.off(gpio).then(res => {
if (config.debugMode) console.log(`[${(Date.now() - config.timestamps.procStart)/1000}] I: ${res}`);
});
} }
return "Shutdown has been initiated."; return "Shutdown has been initiated.";
} else { } else {
return "A shutdown has already been initiated."; // blower.canShutdown() returns true 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.`);
fn.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
fn.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) => {
gpio.read(vacuumPin, (err, status) => { if (process.env.ONPI == 'true') {
if (err) reject(err); gpio.read(vacuumPin, (err, status) => {
resolve(status); if (err) reject(err);
}); 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) => {
gpio.read(pofPin, (err, status) => { if (process.env.ONPI == 'true') {
if (err) reject(err); gpio.read(pofPin, (err, status) => {
resolve(status); if (err) reject(err);
}); 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 { // } else {
config.status.igniter = 0; // config.status.igniter = 0;
statusMsg += `${new Date().toISOString()} I: Simulated igniter turned off.`; // 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 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 +361,66 @@ const functions = {
power: { power: {
igniter: { igniter: {
on(gpio) { on(gpio) {
// TODO
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
gpio.write(igniterPin, true, (err) => { config.timestamps.igniterOn = Date.now();
if (err) reject(err); config.timestamps.igniterOff = Date.now() + config.intervals.igniterStart;
if (process.env.ONPI == 'true') {
gpio.write(igniterPin, true, (err) => {
if (err) reject(err);
config.status.igniter = 1;
resolve('Igniter turned on.');
});
} else {
config.status.igniter = 1; config.status.igniter = 1;
resolve('Igniter turned on.'); resolve('Igniter turned on.');
}); }
}); });
}, },
off(gpio) { off(gpio) {
// TODO
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
gpio.write(igniterPin, false, (err) => { config.timestamps.igniterOff = Date.now();
if (err) reject(err); if (process.env.ONPI == 'true') {
gpio.write(igniterPin, false, (err) => {
if (err) reject(err);
config.status.igniter = 0;
resolve('Igniter turned off.');
});
} else {
config.status.igniter = 0; config.status.igniter = 0;
resolve('Igniter turned off.'); resolve('Igniter turned off.');
}); }
}); });
}, },
}, },
blower: { blower: {
on(gpio) { on(gpio) {
// TODO
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
gpio.write(blowerPin, true, (err) => { config.timestamps.blowerOn = Date.now();
if (err) reject(err); if (process.env.ONPI == 'true') {
gpio.write(blowerPin, true, (err) => {
if (err) reject(err);
config.status.blower = 1;
resolve('Blower turned on.');
});
} else {
config.status.blower = 1; config.status.blower = 1;
resolve('Blower turned on.'); resolve('Blower turned on.');
}); }
}); });
}, },
off(gpio) { off(gpio) {
// TODO config.timestamps.blowerOff = Date.now();
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
gpio.write(blowerPin, false, (err) => { if (process.env.ONPI == 'true') {
if (err) reject(err); gpio.write(blowerPin, false, (err) => {
if (err) reject(err);
config.status.blower = 0;
resolve('Blower turned off.');
});
} else {
config.status.blower = 0; config.status.blower = 0;
resolve('Blower turned off.'); resolve('Blower turned off.');
}); }
}); });
}, },
}, },
@ -379,6 +440,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 +497,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

47
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":
@ -75,16 +75,12 @@ async function main(fn, gpio) {
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":
@ -127,30 +123,27 @@ async function main(fn, gpio) {
function statusCheck(fn, gpio) { function statusCheck(fn, gpio) {
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 => {
if (!vacStatus) {
console.error('No vacuum detected, beginning shutdown procedure.');
fn.commands.shutdown(gpio); fn.commands.shutdown(gpio);
} else {
// Check the Proof of Fire Switch
fn.tests.pof(gpio).then(pofStatus => {
// If the igniter has finished running and no proof of fire is seen, shutdown the stove
if (config.status.igniterFinished && (!pofStatus)) {
console.error('No Proof of Fire after the igniter shut off, beginning shutdown procedure.');
fn.commands.shutdown(gpio);
} else {
if (config.debugMode) console.log(`[${(Date.now() - config.timestamps.procStart)/1000}] I: Vacuum and Proof of Fire OK.`);
main(fn, gpio);
}
});
} }
}); });
// Check the Proof of Fire Switch
fn.tests.pof(gpio).then(status => {
// 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);
});
// blower.canShutdown() returns true only if the blower shutdown has
// been initiated AND the specified cooldown time has passed
if(fn.blower.canShutdown()) {
fn.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
fn.commands.quit();
});
}
} }