Compare commits

...

27 Commits

Author SHA1 Message Date
Skylar Grant a00e8e73a6 Fix data type 2023-11-17 12:39:27 -05:00
Skylar Grant f0f865d2e7 MOAR LOGS 2023-11-17 12:37:12 -05:00
Skylar Grant eea8c9fef5 Improved logging 2023-11-17 12:34:58 -05:00
Skylar Grant c80306271d Adjust sequence 2023-11-17 12:31:59 -05:00
Skylar Grant dc3fc976d8 Forgot to include gpio object 2023-11-17 11:44:41 -05:00
Skylar Grant 09c73ac619 Implement GPIO pins 2023-11-17 11:43:04 -05:00
Skylar Grant 28ac67c834 match default auger times to new portal times 2023-11-17 11:10:29 -05:00
Skylar Grant fbb4b78e41 Disable left enable right 2023-11-17 11:00:50 -05:00
Skylar Grant 60657b2da6 Fixed table addresses 2023-11-17 10:57:37 -05:00
Skylar Grant 2823f9d96d versioning, new name 2023-11-17 10:54:08 -05:00
Skylar Grant 51ba50c0c6 Fixed layouts 2023-11-17 10:54:00 -05:00
Skylar Grant c52fca4b5a Tweak display of elements 2023-11-17 10:31:01 -05:00
Skylar Grant ceab174d04 fix exhaust to blower in db 2023-11-17 10:24:59 -05:00
Skylar Grant ec50ed3516 Adjust naming of status items 2023-11-17 10:22:08 -05:00
Skylar Grant 1919a9c518 Add status indicators for ign/exh 2023-11-17 10:18:40 -05:00
Skylar Grant 78a502e959 Added handlers for ign/exh buttons 2023-11-17 10:06:22 -05:00
Skylar Grant ef726b1853 added dancing jesus back shednod 2023-11-14 20:07:48 -05:00
Skylar Grant 23aa026e9f incomplete addition of exhaust and igniter code 2023-11-14 20:07:37 -05:00
Skylar Grant 67a9fc19ec new&renamed buttons, fixed quit button 2023-11-14 18:06:09 -05:00
Skylar Grant db763223c6 Fix table resizing shenanigans 2023-11-13 18:42:03 -05:00
Skylar Grant 0b6794fb89 Fixed startup failure bug 2023-11-13 18:41:55 -05:00
Skylar Grant 9e6e813b4c Removed on/off time display 2023-11-13 18:32:43 -05:00
Skylar Grant ac1848c48e Fixed quit function blocked by lack of db upd8 2023-11-13 18:28:17 -05:00
Skylar Grant 2358d6eb8f Update status indicators to use new levels 2023-11-13 17:41:03 -05:00
Skylar Grant 6ab9bed2b4 Added timings 2023-11-13 17:35:25 -05:00
Skylar Grant 695e283246 Added some comments 2023-11-10 17:57:59 -05:00
Skylar Grant 43ab7dd367 Add more feed rates 2023-11-10 17:48:56 -05:00
9 changed files with 674 additions and 2384 deletions

View File

@ -103,4 +103,20 @@ For ease of adaption, connection, and prototyping I've decided to use Cat 5 ethe
1 | auger_off | 1400
2 | pause | 5000
3 | igniter_start | 420000
4 | blower_stop | 600000
4 | blower_stop | 600000
# Startup Procedure
1. Manually toggle the "I" Igniter switch ON
2. Wait 60 seconds
3. Manually toggle the "E" Exhaust switch ON
4. Using the Hestia Web Portal, set Feed Rate to 2
5. Enable the Auger
6. Wait up to three minutes for pellets to ignite
7. Manually toggle the "I" Ingniter switch OFF
8. Monitor fire and adjust feed rates as necessary
# Timings
* 60-90 seconds igniter preheat
* 120-180 seconds for pellet ignition
* 4 minutes from ignition to PoF Close
* 7:30 minutes total

45
main.js
View File

@ -9,9 +9,15 @@ portal.start();
dbfn.run(`UPDATE timestamps SET value = ${Date.now()} WHERE key = 'process_start'`).catch(err => console.error(`Error setting process start time: ${err}`));
// Initialization, which then calls main()
fn.commands.refreshConfig().then(res => {
if (process.env.DEBUG) console.log(`[${(Date.now() - config.timestamps.procStart)/1000}] I: ${res.status}`);
config = res.config;
const shutdownNextCycleQuery = "UPDATE status SET value = 0 WHERE key = 'shutdown_next_cycle'";
dbfn.run(shutdownNextCycleQuery).then(res => {
if (process.env.DEBUG) console.log(`[${(Date.now() - config.timestamps.procStart)/1000}] I: ${res.status}`);
console.log(`[${(Date.now() - config.timestamps.procStart) / 1000}] I: Shutdown flag reset.`);
}).catch(err => console.log(`[${(Date.now() - config.timestamps.procStart)/1000}] E: ${err}`));
// Setup for use with the Pi's GPIO pins
switch (process.env.ONPI) {
case 'true':
@ -47,6 +53,45 @@ fn.commands.refreshConfig().then(res => {
});
function main(gpio) {
// Set the Igniter
if (process.env.DEBUG) console.log(`[${(Date.now() - config.timestamps.procStart)/1000}] I: IGN: [${config.status.igniter}] | EXH: [${config.status.exhaust}] | AUG: [${config.status.auger}]`);
switch (config.status.igniter) {
case '0':
fn.igniter.off(gpio).then(res => {
if (process.env.DEBUG) console.log(`[${(Date.now() - config.timestamps.procStart)/1000}] I: ${res}`);
});
break;
case '1':
fn.igniter.on(gpio).then(res => {
if (process.env.DEBUG) console.log(`[${(Date.now() - config.timestamps.procStart)/1000}] I: ${res}`);
});
break;
default:
fn.igniter.off(gpio).then(res => {
if (process.env.DEBUG) console.log(`[${(Date.now() - config.timestamps.procStart)/1000}] I: ${res}`);
});
break;
}
// Set the Exhaust
switch (config.status.exhaust) {
case '0':
fn.exhaust.off(gpio).then(res => {
if (process.env.DEBUG) console.log(`[${(Date.now() - config.timestamps.procStart)/1000}] I: ${res}`);
});
break;
case '1':
fn.exhaust.on(gpio).then(res => {
if (process.env.DEBUG) console.log(`[${(Date.now() - config.timestamps.procStart)/1000}] I: ${res}`);
});
break;
default:
fn.exhaust.off(gpio).then(res => {
if (process.env.DEBUG) console.log(`[${(Date.now() - config.timestamps.procStart)/1000}] I: ${res}`);
});
break;
}
// If the auger is enabled
if (config.status.auger == 1) {
// Run a cycle of the auger

View File

@ -7,15 +7,20 @@
* Add actual data into the responses
*/
// Modules
const express = require('express');
const http = require('http');
const fs = require('fs');
const fn = require('./functions.js').functions;
const { dbfn } = require('./functions.js');
// Grab the current configuration settings from the database to display
var config;
fn.commands.refreshConfig().then(newConfig => {
config = newConfig.config;
});
const { dbfn } = require('./functions.js');
// Create the server
const app = express();
const server = http.createServer(app);
app.use(express.urlencoded());
@ -29,28 +34,70 @@ app.set('view engine', 'html');
// A normal load of the root page
app.get('/', (req, res) => {
// if (process.env.DEBUG) console.log(`[${(Date.now() - config.timestamps.procStart)/1000}] I: ${JSON.stringify(config)}`);
// Render the page, passing the config along for the front end to use
res.render('index', { config: JSON.stringify(config) });
});
// A POST form submission to the root page
app.post('/', (req, response) => {
if (req.body.start != undefined) {
fn.commands.startup();
if (req.body.augerOn != undefined) {
fn.commands.augerOn();
fn.commands.refreshConfig().then(res => {
config = res.config;
response.render('index', { config: JSON.stringify(config) });
return;
});
return;
}
if (req.body.shutdown != undefined) {
fn.commands.shutdown();
if (req.body.augerOff != undefined) {
fn.commands.augerOff();
fn.commands.refreshConfig().then(res => {
config = res.config;
response.render('index', { config: JSON.stringify(config) });
return;
});
return;
}
if (req.body.igniterOn != undefined) {
fn.commands.igniterOn();
fn.commands.refreshConfig().then(res => {
config = res.config;
response.render('index', { config: JSON.stringify(config) });
return;
});
return;
}
if (req.body.igniterOff != undefined) {
fn.commands.igniterOff();
fn.commands.refreshConfig().then(res => {
config = res.config;
response.render('index', { config: JSON.stringify(config) });
return;
});
return;
}
if (req.body.exhaustOn != undefined) {
fn.commands.exhaustOn();
fn.commands.refreshConfig().then(res => {
config = res.config;
response.render('index', { config: JSON.stringify(config) });
return;
});
return;
}
if (req.body.exhaustOff != undefined) {
fn.commands.exhaustOff();
fn.commands.refreshConfig().then(res => {
config = res.config;
response.render('index', { config: JSON.stringify(config) });
return;
});
return;
}
if (req.body.reload != undefined) {
const updateAugerOffIntervalQuery = `UPDATE intervals SET value = '${2000 - req.body.feedRate}' WHERE key = 'auger_off'`;
const updateAugerOnIntervalQuery = `UPDATE intervals SET value = '${req.body.feedRate}' WHERE key = 'auger_on'`;
@ -65,15 +112,24 @@ app.post('/', (req, response) => {
});
}).catch(err => console.log(`E: ${err}`));
}).catch(err => console.log(`E: ${err}`));
return;
}
if (req.body.quit != undefined) {
fn.commands.quit();
fs.appendFile('quit', ".", err => {
if (err) console.error(err);
});
fn.commands.refreshConfig().then(res => {
config = res.config;
response.render('index', { config: JSON.stringify(config) });
return;
});
return;
}
fn.commands.refreshConfig().then(res => {
config = res.config;
response.render('index', { config: JSON.stringify(config) });
return;
});
});
module.exports = {

View File

@ -127,8 +127,8 @@ const createIntervalsTableQuery = "CREATE TABLE IF NOT EXISTS intervals (key var
dbfn.run(createIntervalsTableQuery).then(res => {
console.log(res.status);
const intervalsEntries = {
auger_on: 600,
auger_off: 1400,
auger_on: 500,
auger_off: 1500,
pause: 5000,
igniter_start: 420000,
blower_stop: 600000

View File

@ -2,6 +2,9 @@
// TODO: Move these to config
// Physical Pin numbers for GPIO
const augerPin = 7; // Pin for controlling the relay for the pellet auger motor.
const igniterPin = 13;
const exhaustPin = 15;
const pofPin = 16;
// Require the package for pulling version numbers
const package = require('../package.json');
@ -84,9 +87,83 @@ const functions = {
});
},
},
igniter: {
// Gets called once the Igniter Pin has been setup by rpi-gpio
ready(err) {
if (err) throw err;
console.log('Igniter GPIO Ready');
return;
},
// Turns the Igniter on (Pin 7 high)
on(gpio) {
return new Promise((resolve) => {
if (process.env.ONPI == 'true') {
gpio.write(igniterPin, true, function (err) {
if (err) throw err;
resolve('Igniter turned on.');
});
} else {
resolve('Simulated Igniter turned on.');
}
});
},
// Turns the Igniter off (pin 7 low)
off(gpio) {
return new Promise((resolve) => {
if (process.env.ONPI == 'true') {
gpio.write(igniterPin, false, function (err) {
if (err) throw err;
resolve('Igniter turned off.');
});
} else {
resolve('Simulated Igniter turned off.');
}
});
}
},
exhaust: {
// Gets called once the Exhaust Pin has been setup by rpi-gpio
ready(err) {
if (err) throw err;
console.log('Exhaust GPIO Ready');
return;
},
// Turns the Exhaust on (Pin 7 high)
on(gpio) {
return new Promise((resolve) => {
if (process.env.ONPI == 'true') {
gpio.write(exhaustPin, true, function (err) {
if (err) throw err;
resolve('Exhaust turned on.');
});
} else {
resolve('Simulated Exhaust turned on.');
}
});
},
// Turns the Exhaust off (pin 7 low)
off(gpio) {
return new Promise((resolve) => {
if (process.env.ONPI == 'true') {
gpio.write(exhaustPin, false, function (err) {
if (err) throw err;
resolve('Exhaust turned off.');
});
} else {
resolve('Simulated Exhaust turned off.');
}
});
}
},
commands: {
// Prepare the stove for starting
startup() {
augerOn() { // FKA startup()
// Basic startup just enables the auger
const enableAugerQuery = "UPDATE status SET value = 1 WHERE key = 'auger'";
dbfn.run(enableAugerQuery).then(res => {
@ -94,7 +171,7 @@ const functions = {
return;
}).catch(err => console.log(`[${(Date.now() - config.timestamps.procStart)/1000}] E: ${err}`));
},
shutdown() {
augerOff() { // FKA shutdown()
// Basic shutdown only needs to disable the auger
const disableAugerQuery = "UPDATE status SET value = 0 WHERE key = 'auger'";
dbfn.run(disableAugerQuery).then(res => {
@ -103,6 +180,36 @@ const functions = {
return;
}).catch(err => console.log(`[${(Date.now() - config.timestamps.procStart)/1000}] E: ${err}`));
},
igniterOn() {
const enableIgniterQuery = "UPDATE status SET value = 1 WHERE key = 'igniter'";
dbfn.run(enableIgniterQuery).then(res => {
console.log(`[${(Date.now() - config.timestamps.procStart) / 1000}] I: Igniter enabled.`);
return;
}).catch(err => console.log(`[${(Date.now() - config.timestamps.procStart)/1000}] E: ${err}`));
},
igniterOff() {
const disableIgniterQuery = "UPDATE status SET value = 0 WHERE key = 'igniter'";
dbfn.run(disableIgniterQuery).then(res => {
if (process.env.DEBUG) console.log(`[${(Date.now() - config.timestamps.procStart)/1000}] I: ${res.status}`);
console.log(`[${(Date.now() - config.timestamps.procStart) / 1000}] I: Igniter disabled.`);
return;
}).catch(err => console.log(`[${(Date.now() - config.timestamps.procStart)/1000}] E: ${err}`));
},
exhaustOn() {
const enableExhaustQuery = "UPDATE status SET value = 1 WHERE key = 'blower'";
dbfn.run(enableExhaustQuery).then(res => {
console.log(`[${(Date.now() - config.timestamps.procStart) / 1000}] I: Exhaust enabled.`);
return;
}).catch(err => console.log(`[${(Date.now() - config.timestamps.procStart)/1000}] E: ${err}`));
},
exhaustOff() {
const disableExhaustQuery = "UPDATE status SET value = 0 WHERE key = 'blower'";
dbfn.run(disableExhaustQuery).then(res => {
if (process.env.DEBUG) console.log(`[${(Date.now() - config.timestamps.procStart)/1000}] I: ${res.status}`);
console.log(`[${(Date.now() - config.timestamps.procStart) / 1000}] I: Exhaust disabled.`);
return;
}).catch(err => console.log(`[${(Date.now() - config.timestamps.procStart)/1000}] E: ${err}`));
},
// Pauses the script for the time defined in env variables
pause() {
return new Promise((resolve) => {
@ -158,7 +265,7 @@ const functions = {
let { status } = config;
let { rows } = res;
status.auger = rows.auger;
status.blower = rows.blower;
status.exhaust = rows.blower; // TODO update db to use exhaust not blower
status.igniter = rows.igniter;
status.igniterFinished = rows.igniter_finished;
status.pof = rows.proof_of_fire;
@ -170,8 +277,8 @@ const functions = {
dbfn.all(selectTimestampsQuery).then(res => {
let { timestamps } = config;
let { rows } = res;
timestamps.blowerOff = rows.blower_off;
timestamps.blowerOn = rows.blower_on;
timestamps.exhaustOff = rows.blower_off;
timestamps.exhaustOn = rows.blower_on;
timestamps.igniterOff = rows.igniter_off;
timestamps.igniterOn = rows.igniter_on;
timestamps.procStart = rows.process_start;
@ -255,12 +362,25 @@ const functions = {
gpio.setup(augerPin, gpio.DIR_OUT, (err) => {
if (err) reject(err);
if (process.env.DEBUG) console.log('== Auger pin initialized.');
// Resolve the promise now that all pins have been initialized
resolve('== GPIO Initialized.');
this.auger.ready();
// Init the Igniter pin
gpio.setup(igniterPin, gpio.DIR_OUT, (err) => {
if (err) reject(err);
if (process.env.DEBUG) console.log('== Igniter pin initialized.');
this.igniter.ready();
// Init the Exhaust pin
gpio.setup(exhaustPin, gpio.DIR_OUT, (err) => {
if (err) reject(err);
if (process.env.DEBUG) console.log('== Exhaust pin initialized.');
this.exhaust.ready();
// Resolve the promise now that all pins have been initialized
resolve('== GPIO Initialized.');
});
});
});
} else {
// Resolve the promise
resolve('== GPIO Not Available');
resolve('== GPIO Simulated');
}
});
},
@ -275,6 +395,11 @@ const functions = {
if (err) console.log('Error removing the quit file: ' + err);
config.status.shutdownNextCycle = 1;
config.status.auger = 0;
const shutdownNextCycleQuery = "UPDATE status SET value = 1 WHERE key = 'shutdown_next_cycle'";
dbfn.run(shutdownNextCycleQuery).then(res => {
if (process.env.DEBUG) console.log(`[${(Date.now() - config.timestamps.procStart)/1000}] I: ${res.status}`);
console.log(`[${(Date.now() - config.timestamps.procStart) / 1000}] I: Shutting down next cycle.`);
}).catch(err => console.log(`[${(Date.now() - config.timestamps.procStart)/1000}] E: ${err}`));
resolve();
});
} else {

2692
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{
"name": "pscontrolpanel",
"version": "0.2.1",
"name": "hestia",
"version": "0.3.0",
"requires": true,
"packages": {},
"dependencies": {
@ -10,6 +10,6 @@
"express": "^4.18.2",
"rpi-gpio": "^2.1.7",
"sequelize": "^6.28.0",
"sqlite3": "^5.1.4"
"sqlite3": "^5.1.6"
}
}

View File

@ -87,7 +87,7 @@ html, body {
}
table {
margin: 0 auto;
margin: 10;
color: aqua !important;
}
@ -95,4 +95,12 @@ table, th, td {
border: 1px solid;
border-collapse: collapse;
padding: 3px;
}
th, td {
width: 50%;
}
#quit {
/* visibility: hidden; */
}

View File

@ -21,22 +21,28 @@
</div>
<div id="status" class="row">
<!--
| Auger | rows[0].cells[1] | On Time | rows[0].cells[3] |
| Feed Rate | rows[1].cells[1] | Off Time | rows[1].cells[3] |
| Igniter | rows[0].cells[1]
| Exhaust | rows[1].cells[1]
| Auger | rows[2].cells[1]
| Feed Rate | rows[3].cells[1]
-->
<table id="status-table" class="table table-bordered col-sm-12 col-md-6">
<tr>
<td>Auger</td>
<td>Igniter</td>
<td></td>
<td>On Time</td>
</tr>
<tr>
<td>Exhaust</td>
<td></td>
</tr>
<tr>
<td>Auger</td>
<td></td>
</tr>
<tr>
<td>Feed Rate</td>
<td></td>
<td>Off Time</td>
<td></td>
</tr>
</table>
</div>
@ -47,15 +53,27 @@
<form action="/" method="post">
<!-- Start | Shutdown | Reload Settings -->
<div class="button-container d-flex justify-content-between">
<input class="btn btn-outline-secondary" type="submit" id="ignite" value="Enable Auger" name="start">
<input class="btn btn-outline-secondary" type="submit" id="shutdown" value="Disable Auger" name="shutdown">
<input class="btn btn-outline-secondary" type="submit" id="igniter-off" value="Disable Igniter" name="igniterOff">
<input class="btn btn-outline-secondary" type="submit" id="igniter-on" value="Enable Igniter" name="igniterOn">
</div>
<div class="button-container d-flex justify-content-between">
<input class="btn btn-outline-secondary" type="submit" id="exhaust-off" value="Disable Exhaust" name="exhaustOff">
<input class="btn btn-outline-secondary" type="submit" id="exhaust-on" value="Enable Exhaust" name="exhaustOn">
</div>
<div class="button-container d-flex justify-content-between">
<input class="btn btn-outline-secondary" type="submit" id="auger-off" value="Disable Auger" name="augerOff">
<input class="btn btn-outline-secondary" type="submit" id="auger-on" value="Enable Auger" name="augerOn">
</div>
<!-- Set feed rates -->
<div class="form-group">
<label for="feedRate">Feed Rate: </label>
<select name="feedRate" class="form-control" id="feed-rate-select">
<option value="600">Low</option>
<option value="800">Medium</option>
<option value="500">Low</option>
<option value="625">Medium-Low</option>
<option value="750">Medium</option>
<option value="875">Medium-High</option>
<option value="1000">High</option>
</select>
</div>
@ -64,12 +82,12 @@
</div>
</form>
</div>
<!-- <div class="text-center my-4">
<div class="text-center my-4">
<img src="./dancing_jesus.gif" class="img-fluid">
</div> -->
</div>
<div class="controls-container">
<form action="/" method="POST">
<input class="btn btn-danger" type="submit" id="quit" value="Quit!!" name="quit" style="visibility: hidden;">
<input class="btn btn-danger" type="submit" id="quit" value="Quit!!" name="quit">
</form>
</div>
<!-- <script src="./main.js"></script> -->
@ -113,30 +131,38 @@
// Get the elements we need to update
const statusTable = document.getElementById('status-table');
const augerStatus = statusTable.rows[0].cells[1];
const augerOn = statusTable.rows[0].cells[3];
const augerOff = statusTable.rows[1].cells[3];
const feedRate = statusTable.rows[1].cells[1];
const igniterStatus = statusTable.rows[0].cells[1];
const exhaustStatus = statusTable.rows[1].cells[1];
const augerStatus = statusTable.rows[2].cells[1];
const feedRate = statusTable.rows[3].cells[1];
const feedRateSelect = document.getElementById('feed-rate-select');
// console.log(config);
igniterStatus.innerHTML = parseStatus(config.status.igniter);
exhaustStatus.innerHTML = parseStatus(config.status.exhaust);
augerStatus.innerHTML = parseStatus(config.status.auger);
augerOn.innerHTML = config.intervals.augerOn;
augerOff.innerHTML = config.intervals.augerOff;
switch (config.intervals.augerOn) {
case '600':
case '500':
feedRate.innerHTML = 'Low';
feedRateSelect.selectedIndex = 0;
break;
case '800':
feedRate.innerHTML = 'Medium';
case '625':
feedRate.innerHTML = 'Medium-Low';
feedRateSelect.selectedIndex = 1;
break;
case '750':
feedRate.innerHTML = 'Medium';
feedRateSelect.selectedIndex = 2;
break;
case '875':
feedRate.innerHTML = 'Medium-High';
feedRateSelect.selectedIndex = 3;
break;
case '1000':
feedRate.innerHTML = 'High';
feedRateSelect.selectedIndex = 2;
feedRateSelect.selectedIndex = 4;
break;
default:
feedRate.innerHTML = 'Unknown';