simplifying to temp launch

This commit is contained in:
Skylar Grant 2023-01-06 15:58:42 -05:00
parent f66f705e58
commit b7f9bacfa1
7 changed files with 17601 additions and 565 deletions

8
TODO Normal file
View File

@ -0,0 +1,8 @@
Logic Goal:
1. Start Script & Web Server
2. Await a command from the user.
3. Startup enables auger
4. If auger is enabled, run the cycle command.
5. Once per cycle, read then rewrite the config file for interoperability
6. Set feed rate based on config.

View File

@ -1 +1 @@
{"debugMode":true,"status":{"igniter":0,"blower":0,"auger":0,"igniterFinished":true,"shutdown":1,"vacuum":0,"pof":0},"timestamps":{"procStart":1672761393394,"blowerOn":1672761445945,"blowerOff":1672761459451,"igniterOn":1672761445946,"igniterOff":1672761447442},"intervals":{"augerOn":500,"augerOff":1500,"pause":3000,"igniterStart":5000,"blowerStop":5000},"web":{"port":8080,"ip":"0.0.0.0"}}
{"debugMode":true,"status":{"igniter":0,"blower":0,"auger":0,"igniterFinished":false,"shutdown":0,"vacuum":0,"pof":0},"timestamps":{"procStart":1673037430589,"blowerOn":0,"blowerOff":0,"igniterOn":0,"igniterOff":0},"intervals":{"augerOn":"700","augerOff":"1300","igniterStart":5000,"blowerStop":5000},"web":{"port":8080,"ip":"0.0.0.0"}}

View File

@ -3,16 +3,12 @@
// TODO: Move these to config
// Physical Pin numbers for GPIO
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 blowerPin = 15; // Pin for controlling the relay for the combustion blower/exhaust.
const pofPin = 16; // Pin for sensing the status (open/closed) of the Proof of Fire switch.
// const tempPin = 18; // Pin for receiving data from a DS18B20 OneWire temperature sensor.
const vacuumPin = 22; // Pin for sensing the status (open/closed) of the vacuum switch.
// Require the package for pulling version numbers
const package = require('./package.json');
// Import the config file
var config = require('./config.json');
config.timestamps.procStart = Date.now();
// Get environment variables
const dotenv = require('dotenv').config();
@ -20,6 +16,27 @@ const dotenv = require('dotenv').config();
const fs = require('fs');
const { time } = require('console');
const main = (gpio) => {
// If the auger is enabled
if (config.status.auger == 1) {
// Run a cycle of the auger
functions.auger.cycle(gpio).then(res => {
if (config.debugMode) console.log(`[${(Date.now() - config.timestamps.procStart)/1000}] I: ${res}`);
// Recursion ecursion cursion ursion rsion sion ion on n
main(gpio);
}).catch(err => {
if (config.debugMode) console.log(`[${(Date.now() - config.timestamps.procStart)/1000}] E: ${err}`);
});
} else {
// If the auger is disabled
functions.commands.pause().then(res => {
main(gpio);
}).catch(err => {
if (config.debugMode) console.log(`[${(Date.now() - config.timestamps.procStart)/1000}] E: ${err}`);
main(gpio);
});
}
}
// The functions we'll export to be used in other files
const functions = {
@ -80,7 +97,7 @@ const functions = {
// Log action if in debug mode
// 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("Auger cycled.");
resolve(`Auger cycled (${config.intervals.augerOn}/${config.intervals.augerOff})`);
});
});
});
@ -88,71 +105,16 @@ const functions = {
});
},
},
blower: {
blocksShutdown() {
// If the current time is past the blowerOff timestamp, we can turn finish shutting down the stove
if ((config.timestamps.blowerOff > 0) && (Date.now() > config.timestamps.blowerOff)) {
return false;
// Otherwise, return true because we're not ready to shutdown yet
} else {
return true;
}
}
},
files: {
// Check for a preset-list of files in the root directory of the app
check() {
return new Promise((resolve, reject) => {
// Check for pause file existing
if (fs.existsSync('./pause')) {
// Resolve the promise, letting the main script know what we found
resolve("pause");
}
// Check for reload file existing
if (fs.existsSync('./reload')) {
// Resolve the promise, letting the main script know what we found
resolve("reload");
}
// Check for quit file existing
if (fs.existsSync('./quit')) {
// Resolve the promise, letting the main script know what we found
resolve("quit");
}
// Check for ignite file existing
if (fs.existsSync('./ignite')) {
resolve('ignite');
}
// Check for start file existing
if (fs.existsSync('./start')) {
resolve('start');
}
// Resolve the promise, letting the main script know what we found (nothing)
resolve("none");
});
},
},
commands: {
// Prepare the stove for starting
startup (gpio) {
fs.unlink('./start', (err) => {
if (err) throw err;
});
return new Promise((resolve, reject) => {
if (process.env.ONPI == 'true') {
// Turn the combustion blower on
functions.power.blower.on(gpio).then(res => {
resolve(`I: Combustion blower has been enabled.`);
}).catch(rej => {
reject(`E: There was a problem starting the combustion blower: ${rej}`);
});
} else {
resolve(`I: Simulated combustion blower turned on.`);
}
});
startup () {
// Basic startup just enables the auger
config.status.auger = 1;
return;
},
shutdown() {
// Basic shutdown only needs to disable the auger
config.status.auger = 0;
},
// Pauses the script for the time defined in env variables
pause() {
@ -181,263 +143,23 @@ const functions = {
});
},
// Shutdown the script gracefully
quit() {
// TODO add quit file detection, not always going to be quitting from files
// Delete the quit file
fs.unlink('./quit', (err) => {
if (err) throw err;
if (config.debugMode) console.log('Removed quit file.');
});
// Print out that the script is quitting
console.log('Quitting...');
// Quit the script
process.exit();
},
ignite(gpio) {
return new Promise((resolve, reject) => {
// Check if we got here from a file, then delete it.
if (fs.existsSync('./ignite')) fs.unlink('./ignite', (err) => { if (err) throw err; });
functions.power.blower.on(gpio).then(res => {
if (config.debugMode) console.log(`[${(Date.now() - config.timestamps.procStart)/1000}] I: ${res}`);
// Turn on the igniter
functions.power.igniter.on(gpio).then(res => {
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 => {
reject(err);
});
});
});
},
shutdown(gpio) {
// Only run if a shutdown isn't already started
if (config.status.shutdown == 0) {
// set shutdown flag to 1
config.status.shutdown = 1;
// Check if this was invoked from a 'quit' file, if so, delete the file
if (fs.existsSync('./quit')) fs.unlink('./quit', (err) => { if (err) throw err; });
// If the auger is enabled, disable it
if (config.status.auger == 1) {
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 (config.status.igniter == 1) {
functions.power.igniter.off(gpio).then(res => {
if (config.debugMode) console.log(`[${(Date.now() - config.timestamps.procStart)/1000}] I: ${res}`);
}); // TODO catch an error here
}
// TODO Change this so it gives a delay after shutting down so smoke doesn't enter the house
if (config.status.blower == 1) {
// Set the timestamp to turn the blower off at
config.timestamps.blowerOff = Date.now() + config.intervals.blowerStop;
}
return "Shutdown has been initiated.";
} else {
// 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();
config.status.shutdown = 0;
});
} else {
return "A shutdown has already been initiated and the blower is preventing shutdown.";
}
refreshConfig(newSettings) {
// When the reload button is pressed, the call to this function will contain new config values
// {
// augerOff: 500,
// augerOn: 1500,
// pause: 5000
// }
if (newSettings != undefined) {
config.intervals.augerOff = newSettings.augerOff;
config.intervals.augerOn = newSettings.augerOn;
config.intervals.pause = newSettings.pause;
}
},
writeConfig() {
fs.writeFile('./config.json', JSON.stringify(config), (err) => {
if (err) throw err;
});
}
},
tests: {
vacuum(gpio) {
return new Promise((resolve, reject) => {
if (config.status.blower == 1) {
if (process.env.ONPI == 'true') {
gpio.read(vacuumPin, (err, status) => {
if (err) reject(err);
config.status.vacuum = 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;
}
}
} else {
// If the blower isn't on, the vacuum doesn't matter so always return true
resolve(true);
}
});
},
pof(gpio) {
return new Promise((resolve, reject) => {
if (process.env.ONPI == 'true') {
gpio.read(pofPin, (err, status) => {
if (err) reject(err);
config.status.pof = 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) {
return new Promise((resolve, reject) => {
// Create a blank string to store the status message in as we build it
var statusMsg = "";
// Determine if the igniter is on
if (config.status.igniter == 1) {
statusMsg += "The igniter is on. ";
} else if (config.status.igniter == 0) {
statusMsg += "The igniter is off. ";
} else {
reject("Unable to determine igniter status.");
}
// Run this if the igniter has been turned on
if (config.timestamps.igniterOn > 0) {
if (Date.now() < config.timestamps.igniterOff && config.status.igniter == 1) {
statusMsg += `Started: ${functions.time(config.timestamps.igniterOn)}. `;
statusMsg += `Stopping: ${functions.time(config.timestamps.igniterOff)}. `;
}
// Shut the igniter off if it's past the waiting period
if ((Date.now() > config.timestamps.igniterOff) && (config.status.igniter == 1)) {
// if (process.env.ONPI == 'true') {
// gpio.write(igniterPin, false, (err) => {
// if (err) throw(err);
// config.status.igniter = 0;
// statusMsg += `${new Date().toISOString()} I: Turned off igniter.`;
// functions.tests.pof(gpio).then(res => {
// if (res) {
// config.status.seenFire = true;
// } else {
// reject(`E: No Proof of Fire after igniter shut off.`);
// }
// }).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 => {
if (config.debugMode) console.log(`[${(Date.now() - config.timestamps.procStart)/1000}] I: ${res}`);
});
} else if ((Date.now() > config.timestamps.igniterOff) && (config.status.igniter == 0)) {
statusMsg += `The igniter was turned off at ${new Date(config.timestamps.igniterOff).toISOString()}.`;
}
} else {
statusMsg += 'The igniter hasn\'t been started yet.';
}
resolve(statusMsg);
});
},
blowerOffDelay() {
},
},
power: {
igniter: {
on(gpio) {
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) => {
if (err) reject(err);
config.status.igniter = 1;
resolve('Igniter turned on.');
});
} else {
config.status.igniter = 1;
resolve('Igniter turned on.');
}
});
},
off(gpio) {
return new Promise((resolve, reject) => {
config.timestamps.igniterOff = Date.now();
config.status.igniterFinished = true;
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;
resolve('Igniter turned off.');
}
});
},
},
blower: {
on(gpio) {
return new Promise((resolve, reject) => {
config.timestamps.blowerOn = Date.now();
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;
resolve('Blower turned on.');
}
});
},
off(gpio) {
config.timestamps.blowerOff = Date.now();
return new Promise((resolve, reject) => {
if (process.env.ONPI == 'true') {
gpio.write(blowerPin, false, (err) => {
if (err) reject(err);
config.status.blower = 0;
resolve('Blower turned off.');
});
} else {
config.status.blower = 0;
resolve('Blower turned off.');
}
});
},
},
},
// Sleeps for any given milliseconds
sleep(ms) {
return new Promise((resolve) => {
@ -481,34 +203,8 @@ const functions = {
gpio.setup(augerPin, gpio.DIR_OUT, (err) => {
if (err) reject(err);
if (config.debugMode) console.log('== Auger pin initialized.');
// Init the igniter pin
gpio.setup(igniterPin, gpio.DIR_OUT, (err) => {
if (err) reject(err);
if (config.debugMode) console.log('== Igniter pin initialized.');
// Init the blower pin
gpio.setup(blowerPin, gpio.DIR_OUT, (err) => {
if (err) reject(err);
if (config.debugMode) console.log('== Combustion blower pin initialized.');
// Init the Proof of Fire pin
gpio.setup(pofPin, gpio.DIR_IN, (err) => {
if (err) reject(err);
if (config.debugMode) console.log('== Proof of Fire pin initialized.');
// Init the Vacuum Switch pin
gpio.setup(vacuumPin, gpio.DIR_IN, (err) => {
if (err) reject(err);
if (config.debugMode) console.log('== Vacuum Switch pin initialized.');
// Resolve the promise now that all pins have been initialized
resolve('== GPIO Initialized.');
});
// Init the Temp Sensor pin
// gpio.setup(tempPin, gpio.DIR_IN, (err) => {
// if (err) reject(err);
// if (config.debugMode) console.log('== Temperature pin initialized.');
// });
});
});
});
// Resolve the promise now that all pins have been initialized
resolve('== GPIO Initialized.');
});
} else {
// Resolve the promise
@ -522,5 +218,35 @@ const functions = {
}
}
// Setup for use with the Pi's GPIO pins
switch (process.env.ONPI) {
case 'true':
console.log(`[${(Date.now() - config.timestamps.procStart)/1000}] == Running on a Raspberry Pi.`);
var gpio = require('rpi-gpio');
functions.init(gpio).then((res) => {
console.log(`[${(Date.now() - config.timestamps.procStart)/1000}] I: ${res}`);
main(gpio);
}).catch(rej => {
console.error(`[${(Date.now() - config.timestamps.procStart)/1000}] E: Error during initialization: ${rej}`);
process.exit(1);
});
break;
case 'false':
console.log(`[${(Date.now() - config.timestamps.procStart)/1000}] I: Not running on a Raspberry Pi.`);
var gpio = 'gpio';
functions.init(gpio).then(res => {
console.log(`[${(Date.now() - config.timestamps.procStart)/1000}] I: ${res}`);
main(gpio);
}).catch(rej => {
console.error(`[${(Date.now() - config.timestamps.procStart)/1000}] E: Error during initialization: ${rej}`);
process.exit(1);
});
break;
default:
console.error(`[${Date.now() - config.timestamps.procStart}] E: Problem with ENV file.`);
process.exit(1);
break;
}
// Export the above object, functions, as a module
module.exports = { functions };

17550
log.txt

File diff suppressed because it is too large Load Diff

165
main.js
View File

@ -1,165 +0,0 @@
/* Pellet Stove Control Panel
* Written by Skylar Grant
* v0.2
*
* TODO:
* Update documentation
* Move some of these functions to the functions file so they can be called from the web server
* Or move the web server here and remove the first init
* Or just remove the first init call and start the init elsewhere after main() is called
*/
// Custom functions module to keep main script clean
const fn = require('./functions.js').functions;
// Config File
const config = require('./config.json');
config.timestamps.procStart = Date.now();
// Environment Variables Importing
const dotenv = require('dotenv').config();
// Setup for use with the Pi's GPIO pins
if (process.env.ONPI == 'true') {
console.log(`[${(Date.now() - config.timestamps.procStart)/1000}] == Running on a Raspberry Pi.`);
const gpio = require('rpi-gpio');
fn.init(gpio).then((res) => {
console.log(`[${(Date.now() - config.timestamps.procStart)/1000}] I: ${res}`);
main(fn, gpio);
}).catch(rej => {
console.error(`[${(Date.now() - config.timestamps.procStart)/1000}] E: ${rej}`);
});
} else if (process.env.ONPI == 'false') {
console.log(`[${(Date.now() - config.timestamps.procStart)/1000}] I: Not running on a Raspberry Pi.`);
const gpio = 'gpio';
fn.init(gpio).then(res => {
console.log(`[${(Date.now() - config.timestamps.procStart)/1000}] I: ${res}`);
main(fn, gpio);
}).catch(rej => {
console.error(rej);
});
} else {
console.error(`[${Date.now() - config.timestamps.procStart}] E: Problem with ENV file.`);
}
// TODO Add logic for other sensors
// Main function, turns the auger on, sleeps for the time given in environment variables, then turns the auger off, sleeps, repeats.
async function main(fn, gpio) {
// Check for the existence of certain files
fn.files.check().then((res,rej) => {
// 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}`);
// Choose what to do depending on the result of the check
switch (res) {
case "pause":
// Pause the script
fn.commands.pause().then(() => {
// Rerun this function once the pause has finished
main(fn, gpio);
});
break;
case "reload":
// Reload the environment variables
fn.commands.reload().then(() => {
// Rerun this function once the reload has finished
main(fn, gpio);
}).catch(rej => {
console.error(`[${(Date.now() - config.timestamps.procStart)/1000}] E: ${rej}`);
});
break;
case "quit":
// Quit the script
console.log(fn.commands.shutdown(gpio));
statusCheck(fn, gpio);
break;
case "ignite":
// Start the ignite sequence
fn.commands.ignite(gpio).then(res => {
if (config.debugMode) console.log(`[${(Date.now() - config.timestamps.procStart)/1000}] I: ${res}`);
statusCheck(fn, gpio);
}).catch(rej => {
console.error(`[${(Date.now() - config.timestamps.procStart)/1000}] E: ${rej}`);
fn.commands.shutdown(gpio);
});
break;
case "start":
// Start the stove
fn.commands.startup(gpio).then(res => {
statusCheck(fn, gpio);
}).catch(rej => {
// TODO
});
break;
case "none":
// If no special files are found, cycle the auger normally
if (config.status.auger == 1) {
fn.auger.cycle(gpio).then((res) => {
// Log the auger cycle results if in debug mode.
// if (config.debugMode) console.log(`[${(Date.now() - config.timestamps.procStart)/1000}] I: ${res}`);
// Run the status check function
statusCheck(fn, gpio);
// Rerun this function once the cycle is complete
// main(fn, gpio);
});
} else {
// if (config.debugMode) console.log(`[${(Date.now() - config.timestamps.procStart)/1000}] I: Auger Status: ${config.status.auger}`);
fn.commands.pause().then(res => {
statusCheck(fn, gpio);
});
}
break;
default:
// If we don't get a result from the file check, or for some reason it's an unexpected response, log it and quit the script.
console.error(`[${(Date.now() - config.timestamps.procStart)/1000}] E: No result was received, something is wrong.\nres: ${res}`);
fn.commands.quit();
break;
}
});
}
function statusCheck(fn, gpio) {
// Once per cycle, write the config variable to the file so it can be read by the web server
fn.commands.writeConfig();
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) => {
if (config.debugMode) console.log(`[${(Date.now() - config.timestamps.procStart)/1000}] I: ${res}`);
});
}
// Check the vacuum switch, if the test returns true, the vacuum is sensed
// if it returns false, we will initiate a shutdown
// TODO this is messed up
fn.tests.vacuum(gpio).then(vacStatus => {
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
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.');
console.log(fn.commands.shutdown(gpio));
main(fn, gpio);
} else {
// if (config.debugMode) console.log(`[${(Date.now() - config.timestamps.procStart)/1000}] I: Vacuum and Proof of Fire OK.`);
main(fn, gpio);
}
});
}
});
}
module.exports = main;

View File

@ -14,9 +14,7 @@ const server = http.createServer(app);
const config = require('./config.json');
const fs = require('fs');
// const bodyParser = require('body-parser');
const core = require('./main.js');
const fn = require('./functions.js').functions;
const gpio = require('rpi-gpio');
app.use(express.urlencoded());
@ -38,10 +36,17 @@ app.post('/', (req, res) => {
// console.log(JSON.parse(data));
res.render('index', JSON.parse(data));
if (req.body.start != undefined) {
fn.commands.ignite(gpio);
fn.commands.startup();
}
if (req.body.shutdown != undefined) {
fn.commands.shutdown(gpio);
fn.commands.shutdown();
}
if (req.body.reload != undefined) {
fn.commands.refreshConfig({
augerOff: req.body.augerOff,
augerOn: req.body.augerOn,
pause: req.body.pause
});
}
// res.send(200);
});

View File

@ -16,9 +16,9 @@
<input class="button-unselected" type="submit" id="ignite" value="Start" name="start"><input class="button-unselected" type="submit" id="shutdown" value="Shutdown" name="shutdown"><input class="button-unselected" type="submit" id="reload" value="Reload" name="reload"><br>
</div>
<!-- Set feed rates -->
<!-- <label for="augerOn">Auger On Interval: </label><input type="number" id="auger-on" name="augerOn" min="500" max="1000" value="<%= intervals.augerOn %>">ms<br> -->
<!-- <label for="augerOff">Auger Off Interval: </label><input type="number" id="auger-off" name="augerOff" min="1000" max="2000" value="<%= intervals.augerOff %>">ms<br> -->
<!-- <label for="pauseInt">App Pause Interval: </label><input type="number" id="pause-int" name="pauseInt" min="1000" max="600000" value="<%= intervals.pause %>">ms<br> -->
<label for="augerOn">Auger On Interval: </label><input type="number" id="auger-on" name="augerOn" min="500" max="1000" step="100" value="<%= intervals.augerOn %>">ms<br>
<label for="augerOff">Auger Off Interval: </label><input type="number" id="auger-off" name="augerOff" min="1000" max="2000" step="100" value="<%= intervals.augerOff %>">ms<br>
<label for="pauseInt">App Pause Interval: </label><input type="number" id="pause-int" name="pauseInt" min="1000" max="600000" step="1000" value="<%= intervals.pause %>">ms<br>
</form>
</div>
<div class="subheading">