Init, blank
This commit is contained in:
parent
0b0a09942d
commit
928b1d36da
@ -1,5 +0,0 @@
|
||||
{
|
||||
"database": {
|
||||
"createConfigTable": ""
|
||||
}
|
||||
}
|
93
hestia.sh
93
hestia.sh
@ -1,93 +0,0 @@
|
||||
#!/bin/bash
|
||||
#####################################################
|
||||
# Interactive script for managing Hestia Web Portal #
|
||||
#####################################################
|
||||
# Formatting Tips:
|
||||
# https://misc.flogisoft.com/bash/tip_colors_and_formatting
|
||||
#
|
||||
# Formatting:
|
||||
# \e[1m - Bold
|
||||
# \e[2m - Dim
|
||||
# \e[8m - Hidden (passwords)
|
||||
#
|
||||
# Reset:
|
||||
# \e[0m - Reset All Attributes
|
||||
# \e[21m - Reset Bold/Bright
|
||||
# \e[22m - Reset Dim
|
||||
# \e[28m - Reset Hidden
|
||||
#
|
||||
# Colors:
|
||||
# \e[39m - Default Foreground Color
|
||||
# \e[30m - Black
|
||||
# \e[31m - Red
|
||||
# \e[32m - Green
|
||||
# \e[34m - Blue
|
||||
#####################################################
|
||||
|
||||
# Some initial variables to work with
|
||||
timestamp=$(date "+%Y%m%d_%H%M")
|
||||
filename="backup_$timestamp.tar.gz"
|
||||
|
||||
# Initial Prompt
|
||||
# Bash allows for linebreaks in string literals and will
|
||||
# break lines accordingly in the shell
|
||||
echo -e "
|
||||
[ Hestia Control Panel ]
|
||||
|
||||
This script is being run from: '$(pwd)'
|
||||
Active Nodes: $(ps ax -o pid,user,command | grep 'node main.js' | grep -v grep)
|
||||
|
||||
Please enter an option from below:
|
||||
|
||||
[1] Launch Hestia Web Portal
|
||||
[2] Quit Hestia Web Portal
|
||||
[3] View the logs
|
||||
[4] Update Hestia
|
||||
[5] Set up database
|
||||
|
||||
[0] Quit Control Panel"
|
||||
|
||||
# Wait for input
|
||||
read -p "Option: " opt
|
||||
|
||||
# Execute the correct commands based on input.
|
||||
case "$opt" in
|
||||
1)
|
||||
# Launch Hestia Web Portal
|
||||
clear
|
||||
echo "Launching Hestia Web Portal"
|
||||
nohup node main.js > log.txt &
|
||||
;;
|
||||
2)
|
||||
# Quit Hestia Web Portal
|
||||
clear
|
||||
echo "Quitting Hestia Web Portal Gracefully"
|
||||
touch quit
|
||||
;;
|
||||
3)
|
||||
# View logs
|
||||
clear
|
||||
less +F log.txt
|
||||
;;
|
||||
4)
|
||||
# Update Hestia
|
||||
rm data/config.db
|
||||
git pull
|
||||
;;
|
||||
5)
|
||||
# Set up database
|
||||
node modules/_setupdb.js
|
||||
;;
|
||||
0)
|
||||
# Exit the script
|
||||
clear
|
||||
echo "Quitting..."
|
||||
exit
|
||||
;;
|
||||
*)
|
||||
clear
|
||||
echo "Invalid Option!"
|
||||
;;
|
||||
esac
|
||||
|
||||
exec ./hestia.sh
|
91
main.js
91
main.js
@ -1,91 +0,0 @@
|
||||
const fn = require('./modules/functions.js').functions;
|
||||
// Import the config file
|
||||
var config = require('./templates/config.json');
|
||||
// Database Functions
|
||||
const dbfn = require('./modules/database.js');
|
||||
// Web Portal
|
||||
const portal = require('./modules/_server.js');
|
||||
portal.start();
|
||||
|
||||
dbfn.run(`UPDATE timestamps SET value = ${Date.now()} WHERE key = 'process_start'`).catch(err => console.error(`Error setting process start time: ${err}`));
|
||||
|
||||
fn.commands.refreshConfig().then(res => {
|
||||
if (process.env.DEBUG) console.log(`[${(Date.now() - config.timestamps.procStart)/1000}] I: ${res.status}`);
|
||||
config = res.config;
|
||||
// Setup for use with the Pi's GPIO pins
|
||||
switch (process.env.ONPI) {
|
||||
case 'true':
|
||||
console.log(`== Running on a Raspberry Pi.`);
|
||||
var gpio = require('rpi-gpio');
|
||||
fn.init(gpio).then((res) => {
|
||||
console.log(`[${(Date.now() - config.timestamps.procStart)/1000}] I: ${res}`);
|
||||
main(gpio);
|
||||
}).catch(rej => {
|
||||
console.log(`[${(Date.now() - config.timestamps.procStart)/1000}] E: Error during initialization: ${rej}`);
|
||||
process.exit(1);
|
||||
});
|
||||
break;
|
||||
case 'false':
|
||||
console.log(`I: Not running on a Raspberry Pi.`);
|
||||
var gpio = 'gpio';
|
||||
fn.init(gpio).then(res => {
|
||||
console.log(`[${(Date.now() - config.timestamps.procStart)/1000}] I: ${res}`);
|
||||
main(gpio);
|
||||
}).catch(rej => {
|
||||
console.log(`[${(Date.now() - config.timestamps.procStart)/1000}] E: Error during initialization: ${rej}`);
|
||||
process.exit(1);
|
||||
});
|
||||
break;
|
||||
default:
|
||||
console.log(`[${Date.now() - config.timestamps.procStart}] E: Problem with ENV file.`);
|
||||
process.exit(1);
|
||||
break;
|
||||
}
|
||||
}).catch(rej => {
|
||||
console.error(`[${(Date.now() - config.timestamps.procStart)/1000}] E: Problem refreshing the config: ${rej}`);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
function main(gpio) {
|
||||
// If the auger is enabled
|
||||
if (config.status.auger == 1) {
|
||||
// Run a cycle of the auger
|
||||
fn.auger.cycle(gpio).then(res => {
|
||||
if (process.env.DEBUG) console.log(`[${(Date.now() - config.timestamps.procStart)/1000}] I: ${res}`);
|
||||
fn.checkForQuit().then(n => {
|
||||
fn.commands.refreshConfig().then(res => {
|
||||
if (process.env.DEBUG) console.log(`[${(Date.now() - config.timestamps.procStart)/1000}] I: ${res.status}`);
|
||||
config = res.config;
|
||||
// Recursion ecursion cursion ursion rsion sion ion on n
|
||||
main(gpio);
|
||||
}).catch(rej => {
|
||||
console.error(`[${(Date.now() - config.timestamps.procStart)/1000}] E: Problem refreshing the config: ${rej}`);
|
||||
// Recursion ecursion cursion ursion rsion sion ion on n
|
||||
main(gpio);
|
||||
});
|
||||
});
|
||||
}).catch(err => {
|
||||
if (process.env.DEBUG) console.log(`[${(Date.now() - config.timestamps.procStart)/1000}] E: ${err}`);
|
||||
});
|
||||
} else {
|
||||
// If the auger is disabled
|
||||
fn.commands.pause().then(res => {
|
||||
fn.checkForQuit().then(n => {
|
||||
fn.commands.refreshConfig().then(res => {
|
||||
if (process.env.DEBUG) console.log(`[${(Date.now() - config.timestamps.procStart)/1000}] I: ${res.status}`);
|
||||
config = res.config;
|
||||
// Recursion ecursion cursion ursion rsion sion ion on n
|
||||
main(gpio);
|
||||
}).catch(rej => {
|
||||
console.error(`[${(Date.now() - config.timestamps.procStart)/1000}] E: Problem refreshing the config: ${rej}`);
|
||||
// Recursion ecursion cursion ursion rsion sion ion on n
|
||||
main(gpio);
|
||||
});
|
||||
});
|
||||
}).catch(err => {
|
||||
if (process.env.DEBUG) console.log(`[${(Date.now() - config.timestamps.procStart)/1000}] E: ${err}`);
|
||||
// Recursion ecursion cursion ursion rsion sion ion on n
|
||||
main(gpio);
|
||||
});
|
||||
}
|
||||
}
|
@ -1,83 +0,0 @@
|
||||
/* Pellet Stove Control Panel
|
||||
* Web Configuration Server
|
||||
* v0.0.0 by Skylar Grant
|
||||
*
|
||||
* TODOs:
|
||||
* Implement Express to make it easier
|
||||
* Add actual data into the responses
|
||||
*/
|
||||
|
||||
const express = require('express');
|
||||
const http = require('http');
|
||||
const fn = require('./functions.js').functions;
|
||||
var config;
|
||||
fn.commands.refreshConfig().then(newConfig => {
|
||||
config = newConfig.config;
|
||||
});
|
||||
const { dbfn } = require('./functions.js');
|
||||
|
||||
const app = express();
|
||||
const server = http.createServer(app);
|
||||
app.use(express.urlencoded());
|
||||
// Our root directory for the public web files
|
||||
app.use(express.static(__dirname + '/../www/public'));
|
||||
// Our directory for views used to render the pages
|
||||
app.set('views', __dirname + '/../www/views');
|
||||
// Set .html as the file extension for views
|
||||
app.engine('html', require('ejs').renderFile);
|
||||
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)}`);
|
||||
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();
|
||||
fn.commands.refreshConfig().then(res => {
|
||||
config = res.config;
|
||||
response.render('index', { config: JSON.stringify(config) });
|
||||
return;
|
||||
});
|
||||
}
|
||||
if (req.body.shutdown != undefined) {
|
||||
fn.commands.shutdown();
|
||||
fn.commands.refreshConfig().then(res => {
|
||||
config = res.config;
|
||||
response.render('index', { config: JSON.stringify(config) });
|
||||
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'`;
|
||||
dbfn.run(updateAugerOffIntervalQuery).then(res => {
|
||||
if (process.env.DEBUG) console.log(`[${(Date.now() - config.timestamps.procStart)/1000}] I: Auger off interval updated: ${res.data.changes}`);
|
||||
dbfn.run(updateAugerOnIntervalQuery).then(res => {
|
||||
if (process.env.DEBUG) console.log(`[${(Date.now() - config.timestamps.procStart)/1000}] I: Auger on interval updated: ${res.data.changes}`);
|
||||
fn.commands.refreshConfig().then(res => {
|
||||
config = res.config;
|
||||
response.render('index', { config: JSON.stringify(config) });
|
||||
return;
|
||||
});
|
||||
}).catch(err => console.log(`E: ${err}`));
|
||||
}).catch(err => console.log(`E: ${err}`));
|
||||
}
|
||||
if (req.body.quit != undefined) {
|
||||
fn.commands.quit();
|
||||
fn.commands.refreshConfig().then(res => {
|
||||
config = res.config;
|
||||
response.render('index', { config: JSON.stringify(config) });
|
||||
return;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = {
|
||||
start: () => {
|
||||
server.listen(8080, "0.0.0.0");
|
||||
}
|
||||
};
|
@ -1,158 +0,0 @@
|
||||
const dbfn = require('../modules/database.js');
|
||||
|
||||
// Create `status` table
|
||||
/*
|
||||
+ ----- + ------------- + ---- + --- + ------- + -------------- +
|
||||
| Field | Type | Null | Key | Default | Extra |
|
||||
+ ----- + ------------- + ---- + --- + ------- + -------------- +
|
||||
| key | varchar(100) | No | | | |
|
||||
| value | varchar(1000) | No | | | |
|
||||
+ ----- + ------------- + ---- + --- + ------- + -------------- +
|
||||
|
||||
+ ------------------- +
|
||||
| igniter |
|
||||
| blower |
|
||||
| auger |
|
||||
| igniter_finished |
|
||||
| shutdown_initiated |
|
||||
| vacuum |
|
||||
| proof_of_fire |
|
||||
| shutdown_next_cycle |
|
||||
+ ------------------- +
|
||||
|
||||
CREATE TABLE IF NOT EXISTS status (
|
||||
key varchar(100) NOT NULL,
|
||||
value varchar(1000) NOT NULL
|
||||
);
|
||||
*/
|
||||
|
||||
const createStatusTableQuery = "CREATE TABLE IF NOT EXISTS status (key varchar(100) NOT NULL,value varchar(1000) NOT NULL);";
|
||||
dbfn.run(createStatusTableQuery).then(res => {
|
||||
console.log(res.status);
|
||||
const statusEntries = {
|
||||
igniter: 0,
|
||||
blower: 0,
|
||||
auger: 0,
|
||||
igniter_finished: false,
|
||||
shutdown_initiated: 0,
|
||||
vacuum: 0,
|
||||
proof_of_fire: 0,
|
||||
shutdown_next_cycle: 0
|
||||
};
|
||||
for ( key in statusEntries ){
|
||||
const insertStatusEntryQuery = `INSERT INTO status (key, value) VALUES ("${key}", "${statusEntries[key]}")`;
|
||||
dbfn.run(insertStatusEntryQuery).then(res => {
|
||||
console.log(`${res.status}: ${res.data.lastID}: ${res.data.changes} changes`);
|
||||
}).catch(err => console.error(err));
|
||||
}
|
||||
const selectAllStatusEntriesQuery = "SELECT * FROM status";
|
||||
dbfn.all(selectAllStatusEntriesQuery).then(res => {
|
||||
console.log(res.status);
|
||||
}).catch(err => console.error(err));
|
||||
}).catch(err => {
|
||||
console.error(err);
|
||||
});
|
||||
|
||||
// Create `timestamps` table
|
||||
/*
|
||||
+ ----- + ------------- + ---- + --- + ------- + -------------- +
|
||||
| Field | Type | Null | Key | Default | Extra |
|
||||
+ ----- + ------------- + ---- + --- + ------- + -------------- +
|
||||
| key | varchar(100) | No | | | |
|
||||
| value | varchar(1000) | No | | | |
|
||||
+ ----- + ------------- + ---- + --- + ------- + -------------- +
|
||||
|
||||
+ ------------- +
|
||||
| process_start |
|
||||
| blower_on |
|
||||
| blower_off |
|
||||
| igniter_on |
|
||||
| igniter_off |
|
||||
+ ------------- +
|
||||
|
||||
CREATE TABLE IF NOT EXISTS timestamps (
|
||||
key varchar(100) NOT NULL,
|
||||
value varchar(1000) NOT NULL
|
||||
);
|
||||
*/
|
||||
|
||||
const createTimestampsTableQuery = "CREATE TABLE IF NOT EXISTS timestamps (key varchar(100) NOT NULL,value varchar(1000) NOT NULL);";
|
||||
dbfn.run(createTimestampsTableQuery).then(res => {
|
||||
console.log(res.status);
|
||||
const timestampsEntries = {
|
||||
process_start: 0,
|
||||
blower_on: 0,
|
||||
blower_off: 0,
|
||||
igniter_on: 0,
|
||||
igniter_off: 0
|
||||
};
|
||||
for ( key in timestampsEntries ){
|
||||
const insertTimestampsEntryQuery = `INSERT INTO timestamps (key, value) VALUES ("${key}", "${timestampsEntries[key]}")`;
|
||||
dbfn.run(insertTimestampsEntryQuery).then(res => {
|
||||
console.log(`${res.status}: ${res.data.lastID}: ${res.data.changes} changes`);
|
||||
}).catch(err => console.error(err));
|
||||
}
|
||||
const selectAllTimestampsEntriesQuery = "SELECT * FROM timestamps";
|
||||
dbfn.all(selectAllTimestampsEntriesQuery).then(res => {
|
||||
console.log(res.status);
|
||||
}).catch(err => console.error(err));
|
||||
}).catch(err => {
|
||||
console.error(err);
|
||||
});
|
||||
|
||||
// Create `intervals` table
|
||||
/*
|
||||
+ ----- + ------------- + ---- + --- + ------- + -------------- +
|
||||
| Field | Type | Null | Key | Default | Extra |
|
||||
+ ----- + ------------- + ---- + --- + ------- + -------------- +
|
||||
| key | varchar(100) | No | | | |
|
||||
| value | varchar(1000) | No | | | |
|
||||
+ ----- + ------------- + ---- + --- + ------- + -------------- +
|
||||
|
||||
+ ------------- +
|
||||
| auger_on |
|
||||
| auger_off |
|
||||
| pause |
|
||||
| igniter_start |
|
||||
| blower_stop |
|
||||
+ ------------- +
|
||||
|
||||
CREATE TABLE IF NOT EXISTS intervals (
|
||||
key varchar(100) NOT NULL,
|
||||
value varchar(1000) NOT NULL
|
||||
);
|
||||
*/
|
||||
|
||||
const createIntervalsTableQuery = "CREATE TABLE IF NOT EXISTS intervals (key varchar(100) NOT NULL,value varchar(1000) NOT NULL);";
|
||||
dbfn.run(createIntervalsTableQuery).then(res => {
|
||||
console.log(res.status);
|
||||
const intervalsEntries = {
|
||||
auger_on: 600,
|
||||
auger_off: 1400,
|
||||
pause: 5000,
|
||||
igniter_start: 420000,
|
||||
blower_stop: 600000
|
||||
};
|
||||
for ( key in intervalsEntries ){
|
||||
const insertIntervalsEntryQuery = `INSERT INTO intervals (key, value) VALUES ("${key}", "${intervalsEntries[key]}")`;
|
||||
dbfn.run(insertIntervalsEntryQuery).then(res => {
|
||||
console.log(`${res.status}: ${res.data.lastID}: ${res.data.changes} changes`);
|
||||
}).catch(err => console.error(err));
|
||||
}
|
||||
const selectAllIntervalsEntriesQuery = "SELECT * FROM intervals";
|
||||
dbfn.all(selectAllIntervalsEntriesQuery).then(res => {
|
||||
console.log(res.status);
|
||||
}).catch(err => console.error(err));
|
||||
}).catch(err => {
|
||||
console.error(err);
|
||||
});
|
||||
|
||||
// Show the tables to confirm they were created properly:
|
||||
|
||||
dbfn.showTables().then(res => {
|
||||
res.rows.forEach(row => {
|
||||
console.log("Table: " + JSON.stringify(row));
|
||||
});
|
||||
}).catch(err => {
|
||||
console.error(err);
|
||||
});
|
@ -1,53 +0,0 @@
|
||||
const sqlite3 = require('sqlite3').verbose();
|
||||
|
||||
const db = new sqlite3.Database('./data/config.db', (err) => {
|
||||
if (err) throw `E: DB Connection: ${err.message}`;
|
||||
console.log(`I: Connected to the database.`);
|
||||
});
|
||||
|
||||
module.exports = {
|
||||
run(query) {
|
||||
return new Promise((resolve, reject) => {
|
||||
db.serialize(() => {
|
||||
db.run(query, function(err) {
|
||||
if (err) {
|
||||
reject("Problem executing the query: " + err.message);
|
||||
return;
|
||||
}
|
||||
resolve( { "status": "Query executed successfully: " + query, "data": this });
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
all(query) {
|
||||
return new Promise((resolve, reject) => {
|
||||
db.serialize(() => {
|
||||
db.all(query, (err, rows) => {
|
||||
if (err) {
|
||||
reject("Problem executing the query: " + err.message);
|
||||
return;
|
||||
}
|
||||
// [ { key: 'key_name', value: '0' }, { key: 'key_name', value: '0' } ]
|
||||
let organizedRows = {};
|
||||
rows.forEach(row => {
|
||||
organizedRows[row.key] = row.value;
|
||||
});
|
||||
resolve({ "status": "Query executed successfully: " + query, "rows": organizedRows });
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
showTables() {
|
||||
return new Promise((resolve, reject) => {
|
||||
db.serialize(() => {
|
||||
db.all("SELECT name FROM sqlite_master WHERE type='table'", (err, rows) => {
|
||||
if (err) {
|
||||
reject("Problem executing the query: " + err.message);
|
||||
return;
|
||||
}
|
||||
resolve({ "status": "Tables retreived successfully", "rows": rows });
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
@ -1,292 +0,0 @@
|
||||
// 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
|
||||
const augerPin = 7; // Pin for controlling the relay for the pellet auger motor.
|
||||
|
||||
// Require the package for pulling version numbers
|
||||
const package = require('../package.json');
|
||||
// Database Functions
|
||||
const dbfn = require('./database.js');
|
||||
|
||||
|
||||
// Get environment variables
|
||||
const dotenv = require('dotenv').config();
|
||||
// Module for working with files
|
||||
const fs = require('fs');
|
||||
const { exec } = require('child_process');
|
||||
var config = require('../templates/config.json');
|
||||
|
||||
|
||||
// The functions we'll export to be used in other files
|
||||
const functions = {
|
||||
auger: {
|
||||
// Gets called once the Auger Pin has been setup by rpi-gpio
|
||||
ready(err) {
|
||||
if (err) throw err;
|
||||
console.log('Auger GPIO Ready');
|
||||
return;
|
||||
},
|
||||
// Turns the auger on (Pin 7 high)
|
||||
on(gpio) {
|
||||
return new Promise((resolve) => {
|
||||
if (process.env.ONPI == 'true') {
|
||||
gpio.write(augerPin, true, function (err) {
|
||||
if (err) throw err;
|
||||
resolve('Auger turned on.');
|
||||
});
|
||||
} else {
|
||||
resolve('Simulated auger turned on.');
|
||||
}
|
||||
});
|
||||
|
||||
},
|
||||
// Turns the auger off (pin 7 low)
|
||||
off(gpio) {
|
||||
return new Promise((resolve) => {
|
||||
if (process.env.ONPI == 'true') {
|
||||
gpio.write(augerPin, false, function (err) {
|
||||
if (err) throw err;
|
||||
resolve('Auger turned off.');
|
||||
|
||||
});
|
||||
} else {
|
||||
resolve('Simulated auger turned off.');
|
||||
}
|
||||
});
|
||||
|
||||
},
|
||||
// Cycles the auger using the two functions above this one (functions.auger.on() and functions.auger.off())
|
||||
// Sleeps in between cycles using functions.sleep()
|
||||
cycle(gpio) {
|
||||
return new Promise((resolve) => {
|
||||
// Turn the auger on
|
||||
this.on(gpio).then((res) => {
|
||||
// Log action if in debug mode
|
||||
// if (process.env.DEBUG) console.log(`[${(Date.now() - config.timestamps.procStart)/1000}] I: ${res}`);
|
||||
// Sleep for the time set in env variables
|
||||
functions.sleep(config.intervals.augerOn).then((res) => {
|
||||
// Log action if in debug mode
|
||||
// if (process.env.DEBUG) console.log(`[${(Date.now() - config.timestamps.procStart)/1000}] I: ${res}`);
|
||||
// Turn the auger off
|
||||
this.off(gpio).then((res) => {
|
||||
// Log action if in debug mode
|
||||
// if (process.env.DEBUG) console.log(`[${(Date.now() - config.timestamps.procStart)/1000}] I: ${res}`);
|
||||
// Sleep for the time set in env variables
|
||||
functions.sleep(config.intervals.augerOff).then((res) => {
|
||||
// Log action if in debug mode
|
||||
// if (process.env.DEBUG) 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 (${config.intervals.augerOn}/${config.intervals.augerOff})`);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
},
|
||||
commands: {
|
||||
// Prepare the stove for starting
|
||||
startup() {
|
||||
// Basic startup just enables the auger
|
||||
const enableAugerQuery = "UPDATE status SET value = 1 WHERE key = 'auger'";
|
||||
dbfn.run(enableAugerQuery).then(res => {
|
||||
console.log(`[${(Date.now() - config.timestamps.procStart) / 1000}] I: Auger enabled.`);
|
||||
return;
|
||||
}).catch(err => console.log(`[${(Date.now() - config.timestamps.procStart)/1000}] E: ${err}`));
|
||||
},
|
||||
shutdown() {
|
||||
// Basic shutdown only needs to disable the auger
|
||||
const disableAugerQuery = "UPDATE status SET value = 0 WHERE key = 'auger'";
|
||||
dbfn.run(disableAugerQuery).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: Auger 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) => {
|
||||
if (process.env.DEBUG) console.log(`[${(Date.now() - config.timestamps.procStart) / 1000}] I: Pausing for ${config.intervals.pause}ms`);
|
||||
|
||||
functions.sleep(config.intervals.pause).then((res) => {
|
||||
if (process.env.DEBUG) console.log(`[${(Date.now() - config.timestamps.procStart) / 1000}] I: Pause finished.`);
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
},
|
||||
// Reload the environment variables on the fly
|
||||
reload(envs) {
|
||||
return new Promise((resolve) => {
|
||||
// Re-require dotenv because inheritance in js sucks
|
||||
const dotenv = require('dotenv').config({ override: true });
|
||||
// Delete the reload file
|
||||
fs.unlink('./reload', (err) => {
|
||||
if (err) throw err;
|
||||
if (process.env.DEBUG) console.log('Deleted reload file.');
|
||||
});
|
||||
// Print out the new environment variables
|
||||
// This should be printed regardless of debug status, maybe prettied up TODO?
|
||||
console.log('Reloaded environment variables.');
|
||||
console.log(`ONTIME=${config.intervals.augerOn}\nOFFTIME=${config.intervals.augerOff}\nPAUSETIME=${config.intervals.pause}\nDEBUG=${process.env.DEBUG}\nONPI=${process.env.ONPI}`);
|
||||
// Resolve the promise, letting the main script know we're done reloading the variables and the cycle can continue
|
||||
resolve();
|
||||
});
|
||||
|
||||
},
|
||||
refreshConfig() {
|
||||
return new Promise((resolve, reject) => {
|
||||
// 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;
|
||||
// console.log(`[${(Date.now() - config.timestamps.procStart)/1000}] I: Intervals updated: (${newSettings.augerOn}/${newSettings.augerOff})`);
|
||||
|
||||
// }
|
||||
// fs.writeFile('./config.json', JSON.stringify(config), (err) => {
|
||||
// if (err) reject(err);
|
||||
// resolve();
|
||||
// });
|
||||
|
||||
// Get status
|
||||
const selectStatusQuery = "SELECT * FROM status";
|
||||
dbfn.all(selectStatusQuery).then(res => {
|
||||
let { status } = config;
|
||||
let { rows } = res;
|
||||
status.auger = rows.auger;
|
||||
status.blower = rows.blower;
|
||||
status.igniter = rows.igniter;
|
||||
status.igniterFinished = rows.igniter_finished;
|
||||
status.pof = rows.proof_of_fire;
|
||||
status.shutdownNextCycle = rows.shutdown_next_cycle;
|
||||
status.vacuum = rows.vacuum;
|
||||
|
||||
// Get timestamps
|
||||
const selectTimestampsQuery = "SELECT * FROM timestamps";
|
||||
dbfn.all(selectTimestampsQuery).then(res => {
|
||||
let { timestamps } = config;
|
||||
let { rows } = res;
|
||||
timestamps.blowerOff = rows.blower_off;
|
||||
timestamps.blowerOn = rows.blower_on;
|
||||
timestamps.igniterOff = rows.igniter_off;
|
||||
timestamps.igniterOn = rows.igniter_on;
|
||||
timestamps.procStart = rows.process_start;
|
||||
|
||||
// Get intervals
|
||||
const selectIntervalsQuery = "SELECT * FROM intervals";
|
||||
dbfn.all(selectIntervalsQuery).then(res => {
|
||||
let { intervals } = config;
|
||||
let { rows } = res;
|
||||
intervals.augerOff = rows.auger_off;
|
||||
intervals.augerOn = rows.auger_on;
|
||||
intervals.blowerStop = rows.blower_stop;
|
||||
intervals.igniterStart = rows.igniter_start;
|
||||
intervals.pause = rows.pause;
|
||||
resolve({ "status": "Refreshed the config", "config": config });
|
||||
}).catch(err => {
|
||||
reject(err);
|
||||
return;
|
||||
});
|
||||
}).catch(err => {
|
||||
reject(err);
|
||||
return;
|
||||
});
|
||||
}).catch(err => {
|
||||
reject(err);
|
||||
return;
|
||||
});
|
||||
});
|
||||
},
|
||||
quit() {
|
||||
functions.commands.shutdown();
|
||||
functions.auger.off(gpio).then(res => {
|
||||
console.log(`[${(Date.now() - config.timestamps.procStart) / 1000}] I: Exiting app...`);
|
||||
process.exit(0);
|
||||
}).catch(err => {
|
||||
console.log(`[${(Date.now() - config.timestamps.procStart) / 1000}] E: Unable to shut off auger, rebooting Pi!`);
|
||||
exec('shutdown -r 0');
|
||||
});
|
||||
}
|
||||
},
|
||||
// Sleeps for any given milliseconds
|
||||
sleep(ms) {
|
||||
return new Promise((resolve) => {
|
||||
// if (process.env.DEBUG) console.log(`Sleeping for ${ms}ms`);
|
||||
// Function to be called when setTimeout finishes
|
||||
const finish = () => {
|
||||
// Resolve the promise
|
||||
resolve(`Slept for ${ms}ms`);
|
||||
};
|
||||
// The actual sleep function, sleeps for ms then calls finish()
|
||||
setTimeout(finish, ms);
|
||||
});
|
||||
},
|
||||
// Initializes rpi-gpio, or resolves if not on a raspberry pi
|
||||
init(gpio) {
|
||||
fs.readFile('./templates/config.json', (err, data) => {
|
||||
fs.writeFile('./config.json', data, (err) => {
|
||||
if (err) throw err;
|
||||
config = require('../config.json');
|
||||
})
|
||||
})
|
||||
// TODO this boot splash needs updating
|
||||
return new Promise((resolve, reject) => {
|
||||
// Boot/About/Info
|
||||
console.log(`== Lennox Winslow PS40
|
||||
== Pellet Stove Control Panel
|
||||
== Author: Skylar Grant
|
||||
== Version: v${package.version}
|
||||
==
|
||||
== Startup Time: ${new Date().toISOString()}
|
||||
==
|
||||
== Environment variables:
|
||||
== == ONTIME=${config.intervals.augerOn}
|
||||
== == OFFTIME=${config.intervals.augerOff}
|
||||
== == PAUSETIME=${config.intervals.pause}
|
||||
== == DEBUG=${process.env.DEBUG}
|
||||
== == ONPI=${process.env.ONPI}`);
|
||||
// Set up GPIO 4 (pysical pin 7) as output, then call functions.auger.ready()
|
||||
if (process.env.ONPI == 'true') {
|
||||
// Init the Auger pin
|
||||
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.');
|
||||
});
|
||||
} else {
|
||||
// Resolve the promise
|
||||
resolve('== GPIO Not Available');
|
||||
}
|
||||
});
|
||||
},
|
||||
checkForQuit() {
|
||||
if (config.status.shutdownNextCycle == 1) {
|
||||
console.log(`[${(Date.now() - config.timestamps.procStart) / 1000}] I: Exiting Process!`);
|
||||
process.exit();
|
||||
}
|
||||
return new Promise((resolve, reject) => {
|
||||
if (fs.existsSync('./quit')) {
|
||||
fs.unlink('./quit', err => {
|
||||
if (err) console.log('Error removing the quit file: ' + err);
|
||||
config.status.shutdownNextCycle = 1;
|
||||
config.status.auger = 0;
|
||||
resolve();
|
||||
});
|
||||
} else {
|
||||
resolve('Not shutting down');
|
||||
}
|
||||
});
|
||||
},
|
||||
time(stamp) {
|
||||
const time = new Date(stamp);
|
||||
return `${time.getHours()}:${time.getMinutes()}:${time.getSeconds()}`;
|
||||
}
|
||||
}
|
||||
|
||||
// Export the above object, functions, as a module
|
||||
module.exports = { functions, dbfn };
|
3770
package-lock.json
generated
3770
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
15
package.json
15
package.json
@ -1,15 +0,0 @@
|
||||
{
|
||||
"name": "pscontrolpanel",
|
||||
"version": "0.2.1",
|
||||
"requires": true,
|
||||
"packages": {},
|
||||
"dependencies": {
|
||||
"body-parser": "^1.20.1",
|
||||
"dotenv": "^16.0.3",
|
||||
"ejs": "^3.1.8",
|
||||
"express": "^4.18.2",
|
||||
"rpi-gpio": "^2.1.7",
|
||||
"sequelize": "^6.28.0",
|
||||
"sqlite3": "^5.1.4"
|
||||
}
|
||||
}
|
0
src/assets/hestia.js
Normal file
0
src/assets/hestia.js
Normal file
0
src/assets/main.css
Normal file
0
src/assets/main.css
Normal file
0
src/index.html
Normal file
0
src/index.html
Normal file
@ -1,30 +0,0 @@
|
||||
{
|
||||
"status": {
|
||||
"igniter": 0,
|
||||
"blower": 0,
|
||||
"auger": 0,
|
||||
"igniterFinished": false,
|
||||
"shutdown": 0,
|
||||
"vacuum": 0,
|
||||
"pof": 0,
|
||||
"shutdownNextCycle": 0
|
||||
},
|
||||
"timestamps": {
|
||||
"procStart": 0,
|
||||
"blowerOn": 0,
|
||||
"blowerOff": 0,
|
||||
"igniterOn": 0,
|
||||
"igniterOff": 0
|
||||
},
|
||||
"intervals": {
|
||||
"augerOn": "600",
|
||||
"augerOff": "1400",
|
||||
"pause": "3000",
|
||||
"igniterStart": "5000",
|
||||
"blowerStop": "5000"
|
||||
},
|
||||
"web": {
|
||||
"port": 8080,
|
||||
"ip": "0.0.0.0"
|
||||
}
|
||||
}
|
BIN
www/.DS_Store
vendored
BIN
www/.DS_Store
vendored
Binary file not shown.
Binary file not shown.
Before Width: | Height: | Size: 163 KiB |
@ -1,98 +0,0 @@
|
||||
html, body {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
background-color: #333;
|
||||
color: aqua;
|
||||
font-family: sans-serif;
|
||||
}
|
||||
|
||||
#title {
|
||||
text-align: center;
|
||||
font-size: 35px;
|
||||
margin-top: 10px;
|
||||
padding-bottom: 15px;
|
||||
}
|
||||
|
||||
#title a {
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
#safeties {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.controls-container {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#buttons button {
|
||||
margin: 20px 5px;
|
||||
font-size: 20px;
|
||||
width: 150px;
|
||||
height: 30px;
|
||||
}
|
||||
|
||||
.controls-container input {
|
||||
margin: 5px;
|
||||
}
|
||||
|
||||
.subheading {
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#log-container {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#log-area {
|
||||
width: 100%;
|
||||
height: 500px;
|
||||
background-color: aqua;
|
||||
color: aqua;
|
||||
}
|
||||
|
||||
#trial {
|
||||
display:;
|
||||
color: yellow;
|
||||
font-size: 20px;
|
||||
background-color: red;
|
||||
}
|
||||
|
||||
.button-container {
|
||||
padding: 0;
|
||||
margin-top: 20px;
|
||||
margin-bottom: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.btn {
|
||||
background-color: #333;
|
||||
color: aqua;
|
||||
}
|
||||
|
||||
.button-selected {
|
||||
margin: 5px;
|
||||
border-width: 0px;
|
||||
font-family: times;
|
||||
font-size: 20px;
|
||||
height: 35px;
|
||||
width: 100px;
|
||||
border-top-left-radius: 0px;
|
||||
border-bottom-left-radius: 0px;
|
||||
border-top-right-radius: 0px;
|
||||
border-bottom-right-radius: 0px;
|
||||
}
|
||||
|
||||
table {
|
||||
margin: 0 auto;
|
||||
color: aqua !important;
|
||||
}
|
||||
|
||||
table, th, td {
|
||||
border: 1px solid;
|
||||
border-collapse: collapse;
|
||||
padding: 3px;
|
||||
}
|
@ -1,150 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Hestia Web Portal</title>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<!-- Bootstrap -->
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-GLhlTQ8iRABdZLl6O3oVMWSktQOp6b7In1Zl3/Jr59b6EGGoI1aFkw7cmDA6j6gD" crossorigin="anonymous">
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/js/bootstrap.bundle.min.js" integrity="sha384-w76AqPfDkMBDXo30jS1Sgez6pr3x5MlQ1ZAGC+nuZB+EYdgRZgiwxhTBTkF7CXvN" crossorigin="anonymous"></script>
|
||||
<link rel="stylesheet" href="/main.css">
|
||||
</head>
|
||||
<body onload="refreshData()" class="container">
|
||||
<script>
|
||||
// Get the config file
|
||||
const config = <%- config %>;
|
||||
console.log(<%- config %>);
|
||||
</script>
|
||||
<%- include('trial.html') -%>
|
||||
<div id="title" class="text-center mb-4">
|
||||
<a href='./'>Hestia Web Portal</a>
|
||||
</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] |
|
||||
-->
|
||||
|
||||
<table id="status-table" class="table table-bordered col-sm-12 col-md-6">
|
||||
<tr>
|
||||
<td>Auger</td>
|
||||
<td></td>
|
||||
<td>On Time</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Feed Rate</td>
|
||||
<td></td>
|
||||
<td>Off Time</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="controls-container">
|
||||
<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">
|
||||
</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="1000">High</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="button-container d-flex justify-content-end">
|
||||
<input class="btn btn-outline-secondary" type="submit" id="reload" value="Set Feed Rate" name="reload">
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<!-- <div class="text-center my-4">
|
||||
<img src="./dancing_jesus.gif" class="img-fluid">
|
||||
</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;">
|
||||
</form>
|
||||
</div>
|
||||
<!-- <script src="./main.js"></script> -->
|
||||
<script>
|
||||
function sleep(ms) {
|
||||
return new Promise((resolve, reject) => {
|
||||
setTimeout(() => {
|
||||
resolve();
|
||||
}, ms);
|
||||
});
|
||||
}
|
||||
|
||||
function readJSON(path) {
|
||||
var request = new XMLHttpRequest();
|
||||
request.open("GET", path, false);
|
||||
request.send(null)
|
||||
var JSONObj = JSON.parse(request.responseText);
|
||||
return JSONObj;
|
||||
}
|
||||
|
||||
function parseStatus(data) {
|
||||
switch (data) {
|
||||
case "0":
|
||||
return "Off";
|
||||
break;
|
||||
case "1":
|
||||
return "On";
|
||||
break
|
||||
default:
|
||||
return "Error: " + data;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function refreshData() {
|
||||
// const log = document.getElementById('log-area');
|
||||
// log.contentWindow.location.reload();
|
||||
// sleep(100).then(() => {
|
||||
// document.getElementById('log-area').contentWindow.scrollTo(0, 9999999999);
|
||||
// });
|
||||
|
||||
// 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 feedRateSelect = document.getElementById('feed-rate-select');
|
||||
|
||||
// console.log(config);
|
||||
|
||||
augerStatus.innerHTML = parseStatus(config.status.auger);
|
||||
augerOn.innerHTML = config.intervals.augerOn;
|
||||
augerOff.innerHTML = config.intervals.augerOff;
|
||||
|
||||
switch (config.intervals.augerOn) {
|
||||
case '600':
|
||||
feedRate.innerHTML = 'Low';
|
||||
feedRateSelect.selectedIndex = 0;
|
||||
break;
|
||||
case '800':
|
||||
feedRate.innerHTML = 'Medium';
|
||||
feedRateSelect.selectedIndex = 1;
|
||||
break;
|
||||
case '1000':
|
||||
feedRate.innerHTML = 'High';
|
||||
feedRateSelect.selectedIndex = 2;
|
||||
break;
|
||||
default:
|
||||
feedRate.innerHTML = 'Unknown';
|
||||
break;
|
||||
}
|
||||
feedRate.value = config.intervals.augerOn;
|
||||
};
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -1,65 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Hestia Web Portal</title>
|
||||
<link rel="stylesheet" href="/main.css">
|
||||
</head>
|
||||
<body onload="refreshData()">
|
||||
<%- include('trial.html') -%>
|
||||
<div id="title"><a href='./'>Hestia Web Portal</a></div>
|
||||
<div id="status">
|
||||
<!--
|
||||
| Auger | rows[0].cells[1] | On Time | rows[0].cells[3] |
|
||||
| Feed Rate | rows[1].cells[1] | Off Time | rows[1].cells[3] |
|
||||
-->
|
||||
|
||||
<table id="status-table">
|
||||
<tr>
|
||||
<td>Auger</td>
|
||||
<td></td>
|
||||
<td>On Time</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Feed Rate</td>
|
||||
<td></td>
|
||||
<td>Off Time</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="controls-container">
|
||||
<form action="/" method="post">
|
||||
<!-- Start | Shutdown | Reload Settings -->
|
||||
<div class="button-container">
|
||||
<input class="button-unselected" type="submit" id="ignite" value="Enable Auger" name="start"><input class="button-unselected" type="submit" id="shutdown" value="Disable Auger" name="shutdown"><br>
|
||||
</div>
|
||||
<!-- Set feed rates -->
|
||||
<label for="feedRate">Feed Rate: </label>
|
||||
<select name="feedRate">
|
||||
<option value="600">Low</option>
|
||||
<option value="800">Medium</option>
|
||||
<option value="1000">High</option>
|
||||
</select>
|
||||
<div class="button-container">
|
||||
<input class="button-unselected" type="submit" id="reload" value="Reload" name="reload">
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="button-container">
|
||||
<img src="./dancing_jesus.gif">
|
||||
</div>
|
||||
<div id="log-container">
|
||||
<iframe id="log-area" src="log.txt"></iframe>
|
||||
</div>
|
||||
<div class="controls-container">
|
||||
<form action="/" method="POST">
|
||||
<input class="button-unselected" type="submit" id="quit" value="Quit!!" name="quit" style="visibility: hidden;">
|
||||
</form>
|
||||
</div>
|
||||
<script src="./main.js"></script>
|
||||
</body>
|
||||
</html>
|
@ -1,3 +0,0 @@
|
||||
<body>
|
||||
<marquee id="trial">YOUR FREE TRIAL HAS ENDED, PLEASE PURCHASE A PELLET STOVE SUBSCRIPTION</marquee>
|
||||
</body>
|
Loading…
Reference in New Issue
Block a user