Skip to content

Instantly share code, notes, and snippets.

@rdeprey
Last active November 9, 2021 16:36
Show Gist options
  • Save rdeprey/6395b808c9b72213d8a3f298a63efaca to your computer and use it in GitHub Desktop.
Save rdeprey/6395b808c9b72213d8a3f298a63efaca to your computer and use it in GitHub Desktop.
Automatic Plant Waterer
{
"name": "plant-waterer",
"version": "0.0.1",
"private": true,
"scripts": {
"start": "node ./bin/www"
},
"dependencies": {
"node-schedule": "^1.3.2",
"onoff": "^6.0.0",
"mcp-spi-adc": "^3.1.0"
}
}
// Make sure that you're using Node v16.x and up
// Setup the sensor input
const mcpadc = require('mcp-spi-adc');
const Gpio = require('onoff').Gpio;
const schedule = require('node-schedule');
const completelyWet = 395;
const completelyDry = 780;
const pumpRelay = new Gpio(17, 'high'); // IMPORTANT: Use 'high' if relay uses low level trigger
function getSensorReadings(sensor) {
return new Promise((resolve, reject) => {
sensor.read((readError, reading) => {
if (readError) {
return reject(new Error(`There was an error getting the sensor reading:
${readError}`));
}
return resolve(reading);
});
});
};
function getMoistureLevel() {
const readingPromises = [];
let readings = {};
readings.rawValues = [];
readings.values = [];
return new Promise((resolve, reject) => {
const sensor = mcpadc.open(5, {speedHz: 20000}, (error) => {
if (error) {
return reject(new Error(`There was an error accessing the sensor: ${error}`));
}
let iterator = 50; // Just need a large number of readings to try for better accuracy
while (iterator >= 0) {
readingPromises.push(getSensorReadings(sensor)
.then(reading => {
readings.rawValues.push(reading.rawValue);
readings.values.push(reading.value);
}).catch(error => {
return reject(error);
})
);
iterator--;
}
return Promise.all(readingPromises).then(() => {
const averageRawValue = readings.rawValues.reduce((a, b) => a + b, 0) / 50;
const averageValue = readings.values.reduce((a, b) => a + b, 0) / 50;
// Set the value to a percentage based on the max reading
return resolve({
rawValue: averageRawValue,
value: averageValue,
soilDrynessPercentage: averageRawValue > 0 ? ((averageRawValue / completelyWet) * 100).toFixed(0) : 0,
});
});
});
});
};
function shouldWater(moistureLevel) {
// Adjust this value based on your sensor and the needs of your plant
// Value represents a percentage
if (moistureLevel <= 45) {
return true;
}
return false;
};
function waterThePlant() {
return new Promise((resolve, reject) => {
pumpRelay.read(async (error, status) => {
if (error) {
return reject(new Error(`There was an error getting the pump relay status: ${error}`));
}
const moistureLevel = await getMoistureLevel();
const needsWater = shouldWater(moistureLevel.soilDrynessPercentage);
if (status !== 0 && needsWater) {
pumpRelay.writeSync(0); // closes the circuit and starts the pump
}
return resolve({
status: `The plant is being watered.`,
});
});
});
};
function stopWateringPlant() {
return new Promise((resolve, reject) => {
pumpRelay.read((error, status) => {
if (error) {
return reject(new Error(`There was an error getting the pump relay status: ${error}`));
}
if (status !== 1) {
pumpRelay.writeSync(1); // opens the circuit and stops the pump
}
return resolve({
status: `The plant is not being watered.`,
});
});
});
};
const shouldWaterPlant = () => {
// Run every day at 7 a.m.
return schedule.scheduleJob('0 7 * * *', async () => {
if (shouldWater) {
// Water the plant for three seconds
setTimeout(() => {
waterThePlant();
setTimeout(() => {
stopWateringPlant();
}, 3000);
}, 3000);
}
});
};
shouldWaterPlant();
@rdeprey
Copy link
Author

rdeprey commented Oct 5, 2020

Hey @Tossawon, I'm sorry for the slow reply. I just saw your message. My sensor doesn't light up, but it still reads the moisture level of the soil.

I'm thinking the error means one of two things:

  1. SPI needs to be enabled on your Raspberry Pi. You can use these instructions to do that: https://learn.adafruit.com/adafruits-raspberry-pi-lesson-4-gpio-setup/configuring-spi.

  2. It might be connected to a different pin. If you use a pin other than the CH5 pin for the MCP3008, then you'll need to update line 30 so that it has the correct value, like this:

const sensor = mcpadc.open([REPLACE WITH PIN NUMBER], {speedHz: 20000}, (error) => {

@Tossawon
Copy link

Tossawon commented Oct 23, 2020 via email

@Tossawon
Copy link

Tossawon commented Oct 24, 2020 via email

@LowCulture
Copy link

Tried my hand at this and keep getting the following error:

node:internal/fs/utils:879
throw new ERR_INVALID_ARG_TYPE(
^

TypeError [ERR_INVALID_ARG_TYPE]: The "data" argument must be of type string or an instance of Buffer, TypedArray, or DataView. Received type number (17)
at Object.writeFileSync (node:fs:2146:5)
at exportGpio (/home/pi/irrigation/node_modules/onoff/onoff.js:18:8)
at new Gpio (/home/pi/irrigation/node_modules/onoff/onoff.js:172:36)
at Object. (/home/pi/irrigation/water-plant-script.js:8:19)
at Module._compile (node:internal/modules/cjs/loader:1101:14)
at Object.Module._extensions..js (node:internal/modules/cjs/loader:1153:10)
at Module.load (node:internal/modules/cjs/loader:981:32)
at Function.Module._load (node:internal/modules/cjs/loader:822:12)
at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:79:12)
at node:internal/main/run_main_module:17:47 {
code: 'ERR_INVALID_ARG_TYPE'


Unsure what I'm doing wrong. I am using a digital moisture sensor connected to the Pi GPIO pins themselves(21 and 23), rather than a breadboard. Otherwise, I followed everything in your guide. Can't figure out what variable I'm missing or where to start looking for the thrown error. Would be grateful for any help!

@rdeprey
Copy link
Author

rdeprey commented Sep 3, 2021

Hi @LowCulture, I can try to help you out. What does your code look like from lines 1-8? It looks like the error is triggered on line 8 specifically based on the error stack trace.

@LowCulture
Copy link

That's the thing. I copied your code verbatim and went through everything you did on your medium post. Really scratching my head with this one

@rdeprey
Copy link
Author

rdeprey commented Sep 12, 2021

Hi @LowCulture, I think that I found the issue. I think it's related to newer versions of Node and the onoff package. I didn't run into this with Node 13.x, but I could replicate it with Node 16.x and onoff version ^5.0.0.

I came across this exact error reported to the onoff package here, fivdi/onoff#170, and they mentioned that they fixed it in version 6. I tested using onoff version ^6.0.0 and Node 16.9.1 and that worked for me. Would you mind updating onoff to at least version 6.0.0 and seeing if it works for you?

@LowCulture
Copy link

That did it, thank you for your help with this!

@rdeprey
Copy link
Author

rdeprey commented Oct 3, 2021

I'm glad it's working now. :) Thanks for bringing this up - I'm going to update the post for the latest Node so that others don't run into this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment