1

I am very new to using JSON files to store information and I am getting an error that I am having trouble debugging. For some context, This is a raspberry pi greenhouse project and I am trying to use the json file to store data so that when I turn my system on and off it stores the data. Here is the error I keep getting:

>>> Exception in thread Thread-1:
Traceback (most recent call last):
  File "/usr/lib/python3.9/threading.py", line 954, in _bootstrap_inner
    self.run()
  File "/usr/lib/python3.9/threading.py", line 892, in run
    self._target(*self._args, **self._kwargs)
  File "/home/ben6brewer/Desktop/Greenhouse/GreenHouse.py", line 214, in lcd_updater
    self.lcd_display.screen2(self.get_minutes_elapsed())
  File "/home/ben6brewer/Desktop/Greenhouse/GreenHouse.py", line 227, in get_minutes_elapsed
    data = json.load(json_file)
  File "/usr/lib/python3.9/json/__init__.py", line 293, in load
    return loads(fp.read(),
  File "/usr/lib/python3.9/json/__init__.py", line 346, in loads
    return _default_decoder.decode(s)
  File "/usr/lib/python3.9/json/decoder.py", line 337, in decode
    obj, end = self.raw_decode(s, idx=_w(s, 0).end())
  File "/usr/lib/python3.9/json/decoder.py", line 355, in raw_decode
    raise JSONDecodeError("Expecting value", s, err.value) from None
json.decoder.JSONDecodeError: Expecting value: line 35 column 5 (char 532)

Here is my main greenhouse class below:

import time
import threading
from datetime import datetime
import RPi.GPIO as GPIO
import Adafruit_DHT
from LCD import LCDDisplay
from CreateGraph import GraphGenerator
from SendEmail import EmailSender
from time import sleep
from LCD import LCDScreens
import sys
import select
import json
import os

class GreenhouseController:
    def __init__(self):
        self.relay_pins = [24, 25]
        self.fan_pin = self.relay_pins[0]
        self.mist_pin = self.relay_pins[1]
        self.lowest_temp = 65 # degrees F
        self.highest_temp = 80 # degrees F
        self.lowest_humidity = 80 # %
        self.highest_humidity = 90 # %
        self.graph_generator = GraphGenerator() # Instantiate the GraphGenerator class
        self.email_sender = EmailSender()
        self.lcd_display = LCDDisplay(controller=self)
        self.minute_temperature_data = []
        self.minute_humidity_data = []
        self.hour_temperature_data = []
        self.hour_humidity_data = []

        self.current_second = int(time.time() % 60)
        self.last_second = None

        self.current_minute = int(time.time() // 60) % 60
        self.last_minute = None
        self.program_start_minute = int(time.time() // 60) % 60

        self.current_hour = int(time.time() // 3600) % 24
        self.last_hour = None
        self.program_start_hour = int(time.time() // 3600) % 24

        self.relative_count = 0
        self.system_activated = False

        self.humidity = 0
        self.temperature = 0
        # Get the absolute path to the current script's directory
        # path to json = '/home/ben6brewer/Desktop/Greenhouse/data.json'


        # Set up GPIO pins
        GPIO.setmode(GPIO.BCM)
        GPIO.setwarnings(False)
        GPIO.setup(self.fan_pin, GPIO.OUT)
        GPIO.setup(self.mist_pin, GPIO.OUT)

    def main(self):
        try:
            self.run_lcd()
            while True:
                self.check_user_input()
                if self.first_iteration_check():
                    self.send_boot_message()

                self.update_time()
                self.update_variables()
                # TRUE every 1 minute
                if self.minute_check():
                    self.minute_operations()

                    # True every 10 minutes:
                    if self.ten_minute_check():
                        self.ten_minute_operations()

                        # TRUE every 1 HOUR
                        if self.hour_check():
                            self.hour_operations()

                            # TRUE every 1 day
                            if self.day_check():
                                self.day_operations()

                self.system_activated = False
                if self.humidity_check():
                    self.activate_humidity()

                if self.temperature_check():
                    self.activate_fans()

                if self.system_activated == False:
                    sleep(2)

        except KeyboardInterrupt:
            pass

        GPIO.cleanup()

    @staticmethod
    def get_current_temperature():
        return round((Adafruit_DHT.read_retry(22, 4)[1]) * 9/5 + 32, 2)

    @staticmethod
    def get_current_humidity():
        return round(Adafruit_DHT.read_retry(22, 4)[0], 2)

    # Turns pump on for parameterized seconds
    def pump_on(self, seconds):
        GPIO.output(self.mist_pin, 0)
        sleep(seconds)
        GPIO.output(self.mist_pin, 1)
        self.add_to_total_pump_time(seconds)
        if self.get_total_pump_time() > 60:
            self.email_sender.send_pump_warning(self.get_total_pump_time())
            self.reset_total_pump_time()


    # Turns pump on for parameterized seconds
    def fans_on(self, seconds):
        GPIO.output(self.fan_pin, 0)
        sleep(seconds)
        GPIO.output(self.fan_pin, 1)

    def get_days_elapsed(self):
        return round(self.get_minutes_elapsed() / 1440, 2)

    def update_time(self):
        self.current_second = int(time.time() % 60)
        self.current_minute = int(time.time() // 60) % 60
        self.current_hour = int(time.time() // 3600) % 24

    def update_variables(self):
        self.humidity = self.get_current_humidity()
        self.temperature = self.get_current_temperature()

    def minute_check(self):
        return (self.last_minute is None or self.last_minute != self.current_minute)

    def minute_operations(self):
        self.last_minute = self.current_minute
        self.append_temperature_to_minute_array(self.temperature)
        self.append_humidity_to_minute_array(self.humidity)
        self.update_minutes_in_json(1)

    def ten_minute_check(self):
        return ((self.program_start_minute % 10) == (self.current_minute % 10))

    def ten_minute_operations(self):
        self.fans_on(45)
        sleep(45)

    def first_iteration_check(self):
        return (self.relative_count == 0)

    def send_boot_message(self):
        self.email_sender.send_boot_email(self.get_days_elapsed())

    def hour_check(self):
        return ((self.program_start_minute) == (self.current_minute))

    def hour_operations(self):
        average_temperature = round((sum(self.get_temperature_minute_array())) / len(self.get_temperature_minute_array()), 2)
        self.append_temperature_to_hour_array(average_temperature)

        average_humidity = round((sum(self.get_humidity_minute_array())) / len(self.get_humidity_minute_array()), 2)
        self.append_humidity_to_hour_array(average_humidity)

        self.reset_minute_arrays()
        self.relative_count += 1

    def day_check(self):
        minutes_elapsed = self.get_minutes_elapsed()
        return (minutes_elapsed >= 1440 and minutes_elapsed % 1440 == 0 and self.relative_count > 1)

    def day_operations(self):
        self.graph_generator.create_graph(self.get_days_elapsed(), [self.get_temperature_hour_array(), self.get_humidity_hour_array()])
        self.email_sender.send_email(self.get_days_elapsed())
        self.reset_hour_arrays()

    def humidity_check(self):
        return (self.humidity <= self.lowest_humidity)

    def activate_humidity(self):
        if self.get_current_humidity() < (self.lowest_humidity - 20):
            self.pump_on(30)
        elif self.get_current_humidity() < (self.lowest_humidity - 10):
            self.pump_on(20)
        else:
            self.pump_on(5)
        sleep(10)
        self.system_activated = True

    def temperature_check(self):
        return (self.temperature >= self.highest_temp or self.humidity >= self.highest_humidity)

    def activate_fans(self):
        if self.get_current_humidity() > self.highest_humidity:
            self.fans_on(20)
        elif self.get_current_temperature() > (self.highest_temp):
            self.fans_on(20)
        else:
            self.fans_on(10)
        self.system_activated = True

    def lcd_updater(self):
        current_screen = LCDScreens.SCREEN1

        while True:
            if current_screen == LCDScreens.SCREEN1:
                self.lcd_display.screen1(self.get_current_temperature(), self.get_current_humidity())

            elif current_screen == LCDScreens.SCREEN2:
                self.lcd_display.screen2(self.get_minutes_elapsed())

            # Switch to the next screen
            current_screen = LCDScreens.SCREEN1 if current_screen == LCDScreens.SCREEN2 else current_screen + 1
            sleep(5) # Adjust the delay time as needed (in seconds)

    def run_lcd(self):
        self.lcd_thread = threading.Thread(target=self.lcd_updater)
        self.lcd_thread.daemon = True
        self.lcd_thread.start()

    def get_minutes_elapsed(self):
        with open('/home/ben6brewer/Desktop/Greenhouse/data.json', 'r') as json_file:
            data = json.load(json_file)
            minutes_elapsed = data.get('minutes', 0)
            return minutes_elapsed




    def update_minutes_in_json(self, minutes_to_add):
        with open('/home/ben6brewer/Desktop/Greenhouse/data.json', 'r+') as json_file:
            data = json.load(json_file)
            current_minutes = data.get('minutes', 0)
            new_minutes = current_minutes + minutes_to_add
            data['minutes'] = new_minutes
            json_file.seek(0)
            json.dump(data, json_file, indent=4)
            json_file.truncate()

    def reset_json_data(self):
        data = {
        "minutes": 0,
        "TotalPumpTime": 0,
        "TemperatureMinuteArray": [],
        "HumidityMinuteArray": [],
        "TemperatureHourArray": [],
        "HumidityHourArray": []
        }

        with open('/home/ben6brewer/Desktop/Greenhouse/data.json', 'w') as json_file:
            json.dump(data, json_file, indent=4)

    def append_temperature_to_minute_array(self, temperature):
        with open('/home/ben6brewer/Desktop/Greenhouse/data.json', 'r+') as json_file:
            data = json.load(json_file)
            temperature_array = data.get('TemperatureMinuteArray', [])
            temperature_array.append(temperature)
            data['TemperatureMinuteArray'] = temperature_array
            json_file.seek(0)
            json.dump(data, json_file, indent=4)
            json_file.truncate()

    def append_humidity_to_minute_array(self, humidity):
        with open('/home/ben6brewer/Desktop/Greenhouse/data.json','r+') as json_file:
            data = json.load(json_file)
            humidity_array = data.get('HumidityMinuteArray', [])
            humidity_array.append(humidity)
            data['HumidityMinuteArray'] = humidity_array
            json_file.seek(0)
            json.dump(data, json_file, indent=4)
            json_file.truncate()

    def append_temperature_to_hour_array(self, temperature):
        with open('/home/ben6brewer/Desktop/Greenhouse/data.json', 'r+') as json_file:
            data = json.load(json_file)
            temperature_array = data.get('TemperatureHourArray', [])
            temperature_array.append(temperature)
            data['TemperatureHourArray'] = temperature_array
            json_file.seek(0)
            json.dump(data, json_file, indent=4)
            json_file.truncate()

    def append_humidity_to_hour_array(self, humidity):
        with open('/home/ben6brewer/Desktop/Greenhouse/data.json', 'r+') as json_file:
            data = json.load(json_file)
            humidity_array = data.get('HumidityHourArray', [])
            humidity_array.append(humidity)
            data['HumidityHourArray'] = humidity_array
            json_file.seek(0)
            json.dump(data, json_file, indent=4)
            json_file.truncate()

    def reset_minute_arrays(self):
        with open('/home/ben6brewer/Desktop/Greenhouse/data.json', 'r+') as json_file:
            data = json.load(json_file)
            data['TemperatureMinuteArray'] = []
            data['HumidityMinuteArray'] = []
            json_file.seek(0)
            json.dump(data, json_file, indent=4)
            json_file.truncate()

    def reset_hour_arrays(self):
        with open('/home/ben6brewer/Desktop/Greenhouse/data.json', 'r+') as json_file:
            data = json.load(json_file)
            data['TemperatureHourArray'] = []
            data['HumidityHourArray'] = []
            json_file.seek(0)
            json.dump(data, json_file, indent=4)
            json_file.truncate()

    def get_temperature_minute_array(self):
        with open('/home/ben6brewer/Desktop/Greenhouse/data.json', 'r') as json_file:
            data = json.load(json_file)
            temperature_array = data.get('TemperatureMinuteArray', [])
            return temperature_array

    def get_humidity_minute_array(self):
        with open('/home/ben6brewer/Desktop/Greenhouse/data.json', 'r') as json_file:
            data = json.load(json_file)
            humidity_array = data.get('HumidityMinuteArray', [])
            return humidity_array

    def get_temperature_hour_array(self):
        with open('/home/ben6brewer/Desktop/Greenhouse/data.json', 'r') as json_file:
            data = json.load(json_file)
            temperature_array = data.get('TemperatureHourArray', [])
            return temperature_array

    def get_humidity_hour_array(self):
        with open('/home/ben6brewer/Desktop/Greenhouse/data.json', 'r') as json_file:
            data = json.load(json_file)
            humidity_array = data.get('HumidityHourArray', [])
            return humidity_array

    def add_to_total_pump_time(self, pump_time):
        with open('/home/ben6brewer/Desktop/Greenhouse/data.json', 'r+') as json_file:
            data = json.load(json_file)
            total_pump_time = data.get('TotalPumpTime', 0)
            total_pump_time += pump_time
            data['TotalPumpTime'] = total_pump_time
            json_file.seek(0)
            json.dump(data, json_file, indent=4)
            json_file.truncate()
    def get_total_pump_time(self):
        with open('/home/ben6brewer/Desktop/Greenhouse/data.json', 'r') as json_file:
            data = json.load(json_file)
            total_pump_time = data.get('TotalPumpTime', 0)
            return total_pump_time

    def reset_total_pump_time(self):
        with open('/home/ben6brewer/Desktop/Greenhouse/data.json', 'r+') as json_file:
            data = json.load(json_file)
            data['TotalPumpTime'] = 0
            json_file.seek(0)
            json.dump(data, json_file, indent=4)
            json_file.truncate()

    def check_user_input(self):
        rlist, _, _ = select.select([sys.stdin], [], [], 0.1)

        if rlist:
            user_input = sys.stdin.readline().strip()
            if user_input:
                words = user_input.split()
                val = None
                if words[-1].isdigit():
                    val = int(words.pop())
                    command = ' '.join(words)
                    if command == 'read vars':
                        print('temperature: ' + str(self.get_current_temperature()) + ' F')
                        print('humidity: ' + str(self.get_current_humidity()) + ' %')
                    elif command == 'get time':
                        weeks = self.get_minutes_elapsed() // 10080 # 10080 minutes = 7 days (1 week)
                        remaining_minutes = self.get_minutes_elapsed() % 10080
                        days = remaining_minutes // 1440 # 1440 minutes = 1 day
                        remaining_minutes %= 1440
                        hours = remaining_minutes // 60 # 60 minutes = 1 hour
                        minutes = remaining_minutes % 60
                        print(f'Week: {weeks}, Day: {days}, Hour: {hours}, Minute: {minutes}')
                    elif command == 'stall':
                        print('sleeping for ' + str(val) + ' seconds')
                        sleep(val)
                        self.update_minutes_in_json(val//60)
                        print('done sleeping')
                    elif command == 'pump on':
                        GPIO.output(self.mist_pin, 0)
                        print('pump on for ' + str(val) + ' seconds')
                        sleep(val)
                        GPIO.output(self.mist_pin, 1)
                        print('pump off')
                    elif command == 'fans on':
                        GPIO.output(self.fan_pin, 0)
                        print('fans on for ' + str(val) + ' seconds')
                        sleep(val)
                        GPIO.output(self.fan_pin, 1)
                        print('fans off')
                    elif command == 'pump off':
                        GPIO.output(self.mist_pin, 1)
                        print('pump off')
                    elif command == 'fans off':
                        GPIO.output(self.fan_pin, 1)
                        print('fans off')
                    elif command == 'set lowest humidity':
                        print('setting lowest humidity to ' + str(val) + '%')
                        self.lowest_humidity = val
                        print('updated self.lowest_humidity')
                    elif command == 'set highest humidity':
                        print('setting highest humidity to ' + str(val) + '%')
                        self.highest_humidity = val
                        print('updated self.highest_humidity')
                    elif command == 'set lowest temperature':
                        print('setting lowest temp to ' + str(val) + 'F')
                        self.lowest_temp = val
                        print('updated self.lowest_temp')
                    elif command == 'set highest temperature':
                        print('setting highest temp to ' + str(val) + 'F')
                        self.highest_temp = val
                        print('updated self.highest_temp')
                    elif command == 'AGT preset':
                        self.lowest_temp = 63
                        self.highest_temp = 80
                        self.lowest_humidity = 80
                        self.highest_humidity = 90
                        print(f'temp range: {self.lowest_temp}-{self.highest_temp}, humidity range {self.lowest_humidity}-{self.highest_humidity}')
                        print('Reset Minutes.txt to 0')
                        self.reset_json_data()
                    elif command == 'SG preset':
                        self.lowest_temp = 60
                        self.highest_temp = 80
                        self.lowest_humidity = 40
                        self.highest_humidity = 55
                        print(f'temp range: {self.lowest_temp}-{self.highest_temp}, humidity range {self.lowest_humidity}-{self.highest_humidity}')
                        print('Reset Minutes.txt to 0')
                        self.reset_json_data()
                    elif command == 'reset':
                        print('clearing data')
                        self.reset_json_data()
                        print('data cleared')
                    else:
                        print()
                        print('list valid requests:')
                        print()
                        print('GETTERS / SETTERS:')
                        print('get vars - gets temperature and humidity')
                        print('get time - gets current time statistics')
                        print('stall (seconds) - stalls for x amount of seconds')
                        print('set minutes (minutes) - set the minutes elapsed')
                        print('set lowest humidity (%) - updates self.lowest_humidity')
                        print('set highest humidity (%) - updates self.highest_humidity')
                        print('set lowest temperature (F) - updates self.lowest_temp')
                        print('set highest temperature (F) - updates self.highest_temp')
                        print()
                        print('HARDWARE CONTROLS:')
                        print('pump on (seconds) - turns humidifier on for x seconds')
                        print('fans on (seconds) - turns fans on for x seconds')
                        print('pump off - turns humidifier off')
                        print('fans off - turns fans off')
                        print()
                        print('PRESET GROWING CONDITIONS:')
                        print('AGT preset - ideal growing conditions for AGT')
                        print('SG preset - ideal growing conditions for Super Greens')
                        print()
                        print('SYSTEM RESET')
                        print('reset - clears the json storage file')

controller = GreenhouseController()
controller.main()

Here is the JSON:

{
    "minutes": 34079,
    "TotalPumpTime": 50,
    "TemperatureMinuteArray": [
        66.92,
        66.92,
        67.1,
        67.28,
        67.46,
        67.46,
        67.64,
        67.82,
        68.0,
        67.64,
        67.82,
        67.82
    ],
    "HumidityMinuteArray": [
        79.1,
        81.7,
        84.8,
        87.8,
        89.8,
        87.1,
        86.6,
        88.2,
        89.2,
        81.7,
        82.3,
        85.7
    ],
    "TemperatureHourArray": [
        69.08,
        68.44,
    ],
    "HumidityHourArray": [
        93.7,
        84.73,
    ]
}

I'd like to be able to continue to store the data in the JSON I just can't figure out what this error is.

xampL
  • 9
  • 2
  • 4
    What's line 35 in the JSON file? – Brian61354270 Aug 10 '23 at 00:13
  • Right. The problem is in the JSON, not the Python. – Tim Roberts Aug 10 '23 at 00:14
  • 4
    As a side note, you should NOT have each individual function rewrite that JSON. Just maintain the data in your Python data structures, then have "readdata" and "writedata" functions that copy it from/to the file. Your mainline code knows when the file is stale and needs to be updated. Any time you find yourself writing the same code 3 or 4 times, that code needs to be in a function. – Tim Roberts Aug 10 '23 at 00:16
  • @Brian61354270 here is the code in the json: { "minutes": 0, "TotalPumpTime": 0, "TemperatureMinuteArray": [], "HumidityMinuteArray": [], "TemperatureHourArray": [], "HumidityHourArray": [] } – xampL Aug 10 '23 at 00:34
  • 2
    @xampL That's definitely not the JSON contents your script was trying to decode when you got that error. The error occurred on when it was trying to parse the 5th character on 35th line, which was also the 532nd character in the entire file. There's less that 35 lines and less than 500 characters in that. – Brian61354270 Aug 10 '23 at 00:47
  • @Brian61354270 you are correct. I had to delete some data to make it fit the character requirements on a comment. However, the only differences are the arrays are populated with some values. Here is an example with as much as I could fit: { "minutes": 34079, "TotalPumpTime": 50, "TemperatureMinuteArray": [ 67.82, 67.82 ], "HumidityMinuteArray": [ 82.3, 85.7 ], "TemperatureHourArray": [ 69.08, ], "HumidityHourArray": [ 93.7 ] } – xampL Aug 10 '23 at 01:10
  • 2
    @xampL Don't add new information in the comments. Comments are transient. Instead, please add any new information or clarification to your question with an [edit]. – Brian61354270 Aug 10 '23 at 01:12
  • 2
    @xampL The issue has to due with your file containing invalid JSON, so the _exact_ contents are relevant. I don't know if it's representative of your actual file, but `[ 69.08, ]` is not a valid JSON value. JSON does not allow trailing commas. – Brian61354270 Aug 10 '23 at 01:13
  • @Brian61354270 thanks for letting me know. I'm new to using StackOverflow as well. I've posted the entire JSON file at the bottom. Let me know your thoughts – xampL Aug 10 '23 at 01:22
  • 2
    @xampL Yep, the values for `TemperatureHourArray` and `HumidityHourArray` both have trailing commas. Those aren't allowed in JSON. – Brian61354270 Aug 10 '23 at 01:23
  • 1
    Does this answer your question? [Can you use a trailing comma in a JSON object?](https://stackoverflow.com/questions/201782/can-you-use-a-trailing-comma-in-a-json-object) – Brian61354270 Aug 10 '23 at 01:24
  • @Brian61354270 I don't think I have trailing commas? For example the last value in "HumidityMinuteArray" is 85.7 which is not followed by a comma. There is a comma after the ending of the array but this is because there are multiple objects with the next being "TemperatureHourArray" – xampL Aug 10 '23 at 01:39
  • 1
    @xampL The trailing commas are in the values I mentioned. `HumidityMinuteArray` doesn't have one, but the other two do. – Brian61354270 Aug 10 '23 at 01:41
  • @Brian61354270 wow... thank you so much my program is running again. The extra commas were occuring because I went in the json file and manually deleted some values in the array after testing and forgot to delete the last comma. Thanks for your help – xampL Aug 10 '23 at 01:54

0 Answers0