From 0a349d31e41c372f47f0eddb2547a8ad9e936ad5 Mon Sep 17 00:00:00 2001 From: Skylar Grant Date: Mon, 11 Nov 2024 20:35:21 -0500 Subject: [PATCH] WIP Reworking startup flow --- README.md | 43 ++++++++++++++ src/custom_modules/config.json | 5 +- src/custom_modules/functions.js | 100 ++++++++++++++++++++++++++++++-- src/main.js | 21 ++++--- 4 files changed, 154 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 98365c9..7c85ffb 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,49 @@ Node.js Raspberry Pi Pellet Stove Controller, named after the Greek virgin godde # About This project seeks to replace the OEM control panel on a Lennox Winslow PS40 pellet stove with a Raspberry Pi. I am utilizing a Raspberry Pi 3 Model B+, relays, and temperature snap switches installed on the pellet stove. I chose a Pi 3 for its wireless connectivity and familiarity with the platform. I had previously used an Arduino Nano for a much more rudimentary version of this project and found great difficulty in making adjustments to the code. Additionally the Raspberry Pi platform will allow for expansion in the future to include IoT controls and logging of usage utilizing networked databases. The project will be written using Node.js to handle high-level logic and communications, calling Python scripts to handle interacting with GPIO. I'm now in the process of rewriting this project to a more modern v2. Previously I had settled on using deprecated versions of Node.js to maintain compatibility with various Pi GPIO modules, now I've decided to split the GPIO controls off to a small Python interface script. +# Setting Up the Pi +```bash +# Update and upgrade the system +sudo apt update && sudo apt upgrade -y +# Install Node.js and npm along with Python and pip +sudo apt install nodejs npm python3 python3-pip +# Install PM2 to manage the Node.js process +sudo npm install -g pm2 +# Set up a new user to run the process with home /srv/hestia +sudo useradd -m -s /usr/bin/fish hestia +# Set hestia home to /srv/hestia +sudo usermod -d /srv/hestia hestia +# Create a runners group +sudo groupadd runners +# Add the new user to the runners group +sudo usermod -aG runners hestia +# Add hestia to the gpio group +sudo usermod -aG gpio hestia +# Create a directory for the project +sudo mkdir /srv/hestia +# Change ownership of the directory to the new user +sudo chown hestia:runners /srv/hestia -R +# Set permissions on the directory +sudo chmod 775 /srv/hestia -R +# Change to the new user +sudo su hestia +# Pull the project +git clone https://git.vfsh.dev/voidf1sh/hestia.git /srv/hestia +# Change to the project directory +cd /srv/hestia +# Checkout the backend branch +git checkout v2-back +# Install the project dependencies +npm install +# Test run +node src/main.js +# Start with PM2 +pm2 start src/main.js --name hestia +# Save the process list +pm2 save +# Setup PM2 daemon +pm2 startup +``` # Logic Flow ### Boot diff --git a/src/custom_modules/config.json b/src/custom_modules/config.json index 9aa2ef5..a4ce285 100644 --- a/src/custom_modules/config.json +++ b/src/custom_modules/config.json @@ -61,7 +61,7 @@ ], "power": { "start": { - "exhaustDelay": 5000, + "vacuumCheckDelay": 5000, "igniterPreheat": 60000, "igniterDelay": 420000 }, @@ -69,5 +69,6 @@ "exhaustDelay": 600000 } }, - "gpioScript": "src/python/gpio_interface.py" + "gpioScript": "src/python/gpio_interface.py", + "augerTotalCycleTime": 2000 } \ No newline at end of file diff --git a/src/custom_modules/functions.js b/src/custom_modules/functions.js index 659286b..feff1f6 100644 --- a/src/custom_modules/functions.js +++ b/src/custom_modules/functions.js @@ -52,16 +52,108 @@ module.exports = { }).catch(e => console.error(e)); } }, + routines: { + startup(communicator, state) { + return new Promise(async (resolve, reject) => { + const mod = module.exports; + // Set pins to default states + mod.gpio.setDefaults(communicator, state).then(changes => { + mod.log(changes); + }).catch(e => console.error(e)); + + // Turn on the exhaust + mod.power.exhaust.on(communicator, state).then((res) => { + // Wait for vacuum + mod.sleep(config.power.start.vacuumCheckDelay).then(() => { + // Vacuum switch is in series with auger so no need for logic check + // Turn on the auger + mod.power.auger.on(communicator, state).then((res) => { + // Wait for auger to start + }).catch(e => console.error(e)); + }); + }).catch(e => console.error(e)); + }); + }, + shutdown(communicator, state) { + return new Promise(async (resolve, reject) => { + + }); + }, + cycleAuger() { + const mod = module.exports; + return new Promise(async (resolve, reject) => { + // Check if the auger is enabled + if (process.psState.auger.on) { + mod.power.auger.on().then((res) => { + // Sleep while auger is feeding + mod.sleep(process.psState.auger.feedRate).then(() => { + // Turn off auger + mod.power.auger.off().then((res) => { + resolve('Auger cycle complete.'); + }).catch(e => reject(e)); + }); + }).catch(e => reject(e)); + } else { + resolve('Auger is disabled.'); + } + }); + } + }, power: { + auger: { + on() { + return new Promise(async (resolve, reject) => { + gpio.setPin(process.pinMap.get('auger').board, 1).then(() => { + resolve('Auger powered on.'); + }).catch(e => reject(e)); + }); + }, + off() { + return new Promise(async (resolve, reject) => { + gpio.setPin(process.pinMap.get('auger').board, 0).then(() => { + resolve('Auger powered off.'); + }).catch(e => reject(e)); + }); + } + }, + exhaust: { + on() { + return new Promise(async (resolve, reject) => { + gpio.setPin(process.pinMap.get('exhaust').board, 1).then(() => { + resolve('Exhaust powered on.'); + }).catch(e => reject(e)); + }); + }, + off() { + return new Promise(async (resolve, reject) => { + gpio.setPin(process.pinMap.get('exhaust').board, 0).then(() => { + resolve('Exhaust powered off.'); + }).catch(e => reject(e)); + }); + } + }, + igniter: { + on() { + return new Promise(async (resolve, reject) => { + gpio.setPin(process.pinMap.get('igniter').board, 1).then(() => { + resolve('Igniter powered on.'); + }).catch(e => reject(e)); + }); + }, + off() { + return new Promise(async (resolve, reject) => { + gpio.setPin(process.pinMap.get('igniter').board, 0).then(() => { + resolve('Igniter powered off.'); + }).catch(e => reject(e)); + }); + } + }, start: { init(communicator, state) { return new Promise(async (resolve, reject) => { // TODO: Check pin states? - // Set pins to default states - module.exports.gpio.setDefaults(communicator, state).then(changes => { - module.exports.log(changes); - }).catch(e => console.error(e)); + // Start the exhaust this.exhaust().then((res) => { diff --git a/src/main.js b/src/main.js index 7c0616d..c9f1114 100644 --- a/src/main.js +++ b/src/main.js @@ -4,12 +4,12 @@ const config = require('./custom_modules/config.json'); const fn = require('./custom_modules/functions.js'); const { State, Communicator } = require('./custom_modules/HestiaClasses.js'); -const psState = new State(config); -const comms = new Communicator(psState); +process.psState = new State(config); +const comms = new Communicator(process.psState); -comms.init(psState, config); +comms.init(process.psState, config); -fn.gpio.init(comms, psState); +fn.gpio.init(comms, process.psState); // Sensor detection loop setInterval(() => { @@ -17,9 +17,9 @@ setInterval(() => { if (pin.mode === 'IN') { gpio.readPin(pin.board).then(state => { const boolState = state === '1' ? true : false; - if (boolState !== psState[pin.key].on) { - psState[pin.key].on = boolState; - comms.send(config.mqtt.topics[pin.key], JSON.stringify(psState[pin.key])); + if (boolState !== process.psState[pin.key].on) { + process.psState[pin.key].on = boolState; + comms.send(config.mqtt.topics[pin.key], JSON.stringify(process.psState[pin.key])); fn.log(`${pin.key}: ${state}`); } }).catch(e => console.error(e)); @@ -27,6 +27,9 @@ setInterval(() => { } }, 1000); +// Auger feed loop +setInterval(fn.routines.cycleAuger, config.augerTotalCycleTime); + comms.on('stateChange', (oldState, state) => { console.log(`State change detected.`); fn.handlers.stateChange(oldState, state); @@ -34,10 +37,10 @@ comms.on('stateChange', (oldState, state) => { comms.on('startup', () => { console.log(`Startup detected.`); - fn.power.start.init(comms, psState).catch(e => console.error(e)); + fn.power.start.init(comms, process.psState).catch(e => console.error(e)); }); comms.on('shutdown', () => { console.log(`Shutdown detected.`); - fn.power.stop.init(comms, psState).catch(e => console.error(e)); + fn.power.stop.init(comms, process.psState).catch(e => console.error(e)); }); \ No newline at end of file