I have a raspberry pi 3b+ running a python 3.7 script that is controlling an arduino. The arduino is reading values in from a few different sensors, and sending the data back to the python code, where it is processed and stored. These sensors are measuring temperature, humidity, ambient light level, soil moisture, and water levels of an agroponic system, with the purpose of maintaining ideal values to facilitate plant growth. This is a timer based system, and will turn the grow light on after 16 hours to run for 8 hours and repeat, and the pump will turn on after 18 hours and run for 5 seconds (it's a strong pump) and repeat.
Obviously, plants don't grow in the span of a few hours, and this script would ideally be running for a few months at a time. As the title suggests, that's not happening. To make make matters more confusing, I have a similar system that works by responding to the values read from the sensors, and turns on the grow light or pump as needed when indicated by the sensors. The only difference in the code is instead of a timer, it responds to the sensor input. It works, and works well. In my last test, it ran for a month and a half, and only stopped because I was using a submersible water pump that ended up corroding (I have a peristaltic pump now).
Ideally, both of these systems are started by opening up a terminal on the pi, and running
Things I've tried
- Restarted the raspberry pi running the timer system
- Updated os via prompt in the top right of the desktop that says "updates are available" or something like that
- Set Try/Except throughout the code, including a global try/catch arounds the arduinoDriver file itself, where in the except statements, used the logging module to write output to a file on the machine. We ran the script, and the next day, upon seeing that it had stopped, checked the log file, only to see that nothing had been logged
- Took the sd card out of the raspberry pi, and put it into a different raspberry pi 3b+
- Re-parented the script by running the command
setsid python3 arduinoDriver.py < /dev/zero &> /dev/null &as mentioned in this post
- Running the script through tmux
Raspberry pi model 3b+ Raspios Buster 05/07/2021 is the name the iso file has from the download. I can't find it on their website, and as mentioned above, I updated it as a last ditch attempt to figure out what was wrong before coming here. When I get back to the school tomorrow, I can get the specific release if that would be helpful Arduino Mega 2560
Things I know
- When the script runs, the print statements showing the input from the sensors work, so I know the mega and the sensors are working
- When I get to the system after the process crashes, the terminal window that had it open, as well as any other windows that were opened, are closed. I opened up the browser on the pi itself to start troubleshooting, and I was connected to the internet. This is important because every time the raspberry pi restarts, we have to connect it to the school's guest wifi. This means the system didn't turn off and back on
- From the database table storing our sensor data, we can tell how long (approximately) the script was running. It's lasted anywhere from 15 minutes to 4.5 hours
- The empty log file screams that this is a hardware issue. The fact that this is an issue on 2 separate raspberry pis suggests that this is a software issue
- The only issues I'm aware of that sort of fit the symptoms are memory related. Maybe something incremented to a value too large for the data type to hold, causing a memory issue. Maybe the 1gb of ram on the pi is not enough. From the attached code though, I don't think anything is actually ever incrementing, but rather being overwritten. It still doesn't make sense though when you consider the fact that our sensor-responsive system works perfectly.
The 2 systems were meant to run against each other as a comparison for my (and my 2 classmates') senior project. Given the circumstances, our professors (who are also stumped by this problem) have allowed us to axe the timer system from the research, since the plants need time to grow before finals in May. We're now simply comparing the sensor-reactive system to the efforts of humans watering and taking care of a plant by hand. One of the professors suggested that I reach out to this board and ask for help. I'll be checking in on this relatively frequently, so please let me know if I can supply any more information! I realize the code isn't the most beautiful thing, but like I said, the sensor-reactive code is very similar, so we didn't want to change how it functioned so that way we could keep the research comparable
# Third Party from datetime import datetime, timedelta import serial import time import logging # Proprietary from controllers.sendEmail import notifyLowWater, notifyWaterFilled from controllers.sendData import checkIfDataNeedsSent from controllers.signalArduino import determineSignalToSend from controllers.waterPump import checkIfPumpNeeded from controllers.lightValue import checkIfLightNeeded from controllers.dataArray import DataArray from controllers.database import Database LOG_FORMAT = "%(levelname)s %(asctime)s - %(message)s" logging.basicConfig(filename="error_log.log", filemode="w", format=LOG_FORMAT, level=logging.ERROR) #Sets up the mega 2560 board = serial.Serial( port = '/dev/ttyACM0', baudrate = 115200, timeout = None, ) #Default starting values, needed just to have these guys be global temp = 70 hum = 20 moisture = 0 timeToKeepLightOn = timedelta(hours=8) timeToKeepLightOff = timedelta(hours=16) # timeToKeepLightOn = timedelta(minutes=5) These 2 lines were for testing the script in the classroom # timeToKeepLightOff = timedelta(seconds=20)They Worked lightStartOn = datetime.now() - timeToKeepLightOn #When the light turned on timeLightOn = 0 #This is how long the light has actually been on isLightOn = False lightBool = True #This is for the while board.inWaiting() to make the function only execute once lightOn = False # lightArray = DataArray(101, 20) #These are for the same purposes as the light variables above, but for our pump timeToKeepPumpOn = timedelta(seconds=5) timeToKeepPumpOff = timedelta(hours=18) pumpStartTime = datetime.now() - timeToKeepPumpOn timePumpOn = 0 isPumpOn = False pumpBool = True floatFlag = 'LOW' #This is so we know if we have water in our resevoir from our float sensor emailSent = False emailTimestamp = 0 moistureHigh = 450 #Not important. We copied our sensor-reactive code into a new file and then added the time delta stuff above to make this for a timer moistureArray = DataArray(moistureHigh, 5) timeDataCollected = 0 #Just a time stamp for when we store it. Honestly redundant lastMinuteSent = 1 #We store our data on the 15, 30, 45, and 0 mark of every hour. This ensures we don't collect duplicates envId = 1 #We built our schema to be expandable. This is unimportant, just need it to be able to store our data signalSentBool = False #Checks if we have sent a signal to the mega or not, so we only do it once db = Database() def checkIfEmailNeeded(floatFlag, emailTimestamp): #Commented out, not important global emailSent currentTime = time.time() if(currentTime - emailTimestamp > 86400):#86400 seconds in 24 hours emailSent = False if(floatFlag == 'LOW' and not emailSent): notifyLowWater(currentTime) emailSent = True emailTimestamp = time.time() if(floatFlag == 'HIGH' and emailSent): notifyWaterFilled(currentTime) emailSent = False return emailTimestamp while True: try: while(board.inWaiting() == 0): #emailTimestamp = checkIfEmailNeeded(floatFlag, emailTimestamp) if pumpBool: pumpStartTime, isPumpOn, endTime = checkIfPumpNeeded(floatFlag, pumpStartTime, isPumpOn, timeToKeepPumpOn, timeToKeepPumpOff) #Returns the time the pump started, if it's currently on or not, and if the pump stopped if endTime: timePumpOn += int((datetime.now() - pumpStartTime).total_seconds()) #If it stopped, compute how long it was on pumpBool = False returned = checkIfDataNeedsSent(lastMinuteSent, temp, hum, moistureArray.getAvg(), lightStartOn, timePumpOn, timeDataCollected, envId, db) if returned != lastMinuteSent: #Returned is a 0, 15, 30, or 45, just to make sure we don't send data a bunch of times lastMinuteSent = returned timeLightOn = 0 timePumpOn = 0 if lightBool: lightStartOn, isLightOn, endTime = checkIfLightNeeded(lightStartOn, isLightOn, timeToKeepLightOn, timeToKeepLightOff) #Same logic as the pump if endTime: timeLightOn += int((datetime.now() - lightStartOn).total_seconds()/60) lightBool = False if not signalSentBool: determineSignalToSend(isPumpOn, isLightOn, board) signalSentBool = True timeDataCollected = datetime.now().strftime('%Y-%m-%d-%H:%M:%S') output = board.readline().decode('utf-8', 'ignore').strip().split(',') if len(output) == 6: #reads data from sensors # temp = output # hum = output moisture = int (output) moistureArray.add(moisture) # lightArray.add(output) if 'LOW' in output: floatFlag = 'LOW' else: floatFlag = 'HIGH' pumpBool = True lightBool = True signalSentBool = False logging.info(output) else: logging.info("Incomplete board output: ", output) continue except Exception as error: logging.error('**Error reading board: ', error)