Last active
September 17, 2023 04:06
-
-
Save jdrews/727eaa6a57117bb0f2a22e64a80b7537 to your computer and use it in GitHub Desktop.
Collect temp, humidity and dirt moisture (wetness) from monk makes plant monitor (pmon) and a Vegetronix VH400 and ship to influxdb
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[Unit] | |
Description=Plant Monitor Service | |
After=multi-user.target | |
[Service] | |
WorkingDirectory=/home/alarm/git/pmon/raspberry_pi | |
User=alarm | |
ExecStart=/usr/bin/python /home/alarm/git/pmon/raspberry_pi/plantmondb.py -i <INFLUXDB_IP> | |
Type=simple | |
[Install] | |
WantedBy=multi-user.target |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import time | |
from plant_monitor import PlantMonitor | |
from influxdb_client import InfluxDBClient, Point | |
from datetime import datetime | |
import argparse | |
import logging | |
from logging.handlers import RotatingFileHandler | |
import serial | |
# username = 'username' | |
# password = 'password' | |
def is_float(element: any) -> bool: | |
# If you expect None to be passed: | |
if element is None: | |
return False | |
try: | |
float(element) | |
return True | |
except ValueError: | |
return False | |
parser = argparse.ArgumentParser() | |
parser.add_argument("-i", "--influxhost", help="IP of the influxdb host") | |
parser.add_argument("-p", "--influxport", | |
help="port of the influxdb host", default="8086") | |
parser.add_argument("-s", "--polltimer", | |
help="amount of time in seconds to poll for metrics from sensor", default=60) | |
parser.add_argument("-d", "--influxdatabase", | |
help="name of influx database", default="plantmon") | |
parser.add_argument("-t", "--influxtag", | |
help="a tag to put for metrics in influx db", default="aspidistra") | |
parser.add_argument( | |
"--vh400serial", help="serial port for the vegetronix vh400", default="/dev/ttyUSB0") | |
args = parser.parse_args() | |
retention_policy = 'autogen' | |
bucket = f'{args.influxdatabase}/{retention_policy}' | |
pm = PlantMonitor() | |
logger = logging.getLogger('my_logger') | |
logger.setLevel(logging.DEBUG) | |
handler = RotatingFileHandler('plantmon.log', maxBytes=5000, backupCount=5) | |
logger.addHandler(handler) | |
ser = serial.Serial(port=args.vh400serial, timeout=3) | |
try: | |
while True: | |
wetness_field = pm.get_wetness() | |
temp_c_field = pm.get_temp() | |
humidity_field = pm.get_humidity() | |
vh400_vwc_field = ser.readline().decode().strip() | |
# When the Vegetronix VH400 first starts up, it does not output a number ( just /x00). Skip writing this point if so. | |
if (is_float(vh400_vwc_field)): | |
logger.info(str(datetime.now()) + " -- Wetness (%): " + str(wetness_field) + ", Temp (C): " + str( | |
temp_c_field) + ", Humidity (%): " + str(humidity_field) + ", VWC (%): " + str(vh400_vwc_field)) | |
point = Point("plantmon").tag("tag", args.influxtag).field("wetness_percent", wetness_field).field( | |
"temp_c", temp_c_field).field("humidity_percent", humidity_field).field("vh400_vwc", float(vh400_vwc_field)) | |
else: | |
logger.info(str(datetime.now()) + " -- Wetness (%): " + str(wetness_field) + ", Temp (C): " + str( | |
temp_c_field) + ", Humidity (%): " + str(humidity_field)) | |
point = Point("plantmon").tag("tag", args.influxtag).field("wetness_percent", wetness_field).field( | |
"temp_c", temp_c_field).field("humidity_percent", humidity_field) | |
with InfluxDBClient(url='http://' + args.influxhost + ':' + args.influxport, org='-') as client: | |
with client.write_api() as write_api: | |
logger.info(point.to_line_protocol()) | |
write_api.write(bucket=bucket, record=point) | |
time.sleep(int(args.polltimer)) | |
except Exception as e: | |
logger.error('Error at %s', 'plant monitor loop', exc_info=e) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Set analogPin to the input pin | |
int inputAnalogPin = 0; | |
void setup() { | |
// put your setup code here, to run once: | |
pinMode(inputAnalogPin, INPUT); | |
Serial.begin(9600); | |
} | |
void loop() { | |
// put your main code here, to run repeatedly: | |
Serial.println(readVH400(inputAnalogPin)); | |
delay(1000); | |
} | |
// MIT License (MIT) | |
// | |
// Copyright (c) 2015. Michael Ewald, GeomaticsResearch LLC. | |
// | |
// Permission is hereby granted, free of charge, to any person obtaining a copy | |
// of this software and associated documentation files (the "Software"), to deal | |
// in the Software without restriction, including without limitation the rights | |
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
// copies of the Software, and to permit persons to whom the Software is | |
// furnished to do so, subject to the following conditions: | |
// | |
// The above copyright notice and this permission notice shall be included in | |
// all copies or substantial portions of the Software. | |
// | |
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |
// THE SOFTWARE. | |
// | |
// Author: Michael Ewald (mewald@geomaticsresearch.com) | |
// Web: https://GeomaticsResearch.com | |
// Last-updated: 2015-07-04 | |
// From: https://gist.github.com/lx-88/413b48ced6b79300ea76 | |
float readVH400(int analogPin) { | |
// This function returns Volumetric Water Content by converting the analogPin value to voltage | |
// and then converting voltage to VWC using the piecewise regressions provided by the manufacturer | |
// at http://www.vegetronix.com/Products/VH400/VH400-Piecewise-Curve.phtml | |
// UPDATE 2023-09-16: above page moved to http://www.vegetronix.com/Products/VH400/VH400-Piecewise-Curve | |
// NOTE: You need to set analogPin to input in your setup block | |
// ex. pinMode(<analogPin>, INPUT); | |
// replace <analogPin> with the number of the pin you're going to read from. | |
// Read value and convert to voltage | |
int sensor1DN = analogRead(analogPin); | |
float sensorVoltage = sensor1DN*(5.0 / 1023.0); | |
float VWC; | |
// Calculate VWC | |
if(sensorVoltage <= 1.1) { | |
VWC = 10*sensorVoltage-1; | |
} else if(sensorVoltage > 1.1 && sensorVoltage <= 1.3) { | |
VWC = 25*sensorVoltage-17.5; | |
} else if(sensorVoltage > 1.3 && sensorVoltage <= 1.82) { | |
VWC = 48.08*sensorVoltage-47.5; | |
} else if(sensorVoltage > 1.82) { | |
VWC = 26.32*sensorVoltage-7.89; | |
} | |
return(VWC); | |
} | |
struct VH400 { | |
double analogValue; | |
double analogValue_sd; | |
double voltage; | |
double voltage_sd; | |
double VWC; | |
double VWC_sd; | |
}; | |
struct VH400 readVH400_wStats(int analogPin, int nMeasurements = 100, int delayBetweenMeasurements = 50) { | |
// This variant calculates the mean and standard deviation of 100 measurements over 5 seconds. | |
// It reports mean and standard deviation for the analog value, voltage, and WVC. | |
// This function returns Volumetric Water Content by converting the analogPin value to voltage | |
// and then converting voltage to VWC using the piecewise regressions provided by the manufacturer | |
// at http://www.vegetronix.com/Products/VH400/VH400-Piecewise-Curve.phtml | |
// NOTE: You need to set analogPin to input in your setup block | |
// ex. pinMode(<analogPin>, INPUT); | |
// replace <analogPin> with the number of the pin you're going to read from. | |
struct VH400 result; | |
// Sums for calculating statistics | |
int sensorDNsum = 0; | |
double sensorVoltageSum = 0.0; | |
double sensorVWCSum = 0.0; | |
double sqDevSum_DN = 0.0; | |
double sqDevSum_volts = 0.0; | |
double sqDevSum_VWC = 0.0; | |
// Arrays to hold multiple measurements | |
int sensorDNs[nMeasurements]; | |
double sensorVoltages[nMeasurements]; | |
double sensorVWCs[nMeasurements]; | |
// Make measurements and add to arrays | |
for (int i = 0; i < nMeasurements; i++) { | |
// Read value and convert to voltage | |
int sensorDN = analogRead(analogPin); | |
double sensorVoltage = sensorDN*(5.0 / 1023.0); | |
// Calculate VWC | |
float VWC; | |
if(sensorVoltage <= 1.1) { | |
VWC = 10*sensorVoltage-1; | |
} else if(sensorVoltage > 1.1 && sensorVoltage <= 1.3) { | |
VWC = 25*sensorVoltage-17.5; | |
} else if(sensorVoltage > 1.3 && sensorVoltage <= 1.82) { | |
VWC = 48.08*sensorVoltage-47.5; | |
} else if(sensorVoltage > 1.82) { | |
VWC = 26.32*sensorVoltage-7.89; | |
} | |
// Add to statistics sums | |
sensorDNsum += sensorDN; | |
sensorVoltageSum += sensorVoltage; | |
sensorVWCSum += VWC; | |
// Add to arrays | |
sensorDNs[i] = sensorDN; | |
sensorVoltages[i] = sensorVoltage; | |
sensorVWCs[i] = VWC; | |
// Wait for next measurement | |
delay(delayBetweenMeasurements); | |
} | |
// Calculate means | |
double DN_mean = double(sensorDNsum)/double(nMeasurements); | |
double volts_mean = sensorVoltageSum/double(nMeasurements); | |
double VWC_mean = sensorVWCSum/double(nMeasurements); | |
// Loop back through to calculate SD | |
for (int i = 0; i < nMeasurements; i++) { | |
sqDevSum_DN += pow((DN_mean - double(sensorDNs[i])), 2); | |
sqDevSum_volts += pow((volts_mean - double(sensorVoltages[i])), 2); | |
sqDevSum_VWC += pow((VWC_mean - double(sensorVWCs[i])), 2); | |
} | |
double DN_stDev = sqrt(sqDevSum_DN/double(nMeasurements)); | |
double volts_stDev = sqrt(sqDevSum_volts/double(nMeasurements)); | |
double VWC_stDev = sqrt(sqDevSum_VWC/double(nMeasurements)); | |
// Setup the output struct | |
result.analogValue = DN_mean; | |
result.analogValue_sd = DN_stDev; | |
result.voltage = volts_mean; | |
result.voltage_sd = volts_stDev; | |
result.VWC = VWC_mean; | |
result.VWC_sd = VWC_stDev; | |
// Return the result | |
return(result); | |
} |
Now I added another sensor, a Vegetronix VH400 from https://vegetronix.com/soil-moisture-sensor
The Vegetronix VH400 connected to an Arduino Diecimila over USB and outputs the Volumetric Water Content (VWC) over serial on /dev/ttyUSB0 on the Raspberry Pi.
If you don't have that sensor, just comment out the reading and change the influxdb point writing.
Added the source code running on the Arduino Diecimila that reads from the Vegetronix VH400 over analog pin 0.
This code was sourced from: https://gist.github.com/lx-88/413b48ced6b79300ea76
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Tested on archlinuxarm on a Raspberry Pi 3 using Python 3.11.3
The sensor is a Monk Makes Plant Monitor from https://monkmakes.com/pmon
Source for sensor is here: https://github.com/monkmakes/pmon